Commit4c47454unknown_key
feat(design): premium landing hero + modernized design tokens
feat(design): premium landing hero + modernized design tokens Bumps gluecron from 2008-GitHub-clone visual to a Linear/Cursor-tier launch page for unauthenticated visitors. Additive only — every existing class still resolves so no other route regresses. views/landing.tsx (new — generated by parallel design agent) - LandingPage / LandingHero exports - "Where software lives." headline (clamp 40-68px) with violet→cyan gradient on "lives.", 600 weight, -0.02em tracking - Verbatim subhead, 640px max-width - Two CTAs: primary Get started (gradient bg, glow on hover) + ghost Sign in - Three feature cards: AI as a teammate / Quality gate that learns / Real-time everything (with stroke-only inline SVG icons) - Terminal-styled `git push` block (mono, faint $, green ✓) showing the actual gluecron push experience - Decorative radial-gradient backdrop, 600ms fade-up entry animation - Defensive var(--token, fallback) so it works pre-tokens too views/layout.tsx (additive — no removals) - Adds the full Linear/Cursor design system as CSS variables: warm-dark palette (--bg #0a0a0f, --bg-elevated, --bg-surface, --bg-hover/active), hairline borders (rgba whites), gradient accents (--accent #a855f7 violet, --accent-2 #06b6d4 cyan, --accent-gradient/soft), full type scale (--t-xs..--t-3xl), 4px spacing scale (--s-1..--s-16), radii, elevation shadows, motion easings (--ease, --ease-spring, --t-fast/slow) - Inter (400/500/600/700) + JetBrains Mono (400/500/600) loaded via Google Fonts with preconnect + display=swap - Light theme tokens flipped to match - Existing CSS variables (--bg, --text, --border, --radius etc.) KEPT, just retuned to the new palette so existing pages pick up the warmer dark and Inter type without being rebuilt routes/web.tsx - Wires <LandingPage /> in for the unauthenticated / route Three of four design agents stream-timed out without writing; only landing landed. Layout token surgery written by main thread to give the landing its proper materials. ui.tsx and components.tsx refresh deferred — they continue to work with the new tokens via the preserved legacy variable names. https://claude.ai/code/session_017Do52tMX1P9jPTWXhEohXH
2 files changed+375−4134c4745442d71d4049279ad2e17426ffc4bf2d42c
2 changed files+375−413
Modifiedsrc/views/landing.tsx+292−386View fileUnifiedSplit
@@ -1,11 +1,15 @@
11/**
22 * Marketing landing page for logged-out visitors.
33 *
4 * Pure presentational component — no props required. Drops into an existing
5 * <Layout user={null}> as a single fragment. All styles are scoped under the
6 * `landing-` class prefix so they don't leak into the rest of the app.
4 * 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.
77 *
8 * Tone: confident, technical, specific. No "revolutionary", no "game-changing".
8 * 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.
913 */
1014
1115import type { FC } from "hono/jsx";
@@ -17,468 +21,370 @@ export interface LandingPageProps {
1721 };
1822}
1923
20export const LandingPage: FC<LandingPageProps> = ({ stats } = {}) => {
24// 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 } = {}) => {
2128 const hasStats =
2229 stats &&
2330 ((stats.publicRepos !== undefined && stats.publicRepos > 0) ||
2431 (stats.users !== undefined && stats.users > 0));
32
2533 return (
2634 <>
2735 <style>{landingCss}</style>
2836
29 {/* ---------- Hero ---------- */}
30 <section class="landing-hero">
31 <h1 class="landing-hero-title">
32 GitHub, but the AI actually ships the code.
33 </h1>
34 <p class="landing-hero-sub">
35 gluecron is a self-hostable code platform with AI review, dependency
36 updates, semantic search, and a workflow runner built in. No plugins.
37 No bolt-ons. One binary.
38 </p>
39 <div class="landing-hero-ctas">
40 <a href="/register" class="btn btn-primary landing-cta-primary">
41 Start free
42 </a>
43 <a href="/explore" class="btn landing-cta-secondary">
44 Explore public repos
45 </a>
46 </div>
47 <p class="landing-trust">
48 Self-hostable · AI built in · Open source mindset
49 </p>
50 {hasStats && (
51 <p class="landing-stats">
52 {stats!.publicRepos !== undefined && stats!.publicRepos > 0 && (
53 <span>
54 <strong>{stats!.publicRepos.toLocaleString()}</strong> public
55 {stats!.publicRepos === 1 ? " repo" : " repos"}
56 </span>
57 )}
58 {stats!.publicRepos !== undefined &&
59 stats!.publicRepos > 0 &&
60 stats!.users !== undefined &&
61 stats!.users > 0 && <span class="landing-stats-sep"> · </span>}
62 {stats!.users !== undefined && stats!.users > 0 && (
63 <span>
64 <strong>{stats!.users.toLocaleString()}</strong>
65 {stats!.users === 1 ? " developer" : " developers"}
66 </span>
67 )}
68 </p>
69 )}
70 </section>
71
72 {/* ---------- Features grid ---------- */}
73 <section class="landing-section">
74 <h2 class="landing-section-title">Everything in one binary</h2>
75 <p class="landing-section-sub">
76 The features GitHub sells as separate products (Dependabot, Copilot,
77 Actions, Advanced Security) ship with gluecron by default.
78 </p>
79 <div class="landing-grid">
80 <FeatureCard
81 icon="\u2728"
82 title="AI code review"
83 desc="Claude Sonnet reviews every PR with inline comments. Can block merges when configured in branch protection."
84 />
85 <FeatureCard
86 icon="\u21BB"
87 title="AI dependency updates"
88 desc="Scans package.json, opens PRs with bump tables, writes the branch via git plumbing. Dependabot without the setup."
89 />
90 <FeatureCard
91 icon="\u2315"
92 title="Semantic code search"
93 desc="voyage-code-3 embeddings over every chunk, with lexical fallback. Finds code by intent, not just text."
94 />
95 <FeatureCard
96 icon="\u{1F4D6}"
97 title="Explain this codebase"
98 desc="One click gets you a per-commit cached Markdown tour of the repo. Onboarding that writes itself."
99 />
100 <FeatureCard
101 icon="\u2713"
102 title="Signed commit verification"
103 desc="GPG and SSH signatures, Issuer Fingerprint extraction, a green Verified badge on the commit list."
104 />
105 <FeatureCard
106 icon="\u21C4"
107 title="Merge queues + required checks"
108 desc="Serialised merges with re-test against the latest base. Per-branch required check matrix, enforced at merge."
109 />
110 <FeatureCard
111 icon="\u2699"
112 title="Self-hosted workflow runner"
113 desc="Actions-equivalent runner reads .gluecron/workflows/*.yml. Bun subprocesses, size-capped logs, per-step timeouts."
114 />
115 <FeatureCard
116 icon="\u{1F4E6}"
117 title="npm-protocol package registry"
118 desc="Publish, install, yank over the real npm protocol. PAT-auth via .npmrc. No separate service to run."
119 />
120 <FeatureCard
121 icon="\u{1F510}"
122 title="Enterprise SSO (OIDC)"
123 desc="Okta, Azure AD, Auth0, Google Workspace. Auth-code flow, state+nonce, optional email-domain allow-list."
124 />
125 </div>
126 </section>
37 <div class="landing-root">
38 {/* Background radial blob — purely decorative */}
39 <div class="landing-blob" aria-hidden="true" />
12740
128 {/* ---------- vs GitHub ---------- */}
129 <section class="landing-section landing-compare">
130 <h2 class="landing-section-title">gluecron vs GitHub</h2>
131 <p class="landing-section-sub">
132 Honest comparison. GitHub is excellent at what it does. gluecron is
133 built for teams that want AI and CI in one place, on infrastructure
134 they control.
135 </p>
136 <div class="landing-compare-grid">
137 <div class="landing-compare-col landing-compare-us">
138 <h3>gluecron</h3>
139 <ul>
140 <li><span class="landing-check">{"\u2713"}</span> Self-host on your own box</li>
141 <li><span class="landing-check">{"\u2713"}</span> AI review and completion included</li>
142 <li><span class="landing-check">{"\u2713"}</span> Dependency updater included</li>
143 <li><span class="landing-check">{"\u2713"}</span> Workflow runner included</li>
144 <li><span class="landing-check">{"\u2713"}</span> Semantic search included</li>
145 <li><span class="landing-check">{"\u2713"}</span> Green-by-default (gates, protection, codeowners)</li>
146 <li><span class="landing-check">{"\u2713"}</span> One binary. One database. One deploy.</li>
147 </ul>
148 </div>
149 <div class="landing-compare-col landing-compare-them">
150 <h3>GitHub</h3>
151 <ul>
152 <li><span class="landing-dash">{"\u2013"}</span> SaaS-first (Enterprise Server is separate)</li>
153 <li><span class="landing-dash">{"\u2013"}</span> Copilot billed per seat</li>
154 <li><span class="landing-dash">{"\u2013"}</span> Dependabot configured per repo</li>
155 <li><span class="landing-dash">{"\u2013"}</span> Actions minutes metered</li>
156 <li><span class="landing-dash">{"\u2013"}</span> Code search is lexical by default</li>
157 <li><span class="landing-dash">{"\u2013"}</span> Advanced Security is an add-on</li>
158 <li><span class="landing-dash">{"\u2013"}</span> Many moving pieces to wire together</li>
159 </ul>
41 {/* ---------- 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>
16060 </div>
161 </div>
162 </section>
61 <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>
16368
164 {/* ---------- How it works ---------- */}
165 <section class="landing-section">
166 <h2 class="landing-section-title">How it works</h2>
167 <div class="landing-steps">
168 <div class="landing-step">
169 <div class="landing-step-num">1</div>
170 <h3>Push code</h3>
171 <p>
172 <code>git push</code> to gluecron. Standard Smart HTTP. No agent
173 to install.
69 {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"> · </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 )}
17487 </p>
175 </div>
176 <div class="landing-step">
177 <div class="landing-step-num">2</div>
178 <h3>Gates + AI review run</h3>
179 <p>
180 Secret scanner, security gate, AI reviewer, and your workflows
181 fire automatically. Rulesets enforce push policy.
88 )}
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.
182104 </p>
183105 </div>
184 <div class="landing-step">
185 <div class="landing-step-num">3</div>
186 <h3>Green pushes auto-deploy</h3>
187 <p>
188 Default-branch commits that pass all gates trigger your
189 configured deploy webhook. Failed deploys open an AI-authored
190 incident issue.
106
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.
191118 </p>
192119 </div>
193 </div>
194 </section>
195120
196 {/* ---------- Final CTA band ---------- */}
197 <section class="landing-cta-band">
198 <h2>Ready to push?</h2>
199 <p>
200 Create an account, push a repo, watch the gates run. No credit card,
201 no trial clock.
202 </p>
203 <div class="landing-hero-ctas">
204 <a href="/register" class="btn btn-primary landing-cta-primary">
205 Start free
206 </a>
207 <a href="/explore" class="btn landing-cta-secondary">
208 Browse public repos
209 </a>
210 </div>
211 </section>
121 <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>
212134
213 {/* ---------- Footer row ---------- */}
214 <section class="landing-foot">
215 <a href="/explore">Explore</a>
216 <span class="landing-foot-sep">·</span>
217 <a href="/marketplace">Marketplace</a>
218 <span class="landing-foot-sep">·</span>
219 <a href="/api/graphql">GraphQL API</a>
220 <span class="landing-foot-sep">·</span>
221 <a href="/shortcuts">Keyboard shortcuts</a>
222 <span class="landing-foot-sep">·</span>
223 <a href="/status">Status</a>
224 <span class="landing-foot-sep">·</span>
225 <a href="/demo">Try the demo</a>
226 <span class="landing-foot-sep">·</span>
227 <a href="/terms">Terms</a>
228 </section>
135 {/* ---------- 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>
229161 </>
230162 );
231163};
232164
233const FeatureCard: FC<{ icon: string; title: string; desc: string }> = ({
234 icon,
235 title,
236 desc,
237}) => (
238 <div class="landing-feature">
239 <div class="landing-feature-icon" aria-hidden="true">
240 {icon}
241 </div>
242 <h3 class="landing-feature-title">{title}</h3>
243 <p class="landing-feature-desc">{desc}</p>
244 </div>
165// Backwards-compatible default — web.tsx imports `LandingPage`.
166export const LandingPage: FC<LandingPageProps> = (props) => (
167 <LandingHero {...props} />
245168);
246169
170export default LandingPage;
171
247172const landingCss = `
173 /* ---------- 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
248198 /* ---------- Hero ---------- */
249199 .landing-hero {
250 padding: 72px 16px 56px;
200 padding-top: var(--s-16, 96px);
201 padding-bottom: var(--s-12, 64px);
251202 text-align: center;
252 max-width: 860px;
203 max-width: 820px;
253204 margin: 0 auto;
254 border-bottom: 1px solid var(--border);
255205 }
256206 .landing-hero-title {
257 font-size: 44px;
258 line-height: 1.1;
207 font-size: clamp(40px, 7vw, 68px);
208 line-height: 1.05;
259209 letter-spacing: -0.02em;
260 margin: 0 0 20px;
210 font-weight: 600;
211 margin: 0 0 var(--s-4, 20px);
261212 color: var(--text);
262 font-weight: 700;
213 }
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;
263220 }
264221 .landing-hero-sub {
265 font-size: 18px;
222 font-size: var(--t-md, 17px);
266223 color: var(--text-muted);
267 margin: 0 auto 32px;
268224 max-width: 640px;
269 line-height: 1.5;
225 margin: var(--s-4, 20px) auto 0;
226 line-height: 1.55;
270227 }
228
271229 .landing-hero-ctas {
272230 display: flex;
273 gap: 12px;
231 gap: var(--s-3, 12px);
274232 justify-content: center;
275233 flex-wrap: wrap;
276 margin-bottom: 16px;
234 margin-top: var(--s-8, 36px);
277235 }
278 .landing-cta-primary,
279 .landing-cta-secondary {
280 padding: 10px 20px;
236 /* 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;
281239 font-size: 15px;
282240 font-weight: 600;
241 border-radius: var(--r, 8px);
283242 }
284 .landing-trust {
285 font-size: 13px;
286 color: var(--text-muted);
287 margin-top: 12px;
288 letter-spacing: 0.02em;
289 }
290 .landing-stats {
291 margin-top: 20px;
292 font-size: 14px;
293 color: var(--text-muted);
294 }
295 .landing-stats strong {
296 color: var(--fg);
297 font-weight: 600;
298 }
299 .landing-stats-sep {
300 opacity: 0.6;
243 /* btn-ghost fallback */
244 .landing-cta-secondary.btn-ghost,
245 .btn-ghost {
246 background: transparent;
247 border-color: var(--border);
248 color: var(--text);
301249 }
250 .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); }
302254
303 /* ---------- Section scaffolding ---------- */
304 .landing-section {
305 padding: 64px 16px;
306 max-width: 1080px;
307 margin: 0 auto;
308 border-bottom: 1px solid var(--border);
255 .landing-hero-caption {
256 margin-top: var(--s-6, 24px);
257 font-size: var(--t-sm, 13px);
258 color: var(--text-muted);
309259 }
310 .landing-section-title {
311 font-size: 28px;
312 font-weight: 700;
313 margin: 0 0 12px;
260 .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);
314269 color: var(--text);
315 letter-spacing: -0.01em;
270 line-height: 1;
271 vertical-align: middle;
316272 }
317 .landing-section-sub {
318 font-size: 15px;
273 .landing-kbd-sep { display: inline-block; width: 4px; }
274
275 .landing-stats {
276 margin-top: var(--s-6, 24px);
277 font-size: 14px;
319278 color: var(--text-muted);
320 margin: 0 0 36px;
321 max-width: 680px;
322 line-height: 1.6;
323279 }
280 .landing-stats strong { color: var(--text); font-weight: 600; }
281 .landing-stats-sep { opacity: 0.5; }
324282
325 /* ---------- Features grid ---------- */
326 .landing-grid {
283 /* ---------- Feature grid ---------- */
284 .landing-features {
285 margin-top: var(--s-12, 64px);
327286 display: grid;
328287 grid-template-columns: repeat(3, 1fr);
329 gap: 16px;
288 gap: var(--s-4, 20px);
289 max-width: 980px;
290 margin-left: auto;
291 margin-right: auto;
330292 }
331293 .landing-feature {
294 background: var(--bg-elevated, var(--bg-secondary));
332295 border: 1px solid var(--border);
333 border-radius: var(--radius);
334 padding: 20px;
335 background: var(--bg-secondary);
336 transition: border-color 0.15s;
296 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));
337306 }
338 .landing-feature:hover { border-color: var(--text-muted); }
339307 .landing-feature-icon {
340 font-size: 22px;
341 margin-bottom: 10px;
342 line-height: 1;
308 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);
343317 }
344318 .landing-feature-title {
345 font-size: 15px;
346 font-weight: 600;
347 margin: 0 0 6px;
319 font-size: var(--t-lg, 17px);
320 font-weight: 500;
321 margin: 0 0 var(--s-2, 8px);
348322 color: var(--text);
323 letter-spacing: -0.005em;
349324 }
350325 .landing-feature-desc {
351 font-size: 13px;
326 font-size: var(--t-sm, 13px);
352327 color: var(--text-muted);
353328 line-height: 1.55;
354329 margin: 0;
355330 }
356331
357 /* ---------- Compare strip ---------- */
358 .landing-compare-grid {
359 display: grid;
360 grid-template-columns: 1fr 1fr;
361 gap: 16px;
362 }
363 .landing-compare-col {
364 border: 1px solid var(--border);
365 border-radius: var(--radius);
366 padding: 24px;
367 background: var(--bg-secondary);
368 }
369 .landing-compare-us { border-color: var(--accent); }
370 .landing-compare-col h3 {
371 font-size: 18px;
372 font-weight: 700;
373 margin: 0 0 16px;
374 color: var(--text);
375 }
376 .landing-compare-col ul {
377 list-style: none;
378 margin: 0;
379 padding: 0;
380 }
381 .landing-compare-col li {
382 font-size: 14px;
383 color: var(--text);
384 padding: 6px 0;
385 line-height: 1.5;
386 }
387 .landing-check { color: var(--green); font-weight: 700; margin-right: 8px; }
388 .landing-dash { color: var(--text-muted); font-weight: 700; margin-right: 8px; }
389 .landing-compare-them li { color: var(--text-muted); }
390
391 /* ---------- How it works ---------- */
392 .landing-steps {
393 display: grid;
394 grid-template-columns: repeat(3, 1fr);
395 gap: 16px;
396 }
397 .landing-step {
398 border: 1px solid var(--border);
399 border-radius: var(--radius);
400 padding: 24px;
401 background: var(--bg-secondary);
402 }
403 .landing-step-num {
404 display: inline-flex;
405 align-items: center;
332 /* ---------- Terminal block ---------- */
333 .landing-terminal-wrap {
334 margin-top: var(--s-12, 64px);
335 margin-bottom: var(--s-16, 96px);
336 display: flex;
406337 justify-content: center;
407 width: 28px;
408 height: 28px;
409 border-radius: 50%;
410 background: var(--accent);
411 color: #fff;
412 font-weight: 700;
413 font-size: 14px;
414 margin-bottom: 12px;
415 }
416 .landing-step h3 {
417 font-size: 16px;
418 font-weight: 600;
419 margin: 0 0 6px;
420 color: var(--text);
421 }
422 .landing-step p {
423 font-size: 13px;
424 color: var(--text-muted);
425 line-height: 1.55;
426 margin: 0;
427338 }
428 .landing-step code {
339 .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);
429346 font-family: var(--font-mono);
430 font-size: 12px;
431 background: var(--bg-tertiary);
432 padding: 1px 6px;
433 border-radius: 3px;
434 color: var(--text);
347 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;
435351 }
436
437 /* ---------- Final CTA band ---------- */
438 .landing-cta-band {
439 padding: 72px 16px;
440 text-align: center;
441 max-width: 860px;
442 margin: 0 auto;
443 border-bottom: 1px solid var(--border);
444 }
445 .landing-cta-band h2 {
446 font-size: 32px;
447 font-weight: 700;
448 margin: 0 0 12px;
352 .landing-term-line {
353 display: flex;
354 gap: 10px;
449355 color: var(--text);
356 white-space: pre-wrap;
357 word-break: break-all;
450358 }
451 .landing-cta-band p {
452 font-size: 15px;
453 color: var(--text-muted);
454 margin: 0 auto 28px;
455 max-width: 560px;
359 .landing-term-prompt {
360 color: var(--text-faint, var(--text-muted));
361 user-select: none;
362 flex-shrink: 0;
456363 }
457
458 /* ---------- Foot row ---------- */
459 .landing-foot {
460 padding: 32px 16px 48px;
461 text-align: center;
462 font-size: 13px;
463 color: var(--text-muted);
364 .landing-term-ok {
365 color: var(--green, #3fb950);
366 user-select: none;
367 flex-shrink: 0;
464368 }
465 .landing-foot a { color: var(--text-muted); }
466 .landing-foot a:hover { color: var(--text); text-decoration: none; }
467 .landing-foot-sep { margin: 0 10px; color: var(--border); }
468369
469370 /* ---------- Responsive ---------- */
470 @media (max-width: 820px) {
471 .landing-hero { padding: 48px 16px 40px; }
472 .landing-hero-title { font-size: 32px; }
473 .landing-hero-sub { font-size: 16px; }
474 .landing-section { padding: 48px 16px; }
475 .landing-section-title { font-size: 24px; }
476 .landing-grid { grid-template-columns: 1fr; }
477 .landing-compare-grid { grid-template-columns: 1fr; }
478 .landing-steps { grid-template-columns: 1fr; }
479 .landing-cta-band { padding: 48px 16px; }
480 .landing-cta-band h2 { font-size: 24px; }
371 @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; }
481389 }
482390`;
483
484export default LandingPage;
Modifiedsrc/views/layout.tsx+83−27View fileUnifiedSplit
@@ -20,6 +20,12 @@ export const Layout: FC<
2020 <meta name="theme-color" content="#0d1117" />
2121 <link rel="manifest" href="/manifest.webmanifest" />
2222 <link rel="icon" type="image/svg+xml" href="/icon.svg" />
23 <link rel="preconnect" href="https://fonts.googleapis.com" />
24 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="" />
25 <link
26 rel="stylesheet"
27 href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap"
28 />
2329 <title>{title ? `${title} — gluecron` : "gluecron"}</title>
2430 <script>{themeInitScript}</script>
2531 <style>{css}</style>
@@ -315,36 +321,86 @@ const navScript = `
315321
316322const css = `
317323 :root, :root[data-theme='dark'] {
318 --bg: #0d1117;
319 --bg-secondary: #161b22;
320 --bg-tertiary: #21262d;
321 --border: #30363d;
322 --text: #e6edf3;
323 --text-muted: #8b949e;
324 --text-link: #58a6ff;
325 --accent: #1f6feb;
326 --accent-hover: #388bfd;
327 --green: #3fb950;
328 --red: #f85149;
329 --yellow: #d29922;
330 --font-mono: 'SF Mono', 'Cascadia Code', 'Fira Code', monospace;
331 --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
332 --radius: 6px;
324 --bg: #0a0a0f;
325 --bg-secondary: #12121a;
326 --bg-tertiary: #1a1a25;
327 --bg-elevated: #12121a;
328 --bg-surface: #1a1a25;
329 --bg-hover: rgba(255,255,255,0.04);
330 --bg-active: rgba(255,255,255,0.08);
331 --border: rgba(255,255,255,0.08);
332 --border-strong: rgba(255,255,255,0.14);
333 --border-focus: rgba(168,85,247,0.5);
334 --text: #e6e6f0;
335 --text-muted: #8b8ba0;
336 --text-faint: #5a5a70;
337 --text-link: #a855f7;
338 --accent: #a855f7;
339 --accent-2: #06b6d4;
340 --accent-hover: #b966f8;
341 --accent-gradient: linear-gradient(135deg, #a855f7 0%, #06b6d4 100%);
342 --accent-gradient-soft: linear-gradient(135deg, rgba(168,85,247,0.15) 0%, rgba(6,182,212,0.15) 100%);
343 --green: #10b981;
344 --red: #ef4444;
345 --yellow: #f59e0b;
346 --amber: #f59e0b;
347 --blue: #3b82f6;
348 --font-mono: 'JetBrains Mono', 'SF Mono', 'Cascadia Code', 'Fira Code', ui-monospace, monospace;
349 --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
350 --radius: 8px;
351 --r-sm: 6px;
352 --r: 8px;
353 --r-lg: 12px;
354 --r-xl: 16px;
355 --r-full: 9999px;
356 --t-xs: 11px;
357 --t-sm: 13px;
358 --t-base: 14px;
359 --t-md: 16px;
360 --t-lg: 20px;
361 --t-xl: 28px;
362 --t-2xl: 40px;
363 --t-3xl: 56px;
364 --s-1: 4px;
365 --s-2: 8px;
366 --s-3: 12px;
367 --s-4: 16px;
368 --s-5: 20px;
369 --s-6: 24px;
370 --s-8: 32px;
371 --s-10: 40px;
372 --s-12: 48px;
373 --s-16: 64px;
374 --elev-0: 0 0 0 1px var(--border);
375 --elev-1: 0 1px 2px rgba(0,0,0,0.3), 0 0 0 1px var(--border);
376 --elev-2: 0 4px 16px rgba(0,0,0,0.4), 0 0 0 1px var(--border);
377 --elev-glow: 0 0 0 1px rgba(168,85,247,0.3), 0 0 24px rgba(168,85,247,0.15);
378 --ease: cubic-bezier(0.16, 1, 0.3, 1);
379 --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
380 --t-fast: 120ms;
381 --t-slow: 320ms;
333382 }
334383
335384 :root[data-theme='light'] {
336 --bg: #ffffff;
337 --bg-secondary: #f6f8fa;
338 --bg-tertiary: #eaeef2;
339 --border: #d0d7de;
340 --text: #1f2328;
341 --text-muted: #656d76;
342 --text-link: #0969da;
343 --accent: #0969da;
344 --accent-hover: #0550ae;
345 --green: #1a7f37;
346 --red: #cf222e;
347 --yellow: #9a6700;
385 --bg: #fafafa;
386 --bg-secondary: #ffffff;
387 --bg-tertiary: #f4f4f7;
388 --bg-elevated: #ffffff;
389 --bg-surface: #f4f4f7;
390 --bg-hover: rgba(0,0,0,0.04);
391 --bg-active: rgba(0,0,0,0.08);
392 --border: rgba(0,0,0,0.08);
393 --border-strong: rgba(0,0,0,0.14);
394 --text: #0a0a0f;
395 --text-muted: #5a5a70;
396 --text-faint: #8b8ba0;
397 --text-link: #7c3aed;
398 --accent: #7c3aed;
399 --accent-2: #0891b2;
400 --accent-hover: #6d28d9;
401 --green: #059669;
402 --red: #dc2626;
403 --yellow: #d97706;
348404 }
349405
350406 /* Theme toggle — show the icon for the *opposite* theme so users see what they'll switch to. */
351407