Add interactive demo components and feature sections to homepage

- Introduced `DemoSection`, showcasing live feature demos with interactive cards and test credentials for admin and auth flows.
- Added `FeatureGrid` with dynamic animations, highlighting major application features like RBAC, documentation, and deployment readiness.
- Built reusable `FeatureCard` for feature details, including icons, descriptions, and CTAs.
- Implemented `TechStackSection` to display modern tools and technologies used in the stack with tooltips.
- Updated dependencies: added `framer-motion`, `lucide-react`, and `react-syntax-highlighter`.
This commit is contained in:
2025-11-08 15:46:52 +01:00
parent e02329b734
commit 63c171f83e
17 changed files with 1796 additions and 92 deletions

View File

@@ -22,10 +22,12 @@
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-tabs": "^1.1.13",
"@tanstack/react-query": "^5.90.5",
"@types/react-syntax-highlighter": "^15.5.13",
"axios": "^1.13.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"framer-motion": "^12.23.24",
"gray-matter": "^4.0.3",
"lucide-react": "^0.552.0",
"next": "^15.5.6",
@@ -34,6 +36,7 @@
"react-dom": "^19.0.0",
"react-hook-form": "^7.66.0",
"react-markdown": "^10.1.0",
"react-syntax-highlighter": "^16.1.0",
"recharts": "^2.15.4",
"rehype-autolink-headings": "^7.1.0",
"rehype-highlight": "^7.0.2",
@@ -5185,6 +5188,12 @@
"@types/pg": "*"
}
},
"node_modules/@types/prismjs": {
"version": "1.26.5",
"resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz",
"integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==",
"license": "MIT"
},
"node_modules/@types/react": {
"version": "19.2.2",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
@@ -5204,6 +5213,15 @@
"@types/react": "^19.2.0"
}
},
"node_modules/@types/react-syntax-highlighter": {
"version": "15.5.13",
"resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz",
"integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==",
"license": "MIT",
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/shimmer": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.2.0.tgz",
@@ -8757,6 +8775,19 @@
"reusify": "^1.0.4"
}
},
"node_modules/fault": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz",
"integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==",
"license": "MIT",
"dependencies": {
"format": "^0.2.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/fb-watchman": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
@@ -8910,6 +8941,14 @@
"node": ">= 6"
}
},
"node_modules/format": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz",
"integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==",
"engines": {
"node": ">=0.4.x"
}
},
"node_modules/forwarded-parse": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz",
@@ -8917,6 +8956,33 @@
"dev": true,
"license": "MIT"
},
"node_modules/framer-motion": {
"version": "12.23.24",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.24.tgz",
"integrity": "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==",
"license": "MIT",
"dependencies": {
"motion-dom": "^12.23.23",
"motion-utils": "^12.23.6",
"tslib": "^2.4.0"
},
"peerDependencies": {
"@emotion/is-prop-valid": "*",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/is-prop-valid": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -9456,6 +9522,19 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/hast-util-parse-selector": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz",
"integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==",
"license": "MIT",
"dependencies": {
"@types/hast": "^3.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/hast-util-to-jsx-runtime": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
@@ -9525,6 +9604,23 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/hastscript": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz",
"integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==",
"license": "MIT",
"dependencies": {
"@types/hast": "^3.0.0",
"comma-separated-tokens": "^2.0.0",
"hast-util-parse-selector": "^4.0.0",
"property-information": "^7.0.0",
"space-separated-tokens": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/highlight.js": {
"version": "11.11.1",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz",
@@ -9534,6 +9630,12 @@
"node": ">=12.0.0"
}
},
"node_modules/highlightjs-vue": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz",
"integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==",
"license": "CC0-1.0"
},
"node_modules/html-encoding-sniffer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
@@ -13199,6 +13301,21 @@
"dev": true,
"license": "MIT"
},
"node_modules/motion-dom": {
"version": "12.23.23",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz",
"integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==",
"license": "MIT",
"dependencies": {
"motion-utils": "^12.23.6"
}
},
"node_modules/motion-utils": {
"version": "12.23.6",
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz",
"integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==",
"license": "MIT"
},
"node_modules/mrmime": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
@@ -14216,6 +14333,15 @@
"license": "MIT",
"peer": true
},
"node_modules/prismjs": {
"version": "1.30.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
"integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/progress": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
@@ -14553,6 +14679,49 @@
}
}
},
"node_modules/react-syntax-highlighter": {
"version": "16.1.0",
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-16.1.0.tgz",
"integrity": "sha512-E40/hBiP5rCNwkeBN1vRP+xow1X0pndinO+z3h7HLsHyjztbyjfzNWNKuAsJj+7DLam9iT4AaaOZnueCU+Nplg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.4",
"highlight.js": "^10.4.1",
"highlightjs-vue": "^1.0.0",
"lowlight": "^1.17.0",
"prismjs": "^1.30.0",
"refractor": "^5.0.0"
},
"engines": {
"node": ">= 16.20.2"
},
"peerDependencies": {
"react": ">= 0.14.0"
}
},
"node_modules/react-syntax-highlighter/node_modules/highlight.js": {
"version": "10.7.3",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
"integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==",
"license": "BSD-3-Clause",
"engines": {
"node": "*"
}
},
"node_modules/react-syntax-highlighter/node_modules/lowlight": {
"version": "1.20.0",
"resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz",
"integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==",
"license": "MIT",
"dependencies": {
"fault": "^1.0.0",
"highlight.js": "~10.7.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
@@ -14658,6 +14827,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/refractor": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/refractor/-/refractor-5.0.0.tgz",
"integrity": "sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==",
"license": "MIT",
"dependencies": {
"@types/hast": "^3.0.0",
"@types/prismjs": "^1.0.0",
"hastscript": "^9.0.0",
"parse-entities": "^4.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/regexp.prototype.flags": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",

View File

@@ -35,10 +35,12 @@
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-tabs": "^1.1.13",
"@tanstack/react-query": "^5.90.5",
"@types/react-syntax-highlighter": "^15.5.13",
"axios": "^1.13.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"framer-motion": "^12.23.24",
"gray-matter": "^4.0.3",
"lucide-react": "^0.552.0",
"next": "^15.5.6",
@@ -47,6 +49,7 @@
"react-dom": "^19.0.0",
"react-hook-form": "^7.66.0",
"react-markdown": "^10.1.0",
"react-syntax-highlighter": "^16.1.0",
"recharts": "^2.15.4",
"rehype-autolink-headings": "^7.1.0",
"rehype-highlight": "^7.0.2",

View File

@@ -1,100 +1,95 @@
import Image from "next/image";
/**
* Homepage / Landing Page
* Main landing page for the FastNext Template project
* Showcases features, tech stack, and provides demos for developers
*/
import { Header } from '@/components/home/Header';
import { HeroSection } from '@/components/home/HeroSection';
import { ContextSection } from '@/components/home/ContextSection';
import { AnimatedTerminal } from '@/components/home/AnimatedTerminal';
import { FeatureGrid } from '@/components/home/FeatureGrid';
import { DemoSection } from '@/components/home/DemoSection';
import { StatsSection } from '@/components/home/StatsSection';
import { TechStackSection } from '@/components/home/TechStackSection';
import { PhilosophySection } from '@/components/home/PhilosophySection';
import { QuickStartCode } from '@/components/home/QuickStartCode';
import { CTASection } from '@/components/home/CTASection';
export default function Home() {
return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={180}
height={38}
priority
/>
<ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
<li className="mb-2">
Get started by editing{" "}
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold">
src/app/page.tsx
</code>
.
</li>
<li>Save and see your changes instantly.</li>
</ol>
<div className="min-h-screen">
{/* Header Navigation */}
<Header />
<div className="flex gap-4 items-center flex-col sm:flex-row">
{/* Main Content */}
<main>
{/* Hero Section with CTAs */}
<HeroSection />
{/* What is this template? */}
<ContextSection />
{/* Animated Terminal with Quick Start */}
<AnimatedTerminal />
{/* 6 Feature Cards Grid */}
<FeatureGrid />
{/* Interactive Demo Cards */}
<DemoSection />
{/* Statistics with Animated Counters */}
<StatsSection />
{/* Tech Stack Grid */}
<TechStackSection />
{/* For Developers, By Developers */}
<PhilosophySection />
{/* Quick Start Code Block */}
<QuickStartCode />
{/* Final CTA Section */}
<CTASection />
</main>
{/* Footer */}
<footer className="border-t bg-muted/30">
<div className="container mx-auto px-6 py-8">
<div className="flex flex-col md:flex-row items-center justify-between gap-4">
<div className="text-sm text-muted-foreground">
© {new Date().getFullYear()} FastNext Template. MIT Licensed.
</div>
<div className="flex items-center gap-6 text-sm text-muted-foreground">
<a
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
href="https://github.com/your-org/fast-next-template"
target="_blank"
rel="noopener noreferrer"
className="hover:text-foreground transition-colors"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={20}
height={20}
/>
Deploy now
GitHub
</a>
<a
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
href="https://github.com/your-org/fast-next-template#documentation"
target="_blank"
rel="noopener noreferrer"
className="hover:text-foreground transition-colors"
>
Read our docs
Documentation
</a>
<a
href="https://github.com/your-org/fast-next-template/issues"
target="_blank"
rel="noopener noreferrer"
className="hover:text-foreground transition-colors"
>
Report Issue
</a>
</div>
</main>
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/file.svg"
alt="File icon"
width={16}
height={16}
/>
Learn
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/window.svg"
alt="Window icon"
width={16}
height={16}
/>
Examples
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/globe.svg"
alt="Globe icon"
width={16}
height={16}
/>
Go to nextjs.org
</a>
</div>
</div>
</footer>
</div>
);

View File

@@ -0,0 +1,140 @@
/**
* Animated Terminal
* Terminal with typing animation showing installation/setup commands
*/
'use client';
import { useEffect, useState, useRef } from 'react';
import { motion } from 'framer-motion';
import { Terminal, Play } from 'lucide-react';
import { Button } from '@/components/ui/button';
import Link from 'next/link';
const commands = [
{ text: '# Clone the repository', delay: 0 },
{ text: '$ git clone https://github.com/your-org/fast-next-template.git', delay: 800 },
{ text: '$ cd fast-next-template', delay: 1600 },
{ text: '', delay: 2200 },
{ text: '# Start with Docker (one command)', delay: 2400 },
{ text: '$ docker-compose up', delay: 3200 },
{ text: '', delay: 4000 },
{ text: '✓ Backend running at http://localhost:8000', delay: 4200, isSuccess: true },
{ text: '✓ Frontend running at http://localhost:3000', delay: 4400, isSuccess: true },
{ text: '✓ Admin panel at http://localhost:3000/admin', delay: 4600, isSuccess: true },
{ text: '✓ API docs at http://localhost:8000/docs', delay: 4800, isSuccess: true },
];
export function AnimatedTerminal() {
const [displayedLines, setDisplayedLines] = useState<typeof commands>([]);
const [isAnimating, setIsAnimating] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
const hasAnimated = useRef(false);
useEffect(() => {
// Only animate once when component enters viewport
if (hasAnimated.current) return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting && !hasAnimated.current) {
hasAnimated.current = true;
setIsAnimating(true);
animateCommands();
}
},
{ threshold: 0.3 }
);
if (containerRef.current) {
observer.observe(containerRef.current);
}
return () => observer.disconnect();
}, []);
const animateCommands = () => {
commands.forEach((cmd) => {
setTimeout(() => {
setDisplayedLines((prev) => [...prev, cmd]);
}, cmd.delay);
});
};
return (
<section className="container mx-auto px-6 py-16 md:py-24" ref={containerRef}>
<motion.div
className="mx-auto max-w-4xl"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-100px' }}
transition={{ duration: 0.6 }}
>
{/* Title */}
<div className="text-center mb-8">
<h2 className="text-3xl md:text-4xl font-bold mb-4">Get Started in Seconds</h2>
<p className="text-lg text-muted-foreground">
Clone, run, and start building. No complex setup required.
</p>
</div>
{/* Terminal Window */}
<div className="rounded-lg border bg-card shadow-lg overflow-hidden">
{/* Terminal Header */}
<div className="flex items-center gap-2 border-b bg-muted/50 px-4 py-3">
<div className="flex gap-1.5">
<div className="h-3 w-3 rounded-full bg-red-500" aria-hidden="true" />
<div className="h-3 w-3 rounded-full bg-yellow-500" aria-hidden="true" />
<div className="h-3 w-3 rounded-full bg-green-500" aria-hidden="true" />
</div>
<div className="flex items-center gap-2 ml-4 text-sm text-muted-foreground">
<Terminal className="h-4 w-4" aria-hidden="true" />
<span className="font-mono">bash</span>
</div>
</div>
{/* Terminal Content */}
<div className="bg-slate-950 p-6 font-mono text-sm overflow-x-auto" style={{ minHeight: '400px' }}>
<div className="space-y-2">
{displayedLines.map((line, index) => (
<motion.div
key={index}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.2 }}
className={`${
line.isSuccess
? 'text-green-400'
: line.text.startsWith('#')
? 'text-slate-500'
: line.text.startsWith('$')
? 'text-blue-400'
: 'text-slate-300'
}`}
>
{line.text || '\u00A0'}
{index === displayedLines.length - 1 && isAnimating && !line.isSuccess && (
<span className="inline-block w-2 h-4 ml-1 bg-slate-400 animate-pulse" aria-hidden="true" />
)}
</motion.div>
))}
</div>
</div>
</div>
{/* CTA Below Terminal */}
<div className="mt-8 text-center">
<p className="text-muted-foreground mb-4">
Or try the live demo without installing anything
</p>
<Button asChild size="lg" variant="outline" className="gap-2">
<Link href="/login">
<Play className="h-5 w-5" aria-hidden="true" />
Try Live Demo
</Link>
</Button>
</div>
</motion.div>
</section>
);
}

View File

@@ -0,0 +1,124 @@
/**
* CTA Section
* Final call-to-action footer section
*/
'use client';
import { useState } from 'react';
import Link from 'next/link';
import { motion } from 'framer-motion';
import { Github, Star, Play, ArrowRight } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { DemoCredentialsModal } from './DemoCredentialsModal';
export function CTASection() {
const [demoModalOpen, setDemoModalOpen] = useState(false);
return (
<section className="relative overflow-hidden bg-gradient-to-br from-primary/10 via-background to-background">
{/* Background Pattern */}
<div
className="absolute inset-0 -z-10 bg-[radial-gradient(circle_at_50%_50%,rgba(var(--primary-rgb,120,119,198),0.1),transparent_70%)]"
aria-hidden="true"
/>
<div className="container mx-auto px-6 py-24 md:py-32">
<motion.div
className="mx-auto max-w-3xl text-center space-y-8"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-100px' }}
transition={{ duration: 0.6 }}
>
{/* Headline */}
<h2 className="text-4xl md:text-5xl font-bold tracking-tight">
Start Building,{' '}
<span className="bg-gradient-to-r from-primary to-primary/60 bg-clip-text text-transparent">
Not Boilerplating
</span>
</h2>
{/* Subtext */}
<p className="text-xl text-muted-foreground leading-relaxed">
Clone the repository, read the docs, and ship features on day one.{' '}
<span className="text-foreground font-medium">Free forever, MIT licensed.</span>
</p>
{/* CTAs */}
<div className="flex flex-col sm:flex-row items-center justify-center gap-4 pt-4">
<Button
asChild
size="lg"
className="gap-2 text-base group"
>
<a
href="https://github.com/your-org/fast-next-template"
target="_blank"
rel="noopener noreferrer"
>
<Github className="h-5 w-5" aria-hidden="true" />
Get Started on GitHub
<div className="inline-flex items-center gap-1 ml-2 rounded-full bg-background/20 px-2 py-0.5 text-xs">
<Star className="h-3 w-3 fill-current" aria-hidden="true" />
Star
</div>
</a>
</Button>
<Button
onClick={() => setDemoModalOpen(true)}
size="lg"
variant="outline"
className="gap-2 text-base group"
>
<Play className="h-5 w-5 group-hover:scale-110 transition-transform" aria-hidden="true" />
Try Live Demo
</Button>
<Button
asChild
size="lg"
variant="ghost"
className="gap-2 text-base group"
>
<a
href="https://github.com/your-org/fast-next-template#documentation"
target="_blank"
rel="noopener noreferrer"
>
Read Documentation
<ArrowRight className="h-4 w-4 group-hover:translate-x-1 transition-transform" aria-hidden="true" />
</a>
</Button>
</div>
{/* Extra Info */}
<motion.div
className="pt-8 text-sm text-muted-foreground"
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.2 }}
>
<p>
Need help getting started? Check out the{' '}
<Link href="/dev" className="text-primary hover:underline">
component showcase
</Link>
{' '}or explore the{' '}
<Link href="/admin" className="text-primary hover:underline">
admin dashboard demo
</Link>
.
</p>
</motion.div>
</motion.div>
</div>
{/* Demo Credentials Modal */}
<DemoCredentialsModal
open={demoModalOpen}
onClose={() => setDemoModalOpen(false)}
/>
</section>
);
}

View File

@@ -0,0 +1,59 @@
/**
* Context Section
* Explains what the template is and what you get out of the box
*/
'use client';
import { motion } from 'framer-motion';
import { CheckCircle2 } from 'lucide-react';
export function ContextSection() {
const features = [
'Clone & Deploy in < 5 minutes',
'97% Test Coverage (743 tests)',
'12+ Documentation Guides',
'Zero Commercial Dependencies',
];
return (
<section className="container mx-auto px-6 py-16 md:py-24">
<div className="mx-auto max-w-3xl">
<motion.div
className="text-center space-y-6"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-100px' }}
transition={{ duration: 0.6 }}
>
<h2 className="text-3xl md:text-4xl font-bold">
What You Get Out of the Box
</h2>
<p className="text-lg text-muted-foreground leading-relaxed">
This isn&apos;t a boilerplate generator or a paid SaaS template. It&apos;s a complete,
production-ready codebase you can clone and customize. Everything you need to build
modern web applications without reinventing authentication, authorization, and admin
infrastructure.
</p>
{/* Feature Badges */}
<div className="flex flex-wrap justify-center gap-3 pt-4">
{features.map((feature, index) => (
<motion.div
key={feature}
className="inline-flex items-center gap-2 rounded-full border bg-card px-4 py-2 text-sm font-medium"
initial={{ opacity: 0, scale: 0.9 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.3, delay: index * 0.1 }}
>
<CheckCircle2 className="h-4 w-4 text-green-600" aria-hidden="true" />
<span>{feature}</span>
</motion.div>
))}
</div>
</motion.div>
</div>
</section>
);
}

View File

@@ -0,0 +1,141 @@
/**
* Demo Credentials Modal
* Displays demo login credentials for testing the live application
*/
'use client';
import { useState } from 'react';
import Link from 'next/link';
import { Copy, Check } from 'lucide-react';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
interface DemoCredentialsModalProps {
open: boolean;
onClose: () => void;
}
export function DemoCredentialsModal({ open, onClose }: DemoCredentialsModalProps) {
const [copiedRegular, setCopiedRegular] = useState(false);
const [copiedAdmin, setCopiedAdmin] = useState(false);
const regularCredentials = 'demo@example.com\nDemo123!';
const adminCredentials = 'admin@example.com\nAdmin123!';
const copyToClipboard = async (text: string, type: 'regular' | 'admin') => {
try {
await navigator.clipboard.writeText(text);
if (type === 'regular') {
setCopiedRegular(true);
setTimeout(() => setCopiedRegular(false), 2000);
} else {
setCopiedAdmin(true);
setTimeout(() => setCopiedAdmin(false), 2000);
}
} catch (err) {
console.error('Failed to copy:', err);
}
};
return (
<Dialog open={open} onOpenChange={onClose}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Try the Live Demo</DialogTitle>
<DialogDescription>
Use these credentials to explore the template&apos;s features. Both accounts are pre-configured with sample data.
</DialogDescription>
</DialogHeader>
<div className="space-y-4 my-4">
{/* Regular User Credentials */}
<div className="rounded-lg border p-4 space-y-3 bg-card">
<div className="flex items-center justify-between">
<p className="font-semibold text-sm">Regular User</p>
<Button
variant="ghost"
size="sm"
onClick={() => copyToClipboard(regularCredentials, 'regular')}
className="h-8 gap-2"
>
{copiedRegular ? (
<Check className="h-4 w-4 text-green-600" aria-hidden="true" />
) : (
<Copy className="h-4 w-4" aria-hidden="true" />
)}
<span className="sr-only">Copy regular user credentials</span>
{copiedRegular ? 'Copied!' : 'Copy'}
</Button>
</div>
<div className="font-mono text-sm space-y-1 text-muted-foreground">
<p className="flex items-center gap-2">
<span className="text-xs font-sans text-muted-foreground/70">Email:</span>
<span className="text-foreground">demo@example.com</span>
</p>
<p className="flex items-center gap-2">
<span className="text-xs font-sans text-muted-foreground/70">Password:</span>
<span className="text-foreground">Demo123!</span>
</p>
</div>
<p className="text-xs text-muted-foreground">
Access settings, organizations, and user features
</p>
</div>
{/* Admin User Credentials */}
<div className="rounded-lg border p-4 space-y-3 bg-card">
<div className="flex items-center justify-between">
<p className="font-semibold text-sm">Admin User (Superuser)</p>
<Button
variant="ghost"
size="sm"
onClick={() => copyToClipboard(adminCredentials, 'admin')}
className="h-8 gap-2"
>
{copiedAdmin ? (
<Check className="h-4 w-4 text-green-600" aria-hidden="true" />
) : (
<Copy className="h-4 w-4" aria-hidden="true" />
)}
<span className="sr-only">Copy admin user credentials</span>
{copiedAdmin ? 'Copied!' : 'Copy'}
</Button>
</div>
<div className="font-mono text-sm space-y-1 text-muted-foreground">
<p className="flex items-center gap-2">
<span className="text-xs font-sans text-muted-foreground/70">Email:</span>
<span className="text-foreground">admin@example.com</span>
</p>
<p className="flex items-center gap-2">
<span className="text-xs font-sans text-muted-foreground/70">Password:</span>
<span className="text-foreground">Admin123!</span>
</p>
</div>
<p className="text-xs text-muted-foreground">
Full admin panel access: user management, analytics, bulk operations
</p>
</div>
</div>
<DialogFooter className="flex flex-col sm:flex-row gap-2">
<Button variant="outline" onClick={onClose} className="w-full sm:w-auto">
Close
</Button>
<Button asChild className="w-full sm:w-auto">
<Link href="/login" onClick={onClose}>
Go to Login
</Link>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@@ -0,0 +1,97 @@
/**
* Demo Section
* Interactive demo cards showing live features with demo credentials
*/
'use client';
import Link from 'next/link';
import { motion } from 'framer-motion';
import { Play, Layers, ShieldCheck } from 'lucide-react';
import { Button } from '@/components/ui/button';
const demos = [
{
icon: Layers,
title: 'Component Showcase',
description:
'Browse the complete design system, UI components, and interactive examples built with shadcn/ui and TailwindCSS',
href: '/dev',
cta: 'View Components',
variant: 'outline' as const,
},
{
icon: ShieldCheck,
title: 'Authentication Flow',
description:
'Test the complete auth flow: login, session management, password reset. Full security implementation with JWT and refresh tokens',
href: '/login',
credentials: 'demo@example.com / Demo123!',
cta: 'Try Auth Demo',
variant: 'default' as const,
},
{
icon: Play,
title: 'Admin Dashboard',
description:
'Experience the admin panel with user management, real-time analytics charts, bulk operations, and session monitoring',
href: '/admin',
credentials: 'admin@example.com / Admin123!',
cta: 'Launch Admin',
variant: 'outline' as const,
},
];
export function DemoSection() {
return (
<section className="container mx-auto px-6 py-16 md:py-24 bg-muted/30">
<motion.div
className="text-center mb-12"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-100px' }}
transition={{ duration: 0.6 }}
>
<h2 className="text-3xl md:text-4xl font-bold mb-4">See It In Action</h2>
<p className="text-lg text-muted-foreground max-w-2xl mx-auto">
Explore the template&apos;s capabilities with live demos. Login with demo credentials to test features.
</p>
</motion.div>
<div className="grid md:grid-cols-3 gap-6 max-w-6xl mx-auto">
{demos.map((demo, index) => (
<motion.div
key={demo.title}
className="rounded-lg border bg-card p-6 hover:shadow-lg transition-shadow"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-100px' }}
transition={{ duration: 0.5, delay: index * 0.1 }}
whileHover={{ y: -4 }}
>
{/* Icon */}
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-lg bg-gradient-to-br from-primary to-primary/60">
<demo.icon className="h-6 w-6 text-primary-foreground" aria-hidden="true" />
</div>
<h3 className="text-xl font-semibold mb-2">{demo.title}</h3>
<p className="text-muted-foreground mb-4 leading-relaxed">
{demo.description}
</p>
{demo.credentials && (
<div className="mb-4 p-3 rounded-md bg-muted font-mono text-xs border">
<div className="text-muted-foreground mb-1">Demo Credentials:</div>
<div className="text-foreground">{demo.credentials}</div>
</div>
)}
<Button asChild className="w-full" variant={demo.variant}>
<Link href={demo.href}>{demo.cta} </Link>
</Button>
</motion.div>
))}
</div>
</section>
);
}

View File

@@ -0,0 +1,63 @@
/**
* Feature Card
* Reusable feature card component with icon, title, description, and CTA link
*/
'use client';
import Link from 'next/link';
import { motion } from 'framer-motion';
import { ArrowRight, LucideIcon } from 'lucide-react';
interface FeatureCardProps {
icon: LucideIcon;
title: string;
description: string;
highlight: string;
ctaText: string;
ctaHref: string;
}
export function FeatureCard({
icon: Icon,
title,
description,
highlight,
ctaText,
ctaHref,
}: FeatureCardProps) {
return (
<motion.div
className="group relative rounded-lg border bg-card p-6 hover:shadow-lg transition-all"
whileHover={{ y: -4 }}
transition={{ duration: 0.2 }}
>
{/* Icon with Gradient Background */}
<div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-lg bg-gradient-to-br from-primary to-primary/60">
<Icon className="h-6 w-6 text-primary-foreground" aria-hidden="true" />
</div>
{/* Title */}
<h3 className="text-xl font-semibold mb-2">{title}</h3>
{/* Highlight Badge */}
<div className="mb-3">
<span className="inline-block rounded-full bg-primary/10 px-3 py-1 text-xs font-medium text-primary">
{highlight}
</span>
</div>
{/* Description */}
<p className="text-muted-foreground mb-4 leading-relaxed">{description}</p>
{/* CTA Link */}
<Link
href={ctaHref}
className="inline-flex items-center gap-1 text-sm font-medium text-primary hover:underline group-hover:gap-2 transition-all"
>
{ctaText}
<ArrowRight className="h-4 w-4" aria-hidden="true" />
</Link>
</motion.div>
);
}

View File

@@ -0,0 +1,117 @@
/**
* Feature Grid
* Grid layout displaying 6 main features with stagger animation
*/
'use client';
import { motion } from 'framer-motion';
import { Shield, Users, BarChart3, BookOpen, Server, Code } from 'lucide-react';
import { FeatureCard } from './FeatureCard';
const features = [
{
icon: Shield,
title: 'Authentication & Security',
description:
'JWT authentication with refresh tokens, session management, password reset flow, rate limiting, CSRF protection, and comprehensive security tests preventing common attacks (CVE-2015-9235, session hijacking)',
highlight: 'Battle-tested security',
ctaText: 'View Auth Flow',
ctaHref: '/login',
},
{
icon: Users,
title: 'Multi-Tenant Organizations',
description:
'Complete organization system with 3-tier RBAC (Owner/Admin/Member). Invite members, manage permissions, and scope data access per organization—all batteries included',
highlight: 'Multi-tenancy built-in',
ctaText: 'See Organizations',
ctaHref: '/admin/organizations',
},
{
icon: BarChart3,
title: 'Admin Dashboard',
description:
'Full-featured admin panel with user management, real-time analytics charts, bulk operations, session monitoring, and role-based access controls',
highlight: 'Enterprise-ready admin',
ctaText: 'Try Admin Panel',
ctaHref: '/admin',
},
{
icon: BookOpen,
title: 'Complete Documentation',
description:
'12+ documentation guides covering architecture, design system, testing patterns, deployment, and AI code generation guidelines. Interactive API docs with Swagger and ReDoc',
highlight: 'Developer-first docs',
ctaText: 'Browse Docs',
ctaHref: 'https://github.com/your-org/fast-next-template#documentation',
},
{
icon: Server,
title: 'Production Ready',
description:
'Docker deployment configs, database migrations with Alembic helpers, connection pooling, health checks, monitoring setup, and production security headers',
highlight: 'Deploy with confidence',
ctaText: 'Deployment Guide',
ctaHref: 'https://github.com/your-org/fast-next-template#deployment',
},
{
icon: Code,
title: 'Developer Experience',
description:
'Auto-generated TypeScript API client from OpenAPI spec, hot reload in development, migration helpers (python migrate.py auto), VS Code settings, and comprehensive component library',
highlight: 'Delightful DX',
ctaText: 'Explore Components',
ctaHref: '/dev',
},
];
const containerVariants = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: {
staggerChildren: 0.15,
},
},
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
show: { opacity: 1, y: 0 },
};
export function FeatureGrid() {
return (
<section className="container mx-auto px-6 py-16 md:py-24">
<motion.div
className="text-center mb-12"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-100px' }}
transition={{ duration: 0.6 }}
>
<h2 className="text-3xl md:text-4xl font-bold mb-4">
Comprehensive Features, No Assembly Required
</h2>
<p className="text-lg text-muted-foreground max-w-2xl mx-auto">
Everything you need to build production-grade web applications. Clone, customize, and ship.
</p>
</motion.div>
<motion.div
className="grid gap-6 md:grid-cols-2 lg:grid-cols-3"
variants={containerVariants}
initial="hidden"
whileInView="show"
viewport={{ once: true, margin: '-100px' }}
>
{features.map((feature) => (
<motion.div key={feature.title} variants={itemVariants}>
<FeatureCard {...feature} />
</motion.div>
))}
</motion.div>
</section>
);
}

View File

@@ -0,0 +1,155 @@
/**
* Homepage Header
* Navigation header for the landing page with demo credentials modal
*/
'use client';
import { useState } from 'react';
import Link from 'next/link';
import { Menu, X, Github, Star } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { DemoCredentialsModal } from './DemoCredentialsModal';
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
export function Header() {
const [demoModalOpen, setDemoModalOpen] = useState(false);
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const navLinks = [
{ href: '/dev', label: 'Components' },
{ href: '/admin', label: 'Admin Demo' },
];
return (
<>
<header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div className="container mx-auto flex h-16 items-center justify-between px-6">
{/* Logo */}
<Link href="/" className="font-bold text-xl hover:opacity-80 transition-opacity">
<span className="bg-gradient-to-r from-primary to-primary/60 bg-clip-text text-transparent">
FastNext
</span>
{' '}
<span className="text-foreground">Template</span>
</Link>
{/* Desktop Navigation */}
<nav className="hidden md:flex items-center gap-6">
{navLinks.map((link) => (
<Link
key={link.href}
href={link.href}
className="text-sm font-medium text-muted-foreground hover:text-foreground transition-colors"
>
{link.label}
</Link>
))}
{/* GitHub Link with Star */}
<a
href="https://github.com/your-org/fast-next-template"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 text-sm font-medium text-muted-foreground hover:text-foreground transition-colors"
>
<Github className="h-4 w-4" aria-hidden="true" />
<span>GitHub</span>
<span className="inline-flex items-center gap-1 rounded-full bg-muted px-2 py-0.5 text-xs">
<Star className="h-3 w-3 fill-current" aria-hidden="true" />
<span aria-label="GitHub stars">Star</span>
</span>
</a>
{/* CTAs */}
<Button
onClick={() => setDemoModalOpen(true)}
variant="default"
size="sm"
>
Try Demo
</Button>
<Button
asChild
variant="outline"
size="sm"
>
<Link href="/login">Login</Link>
</Button>
</nav>
{/* Mobile Menu Toggle */}
<Sheet open={mobileMenuOpen} onOpenChange={setMobileMenuOpen}>
<SheetTrigger asChild className="md:hidden">
<Button variant="ghost" size="icon" aria-label="Toggle menu">
{mobileMenuOpen ? (
<X className="h-5 w-5" aria-hidden="true" />
) : (
<Menu className="h-5 w-5" aria-hidden="true" />
)}
</Button>
</SheetTrigger>
<SheetContent side="right" className="w-[300px] sm:w-[400px]">
<nav className="flex flex-col gap-4 mt-8">
{navLinks.map((link) => (
<Link
key={link.href}
href={link.href}
onClick={() => setMobileMenuOpen(false)}
className="text-lg font-medium hover:text-primary transition-colors"
>
{link.href}
</Link>
))}
{/* GitHub Link */}
<a
href="https://github.com/your-org/fast-next-template"
target="_blank"
rel="noopener noreferrer"
onClick={() => setMobileMenuOpen(false)}
className="flex items-center gap-2 text-lg font-medium hover:text-primary transition-colors"
>
<Github className="h-5 w-5" aria-hidden="true" />
<span>GitHub</span>
<span className="inline-flex items-center gap-1 rounded-full bg-muted px-2 py-0.5 text-xs ml-auto">
<Star className="h-3 w-3 fill-current" aria-hidden="true" />
Star
</span>
</a>
<div className="border-t pt-4 mt-4 space-y-3">
<Button
onClick={() => {
setMobileMenuOpen(false);
setDemoModalOpen(true);
}}
variant="default"
className="w-full"
>
Try Demo
</Button>
<Button
asChild
variant="outline"
className="w-full"
>
<Link href="/login" onClick={() => setMobileMenuOpen(false)}>
Login
</Link>
</Button>
</div>
</nav>
</SheetContent>
</Sheet>
</div>
</header>
{/* Demo Credentials Modal */}
<DemoCredentialsModal
open={demoModalOpen}
onClose={() => setDemoModalOpen(false)}
/>
</>
);
}

View File

@@ -0,0 +1,150 @@
/**
* Hero Section
* Main hero section with headline, subheadline, CTAs, and gradient background
*/
'use client';
import { useState } from 'react';
import Link from 'next/link';
import { motion } from 'framer-motion';
import { ArrowRight, Github, Play } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { DemoCredentialsModal } from './DemoCredentialsModal';
export function HeroSection() {
const [demoModalOpen, setDemoModalOpen] = useState(false);
return (
<section className="relative overflow-hidden">
{/* Gradient Background */}
<div className="absolute inset-0 -z-10 bg-gradient-to-br from-primary/5 via-background to-background" aria-hidden="true" />
<div
className="absolute inset-0 -z-10 bg-[radial-gradient(circle_at_30%_20%,rgba(var(--primary-rgb,120,119,198),0.1),transparent_50%)]"
aria-hidden="true"
/>
<div
className="absolute inset-0 -z-10 bg-[radial-gradient(circle_at_70%_80%,rgba(var(--primary-rgb,120,119,198),0.05),transparent_50%)]"
aria-hidden="true"
/>
<div className="container mx-auto px-6 py-24 md:py-32 lg:py-40">
<div className="mx-auto max-w-4xl text-center space-y-8">
{/* Badge */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
<div className="inline-flex items-center gap-2 rounded-full border bg-background/50 px-4 py-1.5 text-sm backdrop-blur">
<span className="inline-block h-2 w-2 rounded-full bg-green-500 animate-pulse" aria-hidden="true" />
<span className="font-medium">MIT Licensed</span>
<span className="text-muted-foreground"></span>
<span className="font-medium">97% Test Coverage</span>
<span className="text-muted-foreground"></span>
<span className="font-medium">Production Ready</span>
</div>
</motion.div>
{/* Headline */}
<motion.h1
className="text-4xl font-bold tracking-tight sm:text-5xl md:text-6xl lg:text-7xl"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.1 }}
>
<span className="block">Everything You Need to Build</span>
<span className="block bg-gradient-to-r from-primary via-primary/80 to-primary/60 bg-clip-text text-transparent">
Modern Web Applications
</span>
</motion.h1>
{/* Subheadline */}
<motion.p
className="mx-auto max-w-2xl text-lg md:text-xl text-muted-foreground leading-relaxed"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.2 }}
>
Production-ready FastAPI + Next.js template with authentication, multi-tenancy, and
comprehensive admin panel. Built by developers, for developers.{' '}
<span className="text-foreground font-medium">Start building features on day one.</span>
</motion.p>
{/* CTAs */}
<motion.div
className="flex flex-col sm:flex-row items-center justify-center gap-4"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.3 }}
>
<Button
size="lg"
onClick={() => setDemoModalOpen(true)}
className="gap-2 text-base group"
>
<Play className="h-5 w-5 group-hover:scale-110 transition-transform" aria-hidden="true" />
Try Live Demo
</Button>
<Button
asChild
size="lg"
variant="outline"
className="gap-2 text-base group"
>
<a
href="https://github.com/your-org/fast-next-template"
target="_blank"
rel="noopener noreferrer"
>
<Github className="h-5 w-5" aria-hidden="true" />
View on GitHub
<ArrowRight className="h-4 w-4 group-hover:translate-x-1 transition-transform" aria-hidden="true" />
</a>
</Button>
<Button
asChild
size="lg"
variant="ghost"
className="gap-2 text-base group"
>
<Link href="/dev">
Explore Components
<ArrowRight className="h-4 w-4 group-hover:translate-x-1 transition-transform" aria-hidden="true" />
</Link>
</Button>
</motion.div>
{/* Quick Stats */}
<motion.div
className="flex flex-wrap items-center justify-center gap-6 pt-8 text-sm text-muted-foreground"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5, delay: 0.4 }}
>
<div className="flex items-center gap-2">
<span className="text-2xl font-bold text-foreground">97%</span>
<span>Test Coverage</span>
</div>
<div className="h-4 w-px bg-border" aria-hidden="true" />
<div className="flex items-center gap-2">
<span className="text-2xl font-bold text-foreground">743</span>
<span>Passing Tests</span>
</div>
<div className="h-4 w-px bg-border" aria-hidden="true" />
<div className="flex items-center gap-2">
<span className="text-2xl font-bold text-foreground">0</span>
<span>Flaky Tests</span>
</div>
</motion.div>
</div>
</div>
{/* Demo Credentials Modal */}
<DemoCredentialsModal
open={demoModalOpen}
onClose={() => setDemoModalOpen(false)}
/>
</section>
);
}

View File

@@ -0,0 +1,103 @@
/**
* Philosophy Section
* "For Developers, By Developers" section explaining why this template exists
*/
'use client';
import { motion } from 'framer-motion';
import { X, Check } from 'lucide-react';
const wontFind = [
'Vendor lock-in to specific services',
'Hidden costs or upgrade prompts',
'Poorly documented "magic"',
'Outdated dependencies',
];
const willFind = [
'Production patterns that actually work',
'Comprehensive test coverage (not aspirational)',
'Clear, honest documentation',
'Active development and improvements',
];
export function PhilosophySection() {
return (
<section className="container mx-auto px-6 py-16 md:py-24">
<div className="mx-auto max-w-4xl">
<motion.div
className="text-center mb-12"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-100px' }}
transition={{ duration: 0.6 }}
>
<h2 className="text-3xl md:text-4xl font-bold mb-6">
Why This Template Exists
</h2>
<div className="space-y-4 text-lg text-muted-foreground leading-relaxed">
<p>
We built this template after rebuilding the same authentication, authorization, and
admin infrastructure for the fifth time. Instead of yet another tutorial or boilerplate
generator, we created a complete, tested, documented codebase that you can clone and
customize.
</p>
<p className="text-foreground font-semibold text-xl">
No vendor lock-in. No subscriptions. No license restrictions.
</p>
<p>
Just clean, modern code with patterns that scale. MIT licensed forever.
</p>
</div>
</motion.div>
<div className="grid md:grid-cols-2 gap-8 mt-12">
{/* What You Won't Find */}
<motion.div
className="rounded-lg border bg-card p-6"
initial={{ opacity: 0, x: -20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true, margin: '-100px' }}
transition={{ duration: 0.5 }}
>
<h3 className="text-xl font-semibold mb-4 flex items-center gap-2">
<X className="h-5 w-5 text-red-600" aria-hidden="true" />
What You Won&apos;t Find Here
</h3>
<ul className="space-y-3">
{wontFind.map((item) => (
<li key={item} className="flex items-start gap-3">
<X className="h-5 w-5 text-red-600 flex-shrink-0 mt-0.5" aria-hidden="true" />
<span className="text-muted-foreground">{item}</span>
</li>
))}
</ul>
</motion.div>
{/* What You Will Find */}
<motion.div
className="rounded-lg border bg-card p-6"
initial={{ opacity: 0, x: 20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true, margin: '-100px' }}
transition={{ duration: 0.5 }}
>
<h3 className="text-xl font-semibold mb-4 flex items-center gap-2">
<Check className="h-5 w-5 text-green-600" aria-hidden="true" />
What You Will Find
</h3>
<ul className="space-y-3">
{willFind.map((item) => (
<li key={item} className="flex items-start gap-3">
<Check className="h-5 w-5 text-green-600 flex-shrink-0 mt-0.5" aria-hidden="true" />
<span className="text-muted-foreground">{item}</span>
</li>
))}
</ul>
</motion.div>
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,101 @@
/**
* Quick Start Code Block
* Code snippet showing quick start commands with copy functionality
*/
'use client';
import { useState } from 'react';
import { motion } from 'framer-motion';
import { Check, Copy } from 'lucide-react';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
import { Button } from '@/components/ui/button';
const codeString = `# Clone and start with Docker
git clone https://github.com/your-org/fast-next-template.git
cd fast-next-template
docker-compose up
# Or set up locally
cd backend && python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cd ../frontend && npm install`;
export function QuickStartCode() {
const [copied, setCopied] = useState(false);
const copyToClipboard = async () => {
try {
await navigator.clipboard.writeText(codeString);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error('Failed to copy:', err);
}
};
return (
<section className="container mx-auto px-6 py-16 md:py-24">
<motion.div
className="mx-auto max-w-4xl"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-100px' }}
transition={{ duration: 0.6 }}
>
<div className="text-center mb-8">
<h2 className="text-3xl md:text-4xl font-bold mb-4">5-Minute Setup</h2>
<p className="text-lg text-muted-foreground">
Clone, run, and start building. It&apos;s that simple.
</p>
</div>
<div className="relative rounded-lg border bg-card shadow-lg overflow-hidden">
{/* Header with Copy Button */}
<div className="flex items-center justify-between border-b bg-muted/50 px-4 py-3">
<div className="flex items-center gap-2 text-sm text-muted-foreground font-mono">
<span className="inline-block h-2 w-2 rounded-full bg-green-500" aria-hidden="true" />
<span>bash</span>
</div>
<Button
variant="ghost"
size="sm"
onClick={copyToClipboard}
className="gap-2"
>
{copied ? (
<>
<Check className="h-4 w-4 text-green-600" aria-hidden="true" />
<span>Copied!</span>
</>
) : (
<>
<Copy className="h-4 w-4" aria-hidden="true" />
<span>Copy</span>
</>
)}
</Button>
</div>
{/* Code Block */}
<div className="overflow-x-auto">
<SyntaxHighlighter
language="bash"
style={vscDarkPlus}
customStyle={{
margin: 0,
padding: '1.5rem',
background: 'transparent',
fontSize: '0.875rem',
}}
wrapLines
>
{codeString}
</SyntaxHighlighter>
</div>
</div>
</motion.div>
</section>
);
}

View File

@@ -0,0 +1,134 @@
/**
* Stats Section
* Animated statistics with counters
*/
'use client';
import { useEffect, useState } from 'react';
import { motion, useInView } from 'framer-motion';
import { useRef } from 'react';
import { CheckCircle2, TestTube, Zap, FileCode } from 'lucide-react';
interface Stat {
icon: React.ComponentType<{ className?: string }>;
value: number;
suffix: string;
label: string;
description: string;
}
const stats: Stat[] = [
{
icon: Zap,
value: 97,
suffix: '%',
label: 'Test Coverage',
description: 'Comprehensive testing across backend and frontend',
},
{
icon: TestTube,
value: 743,
suffix: '',
label: 'Passing Tests',
description: 'Backend, frontend unit, and E2E tests',
},
{
icon: CheckCircle2,
value: 0,
suffix: '',
label: 'Flaky Tests',
description: 'Production-stable test suite',
},
{
icon: FileCode,
value: 30,
suffix: '+',
label: 'API Endpoints',
description: 'Fully documented with OpenAPI',
},
];
function AnimatedCounter({ value, suffix }: { value: number; suffix: string }) {
const [count, setCount] = useState(0);
const ref = useRef<HTMLDivElement>(null);
const isInView = useInView(ref, { once: true, margin: '-100px' });
useEffect(() => {
if (!isInView) return;
const duration = 2000; // 2 seconds
const steps = 60;
const increment = value / steps;
let current = 0;
const timer = setInterval(() => {
current += increment;
if (current >= value) {
setCount(value);
clearInterval(timer);
} else {
setCount(Math.floor(current));
}
}, duration / steps);
return () => clearInterval(timer);
}, [isInView, value]);
return (
<div ref={ref} className="text-5xl md:text-6xl font-bold">
{count}
{suffix}
</div>
);
}
export function StatsSection() {
return (
<section className="container mx-auto px-6 py-16 md:py-24">
<motion.div
className="text-center mb-12"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-100px' }}
transition={{ duration: 0.6 }}
>
<h2 className="text-3xl md:text-4xl font-bold mb-4">Built with Quality in Mind</h2>
<p className="text-lg text-muted-foreground max-w-2xl mx-auto">
Not just another template. Comprehensive testing, documentation, and production-ready patterns.
</p>
</motion.div>
<div className="grid grid-cols-2 lg:grid-cols-4 gap-8 max-w-6xl mx-auto">
{stats.map((stat, index) => (
<motion.div
key={stat.label}
className="text-center space-y-4"
initial={{ opacity: 0, scale: 0.9 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true, margin: '-100px' }}
transition={{ duration: 0.5, delay: index * 0.1 }}
>
{/* Icon */}
<div className="flex justify-center">
<div className="inline-flex h-16 w-16 items-center justify-center rounded-full bg-primary/10">
<stat.icon className="h-8 w-8 text-primary" aria-hidden="true" />
</div>
</div>
{/* Animated Counter */}
<div className="bg-gradient-to-br from-primary to-primary/60 bg-clip-text text-transparent">
<AnimatedCounter value={stat.value} suffix={stat.suffix} />
</div>
{/* Label */}
<div>
<div className="font-semibold text-lg mb-1">{stat.label}</div>
<div className="text-sm text-muted-foreground">{stat.description}</div>
</div>
</motion.div>
))}
</div>
</section>
);
}

View File

@@ -0,0 +1,104 @@
/**
* Tech Stack Section
* Displays technology logos/badges with tooltips
*/
'use client';
import { motion } from 'framer-motion';
interface Tech {
name: string;
description: string;
color: string;
}
const technologies: Tech[] = [
{
name: 'FastAPI',
description: 'Async Python web framework, auto-docs, type hints',
color: 'from-teal-500 to-green-600',
},
{
name: 'Next.js 15',
description: 'React 19, App Router, Server Components',
color: 'from-slate-900 to-slate-700',
},
{
name: 'PostgreSQL',
description: 'Reliable, scalable SQL database',
color: 'from-blue-600 to-blue-800',
},
{
name: 'TypeScript',
description: 'End-to-end type safety',
color: 'from-blue-500 to-blue-700',
},
{
name: 'Docker',
description: 'Containerized deployment',
color: 'from-blue-400 to-blue-600',
},
{
name: 'TailwindCSS',
description: 'Utility-first styling with OKLCH colors',
color: 'from-cyan-500 to-blue-500',
},
{
name: 'shadcn/ui',
description: 'Accessible component library (New York variant)',
color: 'from-slate-800 to-slate-600',
},
{
name: 'Playwright',
description: 'Reliable E2E testing (zero flaky tests)',
color: 'from-green-600 to-emerald-700',
},
];
export function TechStackSection() {
return (
<section className="container mx-auto px-6 py-16 md:py-24 bg-muted/30">
<motion.div
className="text-center mb-12"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-100px' }}
transition={{ duration: 0.6 }}
>
<h2 className="text-3xl md:text-4xl font-bold mb-4">
Modern, Type-Safe, Production-Grade Stack
</h2>
<p className="text-lg text-muted-foreground max-w-2xl mx-auto">
Built with the best tools for full-stack development. Async architecture, type safety, and developer experience.
</p>
</motion.div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 max-w-5xl mx-auto">
{technologies.map((tech, index) => (
<motion.div
key={tech.name}
className="group relative"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-100px' }}
transition={{ duration: 0.4, delay: index * 0.05 }}
whileHover={{ scale: 1.05 }}
>
<div className="rounded-lg border bg-card p-6 text-center hover:shadow-lg transition-all">
{/* Tech Badge */}
<div className={`inline-block rounded-full bg-gradient-to-r ${tech.color} px-4 py-2 text-white font-semibold text-sm mb-2`}>
{tech.name}
</div>
{/* Hover Tooltip */}
<div className="opacity-0 group-hover:opacity-100 transition-opacity">
<p className="text-xs text-muted-foreground mt-2">{tech.description}</p>
</div>
</div>
</motion.div>
))}
</div>
</section>
);
}

View File

@@ -0,0 +1,33 @@
/**
* Hook to detect if user prefers reduced motion
* Respects prefers-reduced-motion media query for accessibility
*/
import { useEffect, useState } from 'react';
export function usePrefersReducedMotion(): boolean {
const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);
useEffect(() => {
// Check if window is defined (SSR safety)
if (typeof window === 'undefined') {
return;
}
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
setPrefersReducedMotion(mediaQuery.matches);
// Listen for changes
const handleChange = (event: MediaQueryListEvent) => {
setPrefersReducedMotion(event.matches);
};
mediaQuery.addEventListener('change', handleChange);
return () => {
mediaQuery.removeEventListener('change', handleChange);
};
}, []);
return prefersReducedMotion;
}