Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol. wisp.place
at v1.0.0 10 kB view raw
1import { useState, useRef, useEffect } from 'react' 2import { createRoot } from 'react-dom/client' 3import { 4 ArrowRight, 5 Shield, 6 Zap, 7 Globe, 8 Lock, 9 Code, 10 Server 11} from 'lucide-react' 12 13import Layout from '@public/layouts' 14import { Button } from '@public/components/ui/button' 15import { Card } from '@public/components/ui/card' 16 17function App() { 18 const [showForm, setShowForm] = useState(false) 19 const inputRef = useRef<HTMLInputElement>(null) 20 21 useEffect(() => { 22 if (showForm) { 23 setTimeout(() => inputRef.current?.focus(), 500) 24 } 25 }, [showForm]) 26 27 return ( 28 <> 29 <div className="min-h-screen"> 30 {/* Header */} 31 <header className="border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50"> 32 <div className="container mx-auto px-4 py-4 flex items-center justify-between"> 33 <div className="flex items-center gap-2"> 34 <div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center"> 35 <Globe className="w-5 h-5 text-primary-foreground" /> 36 </div> 37 <span className="text-xl font-semibold text-foreground"> 38 wisp.place 39 </span> 40 </div> 41 <div className="flex items-center gap-3"> 42 <Button 43 variant="ghost" 44 size="sm" 45 onClick={() => setShowForm(true)} 46 > 47 Sign In 48 </Button> 49 <Button 50 size="sm" 51 className="bg-accent text-accent-foreground hover:bg-accent/90" 52 > 53 Get Started 54 </Button> 55 </div> 56 </div> 57 </header> 58 59 {/* Hero Section */} 60 <section className="container mx-auto px-4 py-20 md:py-32"> 61 <div className="max-w-4xl mx-auto text-center"> 62 <div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-accent/10 border border-accent/20 mb-8"> 63 <span className="w-2 h-2 bg-accent rounded-full animate-pulse"></span> 64 <span className="text-sm text-accent-foreground"> 65 Built on AT Protocol 66 </span> 67 </div> 68 69 <h1 className="text-5xl md:text-7xl font-bold text-balance mb-6 leading-tight"> 70 Your Website.Your Control. Lightning Fast. 71 </h1> 72 73 <p className="text-xl md:text-2xl text-muted-foreground text-balance mb-10 leading-relaxed max-w-3xl mx-auto"> 74 Host static sites in your AT Protocol account. You 75 keep ownership and control. We just serve them fast 76 through our CDN. 77 </p> 78 79 <div className="max-w-md mx-auto relative"> 80 <div 81 className={`transition-all duration-500 ease-in-out ${ 82 showForm 83 ? 'opacity-0 -translate-y-5 pointer-events-none' 84 : 'opacity-100 translate-y-0' 85 }`} 86 > 87 <Button 88 size="lg" 89 className="bg-primary text-primary-foreground hover:bg-primary/90 text-lg px-8 py-6 w-full" 90 onClick={() => setShowForm(true)} 91 > 92 Log in with AT Proto 93 <ArrowRight className="ml-2 w-5 h-5" /> 94 </Button> 95 </div> 96 97 <div 98 className={`transition-all duration-500 ease-in-out absolute inset-0 ${ 99 showForm 100 ? 'opacity-100 translate-y-0' 101 : 'opacity-0 translate-y-5 pointer-events-none' 102 }`} 103 > 104 <form 105 onSubmit={async (e) => { 106 e.preventDefault() 107 try { 108 const handle = 109 inputRef.current?.value 110 const res = await fetch( 111 '/api/auth/signin', 112 { 113 method: 'POST', 114 headers: { 115 'Content-Type': 116 'application/json' 117 }, 118 body: JSON.stringify({ 119 handle 120 }) 121 } 122 ) 123 if (!res.ok) 124 throw new Error( 125 'Request failed' 126 ) 127 const data = await res.json() 128 if (data.url) { 129 window.location.href = data.url 130 } else { 131 alert('Unexpected response') 132 } 133 } catch (error) { 134 console.error( 135 'Login failed:', 136 error 137 ) 138 alert('Authentication failed') 139 } 140 }} 141 className="space-y-3" 142 > 143 <input 144 ref={inputRef} 145 type="text" 146 name="handle" 147 placeholder="Enter your handle (e.g., alice.bsky.social)" 148 className="w-full py-4 px-4 text-lg bg-input border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-accent" 149 /> 150 <button 151 type="submit" 152 className="w-full bg-accent hover:bg-accent/90 text-accent-foreground font-semibold py-4 px-6 text-lg rounded-lg inline-flex items-center justify-center transition-colors" 153 > 154 Continue 155 <ArrowRight className="ml-2 w-5 h-5" /> 156 </button> 157 </form> 158 </div> 159 </div> 160 </div> 161 </section> 162 163 {/* How It Works */} 164 <section className="container mx-auto px-4 py-16 bg-muted/30"> 165 <div className="max-w-3xl mx-auto text-center"> 166 <h2 className="text-3xl md:text-4xl font-bold mb-8"> 167 How it works 168 </h2> 169 <div className="space-y-6 text-left"> 170 <div className="flex gap-4 items-start"> 171 <div className="text-4xl font-bold text-accent/40 min-w-[60px]"> 172 01 173 </div> 174 <div> 175 <h3 className="text-xl font-semibold mb-2"> 176 Upload your static site 177 </h3> 178 <p className="text-muted-foreground"> 179 Your HTML, CSS, and JavaScript files are 180 stored in your AT Protocol account as 181 gzipped blobs and a manifest record. 182 </p> 183 </div> 184 </div> 185 <div className="flex gap-4 items-start"> 186 <div className="text-4xl font-bold text-accent/40 min-w-[60px]"> 187 02 188 </div> 189 <div> 190 <h3 className="text-xl font-semibold mb-2"> 191 We serve it globally 192 </h3> 193 <p className="text-muted-foreground"> 194 Wisp.place reads your site from your 195 account and delivers it through our CDN 196 for fast loading anywhere. 197 </p> 198 </div> 199 </div> 200 <div className="flex gap-4 items-start"> 201 <div className="text-4xl font-bold text-accent/40 min-w-[60px]"> 202 03 203 </div> 204 <div> 205 <h3 className="text-xl font-semibold mb-2"> 206 You stay in control 207 </h3> 208 <p className="text-muted-foreground"> 209 Update or remove your site anytime 210 through your AT Protocol account. No 211 lock-in, no middleman ownership. 212 </p> 213 </div> 214 </div> 215 </div> 216 </div> 217 </section> 218 219 {/* Features Grid */} 220 <section id="features" className="container mx-auto px-4 py-20"> 221 <div className="text-center mb-16"> 222 <h2 className="text-4xl md:text-5xl font-bold mb-4 text-balance"> 223 Why Wisp.place? 224 </h2> 225 <p className="text-xl text-muted-foreground text-balance max-w-2xl mx-auto"> 226 Static site hosting that respects your ownership 227 </p> 228 </div> 229 230 <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6 max-w-6xl mx-auto"> 231 {[ 232 { 233 icon: Shield, 234 title: 'You Own Your Content', 235 description: 236 'Your site lives in your AT Protocol account. Move it to another service anytime, or take it offline yourself.' 237 }, 238 { 239 icon: Zap, 240 title: 'CDN Performance', 241 description: 242 'We cache and serve your site from edge locations worldwide for fast load times.' 243 }, 244 { 245 icon: Lock, 246 title: 'No Vendor Lock-in', 247 description: 248 'Your data stays in your account. Switch providers or self-host whenever you want.' 249 }, 250 { 251 icon: Code, 252 title: 'Simple Deployment', 253 description: 254 'Upload your static files and we handle the rest. No complex configuration needed.' 255 }, 256 { 257 icon: Server, 258 title: 'AT Protocol Native', 259 description: 260 'Built for the decentralized web. Your site has a verifiable identity on the network.' 261 }, 262 { 263 icon: Globe, 264 title: 'Custom Domains', 265 description: 266 'Use your own domain name or a wisp.place subdomain. Your choice, either way.' 267 } 268 ].map((feature, i) => ( 269 <Card 270 key={i} 271 className="p-6 hover:shadow-lg transition-shadow border-2 bg-card" 272 > 273 <div className="w-12 h-12 rounded-lg bg-accent/10 flex items-center justify-center mb-4"> 274 <feature.icon className="w-6 h-6 text-accent" /> 275 </div> 276 <h3 className="text-xl font-semibold mb-2 text-card-foreground"> 277 {feature.title} 278 </h3> 279 <p className="text-muted-foreground leading-relaxed"> 280 {feature.description} 281 </p> 282 </Card> 283 ))} 284 </div> 285 </section> 286 287 {/* CTA Section */} 288 <section className="container mx-auto px-4 py-20"> 289 <div className="max-w-3xl mx-auto text-center bg-accent/5 border border-accent/20 rounded-2xl p-12"> 290 <h2 className="text-3xl md:text-4xl font-bold mb-4"> 291 Ready to deploy? 292 </h2> 293 <p className="text-xl text-muted-foreground mb-8"> 294 Host your static site on your own AT Protocol 295 account today 296 </p> 297 <Button 298 size="lg" 299 className="bg-accent text-accent-foreground hover:bg-accent/90 text-lg px-8 py-6" 300 onClick={() => setShowForm(true)} 301 > 302 Get Started 303 <ArrowRight className="ml-2 w-5 h-5" /> 304 </Button> 305 </div> 306 </section> 307 308 {/* Footer */} 309 <footer className="border-t border-border/40 bg-muted/20"> 310 <div className="container mx-auto px-4 py-8"> 311 <div className="text-center text-sm text-muted-foreground"> 312 <p> 313 Built by{' '} 314 <a 315 href="https://bsky.app/profile/nekomimi.pet" 316 target="_blank" 317 rel="noopener noreferrer" 318 className="text-accent hover:text-accent/80 transition-colors font-medium" 319 > 320 @nekomimi.pet 321 </a> 322 </p> 323 </div> 324 </div> 325 </footer> 326 </div> 327 </> 328 ) 329} 330 331const root = createRoot(document.getElementById('elysia')!) 332root.render( 333 <Layout className="gap-6"> 334 <App /> 335 </Layout> 336)