| 1 | import { ArrowRight, Check, Ghost } from "lucide-react"; |
| 2 | import Image from "next/image |
| 3 | import Link from "next/link |
| 4 | import { redirect } |
| 5 | import type { ReactNode |
| 6 | |
| 7 | import { ThemePicker } |
| 8 | import { Button } |
| 9 | |
| 10 | import { getServerSession } |
| 11 | import { DOCS_URL } |
| 12 | |
| 13 | const FEATURES = [ |
| 14 | { |
| 15 | title: "Your history survives a bad day.", |
| 16 | body: "Every push is backed up the moment it lands. If a server goes down, your code doesn’t go with it.", |
| 17 | }, |
| 18 | { |
| 19 | title: "Stop asking where that code lives.", |
| 20 | body: "Search every repository you can read at once. New code shows up as soon as it’s pushed.", |
| 21 | }, |
| 22 | { |
| 23 | title: "Give access without giving away the keys.", |
| 24 | body: "Organizations and teams, with the right access for each person. A contractor sees one repository. Everyone else sees nothing.", |
| 25 | }, |
| 26 | { |
| 27 | title: "Know which commits are really yours.", |
| 28 | body: "Sign commits with your GPG key and Ghost marks them Verified, where everyone can see it.", |
| 29 | }, |
| 30 | ]; |
| 31 | |
| 32 | const ALSO = [ |
| 33 | "Issues, labels and assignees", |
| 34 | "#123 cross references", |
| 35 | "Merge, squash or rebase", |
| 36 | "Tags and releases", |
| 37 | "Forks and stars", |
| 38 | "Highlighting for 300+ languages", |
| 39 | "Language breakdown", |
| 40 | "Contribution graph", |
| 41 | "Notifications", |
| 42 | ]; |
| 43 | |
| 44 | export default async function |
| 45 | const session = |
| 46 | if (session) redirect |
| 47 | |
| 48 | return ( |
| 49 | <div className="flex min-h-full flex-col overflow-x-clip |
| 50 | <nav |
| 51 | aria-label="Primary" |
| 52 | className="mx-auto flex w-full max-w-7xl items-center gap-1 px-5 py-4 md:px-10 md:py-6" |
| 53 | > |
| 54 | <span className="mr-auto flex items-center gap-2.5 text-lg font-semibold tracking-tight |
| 55 | <Ghost className=" |
| 56 | Ghost |
| 57 | </span> |
| 58 | <Button |
| 59 | asChild |
| 60 | variant="ghost" |
| 61 | size="sm" |
| 62 | className="hidden rounded-full text-muted-foreground md:inline-flex" |
| 63 | > |
| 64 | <a href={DOCS_URL |
| 65 | </Button> |
| 66 | <Button |
| 67 | asChild |
| 68 | variant="ghost" |
| 69 | size="sm" |
| 70 | className="hidden rounded-full text-muted-foreground md:inline-flex" |
| 71 | > |
| 72 | <a href={DOCS_URL |
| 73 | </Button> |
| 74 | <ThemePicker /> |
| 75 | <Button |
| 76 | asChild |
| 77 | variant="ghost" |
| 78 | size="sm" |
| 79 | className="rounded-full px-2.5 md:px-3" |
| 80 | > |
| 81 | <Link href=" |
| 82 | </Button> |
| 83 | <Button |
| 84 | asChild |
| 85 | size="sm" |
| 86 | className="rounded-full bg-foreground text-background hover:bg-foreground/90" |
| 87 | > |
| 88 | <Link href=" |
| 89 | </Button> |
| 90 | </nav> |
| 91 | |
| 92 | <main className="mx-auto flex w-full max-w-7xl flex-1 flex-col px-5 md:px-10 |
| 93 | <section className="flex flex-col gap-6 pt-12 md:gap-10 md:pt-24 |
| 94 | <p className="flex items-center gap-2.5 font-mono text-[11px] tracking-[0.06em] text-primary uppercase md:text-[13px] md:tracking-[0.08em] |
| 95 | <span aria-hidden className |
| 96 | Git hosting · Pull requests · Code review |
| 97 | </p> |
| 98 | <h1 className="max-w-[11ch] text-[46px] leading-none font-semibold tracking-[-0.05em] text-balance md:max-w-5xl md:text-8xl md:leading-[0.96] |
| 99 | Keep shipping when GitHub is down. |
| 100 | </h1> |
| 101 | <div className="flex flex-col gap-7 md:flex-row md:items-end md:justify-between |
| 102 | <p className="max-w-lg text-[17px] leading-relaxed text-pretty text-muted-foreground md:text-xl |
| 103 | Pull requests, reviews, issues and search on a host that |
| 104 | isn’t having an outage today. Use ours, or run it yourself. |
| 105 | </p> |
| 106 | <div className="flex flex-col gap-2.5 sm:flex-row |
| 107 | <Button |
| 108 | asChild |
| 109 | size="lg" |
| 110 | className="h-13 rounded-full px-6 text-base" |
| 111 | > |
| 112 | <Link href=" |
| 113 | Create an account |
| 114 | <ArrowRight /> |
| 115 | </Link> |
| 116 | </Button> |
| 117 | <Button |
| 118 | asChild |
| 119 | size="lg" |
| 120 | variant="outline" |
| 121 | className="h-13 rounded-full px-6 text-base" |
| 122 | > |
| 123 | <a href={DOCS_URL |
| 124 | </Button> |
| 125 | </div> |
| 126 | </div> |
| 127 | </section> |
| 128 | |
| 129 | <section className="mt-24 hidden overflow-hidden rounded-[28px] bg-accent px-16 pt-16 md:block |
| 130 | <Image |
| 131 | src="/landing/pull-request.png" |
| 132 | alt="A pull request in Ghost showing a split diff of a workflow file" |
| 133 | width={2296} |
| 134 | height={1640} |
| 135 | priority |
| 136 | className="rounded-t-xl border border-b-0 shadow-[0_30px_60px_-30px_oklch(0_0_0/0.25)]" |
| 137 | /> |
| 138 | </section> |
| 139 | |
| 140 | <section className="flex flex-col gap-9 pt-24 md:pt-40 lg:flex-row lg:justify-between lg:gap-16 |
| 141 | <div className="flex flex-col gap-4 md:max-w-md md:gap-5 lg:max-w-sm |
| 142 | <h2 className="text-4xl font-semibold tracking-[-0.04em] text-balance md:text-[52px] md:leading-[1.04] |
| 143 | Everything you use daily. Minus the outages. |
| 144 | </h2> |
| 145 | <p className="leading-relaxed text-pretty text-muted-foreground md:text-[17px] |
| 146 | Same git, same pull requests, same reviews. Change the remote and |
| 147 | keep working. |
| 148 | </p> |
| 149 | </div> |
| 150 | <ol className="flex min-w-0 flex-col lg:flex-1 xl:w-[720px] xl:flex-none |
| 151 | {FEATURES.map(( |
| 152 | <li |
| 153 | key={title} |
| 154 | className="flex border-b py-6 first:pt-1 md:py-8 md:first:pt-2.5" |
| 155 | > |
| 156 | <span className="w-10 shrink-0 font-mono text-[13px] leading-[25px] text-muted-foreground md:w-18 md:text-sm md:leading-7 |
| 157 | {String(index + |
| 158 | </span> |
| 159 | <div className="flex min-w-0 flex-col gap-2 |
| 160 | <h3 className="text-xl leading-[25px] font-semibold tracking-[-0.02em] md:text-2xl md:leading-7 |
| 161 | {title} |
| 162 | </h3> |
| 163 | <p className="text-[15px] leading-[23px] text-pretty text-muted-foreground md:text-base md:leading-[25px] |
| 164 | {body} |
| 165 | </p> |
| 166 | </div> |
| 167 | </li> |
| 168 | ))} |
| 169 | </ol> |
| 170 | </section> |
| 171 | |
| 172 | <section className="hidden gap-6 pt-30 md:flex |
| 173 | <ScreenCard |
| 174 | src="/landing/commits.png" |
| 175 | alt="Commit history with Verified and Unverified signature badges" |
| 176 | anchor="right" |
| 177 | title="Verified, or not" |
| 178 | body="See at a glance which commits are really signed." |
| 179 | /> |
| 180 | <ScreenCard |
| 181 | src="/landing/repository.png" |
| 182 | alt="A repository page listing folders with their latest commits" |
| 183 | anchor="left" |
| 184 | title="Browse any branch" |
| 185 | body="Files, latest changes, releases and languages in one view." |
| 186 | /> |
| 187 | </section> |
| 188 | |
| 189 | <section className="-mx-2 pt-24 md:mx-0 md:pt-40 |
| 190 | <div className="relative flex flex-col gap-8 overflow-hidden rounded-3xl bg-foreground px-5 pt-10 pb-5 text-background md:gap-14 md:rounded-[28px] md:p-20 |
| 191 | <div |
| 192 | aria-hidden |
| 193 | className="absolute -top-44 -right-44 size-[420px] rounded-full bg-[radial-gradient(circle,color-mix(in_oklch,var(--primary)_35%,transparent)_0%,transparent_65%)] md:-top-56 md:-right-40 md:size-[720px]" |
| 194 | /> |
| 195 | <div className="relative flex flex-col gap-4 px-2 md:px-0 lg:flex-row lg:items-end lg:justify-between lg:gap-12 |
| 196 | <div className="flex flex-col gap-4 md:shrink-0 |
| 197 | <p className="font-mono text-[13px] tracking-[0.08em] text-background/60 uppercase |
| 198 | Two ways to run it |
| 199 | </p> |
| 200 | <h2 className="text-[40px] leading-[1.05] font-semibold tracking-[-0.045em] md:text-[56px] md:leading-none xl:text-[64px] |
| 201 | Hosted by us. Or by you. |
| 202 | </h2> |
| 203 | </div> |
| 204 | <p className="leading-relaxed text-background/70 md:max-w-md md:text-lg lg:max-w-xs |
| 205 | Same Ghost either way. Start on ours today, or run it on your |
| 206 | own server. |
| 207 | </p> |
| 208 | </div> |
| 209 | <div className="relative flex flex-col gap-3 md:flex-row md:gap-5 |
| 210 | <Plan |
| 211 | label="Ghost Cloud" |
| 212 | tag="Recommended" |
| 213 | title="We run it." |
| 214 | body="Sign up, add a remote, push. Nothing to install." |
| 215 | points={[ |
| 216 | "Ready in about a minute", |
| 217 | "Updates handled for you", |
| 218 | "Invite your team on day one", |
| 219 | ]} |
| 220 | featured |
| 221 | > |
| 222 | <Button |
| 223 | asChild |
| 224 | size="lg" |
| 225 | className="h-12 rounded-full bg-background px-5 text-base text-foreground hover:bg-background/90" |
| 226 | > |
| 227 | <Link href=" |
| 228 | Create an account |
| 229 | <ArrowRight /> |
| 230 | </Link> |
| 231 | </Button> |
| 232 | </Plan> |
| 233 | <Plan |
| 234 | label="Self-hosted" |
| 235 | tag="Your server" |
| 236 | title="You run it." |
| 237 | body="The same app on hardware you control." |
| 238 | points={[ |
| 239 | "Your network, your rules", |
| 240 | "Every feature Cloud has", |
| 241 | "Bring your repositories over with a git push", |
| 242 | ]} |
| 243 | > |
| 244 | <Button |
| 245 | asChild |
| 246 | size="lg" |
| 247 | variant="outline" |
| 248 | className="h-12 rounded-full border-background/20 bg-transparent px-5 text-base text-background hover:bg-background/10 hover:text-background" |
| 249 | > |
| 250 | <a href={DOCS_URL |
| 251 | </Button> |
| 252 | </Plan> |
| 253 | </div> |
| 254 | </div> |
| 255 | </section> |
| 256 | |
| 257 | <section className="flex flex-col gap-5 pt-24 md:gap-8 md:pt-40 |
| 258 | <h2 className="text-[26px] leading-tight font-semibold tracking-[-0.03em] md:text-[32px] |
| 259 | Plus the things you reach for daily. |
| 260 | </h2> |
| 261 | <ul className="flex max-w-5xl flex-wrap gap-2 md:gap-2.5 |
| 262 | {ALSO.map(( |
| 263 | <li |
| 264 | key={item} |
| 265 | className="rounded-full border px-3.5 py-2 text-sm md:px-4.5 md:py-2.5 md:text-base" |
| 266 | > |
| 267 | {item} |
| 268 | </li> |
| 269 | ))} |
| 270 | <li className="rounded-full bg-accent px-3.5 py-2 text-sm text-accent-foreground md:px-4.5 md:py-2.5 md:text-base |
| 271 | Light, dark and custom themes |
| 272 | </li> |
| 273 | </ul> |
| 274 | </section> |
| 275 | |
| 276 | <section className="relative flex flex-col items-start gap-7 pt-30 pb-24 md:gap-9 md:pt-50 md:pb-40 |
| 277 | <Ghost |
| 278 | aria-hidden |
| 279 | strokeWidth={1.25} |
| 280 | className="absolute top-36 right-0 hidden size-90 rotate-8 fill-primary/10 text-primary lg:block" |
| 281 | /> |
| 282 | <p className="max-w-[18ch] text-[40px] leading-[1.05] font-semibold tracking-[-0.05em] text-balance md:max-w-[14ch] md:text-[88px] md:leading-[0.98] |
| 283 | Change the remote. Keep the workflow. |
| 284 | </p> |
| 285 | <div className="flex flex-col items-start gap-1 sm:flex-row sm:items-center sm:gap-3.5 |
| 286 | <Button |
| 287 | asChild |
| 288 | size="lg" |
| 289 | className="h-13 rounded-full px-6 text-base" |
| 290 | > |
| 291 | <Link href=" |
| 292 | Create an account |
| 293 | <ArrowRight /> |
| 294 | </Link> |
| 295 | </Button> |
| 296 | <Button |
| 297 | asChild |
| 298 | size="lg" |
| 299 | variant="link" |
| 300 | className="h-13 px-1 text-base text-foreground sm:px-5" |
| 301 | > |
| 302 | <a href={DOCS_URL |
| 303 | </Button> |
| 304 | </div> |
| 305 | </section> |
| 306 | </main> |
| 307 | |
| 308 | <footer className="border-t |
| 309 | <div className="mx-auto flex w-full max-w-7xl flex-col items-start gap-4 px-5 pt-7 pb-10 text-sm text-muted-foreground md:flex-row md:items-center md:justify-between md:px-10 md:pt-8 |
| 310 | <span className="flex items-center gap-2 |
| 311 | <Ghost className=" |
| 312 | <span className="font-semibold text-foreground |
| 313 | <span>— git hosting for teams that ship</span> |
| 314 | </span> |
| 315 | <div className="flex gap-5 md:gap-7 |
| 316 | <a href={DOCS_URL |
| 317 | Docs |
| 318 | </a> |
| 319 | <a href={DOCS_URL |
| 320 | Self-hosting |
| 321 | </a> |
| 322 | </div> |
| 323 | </div> |
| 324 | </footer> |
| 325 | </div> |
| 326 | ); |
| 327 | } |
| 328 | |
| 329 | function ScreenCard({ |
| 330 | src, |
| 331 | alt, |
| 332 | anchor, |
| 333 | title, |
| 334 | body, |
| 335 | }: { |
| 336 | src: string |
| 337 | alt: string |
| 338 | /** Which edge of the screenshot stays in view; the other edge bleeds off the card. */ |
| 339 | anchor: "left |
| 340 | title: string |
| 341 | body: string |
| 342 | }) { |
| 343 | return ( |
| 344 | <figure className="group flex min-w-0 flex-1 flex-col gap-4.5 |
| 345 | <div className="relative h-[420px] overflow-hidden rounded-2xl border bg-accent |
| 346 | <Image |
| 347 | src={src} |
| 348 | alt={alt} |
| 349 | width={2296} |
| 350 | height={1640} |
| 351 | sizes="800px" |
| 352 | className={`absolute top-10 w-[800px] max-w-none border shadow-[0_20px_40px_-20px_oklch(0_0_0/0.28)] transition-transform duration-200 ease-out group-hover:-translate-y-2 ${ |
| 353 | anchor === "right" |
| 354 | ? "right-10 rounded-tr-xl border-l-0" |
| 355 | : "left-10 rounded-tl-xl border-r-0" |
| 356 | }`} |
| 357 | /> |
| 358 | </div> |
| 359 | <figcaption className="flex flex-col gap-1 |
| 360 | <span className="text-lg font-semibold |
| 361 | <span className="text-[15px] text-muted-foreground |
| 362 | </figcaption> |
| 363 | </figure> |
| 364 | ); |
| 365 | } |
| 366 | |
| 367 | function Plan({ |
| 368 | label, |
| 369 | tag, |
| 370 | title, |
| 371 | body, |
| 372 | points, |
| 373 | featured = false, |
| 374 | children, |
| 375 | }: { |
| 376 | label: string |
| 377 | tag: string |
| 378 | title: string |
| 379 | body: string |
| 380 | points: string |
| 381 | featured?: boolean |
| 382 | children: ReactNode |
| 383 | }) { |
| 384 | return ( |
| 385 | <div |
| 386 | className={`relative flex min-w-0 flex-1 flex-col gap-7 overflow-hidden rounded-[22px] px-6 py-7 md:gap-10 md:p-10 ${ |
| 387 | featured |
| 388 | ? "bg-primary text-primary-foreground" |
| 389 | : "border border-background/15 bg-background/5" |
| 390 | }`} |
| 391 | > |
| 392 | {featured && ( |
| 393 | <Ghost |
| 394 | aria-hidden |
| 395 | strokeWidth={0.5} |
| 396 | className="absolute -right-8 -bottom-10 hidden size-60 -rotate-10 fill-primary-foreground/10 text-primary-foreground/20 md:block" |
| 397 | /> |
| 398 | )} |
| 399 | <div className="flex items-center justify-between gap-3 |
| 400 | <span |
| 401 | className={`font-mono text-xs tracking-[0.08em] uppercase ${featured |
| 402 | > |
| 403 | {label} |
| 404 | </span> |
| 405 | <span |
| 406 | className={`rounded-full px-3 py-1 text-[13px] font-medium ${ |
| 407 | featured |
| 408 | ? "bg-primary-foreground text-primary" |
| 409 | : "border border-background/20 text-background/70" |
| 410 | }`} |
| 411 | > |
| 412 | {tag} |
| 413 | </span> |
| 414 | </div> |
| 415 | <div className="flex flex-col gap-2.5 |
| 416 | <h3 className="text-[32px] leading-[1.05] font-semibold tracking-[-0.035em] md:text-[40px] |
| 417 | {title} |
| 418 | </h3> |
| 419 | <p className="leading-relaxed opacity-80 md:text-[17px] |
| 420 | </div> |
| 421 | <ul className="flex flex-col gap-3.5 |
| 422 | {points.map(( |
| 423 | <li key={point |
| 424 | <Check |
| 425 | aria-hidden |
| 426 | strokeWidth={2.4} |
| 427 | className={`size-4.5 shrink-0 ${featured |
| 428 | /> |
| 429 | {point} |
| 430 | </li> |
| 431 | ))} |
| 432 | </ul> |
| 433 | <div className="relative |
| 434 | </div> |
| 435 | ); |
| 436 | } |