kmcom-nuxt-layers 2.2.4 → 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.
- package/layers/animations/app/components/Motion/CountUp.vue +2 -1
- package/layers/animations/app/components/Motion/Magnetic.vue +2 -1
- package/layers/animations/app/components/Motion/Marquee.vue +6 -2
- package/layers/animations/app/components/Motion/MarqueeText.vue +4 -2
- package/layers/animations/app/components/Motion/Tilt.vue +2 -1
- package/layers/animations/app/composables/useMagneticElement.ts +1 -1
- package/layers/animations/app/composables/useMarqueeCopies.ts +1 -1
- package/layers/animations/app/composables/useTiltEffect.ts +1 -1
- package/layers/content/app/components/Gallery/Lightbox.vue +5 -1
- package/layers/content/app/composables/useBlogPosts.ts +2 -2
- package/layers/content/app/composables/useCollectionItem.ts +1 -3
- package/layers/content/app/composables/useGalleryItems.ts +2 -2
- package/layers/core/app/composables/useCache.ts +0 -1
- package/layers/core/app/plugins/error-handler.ts +36 -36
- package/layers/core/app/plugins/feature-detection.client.ts +15 -15
- package/layers/core/app/plugins/init.ts +121 -129
- package/layers/core/app/plugins/loading.client.ts +27 -27
- package/layers/core/app/plugins/scroll-guard.client.ts +26 -26
- package/layers/feeds/app/plugins/feed-head.ts +63 -63
- package/layers/forms/server/api/contact.post.ts +1 -1
- package/layers/page-transitions/app/plugins/page-transitions.client.ts +9 -9
- package/layers/routing/app/plugins/feature-flags.client.ts +9 -9
- package/layers/routing/app/plugins/scroll-routing.client.ts +18 -17
- package/layers/scroll/app/plugins/locomotive-scroll.client.ts +53 -53
- package/layers/shader/app/plugins/shader.client.ts +21 -21
- package/layers/theme/app/plugins/theme.client.ts +54 -51
- package/layers/visual/app/components/Media/Picture.vue +1 -1
- package/layers/visual/app/composables/gradient.ts +2 -3
- package/package.json +1 -1
|
@@ -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
|
|
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-
|
|
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
|
|
|
@@ -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[]>([])
|
|
@@ -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
|
-
(
|
|
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
|
-
(
|
|
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,49 +1,49 @@
|
|
|
1
1
|
export default defineNuxtPlugin({
|
|
2
2
|
name: 'core:error-handler',
|
|
3
3
|
setup(nuxtApp) {
|
|
4
|
-
|
|
4
|
+
const { logError } = useErrorLog()
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
18
|
-
|
|
17
|
+
// Initialize feature detection
|
|
18
|
+
const features = useFeatures()
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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]
|
|
21
|
+
console.log('🚀 [Core Layer] Initializing...')
|
|
22
|
+
|
|
23
|
+
console.log('[Core Layer] Config:', config.coreLayer)
|
|
48
24
|
}
|
|
49
25
|
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
// Test
|
|
53
|
-
const
|
|
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]
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
65
|
-
const
|
|
39
|
+
// Test @vueuse/nuxt module
|
|
40
|
+
const isOnline = useOnline()
|
|
66
41
|
|
|
67
42
|
if (isDev) {
|
|
68
|
-
console.log('[Core Layer]
|
|
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
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
87
|
-
const {
|
|
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]
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
|
100
|
-
const
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
114
|
-
const { mode, isServer, isClient, isHydrated } = useRendering()
|
|
115
|
-
|
|
136
|
+
// 3. App lifecycle hooks (dev logging)
|
|
116
137
|
if (isDev) {
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
126
|
-
|
|
142
|
+
nuxtApp.hook('app:beforeMount', () => {
|
|
143
|
+
console.log('⏳ [Core Layer] App mounting...')
|
|
144
|
+
})
|
|
127
145
|
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
publicKeys: Object.keys(publicConfig ?? {}),
|
|
154
|
+
nuxtApp.hook('page:finish', () => {
|
|
155
|
+
console.log('✅ [Core Layer] Page navigation finished')
|
|
134
156
|
})
|
|
135
157
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
27
|
-
|
|
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]
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
12
|
+
const route = useRoute()
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4
|
+
const appConfig = useAppConfig()
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const defaultTransition = appConfig.pageTransitions?.default ?? 'fade'
|
|
7
|
+
const duration = appConfig.pageTransitions?.duration ?? 300
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
8
|
-
|
|
7
|
+
const { config } = useRoutingConfig()
|
|
8
|
+
if (!config.runtimeFlags) return
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
})
|
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
export default defineNuxtPlugin({
|
|
2
2
|
name: 'routing:scroll-routing',
|
|
3
3
|
setup() {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
const { config } = useRoutingConfig()
|
|
5
|
+
if (!config.scrollRouting.enabled) return
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
7
|
+
const router = useRouter()
|
|
8
|
+
// fallow-ignore-next-line code-duplication
|
|
9
|
+
const observer = new IntersectionObserver(
|
|
10
|
+
(entries) => {
|
|
11
|
+
const visible = entries.find((e) => e.isIntersecting)
|
|
12
|
+
if (!visible) return
|
|
13
|
+
const id = visible.target.getAttribute('data-section')
|
|
14
|
+
if (!id) return
|
|
15
|
+
const method = config.scrollRouting.mode === 'push' ? 'push' : 'replace'
|
|
16
|
+
router[method]({ hash: `#${id}` })
|
|
17
|
+
},
|
|
18
|
+
{ threshold: 0.5 }
|
|
19
|
+
)
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
onNuxtReady(() => {
|
|
22
|
+
document.querySelectorAll('[data-section]').forEach((el) => observer.observe(el))
|
|
23
|
+
})
|
|
23
24
|
},
|
|
24
25
|
})
|
|
@@ -12,67 +12,67 @@ export type ScrollState = {
|
|
|
12
12
|
export default defineNuxtPlugin({
|
|
13
13
|
name: 'scroll:locomotive-scroll',
|
|
14
14
|
setup() {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
23
|
+
const instance = shallowRef<LocomotiveScroll | null>(null)
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
function destroy() {
|
|
45
|
+
instance.value?.destroy()
|
|
46
|
+
instance.value = null
|
|
47
|
+
}
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
const router = useRouter()
|
|
50
|
+
const appConfig = useAppConfig()
|
|
51
|
+
const smoothScroll: boolean | string[] = appConfig.scroll?.smoothScroll ?? true
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
const nuxtApp = useNuxtApp()
|
|
54
54
|
|
|
55
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
const config = useAppConfig()
|
|
7
|
+
const shaderConfig = (config.shader || {}) as {
|
|
8
|
+
preferWebGPU?: boolean
|
|
9
|
+
}
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
// Check WebGPU support
|
|
12
|
+
const hasWebGPU = await checkWebGPUSupport()
|
|
13
|
+
const useWebGPU = shaderConfig.preferWebGPU && hasWebGPU
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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 =
|
|
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