kmcom-nuxt-layers 2.3.1 → 2.5.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 (76) hide show
  1. package/README.md +2 -0
  2. package/docs/ARCHITECTURE.md +4 -0
  3. package/layers/auth/app/middleware/auth.ts +4 -0
  4. package/layers/auth/nuxt.config.ts +31 -0
  5. package/layers/auth/package.json +15 -0
  6. package/layers/auth/server/routes/auth/github.get.ts +13 -0
  7. package/layers/auth/tsconfig.json +7 -0
  8. package/layers/baseline/app/components/BaselineStatus.vue +17 -0
  9. package/layers/baseline/app/plugins/baseline-status.client.ts +3 -0
  10. package/layers/baseline/nuxt.config.ts +24 -0
  11. package/layers/baseline/package.json +14 -0
  12. package/layers/baseline/tsconfig.json +7 -0
  13. package/layers/content/app/composables/createPortfolioComposables.ts +2 -1
  14. package/layers/content/app/composables/useBlogPosts.ts +2 -1
  15. package/layers/content/app/composables/useGalleryItems.ts +14 -8
  16. package/layers/content/app/composables/usePortfolioItems.ts +18 -10
  17. package/layers/database/nuxt.config.ts +30 -0
  18. package/layers/database/package.json +16 -0
  19. package/layers/database/server/utils/drizzle.ts +5 -0
  20. package/layers/database/server/utils/sql.ts +7 -0
  21. package/layers/database/tsconfig.json +7 -0
  22. package/layers/feeds/server/utils/content-adapter.ts +6 -0
  23. package/layers/forms/app/components/Form/Contact.vue +4 -13
  24. package/layers/forms/app/utils/contact-schema.ts +9 -0
  25. package/layers/forms/server/api/contact.post.ts +23 -6
  26. package/layers/forms/server/api/forms/status.get.ts +1 -3
  27. package/layers/layout/app/utils/gridPlacementStyle.ts +10 -9
  28. package/layers/metadata/app/composables/useMetadataItem.ts +11 -0
  29. package/layers/metadata/app/composables/useMetadataSearch.ts +25 -0
  30. package/layers/metadata/nuxt.config.ts +27 -0
  31. package/layers/metadata/package.json +12 -0
  32. package/layers/metadata/server/api/metadata/lookup.get.ts +40 -0
  33. package/layers/metadata/server/api/metadata/providers.get.ts +7 -0
  34. package/layers/metadata/server/api/metadata/search.get.ts +17 -0
  35. package/layers/metadata/server/api/metadata/sync.post.ts +40 -0
  36. package/layers/metadata/server/utils/metadata/cache.ts +36 -0
  37. package/layers/metadata/server/utils/metadata/errors.ts +24 -0
  38. package/layers/metadata/server/utils/metadata/provider-registry.ts +15 -0
  39. package/layers/metadata/server/utils/metadata/search.ts +42 -0
  40. package/layers/metadata/tsconfig.json +7 -0
  41. package/layers/metadata-comicvine/nuxt.config.ts +36 -0
  42. package/layers/metadata-comicvine/package.json +12 -0
  43. package/layers/metadata-comicvine/server/plugins/comicvine-register.ts +3 -0
  44. package/layers/metadata-comicvine/server/utils/metadata-comicvine/client.ts +52 -0
  45. package/layers/metadata-comicvine/server/utils/metadata-comicvine/normalise.ts +55 -0
  46. package/layers/metadata-comicvine/server/utils/metadata-comicvine/provider.ts +39 -0
  47. package/layers/metadata-comicvine/server/utils/metadata-comicvine/types.ts +36 -0
  48. package/layers/metadata-comicvine/tsconfig.json +7 -0
  49. package/layers/metadata-google-books/nuxt.config.ts +32 -0
  50. package/layers/metadata-google-books/package.json +12 -0
  51. package/layers/metadata-google-books/server/plugins/google-books-register.ts +3 -0
  52. package/layers/metadata-google-books/server/utils/metadata-google-books/client.ts +23 -0
  53. package/layers/metadata-google-books/server/utils/metadata-google-books/normalise.ts +39 -0
  54. package/layers/metadata-google-books/server/utils/metadata-google-books/provider.ts +21 -0
  55. package/layers/metadata-google-books/server/utils/metadata-google-books/types.ts +25 -0
  56. package/layers/metadata-google-books/tsconfig.json +7 -0
  57. package/layers/metadata-openlibrary/nuxt.config.ts +18 -0
  58. package/layers/metadata-openlibrary/package.json +12 -0
  59. package/layers/metadata-openlibrary/server/plugins/openlibrary-register.ts +3 -0
  60. package/layers/metadata-openlibrary/server/utils/metadata-openlibrary/client.ts +30 -0
  61. package/layers/metadata-openlibrary/server/utils/metadata-openlibrary/normalise.ts +81 -0
  62. package/layers/metadata-openlibrary/server/utils/metadata-openlibrary/provider.ts +25 -0
  63. package/layers/metadata-openlibrary/server/utils/metadata-openlibrary/types.ts +48 -0
  64. package/layers/metadata-openlibrary/tsconfig.json +7 -0
  65. package/layers/routing/server/api/feature-flags.get.ts +6 -0
  66. package/layers/scroll/app/plugins/locomotive-scroll.client.ts +6 -1
  67. package/layers/starter/app/app.config.ts +10 -0
  68. package/layers/starter/app/components/StarterHome.vue +34 -25
  69. package/layers/theme/app/components/ThemePicker/Menu.vue +8 -2
  70. package/layers/theme/nuxt.config.ts +1 -1
  71. package/layers/theme/server/plugins/theme-fouc.ts +2 -57
  72. package/layers/theme/server/utils/fouc-config.ts +85 -0
  73. package/package.json +12 -3
  74. package/types/auth.ts +7 -0
  75. package/types/index.ts +2 -0
  76. package/layers/content/app/composables/useCollectionItems.ts +0 -28
@@ -0,0 +1,81 @@
1
+ import type { MetadataRecord, MetadataCreator } from '#layers/metadata/shared/types'
2
+ import type { OpenLibrarySearchDoc, OpenLibraryWork, OpenLibraryEdition } from './types'
3
+
4
+ // fallow-ignore-next-line complexity
5
+ export function normaliseOpenLibrarySearchDoc(doc: OpenLibrarySearchDoc): MetadataRecord {
6
+ const creators: MetadataCreator[] = (doc.author_name ?? []).map((name, i) => ({
7
+ name,
8
+ role: 'author',
9
+ providerId: doc.author_key?.[i]?.replace('/authors/', '') ?? undefined,
10
+ }))
11
+
12
+ const id = doc.key.replace('/works/', '')
13
+
14
+ return {
15
+ id: `openlibrary:work:${id}`,
16
+ provider: 'openlibrary',
17
+ providerId: id,
18
+ mediaType: 'book',
19
+ title: doc.title,
20
+ subtitle: doc.subtitle,
21
+ creators: creators.length ? creators : undefined,
22
+ publisher: doc.publisher?.[0] ?? undefined,
23
+ publishedAt: doc.first_publish_year ? String(doc.first_publish_year) : undefined,
24
+ coverUrl: doc.cover_i ? openLibraryCoverUrl(doc.cover_i) : undefined,
25
+ identifiers: {
26
+ isbn10: doc.isbn?.find((i) => i.length === 10),
27
+ isbn13: doc.isbn?.find((i) => i.length === 13),
28
+ openLibraryId: id,
29
+ },
30
+ sourceUrl: `https://openlibrary.org${doc.key}`,
31
+ raw: doc,
32
+ lastSyncedAt: new Date().toISOString(),
33
+ }
34
+ }
35
+
36
+ // fallow-ignore-next-line complexity
37
+ export function normaliseOpenLibraryWork(work: OpenLibraryWork): MetadataRecord {
38
+ const id = work.key.replace('/works/', '')
39
+ const desc = typeof work.description === 'string' ? work.description : work.description?.value
40
+
41
+ return {
42
+ id: `openlibrary:work:${id}`,
43
+ provider: 'openlibrary',
44
+ providerId: id,
45
+ mediaType: 'book',
46
+ title: work.title,
47
+ subtitle: work.subtitle,
48
+ description: desc,
49
+ coverUrl: work.covers?.[0] ? openLibraryCoverUrl(work.covers[0]) : undefined,
50
+ publishedAt: work.first_publish_date,
51
+ identifiers: { openLibraryId: id },
52
+ sourceUrl: `https://openlibrary.org${work.key}`,
53
+ raw: work,
54
+ lastSyncedAt: new Date().toISOString(),
55
+ }
56
+ }
57
+
58
+ // fallow-ignore-next-line complexity
59
+ export function normaliseOpenLibraryEdition(edition: OpenLibraryEdition): MetadataRecord {
60
+ const id = edition.key.replace('/books/', '')
61
+
62
+ return {
63
+ id: `openlibrary:edition:${id}`,
64
+ provider: 'openlibrary',
65
+ providerId: id,
66
+ mediaType: 'book',
67
+ title: edition.title,
68
+ subtitle: edition.subtitle,
69
+ publisher: edition.publishers?.[0] ?? undefined,
70
+ publishedAt: edition.publish_date,
71
+ coverUrl: edition.covers?.[0] ? openLibraryCoverUrl(edition.covers[0]) : undefined,
72
+ identifiers: {
73
+ isbn10: edition.isbn_10?.[0],
74
+ isbn13: edition.isbn_13?.[0],
75
+ openLibraryId: id,
76
+ },
77
+ sourceUrl: `https://openlibrary.org${edition.key}`,
78
+ raw: edition,
79
+ lastSyncedAt: new Date().toISOString(),
80
+ }
81
+ }
@@ -0,0 +1,25 @@
1
+ import type { MetadataProvider } from '#layers/metadata/shared/types'
2
+
3
+ export const openLibraryProvider: MetadataProvider = {
4
+ id: 'openlibrary',
5
+ label: 'Open Library',
6
+ mediaTypes: ['book', 'graphic-novel', 'collected-edition'],
7
+
8
+ async search({ query, limit = 10 }) {
9
+ const res = await searchOpenLibrary(query, limit)
10
+ return res.docs.map(normaliseOpenLibrarySearchDoc)
11
+ },
12
+
13
+ async lookup({ providerId, resourceType = 'work' }) {
14
+ if (resourceType === 'edition') {
15
+ const edition = await lookupOpenLibraryEdition(providerId)
16
+ return normaliseOpenLibraryEdition(edition)
17
+ }
18
+ const work = await lookupOpenLibraryWork(providerId)
19
+ return normaliseOpenLibraryWork(work)
20
+ },
21
+
22
+ async sync({ providerId, resourceType = 'work' }) {
23
+ return (await openLibraryProvider.lookup!({ provider: 'openlibrary', providerId, resourceType }))!
24
+ },
25
+ }
@@ -0,0 +1,48 @@
1
+ export type OpenLibrarySearchDoc = {
2
+ key: string
3
+ title: string
4
+ subtitle?: string
5
+ author_name?: string[]
6
+ author_key?: string[]
7
+ publisher?: string[]
8
+ first_publish_year?: number
9
+ isbn?: string[]
10
+ cover_i?: number
11
+ number_of_pages_median?: number
12
+ language?: string[]
13
+ }
14
+
15
+ export type OpenLibrarySearchResponse = {
16
+ numFound: number
17
+ docs: OpenLibrarySearchDoc[]
18
+ }
19
+
20
+ export type OpenLibraryWork = {
21
+ key: string
22
+ title: string
23
+ subtitle?: string
24
+ description?: { value: string } | string
25
+ authors?: Array<{ author: { key: string }; type: { key: string } }>
26
+ first_publish_date?: string
27
+ covers?: number[]
28
+ subjects?: string[]
29
+ }
30
+
31
+ export type OpenLibraryEdition = {
32
+ key: string
33
+ title: string
34
+ subtitle?: string
35
+ publishers?: string[]
36
+ publish_date?: string
37
+ isbn_13?: string[]
38
+ isbn_10?: string[]
39
+ covers?: number[]
40
+ works?: Array<{ key: string }>
41
+ number_of_pages?: number
42
+ }
43
+
44
+ export type OpenLibraryAuthor = {
45
+ key: string
46
+ name: string
47
+ personal_name?: string
48
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "../../apps/playground/tsconfig.json",
3
+ "compilerOptions": {
4
+ "strict": true
5
+ },
6
+ "exclude": ["node_modules"]
7
+ }
@@ -0,0 +1,6 @@
1
+ import type { FeatureValue } from '../../app/types/routing'
2
+
3
+ export default defineEventHandler(() => {
4
+ const config = useAppConfig() as { routingLayer?: { features?: Record<string, FeatureValue> } }
5
+ return config.routingLayer?.features ?? {}
6
+ })
@@ -47,7 +47,12 @@ export default defineNuxtPlugin({
47
47
 
48
48
  if (smoothScroll === true) {
49
49
  nuxtApp.hook('app:mounted', () => nextTick(init))
50
- nuxtApp.hook('page:finish', () => nextTick(() => ScrollTrigger.refresh()))
50
+ nuxtApp.hook('page:finish', () =>
51
+ nextTick(() => {
52
+ instance.value?.scrollTo(0, { immediate: true })
53
+ ScrollTrigger.refresh()
54
+ })
55
+ )
51
56
  } else if (Array.isArray(smoothScroll)) {
52
57
  addRouteMiddleware((to, from) => {
53
58
  if (smoothScroll.includes(to.path)) {
@@ -0,0 +1,10 @@
1
+ export default defineAppConfig({})
2
+
3
+ declare module '@nuxt/schema' {
4
+ interface AppConfigInput {
5
+ layers?: Record<string, boolean>
6
+ }
7
+ interface AppConfig {
8
+ layers?: Record<string, boolean>
9
+ }
10
+ }
@@ -11,36 +11,45 @@ gsap.registerPlugin(ScrollTrigger)
11
11
  const heroRef = ref<HTMLElement | null>(null)
12
12
  const sectionsRef = ref<HTMLElement | null>(null)
13
13
 
14
+ let ctx: ReturnType<typeof gsap.context> | null = null
15
+
14
16
  onMounted(() => {
15
- if (heroRef.value) {
16
- gsap.from(Array.from(heroRef.value.children), {
17
- y: 24,
18
- opacity: 0,
19
- duration: 0.7,
20
- stagger: 0.1,
21
- ease: 'power3.out',
22
- })
23
- }
17
+ ctx = gsap.context(() => {
18
+ if (heroRef.value) {
19
+ gsap.from(Array.from(heroRef.value.children), {
20
+ y: 24,
21
+ opacity: 0,
22
+ duration: 0.7,
23
+ stagger: 0.1,
24
+ ease: 'power3.out',
25
+ })
26
+ }
27
+
28
+ if (sectionsRef.value) {
29
+ gsap.from(sectionsRef.value.querySelectorAll('.layer-card'), {
30
+ scrollTrigger: {
31
+ trigger: sectionsRef.value,
32
+ start: 'top 85%',
33
+ toggleActions: 'play none none none',
34
+ },
35
+ y: 32,
36
+ opacity: 0,
37
+ scale: 0.97,
38
+ duration: 0.5,
39
+ stagger: { amount: 0.5 },
40
+ ease: 'power3.out',
41
+ })
42
+ }
43
+ })
44
+ })
24
45
 
25
- if (sectionsRef.value) {
26
- gsap.from(sectionsRef.value.querySelectorAll('.layer-card'), {
27
- scrollTrigger: {
28
- trigger: sectionsRef.value,
29
- start: 'top 85%',
30
- toggleActions: 'play none none none',
31
- },
32
- y: 32,
33
- opacity: 0,
34
- scale: 0.97,
35
- duration: 0.5,
36
- stagger: { amount: 0.5 },
37
- ease: 'power3.out',
38
- })
39
- }
46
+ onUnmounted(() => {
47
+ ctx?.revert()
48
+ ctx = null
40
49
  })
41
50
 
42
51
  const appConfig = useAppConfig()
43
- const activeLayers = appConfig.layers as Record<string, boolean>
52
+ const activeLayers = appConfig.layers ?? {}
44
53
 
45
54
  type LayerDef = {
46
55
  name: string
@@ -1,8 +1,14 @@
1
1
  <script setup lang="ts">
2
2
  import { useThemePreferenceModels } from '../../composables/useThemePreferenceModels'
3
3
 
4
- const { contrastOverride, motionOverride, transparencyOverride, contrastModel, motionModel, transparencyModel } =
5
- useThemePreferenceModels()
4
+ const {
5
+ contrastOverride,
6
+ motionOverride,
7
+ transparencyOverride,
8
+ contrastModel,
9
+ motionModel,
10
+ transparencyModel,
11
+ } = useThemePreferenceModels()
6
12
  </script>
7
13
 
8
14
  <template>
@@ -1,7 +1,7 @@
1
1
  // Theme Layer - Color mode, accent colors, accessibility preferences
2
2
 
3
3
  declare module '@nuxt/schema' {
4
- interface PublicRuntimeConfig {
4
+ type PublicRuntimeConfig = {
5
5
  themeDefaultAccent: string
6
6
  }
7
7
  }
@@ -1,67 +1,12 @@
1
1
  import { buildAccentCSS } from '../utils/accent-css'
2
-
3
- /**
4
- * Nitro render hook — prevents FOUC for all theme preferences by injecting:
5
- *
6
- * 1. A `<style>` tag with CSS rules mapping each `[data-theme-colour='X']` to
7
- * the correct `--ui-color-primary-*`, `--ui-color-secondary-*`, and
8
- * `--ui-color-info-*` values. Each accent selects a coordinated three-colour
9
- * palette so primary, secondary, and accent (info) all change together.
10
- *
11
- * 2. A blocking `<script>` that reads localStorage and restores all
12
- * `data-theme-*` attributes on `<html>` before first paint:
13
- * - `data-theme-colour` from `theme-colour` (defaults to 'blue')
14
- * - `data-theme-contrast` from `theme-contrast`
15
- * - `data-theme-motion` from `theme-motion`
16
- * - `data-theme-transparency` from `theme-transparency`
17
- * - `data-theme-mode` from `theme-mode` (raw string, not JSON — set by Nuxt Color Mode)
18
- *
19
- * Our script runs before CSS link tags (via head.unshift), so setting data-theme-mode
20
- * here prevents the dark-mode FOUC that would otherwise occur when Nuxt Color Mode's
21
- * script runs after stylesheets have downloaded. Nuxt Color Mode's script will then
22
- * set the same value again — this is idempotent and safe.
23
- */
2
+ import { buildThemeInitScript } from '../utils/fouc-config'
24
3
 
25
4
  const accentCSS = buildAccentCSS()
26
5
 
27
- // Blocking init script — restores data-* attributes from localStorage before first paint.
28
- // Written as a self-invoking function to avoid polluting the global scope.
29
- // JSON.parse handles the quoted string that useLocalStorage writes.
30
- // Note: theme-mode is stored as a raw string by Nuxt Color Mode (no JSON.stringify).
31
- function buildInitScript(defaultAccent: string) {
32
- return `(function(){
33
- try{
34
- var h=document.documentElement;
35
- var c=localStorage.getItem('theme-colour');
36
- h.setAttribute('data-theme-colour',c?JSON.parse(c):'${defaultAccent}');
37
- var ct=localStorage.getItem('theme-contrast');
38
- var ctv=ct?JSON.parse(ct):'system';
39
- if(ctv==='on'){h.setAttribute('data-theme-contrast','high')}
40
- else if(ctv==='off'){h.setAttribute('data-theme-contrast','standard')}
41
- else{h.setAttribute('data-theme-contrast',(window.matchMedia&&window.matchMedia('(prefers-contrast:more)').matches)?'high':'standard')}
42
- var m=localStorage.getItem('theme-motion');
43
- var mv=m?JSON.parse(m):'system';
44
- if(mv==='on'){h.setAttribute('data-theme-motion','reduced')}
45
- else if(mv==='off'){h.setAttribute('data-theme-motion','full')}
46
- else{h.setAttribute('data-theme-motion',(window.matchMedia&&window.matchMedia('(prefers-reduced-motion:reduce)').matches)?'reduced':'full')}
47
- var t=localStorage.getItem('theme-transparency');
48
- var tv=t?JSON.parse(t):'system';
49
- if(tv==='on'){h.setAttribute('data-theme-transparency','reduced')}
50
- else if(tv==='off'){h.setAttribute('data-theme-transparency','full')}
51
- else{h.setAttribute('data-theme-transparency',(window.matchMedia&&window.matchMedia('(prefers-reduced-transparency:reduce)').matches)?'reduced':'full')}
52
- var dm=localStorage.getItem('theme-mode');
53
- var dmv=dm||'system';
54
- if(dmv==='dark'){h.setAttribute('data-theme-mode','dark')}
55
- else if(dmv==='light'){h.setAttribute('data-theme-mode','light')}
56
- else{h.setAttribute('data-theme-mode',(window.matchMedia&&window.matchMedia('(prefers-color-scheme:dark)').matches)?'dark':'light')}
57
- }catch(e){}
58
- })()`.replace(/\n\s*/g, '')
59
- }
60
-
61
6
  export default defineNitroPlugin((nitroApp) => {
62
7
  nitroApp.hooks.hook('render:html', (html, { event }) => {
63
8
  const config = useRuntimeConfig(event)
64
- const initScript = buildInitScript(config.public.themeDefaultAccent || 'blue')
9
+ const initScript = buildThemeInitScript(config.public.themeDefaultAccent || 'blue')
65
10
  html.head.unshift(
66
11
  `<style id="theme-accent-css">${accentCSS}</style><script>${initScript}</script>`
67
12
  )
@@ -0,0 +1,85 @@
1
+ type TriStatePreference = {
2
+ varName: string
3
+ storageKey: string
4
+ attribute: string
5
+ jsonParsed: boolean
6
+ conditionA: string
7
+ valueA: string
8
+ conditionB: string
9
+ valueB: string
10
+ mediaQuery: string
11
+ mediaTrue: string
12
+ mediaFalse: string
13
+ }
14
+
15
+ // Each entry produces one tri-state block in the FOUC init script.
16
+ // Adding a new theme preference = adding one entry here.
17
+ const THEME_PREFERENCES: TriStatePreference[] = [
18
+ {
19
+ varName: 'ct',
20
+ storageKey: 'theme-contrast',
21
+ attribute: 'data-theme-contrast',
22
+ jsonParsed: true,
23
+ conditionA: 'on', valueA: 'high',
24
+ conditionB: 'off', valueB: 'standard',
25
+ mediaQuery: '(prefers-contrast:more)',
26
+ mediaTrue: 'high', mediaFalse: 'standard',
27
+ },
28
+ {
29
+ varName: 'm',
30
+ storageKey: 'theme-motion',
31
+ attribute: 'data-theme-motion',
32
+ jsonParsed: true,
33
+ conditionA: 'on', valueA: 'reduced',
34
+ conditionB: 'off', valueB: 'full',
35
+ mediaQuery: '(prefers-reduced-motion:reduce)',
36
+ mediaTrue: 'reduced', mediaFalse: 'full',
37
+ },
38
+ {
39
+ varName: 't',
40
+ storageKey: 'theme-transparency',
41
+ attribute: 'data-theme-transparency',
42
+ jsonParsed: true,
43
+ conditionA: 'on', valueA: 'reduced',
44
+ conditionB: 'off', valueB: 'full',
45
+ mediaQuery: '(prefers-reduced-transparency:reduce)',
46
+ mediaTrue: 'reduced', mediaFalse: 'full',
47
+ },
48
+ {
49
+ varName: 'dm',
50
+ storageKey: 'theme-mode',
51
+ attribute: 'data-theme-mode',
52
+ jsonParsed: false,
53
+ conditionA: 'dark', valueA: 'dark',
54
+ conditionB: 'light', valueB: 'light',
55
+ mediaQuery: '(prefers-color-scheme:dark)',
56
+ mediaTrue: 'dark', mediaFalse: 'light',
57
+ },
58
+ ]
59
+
60
+ function buildPrefFragment(p: TriStatePreference): string {
61
+ const readValue = p.jsonParsed
62
+ ? `${p.varName}?JSON.parse(${p.varName}):'system'`
63
+ : `${p.varName}||'system'`
64
+ return (
65
+ `var ${p.varName}=localStorage.getItem('${p.storageKey}');` +
66
+ `var ${p.varName}v=${readValue};` +
67
+ `if(${p.varName}v==='${p.conditionA}'){h.setAttribute('${p.attribute}','${p.valueA}')}` +
68
+ `else if(${p.varName}v==='${p.conditionB}'){h.setAttribute('${p.attribute}','${p.valueB}')}` +
69
+ `else{h.setAttribute('${p.attribute}',(window.matchMedia&&window.matchMedia('${p.mediaQuery}').matches)?'${p.mediaTrue}':'${p.mediaFalse}')}`
70
+ )
71
+ }
72
+
73
+ export function buildThemeInitScript(defaultAccent: string): string {
74
+ const colourBlock =
75
+ `var c=localStorage.getItem('theme-colour');` +
76
+ `h.setAttribute('data-theme-colour',c?JSON.parse(c):'${defaultAccent}');`
77
+
78
+ return (
79
+ '(function(){try{' +
80
+ 'var h=document.documentElement;' +
81
+ colourBlock +
82
+ THEME_PREFERENCES.map(buildPrefFragment).join('') +
83
+ '}catch(e){}})()'
84
+ )
85
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kmcom-nuxt-layers",
3
3
  "private": false,
4
- "version": "2.3.1",
4
+ "version": "2.5.0",
5
5
  "description": "Composable Nuxt 4 layers for building scalable Vue applications",
6
6
  "exports": {
7
7
  "./layers/core": "./layers/core/nuxt.config.ts",
@@ -21,11 +21,17 @@
21
21
  "./layers/shader": "./layers/shader/nuxt.config.ts",
22
22
  "./layers/mailer": "./layers/mailer/nuxt.config.ts",
23
23
  "./layers/forms": "./layers/forms/nuxt.config.ts",
24
+ "./layers/database": "./layers/database/nuxt.config.ts",
25
+ "./layers/auth": "./layers/auth/nuxt.config.ts",
24
26
  "./layers/theme": "./layers/theme/nuxt.config.ts",
25
27
  "./layers/content": "./layers/content/nuxt.config.ts",
26
28
  "./layers/feeds": "./layers/feeds/nuxt.config.ts",
27
29
  "./layers/routing": "./layers/routing/nuxt.config.ts",
28
- "./layers/starter": "./layers/starter/nuxt.config.ts"
30
+ "./layers/starter": "./layers/starter/nuxt.config.ts",
31
+ "./layers/metadata": "./layers/metadata/nuxt.config.ts",
32
+ "./layers/metadata-comicvine": "./layers/metadata-comicvine/nuxt.config.ts",
33
+ "./layers/metadata-openlibrary": "./layers/metadata-openlibrary/nuxt.config.ts",
34
+ "./layers/metadata-google-books": "./layers/metadata-google-books/nuxt.config.ts"
29
35
  },
30
36
  "files": [
31
37
  "layers/*/nuxt.config.ts",
@@ -159,6 +165,7 @@
159
165
  "@webgpu/glslang": "^0.0.15",
160
166
  "@webgpu/types": "^0.1.70",
161
167
  "browserslist": "^4.28.2",
168
+ "browserslist-config-baseline": "^0.5.0",
162
169
  "changesets": "^1.0.2",
163
170
  "csstype": "^3.2.3",
164
171
  "cypress": "^15.16.0",
@@ -207,7 +214,7 @@
207
214
  "last 2 Firefox major versions",
208
215
  "last 2 Safari major versions",
209
216
  "last 2 Edge major versions",
210
- "baseline widely available"
217
+ "extends browserslist-config-baseline"
211
218
  ],
212
219
  "dependencies": {
213
220
  "node-gyp": "^12.3.0",
@@ -228,6 +235,8 @@
228
235
  "dev:ui": "PLAYGROUND_LAYERS=core,ui pnpm -F playground dev",
229
236
  "dev:layout": "PLAYGROUND_LAYERS=core,ui,layout pnpm -F playground dev",
230
237
  "dev:motion": "PLAYGROUND_LAYERS=core,motion pnpm -F playground dev",
238
+ "dev:database": "PLAYGROUND_LAYERS=core,database pnpm -F playground dev",
239
+ "dev:auth": "PLAYGROUND_LAYERS=core,auth pnpm -F playground dev",
231
240
  "build": "turbo run build",
232
241
  "lint": "turbo run lint",
233
242
  "lint:fix": "turbo run lint -- --fix",
package/types/auth.ts ADDED
@@ -0,0 +1,7 @@
1
+ // fallow-ignore-next-line unused-type
2
+ export type SessionUser = {
3
+ provider: 'github'
4
+ providerId: string
5
+ username: string
6
+ avatarUrl?: string
7
+ }
package/types/index.ts CHANGED
@@ -17,3 +17,5 @@ export type {
17
17
  export type { ScrollLockEvent, LoadingStateEvent, LayerEvent } from './events'
18
18
  export type { MotionConfig, TransitionConfig } from './motion'
19
19
  export type { PublicRuntimeConfig } from './runtime'
20
+ // fallow-ignore-next-line unused-type
21
+ export type { SessionUser } from './auth'
@@ -1,28 +0,0 @@
1
- import type { Collections } from '@nuxt/content'
2
-
3
- type CollectionItem<TCollection extends keyof Collections> = Collections[TCollection]
4
-
5
- export function useCollectionItems<TCollection extends keyof Collections>(args: {
6
- key: string
7
- collection: TCollection
8
- sort: (a: CollectionItem<TCollection>, b: CollectionItem<TCollection>) => number
9
- options?: { limit?: number | undefined }
10
- filter?: (item: CollectionItem<TCollection>) => boolean
11
- }) {
12
- const { key, collection, sort, options = {}, filter } = args
13
-
14
- return useContentData(key, async () => {
15
- let items = (await queryCollection(collection).all()) as CollectionItem<TCollection>[]
16
- items = items.sort(sort)
17
-
18
- if (filter) {
19
- items = items.filter(filter)
20
- }
21
-
22
- if (options.limit !== undefined) {
23
- items = items.slice(0, options.limit)
24
- }
25
-
26
- return items
27
- })
28
- }