forked from
nekomimi.pet/wisp.place-monorepo
Monorepo for Wisp.place. A static site hosting service built on top of the AT Protocol.
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 <div className="min-h-screen">
29 {/* Header */}
30 <header className="border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
31 <div className="container mx-auto px-4 py-4 flex items-center justify-between">
32 <div className="flex items-center gap-2">
33 <div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center">
34 <Globe className="w-5 h-5 text-primary-foreground" />
35 </div>
36 <span className="text-xl font-semibold text-foreground">
37 wisp.place
38 </span>
39 </div>
40 <div className="flex items-center gap-3">
41 <Button
42 variant="ghost"
43 size="sm"
44 onClick={() => setShowForm(true)}
45 >
46 Sign In
47 </Button>
48 <Button
49 size="sm"
50 className="bg-accent text-accent-foreground hover:bg-accent/90"
51 >
52 Get Started
53 </Button>
54 </div>
55 </div>
56 </header>
57
58 {/* Hero Section */}
59 <section className="container mx-auto px-4 py-20 md:py-32">
60 <div className="max-w-4xl mx-auto text-center">
61 <div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-accent/10 border border-accent/20 mb-8">
62 <span className="w-2 h-2 bg-accent rounded-full animate-pulse"></span>
63 <span className="text-sm text-accent-foreground">
64 Built on AT Protocol
65 </span>
66 </div>
67
68 <h1 className="text-5xl md:text-7xl font-bold text-balance mb-6 leading-tight">
69 Host your sites on the{' '}
70 <span className="text-primary">decentralized</span> web
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 Deploy static sites to a truly open network. Your
75 content, your control, your identity. No platform
76 lock-in, ever.
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 = inputRef.current?.value
109 const res = await fetch(
110 '/api/auth/signin',
111 {
112 method: 'POST',
113 headers: {
114 'Content-Type':
115 'application/json'
116 },
117 body: JSON.stringify({ handle })
118 }
119 )
120 if (!res.ok)
121 throw new Error('Request failed')
122 const data = await res.json()
123 if (data.url) {
124 window.location.href = data.url
125 } else {
126 alert('Unexpected response')
127 }
128 } catch (error) {
129 console.error('Login failed:', error)
130 alert('Authentication failed')
131 }
132 }}
133 className="space-y-3"
134 >
135 <input
136 ref={inputRef}
137 type="text"
138 name="handle"
139 placeholder="Enter your handle (e.g., alice.bsky.social)"
140 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"
141 />
142 <button
143 type="submit"
144 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"
145 >
146 Continue
147 <ArrowRight className="ml-2 w-5 h-5" />
148 </button>
149 </form>
150 </div>
151 </div>
152 </div>
153 </section>
154
155 {/* Stats Section */}
156 <section className="container mx-auto px-4 py-16">
157 <div className="grid grid-cols-2 md:grid-cols-4 gap-8 max-w-5xl mx-auto">
158 {[
159 { value: '100%', label: 'Decentralized' },
160 { value: '0ms', label: 'Cold Start' },
161 { value: '∞', label: 'Scalability' },
162 { value: 'You', label: 'Own Your Data' }
163 ].map((stat, i) => (
164 <div key={i} className="text-center">
165 <div className="text-4xl md:text-5xl font-bold text-primary mb-2">
166 {stat.value}
167 </div>
168 <div className="text-sm text-muted-foreground">
169 {stat.label}
170 </div>
171 </div>
172 ))}
173 </div>
174 </section>
175
176 {/* Features Grid */}
177 <section id="features" className="container mx-auto px-4 py-20">
178 <div className="text-center mb-16">
179 <h2 className="text-4xl md:text-5xl font-bold mb-4 text-balance">
180 Built for the open web
181 </h2>
182 <p className="text-xl text-muted-foreground text-balance max-w-2xl mx-auto">
183 Everything you need to deploy and manage static sites on
184 a decentralized network
185 </p>
186 </div>
187
188 <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6 max-w-6xl mx-auto">
189 {[
190 {
191 icon: Shield,
192 title: 'True Ownership',
193 description:
194 'Your content lives on the AT Protocol network. No single company can take it down or lock you out.'
195 },
196 {
197 icon: Zap,
198 title: 'Lightning Fast',
199 description:
200 'Distributed edge network ensures your sites load instantly from anywhere in the world.'
201 },
202 {
203 icon: Lock,
204 title: 'Cryptographic Security',
205 description:
206 'Content-addressed storage and cryptographic verification ensure integrity and authenticity.'
207 },
208 {
209 icon: Code,
210 title: 'Developer Friendly',
211 description:
212 'Simple CLI, Git integration, and familiar workflows. Deploy with a single command.'
213 },
214 {
215 icon: Server,
216 title: 'Zero Vendor Lock-in',
217 description:
218 'Built on open protocols. Migrate your sites anywhere, anytime. Your data is portable.'
219 },
220 {
221 icon: Globe,
222 title: 'Global Network',
223 description:
224 'Leverage the power of decentralized infrastructure for unmatched reliability and uptime.'
225 }
226 ].map((feature, i) => (
227 <Card
228 key={i}
229 className="p-6 hover:shadow-lg transition-shadow border-2 bg-card"
230 >
231 <div className="w-12 h-12 rounded-lg bg-accent/10 flex items-center justify-center mb-4">
232 <feature.icon className="w-6 h-6 text-accent" />
233 </div>
234 <h3 className="text-xl font-semibold mb-2 text-card-foreground">
235 {feature.title}
236 </h3>
237 <p className="text-muted-foreground leading-relaxed">
238 {feature.description}
239 </p>
240 </Card>
241 ))}
242 </div>
243 </section>
244
245 {/* How It Works */}
246 <section
247 id="how-it-works"
248 className="container mx-auto px-4 py-20 bg-muted/30"
249 >
250 <div className="max-w-4xl mx-auto">
251 <h2 className="text-4xl md:text-5xl font-bold text-center mb-16 text-balance">
252 Deploy in three steps
253 </h2>
254
255 <div className="space-y-12">
256 {[
257 {
258 step: '01',
259 title: 'Upload your site',
260 description:
261 'Link your Git repository or upload a folder containing your static site directly.'
262 },
263 {
264 step: '02',
265 title: 'Name and set domain',
266 description:
267 'Name your site and set domain routing to it. You can bring your own domain too.'
268 },
269 {
270 step: '03',
271 title: 'Deploy to AT Protocol',
272 description:
273 'Your site is published to the decentralized network with a permanent, verifiable identity.'
274 }
275 ].map((step, i) => (
276 <div key={i} className="flex gap-6 items-start">
277 <div className="text-6xl font-bold text-accent/20 min-w-[80px]">
278 {step.step}
279 </div>
280 <div className="flex-1 pt-2">
281 <h3 className="text-2xl font-semibold mb-3">
282 {step.title}
283 </h3>
284 <p className="text-lg text-muted-foreground leading-relaxed">
285 {step.description}
286 </p>
287 </div>
288 </div>
289 ))}
290 </div>
291 </div>
292 </section>
293
294 {/* Footer */}
295 <footer className="border-t border-border/40 bg-muted/20">
296 <div className="container mx-auto px-4 py-8">
297 <div className="text-center text-sm text-muted-foreground">
298 <p>
299 Built by{' '}
300 <a
301 href="https://bsky.app/profile/nekomimi.pet"
302 target="_blank"
303 rel="noopener noreferrer"
304 className="text-accent hover:text-accent/80 transition-colors font-medium"
305 >
306 @nekomimi.pet
307 </a>
308 </p>
309 </div>
310 </div>
311 </footer>
312 </div>
313 )
314}
315
316const root = createRoot(document.getElementById('elysia')!)
317root.render(
318 <Layout className="gap-6">
319 <App />
320 </Layout>
321)