@wakastellar/ui 2.3.0 → 2.3.2

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 (63) hide show
  1. package/dist/components/index.d.ts +15 -0
  2. package/dist/components/waka-ad-banner/index.d.ts +36 -0
  3. package/dist/components/waka-ad-fallback/index.d.ts +33 -0
  4. package/dist/components/waka-ad-inline/index.d.ts +15 -0
  5. package/dist/components/waka-ad-interstitial/index.d.ts +26 -0
  6. package/dist/components/waka-ad-placeholder/index.d.ts +17 -0
  7. package/dist/components/waka-ad-provider/index.d.ts +103 -0
  8. package/dist/components/waka-ad-sidebar/index.d.ts +18 -0
  9. package/dist/components/waka-ad-sticky-footer/index.d.ts +17 -0
  10. package/dist/components/waka-content-recommendation/index.d.ts +23 -0
  11. package/dist/components/waka-outstream-video/index.d.ts +24 -0
  12. package/dist/components/waka-sponsored-badge/index.d.ts +20 -0
  13. package/dist/components/waka-sponsored-card/index.d.ts +25 -0
  14. package/dist/components/waka-sponsored-feed/index.d.ts +31 -0
  15. package/dist/components/waka-video-ad/index.d.ts +32 -0
  16. package/dist/components/waka-video-overlay/index.d.ts +26 -0
  17. package/dist/index.cjs.js +177 -171
  18. package/dist/index.d.ts +1 -0
  19. package/dist/index.es.js +14535 -12812
  20. package/dist/utils/security.d.ts +96 -0
  21. package/package.json +4 -4
  22. package/src/blocks/sidebar/index.tsx +6 -6
  23. package/src/components/DataTable/templates/index.tsx +3 -2
  24. package/src/components/index.ts +94 -0
  25. package/src/components/waka-3d-pie-chart/index.tsx +11 -11
  26. package/src/components/waka-achievement-unlock/index.tsx +16 -16
  27. package/src/components/waka-ad-banner/index.tsx +275 -0
  28. package/src/components/waka-ad-fallback/index.tsx +181 -0
  29. package/src/components/waka-ad-inline/index.tsx +103 -0
  30. package/src/components/waka-ad-interstitial/index.tsx +278 -0
  31. package/src/components/waka-ad-placeholder/index.tsx +84 -0
  32. package/src/components/waka-ad-provider/index.tsx +329 -0
  33. package/src/components/waka-ad-sidebar/index.tsx +113 -0
  34. package/src/components/waka-ad-sticky-footer/index.tsx +125 -0
  35. package/src/components/waka-badge-showcase/index.tsx +12 -11
  36. package/src/components/waka-command-bar/index.tsx +2 -1
  37. package/src/components/waka-content-recommendation/index.tsx +294 -0
  38. package/src/components/waka-cost-breakdown/index.tsx +10 -10
  39. package/src/components/waka-funnel-chart/index.tsx +8 -8
  40. package/src/components/waka-health-pulse/index.tsx +6 -6
  41. package/src/components/waka-leaderboard/index.tsx +9 -9
  42. package/src/components/waka-loot-box/index.tsx +20 -20
  43. package/src/components/waka-outstream-video/index.tsx +240 -0
  44. package/src/components/waka-player-card/index.tsx +5 -5
  45. package/src/components/waka-quota-bar/index.tsx +4 -4
  46. package/src/components/waka-radar-score/index.tsx +10 -10
  47. package/src/components/waka-scratch-card/index.tsx +5 -4
  48. package/src/components/waka-server-rack/index.tsx +28 -27
  49. package/src/components/waka-sponsored-badge/index.tsx +97 -0
  50. package/src/components/waka-sponsored-card/index.tsx +275 -0
  51. package/src/components/waka-sponsored-feed/index.tsx +127 -0
  52. package/src/components/waka-spotlight/index.tsx +2 -1
  53. package/src/components/waka-success-explosion/index.tsx +4 -4
  54. package/src/components/waka-video-ad/index.tsx +406 -0
  55. package/src/components/waka-video-overlay/index.tsx +257 -0
  56. package/src/components/waka-xp-bar/index.tsx +13 -13
  57. package/src/styles/base.css +16 -0
  58. package/src/styles/tailwind.preset.js +12 -0
  59. package/src/styles/themes/forest.css +16 -0
  60. package/src/styles/themes/monochrome.css +16 -0
  61. package/src/styles/themes/perpetuity.css +16 -0
  62. package/src/styles/themes/sunset.css +16 -0
  63. package/src/styles/themes/twilight.css +16 -0
@@ -0,0 +1,240 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { useRef, useState, useEffect } from "react"
5
+ import { cn } from "../../utils/cn"
6
+ import { useAdContext, useAdVisibility, type CustomAd } from "../waka-ad-provider"
7
+ import { WakaSponsoredBadge } from "../waka-sponsored-badge"
8
+ import { X, Volume2, VolumeX } from "lucide-react"
9
+
10
+ export interface WakaOutstreamVideoProps {
11
+ /** Unique slot ID */
12
+ slotId: string
13
+ /** Collapse when video ends or is closed */
14
+ collapseOnEnd?: boolean
15
+ /** Collapse animation duration (ms) */
16
+ collapseDuration?: number
17
+ /** Start muted */
18
+ muted?: boolean
19
+ /** Show close button */
20
+ showClose?: boolean
21
+ /** Close after seconds (0 = manual only) */
22
+ closeAfter?: number
23
+ /** Minimum visibility to start playing (0-1) */
24
+ visibilityThreshold?: number
25
+ /** Custom class name */
26
+ className?: string
27
+ /** Callback when video completes */
28
+ onComplete?: () => void
29
+ /** Callback when closed */
30
+ onClose?: () => void
31
+ }
32
+
33
+ export function WakaOutstreamVideo({
34
+ slotId,
35
+ collapseOnEnd = true,
36
+ collapseDuration = 300,
37
+ muted = true,
38
+ showClose = true,
39
+ closeAfter = 0,
40
+ visibilityThreshold = 0.5,
41
+ className,
42
+ onComplete,
43
+ onClose,
44
+ }: WakaOutstreamVideoProps) {
45
+ const containerRef = useRef<HTMLDivElement>(null)
46
+ const videoRef = useRef<HTMLVideoElement>(null)
47
+ const { config, isReady, hasConsent, getCustomAd, trackEvent } = useAdContext()
48
+ const { isVisible } = useAdVisibility(containerRef, visibilityThreshold)
49
+
50
+ const [status, setStatus] = useState<"loading" | "loaded" | "error" | "collapsed">("loading")
51
+ const [ad, setAd] = useState<CustomAd | null>(null)
52
+ const [isMuted, setIsMuted] = useState(muted)
53
+ const [isCollapsing, setIsCollapsing] = useState(false)
54
+ const [containerHeight, setContainerHeight] = useState<number | "auto">("auto")
55
+ const [hasStarted, setHasStarted] = useState(false)
56
+
57
+ // Load ad
58
+ useEffect(() => {
59
+ if (!isReady || hasConsent === false) return
60
+
61
+ const loadAd = async () => {
62
+ try {
63
+ const customAd = await getCustomAd(slotId)
64
+ if (customAd?.videoUrl) {
65
+ setAd(customAd)
66
+ setStatus("loaded")
67
+ trackEvent({ type: "loaded", slotId, timestamp: new Date() })
68
+ } else {
69
+ setStatus("collapsed")
70
+ }
71
+ } catch {
72
+ setStatus("collapsed")
73
+ }
74
+ }
75
+
76
+ loadAd()
77
+ }, [isReady, hasConsent, slotId, getCustomAd, trackEvent])
78
+
79
+ // Play/pause based on visibility
80
+ useEffect(() => {
81
+ if (!videoRef.current || status !== "loaded") return
82
+
83
+ if (isVisible) {
84
+ videoRef.current.play().then(() => {
85
+ if (!hasStarted) {
86
+ setHasStarted(true)
87
+ trackEvent({ type: "impression", slotId, timestamp: new Date() })
88
+ if (ad?.impressionUrl) {
89
+ fetch(ad.impressionUrl, { mode: "no-cors" }).catch(() => {})
90
+ }
91
+ }
92
+ }).catch(() => {})
93
+ } else {
94
+ videoRef.current.pause()
95
+ }
96
+ }, [isVisible, status, hasStarted, slotId, ad, trackEvent])
97
+
98
+ // Auto-close timer
99
+ useEffect(() => {
100
+ if (closeAfter === 0 || !hasStarted) return
101
+
102
+ const timer = setTimeout(() => {
103
+ handleCollapse()
104
+ }, closeAfter * 1000)
105
+
106
+ return () => clearTimeout(timer)
107
+ }, [closeAfter, hasStarted])
108
+
109
+ // Set initial height after load
110
+ useEffect(() => {
111
+ if (status === "loaded" && containerRef.current) {
112
+ setContainerHeight(containerRef.current.offsetHeight)
113
+ }
114
+ }, [status])
115
+
116
+ const handleCollapse = () => {
117
+ if (collapseOnEnd) {
118
+ setIsCollapsing(true)
119
+ setContainerHeight(0)
120
+ setTimeout(() => {
121
+ setStatus("collapsed")
122
+ onClose?.()
123
+ }, collapseDuration)
124
+ } else {
125
+ setStatus("collapsed")
126
+ onClose?.()
127
+ }
128
+ }
129
+
130
+ const handleEnded = () => {
131
+ trackEvent({ type: "viewable", slotId, timestamp: new Date(), data: { completed: true } })
132
+ onComplete?.()
133
+ handleCollapse()
134
+ }
135
+
136
+ const handleToggleMute = () => {
137
+ if (videoRef.current) {
138
+ videoRef.current.muted = !isMuted
139
+ setIsMuted(!isMuted)
140
+ }
141
+ }
142
+
143
+ const handleClick = () => {
144
+ if (!ad) return
145
+
146
+ trackEvent({ type: "click", slotId, timestamp: new Date() })
147
+ if (ad.clickUrl) {
148
+ fetch(ad.clickUrl, { mode: "no-cors" }).catch(() => {})
149
+ }
150
+ if (ad.targetUrl) {
151
+ window.open(ad.targetUrl, "_blank", "noopener,noreferrer")
152
+ }
153
+ }
154
+
155
+ if (status === "collapsed" || status === "error") {
156
+ return null
157
+ }
158
+
159
+ if (status === "loading") {
160
+ return (
161
+ <div
162
+ ref={containerRef}
163
+ className={cn("w-full aspect-video bg-muted animate-pulse rounded-lg", className)}
164
+ />
165
+ )
166
+ }
167
+
168
+ return (
169
+ <div
170
+ ref={containerRef}
171
+ className={cn(
172
+ "relative w-full overflow-hidden rounded-lg bg-black",
173
+ isCollapsing && "transition-all",
174
+ className
175
+ )}
176
+ style={{
177
+ height: containerHeight,
178
+ transitionDuration: `${collapseDuration}ms`,
179
+ }}
180
+ >
181
+ <div className="relative aspect-video">
182
+ {/* Video */}
183
+ <video
184
+ ref={videoRef}
185
+ src={ad?.videoUrl}
186
+ muted={isMuted}
187
+ playsInline
188
+ loop={false}
189
+ onClick={handleClick}
190
+ onEnded={handleEnded}
191
+ className="w-full h-full object-contain cursor-pointer"
192
+ />
193
+
194
+ {/* Overlay controls */}
195
+ <div className="absolute inset-0 pointer-events-none">
196
+ {/* Top bar */}
197
+ <div className="absolute top-0 left-0 right-0 p-3 flex items-center justify-between bg-gradient-to-b from-black/50 to-transparent pointer-events-auto">
198
+ <WakaSponsoredBadge variant="dark" size="sm" sponsor={ad?.sponsor} />
199
+
200
+ <div className="flex items-center gap-2">
201
+ <button
202
+ onClick={handleToggleMute}
203
+ className="p-1.5 rounded-full bg-black/50 hover:bg-black/70 transition-colors"
204
+ >
205
+ {isMuted ? (
206
+ <VolumeX className="h-4 w-4 text-white" />
207
+ ) : (
208
+ <Volume2 className="h-4 w-4 text-white" />
209
+ )}
210
+ </button>
211
+
212
+ {showClose && (
213
+ <button
214
+ onClick={handleCollapse}
215
+ className="p-1.5 rounded-full bg-black/50 hover:bg-black/70 transition-colors"
216
+ >
217
+ <X className="h-4 w-4 text-white" />
218
+ </button>
219
+ )}
220
+ </div>
221
+ </div>
222
+
223
+ {/* CTA button */}
224
+ {ad?.cta && (
225
+ <div className="absolute bottom-3 left-3 right-3 pointer-events-auto">
226
+ <button
227
+ onClick={handleClick}
228
+ className="w-full px-4 py-2 bg-primary text-primary-foreground rounded-lg font-medium hover:bg-primary/90 transition-colors text-center"
229
+ >
230
+ {ad.cta}
231
+ </button>
232
+ </div>
233
+ )}
234
+ </div>
235
+ </div>
236
+ </div>
237
+ )
238
+ }
239
+
240
+ export default WakaOutstreamVideo
@@ -59,7 +59,7 @@ const rarityConfig: Record<PlayerRarity, {
59
59
  common: {
60
60
  borderColor: "border-zinc-400 dark:border-zinc-500",
61
61
  bgGradient: "from-zinc-100 to-zinc-200 dark:from-zinc-800 dark:to-zinc-900",
62
- glowColor: "rgba(161, 161, 170, 0.3)",
62
+ glowColor: "hsl(var(--muted-foreground) / 0.3)",
63
63
  textColor: "text-zinc-600 dark:text-zinc-400",
64
64
  label: "Common",
65
65
  frameGradient: "from-zinc-300 via-zinc-400 to-zinc-300 dark:from-zinc-600 dark:via-zinc-500 dark:to-zinc-600",
@@ -67,7 +67,7 @@ const rarityConfig: Record<PlayerRarity, {
67
67
  rare: {
68
68
  borderColor: "border-blue-400 dark:border-blue-500",
69
69
  bgGradient: "from-blue-50 to-blue-100 dark:from-blue-950 dark:to-blue-900",
70
- glowColor: "rgba(59, 130, 246, 0.4)",
70
+ glowColor: "hsl(var(--info) / 0.4)",
71
71
  textColor: "text-blue-600 dark:text-blue-400",
72
72
  label: "Rare",
73
73
  frameGradient: "from-blue-300 via-blue-500 to-blue-300 dark:from-blue-600 dark:via-blue-400 dark:to-blue-600",
@@ -75,7 +75,7 @@ const rarityConfig: Record<PlayerRarity, {
75
75
  epic: {
76
76
  borderColor: "border-purple-400 dark:border-purple-500",
77
77
  bgGradient: "from-purple-50 to-purple-100 dark:from-purple-950 dark:to-purple-900",
78
- glowColor: "rgba(168, 85, 247, 0.5)",
78
+ glowColor: "hsl(var(--chart-2) / 0.5)",
79
79
  textColor: "text-purple-600 dark:text-purple-400",
80
80
  label: "Epic",
81
81
  frameGradient: "from-purple-300 via-purple-500 to-purple-300 dark:from-purple-600 dark:via-purple-400 dark:to-purple-600",
@@ -83,7 +83,7 @@ const rarityConfig: Record<PlayerRarity, {
83
83
  legendary: {
84
84
  borderColor: "border-amber-400 dark:border-amber-500",
85
85
  bgGradient: "from-amber-50 to-orange-100 dark:from-amber-950 dark:to-orange-900",
86
- glowColor: "rgba(245, 158, 11, 0.5)",
86
+ glowColor: "hsl(var(--warning) / 0.5)",
87
87
  textColor: "text-amber-600 dark:text-amber-400",
88
88
  label: "Legendary",
89
89
  frameGradient: "from-yellow-300 via-amber-500 to-orange-400 dark:from-yellow-500 dark:via-amber-400 dark:to-orange-500",
@@ -91,7 +91,7 @@ const rarityConfig: Record<PlayerRarity, {
91
91
  mythic: {
92
92
  borderColor: "border-rose-400 dark:border-rose-500",
93
93
  bgGradient: "from-rose-50 via-pink-50 to-violet-50 dark:from-rose-950 dark:via-pink-950 dark:to-violet-950",
94
- glowColor: "rgba(244, 63, 94, 0.6)",
94
+ glowColor: "hsl(var(--destructive) / 0.6)",
95
95
  textColor: "text-rose-600 dark:text-rose-400",
96
96
  label: "Mythic",
97
97
  frameGradient: "from-rose-400 via-pink-500 to-violet-500 dark:from-rose-500 dark:via-pink-400 dark:to-violet-400",
@@ -53,10 +53,10 @@ export interface WakaQuotaBarProps {
53
53
  // ============================================================================
54
54
 
55
55
  const defaultColors: WakaQuotaBarColors = {
56
- normal: "bg-green-500",
57
- warning: "bg-yellow-500",
58
- danger: "bg-red-500",
59
- overflow: "bg-red-600",
56
+ normal: "bg-success",
57
+ warning: "bg-warning",
58
+ danger: "bg-destructive",
59
+ overflow: "bg-destructive",
60
60
  }
61
61
 
62
62
  // ============================================================================
@@ -171,14 +171,14 @@ export function WakaRadarScore({
171
171
  .join(" ") + " Z"
172
172
  }
173
173
 
174
- // Default colors for datasets
174
+ // Default colors for datasets - using CSS variables for theme support
175
175
  const defaultColors = [
176
- "#3b82f6", // blue
177
- "#ef4444", // red
178
- "#22c55e", // green
179
- "#f59e0b", // amber
180
- "#8b5cf6", // violet
181
- "#ec4899", // pink
176
+ "hsl(var(--chart-1))",
177
+ "hsl(var(--chart-2))",
178
+ "hsl(var(--chart-3))",
179
+ "hsl(var(--chart-4))",
180
+ "hsl(var(--chart-5))",
181
+ "hsl(var(--primary))",
182
182
  ]
183
183
 
184
184
  const getDatasetColor = (index: number, dataset: RadarDataSet) => {
@@ -443,7 +443,7 @@ export interface WakaRadarScoreSimpleProps
443
443
 
444
444
  export function WakaRadarScoreSimple({
445
445
  data,
446
- color = "#3b82f6",
446
+ color = "hsl(var(--chart-1))",
447
447
  fillOpacity = 0.2,
448
448
  ...props
449
449
  }: WakaRadarScoreSimpleProps) {
@@ -494,14 +494,14 @@ export function WakaRadarScoreCompare({
494
494
  id: "a",
495
495
  label: dataA.label,
496
496
  data: dataA.data,
497
- color: dataA.color || "#3b82f6",
497
+ color: dataA.color || "hsl(var(--chart-1))",
498
498
  fillOpacity: 0.15,
499
499
  },
500
500
  {
501
501
  id: "b",
502
502
  label: dataB.label,
503
503
  data: dataB.data,
504
- color: dataB.color || "#ef4444",
504
+ color: dataB.color || "hsl(var(--chart-2))",
505
505
  fillOpacity: 0.15,
506
506
  },
507
507
  ]
@@ -100,6 +100,7 @@ export interface UseScratchCardReturn {
100
100
  // Rarity Configuration
101
101
  // ============================================================================
102
102
 
103
+ // Rarity configuration using CSS variables for theme support
103
104
  const rarityConfig: Record<PrizeRarity, {
104
105
  gradient: string
105
106
  glowColor: string
@@ -111,7 +112,7 @@ const rarityConfig: Record<PrizeRarity, {
111
112
  }> = {
112
113
  common: {
113
114
  gradient: "from-slate-400 to-slate-600",
114
- glowColor: "#94a3b8",
115
+ glowColor: "hsl(var(--muted-foreground))",
115
116
  borderColor: "border-slate-400",
116
117
  bgColor: "bg-slate-100",
117
118
  textColor: "text-slate-600",
@@ -120,7 +121,7 @@ const rarityConfig: Record<PrizeRarity, {
120
121
  },
121
122
  rare: {
122
123
  gradient: "from-blue-400 to-blue-600",
123
- glowColor: "#60a5fa",
124
+ glowColor: "hsl(var(--info))",
124
125
  borderColor: "border-blue-400",
125
126
  bgColor: "bg-blue-100",
126
127
  textColor: "text-blue-600",
@@ -129,7 +130,7 @@ const rarityConfig: Record<PrizeRarity, {
129
130
  },
130
131
  epic: {
131
132
  gradient: "from-purple-400 to-purple-600",
132
- glowColor: "#a855f7",
133
+ glowColor: "hsl(var(--chart-3))",
133
134
  borderColor: "border-purple-400",
134
135
  bgColor: "bg-purple-100",
135
136
  textColor: "text-purple-600",
@@ -138,7 +139,7 @@ const rarityConfig: Record<PrizeRarity, {
138
139
  },
139
140
  legendary: {
140
141
  gradient: "from-amber-400 via-orange-500 to-red-500",
141
- glowColor: "#fbbf24",
142
+ glowColor: "hsl(var(--warning))",
142
143
  borderColor: "border-amber-400",
143
144
  bgColor: "bg-amber-100",
144
145
  textColor: "text-amber-600",
@@ -49,26 +49,27 @@ export interface WakaServerRackProps {
49
49
  // Constants
50
50
  // ============================================================================
51
51
 
52
+ // Status colors using CSS variables for theme support
52
53
  const STATUS_COLORS = {
53
54
  online: {
54
- led: "#22c55e",
55
- glow: "rgba(34, 197, 94, 0.6)",
56
- bg: "rgba(34, 197, 94, 0.1)",
55
+ led: "hsl(var(--success))",
56
+ glow: "hsl(var(--success) / 0.6)",
57
+ bg: "hsl(var(--success) / 0.1)",
57
58
  },
58
59
  warning: {
59
- led: "#eab308",
60
- glow: "rgba(234, 179, 8, 0.6)",
61
- bg: "rgba(234, 179, 8, 0.1)",
60
+ led: "hsl(var(--warning))",
61
+ glow: "hsl(var(--warning) / 0.6)",
62
+ bg: "hsl(var(--warning) / 0.1)",
62
63
  },
63
64
  offline: {
64
- led: "#ef4444",
65
- glow: "rgba(239, 68, 68, 0.6)",
66
- bg: "rgba(239, 68, 68, 0.1)",
65
+ led: "hsl(var(--destructive))",
66
+ glow: "hsl(var(--destructive) / 0.6)",
67
+ bg: "hsl(var(--destructive) / 0.1)",
67
68
  },
68
69
  maintenance: {
69
- led: "#3b82f6",
70
- glow: "rgba(59, 130, 246, 0.6)",
71
- bg: "rgba(59, 130, 246, 0.1)",
70
+ led: "hsl(var(--info))",
71
+ glow: "hsl(var(--info) / 0.6)",
72
+ bg: "hsl(var(--info) / 0.1)",
72
73
  },
73
74
  }
74
75
 
@@ -87,8 +88,8 @@ interface MetricBarProps {
87
88
 
88
89
  function MetricBar({ value, label, color, animated }: MetricBarProps) {
89
90
  const getBarColor = () => {
90
- if (value >= 90) return "#ef4444"
91
- if (value >= 70) return "#eab308"
91
+ if (value >= 90) return "hsl(var(--destructive))"
92
+ if (value >= 70) return "hsl(var(--warning))"
92
93
  return color
93
94
  }
94
95
 
@@ -245,20 +246,20 @@ function ServerUnitRow({
245
246
  <MetricBar
246
247
  value={server.cpu}
247
248
  label="CPU"
248
- color="#3b82f6"
249
+ color="hsl(var(--chart-1))"
249
250
  animated={animated}
250
251
  />
251
252
  <MetricBar
252
253
  value={server.ram}
253
254
  label="RAM"
254
- color="#8b5cf6"
255
+ color="hsl(var(--chart-3))"
255
256
  animated={animated}
256
257
  />
257
258
  {server.height >= 3 && (
258
259
  <MetricBar
259
260
  value={server.disk}
260
261
  label="DSK"
261
- color="#06b6d4"
262
+ color="hsl(var(--info))"
262
263
  animated={animated}
263
264
  />
264
265
  )}
@@ -279,8 +280,8 @@ function ServerUnitRow({
279
280
  <div className="flex items-center gap-0.5">
280
281
  {server.network && (
281
282
  <div className="flex flex-col items-end mr-2 text-[7px] font-mono text-zinc-500">
282
- <span className="text-green-500">{"\u25B2"} {server.network.out}</span>
283
- <span className="text-blue-400">{"\u25BC"} {server.network.in}</span>
283
+ <span className="text-success">{"\u25B2"} {server.network.out}</span>
284
+ <span className="text-info">{"\u25BC"} {server.network.in}</span>
284
285
  </div>
285
286
  )}
286
287
  {/* Drive activity indicators */}
@@ -344,21 +345,21 @@ function ServerUnitRow({
344
345
  <span className="font-mono">{server.height}U</span>
345
346
  </div>
346
347
  <div className="border-t border-zinc-700 my-2" />
347
- <MetricBar value={server.cpu} label="CPU" color="#3b82f6" />
348
- <MetricBar value={server.ram} label="RAM" color="#8b5cf6" />
349
- <MetricBar value={server.disk} label="DSK" color="#06b6d4" />
348
+ <MetricBar value={server.cpu} label="CPU" color="hsl(var(--chart-1))" />
349
+ <MetricBar value={server.ram} label="RAM" color="hsl(var(--chart-3))" />
350
+ <MetricBar value={server.disk} label="DSK" color="hsl(var(--info))" />
350
351
  {server.network && (
351
352
  <>
352
353
  <div className="border-t border-zinc-700 my-2" />
353
354
  <div className="flex justify-between text-zinc-400">
354
355
  <span>Network In</span>
355
- <span className="font-mono text-blue-400">
356
+ <span className="font-mono text-info">
356
357
  {server.network.in} Mbps
357
358
  </span>
358
359
  </div>
359
360
  <div className="flex justify-between text-zinc-400">
360
361
  <span>Network Out</span>
361
- <span className="font-mono text-green-500">
362
+ <span className="font-mono text-success">
362
363
  {server.network.out} Mbps
363
364
  </span>
364
365
  </div>
@@ -626,10 +627,10 @@ export function WakaServerRack({
626
627
  style={{
627
628
  background: `radial-gradient(ellipse at center, ${
628
629
  servers.some((s) => s.status === "offline")
629
- ? "rgba(239,68,68,0.05)"
630
+ ? "hsl(var(--destructive) / 0.05)"
630
631
  : servers.some((s) => s.status === "warning")
631
- ? "rgba(234,179,8,0.05)"
632
- : "rgba(34,197,94,0.03)"
632
+ ? "hsl(var(--warning) / 0.05)"
633
+ : "hsl(var(--success) / 0.03)"
633
634
  } 0%, transparent 70%)`,
634
635
  filter: "blur(20px)",
635
636
  }}
@@ -0,0 +1,97 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../utils/cn"
5
+
6
+ export type SponsoredBadgeVariant = "default" | "subtle" | "outline" | "dark"
7
+ export type SponsoredBadgeSize = "sm" | "md" | "lg"
8
+
9
+ export interface WakaSponsoredBadgeProps {
10
+ /** Sponsor name (optional) */
11
+ sponsor?: string
12
+ /** Badge variant */
13
+ variant?: SponsoredBadgeVariant
14
+ /** Badge size */
15
+ size?: SponsoredBadgeSize
16
+ /** Show info icon */
17
+ showIcon?: boolean
18
+ /** Custom label text */
19
+ label?: string
20
+ /** Custom class name */
21
+ className?: string
22
+ /** Callback when info is clicked */
23
+ onInfoClick?: () => void
24
+ }
25
+
26
+ export function WakaSponsoredBadge({
27
+ sponsor,
28
+ variant = "default",
29
+ size = "sm",
30
+ showIcon = true,
31
+ label = "Sponsored",
32
+ className,
33
+ onInfoClick,
34
+ }: WakaSponsoredBadgeProps) {
35
+ const variantClasses: Record<SponsoredBadgeVariant, string> = {
36
+ default: "bg-muted text-muted-foreground border-transparent",
37
+ subtle: "bg-black/50 text-white border-transparent backdrop-blur-sm",
38
+ outline: "bg-transparent text-muted-foreground border-muted-foreground/30",
39
+ dark: "bg-black/70 text-white border-transparent",
40
+ }
41
+
42
+ const sizeClasses: Record<SponsoredBadgeSize, string> = {
43
+ sm: "text-[10px] px-1.5 py-0.5 gap-1",
44
+ md: "text-xs px-2 py-1 gap-1.5",
45
+ lg: "text-sm px-2.5 py-1 gap-2",
46
+ }
47
+
48
+ const iconSizes: Record<SponsoredBadgeSize, string> = {
49
+ sm: "h-2.5 w-2.5",
50
+ md: "h-3 w-3",
51
+ lg: "h-3.5 w-3.5",
52
+ }
53
+
54
+ return (
55
+ <span
56
+ className={cn(
57
+ "inline-flex items-center rounded border font-medium",
58
+ variantClasses[variant],
59
+ sizeClasses[size],
60
+ className
61
+ )}
62
+ >
63
+ <span className="whitespace-nowrap">
64
+ {sponsor ? `${label} by ${sponsor}` : label}
65
+ </span>
66
+
67
+ {showIcon && (
68
+ <button
69
+ onClick={(e) => {
70
+ e.preventDefault()
71
+ e.stopPropagation()
72
+ onInfoClick?.()
73
+ }}
74
+ className={cn(
75
+ "rounded-full hover:opacity-70 transition-opacity focus:outline-none focus:ring-1 focus:ring-current",
76
+ iconSizes[size]
77
+ )}
78
+ aria-label="Why am I seeing this ad?"
79
+ >
80
+ <svg
81
+ viewBox="0 0 24 24"
82
+ fill="none"
83
+ stroke="currentColor"
84
+ strokeWidth={2}
85
+ className="w-full h-full"
86
+ >
87
+ <circle cx="12" cy="12" r="10" />
88
+ <path d="M12 16v-4" />
89
+ <path d="M12 8h.01" />
90
+ </svg>
91
+ </button>
92
+ )}
93
+ </span>
94
+ )
95
+ }
96
+
97
+ export default WakaSponsoredBadge