react-achievements 3.9.1 → 4.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.
Files changed (52) hide show
  1. package/README.md +148 -101
  2. package/dist/headless.cjs +317 -0
  3. package/dist/headless.cjs.map +1 -0
  4. package/dist/headless.d.ts +176 -0
  5. package/dist/headless.esm.js +222 -0
  6. package/dist/headless.esm.js.map +1 -0
  7. package/dist/index.cjs +839 -881
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.ts +163 -153
  10. package/dist/index.esm.js +835 -883
  11. package/dist/index.esm.js.map +1 -1
  12. package/dist/web.cjs +1416 -0
  13. package/dist/web.cjs.map +1 -0
  14. package/dist/web.d.ts +534 -0
  15. package/dist/web.esm.js +1306 -0
  16. package/dist/web.esm.js.map +1 -0
  17. package/package.json +13 -28
  18. package/dist/types/__mocks__/confetti-wrapper.d.ts +0 -5
  19. package/dist/types/__mocks__/react-confetti.d.ts +0 -3
  20. package/dist/types/__mocks__/react-toastify.d.ts +0 -13
  21. package/dist/types/core/components/BadgesButton.d.ts +0 -25
  22. package/dist/types/core/components/BadgesButtonWithModal.d.ts +0 -53
  23. package/dist/types/core/components/BadgesModal.d.ts +0 -14
  24. package/dist/types/core/components/ConfettiWrapper.d.ts +0 -6
  25. package/dist/types/core/errors/AchievementErrors.d.ts +0 -55
  26. package/dist/types/core/hooks/useWindowSize.d.ts +0 -16
  27. package/dist/types/core/icons/defaultIcons.d.ts +0 -8
  28. package/dist/types/core/storage/AsyncStorageAdapter.d.ts +0 -48
  29. package/dist/types/core/storage/IndexedDBStorage.d.ts +0 -29
  30. package/dist/types/core/storage/LocalStorage.d.ts +0 -16
  31. package/dist/types/core/storage/MemoryStorage.d.ts +0 -11
  32. package/dist/types/core/storage/OfflineQueueStorage.d.ts +0 -42
  33. package/dist/types/core/storage/RestApiStorage.d.ts +0 -20
  34. package/dist/types/core/styles/defaultStyles.d.ts +0 -2
  35. package/dist/types/core/types.d.ts +0 -115
  36. package/dist/types/core/ui/BuiltInConfetti.d.ts +0 -7
  37. package/dist/types/core/ui/BuiltInModal.d.ts +0 -7
  38. package/dist/types/core/ui/BuiltInNotification.d.ts +0 -7
  39. package/dist/types/core/ui/LegacyWrappers.d.ts +0 -21
  40. package/dist/types/core/ui/interfaces.d.ts +0 -127
  41. package/dist/types/core/ui/legacyDetector.d.ts +0 -40
  42. package/dist/types/core/ui/themes.d.ts +0 -14
  43. package/dist/types/core/utils/configNormalizer.d.ts +0 -3
  44. package/dist/types/core/utils/dataExport.d.ts +0 -34
  45. package/dist/types/core/utils/dataImport.d.ts +0 -50
  46. package/dist/types/hooks/useAchievementEngine.d.ts +0 -36
  47. package/dist/types/hooks/useAchievements.d.ts +0 -1
  48. package/dist/types/hooks/useSimpleAchievements.d.ts +0 -63
  49. package/dist/types/index.d.ts +0 -36
  50. package/dist/types/providers/AchievementProvider.d.ts +0 -47
  51. package/dist/types/setupTests.d.ts +0 -1
  52. package/dist/types/utils/achievementHelpers.d.ts +0 -135
package/dist/web.cjs ADDED
@@ -0,0 +1,1416 @@
1
+ 'use strict';
2
+
3
+ var achievementsEngine = require('achievements-engine');
4
+ var React = require('react');
5
+
6
+ // Type guard to detect async storage
7
+ function isAsyncStorage(storage) {
8
+ // Check if methods return Promises
9
+ const testResult = storage.getMetrics();
10
+ return testResult && typeof testResult.then === 'function';
11
+ }
12
+ var StorageType;
13
+ (function (StorageType) {
14
+ StorageType["Local"] = "local";
15
+ StorageType["Memory"] = "memory";
16
+ StorageType["IndexedDB"] = "indexeddb";
17
+ StorageType["RestAPI"] = "restapi"; // Asynchronous REST API storage
18
+ })(StorageType || (StorageType = {}));
19
+
20
+ /******************************************************************************
21
+ Copyright (c) Microsoft Corporation.
22
+
23
+ Permission to use, copy, modify, and/or distribute this software for any
24
+ purpose with or without fee is hereby granted.
25
+
26
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
27
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
28
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
29
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
30
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
31
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
32
+ PERFORMANCE OF THIS SOFTWARE.
33
+ ***************************************************************************** */
34
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
35
+
36
+
37
+ function __rest(s, e) {
38
+ var t = {};
39
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
40
+ t[p] = s[p];
41
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
42
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
43
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
44
+ t[p[i]] = s[p[i]];
45
+ }
46
+ return t;
47
+ }
48
+
49
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
+ var e = new Error(message);
51
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
+ };
53
+
54
+ const warnedMessages = new Set();
55
+ function warnDeprecation(message) {
56
+ var _a, _b;
57
+ const isProduction = typeof globalThis !== 'undefined' &&
58
+ ((_b = (_a = globalThis.process) === null || _a === void 0 ? void 0 : _a.env) === null || _b === void 0 ? void 0 : _b.NODE_ENV) === 'production';
59
+ if (isProduction || warnedMessages.has(message)) {
60
+ return;
61
+ }
62
+ warnedMessages.add(message);
63
+ console.warn(`[react-achievements] ${message}`);
64
+ }
65
+
66
+ const AchievementContext = React.createContext(undefined);
67
+ const getAllAchievementRecord = (engine) => {
68
+ return Object.fromEntries(engine.getAllAchievements().map((achievement) => [
69
+ achievement.achievementId,
70
+ achievement,
71
+ ]));
72
+ };
73
+ const AchievementProvider$1 = ({ achievements: achievementsConfig, storage = 'local', children, onError, useBuiltInUI, restApiConfig, engine: externalEngine, eventMapping, icons = {}, }) => {
74
+ if (useBuiltInUI !== undefined) {
75
+ warnDeprecation('`useBuiltInUI` is deprecated and is now a no-op because built-in UI is the default. It will be removed in 4.2.');
76
+ }
77
+ if (achievementsConfig && externalEngine) {
78
+ throw new Error('Cannot provide both "achievements" and "engine" props to AchievementProvider.\n\n' +
79
+ 'Choose one pattern:\n' +
80
+ '1. Direct metric tracking: <AchievementProvider achievements={config}>\n' +
81
+ '2. Event-based tracking: <AchievementProvider engine={myEngine}>');
82
+ }
83
+ const isProviderCreatedEngine = Boolean(achievementsConfig);
84
+ const [engine] = React.useState(() => {
85
+ if (externalEngine) {
86
+ return externalEngine;
87
+ }
88
+ if (!achievementsConfig) {
89
+ throw new Error('AchievementProvider requires either "achievements" or "engine" prop.\n\n' +
90
+ '1. Direct metric tracking: <AchievementProvider achievements={config}>\n' +
91
+ '2. Event-based tracking: <AchievementProvider engine={myEngine}>');
92
+ }
93
+ return new achievementsEngine.AchievementEngine({
94
+ achievements: achievementsConfig,
95
+ storage: storage,
96
+ restApiConfig,
97
+ onError: onError,
98
+ eventMapping,
99
+ });
100
+ });
101
+ const [achievementState, setAchievementState] = React.useState(() => ({
102
+ unlocked: [...engine.getUnlocked()],
103
+ all: getAllAchievementRecord(engine),
104
+ }));
105
+ const syncAchievementState = React.useCallback(() => {
106
+ setAchievementState({
107
+ unlocked: [...engine.getUnlocked()],
108
+ all: getAllAchievementRecord(engine),
109
+ });
110
+ }, [engine]);
111
+ React.useEffect(() => {
112
+ return () => {
113
+ if (!externalEngine) {
114
+ engine.destroy();
115
+ }
116
+ };
117
+ }, [engine, externalEngine]);
118
+ React.useEffect(() => {
119
+ const unsubscribeUnlocked = engine.on('achievement:unlocked', syncAchievementState);
120
+ const unsubscribeStateChanged = engine.on('state:changed', syncAchievementState);
121
+ return () => {
122
+ unsubscribeUnlocked();
123
+ unsubscribeStateChanged();
124
+ };
125
+ }, [engine, syncAchievementState]);
126
+ const update = (newMetrics) => {
127
+ engine.update(newMetrics);
128
+ };
129
+ const reset = () => {
130
+ engine.reset();
131
+ syncAchievementState();
132
+ };
133
+ const getState = () => {
134
+ const metrics = engine.getMetrics();
135
+ const unlocked = engine.getUnlocked();
136
+ const metricsInArrayFormat = {};
137
+ Object.entries(metrics).forEach(([key, value]) => {
138
+ metricsInArrayFormat[key] = Array.isArray(value) ? value : [value];
139
+ });
140
+ return {
141
+ metrics: metricsInArrayFormat,
142
+ unlocked: [...unlocked],
143
+ };
144
+ };
145
+ const exportData = () => {
146
+ return engine.export();
147
+ };
148
+ const importData = (jsonString, options) => {
149
+ const result = engine.import(jsonString, options);
150
+ syncAchievementState();
151
+ return result;
152
+ };
153
+ const getAllAchievements = () => {
154
+ return engine.getAllAchievements();
155
+ };
156
+ return (React.createElement(AchievementContext.Provider, { value: {
157
+ update,
158
+ achievements: achievementState,
159
+ reset,
160
+ getState,
161
+ exportData,
162
+ importData,
163
+ getAllAchievements,
164
+ engine,
165
+ icons,
166
+ _isLegacyPattern: isProviderCreatedEngine,
167
+ } }, children));
168
+ };
169
+
170
+ /**
171
+ * Access the active AchievementEngine instance.
172
+ *
173
+ * In v4 this works with both provider-created engines (`achievements` prop) and
174
+ * injected engines (`engine` prop).
175
+ */
176
+ const useAchievementEngine = () => {
177
+ const context = React.useContext(AchievementContext);
178
+ if (!context) {
179
+ throw new Error('useAchievementEngine must be used within an AchievementProvider.\n\n' +
180
+ 'Wrap your component tree:\n' +
181
+ '<AchievementProvider achievements={achievements}>\n' +
182
+ ' <YourComponent />\n' +
183
+ '</AchievementProvider>');
184
+ }
185
+ return context.engine;
186
+ };
187
+
188
+ /**
189
+ * Built-in theme presets
190
+ */
191
+ const builtInThemes = {
192
+ /**
193
+ * Modern theme - Dark gradients with vibrant accents
194
+ * Inspired by contemporary achievement systems (Discord, Steam, Xbox)
195
+ */
196
+ modern: {
197
+ name: 'modern',
198
+ notification: {
199
+ background: 'linear-gradient(135deg, rgba(30, 30, 50, 0.98) 0%, rgba(50, 50, 70, 0.98) 100%)',
200
+ textColor: '#ffffff',
201
+ accentColor: '#4CAF50',
202
+ borderRadius: '12px',
203
+ boxShadow: '0 8px 32px rgba(0, 0, 0, 0.4)',
204
+ fontSize: {
205
+ header: '12px',
206
+ title: '18px',
207
+ description: '14px',
208
+ },
209
+ },
210
+ modal: {
211
+ overlayColor: 'rgba(0, 0, 0, 0.85)',
212
+ background: 'linear-gradient(135deg, #1e1e32 0%, #323246 100%)',
213
+ textColor: '#ffffff',
214
+ accentColor: '#4CAF50',
215
+ borderRadius: '16px',
216
+ headerFontSize: '28px',
217
+ },
218
+ confetti: {
219
+ colors: ['#FFD700', '#4CAF50', '#2196F3', '#FF6B6B'],
220
+ particleCount: 50,
221
+ shapes: ['circle', 'square'],
222
+ },
223
+ },
224
+ /**
225
+ * Minimal theme - Clean, light design with subtle accents
226
+ * Perfect for professional or minimalist applications
227
+ */
228
+ minimal: {
229
+ name: 'minimal',
230
+ notification: {
231
+ background: 'rgba(255, 255, 255, 0.98)',
232
+ textColor: '#333333',
233
+ accentColor: '#4CAF50',
234
+ borderRadius: '8px',
235
+ boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
236
+ fontSize: {
237
+ header: '11px',
238
+ title: '16px',
239
+ description: '13px',
240
+ },
241
+ },
242
+ modal: {
243
+ overlayColor: 'rgba(0, 0, 0, 0.5)',
244
+ background: '#ffffff',
245
+ textColor: '#333333',
246
+ accentColor: '#4CAF50',
247
+ borderRadius: '12px',
248
+ headerFontSize: '24px',
249
+ },
250
+ confetti: {
251
+ colors: ['#4CAF50', '#2196F3'],
252
+ particleCount: 30,
253
+ shapes: ['circle'],
254
+ },
255
+ },
256
+ /**
257
+ * Gamified theme - Modern gaming aesthetic with sci-fi colors
258
+ * Dark navy backgrounds with cyan and orange accents (2024 gaming trend)
259
+ * Features square/badge-shaped achievement cards
260
+ */
261
+ gamified: {
262
+ name: 'gamified',
263
+ notification: {
264
+ background: 'linear-gradient(135deg, rgba(5, 8, 22, 0.98) 0%, rgba(15, 23, 42, 0.98) 100%)',
265
+ textColor: '#22d3ee', // Bright cyan
266
+ accentColor: '#f97316', // Bright orange
267
+ borderRadius: '6px',
268
+ boxShadow: '0 8px 32px rgba(34, 211, 238, 0.4), 0 0 20px rgba(249, 115, 22, 0.3)',
269
+ fontSize: {
270
+ header: '13px',
271
+ title: '20px',
272
+ description: '15px',
273
+ },
274
+ },
275
+ modal: {
276
+ overlayColor: 'rgba(5, 8, 22, 0.85)',
277
+ background: 'linear-gradient(135deg, #0f172a 0%, #050816 100%)',
278
+ textColor: '#22d3ee', // Bright cyan
279
+ accentColor: '#f97316', // Bright orange
280
+ borderRadius: '8px',
281
+ headerFontSize: '32px',
282
+ achievementCardBorderRadius: '8px', // Square badge-like cards
283
+ achievementLayout: 'badge', // Use badge/grid layout instead of horizontal list
284
+ },
285
+ confetti: {
286
+ colors: ['#22d3ee', '#f97316', '#a855f7', '#eab308'], // Cyan, orange, purple, yellow
287
+ particleCount: 100,
288
+ shapes: ['circle', 'square'],
289
+ },
290
+ },
291
+ };
292
+ /**
293
+ * Retrieve a theme by name (internal use only)
294
+ * Only checks built-in themes
295
+ *
296
+ * @param name - Theme name (built-in only)
297
+ * @returns Theme configuration or undefined if not found
298
+ * @internal
299
+ */
300
+ function getTheme(name) {
301
+ return builtInThemes[name];
302
+ }
303
+
304
+ const defaultAchievementIcons = {
305
+ // Essential fallback icons for system use
306
+ default: '⭐', // Fallback when no icon is provided
307
+ loading: '⏳', // For loading states
308
+ error: '⚠️', // For error states
309
+ success: '✅', // For success states
310
+ // A few common icons for backward compatibility
311
+ trophy: '🏆',
312
+ star: '⭐',
313
+ };
314
+
315
+ /**
316
+ * Built-in notification component
317
+ * Modern, theme-aware achievement notification with smooth animations
318
+ */
319
+ const BuiltInNotification = ({ achievement, onClose, duration = 5000, position = 'top-center', theme = 'modern', icons = {}, stackIndex = 0, }) => {
320
+ var _a, _b, _c;
321
+ const [isVisible, setIsVisible] = React.useState(false);
322
+ const [isExiting, setIsExiting] = React.useState(false);
323
+ // Merge custom icons with defaults
324
+ const mergedIcons = Object.assign(Object.assign({}, defaultAchievementIcons), icons);
325
+ // Get theme configuration
326
+ const themeConfig = getTheme(theme) || builtInThemes.modern;
327
+ const { notification: themeStyles } = themeConfig;
328
+ React.useEffect(() => {
329
+ // Slide in animation
330
+ const showTimer = setTimeout(() => setIsVisible(true), 10);
331
+ // Auto-dismiss
332
+ const dismissTimer = setTimeout(() => {
333
+ setIsExiting(true);
334
+ setTimeout(() => onClose === null || onClose === void 0 ? void 0 : onClose(), 300);
335
+ }, duration);
336
+ return () => {
337
+ clearTimeout(showTimer);
338
+ clearTimeout(dismissTimer);
339
+ };
340
+ }, [duration, onClose]);
341
+ const getPositionStyles = () => {
342
+ const stackedOffset = 20 + stackIndex * 104;
343
+ const base = {
344
+ position: 'fixed',
345
+ zIndex: 9999,
346
+ };
347
+ switch (position) {
348
+ case 'top-center':
349
+ return Object.assign(Object.assign({}, base), { top: stackedOffset, left: '50%', transform: 'translateX(-50%)' });
350
+ case 'top-left':
351
+ return Object.assign(Object.assign({}, base), { top: stackedOffset, left: 20 });
352
+ case 'top-right':
353
+ return Object.assign(Object.assign({}, base), { top: stackedOffset, right: 20 });
354
+ case 'bottom-center':
355
+ return Object.assign(Object.assign({}, base), { bottom: stackedOffset, left: '50%', transform: 'translateX(-50%)' });
356
+ case 'bottom-left':
357
+ return Object.assign(Object.assign({}, base), { bottom: stackedOffset, left: 20 });
358
+ case 'bottom-right':
359
+ return Object.assign(Object.assign({}, base), { bottom: stackedOffset, right: 20 });
360
+ default:
361
+ return Object.assign(Object.assign({}, base), { top: stackedOffset, left: '50%', transform: 'translateX(-50%)' });
362
+ }
363
+ };
364
+ const containerStyles = Object.assign(Object.assign({}, getPositionStyles()), { background: themeStyles.background, borderRadius: themeStyles.borderRadius, boxShadow: themeStyles.boxShadow, padding: '16px 24px', minWidth: '320px', maxWidth: '500px', display: 'flex', alignItems: 'center', gap: '16px', opacity: isVisible && !isExiting ? 1 : 0, transform: position.startsWith('top')
365
+ ? `translateY(${isVisible && !isExiting ? '0' : '-20px'}) ${position.includes('center') ? 'translateX(-50%)' : ''}`
366
+ : `translateY(${isVisible && !isExiting ? '0' : '20px'}) ${position.includes('center') ? 'translateX(-50%)' : ''}`, transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', pointerEvents: isVisible ? 'auto' : 'none' });
367
+ const iconStyles = {
368
+ fontSize: '48px',
369
+ lineHeight: 1,
370
+ flexShrink: 0,
371
+ };
372
+ const contentStyles = {
373
+ flex: 1,
374
+ color: themeStyles.textColor,
375
+ minWidth: 0,
376
+ };
377
+ const headerStyles = {
378
+ fontSize: ((_a = themeStyles.fontSize) === null || _a === void 0 ? void 0 : _a.header) || '12px',
379
+ textTransform: 'uppercase',
380
+ letterSpacing: '1px',
381
+ opacity: 0.8,
382
+ marginBottom: '4px',
383
+ color: themeStyles.accentColor,
384
+ fontWeight: 600,
385
+ };
386
+ const titleStyles = {
387
+ fontSize: ((_b = themeStyles.fontSize) === null || _b === void 0 ? void 0 : _b.title) || '18px',
388
+ fontWeight: 'bold',
389
+ marginBottom: '4px',
390
+ overflow: 'hidden',
391
+ textOverflow: 'ellipsis',
392
+ whiteSpace: 'nowrap',
393
+ };
394
+ const descriptionStyles = {
395
+ fontSize: ((_c = themeStyles.fontSize) === null || _c === void 0 ? void 0 : _c.description) || '14px',
396
+ opacity: 0.9,
397
+ overflow: 'hidden',
398
+ textOverflow: 'ellipsis',
399
+ display: '-webkit-box',
400
+ WebkitLineClamp: 2,
401
+ WebkitBoxOrient: 'vertical',
402
+ };
403
+ const closeButtonStyles = {
404
+ background: 'none',
405
+ border: 'none',
406
+ color: themeStyles.textColor,
407
+ fontSize: '24px',
408
+ cursor: 'pointer',
409
+ opacity: 0.6,
410
+ transition: 'opacity 0.2s',
411
+ padding: '4px',
412
+ flexShrink: 0,
413
+ lineHeight: 1,
414
+ };
415
+ // If achievementIconKey exists but not in mergedIcons, use it directly (might be an emoji)
416
+ // Otherwise, look up in mergedIcons or fall back to default
417
+ const icon = (achievement.achievementIconKey &&
418
+ mergedIcons[achievement.achievementIconKey]) ||
419
+ achievement.achievementIconKey ||
420
+ mergedIcons.default ||
421
+ '⭐';
422
+ return (React.createElement("div", { style: containerStyles, "data-testid": "built-in-notification" },
423
+ React.createElement("div", { style: iconStyles }, icon),
424
+ React.createElement("div", { style: contentStyles },
425
+ React.createElement("div", { style: headerStyles }, "Achievement Unlocked!"),
426
+ React.createElement("div", { style: titleStyles }, achievement.achievementTitle),
427
+ achievement.achievementDescription && (React.createElement("div", { style: descriptionStyles }, achievement.achievementDescription))),
428
+ React.createElement("button", { onClick: () => {
429
+ setIsExiting(true);
430
+ setTimeout(() => onClose === null || onClose === void 0 ? void 0 : onClose(), 300);
431
+ }, style: closeButtonStyles, onMouseEnter: (e) => (e.currentTarget.style.opacity = '1'), onMouseLeave: (e) => (e.currentTarget.style.opacity = '0.6'), "aria-label": "Close notification" }, "\u00D7")));
432
+ };
433
+
434
+ /**
435
+ * Hook to track window dimensions
436
+ * Replacement for react-use's useWindowSize
437
+ *
438
+ * @returns Object with width and height properties
439
+ *
440
+ * @example
441
+ * ```tsx
442
+ * const { width, height } = useWindowSize();
443
+ * console.log(`Window size: ${width}x${height}`);
444
+ * ```
445
+ */
446
+ function useWindowSize() {
447
+ const [size, setSize] = React.useState({
448
+ width: typeof window !== 'undefined' ? window.innerWidth : 0,
449
+ height: typeof window !== 'undefined' ? window.innerHeight : 0,
450
+ });
451
+ React.useEffect(() => {
452
+ // Handle SSR - window may not be defined
453
+ if (typeof window === 'undefined') {
454
+ return;
455
+ }
456
+ const handleResize = () => {
457
+ setSize({
458
+ width: window.innerWidth,
459
+ height: window.innerHeight,
460
+ });
461
+ };
462
+ // Set initial size
463
+ handleResize();
464
+ // Add event listener
465
+ window.addEventListener('resize', handleResize);
466
+ // Cleanup
467
+ return () => {
468
+ window.removeEventListener('resize', handleResize);
469
+ };
470
+ }, []);
471
+ return size;
472
+ }
473
+
474
+ /**
475
+ * Built-in confetti component
476
+ * Lightweight CSS-based confetti animation
477
+ */
478
+ const BuiltInConfetti = ({ show, duration = 5000, particleCount = 50, colors = ['#FFD700', '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7'], }) => {
479
+ const [isVisible, setIsVisible] = React.useState(false);
480
+ const { width, height } = useWindowSize();
481
+ React.useEffect(() => {
482
+ if (show) {
483
+ setIsVisible(true);
484
+ const timer = setTimeout(() => setIsVisible(false), duration);
485
+ return () => clearTimeout(timer);
486
+ }
487
+ else {
488
+ setIsVisible(false);
489
+ }
490
+ }, [show, duration]);
491
+ if (!isVisible)
492
+ return null;
493
+ const containerStyles = {
494
+ position: 'fixed',
495
+ top: 0,
496
+ left: 0,
497
+ width: '100%',
498
+ height: '100%',
499
+ pointerEvents: 'none',
500
+ zIndex: 10001,
501
+ overflow: 'hidden',
502
+ };
503
+ // Generate particles
504
+ const particles = Array.from({ length: particleCount }, (_, i) => {
505
+ const color = colors[Math.floor(Math.random() * colors.length)];
506
+ const startX = Math.random() * width;
507
+ const rotation = Math.random() * 360;
508
+ const fallDuration = 3 + Math.random() * 2; // 3-5 seconds
509
+ const delay = Math.random() * 0.5; // 0-0.5s delay
510
+ const shape = Math.random() > 0.5 ? 'circle' : 'square';
511
+ const particleStyles = {
512
+ position: 'absolute',
513
+ top: '-20px',
514
+ left: `${startX}px`,
515
+ width: '10px',
516
+ height: '10px',
517
+ backgroundColor: color,
518
+ borderRadius: shape === 'circle' ? '50%' : '0',
519
+ transform: `rotate(${rotation}deg)`,
520
+ animation: `confettiFall ${fallDuration}s linear ${delay}s forwards`,
521
+ opacity: 0.9,
522
+ };
523
+ return React.createElement("div", { key: i, style: particleStyles, "data-testid": "confetti-particle" });
524
+ });
525
+ return (React.createElement(React.Fragment, null,
526
+ React.createElement("style", null, `
527
+ @keyframes confettiFall {
528
+ 0% {
529
+ transform: translateY(0) rotate(0deg);
530
+ opacity: 1;
531
+ }
532
+ 100% {
533
+ transform: translateY(${height + 50}px) rotate(720deg);
534
+ opacity: 0;
535
+ }
536
+ }
537
+ `),
538
+ React.createElement("div", { style: containerStyles, "data-testid": "built-in-confetti" }, particles)));
539
+ };
540
+
541
+ const NOTIFICATION_DURATION_MS = 5000;
542
+ const AchievementUIContext = React.createContext({
543
+ icons: {},
544
+ ui: {},
545
+ });
546
+ const AchievementEffects = ({ icons, ui }) => {
547
+ const engine = useAchievementEngine();
548
+ const seenAchievementsRef = React.useRef(new Set(engine.getUnlocked()));
549
+ const confettiTimerRef = React.useRef(null);
550
+ const [showConfetti, setShowConfetti] = React.useState(false);
551
+ const [notifications, setNotifications] = React.useState([]);
552
+ React.useEffect(() => {
553
+ const unsubscribeUnlocked = engine.on('achievement:unlocked', (event) => {
554
+ if (seenAchievementsRef.current.has(event.achievementId)) {
555
+ return;
556
+ }
557
+ seenAchievementsRef.current.add(event.achievementId);
558
+ const unlockedAchievement = {
559
+ achievementId: event.achievementId,
560
+ achievementTitle: event.achievementTitle,
561
+ achievementDescription: event.achievementDescription,
562
+ achievementIconKey: event.achievementIconKey,
563
+ isUnlocked: true,
564
+ };
565
+ if (ui.enableNotifications !== false) {
566
+ setNotifications((currentNotifications) => {
567
+ if (currentNotifications.some((notification) => notification.achievementId === unlockedAchievement.achievementId)) {
568
+ return currentNotifications;
569
+ }
570
+ return [...currentNotifications, unlockedAchievement];
571
+ });
572
+ }
573
+ if (ui.enableConfetti !== false) {
574
+ if (confettiTimerRef.current) {
575
+ clearTimeout(confettiTimerRef.current);
576
+ }
577
+ setShowConfetti(true);
578
+ confettiTimerRef.current = setTimeout(() => {
579
+ setShowConfetti(false);
580
+ confettiTimerRef.current = null;
581
+ }, NOTIFICATION_DURATION_MS);
582
+ }
583
+ });
584
+ const unsubscribeStateChanged = engine.on('state:changed', () => {
585
+ const unlocked = new Set(engine.getUnlocked());
586
+ seenAchievementsRef.current.forEach((achievementId) => {
587
+ if (!unlocked.has(achievementId)) {
588
+ seenAchievementsRef.current.delete(achievementId);
589
+ }
590
+ });
591
+ });
592
+ return () => {
593
+ unsubscribeUnlocked();
594
+ unsubscribeStateChanged();
595
+ if (confettiTimerRef.current) {
596
+ clearTimeout(confettiTimerRef.current);
597
+ }
598
+ };
599
+ }, [engine, ui.enableConfetti, ui.enableNotifications]);
600
+ const NotificationComponent = ui.NotificationComponent || BuiltInNotification;
601
+ const ConfettiComponentResolved = ui.ConfettiComponent || BuiltInConfetti;
602
+ return (React.createElement(React.Fragment, null,
603
+ ui.enableNotifications !== false &&
604
+ notifications.map((notification, index) => (React.createElement(NotificationComponent, { key: notification.achievementId, achievement: notification, onClose: () => setNotifications((currentNotifications) => currentNotifications.filter((currentNotification) => currentNotification.achievementId !== notification.achievementId)), duration: NOTIFICATION_DURATION_MS, position: ui.notificationPosition || 'top-center', theme: ui.theme || 'modern', icons: icons, stackIndex: index }))),
605
+ ui.enableConfetti !== false && (React.createElement(ConfettiComponentResolved, { show: showConfetti, duration: NOTIFICATION_DURATION_MS }))));
606
+ };
607
+ const AchievementProvider = (_a) => {
608
+ var { children, icons = {}, ui = {}, useBuiltInUI } = _a, providerProps = __rest(_a, ["children", "icons", "ui", "useBuiltInUI"]);
609
+ if (useBuiltInUI !== undefined) {
610
+ warnDeprecation('`useBuiltInUI` is deprecated and is now a no-op because built-in UI is the default. It will be removed in 4.2.');
611
+ }
612
+ const uiContextValue = React.useMemo(() => ({ icons, ui }), [icons, ui]);
613
+ return (React.createElement(AchievementUIContext.Provider, { value: uiContextValue },
614
+ React.createElement(AchievementProvider$1, Object.assign({}, providerProps, { icons: icons }),
615
+ children,
616
+ React.createElement(AchievementEffects, { icons: icons, ui: ui }))));
617
+ };
618
+
619
+ const useAchievements = () => {
620
+ const context = React.useContext(AchievementContext);
621
+ if (!context) {
622
+ throw new Error('useAchievements must be used within an AchievementProvider');
623
+ }
624
+ return context;
625
+ };
626
+
627
+ const useAchievementState = () => {
628
+ const { achievements, getAllAchievements, getState } = useAchievements();
629
+ const allAchievements = getAllAchievements();
630
+ const unlockedIds = achievements.unlocked;
631
+ const unlockedAchievementSet = new Set(unlockedIds);
632
+ const unlockedAchievements = allAchievements.filter((achievement) => unlockedAchievementSet.has(achievement.achievementId));
633
+ return {
634
+ unlockedIds,
635
+ unlockedAchievements,
636
+ allAchievements,
637
+ unlockedCount: unlockedIds.length,
638
+ totalCount: allAchievements.length,
639
+ metrics: getState().metrics,
640
+ };
641
+ };
642
+
643
+ const defaultStyles = {
644
+ badgesButton: {
645
+ backgroundColor: '#4CAF50',
646
+ color: 'white',
647
+ padding: '10px 20px',
648
+ border: 'none',
649
+ borderRadius: '20px',
650
+ cursor: 'pointer',
651
+ display: 'flex',
652
+ alignItems: 'center',
653
+ gap: '8px',
654
+ fontSize: '16px',
655
+ boxShadow: '0 2px 5px rgba(0,0,0,0.2)',
656
+ transition: 'transform 0.2s ease-in-out',
657
+ },
658
+ badgesModal: {
659
+ overlay: {
660
+ position: 'fixed',
661
+ top: 0,
662
+ right: 0,
663
+ bottom: 0,
664
+ left: 0,
665
+ backgroundColor: 'rgba(0, 0, 0, 0.75)',
666
+ display: 'flex',
667
+ alignItems: 'center',
668
+ justifyContent: 'center',
669
+ zIndex: 10000,
670
+ },
671
+ content: {
672
+ position: 'relative',
673
+ background: '#fff',
674
+ borderRadius: '8px',
675
+ padding: '20px',
676
+ maxWidth: '500px',
677
+ width: '90%',
678
+ maxHeight: '80vh',
679
+ overflow: 'auto',
680
+ },
681
+ header: {
682
+ display: 'flex',
683
+ justifyContent: 'space-between',
684
+ alignItems: 'center',
685
+ marginBottom: '20px',
686
+ },
687
+ closeButton: {
688
+ background: 'none',
689
+ border: 'none',
690
+ fontSize: '24px',
691
+ cursor: 'pointer',
692
+ padding: '0',
693
+ },
694
+ achievementList: {
695
+ display: 'flex',
696
+ flexDirection: 'column',
697
+ gap: '16px',
698
+ },
699
+ achievementItem: {
700
+ display: 'flex',
701
+ gap: '16px',
702
+ padding: '16px',
703
+ borderRadius: '8px',
704
+ backgroundColor: '#f5f5f5',
705
+ alignItems: 'center',
706
+ },
707
+ achievementTitle: {
708
+ margin: '0',
709
+ fontSize: '18px',
710
+ fontWeight: 'bold',
711
+ },
712
+ achievementDescription: {
713
+ margin: '4px 0 0 0',
714
+ color: '#666',
715
+ },
716
+ achievementIcon: {
717
+ fontSize: '32px',
718
+ display: 'flex',
719
+ alignItems: 'center',
720
+ justifyContent: 'center',
721
+ },
722
+ lockIcon: {
723
+ fontSize: '24px',
724
+ position: 'absolute',
725
+ top: '50%',
726
+ right: '16px',
727
+ transform: 'translateY(-50%)',
728
+ },
729
+ lockedAchievementItem: {
730
+ display: 'flex',
731
+ gap: '16px',
732
+ padding: '16px',
733
+ borderRadius: '8px',
734
+ backgroundColor: '#e0e0e0',
735
+ alignItems: 'center',
736
+ opacity: 0.5,
737
+ },
738
+ },
739
+ levelProgress: {
740
+ container: {
741
+ display: 'flex',
742
+ flexDirection: 'column',
743
+ gap: '8px',
744
+ padding: '12px 16px',
745
+ },
746
+ header: {
747
+ display: 'flex',
748
+ alignItems: 'center',
749
+ justifyContent: 'space-between',
750
+ gap: '12px',
751
+ fontWeight: 600,
752
+ },
753
+ levelLabel: {
754
+ fontSize: '14px',
755
+ },
756
+ valueText: {
757
+ fontSize: '12px',
758
+ opacity: 0.8,
759
+ },
760
+ progressTrack: {
761
+ position: 'relative',
762
+ height: '10px',
763
+ backgroundColor: 'rgba(255, 255, 255, 0.2)',
764
+ borderRadius: '999px',
765
+ overflow: 'hidden',
766
+ },
767
+ progressBar: {
768
+ height: '100%',
769
+ borderRadius: '999px',
770
+ transition: 'width 0.2s ease-in-out',
771
+ },
772
+ progressText: {
773
+ fontSize: '12px',
774
+ opacity: 0.8,
775
+ textAlign: 'right',
776
+ },
777
+ },
778
+ };
779
+
780
+ const resolveIcon = (achievement, icons) => {
781
+ return ((achievement.achievementIconKey && icons[achievement.achievementIconKey]) ||
782
+ achievement.achievementIconKey ||
783
+ icons.default ||
784
+ '⭐');
785
+ };
786
+ const AchievementsList = ({ achievements, showLocked = true, showUnlockConditions = false, icons = {}, styles = {}, emptyState, className, renderAchievement, }) => {
787
+ const context = React.useContext(AchievementContext);
788
+ const uiContext = React.useContext(AchievementUIContext);
789
+ const mergedIcons = Object.assign(Object.assign(Object.assign(Object.assign({}, defaultAchievementIcons), context === null || context === void 0 ? void 0 : context.icons), uiContext.icons), icons);
790
+ const sourceAchievements = achievements || (context === null || context === void 0 ? void 0 : context.getAllAchievements());
791
+ if (!sourceAchievements) {
792
+ throw new Error('AchievementsList requires either an achievements prop or an AchievementProvider parent.');
793
+ }
794
+ const achievementsToDisplay = showLocked
795
+ ? sourceAchievements
796
+ : sourceAchievements.filter((achievement) => achievement.isUnlocked);
797
+ if (achievementsToDisplay.length === 0) {
798
+ return (React.createElement("p", { style: { textAlign: 'center', color: '#666' } }, emptyState || 'No achievements configured.'));
799
+ }
800
+ return (React.createElement("div", { className: className, style: Object.assign(Object.assign({}, defaultStyles.badgesModal.achievementList), styles === null || styles === void 0 ? void 0 : styles.achievementList), "data-testid": "achievements-list" }, achievementsToDisplay.map((achievement, index) => {
801
+ const isLocked = !achievement.isUnlocked;
802
+ const icon = resolveIcon(achievement, mergedIcons);
803
+ if (renderAchievement) {
804
+ return (React.createElement(React.Fragment, { key: achievement.achievementId }, renderAchievement({ achievement, isLocked, icon, index })));
805
+ }
806
+ return (React.createElement("div", { key: achievement.achievementId, style: Object.assign(Object.assign(Object.assign({}, (isLocked
807
+ ? Object.assign(Object.assign({}, defaultStyles.badgesModal.lockedAchievementItem), styles === null || styles === void 0 ? void 0 : styles.lockedAchievementItem) : defaultStyles.badgesModal.achievementItem)), styles === null || styles === void 0 ? void 0 : styles.achievementItem), { position: 'relative' }), "data-testid": "achievement-list-item", "data-unlocked": achievement.isUnlocked ? 'true' : 'false' },
808
+ React.createElement("div", { style: Object.assign(Object.assign(Object.assign({}, defaultStyles.badgesModal.achievementIcon), styles === null || styles === void 0 ? void 0 : styles.achievementIcon), { opacity: isLocked ? 0.4 : 1 }) }, icon),
809
+ React.createElement("div", { style: { flex: 1 } },
810
+ React.createElement("h3", { style: Object.assign(Object.assign(Object.assign({}, defaultStyles.badgesModal.achievementTitle), styles === null || styles === void 0 ? void 0 : styles.achievementTitle), { color: isLocked ? '#999' : undefined }) }, achievement.achievementTitle),
811
+ achievement.achievementDescription && (React.createElement("p", { style: Object.assign(Object.assign(Object.assign({}, defaultStyles.badgesModal.achievementDescription), styles === null || styles === void 0 ? void 0 : styles.achievementDescription), { color: isLocked ? '#aaa' : '#666' }) },
812
+ achievement.achievementDescription,
813
+ showUnlockConditions && isLocked && (React.createElement("span", { style: {
814
+ display: 'block',
815
+ fontSize: '12px',
816
+ marginTop: '4px',
817
+ fontStyle: 'italic',
818
+ color: '#888',
819
+ } },
820
+ "\uD83D\uDD13 ",
821
+ achievement.achievementDescription))))),
822
+ isLocked && (React.createElement("div", { style: Object.assign(Object.assign({}, defaultStyles.badgesModal.lockIcon), styles === null || styles === void 0 ? void 0 : styles.lockIcon) }, "\uD83D\uDD12"))));
823
+ })));
824
+ };
825
+
826
+ const AchievementsModal = ({ isOpen, onClose, achievements, title = '🏆 Achievements', styles = {}, icons = {}, showLocked = true, showUnlockConditions = false, emptyState, renderAchievement, theme, }) => {
827
+ const context = React.useContext(AchievementContext);
828
+ const uiContext = React.useContext(AchievementUIContext);
829
+ React.useEffect(() => {
830
+ if (!isOpen || typeof document === 'undefined') {
831
+ return;
832
+ }
833
+ const previousOverflow = document.body.style.overflow;
834
+ document.body.style.overflow = 'hidden';
835
+ const handleKeyDown = (event) => {
836
+ if (event.key === 'Escape') {
837
+ onClose();
838
+ }
839
+ };
840
+ document.addEventListener('keydown', handleKeyDown);
841
+ return () => {
842
+ document.body.style.overflow = previousOverflow;
843
+ document.removeEventListener('keydown', handleKeyDown);
844
+ };
845
+ }, [isOpen, onClose]);
846
+ if (!isOpen)
847
+ return null;
848
+ const CustomModal = uiContext.ui.ModalComponent;
849
+ const sourceAchievements = achievements || (context === null || context === void 0 ? void 0 : context.getAllAchievements());
850
+ const modalAchievements = showLocked
851
+ ? sourceAchievements
852
+ : sourceAchievements === null || sourceAchievements === void 0 ? void 0 : sourceAchievements.filter((achievement) => achievement.isUnlocked);
853
+ const resolvedTheme = theme || uiContext.ui.theme || 'modern';
854
+ const mergedIcons = Object.assign(Object.assign(Object.assign(Object.assign({}, defaultAchievementIcons), context === null || context === void 0 ? void 0 : context.icons), uiContext.icons), icons);
855
+ if (CustomModal) {
856
+ if (!modalAchievements) {
857
+ throw new Error('AchievementsModal requires either an achievements prop or an AchievementProvider parent.');
858
+ }
859
+ return (React.createElement(CustomModal, { isOpen: isOpen, onClose: onClose, achievements: modalAchievements, icons: mergedIcons, theme: resolvedTheme }));
860
+ }
861
+ return (React.createElement("div", { style: Object.assign(Object.assign({}, defaultStyles.badgesModal.overlay), styles === null || styles === void 0 ? void 0 : styles.overlay), role: "presentation", onClick: onClose, "data-testid": "achievements-modal-overlay" },
862
+ React.createElement("div", { style: Object.assign(Object.assign({}, defaultStyles.badgesModal.content), styles === null || styles === void 0 ? void 0 : styles.content), role: "dialog", "aria-modal": "true", "aria-labelledby": "achievements-modal-title", onClick: (event) => event.stopPropagation(), "data-testid": "achievements-modal" },
863
+ React.createElement("div", { style: Object.assign(Object.assign({}, defaultStyles.badgesModal.header), styles === null || styles === void 0 ? void 0 : styles.header) },
864
+ React.createElement("h2", { id: "achievements-modal-title", style: { margin: 0 } }, title),
865
+ React.createElement("button", { onClick: onClose, style: Object.assign(Object.assign({}, defaultStyles.badgesModal.closeButton), styles === null || styles === void 0 ? void 0 : styles.closeButton), "aria-label": "Close" }, "\u00D7")),
866
+ React.createElement(AchievementsList, { achievements: modalAchievements, showLocked: showLocked, showUnlockConditions: showUnlockConditions, icons: icons, styles: styles, emptyState: emptyState, renderAchievement: renderAchievement }))));
867
+ };
868
+
869
+ const getPositionStyles$1 = (position) => {
870
+ const base = {
871
+ position: 'fixed',
872
+ margin: '20px',
873
+ zIndex: 1000,
874
+ };
875
+ switch (position) {
876
+ case 'top-left':
877
+ return Object.assign(Object.assign({}, base), { top: 0, left: 0 });
878
+ case 'top-right':
879
+ return Object.assign(Object.assign({}, base), { top: 0, right: 0 });
880
+ case 'bottom-left':
881
+ return Object.assign(Object.assign({}, base), { bottom: 0, left: 0 });
882
+ case 'bottom-right':
883
+ return Object.assign(Object.assign({}, base), { bottom: 0, right: 0 });
884
+ }
885
+ };
886
+ const AchievementsWidget = ({ position = 'bottom-right', placement = 'fixed', showAllAchievements = true, showUnlockConditions = false, showCount = true, icons, theme, label = 'Achievements', icon = '🏆', triggerClassName, renderTrigger, buttonStyles, modalStyles, modalTitle, emptyState, renderAchievement, }) => {
887
+ const uiContext = React.useContext(AchievementUIContext);
888
+ const [isModalOpen, setIsModalOpen] = React.useState(false);
889
+ const { unlockedAchievements, allAchievements, unlockedCount, totalCount } = useAchievementState();
890
+ const resolvedTheme = theme || uiContext.ui.theme || 'modern';
891
+ const themeConfig = getTheme(resolvedTheme) || builtInThemes.modern;
892
+ const modalAchievements = showAllAchievements ? allAchievements : unlockedAchievements;
893
+ const openModal = () => setIsModalOpen(true);
894
+ const buttonBaseStyles = placement === 'inline'
895
+ ? Object.assign({ backgroundColor: 'transparent', color: 'inherit', padding: '10px 12px', border: 'none', borderRadius: '6px', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '8px', font: 'inherit', width: '100%', textAlign: 'left' }, buttonStyles) : Object.assign(Object.assign({ backgroundColor: themeConfig.notification.accentColor, color: 'white', padding: '10px 20px', border: 'none', borderRadius: '20px', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '8px', fontSize: '16px', boxShadow: '0 2px 5px rgba(0,0,0,0.2)', transition: 'transform 0.2s ease-in-out' }, getPositionStyles$1(position)), buttonStyles);
896
+ const buttonProps = {
897
+ type: 'button',
898
+ onClick: openModal,
899
+ style: buttonBaseStyles,
900
+ className: triggerClassName,
901
+ 'data-placement': placement,
902
+ 'data-testid': 'achievements-widget-button',
903
+ 'aria-label': `${label}: ${unlockedCount} of ${totalCount} achievements unlocked`,
904
+ };
905
+ return (React.createElement(React.Fragment, null,
906
+ renderTrigger ? (renderTrigger({
907
+ open: openModal,
908
+ label,
909
+ unlockedCount,
910
+ totalCount,
911
+ unlockedAchievements,
912
+ allAchievements,
913
+ buttonProps,
914
+ })) : (React.createElement("button", Object.assign({}, buttonProps, { onMouseEnter: (event) => {
915
+ if (placement !== 'inline') {
916
+ event.currentTarget.style.transform = 'scale(1.05)';
917
+ }
918
+ }, onMouseLeave: (event) => {
919
+ if (placement !== 'inline') {
920
+ event.currentTarget.style.transform = 'scale(1)';
921
+ }
922
+ } }),
923
+ React.createElement("span", null, icon),
924
+ React.createElement("span", { style: { flex: 1 } }, label),
925
+ showCount && React.createElement("span", null, unlockedCount))),
926
+ React.createElement(AchievementsModal, { isOpen: isModalOpen, onClose: () => setIsModalOpen(false), achievements: modalAchievements, showUnlockConditions: showUnlockConditions, icons: icons, styles: modalStyles, title: modalTitle, emptyState: emptyState, renderAchievement: renderAchievement, theme: resolvedTheme })));
927
+ };
928
+
929
+ const getPositionStyles = (position) => {
930
+ const base = {
931
+ position: 'fixed',
932
+ margin: '20px',
933
+ zIndex: 1000,
934
+ };
935
+ switch (position) {
936
+ case 'top-left':
937
+ return Object.assign(Object.assign({}, base), { top: 0, left: 0 });
938
+ case 'top-right':
939
+ return Object.assign(Object.assign({}, base), { top: 0, right: 0 });
940
+ case 'bottom-left':
941
+ return Object.assign(Object.assign({}, base), { bottom: 0, left: 0 });
942
+ case 'bottom-right':
943
+ return Object.assign(Object.assign({}, base), { bottom: 0, right: 0 });
944
+ }
945
+ };
946
+ /**
947
+ * @deprecated Use `AchievementsWidget` for new integrations. This v3
948
+ * compatibility wrapper will be removed in 4.2.
949
+ */
950
+ const BadgesButton = ({ onClick, position = 'bottom-right', placement = 'fixed', styles = {}, unlockedAchievements, theme = 'modern', }) => {
951
+ React.useEffect(() => {
952
+ warnDeprecation('`BadgesButton` is deprecated. Use `AchievementsWidget` instead. `BadgesButton` will be removed in 4.2.');
953
+ }, []);
954
+ // Get theme configuration for consistent styling
955
+ const themeConfig = getTheme(theme) || builtInThemes.modern;
956
+ const accentColor = themeConfig.notification.accentColor;
957
+ // Different styling for fixed vs inline placement
958
+ const baseStyles = placement === 'inline'
959
+ ? Object.assign({
960
+ // Inline mode: looks like a navigation item
961
+ backgroundColor: 'transparent', color: themeConfig.notification.textColor, padding: '12px 16px', border: 'none', borderRadius: '6px', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '8px', fontSize: '15px', width: '100%', textAlign: 'left', transition: 'background-color 0.2s ease-in-out' }, styles) : Object.assign(Object.assign({
962
+ // Fixed mode: floating button
963
+ backgroundColor: accentColor, color: 'white', padding: '10px 20px', border: 'none', borderRadius: '20px', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '8px', fontSize: '16px', boxShadow: '0 2px 5px rgba(0,0,0,0.2)', transition: 'transform 0.2s ease-in-out' }, getPositionStyles(position)), styles);
964
+ return (React.createElement("button", { onClick: onClick, style: baseStyles, onMouseEnter: (e) => {
965
+ if (placement === 'inline') {
966
+ // Inline mode: subtle background color change
967
+ e.target.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
968
+ }
969
+ else {
970
+ // Fixed mode: scale transformation
971
+ e.target.style.transform = 'scale(1.05)';
972
+ }
973
+ }, onMouseLeave: (e) => {
974
+ if (placement === 'inline') {
975
+ e.target.style.backgroundColor = 'transparent';
976
+ }
977
+ else {
978
+ e.target.style.transform = 'scale(1)';
979
+ }
980
+ }, "data-placement": placement, "data-testid": "badges-button" },
981
+ "\uD83C\uDFC6 Achievements (",
982
+ unlockedAchievements.length,
983
+ ")"));
984
+ };
985
+
986
+ /**
987
+ * @deprecated Use `AchievementsModal`, `AchievementsWidget`, or
988
+ * `AchievementsList` for new integrations. This v3 compatibility wrapper will
989
+ * be removed in 4.2.
990
+ */
991
+ const BadgesModal = ({ isOpen, onClose, achievements, styles = {}, icons = {}, showAllAchievements = false, showUnlockConditions = false, allAchievements, }) => {
992
+ React.useEffect(() => {
993
+ warnDeprecation('`BadgesModal` is deprecated. Use `AchievementsWidget` or `AchievementsList` instead. `BadgesModal` will be removed in 4.2.');
994
+ }, []);
995
+ const achievementsToDisplay = showAllAchievements && allAchievements
996
+ ? allAchievements
997
+ : achievements.map((achievement) => (Object.assign(Object.assign({}, achievement), { isUnlocked: true })));
998
+ return (React.createElement(AchievementsModal, { isOpen: isOpen, onClose: onClose, achievements: achievementsToDisplay, styles: styles, icons: icons, showUnlockConditions: showUnlockConditions }));
999
+ };
1000
+
1001
+ /**
1002
+ * @deprecated Use `AchievementsWidget` for new integrations. This v3
1003
+ * compatibility wrapper will be removed in 4.2.
1004
+ */
1005
+ const BadgesButtonWithModal = ({ unlockedAchievements, position = 'bottom-right', placement = 'fixed', showAllAchievements = false, allAchievements, showUnlockConditions = false, icons, theme = 'modern', buttonStyles, modalStyles, }) => {
1006
+ const [isModalOpen, setIsModalOpen] = React.useState(false);
1007
+ return (React.createElement(React.Fragment, null,
1008
+ React.createElement(BadgesButton, { onClick: () => setIsModalOpen(true), unlockedAchievements: unlockedAchievements, position: position, placement: placement, theme: theme, styles: buttonStyles }),
1009
+ React.createElement(BadgesModal, { isOpen: isModalOpen, onClose: () => setIsModalOpen(false), achievements: unlockedAchievements, showAllAchievements: showAllAchievements, allAchievements: allAchievements, showUnlockConditions: showUnlockConditions, icons: icons, styles: modalStyles })));
1010
+ };
1011
+
1012
+ /**
1013
+ * @deprecated Use the provider `ui.ConfettiComponent` option or the built-in
1014
+ * confetti default. This v3 compatibility wrapper will be removed in 4.2.
1015
+ */
1016
+ const ConfettiWrapper = ({ show }) => {
1017
+ React.useEffect(() => {
1018
+ warnDeprecation('`ConfettiWrapper` is deprecated. Use the provider `ui.ConfettiComponent` option or built-in confetti defaults instead. `ConfettiWrapper` will be removed in 4.2.');
1019
+ }, []);
1020
+ return React.createElement(BuiltInConfetti, { show: show, particleCount: 200 });
1021
+ };
1022
+
1023
+ const clamp = (value, min, max) => {
1024
+ return Math.min(Math.max(value, min), max);
1025
+ };
1026
+ const LevelProgress = ({ level, currentXP, nextLevelXP, label = 'Level', valueLabel, showValues = true, showPercent = true, theme = 'modern', styles = {}, className, }) => {
1027
+ const themeConfig = getTheme(theme) || builtInThemes.modern;
1028
+ const safeMax = nextLevelXP > 0 ? nextLevelXP : 1;
1029
+ const clampedCurrent = clamp(currentXP, 0, safeMax);
1030
+ const progress = Math.round((clampedCurrent / safeMax) * 100);
1031
+ const computedValueLabel = valueLabel !== null && valueLabel !== void 0 ? valueLabel : `${clampedCurrent} / ${safeMax} XP`;
1032
+ const containerStyles = Object.assign(Object.assign(Object.assign({}, defaultStyles.levelProgress.container), { background: themeConfig.notification.background, color: themeConfig.notification.textColor, borderRadius: themeConfig.notification.borderRadius, boxShadow: themeConfig.notification.boxShadow }), styles === null || styles === void 0 ? void 0 : styles.container);
1033
+ const progressBarStyles = Object.assign(Object.assign(Object.assign({}, defaultStyles.levelProgress.progressBar), { backgroundColor: themeConfig.notification.accentColor, width: `${progress}%` }), styles === null || styles === void 0 ? void 0 : styles.progressBar);
1034
+ return (React.createElement("div", { className: className, style: containerStyles, "data-testid": "level-progress" },
1035
+ React.createElement("div", { style: Object.assign(Object.assign({}, defaultStyles.levelProgress.header), styles === null || styles === void 0 ? void 0 : styles.header) },
1036
+ React.createElement("div", { style: Object.assign(Object.assign({}, defaultStyles.levelProgress.levelLabel), styles === null || styles === void 0 ? void 0 : styles.levelLabel) },
1037
+ label,
1038
+ " ",
1039
+ level),
1040
+ showValues && (React.createElement("div", { style: Object.assign(Object.assign({}, defaultStyles.levelProgress.valueText), styles === null || styles === void 0 ? void 0 : styles.valueText) }, computedValueLabel))),
1041
+ React.createElement("div", { style: Object.assign(Object.assign({}, defaultStyles.levelProgress.progressTrack), styles === null || styles === void 0 ? void 0 : styles.progressTrack), role: "progressbar", "aria-valuemin": 0, "aria-valuemax": safeMax, "aria-valuenow": clampedCurrent, "aria-label": "Level progress" },
1042
+ React.createElement("div", { style: progressBarStyles })),
1043
+ showPercent && (React.createElement("div", { style: Object.assign(Object.assign({}, defaultStyles.levelProgress.progressText), styles === null || styles === void 0 ? void 0 : styles.progressText) },
1044
+ progress,
1045
+ "%"))));
1046
+ };
1047
+
1048
+ /**
1049
+ * A simplified hook for achievement tracking.
1050
+ * Provides the v4 happy path for direct metric updates plus explicit state names.
1051
+ */
1052
+ const useSimpleAchievements = () => {
1053
+ const { update, reset, getState, exportData, importData } = useAchievements();
1054
+ const achievementState = useAchievementState();
1055
+ const track = (metric, value) => update({ [metric]: value });
1056
+ const increment = (metric, amount = 1) => {
1057
+ const currentState = getState();
1058
+ const currentMetricArray = currentState.metrics[metric] || [0];
1059
+ const currentValue = Array.isArray(currentMetricArray)
1060
+ ? currentMetricArray[0]
1061
+ : currentMetricArray;
1062
+ const newValue = (typeof currentValue === 'number' ? currentValue : 0) + amount;
1063
+ update({ [metric]: newValue });
1064
+ };
1065
+ const trackMultiple = (metrics) => update(metrics);
1066
+ return {
1067
+ track,
1068
+ increment,
1069
+ trackMultiple,
1070
+ unlockedIds: achievementState.unlockedIds,
1071
+ unlockedAchievements: achievementState.unlockedAchievements,
1072
+ allAchievements: achievementState.allAchievements,
1073
+ unlockedCount: achievementState.unlockedCount,
1074
+ totalCount: achievementState.totalCount,
1075
+ metrics: achievementState.metrics,
1076
+ reset,
1077
+ getState,
1078
+ exportData,
1079
+ importData,
1080
+ getAllAchievements: () => achievementState.allAchievements,
1081
+ /**
1082
+ * @deprecated Use `unlockedIds` instead. This alias will be removed in 4.2.
1083
+ */
1084
+ unlocked: achievementState.unlockedIds,
1085
+ /**
1086
+ * @deprecated Use `allAchievements` instead. This alias will be removed in 4.2.
1087
+ */
1088
+ all: achievementState.allAchievements,
1089
+ };
1090
+ };
1091
+
1092
+ /**
1093
+ * Built-in modal component
1094
+ * Modern, theme-aware achievement modal with smooth animations
1095
+ */
1096
+ const BuiltInModal = ({ isOpen, onClose, achievements, icons = {}, theme = 'modern', }) => {
1097
+ // Merge custom icons with defaults
1098
+ const mergedIcons = Object.assign(Object.assign({}, defaultAchievementIcons), icons);
1099
+ // Get theme configuration
1100
+ const themeConfig = getTheme(theme) || builtInThemes.modern;
1101
+ const { modal: themeStyles } = themeConfig;
1102
+ React.useEffect(() => {
1103
+ if (isOpen) {
1104
+ // Lock body scroll when modal is open
1105
+ document.body.style.overflow = 'hidden';
1106
+ }
1107
+ else {
1108
+ // Restore body scroll
1109
+ document.body.style.overflow = '';
1110
+ }
1111
+ return () => {
1112
+ document.body.style.overflow = '';
1113
+ };
1114
+ }, [isOpen]);
1115
+ if (!isOpen)
1116
+ return null;
1117
+ const overlayStyles = {
1118
+ position: 'fixed',
1119
+ top: 0,
1120
+ left: 0,
1121
+ right: 0,
1122
+ bottom: 0,
1123
+ backgroundColor: themeStyles.overlayColor,
1124
+ display: 'flex',
1125
+ alignItems: 'center',
1126
+ justifyContent: 'center',
1127
+ zIndex: 10000,
1128
+ animation: 'fadeIn 0.3s ease-in-out',
1129
+ };
1130
+ const modalStyles = {
1131
+ background: themeStyles.background,
1132
+ borderRadius: themeStyles.borderRadius,
1133
+ padding: '32px',
1134
+ maxWidth: '600px',
1135
+ width: '90%',
1136
+ maxHeight: '80vh',
1137
+ overflow: 'auto',
1138
+ boxShadow: '0 20px 60px rgba(0, 0, 0, 0.5)',
1139
+ animation: 'scaleIn 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
1140
+ position: 'relative',
1141
+ };
1142
+ const headerStyles = {
1143
+ display: 'flex',
1144
+ justifyContent: 'space-between',
1145
+ alignItems: 'center',
1146
+ marginBottom: '24px',
1147
+ };
1148
+ const titleStyles = {
1149
+ margin: 0,
1150
+ color: themeStyles.textColor,
1151
+ fontSize: themeStyles.headerFontSize || '28px',
1152
+ fontWeight: 'bold',
1153
+ };
1154
+ const closeButtonStyles = {
1155
+ background: 'none',
1156
+ border: 'none',
1157
+ fontSize: '32px',
1158
+ cursor: 'pointer',
1159
+ color: themeStyles.textColor,
1160
+ opacity: 0.6,
1161
+ transition: 'opacity 0.2s',
1162
+ padding: 0,
1163
+ lineHeight: 1,
1164
+ };
1165
+ const isBadgeLayout = themeStyles.achievementLayout === 'badge';
1166
+ const listStyles = isBadgeLayout
1167
+ ? {
1168
+ display: 'grid',
1169
+ gridTemplateColumns: 'repeat(auto-fill, minmax(140px, 1fr))',
1170
+ gap: '16px',
1171
+ }
1172
+ : {
1173
+ display: 'flex',
1174
+ flexDirection: 'column',
1175
+ gap: '12px',
1176
+ };
1177
+ const getAchievementItemStyles = (isUnlocked) => {
1178
+ const baseStyles = {
1179
+ borderRadius: themeStyles.achievementCardBorderRadius || '12px',
1180
+ backgroundColor: isUnlocked
1181
+ ? `${themeStyles.accentColor}1A` // 10% opacity
1182
+ : 'rgba(255, 255, 255, 0.05)',
1183
+ border: `2px solid ${isUnlocked ? themeStyles.accentColor : 'rgba(255, 255, 255, 0.1)'}`,
1184
+ opacity: isUnlocked ? 1 : 0.5,
1185
+ transition: 'all 0.2s',
1186
+ };
1187
+ if (isBadgeLayout) {
1188
+ // Badge layout: vertical, centered, square-ish
1189
+ return Object.assign(Object.assign({}, baseStyles), { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', textAlign: 'center', padding: '20px 12px', aspectRatio: '1 / 1.1', minHeight: '160px' });
1190
+ }
1191
+ else {
1192
+ // Horizontal layout (default)
1193
+ return Object.assign(Object.assign({}, baseStyles), { display: 'flex', gap: '16px', padding: '16px' });
1194
+ }
1195
+ };
1196
+ const getIconContainerStyles = (isUnlocked) => {
1197
+ if (isBadgeLayout) {
1198
+ return {
1199
+ fontSize: '48px',
1200
+ lineHeight: 1,
1201
+ marginBottom: '8px',
1202
+ opacity: isUnlocked ? 1 : 0.3,
1203
+ };
1204
+ }
1205
+ return {
1206
+ fontSize: '40px',
1207
+ flexShrink: 0,
1208
+ lineHeight: 1,
1209
+ opacity: isUnlocked ? 1 : 0.3,
1210
+ };
1211
+ };
1212
+ const contentStyles = isBadgeLayout
1213
+ ? {
1214
+ width: '100%',
1215
+ }
1216
+ : {
1217
+ flex: 1,
1218
+ minWidth: 0,
1219
+ };
1220
+ const achievementTitleStyles = isBadgeLayout
1221
+ ? {
1222
+ margin: '0 0 4px 0',
1223
+ color: themeStyles.textColor,
1224
+ fontSize: '14px',
1225
+ fontWeight: 'bold',
1226
+ lineHeight: '1.3',
1227
+ }
1228
+ : {
1229
+ margin: '0 0 8px 0',
1230
+ color: themeStyles.textColor,
1231
+ fontSize: '18px',
1232
+ fontWeight: 'bold',
1233
+ overflow: 'hidden',
1234
+ textOverflow: 'ellipsis',
1235
+ whiteSpace: 'nowrap',
1236
+ };
1237
+ const achievementDescriptionStyles = isBadgeLayout
1238
+ ? {
1239
+ margin: 0,
1240
+ color: themeStyles.textColor,
1241
+ opacity: 0.7,
1242
+ fontSize: '11px',
1243
+ lineHeight: '1.3',
1244
+ }
1245
+ : {
1246
+ margin: 0,
1247
+ color: themeStyles.textColor,
1248
+ opacity: 0.8,
1249
+ fontSize: '14px',
1250
+ };
1251
+ const getLockIconStyles = () => {
1252
+ if (isBadgeLayout) {
1253
+ return {
1254
+ position: 'absolute',
1255
+ top: '8px',
1256
+ right: '8px',
1257
+ fontSize: '18px',
1258
+ opacity: 0.6,
1259
+ };
1260
+ }
1261
+ return {
1262
+ fontSize: '24px',
1263
+ flexShrink: 0,
1264
+ opacity: 0.5,
1265
+ };
1266
+ };
1267
+ return (React.createElement(React.Fragment, null,
1268
+ React.createElement("style", null, `
1269
+ @keyframes fadeIn {
1270
+ from { opacity: 0; }
1271
+ to { opacity: 1; }
1272
+ }
1273
+ @keyframes scaleIn {
1274
+ from {
1275
+ transform: scale(0.9);
1276
+ opacity: 0;
1277
+ }
1278
+ to {
1279
+ transform: scale(1);
1280
+ opacity: 1;
1281
+ }
1282
+ }
1283
+ `),
1284
+ React.createElement("div", { style: overlayStyles, onClick: onClose, "data-testid": "built-in-modal-overlay" },
1285
+ React.createElement("div", { style: modalStyles, onClick: (e) => e.stopPropagation(), "data-testid": "built-in-modal" },
1286
+ React.createElement("div", { style: headerStyles },
1287
+ React.createElement("h2", { style: titleStyles }, "\uD83C\uDFC6 Achievements"),
1288
+ React.createElement("button", { onClick: onClose, style: closeButtonStyles, onMouseEnter: (e) => (e.currentTarget.style.opacity = '1'), onMouseLeave: (e) => (e.currentTarget.style.opacity = '0.6'), "aria-label": "Close modal" }, "\u00D7")),
1289
+ React.createElement("div", { style: listStyles }, achievements.length === 0 ? (React.createElement("div", { style: { textAlign: 'center', padding: '40px 20px', color: themeStyles.textColor, opacity: 0.6 } }, "No achievements yet. Start exploring to unlock them!")) : (achievements.map((achievement) => {
1290
+ // If achievementIconKey exists but not in mergedIcons, use it directly (might be an emoji)
1291
+ // Otherwise, look up in mergedIcons or fall back to default
1292
+ const icon = (achievement.achievementIconKey &&
1293
+ mergedIcons[achievement.achievementIconKey]) ||
1294
+ achievement.achievementIconKey ||
1295
+ mergedIcons.default ||
1296
+ '⭐';
1297
+ return (React.createElement("div", { key: achievement.achievementId, style: Object.assign(Object.assign({}, getAchievementItemStyles(achievement.isUnlocked)), { position: isBadgeLayout ? 'relative' : 'static' }) },
1298
+ React.createElement("div", { style: getIconContainerStyles(achievement.isUnlocked) }, icon),
1299
+ React.createElement("div", { style: contentStyles },
1300
+ React.createElement("h3", { style: achievementTitleStyles }, achievement.achievementTitle),
1301
+ React.createElement("p", { style: achievementDescriptionStyles }, achievement.achievementDescription)),
1302
+ !achievement.isUnlocked && (React.createElement("div", { style: getLockIconStyles() }, "\uD83D\uDD12"))));
1303
+ })))))));
1304
+ };
1305
+
1306
+ Object.defineProperty(exports, 'AchievementBuilder', {
1307
+ enumerable: true,
1308
+ get: function () { return achievementsEngine.AchievementBuilder; }
1309
+ });
1310
+ Object.defineProperty(exports, 'AchievementEngine', {
1311
+ enumerable: true,
1312
+ get: function () { return achievementsEngine.AchievementEngine; }
1313
+ });
1314
+ Object.defineProperty(exports, 'AchievementError', {
1315
+ enumerable: true,
1316
+ get: function () { return achievementsEngine.AchievementError; }
1317
+ });
1318
+ Object.defineProperty(exports, 'AsyncStorageAdapter', {
1319
+ enumerable: true,
1320
+ get: function () { return achievementsEngine.AsyncStorageAdapter; }
1321
+ });
1322
+ Object.defineProperty(exports, 'ConfigurationError', {
1323
+ enumerable: true,
1324
+ get: function () { return achievementsEngine.ConfigurationError; }
1325
+ });
1326
+ Object.defineProperty(exports, 'ImportValidationError', {
1327
+ enumerable: true,
1328
+ get: function () { return achievementsEngine.ImportValidationError; }
1329
+ });
1330
+ Object.defineProperty(exports, 'IndexedDBStorage', {
1331
+ enumerable: true,
1332
+ get: function () { return achievementsEngine.IndexedDBStorage; }
1333
+ });
1334
+ Object.defineProperty(exports, 'LocalStorage', {
1335
+ enumerable: true,
1336
+ get: function () { return achievementsEngine.LocalStorage; }
1337
+ });
1338
+ Object.defineProperty(exports, 'MemoryStorage', {
1339
+ enumerable: true,
1340
+ get: function () { return achievementsEngine.MemoryStorage; }
1341
+ });
1342
+ Object.defineProperty(exports, 'OfflineQueueStorage', {
1343
+ enumerable: true,
1344
+ get: function () { return achievementsEngine.OfflineQueueStorage; }
1345
+ });
1346
+ Object.defineProperty(exports, 'RestApiStorage', {
1347
+ enumerable: true,
1348
+ get: function () { return achievementsEngine.RestApiStorage; }
1349
+ });
1350
+ Object.defineProperty(exports, 'StorageError', {
1351
+ enumerable: true,
1352
+ get: function () { return achievementsEngine.StorageError; }
1353
+ });
1354
+ Object.defineProperty(exports, 'StorageQuotaError', {
1355
+ enumerable: true,
1356
+ get: function () { return achievementsEngine.StorageQuotaError; }
1357
+ });
1358
+ Object.defineProperty(exports, 'StorageType', {
1359
+ enumerable: true,
1360
+ get: function () { return achievementsEngine.StorageType; }
1361
+ });
1362
+ Object.defineProperty(exports, 'SyncError', {
1363
+ enumerable: true,
1364
+ get: function () { return achievementsEngine.SyncError; }
1365
+ });
1366
+ Object.defineProperty(exports, 'createConfigHash', {
1367
+ enumerable: true,
1368
+ get: function () { return achievementsEngine.createConfigHash; }
1369
+ });
1370
+ Object.defineProperty(exports, 'exportAchievementData', {
1371
+ enumerable: true,
1372
+ get: function () { return achievementsEngine.exportAchievementData; }
1373
+ });
1374
+ Object.defineProperty(exports, 'importAchievementData', {
1375
+ enumerable: true,
1376
+ get: function () { return achievementsEngine.importAchievementData; }
1377
+ });
1378
+ Object.defineProperty(exports, 'isAchievementError', {
1379
+ enumerable: true,
1380
+ get: function () { return achievementsEngine.isAchievementError; }
1381
+ });
1382
+ Object.defineProperty(exports, 'isRecoverableError', {
1383
+ enumerable: true,
1384
+ get: function () { return achievementsEngine.isRecoverableError; }
1385
+ });
1386
+ Object.defineProperty(exports, 'isSimpleConfig', {
1387
+ enumerable: true,
1388
+ get: function () { return achievementsEngine.isSimpleConfig; }
1389
+ });
1390
+ Object.defineProperty(exports, 'normalizeAchievements', {
1391
+ enumerable: true,
1392
+ get: function () { return achievementsEngine.normalizeAchievements; }
1393
+ });
1394
+ exports.AchievementContext = AchievementContext;
1395
+ exports.AchievementProvider = AchievementProvider;
1396
+ exports.AchievementsList = AchievementsList;
1397
+ exports.AchievementsModal = AchievementsModal;
1398
+ exports.AchievementsWidget = AchievementsWidget;
1399
+ exports.BadgesButton = BadgesButton;
1400
+ exports.BadgesButtonWithModal = BadgesButtonWithModal;
1401
+ exports.BadgesModal = BadgesModal;
1402
+ exports.BuiltInConfetti = BuiltInConfetti;
1403
+ exports.BuiltInModal = BuiltInModal;
1404
+ exports.BuiltInNotification = BuiltInNotification;
1405
+ exports.ConfettiWrapper = ConfettiWrapper;
1406
+ exports.HeadlessAchievementProvider = AchievementProvider$1;
1407
+ exports.LevelProgress = LevelProgress;
1408
+ exports.defaultAchievementIcons = defaultAchievementIcons;
1409
+ exports.defaultStyles = defaultStyles;
1410
+ exports.isAsyncStorage = isAsyncStorage;
1411
+ exports.useAchievementEngine = useAchievementEngine;
1412
+ exports.useAchievementState = useAchievementState;
1413
+ exports.useAchievements = useAchievements;
1414
+ exports.useSimpleAchievements = useSimpleAchievements;
1415
+ exports.useWindowSize = useWindowSize;
1416
+ //# sourceMappingURL=web.cjs.map