bsmnt 0.2.0 → 0.2.4

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 (59) hide show
  1. package/package.json +21 -4
  2. package/src/templates/next-default/.biome/plugins/no-anchor-element.grit +12 -0
  3. package/src/templates/next-default/.biome/plugins/no-relative-parent-imports.grit +10 -0
  4. package/src/templates/next-default/.biome/plugins/no-unnecessary-forwardref.grit +9 -0
  5. package/src/templates/next-default/.github/PULL_REQUEST_TEMPLATE.md +14 -0
  6. package/src/templates/next-default/.vscode/extensions.json +20 -0
  7. package/src/templates/next-default/.vscode/settings.json +105 -0
  8. package/src/templates/next-default/app/favicon.ico +0 -0
  9. package/src/templates/next-default/app/layout.tsx +104 -0
  10. package/src/templates/next-default/app/page.tsx +11 -0
  11. package/src/templates/next-default/app/robots.ts +15 -0
  12. package/src/templates/next-default/app/sitemap.ts +16 -0
  13. package/src/templates/next-default/biome.json +2 -2
  14. package/src/templates/next-default/components/layout/footer/index.tsx +31 -0
  15. package/src/templates/next-default/components/layout/header/index.tsx +9 -0
  16. package/src/templates/next-default/components/layout/theme/index.tsx +66 -0
  17. package/src/templates/next-default/components/layout/wrapper/index.tsx +63 -0
  18. package/src/templates/next-default/components/ui/README.md +77 -0
  19. package/src/templates/next-default/components/ui/image/README.md +37 -0
  20. package/src/templates/next-default/components/ui/image/index.tsx +219 -0
  21. package/src/templates/next-default/components/ui/link/index.tsx +152 -0
  22. package/src/templates/next-default/lib/utils/metadata.ts +26 -26
  23. package/src/templates/next-default/package.json +4 -4
  24. package/src/templates/next-default/public/fonts/geist/Geist-Mono.woff2 +0 -0
  25. package/src/templates/next-experiments/app/layout.tsx +18 -18
  26. package/src/templates/next-experiments/app/robots.ts +3 -3
  27. package/src/templates/next-experiments/app/sitemap.ts +4 -4
  28. package/src/templates/next-experiments/biome.json +2 -2
  29. package/src/templates/next-experiments/components/layout/theme/index.tsx +1 -1
  30. package/src/templates/next-experiments/components/layout/wrapper/index.tsx +7 -9
  31. package/src/templates/next-experiments/components/ui/image/index.tsx +39 -46
  32. package/src/templates/next-experiments/components/ui/link/index.tsx +6 -0
  33. package/src/templates/next-experiments/lib/utils/metadata.ts +26 -26
  34. package/src/templates/next-experiments/package.json +4 -4
  35. package/src/templates/next-webgl/app/layout.tsx +18 -18
  36. package/src/templates/next-webgl/app/robots.ts +3 -3
  37. package/src/templates/next-webgl/app/sitemap.ts +4 -4
  38. package/src/templates/next-webgl/biome.json +2 -2
  39. package/src/templates/next-webgl/components/layout/theme/index.tsx +1 -1
  40. package/src/templates/next-webgl/components/layout/wrapper/index.tsx +0 -2
  41. package/src/templates/next-webgl/components/ui/image/index.tsx +6 -11
  42. package/src/templates/next-webgl/components/ui/link/index.tsx +9 -2
  43. package/src/templates/next-webgl/components/webgl/components/scene/index.tsx +1 -0
  44. package/src/templates/next-webgl/lib/utils/metadata.ts +26 -26
  45. package/src/templates/next-webgl/package.json +4 -4
  46. package/src/templates/next-experiments/.cursor/rules/README.md +0 -184
  47. package/src/templates/next-experiments/.cursor/rules/architecture.mdc +0 -437
  48. package/src/templates/next-experiments/.cursor/rules/components.mdc +0 -436
  49. package/src/templates/next-experiments/.cursor/rules/integrations.mdc +0 -447
  50. package/src/templates/next-experiments/.cursor/rules/main.mdc +0 -278
  51. package/src/templates/next-experiments/.cursor/rules/styling.mdc +0 -433
  52. package/src/templates/next-experiments/.github/workflows/lighthouse-to-slack.yml +0 -136
  53. package/src/templates/next-webgl/.cursor/rules/README.md +0 -184
  54. package/src/templates/next-webgl/.cursor/rules/architecture.mdc +0 -437
  55. package/src/templates/next-webgl/.cursor/rules/components.mdc +0 -436
  56. package/src/templates/next-webgl/.cursor/rules/integrations.mdc +0 -447
  57. package/src/templates/next-webgl/.cursor/rules/main.mdc +0 -278
  58. package/src/templates/next-webgl/.cursor/rules/styling.mdc +0 -433
  59. package/src/templates/next-webgl/.github/workflows/lighthouse-to-slack.yml +0 -136
@@ -4,13 +4,13 @@
4
4
  * Next.js Image wrapper with optimized defaults and error handling.
5
5
  * Always use this component instead of next/image directly.
6
6
  */
7
- "use client"
7
+ "use client";
8
8
 
9
- import cn from "clsx"
10
- import NextImage, { type ImageProps as NextImageProps } from "next/image"
11
- import type { CSSProperties, Ref } from "react"
12
- import { breakpoints } from "@/lib/styles/config"
13
- import s from "./image.module.css"
9
+ import cn from "clsx";
10
+ import NextImage, { type ImageProps as NextImageProps } from "next/image";
11
+ import type { CSSProperties, Ref } from "react";
12
+ import { breakpoints } from "@/lib/styles/config";
13
+ import s from "./image.module.css";
14
14
 
15
15
  /**
16
16
  * Enhanced Image component props extending Next.js Image.
@@ -20,26 +20,24 @@ import s from "./image.module.css"
20
20
  */
21
21
  export type ImageProps = Omit<NextImageProps, "objectFit" | "alt"> & {
22
22
  /** CSS object-fit property for image positioning */
23
- objectFit?: CSSProperties["objectFit"]
23
+ objectFit?: CSSProperties["objectFit"];
24
24
  /** Display as block element (adds display: block) */
25
- block?: boolean
25
+ block?: boolean;
26
26
  /** Size on mobile devices (e.g., "100vw", "50vw") */
27
- mobileSize?: `${number}vw`
27
+ mobileSize?: `${number}vw`;
28
28
  /** Size on desktop devices (e.g., "33vw", "25vw") */
29
- desktopSize?: `${number}vw`
29
+ desktopSize?: `${number}vw`;
30
30
  /** Ref for accessing the underlying img element */
31
- ref?: Ref<HTMLImageElement>
32
- /** Alt text for accessibility (required for meaningful images) */
33
- alt?: string
31
+ ref?: Ref<HTMLImageElement>;
34
32
  /** Aspect ratio for automatic placeholder and layout stability */
35
- aspectRatio?: number
36
- }
33
+ aspectRatio?: number;
34
+ };
37
35
 
38
36
  // Memoize helper functions to avoid recreation
39
37
  const toBase64 = (str: string) =>
40
38
  typeof window === "undefined"
41
39
  ? Buffer.from(str).toString("base64")
42
- : window.btoa(str)
40
+ : window.btoa(str);
43
41
 
44
42
  // Helper to generate blur placeholder with transparent background by default
45
43
  const generateShimmer = (w: number, h: number) => `
@@ -54,7 +52,7 @@ const generateShimmer = (w: number, h: number) => `
54
52
  <rect width="${w}" height="${h}" fill="rgba(0,0,0,0)" />
55
53
  <rect id="r" width="${w}" height="${h}" fill="url(#g)" />
56
54
  <animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
57
- </svg>`
55
+ </svg>`;
58
56
 
59
57
  // Helper to determine if blur placeholder should be used
60
58
  const shouldUseBlurPlaceholder = (
@@ -62,10 +60,10 @@ const shouldUseBlurPlaceholder = (
62
60
  placeholder: string,
63
61
  blurDataURL: string | undefined
64
62
  ): boolean => {
65
- if (!src) return false
66
- const isSvg = typeof src === "string" && src.includes(".svg")
67
- return !isSvg && placeholder === "blur" && !blurDataURL
68
- }
63
+ if (!src) return false;
64
+ const isSvg = typeof src === "string" && src.includes(".svg");
65
+ return !isSvg && placeholder === "blur" && !blurDataURL;
66
+ };
69
67
 
70
68
  // Helper to generate blur data URL
71
69
  const generateBlurDataURL = (
@@ -73,11 +71,11 @@ const generateBlurDataURL = (
73
71
  aspectRatio: number | undefined,
74
72
  existingBlurDataURL: string | undefined
75
73
  ): string | undefined => {
76
- if (!(shouldUse && aspectRatio)) return existingBlurDataURL
74
+ if (!(shouldUse && aspectRatio)) return existingBlurDataURL;
77
75
 
78
- const shimmerSvg = generateShimmer(700, Math.round(700 / aspectRatio))
79
- return `data:image/svg+xml;base64,${toBase64(shimmerSvg)}`
80
- }
76
+ const shimmerSvg = generateShimmer(700, Math.round(700 / aspectRatio));
77
+ return `data:image/svg+xml;base64,${toBase64(shimmerSvg)}`;
78
+ };
81
79
 
82
80
  // Helper to determine final placeholder value
83
81
  const getFinalPlaceholder = (
@@ -89,11 +87,11 @@ const getFinalPlaceholder = (
89
87
  if (!shouldUse) {
90
88
  return originalPlaceholder === "blur" && !blurDataURL
91
89
  ? "empty"
92
- : originalPlaceholder
90
+ : originalPlaceholder;
93
91
  }
94
92
 
95
- return aspectRatio || blurDataURL ? "blur" : "empty"
96
- }
93
+ return aspectRatio || blurDataURL ? "blur" : "empty";
94
+ };
97
95
 
98
96
  /**
99
97
  * Enhanced Image component with responsive sizing and automatic optimizations.
@@ -102,14 +100,14 @@ const getFinalPlaceholder = (
102
100
  * - Automatic responsive sizes generation
103
101
  * - Smart blur placeholders with aspect ratio support
104
102
  * - Performance optimizations (lazy loading by default)
105
- * - Preload for LCP images
103
+ * - Priority flag for LCP images
106
104
  *
107
105
  * @param props - Image props extending Next.js Image
108
106
  * @param props.aspectRatio - Aspect ratio for layout stability and blur placeholder
109
107
  * @param props.mobileSize - Size on mobile (e.g., "100vw")
110
108
  * @param props.desktopSize - Size on desktop (e.g., "50vw")
111
109
  * @param props.block - Display as block element
112
- * @param props.preload - Enable preloading for LCP images
110
+ * @param props.priority - Prioritize loading for LCP images
113
111
  *
114
112
  * @example
115
113
  * ```tsx
@@ -123,12 +121,12 @@ const getFinalPlaceholder = (
123
121
  *
124
122
  * @example
125
123
  * ```tsx
126
- * // LCP image with preload
124
+ * // LCP image with priority
127
125
  * <Image
128
126
  * src="/hero.jpg"
129
127
  * alt="Hero image"
130
128
  * aspectRatio={16/9}
131
- * preload // Preloads image for LCP
129
+ * priority // Preloads image for LCP
132
130
  * />
133
131
  * ```
134
132
  *
@@ -147,7 +145,6 @@ const getFinalPlaceholder = (
147
145
  export function Image({
148
146
  style,
149
147
  className,
150
- loading,
151
148
  objectFit = "cover",
152
149
  quality = 90,
153
150
  alt = "",
@@ -163,38 +160,35 @@ export function Image({
163
160
  ref,
164
161
  aspectRatio,
165
162
  placeholder = "blur",
166
- preload = false,
163
+ priority = false,
167
164
  ...props
168
165
  }: ImageProps) {
169
- // Determine loading strategy
170
- const finalLoading = loading ?? (preload ? "eager" : "lazy")
171
-
172
166
  // Generate responsive sizes if not provided
173
167
  const finalSizes =
174
168
  sizes ||
175
- `(max-width: ${breakpoints.desktop}px) ${mobileSize}, ${desktopSize}`
169
+ `(max-width: ${breakpoints.desktop}px) ${mobileSize}, ${desktopSize}`;
176
170
 
177
171
  // Early return after hooks
178
- if (!src) return null
172
+ if (!src) return null;
179
173
 
180
174
  // Determine SVG status and placeholder logic
181
- const isSvg = typeof src === "string" && src.includes(".svg")
175
+ const isSvg = typeof src === "string" && src.includes(".svg");
182
176
  const shouldUsePlaceholder = shouldUseBlurPlaceholder(
183
177
  src,
184
178
  placeholder,
185
179
  props.blurDataURL
186
- )
180
+ );
187
181
  const blurDataURL = generateBlurDataURL(
188
182
  shouldUsePlaceholder,
189
183
  aspectRatio,
190
184
  props.blurDataURL
191
- )
185
+ );
192
186
  const finalPlaceholder = getFinalPlaceholder(
193
187
  shouldUsePlaceholder,
194
188
  aspectRatio,
195
189
  props.blurDataURL,
196
190
  placeholder
197
- )
191
+ );
198
192
 
199
193
  return (
200
194
  <NextImage
@@ -202,7 +196,7 @@ export function Image({
202
196
  fill={!block}
203
197
  {...(width !== undefined && { width })}
204
198
  {...(height !== undefined && { height })}
205
- loading={finalLoading}
199
+ priority={priority}
206
200
  quality={quality}
207
201
  alt={alt}
208
202
  style={{
@@ -217,8 +211,7 @@ export function Image({
217
211
  onDragStart={(e) => e.preventDefault()}
218
212
  {...(finalPlaceholder && { placeholder: finalPlaceholder })}
219
213
  {...(blurDataURL && { blurDataURL })}
220
- preload={preload}
221
214
  {...props}
222
215
  />
223
- )
216
+ );
224
217
  }
@@ -112,6 +112,12 @@ export function Link({
112
112
  return <div {...getDivProps(props)}>{children}</div>
113
113
  }
114
114
 
115
+ // Block dangerous URIs (javascript:, data:, vbscript:)
116
+ const isDangerousHref = /^(javascript|data|vbscript):/i.test(href)
117
+ if (isDangerousHref) {
118
+ return <span {...getDivProps(props)}>{children}</span>
119
+ }
120
+
115
121
  // For SSR, check if it's external based on the href pattern
116
122
  const isExternalSSR =
117
123
  href.startsWith("http://") || href.startsWith("https://")
@@ -1,4 +1,4 @@
1
- import type { Metadata } from "next"
1
+ import type { Metadata } from "next";
2
2
 
3
3
  /**
4
4
  * Metadata Generation Utilities
@@ -8,26 +8,26 @@ import type { Metadata } from "next"
8
8
  */
9
9
 
10
10
  interface GenerateMetadataOptions {
11
- title?: string
12
- description?: string
13
- keywords?: string[]
11
+ title?: string;
12
+ description?: string;
13
+ keywords?: string[];
14
14
  image?: {
15
- url?: string
16
- width?: number
17
- height?: number
18
- alt?: string
19
- }
20
- url?: string
21
- siteName?: string
22
- noIndex?: boolean
23
- type?: "website" | "article"
24
- publishedTime?: string
25
- modifiedTime?: string
26
- authors?: string[]
15
+ url?: string;
16
+ width?: number;
17
+ height?: number;
18
+ alt?: string;
19
+ };
20
+ url?: string;
21
+ siteName?: string;
22
+ noIndex?: boolean;
23
+ type?: "website" | "article";
24
+ publishedTime?: string;
25
+ modifiedTime?: string;
26
+ authors?: string[];
27
27
  }
28
28
 
29
29
  const APP_BASE_URL =
30
- process.env.NEXT_PUBLIC_BASE_URL ?? "https://localhost:3000"
30
+ process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000";
31
31
 
32
32
  /**
33
33
  * Generate complete metadata object for pages
@@ -62,13 +62,13 @@ export function generatePageMetadata(
62
62
  publishedTime,
63
63
  modifiedTime,
64
64
  authors,
65
- } = options
65
+ } = options;
66
66
 
67
- const fullUrl = url ? `${APP_BASE_URL}${url}` : APP_BASE_URL
68
- const imageUrl = image?.url || "/opengraph-image.jpg"
69
- const imageWidth = image?.width || 1200
70
- const imageHeight = image?.height || 630
71
- const imageAlt = image?.alt || title || siteName
67
+ const fullUrl = url ? `${APP_BASE_URL}${url}` : APP_BASE_URL;
68
+ const imageUrl = image?.url || "/opengraph-image.jpg";
69
+ const imageWidth = image?.width || 1200;
70
+ const imageHeight = image?.height || 630;
71
+ const imageAlt = image?.alt || title || siteName;
72
72
 
73
73
  const metadata: Metadata = {
74
74
  metadataBase: new URL(APP_BASE_URL),
@@ -113,14 +113,14 @@ export function generatePageMetadata(
113
113
  other: {
114
114
  "fb:app_id": process.env.NEXT_PUBLIC_FACEBOOK_APP_ID || "",
115
115
  },
116
- }
116
+ };
117
117
 
118
118
  if (noIndex) {
119
119
  metadata.robots = {
120
120
  index: false,
121
121
  follow: false,
122
- }
122
+ };
123
123
  }
124
124
 
125
- return metadata
125
+ return metadata;
126
126
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "basement-next-starter-experiments",
2
+ "name": "bsmnt-experiments-starter",
3
3
  "description": "Basement Next.js starter template",
4
4
  "version": "0.1.0",
5
5
  "private": true,
@@ -31,7 +31,7 @@
31
31
  "class-variance-authority": "^0.7.1",
32
32
  "leva": "^0.9.35",
33
33
  "lucide-react": "^0.474.0",
34
- "next": "16.1.4",
34
+ "next": "16.1.6",
35
35
  "react": "19.2.4",
36
36
  "react-dom": "19.2.4",
37
37
  "react-use": "^17.6.0",
@@ -41,10 +41,10 @@
41
41
  "zustand": "^5.0.10"
42
42
  },
43
43
  "devDependencies": {
44
- "@biomejs/biome": "2.3.11",
44
+ "@biomejs/biome": "2.3.14",
45
45
  "@clack/prompts": "^0.11.0",
46
46
  "@csstools/postcss-global-data": "^3.1.0",
47
- "@next/bundle-analyzer": "16.1.1",
47
+ "@next/bundle-analyzer": "16.1.6",
48
48
  "@svgr/webpack": "^8.1.0",
49
49
  "@tailwindcss/postcss": "^4.1.18",
50
50
  "@types/bun": "^1.3.6",
@@ -1,23 +1,23 @@
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"
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
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
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
15
  const APP_BASE_URL =
16
- process.env.NEXT_PUBLIC_BASE_URL ?? "https://localhost:3000"
16
+ process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000";
17
17
 
18
18
  const geist = Geist({
19
19
  subsets: ["latin"],
20
- })
20
+ });
21
21
 
22
22
  export const metadata: Metadata = {
23
23
  alternates: {
@@ -70,12 +70,12 @@ export const metadata: Metadata = {
70
70
  template: APP_TITLE_TEMPLATE,
71
71
  },
72
72
  },
73
- }
73
+ };
74
74
 
75
75
  export const viewport: Viewport = {
76
76
  colorScheme: "normal",
77
77
  themeColor: themes.dark.primary,
78
- }
78
+ };
79
79
 
80
80
  export default async function Layout({ children }: PropsWithChildren) {
81
81
  return (
@@ -100,5 +100,5 @@ export default async function Layout({ children }: PropsWithChildren) {
100
100
  {children}
101
101
  </body>
102
102
  </html>
103
- )
103
+ );
104
104
  }
@@ -1,7 +1,7 @@
1
- import type { MetadataRoute } from "next"
1
+ import type { MetadataRoute } from "next";
2
2
 
3
3
  const APP_BASE_URL =
4
- process.env.NEXT_PUBLIC_BASE_URL ?? "https://localhost:3000"
4
+ process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000";
5
5
 
6
6
  export default function robots(): MetadataRoute.Robots {
7
7
  return {
@@ -11,5 +11,5 @@ export default function robots(): MetadataRoute.Robots {
11
11
  disallow: [],
12
12
  },
13
13
  sitemap: `${APP_BASE_URL}/sitemap.xml`,
14
- }
14
+ };
15
15
  }
@@ -1,7 +1,7 @@
1
- import type { MetadataRoute } from "next"
1
+ import type { MetadataRoute } from "next";
2
2
 
3
3
  const APP_BASE_URL =
4
- process.env.NEXT_PUBLIC_BASE_URL ?? "https://localhost:3000"
4
+ process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000";
5
5
 
6
6
  export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
7
7
  const baseRoutes: MetadataRoute.Sitemap = [
@@ -11,6 +11,6 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
11
11
  changeFrequency: "daily",
12
12
  priority: 1,
13
13
  },
14
- ]
15
- return baseRoutes
14
+ ];
15
+ return baseRoutes;
16
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
  }
@@ -47,7 +47,7 @@ export function Theme({
47
47
 
48
48
  return (
49
49
  <>
50
- {global && (
50
+ {global && /^[a-z]+$/.test(currentTheme) && (
51
51
  <script>
52
52
  {`document.documentElement.setAttribute('data-theme', '${currentTheme}');`}
53
53
  </script>
@@ -3,8 +3,6 @@
3
3
  *
4
4
  * Customize the Header and Footer components for your project needs.
5
5
  */
6
- "use client"
7
-
8
6
  import cn from "clsx"
9
7
  import { Footer } from "@/components/layout/footer"
10
8
  import { Header } from "@/components/layout/header"
@@ -102,14 +102,14 @@ const getFinalPlaceholder = (
102
102
  * - Automatic responsive sizes generation
103
103
  * - Smart blur placeholders with aspect ratio support
104
104
  * - Performance optimizations (lazy loading by default)
105
- * - Preload for LCP images
105
+ * - Priority flag for LCP images
106
106
  *
107
107
  * @param props - Image props extending Next.js Image
108
108
  * @param props.aspectRatio - Aspect ratio for layout stability and blur placeholder
109
109
  * @param props.mobileSize - Size on mobile (e.g., "100vw")
110
110
  * @param props.desktopSize - Size on desktop (e.g., "50vw")
111
111
  * @param props.block - Display as block element
112
- * @param props.preload - Enable preloading for LCP images
112
+ * @param props.priority - Prioritize loading for LCP images
113
113
  *
114
114
  * @example
115
115
  * ```tsx
@@ -123,12 +123,12 @@ const getFinalPlaceholder = (
123
123
  *
124
124
  * @example
125
125
  * ```tsx
126
- * // LCP image with preload
126
+ * // LCP image with priority
127
127
  * <Image
128
128
  * src="/hero.jpg"
129
129
  * alt="Hero image"
130
130
  * aspectRatio={16/9}
131
- * preload // Preloads image for LCP
131
+ * priority // Preloads image for LCP
132
132
  * />
133
133
  * ```
134
134
  *
@@ -147,7 +147,6 @@ const getFinalPlaceholder = (
147
147
  export function Image({
148
148
  style,
149
149
  className,
150
- loading,
151
150
  objectFit = "cover",
152
151
  quality = 90,
153
152
  alt = "",
@@ -163,12 +162,9 @@ export function Image({
163
162
  ref,
164
163
  aspectRatio,
165
164
  placeholder = "blur",
166
- preload = false,
165
+ priority = false,
167
166
  ...props
168
167
  }: ImageProps) {
169
- // Determine loading strategy
170
- const finalLoading = loading ?? (preload ? "eager" : "lazy")
171
-
172
168
  // Generate responsive sizes if not provided
173
169
  const finalSizes =
174
170
  sizes ||
@@ -202,7 +198,7 @@ export function Image({
202
198
  fill={!block}
203
199
  {...(width !== undefined && { width })}
204
200
  {...(height !== undefined && { height })}
205
- loading={finalLoading}
201
+ priority={priority}
206
202
  quality={quality}
207
203
  alt={alt}
208
204
  style={{
@@ -217,7 +213,6 @@ export function Image({
217
213
  onDragStart={(e) => e.preventDefault()}
218
214
  {...(finalPlaceholder && { placeholder: finalPlaceholder })}
219
215
  {...(blurDataURL && { blurDataURL })}
220
- preload={preload}
221
216
  {...props}
222
217
  />
223
218
  )
@@ -1,6 +1,7 @@
1
1
  "use client"
2
2
 
3
3
  import NextLink from "next/link"
4
+ import Link from "next/link"
4
5
  import { usePathname } from "next/navigation"
5
6
  import {
6
7
  type AnchorHTMLAttributes,
@@ -112,13 +113,19 @@ export function Link({
112
113
  return <div {...getDivProps(props)}>{children}</div>
113
114
  }
114
115
 
116
+ // Block dangerous URIs (javascript:, data:, vbscript:)
117
+ const isDangerousHref = /^(javascript|data|vbscript):/i.test(href)
118
+ if (isDangerousHref) {
119
+ return <span {...getDivProps(props)}>{children}</span>
120
+ }
121
+
115
122
  // For SSR, check if it's external based on the href pattern
116
123
  const isExternalSSR =
117
124
  href.startsWith("http://") || href.startsWith("https://")
118
125
 
119
126
  if (isExternalSSR || isExternal) {
120
127
  return (
121
- <a
128
+ <Link
122
129
  href={href}
123
130
  target="_blank"
124
131
  rel="noopener noreferrer"
@@ -127,7 +134,7 @@ export function Link({
127
134
  {...props}
128
135
  >
129
136
  {children}
130
- </a>
137
+ </Link>
131
138
  )
132
139
  }
133
140
 
@@ -8,6 +8,7 @@ const Scene = () => {
8
8
  const meshRef = useRef<Mesh>(null)
9
9
 
10
10
  useFrame((_state, delta) => {
11
+ if (!meshRef.current) return
11
12
  meshRef.current.rotation.x += delta
12
13
  meshRef.current.rotation.y += delta
13
14
  })
@@ -1,4 +1,4 @@
1
- import type { Metadata } from "next"
1
+ import type { Metadata } from "next";
2
2
 
3
3
  /**
4
4
  * Metadata Generation Utilities
@@ -8,26 +8,26 @@ import type { Metadata } from "next"
8
8
  */
9
9
 
10
10
  interface GenerateMetadataOptions {
11
- title?: string
12
- description?: string
13
- keywords?: string[]
11
+ title?: string;
12
+ description?: string;
13
+ keywords?: string[];
14
14
  image?: {
15
- url?: string
16
- width?: number
17
- height?: number
18
- alt?: string
19
- }
20
- url?: string
21
- siteName?: string
22
- noIndex?: boolean
23
- type?: "website" | "article"
24
- publishedTime?: string
25
- modifiedTime?: string
26
- authors?: string[]
15
+ url?: string;
16
+ width?: number;
17
+ height?: number;
18
+ alt?: string;
19
+ };
20
+ url?: string;
21
+ siteName?: string;
22
+ noIndex?: boolean;
23
+ type?: "website" | "article";
24
+ publishedTime?: string;
25
+ modifiedTime?: string;
26
+ authors?: string[];
27
27
  }
28
28
 
29
29
  const APP_BASE_URL =
30
- process.env.NEXT_PUBLIC_BASE_URL ?? "https://localhost:3000"
30
+ process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000";
31
31
 
32
32
  /**
33
33
  * Generate complete metadata object for pages
@@ -62,13 +62,13 @@ export function generatePageMetadata(
62
62
  publishedTime,
63
63
  modifiedTime,
64
64
  authors,
65
- } = options
65
+ } = options;
66
66
 
67
- const fullUrl = url ? `${APP_BASE_URL}${url}` : APP_BASE_URL
68
- const imageUrl = image?.url || "/opengraph-image.jpg"
69
- const imageWidth = image?.width || 1200
70
- const imageHeight = image?.height || 630
71
- const imageAlt = image?.alt || title || siteName
67
+ const fullUrl = url ? `${APP_BASE_URL}${url}` : APP_BASE_URL;
68
+ const imageUrl = image?.url || "/opengraph-image.jpg";
69
+ const imageWidth = image?.width || 1200;
70
+ const imageHeight = image?.height || 630;
71
+ const imageAlt = image?.alt || title || siteName;
72
72
 
73
73
  const metadata: Metadata = {
74
74
  metadataBase: new URL(APP_BASE_URL),
@@ -113,14 +113,14 @@ export function generatePageMetadata(
113
113
  other: {
114
114
  "fb:app_id": process.env.NEXT_PUBLIC_FACEBOOK_APP_ID || "",
115
115
  },
116
- }
116
+ };
117
117
 
118
118
  if (noIndex) {
119
119
  metadata.robots = {
120
120
  index: false,
121
121
  follow: false,
122
- }
122
+ };
123
123
  }
124
124
 
125
- return metadata
125
+ return metadata;
126
126
  }