new full landing page

This commit is contained in:
2026-01-27 21:04:24 +00:00
parent 13e678f8e8
commit 99d53d85f6
5 changed files with 326 additions and 333 deletions

View File

@@ -12,10 +12,13 @@ import {
Close as PixelClose, Close as PixelClose,
MessagePlus as PixelCommentSend, MessagePlus as PixelCommentSend,
CreditCard as PixelCreditCard, CreditCard as PixelCreditCard,
CreditCardDelete as PixelCreditCardDelete,
Dashboard as PixelDashboard, Dashboard as PixelDashboard,
Debug as PixelDebug, Debug as PixelDebug,
DebugOff as PixelDebugOff, DebugOff as PixelDebugOff,
Edit as PixelEdit, Edit as PixelEdit,
EyeClosed as PixelEyeClosed,
AddGrid as PixelGridAdd,
Home as PixelHome, Home as PixelHome,
InfoBox as PixelInfo, InfoBox as PixelInfo,
Link as PixelLink, Link as PixelLink,
@@ -55,6 +58,7 @@ import {
DotsSixVerticalIcon as PhosphorDotsSixVertical, DotsSixVerticalIcon as PhosphorDotsSixVertical,
DotsThreeVerticalIcon as PhosphorDotsThreeVertical, DotsThreeVerticalIcon as PhosphorDotsThreeVertical,
PencilSimpleIcon as PhosphorEdit, PencilSimpleIcon as PhosphorEdit,
EyeClosedIcon as PhosphorEyeClosed,
HashIcon as PhosphorHash, HashIcon as PhosphorHash,
HashStraightIcon as PhosphorHashStraight, HashStraightIcon as PhosphorHashStraight,
HouseIcon as PhosphorHome, HouseIcon as PhosphorHome,
@@ -73,6 +77,7 @@ import {
RocketLaunchIcon as PhosphorRocketLaunch, RocketLaunchIcon as PhosphorRocketLaunch,
HardDrivesIcon as PhosphorServer, HardDrivesIcon as PhosphorServer,
ShieldCheckIcon as PhosphorShieldCheck, ShieldCheckIcon as PhosphorShieldCheck,
StackPlusIcon as PhosphorStackPlus,
StopIcon as PhosphorStop, StopIcon as PhosphorStop,
SunIcon as PhosphorSun, SunIcon as PhosphorSun,
TimerIcon as PhosphorTimer, TimerIcon as PhosphorTimer,
@@ -100,6 +105,8 @@ import {
CreditCard, CreditCard,
Edit, Edit,
EllipsisVertical, EllipsisVertical,
EyeClosed,
Grid2x2Plus as GridAdd,
GripVerticalIcon, GripVerticalIcon,
Hash, Hash,
InfoIcon, InfoIcon,
@@ -155,12 +162,15 @@ const icons = {
circleQuestionMark: { lucide: CircleQuestionMark, pixel: PixelNoteDelete, phosphor: PhosphorQuestion }, circleQuestionMark: { lucide: CircleQuestionMark, pixel: PixelNoteDelete, phosphor: PhosphorQuestion },
comment: { lucide: MessageSquarePlus, pixel: PixelCommentSend, phosphor: PhosphorComment }, comment: { lucide: MessageSquarePlus, pixel: PixelCommentSend, phosphor: PhosphorComment },
creditCard: { lucide: CreditCard, pixel: PixelCreditCard, phosphor: PhosphorCreditCard }, creditCard: { lucide: CreditCard, pixel: PixelCreditCard, phosphor: PhosphorCreditCard },
creditCardDelete: { lucide: CreditCard, pixel: PixelCreditCardDelete, phosphor: PhosphorCreditCard },
edit: { lucide: Edit, pixel: PixelEdit, phosphor: PhosphorEdit }, edit: { lucide: Edit, pixel: PixelEdit, phosphor: PhosphorEdit },
ellipsisVertical: { ellipsisVertical: {
lucide: EllipsisVertical, lucide: EllipsisVertical,
pixel: PixelMoreVertical, pixel: PixelMoreVertical,
phosphor: PhosphorDotsThreeVertical, phosphor: PhosphorDotsThreeVertical,
}, },
eyeClosed: { lucide: EyeClosed, pixel: PixelEyeClosed, phosphor: PhosphorEyeClosed },
gridAdd: { lucide: GridAdd, pixel: PixelGridAdd, phosphor: PhosphorStackPlus },
gripVerticalIcon: { gripVerticalIcon: {
lucide: GripVerticalIcon, lucide: GripVerticalIcon,
pixel: PixelViewportWide, pixel: PixelViewportWide,

View File

@@ -5,10 +5,12 @@ import { cn } from "@/lib/utils";
function Switch({ function Switch({
className, className,
thumbClassName,
size = "default", size = "default",
...props ...props
}: React.ComponentProps<typeof SwitchPrimitive.Root> & { }: React.ComponentProps<typeof SwitchPrimitive.Root> & {
size?: "sm" | "default"; thumbClassName?: string;
size?: "sm" | "default" | "lg";
}) { }) {
return ( return (
<SwitchPrimitive.Root <SwitchPrimitive.Root
@@ -18,8 +20,8 @@ function Switch({
"peer data-[state=checked]:bg-personality data-[state=unchecked]:bg-input", "peer data-[state=checked]:bg-personality data-[state=unchecked]:bg-input",
"focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80", "focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80",
"group/switch inline-flex shrink-0 items-center rounded-full border border-transparent", "group/switch inline-flex shrink-0 items-center rounded-full border border-transparent",
"outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50", "outline-none focus-visible:ring-[3px] cursor-pointer disabled:cursor-not-allowed disabled:opacity-50",
"data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6", "data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6 data-[size=lg]:h-7 data-[size=lg]:w-12",
className, className,
)} )}
{...props} {...props}
@@ -27,7 +29,8 @@ function Switch({
<SwitchPrimitive.Thumb <SwitchPrimitive.Thumb
data-slot="switch-thumb" data-slot="switch-thumb"
className={cn( className={cn(
"bg-background dark:data-[state=unchecked]:bg-personality dark:data-[state=checked]:bg-primary-foreground pointer-events-none block rounded-full ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0", "bg-background dark:data-[state=unchecked]:bg-personality dark:data-[state=checked]:bg-primary-foreground pointer-events-none block rounded-full ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=lg]/switch:size-6 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0",
thumbClassName,
)} )}
/> />
</SwitchPrimitive.Root> </SwitchPrimitive.Root>

View File

@@ -13,7 +13,6 @@ import Issues from "@/pages/Issues";
import Landing from "@/pages/Landing"; import Landing from "@/pages/Landing";
import Login from "@/pages/Login"; import Login from "@/pages/Login";
import NotFound from "@/pages/NotFound"; import NotFound from "@/pages/NotFound";
import Pricing from "@/pages/Pricing";
import Test from "@/pages/Test"; import Test from "@/pages/Test";
import Timeline from "@/pages/Timeline"; import Timeline from "@/pages/Timeline";
@@ -28,7 +27,6 @@ ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
{/* public routes */} {/* public routes */}
<Route path="/" element={<Landing />} /> <Route path="/" element={<Landing />} />
<Route path="/font" element={<Font />} /> <Route path="/font" element={<Font />} />
<Route path="/pricing" element={<Pricing />} />
<Route path="/login" element={<Login />} /> <Route path="/login" element={<Login />} />
{/* authed routes */} {/* authed routes */}

View File

@@ -1,32 +1,141 @@
import { HumanRun } from "@nsmr/pixelart-react"; import { useState } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { useSession } from "@/components/session-provider"; import { useSession } from "@/components/session-provider";
import ThemeToggle from "@/components/theme-toggle"; import ThemeToggle from "@/components/theme-toggle";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import Icon from "@/components/ui/icon"; import Icon from "@/components/ui/icon";
import { Switch } from "@/components/ui/switch";
import { cn } from "@/lib/utils";
const pricingTiers = [
{
name: "Starter",
price: "£0",
priceAnnual: "£0",
period: "Free forever",
periodAnnual: "Free forever",
description: "Perfect for side projects and solo developers",
tagline: "For solo devs and small projects",
features: [
"1 organisation (owned or joined)",
"1 project",
"100 issues",
"Up to 5 team members",
"Email support",
],
cta: "Get started free",
ctaLink: "/login",
highlighted: false,
},
{
name: "Pro",
price: "£11.99",
priceAnnual: "£9.99",
period: "per user/month",
periodAnnual: "per user/month",
description: "For growing teams and professionals",
tagline: "Most Popular",
features: [
"Everything in starter",
"Unlimited organisations",
"Unlimited projects",
"Unlimited issues",
"Advanced time tracking & reports",
"Custom issue statuses",
"Priority email support",
],
cta: "Try pro free for 14 days",
ctaLink: "/login",
highlighted: true,
},
];
const faqs = [
{
question: "Can I switch plans?",
answer:
"Yes, you can upgrade or downgrade at any time. Changes take effect immediately, and we'll prorate any charges.",
},
{
question: "Is there a free trial?",
answer: "Yes, pro plan includes a 14-day free trial with full access. No credit card required to start.",
},
{
question: "What payment methods do you accept?",
answer: "We accept all major credit cards.",
},
{
question: "What if I need more users?",
answer:
"Pro plan pricing scales with your team. Add or remove users anytime, and we'll adjust your billing automatically.",
},
{
question: "What happens when my trial ends?",
answer:
"You'll automatically downgrade to the free starter plan. No charges unless you actively upgrade to pro.",
},
{
question: "Can I cancel anytime?",
answer:
"Absolutely. Cancel anytime with no questions asked. You'll keep access until the end of your billing period.",
},
{
question: "Do you offer refunds?",
answer: "Yes, we offer a 30-day money-back guarantee. If Sprint isn't right for you, just let us know.",
},
];
export default function Landing() { export default function Landing() {
const { user, isLoading } = useSession(); const { user, isLoading } = useSession();
const [billingPeriod, setBillingPeriod] = useState<"monthly" | "annual">("monthly");
return ( return (
<div className="min-h-screen flex flex-col"> <div className="min-h-screen flex flex-col" id="top">
<header className="relative flex items-center justify-center p-2 border-b"> <header className="sticky top-0 z-50 w-full border-b bg-background/80 backdrop-blur-md">
<div className="text-3xl font-basteleur font-700">Sprint</div> <div className="w-full flex h-14 items-center justify-between px-2">
<nav className="absolute right-2 flex items-center gap-4"> <div className="flex items-center gap-2">
<Link to="/pricing" className="text-sm hover:text-personality transition-colors"> <img src="/favicon.svg" alt="Sprint" className="size-12 -mt-0.5" />
Pricing <span className="text-3xl font-basteleur font-700 transition-colors -mt-0.5">Sprint</span>
</Link> </div>
<ThemeToggle /> <nav className="flex items-center gap-6">
{!isLoading && user ? ( <a
<Button asChild variant="outline" size="sm"> href="#top"
<Link to="/issues">Open app</Link> className="hidden md:block text-sm font-500 hover:text-personality transition-colors"
</Button> >
) : ( Home
<Button asChild variant="outline" size="sm"> </a>
<Link to="/login">Sign in</Link> <a
</Button> href="#features"
)} className="hidden md:block text-sm font-500 hover:text-personality transition-colors"
</nav> >
Features
</a>
<a
href="#pricing"
className="hidden md:block text-sm font-500 hover:text-personality transition-colors"
>
Pricing
</a>
<a
href="#faq"
className="hidden md:block text-sm font-500 hover:text-personality transition-colors"
>
FAQ
</a>
<div className="flex items-center gap-2">
<ThemeToggle />
{!isLoading && user ? (
<Button asChild variant="outline" size="sm">
<Link to="/issues">Open app</Link>
</Button>
) : (
<Button asChild variant="outline" size="sm">
<Link to="/login">Sign in</Link>
</Button>
)}
</div>
</nav>
</div>
</header> </header>
<main className="flex-1 flex flex-col items-center py-16 px-4"> <main className="flex-1 flex flex-col items-center py-16 px-4">
@@ -34,7 +143,7 @@ export default function Landing() {
{/* hero section */} {/* hero section */}
<div className="text-center space-y-8 pt-8"> <div className="text-center space-y-8 pt-8">
<div className="flex justify-center mb-8"> <div className="flex justify-center mb-8">
<HumanRun size={144} fill="var(--foreground)" /> <img src="/favicon.svg" alt="Sprint" className="size-48" />
</div> </div>
<div className="space-y-4"> <div className="space-y-4">
<h1 className="text-[64px] font-basteleur font-700 leading-tight"> <h1 className="text-[64px] font-basteleur font-700 leading-tight">
@@ -57,7 +166,7 @@ export default function Landing() {
<Link to="/login">Start free trial</Link> <Link to="/login">Start free trial</Link>
</Button> </Button>
<Button asChild variant="outline" size="lg" className="text-lg px-8 py-6"> <Button asChild variant="outline" size="lg" className="text-lg px-8 py-6">
<Link to="/pricing">See pricing</Link> <a href="#pricing">See pricing</a>
</Button> </Button>
</> </>
)} )}
@@ -69,38 +178,58 @@ export default function Landing() {
{/* problem section */} {/* problem section */}
<div className="max-w-4xl mx-auto space-y-16"> <div className="max-w-4xl mx-auto space-y-16">
<h2 className="text-4xl font-basteleur font-700 text-center"> <h2 className="text-4xl font-basteleur font-700 text-center">
Tired of spending more time managing Jira than building your product? Tired of spending more time managing Jira than building products?
</h2> </h2>
<div className="grid md:grid-cols-3 gap-6"> <div className="grid md:grid-cols-3 gap-6">
<div className="space-y-2"> <div className="space-y-2">
<Icon icon="timerOff" className="size-16 mx-auto" color={"var(--muted-foreground)"} /> <Icon
icon="timerOff"
iconStyle={"pixel"}
className="size-16 mx-auto"
color={"var(--muted-foreground)"}
/>
<p className="text-center text-muted-foreground"> <p className="text-center text-muted-foreground">
Wasting hours configuring workflows instead of shipping features Wasting hours configuring workflows instead of shipping features
</p> </p>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Icon icon="box" className="size-16 mx-auto" color={"var(--muted-foreground)"} /> <Icon
icon="gridAdd"
iconStyle={"pixel"}
className="size-16 mx-auto"
color={"var(--muted-foreground)"}
/>
<p className="text-center text-muted-foreground"> <p className="text-center text-muted-foreground">
Drowning in features you'll never use while missing the basics Drowning in features you'll never use while missing the basics
</p> </p>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Icon icon="bugOff" className="size-16 mx-auto" color={"var(--muted-foreground)"} /> <Icon
icon="bugOff"
iconStyle={"pixel"}
className="size-16 mx-auto"
color={"var(--muted-foreground)"}
/>
<p className="text-center text-muted-foreground"> <p className="text-center text-muted-foreground">
Context switching kills momentum—your flow state doesn't stand a chance The software is full of bugs. Your flow state doesn't stand a chance
</p> </p>
</div> </div>
</div> </div>
</div> </div>
{/* solution section */} {/* solution section */}
<div className="max-w-5xl mx-auto space-y-12"> <div id="features" className="max-w-5xl mx-auto space-y-12 scroll-mt-20 border-t pt-24">
<h2 className="text-5xl font-basteleur font-700 text-center"> <h2 className="text-5xl font-basteleur font-700 text-center">
Everything you need, nothing you don't Everything you need, nothing you don't
</h2> </h2>
<div className="grid md:grid-cols-2 gap-8"> <div className="grid md:grid-cols-2 gap-8">
<div className="border p-8 space-y-4"> <div className="border p-8 space-y-4">
<Icon icon="layoutDashboard" className="size-10" color={"var(--personality)"} /> <Icon
icon="layoutDashboard"
iconStyle={"pixel"}
className="size-10"
color={"var(--personality)"}
/>
<h3 className="text-2xl font-basteleur font-700">See your work at a glance</h3> <h3 className="text-2xl font-basteleur font-700">See your work at a glance</h3>
<p className="text-muted-foreground text-lg"> <p className="text-muted-foreground text-lg">
Beautiful, intuitive issue tracking with customizable statuses. Organize by projects and Beautiful, intuitive issue tracking with customizable statuses. Organize by projects and
@@ -109,7 +238,7 @@ export default function Landing() {
</div> </div>
<div className="border p-8 space-y-4"> <div className="border p-8 space-y-4">
<Icon icon="timer" className="size-10" color={"var(--personality)"} /> <Icon icon="timer" iconStyle={"pixel"} className="size-10" color={"var(--personality)"} />
<h3 className="text-2xl font-basteleur font-700">Track time without thinking</h3> <h3 className="text-2xl font-basteleur font-700">Track time without thinking</h3>
<p className="text-muted-foreground text-lg"> <p className="text-muted-foreground text-lg">
Built-in time tracking that actually works. Start, pause, resume. See where your time goes Built-in time tracking that actually works. Start, pause, resume. See where your time goes
@@ -118,7 +247,7 @@ export default function Landing() {
</div> </div>
<div className="border p-8 space-y-4"> <div className="border p-8 space-y-4">
<Icon icon="rocket" className="size-10" color={"var(--personality)"} /> <Icon icon="rocket" iconStyle={"pixel"} className="size-10" color={"var(--personality)"} />
<h3 className="text-2xl font-basteleur font-700">Ship in sprints</h3> <h3 className="text-2xl font-basteleur font-700">Ship in sprints</h3>
<p className="text-muted-foreground text-lg"> <p className="text-muted-foreground text-lg">
Sprint planning that actually works. Set date ranges, assign issues, track velocity. Keep Sprint planning that actually works. Set date ranges, assign issues, track velocity. Keep
@@ -127,7 +256,7 @@ export default function Landing() {
</div> </div>
<div className="border p-8 space-y-4"> <div className="border p-8 space-y-4">
<Icon icon="checkBox" className="size-10" color={"var(--personality)"} /> <Icon icon="checkBox" iconStyle={"pixel"} className="size-10" color={"var(--personality)"} />
<h3 className="text-2xl font-basteleur font-700">Only use what you need</h3> <h3 className="text-2xl font-basteleur font-700">Only use what you need</h3>
<p className="text-muted-foreground text-lg"> <p className="text-muted-foreground text-lg">
Every feature is optional. Sprints, time tracking, and other modules can be enabled or Every feature is optional. Sprints, time tracking, and other modules can be enabled or
@@ -149,23 +278,165 @@ export default function Landing() {
"Customizable issue statuses", "Customizable issue statuses",
"Sprint planning with date ranges", "Sprint planning with date ranges",
"Built-in time tracking", "Built-in time tracking",
"Role-based access control",
"Native desktop app (Tauri)", "Native desktop app (Tauri)",
"Self-hostable on your infrastructure",
"Clean, resizable interface", "Clean, resizable interface",
"Issue assignment and collaboration", "Issue assignment and collaboration",
"Individual feature toggles (org level)", "Individual feature toggles (org level)",
].map((feature) => ( ].map((feature) => (
<div key={feature} className="flex items-start gap-2"> <div key={feature} className="flex items-start gap-2">
<Icon icon="check" className="size-5 shrink-0 mt-0.5" color={"var(--personality)"} /> <Icon icon="check" iconStyle={"pixel"} className="size-6" color={"var(--personality)"} />
<span>{feature}</span> <span>{feature}</span>
</div> </div>
))} ))}
</div> </div>
</div> </div>
{/* pricing section */}
<div
id="pricing"
className="max-w-5xl mx-auto space-y-16 flex flex-col items-center border-t pt-24 scroll-mt-20"
>
<div className="text-center space-y-6">
<h2 className="text-5xl font-basteleur font-700">Simple, transparent pricing</h2>
<p className="text-xl text-muted-foreground max-w-2xl mx-auto">
Choose the plan that fits your team. Scale as you grow.
</p>
{/* billing toggle */}
<div className="flex items-center justify-center gap-4 pt-4">
<button
type="button"
onClick={() => setBillingPeriod("monthly")}
className={cn(
"text-lg transition-colors",
billingPeriod === "monthly" ? "text-foreground font-700" : "text-muted-foreground",
)}
>
monthly
</button>
<Switch
size="lg"
checked={billingPeriod === "annual"}
onCheckedChange={(checked) => setBillingPeriod(checked ? "annual" : "monthly")}
className="bg-border data-[state=checked]:bg-border! data-[state=unchecked]:bg-border!"
thumbClassName="bg-personality dark:bg-personality data-[state=checked]:bg-personality! data-[state=unchecked]:bg-personality!"
aria-label="toggle billing period"
/>
<button
type="button"
onClick={() => setBillingPeriod("annual")}
className={cn(
"text-lg transition-colors",
billingPeriod === "annual" ? "text-foreground font-700" : "text-muted-foreground",
)}
>
annual
</button>
<span className="text-sm px-3 py-1 bg-personality/10 text-personality rounded-full font-600">
Save 17%
</span>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 w-full max-w-4xl">
{pricingTiers.map((tier) => (
<div
key={tier.name}
className={cn(
"flex flex-col border p-8 space-y-6 relative",
tier.highlighted ? "border-2 border-personality shadow-lg scale-105" : "border-border",
)}
>
{tier.highlighted && (
<div className="absolute -top-4 left-4 bg-personality text-background px-3 py-1 text-xs font-700">
{tier.tagline}
</div>
)}
<div className="space-y-4">
<h3 className="text-3xl font-basteleur font-700">{tier.name}</h3>
<div className="flex items-baseline gap-2">
<span className="text-4xl font-700">
{billingPeriod === "annual" ? tier.priceAnnual : tier.price}
</span>
<span className="text-sm text-muted-foreground">
{billingPeriod === "annual" ? tier.periodAnnual : tier.period}
</span>
</div>
<p className="text-muted-foreground">{tier.description}</p>
</div>
<ul className="space-y-3 flex-1">
{tier.features.map((feature) => (
<li key={feature} className="flex items-start gap-2 text-sm">
<Icon
icon="check"
iconStyle={"pixel"}
className="size-6 -mt-0.5"
color="var(--personality)"
/>
<span>{feature}</span>
</li>
))}
</ul>
<Button
asChild
variant={tier.highlighted ? "default" : "outline"}
className={cn(
"font-700 py-6",
tier.highlighted ? "bg-personality hover:bg-personality/90 text-background" : "",
)}
>
<Link to={tier.ctaLink}>{tier.cta}</Link>
</Button>
</div>
))}
</div>
{/* trust signals */}
<div className="grid md:grid-cols-3 gap-8 w-full border-t pt-16 pb-8">
<div className="flex flex-col items-center text-center gap-2">
<Icon icon="eyeClosed" iconStyle={"pixel"} className="size-8" color="var(--personality)" />
<p className="font-700">Secure & Encrypted</p>
<p className="text-sm text-muted-foreground">Your data is safe with us</p>
</div>
<div className="flex flex-col items-center text-center gap-2">
<Icon
icon="creditCardDelete"
iconStyle={"pixel"}
className="size-8"
color="var(--personality)"
/>
<p className="font-700">No Card Required</p>
<p className="text-sm text-muted-foreground">Start your trial instantly</p>
</div>
<div className="flex flex-col items-center text-center gap-2">
<Icon icon="rotateCcw" iconStyle={"pixel"} className="size-8" color="var(--personality)" />
<p className="font-700">Money Back Guarantee</p>
<p className="text-sm text-muted-foreground">30-day no-risk policy</p>
</div>
</div>
{/* faq section */}
<div className="w-full max-w-5xl flex justify-center border-t pt-24 scroll-mt-20" id="faq">
<div className="w-full max-w-4xl flex flex-col items-center space-y-12">
<h2 className="text-5xl font-basteleur font-700 text-center">Frequently Asked Questions</h2>
<div className="grid gap-8 max-w-3xl">
{faqs.map((faq) => (
<div key={faq.question} className="space-y-2">
<h4 className="text-lg font-700">{faq.question}</h4>
<p className="text-muted-foreground">{faq.answer}</p>
</div>
))}
</div>
</div>
</div>
</div>
{/* TODO:> commented out until we have actual testimonies */}
{/* social proof placeholder */} {/* social proof placeholder */}
<div className="max-w-4xl mx-auto text-center space-y-8"> {/* <div className="max-w-4xl mx-auto text-center space-y-8">
<h2 className="text-4xl font-basteleur font-700">Join developers who've escaped Jira</h2> <h2 className="text-4xl font-basteleur font-700">Join developers who've escaped Jira</h2>
<div className="grid md:grid-cols-3 gap-8"> <div className="grid md:grid-cols-3 gap-8">
<div className="border p-6 space-y-4"> <div className="border p-6 space-y-4">
@@ -187,13 +458,13 @@ export default function Landing() {
<p className="text-sm font-700">Early user feedback</p> <p className="text-sm font-700">Early user feedback</p>
</div> </div>
</div> </div>
</div> </div> */}
{/* final cta */} {/* final cta */}
<div className="text-center space-y-6 border-t pt-16"> <div className="max-w-5xl mx-auto text-center space-y-6 border-t pt-16">
<h2 className="text-5xl font-basteleur font-700">Ready to ship faster?</h2> <h2 className="text-5xl font-basteleur font-700">Ready to ship faster?</h2>
<p className="text-xl text-muted-foreground"> <p className="text-xl text-muted-foreground">
Start tracking issues, managing sprints, and shipping product in minutes Start tracking issues, managing sprints, and shipping products in minutes
</p> </p>
<div className="flex flex-col sm:flex-row items-center justify-center gap-4"> <div className="flex flex-col sm:flex-row items-center justify-center gap-4">
{!isLoading && user ? ( {!isLoading && user ? (

View File

@@ -1,289 +0,0 @@
import { useState } from "react";
import { Link } from "react-router-dom";
import { useSession } from "@/components/session-provider";
import ThemeToggle from "@/components/theme-toggle";
import { Button } from "@/components/ui/button";
import Icon from "@/components/ui/icon";
import { cn } from "@/lib/utils";
const pricingTiers = [
{
name: "Starter",
price: "£0",
priceAnnual: "£0",
period: "Free forever",
description: "Perfect for side projects and solo developers",
tagline: "For solo devs and small projects",
features: [
"1 organisation (owned or joined)",
"1 project",
"100 issues",
"Up to 5 team members",
"Basic time tracking",
"Email support",
],
cta: "Get started free",
ctaLink: "/login",
highlighted: false,
},
{
name: "Pro",
price: "£11.99",
priceAnnual: "£9.99",
period: "per user/month",
periodAnnual: "per user/month",
description: "For growing teams and professionals",
tagline: "Most Popular",
features: [
"Everything in starter",
"Unlimited organisations",
"Unlimited projects",
"Unlimited issues",
"Advanced time tracking & reports",
"Sprint velocity tracking",
"Priority email support",
"Custom issue statuses",
"Role-based permissions",
],
cta: "Try pro free for 14 days",
ctaLink: "/login",
highlighted: true,
},
];
const faqs = [
{
question: "Can I switch plans?",
answer:
"Yes, you can upgrade or downgrade at any time. Changes take effect immediately, and we'll prorate any charges.",
},
{
question: "Is there a free trial?",
answer: "Yes, pro plan includes a 14-day free trial with full access. No credit card required to start.",
},
{
question: "What payment methods do you accept?",
answer: "We accept all major credit cards.",
},
{
question: "What if I need more users?",
answer:
"Pro plan pricing scales with your team. Add or remove users anytime, and we'll adjust your billing automatically.",
},
{
question: "What happens when my trial ends?",
answer:
"You'll automatically downgrade to the free starter plan. No charges unless you actively upgrade to pro.",
},
{
question: "Can I cancel anytime?",
answer:
"Absolutely. Cancel anytime with no questions asked. You'll keep access until the end of your billing period.",
},
{
question: "Do you offer refunds?",
answer: "Yes, we offer a 30-day money-back guarantee. If Sprint isn't right for you, just let us know.",
},
];
export default function Pricing() {
const { user, isLoading } = useSession();
const [billingPeriod, setBillingPeriod] = useState<"monthly" | "annual">("monthly");
return (
<div className="min-h-screen flex flex-col">
<header className="relative flex items-center justify-center p-2 border-b">
<div className="text-3xl font-basteleur font-700">Sprint</div>
<nav className="absolute right-2 flex items-center gap-4">
<Link to="/" className="text-sm hover:text-personality transition-colors">
Home
</Link>
<ThemeToggle />
{!isLoading && user ? (
<Button asChild variant="outline" size="sm">
<Link to="/issues">Open app</Link>
</Button>
) : (
<Button asChild variant="outline" size="sm">
<Link to="/login">Sign in</Link>
</Button>
)}
</nav>
</header>
<main className="flex-1 flex flex-col items-center py-16 px-4">
<div className="max-w-5xl w-full space-y-16 flex flex-col items-center">
{/* header section */}
<div className="text-center space-y-8">
<h1 className="text-5xl font-basteleur font-700">Simple, transparent pricing</h1>
<p className="text-2xl font-goudy text-muted-foreground">
Choose the plan that fits your team. Scale as you grow.
</p>
{/* billing toggle */}
<div className="flex items-center justify-center gap-4">
<button
type="button"
onClick={() => setBillingPeriod("monthly")}
className={cn(
"text-lg transition-colors",
billingPeriod === "monthly" ? "text-foreground font-700" : "text-muted-foreground",
)}
>
monthly
</button>
<button
type="button"
className="relative w-14 h-8 bg-border rounded-full"
onClick={() => setBillingPeriod(billingPeriod === "monthly" ? "annual" : "monthly")}
aria-label="toggle billing period"
>
<div
className={cn(
"absolute top-1 w-6 h-6 bg-personality rounded-full transition-transform",
billingPeriod === "annual" ? "translate-x-7" : "translate-x-1",
)}
/>
</button>
<button
type="button"
onClick={() => setBillingPeriod("annual")}
className={cn(
"text-lg transition-colors",
billingPeriod === "annual" ? "text-foreground font-700" : "text-muted-foreground",
)}
>
annual
</button>
<span className="text-md px-3 pb-1 pt-1.25 bg-personality/10 text-personality rounded-full font-600">
Save 17%
</span>
</div>
</div>
{/* pricing tiers */}
<div className="max-w-3xl w-full grid grid-cols-1 md:grid-cols-2 gap-8">
{pricingTiers.map((tier) => (
<div
key={tier.name}
className={`flex flex-col border p-8 ${
tier.highlighted
? "border-2 border-personality shadow-lg relative scale-105"
: "border-border"
}`}
>
{tier.highlighted && (
<div className="absolute -top-4 left-1/2 -translate-x-1/2 bg-personality text-background px-4 py-1 text-sm font-600">
{tier.tagline}
</div>
)}
<div className="space-y-4 mb-8">
<h2 className="text-3xl font-basteleur font-700">{tier.name}</h2>
<div className="space-y-1">
<div className="flex items-baseline gap-2">
<span className="text-4xl font-600">
{billingPeriod === "annual" ? tier.priceAnnual : tier.price}
</span>
<span className="text-sm text-muted-foreground">
{billingPeriod === "annual" ? tier.periodAnnual : tier.period}
</span>
</div>
</div>
<p className="text-lg font-goudy text-muted-foreground">{tier.description}</p>
</div>
<ul className="space-y-3 mb-8 flex-1">
{tier.features.map((feature) => (
<li key={feature} className="flex items-start gap-2">
<Icon icon="check" className="size-5 text-personality shrink-0 mt-0.5" />
<span className={"text-sm"}>{feature}</span>
</li>
))}
</ul>
<Button
asChild
variant={tier.highlighted ? "default" : "outline"}
className={cn(
"font-700 h-auto py-3 whitespace-normal text-center",
tier.highlighted ? "bg-personality hover:bg-personality/90" : "",
)}
>
<Link to={tier.ctaLink}>{tier.cta}</Link>
</Button>
</div>
))}
</div>
{/* trust signals */}
<div className="text-center space-y-6 border-t pt-12">
<div className="grid md:grid-cols-3 gap-8 max-w-4xl mx-auto">
<div className="flex flex-col items-center gap-2">
<Icon icon="shieldCheck" className="size-8 text-personality" />
<p className="font-goudy font-700">Secure & Encrypted</p>
<p className="text-sm text-muted-foreground">Your data is encrypted and secure</p>
</div>
<div className="flex flex-col items-center gap-2">
<Icon icon="creditCard" className="size-8 text-personality" />
<p className="font-goudy font-700">No Credit Card Required</p>
<p className="text-sm text-muted-foreground">Start your free trial today</p>
</div>
<div className="flex flex-col items-center gap-2">
<Icon icon="rotateCcw" className="size-8 text-personality" />
<p className="font-goudy font-700">30-day money back</p>
<p className="text-sm text-muted-foreground">Cancel anytime, no questions</p>
</div>
</div>
</div>
{/* faq section */}
<div className="max-w-3xl mx-auto space-y-8 border-t pt-12">
<h2 className="text-4xl font-basteleur font-700 text-center">Frequently Asked Questions</h2>
<div className="space-y-6">
{faqs.map((faq) => (
<div key={faq.question} className="space-y-2">
<h3 className="text-xl font-goudy font-700">{faq.question}</h3>
<p className="text-muted-foreground font-goudy">{faq.answer}</p>
</div>
))}
</div>
</div>
{/* final cta */}
<div className="text-center space-y-6 border-t pt-12">
<h2 className="text-4xl font-basteleur font-700">Still have questions?</h2>
<p className="text-xl font-goudy text-muted-foreground">
We're here to help. Get in touch with any questions.
</p>
<a
href="mailto:ob248@proton.me?subject=Sprint Pricing Question"
className="inline-block text-personality hover:underline font-goudy text-lg font-700"
>
Contact Us
</a>
</div>
</div>
</main>
<footer className="flex justify-center gap-2 items-center py-1 border-t">
<span className="font-300 text-lg text-muted-foreground font-goudy">
Built by{" "}
<a
href="https://ob248.com"
target="_blank"
rel="noopener noreferrer"
className="hover:text-personality font-goudy font-700"
>
Oliver Bryan
</a>
</span>
<a href="https://ob248.com" target="_blank" rel="noopener noreferrer">
<img src="oliver-bryan.svg" alt="Oliver Bryan" className="w-4 h-4" />
</a>
</footer>
</div>
);
}