CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
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.
| 2b821b7 | 1 | /** |
| 2 | * Marketing landing page for logged-out visitors. | |
| 3 | * | |
| 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. | |
| 7 | * | |
| 8 | * Tone: confident, technical, specific. No "revolutionary", no "game-changing". | |
| 9 | */ | |
| 10 | ||
| 11 | import type { FC } from "hono/jsx"; | |
| 12 | ||
| 13 | export interface LandingPageProps { | |
| 14 | stats?: { | |
| 15 | publicRepos?: number; | |
| 16 | users?: number; | |
| 17 | }; | |
| 18 | } | |
| 19 | ||
| 8e9f1d9 | 20 | export const LandingPage: FC<LandingPageProps> = ({ stats } = {}) => { |
| 21 | const hasStats = | |
| 22 | stats && | |
| 23 | ((stats.publicRepos !== undefined && stats.publicRepos > 0) || | |
| 24 | (stats.users !== undefined && stats.users > 0)); | |
| 2b821b7 | 25 | return ( |
| 26 | <> | |
| 27 | <style>{landingCss}</style> | |
| 28 | ||
| 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> | |
| 8e9f1d9 | 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 | )} | |
| 2b821b7 | 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> | |
| 127 | ||
| 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> | |
| 160 | </div> | |
| 161 | </div> | |
| 162 | </section> | |
| 163 | ||
| 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. | |
| 174 | </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. | |
| 182 | </p> | |
| 183 | </div> | |
| 184 | <div class="landing-step"> | |
| 185 | <div class="landing-step-num">3</div> | |
| 186 | <h3>Green pushes auto-deploy</h3> | |
| 187 | <p> | |
| 90fa787 | 188 | Default-branch commits that pass all gates trigger your |
| 189 | configured deploy webhook. Failed deploys open an AI-authored | |
| 190 | incident issue. | |
| 2b821b7 | 191 | </p> |
| 192 | </div> | |
| 193 | </div> | |
| 194 | </section> | |
| 195 | ||
| 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> | |
| 212 | ||
| 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> | |
| 2316be6 | 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> | |
| 2b821b7 | 227 | <a href="/terms">Terms</a> |
| 228 | </section> | |
| 229 | </> | |
| 230 | ); | |
| 231 | }; | |
| 232 | ||
| 233 | const 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> | |
| 245 | ); | |
| 246 | ||
| 247 | const landingCss = ` | |
| 248 | /* ---------- Hero ---------- */ | |
| 249 | .landing-hero { | |
| 250 | padding: 72px 16px 56px; | |
| 251 | text-align: center; | |
| 252 | max-width: 860px; | |
| 253 | margin: 0 auto; | |
| 254 | border-bottom: 1px solid var(--border); | |
| 255 | } | |
| 256 | .landing-hero-title { | |
| 257 | font-size: 44px; | |
| 258 | line-height: 1.1; | |
| 259 | letter-spacing: -0.02em; | |
| 260 | margin: 0 0 20px; | |
| 261 | color: var(--text); | |
| 262 | font-weight: 700; | |
| 263 | } | |
| 264 | .landing-hero-sub { | |
| 265 | font-size: 18px; | |
| 266 | color: var(--text-muted); | |
| 267 | margin: 0 auto 32px; | |
| 268 | max-width: 640px; | |
| 269 | line-height: 1.5; | |
| 270 | } | |
| 271 | .landing-hero-ctas { | |
| 272 | display: flex; | |
| 273 | gap: 12px; | |
| 274 | justify-content: center; | |
| 275 | flex-wrap: wrap; | |
| 276 | margin-bottom: 16px; | |
| 277 | } | |
| 278 | .landing-cta-primary, | |
| 279 | .landing-cta-secondary { | |
| 280 | padding: 10px 20px; | |
| 281 | font-size: 15px; | |
| 282 | font-weight: 600; | |
| 283 | } | |
| 284 | .landing-trust { | |
| 285 | font-size: 13px; | |
| 286 | color: var(--text-muted); | |
| 287 | margin-top: 12px; | |
| 288 | letter-spacing: 0.02em; | |
| 289 | } | |
| 8e9f1d9 | 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; | |
| 301 | } | |
| 2b821b7 | 302 | |
| 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); | |
| 309 | } | |
| 310 | .landing-section-title { | |
| 311 | font-size: 28px; | |
| 312 | font-weight: 700; | |
| 313 | margin: 0 0 12px; | |
| 314 | color: var(--text); | |
| 315 | letter-spacing: -0.01em; | |
| 316 | } | |
| 317 | .landing-section-sub { | |
| 318 | font-size: 15px; | |
| 319 | color: var(--text-muted); | |
| 320 | margin: 0 0 36px; | |
| 321 | max-width: 680px; | |
| 322 | line-height: 1.6; | |
| 323 | } | |
| 324 | ||
| 325 | /* ---------- Features grid ---------- */ | |
| 326 | .landing-grid { | |
| 327 | display: grid; | |
| 328 | grid-template-columns: repeat(3, 1fr); | |
| 329 | gap: 16px; | |
| 330 | } | |
| 331 | .landing-feature { | |
| 332 | border: 1px solid var(--border); | |
| 333 | border-radius: var(--radius); | |
| 334 | padding: 20px; | |
| 335 | background: var(--bg-secondary); | |
| 336 | transition: border-color 0.15s; | |
| 337 | } | |
| 338 | .landing-feature:hover { border-color: var(--text-muted); } | |
| 339 | .landing-feature-icon { | |
| 340 | font-size: 22px; | |
| 341 | margin-bottom: 10px; | |
| 342 | line-height: 1; | |
| 343 | } | |
| 344 | .landing-feature-title { | |
| 345 | font-size: 15px; | |
| 346 | font-weight: 600; | |
| 347 | margin: 0 0 6px; | |
| 348 | color: var(--text); | |
| 349 | } | |
| 350 | .landing-feature-desc { | |
| 351 | font-size: 13px; | |
| 352 | color: var(--text-muted); | |
| 353 | line-height: 1.55; | |
| 354 | margin: 0; | |
| 355 | } | |
| 356 | ||
| 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; | |
| 406 | 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; | |
| 427 | } | |
| 428 | .landing-step code { | |
| 429 | 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); | |
| 435 | } | |
| 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; | |
| 449 | color: var(--text); | |
| 450 | } | |
| 451 | .landing-cta-band p { | |
| 452 | font-size: 15px; | |
| 453 | color: var(--text-muted); | |
| 454 | margin: 0 auto 28px; | |
| 455 | max-width: 560px; | |
| 456 | } | |
| 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); | |
| 464 | } | |
| 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); } | |
| 468 | ||
| 469 | /* ---------- 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; } | |
| 481 | } | |
| 482 | `; | |
| 483 | ||
| 484 | export default LandingPage; |