g1t/apps/web/app/components/landing.tsx
| 1 | import { ArrowRight } from "lucide-react"; |
| 2 | import type { ReactNode } from "react"; |
| 3 | import { Link } from "react-router"; |
| 4 | |
| 5 | import { type Provider, PROVIDERS, type Repo } from "@g1t/contracts"; |
| 6 | |
| 7 | import { ConvergeArt, PlanArt, QueueArt, TeamArt, WhyArt } from "./art"; |
| 8 | import { ProviderMark } from "./model-providers"; |
| 9 | import { RepoList } from "./repo-list"; |
| 10 | import { ButtonLink, CopyLine } from "./ui"; |
| 11 | |
| 12 | /** When the free allowance on g1t's models ends (billing's TRIAL_UNTIL). */ |
| 13 | const TRIAL_ENDS = Date.parse("2026-10-23T06:59:59Z"); |
| 14 | |
| 15 | /** A small label above a heading, in the mono face. */ |
| 16 | function Eyebrow({ children }: { children: ReactNode }) { |
| 17 | return <p className="font-mono text-xs tracking-[0.2em] text-merged uppercase">{children}</p>; |
| 18 | } |
| 19 | |
| 20 | function Tags({ items }: { items: string[] }) { |
| 21 | return ( |
| 22 | <ul className="mt-5 flex flex-wrap gap-1.5"> |
| 23 | {items.map((item) => ( |
| 24 | <li |
| 25 | key={item} |
| 26 | className="rounded-full bg-bg/80 px-2.5 py-1 font-mono text-[0.6875rem] tracking-wide text-muted uppercase ring-1 ring-line" |
| 27 | > |
| 28 | {item} |
| 29 | </li> |
| 30 | ))} |
| 31 | </ul> |
| 32 | ); |
| 33 | } |
| 34 | |
| 35 | /** A card with a line drawing above its words, on a faint grid. */ |
| 36 | function Card({ |
| 37 | title, |
| 38 | art, |
| 39 | tags, |
| 40 | wide, |
| 41 | children, |
| 42 | }: { |
| 43 | title: string; |
| 44 | art: ReactNode; |
| 45 | tags?: string[]; |
| 46 | wide?: boolean; |
| 47 | children: ReactNode; |
| 48 | }) { |
| 49 | return ( |
| 50 | <article |
| 51 | className={`group relative overflow-hidden rounded-3xl bg-surface p-7 ring-1 ring-line transition-colors hover:ring-line-strong ${ |
| 52 | wide ? "md:col-span-2" : "" |
| 53 | }`} |
| 54 | > |
| 55 | <h3 className="text-lg font-semibold tracking-tight">{title}</h3> |
| 56 | <div className={`mx-auto mt-2 ${wide ? "max-w-md" : "max-w-xs"} opacity-90 transition-opacity group-hover:opacity-100`}> |
| 57 | {art} |
| 58 | </div> |
| 59 | <p className="mt-2 text-sm leading-6 text-muted">{children}</p> |
| 60 | {tags && <Tags items={tags} />} |
| 61 | </article> |
| 62 | ); |
| 63 | } |
| 64 | |
| 65 | const FLOW: [string, string][] = [ |
| 66 | ["Brief", "Write the outcome you want, in plain words."], |
| 67 | ["Plan", "A planner agent splits it into issues, each with checks that prove it done, and the order they depend on."], |
| 68 | ["Agents", "Each issue gets an agent as it unblocks. They know what the others are doing, and tell each other."], |
| 69 | ["Review", "Checks run in clean sandboxes; a second agent reviews; you ask for changes and the agent makes them."], |
| 70 | ["Main", "The queue tests every change together with what lands before it, and main moves only to what passed."], |
| 71 | ]; |
| 72 | |
| 73 | const BASICS = [ |
| 74 | "Issues and labels", |
| 75 | "Branches and forks", |
| 76 | "Pull requests", |
| 77 | "Line comments and reviews", |
| 78 | "Protected branches", |
| 79 | "Required approvals", |
| 80 | "Workspaces and teams", |
| 81 | "Access tokens", |
| 82 | "Git over HTTPS", |
| 83 | "REST API", |
| 84 | "MCP server", |
| 85 | "OAuth sign-in", |
| 86 | "Any model provider", |
| 87 | "Sentry, Jira, Linear", |
| 88 | ]; |
| 89 | |
| 90 | const COMPARISON: [string, string, string][] = [ |
| 91 | ["What you hand over", "A pull request to review", "An outcome, which becomes a plan of issues and lands on main"], |
| 92 | ["Many agents at once", "Separate pull requests to compare and untangle", "A team: told about each other's work, filing what they find, talking through the forge"], |
| 93 | ["Is it done?", "Whatever the author says they ran", "The issue's checks, run by the forge in a clean sandbox"], |
| 94 | ["When two changes collide", "A merge conflict, or a broken main", "Tested together in the queue first; the one that breaks goes back to its agent"], |
| 95 | ["What main promises", "Whatever passed when each change merged", "Every check of every issue that ever landed, on every change since"], |
| 96 | ["Why is this line here?", "A commit message, if you are lucky", "The commit, the pull request, the issue and the agent's own account"], |
| 97 | ["You in the loop", "Reading every diff", "Asking for changes, approving, steering; the agents do the rest"], |
| 98 | ]; |
| 99 | |
| 100 | /** The systems g1t connects to, shown on the landing page. */ |
| 101 | const STACK: Provider[] = [ |
| 102 | "anthropic", |
| 103 | "openai", |
| 104 | "gemini", |
| 105 | "xai", |
| 106 | "mistral", |
| 107 | "deepseek", |
| 108 | "azure_openai", |
| 109 | "openrouter", |
| 110 | "groq", |
| 111 | "sentry", |
| 112 | "jira", |
| 113 | "linear", |
| 114 | ]; |
| 115 | |
| 116 | /** What happens to a production error once Sentry is connected. */ |
| 117 | const LOOP = [ |
| 118 | "Sentry: TypeError in checkout", |
| 119 | "g1t opens an issue, with the stack trace", |
| 120 | "An agent fixes it; another reviews it", |
| 121 | "The merge queue lands it on main", |
| 122 | "Sentry marks the error resolved", |
| 123 | ]; |
| 124 | |
| 125 | const PLATFORM = ["Workers", "Artifacts", "Containers", "D1", "Queues", "AI Gateway", "Rust"]; |
| 126 | |
| 127 | export function Landing({ repos }: { repos: Repo[] }) { |
| 128 | return ( |
| 129 | <main> |
| 130 | {/* The opening: the sky after dusk, agents converging on main. */} |
| 131 | <section className="dusk relative overflow-hidden"> |
| 132 | <div |
| 133 | aria-hidden="true" |
| 134 | className="dusk-planet pointer-events-none absolute -top-[30rem] left-1/2 size-[60rem] -translate-x-[10%] rounded-full" |
| 135 | /> |
| 136 | <div className="relative mx-auto max-w-6xl px-4 pt-24 pb-6 text-center"> |
| 137 | <Link |
| 138 | to="https://docs.g1t.sh/concepts/overview/" |
| 139 | className="inline-flex animate-fade-up items-center gap-2 rounded-full bg-bg/50 px-3 py-1 text-xs text-fg-soft ring-1 ring-white/10 backdrop-blur transition-colors hover:bg-bg/70" |
| 140 | > |
| 141 | <span className="size-1.5 rounded-full bg-accent" /> |
| 142 | A git forge for teams of agents |
| 143 | <ArrowRight size={12} /> |
| 144 | </Link> |
| 145 | <h1 className="mx-auto mt-7 max-w-4xl animate-fade-up text-5xl leading-[1.04] font-semibold tracking-[-0.035em] text-balance sm:text-7xl"> |
| 146 | Hand off the outcome. |
| 147 | <br /> |
| 148 | <span className="bg-gradient-to-r from-[#d9d1ff] via-[#ffd4b8] to-accent bg-clip-text text-transparent"> |
| 149 | A team of agents ships it. |
| 150 | </span> |
| 151 | </h1> |
| 152 | <p className="mx-auto mt-7 max-w-2xl animate-fade-up text-lg leading-8 text-fg-soft/80 text-balance"> |
| 153 | Everything you expect from GitHub: issues, branches, pull requests, review. On top of |
| 154 | it, describe what you want and g1t plans it, puts agents on every part at once, keeps |
| 155 | them working as a team, and lands it on main through checks that hold. |
| 156 | </p> |
| 157 | <div className="mt-9 flex animate-fade-up flex-wrap items-center justify-center gap-3"> |
| 158 | <ButtonLink to="/register" variant="primary" large> |
| 159 | Get started |
| 160 | <ArrowRight size={15} /> |
| 161 | </ButtonLink> |
| 162 | <ButtonLink to="/syntaqx/hello/pulls?state=closed" variant="quiet" large> |
| 163 | Watch agents at work |
| 164 | </ButtonLink> |
| 165 | </div> |
| 166 | {Date.now() < TRIAL_ENDS && ( |
| 167 | <p className="mx-auto mt-5 max-w-xl animate-fade-up text-sm text-fg-soft/70 text-balance"> |
| 168 | Free to try: every new workspace gets $1 of agent time on g1t's models, no key needed, until |
| 169 | October 22. Bring your own model for more. |
| 170 | </p> |
| 171 | )} |
| 172 | </div> |
| 173 | <div className="relative mx-auto max-w-5xl px-4 pb-4"> |
| 174 | <ConvergeArt className="w-full" /> |
| 175 | </div> |
| 176 | <div aria-hidden="true" className="h-24 bg-gradient-to-b from-transparent to-bg" /> |
| 177 | </section> |
| 178 | |
| 179 | {/* The three things only g1t does. */} |
| 180 | <section className="mx-auto max-w-6xl px-4 py-24"> |
| 181 | <Eyebrow>Agents as a team</Eyebrow> |
| 182 | <h2 className="mt-3 max-w-3xl text-3xl font-semibold tracking-tight text-balance sm:text-4xl"> |
| 183 | Hosting git is the easy part. g1t is built for what comes after: many agents, one |
| 184 | codebase, nobody refereeing. |
| 185 | </h2> |
| 186 | <div className="mt-12 grid gap-4 md:grid-cols-3"> |
| 187 | <Card title="Outcomes, not pull requests" art={<PlanArt className="w-full" />} tags={["Brief", "Plan", "Dependencies", "Checks"]}> |
| 188 | Write what you want. A planner agent turns it into issues with acceptance checks and |
| 189 | the order they depend on; agents start on each one as it unblocks. |
| 190 | </Card> |
| 191 | <Card title="Agents that work as a team" art={<TeamArt className="w-full" />} tags={["Overlap", "Handoffs", "Questions", "People first"]}> |
| 192 | Every agent is told what the others are changing. They open issues for what they find |
| 193 | instead of widening their change, comment on each other's work, and defer to people. |
| 194 | </Card> |
| 195 | <Card title="Main only moves forward" art={<QueueArt className="w-full" />} tags={["Merge queue", "Speculative", "Contract checks"]}> |
| 196 | Changes are tested together with whatever lands before them, against every check that |
| 197 | ever passed. One that breaks goes back to its agent; main never sees it. |
| 198 | </Card> |
| 199 | <Card title="Why is this line here?" art={<WhyArt className="w-full" />} tags={["Why-blame", "Sessions", "Provenance"]} wide> |
| 200 | Pick any line. g1t shows the commit that last changed it, the pull request and issue |
| 201 | it came from, and the agent's own account of why: the reasoning stays with the code. |
| 202 | </Card> |
| 203 | <article className="rounded-3xl bg-surface p-7 ring-1 ring-line"> |
| 204 | <h3 className="text-lg font-semibold tracking-tight">And everything you expect</h3> |
| 205 | <p className="mt-2 text-sm leading-6 text-muted"> |
| 206 | Work by hand exactly as you would on GitHub. Agents are a teammate you can assign, |
| 207 | not a different way of working. |
| 208 | </p> |
| 209 | <Tags items={BASICS} /> |
| 210 | </article> |
| 211 | </div> |
| 212 | </section> |
| 213 | |
| 214 | {/* How it flows */} |
| 215 | <section className="border-y border-line bg-surface/40"> |
| 216 | <div className="mx-auto max-w-6xl px-4 py-24"> |
| 217 | <Eyebrow>How it flows</Eyebrow> |
| 218 | <h2 className="mt-3 text-3xl font-semibold tracking-tight">From a sentence to main</h2> |
| 219 | <ol className="relative mt-12 grid gap-6 md:grid-cols-5"> |
| 220 | <span |
| 221 | aria-hidden="true" |
| 222 | className="absolute top-4 right-8 left-8 hidden h-px bg-gradient-to-r from-merged/50 via-warn/50 to-accent/70 md:block" |
| 223 | /> |
| 224 | {FLOW.map(([step, about], index) => ( |
| 225 | <li key={step} className="relative"> |
| 226 | <span |
| 227 | className={`relative flex size-8 items-center justify-center rounded-full font-mono text-xs ring-1 ${ |
| 228 | index === FLOW.length - 1 |
| 229 | ? "bg-accent text-bg ring-accent" |
| 230 | : "bg-bg text-fg-soft ring-line-strong" |
| 231 | }`} |
| 232 | > |
| 233 | {index + 1} |
| 234 | </span> |
| 235 | <h3 className="mt-4 font-semibold">{step}</h3> |
| 236 | <p className="mt-1.5 text-sm leading-6 text-muted">{about}</p> |
| 237 | </li> |
| 238 | ))} |
| 239 | </ol> |
| 240 | </div> |
| 241 | </section> |
| 242 | |
| 243 | {/* Connect */} |
| 244 | <section className="mx-auto grid max-w-6xl items-center gap-12 px-4 py-24 lg:grid-cols-2"> |
| 245 | <div> |
| 246 | <Eyebrow>Bring your own agent</Eyebrow> |
| 247 | <h2 className="mt-3 text-3xl font-semibold tracking-tight text-balance"> |
| 248 | Any agent joins the team with one command |
| 249 | </h2> |
| 250 | <p className="mt-4 max-w-md leading-7 text-muted"> |
| 251 | Add g1t to Claude Code and it can read the plan, take an issue, open a pull request |
| 252 | with a fork to push to and see what the others are doing. Install the hook and its |
| 253 | session is recorded onto the pull request as it works. g1t's own agents use the |
| 254 | same tools. |
| 255 | </p> |
| 256 | <div className="mt-6"> |
| 257 | <ButtonLink to="https://docs.g1t.sh/guides/bring-your-own-agent/" variant="quiet"> |
| 258 | Connect an agent |
| 259 | <ArrowRight size={14} /> |
| 260 | </ButtonLink> |
| 261 | </div> |
| 262 | </div> |
| 263 | <div className="space-y-3"> |
| 264 | <CopyLine prompt text="claude mcp add --transport http g1t https://mcp.g1t.sh" /> |
| 265 | <CopyLine prompt text="curl -fsSL https://g1t.sh/install/claude.sh | sh" /> |
| 266 | <CopyLine prompt text="git clone https://g1t.sh/syntaqx/g1t.git" /> |
| 267 | <CopyLine prompt text="curl https://api.g1t.sh/repos/syntaqx/g1t/queue" /> |
| 268 | </div> |
| 269 | </section> |
| 270 | |
| 271 | {/* Your stack */} |
| 272 | <section className="mx-auto grid max-w-6xl items-center gap-12 px-4 pb-24 lg:grid-cols-2"> |
| 273 | <div className="lg:order-2"> |
| 274 | <Eyebrow>Your stack</Eyebrow> |
| 275 | <h2 className="mt-3 text-3xl font-semibold tracking-tight text-balance"> |
| 276 | Your models. Your alerts. Your tickets. |
| 277 | </h2> |
| 278 | <p className="mt-4 max-w-md leading-7 text-muted"> |
| 279 | Run g1t's agents on g1t's models, or on your own accounts with any of the labs and |
| 280 | platforms, and choose which model does which work. Keys stay with g1t: an agent's |
| 281 | sandbox only ever holds a token for its own run. |
| 282 | </p> |
| 283 | <p className="mt-4 max-w-md leading-7 text-muted"> |
| 284 | Connect Sentry and a new error becomes an issue, an agent fixes it, and Sentry hears |
| 285 | it was resolved, before anyone was paged. Mention TECH-1234 and the agent reads the |
| 286 | Jira ticket. |
| 287 | </p> |
| 288 | <div className="mt-6"> |
| 289 | <ButtonLink to="https://docs.g1t.sh/guides/integrations/" variant="quiet"> |
| 290 | See the integrations |
| 291 | <ArrowRight size={14} /> |
| 292 | </ButtonLink> |
| 293 | </div> |
| 294 | </div> |
| 295 | <div className="space-y-6 lg:order-1"> |
| 296 | <div className="grid grid-cols-3 gap-2 sm:grid-cols-4"> |
| 297 | {STACK.map((provider) => ( |
| 298 | <div |
| 299 | key={provider} |
| 300 | className="flex flex-col items-center gap-2 rounded-xl bg-surface px-2 py-3 ring-1 ring-line" |
| 301 | title={PROVIDERS[provider].label} |
| 302 | > |
| 303 | <ProviderMark provider={provider} size={30} /> |
| 304 | <span className="w-full text-center text-xs leading-tight text-muted">{PROVIDERS[provider].label}</span> |
| 305 | </div> |
| 306 | ))} |
| 307 | </div> |
| 308 | <ol className="space-y-2 font-mono text-[0.8125rem]"> |
| 309 | {LOOP.map((step, index) => ( |
| 310 | <li key={step} className="flex items-center gap-3 text-fg-soft"> |
| 311 | <span className="flex size-6 shrink-0 items-center justify-center rounded-full bg-surface text-xs text-faint ring-1 ring-line"> |
| 312 | {index + 1} |
| 313 | </span> |
| 314 | {step} |
| 315 | </li> |
| 316 | ))} |
| 317 | </ol> |
| 318 | </div> |
| 319 | </section> |
| 320 | |
| 321 | {/* Comparison */} |
| 322 | <section className="mx-auto max-w-6xl px-4 pb-24"> |
| 323 | <Eyebrow>What changes</Eyebrow> |
| 324 | <h2 className="mt-3 text-3xl font-semibold tracking-tight">The same git. A different job.</h2> |
| 325 | <div className="mt-10 overflow-hidden rounded-3xl ring-1 ring-line"> |
| 326 | <div className="grid grid-cols-[1fr_1.3fr_1.6fr] bg-surface text-sm font-medium"> |
| 327 | <div className="px-5 py-3" /> |
| 328 | <div className="px-5 py-3 text-muted">A forge built for people</div> |
| 329 | <div className="px-5 py-3 text-accent">g1t</div> |
| 330 | </div> |
| 331 | {COMPARISON.map(([topic, before, after]) => ( |
| 332 | <div key={topic} className="grid grid-cols-[1fr_1.3fr_1.6fr] border-t border-line text-sm"> |
| 333 | <div className="px-5 py-4 font-medium">{topic}</div> |
| 334 | <div className="px-5 py-4 text-muted">{before}</div> |
| 335 | <div className="px-5 py-4">{after}</div> |
| 336 | </div> |
| 337 | ))} |
| 338 | </div> |
| 339 | </section> |
| 340 | |
| 341 | {/* Open source */} |
| 342 | <section className="mx-auto max-w-6xl px-4 pb-8"> |
| 343 | <div className="dusk relative overflow-hidden rounded-3xl p-10 text-center ring-1 ring-line"> |
| 344 | <h2 className="text-2xl font-semibold tracking-tight">Open source, and built on itself</h2> |
| 345 | <p className="mx-auto mt-3 max-w-xl text-fg-soft/80"> |
| 346 | g1t's own source lives on g1t, and its changes land through its own queue. It runs |
| 347 | entirely on Cloudflare's developer platform. |
| 348 | </p> |
| 349 | <ul className="mt-6 flex flex-wrap justify-center gap-2"> |
| 350 | {PLATFORM.map((name) => ( |
| 351 | <li key={name} className="rounded-full bg-bg/60 px-3 py-1 font-mono text-xs text-fg-soft ring-1 ring-white/10"> |
| 352 | {name} |
| 353 | </li> |
| 354 | ))} |
| 355 | </ul> |
| 356 | <div className="mt-8 flex flex-wrap justify-center gap-3"> |
| 357 | <ButtonLink to="/syntaqx/g1t" variant="primary" large> |
| 358 | Browse the source |
| 359 | </ButtonLink> |
| 360 | <ButtonLink to="/register" variant="quiet" large> |
| 361 | Create an account |
| 362 | </ButtonLink> |
| 363 | </div> |
| 364 | </div> |
| 365 | |
| 366 | {repos.length > 0 && ( |
| 367 | <div className="mt-20"> |
| 368 | <div className="flex items-baseline justify-between"> |
| 369 | <h2 className="text-sm font-medium text-muted">Public repositories</h2> |
| 370 | <Link to="/explore" className="text-sm text-muted hover:text-fg"> |
| 371 | Explore all |
| 372 | </Link> |
| 373 | </div> |
| 374 | <RepoList repos={repos.slice(0, 6)} /> |
| 375 | </div> |
| 376 | )} |
| 377 | </section> |
| 378 | </main> |
| 379 | ); |
| 380 | } |