fix landing hero

Kim committed Mar 19, 2026 at 22:17 UTC 55d066fac393e1505d57e575a55599650027ef90
3 files changed +74 -61
frontend/src/components/Header.tsx
+5 -5
@@ -28,12 +28,12 @@ export function Header({
28
29 return (
30 <header className="flex flex-wrap items-center justify-between gap-4 px-1 py-2 sm:px-2">
31 - <div className="flex items-center gap-4 text-foreground">
31 + <div className="flex items-center gap-5 text-foreground">
32 <div className="flex h-12 w-12 items-center justify-center">
33 <svg
34 xmlns="http://www.w3.org/2000/svg"
35 - width="26"
36 - height="26"
35 + width="27"
36 + height="27"
37 viewBox="0 0 906.26 1457.543"
38 >
39 <defs>
@@ -58,7 +58,7 @@ export function Header({
58
59 <div className="space-y-1">
60 <div className="flex flex-wrap items-center gap-2">
61 - <h2 className="text-xl font-extrabold tracking-tight text-foreground">
61 + <h2 className="text-2xl font-extrabold tracking-tight text-foreground">
62 {title}
63 </h2>
64 {releaseVersion && (
@@ -72,7 +72,7 @@ export function Header({
72
73 <div className="flex flex-wrap items-center gap-3 sm:gap-4">
74 {!isAdmin && (
75 - <nav className="hidden items-center gap-5 text-sm font-medium text-text-muted md:flex">
75 + <nav className="hidden items-center gap-6 text-[15px] font-medium text-text-muted md:flex">
76 <a href="#live-servers" className="transition-colors hover:text-foreground">
77 Live apps
78 </a>
frontend/src/components/LandingHero.tsx
+17 -6
@@ -5,8 +5,15 @@ export function LandingHero() {
5 return (
6 <section
7 aria-labelledby="landing-title"
8 - className="px-0 py-8 sm:py-10 lg:py-12"
8 + className="relative overflow-hidden px-0 py-10 sm:py-12 lg:py-16"
9 >
10 + <div
11 + aria-hidden="true"
12 + className="pointer-events-none absolute inset-0"
13 + >
14 + <div className="absolute inset-0 opacity-45 [background-image:radial-gradient(rgba(99,102,241,0.12)_0.8px,transparent_0.8px)] [background-size:14px_14px] [mask-image:linear-gradient(to_bottom,white,transparent_82%)]" />
15 + </div>
16 +
17 <a
18 href="#live-servers"
19 className="sr-only focus:not-sr-only focus:absolute focus:left-6 focus:top-6 focus:z-20 focus:rounded-full focus:bg-background focus:px-4 focus:py-2 focus:text-sm focus:font-medium focus:text-foreground"
@@ -14,19 +21,23 @@ export function LandingHero() {
21 Skip to live servers
22 </a>
23
17 - <div className="mx-auto max-w-4xl text-center">
24 + <div className="relative mx-auto max-w-4xl text-center">
25 <h1
26 id="landing-title"
20 - className="text-4xl font-extrabold tracking-tight text-foreground sm:text-5xl lg:text-6xl"
27 + className="text-5xl font-extrabold tracking-tight text-foreground sm:text-6xl lg:text-7xl"
28 >
22 - Expose localhost instantly
29 + <span className="block">Expose Local Apps</span>
30 + <span className="mt-2 block bg-[linear-gradient(90deg,#7c6cff_0%,#6c8fff_46%,#57c6ff_100%)] bg-clip-text text-transparent">
31 + To The Public Internet
32 + </span>
33 </h1>
34 <p className="mx-auto mt-5 max-w-2xl text-lg leading-8 text-text-muted">
25 - No signup. No config. Public URL in seconds.
35 + Portal turns localhost into a public HTTPS URL. No port forwarding,
36 + NAT setup, or DNS configuration.
37 </p>
38 </div>
39
29 - <div className="mx-auto mt-10 max-w-[560px] rounded-[1.75rem] border border-white/10 bg-slate-950 p-5 text-white shadow-[0_30px_72px_rgba(15,23,42,0.22)] sm:p-6">
40 + <div className="relative mx-auto mt-10 max-w-[560px] rounded-[1.75rem] border border-white/10 bg-slate-950 p-5 text-white shadow-[0_30px_72px_rgba(15,23,42,0.22)] sm:p-6">
41 <div className="mb-5 flex items-center gap-3">
42 <Terminal className="h-5 w-5 text-green-status" />
43 <h2
frontend/src/components/TunnelCommandForm.tsx
+52 -50
@@ -303,27 +303,26 @@ export function TunnelCommandForm({
303 : tunnelStatus === "registered"
304 ? "URL reserved"
305 : "Waiting";
306 - const tunnelStatusLabel =
307 - tunnelStatus === "alive"
308 - ? ""
309 - : tunnelStatus === "registered"
310 - ? "Tunnel registered on this relay"
311 - : "";
306 + const heroSectionLabelClass = cn(
307 + "text-xs font-semibold uppercase tracking-[0.24em]",
308 + isTerminal ? "text-slate-400" : "text-text-muted"
309 + );
310
311 const commandSection = (
312 <div className={cn("space-y-2", isHero && "space-y-3")}>
315 - <label
316 - className={cn(
317 - "text-sm font-medium",
318 - isTerminal ? "text-slate-200" : "text-foreground"
319 - )}
320 - >
321 - {isHero ? "Copy this command" : "Generated Command"}
322 - </label>
323 - {isHero && (
324 - <p className="text-xs leading-5 text-slate-400 sm:text-sm">
325 - Copy and paste it into your terminal to publish a public URL.
326 - </p>
313 + {isHero ? (
314 + <label className={heroSectionLabelClass}>
315 + Command
316 + </label>
317 + ) : (
318 + <label
319 + className={cn(
320 + "text-sm font-medium",
321 + isTerminal ? "text-slate-200" : "text-foreground"
322 + )}
323 + >
324 + Generated Command
325 + </label>
326 )}
327 <div className="relative">
328 <pre
@@ -374,43 +373,38 @@ export function TunnelCommandForm({
373 )}
374
375 {isHero && (
377 - <div
378 - className={cn(
379 - "space-y-2 rounded-xl border px-4 py-3",
380 - isTerminal ? "border-white/10 bg-white/5" : "border-border bg-white"
381 - )}
382 - >
383 - <div
384 - className={cn(
385 - "flex items-center gap-2 text-sm font-semibold",
386 - isTerminal ? "text-slate-200" : "text-foreground"
387 - )}
388 - >
389 - <span className={cn("h-2 w-2 rounded-full", tunnelStatusTone)} aria-hidden="true" />
390 - <span>{tunnelStatusHeadline}</span>
391 - </div>
392 - <a
393 - href={previewURL}
394 - target="_blank"
395 - rel="noopener noreferrer"
396 - className={cn(
397 - "block overflow-x-auto whitespace-nowrap font-mono text-base font-medium underline-offset-4 hover:underline sm:text-lg",
398 - isTerminal ? "text-green-status" : "text-primary"
399 - )}
400 - >
401 - {previewURL}
402 - </a>
403 - {tunnelStatusLabel !== "" && (
376 + <>
377 + <div className="space-y-1.5 pt-1">
378 + <p className={heroSectionLabelClass}>Public URL</p>
379 <div
380 className={cn(
406 - "flex items-center gap-2 text-xs font-medium",
407 - isTerminal ? "text-slate-300" : "text-text-muted"
381 + "space-y-4 rounded-xl border px-4 py-3",
382 + isTerminal ? "border-white/10 bg-white/5" : "border-border bg-white"
383 )}
384 >
410 - <span>{tunnelStatusLabel}</span>
385 + <div
386 + className={cn(
387 + "flex items-center gap-2 text-sm font-semibold",
388 + isTerminal ? "text-slate-200" : "text-foreground"
389 + )}
390 + >
391 + <span className={cn("h-2 w-2 rounded-full", tunnelStatusTone)} aria-hidden="true" />
392 + <span>{tunnelStatusHeadline}</span>
393 + </div>
394 + <a
395 + href={previewURL}
396 + target="_blank"
397 + rel="noopener noreferrer"
398 + className={cn(
399 + "block overflow-x-auto whitespace-nowrap font-mono text-base font-medium underline-offset-4 hover:underline sm:text-lg",
400 + isTerminal ? "text-green-status" : "text-primary"
401 + )}
402 + >
403 + {previewURL}
404 + </a>
405 </div>
412 - )}
413 - </div>
406 + </div>
407 + </>
408 )}
409 </div>
410 );
@@ -434,10 +428,18 @@ export function TunnelCommandForm({
428 ? "border-white/8 bg-black/20 text-slate-300 hover:text-white"
429 : "border-border bg-white text-text-muted hover:text-foreground"
430 );
431 + const optionSectionLabel = isHero ? (
432 + <div className="pt-1">
433 + <p className={heroSectionLabelClass}>
434 + Option
435 + </p>
436 + </div>
437 + ) : null;
438
439 return (
440 <div className={cn("space-y-5", className)}>
441 {isHero && commandSection}
442 + {optionSectionLabel}
443
444 {isHero ? (
445 <div className="grid gap-3 sm:grid-cols-2">