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>