@wakastellar/ui 2.0.0 → 2.1.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 (290) hide show
  1. package/dist/cli/commands/add.d.ts +7 -0
  2. package/dist/cli/commands/init.d.ts +6 -0
  3. package/dist/cli/commands/list.d.ts +5 -0
  4. package/dist/cli/commands/search.d.ts +1 -0
  5. package/dist/cli/index.cjs +4844 -0
  6. package/dist/cli/index.d.ts +1 -0
  7. package/dist/cli/utils/config.d.ts +29 -0
  8. package/dist/cli/utils/logger.d.ts +20 -0
  9. package/dist/cli/utils/registry.d.ts +23 -0
  10. package/package.json +14 -3
  11. package/src/blocks/activity-timeline/index.tsx +586 -0
  12. package/src/blocks/calendar-view/index.tsx +756 -0
  13. package/src/blocks/chat/index.tsx +1018 -0
  14. package/src/blocks/chat/widget.tsx +504 -0
  15. package/src/blocks/dashboard/index.tsx +522 -0
  16. package/src/blocks/empty-states/index.tsx +452 -0
  17. package/src/blocks/error-pages/index.tsx +426 -0
  18. package/src/blocks/faq/index.tsx +479 -0
  19. package/src/blocks/file-manager/index.tsx +890 -0
  20. package/src/blocks/footer/index.tsx +133 -0
  21. package/src/blocks/header/index.tsx +357 -0
  22. package/src/blocks/headtab/index.tsx +139 -0
  23. package/src/blocks/i18n-editor/index.tsx +1016 -0
  24. package/src/blocks/index.ts +80 -0
  25. package/src/blocks/kanban-board/index.tsx +779 -0
  26. package/src/blocks/landing/index.tsx +677 -0
  27. package/src/blocks/language-selector/index.tsx +88 -0
  28. package/src/blocks/layout/index.tsx +159 -0
  29. package/src/blocks/login/index.tsx +339 -0
  30. package/src/blocks/login/types.ts +131 -0
  31. package/src/blocks/pricing/index.tsx +564 -0
  32. package/src/blocks/profile/index.tsx +746 -0
  33. package/src/blocks/settings/index.tsx +558 -0
  34. package/src/blocks/sidebar/index.tsx +713 -0
  35. package/src/blocks/theme-creator-block/index.tsx +835 -0
  36. package/src/blocks/user-management/index.tsx +1037 -0
  37. package/src/blocks/wizard/index.tsx +719 -0
  38. package/src/components/DataTable/DataTable.tsx +406 -0
  39. package/src/components/DataTable/DataTableAdvanced.tsx +720 -0
  40. package/src/components/DataTable/DataTableBody.tsx +216 -0
  41. package/src/components/DataTable/DataTableCell.tsx +172 -0
  42. package/src/components/DataTable/DataTableColumnResizer.tsx +62 -0
  43. package/src/components/DataTable/DataTableConflictResolver.tsx +478 -0
  44. package/src/components/DataTable/DataTableContextMenu.tsx +219 -0
  45. package/src/components/DataTable/DataTableEditCell.tsx +279 -0
  46. package/src/components/DataTable/DataTableFilterBuilder.tsx +519 -0
  47. package/src/components/DataTable/DataTableFilters.tsx +535 -0
  48. package/src/components/DataTable/DataTableGrouping.tsx +147 -0
  49. package/src/components/DataTable/DataTableHeader.tsx +172 -0
  50. package/src/components/DataTable/DataTablePagination.tsx +125 -0
  51. package/src/components/DataTable/DataTableSelection.tsx +269 -0
  52. package/src/components/DataTable/DataTableSyncStatus.tsx +281 -0
  53. package/src/components/DataTable/DataTableToolbar.tsx +262 -0
  54. package/src/components/DataTable/README.md +446 -0
  55. package/src/components/DataTable/__tests__/DataTableAdvanced.test.tsx +426 -0
  56. package/src/components/DataTable/__tests__/DataTableEdit.test.tsx +329 -0
  57. package/src/components/DataTable/__tests__/useDataTableAdvanced.test.ts +455 -0
  58. package/src/components/DataTable/examples/EditExample.tsx +166 -0
  59. package/src/components/DataTable/formatters/index.ts +335 -0
  60. package/src/components/DataTable/hooks/__tests__/useDataTableEdit.test.ts +239 -0
  61. package/src/components/DataTable/hooks/useDataTable.ts +145 -0
  62. package/src/components/DataTable/hooks/useDataTableAdvanced.ts +342 -0
  63. package/src/components/DataTable/hooks/useDataTableAdvancedFilters.ts +637 -0
  64. package/src/components/DataTable/hooks/useDataTableColumnTemplates.ts +186 -0
  65. package/src/components/DataTable/hooks/useDataTableEdit.ts +167 -0
  66. package/src/components/DataTable/hooks/useDataTableExport.ts +227 -0
  67. package/src/components/DataTable/hooks/useDataTableImport.ts +216 -0
  68. package/src/components/DataTable/hooks/useDataTableOffline.ts +481 -0
  69. package/src/components/DataTable/hooks/useDataTableTheme.ts +213 -0
  70. package/src/components/DataTable/hooks/useDataTableVirtualization.ts +99 -0
  71. package/src/components/DataTable/hooks/useTableLayout.ts +85 -0
  72. package/src/components/DataTable/index.ts +81 -0
  73. package/src/components/DataTable/services/IndexedDBService.ts +504 -0
  74. package/src/components/DataTable/templates/index.tsx +803 -0
  75. package/src/components/DataTable/types.ts +504 -0
  76. package/src/components/DataTable/utils.ts +164 -0
  77. package/src/components/DataTable/workers/exportWorker.ts +213 -0
  78. package/src/components/accordion/index.tsx +61 -0
  79. package/src/components/alert/index.tsx +61 -0
  80. package/src/components/alert-dialog/index.tsx +146 -0
  81. package/src/components/aspect-ratio/index.tsx +12 -0
  82. package/src/components/avatar/index.tsx +54 -0
  83. package/src/components/badge/Badge.stories.tsx +64 -0
  84. package/src/components/badge/index.tsx +38 -0
  85. package/src/components/button/Button.stories.tsx +173 -0
  86. package/src/components/button/index.tsx +56 -0
  87. package/src/components/calendar/index.tsx +73 -0
  88. package/src/components/card/index.tsx +78 -0
  89. package/src/components/checkbox/index.tsx +34 -0
  90. package/src/components/code/index.tsx +229 -0
  91. package/src/components/collapsible/index.tsx +16 -0
  92. package/src/components/command/index.tsx +162 -0
  93. package/src/components/context-menu/index.tsx +204 -0
  94. package/src/components/dialog/index.tsx +126 -0
  95. package/src/components/dropdown-menu/index.tsx +204 -0
  96. package/src/components/error-boundary/ErrorBoundary.tsx +281 -0
  97. package/src/components/error-boundary/index.ts +7 -0
  98. package/src/components/form/index.tsx +183 -0
  99. package/src/components/hover-card/index.tsx +33 -0
  100. package/src/components/index.ts +368 -0
  101. package/src/components/input/Input.stories.tsx +100 -0
  102. package/src/components/input/index.tsx +27 -0
  103. package/src/components/input-otp/index.tsx +277 -0
  104. package/src/components/label/index.tsx +30 -0
  105. package/src/components/language-selector/index.tsx +341 -0
  106. package/src/components/menubar/index.tsx +240 -0
  107. package/src/components/navigation-menu/index.tsx +134 -0
  108. package/src/components/popover/index.tsx +35 -0
  109. package/src/components/progress/index.tsx +32 -0
  110. package/src/components/radio-group/index.tsx +48 -0
  111. package/src/components/scroll-area/index.tsx +52 -0
  112. package/src/components/select/index.tsx +164 -0
  113. package/src/components/separator/index.tsx +35 -0
  114. package/src/components/sheet/index.tsx +147 -0
  115. package/src/components/skeleton/index.tsx +22 -0
  116. package/src/components/slider/index.tsx +32 -0
  117. package/src/components/switch/index.tsx +33 -0
  118. package/src/components/table/index.tsx +117 -0
  119. package/src/components/tabs/index.tsx +59 -0
  120. package/src/components/textarea/index.tsx +30 -0
  121. package/src/components/theme-selector/index.tsx +327 -0
  122. package/src/components/toast/index.tsx +133 -0
  123. package/src/components/toaster/index.tsx +34 -0
  124. package/src/components/toggle/index.tsx +49 -0
  125. package/src/components/tooltip/index.tsx +34 -0
  126. package/src/components/typography/index.tsx +276 -0
  127. package/src/components/waka-3d-pie-chart/index.tsx +486 -0
  128. package/src/components/waka-achievement-unlock/index.tsx +716 -0
  129. package/src/components/waka-activity-feed/index.tsx +686 -0
  130. package/src/components/waka-address-autocomplete/index.tsx +1202 -0
  131. package/src/components/waka-admincrumb/index.tsx +349 -0
  132. package/src/components/waka-alert-stack/index.tsx +827 -0
  133. package/src/components/waka-allocation-matrix/index.tsx +1278 -0
  134. package/src/components/waka-approval-chain/index.tsx +766 -0
  135. package/src/components/waka-audit-log/index.tsx +1475 -0
  136. package/src/components/waka-autocomplete/index.tsx +358 -0
  137. package/src/components/waka-badge-showcase/index.tsx +704 -0
  138. package/src/components/waka-barcode/index.tsx +260 -0
  139. package/src/components/waka-biometric-prompt/index.tsx +765 -0
  140. package/src/components/waka-bottom-sheet/index.tsx +495 -0
  141. package/src/components/waka-breadcrumb/index.tsx +376 -0
  142. package/src/components/waka-breadcrumb-path/index.tsx +513 -0
  143. package/src/components/waka-budget-burn/index.tsx +1234 -0
  144. package/src/components/waka-capacity-planner/index.tsx +1107 -0
  145. package/src/components/waka-carousel/index.tsx +893 -0
  146. package/src/components/waka-cart-summary/index.tsx +1055 -0
  147. package/src/components/waka-challenge-timer/index.tsx +1044 -0
  148. package/src/components/waka-charts/WakaAreaChart.tsx +251 -0
  149. package/src/components/waka-charts/WakaBarChart.tsx +222 -0
  150. package/src/components/waka-charts/WakaChart.tsx +124 -0
  151. package/src/components/waka-charts/WakaLineChart.tsx +219 -0
  152. package/src/components/waka-charts/WakaMiniChart.tsx +133 -0
  153. package/src/components/waka-charts/WakaPieChart.tsx +214 -0
  154. package/src/components/waka-charts/WakaSparkline.tsx +229 -0
  155. package/src/components/waka-charts/dataTableHelpers.ts +109 -0
  156. package/src/components/waka-charts/hooks/useChartTheme.ts +123 -0
  157. package/src/components/waka-charts/hooks/useRechartsLoader.ts +234 -0
  158. package/src/components/waka-charts/index.ts +90 -0
  159. package/src/components/waka-charts/types.ts +330 -0
  160. package/src/components/waka-chat-bubble/index.tsx +1060 -0
  161. package/src/components/waka-checklist/index.tsx +1067 -0
  162. package/src/components/waka-checkout-stepper/index.tsx +976 -0
  163. package/src/components/waka-cohort-table/index.tsx +1011 -0
  164. package/src/components/waka-color-picker/index.tsx +447 -0
  165. package/src/components/waka-combo-counter/index.tsx +864 -0
  166. package/src/components/waka-combobox/index.tsx +497 -0
  167. package/src/components/waka-command-bar/index.tsx +403 -0
  168. package/src/components/waka-compare-period/index.tsx +1230 -0
  169. package/src/components/waka-connection-matrix/index.tsx +1053 -0
  170. package/src/components/waka-contribution-graph/index.tsx +552 -0
  171. package/src/components/waka-cost-breakdown/index.tsx +1065 -0
  172. package/src/components/waka-coupon-input/index.tsx +592 -0
  173. package/src/components/waka-credit-card-input/index.tsx +982 -0
  174. package/src/components/waka-daily-reward/index.tsx +762 -0
  175. package/src/components/waka-date-range-picker/index.tsx +378 -0
  176. package/src/components/waka-datetime-picker/index.tsx +793 -0
  177. package/src/components/waka-datetime-picker.form-integration/index.tsx +402 -0
  178. package/src/components/waka-deployment-lane/index.tsx +673 -0
  179. package/src/components/waka-device-trust/index.tsx +1259 -0
  180. package/src/components/waka-dock/index.tsx +285 -0
  181. package/src/components/waka-drawer/index.tsx +319 -0
  182. package/src/components/waka-empty-state/index.tsx +545 -0
  183. package/src/components/waka-error-shake/index.tsx +398 -0
  184. package/src/components/waka-feature-announcement/index.tsx +991 -0
  185. package/src/components/waka-file-upload/index.tsx +437 -0
  186. package/src/components/waka-floating-nav/index.tsx +413 -0
  187. package/src/components/waka-flow-diagram/index.tsx +508 -0
  188. package/src/components/waka-funnel-chart/index.tsx +823 -0
  189. package/src/components/waka-glow-card/index.tsx +246 -0
  190. package/src/components/waka-goal-progress/index.tsx +1025 -0
  191. package/src/components/waka-haptic-button/index.tsx +388 -0
  192. package/src/components/waka-health-pulse/index.tsx +451 -0
  193. package/src/components/waka-heatmap/index.tsx +1026 -0
  194. package/src/components/waka-hotspot/index.tsx +682 -0
  195. package/src/components/waka-image/index.tsx +373 -0
  196. package/src/components/waka-incident-timeline/index.tsx +686 -0
  197. package/src/components/waka-invoice-preview/index.tsx +829 -0
  198. package/src/components/waka-kanban/index.tsx +646 -0
  199. package/src/components/waka-kpi-dashboard/index.tsx +755 -0
  200. package/src/components/waka-leaderboard/index.tsx +746 -0
  201. package/src/components/waka-level-progress/index.tsx +665 -0
  202. package/src/components/waka-liquid-button/index.tsx +520 -0
  203. package/src/components/waka-loading-orbit/index.tsx +478 -0
  204. package/src/components/waka-loot-box/index.tsx +1091 -0
  205. package/src/components/waka-magic-link/index.tsx +321 -0
  206. package/src/components/waka-magnetic-button/index.tsx +567 -0
  207. package/src/components/waka-mention-input/index.tsx +953 -0
  208. package/src/components/waka-metric-sparkline/index.tsx +627 -0
  209. package/src/components/waka-milestone-road/index.tsx +1064 -0
  210. package/src/components/waka-modal/index.tsx +374 -0
  211. package/src/components/waka-morph-button/index.tsx +495 -0
  212. package/src/components/waka-network-topology/index.tsx +801 -0
  213. package/src/components/waka-notifications/index.tsx +414 -0
  214. package/src/components/waka-number-input/index.tsx +373 -0
  215. package/src/components/waka-orbital-menu/index.tsx +445 -0
  216. package/src/components/waka-order-tracker/index.tsx +1041 -0
  217. package/src/components/waka-pagination/index.tsx +393 -0
  218. package/src/components/waka-password-strength/index.tsx +824 -0
  219. package/src/components/waka-payment-method-picker/index.tsx +715 -0
  220. package/src/components/waka-permission-matrix/index.tsx +1302 -0
  221. package/src/components/waka-phone-input/index.tsx +801 -0
  222. package/src/components/waka-pipeline-view/index.tsx +604 -0
  223. package/src/components/waka-player-card/index.tsx +691 -0
  224. package/src/components/waka-points-popup/index.tsx +366 -0
  225. package/src/components/waka-power-up/index.tsx +1155 -0
  226. package/src/components/waka-presence-indicator/index.tsx +1181 -0
  227. package/src/components/waka-pricing-table/index.tsx +755 -0
  228. package/src/components/waka-product-card/index.tsx +786 -0
  229. package/src/components/waka-progress-onboarding/index.tsx +878 -0
  230. package/src/components/waka-pull-to-refresh/index.tsx +451 -0
  231. package/src/components/waka-qrcode/index.tsx +232 -0
  232. package/src/components/waka-quest-card/index.tsx +1275 -0
  233. package/src/components/waka-quota-bar/index.tsx +693 -0
  234. package/src/components/waka-radar-score/index.tsx +512 -0
  235. package/src/components/waka-rank-badge/index.tsx +813 -0
  236. package/src/components/waka-rating-input/index.tsx +560 -0
  237. package/src/components/waka-reaction-picker/index.tsx +1062 -0
  238. package/src/components/waka-region-map/index.tsx +730 -0
  239. package/src/components/waka-resource-gauge/index.tsx +654 -0
  240. package/src/components/waka-resource-pool/index.tsx +1035 -0
  241. package/src/components/waka-rich-text-editor/index.tsx +594 -0
  242. package/src/components/waka-rollback-slider/index.tsx +891 -0
  243. package/src/components/waka-sankey-diagram/index.tsx +1032 -0
  244. package/src/components/waka-schedule-picker/index.tsx +1060 -0
  245. package/src/components/waka-scratch-card/index.tsx +914 -0
  246. package/src/components/waka-season-pass/index.tsx +886 -0
  247. package/src/components/waka-security-score/index.tsx +1126 -0
  248. package/src/components/waka-segmented-control/index.tsx +238 -0
  249. package/src/components/waka-server-rack/index.tsx +764 -0
  250. package/src/components/waka-session-manager/index.tsx +815 -0
  251. package/src/components/waka-signature-pad/index.tsx +744 -0
  252. package/src/components/waka-skeleton-wave/index.tsx +454 -0
  253. package/src/components/waka-skill-tree/index.tsx +1031 -0
  254. package/src/components/waka-sla-tracker/index.tsx +798 -0
  255. package/src/components/waka-slider-range/index.tsx +765 -0
  256. package/src/components/waka-spin-wheel/index.tsx +671 -0
  257. package/src/components/waka-spinner/index.tsx +284 -0
  258. package/src/components/waka-spotlight/index.tsx +410 -0
  259. package/src/components/waka-stat/index.tsx +428 -0
  260. package/src/components/waka-stats-hexagon/index.tsx +824 -0
  261. package/src/components/waka-status-matrix/index.tsx +565 -0
  262. package/src/components/waka-stepper/index.tsx +489 -0
  263. package/src/components/waka-streak-counter/index.tsx +334 -0
  264. package/src/components/waka-success-explosion/index.tsx +453 -0
  265. package/src/components/waka-swipe-card/index.tsx +574 -0
  266. package/src/components/waka-tabs-morph/index.tsx +509 -0
  267. package/src/components/waka-tag-input/index.tsx +877 -0
  268. package/src/components/waka-team-banner/index.tsx +1183 -0
  269. package/src/components/waka-terminal-output/index.tsx +836 -0
  270. package/src/components/waka-theme-creator/index.tsx +762 -0
  271. package/src/components/waka-theme-manager/index.tsx +654 -0
  272. package/src/components/waka-thread-view/index.tsx +874 -0
  273. package/src/components/waka-tilt-card/index.tsx +250 -0
  274. package/src/components/waka-time-picker/index.tsx +479 -0
  275. package/src/components/waka-timeline/index.tsx +385 -0
  276. package/src/components/waka-tooltip-tour/index.tsx +855 -0
  277. package/src/components/waka-tour-guide/index.tsx +920 -0
  278. package/src/components/waka-tournament-bracket/index.tsx +1276 -0
  279. package/src/components/waka-tree/index.tsx +557 -0
  280. package/src/components/waka-treemap-chart/index.tsx +1031 -0
  281. package/src/components/waka-two-factor-setup/index.tsx +995 -0
  282. package/src/components/waka-typewriter/index.tsx +566 -0
  283. package/src/components/waka-typing-indicator/index.tsx +649 -0
  284. package/src/components/waka-versus-card/index.tsx +1026 -0
  285. package/src/components/waka-video/index.tsx +557 -0
  286. package/src/components/waka-video-call/index.tsx +1087 -0
  287. package/src/components/waka-virtual-list/index.tsx +327 -0
  288. package/src/components/waka-voice-message/index.tsx +1019 -0
  289. package/src/components/waka-welcome-modal/index.tsx +790 -0
  290. package/src/components/waka-xp-bar/index.tsx +799 -0
@@ -0,0 +1,716 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../utils/cn"
5
+ import { Trophy, Star, Crown, Gem, Shield, Zap, Award, Target } from "lucide-react"
6
+
7
+ // ============================================================================
8
+ // Types
9
+ // ============================================================================
10
+
11
+ export type AchievementRarity = "common" | "rare" | "epic" | "legendary"
12
+
13
+ export interface AchievementData {
14
+ /** Unique identifier */
15
+ id: string
16
+ /** Achievement title */
17
+ title: string
18
+ /** Achievement description */
19
+ description: string
20
+ /** Icon name or custom React node */
21
+ icon?: React.ReactNode
22
+ /** Rarity level */
23
+ rarity?: AchievementRarity
24
+ /** XP reward */
25
+ xpReward?: number
26
+ /** Optional image URL for badge */
27
+ imageUrl?: string
28
+ /** Unlock date */
29
+ unlockedAt?: Date
30
+ }
31
+
32
+ export interface WakaAchievementUnlockProps {
33
+ /** Achievement data */
34
+ achievement: AchievementData
35
+ /** Whether the achievement is unlocked (triggers animation) */
36
+ isUnlocked?: boolean
37
+ /** Audio source URL for unlock sound */
38
+ audioSrc?: string
39
+ /** Enable sound effects */
40
+ enableSound?: boolean
41
+ /** Callback when unlock animation completes */
42
+ onAnimationComplete?: () => void
43
+ /** Callback when close is triggered */
44
+ onClose?: () => void
45
+ /** Auto-hide after delay (ms), 0 to disable */
46
+ autoHideDelay?: number
47
+ /** Size variant */
48
+ size?: "sm" | "default" | "lg"
49
+ /** Custom className */
50
+ className?: string
51
+ }
52
+
53
+ export interface WakaAchievementBadgeProps {
54
+ /** Achievement data */
55
+ achievement: AchievementData
56
+ /** Whether the achievement is locked */
57
+ locked?: boolean
58
+ /** Show progress towards unlock */
59
+ progress?: number
60
+ /** Size variant */
61
+ size?: "sm" | "default" | "lg"
62
+ /** Clickable badge */
63
+ onClick?: () => void
64
+ /** Custom className */
65
+ className?: string
66
+ }
67
+
68
+ // ============================================================================
69
+ // Rarity Configuration
70
+ // ============================================================================
71
+
72
+ const rarityConfig = {
73
+ common: {
74
+ gradient: "from-slate-400 to-slate-600",
75
+ glow: "shadow-slate-400/50",
76
+ glowColor: "#94a3b8",
77
+ borderColor: "border-slate-400",
78
+ bgColor: "bg-slate-100",
79
+ textColor: "text-slate-600",
80
+ particleColors: ["#94a3b8", "#cbd5e1", "#64748b"],
81
+ label: "Common",
82
+ },
83
+ rare: {
84
+ gradient: "from-blue-400 to-blue-600",
85
+ glow: "shadow-blue-400/50",
86
+ glowColor: "#60a5fa",
87
+ borderColor: "border-blue-400",
88
+ bgColor: "bg-blue-100",
89
+ textColor: "text-blue-600",
90
+ particleColors: ["#60a5fa", "#93c5fd", "#3b82f6"],
91
+ label: "Rare",
92
+ },
93
+ epic: {
94
+ gradient: "from-purple-400 to-purple-600",
95
+ glow: "shadow-purple-400/50",
96
+ glowColor: "#a855f7",
97
+ borderColor: "border-purple-400",
98
+ bgColor: "bg-purple-100",
99
+ textColor: "text-purple-600",
100
+ particleColors: ["#a855f7", "#c084fc", "#9333ea"],
101
+ label: "Epic",
102
+ },
103
+ legendary: {
104
+ gradient: "from-amber-400 via-orange-500 to-red-500",
105
+ glow: "shadow-amber-400/50",
106
+ glowColor: "#fbbf24",
107
+ borderColor: "border-amber-400",
108
+ bgColor: "bg-amber-100",
109
+ textColor: "text-amber-600",
110
+ particleColors: ["#fbbf24", "#f59e0b", "#ef4444", "#fcd34d"],
111
+ label: "Legendary",
112
+ },
113
+ }
114
+
115
+ // ============================================================================
116
+ // Particle Effect Component
117
+ // ============================================================================
118
+
119
+ interface ParticleEffectProps {
120
+ active: boolean
121
+ colors: string[]
122
+ count?: number
123
+ }
124
+
125
+ function ParticleEffect({ active, colors, count = 40 }: ParticleEffectProps) {
126
+ if (!active) return null
127
+
128
+ return (
129
+ <div className="pointer-events-none absolute inset-0 overflow-hidden">
130
+ {[...Array(count)].map((_, i) => {
131
+ const angle = (i / count) * 360
132
+ const distance = 80 + Math.random() * 60
133
+ const size = 4 + Math.random() * 6
134
+ const duration = 0.8 + Math.random() * 0.4
135
+
136
+ return (
137
+ <div
138
+ key={i}
139
+ className="absolute left-1/2 top-1/2 rounded-full animate-particle"
140
+ style={{
141
+ width: size,
142
+ height: size,
143
+ backgroundColor: colors[Math.floor(Math.random() * colors.length)],
144
+ "--angle": `${angle}deg`,
145
+ "--distance": `${distance}px`,
146
+ animationDuration: `${duration}s`,
147
+ animationDelay: `${Math.random() * 0.2}s`,
148
+ } as React.CSSProperties}
149
+ />
150
+ )
151
+ })}
152
+ <style>{`
153
+ @keyframes particle {
154
+ 0% {
155
+ transform: translate(-50%, -50%) rotate(var(--angle)) translateX(0) scale(1);
156
+ opacity: 1;
157
+ }
158
+ 100% {
159
+ transform: translate(-50%, -50%) rotate(var(--angle)) translateX(var(--distance)) scale(0);
160
+ opacity: 0;
161
+ }
162
+ }
163
+ .animate-particle {
164
+ animation: particle 1s ease-out forwards;
165
+ }
166
+ `}</style>
167
+ </div>
168
+ )
169
+ }
170
+
171
+ // ============================================================================
172
+ // Glow Effect Component
173
+ // ============================================================================
174
+
175
+ interface GlowEffectProps {
176
+ active: boolean
177
+ color: string
178
+ }
179
+
180
+ function GlowEffect({ active, color }: GlowEffectProps) {
181
+ if (!active) return null
182
+
183
+ return (
184
+ <>
185
+ <div
186
+ className="absolute inset-0 rounded-full animate-glow-pulse"
187
+ style={{
188
+ background: `radial-gradient(circle, ${color}40 0%, transparent 70%)`,
189
+ }}
190
+ />
191
+ <div
192
+ className="absolute inset-0 rounded-full animate-glow-ring"
193
+ style={{
194
+ boxShadow: `0 0 60px 20px ${color}60`,
195
+ }}
196
+ />
197
+ <style>{`
198
+ @keyframes glow-pulse {
199
+ 0%, 100% { transform: scale(1); opacity: 0.8; }
200
+ 50% { transform: scale(1.2); opacity: 1; }
201
+ }
202
+ @keyframes glow-ring {
203
+ 0% { transform: scale(0.8); opacity: 0; }
204
+ 50% { transform: scale(1.1); opacity: 1; }
205
+ 100% { transform: scale(1.3); opacity: 0; }
206
+ }
207
+ .animate-glow-pulse {
208
+ animation: glow-pulse 2s ease-in-out infinite;
209
+ }
210
+ .animate-glow-ring {
211
+ animation: glow-ring 1.5s ease-out infinite;
212
+ }
213
+ `}</style>
214
+ </>
215
+ )
216
+ }
217
+
218
+ // ============================================================================
219
+ // Shine Effect Component
220
+ // ============================================================================
221
+
222
+ function ShineEffect({ active }: { active: boolean }) {
223
+ if (!active) return null
224
+
225
+ return (
226
+ <>
227
+ <div className="absolute inset-0 overflow-hidden rounded-full">
228
+ <div className="absolute inset-0 animate-shine" />
229
+ </div>
230
+ <style>{`
231
+ @keyframes shine {
232
+ 0% {
233
+ transform: translateX(-100%) rotate(45deg);
234
+ }
235
+ 50%, 100% {
236
+ transform: translateX(100%) rotate(45deg);
237
+ }
238
+ }
239
+ .animate-shine {
240
+ background: linear-gradient(
241
+ 90deg,
242
+ transparent 0%,
243
+ rgba(255, 255, 255, 0.4) 50%,
244
+ transparent 100%
245
+ );
246
+ animation: shine 2s ease-in-out infinite;
247
+ }
248
+ `}</style>
249
+ </>
250
+ )
251
+ }
252
+
253
+ // ============================================================================
254
+ // Default Icon Component
255
+ // ============================================================================
256
+
257
+ function DefaultIcon({ rarity }: { rarity: AchievementRarity }) {
258
+ const icons = {
259
+ common: Shield,
260
+ rare: Award,
261
+ epic: Gem,
262
+ legendary: Crown,
263
+ }
264
+ const Icon = icons[rarity]
265
+ return <Icon className="h-full w-full" />
266
+ }
267
+
268
+ // ============================================================================
269
+ // Achievement Unlock Modal Component
270
+ // ============================================================================
271
+
272
+ export function WakaAchievementUnlock({
273
+ achievement,
274
+ isUnlocked = false,
275
+ audioSrc,
276
+ enableSound = true,
277
+ onAnimationComplete,
278
+ onClose,
279
+ autoHideDelay = 5000,
280
+ size = "default",
281
+ className,
282
+ }: WakaAchievementUnlockProps) {
283
+ const [showModal, setShowModal] = React.useState(false)
284
+ const [animationPhase, setAnimationPhase] = React.useState<"idle" | "reveal" | "particles" | "complete">("idle")
285
+ const audioRef = React.useRef<HTMLAudioElement | null>(null)
286
+
287
+ const rarity = achievement.rarity || "common"
288
+ const config = rarityConfig[rarity]
289
+
290
+ // Size configurations
291
+ const sizeConfig = {
292
+ sm: {
293
+ container: "w-64 p-4",
294
+ badge: "h-16 w-16",
295
+ iconSize: "h-8 w-8",
296
+ title: "text-lg",
297
+ description: "text-xs",
298
+ },
299
+ default: {
300
+ container: "w-80 p-6",
301
+ badge: "h-24 w-24",
302
+ iconSize: "h-12 w-12",
303
+ title: "text-xl",
304
+ description: "text-sm",
305
+ },
306
+ lg: {
307
+ container: "w-96 p-8",
308
+ badge: "h-32 w-32",
309
+ iconSize: "h-16 w-16",
310
+ title: "text-2xl",
311
+ description: "text-base",
312
+ },
313
+ }
314
+
315
+ const sizes = sizeConfig[size]
316
+
317
+ // Handle unlock animation
318
+ React.useEffect(() => {
319
+ if (isUnlocked && animationPhase === "idle") {
320
+ setShowModal(true)
321
+ setAnimationPhase("reveal")
322
+
323
+ // Play sound
324
+ if (enableSound && audioSrc) {
325
+ audioRef.current = new Audio(audioSrc)
326
+ audioRef.current.play().catch(() => {})
327
+ }
328
+
329
+ // Animation phases
330
+ setTimeout(() => setAnimationPhase("particles"), 300)
331
+ setTimeout(() => {
332
+ setAnimationPhase("complete")
333
+ onAnimationComplete?.()
334
+ }, 1500)
335
+
336
+ // Auto hide
337
+ if (autoHideDelay > 0) {
338
+ setTimeout(() => {
339
+ setShowModal(false)
340
+ onClose?.()
341
+ }, autoHideDelay)
342
+ }
343
+ }
344
+ }, [isUnlocked, animationPhase, enableSound, audioSrc, autoHideDelay, onAnimationComplete, onClose])
345
+
346
+ // Reset when closed
347
+ React.useEffect(() => {
348
+ if (!isUnlocked) {
349
+ setAnimationPhase("idle")
350
+ setShowModal(false)
351
+ }
352
+ }, [isUnlocked])
353
+
354
+ if (!showModal) return null
355
+
356
+ return (
357
+ <div className="fixed inset-0 z-50 flex items-center justify-center">
358
+ {/* Backdrop */}
359
+ <div
360
+ className="absolute inset-0 bg-black/60 backdrop-blur-sm animate-fadeIn"
361
+ onClick={() => {
362
+ setShowModal(false)
363
+ onClose?.()
364
+ }}
365
+ />
366
+
367
+ {/* Modal */}
368
+ <div
369
+ className={cn(
370
+ "relative flex flex-col items-center rounded-2xl border bg-card text-card-foreground shadow-2xl",
371
+ "animate-bounceIn",
372
+ sizes.container,
373
+ className
374
+ )}
375
+ >
376
+ {/* Achievement Unlocked Text */}
377
+ <div className="mb-4 text-center">
378
+ <div className="text-xs font-semibold uppercase tracking-widest text-muted-foreground animate-slideDown">
379
+ Achievement Unlocked!
380
+ </div>
381
+ </div>
382
+
383
+ {/* Badge with effects */}
384
+ <div className="relative mb-4">
385
+ <GlowEffect active={animationPhase !== "idle"} color={config.glowColor} />
386
+ <ParticleEffect
387
+ active={animationPhase === "particles" || animationPhase === "complete"}
388
+ colors={config.particleColors}
389
+ />
390
+
391
+ {/* Badge */}
392
+ <div
393
+ className={cn(
394
+ "relative flex items-center justify-center rounded-full border-4 bg-gradient-to-br",
395
+ "transition-all duration-500",
396
+ config.gradient,
397
+ config.borderColor,
398
+ sizes.badge,
399
+ animationPhase === "idle" && "scale-0 opacity-0",
400
+ animationPhase === "reveal" && "scale-110 opacity-100",
401
+ animationPhase !== "idle" && "scale-100 opacity-100"
402
+ )}
403
+ >
404
+ <ShineEffect active={animationPhase === "complete"} />
405
+ <div className={cn("text-white", sizes.iconSize)}>
406
+ {achievement.icon || <DefaultIcon rarity={rarity} />}
407
+ </div>
408
+ </div>
409
+ </div>
410
+
411
+ {/* Rarity Badge */}
412
+ <div
413
+ className={cn(
414
+ "mb-2 rounded-full px-3 py-1 text-xs font-bold uppercase tracking-wider",
415
+ config.bgColor,
416
+ config.textColor
417
+ )}
418
+ >
419
+ {config.label}
420
+ </div>
421
+
422
+ {/* Title */}
423
+ <h3
424
+ className={cn(
425
+ "mb-1 font-bold text-center",
426
+ sizes.title,
427
+ animationPhase !== "idle" && "animate-slideUp"
428
+ )}
429
+ >
430
+ {achievement.title}
431
+ </h3>
432
+
433
+ {/* Description */}
434
+ <p
435
+ className={cn(
436
+ "text-center text-muted-foreground",
437
+ sizes.description,
438
+ animationPhase !== "idle" && "animate-slideUp"
439
+ )}
440
+ style={{ animationDelay: "0.1s" }}
441
+ >
442
+ {achievement.description}
443
+ </p>
444
+
445
+ {/* XP Reward */}
446
+ {achievement.xpReward && (
447
+ <div
448
+ className={cn(
449
+ "mt-4 flex items-center gap-2 rounded-full bg-gradient-to-r from-yellow-400 to-orange-500 px-4 py-2 text-white shadow-lg",
450
+ animationPhase !== "idle" && "animate-slideUp"
451
+ )}
452
+ style={{ animationDelay: "0.2s" }}
453
+ >
454
+ <Zap className="h-4 w-4" />
455
+ <span className="font-bold">+{achievement.xpReward} XP</span>
456
+ </div>
457
+ )}
458
+
459
+ <style>{`
460
+ @keyframes fadeIn {
461
+ from { opacity: 0; }
462
+ to { opacity: 1; }
463
+ }
464
+ @keyframes bounceIn {
465
+ 0% { transform: scale(0.5); opacity: 0; }
466
+ 50% { transform: scale(1.05); }
467
+ 100% { transform: scale(1); opacity: 1; }
468
+ }
469
+ @keyframes slideDown {
470
+ from { transform: translateY(-20px); opacity: 0; }
471
+ to { transform: translateY(0); opacity: 1; }
472
+ }
473
+ @keyframes slideUp {
474
+ from { transform: translateY(20px); opacity: 0; }
475
+ to { transform: translateY(0); opacity: 1; }
476
+ }
477
+ .animate-fadeIn { animation: fadeIn 0.3s ease-out; }
478
+ .animate-bounceIn { animation: bounceIn 0.5s ease-out; }
479
+ .animate-slideDown { animation: slideDown 0.5s ease-out; }
480
+ .animate-slideUp { animation: slideUp 0.5s ease-out backwards; }
481
+ `}</style>
482
+ </div>
483
+ </div>
484
+ )
485
+ }
486
+
487
+ // ============================================================================
488
+ // Achievement Badge Component (for display in lists/grids)
489
+ // ============================================================================
490
+
491
+ export function WakaAchievementBadge({
492
+ achievement,
493
+ locked = false,
494
+ progress,
495
+ size = "default",
496
+ onClick,
497
+ className,
498
+ }: WakaAchievementBadgeProps) {
499
+ const [isHovered, setIsHovered] = React.useState(false)
500
+ const rarity = achievement.rarity || "common"
501
+ const config = rarityConfig[rarity]
502
+
503
+ const sizeConfig = {
504
+ sm: {
505
+ badge: "h-12 w-12",
506
+ iconSize: "h-6 w-6",
507
+ },
508
+ default: {
509
+ badge: "h-16 w-16",
510
+ iconSize: "h-8 w-8",
511
+ },
512
+ lg: {
513
+ badge: "h-24 w-24",
514
+ iconSize: "h-12 w-12",
515
+ },
516
+ }
517
+
518
+ const sizes = sizeConfig[size]
519
+
520
+ return (
521
+ <div
522
+ className={cn(
523
+ "group relative cursor-pointer",
524
+ onClick && "cursor-pointer",
525
+ className
526
+ )}
527
+ onClick={onClick}
528
+ onMouseEnter={() => setIsHovered(true)}
529
+ onMouseLeave={() => setIsHovered(false)}
530
+ >
531
+ {/* Badge */}
532
+ <div
533
+ className={cn(
534
+ "relative flex items-center justify-center rounded-full border-2 transition-all duration-300",
535
+ locked ? "bg-muted border-muted-foreground/30" : cn("bg-gradient-to-br", config.gradient, config.borderColor),
536
+ sizes.badge,
537
+ !locked && isHovered && "scale-110 shadow-lg",
538
+ !locked && config.glow
539
+ )}
540
+ style={!locked && isHovered ? { boxShadow: `0 0 20px 4px ${config.glowColor}40` } : {}}
541
+ >
542
+ {/* Lock overlay */}
543
+ {locked && (
544
+ <div className="absolute inset-0 flex items-center justify-center rounded-full bg-black/40">
545
+ <Target className="h-6 w-6 text-white/60" />
546
+ </div>
547
+ )}
548
+
549
+ {/* Icon */}
550
+ {!locked && (
551
+ <div className={cn("text-white", sizes.iconSize)}>
552
+ {achievement.icon || <DefaultIcon rarity={rarity} />}
553
+ </div>
554
+ )}
555
+
556
+ {/* Progress ring */}
557
+ {locked && progress !== undefined && progress > 0 && (
558
+ <svg
559
+ className="absolute inset-0"
560
+ viewBox="0 0 100 100"
561
+ >
562
+ <circle
563
+ cx="50"
564
+ cy="50"
565
+ r="46"
566
+ fill="none"
567
+ stroke="currentColor"
568
+ strokeWidth="4"
569
+ className="text-muted-foreground/20"
570
+ />
571
+ <circle
572
+ cx="50"
573
+ cy="50"
574
+ r="46"
575
+ fill="none"
576
+ stroke={config.glowColor}
577
+ strokeWidth="4"
578
+ strokeLinecap="round"
579
+ strokeDasharray={`${progress * 2.89} 289`}
580
+ transform="rotate(-90 50 50)"
581
+ className="transition-all duration-500"
582
+ />
583
+ </svg>
584
+ )}
585
+
586
+ {/* Shine effect on hover */}
587
+ {!locked && <ShineEffect active={isHovered} />}
588
+ </div>
589
+
590
+ {/* Tooltip on hover */}
591
+ {isHovered && (
592
+ <div className="absolute left-1/2 top-full z-10 mt-2 -translate-x-1/2 whitespace-nowrap rounded-lg bg-popover px-3 py-2 text-sm shadow-lg border animate-fadeIn">
593
+ <div className="font-semibold">{achievement.title}</div>
594
+ {locked && progress !== undefined && (
595
+ <div className="text-xs text-muted-foreground">
596
+ {Math.round(progress)}% complete
597
+ </div>
598
+ )}
599
+ <style>{`
600
+ @keyframes fadeIn {
601
+ from { opacity: 0; transform: translate(-50%, 10px); }
602
+ to { opacity: 1; transform: translate(-50%, 0); }
603
+ }
604
+ .animate-fadeIn { animation: fadeIn 0.2s ease-out; }
605
+ `}</style>
606
+ </div>
607
+ )}
608
+ </div>
609
+ )
610
+ }
611
+
612
+ // ============================================================================
613
+ // Achievement Grid Component
614
+ // ============================================================================
615
+
616
+ export interface WakaAchievementGridProps {
617
+ achievements: (AchievementData & { locked?: boolean; progress?: number })[]
618
+ columns?: 3 | 4 | 5 | 6
619
+ size?: "sm" | "default" | "lg"
620
+ onAchievementClick?: (achievement: AchievementData) => void
621
+ className?: string
622
+ }
623
+
624
+ export function WakaAchievementGrid({
625
+ achievements,
626
+ columns = 4,
627
+ size = "default",
628
+ onAchievementClick,
629
+ className,
630
+ }: WakaAchievementGridProps) {
631
+ const columnClasses = {
632
+ 3: "grid-cols-3",
633
+ 4: "grid-cols-4",
634
+ 5: "grid-cols-5",
635
+ 6: "grid-cols-6",
636
+ }
637
+
638
+ return (
639
+ <div className={cn("grid gap-4", columnClasses[columns], className)}>
640
+ {achievements.map((achievement) => (
641
+ <div key={achievement.id} className="flex flex-col items-center gap-2">
642
+ <WakaAchievementBadge
643
+ achievement={achievement}
644
+ locked={achievement.locked}
645
+ progress={achievement.progress}
646
+ size={size}
647
+ onClick={() => onAchievementClick?.(achievement)}
648
+ />
649
+ <span className="text-xs text-center text-muted-foreground truncate max-w-full">
650
+ {achievement.title}
651
+ </span>
652
+ </div>
653
+ ))}
654
+ </div>
655
+ )
656
+ }
657
+
658
+ // ============================================================================
659
+ // Hook for managing achievements
660
+ // ============================================================================
661
+
662
+ export interface UseAchievementsOptions {
663
+ achievements: AchievementData[]
664
+ unlockedIds?: string[]
665
+ onUnlock?: (achievement: AchievementData) => void
666
+ }
667
+
668
+ export function useAchievements({ achievements, unlockedIds = [], onUnlock }: UseAchievementsOptions) {
669
+ const [unlocked, setUnlocked] = React.useState<Set<string>>(new Set(unlockedIds))
670
+ const [currentUnlock, setCurrentUnlock] = React.useState<AchievementData | null>(null)
671
+ const [pendingUnlocks, setPendingUnlocks] = React.useState<AchievementData[]>([])
672
+
673
+ const unlock = React.useCallback((achievementId: string) => {
674
+ if (unlocked.has(achievementId)) return
675
+
676
+ const achievement = achievements.find((a) => a.id === achievementId)
677
+ if (!achievement) return
678
+
679
+ setUnlocked((prev) => new Set([...prev, achievementId]))
680
+ setPendingUnlocks((prev) => [...prev, { ...achievement, unlockedAt: new Date() }])
681
+ onUnlock?.(achievement)
682
+ }, [achievements, unlocked, onUnlock])
683
+
684
+ // Process pending unlocks one by one
685
+ React.useEffect(() => {
686
+ if (!currentUnlock && pendingUnlocks.length > 0) {
687
+ setCurrentUnlock(pendingUnlocks[0])
688
+ setPendingUnlocks((prev) => prev.slice(1))
689
+ }
690
+ }, [currentUnlock, pendingUnlocks])
691
+
692
+ const dismissCurrentUnlock = React.useCallback(() => {
693
+ setCurrentUnlock(null)
694
+ }, [])
695
+
696
+ const isUnlocked = React.useCallback((achievementId: string) => {
697
+ return unlocked.has(achievementId)
698
+ }, [unlocked])
699
+
700
+ const getProgress = React.useCallback((achievementId: string) => {
701
+ // This should be extended to track actual progress
702
+ return unlocked.has(achievementId) ? 100 : 0
703
+ }, [unlocked])
704
+
705
+ return {
706
+ unlocked,
707
+ currentUnlock,
708
+ unlock,
709
+ dismissCurrentUnlock,
710
+ isUnlocked,
711
+ getProgress,
712
+ unlockedCount: unlocked.size,
713
+ totalCount: achievements.length,
714
+ completionPercentage: (unlocked.size / achievements.length) * 100,
715
+ }
716
+ }