Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesPull RequestsActionsSecurityInsightsSettings
✨ AI
More
Blame · Line-by-line history

landing.tsx

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

landing.tsxBlame390 lines · 1 contributor
2b821b7Claude1/**
2 * Marketing landing page for logged-out visitors.
3 *
4c47454Claude4 * Hero-first redesign — Linear/Cursor tier launch page.
5 * Pure presentational component. Drops into <Layout user={null}>.
6 * All styles scoped under `.landing-` class prefix so they don't leak.
2b821b7Claude7 *
4c47454Claude8 * Tone: confident, technical, specific. No marketing buzzwords.
9 *
10 * Falls back gracefully when newer design tokens (--bg-surface, --accent-2,
11 * --accent-gradient, --t-*, --s-*, --r-*, --ease, etc.) aren't yet defined,
12 * by using `var(--token, fallback)` everywhere.
2b821b7Claude13 */
14
15import type { FC } from "hono/jsx";
16
17export interface LandingPageProps {
18 stats?: {
19 publicRepos?: number;
20 users?: number;
21 };
22}
23
4c47454Claude24// Public exports — both names kept for backwards compatibility with the
25// web.tsx import. <LandingHero /> is a focused hero-only render; <LandingPage>
26// renders the same (the hero IS the page in the new design).
27export const LandingHero: FC<LandingPageProps> = ({ stats } = {}) => {
8e9f1d9Claude28 const hasStats =
29 stats &&
30 ((stats.publicRepos !== undefined && stats.publicRepos > 0) ||
31 (stats.users !== undefined && stats.users > 0));
4c47454Claude32
2b821b7Claude33 return (
34 <>
35 <style>{landingCss}</style>
36
4c47454Claude37 <div class="landing-root">
38 {/* Background radial blob — purely decorative */}
39 <div class="landing-blob" aria-hidden="true" />
2b821b7Claude40
4c47454Claude41 {/* ---------- Hero ---------- */}
42 <section class="landing-hero">
43 <h1 class="landing-hero-title">
44 Where software{" "}
45 <span class="landing-hero-grad">lives.</span>
46 </h1>
47 <p class="landing-hero-sub">
48 AI-native code intelligence. Self-hosting, automated CI, push-time
49 gate enforcement. Your software ships itself, fixes itself, gets
50 better every day.
51 </p>
52 <div class="landing-hero-ctas">
53 <a href="/register" class="btn btn-primary btn-lg landing-cta-primary">
54 Get started
55 <span class="landing-cta-arrow" aria-hidden="true">{"→"}</span>
56 </a>
57 <a href="/login" class="btn btn-ghost btn-lg landing-cta-secondary">
58 Sign in
59 </a>
2b821b7Claude60 </div>
4c47454Claude61 <p class="landing-hero-caption">
62 Already have a repo?{" "}
63 <kbd class="landing-kbd">git</kbd>
64 <span class="landing-kbd-sep">{" "}</span>
65 <kbd class="landing-kbd">push</kbd>
66 {" "}it directly.
67 </p>
2b821b7Claude68
4c47454Claude69 {hasStats && (
70 <p class="landing-stats">
71 {stats!.publicRepos !== undefined && stats!.publicRepos > 0 && (
72 <span>
73 <strong>{stats!.publicRepos.toLocaleString()}</strong> public
74 {stats!.publicRepos === 1 ? " repo" : " repos"}
75 </span>
76 )}
77 {stats!.publicRepos !== undefined &&
78 stats!.publicRepos > 0 &&
79 stats!.users !== undefined &&
80 stats!.users > 0 && <span class="landing-stats-sep"> &middot; </span>}
81 {stats!.users !== undefined && stats!.users > 0 && (
82 <span>
83 <strong>{stats!.users.toLocaleString()}</strong>
84 {stats!.users === 1 ? " developer" : " developers"}
85 </span>
86 )}
2b821b7Claude87 </p>
4c47454Claude88 )}
89 </section>
90
91 {/* ---------- Feature grid ---------- */}
92 <section class="landing-features">
93 <div class="landing-feature">
94 <div class="landing-feature-icon" aria-hidden="true">
95 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
96 <path d="M12 3l1.9 4.6L18.5 9l-3.6 3 1 4.8L12 14.5 8.1 16.8l1-4.8L5.5 9l4.6-1.4z" />
97 </svg>
98 </div>
99 <h3 class="landing-feature-title">AI as a teammate</h3>
100 <p class="landing-feature-desc">
101 Spec-to-PR drafts entire features from plain English. Auto-explain
102 reviews every diff. The AI commits with its own account, visible
103 in your history.
2b821b7Claude104 </p>
105 </div>
4c47454Claude106
107 <div class="landing-feature">
108 <div class="landing-feature-icon" aria-hidden="true">
109 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
110 <path d="M12 2.5l8 3.5v6c0 5-3.5 8.5-8 9.5-4.5-1-8-4.5-8-9.5v-6z" />
111 <path d="M9 12l2 2 4-4" />
112 </svg>
113 </div>
114 <h3 class="landing-feature-title">Quality gate that learns</h3>
115 <p class="landing-feature-desc">
116 GateTest scans every push. Auto-repair fixes regressions before
117 you see them. Required checks block bad PRs from merging.
2b821b7Claude118 </p>
119 </div>
120
4c47454Claude121 <div class="landing-feature">
122 <div class="landing-feature-icon" aria-hidden="true">
123 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
124 <path d="M13 2L4 14h7l-1 8 9-12h-7z" />
125 </svg>
126 </div>
127 <h3 class="landing-feature-title">Real-time everything</h3>
128 <p class="landing-feature-desc">
129 Live workflow logs streaming over SSE. Live PR review presence.
130 Live deploys you watch happen. No polling, no refresh.
131 </p>
132 </div>
133 </section>
2b821b7Claude134
4c47454Claude135 {/* ---------- Terminal block ---------- */}
136 <section class="landing-terminal-wrap">
137 <div class="landing-terminal" role="img" aria-label="Example git push to gluecron with passing gates">
138 <div class="landing-term-line">
139 <span class="landing-term-prompt">$</span>
140 <span>git remote add gluecron https://gluecron.com/you/your-repo.git</span>
141 </div>
142 <div class="landing-term-line">
143 <span class="landing-term-prompt">$</span>
144 <span>git push -u gluecron main</span>
145 </div>
146 <div class="landing-term-line">
147 <span class="landing-term-ok">{"✓"}</span>
148 <span>pushed to gluecron.com/you/your-repo</span>
149 </div>
150 <div class="landing-term-line">
151 <span class="landing-term-ok">{"✓"}</span>
152 <span>GateTest passed (12 rules, 0 violations)</span>
153 </div>
154 <div class="landing-term-line">
155 <span class="landing-term-ok">{"✓"}</span>
156 <span>deployed to your-repo.gluecron.com</span>
157 </div>
158 </div>
159 </section>
160 </div>
2b821b7Claude161 </>
162 );
163};
164
4c47454Claude165// Backwards-compatible default — web.tsx imports `LandingPage`.
166export const LandingPage: FC<LandingPageProps> = (props) => (
167 <LandingHero {...props} />
2b821b7Claude168);
169
4c47454Claude170export default LandingPage;
171
2b821b7Claude172const landingCss = `
4c47454Claude173 /* ---------- Root + fade-in ---------- */
174 .landing-root {
175 position: relative;
176 max-width: 1080px;
177 margin: 0 auto;
178 padding: 0 16px;
179 opacity: 0;
180 animation: landingFadeUp 600ms var(--ease, cubic-bezier(0.2, 0.8, 0.2, 1)) forwards;
181 overflow: hidden;
182 }
183 @keyframes landingFadeUp {
184 from { opacity: 0; transform: translateY(8px); }
185 to { opacity: 1; transform: translateY(0); }
186 }
187
188 /* Background radial gradient blob — sits behind everything */
189 .landing-blob {
190 position: absolute;
191 inset: 0;
192 z-index: 0;
193 pointer-events: none;
194 background: radial-gradient(circle at 50% 30%, rgba(168, 85, 247, 0.15), transparent 50%);
195 }
196 .landing-root > section { position: relative; z-index: 1; }
197
2b821b7Claude198 /* ---------- Hero ---------- */
199 .landing-hero {
4c47454Claude200 padding-top: var(--s-16, 96px);
201 padding-bottom: var(--s-12, 64px);
2b821b7Claude202 text-align: center;
4c47454Claude203 max-width: 820px;
2b821b7Claude204 margin: 0 auto;
205 }
206 .landing-hero-title {
4c47454Claude207 font-size: clamp(40px, 7vw, 68px);
208 line-height: 1.05;
2b821b7Claude209 letter-spacing: -0.02em;
4c47454Claude210 font-weight: 600;
211 margin: 0 0 var(--s-4, 20px);
2b821b7Claude212 color: var(--text);
4c47454Claude213 }
214 .landing-hero-grad {
215 background: var(--accent-gradient, linear-gradient(135deg, #a855f7 0%, #6366f1 50%, #3b82f6 100%));
216 -webkit-background-clip: text;
217 background-clip: text;
218 -webkit-text-fill-color: transparent;
219 color: transparent;
2b821b7Claude220 }
221 .landing-hero-sub {
4c47454Claude222 font-size: var(--t-md, 17px);
2b821b7Claude223 color: var(--text-muted);
224 max-width: 640px;
4c47454Claude225 margin: var(--s-4, 20px) auto 0;
226 line-height: 1.55;
2b821b7Claude227 }
4c47454Claude228
2b821b7Claude229 .landing-hero-ctas {
230 display: flex;
4c47454Claude231 gap: var(--s-3, 12px);
2b821b7Claude232 justify-content: center;
233 flex-wrap: wrap;
4c47454Claude234 margin-top: var(--s-8, 36px);
2b821b7Claude235 }
4c47454Claude236 /* btn-lg fallback in case Agent B hasn't shipped it yet */
237 .btn-lg, .landing-cta-primary, .landing-cta-secondary {
238 padding: 12px 22px;
2b821b7Claude239 font-size: 15px;
240 font-weight: 600;
4c47454Claude241 border-radius: var(--r, 8px);
2b821b7Claude242 }
4c47454Claude243 /* btn-ghost fallback */
244 .landing-cta-secondary.btn-ghost,
245 .btn-ghost {
246 background: transparent;
247 border-color: var(--border);
248 color: var(--text);
8e9f1d9Claude249 }
4c47454Claude250 .btn-ghost:hover { background: var(--bg-secondary); }
251 .landing-cta-primary { display: inline-flex; align-items: center; gap: 8px; }
252 .landing-cta-arrow { transition: transform var(--t-fast, 120ms) var(--ease, ease); display: inline-block; }
253 .landing-cta-primary:hover .landing-cta-arrow { transform: translateX(3px); }
2b821b7Claude254
4c47454Claude255 .landing-hero-caption {
256 margin-top: var(--s-6, 24px);
257 font-size: var(--t-sm, 13px);
258 color: var(--text-muted);
2b821b7Claude259 }
4c47454Claude260 .landing-kbd {
261 display: inline-block;
262 padding: 2px 8px;
263 font-family: var(--font-mono);
264 font-size: 12px;
265 background: var(--bg-surface, var(--bg-tertiary));
266 border: 1px solid var(--border);
267 border-bottom-width: 2px;
268 border-radius: var(--r-sm, 4px);
2b821b7Claude269 color: var(--text);
4c47454Claude270 line-height: 1;
271 vertical-align: middle;
2b821b7Claude272 }
4c47454Claude273 .landing-kbd-sep { display: inline-block; width: 4px; }
274
275 .landing-stats {
276 margin-top: var(--s-6, 24px);
277 font-size: 14px;
2b821b7Claude278 color: var(--text-muted);
279 }
4c47454Claude280 .landing-stats strong { color: var(--text); font-weight: 600; }
281 .landing-stats-sep { opacity: 0.5; }
2b821b7Claude282
4c47454Claude283 /* ---------- Feature grid ---------- */
284 .landing-features {
285 margin-top: var(--s-12, 64px);
2b821b7Claude286 display: grid;
287 grid-template-columns: repeat(3, 1fr);
4c47454Claude288 gap: var(--s-4, 20px);
289 max-width: 980px;
290 margin-left: auto;
291 margin-right: auto;
2b821b7Claude292 }
293 .landing-feature {
4c47454Claude294 background: var(--bg-elevated, var(--bg-secondary));
2b821b7Claude295 border: 1px solid var(--border);
4c47454Claude296 border-radius: var(--r-lg, 10px);
297 padding: var(--s-6, 24px);
298 transition: transform var(--t-base, 180ms) var(--ease, ease),
299 border-color var(--t-base, 180ms) var(--ease, ease),
300 box-shadow var(--t-base, 180ms) var(--ease, ease);
301 }
302 .landing-feature:hover {
303 transform: translateY(-2px);
304 border-color: var(--accent, #1f6feb);
305 box-shadow: var(--elev-2, 0 6px 20px rgba(0,0,0,0.25));
2b821b7Claude306 }
307 .landing-feature-icon {
4c47454Claude308 display: inline-flex;
309 align-items: center;
310 justify-content: center;
311 width: 36px;
312 height: 36px;
313 border-radius: var(--r, 8px);
314 background: var(--accent-gradient-soft, rgba(99, 102, 241, 0.12));
315 color: var(--accent, #818cf8);
316 margin-bottom: var(--s-3, 14px);
2b821b7Claude317 }
318 .landing-feature-title {
4c47454Claude319 font-size: var(--t-lg, 17px);
320 font-weight: 500;
321 margin: 0 0 var(--s-2, 8px);
2b821b7Claude322 color: var(--text);
4c47454Claude323 letter-spacing: -0.005em;
2b821b7Claude324 }
325 .landing-feature-desc {
4c47454Claude326 font-size: var(--t-sm, 13px);
2b821b7Claude327 color: var(--text-muted);
328 line-height: 1.55;
329 margin: 0;
330 }
331
4c47454Claude332 /* ---------- Terminal block ---------- */
333 .landing-terminal-wrap {
334 margin-top: var(--s-12, 64px);
335 margin-bottom: var(--s-16, 96px);
336 display: flex;
2b821b7Claude337 justify-content: center;
338 }
4c47454Claude339 .landing-terminal {
340 width: 100%;
341 max-width: 720px;
342 background: var(--bg-surface, var(--bg-secondary));
343 border: 1px solid var(--border);
344 border-radius: var(--r-lg, 10px);
345 padding: var(--s-6, 24px);
2b821b7Claude346 font-family: var(--font-mono);
4c47454Claude347 font-size: var(--t-sm, 13px);
348 line-height: 1.7;
349 box-shadow: var(--elev-1, 0 2px 8px rgba(0,0,0,0.15));
350 text-align: left;
2b821b7Claude351 }
4c47454Claude352 .landing-term-line {
353 display: flex;
354 gap: 10px;
2b821b7Claude355 color: var(--text);
4c47454Claude356 white-space: pre-wrap;
357 word-break: break-all;
2b821b7Claude358 }
4c47454Claude359 .landing-term-prompt {
360 color: var(--text-faint, var(--text-muted));
361 user-select: none;
362 flex-shrink: 0;
2b821b7Claude363 }
4c47454Claude364 .landing-term-ok {
365 color: var(--green, #3fb950);
366 user-select: none;
367 flex-shrink: 0;
2b821b7Claude368 }
369
370 /* ---------- Responsive ---------- */
4c47454Claude371 @media (max-width: 768px) {
372 .landing-hero { padding-top: var(--s-12, 56px); padding-bottom: var(--s-8, 36px); }
373 .landing-hero-title { font-size: clamp(34px, 9vw, 44px); }
374 .landing-hero-sub { font-size: 15px; }
375 .landing-features {
376 grid-template-columns: 1fr;
377 margin-top: var(--s-10, 48px);
378 }
379 .landing-terminal-wrap {
380 margin-top: var(--s-10, 48px);
381 margin-bottom: var(--s-12, 64px);
382 }
383 .landing-terminal {
384 font-size: 12px;
385 padding: 16px;
386 }
387 .landing-hero-ctas { gap: 10px; }
388 .landing-cta-primary, .landing-cta-secondary { width: 100%; justify-content: center; }
2b821b7Claude389 }
390`;