| 1 | import { TunnelCommandForm } from "@/components/TunnelCommandForm"; |
| 2 | |
| 3 | const coreFeatures = [ |
| 4 | { |
| 5 | eyebrow: "Ingress", |
| 6 | title: "Public HTTPS for localhost", |
| 7 | description: |
| 8 | "Publish local services through public relays without opening inbound ports.", |
| 9 | }, |
| 10 | { |
| 11 | eyebrow: "TLS", |
| 12 | title: "Keyless end-to-end tenant TLS", |
| 13 | description: |
| 14 | "Relays sign handshakes without session keys; ECH hides SNI and self-probes flag suspected MITM.", |
| 15 | }, |
| 16 | { |
| 17 | eyebrow: "Relays", |
| 18 | title: "Self-hosted anonymous relays", |
| 19 | description: |
| 20 | "Use discovered public relays or run your own without a central account or operator.", |
| 21 | }, |
| 22 | { |
| 23 | eyebrow: "Multi-hop", |
| 24 | title: "Multihop Routing (like Tor)", |
| 25 | description: |
| 26 | "Route through multiple relays so each hop sees only its neighbors, not the full path.", |
| 27 | }, |
| 28 | { |
| 29 | eyebrow: "Payments", |
| 30 | title: "x402 for the agentic web", |
| 31 | description: |
| 32 | "Agents and browsers pay with Sui USDC in-flow; the tunnel enforces access to protected routes.", |
| 33 | }, |
| 34 | { |
| 35 | eyebrow: "Transport", |
| 36 | title: "Web traffic and raw protocols", |
| 37 | description: |
| 38 | "Carry HTTPS, raw TCP, and UDP workloads without SSH or WebSocket overlays.", |
| 39 | }, |
| 40 | ] as const; |
| 41 | |
| 42 | export function LandingHero() { |
| 43 | return ( |
| 44 | <section |
| 45 | aria-labelledby="landing-title" |
| 46 | className="relative pt-10 sm:pt-12 lg:pt-14" |
| 47 | > |
| 48 | <div aria-hidden="true" className="pointer-events-none absolute inset-0"> |
| 49 | <div |
| 50 | className="absolute inset-0 opacity-45 bg-size-[14px_14px] mask-[linear-gradient(to_bottom,white,transparent_80%)]" |
| 51 | style={{ |
| 52 | backgroundImage: |
| 53 | "radial-gradient(var(--hero-grid-dot) 0.8px, transparent 0.8px)", |
| 54 | }} |
| 55 | /> |
| 56 | </div> |
| 57 | |
| 58 | <a |
| 59 | href="#live-servers" |
| 60 | className="sr-only focus:not-sr-only focus:absolute focus:left-6 focus:top-6 focus:z-20 focus:rounded-md focus:bg-background focus:px-4 focus:py-2 focus:text-sm focus:font-medium focus:text-foreground" |
| 61 | > |
| 62 | Skip to live servers |
| 63 | </a> |
| 64 | |
| 65 | <div className="relative mx-auto max-w-6xl"> |
| 66 | <div className="mx-auto max-w-4xl text-center"> |
| 67 | <h1 |
| 68 | id="landing-title" |
| 69 | className="text-4xl font-extrabold tracking-normal text-foreground sm:text-5xl lg:text-7xl" |
| 70 | style={{ lineHeight: 0.96 }} |
| 71 | > |
| 72 | <span className="block">Expose Local Apps</span> |
| 73 | <span className="mt-2 block text-primary"> |
| 74 | To The Public Internet |
| 75 | </span> |
| 76 | </h1> |
| 77 | <p className="mx-auto mt-5 max-w-2xl text-base leading-7 text-text-muted sm:text-lg"> |
| 78 | Use relays as the public edge while your tunnel remains the app |
| 79 | endpoint and owns routing behavior. |
| 80 | </p> |
| 81 | </div> |
| 82 | |
| 83 | <div id="quick-start" className="relative mt-9 scroll-mt-24 sm:mt-10"> |
| 84 | <div |
| 85 | className="relative mx-auto w-full max-w-150 rounded-lg border px-4 py-5 sm:px-5 sm:py-6" |
| 86 | style={{ |
| 87 | background: "var(--hero-terminal-bg)", |
| 88 | borderColor: "var(--hero-terminal-border)", |
| 89 | color: "var(--hero-terminal-foreground)", |
| 90 | boxShadow: "0 18px 44px var(--hero-terminal-shadow)", |
| 91 | }} |
| 92 | > |
| 93 | <div className="mb-5 flex min-w-0 items-center justify-between gap-3"> |
| 94 | <div className="flex min-w-0 items-center gap-3"> |
| 95 | <span |
| 96 | aria-hidden="true" |
| 97 | className="shrink-0 font-mono text-lg leading-none" |
| 98 | style={{ color: "var(--hero-terminal-accent)" }} |
| 99 | > |
| 100 | {">"} |
| 101 | </span> |
| 102 | <h2 |
| 103 | id="tunnel-preview" |
| 104 | className="min-w-0 text-xl font-bold tracking-normal sm:text-2xl" |
| 105 | > |
| 106 | Start a tunnel |
| 107 | </h2> |
| 108 | </div> |
| 109 | </div> |
| 110 | <TunnelCommandForm theme="terminal" mode="hero" /> |
| 111 | </div> |
| 112 | </div> |
| 113 | </div> |
| 114 | |
| 115 | <div className="relative mt-10 -mx-4 w-auto sm:-mx-6 md:-mx-8"> |
| 116 | <div className="overflow-hidden border-y border-border/80 bg-border/80"> |
| 117 | <div className="grid gap-px sm:grid-cols-2 lg:grid-cols-3"> |
| 118 | {coreFeatures.map(({ eyebrow, title, description }) => ( |
| 119 | <article |
| 120 | key={title} |
| 121 | className="flex min-h-48 bg-background p-6 text-left transition-colors duration-200 hover:bg-secondary/35 sm:min-h-52 sm:p-7" |
| 122 | > |
| 123 | <div className="flex h-full flex-col space-y-3"> |
| 124 | <p className="text-[11px] font-semibold uppercase tracking-normal text-primary/80"> |
| 125 | {eyebrow} |
| 126 | </p> |
| 127 | <h3 className="text-[1.2rem] font-semibold tracking-normal text-foreground sm:text-[1.32rem] sm:leading-tight"> |
| 128 | {title} |
| 129 | </h3> |
| 130 | <p className="max-w-[30ch] text-[0.95rem] leading-6 text-text-muted"> |
| 131 | {description} |
| 132 | </p> |
| 133 | <div className="mt-auto pt-4"> |
| 134 | <div className="h-px w-10 bg-primary/45" /> |
| 135 | </div> |
| 136 | </div> |
| 137 | </article> |
| 138 | ))} |
| 139 | </div> |
| 140 | </div> |
| 141 | </div> |
| 142 | </section> |
| 143 | ); |
| 144 | } |