refactor: change ui theme

Hee Sung Son committed Jan 18, 2026 at 01:04 UTC f578979fd74867a2a0250ad432e3aed28479caac
7 files changed +300 -174
cmd/relay-server/frontend/src/components/Header.tsx
+4 -4
@@ -47,7 +47,7 @@ export function Header({ title = "PORTAL", isAdmin, onLogout }: HeaderProps) {
47
48 return (
49 <header className="flex items-center justify-between whitespace-nowrap border-b border-solid border-b-border px-4 sm:px-6 py-3">
50 - <div className="flex items-center gap-4 text-foreground">
50 + <div className="flex items-center gap-1 sm:gap-4 text-foreground">
51 <div className="text-primary size-6">
52 <svg
53 xmlns="http://www.w3.org/2000/svg"
@@ -56,16 +56,16 @@ export function Header({ title = "PORTAL", isAdmin, onLogout }: HeaderProps) {
56 viewBox="0 0 906.26 1457.543"
57 >
58 <path
59 - fill="#17C0E9"
59 + fill="currentColor"
60 d="M254.854 137.158c-34.46 84.407-88.363 149.39-110.934 245.675 90.926-187.569 308.397-483.654 554.729-348.685 135.487 74.216 194.878 270.78 206.058 467.566 21.924 385.996-190.977 853.604-467.585 943.057-174.879 56.543-307.375-86.447-364.527-198.115-176.498-344.82 2.041-910.077 182.259-1109.498zm198.13 7.918C202.61 280.257 4.622 968.542 207.322 1270.414c51.713 77.029 194.535 160.648 285.294 71.318-209.061 31.529-288.389-176.143-301.145-340.765 31.411 147.743 139.396 326.12 309.075 253.588 251.957-107.723 376.778-648.46 269.433-966.817 22.394 134.616 15.572 317.711-47.551 412.087 86.655-230.615 7.903-704.478-269.444-554.749z"
61 ></path>
62 </svg>
63 </div>
64 - <h2 className="text-foreground text-lg font-bold leading-tight tracking-[-0.015em]">
64 + <h2 className="text-foreground text-lg font-bold leading-tight tracking-[0.3em]">
65 {title}
66 </h2>
67 </div>
68 - <div className="flex items-center gap-3">
68 + <div className="flex items-center gap-1 sm:gap-3">
69 <a
70 href="https://github.com/gosuda/portal"
71 target="_blank"
cmd/relay-server/frontend/src/components/SearchBar.tsx
+3 -3
@@ -38,15 +38,15 @@ export function SearchBar({
38 return (
39 <div className="flex flex-wrap mt-4 sm:mt-6 items-center gap-3 px-4 sm:px-6">
40 <div className="flex gap-2 items-center w-full">
41 - <label className="flex min-w-[220px] flex-1 items-stretch h-10">
42 - <div className="text-text-muted flex items-center justify-center pl-3 pr-2 rounded-l-md bg-border">
41 + <label className="flex min-w-[220px] flex-1 items-stretch h-10 relative">
42 + <div className="left-0 h-full absolute text-text-muted flex items-center justify-center pl-3 pr-2 rounded-l-md bg-border border border-border">
43 <Search className="w-4 h-4" />
44 </div>
45 <Input
46 placeholder="Search by server name..."
47 value={searchQuery}
48 onChange={(e) => onSearchChange(e.target.value)}
49 - className="rounded-l-none h-10"
49 + className="h-10 pl-12 border border-border rounded-md"
50 />
51 </label>
52 {/* Mobile filter button - only show for admin */}
cmd/relay-server/frontend/src/components/ServerCard.tsx
+164 -114
@@ -211,7 +211,7 @@ export function ServerCard({
211 <Link
212 to={navigationPath}
213 state={navigationState}
214 - className="relative cursor-pointer"
214 + className="relative cursor-pointer block"
215 >
216 {children}
217 </Link>
@@ -219,151 +219,202 @@ export function ServerCard({
219
220 return (
221 <Wrapper>
222 - <div
222 + <article
223 data-hero-key={`server-bg-${serverId}`}
224 className={clsx(
225 - "relative bg-center bg-no-repeat bg-cover rounded-xl shadow-lg hover:shadow-xl transition-shadow duration-300 z-1 border border-foreground dark:border-foreground/40",
225 + "relative w-full overflow-hidden rounded-3xl group border border-white/10 shadow-lg",
226 showAdminControls ? "h-[286px]" : "h-[174.5px]"
227 )}
228 - style={{ ...(thumbnail && { backgroundImage: `url(${thumbnail})` }) }}
228 >
230 - {/* Admin mode: Checkbox for selection / Normal mode: Favorite star */}
231 - {showAdminControls ? (
232 - <button
233 - onClick={handleSelectClick}
234 - className={clsx(
235 - "absolute top-3 right-3 z-10 p-1.5 rounded-full transition-colors duration-200 cursor-pointer border",
236 - isSelected
237 - ? "bg-primary border-primary"
238 - : "bg-background/80 hover:bg-background border-foreground/30"
239 - )}
240 - aria-label={isSelected ? "Deselect" : "Select"}
241 - >
242 - <svg
243 - xmlns="http://www.w3.org/2000/svg"
244 - viewBox="0 0 24 24"
245 - className="w-5 h-5 transition-colors duration-200"
246 - fill="none"
247 - stroke={isSelected ? "white" : "currentColor"}
248 - strokeWidth="3"
249 - strokeLinecap="round"
250 - strokeLinejoin="round"
251 - >
252 - {isSelected && <polyline points="20 6 9 17 4 12" />}
253 - </svg>
254 - </button>
255 - ) : (
256 - <button
257 - onClick={handleFavoriteClick}
258 - className="absolute top-3 right-3 z-10 p-2 rounded-full bg-background/80 hover:bg-background transition-colors duration-200 cursor-pointer"
259 - aria-label={
260 - isFavorite ? "Remove from favorites" : "Add to favorites"
261 - }
262 - >
263 - <svg
264 - xmlns="http://www.w3.org/2000/svg"
265 - viewBox="0 0 24 24"
266 - className="w-5 h-5 transition-colors duration-200"
267 - fill={isFavorite ? "currentColor" : "none"}
268 - stroke="currentColor"
269 - strokeWidth="2"
270 - strokeLinecap="round"
271 - strokeLinejoin="round"
272 - style={{ color: isFavorite ? "var(--primary)" : "currentColor" }}
273 - >
274 - <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" />
275 - </svg>
276 - </button>
277 - )}
229 + {/* Background Image */}
230 + <div
231 + className="absolute inset-0 bg-cover bg-center transition-transform duration-700 group-hover:scale-105"
232 + style={{
233 + backgroundImage: thumbnail
234 + ? `url(${thumbnail})`
235 + : "linear-gradient(135deg, var(--card) 0%, var(--background) 100%)",
236 + }}
237 + />
238 +
239 + {/* Gradient Overlay */}
240 + <div className="absolute inset-0 bg-linear-to-t from-black via-black/60 to-transparent" />
241
279 - {/* Content overlay - not part of hero transition */}
280 - <div className="relative h-full w-full bg-background/80 rounded-xl flex flex-col gap-4 p-4 items-start text-start">
281 - <div className="w-full flex flex-1 flex-col justify-between gap-4">
282 - <div className="flex flex-col gap-2">
283 - <div className="flex items-center gap-2">
284 - <div
285 - className={clsx(
286 - "w-2.5 h-2.5 rounded-full",
287 - online ? "bg-green-status" : "bg-red-500"
288 - )}
289 - />
290 - <p
291 - className={clsx(
292 - "text-sm font-medium leading-normal",
293 - online ? "text-green-status" : "text-red-500"
294 - )}
242 + {/* Content */}
243 + <div className="relative z-10 flex h-full flex-col justify-between p-5">
244 + {/* Top Row: Status Badge + Action Button */}
245 + <div className="flex items-start justify-between">
246 + {/* Online/Offline Status Badge */}
247 + <div className="flex items-center gap-2 rounded-full bg-black/40 px-3 py-1 backdrop-blur-sm border border-white/5">
248 + <div
249 + className={clsx(
250 + "size-2 rounded-full",
251 + online
252 + ? "bg-primary shadow-[0_0_8px_rgba(0,219,219,0.8)] animate-pulse"
253 + : "bg-gray-500"
254 + )}
255 + />
256 + <span
257 + className={clsx(
258 + "text-[10px] font-bold uppercase tracking-wider",
259 + online ? "text-white" : "text-white/60"
260 + )}
261 + >
262 + {online ? "Online" : "Offline"}
263 + {formattedDuration && online && ` · ${formattedDuration}`}
264 + </span>
265 + </div>
266 +
267 + {/* Admin mode: Checkbox / Normal mode: Favorite star */}
268 + {showAdminControls ? (
269 + <button
270 + onClick={handleSelectClick}
271 + className={clsx(
272 + "flex size-8 items-center justify-center rounded-full backdrop-blur-md transition-colors border border-white/5 cursor-pointer",
273 + isSelected
274 + ? "bg-primary text-black"
275 + : "bg-black/40 text-white/70 hover:bg-primary hover:text-black"
276 + )}
277 + aria-label={isSelected ? "Deselect" : "Select"}
278 + >
279 + <svg
280 + xmlns="http://www.w3.org/2000/svg"
281 + viewBox="0 0 24 24"
282 + className="w-[18px] h-[18px]"
283 + fill="none"
284 + stroke="currentColor"
285 + strokeWidth="3"
286 + strokeLinecap="round"
287 + strokeLinejoin="round"
288 + >
289 + {isSelected && <polyline points="20 6 9 17 4 12" />}
290 + </svg>
291 + </button>
292 + ) : (
293 + <button
294 + onClick={handleFavoriteClick}
295 + className={clsx(
296 + "flex size-8 items-center justify-center rounded-full backdrop-blur-md transition-colors border border-white/5 cursor-pointer",
297 + isFavorite
298 + ? "bg-primary text-black"
299 + : "bg-black/40 text-white/70 hover:bg-primary hover:text-black"
300 + )}
301 + aria-label={
302 + isFavorite ? "Remove from favorites" : "Add to favorites"
303 + }
304 + >
305 + <svg
306 + xmlns="http://www.w3.org/2000/svg"
307 + viewBox="0 0 24 24"
308 + className="w-[18px] h-[18px]"
309 + fill={isFavorite ? "currentColor" : "none"}
310 + stroke="currentColor"
311 + strokeWidth="2"
312 + strokeLinecap="round"
313 + strokeLinejoin="round"
314 >
296 - {online ? "Online" : "Offline"}
297 - {formattedDuration && online && ` (${formattedDuration})`}
298 - </p>
315 + <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" />
316 + </svg>
317 + </button>
318 + )}
319 + </div>
320 +
321 + {/* Bottom Content */}
322 + <div className="flex flex-col gap-3">
323 + {/* Server Info Row */}
324 + <div className="flex items-end justify-between gap-3">
325 + <div className="flex flex-col gap-1.5 flex-1 min-w-0">
326 + {/* Server Name */}
327 + <h3 className="font-display text-xl font-bold leading-tight text-white truncate">
328 + {name}
329 + </h3>
330 +
331 + {/* Description */}
332 + {description && (
333 + <p className="text-xs text-white/70 line-clamp-1 font-medium">
334 + {description}
335 + </p>
336 + )}
337 +
338 + {/* Tags */}
339 + {tags && tags.length > 0 && (
340 + <ScrollArea className="w-full mt-1">
341 + <div className="flex gap-1.5 min-w-max">
342 + {tags.map((tag, index) => (
343 + <span
344 + key={index}
345 + className="rounded bg-primary/20 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wider text-primary border border-primary/30 whitespace-nowrap"
346 + >
347 + #{tag}
348 + </span>
349 + ))}
350 + </div>
351 + <ScrollBar orientation="horizontal" />
352 + </ScrollArea>
353 + )}
354 +
355 + {/* Owner */}
356 + {owner && (
357 + <span className="text-[10px] font-medium text-white/50">
358 + by {owner}
359 + </span>
360 + )}
361 </div>
300 - <p className="text-foreground text-lg font-bold leading-tight truncate max-w-full">
301 - {name}
302 - </p>
303 - {description && (
304 - <p className="text-text-muted text-sm font-normal leading-normal truncate max-w-full">
305 - {description}
306 - </p>
307 - )}
308 - {tags && tags.length > 0 && (
309 - <ScrollArea className="w-full mt-1">
310 - <div className="flex gap-1.5 min-w-max">
311 - {tags.map((tag, index) => (
312 - <span
313 - key={index}
314 - className="px-2 py-1 bg-secondary text-primary text-xs font-medium rounded whitespace-nowrap"
315 - >
316 - {tag}
317 - </span>
318 - ))}
362 +
363 + {/* Thumbnail Avatar (if no admin controls) */}
364 + {!showAdminControls && thumbnail && (
365 + <div className="shrink-0">
366 + <div className="size-10 overflow-hidden rounded-xl border border-white/20 shadow-lg">
367 + <img
368 + alt={`${name} avatar`}
369 + className="h-full w-full object-cover"
370 + src={thumbnail}
371 + />
372 </div>
320 - <ScrollBar orientation="horizontal" />
321 - </ScrollArea>
322 - )}
323 - {owner && (
324 - <p className="text-text-muted text-xs font-normal leading-normal truncate max-w-full">
325 - by {owner}
326 - </p>
373 + </div>
374 )}
375 </div>
376 +
377 + {/* Admin Controls */}
378 {showAdminControls && leaseId && (
330 - <div className="flex flex-col gap-2 w-full">
331 - {/* BPS Row: display on left, settings button on right */}
379 + <div className="flex flex-col gap-2 w-full mt-2">
380 + {/* BPS Row */}
381 <div className="flex items-center justify-between w-full">
333 - <span className="text-sm text-text-muted">
382 + <span className="text-xs text-white/60">
383 BPS:{" "}
335 - <span className="font-medium text-foreground">
384 + <span className="font-medium text-white">
385 {formatBPS(bps)}
386 </span>
387 </span>
388 <button
389 onClick={handleBPSSettingsClick}
341 - className="px-3 py-1 text-xs rounded bg-secondary hover:bg-secondary/80 text-secondary-foreground transition-colors cursor-pointer"
390 + className="px-3 py-1 text-[10px] rounded-full bg-white/10 hover:bg-white/20 text-white/80 transition-colors cursor-pointer border border-white/10"
391 >
392 Settings
393 </button>
394 </div>
346 - {/* IP display (for approved items in both auto and manual mode) */}
395 +
396 + {/* IP display */}
397 {isApproved && ip && (
348 - <div className="text-xs text-text-muted">
398 + <div className="text-[10px] text-white/50">
399 IP: <span className="font-mono">{ip}</span>
400 {isIPBanned && (
351 - <span className="ml-2 text-red-500">(Banned)</span>
401 + <span className="ml-2 text-red-400">(Banned)</span>
402 )}
403 </div>
404 )}
355 - {/* Approve/Deny buttons: show only for unapproved items (both manual and auto mode) */}
405 +
406 + {/* Approve/Deny buttons */}
407 {!isApproved && !isDenied ? (
408 <div className="flex gap-2 w-full">
409 <button
410 onClick={handleApproveClick}
360 - className="flex-1 px-4 py-2 rounded font-medium transition-colors cursor-pointer text-white bg-green-600 hover:bg-green-700"
411 + className="flex-1 px-4 py-2 rounded-lg font-medium text-xs transition-colors cursor-pointer text-white bg-green-600/80 hover:bg-green-600 backdrop-blur-sm"
412 >
413 Approve
414 </button>
415 <button
416 onClick={handleDenyClick}
366 - className="flex-1 px-4 py-2 rounded font-medium transition-colors cursor-pointer text-white bg-red-600 hover:bg-red-700"
417 + className="flex-1 px-4 py-2 rounded-lg font-medium text-xs transition-colors cursor-pointer text-white bg-red-600/80 hover:bg-red-600 backdrop-blur-sm"
418 >
419 Deny
420 </button>
@@ -372,10 +423,10 @@ export function ServerCard({
423 <button
424 onClick={ip ? handleIPBanClick : handleBanClick}
425 className={clsx(
375 - "w-full px-4 py-2 rounded font-medium transition-colors cursor-pointer text-white",
426 + "w-full px-4 py-2 rounded-lg font-medium text-xs transition-colors cursor-pointer text-white backdrop-blur-sm",
427 (ip ? isIPBanned : isBanned)
377 - ? "bg-green-600 hover:bg-green-700"
378 - : "bg-red-600 hover:bg-red-700"
428 + ? "bg-green-600/80 hover:bg-green-600"
429 + : "bg-red-600/80 hover:bg-red-600"
430 )}
431 >
432 {ip
@@ -391,12 +442,11 @@ export function ServerCard({
442 )}
443 </div>
444 </div>
394 - </div>
395 - <div className="absolute top-2 left-2 h-full w-full bg-secondary/70 rounded-xl z-0" />
445 + </article>
446
447 {/* BPS Settings Modal */}
448 <Dialog open={showBPSModal} onOpenChange={setShowBPSModal}>
399 - <DialogContent className="max-w-sm rounded-sm">
449 + <DialogContent className="max-w-sm rounded-xl">
450 <DialogHeader>
451 <DialogTitle>BPS Settings</DialogTitle>
452 <DialogDescription>
cmd/relay-server/frontend/src/components/select/SortbySelect.tsx
+1 -1
@@ -24,7 +24,7 @@ export const SortbySelect = ({
24 <Select value={sortBy} onValueChange={onSortByChange}>
25 <SelectTrigger
26 className={clsx(
27 - "w-[150px] h-10",
27 + "w-[150px] h-10 border-border!",
28 hideFiltersOnMobile && "hidden sm:flex",
29 className
30 )}
cmd/relay-server/frontend/src/components/select/StatusSelect.tsx
+1 -1
@@ -24,7 +24,7 @@ export const StatusSelect = ({
24 <Select value={status} onValueChange={onStatusChange}>
25 <SelectTrigger
26 className={clsx(
27 - "w-[130px] h-10",
27 + "w-[130px] h-10 border-border!",
28 hideFiltersOnMobile && "hidden sm:flex",
29 className
30 )}
cmd/relay-server/frontend/src/index.css
+125 -49
@@ -1,17 +1,22 @@
1 -@import url('https://fonts.googleapis.com/css2?family=Public+Sans:wght@400;500;700;900&display=swap');
1 +@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Noto+Sans:wght@400;500;600;700&display=swap');
2 @import "tailwindcss";
3
4 @custom-variant dark (&:where(.dark, .dark *));
5
6 @theme {
7 /* Font families */
8 - --font-family-display: "Public Sans", sans-serif;
8 + --font-family-display: "Space Grotesk", sans-serif;
9 + --font-family-sans: "Noto Sans", sans-serif;
10
11 /* Border radius */
12 --radius: 0.5rem;
13 --radius-lg: 0.75rem;
14 --radius-xl: 1rem;
14 -
15 +
16 + /* Glass effect tokens */
17 + --glass-blur: 12px;
18 +
19 + /* Color mappings */
20 --color-background: var(--background);
21 --color-foreground: var(--foreground);
22 --color-card: var(--card);
@@ -33,61 +38,88 @@
38 --color-ring: var(--ring);
39 --color-green-status: var(--green-status);
40 --color-text-muted: var(--text-muted);
41 +
42 + /* Cyberpunk accent colors */
43 + --color-accent-orange: var(--accent-orange);
44 + --color-accent-magenta: var(--accent-magenta);
45 + --color-neon-cyan: var(--neon-cyan);
46 }
47
48 @layer base {
49 :root {
40 - /* Light Theme (OKLCH) - Maintains the color tone of dark theme while inverting lightness */
41 - --background: oklch(98% 0.005 240); /* Soft white instead of pure white */
42 - --foreground: oklch(20% 0.03 215); /* Deep blue-gray text */
43 - --card: oklch(100% 0 0); /* Pure white card (differentiated from background) */
44 - --card-foreground: oklch(20% 0.03 215);
50 + /* Light Theme - Clean slate-based design */
51 + --background: oklch(97% 0.005 250); /* #F5F7FA */
52 + --foreground: oklch(25% 0.02 250); /* Slate 800 #1e293b */
53 + --card: oklch(100% 0 0); /* White */
54 + --card-foreground: oklch(25% 0.02 250);
55 --popover: oklch(100% 0 0);
46 - --popover-foreground: oklch(20% 0.03 215);
47 - --primary: oklch(55% 0.13 210); /* Darker and less saturated than dark theme, ~#1b8bc4 */
48 - --primary-foreground: oklch(100% 0 0); /* White text */
49 - --secondary: oklch(95% 0.01 220); /* Light blue-gray background */
50 - --secondary-foreground: oklch(25% 0.03 215);
51 - --muted: oklch(94% 0.01 220); /* Light gray */
52 - --muted-foreground: oklch(50% 0.04 220); /* Mid-tone gray text */
53 - --accent: oklch(94% 0.02 210); /* Light accent background */
54 - --accent-foreground: oklch(25% 0.03 215);
55 - --destructive: oklch(55% 0.20 25); /* Red - slightly darker than dark theme */
56 - --destructive-foreground: oklch(98% 0.01 240);
57 - --border: oklch(90% 0.01 220); /* Subtle border */
58 - --input: oklch(96% 0.005 220); /* Input field background */
59 - --ring: oklch(55% 0.13 210); /* Same as primary */
56 + --popover-foreground: oklch(25% 0.02 250);
57 + --primary: oklch(75% 0.14 195); /* #00dbdb cyan - slightly darker for light bg */
58 + --primary-foreground: oklch(100% 0 0);
59 + --secondary: oklch(92% 0.01 250); /* Slate 200 */
60 + --secondary-foreground: oklch(45% 0.02 250); /* Slate 600 */
61 + --muted: oklch(95% 0.005 250); /* Slate 100 */
62 + --muted-foreground: oklch(55% 0.03 250); /* Slate 500 #64748b */
63 + --accent: oklch(92% 0.01 250);
64 + --accent-foreground: oklch(25% 0.02 250);
65 + --destructive: oklch(55% 0.20 25);
66 + --destructive-foreground: oklch(100% 0 0);
67 + --border: oklch(90% 0.01 250); /* Slate 200 border */
68 + --input: oklch(95% 0.005 250); /* Slate 100 */
69 + --ring: oklch(75% 0.14 195);
70
71 /* Custom light theme colors */
62 - --green-status: oklch(55% 0.14 150); /* Darker green than dark theme */
63 - --text-muted: oklch(50% 0.04 220); /* Soft gray text */
72 + --green-status: oklch(55% 0.16 150); /* Green 600 */
73 + --text-muted: oklch(55% 0.03 250); /* Slate 500 */
74 + --accent-orange: oklch(60% 0.20 35); /* Orange */
75 + --accent-magenta: oklch(50% 0.22 350); /* Magenta */
76 + --neon-cyan: oklch(75% 0.14 195);
77 +
78 + /* Glass effects - light mode */
79 + --glass-bg: oklch(100% 0 0 / 0.7);
80 + --glass-bg-hover: oklch(100% 0 0 / 0.9);
81 + --glass-border: oklch(100% 0 0 / 0.5);
82 + --glass-border-hover: oklch(75% 0.14 195 / 0.3);
83 + --shadow-neon: 0 0 10px oklch(75% 0.14 195 / 0.4), 0 0 20px oklch(75% 0.14 195 / 0.2);
84 + --shadow-glass: 0 4px 30px oklch(0% 0 0 / 0.05);
85 }
86
87 .dark {
67 - /* Dark Theme (OKLCH) */
68 - --background: oklch(18% 0.04 210); /* #0f1e23 */
69 - --foreground: oklch(98% 0.01 240); /* white text */
70 - --card: oklch(22% 0.05 215); /* #192433 */
71 - --card-foreground: oklch(98% 0.01 240);
72 - --popover: oklch(22% 0.05 215);
73 - --popover-foreground: oklch(98% 0.01 240);
74 - --primary: oklch(75% 0.15 210); /* #47cdff */
75 - --primary-foreground: oklch(0% 0 0);
76 - --secondary: oklch(28% 0.04 215); /* #233348 */
77 - --secondary-foreground: oklch(98% 0.01 240);
78 - --muted: oklch(28% 0.04 215);
79 - --muted-foreground: oklch(70% 0.05 230); /* #92a9c9 */
80 - --accent: oklch(28% 0.04 215);
81 - --accent-foreground: oklch(98% 0.01 240);
88 + /* Dark Theme - Cyberpunk Neon */
89 + --background: oklch(15% 0 0); /* #121212 deep charcoal */
90 + --foreground: oklch(98% 0.01 0);
91 + --card: oklch(20% 0 0); /* #1E1E1E surface dark */
92 + --card-foreground: oklch(98% 0.01 0);
93 + --popover: oklch(20% 0 0);
94 + --popover-foreground: oklch(98% 0.01 0);
95 + --primary: oklch(80% 0.14 195); /* #00dbdb cyan */
96 + --primary-foreground: oklch(10% 0 0);
97 + --secondary: oklch(25% 0.02 195);
98 + --secondary-foreground: oklch(98% 0.01 0);
99 + --muted: oklch(25% 0.01 0);
100 + --muted-foreground: oklch(65% 0.02 0);
101 + --accent: oklch(25% 0.03 195);
102 + --accent-foreground: oklch(98% 0.01 0);
103 --destructive: oklch(62% 0.22 25);
83 - --destructive-foreground: oklch(98% 0.01 240);
84 - --border: oklch(26% 0.04 215); /* #233348 */
85 - --input: oklch(26% 0.04 215);
86 - --ring: oklch(75% 0.15 210); /* #47cdff */
104 + --destructive-foreground: oklch(98% 0 0);
105 + --border: oklch(100% 0 0 / 0.1); /* white/10 for dark mode */
106 + --input: oklch(22% 0.01 0);
107 + --ring: oklch(80% 0.14 195);
108 +
109 + /* Custom dark theme colors - Cyberpunk */
110 + --green-status: oklch(75% 0.16 150);
111 + --text-muted: oklch(65% 0.02 0);
112 + --accent-orange: oklch(65% 0.20 35); /* #FF5733 */
113 + --accent-magenta: oklch(55% 0.25 350); /* #E6007A */
114 + --neon-cyan: oklch(80% 0.14 195);
115
88 - /* Custom dark theme colors */
89 - --green-status: oklch(78% 0.16 170);
90 - --text-muted: oklch(70% 0.05 230);
116 + /* Glass effects - dark mode */
117 + --glass-bg: oklch(20% 0 0 / 0.4);
118 + --glass-bg-hover: oklch(20% 0 0 / 0.6);
119 + --glass-border: oklch(100% 0 0 / 0.08);
120 + --glass-border-hover: oklch(80% 0.14 195 / 0.3);
121 + --shadow-neon: 0 0 10px oklch(80% 0.14 195 / 0.5), 0 0 20px oklch(80% 0.14 195 / 0.3);
122 + --shadow-glass: 0 4px 30px oklch(0% 0 0 / 0.1);
123 }
124
125 * {
@@ -97,6 +129,10 @@
129 body {
130 background-color: var(--background);
131 color: var(--foreground);
132 + font-family: var(--font-family-sans);
133 + }
134 +
135 + h1, h2, h3, h4, h5, h6 {
136 font-family: var(--font-family-display);
137 }
138
@@ -115,11 +151,51 @@
151
152 /* Hide scrollbar for horizontal scroll */
153 .scrollbar-hide {
118 - -ms-overflow-style: none; /* IE and Edge */
119 - scrollbar-width: none; /* Firefox */
154 + -ms-overflow-style: none;
155 + scrollbar-width: none;
156 }
157
158 .scrollbar-hide::-webkit-scrollbar {
123 - display: none; /* Chrome, Safari and Opera */
159 + display: none;
160 + }
161 +}
162 +
163 +@layer components {
164 + /* Glass card effect */
165 + .glass-card {
166 + background: var(--glass-bg);
167 + backdrop-filter: blur(var(--glass-blur));
168 + -webkit-backdrop-filter: blur(var(--glass-blur));
169 + border: 1px solid var(--glass-border);
170 + box-shadow: var(--shadow-glass);
171 + transition: all 0.3s ease;
172 + }
173 +
174 + .glass-card:hover {
175 + border-color: var(--glass-border-hover);
176 + background: var(--glass-bg-hover);
177 + }
178 +
179 + /* Neon glow effects */
180 + .neon-glow {
181 + box-shadow: var(--shadow-neon);
182 + }
183 +
184 + .neon-text {
185 + text-shadow: 0 0 10px oklch(80% 0.14 195 / 0.7);
186 + }
187 +
188 + .dark .neon-text {
189 + text-shadow: 0 0 10px oklch(80% 0.14 195 / 0.7);
190 + }
191 +
192 + :root .neon-text {
193 + text-shadow: 0 0 10px oklch(45% 0.14 195 / 0.5);
194 + }
195 +
196 + /* Neon border on focus/hover */
197 + .neon-border {
198 + border-color: var(--neon-cyan);
199 + box-shadow: 0 0 5px var(--neon-cyan);
200 }
201 }
cmd/relay-server/frontend/src/pages/AdminLogin.tsx
+2 -2
@@ -76,12 +76,12 @@ export function AdminLogin() {
76 viewBox="0 0 906.26 1457.543"
77 >
78 <path
79 - fill="#17C0E9"
79 + fill="currentColor"
80 d="M254.854 137.158c-34.46 84.407-88.363 149.39-110.934 245.675 90.926-187.569 308.397-483.654 554.729-348.685 135.487 74.216 194.878 270.78 206.058 467.566 21.924 385.996-190.977 853.604-467.585 943.057-174.879 56.543-307.375-86.447-364.527-198.115-176.498-344.82 2.041-910.077 182.259-1109.498zm198.13 7.918C202.61 280.257 4.622 968.542 207.322 1270.414c51.713 77.029 194.535 160.648 285.294 71.318-209.061 31.529-288.389-176.143-301.145-340.765 31.411 147.743 139.396 326.12 309.075 253.588 251.957-107.723 376.778-648.46 269.433-966.817 22.394 134.616 15.572 317.711-47.551 412.087 86.655-230.615 7.903-704.478-269.444-554.749z"
81 ></path>
82 </svg>
83 </div>
84 - <h2 className="text-foreground text-lg font-bold leading-tight tracking-[-0.015em]">
84 + <h2 className="text-foreground text-lg font-bold leading-tight tracking-[0.3em]">
85 PORTAL ADMIN
86 </h2>
87 </div>