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.
- package/docs/ARCHITECTURE.md +21 -17
- package/docs/FEEDS.md +42 -34
- package/docs/IMPROVE-AUDIT-README.md +9 -10
- package/docs/IMPROVE-AUDIT-RESULTS.md +16 -17
- package/docs/IMPROVE-DEEP-AUDIT-RESULTS.md +29 -29
- package/docs/LAYERS-FIXES.md +1 -0
- package/docs/LAYOUT.md +91 -86
- package/docs/MIGRATION.md +93 -60
- package/docs/UPAGE-GRID-INTEGRATION.md +12 -10
- package/docs/USAGE.md +29 -38
- package/layers/animations/app/utils/pointerMotion.ts +1 -4
- package/layers/baseline/app/components/BaselineStatus.vue +13 -13
- package/layers/baseline/app/plugins/baseline-status.client.ts +4 -1
- package/layers/canvas/app/composables/useRendererCapabilities.ts +2 -2
- package/layers/content/app/components/Blog/Card.vue +1 -0
- package/layers/content/app/components/Gallery/Card.vue +1 -1
- package/layers/content/app/components/Gallery/Detail.vue +8 -1
- package/layers/content/app/components/Portfolio/Card.vue +1 -0
- package/layers/content/app/components/Portfolio/ColorPalette.vue +8 -1
- package/layers/content/app/components/Portfolio/Typography.vue +12 -2
- package/layers/content/app/composables/useGalleryItems.ts +3 -2
- package/layers/content/app/composables/usePortfolioItems.ts +3 -2
- package/layers/core/app/components/DiagnosticsPage.vue +15 -3
- package/layers/core/app/utils/featureClasses.test.ts +1 -3
- package/layers/core/app/utils/featureClasses.ts +1 -3
- package/layers/core/app/utils/helpers.test.ts +6 -2
- package/layers/core/nuxt.config.ts +2 -0
- package/layers/feeds/app/assets/css/feeds.css +3 -2
- package/layers/feeds/app/components/Feeds/Index.vue +36 -6
- package/layers/feeds/app/components/Feeds/RouteCard.vue +1 -3
- package/layers/feeds/app/plugins/feed-head.ts +2 -2
- package/layers/feeds/package.json +1 -1
- package/layers/feeds/public/feed/style.css +9 -9
- package/layers/feeds/server/utils/content-adapter.test.ts +3 -4
- package/layers/feeds/server/utils/content-adapter.ts +1 -1
- package/layers/feeds/server/utils/feed-author.test.ts +3 -3
- package/layers/feeds/server/utils/feed-config.test.ts +28 -6
- package/layers/feeds/server/utils/feed-config.ts +1 -4
- package/layers/feeds/server/utils/feed-service.ts +1 -1
- package/layers/feeds/server/utils/formats/rss.ts +1 -1
- package/layers/feeds/server/utils/formats.test.ts +1 -1
- package/layers/forms/app/utils/fieldProps.ts +1 -2
- package/layers/forms/server/api/contact.post.ts +4 -1
- package/layers/layout/app/assets/css/layout/grids.css +12 -3
- package/layers/layout/app/assets/css/layout/modes/fluid.css +9 -0
- package/layers/layout/app/components/Layout/Section/Sidebar.vue +11 -4
- package/layers/layout/app/utils/gridPlacementStyle.test.ts +3 -9
- package/layers/layout/app/utils/gridPlacementStyle.ts +10 -7
- package/layers/mailer/server/utils/email.ts +4 -3
- package/layers/mailer/server/utils/hooks.ts +1 -1
- package/layers/metadata/app/composables/useMetadataItem.ts +6 -2
- package/layers/metadata/app/composables/useMetadataSearch.ts +6 -2
- package/layers/metadata/providers/comicvine/server/utils/metadata-comicvine/client.ts +5 -5
- package/layers/metadata/providers/comicvine/server/utils/metadata-comicvine/provider.ts +11 -3
- package/layers/metadata/providers/google-books/server/utils/metadata-google-books/client.ts +9 -6
- package/layers/metadata/providers/google-books/server/utils/metadata-google-books/provider.ts +6 -3
- package/layers/metadata/providers/openlibrary/server/utils/metadata-openlibrary/normalise.test.ts +3 -1
- package/layers/metadata/providers/openlibrary/server/utils/metadata-openlibrary/provider.ts +4 -2
- package/layers/metadata/providers/themoviedb/server/utils/metadata-tmdb/client.test.ts +1 -3
- package/layers/metadata/providers/themoviedb/server/utils/metadata-tmdb/provider.ts +3 -1
- package/layers/metadata/server/api/metadata/status.get.ts +13 -18
- package/layers/metadata/server/utils/metadata/provider-registry.test.ts +1 -1
- package/layers/metadata/server/utils/metadata/search.test.ts +18 -18
- package/layers/metadata/server/utils/metadata/search.ts +16 -8
- package/layers/navigation/app/utils/site.test.ts +1 -3
- package/layers/scroll/app/components/Motion/PinnedSection.vue +11 -1
- package/layers/scroll/app/composables/useSectionProgress.ts +1 -1
- package/layers/scroll/app/plugins/locomotive-scroll.client.ts +6 -0
- package/layers/scroll/app/utils/scroll.ts +5 -1
- package/layers/seo/app/utils/seoConfig.test.ts +1 -1
- package/layers/shader/app/assets/css/shader.css +1 -1
- package/layers/shader/app/components/Material/AmbientAurora.client.vue +16 -4
- package/layers/shader/app/components/Material/AmbientFlow.client.vue +21 -5
- package/layers/shader/app/components/Material/AmbientGradientMesh.client.vue +21 -5
- package/layers/shader/app/components/Material/AmbientNebula.client.vue +21 -5
- package/layers/shader/app/components/Material/AmbientOcean.client.vue +16 -4
- package/layers/shader/app/components/Material/Gradient.client.vue +1 -1
- package/layers/shader/app/components/Material/Image.client.vue +1 -1
- package/layers/shader/app/components/Material/Noise.client.vue +11 -3
- package/layers/shader/app/composables/useAmbientMaterials.ts +0 -1
- package/layers/shader/app/shaders/common/blend.ts +5 -1
- package/layers/shader/app/shaders/common/grain.ts +5 -1
- package/layers/shader/app/shaders/common/lighting.ts +1 -6
- package/layers/shader/app/shaders/common/noise.ts +3 -1
- package/layers/shader/app/shaders/common/patterns.ts +1 -1
- package/layers/shader/app/shaders/common/shapes.ts +5 -1
- package/layers/shader/app/shaders/common/uv.ts +19 -2
- package/layers/shader/app/shaders/createMaterial.ts +11 -6
- package/layers/shader/app/shaders/layers/shaderGradient.ts +2 -12
- package/layers/shader/app/shaders/types.ts +1 -1
- package/layers/shader/app/types/uniforms.ts +1 -0
- package/layers/shader/app/utils/tsl/animation.ts +34 -36
- package/layers/starter/app/components/StarterDesignSystem.vue +120 -120
- package/layers/starter/app/components/StarterHome.vue +233 -208
- package/layers/theme/app/assets/css/theme.css +1 -1
- package/layers/theme/server/utils/accent-css.test.ts +17 -2
- package/layers/theme/server/utils/fouc-config.test.ts +7 -3
- package/layers/theme/server/utils/fouc-config.ts +24 -12
- package/layers/transitions/app/assets/css/transitions.css +6 -0
- package/layers/typography/app/assets/css/typography.css +313 -0
- package/layers/typography/app/components/Typography/Headline.vue +23 -14
- package/layers/typography/app/components/Typography/HeadlineScreen.vue +5 -12
- package/layers/typography/app/components/Typography/index.vue +4 -0
- package/layers/typography/app/composables/typography.ts +45 -5
- package/layers/typography/app/types/typography.ts +36 -7
- package/layers/typography/nuxt.config.ts +2 -0
- package/layers/visual/app/app.config.ts +1 -1
- package/layers/visual/app/assets/css/visual.css +1 -1
- package/layers/visual/app/composables/accent.ts +1 -2
- package/layers/visual/app/composables/tint.ts +1 -2
- package/layers/visual/app/types/media.ts +2 -2
- package/layers/visual/app/utils/gradientStyle.test.ts +1 -2
- package/layers/visual/app/utils/responsiveSizes.ts +0 -1
- package/package.json +36 -16
package/layers/metadata/providers/google-books/server/utils/metadata-google-books/provider.ts
CHANGED
|
@@ -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
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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
|
}
|
package/layers/metadata/providers/openlibrary/server/utils/metadata-openlibrary/normalise.test.ts
CHANGED
|
@@ -136,7 +136,9 @@ describe('normaliseOpenLibraryEdition', () => {
|
|
|
136
136
|
})
|
|
137
137
|
|
|
138
138
|
it('takes publisher from publishers[0]', () => {
|
|
139
|
-
const record = normaliseOpenLibraryEdition(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4
|
+
return Promise.all(
|
|
5
5
|
providers.map(async (p) => {
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
7
|
-
|
|
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.
|
|
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 (
|
|
26
|
-
|
|
27
|
-
if (outcome.status === 'fulfilled') {
|
|
34
|
+
for (const outcome of settled) {
|
|
35
|
+
if ('value' in outcome) {
|
|
28
36
|
results.push(...outcome.value)
|
|
29
|
-
|
|
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,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(
|
|
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' }
|
|
@@ -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(
|
|
62
|
-
|
|
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(
|
|
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(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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(
|
|
66
|
-
|
|
67
|
-
|
|
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(
|
|
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(
|
|
63
|
-
|
|
64
|
-
|
|
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,
|