create-cronus-app 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +173 -0
- package/dist/compose.d.ts +56 -0
- package/dist/compose.js +92 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +255 -0
- package/dist/scaffold.d.ts +31 -0
- package/dist/scaffold.js +118 -0
- package/dist/utils.d.ts +121 -0
- package/dist/utils.js +285 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.js +3 -0
- package/package.json +56 -0
- package/templates/dashboard/README.md +61 -0
- package/templates/dashboard/app/globals.css +16 -0
- package/templates/dashboard/app/layout.tsx +40 -0
- package/templates/dashboard/app/page.tsx +77 -0
- package/templates/dashboard/app/settings/page.tsx +21 -0
- package/templates/dashboard/components/dashboard-shell.tsx +120 -0
- package/templates/dashboard/components/orders-table.tsx +132 -0
- package/templates/dashboard/components/revenue-chart.tsx +50 -0
- package/templates/dashboard/components/settings-form.tsx +167 -0
- package/templates/dashboard/cronus-ui.json +17 -0
- package/templates/dashboard/gitignore +34 -0
- package/templates/dashboard/next.config.mjs +11 -0
- package/templates/dashboard/package.json +30 -0
- package/templates/dashboard/postcss.config.mjs +7 -0
- package/templates/dashboard/tsconfig.json +23 -0
- package/templates/default/README.md +46 -0
- package/templates/default/app/globals.css +16 -0
- package/templates/default/app/layout.tsx +39 -0
- package/templates/default/app/page.tsx +112 -0
- package/templates/default/cronus-ui.json +17 -0
- package/templates/default/gitignore +34 -0
- package/templates/default/next.config.mjs +11 -0
- package/templates/default/package.json +27 -0
- package/templates/default/postcss.config.mjs +7 -0
- package/templates/default/tsconfig.json +23 -0
- package/templates/marketing/README.md +65 -0
- package/templates/marketing/app/globals.css +16 -0
- package/templates/marketing/app/layout.tsx +39 -0
- package/templates/marketing/app/page.tsx +25 -0
- package/templates/marketing/components/faq.tsx +62 -0
- package/templates/marketing/components/feature-grid.tsx +67 -0
- package/templates/marketing/components/hero.tsx +61 -0
- package/templates/marketing/components/pricing.tsx +116 -0
- package/templates/marketing/components/site-footer.tsx +91 -0
- package/templates/marketing/components/site-header.tsx +47 -0
- package/templates/marketing/components/testimonials.tsx +91 -0
- package/templates/marketing/components/waitlist-cta.tsx +93 -0
- package/templates/marketing/cronus-ui.json +17 -0
- package/templates/marketing/gitignore +34 -0
- package/templates/marketing/next.config.mjs +11 -0
- package/templates/marketing/package.json +28 -0
- package/templates/marketing/postcss.config.mjs +7 -0
- package/templates/marketing/tsconfig.json +23 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Badge } from "@cronus-ui/ui/badge";
|
|
2
|
+
import { Card, CardDescription, CardHeader, CardTitle } from "@cronus-ui/ui/card";
|
|
3
|
+
import { BarChart3, Layers, Lock, Palette, Workflow, Zap } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
const FEATURES = [
|
|
6
|
+
{
|
|
7
|
+
icon: Zap,
|
|
8
|
+
title: "Lightning fast",
|
|
9
|
+
description: "Optimized rendering and zero-runtime styling keep every interaction instant.",
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
icon: Palette,
|
|
13
|
+
title: "Themeable by design",
|
|
14
|
+
description: "Swap a palette and every surface, border, and gradient updates live.",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
icon: Lock,
|
|
18
|
+
title: "Secure by default",
|
|
19
|
+
description: "SOC 2 compliant infrastructure with end-to-end encryption on every request.",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
icon: Layers,
|
|
23
|
+
title: "Composable primitives",
|
|
24
|
+
description: "Build complex layouts from small, predictable, accessible building blocks.",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
icon: BarChart3,
|
|
28
|
+
title: "Insightful analytics",
|
|
29
|
+
description: "Understand how people use your product with real-time usage dashboards.",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
icon: Workflow,
|
|
33
|
+
title: "Automate anything",
|
|
34
|
+
description: "Connect your favorite tools and wire up workflows without writing glue code.",
|
|
35
|
+
},
|
|
36
|
+
] as const;
|
|
37
|
+
|
|
38
|
+
/** Six capability cards with gradient icon chips. */
|
|
39
|
+
export function FeatureGrid() {
|
|
40
|
+
return (
|
|
41
|
+
<section id="features" className="scroll-mt-16 px-6 py-20">
|
|
42
|
+
<div className="mx-auto max-w-2xl text-center">
|
|
43
|
+
<Badge variant="secondary">Features</Badge>
|
|
44
|
+
<h2 className="mt-4 font-display text-4xl font-semibold tracking-tight text-fg">
|
|
45
|
+
Everything you need, nothing you don't
|
|
46
|
+
</h2>
|
|
47
|
+
<p className="mt-4 text-balance text-fg-secondary">
|
|
48
|
+
A thoughtfully crafted set of tools that scales from your first prototype to production.
|
|
49
|
+
</p>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div className="mx-auto mt-14 grid max-w-5xl grid-cols-[repeat(auto-fit,minmax(min(100%,16rem),1fr))] gap-6">
|
|
53
|
+
{FEATURES.map((feature) => (
|
|
54
|
+
<Card key={feature.title} className="border-border">
|
|
55
|
+
<CardHeader>
|
|
56
|
+
<span className="grid size-11 place-items-center rounded-xl bg-gradient-primary text-primary-foreground shadow-glow">
|
|
57
|
+
<feature.icon aria-hidden="true" className="size-5" />
|
|
58
|
+
</span>
|
|
59
|
+
<CardTitle className="mt-4">{feature.title}</CardTitle>
|
|
60
|
+
<CardDescription>{feature.description}</CardDescription>
|
|
61
|
+
</CardHeader>
|
|
62
|
+
</Card>
|
|
63
|
+
))}
|
|
64
|
+
</div>
|
|
65
|
+
</section>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Avatar, AvatarFallback } from "@cronus-ui/ui/avatar";
|
|
2
|
+
import { Badge } from "@cronus-ui/ui/badge";
|
|
3
|
+
import { Button } from "@cronus-ui/ui/button";
|
|
4
|
+
import { ArrowRight, Sparkles } from "lucide-react";
|
|
5
|
+
|
|
6
|
+
const FOUNDER_INITIALS = ["AK", "MR", "JD", "SL"] as const;
|
|
7
|
+
|
|
8
|
+
/** Centered hero: announcement badge, display headline, CTAs, social proof. */
|
|
9
|
+
export function Hero() {
|
|
10
|
+
return (
|
|
11
|
+
<section className="relative overflow-hidden px-6 py-24 sm:py-32">
|
|
12
|
+
<div
|
|
13
|
+
aria-hidden="true"
|
|
14
|
+
className="pointer-events-none absolute inset-x-0 -top-24 -z-10 h-80 bg-gradient-primary opacity-20 blur-3xl"
|
|
15
|
+
/>
|
|
16
|
+
<div className="mx-auto flex max-w-3xl flex-col items-center text-center">
|
|
17
|
+
<Badge variant="primary" className="gap-1.5">
|
|
18
|
+
<Sparkles aria-hidden="true" className="size-3.5" />
|
|
19
|
+
Now in public beta
|
|
20
|
+
</Badge>
|
|
21
|
+
|
|
22
|
+
<h1 className="mt-6 text-balance font-display text-5xl font-semibold leading-[1.05] tracking-tight text-fg sm:text-6xl">
|
|
23
|
+
Ship products your team is{" "}
|
|
24
|
+
<span className="bg-gradient-primary bg-clip-text text-transparent">proud of</span>
|
|
25
|
+
</h1>
|
|
26
|
+
|
|
27
|
+
<p className="mt-6 max-w-xl text-balance text-lg text-fg-secondary">
|
|
28
|
+
A modern toolkit for building accessible, themeable interfaces — so you spend less time on
|
|
29
|
+
plumbing and more time on the product your customers love.
|
|
30
|
+
</p>
|
|
31
|
+
|
|
32
|
+
<div className="mt-9 flex flex-col items-center gap-3 sm:flex-row">
|
|
33
|
+
<Button variant="primary" size="lg" asChild>
|
|
34
|
+
<a href="#waitlist">
|
|
35
|
+
Join the waitlist
|
|
36
|
+
<ArrowRight aria-hidden="true" />
|
|
37
|
+
</a>
|
|
38
|
+
</Button>
|
|
39
|
+
<Button variant="outline" size="lg" asChild>
|
|
40
|
+
<a href="#features">See what's inside</a>
|
|
41
|
+
</Button>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div className="mt-10 flex flex-col items-center gap-3 sm:flex-row sm:gap-4">
|
|
45
|
+
<div className="flex -space-x-2">
|
|
46
|
+
{FOUNDER_INITIALS.map((initials) => (
|
|
47
|
+
<Avatar key={initials} className="size-9 border-2 border-surface-base shadow-xs">
|
|
48
|
+
<AvatarFallback className="bg-surface-overlay text-xs text-fg-secondary">
|
|
49
|
+
{initials}
|
|
50
|
+
</AvatarFallback>
|
|
51
|
+
</Avatar>
|
|
52
|
+
))}
|
|
53
|
+
</div>
|
|
54
|
+
<p className="text-sm text-fg-tertiary">
|
|
55
|
+
Loved by <span className="font-medium text-fg">9,000+</span> builders
|
|
56
|
+
</p>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</section>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Badge } from "@cronus-ui/ui/badge";
|
|
2
|
+
import { Button } from "@cronus-ui/ui/button";
|
|
3
|
+
import {
|
|
4
|
+
Card,
|
|
5
|
+
CardContent,
|
|
6
|
+
CardDescription,
|
|
7
|
+
CardFooter,
|
|
8
|
+
CardHeader,
|
|
9
|
+
CardTitle,
|
|
10
|
+
} from "@cronus-ui/ui/card";
|
|
11
|
+
import { Check, Sparkles } from "lucide-react";
|
|
12
|
+
|
|
13
|
+
const PRICING_TIERS = [
|
|
14
|
+
{
|
|
15
|
+
name: "Starter",
|
|
16
|
+
price: "$0",
|
|
17
|
+
cadence: "/ month",
|
|
18
|
+
description: "Everything you need to launch your first project.",
|
|
19
|
+
features: ["Up to 3 projects", "Community support", "1 GB storage", "Basic analytics"],
|
|
20
|
+
cta: "Get started",
|
|
21
|
+
featured: false,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: "Pro",
|
|
25
|
+
price: "$29",
|
|
26
|
+
cadence: "/ month",
|
|
27
|
+
description: "For growing teams that need more power and polish.",
|
|
28
|
+
features: [
|
|
29
|
+
"Unlimited projects",
|
|
30
|
+
"Priority support",
|
|
31
|
+
"50 GB storage",
|
|
32
|
+
"Advanced analytics",
|
|
33
|
+
"Custom domains",
|
|
34
|
+
],
|
|
35
|
+
cta: "Start free trial",
|
|
36
|
+
featured: true,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "Enterprise",
|
|
40
|
+
price: "Custom",
|
|
41
|
+
cadence: "",
|
|
42
|
+
description: "Dedicated infrastructure and support at scale.",
|
|
43
|
+
features: ["Everything in Pro", "SSO & SAML", "Dedicated success manager", "99.99% uptime SLA"],
|
|
44
|
+
cta: "Contact sales",
|
|
45
|
+
featured: false,
|
|
46
|
+
},
|
|
47
|
+
] as const;
|
|
48
|
+
|
|
49
|
+
/** Three pricing tiers with a featured middle card. */
|
|
50
|
+
export function Pricing() {
|
|
51
|
+
return (
|
|
52
|
+
<section id="pricing" className="scroll-mt-16 px-6 py-20">
|
|
53
|
+
<div className="mx-auto max-w-2xl text-center">
|
|
54
|
+
<Badge variant="secondary">Pricing</Badge>
|
|
55
|
+
<h2 className="mt-4 font-display text-4xl font-semibold tracking-tight text-fg">
|
|
56
|
+
Simple, transparent pricing
|
|
57
|
+
</h2>
|
|
58
|
+
<p className="mt-4 text-balance text-fg-secondary">
|
|
59
|
+
Start for free and scale as you grow. No hidden fees, cancel anytime.
|
|
60
|
+
</p>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div className="mx-auto mt-14 grid max-w-5xl grid-cols-[repeat(auto-fit,minmax(min(100%,16rem),1fr))] items-start gap-6">
|
|
64
|
+
{PRICING_TIERS.map((tier) => (
|
|
65
|
+
<Card
|
|
66
|
+
key={tier.name}
|
|
67
|
+
className={
|
|
68
|
+
tier.featured
|
|
69
|
+
? "relative border-primary shadow-glow lg:-translate-y-3"
|
|
70
|
+
: "border-border"
|
|
71
|
+
}
|
|
72
|
+
>
|
|
73
|
+
{tier.featured ? (
|
|
74
|
+
<Badge
|
|
75
|
+
variant="primary"
|
|
76
|
+
className="absolute -top-3 left-1/2 -translate-x-1/2 gap-1.5"
|
|
77
|
+
>
|
|
78
|
+
<Sparkles aria-hidden="true" className="size-3.5" />
|
|
79
|
+
Most popular
|
|
80
|
+
</Badge>
|
|
81
|
+
) : null}
|
|
82
|
+
<CardHeader>
|
|
83
|
+
<CardTitle>{tier.name}</CardTitle>
|
|
84
|
+
<CardDescription>{tier.description}</CardDescription>
|
|
85
|
+
<div className="mt-2 flex items-baseline gap-1">
|
|
86
|
+
<span className="font-display text-4xl font-semibold tracking-tight text-fg">
|
|
87
|
+
{tier.price}
|
|
88
|
+
</span>
|
|
89
|
+
{tier.cadence ? (
|
|
90
|
+
<span className="text-sm text-fg-tertiary">{tier.cadence}</span>
|
|
91
|
+
) : null}
|
|
92
|
+
</div>
|
|
93
|
+
</CardHeader>
|
|
94
|
+
<CardContent>
|
|
95
|
+
<ul className="flex flex-col gap-3">
|
|
96
|
+
{tier.features.map((feature) => (
|
|
97
|
+
<li key={feature} className="flex items-center gap-2.5 text-sm text-fg-secondary">
|
|
98
|
+
<span className="grid size-5 shrink-0 place-items-center rounded-full bg-primary/15">
|
|
99
|
+
<Check aria-hidden="true" className="size-3 text-primary" />
|
|
100
|
+
</span>
|
|
101
|
+
{feature}
|
|
102
|
+
</li>
|
|
103
|
+
))}
|
|
104
|
+
</ul>
|
|
105
|
+
</CardContent>
|
|
106
|
+
<CardFooter>
|
|
107
|
+
<Button variant={tier.featured ? "primary" : "outline"} className="w-full">
|
|
108
|
+
{tier.cta}
|
|
109
|
+
</Button>
|
|
110
|
+
</CardFooter>
|
|
111
|
+
</Card>
|
|
112
|
+
))}
|
|
113
|
+
</div>
|
|
114
|
+
</section>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Separator } from "@cronus-ui/ui/separator";
|
|
2
|
+
import { Sparkles } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
const FOOTER_COLUMNS = [
|
|
5
|
+
{
|
|
6
|
+
heading: "Product",
|
|
7
|
+
links: [
|
|
8
|
+
{ label: "Features", href: "#features" },
|
|
9
|
+
{ label: "Pricing", href: "#pricing" },
|
|
10
|
+
{ label: "FAQ", href: "#faq" },
|
|
11
|
+
{ label: "Waitlist", href: "#waitlist" },
|
|
12
|
+
],
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
heading: "Company",
|
|
16
|
+
links: [
|
|
17
|
+
{ label: "About", href: "#about" },
|
|
18
|
+
{ label: "Careers", href: "#careers" },
|
|
19
|
+
{ label: "Contact", href: "#contact" },
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
heading: "Resources",
|
|
24
|
+
links: [
|
|
25
|
+
{ label: "Documentation", href: "#docs" },
|
|
26
|
+
{ label: "Guides", href: "#guides" },
|
|
27
|
+
{ label: "Support", href: "#support" },
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
] as const;
|
|
31
|
+
|
|
32
|
+
/** Brand block, link columns, and the legal bottom bar. */
|
|
33
|
+
export function SiteFooter() {
|
|
34
|
+
return (
|
|
35
|
+
<footer className="border-t border-border bg-surface-base px-6 py-16">
|
|
36
|
+
<div className="mx-auto max-w-6xl">
|
|
37
|
+
<div className="grid gap-12 lg:grid-cols-[1.5fr_repeat(3,1fr)]">
|
|
38
|
+
<div className="max-w-xs">
|
|
39
|
+
<div className="flex items-center gap-2.5">
|
|
40
|
+
<span className="grid size-9 place-items-center rounded-xl bg-gradient-primary text-primary-foreground shadow-glow">
|
|
41
|
+
<Sparkles aria-hidden="true" className="size-5" />
|
|
42
|
+
</span>
|
|
43
|
+
<span className="font-display text-lg font-semibold text-fg">__APP_NAME__</span>
|
|
44
|
+
</div>
|
|
45
|
+
<p className="mt-4 text-sm leading-6 text-fg-secondary">
|
|
46
|
+
Built with Cronus UI — themeable, accessible React components for teams who ship
|
|
47
|
+
polished products fast.
|
|
48
|
+
</p>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
{FOOTER_COLUMNS.map((column) => (
|
|
52
|
+
<div key={column.heading}>
|
|
53
|
+
<h3 className="text-sm font-medium text-fg">{column.heading}</h3>
|
|
54
|
+
<ul className="mt-4 flex flex-col gap-3">
|
|
55
|
+
{column.links.map((link) => (
|
|
56
|
+
<li key={link.label}>
|
|
57
|
+
<a
|
|
58
|
+
href={link.href}
|
|
59
|
+
className="text-sm text-fg-secondary transition-colors hover:text-fg"
|
|
60
|
+
>
|
|
61
|
+
{link.label}
|
|
62
|
+
</a>
|
|
63
|
+
</li>
|
|
64
|
+
))}
|
|
65
|
+
</ul>
|
|
66
|
+
</div>
|
|
67
|
+
))}
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<Separator className="my-8" />
|
|
71
|
+
|
|
72
|
+
<div className="flex flex-col items-center justify-between gap-4 sm:flex-row">
|
|
73
|
+
<p className="text-sm text-fg-tertiary">
|
|
74
|
+
© {new Date().getFullYear()} __APP_NAME__. All rights reserved.
|
|
75
|
+
</p>
|
|
76
|
+
<div className="flex items-center gap-6">
|
|
77
|
+
<a
|
|
78
|
+
href="#privacy"
|
|
79
|
+
className="text-sm text-fg-secondary transition-colors hover:text-fg"
|
|
80
|
+
>
|
|
81
|
+
Privacy
|
|
82
|
+
</a>
|
|
83
|
+
<a href="#terms" className="text-sm text-fg-secondary transition-colors hover:text-fg">
|
|
84
|
+
Terms
|
|
85
|
+
</a>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</footer>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Button } from "@cronus-ui/ui/button";
|
|
2
|
+
import { Sparkles } from "lucide-react";
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
|
|
5
|
+
const NAV_LINKS = [
|
|
6
|
+
{ label: "Features", href: "#features" },
|
|
7
|
+
{ label: "Pricing", href: "#pricing" },
|
|
8
|
+
{ label: "FAQ", href: "#faq" },
|
|
9
|
+
] as const;
|
|
10
|
+
|
|
11
|
+
/** Sticky translucent header: brand, anchor nav, and the primary CTA. */
|
|
12
|
+
export function SiteHeader() {
|
|
13
|
+
return (
|
|
14
|
+
<header className="sticky top-0 z-50 border-b border-border/70 bg-surface-base/70 backdrop-blur-xl">
|
|
15
|
+
<nav
|
|
16
|
+
aria-label="Primary"
|
|
17
|
+
className="mx-auto flex h-16 w-full max-w-6xl items-center justify-between gap-4 px-6"
|
|
18
|
+
>
|
|
19
|
+
<Link href="/" className="flex items-center gap-2.5">
|
|
20
|
+
<span className="grid size-8 place-items-center rounded-lg bg-gradient-primary text-primary-foreground shadow-glow">
|
|
21
|
+
<Sparkles className="size-4" aria-hidden="true" />
|
|
22
|
+
</span>
|
|
23
|
+
<span className="font-display text-base font-semibold tracking-tight text-fg">
|
|
24
|
+
__APP_NAME__
|
|
25
|
+
</span>
|
|
26
|
+
</Link>
|
|
27
|
+
|
|
28
|
+
<ul className="hidden items-center gap-1 sm:flex">
|
|
29
|
+
{NAV_LINKS.map((link) => (
|
|
30
|
+
<li key={link.href}>
|
|
31
|
+
<a
|
|
32
|
+
href={link.href}
|
|
33
|
+
className="rounded-lg px-3 py-2 text-sm text-fg-secondary transition-colors hover:text-fg"
|
|
34
|
+
>
|
|
35
|
+
{link.label}
|
|
36
|
+
</a>
|
|
37
|
+
</li>
|
|
38
|
+
))}
|
|
39
|
+
</ul>
|
|
40
|
+
|
|
41
|
+
<Button variant="primary" size="sm" asChild>
|
|
42
|
+
<a href="#waitlist">Join the waitlist</a>
|
|
43
|
+
</Button>
|
|
44
|
+
</nav>
|
|
45
|
+
</header>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Avatar, AvatarFallback } from "@cronus-ui/ui/avatar";
|
|
2
|
+
import { Badge } from "@cronus-ui/ui/badge";
|
|
3
|
+
import { Card, CardContent } from "@cronus-ui/ui/card";
|
|
4
|
+
import { Sparkles } from "lucide-react";
|
|
5
|
+
|
|
6
|
+
const TESTIMONIALS = [
|
|
7
|
+
{
|
|
8
|
+
quote:
|
|
9
|
+
"We replaced a tangle of one-off components with one themeable system. Our redesign shipped weeks early.",
|
|
10
|
+
name: "Amara Okafor",
|
|
11
|
+
role: "VP Design, Northwind",
|
|
12
|
+
initials: "AO",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
quote:
|
|
16
|
+
"Every surface inherits our brand the moment we change a token. It's the closest thing to magic our team has shipped.",
|
|
17
|
+
name: "Marco Rossi",
|
|
18
|
+
role: "Staff Engineer, Lumen",
|
|
19
|
+
initials: "MR",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
quote:
|
|
23
|
+
"Accessibility used to be an afterthought. Now it's the default — we pass audits without a scramble before launch.",
|
|
24
|
+
name: "Priya Nair",
|
|
25
|
+
role: "Head of Product, Cobalt",
|
|
26
|
+
initials: "PN",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
quote:
|
|
30
|
+
"We onboarded three new engineers in a day. The sections read like documentation you can paste straight into the app.",
|
|
31
|
+
name: "Jonas Berg",
|
|
32
|
+
role: "Engineering Lead, Fathom",
|
|
33
|
+
initials: "JB",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
quote:
|
|
37
|
+
"The cards, the gradients, the polish — it all feels premium out of the box. Our marketing site looks bespoke.",
|
|
38
|
+
name: "Sofia Lindqvist",
|
|
39
|
+
role: "Founder, Driftwood",
|
|
40
|
+
initials: "SL",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
quote:
|
|
44
|
+
"This is the first design system our designers and engineers actually agree on. That alone paid for itself.",
|
|
45
|
+
name: "David Chen",
|
|
46
|
+
role: "CTO, Parallel",
|
|
47
|
+
initials: "DC",
|
|
48
|
+
},
|
|
49
|
+
] as const;
|
|
50
|
+
|
|
51
|
+
/** Quote cards in a responsive three-column grid. */
|
|
52
|
+
export function Testimonials() {
|
|
53
|
+
return (
|
|
54
|
+
<section className="px-6 py-20">
|
|
55
|
+
<div className="mx-auto flex max-w-2xl flex-col items-center text-center">
|
|
56
|
+
<Badge variant="secondary" className="gap-1.5">
|
|
57
|
+
<Sparkles aria-hidden="true" className="size-3.5" />
|
|
58
|
+
Loved by teams
|
|
59
|
+
</Badge>
|
|
60
|
+
<h2 className="mt-4 text-balance font-display text-3xl font-semibold tracking-tight text-fg sm:text-4xl">
|
|
61
|
+
Loved by modern product teams
|
|
62
|
+
</h2>
|
|
63
|
+
<p className="mt-4 text-balance text-fg-secondary">
|
|
64
|
+
From seed-stage startups to public companies — teams ship faster when every surface is
|
|
65
|
+
accessible, themeable, and consistent.
|
|
66
|
+
</p>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div className="mx-auto mt-14 grid max-w-5xl gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
70
|
+
{TESTIMONIALS.map((testimonial) => (
|
|
71
|
+
<Card key={testimonial.name} className="border-border">
|
|
72
|
+
<CardContent className="flex h-full flex-col gap-5 pt-6">
|
|
73
|
+
<p className="text-sm leading-6 text-fg-secondary">“{testimonial.quote}”</p>
|
|
74
|
+
<div className="mt-auto flex items-center gap-3">
|
|
75
|
+
<Avatar className="size-9">
|
|
76
|
+
<AvatarFallback className="bg-surface-overlay text-xs text-fg-secondary">
|
|
77
|
+
{testimonial.initials}
|
|
78
|
+
</AvatarFallback>
|
|
79
|
+
</Avatar>
|
|
80
|
+
<div className="min-w-0">
|
|
81
|
+
<p className="truncate text-sm font-medium text-fg">{testimonial.name}</p>
|
|
82
|
+
<p className="truncate text-xs text-fg-tertiary">{testimonial.role}</p>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</CardContent>
|
|
86
|
+
</Card>
|
|
87
|
+
))}
|
|
88
|
+
</div>
|
|
89
|
+
</section>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Button } from "@cronus-ui/ui/button";
|
|
4
|
+
import { Input } from "@cronus-ui/ui/input";
|
|
5
|
+
import { Label } from "@cronus-ui/ui/label";
|
|
6
|
+
import { ArrowRight, Check } from "lucide-react";
|
|
7
|
+
import { useState } from "react";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Waitlist email capture that swaps to a joined confirmation on submit. The
|
|
11
|
+
* swap area is a persistent `aria-live` region so the change is announced to
|
|
12
|
+
* screen readers. Replace the `onSubmit` with your API call or server action.
|
|
13
|
+
*/
|
|
14
|
+
export function WaitlistCta() {
|
|
15
|
+
const [email, setEmail] = useState("");
|
|
16
|
+
const [joined, setJoined] = useState(false);
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<section id="waitlist" className="scroll-mt-16 px-6 py-20">
|
|
20
|
+
<div className="relative mx-auto max-w-5xl overflow-hidden rounded-2xl bg-gradient-primary px-8 py-16 text-center shadow-glow sm:px-16">
|
|
21
|
+
<div
|
|
22
|
+
aria-hidden="true"
|
|
23
|
+
className="pointer-events-none absolute inset-0 [mask-image:radial-gradient(ellipse_at_center,black,transparent_75%)]"
|
|
24
|
+
style={{
|
|
25
|
+
backgroundImage: "radial-gradient(circle, rgba(255,255,255,0.25) 1px, transparent 1px)",
|
|
26
|
+
backgroundSize: "22px 22px",
|
|
27
|
+
}}
|
|
28
|
+
/>
|
|
29
|
+
<div className="relative mx-auto max-w-2xl">
|
|
30
|
+
<h2 className="font-display text-4xl font-semibold tracking-tight text-primary-foreground sm:text-5xl">
|
|
31
|
+
Be first in line
|
|
32
|
+
</h2>
|
|
33
|
+
<p className="mt-4 text-balance text-lg text-primary-foreground/80">
|
|
34
|
+
Join the waitlist and we'll email your invite the moment a spot opens. No spam, and
|
|
35
|
+
you can leave the list any time.
|
|
36
|
+
</p>
|
|
37
|
+
|
|
38
|
+
<div aria-live="polite" className="mx-auto mt-8 max-w-md">
|
|
39
|
+
{joined ? (
|
|
40
|
+
<div
|
|
41
|
+
role="status"
|
|
42
|
+
className="flex items-center gap-3 rounded-xl bg-surface-base px-4 py-3 text-left shadow-sm"
|
|
43
|
+
>
|
|
44
|
+
<span className="inline-flex size-9 shrink-0 items-center justify-center rounded-full bg-success/10 text-success">
|
|
45
|
+
<Check aria-hidden="true" className="size-4" />
|
|
46
|
+
</span>
|
|
47
|
+
<div className="min-w-0 flex-1">
|
|
48
|
+
<p className="text-sm font-medium text-fg">You're on the list</p>
|
|
49
|
+
<p className="truncate text-sm text-fg-tertiary">
|
|
50
|
+
We'll email {email} when your invite is ready.
|
|
51
|
+
</p>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
) : (
|
|
55
|
+
<form
|
|
56
|
+
aria-label="Join the waitlist"
|
|
57
|
+
className="flex flex-col gap-3 sm:flex-row"
|
|
58
|
+
onSubmit={(event) => {
|
|
59
|
+
event.preventDefault();
|
|
60
|
+
setJoined(true);
|
|
61
|
+
}}
|
|
62
|
+
>
|
|
63
|
+
<div className="flex-1 text-left">
|
|
64
|
+
<Label htmlFor="waitlist-email" className="sr-only">
|
|
65
|
+
Email address
|
|
66
|
+
</Label>
|
|
67
|
+
<Input
|
|
68
|
+
id="waitlist-email"
|
|
69
|
+
type="email"
|
|
70
|
+
required
|
|
71
|
+
autoComplete="email"
|
|
72
|
+
placeholder="you@company.com"
|
|
73
|
+
value={email}
|
|
74
|
+
onChange={(event) => setEmail(event.target.value)}
|
|
75
|
+
className="h-11 border-transparent bg-surface-base text-fg placeholder:text-fg-tertiary"
|
|
76
|
+
/>
|
|
77
|
+
</div>
|
|
78
|
+
<Button type="submit" variant="secondary" size="lg">
|
|
79
|
+
Join the waitlist
|
|
80
|
+
<ArrowRight aria-hidden="true" />
|
|
81
|
+
</Button>
|
|
82
|
+
</form>
|
|
83
|
+
)}
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<p className="mt-4 text-sm text-primary-foreground/70">
|
|
87
|
+
No spam. Unsubscribe at any time.
|
|
88
|
+
</p>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
</section>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"aliases": {
|
|
3
|
+
"ui": "@/components/ui",
|
|
4
|
+
"lib": "@/lib",
|
|
5
|
+
"blocks": "@/components/blocks"
|
|
6
|
+
},
|
|
7
|
+
"paths": {
|
|
8
|
+
"ui": "components/ui",
|
|
9
|
+
"lib": "lib",
|
|
10
|
+
"blocks": "components/blocks"
|
|
11
|
+
},
|
|
12
|
+
"registry": "https://raw.githubusercontent.com/pedrogbraz/cronus-ui/v0.6.0/registry",
|
|
13
|
+
"theme": {
|
|
14
|
+
"name": "__THEME__",
|
|
15
|
+
"mode": "__MODE__"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# dependencies
|
|
2
|
+
/node_modules
|
|
3
|
+
/.pnp
|
|
4
|
+
.pnp.*
|
|
5
|
+
|
|
6
|
+
# next.js
|
|
7
|
+
/.next/
|
|
8
|
+
/out/
|
|
9
|
+
|
|
10
|
+
# production
|
|
11
|
+
/build
|
|
12
|
+
|
|
13
|
+
# misc
|
|
14
|
+
.DS_Store
|
|
15
|
+
*.pem
|
|
16
|
+
|
|
17
|
+
# debug
|
|
18
|
+
npm-debug.log*
|
|
19
|
+
yarn-debug.log*
|
|
20
|
+
yarn-error.log*
|
|
21
|
+
.pnpm-debug.log*
|
|
22
|
+
|
|
23
|
+
# env files
|
|
24
|
+
.env*.local
|
|
25
|
+
|
|
26
|
+
# typescript
|
|
27
|
+
*.tsbuildinfo
|
|
28
|
+
next-env.d.ts
|
|
29
|
+
|
|
30
|
+
# lockfiles (keep the one your package manager writes)
|
|
31
|
+
# package-lock.json
|
|
32
|
+
# pnpm-lock.yaml
|
|
33
|
+
# yarn.lock
|
|
34
|
+
# bun.lockb
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @cronus-ui/ui ships ESM with its `"use client"` boundaries already in place,
|
|
3
|
+
* so no `transpilePackages` is needed when it's installed (the normal case).
|
|
4
|
+
*
|
|
5
|
+
* @type {import('next').NextConfig}
|
|
6
|
+
*/
|
|
7
|
+
const nextConfig = {
|
|
8
|
+
reactStrictMode: true,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default nextConfig;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__APP_NAME__",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "next dev",
|
|
8
|
+
"build": "next build",
|
|
9
|
+
"start": "next start"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@cronus-ui/theme": "^0.6.0",
|
|
13
|
+
"@cronus-ui/tokens": "^0.6.0",
|
|
14
|
+
"@cronus-ui/ui": "^0.6.0",
|
|
15
|
+
"lucide-react": "^0.577.0",
|
|
16
|
+
"next": "16.2.10",
|
|
17
|
+
"react": "^19.2.0",
|
|
18
|
+
"react-dom": "^19.2.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@tailwindcss/postcss": "^4.3.0",
|
|
22
|
+
"@types/node": "^22.10.0",
|
|
23
|
+
"@types/react": "^19.0.0",
|
|
24
|
+
"@types/react-dom": "^19.0.0",
|
|
25
|
+
"tailwindcss": "^4.3.0",
|
|
26
|
+
"typescript": "^6.0.3"
|
|
27
|
+
}
|
|
28
|
+
}
|