remove default thumnail image.
Hee Sung Son committed
Nov 24, 2025 at 21:01 UTC
03d4633a837c5ef9c6997455e622617282b2915c
2 files changed
+102
-59
cmd/relay-server/frontend/src/components/ServerCard.tsx
+1
-4
@@ -25,9 +25,6 @@ export function ServerCard({
25
navigationPath,
26
navigationState,
27
}: ServerCardProps) {
28
- const defaultThumbnail =
29
- "https://cdn.jsdelivr.net/gh/gosuda/portal@main/portal.jpg";
30
-
28
return (
29
<Link
30
to={navigationPath}
@@ -37,7 +34,7 @@ export function ServerCard({
34
<div
35
data-hero-key={`server-bg-${serverId}`}
36
className="relative h-[174.5px] bg-center bg-no-repeat bg-cover rounded-xl shadow-lg hover:shadow-xl transition-shadow duration-300 cursor-pointer z-1 border border-foreground/40"
40
- style={{ backgroundImage: `url(${thumbnail || defaultThumbnail})` }}
37
+ style={{ ...(thumbnail && { backgroundImage: `url(${thumbnail})` }) }}
38
>
39
{/* Content overlay - not part of hero transition */}
40
<div className="relative h-full w-full bg-background/80 rounded-xl flex flex-col gap-4 p-4 items-start text-start">
cmd/relay-server/frontend/src/pages/ServerDetail.tsx
+101
-55
@@ -18,13 +18,15 @@ export function ServerDetail() {
18
const navigate = useNavigate();
19
const server = location.state as ServerDetailState;
20
21
- const isPush = () => localStorage.getItem("isPush") === "true";
22
-
21
+ // Detect back navigation using pageshow event
22
useEffect(() => {
23
const handlePageShow = () => {
25
- if (isPush()) {
24
+ // Navigate to home if restored from bfcache or has push flag in localStorage
25
+ const hasPushFlag = localStorage.getItem("isPush") === "true";
26
+
27
+ if (hasPushFlag) {
28
localStorage.removeItem("isPush");
27
- navigate("/");
29
+ navigate("/", { replace: true });
30
}
31
};
32
@@ -33,21 +35,30 @@ export function ServerDetail() {
35
return () => {
36
window.removeEventListener("pageshow", handlePageShow);
37
};
36
- }, []);
38
+ }, [navigate]);
39
40
useEffect(() => {
41
if (typeof localStorage === "undefined") return;
42
+ if (!server) {
43
+ navigate("/");
44
+ return;
45
+ }
46
+
47
+ // Check localStorage immediately (before pageshow)
48
+ const hasPushFlag = localStorage.getItem("isPush") === "true";
49
41
- // Redirect to actual server URL after animation
50
+ if (hasPushFlag) {
51
+ // If flag already exists, go home (double-check with pageshow handler)
52
+ localStorage.removeItem("isPush");
53
+ navigate("/");
54
+ return;
55
+ }
56
+
57
+ // Redirect after animation
58
const timer = setTimeout(() => {
43
- if (isPush()) {
44
- localStorage.removeItem("isPush");
45
- navigate("/");
46
- } else {
47
- localStorage.setItem("isPush", "true");
48
- window.location.href = server.serverUrl;
49
- }
50
- }, 300);
59
+ localStorage.setItem("isPush", "true");
60
+ window.location.href = server.serverUrl;
61
+ }, 500);
62
63
return () => {
64
clearTimeout(timer);
@@ -61,59 +72,94 @@ export function ServerDetail() {
72
73
const { id, thumbnail, name, online, description, tags, owner } = server;
74
64
- const defaultThumbnail =
65
- "https://cdn.jsdelivr.net/gh/gosuda/portal@main/portal.jpg";
75
+ // Base size multiplier (1 = default, 2 = 2x size)
76
+ const basicSize = 2.5;
77
78
return (
79
<SsgoiTransition id={`/server/${id}`}>
80
<div
81
data-hero-key={`server-bg-${id}`}
82
className="fixed inset-0 bg-center bg-no-repeat bg-cover w-screen h-screen"
72
- style={{ backgroundImage: `url(${thumbnail || defaultThumbnail})` }}
83
+ style={{ ...(thumbnail && { backgroundImage: `url(${thumbnail})` }) }}
84
>
74
- {/* Content overlay - not part of hero transition */}
75
- <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 h-[174.5px] w-full min-[500px]:w-[50%] md:w-[33.33%] bg-background/70 rounded-xl flex flex-col gap-4 p-4 items-start text-start z-1">
76
- <div className="w-full flex flex-1 flex-col justify-between gap-4">
77
- <div className="flex flex-col gap-2">
78
- <div className="flex items-center gap-2">
85
+ {/* Content overlay - Full screen */}
86
+ <div className="absolute inset-0 flex items-center justify-center p-6 md:p-12">
87
+ <div className="w-full h-full max-w-7xl bg-background/70 backdrop-blur-sm rounded-xl flex flex-col gap-6 p-8 md:p-12 items-start text-start">
88
+ <div className="w-full h-full flex flex-col justify-center gap-6 md:gap-8">
89
+ <div
90
+ className="flex flex-col"
91
+ style={{ gap: `${1 * basicSize}%` }}
92
+ >
93
<div
80
- className={`w-2.5 h-2.5 rounded-full ${
81
- online ? "bg-green-status" : "bg-red-500"
82
- }`}
83
- />
94
+ className="flex items-center"
95
+ style={{ gap: `${0.8 * basicSize}%` }}
96
+ >
97
+ <div
98
+ className={`rounded-full ${
99
+ online ? "bg-green-status" : "bg-red-500"
100
+ }`}
101
+ style={{
102
+ width: `${1 * basicSize}vw`,
103
+ height: `${1 * basicSize}vw`,
104
+ }}
105
+ />
106
+ <p
107
+ className={`font-medium leading-normal ${
108
+ online ? "text-green-status" : "text-red-500"
109
+ }`}
110
+ style={{ fontSize: `${1.8 * basicSize}vw` }}
111
+ >
112
+ {online ? "Online" : "Offline"}
113
+ </p>
114
+ </div>
115
<p
85
- className={`text-sm font-medium leading-normal ${
86
- online ? "text-green-status" : "text-red-500"
87
- }`}
116
+ className="text-foreground font-bold leading-tight"
117
+ style={{ fontSize: `${6 * basicSize}vw` }}
118
>
89
- {online ? "Online" : "Offline"}
119
+ {name}
120
</p>
121
+ {description && (
122
+ <p
123
+ className="text-text-muted font-normal leading-normal max-w-4xl"
124
+ style={{ fontSize: `${2.5 * basicSize}vw` }}
125
+ >
126
+ {description}
127
+ </p>
128
+ )}
129
+ {tags && tags.length > 0 && (
130
+ <div
131
+ className="flex flex-wrap"
132
+ style={{
133
+ gap: `${0.8 * basicSize}vw`,
134
+ marginTop: `${0.5 * basicSize}%`,
135
+ }}
136
+ >
137
+ {tags.map((tag, index) => (
138
+ <span
139
+ key={index}
140
+ className="bg-secondary text-primary font-medium rounded-lg"
141
+ style={{
142
+ padding: `${0.6 * basicSize}vw ${1.2 * basicSize}vw`,
143
+ fontSize: `${1.5 * basicSize}vw`,
144
+ }}
145
+ >
146
+ {tag}
147
+ </span>
148
+ ))}
149
+ </div>
150
+ )}
151
+ {owner && (
152
+ <p
153
+ className="text-text-muted font-normal leading-normal"
154
+ style={{
155
+ fontSize: `${1.8 * basicSize}vw`,
156
+ marginTop: `${1 * basicSize}%`,
157
+ }}
158
+ >
159
+ by {owner}
160
+ </p>
161
+ )}
162
</div>
92
- <p className="text-foreground text-lg font-bold leading-tight truncate max-w-full">
93
- {name}
94
- </p>
95
- {description && (
96
- <p className="text-text-muted text-sm font-normal leading-normal truncate max-w-full">
97
- {description}
98
- </p>
99
- )}
100
- {tags && tags.length > 0 && (
101
- <div className="flex flex-wrap gap-1.5 mt-1">
102
- {tags.map((tag, index) => (
103
- <span
104
- key={index}
105
- className="px-2 py-1 bg-secondary text-primary text-xs font-medium rounded truncate max-w-[120px]"
106
- >
107
- {tag}
108
- </span>
109
- ))}
110
- </div>
111
- )}
112
- {owner && (
113
- <p className="text-text-muted text-xs font-normal leading-normal truncate max-w-full">
114
- by {owner}
115
- </p>
116
- )}
163
</div>
164
</div>
165
</div>