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>