create-bw-app 0.3.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 (41) hide show
  1. package/README.md +34 -0
  2. package/bin/create-bw-app.mjs +9 -0
  3. package/package.json +27 -0
  4. package/src/cli.mjs +78 -0
  5. package/src/constants.mjs +114 -0
  6. package/src/generator.mjs +821 -0
  7. package/template/base/app/bootstrap/page.tsx +75 -0
  8. package/template/base/app/globals.css +545 -0
  9. package/template/base/app/layout.tsx +16 -0
  10. package/template/base/app/page.tsx +144 -0
  11. package/template/base/app/playground/auth/auth-playground.tsx +112 -0
  12. package/template/base/app/playground/auth/page.tsx +5 -0
  13. package/template/base/app/playground/layout.tsx +41 -0
  14. package/template/base/app/preview/app-shell/page.tsx +11 -0
  15. package/template/base/app/preview/app-shell-preview.tsx +185 -0
  16. package/template/base/config/bootstrap.ts +125 -0
  17. package/template/base/config/brand.ts +21 -0
  18. package/template/base/config/client.ts +18 -0
  19. package/template/base/config/env.ts +64 -0
  20. package/template/base/config/modules.ts +62 -0
  21. package/template/base/next-env.d.ts +6 -0
  22. package/template/base/public/brand/logo-dark.svg +7 -0
  23. package/template/base/public/brand/logo-light.svg +7 -0
  24. package/template/base/public/brand/logo-mark.svg +5 -0
  25. package/template/base/tsconfig.json +36 -0
  26. package/template/modules/admin/app/api/admin/users/roles/route.ts +6 -0
  27. package/template/modules/admin/app/api/admin/users/route.ts +6 -0
  28. package/template/modules/admin/app/playground/admin/page.tsx +102 -0
  29. package/template/modules/crm/app/playground/crm/page.tsx +103 -0
  30. package/template/modules/projects/app/playground/projects/page.tsx +93 -0
  31. package/template/site/base/app/globals.css +68 -0
  32. package/template/site/base/app/layout.tsx +17 -0
  33. package/template/site/base/app/page.tsx +165 -0
  34. package/template/site/base/components/ui/badge.tsx +28 -0
  35. package/template/site/base/components/ui/button.tsx +52 -0
  36. package/template/site/base/components/ui/card.tsx +28 -0
  37. package/template/site/base/components.json +17 -0
  38. package/template/site/base/lib/utils.ts +6 -0
  39. package/template/site/base/next-env.d.ts +6 -0
  40. package/template/site/base/postcss.config.mjs +7 -0
  41. package/template/site/base/tsconfig.json +23 -0
@@ -0,0 +1,64 @@
1
+ export type StarterEnvScope = "public" | "server";
2
+
3
+ export type StarterEnvRequirement = {
4
+ key: string;
5
+ scope: StarterEnvScope;
6
+ description: string;
7
+ requiredFor: string[];
8
+ };
9
+
10
+ export type StarterEnvStatus = StarterEnvRequirement & {
11
+ present: boolean;
12
+ };
13
+
14
+ export const starterEnvRequirements: StarterEnvRequirement[] = [
15
+ {
16
+ key: "NEXT_PUBLIC_APP_URL",
17
+ scope: "public",
18
+ description: "Canonical base URL used by auth callbacks and platform links.",
19
+ requiredFor: ["core-auth"],
20
+ },
21
+ {
22
+ key: "NEXT_PUBLIC_SUPABASE_URL",
23
+ scope: "public",
24
+ description: "Supabase project URL for client-side and server-side API access.",
25
+ requiredFor: ["crm", "projects", "admin"],
26
+ },
27
+ {
28
+ key: "NEXT_PUBLIC_SUPABASE_ANON_KEY",
29
+ scope: "public",
30
+ description: "Supabase anonymous key for browser auth and RPC calls.",
31
+ requiredFor: ["crm", "projects", "admin"],
32
+ },
33
+ {
34
+ key: "SUPABASE_SERVICE_ROLE_KEY",
35
+ scope: "server",
36
+ description: "Server role key for privileged admin, CRM, and project actions.",
37
+ requiredFor: ["crm", "projects", "admin"],
38
+ },
39
+ {
40
+ key: "RESEND_API_KEY",
41
+ scope: "server",
42
+ description: "Email delivery key for auth and marketing-related server flows.",
43
+ requiredFor: ["core-auth"],
44
+ },
45
+ ];
46
+
47
+ export function getStarterEnvStatus() {
48
+ return starterEnvRequirements.map(
49
+ (requirement) =>
50
+ ({
51
+ ...requirement,
52
+ present: typeof process.env[requirement.key] === "string" && process.env[requirement.key]!.trim().length > 0,
53
+ }) satisfies StarterEnvStatus,
54
+ );
55
+ }
56
+
57
+ export function isStarterEnvReady(requiredModules: string[]) {
58
+ const relevant = getStarterEnvStatus().filter((item) => item.requiredFor.some((moduleKey) => requiredModules.includes(moduleKey)));
59
+ return {
60
+ allReady: relevant.every((item) => item.present),
61
+ items: relevant,
62
+ missing: relevant.filter((item) => !item.present),
63
+ };
64
+ }
@@ -0,0 +1,62 @@
1
+ export type StarterModuleKey = "core-auth" | "crm" | "projects" | "admin";
2
+
3
+ export type StarterModuleConfig = {
4
+ key: StarterModuleKey;
5
+ label: string;
6
+ description: string;
7
+ enabled: boolean;
8
+ packageName: string;
9
+ playgroundHref?: string;
10
+ placement: "core" | "primary" | "admin";
11
+ };
12
+
13
+ function envFlag(value: string | undefined, defaultValue: boolean) {
14
+ if (typeof value !== "string") return defaultValue;
15
+ const normalized = value.trim().toLowerCase();
16
+ if (["1", "true", "yes", "on"].includes(normalized)) return true;
17
+ if (["0", "false", "no", "off"].includes(normalized)) return false;
18
+ return defaultValue;
19
+ }
20
+
21
+ export const starterModuleConfig: StarterModuleConfig[] = [
22
+ {
23
+ key: "core-auth",
24
+ label: "Core Auth",
25
+ description: "Login, reset-password, callback URLs, and shared auth validation utilities.",
26
+ enabled: true,
27
+ packageName: "@brightweblabs/core-auth",
28
+ playgroundHref: "/playground/auth",
29
+ placement: "core",
30
+ },
31
+ {
32
+ key: "crm",
33
+ label: "CRM",
34
+ description: "Contacts, marketing audience, and CRM server/data layer.",
35
+ enabled: envFlag(process.env.NEXT_PUBLIC_ENABLE_CRM, true),
36
+ packageName: "@brightweblabs/module-crm",
37
+ playgroundHref: "/playground/crm",
38
+ placement: "primary",
39
+ },
40
+ {
41
+ key: "projects",
42
+ label: "Projects",
43
+ description: "Project portfolio, detail routes, and work-management server logic.",
44
+ enabled: envFlag(process.env.NEXT_PUBLIC_ENABLE_PROJECTS, true),
45
+ packageName: "@brightweblabs/module-projects",
46
+ playgroundHref: "/playground/projects",
47
+ placement: "primary",
48
+ },
49
+ {
50
+ key: "admin",
51
+ label: "Admin",
52
+ description: "User role governance, admin tools, and access-control surfaces.",
53
+ enabled: envFlag(process.env.NEXT_PUBLIC_ENABLE_ADMIN, true),
54
+ packageName: "@brightweblabs/module-admin",
55
+ playgroundHref: "/playground/admin",
56
+ placement: "admin",
57
+ },
58
+ ];
59
+
60
+ export function getEnabledStarterModules() {
61
+ return starterModuleConfig.filter((moduleConfig) => moduleConfig.enabled);
62
+ }
@@ -0,0 +1,6 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+ import "./.next/types/routes.d.ts";
4
+
5
+ // NOTE: This file should not be edited
6
+ // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
@@ -0,0 +1,7 @@
1
+ <svg width="176" height="44" viewBox="0 0 176 44" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="44" height="44" rx="12" fill="#F5F4EC"/>
3
+ <path d="M12 30V14H21.72C25.24 14 27.68 16.08 27.68 19.24C27.68 21.78 26.22 23.63 23.99 24.32L29.3 30H24.9L20.15 24.86H15.8V30H12Z" fill="#102015"/>
4
+ <circle cx="32.5" cy="12.5" r="3.5" fill="#2C8A53"/>
5
+ <text x="58" y="19" fill="#F5F4EC" font-family="Georgia, serif" font-size="11" letter-spacing="2">BRIGHTWEB</text>
6
+ <text x="58" y="33" fill="#C8D0C9" font-family="'IBM Plex Sans', sans-serif" font-size="15">starter client</text>
7
+ </svg>
@@ -0,0 +1,7 @@
1
+ <svg width="176" height="44" viewBox="0 0 176 44" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="44" height="44" rx="12" fill="#13251A"/>
3
+ <path d="M12 30V14H21.72C25.24 14 27.68 16.08 27.68 19.24C27.68 21.78 26.22 23.63 23.99 24.32L29.3 30H24.9L20.15 24.86H15.8V30H12Z" fill="#F5F4EC"/>
4
+ <circle cx="32.5" cy="12.5" r="3.5" fill="#71C48B"/>
5
+ <text x="58" y="19" fill="#13251A" font-family="Georgia, serif" font-size="11" letter-spacing="2">BRIGHTWEB</text>
6
+ <text x="58" y="33" fill="#516252" font-family="'IBM Plex Sans', sans-serif" font-size="15">starter client</text>
7
+ </svg>
@@ -0,0 +1,5 @@
1
+ <svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="64" height="64" rx="18" fill="#13251A"/>
3
+ <path d="M19 42V22H31.8C36.4 22 39.6 24.7 39.6 28.8C39.6 32.1 37.7 34.5 34.8 35.4L41.8 42H36L29.7 36H24V42H19ZM24 31.9H31.1C33.5 31.9 34.7 30.8 34.7 29C34.7 27.2 33.5 26.1 31.1 26.1H24V31.9Z" fill="#F5F4EC"/>
4
+ <circle cx="46.5" cy="18.5" r="4.5" fill="#71C48B"/>
5
+ </svg>
@@ -0,0 +1,36 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": [
5
+ "dom",
6
+ "dom.iterable",
7
+ "esnext"
8
+ ],
9
+ "allowJs": true,
10
+ "skipLibCheck": true,
11
+ "strict": true,
12
+ "noEmit": true,
13
+ "esModuleInterop": true,
14
+ "module": "esnext",
15
+ "moduleResolution": "bundler",
16
+ "resolveJsonModule": true,
17
+ "isolatedModules": true,
18
+ "jsx": "react-jsx",
19
+ "incremental": true,
20
+ "plugins": [
21
+ {
22
+ "name": "next"
23
+ }
24
+ ]
25
+ },
26
+ "include": [
27
+ "next-env.d.ts",
28
+ "**/*.ts",
29
+ "**/*.tsx",
30
+ ".next/types/**/*.ts",
31
+ ".next/dev/types/**/*.ts"
32
+ ],
33
+ "exclude": [
34
+ "node_modules"
35
+ ]
36
+ }
@@ -0,0 +1,6 @@
1
+ export const dynamic = "force-dynamic";
2
+
3
+ export async function POST(request: Request) {
4
+ const { handleAdminUsersRoleChangeRequest } = await import("@brightweblabs/module-admin");
5
+ return handleAdminUsersRoleChangeRequest(request);
6
+ }
@@ -0,0 +1,6 @@
1
+ export const dynamic = "force-dynamic";
2
+
3
+ export async function GET(request: Request) {
4
+ const { handleAdminUsersGetRequest } = await import("@brightweblabs/module-admin");
5
+ return handleAdminUsersGetRequest(request);
6
+ }
@@ -0,0 +1,102 @@
1
+ export const dynamic = "force-dynamic";
2
+
3
+ function formatError(error: unknown) {
4
+ if (error instanceof Error) {
5
+ return error.message;
6
+ }
7
+
8
+ return "Unknown error";
9
+ }
10
+
11
+ export default async function AdminPlaygroundPage() {
12
+ try {
13
+ const { getAdminUsersPageData } = await import("@brightweblabs/module-admin");
14
+ const { users } = await getAdminUsersPageData();
15
+
16
+ return (
17
+ <>
18
+ <article className="panel">
19
+ <div className="panel-inner">
20
+ <p className="eyebrow">Admin Module</p>
21
+ <h1>Admin users and roles playground</h1>
22
+ <p className="muted">
23
+ This route previews the shared admin governance module without relying on the BeGreen client app.
24
+ </p>
25
+ </div>
26
+ </article>
27
+
28
+ <article className="grid">
29
+ <div className="panel">
30
+ <div className="panel-inner">
31
+ <p className="status ok">Connected</p>
32
+ <h2>User governance snapshot</h2>
33
+ <ul className="list">
34
+ <li>Total users in page: {users.data.length}</li>
35
+ <li>Total users overall: {users.pagination.total}</li>
36
+ <li>Current page: {users.pagination.page}</li>
37
+ <li>Total pages: {users.pagination.totalPages}</li>
38
+ </ul>
39
+ </div>
40
+ </div>
41
+
42
+ <div className="panel">
43
+ <div className="panel-inner">
44
+ <h2>Role mix</h2>
45
+ <div className="code-box">
46
+ <pre>
47
+ {JSON.stringify(
48
+ users.data.reduce<Record<string, number>>((acc, row) => {
49
+ acc[row.role] = (acc[row.role] ?? 0) + 1;
50
+ return acc;
51
+ }, {}),
52
+ null,
53
+ 2,
54
+ )}
55
+ </pre>
56
+ </div>
57
+ </div>
58
+ </div>
59
+ </article>
60
+
61
+ <article className="panel">
62
+ <div className="panel-inner">
63
+ <h2>First admin users</h2>
64
+ <div className="code-box">
65
+ <pre>{JSON.stringify(users.data.slice(0, 5), null, 2)}</pre>
66
+ </div>
67
+ </div>
68
+ </article>
69
+ </>
70
+ );
71
+ } catch (error) {
72
+ return (
73
+ <>
74
+ <article className="panel">
75
+ <div className="panel-inner">
76
+ <p className="eyebrow">Admin Module</p>
77
+ <h1>Admin users and roles playground</h1>
78
+ <p className="muted">
79
+ The admin package is wired, but this starter app still needs a configured client database and admin-ready
80
+ auth environment.
81
+ </p>
82
+ </div>
83
+ </article>
84
+
85
+ <article className="panel">
86
+ <div className="panel-inner stack">
87
+ <p className="status warn">Configuration required</p>
88
+ <div className="result-box">
89
+ <strong>Current error</strong>
90
+ <p>{formatError(error)}</p>
91
+ </div>
92
+ <ul className="list">
93
+ <li>Set the Supabase public URL and anon key.</li>
94
+ <li>Set the Supabase service role key.</li>
95
+ <li>Ensure the client database includes the admin role assignment tables and RPCs.</li>
96
+ </ul>
97
+ </div>
98
+ </article>
99
+ </>
100
+ );
101
+ }
102
+ }
@@ -0,0 +1,103 @@
1
+ export const dynamic = "force-dynamic";
2
+
3
+ function formatError(error: unknown) {
4
+ if (error instanceof Error) {
5
+ return error.message;
6
+ }
7
+
8
+ return "Unknown error";
9
+ }
10
+
11
+ export default async function CrmPlaygroundPage() {
12
+ try {
13
+ const { getCrmDashboardData } = await import("@brightweblabs/module-crm");
14
+ const data = await getCrmDashboardData();
15
+
16
+ return (
17
+ <>
18
+ <article className="panel">
19
+ <div className="panel-inner">
20
+ <p className="eyebrow">CRM Module</p>
21
+ <h1>CRM server/data playground</h1>
22
+ <p className="muted">
23
+ This route calls the external `@brightweblabs/module-crm` package directly and shows a thin data summary.
24
+ </p>
25
+ </div>
26
+ </article>
27
+
28
+ <article className="grid">
29
+ <div className="panel">
30
+ <div className="panel-inner">
31
+ <p className="status ok">Connected</p>
32
+ <h2>Snapshot</h2>
33
+ <ul className="list">
34
+ <li>Total contacts: {data.stats.total}</li>
35
+ <li>Organizations loaded: {data.organizations.length}</li>
36
+ <li>Timeline events: {data.statusLog.length}</li>
37
+ <li>Owner options: {data.ownerOptions.length}</li>
38
+ </ul>
39
+ </div>
40
+ </div>
41
+
42
+ <div className="panel">
43
+ <div className="panel-inner">
44
+ <h2>Statuses</h2>
45
+ <div className="code-box">
46
+ <pre>{JSON.stringify(data.stats.byStatus, null, 2)}</pre>
47
+ </div>
48
+ </div>
49
+ </div>
50
+ </article>
51
+
52
+ <article className="panel">
53
+ <div className="panel-inner">
54
+ <h2>First records</h2>
55
+ <div className="code-box">
56
+ <pre>
57
+ {JSON.stringify(
58
+ {
59
+ organizations: data.organizations.slice(0, 2),
60
+ contacts: data.contacts.slice(0, 3),
61
+ statusLog: data.statusLog.slice(0, 3),
62
+ },
63
+ null,
64
+ 2,
65
+ )}
66
+ </pre>
67
+ </div>
68
+ </div>
69
+ </article>
70
+ </>
71
+ );
72
+ } catch (error) {
73
+ return (
74
+ <>
75
+ <article className="panel">
76
+ <div className="panel-inner">
77
+ <p className="eyebrow">CRM Module</p>
78
+ <h1>CRM server/data playground</h1>
79
+ <p className="muted">
80
+ The package is wired, but this sandbox still needs valid environment variables and database access.
81
+ </p>
82
+ </div>
83
+ </article>
84
+
85
+ <article className="panel">
86
+ <div className="panel-inner stack">
87
+ <p className="status warn">Configuration required</p>
88
+ <div className="result-box">
89
+ <strong>Current error</strong>
90
+ <p>{formatError(error)}</p>
91
+ </div>
92
+ <ul className="list">
93
+ <li>Set `NEXT_PUBLIC_APP_URL`.</li>
94
+ <li>Set `NEXT_PUBLIC_SUPABASE_URL`.</li>
95
+ <li>Set `NEXT_PUBLIC_SUPABASE_ANON_KEY`.</li>
96
+ <li>If you need CRM writes or email flows, also set the server-only keys in the platform repo.</li>
97
+ </ul>
98
+ </div>
99
+ </article>
100
+ </>
101
+ );
102
+ }
103
+ }
@@ -0,0 +1,93 @@
1
+ export const dynamic = "force-dynamic";
2
+
3
+ function formatError(error: unknown) {
4
+ if (error instanceof Error) {
5
+ return error.message;
6
+ }
7
+
8
+ return "Unknown error";
9
+ }
10
+
11
+ export default async function ProjectsPlaygroundPage() {
12
+ try {
13
+ const { getProjectsPortfolioPageData } = await import("@brightweblabs/module-projects");
14
+ const data = await getProjectsPortfolioPageData();
15
+
16
+ return (
17
+ <>
18
+ <article className="panel">
19
+ <div className="panel-inner">
20
+ <p className="eyebrow">Projects Module</p>
21
+ <h1>Projects portfolio playground</h1>
22
+ <p className="muted">
23
+ This route calls the external `@brightweblabs/module-projects` package directly and shows a thin portfolio
24
+ snapshot for a starter client.
25
+ </p>
26
+ </div>
27
+ </article>
28
+
29
+ <article className="grid">
30
+ <div className="panel">
31
+ <div className="panel-inner">
32
+ <p className={`status ${data.schemaMissing ? "warn" : "ok"}`}>{data.schemaMissing ? "Schema missing" : "Connected"}</p>
33
+ <h2>Portfolio snapshot</h2>
34
+ <ul className="list">
35
+ <li>Total open projects: {data.portfolioStats.total}</li>
36
+ <li>At risk: {data.portfolioStats.atRisk}</li>
37
+ <li>Overdue: {data.portfolioStats.overdue}</li>
38
+ <li>Organizations loaded: {data.organizationOptions.length}</li>
39
+ </ul>
40
+ </div>
41
+ </div>
42
+
43
+ <div className="panel">
44
+ <div className="panel-inner">
45
+ <h2>Status distribution</h2>
46
+ <div className="code-box">
47
+ <pre>{JSON.stringify(data.portfolioStats, null, 2)}</pre>
48
+ </div>
49
+ </div>
50
+ </div>
51
+ </article>
52
+
53
+ <article className="panel">
54
+ <div className="panel-inner">
55
+ <h2>First project records</h2>
56
+ <div className="code-box">
57
+ <pre>{JSON.stringify(data.result.items.slice(0, 3), null, 2)}</pre>
58
+ </div>
59
+ </div>
60
+ </article>
61
+ </>
62
+ );
63
+ } catch (error) {
64
+ return (
65
+ <>
66
+ <article className="panel">
67
+ <div className="panel-inner">
68
+ <p className="eyebrow">Projects Module</p>
69
+ <h1>Projects portfolio playground</h1>
70
+ <p className="muted">
71
+ The package is wired, but this starter app still needs the client project infrastructure configured.
72
+ </p>
73
+ </div>
74
+ </article>
75
+
76
+ <article className="panel">
77
+ <div className="panel-inner stack">
78
+ <p className="status warn">Configuration required</p>
79
+ <div className="result-box">
80
+ <strong>Current error</strong>
81
+ <p>{formatError(error)}</p>
82
+ </div>
83
+ <ul className="list">
84
+ <li>Set the Supabase public URL and anon key.</li>
85
+ <li>Set the Supabase service role key for server access.</li>
86
+ <li>Ensure the projects schema exists in the target client database.</li>
87
+ </ul>
88
+ </div>
89
+ </article>
90
+ </>
91
+ );
92
+ }
93
+ }
@@ -0,0 +1,68 @@
1
+ @import "tailwindcss";
2
+
3
+ @theme inline {
4
+ --font-sans: var(--font-sans);
5
+ --font-display: var(--font-display);
6
+ --color-background: var(--background);
7
+ --color-foreground: var(--foreground);
8
+ --color-muted-foreground: var(--muted-foreground);
9
+ --color-border: var(--border);
10
+ --color-panel: var(--panel);
11
+ --color-accent: var(--accent);
12
+ --color-accent-soft: var(--accent-soft);
13
+ --color-ring: var(--ring);
14
+ }
15
+
16
+ :root {
17
+ --font-sans: "Avenir Next", "Segoe UI", "Helvetica Neue", sans-serif;
18
+ --font-display: "Iowan Old Style", "Palatino Linotype", "Book Antiqua", Georgia, serif;
19
+ --background: #f5efe6;
20
+ --foreground: #1e1e1a;
21
+ --muted-foreground: #5d5b55;
22
+ --border: rgba(32, 31, 27, 0.12);
23
+ --panel: rgba(255, 251, 246, 0.72);
24
+ --accent: #ab6436;
25
+ --accent-soft: rgba(171, 100, 54, 0.12);
26
+ --ring: rgba(171, 100, 54, 0.35);
27
+ }
28
+
29
+ * {
30
+ box-sizing: border-box;
31
+ }
32
+
33
+ html {
34
+ scroll-behavior: smooth;
35
+ }
36
+
37
+ body {
38
+ margin: 0;
39
+ min-height: 100vh;
40
+ background:
41
+ radial-gradient(circle at top left, rgba(216, 168, 124, 0.18), transparent 22rem),
42
+ radial-gradient(circle at 85% 15%, rgba(81, 111, 95, 0.14), transparent 16rem),
43
+ linear-gradient(180deg, #fcf9f4 0%, var(--background) 46%, #ede4d4 100%);
44
+ color: var(--foreground);
45
+ font-family: var(--font-sans), sans-serif;
46
+ }
47
+
48
+ body::before {
49
+ content: "";
50
+ position: fixed;
51
+ inset: 0;
52
+ pointer-events: none;
53
+ background-image:
54
+ linear-gradient(rgba(32, 31, 27, 0.03) 1px, transparent 1px),
55
+ linear-gradient(90deg, rgba(32, 31, 27, 0.03) 1px, transparent 1px);
56
+ background-size: 7rem 7rem;
57
+ mask-image: radial-gradient(circle at center, black, transparent 80%);
58
+ opacity: 0.5;
59
+ }
60
+
61
+ a {
62
+ color: inherit;
63
+ text-decoration: none;
64
+ }
65
+
66
+ ::selection {
67
+ background: rgba(171, 100, 54, 0.18);
68
+ }
@@ -0,0 +1,17 @@
1
+ import type { Metadata } from "next";
2
+ import type { ReactNode } from "react";
3
+ import { siteConfig } from "@/config/site";
4
+ import "./globals.css";
5
+
6
+ export const metadata: Metadata = {
7
+ title: siteConfig.name,
8
+ description: siteConfig.description,
9
+ };
10
+
11
+ export default function RootLayout({ children }: { children: ReactNode }) {
12
+ return (
13
+ <html lang="en">
14
+ <body>{children}</body>
15
+ </html>
16
+ );
17
+ }