Loading
SaaS 랜딩 페이지 — Hero · Features · Pricing · FAQ · CTA.
"use client";
import * as React from "react";
import { cn } from "@/lib/utils";
import { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
/**
* Landing Page — SaaS 랜딩 페이지 템플릿.
*
* 구성: 상단 네비 · Hero(중앙 정렬 + CTA) · Features 3종 · Pricing 3단 · FAQ · CTA 푸터.
* Groudit 컴포넌트(Card / Badge / Button)로 조합한 마케팅 페이지 패턴.
*/
const FEATURES = [
{ icon: "⚡", title: "Fast by default", body: "복사해서 붙이면 끝. 빌드 설정 없이 바로 동작." },
{ icon: "🎨", title: "Themeable", body: "CSS 변수 한 줄로 라이트/다크/커스텀 테마 전환." },
{ icon: "♿", title: "Accessible", body: "Base UI 위에서 키보드·ARIA·WCAG 기본 탑재." },
];
const PLANS = [
{ name: "Free", price: "$0", features: ["50+ 컴포넌트", "MIT 라이선스", "커뮤니티"], highlight: false },
{ name: "Pro", price: "$19", features: ["블록 · 템플릿", "우선 지원", "Figma 키트"], highlight: true },
{ name: "Team", price: "$49", features: ["멀티 시트", "전용 채널", "온보딩"], highlight: false },
];
const FAQS = [
{ q: "정말 무료인가요?", a: "핵심 컴포넌트는 MIT 라이선스로 영구 무료입니다." },
{ q: "내 프로젝트에 써도 되나요?", a: "네. 복사해서 자유롭게 수정·재배포할 수 있습니다." },
{ q: "프레임워크 제한이 있나요?", a: "React 19 + Tailwind CSS 기반이면 어디든 동작합니다." },
];
export function LandingPageTemplate() {
const [openFaq, setOpenFaq] = React.useState(0);
return (
<div className="h-[640px] w-full overflow-y-auto rounded-lg border border-border bg-background">
{/* Nav */}
<header className="sticky top-0 z-10 flex h-14 items-center justify-between border-b border-border bg-background/80 px-5 backdrop-blur">
<div className="flex items-center gap-2">
<div className="flex size-7 items-center justify-center rounded-md bg-primary text-xs font-bold text-primary-foreground">
G
</div>
<span className="text-sm font-semibold">Acme</span>
</div>
<nav className="hidden items-center gap-5 text-sm text-muted-foreground sm:flex">
<span>Features</span>
<span>Pricing</span>
<span>FAQ</span>
</nav>
<Button size="sm">Get Started</Button>
</header>
{/* Hero */}
<section className="mx-auto max-w-2xl px-6 py-14 text-center">
<Badge variant="secondary" className="mb-4">v1.0 출시</Badge>
<h1 className="text-3xl font-bold tracking-tight sm:text-4xl">
더 빠르게 만드는{" "}
<span className="bg-gradient-to-r from-primary to-(--informative) bg-clip-text text-transparent">
제품 UI
</span>
</h1>
<p className="mx-auto mt-3 max-w-md text-sm text-muted-foreground">
복사해서 바로 쓰는 React 컴포넌트. 설치도, 잠금도 없이 당신의 코드가 됩니다.
</p>
<div className="mt-6 flex items-center justify-center gap-2">
<Button>지금 시작하기</Button>
<Button variant="outline">컴포넌트 보기</Button>
</div>
</section>
{/* Features */}
<section className="mx-auto max-w-4xl px-6 py-8">
<div className="grid gap-4 sm:grid-cols-3">
{FEATURES.map((f) => (
<Card key={f.title}>
<CardContent className="p-5">
<div className="text-2xl">{f.icon}</div>
<h3 className="mt-3 text-sm font-semibold">{f.title}</h3>
<p className="mt-1 text-xs text-muted-foreground">{f.body}</p>
</CardContent>
</Card>
))}
</div>
</section>
{/* Pricing */}
<section className="mx-auto max-w-4xl px-6 py-8">
<h2 className="mb-5 text-center text-lg font-bold tracking-tight">요금제</h2>
<div className="grid items-stretch gap-4 sm:grid-cols-3">
{PLANS.map((p) => (
<div
key={p.name}
className={cn(
"flex flex-col rounded-xl border p-5",
p.highlight ? "border-primary ring-1 ring-primary/40" : "border-border",
)}
>
{p.highlight && <Badge className="mb-2 w-fit">추천</Badge>}
<h3 className="text-sm font-semibold">{p.name}</h3>
<div className="mt-1 text-2xl font-bold">
{p.price}
<span className="text-xs font-normal text-muted-foreground">/mo</span>
</div>
<ul className="mt-3 flex-1 space-y-1.5 text-xs text-muted-foreground">
{p.features.map((f) => (
<li key={f} className="flex items-center gap-1.5">
<span className="text-primary">✓</span>
{f}
</li>
))}
</ul>
<Button className="mt-4 w-full" variant={p.highlight ? "default" : "outline"}>
선택
</Button>
</div>
))}
</div>
</section>
{/* FAQ */}
<section className="mx-auto max-w-2xl px-6 py-8">
<h2 className="mb-4 text-center text-lg font-bold tracking-tight">자주 묻는 질문</h2>
<div className="divide-y divide-border rounded-xl border border-border">
{FAQS.map((item, i) => (
<div key={item.q}>
<button
type="button"
onClick={() => setOpenFaq(openFaq === i ? -1 : i)}
className="flex w-full items-center justify-between gap-2 px-4 py-3 text-left text-sm font-medium"
aria-expanded={openFaq === i}
>
{item.q}
<span className={cn("text-muted-foreground transition-transform", openFaq === i && "rotate-180")}>⌄</span>
</button>
{openFaq === i && (
<p className="px-4 pb-3 text-xs text-muted-foreground">{item.a}</p>
)}
</div>
))}
</div>
</section>
{/* CTA */}
<section className="mx-6 mb-10 rounded-2xl bg-gradient-to-br from-primary to-(--informative) p-8 text-center text-primary-foreground">
<h2 className="text-xl font-bold tracking-tight">지금 시작하세요</h2>
<p className="mx-auto mt-2 max-w-sm text-sm opacity-90">
5분이면 첫 컴포넌트를 화면에 올릴 수 있습니다.
</p>
<Button variant="secondary" className="mt-5">
무료로 시작 →
</Button>
</section>
</div>
);
}