create-landing-app 0.1.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.
Files changed (91) hide show
  1. package/dist/index.js +21 -0
  2. package/dist/install.js +18 -0
  3. package/dist/prompts.js +62 -0
  4. package/dist/scaffold.js +159 -0
  5. package/dist/utils/__tests__/merge-json.test.js +144 -0
  6. package/dist/utils/__tests__/replace-tokens.test.js +212 -0
  7. package/dist/utils/copy-dir.js +22 -0
  8. package/dist/utils/merge-json.js +19 -0
  9. package/dist/utils/replace-tokens.js +8 -0
  10. package/package.json +48 -0
  11. package/templates/nextjs/base/.env.example +8 -0
  12. package/templates/nextjs/base/.github/workflows/ci.yml +40 -0
  13. package/templates/nextjs/base/.husky/commit-msg +7 -0
  14. package/templates/nextjs/base/.husky/pre-commit +3 -0
  15. package/templates/nextjs/base/.husky/pre-push +46 -0
  16. package/templates/nextjs/base/.lighthouserc.json +28 -0
  17. package/templates/nextjs/base/.prettierignore +11 -0
  18. package/templates/nextjs/base/.prettierrc.json +10 -0
  19. package/templates/nextjs/base/Dockerfile +42 -0
  20. package/templates/nextjs/base/app/globals.css +82 -0
  21. package/templates/nextjs/base/app/layout.tsx +32 -0
  22. package/templates/nextjs/base/app/not-found.tsx +13 -0
  23. package/templates/nextjs/base/app/page.tsx +15 -0
  24. package/templates/nextjs/base/app/robots.ts +9 -0
  25. package/templates/nextjs/base/commitlint.config.mjs +32 -0
  26. package/templates/nextjs/base/components/navs/navbar-mobile.tsx +39 -0
  27. package/templates/nextjs/base/components/navs/navbar.tsx +39 -0
  28. package/templates/nextjs/base/components/providers.tsx +12 -0
  29. package/templates/nextjs/base/components/sections/features-section.tsx +78 -0
  30. package/templates/nextjs/base/components/sections/footer-section.tsx +98 -0
  31. package/templates/nextjs/base/components/sections/hero-section.tsx +74 -0
  32. package/templates/nextjs/base/components/ui/accordion.tsx +47 -0
  33. package/templates/nextjs/base/components/ui/button.tsx +44 -0
  34. package/templates/nextjs/base/components/ui/dialog.tsx +61 -0
  35. package/templates/nextjs/base/components/ui/dropdown-menu.tsx +55 -0
  36. package/templates/nextjs/base/components/ui/sonner.tsx +6 -0
  37. package/templates/nextjs/base/components.json +19 -0
  38. package/templates/nextjs/base/constants/common.ts +15 -0
  39. package/templates/nextjs/base/eslint.config.mjs +25 -0
  40. package/templates/nextjs/base/lib/metadata.ts +36 -0
  41. package/templates/nextjs/base/lib/utils.ts +7 -0
  42. package/templates/nextjs/base/next.config.ts +33 -0
  43. package/templates/nextjs/base/package.json +61 -0
  44. package/templates/nextjs/base/postcss.config.mjs +7 -0
  45. package/templates/nextjs/base/scripts/build-and-scan.sh +127 -0
  46. package/templates/nextjs/base/scripts/lighthouse-check.sh +86 -0
  47. package/templates/nextjs/base/styles/theme.css +63 -0
  48. package/templates/nextjs/base/tsconfig.json +21 -0
  49. package/templates/nextjs/base/types/index.ts +16 -0
  50. package/templates/nextjs/optional/docker/files/.dockerignore +6 -0
  51. package/templates/nextjs/optional/docker/files/Dockerfile +36 -0
  52. package/templates/nextjs/optional/docker/files/docker-compose.yml +9 -0
  53. package/templates/nextjs/optional/i18n-dict/files/app/[lang]/layout.tsx +19 -0
  54. package/templates/nextjs/optional/i18n-dict/files/app/[lang]/page.tsx +15 -0
  55. package/templates/nextjs/optional/i18n-dict/files/components/navs/language-switcher.tsx +39 -0
  56. package/templates/nextjs/optional/i18n-dict/files/components/navs/navbar-mobile.tsx +41 -0
  57. package/templates/nextjs/optional/i18n-dict/files/components/navs/navbar.tsx +41 -0
  58. package/templates/nextjs/optional/i18n-dict/files/components/providers.tsx +16 -0
  59. package/templates/nextjs/optional/i18n-dict/files/components/sections/features-section.tsx +80 -0
  60. package/templates/nextjs/optional/i18n-dict/files/components/sections/footer-section.tsx +98 -0
  61. package/templates/nextjs/optional/i18n-dict/files/dictionaries/en.json +21 -0
  62. package/templates/nextjs/optional/i18n-dict/files/dictionaries/vi.json +21 -0
  63. package/templates/nextjs/optional/i18n-dict/files/get-dictionary.ts +10 -0
  64. package/templates/nextjs/optional/i18n-dict/files/i18n-config.ts +6 -0
  65. package/templates/nextjs/optional/i18n-dict/files/lib/dict-context.tsx +23 -0
  66. package/templates/nextjs/optional/i18n-dict/files/middleware.ts +31 -0
  67. package/templates/nextjs/optional/i18n-dict/pkg.json +9 -0
  68. package/templates/nextjs/optional/sections/about/files/components/sections/about-section.tsx +36 -0
  69. package/templates/nextjs/optional/sections/about/inject/app__[lang]__page.tsx +5 -0
  70. package/templates/nextjs/optional/sections/about/inject/app__page.tsx +5 -0
  71. package/templates/nextjs/optional/sections/about/inject/constants__common.ts +2 -0
  72. package/templates/nextjs/optional/sections/blog/files/components/sections/blog-section.tsx +191 -0
  73. package/templates/nextjs/optional/sections/blog/inject/app__[lang]__page.tsx +5 -0
  74. package/templates/nextjs/optional/sections/blog/inject/app__page.tsx +5 -0
  75. package/templates/nextjs/optional/sections/blog/inject/constants__common.ts +2 -0
  76. package/templates/nextjs/optional/sections/contact/files/components/sections/contact-section.tsx +79 -0
  77. package/templates/nextjs/optional/sections/contact/inject/app__[lang]__page.tsx +5 -0
  78. package/templates/nextjs/optional/sections/contact/inject/app__page.tsx +5 -0
  79. package/templates/nextjs/optional/sections/contact/inject/constants__common.ts +2 -0
  80. package/templates/nextjs/optional/tanstack-query/files/lib/custom-fetch.ts +9 -0
  81. package/templates/nextjs/optional/tanstack-query/files/lib/query-client.ts +21 -0
  82. package/templates/nextjs/optional/tanstack-query/inject/components__providers.tsx +9 -0
  83. package/templates/nextjs/optional/tanstack-query/pkg.json +5 -0
  84. package/templates/nextjs/optional/zustand/files/store/ui-store.ts +16 -0
  85. package/templates/nextjs/optional/zustand/inject/components__providers.tsx +3 -0
  86. package/templates/nextjs/optional/zustand/pkg.json +5 -0
  87. package/templates/nextjs/themes/dark.css +36 -0
  88. package/templates/nextjs/themes/forest.css +58 -0
  89. package/templates/nextjs/themes/ocean.css +58 -0
  90. package/templates/nextjs/themes/pila.css +75 -0
  91. package/templates/nextjs/themes/purple.css +58 -0
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "create-landing-app",
3
+ "version": "0.1.0",
4
+ "description": "Create a production-ready Next.js landing page with one command",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-landing-app": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "templates"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc -p tsconfig.json",
15
+ "dev": "tsc -p tsconfig.json --watch",
16
+ "prepublishOnly": "yarn build && rm -rf ./templates && cp -r ../../templates ./templates",
17
+ "test": "node --experimental-vm-modules node_modules/.bin/jest"
18
+ },
19
+ "keywords": [
20
+ "nextjs",
21
+ "landing-page",
22
+ "boilerplate",
23
+ "scaffold",
24
+ "create-app",
25
+ "tailwindcss",
26
+ "typescript",
27
+ "starter"
28
+ ],
29
+ "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/LamHana/landing-page-boilerplate"
33
+ },
34
+ "homepage": "https://github.com/LamHana/landing-page-boilerplate#readme",
35
+ "dependencies": {
36
+ "@clack/prompts": "^0.9.0",
37
+ "kolorist": "^1.8.0",
38
+ "execa": "^9.0.0"
39
+ },
40
+ "devDependencies": {
41
+ "@types/jest": "^29.5.0",
42
+ "@types/node": "^20.0.0",
43
+ "jest": "^29.5.0",
44
+ "ts-jest": "^29.1.0",
45
+ "typescript": "^5.0.0"
46
+ },
47
+ "engines": { "node": ">=18.0.0" }
48
+ }
@@ -0,0 +1,8 @@
1
+ # Site
2
+ NEXT_PUBLIC_SITE_URL=https://yourdomain.com
3
+
4
+ # Analytics (optional)
5
+ NEXT_PUBLIC_GOOGLE_ANALYTICS_ID=
6
+
7
+ # S3 / Storage (optional)
8
+ NEXT_PUBLIC_S3_DOMAIN=
@@ -0,0 +1,40 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, dev]
6
+ pull_request:
7
+ branches: [main, dev]
8
+
9
+ jobs:
10
+ lint:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: actions/setup-node@v4
15
+ with: { node-version: 20 }
16
+ - run: yarn install --frozen-lockfile
17
+ - run: yarn format:check
18
+ - run: yarn lint
19
+
20
+ lighthouse:
21
+ runs-on: ubuntu-latest
22
+ needs: lint
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+ - uses: actions/setup-node@v4
26
+ with: { node-version: 20 }
27
+ - run: yarn install --frozen-lockfile
28
+ - run: yarn build
29
+ - run: yarn lighthouse
30
+ env:
31
+ LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
32
+
33
+ security-scan:
34
+ runs-on: ubuntu-latest
35
+ needs: lint
36
+ steps:
37
+ - uses: actions/checkout@v4
38
+ - run: FAIL_ON_VULN=true bash ./scripts/build-and-scan.sh
39
+ env:
40
+ APP_NAME: __PROJECT_NAME__
@@ -0,0 +1,7 @@
1
+ #!/bin/sh
2
+ # commit-msg: enforce conventional commit format
3
+ # Examples of valid commits:
4
+ # feat(navbar): add mobile menu
5
+ # fix(hero): correct CTA button color
6
+ # chore: update dependencies
7
+ npx --no -- commitlint --edit "$1"
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+ # pre-commit: format and lint staged files only (fast)
3
+ npx lint-staged --config package.json
@@ -0,0 +1,46 @@
1
+ #!/bin/sh
2
+ set -e
3
+
4
+ RED='\033[0;31m'
5
+ GREEN='\033[0;32m'
6
+ YELLOW='\033[1;33m'
7
+ NC='\033[0m'
8
+
9
+ echo ""
10
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
11
+ echo " Pre-push quality checks"
12
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
13
+ echo ""
14
+
15
+ # ── Step 1: Next.js build (always runs) ──────────────────
16
+ echo "${GREEN}[1/3] Building Next.js...${NC}"
17
+ npm run build
18
+ echo "${GREEN}✅ Build passed${NC}"
19
+ echo ""
20
+
21
+ # ── Step 2: Lighthouse CI (skip with SKIP_LIGHTHOUSE=true) ──
22
+ if [ "${SKIP_LIGHTHOUSE}" = "true" ]; then
23
+ echo "${YELLOW}[2/3] Lighthouse skipped (SKIP_LIGHTHOUSE=true)${NC}"
24
+ else
25
+ echo "${GREEN}[2/3] Running Lighthouse CI...${NC}"
26
+ bash ./scripts/lighthouse-check.sh
27
+ echo "${GREEN}✅ Lighthouse passed${NC}"
28
+ fi
29
+ echo ""
30
+
31
+ # ── Step 3: Docker build + security scan (opt-in with RUN_SCAN=true) ──
32
+ if [ "${RUN_SCAN}" = "true" ]; then
33
+ if ! command -v docker > /dev/null 2>&1; then
34
+ echo "${YELLOW}[3/3] Security scan skipped (Docker not found)${NC}"
35
+ else
36
+ echo "${GREEN}[3/3] Running Docker build + security scan...${NC}"
37
+ FAIL_ON_VULN=false bash ./scripts/build-and-scan.sh
38
+ echo "${GREEN}✅ Scan completed${NC}"
39
+ fi
40
+ else
41
+ echo "${YELLOW}[3/3] Security scan skipped (set RUN_SCAN=true to enable)${NC}"
42
+ fi
43
+
44
+ echo ""
45
+ echo "${GREEN}✅ All pre-push checks passed. Pushing...${NC}"
46
+ echo ""
@@ -0,0 +1,28 @@
1
+ {
2
+ "ci": {
3
+ "collect": {
4
+ "url": ["http://localhost:3000"],
5
+ "numberOfRuns": 1,
6
+ "settings": {
7
+ "preset": "desktop",
8
+ "throttlingMethod": "simulate"
9
+ }
10
+ },
11
+ "assert": {
12
+ "assertions": {
13
+ "categories:performance": ["warn", { "minScore": 0.8 }],
14
+ "categories:accessibility": ["error", { "minScore": 0.9 }],
15
+ "categories:best-practices": ["warn", { "minScore": 0.9 }],
16
+ "categories:seo": ["warn", { "minScore": 0.9 }],
17
+ "first-contentful-paint": ["warn", { "maxNumericValue": 2000 }],
18
+ "largest-contentful-paint": ["error", { "maxNumericValue": 3000 }],
19
+ "total-blocking-time": ["warn", { "maxNumericValue": 300 }],
20
+ "cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
21
+ "interactive": ["warn", { "maxNumericValue": 4000 }]
22
+ }
23
+ },
24
+ "upload": {
25
+ "target": "temporary-public-storage"
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,11 @@
1
+ node_modules
2
+ .next
3
+ out
4
+ dist
5
+ build
6
+ *.min.js
7
+ *.min.css
8
+ pnpm-lock.yaml
9
+ yarn.lock
10
+ bun.lockb
11
+ package-lock.json
@@ -0,0 +1,10 @@
1
+ {
2
+ "semi": true,
3
+ "singleQuote": false,
4
+ "tabWidth": 2,
5
+ "trailingComma": "es5",
6
+ "printWidth": 100,
7
+ "bracketSameLine": false,
8
+ "plugins": ["prettier-plugin-tailwindcss"],
9
+ "tailwindFunctions": ["cn", "cva"]
10
+ }
@@ -0,0 +1,42 @@
1
+ # Stage 1: Install dependencies
2
+ FROM node:20-alpine AS deps
3
+ RUN corepack enable
4
+ WORKDIR /app
5
+ COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* bun.lock* bun.lockb* ./
6
+ RUN \
7
+ if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
8
+ elif [ -f package-lock.json ]; then npm ci; \
9
+ elif [ -f pnpm-lock.yaml ]; then pnpm i --frozen-lockfile; \
10
+ elif [ -f bun.lock ] || [ -f bun.lockb ]; then npm i -g bun && bun install --frozen-lockfile; \
11
+ else echo "No lockfile found." && exit 1; \
12
+ fi
13
+
14
+ # Stage 2: Build
15
+ FROM node:20-alpine AS builder
16
+ WORKDIR /app
17
+ COPY --from=deps /app/node_modules ./node_modules
18
+ COPY . .
19
+ RUN \
20
+ if [ -f yarn.lock ]; then yarn build; \
21
+ elif [ -f package-lock.json ]; then npm run build; \
22
+ elif [ -f pnpm-lock.yaml ]; then pnpm run build; \
23
+ elif [ -f bun.lock ] || [ -f bun.lockb ]; then npm i -g bun && bun run build; \
24
+ else npm run build; \
25
+ fi
26
+
27
+ # Stage 3: Production runner (standalone output)
28
+ FROM node:20-alpine AS runner
29
+ WORKDIR /app
30
+ ENV NODE_ENV=production
31
+ ENV PORT=3000
32
+
33
+ # Run as non-root user for security (OWASP A05)
34
+ RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs
35
+ USER nextjs
36
+
37
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
38
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
39
+ COPY --from=builder --chown=nextjs:nodejs /app/public ./public
40
+
41
+ EXPOSE 3000
42
+ CMD ["node", "server.js"]
@@ -0,0 +1,82 @@
1
+ @import "tailwindcss";
2
+ @import "tw-animate-css";
3
+ @import "../styles/theme.css"; /* theme variables — edit this file to rebrand */
4
+
5
+ @custom-variant dark (&:is(.dark *));
6
+
7
+ /* Map Tailwind v4 color tokens to CSS variables */
8
+ @theme inline {
9
+ --color-background: var(--background);
10
+ --color-foreground: var(--foreground);
11
+ --color-primary: var(--primary);
12
+ --color-primary-foreground: var(--primary-foreground);
13
+ --color-secondary: var(--secondary);
14
+ --color-secondary-foreground: var(--secondary-foreground);
15
+ --color-muted: var(--muted);
16
+ --color-muted-foreground: var(--muted-foreground);
17
+ --color-accent: var(--accent);
18
+ --color-accent-foreground: var(--accent-foreground);
19
+ --color-destructive: var(--destructive);
20
+ --color-border: var(--border);
21
+ --color-input: var(--input);
22
+ --color-ring: var(--ring);
23
+ --color-card: var(--card);
24
+ --color-card-foreground: var(--card-foreground);
25
+ --color-popover: var(--popover);
26
+ --color-popover-foreground: var(--popover-foreground);
27
+ --radius-sm: calc(var(--radius) - 4px);
28
+ --radius-md: calc(var(--radius) - 2px);
29
+ --radius-lg: var(--radius);
30
+ --radius-xl: calc(var(--radius) + 4px);
31
+ --breakpoint-tablet: 1024px;
32
+ --breakpoint-mobile: 768px;
33
+ }
34
+
35
+ @layer base {
36
+ * { @apply border-border outline-ring/50; }
37
+ body { @apply bg-background text-foreground; }
38
+ }
39
+
40
+ /* Hide scrollbar */
41
+ ::-webkit-scrollbar { display: none; }
42
+ html { -ms-overflow-style: none; scrollbar-width: none; }
43
+ body { overflow-x: hidden; }
44
+
45
+ /* Layout container — use this on all sections for consistent max-width */
46
+ .content-container {
47
+ max-width: 1440px;
48
+ margin-left: auto;
49
+ margin-right: auto;
50
+ padding-left: 20px;
51
+ padding-right: 20px;
52
+ }
53
+ @media (max-width: 1024px) {
54
+ .content-container { padding-left: 40px !important; padding-right: 40px !important; }
55
+ }
56
+ @media (max-width: 768px) {
57
+ .content-container { padding-left: 20px !important; padding-right: 20px !important; }
58
+ }
59
+
60
+ /* Brand utilities — sourced from theme.css vars */
61
+ .btn-primary {
62
+ background-color: var(--brand-action);
63
+ border-radius: 8px;
64
+ box-shadow: 0px 1px 2px rgba(10, 13, 18, 0.05);
65
+ }
66
+ .btn-outline {
67
+ border: 1px solid var(--brand-border);
68
+ background: var(--background);
69
+ border-radius: var(--radius-md);
70
+ }
71
+ .bg-footer {
72
+ background: linear-gradient(279deg, var(--brand-footer-from) 1.61%, var(--brand-footer-to) 100.79%);
73
+ }
74
+ .text-gradient-brand {
75
+ background: linear-gradient(90deg, var(--brand-gradient-from), var(--brand-gradient-to));
76
+ background-clip: text;
77
+ -webkit-background-clip: text;
78
+ -webkit-text-fill-color: transparent;
79
+ }
80
+ .header-shadow {
81
+ box-shadow: 0 0px 24px 0px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
82
+ }
@@ -0,0 +1,32 @@
1
+ import type { Metadata } from "next";
2
+ import { Inter } from "next/font/google";
3
+ import { ThemeProvider } from "next-themes";
4
+ import { Toaster } from "@/components/ui/sonner";
5
+ import Providers from "@/components/providers";
6
+ import { createMetadata } from "@/lib/metadata";
7
+ import "./globals.css";
8
+ // __PROVIDERS_IMPORT__
9
+
10
+ const inter = Inter({ variable: "--font-inter", subsets: ["latin"] });
11
+
12
+ export const metadata: Metadata = createMetadata({
13
+ title: "__PROJECT_NAME__",
14
+ description: "A modern landing page",
15
+ });
16
+
17
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
18
+ return (
19
+ <html lang="en" suppressHydrationWarning>
20
+ <body className={`${inter.variable} font-sans antialiased`}>
21
+ <ThemeProvider attribute="class" defaultTheme="light" enableSystem>
22
+ <Providers>
23
+ {/* __PROVIDERS_WRAP_START__ */}
24
+ {children}
25
+ {/* __PROVIDERS_WRAP_END__ */}
26
+ </Providers>
27
+ </ThemeProvider>
28
+ <Toaster />
29
+ </body>
30
+ </html>
31
+ );
32
+ }
@@ -0,0 +1,13 @@
1
+ import Link from "next/link";
2
+
3
+ export default function NotFound() {
4
+ return (
5
+ <div className="flex min-h-screen flex-col items-center justify-center gap-4">
6
+ <h1 className="text-6xl font-bold text-primary">404</h1>
7
+ <p className="text-muted-foreground">Page not found</p>
8
+ <Link href="/" className="btn-primary px-6 py-2 text-white rounded-lg">
9
+ Back to home
10
+ </Link>
11
+ </div>
12
+ );
13
+ }
@@ -0,0 +1,15 @@
1
+ import HeroSection from "@/components/sections/hero-section";
2
+ import FeaturesSection from "@/components/sections/features-section";
3
+ import FooterSection from "@/components/sections/footer-section";
4
+ // __PAGE_IMPORTS__
5
+
6
+ export default function Home() {
7
+ return (
8
+ <main>
9
+ <HeroSection />
10
+ <FeaturesSection />
11
+ {/* __PAGE_SECTIONS__ */}
12
+ <FooterSection />
13
+ </main>
14
+ );
15
+ }
@@ -0,0 +1,9 @@
1
+ import type { MetadataRoute } from "next";
2
+
3
+ export default function robots(): MetadataRoute.Robots {
4
+ const baseUrl = process.env.NEXT_PUBLIC_SITE_URL ?? "https://yourdomain.com";
5
+ return {
6
+ rules: { userAgent: "*", allow: "/" },
7
+ sitemap: `${baseUrl}/sitemap.xml`,
8
+ };
9
+ }
@@ -0,0 +1,32 @@
1
+ /** @type {import('@commitlint/types').UserConfig} */
2
+ const config = {
3
+ extends: ["@commitlint/config-conventional"],
4
+ rules: {
5
+ // Allowed commit types
6
+ "type-enum": [
7
+ 2,
8
+ "always",
9
+ [
10
+ "feat", // new feature
11
+ "fix", // bug fix
12
+ "docs", // documentation only
13
+ "style", // formatting, whitespace
14
+ "refactor", // code change that's not feat or fix
15
+ "perf", // performance improvement
16
+ "test", // tests
17
+ "build", // build system, deps
18
+ "ci", // CI/CD
19
+ "chore", // other changes
20
+ "revert", // revert a commit
21
+ ],
22
+ ],
23
+ // Subject line max length
24
+ "subject-max-length": [2, "always", 100],
25
+ // No period at end of subject
26
+ "subject-full-stop": [2, "never", "."],
27
+ // Subject must start lowercase
28
+ "subject-case": [2, "never", ["sentence-case", "start-case", "pascal-case", "upper-case"]],
29
+ },
30
+ };
31
+
32
+ export default config;
@@ -0,0 +1,39 @@
1
+ "use client";
2
+ import { useState } from "react";
3
+ import Link from "next/link";
4
+ import { Menu, X } from "lucide-react";
5
+ import { NAV_LINKS } from "@/constants/common";
6
+ import { Button } from "@/components/ui/button";
7
+ import { cn } from "@/lib/utils";
8
+
9
+ export default function NavbarMobile() {
10
+ const [open, setOpen] = useState(false);
11
+
12
+ return (
13
+ <div className="md:hidden">
14
+ <button onClick={() => setOpen(!open)} aria-label="Toggle menu">
15
+ {open ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
16
+ </button>
17
+
18
+ {/* Slide-down mobile menu */}
19
+ <div
20
+ className={cn(
21
+ "absolute inset-x-0 top-16 flex flex-col gap-4 border-b bg-background p-6 transition-all duration-200",
22
+ open ? "visible opacity-100" : "invisible pointer-events-none opacity-0"
23
+ )}
24
+ >
25
+ {NAV_LINKS.map((link) => (
26
+ <Link
27
+ key={link.href}
28
+ href={link.href}
29
+ onClick={() => setOpen(false)}
30
+ className="text-sm font-medium"
31
+ >
32
+ {link.label}
33
+ </Link>
34
+ ))}
35
+ <Button className="w-full">Get started</Button>
36
+ </div>
37
+ </div>
38
+ );
39
+ }
@@ -0,0 +1,39 @@
1
+ import Link from "next/link";
2
+ import { NAV_LINKS, SITE_NAME } from "@/constants/common";
3
+ import NavbarMobile from "./navbar-mobile";
4
+ import { Button } from "@/components/ui/button";
5
+ // __NAVBAR_IMPORT__
6
+
7
+ // Server component — no animation needed at nav level
8
+ export default function Navbar() {
9
+ return (
10
+ <header className="sticky top-0 z-50 w-full border-b border-border/40 bg-background/80 backdrop-blur-sm header-shadow">
11
+ <div className="content-container flex h-16 items-center justify-between">
12
+ {/* Logo */}
13
+ <Link href="/" className="flex items-center gap-2 text-xl font-bold text-primary">
14
+ {SITE_NAME}
15
+ </Link>
16
+
17
+ {/* Desktop nav */}
18
+ <nav className="hidden items-center gap-6 md:flex">
19
+ {NAV_LINKS.map((link) => (
20
+ <Link
21
+ key={link.href}
22
+ href={link.href}
23
+ className="text-sm font-medium text-muted-foreground transition-colors hover:text-foreground"
24
+ >
25
+ {link.label}
26
+ </Link>
27
+ ))}
28
+ </nav>
29
+
30
+ {/* CTA + mobile toggle */}
31
+ <div className="flex items-center gap-3">
32
+ {/* __NAVBAR_EXTRAS__ */}
33
+ <Button className="hidden md:flex">Get started</Button>
34
+ <NavbarMobile />
35
+ </div>
36
+ </div>
37
+ </header>
38
+ );
39
+ }
@@ -0,0 +1,12 @@
1
+ "use client";
2
+ // __PROVIDERS_IMPORT__
3
+
4
+ export default function Providers({ children }: { children: React.ReactNode }) {
5
+ return (
6
+ <>
7
+ {/* __PROVIDERS_WRAP_START__ */}
8
+ {children}
9
+ {/* __PROVIDERS_WRAP_END__ */}
10
+ </>
11
+ );
12
+ }
@@ -0,0 +1,78 @@
1
+ "use client";
2
+ import { motion } from "motion/react";
3
+ import { Zap, Shield, Palette, Globe, Code, Rocket } from "lucide-react";
4
+
5
+ const FEATURES = [
6
+ {
7
+ icon: Zap,
8
+ title: "Lightning fast",
9
+ description: "Built on Next.js 15 with Turbopack. Dev server starts in milliseconds.",
10
+ },
11
+ {
12
+ icon: Shield,
13
+ title: "Secure by default",
14
+ description: "Best-practice security headers, no inline scripts, CSP-ready.",
15
+ },
16
+ {
17
+ icon: Palette,
18
+ title: "Fully themeable",
19
+ description: "5 beautiful presets. One CSS file to rebrand completely.",
20
+ },
21
+ {
22
+ icon: Globe,
23
+ title: "i18n ready",
24
+ description: "Optional dictionary-based translation. Zero extra dependencies.",
25
+ },
26
+ {
27
+ icon: Code,
28
+ title: "Clean code",
29
+ description: "TypeScript, ESLint, component-first architecture. Easy to extend.",
30
+ },
31
+ {
32
+ icon: Rocket,
33
+ title: "Deploy anywhere",
34
+ description: "Docker-ready standalone output. Ships to any VPS in minutes.",
35
+ },
36
+ ];
37
+
38
+ export default function FeaturesSection() {
39
+ return (
40
+ <section id="features" className="bg-secondary/30 py-24">
41
+ <div className="content-container">
42
+ {/* Header */}
43
+ <motion.div
44
+ initial={{ opacity: 0, y: 30 }}
45
+ whileInView={{ opacity: 1, y: 0 }}
46
+ viewport={{ once: true }}
47
+ transition={{ duration: 0.6 }}
48
+ className="mb-16 space-y-4 text-center"
49
+ >
50
+ <h2 className="text-4xl font-bold">Everything you need</h2>
51
+ <p className="mx-auto max-w-2xl text-lg text-muted-foreground">
52
+ Built with the best tools in the ecosystem. No bloat, just what matters.
53
+ </p>
54
+ </motion.div>
55
+
56
+ {/* Feature grid */}
57
+ <div className="grid gap-8 sm:grid-cols-2 lg:grid-cols-3">
58
+ {FEATURES.map((feature, i) => (
59
+ <motion.div
60
+ key={feature.title}
61
+ initial={{ opacity: 0, y: 30 }}
62
+ whileInView={{ opacity: 1, y: 0 }}
63
+ viewport={{ once: true }}
64
+ transition={{ duration: 0.5, delay: i * 0.08 }}
65
+ className="group rounded-2xl border border-border bg-background p-6 transition-all duration-300 hover:border-primary/40 hover:shadow-md"
66
+ >
67
+ <div className="mb-4 flex h-12 w-12 items-center justify-center rounded-xl bg-accent transition-colors group-hover:bg-primary/10">
68
+ <feature.icon className="h-6 w-6 text-primary" />
69
+ </div>
70
+ <h3 className="mb-2 text-lg font-semibold">{feature.title}</h3>
71
+ <p className="text-sm leading-relaxed text-muted-foreground">{feature.description}</p>
72
+ </motion.div>
73
+ ))}
74
+ </div>
75
+ </div>
76
+ </section>
77
+ );
78
+ }