bsmnt 0.2.0 → 0.2.5

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 (60) hide show
  1. package/README.md +1 -1
  2. package/package.json +22 -5
  3. package/src/templates/next-default/.biome/plugins/no-anchor-element.grit +12 -0
  4. package/src/templates/next-default/.biome/plugins/no-relative-parent-imports.grit +10 -0
  5. package/src/templates/next-default/.biome/plugins/no-unnecessary-forwardref.grit +9 -0
  6. package/src/templates/next-default/.github/PULL_REQUEST_TEMPLATE.md +14 -0
  7. package/src/templates/next-default/.vscode/extensions.json +20 -0
  8. package/src/templates/next-default/.vscode/settings.json +105 -0
  9. package/src/templates/next-default/app/favicon.ico +0 -0
  10. package/src/templates/next-default/app/layout.tsx +104 -0
  11. package/src/templates/next-default/app/page.tsx +11 -0
  12. package/src/templates/next-default/app/robots.ts +15 -0
  13. package/src/templates/next-default/app/sitemap.ts +16 -0
  14. package/src/templates/next-default/biome.json +2 -2
  15. package/src/templates/next-default/components/layout/footer/index.tsx +31 -0
  16. package/src/templates/next-default/components/layout/header/index.tsx +9 -0
  17. package/src/templates/next-default/components/layout/theme/index.tsx +66 -0
  18. package/src/templates/next-default/components/layout/wrapper/index.tsx +63 -0
  19. package/src/templates/next-default/components/ui/README.md +77 -0
  20. package/src/templates/next-default/components/ui/image/README.md +37 -0
  21. package/src/templates/next-default/components/ui/image/index.tsx +219 -0
  22. package/src/templates/next-default/components/ui/link/index.tsx +152 -0
  23. package/src/templates/next-default/lib/utils/metadata.ts +26 -26
  24. package/src/templates/next-default/package.json +4 -4
  25. package/src/templates/next-default/public/fonts/geist/Geist-Mono.woff2 +0 -0
  26. package/src/templates/next-experiments/app/layout.tsx +18 -18
  27. package/src/templates/next-experiments/app/robots.ts +3 -3
  28. package/src/templates/next-experiments/app/sitemap.ts +4 -4
  29. package/src/templates/next-experiments/biome.json +2 -2
  30. package/src/templates/next-experiments/components/layout/theme/index.tsx +1 -1
  31. package/src/templates/next-experiments/components/layout/wrapper/index.tsx +7 -9
  32. package/src/templates/next-experiments/components/ui/image/index.tsx +39 -46
  33. package/src/templates/next-experiments/components/ui/link/index.tsx +6 -0
  34. package/src/templates/next-experiments/lib/utils/metadata.ts +26 -26
  35. package/src/templates/next-experiments/package.json +4 -4
  36. package/src/templates/next-webgl/app/layout.tsx +18 -18
  37. package/src/templates/next-webgl/app/robots.ts +3 -3
  38. package/src/templates/next-webgl/app/sitemap.ts +4 -4
  39. package/src/templates/next-webgl/biome.json +2 -2
  40. package/src/templates/next-webgl/components/layout/theme/index.tsx +1 -1
  41. package/src/templates/next-webgl/components/layout/wrapper/index.tsx +0 -2
  42. package/src/templates/next-webgl/components/ui/image/index.tsx +6 -11
  43. package/src/templates/next-webgl/components/ui/link/index.tsx +9 -2
  44. package/src/templates/next-webgl/components/webgl/components/scene/index.tsx +1 -0
  45. package/src/templates/next-webgl/lib/utils/metadata.ts +26 -26
  46. package/src/templates/next-webgl/package.json +4 -4
  47. package/src/templates/next-experiments/.cursor/rules/README.md +0 -184
  48. package/src/templates/next-experiments/.cursor/rules/architecture.mdc +0 -437
  49. package/src/templates/next-experiments/.cursor/rules/components.mdc +0 -436
  50. package/src/templates/next-experiments/.cursor/rules/integrations.mdc +0 -447
  51. package/src/templates/next-experiments/.cursor/rules/main.mdc +0 -278
  52. package/src/templates/next-experiments/.cursor/rules/styling.mdc +0 -433
  53. package/src/templates/next-experiments/.github/workflows/lighthouse-to-slack.yml +0 -136
  54. package/src/templates/next-webgl/.cursor/rules/README.md +0 -184
  55. package/src/templates/next-webgl/.cursor/rules/architecture.mdc +0 -437
  56. package/src/templates/next-webgl/.cursor/rules/components.mdc +0 -436
  57. package/src/templates/next-webgl/.cursor/rules/integrations.mdc +0 -447
  58. package/src/templates/next-webgl/.cursor/rules/main.mdc +0 -278
  59. package/src/templates/next-webgl/.cursor/rules/styling.mdc +0 -433
  60. package/src/templates/next-webgl/.github/workflows/lighthouse-to-slack.yml +0 -136
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # bsmnt `1.0.0`
1
+ # bsmnt `0.2.5`
2
2
 
3
3
  Your project starts here. One command to go from zero to a fully configured Next.js project with our stack, CMS, agent skills, and everything wired up.
4
4
 
package/package.json CHANGED
@@ -1,17 +1,24 @@
1
1
  {
2
2
  "name": "bsmnt",
3
- "version": "0.2.0",
3
+ "version": "0.2.5",
4
4
  "packageManager": "bun@1.2.20",
5
5
  "description": "CLI to scaffold basement projects and add integrations",
6
6
  "type": "module",
7
+ "workspaces": [
8
+ ".",
9
+ "src/templates/*"
10
+ ],
7
11
  "bin": {
8
- "bsmnt": "./index.js"
12
+ "bsmnt": "index.js"
9
13
  },
10
14
  "main": "./dist/index.js",
11
15
  "files": [
12
16
  "dist",
13
17
  "index.js",
14
18
  "src/templates",
19
+ "!src/templates/*/node_modules",
20
+ "!src/templates/*/.next",
21
+ "!src/templates/*/bun.lock",
15
22
  "src/template-hooks",
16
23
  "plugins"
17
24
  ],
@@ -22,10 +29,20 @@
22
29
  "lint": "biome check .",
23
30
  "lint:fix": "biome check --write .",
24
31
  "changeset": "changeset",
25
- "version-packages": "changeset version",
32
+ "version-packages": "changeset version && node scripts/sync-readme-version.js",
26
33
  "release": "changeset publish",
27
- "prepublishOnly": "bun run build",
28
- "default": "turbo run dev --filter=basement-next-starter"
34
+ "prepublishOnly": "rm -rf dist && bun run build",
35
+ "default": "turbo run dev --filter=bsmnt-next-starter",
36
+ "webgpu": "turbo run dev --filter=bsmnt-webgpu-starter",
37
+ "experiments": "turbo run dev --filter=bsmnt-experiments-starter",
38
+ "build:default": "turbo run build --filter=bsmnt-next-starter",
39
+ "build:webgpu": "turbo run build --filter=bsmnt-webgpu-starter",
40
+ "build:experiments": "turbo run build --filter=bsmnt-experiments-starter",
41
+ "build:all": "turbo run build --filter=bsmnt-next-starter --filter=bsmnt-webgpu-starter --filter=bsmnt-experiments-starter",
42
+ "lint:default": "turbo run lint --filter=bsmnt-next-starter",
43
+ "lint:webgpu": "turbo run lint --filter=bsmnt-webgpu-starter",
44
+ "lint:experiments": "turbo run lint --filter=bsmnt-experiments-starter",
45
+ "lint:all": "turbo run lint --filter=bsmnt-next-starter --filter=bsmnt-webgpu-starter --filter=bsmnt-experiments-starter"
29
46
  },
30
47
  "keywords": [
31
48
  "webgpu",
@@ -0,0 +1,12 @@
1
+ language js;
2
+
3
+ `<a $attrs>$content</a>` as $anchor where {
4
+ !$anchor <: within `if ($condition) { return ($jsx) }` where {
5
+ $condition <: contains or { `isExternal`, `isExternalSSR` }
6
+ },
7
+ register_diagnostic(
8
+ span = $anchor,
9
+ message = "Use custom Link component instead of <a> element. The Link component handles both internal and external links automatically.",
10
+ severity = "error"
11
+ )
12
+ }
@@ -0,0 +1,10 @@
1
+ language js;
2
+
3
+ `import $imports from $source` as $import where {
4
+ $source <: r"['\"]\.\.\/\.\.\/.*['\"]",
5
+ register_diagnostic(
6
+ span = $import,
7
+ message = "Use alias imports (~/dir/) instead of deep relative imports (../../). Single level imports (../) are allowed for colocated files.",
8
+ severity = "error"
9
+ )
10
+ }
@@ -0,0 +1,9 @@
1
+ language js;
2
+
3
+ `forwardRef($func)` as $ref where {
4
+ register_diagnostic(
5
+ span = $ref,
6
+ message = "forwardRef is unnecessary in React 19 with the compiler",
7
+ severity = "warning"
8
+ )
9
+ }
@@ -0,0 +1,14 @@
1
+ ## Summary
2
+
3
+ <!-- Brief description of what this PR does -->
4
+
5
+ ## Changes
6
+
7
+ -
8
+
9
+ ## Checklist
10
+
11
+ - [ ] `bun lint` passes
12
+ - [ ] Tested locally in dev mode
13
+ - [ ] No breaking changes (or documented in summary)
14
+
@@ -0,0 +1,20 @@
1
+ {
2
+ "recommendations": [
3
+ // Formatting & Linting
4
+ "biomejs.biome",
5
+
6
+ // CSS & Styling
7
+ "bradlc.vscode-tailwindcss",
8
+ "csstools.postcss",
9
+
10
+ // GraphQL (Shopify, etc.)
11
+ "graphql.vscode-graphql-syntax",
12
+
13
+ // Sanity CMS (GROQ syntax + validation)
14
+ "sanity-io.vscode-sanity",
15
+
16
+ // DX Enhancements
17
+ "yoavbls.pretty-ts-errors",
18
+ "waderyan.gitblame"
19
+ ]
20
+ }
@@ -0,0 +1,105 @@
1
+ {
2
+ // Language-specific formatters (explicit per-language ensures no fallback to other formatters)
3
+ "[css]": {
4
+ "editor.defaultFormatter": "biomejs.biome"
5
+ },
6
+ "[javascript]": {
7
+ "editor.defaultFormatter": "biomejs.biome"
8
+ },
9
+ "[javascriptreact]": {
10
+ "editor.defaultFormatter": "biomejs.biome"
11
+ },
12
+ "[json]": {
13
+ "editor.defaultFormatter": "biomejs.biome"
14
+ },
15
+ "[jsonc]": {
16
+ "editor.defaultFormatter": "biomejs.biome"
17
+ },
18
+ "[postcss]": {
19
+ "editor.defaultFormatter": "biomejs.biome"
20
+ },
21
+ "[typescript]": {
22
+ "editor.defaultFormatter": "biomejs.biome"
23
+ },
24
+ "[typescriptreact]": {
25
+ "editor.defaultFormatter": "biomejs.biome"
26
+ },
27
+ "colorize.languages": [
28
+ "javascript",
29
+ "typescript",
30
+ "css",
31
+ "scss",
32
+ "less",
33
+ "html",
34
+ "json",
35
+ "yaml",
36
+ "markdown",
37
+ "glsl"
38
+ ],
39
+ "css.lint.unknownAtRules": "ignore",
40
+
41
+ // CSS (Tailwind v4)
42
+ "css.lint.validProperties": ["user-drag"],
43
+ "editor.codeActionsOnSave": {
44
+ "source.fixAll.biome": "always",
45
+ "source.organizeImports.biome": "always"
46
+ },
47
+ "editor.defaultFormatter": "biomejs.biome",
48
+
49
+ // Formatting
50
+ "editor.formatOnSave": true,
51
+ "editor.formatOnSaveMode": "file",
52
+
53
+ // File associations
54
+ "files.associations": {
55
+ "*.css": "css",
56
+ "*.json": "jsonc"
57
+ },
58
+
59
+ // General
60
+ "files.eol": "\n",
61
+ // "typescript.experimental.useTsgo": true, // Disabled: tsgo doesn't support Next.js plugin
62
+ "javascript.suggest.autoImports": true,
63
+ // Disable Prettier to prevent silent formatter conflicts
64
+ "prettier.enable": false,
65
+
66
+ // Search exclusions (performance)
67
+ "search.exclude": {
68
+ "**/.next": true,
69
+ "**/.vercel": true,
70
+ "**/bun.lock": true,
71
+ "**/lib/integrations/sanity/sanity.types.ts": true,
72
+ "**/node_modules": true
73
+ },
74
+ "tailwindCSS.classAttributes": [
75
+ "class",
76
+ "className",
77
+ ".*ClassName",
78
+ ".*Styles"
79
+ ],
80
+ "tailwindCSS.classFunctions": ["cn", "cva", "twMerge", "cx"],
81
+ "tailwindCSS.experimental.classRegex": [
82
+ ["cn\\(([^)]*)\\)", "(?:'|\"|`)([^\"'`]*)(?:'|\"|`)"],
83
+ ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
84
+ ],
85
+ "tailwindCSS.experimental.configFile": "./lib/styles/css/tailwind.css",
86
+ "tailwindCSS.includeLanguages": {
87
+ "typescriptreact": "html"
88
+ },
89
+ "typescript.preferences.importModuleSpecifier": "non-relative",
90
+
91
+ // TypeScript
92
+ "typescript.suggest.autoImports": true,
93
+ "typescript.tsdk": "node_modules/typescript/lib",
94
+
95
+ // Editor labels (fixed for .tsx files)
96
+ "workbench.editor.customLabels.patterns": {
97
+ "**/app/**/layout.tsx": "${dirname(1)}/${dirname} <layout>",
98
+ "**/app/**/page.tsx": "${dirname(1)}/${dirname} <page>",
99
+ "**/app/api/**/route.ts": "${dirname(1)}/${dirname} <route>",
100
+ "**/components/**/index.tsx": "${dirname} <component>"
101
+ },
102
+ "workbench.editorAssociations": {
103
+ "*.svg": "default"
104
+ }
105
+ }
@@ -0,0 +1,104 @@
1
+ import type { Metadata, Viewport } from "next";
2
+ import { Geist } from "next/font/google";
3
+ import { type PropsWithChildren, Suspense } from "react";
4
+ import { Link } from "@/components/ui/link";
5
+ import { themes } from "@/lib/styles/colors";
6
+ import { fontsVariable } from "@/lib/styles/fonts";
7
+ import AppData from "@/package.json";
8
+ import "@/lib/styles/css/index.css";
9
+ import { cn } from "@/lib/styles/cn";
10
+
11
+ const APP_NAME = AppData.name;
12
+ const APP_DEFAULT_TITLE = "Basement Starter";
13
+ const APP_TITLE_TEMPLATE = "%s - Basement Starter";
14
+ const APP_DESCRIPTION = AppData.description;
15
+ const APP_BASE_URL =
16
+ process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000";
17
+
18
+ const geist = Geist({
19
+ subsets: ["latin"],
20
+ });
21
+
22
+ export const metadata: Metadata = {
23
+ alternates: {
24
+ canonical: "/",
25
+ languages: {
26
+ "en-US": "/en-US",
27
+ },
28
+ },
29
+ appleWebApp: {
30
+ capable: true,
31
+ statusBarStyle: "default",
32
+ title: APP_DEFAULT_TITLE,
33
+ },
34
+ applicationName: APP_NAME,
35
+ authors: [{ name: "basement.studio", url: "https://basement.studio" }],
36
+ description: APP_DESCRIPTION,
37
+ formatDetection: { telephone: false },
38
+ metadataBase: new URL(APP_BASE_URL),
39
+ openGraph: {
40
+ description: APP_DESCRIPTION,
41
+ images: [
42
+ {
43
+ alt: APP_DEFAULT_TITLE,
44
+ height: 630,
45
+ url: "/opengraph-image.jpg",
46
+ width: 1200,
47
+ },
48
+ ],
49
+ locale: "en_US",
50
+ siteName: APP_NAME,
51
+ title: {
52
+ default: APP_DEFAULT_TITLE,
53
+ template: APP_TITLE_TEMPLATE,
54
+ },
55
+ type: "website",
56
+ url: APP_BASE_URL,
57
+ },
58
+ other: {
59
+ "fb:app_id": process.env.NEXT_PUBLIC_FACEBOOK_APP_ID || "",
60
+ },
61
+ title: {
62
+ default: APP_DEFAULT_TITLE,
63
+ template: APP_TITLE_TEMPLATE,
64
+ },
65
+ twitter: {
66
+ card: "summary_large_image",
67
+ description: APP_DESCRIPTION,
68
+ title: {
69
+ default: APP_DEFAULT_TITLE,
70
+ template: APP_TITLE_TEMPLATE,
71
+ },
72
+ },
73
+ };
74
+
75
+ export const viewport: Viewport = {
76
+ colorScheme: "normal",
77
+ themeColor: themes.dark.primary,
78
+ };
79
+
80
+ export default async function Layout({ children }: PropsWithChildren) {
81
+ return (
82
+ <html
83
+ lang="en"
84
+ dir="ltr"
85
+ className={cn(fontsVariable, geist.className)}
86
+ // NOTE: This is due to the data-theme attribute being set which causes hydration errors
87
+ suppressHydrationWarning
88
+ >
89
+ <body>
90
+ {/* Skip link for keyboard navigation accessibility */}
91
+ <Suspense fallback={null}>
92
+ <Link
93
+ href="#main-content"
94
+ className="sr-only focus:not-sr-only focus:fixed focus:top-4 focus:left-4 focus:z-9999 focus:rounded focus:bg-black focus:px-4 focus:py-2 focus:text-white focus:outline-none focus:ring-2 focus:ring-white"
95
+ >
96
+ Skip to main content
97
+ </Link>
98
+ </Suspense>
99
+
100
+ {children}
101
+ </body>
102
+ </html>
103
+ );
104
+ }
@@ -0,0 +1,11 @@
1
+ import { Wrapper } from "@/components/layout/wrapper"
2
+
3
+ export default function Home() {
4
+ return (
5
+ <Wrapper theme="dark">
6
+ <section className="flex grow items-center justify-center">
7
+ <h1 className="font-mono text-xl uppercase">Basement Next Starter</h1>
8
+ </section>
9
+ </Wrapper>
10
+ )
11
+ }
@@ -0,0 +1,15 @@
1
+ import type { MetadataRoute } from "next";
2
+
3
+ const APP_BASE_URL =
4
+ process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000";
5
+
6
+ export default function robots(): MetadataRoute.Robots {
7
+ return {
8
+ rules: {
9
+ userAgent: "*",
10
+ allow: "/",
11
+ disallow: [],
12
+ },
13
+ sitemap: `${APP_BASE_URL}/sitemap.xml`,
14
+ };
15
+ }
@@ -0,0 +1,16 @@
1
+ import type { MetadataRoute } from "next";
2
+
3
+ const APP_BASE_URL =
4
+ process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000";
5
+
6
+ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
7
+ const baseRoutes: MetadataRoute.Sitemap = [
8
+ {
9
+ url: APP_BASE_URL,
10
+ lastModified: new Date(),
11
+ changeFrequency: "daily",
12
+ priority: 1,
13
+ },
14
+ ];
15
+ return baseRoutes;
16
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
- "root": false,
2
+ "root": true,
3
3
  "$schema": "node_modules/@biomejs/biome/configuration_schema.json",
4
4
  "assist": {
5
5
  "actions": {
@@ -245,6 +245,6 @@
245
245
  "vcs": {
246
246
  "clientKind": "git",
247
247
  "enabled": true,
248
- "useIgnoreFile": true
248
+ "useIgnoreFile": false
249
249
  }
250
250
  }
@@ -0,0 +1,31 @@
1
+ import Logo from "@/components/basement.svg"
2
+ import { Link } from "@/components/ui/link"
3
+
4
+ export function Footer() {
5
+ return (
6
+ <footer className="flex flex-col items-center justify-between p-safe font-mono uppercase 2xl:flex-row 2xl:items-end">
7
+ <Link
8
+ href="https://basement.studio/"
9
+ className="link"
10
+ aria-label="Basement Studio"
11
+ >
12
+ <Logo className="w-148 text-secondary" />
13
+ </Link>
14
+ <div>
15
+ <Link
16
+ href="https://github.com/basementstudio/next-starter/generate"
17
+ className="link"
18
+ >
19
+ use this template
20
+ </Link>
21
+ {" / "}
22
+ <Link
23
+ href="https://github.com/basementstudio/next-starter"
24
+ className="link"
25
+ >
26
+ github
27
+ </Link>
28
+ </div>
29
+ </footer>
30
+ )
31
+ }
@@ -0,0 +1,9 @@
1
+ export function Header() {
2
+ return (
3
+ <nav className="sticky top-0 z-2 flex flex-col font-mono uppercase">
4
+ <div className="inline-flex px-safe py-safe">
5
+ <div className="font-bold text-xl">Basement Next Starter</div>
6
+ </div>
7
+ </nav>
8
+ )
9
+ }
@@ -0,0 +1,66 @@
1
+ "use client"
2
+
3
+ import { usePathname } from "next/navigation"
4
+ import { createContext, useContext, useEffect, useState } from "react"
5
+ import type { Themes } from "@/lib/styles/colors"
6
+ import { type ThemeName, themes } from "@/lib/styles/config"
7
+
8
+ export const ThemeContext = createContext<{
9
+ name: ThemeName
10
+ theme: Themes[ThemeName]
11
+ setThemeName: (theme: ThemeName) => void
12
+ }>({
13
+ name: "light",
14
+ theme: themes.light,
15
+ setThemeName: () => {
16
+ void 0
17
+ },
18
+ })
19
+
20
+ export function useTheme() {
21
+ return useContext(ThemeContext)
22
+ }
23
+
24
+ export function Theme({
25
+ children,
26
+ theme,
27
+ global,
28
+ }: {
29
+ children: React.ReactNode
30
+ theme: ThemeName
31
+ global?: boolean
32
+ }) {
33
+ const pathname = usePathname()
34
+
35
+ const [currentTheme, setCurrentTheme] = useState(theme)
36
+
37
+ useEffect(() => {
38
+ setCurrentTheme(theme)
39
+ }, [theme])
40
+
41
+ // biome-ignore lint/correctness/useExhaustiveDependencies: we need to trigger on path change
42
+ useEffect(() => {
43
+ if (global) {
44
+ document.documentElement.setAttribute("data-theme", currentTheme)
45
+ }
46
+ }, [pathname, currentTheme, global])
47
+
48
+ return (
49
+ <>
50
+ {global && /^[a-z]+$/.test(currentTheme) && (
51
+ <script>
52
+ {`document.documentElement.setAttribute('data-theme', '${currentTheme}');`}
53
+ </script>
54
+ )}
55
+ <ThemeContext.Provider
56
+ value={{
57
+ name: currentTheme,
58
+ theme: themes[currentTheme],
59
+ setThemeName: setCurrentTheme,
60
+ }}
61
+ >
62
+ {children}
63
+ </ThemeContext.Provider>
64
+ </>
65
+ )
66
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Main page wrapper providing theme, smooth scrolling, and WebGL context.
3
+ *
4
+ * Customize the Header and Footer components for your project needs.
5
+ */
6
+ import cn from "clsx"
7
+ import { Footer } from "@/components/layout/footer"
8
+ import { Header } from "@/components/layout/header"
9
+ import { Theme } from "@/components/layout/theme"
10
+ import type { ThemeName } from "@/lib/styles/config"
11
+
12
+ /**
13
+ * Props for the Wrapper component.
14
+ */
15
+ interface WrapperProps extends React.HTMLAttributes<HTMLDivElement> {
16
+ /** Theme to apply ('dark' | 'light'). Defaults to 'dark'. */
17
+ theme?: ThemeName
18
+ }
19
+
20
+ /**
21
+ * Main page wrapper component providing theme.
22
+ *
23
+ * This component serves as the root container for pages, automatically handling
24
+ * theme application and layout structure.
25
+ * It includes navigation and footer.
26
+ *
27
+ * @param props - Component props
28
+ * @param props.theme - Color theme to apply to the page
29
+ * @param props.children - Page content
30
+ * @param props.className - Additional CSS classes
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * // Basic usage with theme
35
+ * export default function Page() {
36
+ * return (
37
+ * <Wrapper theme="dark">
38
+ * <section>My page content</section>
39
+ * </Wrapper>
40
+ * )
41
+ * }
42
+ * ```
43
+ */
44
+ export function Wrapper({
45
+ children,
46
+ theme = "dark",
47
+ className,
48
+ ...props
49
+ }: WrapperProps) {
50
+ return (
51
+ <Theme theme={theme} global>
52
+ <Header />
53
+ <main
54
+ id="main-content"
55
+ className={cn("relative flex grow flex-col", className)}
56
+ {...props}
57
+ >
58
+ {children}
59
+ </main>
60
+ <Footer />
61
+ </Theme>
62
+ )
63
+ }
@@ -0,0 +1,77 @@
1
+ # Components
2
+
3
+ UI components organized by purpose.
4
+
5
+ ```
6
+ components/
7
+ ├── ui/ → Primitives (reusable across projects)
8
+ ├── layout/ → Site chrome (customize per project)
9
+ └── magic/ → Animation & visual enhancements
10
+ ```
11
+
12
+ ## Direct Imports (Recommended)
13
+
14
+ ```tsx
15
+ // UI Components - import directly for better clarity
16
+ import { Image } from '@/components/ui/image'
17
+ import { Link } from '@/components/ui/link'
18
+
19
+ // Layout Components
20
+ import { Wrapper } from '@/components/layout/wrapper'
21
+ import { Header } from '@/components/layout/header'
22
+ import { Footer } from '@/components/layout/footer'
23
+
24
+ // Magic Components
25
+ import { Marquee } from '@/components/magic/marquee'
26
+ ```
27
+
28
+ ## UI Components
29
+
30
+ | Component | Purpose |
31
+ |-----------|---------|
32
+ | `image/` | Optimized images (always use this, never `next/image`) |
33
+ | `link/` | Smart navigation (auto-detects external) |
34
+
35
+ ## Layout Components
36
+
37
+ | Component | Purpose |
38
+ |-----------|---------|
39
+ | `wrapper/` | Page wrapper (theme, WebGL, Lenis) |
40
+ | `header/` | Site header/navigation |
41
+ | `footer/` | Site footer |
42
+ | `lenis/` | Smooth scrolling |
43
+
44
+ ```tsx
45
+ <Wrapper theme="dark" webgl lenis>
46
+ <section>Your content</section>
47
+ </Wrapper>
48
+ ```
49
+
50
+ ## Effects Components
51
+
52
+ | Component | Purpose |
53
+ |-----------|---------|
54
+ | `marquee/` | Infinite scrolling text |
55
+
56
+ ## Best Practices
57
+
58
+ ```tsx
59
+ // ✅ Always use custom Image
60
+ import { Image } from '@/components/ui/image'
61
+ <Image src="/photo.jpg" alt="Photo" aspectRatio={16/9} />
62
+
63
+ // ✅ Always use custom Link
64
+ import { Link } from '@/components/ui/link'
65
+ <Link href="/about">Internal</Link>
66
+ <Link href="https://example.com">External</Link>
67
+
68
+ // ✅ CSS Modules + Tailwind
69
+ import s from './component.module.css'
70
+ <div className={cn(s.wrapper, 'flex items-center')} />
71
+ ```
72
+
73
+ ## Related
74
+
75
+ - [Image docs](ui/image/README.md)
76
+ - [Real Viewport docs](ui/real-viewport/README.md)
77
+ - [WebGL Components](../lib/webgl/README.md)