kmcom-nuxt-layers 2.2.11 → 2.2.13
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/docs/FALLOW-COMPLEXITY-DUPLICATION-AUDIT.md +65 -0
- package/docs/FEEDS.md +1 -2
- package/docs/IMPROVE-AUDIT-README.md +30 -0
- package/docs/IMPROVE-AUDIT-RESULTS.md +52 -0
- package/docs/IMPROVE-DEEP-AUDIT-RESULTS.md +81 -0
- package/docs/fallow-refactor/apps-debug.md +27 -0
- package/docs/fallow-refactor/apps-playground.md +46 -0
- package/docs/fallow-refactor/apps-visual-identity.md +41 -0
- package/docs/fallow-refactor/layers-animations.md +34 -0
- package/docs/fallow-refactor/layers-canvas.md +32 -0
- package/docs/fallow-refactor/layers-content.md +33 -0
- package/docs/fallow-refactor/layers-core.md +39 -0
- package/docs/fallow-refactor/layers-feeds.md +39 -0
- package/docs/fallow-refactor/layers-forms.md +30 -0
- package/docs/fallow-refactor/layers-layout.md +42 -0
- package/docs/fallow-refactor/layers-mailer.md +32 -0
- package/docs/fallow-refactor/layers-motion.md +27 -0
- package/docs/fallow-refactor/layers-navigation.md +31 -0
- package/docs/fallow-refactor/layers-page-transitions.md +30 -0
- package/docs/fallow-refactor/layers-routing.md +33 -0
- package/docs/fallow-refactor/layers-scripts.md +35 -0
- package/docs/fallow-refactor/layers-scroll.md +38 -0
- package/docs/fallow-refactor/layers-seo.md +32 -0
- package/docs/fallow-refactor/layers-shader.md +53 -0
- package/docs/fallow-refactor/layers-theme.md +33 -0
- package/docs/fallow-refactor/layers-transitions.md +27 -0
- package/docs/fallow-refactor/layers-typography.md +29 -0
- package/docs/fallow-refactor/layers-ui.md +27 -0
- package/docs/fallow-refactor/layers-visual.md +34 -0
- package/layers/animations/app/composables/useMagneticElement.ts +11 -9
- package/layers/animations/app/composables/useTiltEffect.ts +11 -9
- package/layers/animations/app/utils/pointerMotion.ts +31 -0
- package/layers/canvas/app/components/ShaderCanvas.vue +2 -2
- package/layers/content/app/composables/useCollectionItems.ts +28 -0
- package/layers/content/app/composables/useGalleryItems.ts +8 -14
- package/layers/content/app/composables/usePortfolioItems.ts +10 -18
- package/layers/core/app/composables/useBrowser.ts +9 -82
- package/layers/core/app/composables/useFeatures.ts +3 -27
- package/layers/core/app/plugins/init.ts +157 -135
- package/layers/core/app/utils/browserInfo.ts +115 -0
- package/layers/core/app/utils/featureClasses.ts +40 -0
- package/layers/core/app/utils/helpers.test.ts +51 -0
- package/layers/feeds/app/app.config.ts +4 -2
- package/layers/feeds/app/components/Feeds/Index.vue +229 -0
- package/layers/feeds/app/components/Feeds/RouteCard.vue +75 -0
- package/layers/feeds/app/plugins/feed-head.ts +27 -49
- package/layers/feeds/app/utils/feed-catalog.ts +184 -0
- package/layers/feeds/nuxt.config.ts +0 -1
- package/layers/feeds/package.json +1 -0
- package/layers/feeds/server/utils/content-adapter.test.ts +68 -0
- package/layers/feeds/server/utils/content-adapter.ts +2 -22
- package/layers/feeds/server/utils/feed-author.ts +32 -0
- package/layers/feeds/server/utils/feed-config.ts +88 -0
- package/layers/feeds/server/utils/feed-service.ts +11 -30
- package/layers/feeds/server/utils/feed-xml.ts +26 -0
- package/layers/feeds/server/utils/formats/rss.ts +10 -15
- package/layers/feeds/server/utils/formats.test.ts +71 -0
- package/layers/forms/app/components/Form/Field.vue +42 -30
- package/layers/forms/app/utils/fieldProps.ts +65 -0
- package/layers/layout/app/components/Layout/Grid/Item.vue +29 -146
- package/layers/layout/app/utils/gridPlacementStyle.ts +195 -0
- package/layers/mailer/app/types/mailer.ts +7 -25
- package/layers/mailer/server/utils/email.ts +28 -13
- package/layers/mailer/server/utils/hooks.ts +1 -20
- package/layers/navigation/app/composables/useSite.ts +2 -9
- package/layers/navigation/app/utils/site.ts +26 -0
- package/layers/routing/app/utils/resolveRoute.test.ts +47 -0
- package/layers/routing/app/utils/resolveRoute.ts +19 -10
- package/layers/scripts/app/composables/useAnalytics.ts +8 -41
- package/layers/scripts/app/composables/useGtm.ts +6 -13
- package/layers/scripts/app/utils/scriptClients.ts +70 -0
- package/layers/scroll/app/composables/useSmoothScroll.ts +9 -43
- package/layers/scroll/app/utils/scroll.ts +103 -0
- package/layers/seo/app/composables/useSeoConfig.ts +3 -9
- package/layers/seo/app/utils/seoConfig.ts +38 -0
- package/layers/shader/app/components/Material/AmbientAurora.client.vue +11 -33
- package/layers/shader/app/components/Material/AmbientFlow.client.vue +10 -37
- package/layers/shader/app/components/Material/AmbientGradientMesh.client.vue +10 -37
- package/layers/shader/app/components/Material/AmbientNebula.client.vue +12 -37
- package/layers/shader/app/components/Material/AmbientOcean.client.vue +9 -33
- package/layers/shader/app/components/Material/Gradient.client.vue +25 -46
- package/layers/shader/app/components/Material/Image.client.vue +10 -55
- package/layers/shader/app/components/Material/Node.client.vue +18 -5
- package/layers/shader/app/components/Material/Noise.client.vue +9 -43
- package/layers/shader/app/components/Preset/ThemeBubble.client.vue +2 -1
- package/layers/shader/app/components/Preset/ThemeFlow.client.vue +2 -1
- package/layers/shader/app/components/Preset/ThemeGradient.client.vue +2 -1
- package/layers/shader/app/components/Preset/ThemeLavaLamp.client.vue +2 -1
- package/layers/shader/app/components/Preset/ThemePlasma.client.vue +2 -1
- package/layers/shader/app/components/Preset/ThemeWave.client.vue +2 -1
- package/layers/shader/app/components/Shader/Background.client.vue +44 -24
- package/layers/shader/app/composables/useAmbientMaterials.ts +5 -1
- package/layers/shader/app/composables/useShader.ts +38 -23
- package/layers/shader/app/composables/useShaderGraph.ts +11 -6
- package/layers/shader/app/composables/useShaderMixBlend.ts +4 -4
- package/layers/shader/app/composables/useShaderRuntime.ts +0 -1
- package/layers/shader/app/composables/useShaderVec2.ts +2 -4
- package/layers/shader/app/composables/useThemePreset.ts +34 -8
- package/layers/shader/app/composables/useUniformWatchers.ts +15 -0
- package/layers/shader/app/composables/useUniforms.ts +0 -1
- package/layers/shader/app/shaders/common/blend.ts +4 -4
- package/layers/shader/app/shaders/common/effects.ts +38 -21
- package/layers/shader/app/shaders/common/grain.ts +46 -49
- package/layers/shader/app/shaders/common/lighting.ts +17 -15
- package/layers/shader/app/shaders/common/math.ts +2 -4
- package/layers/shader/app/shaders/common/nodes.ts +17 -0
- package/layers/shader/app/shaders/common/palette.ts +21 -11
- package/layers/shader/app/shaders/common/patterns.ts +25 -14
- package/layers/shader/app/shaders/common/shapes.ts +97 -88
- package/layers/shader/app/shaders/common/uv.ts +33 -34
- package/layers/shader/app/shaders/createMaterial.ts +92 -78
- package/layers/shader/app/shaders/layers/paperShading.ts +22 -10
- package/layers/shader/app/shaders/layers/shaderGradient.ts +46 -21
- package/layers/shader/app/utils/tsl/tween.ts +2 -4
- package/layers/shader/package.json +5 -1
- package/layers/theme/app/components/ThemePicker/Menu.vue +3 -25
- package/layers/theme/app/composables/useThemePreferenceModels.ts +39 -0
- package/layers/theme/server/plugins/theme-fouc.ts +1 -92
- package/layers/theme/server/utils/accent-css.ts +75 -0
- package/layers/typography/app/composables/typography.ts +3 -7
- package/layers/visual/app/composables/accent.ts +2 -9
- package/layers/visual/app/composables/gradient.ts +33 -46
- package/layers/visual/app/composables/picture.ts +2 -79
- package/layers/visual/app/utils/colorTokens.ts +23 -0
- package/layers/visual/app/utils/gradientStyle.ts +41 -0
- package/layers/visual/app/utils/responsiveSizes.ts +49 -0
- package/package.json +17 -5
- package/layers/feeds/server/routes/feed/discovery.get.ts +0 -29
|
@@ -1,25 +1,17 @@
|
|
|
1
1
|
import type { PortfolioQueryOptions } from '../types/content'
|
|
2
|
+
import { useCollectionItems } from './useCollectionItems'
|
|
2
3
|
|
|
3
4
|
export function usePortfolioItems(options: PortfolioQueryOptions = {}) {
|
|
4
5
|
const { featured, tags, limit } = options
|
|
5
6
|
|
|
6
|
-
return
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (tags?.length) {
|
|
16
|
-
items = items.filter((item) => item.tags?.some((tag: string) => tags.includes(tag)))
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (limit) {
|
|
20
|
-
items = items.slice(0, limit)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return items
|
|
7
|
+
return useCollectionItems({
|
|
8
|
+
key: 'portfolio-items',
|
|
9
|
+
collection: 'portfolio',
|
|
10
|
+
sort: (a, b) => (b.year ?? 0) - (a.year ?? 0),
|
|
11
|
+
options: { limit },
|
|
12
|
+
// fallow-ignore-next-line complexity
|
|
13
|
+
filter: (item) =>
|
|
14
|
+
(featured === undefined || item.featured === featured) &&
|
|
15
|
+
(!tags?.length || Boolean(item.tags?.some((tag: string) => tags.includes(tag)))),
|
|
24
16
|
})
|
|
25
17
|
}
|
|
@@ -1,63 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
os: string
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Parse user agent to detect browser information
|
|
12
|
-
*/
|
|
13
|
-
function parseBrowserInfo(): BrowserInfo {
|
|
14
|
-
if (!import.meta.client) {
|
|
15
|
-
return {
|
|
16
|
-
name: 'unknown',
|
|
17
|
-
version: '0',
|
|
18
|
-
engine: 'unknown',
|
|
19
|
-
os: 'unknown',
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const ua = navigator.userAgent
|
|
24
|
-
let name = 'unknown'
|
|
25
|
-
let version = '0'
|
|
26
|
-
let engine = 'unknown'
|
|
27
|
-
let os = 'unknown'
|
|
28
|
-
|
|
29
|
-
// Detect OS
|
|
30
|
-
if (ua.includes('Win')) os = 'windows'
|
|
31
|
-
else if (ua.includes('Mac')) os = 'macos'
|
|
32
|
-
else if (ua.includes('Linux')) os = 'linux'
|
|
33
|
-
else if (ua.includes('Android')) os = 'android'
|
|
34
|
-
else if (ua.includes('iOS') || ua.includes('iPhone') || ua.includes('iPad')) os = 'ios'
|
|
35
|
-
|
|
36
|
-
// Detect browser (order matters - check most specific first)
|
|
37
|
-
if (ua.includes('Edg/')) {
|
|
38
|
-
name = 'edge'
|
|
39
|
-
engine = 'blink'
|
|
40
|
-
version = ua.match(/Edg\/([\d.]+)/)?.[1] || '0'
|
|
41
|
-
} else if (ua.includes('Chrome/') && !ua.includes('Edg')) {
|
|
42
|
-
name = 'chrome'
|
|
43
|
-
engine = 'blink'
|
|
44
|
-
version = ua.match(/Chrome\/([\d.]+)/)?.[1] || '0'
|
|
45
|
-
} else if (ua.includes('Safari/') && !ua.includes('Chrome')) {
|
|
46
|
-
name = 'safari'
|
|
47
|
-
engine = 'webkit'
|
|
48
|
-
version = ua.match(/Version\/([\d.]+)/)?.[1] || '0'
|
|
49
|
-
} else if (ua.includes('Firefox/')) {
|
|
50
|
-
name = 'firefox'
|
|
51
|
-
engine = 'gecko'
|
|
52
|
-
version = ua.match(/Firefox\/([\d.]+)/)?.[1] || '0'
|
|
53
|
-
} else if (ua.includes('Opera/') || ua.includes('OPR/')) {
|
|
54
|
-
name = 'opera'
|
|
55
|
-
engine = 'blink'
|
|
56
|
-
version = ua.match(/(?:Opera|OPR)\/([\d.]+)/)?.[1] || '0'
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return { name, version, engine, os }
|
|
60
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
getBrowserVersionParts,
|
|
3
|
+
isBrowserAtLeast,
|
|
4
|
+
parseBrowserInfo,
|
|
5
|
+
type BrowserInfo,
|
|
6
|
+
} from '#layers/core/app/utils/browserInfo'
|
|
61
7
|
|
|
62
8
|
/**
|
|
63
9
|
* Browser detection composable
|
|
@@ -93,15 +39,11 @@ export function useBrowser() {
|
|
|
93
39
|
|
|
94
40
|
// Version helpers
|
|
95
41
|
const majorVersion = computed(() => {
|
|
96
|
-
|
|
97
|
-
const major = parts[0]
|
|
98
|
-
return major ? parseInt(major, 10) : 0
|
|
42
|
+
return getBrowserVersionParts(info.value.version).major
|
|
99
43
|
})
|
|
100
44
|
|
|
101
45
|
const minorVersion = computed(() => {
|
|
102
|
-
|
|
103
|
-
const minor = parts[1] || '0'
|
|
104
|
-
return parseInt(minor, 10)
|
|
46
|
+
return getBrowserVersionParts(info.value.version).minor
|
|
105
47
|
})
|
|
106
48
|
|
|
107
49
|
/**
|
|
@@ -109,22 +51,7 @@ export function useBrowser() {
|
|
|
109
51
|
* @param minVersion - Minimum version string (e.g., "100" or "100.0")
|
|
110
52
|
*/
|
|
111
53
|
const isAtLeast = (minVersion: string): boolean => {
|
|
112
|
-
|
|
113
|
-
const minMajor = parts[0]
|
|
114
|
-
const minMinor = parts[1] || '0'
|
|
115
|
-
|
|
116
|
-
if (!minMajor) return false
|
|
117
|
-
|
|
118
|
-
const major = majorVersion.value
|
|
119
|
-
const minor = minorVersion.value
|
|
120
|
-
|
|
121
|
-
const minMajorNum = parseInt(minMajor, 10)
|
|
122
|
-
const minMinorNum = parseInt(minMinor, 10)
|
|
123
|
-
|
|
124
|
-
if (major > minMajorNum) return true
|
|
125
|
-
if (major === minMajorNum && minor >= minMinorNum) return true
|
|
126
|
-
|
|
127
|
-
return false
|
|
54
|
+
return isBrowserAtLeast(info.value.version, minVersion)
|
|
128
55
|
}
|
|
129
56
|
|
|
130
57
|
return {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// composables/useFeatures.ts
|
|
2
2
|
import type { FeatureDetection } from '#layers/core/app/types/detection'
|
|
3
|
+
import { getFeatureClassNames, removeFeatureClasses } from '#layers/core/app/utils/featureClasses'
|
|
3
4
|
import {
|
|
4
5
|
usePreferredContrast,
|
|
5
6
|
usePreferredDark,
|
|
@@ -139,34 +140,9 @@ async function checkImageFormat(format: 'webp' | 'avif'): Promise<boolean> {
|
|
|
139
140
|
function applyFeatureClasses(features: FeatureDetection) {
|
|
140
141
|
if (!import.meta.client || !document.documentElement) return
|
|
141
142
|
|
|
142
|
-
// Remove old classes first
|
|
143
143
|
const htmlClasses = document.documentElement.classList
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
)
|
|
147
|
-
htmlClasses.remove(...classesToRemove)
|
|
148
|
-
|
|
149
|
-
const classes: string[] = []
|
|
150
|
-
|
|
151
|
-
// CSS features
|
|
152
|
-
classes.push(features.grid ? 'supports-grid' : 'no-grid')
|
|
153
|
-
classes.push(features.subgrid ? 'supports-subgrid' : 'no-subgrid')
|
|
154
|
-
classes.push(features.containerQueries ? 'supports-container-queries' : 'no-container-queries')
|
|
155
|
-
classes.push(features.has ? 'supports-has' : 'no-has')
|
|
156
|
-
classes.push(features.aspectRatio ? 'supports-aspect-ratio' : 'no-aspect-ratio')
|
|
157
|
-
classes.push(features.backdropFilter ? 'supports-backdrop-filter' : 'no-backdrop-filter')
|
|
158
|
-
|
|
159
|
-
// JS APIs
|
|
160
|
-
if (features.intersectionObserver) classes.push('has-intersection-observer')
|
|
161
|
-
if (features.resizeObserver) classes.push('has-resize-observer')
|
|
162
|
-
if (features.serviceWorker) classes.push('has-service-worker')
|
|
163
|
-
if (features.webGL) classes.push('has-webgl')
|
|
164
|
-
|
|
165
|
-
// Image formats
|
|
166
|
-
if (features.webp) classes.push('supports-webp')
|
|
167
|
-
if (features.avif) classes.push('supports-avif')
|
|
168
|
-
|
|
169
|
-
htmlClasses.add(...classes)
|
|
144
|
+
removeFeatureClasses(htmlClasses)
|
|
145
|
+
htmlClasses.add(...getFeatureClassNames(features))
|
|
170
146
|
}
|
|
171
147
|
|
|
172
148
|
/**
|
|
@@ -9,155 +9,177 @@
|
|
|
9
9
|
*
|
|
10
10
|
* This runs before other plugins and ensures the app is ready.
|
|
11
11
|
*/
|
|
12
|
+
|
|
13
|
+
function logCoreDeviceState(isDev: boolean) {
|
|
14
|
+
const device = useDevice()
|
|
15
|
+
if (isDev && device) {
|
|
16
|
+
console.log('[Core Layer] Device detection:', {
|
|
17
|
+
mobile: device.isMobile,
|
|
18
|
+
desktop: device.isDesktop,
|
|
19
|
+
tablet: device.isTablet,
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function logCoreOnlineState(isDev: boolean) {
|
|
25
|
+
const isOnline = useOnline()
|
|
26
|
+
if (isDev) {
|
|
27
|
+
console.log('[Core Layer] VueUse loaded, online status:', isOnline.value)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function logCoreBrowserState(isDev: boolean) {
|
|
32
|
+
const { name, version, engine, os } = useBrowser()
|
|
33
|
+
|
|
34
|
+
if (isDev) {
|
|
35
|
+
console.log('[Core Layer] Browser detection:', {
|
|
36
|
+
name: name.value,
|
|
37
|
+
version: version.value,
|
|
38
|
+
engine: engine.value,
|
|
39
|
+
os: os.value,
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function logCoreScreenState(isDev: boolean) {
|
|
45
|
+
const { breakpoint, isRetina, orientation } = useScreen()
|
|
46
|
+
|
|
47
|
+
if (isDev) {
|
|
48
|
+
console.log('[Core Layer] Screen detection:', {
|
|
49
|
+
breakpoint: breakpoint.value,
|
|
50
|
+
retina: isRetina.value,
|
|
51
|
+
orientation: orientation.value,
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function logCoreNetworkState(isDev: boolean) {
|
|
57
|
+
const { connectionQuality, effectiveType, saveData } = useNetworkInfo()
|
|
58
|
+
|
|
59
|
+
if (isDev) {
|
|
60
|
+
console.log('[Core Layer] Network detection:', {
|
|
61
|
+
quality: connectionQuality.value,
|
|
62
|
+
type: effectiveType.value,
|
|
63
|
+
dataSaver: saveData.value,
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function logCoreFeatureState(isDev: boolean) {
|
|
69
|
+
const { grid, subgrid, containerQueries, webGL, darkMode } = useFeatures()
|
|
70
|
+
|
|
71
|
+
if (isDev) {
|
|
72
|
+
console.log('[Core Layer] Feature detection:', {
|
|
73
|
+
grid: grid.value,
|
|
74
|
+
subgrid: subgrid.value,
|
|
75
|
+
containerQueries: containerQueries.value,
|
|
76
|
+
webGL: webGL.value,
|
|
77
|
+
darkMode: darkMode.value,
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function logCoreCacheState(isDev: boolean) {
|
|
83
|
+
const { offlineReady, isOnline: cacheOnline } = useCache()
|
|
84
|
+
|
|
85
|
+
if (isDev) {
|
|
86
|
+
console.log('[Core Layer] Cache status:', {
|
|
87
|
+
online: cacheOnline.value,
|
|
88
|
+
offlineReady: offlineReady.value,
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function logCoreClientDiagnostics(isDev: boolean) {
|
|
94
|
+
if (!import.meta.client) return
|
|
95
|
+
|
|
96
|
+
logCoreBrowserState(isDev)
|
|
97
|
+
logCoreScreenState(isDev)
|
|
98
|
+
logCoreNetworkState(isDev)
|
|
99
|
+
logCoreFeatureState(isDev)
|
|
100
|
+
logCoreCacheState(isDev)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function logCoreRenderingState(isDev: boolean) {
|
|
104
|
+
const { mode, isServer, isClient, isHydrated } = useRendering()
|
|
105
|
+
|
|
106
|
+
if (isDev) {
|
|
107
|
+
console.log('[Core Layer] Rendering mode:', {
|
|
108
|
+
mode: mode.value,
|
|
109
|
+
server: isServer.value,
|
|
110
|
+
client: isClient.value,
|
|
111
|
+
hydrated: isHydrated.value,
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function logCoreEnvironmentState(isDev: boolean) {
|
|
117
|
+
const env = useEnv() as unknown as Record<string, unknown>
|
|
118
|
+
|
|
119
|
+
if (isDev) {
|
|
120
|
+
const publicConfig = env.public as Record<string, unknown> | undefined
|
|
121
|
+
|
|
122
|
+
console.log('[Core Layer] Environment config loaded:', {
|
|
123
|
+
hasPublicConfig: Boolean(publicConfig),
|
|
124
|
+
publicKeys: Object.keys(publicConfig ?? {}),
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function verifyCoreLayerModules(isDev: boolean) {
|
|
130
|
+
try {
|
|
131
|
+
logCoreDeviceState(isDev)
|
|
132
|
+
logCoreOnlineState(isDev)
|
|
133
|
+
logCoreClientDiagnostics(isDev)
|
|
134
|
+
logCoreRenderingState(isDev)
|
|
135
|
+
logCoreEnvironmentState(isDev)
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error('[Core Layer] Module verification failed:', error)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
type HookableNuxtApp = Pick<ReturnType<typeof useNuxtApp>, 'hook'>
|
|
142
|
+
|
|
143
|
+
function registerCoreLayerHooks(nuxtApp: HookableNuxtApp, isDev: boolean) {
|
|
144
|
+
if (!isDev) return
|
|
145
|
+
|
|
146
|
+
nuxtApp.hook('app:created', () => {
|
|
147
|
+
console.log('✅ [Core Layer] App created')
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
nuxtApp.hook('app:beforeMount', () => {
|
|
151
|
+
console.log('⏳ [Core Layer] App mounting...')
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
nuxtApp.hook('app:mounted', () => {
|
|
155
|
+
console.log('✅ [Core Layer] App mounted')
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
nuxtApp.hook('page:start', () => {
|
|
159
|
+
console.log('📄 [Core Layer] Page navigation started')
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
nuxtApp.hook('page:finish', () => {
|
|
163
|
+
console.log('✅ [Core Layer] Page navigation finished')
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
|
|
12
167
|
export default defineNuxtPlugin({
|
|
13
168
|
name: 'core:init',
|
|
14
169
|
setup(nuxtApp) {
|
|
15
170
|
const config = useAppConfig()
|
|
16
|
-
// const isDev = import.meta.dev
|
|
17
171
|
const isDev = process.env.NODE_ENV === 'development'
|
|
18
172
|
|
|
19
|
-
// 1. Log initialization (dev only)
|
|
20
173
|
if (isDev) {
|
|
21
174
|
console.log('🚀 [Core Layer] Initializing...')
|
|
22
175
|
|
|
23
176
|
console.log('[Core Layer] Config:', config.coreLayer)
|
|
24
177
|
}
|
|
25
178
|
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
// Test @nuxtjs/device module (SSR-safe: returns undefined before device plugin runs)
|
|
29
|
-
const device = useDevice()
|
|
30
|
-
|
|
31
|
-
if (isDev && device) {
|
|
32
|
-
console.log('[Core Layer] Device detection:', {
|
|
33
|
-
mobile: device.isMobile,
|
|
34
|
-
desktop: device.isDesktop,
|
|
35
|
-
tablet: device.isTablet,
|
|
36
|
-
})
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Test @vueuse/nuxt module
|
|
40
|
-
const isOnline = useOnline()
|
|
41
|
-
|
|
42
|
-
if (isDev) {
|
|
43
|
-
console.log('[Core Layer] VueUse loaded, online status:', isOnline.value)
|
|
44
|
-
}
|
|
45
|
-
|
|
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
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Test rendering mode detection (works on both server and client)
|
|
110
|
-
const { mode, isServer, isClient, isHydrated } = useRendering()
|
|
111
|
-
|
|
112
|
-
if (isDev) {
|
|
113
|
-
console.log('[Core Layer] Rendering mode:', {
|
|
114
|
-
mode: mode.value,
|
|
115
|
-
server: isServer.value,
|
|
116
|
-
client: isClient.value,
|
|
117
|
-
hydrated: isHydrated.value,
|
|
118
|
-
})
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Test environment access (works on both server and client)
|
|
122
|
-
const env = useEnv() as unknown as Record<string, unknown>
|
|
123
|
-
|
|
124
|
-
if (isDev) {
|
|
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 ?? {}),
|
|
130
|
-
})
|
|
131
|
-
}
|
|
132
|
-
} catch (error) {
|
|
133
|
-
console.error('[Core Layer] Module verification failed:', error)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// 3. App lifecycle hooks (dev logging)
|
|
137
|
-
if (isDev) {
|
|
138
|
-
nuxtApp.hook('app:created', () => {
|
|
139
|
-
console.log('✅ [Core Layer] App created')
|
|
140
|
-
})
|
|
179
|
+
verifyCoreLayerModules(isDev)
|
|
141
180
|
|
|
142
|
-
|
|
143
|
-
console.log('⏳ [Core Layer] App mounting...')
|
|
144
|
-
})
|
|
145
|
-
|
|
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
|
-
})
|
|
153
|
-
|
|
154
|
-
nuxtApp.hook('page:finish', () => {
|
|
155
|
-
console.log('✅ [Core Layer] Page navigation finished')
|
|
156
|
-
})
|
|
157
|
-
}
|
|
181
|
+
registerCoreLayerHooks(nuxtApp, isDev)
|
|
158
182
|
|
|
159
|
-
// 4. Provide global helpers (optional)
|
|
160
|
-
// Make core utilities available throughout the app
|
|
161
183
|
return {
|
|
162
184
|
provide: {
|
|
163
185
|
coreLayer: {
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
export type BrowserInfo = {
|
|
2
|
+
name: string
|
|
3
|
+
version: string
|
|
4
|
+
engine: string
|
|
5
|
+
os: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
type BrowserRule = {
|
|
9
|
+
match: (ua: string) => boolean
|
|
10
|
+
name: BrowserInfo['name']
|
|
11
|
+
engine: BrowserInfo['engine']
|
|
12
|
+
version: (ua: string) => string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type OsRule = {
|
|
16
|
+
match: (ua: string) => boolean
|
|
17
|
+
os: BrowserInfo['os']
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const OS_RULES: OsRule[] = [
|
|
21
|
+
{ match: (ua) => ua.includes('Android'), os: 'android' },
|
|
22
|
+
{ match: (ua) => ua.includes('iOS') || ua.includes('iPhone') || ua.includes('iPad'), os: 'ios' },
|
|
23
|
+
{ match: (ua) => ua.includes('Win'), os: 'windows' },
|
|
24
|
+
{ match: (ua) => ua.includes('Mac'), os: 'macos' },
|
|
25
|
+
{ match: (ua) => ua.includes('Linux'), os: 'linux' },
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
const BROWSER_RULES: BrowserRule[] = [
|
|
29
|
+
{
|
|
30
|
+
match: (ua) => ua.includes('Edg/'),
|
|
31
|
+
name: 'edge',
|
|
32
|
+
engine: 'blink',
|
|
33
|
+
version: (ua) => ua.match(/Edg\/([\d.]+)/)?.[1] ?? '0',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
match: (ua) => ua.includes('Chrome/') && !ua.includes('Edg'),
|
|
37
|
+
name: 'chrome',
|
|
38
|
+
engine: 'blink',
|
|
39
|
+
version: (ua) => ua.match(/Chrome\/([\d.]+)/)?.[1] ?? '0',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
match: (ua) => ua.includes('Safari/') && !ua.includes('Chrome'),
|
|
43
|
+
name: 'safari',
|
|
44
|
+
engine: 'webkit',
|
|
45
|
+
version: (ua) => ua.match(/Version\/([\d.]+)/)?.[1] ?? '0',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
match: (ua) => ua.includes('Firefox/'),
|
|
49
|
+
name: 'firefox',
|
|
50
|
+
engine: 'gecko',
|
|
51
|
+
version: (ua) => ua.match(/Firefox\/([\d.]+)/)?.[1] ?? '0',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
match: (ua) => ua.includes('Opera/') || ua.includes('OPR/'),
|
|
55
|
+
name: 'opera',
|
|
56
|
+
engine: 'blink',
|
|
57
|
+
version: (ua) => ua.match(/(?:Opera|OPR)\/([\d.]+)/)?.[1] ?? '0',
|
|
58
|
+
},
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
function detectOs(ua: string): BrowserInfo['os'] {
|
|
62
|
+
return OS_RULES.find((rule) => rule.match(ua))?.os ?? 'unknown'
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function detectBrowser(ua: string): Pick<BrowserInfo, 'name' | 'version' | 'engine'> {
|
|
66
|
+
const rule = BROWSER_RULES.find((candidate) => candidate.match(ua))
|
|
67
|
+
if (!rule) return { name: 'unknown', version: '0', engine: 'unknown' }
|
|
68
|
+
return {
|
|
69
|
+
name: rule.name,
|
|
70
|
+
version: rule.version(ua),
|
|
71
|
+
engine: rule.engine,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function parseBrowserInfo(userAgent?: string | null): BrowserInfo {
|
|
76
|
+
if (!import.meta.client) {
|
|
77
|
+
return {
|
|
78
|
+
name: 'unknown',
|
|
79
|
+
version: '0',
|
|
80
|
+
engine: 'unknown',
|
|
81
|
+
os: 'unknown',
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const ua = userAgent ?? navigator.userAgent
|
|
86
|
+
return {
|
|
87
|
+
...detectBrowser(ua),
|
|
88
|
+
os: detectOs(ua),
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function getBrowserVersionParts(version: string) {
|
|
93
|
+
const [major = '0', minor = '0'] = version.split('.')
|
|
94
|
+
return {
|
|
95
|
+
major: Number.parseInt(major, 10) || 0,
|
|
96
|
+
minor: Number.parseInt(minor, 10) || 0,
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// fallow-ignore-next-line complexity
|
|
101
|
+
export function isBrowserAtLeast(version: string, minVersion: string): boolean {
|
|
102
|
+
const current = getBrowserVersionParts(version)
|
|
103
|
+
const [minMajor = '', minMinor = '0'] = minVersion.split('.')
|
|
104
|
+
|
|
105
|
+
if (!minMajor) return false
|
|
106
|
+
|
|
107
|
+
const required = {
|
|
108
|
+
major: Number.parseInt(minMajor, 10) || 0,
|
|
109
|
+
minor: Number.parseInt(minMinor, 10) || 0,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (current.major > required.major) return true
|
|
113
|
+
if (current.major < required.major) return false
|
|
114
|
+
return current.minor >= required.minor
|
|
115
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { FeatureDetection } from '#layers/core/app/types/detection'
|
|
2
|
+
|
|
3
|
+
const PREFIXES = ['supports-', 'no-', 'has-'] as const
|
|
4
|
+
|
|
5
|
+
const SUPPORT_CLASS_MAP: Array<[keyof FeatureDetection, string, string]> = [
|
|
6
|
+
['grid', 'supports-grid', 'no-grid'],
|
|
7
|
+
['subgrid', 'supports-subgrid', 'no-subgrid'],
|
|
8
|
+
['containerQueries', 'supports-container-queries', 'no-container-queries'],
|
|
9
|
+
['has', 'supports-has', 'no-has'],
|
|
10
|
+
['aspectRatio', 'supports-aspect-ratio', 'no-aspect-ratio'],
|
|
11
|
+
['backdropFilter', 'supports-backdrop-filter', 'no-backdrop-filter'],
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
const EXTRA_CLASS_MAP: Array<[keyof FeatureDetection, string]> = [
|
|
15
|
+
['intersectionObserver', 'has-intersection-observer'],
|
|
16
|
+
['resizeObserver', 'has-resize-observer'],
|
|
17
|
+
['serviceWorker', 'has-service-worker'],
|
|
18
|
+
['webGL', 'has-webgl'],
|
|
19
|
+
['webp', 'supports-webp'],
|
|
20
|
+
['avif', 'supports-avif'],
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
export function getFeatureClassNames(features: FeatureDetection): string[] {
|
|
24
|
+
const classes = SUPPORT_CLASS_MAP.map(([key, supportedClass, unsupportedClass]) =>
|
|
25
|
+
features[key] ? supportedClass : unsupportedClass
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
classes.push(
|
|
29
|
+
...EXTRA_CLASS_MAP.flatMap(([key, className]) => (features[key] ? [className] : []))
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
return classes
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function removeFeatureClasses(classList: DOMTokenList) {
|
|
36
|
+
const classNames = Array.from(classList).filter((cls) =>
|
|
37
|
+
PREFIXES.some((prefix) => cls.startsWith(prefix))
|
|
38
|
+
)
|
|
39
|
+
classList.remove(...classNames)
|
|
40
|
+
}
|