mirror of
https://github.com/hex248/sprint.git
synced 2026-02-07 18:23:03 +00:00
new full landing page
This commit is contained in:
@@ -12,10 +12,13 @@ import {
|
||||
Close as PixelClose,
|
||||
MessagePlus as PixelCommentSend,
|
||||
CreditCard as PixelCreditCard,
|
||||
CreditCardDelete as PixelCreditCardDelete,
|
||||
Dashboard as PixelDashboard,
|
||||
Debug as PixelDebug,
|
||||
DebugOff as PixelDebugOff,
|
||||
Edit as PixelEdit,
|
||||
EyeClosed as PixelEyeClosed,
|
||||
AddGrid as PixelGridAdd,
|
||||
Home as PixelHome,
|
||||
InfoBox as PixelInfo,
|
||||
Link as PixelLink,
|
||||
@@ -55,6 +58,7 @@ import {
|
||||
DotsSixVerticalIcon as PhosphorDotsSixVertical,
|
||||
DotsThreeVerticalIcon as PhosphorDotsThreeVertical,
|
||||
PencilSimpleIcon as PhosphorEdit,
|
||||
EyeClosedIcon as PhosphorEyeClosed,
|
||||
HashIcon as PhosphorHash,
|
||||
HashStraightIcon as PhosphorHashStraight,
|
||||
HouseIcon as PhosphorHome,
|
||||
@@ -73,6 +77,7 @@ import {
|
||||
RocketLaunchIcon as PhosphorRocketLaunch,
|
||||
HardDrivesIcon as PhosphorServer,
|
||||
ShieldCheckIcon as PhosphorShieldCheck,
|
||||
StackPlusIcon as PhosphorStackPlus,
|
||||
StopIcon as PhosphorStop,
|
||||
SunIcon as PhosphorSun,
|
||||
TimerIcon as PhosphorTimer,
|
||||
@@ -100,6 +105,8 @@ import {
|
||||
CreditCard,
|
||||
Edit,
|
||||
EllipsisVertical,
|
||||
EyeClosed,
|
||||
Grid2x2Plus as GridAdd,
|
||||
GripVerticalIcon,
|
||||
Hash,
|
||||
InfoIcon,
|
||||
@@ -155,12 +162,15 @@ const icons = {
|
||||
circleQuestionMark: { lucide: CircleQuestionMark, pixel: PixelNoteDelete, phosphor: PhosphorQuestion },
|
||||
comment: { lucide: MessageSquarePlus, pixel: PixelCommentSend, phosphor: PhosphorComment },
|
||||
creditCard: { lucide: CreditCard, pixel: PixelCreditCard, phosphor: PhosphorCreditCard },
|
||||
creditCardDelete: { lucide: CreditCard, pixel: PixelCreditCardDelete, phosphor: PhosphorCreditCard },
|
||||
edit: { lucide: Edit, pixel: PixelEdit, phosphor: PhosphorEdit },
|
||||
ellipsisVertical: {
|
||||
lucide: EllipsisVertical,
|
||||
pixel: PixelMoreVertical,
|
||||
phosphor: PhosphorDotsThreeVertical,
|
||||
},
|
||||
eyeClosed: { lucide: EyeClosed, pixel: PixelEyeClosed, phosphor: PhosphorEyeClosed },
|
||||
gridAdd: { lucide: GridAdd, pixel: PixelGridAdd, phosphor: PhosphorStackPlus },
|
||||
gripVerticalIcon: {
|
||||
lucide: GripVerticalIcon,
|
||||
pixel: PixelViewportWide,
|
||||
|
||||
@@ -5,10 +5,12 @@ import { cn } from "@/lib/utils";
|
||||
|
||||
function Switch({
|
||||
className,
|
||||
thumbClassName,
|
||||
size = "default",
|
||||
...props
|
||||
}: React.ComponentProps<typeof SwitchPrimitive.Root> & {
|
||||
size?: "sm" | "default";
|
||||
thumbClassName?: string;
|
||||
size?: "sm" | "default" | "lg";
|
||||
}) {
|
||||
return (
|
||||
<SwitchPrimitive.Root
|
||||
@@ -18,8 +20,8 @@ function Switch({
|
||||
"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",
|
||||
"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",
|
||||
"data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6",
|
||||
"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=lg]:h-7 data-[size=lg]:w-12",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -27,7 +29,8 @@ function Switch({
|
||||
<SwitchPrimitive.Thumb
|
||||
data-slot="switch-thumb"
|
||||
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>
|
||||
|
||||
@@ -13,7 +13,6 @@ import Issues from "@/pages/Issues";
|
||||
import Landing from "@/pages/Landing";
|
||||
import Login from "@/pages/Login";
|
||||
import NotFound from "@/pages/NotFound";
|
||||
import Pricing from "@/pages/Pricing";
|
||||
import Test from "@/pages/Test";
|
||||
import Timeline from "@/pages/Timeline";
|
||||
|
||||
@@ -28,7 +27,6 @@ ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||
{/* public routes */}
|
||||
<Route path="/" element={<Landing />} />
|
||||
<Route path="/font" element={<Font />} />
|
||||
<Route path="/pricing" element={<Pricing />} />
|
||||
<Route path="/login" element={<Login />} />
|
||||
|
||||
{/* authed routes */}
|
||||
|
||||
@@ -1,21 +1,128 @@
|
||||
import { HumanRun } from "@nsmr/pixelart-react";
|
||||
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 { 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() {
|
||||
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="/pricing" className="text-sm hover:text-personality transition-colors">
|
||||
<div className="min-h-screen flex flex-col" id="top">
|
||||
<header className="sticky top-0 z-50 w-full border-b bg-background/80 backdrop-blur-md">
|
||||
<div className="w-full flex h-14 items-center justify-between px-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<img src="/favicon.svg" alt="Sprint" className="size-12 -mt-0.5" />
|
||||
<span className="text-3xl font-basteleur font-700 transition-colors -mt-0.5">Sprint</span>
|
||||
</div>
|
||||
<nav className="flex items-center gap-6">
|
||||
<a
|
||||
href="#top"
|
||||
className="hidden md:block text-sm font-500 hover:text-personality transition-colors"
|
||||
>
|
||||
Home
|
||||
</a>
|
||||
<a
|
||||
href="#features"
|
||||
className="hidden md:block text-sm font-500 hover:text-personality transition-colors"
|
||||
>
|
||||
Features
|
||||
</a>
|
||||
<a
|
||||
href="#pricing"
|
||||
className="hidden md:block text-sm font-500 hover:text-personality transition-colors"
|
||||
>
|
||||
Pricing
|
||||
</Link>
|
||||
</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">
|
||||
@@ -26,7 +133,9 @@ export default function Landing() {
|
||||
<Link to="/login">Sign in</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="flex-1 flex flex-col items-center py-16 px-4">
|
||||
@@ -34,7 +143,7 @@ export default function Landing() {
|
||||
{/* hero section */}
|
||||
<div className="text-center space-y-8 pt-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 className="space-y-4">
|
||||
<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>
|
||||
</Button>
|
||||
<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>
|
||||
</>
|
||||
)}
|
||||
@@ -69,38 +178,58 @@ export default function Landing() {
|
||||
{/* problem section */}
|
||||
<div className="max-w-4xl mx-auto space-y-16">
|
||||
<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>
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
<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">
|
||||
Wasting hours configuring workflows instead of shipping features
|
||||
</p>
|
||||
</div>
|
||||
<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">
|
||||
Drowning in features you'll never use while missing the basics
|
||||
</p>
|
||||
</div>
|
||||
<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">
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 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">
|
||||
Everything you need, nothing you don't
|
||||
</h2>
|
||||
<div className="grid md:grid-cols-2 gap-8">
|
||||
<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>
|
||||
<p className="text-muted-foreground text-lg">
|
||||
Beautiful, intuitive issue tracking with customizable statuses. Organize by projects and
|
||||
@@ -109,7 +238,7 @@ export default function Landing() {
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<p className="text-muted-foreground text-lg">
|
||||
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 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>
|
||||
<p className="text-muted-foreground text-lg">
|
||||
Sprint planning that actually works. Set date ranges, assign issues, track velocity. Keep
|
||||
@@ -127,7 +256,7 @@ export default function Landing() {
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<p className="text-muted-foreground text-lg">
|
||||
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",
|
||||
"Sprint planning with date ranges",
|
||||
"Built-in time tracking",
|
||||
"Role-based access control",
|
||||
"Native desktop app (Tauri)",
|
||||
"Self-hostable on your infrastructure",
|
||||
"Clean, resizable interface",
|
||||
"Issue assignment and collaboration",
|
||||
"Individual feature toggles (org level)",
|
||||
].map((feature) => (
|
||||
<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>
|
||||
</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 */}
|
||||
<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>
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
{/* 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>
|
||||
<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>
|
||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||
{!isLoading && user ? (
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user