aboutsummaryrefslogtreecommitdiffstats
path: root/apps/landing
diff options
context:
space:
mode:
Diffstat (limited to 'apps/landing')
-rw-r--r--apps/landing/package.json4
-rw-r--r--apps/landing/src/App.tsx2
-rw-r--r--apps/landing/src/Apps.tsx112
-rw-r--r--apps/landing/src/Homepage.tsx34
-rw-r--r--apps/landing/src/Navbar.tsx19
-rw-r--r--apps/landing/src/Pricing.tsx157
-rw-r--r--apps/landing/src/constants.ts2
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";