kmcom-nuxt-layers 2.2.5 → 2.2.6

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 (27) hide show
  1. package/layers/animations/app/components/Motion/CountUp.vue +2 -1
  2. package/layers/animations/app/components/Motion/Magnetic.vue +2 -1
  3. package/layers/animations/app/components/Motion/Marquee.vue +6 -2
  4. package/layers/animations/app/components/Motion/MarqueeText.vue +4 -2
  5. package/layers/animations/app/components/Motion/Tilt.vue +2 -1
  6. package/layers/animations/app/composables/useMagneticElement.ts +1 -1
  7. package/layers/animations/app/composables/useMarqueeCopies.ts +1 -1
  8. package/layers/animations/app/composables/useTiltEffect.ts +1 -1
  9. package/layers/content/app/components/Gallery/Lightbox.vue +5 -1
  10. package/layers/content/app/composables/useBlogPosts.ts +2 -2
  11. package/layers/content/app/composables/useCollectionItem.ts +1 -3
  12. package/layers/content/app/composables/useGalleryItems.ts +2 -2
  13. package/layers/core/app/composables/useCache.ts +0 -1
  14. package/layers/core/app/plugins/error-handler.ts +36 -36
  15. package/layers/core/app/plugins/feature-detection.client.ts +15 -15
  16. package/layers/core/app/plugins/init.ts +121 -129
  17. package/layers/core/app/plugins/loading.client.ts +27 -27
  18. package/layers/core/app/plugins/scroll-guard.client.ts +26 -26
  19. package/layers/feeds/app/plugins/feed-head.ts +63 -63
  20. package/layers/forms/server/api/contact.post.ts +1 -1
  21. package/layers/page-transitions/app/plugins/page-transitions.client.ts +9 -9
  22. package/layers/routing/app/plugins/feature-flags.client.ts +9 -9
  23. package/layers/scroll/app/plugins/locomotive-scroll.client.ts +53 -53
  24. package/layers/shader/app/plugins/shader.client.ts +21 -21
  25. package/layers/theme/app/plugins/theme.client.ts +54 -51
  26. package/layers/visual/app/composables/gradient.ts +2 -3
  27. package/package.json +1 -1
@@ -21,7 +21,8 @@
21
21
  as?: string
22
22
  }>()
23
23
 
24
- const el = ref<HTMLElement | null>(null)
24
+ // const el = ref<HTMLElement | null>(null)
25
+ const el = useTemplateRef<HTMLElement | null>(null)
25
26
  const { displayValue, isComplete } = useCountUp(el, {
26
27
  to,
27
28
  from,
@@ -11,7 +11,8 @@
11
11
  stiffness?: number
12
12
  }>()
13
13
 
14
- const el = ref<HTMLElement | null>(null)
14
+ // const el = ref<HTMLElement | null>(null)
15
+ const el = useTemplateRef<HTMLElement | null>(null)
15
16
  const { isActive } = useMagneticElement(el, { strength, radius, damping, stiffness })
16
17
  </script>
17
18
 
@@ -52,8 +52,12 @@
52
52
  const { gsap } = useGsap()
53
53
  const { velocity: scrollVelocity, direction: scrollDirection } = useSmoothScroll()
54
54
 
55
- const containerRef = ref<HTMLElement | null>(null)
56
- const contentRef = ref<HTMLElement | null>(null)
55
+ // const containerRef = ref<HTMLElement | null>(null)
56
+ const containerRef = useTemplateRef<HTMLElement | null>(null)
57
+
58
+ // const contentRef = ref<HTMLElement | null>(null)
59
+ const contentRef = useTemplateRef<HTMLElement | null>(null)
60
+
57
61
  const tweenRef = ref<gsap.core.Tween | null>(null)
58
62
 
59
63
  const isPaused = ref(false)
@@ -35,7 +35,9 @@
35
35
  const { gsap, ScrollTrigger } = useGsap()
36
36
  const { velocityFactor } = useMarqueeVelocity({ damping, stiffness, velocityMapping })
37
37
 
38
- const containerRef = ref<HTMLElement[]>([])
38
+ // const containerRef = ref<HTMLElement[]>([])
39
+ const containerRef = useTemplateRef<HTMLElement[]>([])
40
+
39
41
  const copyRefs = ref<HTMLSpanElement[]>([])
40
42
 
41
43
  const { copyWidths, calculatedCopies } = useMarqueeCopies(
@@ -130,7 +132,7 @@
130
132
  :style="parallaxStyle"
131
133
  >
132
134
  <div
133
- :class="`${scrollerClassName} font-styles-logo flex py-2 text-center font-sans text-4xl font-medium tracking-[-0.02em] whitespace-nowrap text-neutral drop-shadow md:text-[5rem] md:leading-[5rem]`"
135
+ :class="`${scrollerClassName} font-styles-logo flex py-2 text-center font-sans text-4xl font-medium tracking-[-0.02em] whitespace-nowrap text-neutral drop-shadow md:text-[5rem] md:leading-20`"
134
136
  :style="{ transform: `translateX(${scrollTransforms[index] ?? '0px'})`, ...scrollerStyle }"
135
137
  >
136
138
  <span
@@ -11,7 +11,8 @@
11
11
  stiffness?: number
12
12
  }>()
13
13
 
14
- const el = ref<HTMLElement | null>(null)
14
+ // const el = ref<HTMLElement | null>(null)
15
+ const el = useTemplateRef<HTMLElement | null>(null)
15
16
  useTiltEffect(el, { maxTilt, perspective, damping, stiffness })
16
17
  </script>
17
18
 
@@ -5,7 +5,7 @@ export function useMagneticElement(
5
5
  radius?: number
6
6
  damping?: number
7
7
  stiffness?: number
8
- } = {},
8
+ } = {}
9
9
  ) {
10
10
  const { gsap } = useGsap()
11
11
  const { elementX, elementY, elementWidth, elementHeight } = useMouseInElement(elementRef)
@@ -3,7 +3,7 @@ import type { MaybeRef } from 'vue'
3
3
  export function useMarqueeCopies(
4
4
  containerRefs: Ref<HTMLElement[]>,
5
5
  copyRefs: Ref<HTMLSpanElement[]>,
6
- rowCount: MaybeRef<number>,
6
+ rowCount: MaybeRef<number>
7
7
  ) {
8
8
  const copyWidths = ref<number[]>([])
9
9
  const calculatedCopies = ref<number[]>([])
@@ -5,7 +5,7 @@ export function useTiltEffect(
5
5
  perspective?: number
6
6
  damping?: number
7
7
  stiffness?: number
8
- } = {},
8
+ } = {}
9
9
  ) {
10
10
  const { gsap } = useGsap()
11
11
  const { elementX, elementY, elementWidth, elementHeight, isOutside } =
@@ -57,7 +57,11 @@
57
57
  color="neutral"
58
58
  variant="ghost"
59
59
  class="absolute top-4 right-4 text-white"
60
- @click="() => { open = false }"
60
+ @click="
61
+ () => {
62
+ open = false
63
+ }
64
+ "
61
65
  />
62
66
 
63
67
  <!-- Prev -->
@@ -4,8 +4,8 @@ export function useBlogPosts(options: BlogQueryOptions = {}) {
4
4
  const { excludeDrafts = true, tags, limit } = options
5
5
 
6
6
  return useContentData('blog-posts', async () => {
7
- let posts = (await queryCollection('blog').all()).sort(
8
- (a, b) => (b.date ?? '').localeCompare(a.date ?? '')
7
+ let posts = (await queryCollection('blog').all()).sort((a, b) =>
8
+ (b.date ?? '').localeCompare(a.date ?? '')
9
9
  )
10
10
 
11
11
  if (excludeDrafts) {
@@ -2,8 +2,6 @@ import type { Collections } from '@nuxt/content'
2
2
 
3
3
  export function useCollectionItem(collection: keyof Collections, slug: string) {
4
4
  return useContentData(`${collection}-${slug}`, () =>
5
- queryCollection(collection)
6
- .path(`/${collection}/${slug}`)
7
- .first()
5
+ queryCollection(collection).path(`/${collection}/${slug}`).first()
8
6
  )
9
7
  }
@@ -4,8 +4,8 @@ export function useGalleryItems(options: GalleryQueryOptions = {}) {
4
4
  const { tags, limit } = options
5
5
 
6
6
  return useContentData('gallery-items', async () => {
7
- let items = (await queryCollection('gallery').all()).sort(
8
- (a, b) => (b.date ?? '').localeCompare(a.date ?? '')
7
+ let items = (await queryCollection('gallery').all()).sort((a, b) =>
8
+ (b.date ?? '').localeCompare(a.date ?? '')
9
9
  )
10
10
 
11
11
  if (tags?.length) {
@@ -1,4 +1,3 @@
1
-
2
1
  // composables/useCache.ts
3
2
  import { useOnline } from '@vueuse/core'
4
3
 
@@ -1,49 +1,49 @@
1
1
  export default defineNuxtPlugin({
2
2
  name: 'core:error-handler',
3
3
  setup(nuxtApp) {
4
- const { logError } = useErrorLog()
4
+ const { logError } = useErrorLog()
5
5
 
6
- // Global Vue error handler
7
- nuxtApp.vueApp.config.errorHandler = (error, instance, info) => {
8
- // Get component name for context
9
- const componentName =
10
- instance?.$options?.name || instance?.$options?.__name || 'Unknown Component'
6
+ // Global Vue error handler
7
+ nuxtApp.vueApp.config.errorHandler = (error, instance, info) => {
8
+ // Get component name for context
9
+ const componentName =
10
+ instance?.$options?.name || instance?.$options?.__name || 'Unknown Component'
11
11
 
12
- // Log error with context
13
- logError(error, {
14
- component: componentName,
15
- info: String(info),
16
- type: 'vue-error',
17
- })
18
- }
12
+ // Log error with context
13
+ logError(error, {
14
+ component: componentName,
15
+ info: String(info),
16
+ type: 'vue-error',
17
+ })
18
+ }
19
19
 
20
- // Nuxt-specific error hook (catches errors during SSR, routing, etc.)
21
- nuxtApp.hook('vue:error', (error, instance, info) => {
22
- // Get component name for context
23
- const componentName =
24
- instance?.$options?.name || instance?.$options?.__name || 'Unknown Component'
20
+ // Nuxt-specific error hook (catches errors during SSR, routing, etc.)
21
+ nuxtApp.hook('vue:error', (error, instance, info) => {
22
+ // Get component name for context
23
+ const componentName =
24
+ instance?.$options?.name || instance?.$options?.__name || 'Unknown Component'
25
25
 
26
- // Log error with context
27
- logError(error, {
28
- component: componentName,
29
- info: String(info),
30
- type: 'nuxt-error',
26
+ // Log error with context
27
+ logError(error, {
28
+ component: componentName,
29
+ info: String(info),
30
+ type: 'nuxt-error',
31
+ })
31
32
  })
32
- })
33
33
 
34
- // Handle app initialization errors
35
- nuxtApp.hook('app:error', (error) => {
36
- logError(error, {
37
- type: 'app-error',
38
- info: 'Application initialization error',
34
+ // Handle app initialization errors
35
+ nuxtApp.hook('app:error', (error) => {
36
+ logError(error, {
37
+ type: 'app-error',
38
+ info: 'Application initialization error',
39
+ })
39
40
  })
40
- })
41
41
 
42
- // Handle page errors
43
- nuxtApp.hook('app:error:cleared', () => {
44
- if (import.meta.dev) {
45
- console.log('✅ Error cleared')
46
- }
47
- })
42
+ // Handle page errors
43
+ nuxtApp.hook('app:error:cleared', () => {
44
+ if (import.meta.dev) {
45
+ console.log('✅ Error cleared')
46
+ }
47
+ })
48
48
  },
49
49
  })
@@ -14,21 +14,21 @@
14
14
  export default defineNuxtPlugin({
15
15
  name: 'core:feature-detection',
16
16
  setup() {
17
- // Initialize feature detection
18
- const features = useFeatures()
17
+ // Initialize feature detection
18
+ const features = useFeatures()
19
19
 
20
- if (import.meta.dev && process.env.NODE_ENV === 'development') {
21
- console.log('[Feature Detection] Initialized:', {
22
- grid: features.grid.value,
23
- subgrid: features.subgrid.value,
24
- containerQueries: features.containerQueries.value,
25
- has: features.has.value,
26
- webGL: features.webGL.value,
27
- darkMode: features.darkMode.value,
28
- reducedMotion: features.reducedMotion.value,
29
- webp: features.webp.value,
30
- avif: features.avif.value,
31
- })
32
- }
20
+ if (import.meta.dev && process.env.NODE_ENV === 'development') {
21
+ console.log('[Feature Detection] Initialized:', {
22
+ grid: features.grid.value,
23
+ subgrid: features.subgrid.value,
24
+ containerQueries: features.containerQueries.value,
25
+ has: features.has.value,
26
+ webGL: features.webGL.value,
27
+ darkMode: features.darkMode.value,
28
+ reducedMotion: features.reducedMotion.value,
29
+ webp: features.webp.value,
30
+ avif: features.avif.value,
31
+ })
32
+ }
33
33
  },
34
34
  })
@@ -12,167 +12,159 @@
12
12
  export default defineNuxtPlugin({
13
13
  name: 'core:init',
14
14
  setup(nuxtApp) {
15
- const config = useAppConfig()
16
- // const isDev = import.meta.dev
17
- const isDev = process.env.NODE_ENV === 'development'
18
-
19
- // ============================================================
20
- // 1. Log initialization (dev only)
21
- // ============================================================
22
- if (isDev) {
23
- console.log('🚀 [Core Layer] Initializing...')
24
-
25
- console.log('[Core Layer] Config:', config.coreLayer)
26
- }
27
-
28
- // ============================================================
29
- // 2. Verify modules and composables are loaded
30
- // ============================================================
31
- try {
32
- // Test @nuxtjs/device module (SSR-safe: returns undefined before device plugin runs)
33
- const device = useDevice()
34
-
35
- if (isDev && device) {
36
- console.log('[Core Layer] Device detection:', {
37
- mobile: device.isMobile,
38
- desktop: device.isDesktop,
39
- tablet: device.isTablet,
40
- })
41
- }
42
-
43
- // Test @vueuse/nuxt module
44
- const isOnline = useOnline()
15
+ const config = useAppConfig()
16
+ // const isDev = import.meta.dev
17
+ const isDev = process.env.NODE_ENV === 'development'
45
18
 
19
+ // 1. Log initialization (dev only)
46
20
  if (isDev) {
47
- console.log('[Core Layer] VueUse loaded, online status:', isOnline.value)
21
+ console.log('🚀 [Core Layer] Initializing...')
22
+
23
+ console.log('[Core Layer] Config:', config.coreLayer)
48
24
  }
49
25
 
50
- // Client-only detection (requires browser APIs)
51
- if (import.meta.client) {
52
- // Test browser detection composable
53
- const { name, version, engine, os } = useBrowser()
26
+ // 2. Verify modules and composables are loaded
27
+ try {
28
+ // Test @nuxtjs/device module (SSR-safe: returns undefined before device plugin runs)
29
+ const device = useDevice()
54
30
 
55
- if (isDev) {
56
- console.log('[Core Layer] Browser detection:', {
57
- name: name.value,
58
- version: version.value,
59
- engine: engine.value,
60
- os: os.value,
31
+ if (isDev && device) {
32
+ console.log('[Core Layer] Device detection:', {
33
+ mobile: device.isMobile,
34
+ desktop: device.isDesktop,
35
+ tablet: device.isTablet,
61
36
  })
62
37
  }
63
38
 
64
- // Test screen/breakpoint composable
65
- const { breakpoint, isRetina, orientation } = useScreen()
39
+ // Test @vueuse/nuxt module
40
+ const isOnline = useOnline()
66
41
 
67
42
  if (isDev) {
68
- console.log('[Core Layer] Screen detection:', {
69
- breakpoint: breakpoint.value,
70
- retina: isRetina.value,
71
- orientation: orientation.value,
72
- })
43
+ console.log('[Core Layer] VueUse loaded, online status:', isOnline.value)
73
44
  }
74
45
 
75
- // Test network info composable
76
- const { connectionQuality, effectiveType, saveData } = useNetworkInfo()
77
-
78
- if (isDev) {
79
- console.log('[Core Layer] Network detection:', {
80
- quality: connectionQuality.value,
81
- type: effectiveType.value,
82
- dataSaver: saveData.value,
83
- })
46
+ // Client-only detection (requires browser APIs)
47
+ if (import.meta.client) {
48
+ // Test browser detection composable
49
+ const { name, version, engine, os } = useBrowser()
50
+
51
+ if (isDev) {
52
+ console.log('[Core Layer] Browser detection:', {
53
+ name: name.value,
54
+ version: version.value,
55
+ engine: engine.value,
56
+ os: os.value,
57
+ })
58
+ }
59
+
60
+ // Test screen/breakpoint composable
61
+ const { breakpoint, isRetina, orientation } = useScreen()
62
+
63
+ if (isDev) {
64
+ console.log('[Core Layer] Screen detection:', {
65
+ breakpoint: breakpoint.value,
66
+ retina: isRetina.value,
67
+ orientation: orientation.value,
68
+ })
69
+ }
70
+
71
+ // Test network info composable
72
+ const { connectionQuality, effectiveType, saveData } = useNetworkInfo()
73
+
74
+ if (isDev) {
75
+ console.log('[Core Layer] Network detection:', {
76
+ quality: connectionQuality.value,
77
+ type: effectiveType.value,
78
+ dataSaver: saveData.value,
79
+ })
80
+ }
81
+
82
+ // Test feature detection composable
83
+ const { grid, subgrid, containerQueries, webGL, darkMode } = useFeatures()
84
+
85
+ if (isDev) {
86
+ console.log('[Core Layer] Feature detection:', {
87
+ grid: grid.value,
88
+ subgrid: subgrid.value,
89
+ containerQueries: containerQueries.value,
90
+ webGL: webGL.value,
91
+ darkMode: darkMode.value,
92
+ })
93
+ }
94
+
95
+ // Test cache management composable
96
+ const { offlineReady, isOnline: cacheOnline } = useCache()
97
+
98
+ if (isDev) {
99
+ console.log('[Core Layer] Cache status:', {
100
+ online: cacheOnline.value,
101
+ offlineReady: offlineReady.value,
102
+ })
103
+ }
104
+
105
+ // PWA composable is only available in production
106
+ // Use usePWAInfo() from core layer for PWA status
84
107
  }
85
108
 
86
- // Test feature detection composable
87
- const { grid, subgrid, containerQueries, webGL, darkMode } = useFeatures()
109
+ // Test rendering mode detection (works on both server and client)
110
+ const { mode, isServer, isClient, isHydrated } = useRendering()
88
111
 
89
112
  if (isDev) {
90
- console.log('[Core Layer] Feature detection:', {
91
- grid: grid.value,
92
- subgrid: subgrid.value,
93
- containerQueries: containerQueries.value,
94
- webGL: webGL.value,
95
- darkMode: darkMode.value,
113
+ console.log('[Core Layer] Rendering mode:', {
114
+ mode: mode.value,
115
+ server: isServer.value,
116
+ client: isClient.value,
117
+ hydrated: isHydrated.value,
96
118
  })
97
119
  }
98
120
 
99
- // Test cache management composable
100
- const { offlineReady, isOnline: cacheOnline } = useCache()
121
+ // Test environment access (works on both server and client)
122
+ const env = useEnv() as unknown as Record<string, unknown>
101
123
 
102
124
  if (isDev) {
103
- console.log('[Core Layer] Cache status:', {
104
- online: cacheOnline.value,
105
- offlineReady: offlineReady.value,
125
+ const publicConfig = env.public as Record<string, unknown> | undefined
126
+
127
+ console.log('[Core Layer] Environment config loaded:', {
128
+ hasPublicConfig: Boolean(publicConfig),
129
+ publicKeys: Object.keys(publicConfig ?? {}),
106
130
  })
107
131
  }
108
-
109
- // PWA composable is only available in production
110
- // Use usePWAInfo() from core layer for PWA status
132
+ } catch (error) {
133
+ console.error('[Core Layer] Module verification failed:', error)
111
134
  }
112
135
 
113
- // Test rendering mode detection (works on both server and client)
114
- const { mode, isServer, isClient, isHydrated } = useRendering()
115
-
136
+ // 3. App lifecycle hooks (dev logging)
116
137
  if (isDev) {
117
- console.log('[Core Layer] Rendering mode:', {
118
- mode: mode.value,
119
- server: isServer.value,
120
- client: isClient.value,
121
- hydrated: isHydrated.value,
138
+ nuxtApp.hook('app:created', () => {
139
+ console.log('✅ [Core Layer] App created')
122
140
  })
123
- }
124
141
 
125
- // Test environment access (works on both server and client)
126
- const env = useEnv() as unknown as Record<string, unknown>
142
+ nuxtApp.hook('app:beforeMount', () => {
143
+ console.log('⏳ [Core Layer] App mounting...')
144
+ })
127
145
 
128
- if (isDev) {
129
- const publicConfig = env.public as Record<string, unknown> | undefined
146
+ nuxtApp.hook('app:mounted', () => {
147
+ console.log('✅ [Core Layer] App mounted')
148
+ })
149
+
150
+ nuxtApp.hook('page:start', () => {
151
+ console.log('📄 [Core Layer] Page navigation started')
152
+ })
130
153
 
131
- console.log('[Core Layer] Environment config loaded:', {
132
- hasPublicConfig: Boolean(publicConfig),
133
- publicKeys: Object.keys(publicConfig ?? {}),
154
+ nuxtApp.hook('page:finish', () => {
155
+ console.log('✅ [Core Layer] Page navigation finished')
134
156
  })
135
157
  }
136
- } catch (error) {
137
- console.error('[Core Layer] Module verification failed:', error)
138
- }
139
-
140
- // ============================================================
141
- // 3. App lifecycle hooks (dev logging)
142
- // ============================================================
143
- if (isDev) {
144
- nuxtApp.hook('app:created', () => {
145
- console.log('✅ [Core Layer] App created')
146
- })
147
-
148
- nuxtApp.hook('app:beforeMount', () => {
149
- console.log('⏳ [Core Layer] App mounting...')
150
- })
151
-
152
- nuxtApp.hook('app:mounted', () => {
153
- console.log('✅ [Core Layer] App mounted')
154
- })
155
-
156
- nuxtApp.hook('page:start', () => {
157
- console.log('📄 [Core Layer] Page navigation started')
158
- })
159
-
160
- nuxtApp.hook('page:finish', () => {
161
- console.log('✅ [Core Layer] Page navigation finished')
162
- })
163
- }
164
-
165
- // ============================================================
166
- // 4. Provide global helpers (optional)
167
- // ============================================================
168
- // Make core utilities available throughout the app
169
- return {
170
- provide: {
171
- coreLayer: {
172
- version: '1.0.0',
173
- initialized: true,
158
+
159
+ // 4. Provide global helpers (optional)
160
+ // Make core utilities available throughout the app
161
+ return {
162
+ provide: {
163
+ coreLayer: {
164
+ version: '1.0.0',
165
+ initialized: true,
166
+ },
174
167
  },
175
- },
176
- }
168
+ }
177
169
  },
178
170
  })
@@ -26,32 +26,32 @@ type CoreLayerConfig = {
26
26
  export default defineNuxtPlugin({
27
27
  name: 'core:loading',
28
28
  setup(nuxtApp) {
29
- const config = useAppConfig()
30
- const coreLayer = config.coreLayer as CoreLayerConfig | undefined
31
-
32
- // console.log('[Loading Plugin] Config:', coreLayer?.loading)
33
-
34
- // Check if loading is enabled
35
- if (coreLayer?.loading?.enabled === false) {
36
- // console.log('[Loading Plugin] Disabled')
37
- return // Disabled, skip initialization
38
- }
39
-
40
- const { startLoading, stopLoading } = useLoading()
41
-
42
- // console.log('[Loading Plugin] Starting loading...')
43
- // Start loading immediately on plugin load
44
- startLoading()
45
-
46
- // Stop loading when app is fully mounted
47
- nuxtApp.hook('app:mounted', () => {
48
- // console.log('[Loading Plugin] App mounted, will stop in 500ms')
49
- // Add a small delay to ensure LoadingScreen component has mounted
50
- // and is visible before we stop loading
51
- setTimeout(() => {
52
- // console.log('[Loading Plugin] Stopping loading now')
53
- stopLoading()
54
- }, 500) // 500ms delay to ensure component is visible
55
- })
29
+ const config = useAppConfig()
30
+ const coreLayer = config.coreLayer as CoreLayerConfig | undefined
31
+
32
+ // console.log('[Loading Plugin] Config:', coreLayer?.loading)
33
+
34
+ // Check if loading is enabled
35
+ if (coreLayer?.loading?.enabled === false) {
36
+ // console.log('[Loading Plugin] Disabled')
37
+ return // Disabled, skip initialization
38
+ }
39
+
40
+ const { startLoading, stopLoading } = useLoading()
41
+
42
+ // console.log('[Loading Plugin] Starting loading...')
43
+ // Start loading immediately on plugin load
44
+ startLoading()
45
+
46
+ // Stop loading when app is fully mounted
47
+ nuxtApp.hook('app:mounted', () => {
48
+ // console.log('[Loading Plugin] App mounted, will stop in 500ms')
49
+ // Add a small delay to ensure LoadingScreen component has mounted
50
+ // and is visible before we stop loading
51
+ setTimeout(() => {
52
+ // console.log('[Loading Plugin] Stopping loading now')
53
+ stopLoading()
54
+ }, 500) // 500ms delay to ensure component is visible
55
+ })
56
56
  },
57
57
  })
@@ -23,34 +23,34 @@ type CoreLayerConfig = {
23
23
  export default defineNuxtPlugin({
24
24
  name: 'core:scroll-guard',
25
25
  setup() {
26
- const config = useAppConfig()
27
- const coreLayer = config.coreLayer as CoreLayerConfig | undefined
26
+ const config = useAppConfig()
27
+ const coreLayer = config.coreLayer as CoreLayerConfig | undefined
28
+
29
+ if (coreLayer?.scrollGuard?.enabled === false) {
30
+ if (import.meta.dev) {
31
+ console.log('[Scroll Guard] Disabled via config')
32
+ }
33
+ return
34
+ }
35
+
36
+ const { enable, clampedCount } = useScrollGuard()
37
+
38
+ enable()
28
39
 
29
- if (coreLayer?.scrollGuard?.enabled === false) {
30
40
  if (import.meta.dev) {
31
- console.log('[Scroll Guard] Disabled via config')
41
+ console.log('[Scroll Guard] Initialized', {
42
+ strict: coreLayer?.scrollGuard?.strict ?? true,
43
+ debug: coreLayer?.scrollGuard?.debug ?? false,
44
+ excludeSelectors: coreLayer?.scrollGuard?.excludeSelectors ?? [
45
+ '.carousel',
46
+ '.overflow-intent',
47
+ ],
48
+ })
49
+
50
+ // Log clamped count after initial scan settles
51
+ requestAnimationFrame(() => {
52
+ console.log(`[Scroll Guard] Clamped ${clampedCount.value} element(s)`)
53
+ })
32
54
  }
33
- return
34
- }
35
-
36
- const { enable, clampedCount } = useScrollGuard()
37
-
38
- enable()
39
-
40
- if (import.meta.dev) {
41
- console.log('[Scroll Guard] Initialized', {
42
- strict: coreLayer?.scrollGuard?.strict ?? true,
43
- debug: coreLayer?.scrollGuard?.debug ?? false,
44
- excludeSelectors: coreLayer?.scrollGuard?.excludeSelectors ?? [
45
- '.carousel',
46
- '.overflow-intent',
47
- ],
48
- })
49
-
50
- // Log clamped count after initial scan settles
51
- requestAnimationFrame(() => {
52
- console.log(`[Scroll Guard] Clamped ${clampedCount.value} element(s)`)
53
- })
54
- }
55
55
  },
56
56
  })
@@ -1,74 +1,74 @@
1
1
  export default defineNuxtPlugin({
2
2
  name: 'feeds:feed-head',
3
3
  setup() {
4
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
5
- const appConfig = useAppConfig() as any
6
- const site = appConfig.site ?? {}
7
- const feedConfig = appConfig.feedsLayer?.feed ?? {}
8
- const collections: string[] = feedConfig.collections ?? ['blog']
9
- const defaultCollection: string = feedConfig.defaultCollection ?? 'blog'
10
- const siteTitle: string = site.title ?? ''
4
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5
+ const appConfig = useAppConfig() as any
6
+ const site = appConfig.site ?? {}
7
+ const feedConfig = appConfig.feedsLayer?.feed ?? {}
8
+ const collections: string[] = feedConfig.collections ?? ['blog']
9
+ const defaultCollection: string = feedConfig.defaultCollection ?? 'blog'
10
+ const siteTitle: string = site.title ?? ''
11
11
 
12
- const route = useRoute()
12
+ const route = useRoute()
13
13
 
14
- useHead(() => {
15
- // Derive which collection (if any) the current page belongs to
16
- const segment = route.path.split('/').filter(Boolean)[0] ?? ''
17
- const currentCollection = collections.includes(segment) ? segment : null
14
+ useHead(() => {
15
+ // Derive which collection (if any) the current page belongs to
16
+ const segment = route.path.split('/').filter(Boolean)[0] ?? ''
17
+ const currentCollection = collections.includes(segment) ? segment : null
18
18
 
19
- // Always present: main site feeds via the shorthand routes
20
- const mainLinks = [
21
- {
22
- rel: 'alternate',
23
- type: 'application/rss+xml',
24
- title: `${siteTitle || 'Site'} (RSS)`,
25
- href: '/feed/rss',
26
- },
27
- {
28
- rel: 'alternate',
29
- type: 'application/atom+xml',
30
- title: `${siteTitle || 'Site'} (Atom)`,
31
- href: '/feed/atom',
32
- },
33
- {
34
- rel: 'alternate',
35
- type: 'application/feed+json',
36
- title: `${siteTitle || 'Site'} (JSON Feed)`,
37
- href: '/feed/json',
38
- },
39
- ]
19
+ // Always present: main site feeds via the shorthand routes
20
+ const mainLinks = [
21
+ {
22
+ rel: 'alternate',
23
+ type: 'application/rss+xml',
24
+ title: `${siteTitle || 'Site'} (RSS)`,
25
+ href: '/feed/rss',
26
+ },
27
+ {
28
+ rel: 'alternate',
29
+ type: 'application/atom+xml',
30
+ title: `${siteTitle || 'Site'} (Atom)`,
31
+ href: '/feed/atom',
32
+ },
33
+ {
34
+ rel: 'alternate',
35
+ type: 'application/feed+json',
36
+ title: `${siteTitle || 'Site'} (JSON Feed)`,
37
+ href: '/feed/json',
38
+ },
39
+ ]
40
40
 
41
- // Collection-specific feeds — only when on a non-default collection's pages.
42
- // The default collection is already covered by the main shorthand links above.
43
- const label =
44
- currentCollection && currentCollection !== defaultCollection
45
- ? currentCollection.charAt(0).toUpperCase() + currentCollection.slice(1)
46
- : null
41
+ // Collection-specific feeds — only when on a non-default collection's pages.
42
+ // The default collection is already covered by the main shorthand links above.
43
+ const label =
44
+ currentCollection && currentCollection !== defaultCollection
45
+ ? currentCollection.charAt(0).toUpperCase() + currentCollection.slice(1)
46
+ : null
47
47
 
48
- const collectionLinks = label
49
- ? [
50
- {
51
- rel: 'alternate',
52
- type: 'application/rss+xml',
53
- title: `${label} (RSS)`,
54
- href: `/feed/${currentCollection}/rss`,
55
- },
56
- {
57
- rel: 'alternate',
58
- type: 'application/atom+xml',
59
- title: `${label} (Atom)`,
60
- href: `/feed/${currentCollection}/atom`,
61
- },
62
- {
63
- rel: 'alternate',
64
- type: 'application/feed+json',
65
- title: `${label} (JSON Feed)`,
66
- href: `/feed/${currentCollection}/json`,
67
- },
68
- ]
69
- : []
48
+ const collectionLinks = label
49
+ ? [
50
+ {
51
+ rel: 'alternate',
52
+ type: 'application/rss+xml',
53
+ title: `${label} (RSS)`,
54
+ href: `/feed/${currentCollection}/rss`,
55
+ },
56
+ {
57
+ rel: 'alternate',
58
+ type: 'application/atom+xml',
59
+ title: `${label} (Atom)`,
60
+ href: `/feed/${currentCollection}/atom`,
61
+ },
62
+ {
63
+ rel: 'alternate',
64
+ type: 'application/feed+json',
65
+ title: `${label} (JSON Feed)`,
66
+ href: `/feed/${currentCollection}/json`,
67
+ },
68
+ ]
69
+ : []
70
70
 
71
- return { link: [...mainLinks, ...collectionLinks] }
72
- })
71
+ return { link: [...mainLinks, ...collectionLinks] }
72
+ })
73
73
  },
74
74
  })
@@ -1,6 +1,6 @@
1
- import { z } from 'zod'
2
1
  import { sendContactEmail } from '#layers/mailer/server/utils/email'
3
2
  import { mailerLayerHooks } from '#layers/mailer/server/utils/hooks'
3
+ import { z } from 'zod'
4
4
 
5
5
  const contactSchema = z.object({
6
6
  name: z.string().min(3, 'Name must be at least 3 characters'),
@@ -1,16 +1,16 @@
1
1
  export default defineNuxtPlugin({
2
2
  name: 'page-transitions:page-transitions',
3
3
  setup(nuxtApp) {
4
- const appConfig = useAppConfig()
4
+ const appConfig = useAppConfig()
5
5
 
6
- const defaultTransition = appConfig.pageTransitions?.default ?? 'fade'
7
- const duration = appConfig.pageTransitions?.duration ?? 300
6
+ const defaultTransition = appConfig.pageTransitions?.default ?? 'fade'
7
+ const duration = appConfig.pageTransitions?.duration ?? 300
8
8
 
9
- nuxtApp.hook('page:start', () => {
10
- useState('page-transition:current').value = {
11
- name: defaultTransition,
12
- duration,
13
- }
14
- })
9
+ nuxtApp.hook('page:start', () => {
10
+ useState('page-transition:current').value = {
11
+ name: defaultTransition,
12
+ duration,
13
+ }
14
+ })
15
15
  },
16
16
  })
@@ -4,15 +4,15 @@ import type { FeatureValue } from '../types/routing'
4
4
  export default defineNuxtPlugin({
5
5
  name: 'routing:feature-flags',
6
6
  async setup() {
7
- const { config } = useRoutingConfig()
8
- if (!config.runtimeFlags) return
7
+ const { config } = useRoutingConfig()
8
+ if (!config.runtimeFlags) return
9
9
 
10
- const { runtimeFlags } = useFeatureFlags()
11
- try {
12
- const data = await $fetch<Record<string, FeatureValue>>('/api/feature-flags')
13
- runtimeFlags.value = data
14
- } catch (e) {
15
- if (config.debug) console.warn('[routing] Failed to fetch feature flags', e)
16
- }
10
+ const { runtimeFlags } = useFeatureFlags()
11
+ try {
12
+ const data = await $fetch<Record<string, FeatureValue>>('/api/feature-flags')
13
+ runtimeFlags.value = data
14
+ } catch (e) {
15
+ if (config.debug) console.warn('[routing] Failed to fetch feature flags', e)
16
+ }
17
17
  },
18
18
  })
@@ -12,67 +12,67 @@ export type ScrollState = {
12
12
  export default defineNuxtPlugin({
13
13
  name: 'scroll:locomotive-scroll',
14
14
  setup() {
15
- const scrollState = reactive<ScrollState>({
16
- scroll: 0,
17
- limit: 0,
18
- velocity: 0,
19
- direction: 0,
20
- progress: 0,
21
- })
15
+ const scrollState = reactive<ScrollState>({
16
+ scroll: 0,
17
+ limit: 0,
18
+ velocity: 0,
19
+ direction: 0,
20
+ progress: 0,
21
+ })
22
22
 
23
- const instance = shallowRef<LocomotiveScroll | null>(null)
23
+ const instance = shallowRef<LocomotiveScroll | null>(null)
24
24
 
25
- function init() {
26
- if (instance.value) return
27
- instance.value = new LocomotiveScroll({
28
- lenisOptions: {
29
- lerp: 0.1,
30
- smoothWheel: true,
31
- wheelMultiplier: 1,
32
- },
33
- scrollCallback: ({ scroll, limit, velocity, direction, progress }) => {
34
- scrollState.scroll = scroll
35
- scrollState.limit = limit
36
- scrollState.velocity = velocity
37
- scrollState.direction = direction
38
- scrollState.progress = progress
39
- ScrollTrigger.update()
40
- },
41
- })
42
- }
25
+ function init() {
26
+ if (instance.value) return
27
+ instance.value = new LocomotiveScroll({
28
+ lenisOptions: {
29
+ lerp: 0.1,
30
+ smoothWheel: true,
31
+ wheelMultiplier: 1,
32
+ },
33
+ scrollCallback: ({ scroll, limit, velocity, direction, progress }) => {
34
+ scrollState.scroll = scroll
35
+ scrollState.limit = limit
36
+ scrollState.velocity = velocity
37
+ scrollState.direction = direction
38
+ scrollState.progress = progress
39
+ ScrollTrigger.update()
40
+ },
41
+ })
42
+ }
43
43
 
44
- function destroy() {
45
- instance.value?.destroy()
46
- instance.value = null
47
- }
44
+ function destroy() {
45
+ instance.value?.destroy()
46
+ instance.value = null
47
+ }
48
48
 
49
- const router = useRouter()
50
- const appConfig = useAppConfig()
51
- const smoothScroll: boolean | string[] = appConfig.scroll?.smoothScroll ?? true
49
+ const router = useRouter()
50
+ const appConfig = useAppConfig()
51
+ const smoothScroll: boolean | string[] = appConfig.scroll?.smoothScroll ?? true
52
52
 
53
- const nuxtApp = useNuxtApp()
53
+ const nuxtApp = useNuxtApp()
54
54
 
55
- if (smoothScroll === true) {
56
- nuxtApp.hook('app:mounted', () => nextTick(init))
57
- nuxtApp.hook('page:finish', () => nextTick(() => ScrollTrigger.refresh()))
58
- } else if (Array.isArray(smoothScroll)) {
59
- addRouteMiddleware((to, from) => {
60
- if (smoothScroll.includes(to.path)) {
61
- nextTick(init)
62
- } else if (from?.path && smoothScroll.includes(from.path)) {
63
- destroy()
64
- }
65
- })
66
- if (smoothScroll.includes(router.currentRoute.value.path)) {
55
+ if (smoothScroll === true) {
67
56
  nuxtApp.hook('app:mounted', () => nextTick(init))
57
+ nuxtApp.hook('page:finish', () => nextTick(() => ScrollTrigger.refresh()))
58
+ } else if (Array.isArray(smoothScroll)) {
59
+ addRouteMiddleware((to, from) => {
60
+ if (smoothScroll.includes(to.path)) {
61
+ nextTick(init)
62
+ } else if (from?.path && smoothScroll.includes(from.path)) {
63
+ destroy()
64
+ }
65
+ })
66
+ if (smoothScroll.includes(router.currentRoute.value.path)) {
67
+ nuxtApp.hook('app:mounted', () => nextTick(init))
68
+ }
68
69
  }
69
- }
70
70
 
71
- return {
72
- provide: {
73
- locomotiveScroll: instance,
74
- scrollState,
75
- },
76
- }
71
+ return {
72
+ provide: {
73
+ locomotiveScroll: instance,
74
+ scrollState,
75
+ },
76
+ }
77
77
  },
78
78
  })
@@ -3,30 +3,30 @@ import { checkWebGPUSupport } from '#layers/canvas/app/composables/useRendererCa
3
3
  export default defineNuxtPlugin({
4
4
  name: 'shader:shader',
5
5
  async setup() {
6
- const config = useAppConfig()
7
- const shaderConfig = (config.shader || {}) as {
8
- preferWebGPU?: boolean
9
- }
6
+ const config = useAppConfig()
7
+ const shaderConfig = (config.shader || {}) as {
8
+ preferWebGPU?: boolean
9
+ }
10
10
 
11
- // Check WebGPU support
12
- const hasWebGPU = await checkWebGPUSupport()
13
- const useWebGPU = shaderConfig.preferWebGPU && hasWebGPU
11
+ // Check WebGPU support
12
+ const hasWebGPU = await checkWebGPUSupport()
13
+ const useWebGPU = shaderConfig.preferWebGPU && hasWebGPU
14
14
 
15
- // Log renderer info in development
16
- if (import.meta.dev) {
17
- console.log('[Shader Layer] Initialized')
18
- console.log(`[Shader Layer] WebGPU supported: ${hasWebGPU}`)
19
- console.log(`[Shader Layer] Using: ${useWebGPU ? 'WebGPU' : 'WebGL'}`)
20
- }
15
+ // Log renderer info in development
16
+ if (import.meta.dev) {
17
+ console.log('[Shader Layer] Initialized')
18
+ console.log(`[Shader Layer] WebGPU supported: ${hasWebGPU}`)
19
+ console.log(`[Shader Layer] Using: ${useWebGPU ? 'WebGPU' : 'WebGL'}`)
20
+ }
21
21
 
22
- return {
23
- provide: {
24
- shader: {
25
- hasWebGPU,
26
- useWebGPU,
27
- config: shaderConfig,
22
+ return {
23
+ provide: {
24
+ shader: {
25
+ hasWebGPU,
26
+ useWebGPU,
27
+ config: shaderConfig,
28
+ },
28
29
  },
29
- },
30
- }
30
+ }
31
31
  },
32
32
  })
@@ -3,56 +3,59 @@ import type { AccentColor, PreferenceOverride } from '#layers/theme/app/types/th
3
3
  export default defineNuxtPlugin({
4
4
  name: 'theme:theme',
5
5
  setup() {
6
- const { setAccent, activeAccent } = useAccentColor()
7
- const { setContrastOverride, effectiveHighContrast } = useThemeContrast()
8
- const { setMotionOverride, effectiveReducedMotion } = useThemeMotion()
9
- const { setTransparencyOverride, effectiveReducedTransparency } = useThemeTransparency()
10
-
11
- // Restore persisted preferences. useState defaults were serialized from the
12
- // server, so hydration matches. We update to the stored values here, after
13
- // hydration completes, to avoid any mismatch.
14
- const storedAccent = localStorage.getItem('theme-colour')
15
- if (storedAccent) setAccent(storedAccent as AccentColor)
16
-
17
- const storedContrast = localStorage.getItem('theme-contrast')
18
- if (storedContrast) setContrastOverride(storedContrast as PreferenceOverride)
19
-
20
- const storedMotion = localStorage.getItem('theme-motion')
21
- if (storedMotion) setMotionOverride(storedMotion as PreferenceOverride)
22
-
23
- const storedTransparency = localStorage.getItem('theme-transparency')
24
- if (storedTransparency) setTransparencyOverride(storedTransparency as PreferenceOverride)
25
-
26
- watch(
27
- activeAccent,
28
- (color) => {
29
- document.documentElement.setAttribute('data-theme-colour', color)
30
- },
31
- { immediate: true }
32
- )
33
-
34
- watch(
35
- effectiveHighContrast,
36
- (high) => {
37
- document.documentElement.setAttribute('data-theme-contrast', high ? 'high' : 'standard')
38
- },
39
- { immediate: true }
40
- )
41
-
42
- watch(
43
- effectiveReducedMotion,
44
- (reduced) => {
45
- document.documentElement.setAttribute('data-theme-motion', reduced ? 'reduced' : 'full')
46
- },
47
- { immediate: true }
48
- )
49
-
50
- watch(
51
- effectiveReducedTransparency,
52
- (reduced) => {
53
- document.documentElement.setAttribute('data-theme-transparency', reduced ? 'reduced' : 'full')
54
- },
55
- { immediate: true }
56
- )
6
+ const { setAccent, activeAccent } = useAccentColor()
7
+ const { setContrastOverride, effectiveHighContrast } = useThemeContrast()
8
+ const { setMotionOverride, effectiveReducedMotion } = useThemeMotion()
9
+ const { setTransparencyOverride, effectiveReducedTransparency } = useThemeTransparency()
10
+
11
+ // Restore persisted preferences. useState defaults were serialized from the
12
+ // server, so hydration matches. We update to the stored values here, after
13
+ // hydration completes, to avoid any mismatch.
14
+ const storedAccent = localStorage.getItem('theme-colour')
15
+ if (storedAccent) setAccent(storedAccent as AccentColor)
16
+
17
+ const storedContrast = localStorage.getItem('theme-contrast')
18
+ if (storedContrast) setContrastOverride(storedContrast as PreferenceOverride)
19
+
20
+ const storedMotion = localStorage.getItem('theme-motion')
21
+ if (storedMotion) setMotionOverride(storedMotion as PreferenceOverride)
22
+
23
+ const storedTransparency = localStorage.getItem('theme-transparency')
24
+ if (storedTransparency) setTransparencyOverride(storedTransparency as PreferenceOverride)
25
+
26
+ watch(
27
+ activeAccent,
28
+ (color) => {
29
+ document.documentElement.setAttribute('data-theme-colour', color)
30
+ },
31
+ { immediate: true }
32
+ )
33
+
34
+ watch(
35
+ effectiveHighContrast,
36
+ (high) => {
37
+ document.documentElement.setAttribute('data-theme-contrast', high ? 'high' : 'standard')
38
+ },
39
+ { immediate: true }
40
+ )
41
+
42
+ watch(
43
+ effectiveReducedMotion,
44
+ (reduced) => {
45
+ document.documentElement.setAttribute('data-theme-motion', reduced ? 'reduced' : 'full')
46
+ },
47
+ { immediate: true }
48
+ )
49
+
50
+ watch(
51
+ effectiveReducedTransparency,
52
+ (reduced) => {
53
+ document.documentElement.setAttribute(
54
+ 'data-theme-transparency',
55
+ reduced ? 'reduced' : 'full'
56
+ )
57
+ },
58
+ { immediate: true }
59
+ )
57
60
  },
58
61
  })
@@ -62,9 +62,8 @@ export function useGradient(
62
62
  const presets = (appConfig.uiLayer as Record<string, unknown> | undefined)?.['gradients'] as
63
63
  | Record<string, GradientConfig>
64
64
  | undefined
65
- let resolved: GradientConfig = typeof raw === 'string'
66
- ? (presets?.[raw] ?? DEFAULT_CONFIG)
67
- : raw
65
+ let resolved: GradientConfig =
66
+ typeof raw === 'string' ? (presets?.[raw] ?? DEFAULT_CONFIG) : raw
68
67
 
69
68
  if (override) {
70
69
  resolved = { ...resolved, ...override }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kmcom-nuxt-layers",
3
3
  "private": false,
4
- "version": "2.2.5",
4
+ "version": "2.2.6",
5
5
  "description": "Composable Nuxt 4 layers for building scalable Vue applications",
6
6
  "exports": {
7
7
  "./layers/core": "./layers/core/nuxt.config.ts",