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.
Files changed (128) hide show
  1. package/docs/FALLOW-COMPLEXITY-DUPLICATION-AUDIT.md +65 -0
  2. package/docs/FEEDS.md +1 -2
  3. package/docs/IMPROVE-AUDIT-README.md +30 -0
  4. package/docs/IMPROVE-AUDIT-RESULTS.md +52 -0
  5. package/docs/IMPROVE-DEEP-AUDIT-RESULTS.md +81 -0
  6. package/docs/fallow-refactor/apps-debug.md +27 -0
  7. package/docs/fallow-refactor/apps-playground.md +46 -0
  8. package/docs/fallow-refactor/apps-visual-identity.md +41 -0
  9. package/docs/fallow-refactor/layers-animations.md +34 -0
  10. package/docs/fallow-refactor/layers-canvas.md +32 -0
  11. package/docs/fallow-refactor/layers-content.md +33 -0
  12. package/docs/fallow-refactor/layers-core.md +39 -0
  13. package/docs/fallow-refactor/layers-feeds.md +39 -0
  14. package/docs/fallow-refactor/layers-forms.md +30 -0
  15. package/docs/fallow-refactor/layers-layout.md +42 -0
  16. package/docs/fallow-refactor/layers-mailer.md +32 -0
  17. package/docs/fallow-refactor/layers-motion.md +27 -0
  18. package/docs/fallow-refactor/layers-navigation.md +31 -0
  19. package/docs/fallow-refactor/layers-page-transitions.md +30 -0
  20. package/docs/fallow-refactor/layers-routing.md +33 -0
  21. package/docs/fallow-refactor/layers-scripts.md +35 -0
  22. package/docs/fallow-refactor/layers-scroll.md +38 -0
  23. package/docs/fallow-refactor/layers-seo.md +32 -0
  24. package/docs/fallow-refactor/layers-shader.md +53 -0
  25. package/docs/fallow-refactor/layers-theme.md +33 -0
  26. package/docs/fallow-refactor/layers-transitions.md +27 -0
  27. package/docs/fallow-refactor/layers-typography.md +29 -0
  28. package/docs/fallow-refactor/layers-ui.md +27 -0
  29. package/docs/fallow-refactor/layers-visual.md +34 -0
  30. package/layers/animations/app/composables/useMagneticElement.ts +11 -9
  31. package/layers/animations/app/composables/useTiltEffect.ts +11 -9
  32. package/layers/animations/app/utils/pointerMotion.ts +31 -0
  33. package/layers/canvas/app/components/ShaderCanvas.vue +2 -2
  34. package/layers/content/app/composables/useCollectionItems.ts +28 -0
  35. package/layers/content/app/composables/useGalleryItems.ts +8 -14
  36. package/layers/content/app/composables/usePortfolioItems.ts +10 -18
  37. package/layers/core/app/composables/useBrowser.ts +9 -82
  38. package/layers/core/app/composables/useFeatures.ts +3 -27
  39. package/layers/core/app/plugins/init.ts +157 -135
  40. package/layers/core/app/utils/browserInfo.ts +115 -0
  41. package/layers/core/app/utils/featureClasses.ts +40 -0
  42. package/layers/core/app/utils/helpers.test.ts +51 -0
  43. package/layers/feeds/app/app.config.ts +4 -2
  44. package/layers/feeds/app/components/Feeds/Index.vue +229 -0
  45. package/layers/feeds/app/components/Feeds/RouteCard.vue +75 -0
  46. package/layers/feeds/app/plugins/feed-head.ts +27 -49
  47. package/layers/feeds/app/utils/feed-catalog.ts +184 -0
  48. package/layers/feeds/nuxt.config.ts +0 -1
  49. package/layers/feeds/package.json +1 -0
  50. package/layers/feeds/server/utils/content-adapter.test.ts +68 -0
  51. package/layers/feeds/server/utils/content-adapter.ts +2 -22
  52. package/layers/feeds/server/utils/feed-author.ts +32 -0
  53. package/layers/feeds/server/utils/feed-config.ts +88 -0
  54. package/layers/feeds/server/utils/feed-service.ts +11 -30
  55. package/layers/feeds/server/utils/feed-xml.ts +26 -0
  56. package/layers/feeds/server/utils/formats/rss.ts +10 -15
  57. package/layers/feeds/server/utils/formats.test.ts +71 -0
  58. package/layers/forms/app/components/Form/Field.vue +42 -30
  59. package/layers/forms/app/utils/fieldProps.ts +65 -0
  60. package/layers/layout/app/components/Layout/Grid/Item.vue +29 -146
  61. package/layers/layout/app/utils/gridPlacementStyle.ts +195 -0
  62. package/layers/mailer/app/types/mailer.ts +7 -25
  63. package/layers/mailer/server/utils/email.ts +28 -13
  64. package/layers/mailer/server/utils/hooks.ts +1 -20
  65. package/layers/navigation/app/composables/useSite.ts +2 -9
  66. package/layers/navigation/app/utils/site.ts +26 -0
  67. package/layers/routing/app/utils/resolveRoute.test.ts +47 -0
  68. package/layers/routing/app/utils/resolveRoute.ts +19 -10
  69. package/layers/scripts/app/composables/useAnalytics.ts +8 -41
  70. package/layers/scripts/app/composables/useGtm.ts +6 -13
  71. package/layers/scripts/app/utils/scriptClients.ts +70 -0
  72. package/layers/scroll/app/composables/useSmoothScroll.ts +9 -43
  73. package/layers/scroll/app/utils/scroll.ts +103 -0
  74. package/layers/seo/app/composables/useSeoConfig.ts +3 -9
  75. package/layers/seo/app/utils/seoConfig.ts +38 -0
  76. package/layers/shader/app/components/Material/AmbientAurora.client.vue +11 -33
  77. package/layers/shader/app/components/Material/AmbientFlow.client.vue +10 -37
  78. package/layers/shader/app/components/Material/AmbientGradientMesh.client.vue +10 -37
  79. package/layers/shader/app/components/Material/AmbientNebula.client.vue +12 -37
  80. package/layers/shader/app/components/Material/AmbientOcean.client.vue +9 -33
  81. package/layers/shader/app/components/Material/Gradient.client.vue +25 -46
  82. package/layers/shader/app/components/Material/Image.client.vue +10 -55
  83. package/layers/shader/app/components/Material/Node.client.vue +18 -5
  84. package/layers/shader/app/components/Material/Noise.client.vue +9 -43
  85. package/layers/shader/app/components/Preset/ThemeBubble.client.vue +2 -1
  86. package/layers/shader/app/components/Preset/ThemeFlow.client.vue +2 -1
  87. package/layers/shader/app/components/Preset/ThemeGradient.client.vue +2 -1
  88. package/layers/shader/app/components/Preset/ThemeLavaLamp.client.vue +2 -1
  89. package/layers/shader/app/components/Preset/ThemePlasma.client.vue +2 -1
  90. package/layers/shader/app/components/Preset/ThemeWave.client.vue +2 -1
  91. package/layers/shader/app/components/Shader/Background.client.vue +44 -24
  92. package/layers/shader/app/composables/useAmbientMaterials.ts +5 -1
  93. package/layers/shader/app/composables/useShader.ts +38 -23
  94. package/layers/shader/app/composables/useShaderGraph.ts +11 -6
  95. package/layers/shader/app/composables/useShaderMixBlend.ts +4 -4
  96. package/layers/shader/app/composables/useShaderRuntime.ts +0 -1
  97. package/layers/shader/app/composables/useShaderVec2.ts +2 -4
  98. package/layers/shader/app/composables/useThemePreset.ts +34 -8
  99. package/layers/shader/app/composables/useUniformWatchers.ts +15 -0
  100. package/layers/shader/app/composables/useUniforms.ts +0 -1
  101. package/layers/shader/app/shaders/common/blend.ts +4 -4
  102. package/layers/shader/app/shaders/common/effects.ts +38 -21
  103. package/layers/shader/app/shaders/common/grain.ts +46 -49
  104. package/layers/shader/app/shaders/common/lighting.ts +17 -15
  105. package/layers/shader/app/shaders/common/math.ts +2 -4
  106. package/layers/shader/app/shaders/common/nodes.ts +17 -0
  107. package/layers/shader/app/shaders/common/palette.ts +21 -11
  108. package/layers/shader/app/shaders/common/patterns.ts +25 -14
  109. package/layers/shader/app/shaders/common/shapes.ts +97 -88
  110. package/layers/shader/app/shaders/common/uv.ts +33 -34
  111. package/layers/shader/app/shaders/createMaterial.ts +92 -78
  112. package/layers/shader/app/shaders/layers/paperShading.ts +22 -10
  113. package/layers/shader/app/shaders/layers/shaderGradient.ts +46 -21
  114. package/layers/shader/app/utils/tsl/tween.ts +2 -4
  115. package/layers/shader/package.json +5 -1
  116. package/layers/theme/app/components/ThemePicker/Menu.vue +3 -25
  117. package/layers/theme/app/composables/useThemePreferenceModels.ts +39 -0
  118. package/layers/theme/server/plugins/theme-fouc.ts +1 -92
  119. package/layers/theme/server/utils/accent-css.ts +75 -0
  120. package/layers/typography/app/composables/typography.ts +3 -7
  121. package/layers/visual/app/composables/accent.ts +2 -9
  122. package/layers/visual/app/composables/gradient.ts +33 -46
  123. package/layers/visual/app/composables/picture.ts +2 -79
  124. package/layers/visual/app/utils/colorTokens.ts +23 -0
  125. package/layers/visual/app/utils/gradientStyle.ts +41 -0
  126. package/layers/visual/app/utils/responsiveSizes.ts +49 -0
  127. package/package.json +17 -5
  128. 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 useContentData('portfolio-items', async () => {
7
- let items = (await queryCollection('portfolio').all()).sort(
8
- (a, b) => (b.year ?? 0) - (a.year ?? 0)
9
- )
10
-
11
- if (featured !== undefined) {
12
- items = items.filter((item) => item.featured === featured)
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
- // composables/useBrowser.ts
2
-
3
- type BrowserInfo = {
4
- name: string
5
- version: string
6
- engine: string
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
- const parts = info.value.version.split('.')
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
- const parts = info.value.version.split('.')
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
- const parts = minVersion.split('.')
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
- const classesToRemove = Array.from(htmlClasses).filter(
145
- (cls) => cls.startsWith('supports-') || cls.startsWith('no-') || cls.startsWith('has-')
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
- // 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()
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
- nuxtApp.hook('app:beforeMount', () => {
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
+ }