@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.
- package/dist/components/index.d.ts +15 -0
- package/dist/components/waka-ad-banner/index.d.ts +36 -0
- package/dist/components/waka-ad-fallback/index.d.ts +33 -0
- package/dist/components/waka-ad-inline/index.d.ts +15 -0
- package/dist/components/waka-ad-interstitial/index.d.ts +26 -0
- package/dist/components/waka-ad-placeholder/index.d.ts +17 -0
- package/dist/components/waka-ad-provider/index.d.ts +103 -0
- package/dist/components/waka-ad-sidebar/index.d.ts +18 -0
- package/dist/components/waka-ad-sticky-footer/index.d.ts +17 -0
- package/dist/components/waka-content-recommendation/index.d.ts +23 -0
- package/dist/components/waka-outstream-video/index.d.ts +24 -0
- package/dist/components/waka-sponsored-badge/index.d.ts +20 -0
- package/dist/components/waka-sponsored-card/index.d.ts +25 -0
- package/dist/components/waka-sponsored-feed/index.d.ts +31 -0
- package/dist/components/waka-video-ad/index.d.ts +32 -0
- package/dist/components/waka-video-overlay/index.d.ts +26 -0
- package/dist/index.cjs.js +177 -171
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +14535 -12812
- package/dist/utils/security.d.ts +96 -0
- package/package.json +4 -4
- package/src/blocks/sidebar/index.tsx +6 -6
- package/src/components/DataTable/templates/index.tsx +3 -2
- package/src/components/index.ts +94 -0
- package/src/components/waka-3d-pie-chart/index.tsx +11 -11
- package/src/components/waka-achievement-unlock/index.tsx +16 -16
- package/src/components/waka-ad-banner/index.tsx +275 -0
- package/src/components/waka-ad-fallback/index.tsx +181 -0
- package/src/components/waka-ad-inline/index.tsx +103 -0
- package/src/components/waka-ad-interstitial/index.tsx +278 -0
- package/src/components/waka-ad-placeholder/index.tsx +84 -0
- package/src/components/waka-ad-provider/index.tsx +329 -0
- package/src/components/waka-ad-sidebar/index.tsx +113 -0
- package/src/components/waka-ad-sticky-footer/index.tsx +125 -0
- package/src/components/waka-badge-showcase/index.tsx +12 -11
- package/src/components/waka-command-bar/index.tsx +2 -1
- package/src/components/waka-content-recommendation/index.tsx +294 -0
- package/src/components/waka-cost-breakdown/index.tsx +10 -10
- package/src/components/waka-funnel-chart/index.tsx +8 -8
- package/src/components/waka-health-pulse/index.tsx +6 -6
- package/src/components/waka-leaderboard/index.tsx +9 -9
- package/src/components/waka-loot-box/index.tsx +20 -20
- package/src/components/waka-outstream-video/index.tsx +240 -0
- package/src/components/waka-player-card/index.tsx +5 -5
- package/src/components/waka-quota-bar/index.tsx +4 -4
- package/src/components/waka-radar-score/index.tsx +10 -10
- package/src/components/waka-scratch-card/index.tsx +5 -4
- package/src/components/waka-server-rack/index.tsx +28 -27
- package/src/components/waka-sponsored-badge/index.tsx +97 -0
- package/src/components/waka-sponsored-card/index.tsx +275 -0
- package/src/components/waka-sponsored-feed/index.tsx +127 -0
- package/src/components/waka-spotlight/index.tsx +2 -1
- package/src/components/waka-success-explosion/index.tsx +4 -4
- package/src/components/waka-video-ad/index.tsx +406 -0
- package/src/components/waka-video-overlay/index.tsx +257 -0
- package/src/components/waka-xp-bar/index.tsx +13 -13
- package/src/styles/base.css +16 -0
- package/src/styles/tailwind.preset.js +12 -0
- package/src/styles/themes/forest.css +16 -0
- package/src/styles/themes/monochrome.css +16 -0
- package/src/styles/themes/perpetuity.css +16 -0
- package/src/styles/themes/sunset.css +16 -0
- 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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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-
|
|
57
|
-
warning: "bg-
|
|
58
|
-
danger: "bg-
|
|
59
|
-
overflow: "bg-
|
|
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
|
-
"
|
|
177
|
-
"
|
|
178
|
-
"
|
|
179
|
-
"
|
|
180
|
-
"
|
|
181
|
-
"
|
|
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 = "
|
|
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 || "
|
|
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 || "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
55
|
-
glow: "
|
|
56
|
-
bg: "
|
|
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: "
|
|
60
|
-
glow: "
|
|
61
|
-
bg: "
|
|
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: "
|
|
65
|
-
glow: "
|
|
66
|
-
bg: "
|
|
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: "
|
|
70
|
-
glow: "
|
|
71
|
-
bg: "
|
|
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 "
|
|
91
|
-
if (value >= 70) return "
|
|
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="
|
|
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="
|
|
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="
|
|
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-
|
|
283
|
-
<span className="text-
|
|
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="
|
|
348
|
-
<MetricBar value={server.ram} label="RAM" color="
|
|
349
|
-
<MetricBar value={server.disk} label="DSK" color="
|
|
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-
|
|
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-
|
|
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
|
-
? "
|
|
630
|
+
? "hsl(var(--destructive) / 0.05)"
|
|
630
631
|
: servers.some((s) => s.status === "warning")
|
|
631
|
-
? "
|
|
632
|
-
: "
|
|
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
|