kmcom-nuxt-layers 2.6.2 → 2.7.0

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 (114) hide show
  1. package/docs/ARCHITECTURE.md +21 -17
  2. package/docs/FEEDS.md +42 -34
  3. package/docs/IMPROVE-AUDIT-README.md +9 -10
  4. package/docs/IMPROVE-AUDIT-RESULTS.md +16 -17
  5. package/docs/IMPROVE-DEEP-AUDIT-RESULTS.md +29 -29
  6. package/docs/LAYERS-FIXES.md +1 -0
  7. package/docs/LAYOUT.md +91 -86
  8. package/docs/MIGRATION.md +93 -60
  9. package/docs/UPAGE-GRID-INTEGRATION.md +12 -10
  10. package/docs/USAGE.md +29 -38
  11. package/layers/animations/app/utils/pointerMotion.ts +1 -4
  12. package/layers/baseline/app/components/BaselineStatus.vue +13 -13
  13. package/layers/baseline/app/plugins/baseline-status.client.ts +4 -1
  14. package/layers/canvas/app/composables/useRendererCapabilities.ts +2 -2
  15. package/layers/content/app/components/Blog/Card.vue +1 -0
  16. package/layers/content/app/components/Gallery/Card.vue +1 -1
  17. package/layers/content/app/components/Gallery/Detail.vue +8 -1
  18. package/layers/content/app/components/Portfolio/Card.vue +1 -0
  19. package/layers/content/app/components/Portfolio/ColorPalette.vue +8 -1
  20. package/layers/content/app/components/Portfolio/Typography.vue +12 -2
  21. package/layers/content/app/composables/useGalleryItems.ts +3 -2
  22. package/layers/content/app/composables/usePortfolioItems.ts +3 -2
  23. package/layers/core/app/components/DiagnosticsPage.vue +15 -3
  24. package/layers/core/app/utils/featureClasses.test.ts +1 -3
  25. package/layers/core/app/utils/featureClasses.ts +1 -3
  26. package/layers/core/app/utils/helpers.test.ts +6 -2
  27. package/layers/core/nuxt.config.ts +2 -0
  28. package/layers/feeds/app/assets/css/feeds.css +3 -2
  29. package/layers/feeds/app/components/Feeds/Index.vue +36 -6
  30. package/layers/feeds/app/components/Feeds/RouteCard.vue +1 -3
  31. package/layers/feeds/app/plugins/feed-head.ts +2 -2
  32. package/layers/feeds/package.json +1 -1
  33. package/layers/feeds/public/feed/style.css +9 -9
  34. package/layers/feeds/server/utils/content-adapter.test.ts +3 -4
  35. package/layers/feeds/server/utils/content-adapter.ts +1 -1
  36. package/layers/feeds/server/utils/feed-author.test.ts +3 -3
  37. package/layers/feeds/server/utils/feed-config.test.ts +28 -6
  38. package/layers/feeds/server/utils/feed-config.ts +1 -4
  39. package/layers/feeds/server/utils/feed-service.ts +1 -1
  40. package/layers/feeds/server/utils/formats/rss.ts +1 -1
  41. package/layers/feeds/server/utils/formats.test.ts +1 -1
  42. package/layers/forms/app/utils/fieldProps.ts +1 -2
  43. package/layers/forms/server/api/contact.post.ts +4 -1
  44. package/layers/layout/app/assets/css/layout/grids.css +12 -3
  45. package/layers/layout/app/assets/css/layout/modes/fluid.css +9 -0
  46. package/layers/layout/app/components/Layout/Section/Sidebar.vue +11 -4
  47. package/layers/layout/app/utils/gridPlacementStyle.test.ts +3 -9
  48. package/layers/layout/app/utils/gridPlacementStyle.ts +10 -7
  49. package/layers/mailer/server/utils/email.ts +4 -3
  50. package/layers/mailer/server/utils/hooks.ts +1 -1
  51. package/layers/metadata/app/composables/useMetadataItem.ts +6 -2
  52. package/layers/metadata/app/composables/useMetadataSearch.ts +6 -2
  53. package/layers/metadata/providers/comicvine/server/utils/metadata-comicvine/client.ts +5 -5
  54. package/layers/metadata/providers/comicvine/server/utils/metadata-comicvine/provider.ts +11 -3
  55. package/layers/metadata/providers/google-books/server/utils/metadata-google-books/client.ts +9 -6
  56. package/layers/metadata/providers/google-books/server/utils/metadata-google-books/provider.ts +6 -3
  57. package/layers/metadata/providers/openlibrary/server/utils/metadata-openlibrary/normalise.test.ts +3 -1
  58. package/layers/metadata/providers/openlibrary/server/utils/metadata-openlibrary/provider.ts +4 -2
  59. package/layers/metadata/providers/themoviedb/server/utils/metadata-tmdb/client.test.ts +1 -3
  60. package/layers/metadata/providers/themoviedb/server/utils/metadata-tmdb/provider.ts +3 -1
  61. package/layers/metadata/server/api/metadata/status.get.ts +13 -18
  62. package/layers/metadata/server/utils/metadata/provider-registry.test.ts +1 -1
  63. package/layers/metadata/server/utils/metadata/search.test.ts +18 -18
  64. package/layers/metadata/server/utils/metadata/search.ts +16 -8
  65. package/layers/navigation/app/utils/site.test.ts +1 -3
  66. package/layers/scroll/app/components/Motion/PinnedSection.vue +11 -1
  67. package/layers/scroll/app/composables/useSectionProgress.ts +1 -1
  68. package/layers/scroll/app/plugins/locomotive-scroll.client.ts +6 -0
  69. package/layers/scroll/app/utils/scroll.ts +5 -1
  70. package/layers/seo/app/utils/seoConfig.test.ts +1 -1
  71. package/layers/shader/app/assets/css/shader.css +1 -1
  72. package/layers/shader/app/components/Material/AmbientAurora.client.vue +16 -4
  73. package/layers/shader/app/components/Material/AmbientFlow.client.vue +21 -5
  74. package/layers/shader/app/components/Material/AmbientGradientMesh.client.vue +21 -5
  75. package/layers/shader/app/components/Material/AmbientNebula.client.vue +21 -5
  76. package/layers/shader/app/components/Material/AmbientOcean.client.vue +16 -4
  77. package/layers/shader/app/components/Material/Gradient.client.vue +1 -1
  78. package/layers/shader/app/components/Material/Image.client.vue +1 -1
  79. package/layers/shader/app/components/Material/Noise.client.vue +11 -3
  80. package/layers/shader/app/composables/useAmbientMaterials.ts +0 -1
  81. package/layers/shader/app/shaders/common/blend.ts +5 -1
  82. package/layers/shader/app/shaders/common/grain.ts +5 -1
  83. package/layers/shader/app/shaders/common/lighting.ts +1 -6
  84. package/layers/shader/app/shaders/common/noise.ts +3 -1
  85. package/layers/shader/app/shaders/common/patterns.ts +1 -1
  86. package/layers/shader/app/shaders/common/shapes.ts +5 -1
  87. package/layers/shader/app/shaders/common/uv.ts +19 -2
  88. package/layers/shader/app/shaders/createMaterial.ts +11 -6
  89. package/layers/shader/app/shaders/layers/shaderGradient.ts +2 -12
  90. package/layers/shader/app/shaders/types.ts +1 -1
  91. package/layers/shader/app/types/uniforms.ts +1 -0
  92. package/layers/shader/app/utils/tsl/animation.ts +34 -36
  93. package/layers/starter/app/components/StarterDesignSystem.vue +120 -120
  94. package/layers/starter/app/components/StarterHome.vue +233 -208
  95. package/layers/theme/app/assets/css/theme.css +1 -1
  96. package/layers/theme/server/utils/accent-css.test.ts +17 -2
  97. package/layers/theme/server/utils/fouc-config.test.ts +7 -3
  98. package/layers/theme/server/utils/fouc-config.ts +24 -12
  99. package/layers/transitions/app/assets/css/transitions.css +6 -0
  100. package/layers/typography/app/assets/css/typography.css +313 -0
  101. package/layers/typography/app/components/Typography/Headline.vue +23 -14
  102. package/layers/typography/app/components/Typography/HeadlineScreen.vue +5 -12
  103. package/layers/typography/app/components/Typography/index.vue +4 -0
  104. package/layers/typography/app/composables/typography.ts +45 -5
  105. package/layers/typography/app/types/typography.ts +36 -7
  106. package/layers/typography/nuxt.config.ts +2 -0
  107. package/layers/visual/app/app.config.ts +1 -1
  108. package/layers/visual/app/assets/css/visual.css +1 -1
  109. package/layers/visual/app/composables/accent.ts +1 -2
  110. package/layers/visual/app/composables/tint.ts +1 -2
  111. package/layers/visual/app/types/media.ts +2 -2
  112. package/layers/visual/app/utils/gradientStyle.test.ts +1 -2
  113. package/layers/visual/app/utils/responsiveSizes.ts +0 -1
  114. package/package.json +36 -16
@@ -8,8 +8,8 @@ export const googleBooksProvider: MetadataProvider = {
8
8
  async search({ query, mediaType, limit = 10 }) {
9
9
  const subjectHints: Record<string, string> = {
10
10
  'graphic-novel': '+subject:"Comics & Graphic Novels"',
11
- 'manga': '+subject:manga',
12
- 'book': '+-subject:"Comics & Graphic Novels"+-subject:manga',
11
+ manga: '+subject:manga',
12
+ book: '+-subject:"Comics & Graphic Novels"+-subject:manga',
13
13
  }
14
14
  const q = query + (mediaType ? (subjectHints[mediaType] ?? '') : '')
15
15
  const res = await searchGoogleBooks(q, limit)
@@ -23,6 +23,9 @@ export const googleBooksProvider: MetadataProvider = {
23
23
  },
24
24
 
25
25
  async sync({ providerId }) {
26
- return (await googleBooksProvider.lookup!({ provider: 'google-books', providerId }))!
26
+ const record = await googleBooksProvider.lookup({ provider: 'google-books', providerId })
27
+ if (!record)
28
+ throw new MetadataProviderError('google-books', `No record found for ${providerId}`)
29
+ return record
27
30
  },
28
31
  }
@@ -136,7 +136,9 @@ describe('normaliseOpenLibraryEdition', () => {
136
136
  })
137
137
 
138
138
  it('takes publisher from publishers[0]', () => {
139
- const record = normaliseOpenLibraryEdition(baseEdition({ publishers: ['DC Comics', 'Vertigo'] }))
139
+ const record = normaliseOpenLibraryEdition(
140
+ baseEdition({ publishers: ['DC Comics', 'Vertigo'] })
141
+ )
140
142
  expect(record.publisher).toBe('DC Comics')
141
143
  })
142
144
  })
@@ -20,10 +20,12 @@ export const openLibraryProvider: MetadataProvider = {
20
20
  },
21
21
 
22
22
  async sync({ providerId, resourceType = 'work' }) {
23
- return (await openLibraryProvider.lookup!({
23
+ const record = await openLibraryProvider.lookup({
24
24
  provider: 'openlibrary',
25
25
  providerId,
26
26
  resourceType,
27
- }))!
27
+ })
28
+ if (!record) throw new MetadataProviderError('openlibrary', `No record found for ${providerId}`)
29
+ return record
28
30
  },
29
31
  }
@@ -24,8 +24,6 @@ describe('tmdbImageUrl', () => {
24
24
  })
25
25
 
26
26
  it('prefixes imageBaseUrl from runtime config', () => {
27
- expect(tmdbImageUrl('/backdrop.jpg')).toBe(
28
- 'https://image.tmdb.org/t/p/w500/backdrop.jpg'
29
- )
27
+ expect(tmdbImageUrl('/backdrop.jpg')).toBe('https://image.tmdb.org/t/p/w500/backdrop.jpg')
30
28
  })
31
29
  })
@@ -32,6 +32,8 @@ export const tmdbProvider: MetadataProvider = {
32
32
  },
33
33
 
34
34
  async sync(input) {
35
- return (await tmdbProvider.lookup!(input))!
35
+ const record = await tmdbProvider.lookup(input)
36
+ if (!record) throw new MetadataProviderError('tmdb', `No record found for ${input.providerId}`)
37
+ return record
36
38
  },
37
39
  }
@@ -1,24 +1,19 @@
1
1
  export default defineEventHandler(async () => {
2
2
  const providers = getProviders()
3
3
 
4
- const results = await Promise.allSettled(
4
+ return Promise.all(
5
5
  providers.map(async (p) => {
6
- await p.search({ query: 'test', limit: 1 })
7
- return { id: p.id, label: p.label, ok: true, error: null }
8
- }),
6
+ try {
7
+ await p.search({ query: 'test', limit: 1 })
8
+ return { id: p.id, label: p.label, ok: true, error: null }
9
+ } catch (err) {
10
+ return {
11
+ id: p.id,
12
+ label: p.label,
13
+ ok: false,
14
+ error: err instanceof Error ? err.message : String(err),
15
+ }
16
+ }
17
+ })
9
18
  )
10
-
11
- return results.map((outcome, i) => {
12
- const p = providers[i]!
13
- if (outcome.status === 'fulfilled') {
14
- return { id: p.id, label: p.label, ok: true, error: null }
15
- }
16
- const err = outcome.reason
17
- return {
18
- id: p.id,
19
- label: p.label,
20
- ok: false,
21
- error: err instanceof Error ? err.message : String(err),
22
- }
23
- })
24
19
  })
@@ -1,7 +1,7 @@
1
+ import type { MetadataProvider } from '#layers/metadata/shared/types'
1
2
  import { describe, expect, it } from 'vitest'
2
3
 
3
4
  import { getProvider, getProviders, registerProvider } from './provider-registry'
4
- import type { MetadataProvider } from '#layers/metadata/shared/types'
5
5
 
6
6
  function makeProvider(overrides: Partial<MetadataProvider> = {}): MetadataProvider {
7
7
  return {
@@ -1,17 +1,17 @@
1
- import { beforeEach, describe, expect, it, vi } from 'vitest'
2
-
3
- import { hashQuery, makeCacheKey } from './cache'
4
1
  import type {
5
2
  MetadataProvider,
6
3
  MetadataSearchInput,
7
4
  MetadataSearchResult,
8
5
  } from '#layers/metadata/shared/types'
6
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
7
+
8
+ import { hashQuery, makeCacheKey } from './cache'
9
9
 
10
10
  // search.ts calls getProviders()/makeCacheKey()/hashQuery()/getSearchCache()/setSearchCache()
11
11
  // as Nitro auto-imports (sibling server/utils files). Stub the pure cache helpers with their
12
12
  // real implementations, and control provider/cache lookups directly via mocks.
13
- let mockProviders: MetadataProvider[] = []
14
- const getProvidersMock = vi.fn(() => mockProviders)
13
+ const providerState: { mockProviders: MetadataProvider[] } = { mockProviders: [] }
14
+ const getProvidersMock = vi.fn(() => providerState.mockProviders)
15
15
  const getSearchCacheMock = vi.fn(async () => null as MetadataSearchResult[] | null)
16
16
  const setSearchCacheMock = vi.fn(async () => undefined)
17
17
 
@@ -46,7 +46,7 @@ function makeProvider(overrides: Partial<MetadataProvider> = {}): MetadataProvid
46
46
  }
47
47
 
48
48
  beforeEach(() => {
49
- mockProviders = []
49
+ providerState.mockProviders = []
50
50
  getProvidersMock.mockClear()
51
51
  getSearchCacheMock.mockClear()
52
52
  getSearchCacheMock.mockResolvedValue(null)
@@ -61,7 +61,7 @@ describe('searchMetadata', () => {
61
61
  it('targets only providers named in input.providers', async () => {
62
62
  const a = vi.fn(async () => [makeResult({ id: 'a-1' })])
63
63
  const b = vi.fn(async () => [makeResult({ id: 'b-1' })])
64
- mockProviders = [
64
+ providerState.mockProviders = [
65
65
  makeProvider({ id: 'comicvine', search: a }),
66
66
  makeProvider({ id: 'openlibrary', mediaTypes: ['book'], search: b }),
67
67
  ]
@@ -75,7 +75,7 @@ describe('searchMetadata', () => {
75
75
  it('targets providers by mediaType when providers[] is not given', async () => {
76
76
  const comic = vi.fn(async () => [makeResult()])
77
77
  const book = vi.fn(async () => [makeResult({ mediaType: 'book' })])
78
- mockProviders = [
78
+ providerState.mockProviders = [
79
79
  makeProvider({ id: 'comicvine', mediaTypes: ['comic'], search: comic }),
80
80
  makeProvider({ id: 'openlibrary', mediaTypes: ['book'], search: book }),
81
81
  ]
@@ -89,7 +89,7 @@ describe('searchMetadata', () => {
89
89
  it('targets all providers when neither providers[] nor mediaType is given', async () => {
90
90
  const comic = vi.fn(async () => [makeResult()])
91
91
  const book = vi.fn(async () => [makeResult({ mediaType: 'book' })])
92
- mockProviders = [
92
+ providerState.mockProviders = [
93
93
  makeProvider({ id: 'comicvine', mediaTypes: ['comic'], search: comic }),
94
94
  makeProvider({ id: 'openlibrary', mediaTypes: ['book'], search: book }),
95
95
  ]
@@ -101,7 +101,7 @@ describe('searchMetadata', () => {
101
101
  })
102
102
 
103
103
  it('returns [] when no providers match the given targets', async () => {
104
- mockProviders = [makeProvider({ id: 'comicvine', mediaTypes: ['comic'] })]
104
+ providerState.mockProviders = [makeProvider({ id: 'comicvine', mediaTypes: ['comic'] })]
105
105
 
106
106
  const result = await searchMetadata(baseInput({ mediaType: 'movie' }))
107
107
 
@@ -110,7 +110,7 @@ describe('searchMetadata', () => {
110
110
  })
111
111
 
112
112
  it('returns [] when providers[] names an id with no registered provider', async () => {
113
- mockProviders = [makeProvider({ id: 'comicvine' })]
113
+ providerState.mockProviders = [makeProvider({ id: 'comicvine' })]
114
114
 
115
115
  const result = await searchMetadata(baseInput({ providers: ['tmdb'] }))
116
116
 
@@ -122,7 +122,7 @@ describe('searchMetadata', () => {
122
122
  const failing = vi.fn(async () => {
123
123
  throw new Error('boom')
124
124
  })
125
- mockProviders = [
125
+ providerState.mockProviders = [
126
126
  makeProvider({ id: 'comicvine', search: ok }),
127
127
  makeProvider({ id: 'openlibrary', mediaTypes: ['book'], search: failing }),
128
128
  ]
@@ -132,7 +132,7 @@ describe('searchMetadata', () => {
132
132
  const result = await searchMetadata(baseInput())
133
133
 
134
134
  expect(result).toHaveLength(1)
135
- expect(result[0]!.id).toBe('ok-1')
135
+ expect(result.at(0)?.id).toBe('ok-1')
136
136
  expect(consoleErrorSpy).toHaveBeenCalled()
137
137
 
138
138
  consoleErrorSpy.mockRestore()
@@ -145,7 +145,7 @@ describe('searchMetadata', () => {
145
145
  const b = vi.fn(async () => [
146
146
  makeResult({ id: 'b-1', title: 'WATCHMEN', mediaType: 'comic', publishedAt: '1986-12-25' }),
147
147
  ])
148
- mockProviders = [
148
+ providerState.mockProviders = [
149
149
  makeProvider({ id: 'comicvine', search: a }),
150
150
  makeProvider({ id: 'openlibrary', mediaTypes: ['comic'], search: b }),
151
151
  ]
@@ -153,7 +153,7 @@ describe('searchMetadata', () => {
153
153
  const result = await searchMetadata(baseInput())
154
154
 
155
155
  expect(result).toHaveLength(1)
156
- expect(result[0]!.id).toBe('a-1')
156
+ expect(result.at(0)?.id).toBe('a-1')
157
157
  })
158
158
 
159
159
  it('does not dedupe results with different years', async () => {
@@ -163,7 +163,7 @@ describe('searchMetadata', () => {
163
163
  const b = vi.fn(async () => [
164
164
  makeResult({ id: 'b-1', title: 'Watchmen', publishedAt: '2019-10-20' }),
165
165
  ])
166
- mockProviders = [
166
+ providerState.mockProviders = [
167
167
  makeProvider({ id: 'comicvine', search: a }),
168
168
  makeProvider({ id: 'openlibrary', mediaTypes: ['comic'], search: b }),
169
169
  ]
@@ -177,7 +177,7 @@ describe('searchMetadata', () => {
177
177
  const cached = [makeResult({ id: 'cached-1' })]
178
178
  getSearchCacheMock.mockResolvedValue(cached)
179
179
  const search = vi.fn(async () => [makeResult({ id: 'live-1' })])
180
- mockProviders = [makeProvider({ id: 'comicvine', search })]
180
+ providerState.mockProviders = [makeProvider({ id: 'comicvine', search })]
181
181
 
182
182
  const result = await searchMetadata(baseInput())
183
183
 
@@ -188,7 +188,7 @@ describe('searchMetadata', () => {
188
188
 
189
189
  it('writes deduped results to the cache after a live search', async () => {
190
190
  const search = vi.fn(async () => [makeResult({ id: 'live-1' })])
191
- mockProviders = [makeProvider({ id: 'comicvine', search })]
191
+ providerState.mockProviders = [makeProvider({ id: 'comicvine', search })]
192
192
 
193
193
  const result = await searchMetadata(baseInput())
194
194
 
@@ -3,8 +3,9 @@ import type { MetadataSearchInput, MetadataSearchResult } from '#layers/metadata
3
3
  // fallow-ignore-next-line complexity
4
4
  export async function searchMetadata(input: MetadataSearchInput): Promise<MetadataSearchResult[]> {
5
5
  const all = getProviders()
6
- const targets = input.providers?.length
7
- ? all.filter((p) => input.providers!.includes(p.id))
6
+ const requestedProviders = input.providers
7
+ const targets = requestedProviders?.length
8
+ ? all.filter((p) => requestedProviders.includes(p.id))
8
9
  : all.filter((p) => !input.mediaType || p.mediaTypes.includes(input.mediaType))
9
10
 
10
11
  if (!targets.length) return []
@@ -19,16 +20,23 @@ export async function searchMetadata(input: MetadataSearchInput): Promise<Metada
19
20
  const cached = await getSearchCache<MetadataSearchResult[]>(cacheKey)
20
21
  if (cached) return cached
21
22
 
22
- const settled = await Promise.allSettled(targets.map((p) => p.search(input)))
23
+ const settled = await Promise.all(
24
+ targets.map(async (p) => {
25
+ try {
26
+ return { provider: p, value: await p.search(input) }
27
+ } catch (reason) {
28
+ return { provider: p, reason }
29
+ }
30
+ })
31
+ )
23
32
 
24
33
  const results: MetadataSearchResult[] = []
25
- for (let i = 0; i < settled.length; i++) {
26
- const outcome = settled[i]!
27
- if (outcome.status === 'fulfilled') {
34
+ for (const outcome of settled) {
35
+ if ('value' in outcome) {
28
36
  results.push(...outcome.value)
29
- } else {
30
- console.error(`[metadata] provider "${targets[i]!.id}" search failed:`, outcome.reason)
37
+ continue
31
38
  }
39
+ console.error(`[metadata] provider "${outcome.provider.id}" search failed:`, outcome.reason)
32
40
  }
33
41
 
34
42
  const deduped = deduplicateResults(results)
@@ -14,9 +14,7 @@ describe('resolveSiteConfig', () => {
14
14
  })
15
15
 
16
16
  it('splits the title and subtitle into words', () => {
17
- expect(
18
- resolveSiteConfig({ title: 'Nuxt Layers', subtitle: 'Composable Vue apps' })
19
- ).toEqual({
17
+ expect(resolveSiteConfig({ title: 'Nuxt Layers', subtitle: 'Composable Vue apps' })).toEqual({
20
18
  title: 'Nuxt Layers',
21
19
  titleWords: ['Nuxt', 'Layers'],
22
20
  subtitle: 'Composable Vue apps',
@@ -73,8 +73,18 @@
73
73
 
74
74
  <template>
75
75
  <section ref="sectionRef" class="motion-pinned-section h-screen relative">
76
- <div ref="contentRef" class="h-full flex items-center justify-center">
76
+ <div ref="contentRef" class="motion-pinned-content h-full flex items-center justify-center">
77
77
  <slot />
78
78
  </div>
79
79
  </section>
80
80
  </template>
81
+
82
+ <style scoped>
83
+ /* Match gsap.set()'s initial state (opacity:0, y:60, scale:0.9) so the SSR/first-paint
84
+ frame is already hidden — GSAP then animates from this state instead of visibly
85
+ snapping into it post-mount, which reads as a layout shift. */
86
+ .motion-pinned-content > :deep(*) {
87
+ transform: translateY(60px) scale(0.9);
88
+ opacity: 0;
89
+ }
90
+ </style>
@@ -1,5 +1,5 @@
1
- import type { ScrollTrigger } from 'gsap/ScrollTrigger'
2
1
  import type { MaybeRef } from 'vue'
2
+ import type { ScrollTrigger } from 'gsap/ScrollTrigger'
3
3
 
4
4
  type SectionProgressOptions = {
5
5
  start?: string
@@ -1,5 +1,6 @@
1
1
  import { ScrollTrigger } from 'gsap/ScrollTrigger'
2
2
  import LocomotiveScroll from 'locomotive-scroll'
3
+
3
4
  import type { ScrollState } from '../types/scroll'
4
5
 
5
6
  export default defineNuxtPlugin({
@@ -32,6 +33,11 @@ export default defineNuxtPlugin({
32
33
  ScrollTrigger.update()
33
34
  },
34
35
  })
36
+
37
+ // ScrollTriggers created before Lenis exists (page:finish can fire first)
38
+ // refresh against a null instance — remeasure once real layout settles.
39
+ requestAnimationFrame(() => ScrollTrigger.refresh())
40
+ document.fonts?.ready?.then(() => ScrollTrigger.refresh())
35
41
  }
36
42
 
37
43
  function destroy() {
@@ -91,7 +91,11 @@ export function runNativeScroll(target: ScrollTarget, options?: ScrollToOptions)
91
91
  return
92
92
  }
93
93
 
94
- runSmoothScroll(targetPosition, resolveNativeScrollDelay(options), resolveScrollOnComplete(options))
94
+ runSmoothScroll(
95
+ targetPosition,
96
+ resolveNativeScrollDelay(options),
97
+ resolveScrollOnComplete(options)
98
+ )
95
99
  }
96
100
 
97
101
  export function runLocomotiveScroll(
@@ -1,6 +1,6 @@
1
+ import type { SiteConfig } from '#layers/core/app/types/site'
1
2
  import { describe, expect, it } from 'vitest'
2
3
 
3
- import type { SiteConfig } from '#layers/core/app/types/site'
4
4
  import { resolveSeoConfig } from './seoConfig'
5
5
 
6
6
  const site: SiteConfig = { title: 'Nuxt Layers', description: 'A composable layer system' }
@@ -12,7 +12,7 @@
12
12
  z-index: 9999;
13
13
  backdrop-filter: blur(8px);
14
14
  border-radius: 0.5rem;
15
- background: rgba(0, 0, 0, 0.8);
15
+ background: rgb(0 0 0 / 0.8);
16
16
  padding: 0.75rem 1rem;
17
17
  min-width: 200px;
18
18
  color: #fff;
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { watchUniformProp } from '#layers/shader/app/composables/useUniformWatchers'
2
3
  import { Color, DoubleSide } from 'three'
3
4
  import {
4
5
  add,
@@ -18,7 +19,6 @@
18
19
  import { fbm2D, simplexNoise2D } from '../../shaders/common/noise'
19
20
  import { cosinePalette } from '../../shaders/common/palette'
20
21
  import type { TSLNode } from '../../shaders/types'
21
- import { watchUniformProp } from '#layers/shader/app/composables/useUniformWatchers'
22
22
 
23
23
  const {
24
24
  speed = 1.0,
@@ -58,10 +58,22 @@
58
58
  watchUniformProp(() => mouseX, mouseXUniform)
59
59
  watchUniformProp(() => mouseY, mouseYUniform)
60
60
  watchUniformProp(() => mouseStrength, mouseStrengthUniform)
61
- watchUniformProp(() => color1, color1Uniform, (val) => new Color(val))
62
- watchUniformProp(() => color2, color2Uniform, (val) => new Color(val))
61
+ watchUniformProp(
62
+ () => color1,
63
+ color1Uniform,
64
+ (val) => new Color(val)
65
+ )
66
+ watchUniformProp(
67
+ () => color2,
68
+ color2Uniform,
69
+ (val) => new Color(val)
70
+ )
63
71
  // fallow-ignore-next-line code-duplication
64
- watchUniformProp(() => color3, color3Uniform, (val) => new Color(val))
72
+ watchUniformProp(
73
+ () => color3,
74
+ color3Uniform,
75
+ (val) => new Color(val)
76
+ )
65
77
 
66
78
  const material = computed(() => {
67
79
  const mat = new MeshBasicNodeMaterial()
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { watchUniformProp } from '#layers/shader/app/composables/useUniformWatchers'
2
3
  import { Color, DoubleSide } from 'three'
3
4
  import {
4
5
  add,
@@ -18,7 +19,6 @@
18
19
  import { fbm2D, ridgedFbm2d, warpedFbmCoords } from '../../shaders/common/noise'
19
20
  import { cosinePalette } from '../../shaders/common/palette'
20
21
  import type { TSLNode } from '../../shaders/types'
21
- import { watchUniformProp } from '#layers/shader/app/composables/useUniformWatchers'
22
22
 
23
23
  const {
24
24
  speed = 1.0,
@@ -60,10 +60,26 @@
60
60
  watchUniformProp(() => mouseX, mouseXUniform)
61
61
  watchUniformProp(() => mouseY, mouseYUniform)
62
62
  watchUniformProp(() => mouseStrength, mouseStrengthUniform)
63
- watchUniformProp(() => color1, color1Uniform, (val) => new Color(val))
64
- watchUniformProp(() => color2, color2Uniform, (val) => new Color(val))
65
- watchUniformProp(() => color3, color3Uniform, (val) => new Color(val))
66
- watchUniformProp(() => color4, color4Uniform, (val) => new Color(val))
63
+ watchUniformProp(
64
+ () => color1,
65
+ color1Uniform,
66
+ (val) => new Color(val)
67
+ )
68
+ watchUniformProp(
69
+ () => color2,
70
+ color2Uniform,
71
+ (val) => new Color(val)
72
+ )
73
+ watchUniformProp(
74
+ () => color3,
75
+ color3Uniform,
76
+ (val) => new Color(val)
77
+ )
78
+ watchUniformProp(
79
+ () => color4,
80
+ color4Uniform,
81
+ (val) => new Color(val)
82
+ )
67
83
 
68
84
  const material = computed(() => {
69
85
  const mat = new MeshBasicNodeMaterial()
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { watchUniformProp } from '#layers/shader/app/composables/useUniformWatchers'
2
3
  import { Color, DoubleSide } from 'three'
3
4
  import {
4
5
  add,
@@ -19,7 +20,6 @@
19
20
 
20
21
  import { fbm2D, simplexNoise2D } from '../../shaders/common/noise'
21
22
  import type { TSLNode } from '../../shaders/types'
22
- import { watchUniformProp } from '#layers/shader/app/composables/useUniformWatchers'
23
23
 
24
24
  const {
25
25
  speed = 1.0,
@@ -61,10 +61,26 @@
61
61
  watchUniformProp(() => mouseX, mouseXUniform)
62
62
  watchUniformProp(() => mouseY, mouseYUniform)
63
63
  watchUniformProp(() => mouseStrength, mouseStrengthUniform)
64
- watchUniformProp(() => color1, color1Uniform, (val) => new Color(val))
65
- watchUniformProp(() => color2, color2Uniform, (val) => new Color(val))
66
- watchUniformProp(() => color3, color3Uniform, (val) => new Color(val))
67
- watchUniformProp(() => color4, color4Uniform, (val) => new Color(val))
64
+ watchUniformProp(
65
+ () => color1,
66
+ color1Uniform,
67
+ (val) => new Color(val)
68
+ )
69
+ watchUniformProp(
70
+ () => color2,
71
+ color2Uniform,
72
+ (val) => new Color(val)
73
+ )
74
+ watchUniformProp(
75
+ () => color3,
76
+ color3Uniform,
77
+ (val) => new Color(val)
78
+ )
79
+ watchUniformProp(
80
+ () => color4,
81
+ color4Uniform,
82
+ (val) => new Color(val)
83
+ )
68
84
 
69
85
  const material = computed(() => {
70
86
  const mat = new MeshBasicNodeMaterial()
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { watchUniformProp } from '#layers/shader/app/composables/useUniformWatchers'
2
3
  import { Color, DoubleSide } from 'three'
3
4
  import {
4
5
  add,
@@ -19,7 +20,6 @@
19
20
  import { fbm3D, hash21, ridgedFbm2d, voronoi2D } from '../../shaders/common/noise'
20
21
  import { cosinePalette } from '../../shaders/common/palette'
21
22
  import type { TSLNode } from '../../shaders/types'
22
- import { watchUniformProp } from '#layers/shader/app/composables/useUniformWatchers'
23
23
 
24
24
  const {
25
25
  // fallow-ignore-next-line code-duplication
@@ -62,11 +62,27 @@
62
62
  watchUniformProp(() => mouseX, mouseXUniform)
63
63
  watchUniformProp(() => mouseY, mouseYUniform)
64
64
  watchUniformProp(() => mouseStrength, mouseStrengthUniform)
65
- watchUniformProp(() => color1, color1Uniform, (val) => new Color(val))
66
- watchUniformProp(() => color2, color2Uniform, (val) => new Color(val))
67
- watchUniformProp(() => color3, color3Uniform, (val) => new Color(val))
65
+ watchUniformProp(
66
+ () => color1,
67
+ color1Uniform,
68
+ (val) => new Color(val)
69
+ )
70
+ watchUniformProp(
71
+ () => color2,
72
+ color2Uniform,
73
+ (val) => new Color(val)
74
+ )
75
+ watchUniformProp(
76
+ () => color3,
77
+ color3Uniform,
78
+ (val) => new Color(val)
79
+ )
68
80
  // fallow-ignore-next-line code-duplication
69
- watchUniformProp(() => color4, color4Uniform, (val) => new Color(val))
81
+ watchUniformProp(
82
+ () => color4,
83
+ color4Uniform,
84
+ (val) => new Color(val)
85
+ )
70
86
 
71
87
  const material = computed(() => {
72
88
  const mat = new MeshBasicNodeMaterial()
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { watchUniformProp } from '#layers/shader/app/composables/useUniformWatchers'
2
3
  import { Color, DoubleSide } from 'three'
3
4
  import {
4
5
  add,
@@ -20,7 +21,6 @@
20
21
 
21
22
  import { simplexNoise2D } from '../../shaders/common/noise'
22
23
  import type { TSLNode } from '../../shaders/types'
23
- import { watchUniformProp } from '#layers/shader/app/composables/useUniformWatchers'
24
24
 
25
25
  const {
26
26
  speed = 1.0,
@@ -59,9 +59,21 @@
59
59
  watchUniformProp(() => mouseX, mouseXUniform)
60
60
  watchUniformProp(() => mouseY, mouseYUniform)
61
61
  watchUniformProp(() => mouseStrength, mouseStrengthUniform)
62
- watchUniformProp(() => color1, color1Uniform, (val) => new Color(val))
63
- watchUniformProp(() => color2, color2Uniform, (val) => new Color(val))
64
- watchUniformProp(() => color3, color3Uniform, (val) => new Color(val))
62
+ watchUniformProp(
63
+ () => color1,
64
+ color1Uniform,
65
+ (val) => new Color(val)
66
+ )
67
+ watchUniformProp(
68
+ () => color2,
69
+ color2Uniform,
70
+ (val) => new Color(val)
71
+ )
72
+ watchUniformProp(
73
+ () => color3,
74
+ color3Uniform,
75
+ (val) => new Color(val)
76
+ )
65
77
 
66
78
  const material = computed(() => {
67
79
  const mat = new MeshBasicNodeMaterial()
@@ -1,10 +1,10 @@
1
1
  <script setup lang="ts">
2
+ import { watchUniformProp } from '#layers/shader/app/composables/useUniformWatchers'
2
3
  import { Color, DoubleSide } from 'three'
3
4
  import { mix, sin, time as tslTime, uniform, uv, vec3 } from 'three/tsl'
4
5
  import { MeshBasicNodeMaterial } from 'three/webgpu'
5
6
 
6
7
  import type { TSLNode } from '../../types'
7
- import { watchUniformProp } from '#layers/shader/app/composables/useUniformWatchers'
8
8
 
9
9
  const {
10
10
  colors = ['#4f46e5', '#7c3aed', '#ec4899'],
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { watchUniformProp } from '#layers/shader/app/composables/useUniformWatchers'
2
3
  import { DoubleSide, type Texture } from 'three'
3
4
  import {
4
5
  float,
@@ -17,7 +18,6 @@
17
18
 
18
19
  import type { TSLNode } from '../../types'
19
20
  import { simplexNoise2D } from '../../utils/tsl/noise'
20
- import { watchUniformProp } from '#layers/shader/app/composables/useUniformWatchers'
21
21
 
22
22
  const {
23
23
  src,