diff options
Diffstat (limited to 'apps/landing')
| -rw-r--r-- | apps/landing/package.json | 4 | ||||
| -rw-r--r-- | apps/landing/src/App.tsx | 2 | ||||
| -rw-r--r-- | apps/landing/src/Apps.tsx | 112 | ||||
| -rw-r--r-- | apps/landing/src/Homepage.tsx | 34 | ||||
| -rw-r--r-- | apps/landing/src/Navbar.tsx | 19 | ||||
| -rw-r--r-- | apps/landing/src/Pricing.tsx | 157 | ||||
| -rw-r--r-- | apps/landing/src/constants.ts | 2 |
7 files changed, 262 insertions, 68 deletions
diff --git a/apps/landing/package.json b/apps/landing/package.json index b8329356..7c2a485d 100644 --- a/apps/landing/package.json +++ b/apps/landing/package.json @@ -20,8 +20,8 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "lucide-react": "^0.501.0", - "react": "^19.1.0", - "react-dom": "^19.1.0", + "react": "^19.2.1", + "react-dom": "^19.2.1", "react-router": "^7.7.1", "sharp": "^0.33.3", "tailwind-merge": "^2.2.1", diff --git a/apps/landing/src/App.tsx b/apps/landing/src/App.tsx index 45a019c5..7448aa86 100644 --- a/apps/landing/src/App.tsx +++ b/apps/landing/src/App.tsx @@ -1,3 +1,4 @@ +import Apps from "@/src/Apps"; import Homepage from "@/src/Homepage"; import Pricing from "@/src/Pricing"; import Privacy from "@/src/Privacy"; @@ -10,6 +11,7 @@ export default function App() { <BrowserRouter> <Routes> <Route path="/" element={<Homepage />} /> + <Route path="/apps" element={<Apps />} /> <Route path="/pricing" element={<Pricing />} /> <Route path="/privacy" element={<Privacy />} /> </Routes> diff --git a/apps/landing/src/Apps.tsx b/apps/landing/src/Apps.tsx new file mode 100644 index 00000000..7e6a7137 --- /dev/null +++ b/apps/landing/src/Apps.tsx @@ -0,0 +1,112 @@ +import NavBar from "./Navbar"; +import appStoreBadge from "/app-store-badge.png?url"; +import chromeExtensionBadge from "/chrome-extension-badge.png?url"; +import firefoxAddonBadge from "/firefox-addon.png?url"; +import playStoreBadge from "/google-play-badge.webp?url"; + +interface Listing { + name: string; + description: string; + url: string; + badge: string; +} + +const mobileApps: Listing[] = [ + { + name: "iOS App", + description: "Save links and notes from your iPhone and iPad.", + url: "https://apps.apple.com/us/app/karakeep-app/id6479258022", + badge: appStoreBadge, + }, + { + name: "Android App", + description: "Capture and organize content on Android devices.", + url: "https://play.google.com/store/apps/details?id=app.hoarder.hoardermobile&pcampaignid=web_share", + badge: playStoreBadge, + }, +]; + +const browserExtensions: Listing[] = [ + { + name: "Chrome Extension", + description: "One-click saving from Chrome.", + url: "https://chromewebstore.google.com/detail/karakeep/kgcjekpmcjjogibpjebkhaanilehneje", + badge: chromeExtensionBadge, + }, + { + name: "Firefox Add-on", + description: "Save pages directly from Firefox.", + url: "https://addons.mozilla.org/en-US/firefox/addon/karakeep/", + badge: firefoxAddonBadge, + }, +]; + +function ListingSection({ + title, + description, + items, +}: { + title: string; + description: string; + items: Listing[]; +}) { + return ( + <section className="rounded-2xl border border-gray-200 bg-white p-6 shadow-sm sm:p-8"> + <h2 className="text-2xl font-semibold text-gray-900">{title}</h2> + <p className="mt-2 text-gray-600">{description}</p> + <div className="mt-6 grid grid-cols-1 gap-5 sm:grid-cols-2"> + {items.map((item) => ( + <a + key={item.name} + href={item.url} + target="_blank" + rel="noreferrer" + className="flex flex-col gap-4 rounded-xl border border-gray-200 p-4 transition-colors hover:border-gray-300" + > + <div> + <h3 className="font-semibold text-gray-900">{item.name}</h3> + <p className="mt-1 text-sm text-gray-600">{item.description}</p> + </div> + <div className="h-12 w-fit"> + <img + className="h-full w-auto object-contain" + alt={item.name} + src={item.badge} + /> + </div> + </a> + ))} + </div> + </section> + ); +} + +export default function Apps() { + return ( + <div className="min-h-screen bg-gray-50"> + <div className="container mx-auto pb-16"> + <NavBar /> + <main className="px-4 py-8 sm:px-6 sm:py-14"> + <h1 className="text-4xl font-bold text-gray-900 sm:text-5xl"> + Apps & Extensions + </h1> + <p className="mt-3 max-w-2xl text-base text-gray-600 sm:text-lg"> + Use Karakeep anywhere with our mobile apps and browser extensions. + </p> + <div className="mt-10 space-y-6"> + <ListingSection + title="Mobile Apps" + description="Take your bookmarks with you on iOS and Android." + items={mobileApps} + /> + <ListingSection + title="Browser Extensions" + description="Save content from your browser in one click." + items={browserExtensions} + /> + </div> + </main> + </div> + </div> + ); +} diff --git a/apps/landing/src/Homepage.tsx b/apps/landing/src/Homepage.tsx index 07229549..26a0f2fe 100644 --- a/apps/landing/src/Homepage.tsx +++ b/apps/landing/src/Homepage.tsx @@ -8,6 +8,7 @@ import { Github, Highlighter, Plug, + Rocket, Rss, Server, Star, @@ -17,7 +18,12 @@ import { Workflow, } from "lucide-react"; -import { DEMO_LINK, DOCS_LINK, GITHUB_LINK } from "./constants"; +import { + CLOUD_SIGNUP_LINK, + DEMO_LINK, + DOCS_LINK, + GITHUB_LINK, +} from "./constants"; import NavBar from "./Navbar"; import appStoreBadge from "/app-store-badge.png?url"; import chromeExtensionBadge from "/chrome-extension-badge.png?url"; @@ -119,6 +125,29 @@ const featuresList = [ const currentYear = new Date().getFullYear(); +function Banner() { + return ( + <div className="border-b border-amber-200 bg-gradient-to-r from-amber-50 via-orange-50 to-rose-50 px-3 py-2 text-center sm:px-4 sm:py-3"> + <div className="container flex flex-wrap items-center justify-center gap-x-2 gap-y-1 text-xs text-slate-700 sm:gap-3 sm:text-base"> + <div className="flex flex-wrap items-center justify-center gap-1 px-2 py-0.5 sm:px-3 sm:py-1"> + <Rocket className="size-4 text-amber-600 sm:size-5" /> + <span className="font-semibold text-slate-800"> + Karakeep Cloud Public Beta is Now Live + </span> + </div> + <a + href={CLOUD_SIGNUP_LINK} + target="_blank" + rel="noreferrer" + className="inline-flex items-center justify-center gap-1 text-xs font-semibold text-amber-700 underline decoration-amber-400 underline-offset-2 transition-all hover:text-amber-800 sm:rounded-full sm:border sm:border-amber-300 sm:bg-amber-500 sm:px-3 sm:py-1 sm:text-sm sm:text-white sm:no-underline sm:shadow-sm sm:hover:border-amber-400 sm:hover:bg-amber-600" + > + Join Now <span className="hidden sm:inline">→</span> + </a> + </div> + </div> + ); +} + function Hero() { return ( <div className="mt-10 flex flex-grow flex-col items-center justify-center gap-6 sm:mt-20"> @@ -144,7 +173,7 @@ function Hero() { className="inline-flex items-center gap-2 rounded-full border border-gray-200 bg-white px-4 py-2 text-sm text-gray-700 shadow-sm transition-all hover:border-gray-300 hover:shadow-md" > <Star className="size-4 fill-yellow-400 text-yellow-400" /> - <span className="font-semibold">21k</span> + <span className="font-semibold">22k</span> <span className="text-gray-500">stars on GitHub</span> </a> </div> @@ -269,6 +298,7 @@ function Screenshots() { export default function Homepage() { return ( <div className="flex min-h-screen flex-col"> + <Banner /> <div className="container flex flex-col pb-10"> <NavBar /> <Hero /> diff --git a/apps/landing/src/Navbar.tsx b/apps/landing/src/Navbar.tsx index 15355170..e60a6d7b 100644 --- a/apps/landing/src/Navbar.tsx +++ b/apps/landing/src/Navbar.tsx @@ -2,7 +2,7 @@ import { buttonVariants } from "@/components/ui/button"; import { cn } from "@/lib/utils"; import { Link } from "react-router"; -import { DEMO_LINK, DOCS_LINK, GITHUB_LINK } from "./constants"; +import { CLOUD_SIGNUP_LINK, DOCS_LINK, GITHUB_LINK } from "./constants"; import Logo from "/icons/karakeep-full.svg?url"; export default function NavBar() { @@ -31,6 +31,17 @@ export default function NavBar() { > Login </a> + <a + href={CLOUD_SIGNUP_LINK} + target="_blank" + className={cn( + "px-3 py-1.5 text-xs", + buttonVariants({ variant: "default", size: "sm" }), + )} + rel="noreferrer" + > + Get Started + </a> </div> {/* Desktop navigation - show all items */} @@ -66,15 +77,15 @@ export default function NavBar() { Login </a> <a - href={DEMO_LINK} + href={CLOUD_SIGNUP_LINK} target="_blank" className={cn( - "text flex h-full w-28 gap-2", + "text flex h-full w-32 gap-2", buttonVariants({ variant: "default" }), )} rel="noreferrer" > - Try Demo + Get Started </a> </div> </div> diff --git a/apps/landing/src/Pricing.tsx b/apps/landing/src/Pricing.tsx index 9240ba76..962a09fe 100644 --- a/apps/landing/src/Pricing.tsx +++ b/apps/landing/src/Pricing.tsx @@ -2,9 +2,11 @@ import { buttonVariants } from "@/components/ui/button"; import { cn } from "@/lib/utils"; import { Check, ExternalLink } from "lucide-react"; -import { DOCS_LINK, GITHUB_LINK, WAITLIST_LINK } from "./constants"; +import { CLOUD_SIGNUP_LINK, DOCS_LINK, GITHUB_LINK } from "./constants"; import NavBar from "./Navbar"; +const CONTACT_EMAIL = "mailto:support@karakeep.app"; + const pricingTiers = [ { name: "Free", @@ -17,7 +19,7 @@ const pricingTiers = [ "Mobile & web apps", "Browser extensions", ], - buttonText: "Join Waitlist", + buttonText: "Get Started", buttonVariant: "outline" as const, popular: false, }, @@ -34,7 +36,7 @@ const pricingTiers = [ "Mobile & web apps", "Browser extensions", ], - buttonText: "Join Waitlist", + buttonText: "Get Started", buttonVariant: "default" as const, popular: true, }, @@ -56,6 +58,23 @@ const pricingTiers = [ popular: false, isGitHub: true, }, + { + name: "Corporate", + price: "Custom", + period: "per seat", + description: "For teams and organizations", + features: [ + "Everything in Pro", + "Custom deployment & domain", + "Single Sign-On (SSO)", + "User management", + "Priority support", + ], + buttonText: "Contact Us", + buttonVariant: "outline" as const, + popular: false, + isContact: true, + }, ]; function PricingHeader() { @@ -75,66 +94,86 @@ function PricingHeader() { } function PricingCards() { - return ( - <div className="mx-auto mt-16 grid max-w-6xl grid-cols-1 gap-8 px-6 md:grid-cols-3"> - {pricingTiers.map((tier) => ( - <div - key={tier.name} - className={cn( - "relative rounded-2xl border bg-white p-8 shadow-sm", - tier.popular && "border-purple-500 shadow-lg", + const renderCard = (tier: (typeof pricingTiers)[number]) => ( + <div + key={tier.name} + className={cn( + "relative rounded-2xl border bg-white p-8 shadow-sm", + tier.popular && "border-purple-500 shadow-lg", + )} + > + <div className="text-center"> + <h3 className="text-xl font-semibold">{tier.name}</h3> + <div className="mt-4 flex items-baseline justify-center"> + <span className="text-4xl font-bold">{tier.price}</span> + {tier.period && ( + <span className="ml-1 text-gray-500">/{tier.period}</span> )} - > - <div className="text-center"> - <h3 className="text-xl font-semibold">{tier.name}</h3> - <div className="mt-4 flex items-baseline justify-center"> - <span className="text-4xl font-bold">{tier.price}</span> - {tier.period && ( - <span className="ml-1 text-gray-500">/{tier.period}</span> - )} - </div> - <p className="mt-2 text-gray-600">{tier.description}</p> - </div> + </div> + <p className="mt-2 text-gray-600">{tier.description}</p> + </div> - <ul className="mt-8 space-y-3"> - {tier.features.map((feature) => ( - <li key={feature} className="flex items-center"> - <Check className="h-5 w-5 text-green-500" /> - <span className="ml-3 text-gray-700">{feature}</span> - </li> - ))} - </ul> + <ul className="mt-8 space-y-3"> + {tier.features.map((feature) => ( + <li key={feature} className="flex items-center"> + <Check className="h-5 w-5 text-green-500" /> + <span className="ml-3 text-gray-700">{feature}</span> + </li> + ))} + </ul> - <div className="mt-8"> - {tier.isGitHub ? ( - <a - href={GITHUB_LINK} - target="_blank" - className={cn( - "flex w-full items-center justify-center gap-2", - buttonVariants({ variant: tier.buttonVariant, size: "lg" }), - )} - rel="noreferrer" - > - <ExternalLink className="h-4 w-4" /> - {tier.buttonText} - </a> - ) : ( - <a - href={WAITLIST_LINK} - target="_blank" - className={cn( - "flex w-full items-center justify-center", - buttonVariants({ variant: tier.buttonVariant, size: "lg" }), - )} - rel="noreferrer" - > - {tier.buttonText} - </a> + <div className="mt-8"> + {tier.isContact ? ( + <a + href={CONTACT_EMAIL} + className={cn( + "flex w-full items-center justify-center", + buttonVariants({ variant: tier.buttonVariant, size: "lg" }), )} - </div> - </div> - ))} + > + {tier.buttonText} + </a> + ) : tier.isGitHub ? ( + <a + href={GITHUB_LINK} + target="_blank" + className={cn( + "flex w-full items-center justify-center gap-2", + buttonVariants({ variant: tier.buttonVariant, size: "lg" }), + )} + rel="noreferrer" + > + <ExternalLink className="h-4 w-4" /> + {tier.buttonText} + </a> + ) : ( + <a + href={CLOUD_SIGNUP_LINK} + target="_blank" + className={cn( + "flex w-full items-center justify-center", + buttonVariants({ variant: tier.buttonVariant, size: "lg" }), + )} + rel="noreferrer" + > + {tier.buttonText} + </a> + )} + </div> + </div> + ); + + return ( + <div className="mx-auto mt-16 max-w-6xl px-6"> + {/* First 3 tiers */} + <div className="grid grid-cols-1 gap-8 md:grid-cols-3"> + {pricingTiers.slice(0, 3).map(renderCard)} + </div> + + {/* Corporate tier - centered below */} + <div className="mt-8 flex justify-center"> + <div className="w-full md:max-w-sm">{renderCard(pricingTiers[3])}</div> + </div> </div> ); } diff --git a/apps/landing/src/constants.ts b/apps/landing/src/constants.ts index b75cecae..617b8f4b 100644 --- a/apps/landing/src/constants.ts +++ b/apps/landing/src/constants.ts @@ -1,4 +1,4 @@ export const GITHUB_LINK = "https://github.com/karakeep-app/karakeep"; export const DOCS_LINK = "https://docs.karakeep.app"; export const DEMO_LINK = "https://try.karakeep.app"; -export const WAITLIST_LINK = "https://tally.so/r/wo8zzx"; +export const CLOUD_SIGNUP_LINK = "https://cloud.karakeep.app/signup"; |
