kmcom-nuxt-layers 4.0.0 → 5.0.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 (103) hide show
  1. package/docs/superpowers/plans/2026-09-03-layout-layer-fluid-rebuild.md +8 -8
  2. package/docs/superpowers/specs/2026-08-30-layer-taxonomy-tier-7-design.md +1 -1
  3. package/docs/superpowers/specs/2026-08-31-swiss-grid-layer-design.md +3 -3
  4. package/docs/superpowers/specs/2026-09-03-layout-layer-fluid-rebuild-design.md +3 -3
  5. package/layers/content/app/components/Blog/Article.vue +1 -1
  6. package/layers/content/app/components/Gallery/Detail.vue +8 -8
  7. package/layers/content/app/components/Gallery/Grid.vue +8 -8
  8. package/layers/content/app/components/Gallery/ImageDetail.vue +6 -6
  9. package/layers/content/app/components/Gallery/Lightbox.vue +3 -3
  10. package/layers/content/app/components/NuxtContent/Detail.vue +1 -1
  11. package/layers/content/app/components/NuxtContent/List.vue +1 -1
  12. package/layers/content/app/components/Portfolio/ColorPalette.vue +5 -5
  13. package/layers/content/app/components/Portfolio/Detail.vue +1 -1
  14. package/layers/content/app/components/Portfolio/List.vue +1 -1
  15. package/layers/content/app/components/Portfolio/Typography.vue +5 -5
  16. package/layers/content/app/components/content/Figure.vue +1 -1
  17. package/layers/core/app/assets/css/core.css +9 -9
  18. package/layers/core/app/assets/css/layout.css +1 -1
  19. package/layers/core/app/components/DiagnosticsPage.vue +15 -15
  20. package/layers/core/app/components/Element.test.ts +1 -1
  21. package/layers/core/app/components/ElementTw.test.ts +1 -1
  22. package/layers/core/app/components/ErrorBoundary.vue +5 -5
  23. package/layers/core/app/composables/useElementGrid.test.ts +1 -1
  24. package/layers/core/app/composables/useElementInteraction.test.ts +1 -1
  25. package/layers/core/app/composables/useElementLayout.test.ts +1 -1
  26. package/layers/core/app/composables/useElementSizing.test.ts +1 -1
  27. package/layers/core/app/composables/useElementSurface.test.ts +1 -1
  28. package/layers/core/app/composables/useLayoutAttrs.test.ts +1 -1
  29. package/layers/core/app/error.vue +1 -1
  30. package/layers/core/app/pages/[...slug].vue +2 -2
  31. package/layers/core/app/utils/browserInfo.test.ts +1 -1
  32. package/layers/core/app/utils/featureClasses.test.ts +1 -1
  33. package/layers/core/app/utils/scrollGuardClamp.test.ts +1 -1
  34. package/layers/core/app/utils/styleToTailwindClasses.test.ts +1 -1
  35. package/layers/data/forms/app/utils/contact-schema.test.ts +1 -1
  36. package/layers/data/forms/app/utils/fieldProps.test.ts +1 -1
  37. package/layers/data/mailer/shared/contact.ts +23 -0
  38. package/layers/data/metadata/providers/comicvine/server/utils/metadata-comicvine/normalise.test.ts +1 -1
  39. package/layers/data/metadata/providers/google-books/server/utils/metadata-google-books/client.test.ts +1 -1
  40. package/layers/data/metadata/providers/google-books/server/utils/metadata-google-books/normalise.test.ts +1 -1
  41. package/layers/data/metadata/providers/openlibrary/server/utils/metadata-openlibrary/client.test.ts +1 -1
  42. package/layers/data/metadata/providers/openlibrary/server/utils/metadata-openlibrary/normalise.test.ts +1 -1
  43. package/layers/data/metadata/providers/themoviedb/server/utils/metadata-tmdb/client.test.ts +1 -1
  44. package/layers/data/metadata/providers/themoviedb/server/utils/metadata-tmdb/normalise.test.ts +1 -1
  45. package/layers/data/metadata/server/utils/metadata/cache.test.ts +1 -1
  46. package/layers/data/metadata/server/utils/metadata/provider-registry.test.ts +1 -1
  47. package/layers/data/metadata/server/utils/metadata/search.test.ts +1 -1
  48. package/layers/data/metadata/shared/types/index.ts +2 -0
  49. package/layers/data/metadata/shared/types/metadata.ts +59 -0
  50. package/layers/data/metadata/shared/types/providers.ts +36 -0
  51. package/layers/delivery/feeds/app/assets/css/feeds.css +23 -11
  52. package/layers/delivery/feeds/app/components/Feeds/Index.vue +10 -10
  53. package/layers/delivery/feeds/app/components/Feeds/RouteCard.vue +1 -1
  54. package/layers/delivery/feeds/public/feed/style.css +16 -16
  55. package/layers/delivery/feeds/server/utils/content-adapter.test.ts +1 -1
  56. package/layers/delivery/feeds/server/utils/feed-author.test.ts +1 -1
  57. package/layers/delivery/feeds/server/utils/feed-config.test.ts +1 -1
  58. package/layers/delivery/feeds/server/utils/formats.test.ts +1 -1
  59. package/layers/delivery/scripts/app/components/BaselineStatus.vue +3 -1
  60. package/layers/delivery/seo/app/utils/seoConfig.test.ts +1 -1
  61. package/layers/design-system/theming/app/assets/css/theme.css +0 -1
  62. package/layers/design-system/theming/app/components/ThemePicker/AccentButton.vue +2 -2
  63. package/layers/design-system/theming/app/components/ThemePicker/Menu.vue +3 -3
  64. package/layers/design-system/theming/app/composables/useAccentColor.ts +4 -1
  65. package/layers/design-system/theming/app/plugins/theme.client.ts +15 -13
  66. package/layers/design-system/theming/app/utils/themeAdapter.ts +1 -1
  67. package/layers/design-system/theming/nuxt.config.ts +1 -1
  68. package/layers/design-system/theming/server/utils/accent-css.test.ts +1 -1
  69. package/layers/design-system/theming/server/utils/fouc-config.test.ts +1 -1
  70. package/layers/design-system/typography/app/components/Typography/TextStroke.vue +1 -1
  71. package/layers/design-system/visual/app/assets/css/visual.css +0 -1
  72. package/layers/design-system/visual/app/components/Accent/Scene.vue +2 -2
  73. package/layers/design-system/visual/app/components/Tint/Overlay.vue +1 -1
  74. package/layers/design-system/visual/app/utils/colorTokens.test.ts +1 -1
  75. package/layers/design-system/visual/app/utils/gradientStyle.test.ts +1 -1
  76. package/layers/design-system/visual/app/utils/responsiveSizes.test.ts +1 -1
  77. package/layers/motion/animations/app/components/Motion/Marquee.vue +1 -1
  78. package/layers/motion/animations/app/components/Motion/MarqueeText.vue +3 -3
  79. package/layers/motion/scroll/app/components/Motion/HorizontalScroll.vue +1 -1
  80. package/layers/motion/scroll/app/components/Motion/PinnedSection.vue +2 -2
  81. package/layers/motion/scroll/app/components/Motion/ScrollProgress.vue +2 -2
  82. package/layers/motion/scroll/app/components/Motion/ScrollStats.vue +6 -6
  83. package/layers/motion/transitions/app/assets/css/transitions.css +12 -12
  84. package/layers/render/canvas/app/composables/useRendererCapabilities.ts +2 -1
  85. package/layers/render/canvas/package.json +2 -1
  86. package/layers/render/shader/app/assets/css/shader.css +5 -5
  87. package/layers/render/shader/app/components/Mesh/Plane.vue +3 -1
  88. package/layers/render/shader/app/shaders/common/grain.ts +1 -1
  89. package/layers/render/shader/app/types/tsl.ts +1 -1
  90. package/layers/render/shader/app/utils/tsl/index.ts +2 -0
  91. package/layers/structure/grid/app/assets/css/grid.css +2 -0
  92. package/layers/structure/grid/app/composables/useGridTracks.test.ts +1 -1
  93. package/layers/structure/grid/app/types/tracks.test.ts +1 -1
  94. package/layers/structure/grid/app/utils/placement.test.ts +1 -1
  95. package/layers/structure/layout/app/components/Layout/Grid/Debug.vue +1 -1
  96. package/layers/structure/layout/app/components/Layout/Page/Header.vue +3 -3
  97. package/layers/structure/layout/app/components/Layout/Page/index.vue +16 -11
  98. package/layers/structure/layout/app/utils/gridPlacementStyle.test.ts +1 -1
  99. package/layers/structure/navigation/app/components/Mast/Nav.vue +1 -1
  100. package/layers/structure/navigation/app/components/Mast/NavModal.vue +2 -2
  101. package/layers/structure/navigation/app/utils/site.test.ts +1 -1
  102. package/layers/structure/routing/app/utils/resolveRoute.test.ts +1 -1
  103. package/package.json +22 -32
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from 'vitest'
1
+ import { describe, expect, it } from 'vite-plus/test'
2
2
 
3
3
  import { useElementLayout } from './useElementLayout'
4
4
 
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from 'vitest'
1
+ import { describe, expect, it } from 'vite-plus/test'
2
2
 
3
3
  import { useElementSizing } from './useElementSizing'
4
4
 
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from 'vitest'
1
+ import { describe, expect, it } from 'vite-plus/test'
2
2
 
3
3
  import { useElementSurface } from './useElementSurface'
4
4
 
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from 'vitest'
1
+ import { describe, expect, it } from 'vite-plus/test'
2
2
 
3
3
  import { useLayoutAttrs } from './useLayoutAttrs'
4
4
 
@@ -34,7 +34,7 @@
34
34
  <template #description>
35
35
  <div class="space-y-2">
36
36
  <p class="text-muted">{{ message }}</p>
37
- <code v-if="error?.statusMessage" class="block rounded bg-elevated px-2 py-1 text-sm">
37
+ <code v-if="error?.statusMessage" class="bg-elevated block rounded px-2 py-1 text-sm">
38
38
  {{ error.statusMessage }}
39
39
  </code>
40
40
  </div>
@@ -88,10 +88,10 @@
88
88
  <UEmpty :icon :title :description size="xl">
89
89
  <!-- Show attempted path -->
90
90
  <template v-if="config.showPath !== false" #description>
91
- <p class="mb-2 text-muted">
91
+ <p class="text-muted mb-2">
92
92
  {{ description }}
93
93
  </p>
94
- <code class="rounded bg-elevated px-2 py-1 text-sm">
94
+ <code class="bg-elevated rounded px-2 py-1 text-sm">
95
95
  {{ attemptedPath }}
96
96
  </code>
97
97
  </template>
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from 'vitest'
1
+ import { describe, expect, it } from 'vite-plus/test'
2
2
 
3
3
  import { getBrowserVersionParts, isBrowserAtLeast, parseBrowserInfo } from './browserInfo'
4
4
 
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from 'vitest'
1
+ import { describe, expect, it } from 'vite-plus/test'
2
2
 
3
3
  import type { FeatureDetection } from '../types/detection'
4
4
  import { getFeatureClassNames } from './featureClasses'
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from 'vitest'
1
+ import { describe, expect, it } from 'vite-plus/test'
2
2
 
3
3
  import { isOverflowing, shouldExclude } from './scrollGuardClamp'
4
4
 
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from 'vitest'
1
+ import { describe, expect, it } from 'vite-plus/test'
2
2
 
3
3
  import { styleToTailwindClasses } from './styleToTailwindClasses'
4
4
 
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from 'vitest'
1
+ import { describe, expect, it } from 'vite-plus/test'
2
2
 
3
3
  import { contactSchema } from './contact-schema'
4
4
 
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from 'vitest'
1
+ import { describe, expect, it } from 'vite-plus/test'
2
2
 
3
3
  import type { FieldConfig } from '../types/fields'
4
4
  import {
@@ -0,0 +1,23 @@
1
+ // fallow-ignore-file unused-file
2
+
3
+ export type ContactEmailData = {
4
+ name: string
5
+ email: string
6
+ message: string
7
+ }
8
+
9
+ export type ContactSubmittedPayload = ContactEmailData
10
+
11
+ export type ContactSentPayload = {
12
+ messageId: string
13
+ } & ContactSubmittedPayload
14
+
15
+ export type ContactFailedPayload = {
16
+ error: unknown
17
+ } & ContactSubmittedPayload
18
+
19
+ export type MailerLayerHooks = {
20
+ 'contact:submitted': (payload: ContactSubmittedPayload) => void
21
+ 'contact:sent': (payload: ContactSentPayload) => void
22
+ 'contact:failed': (payload: ContactFailedPayload) => void
23
+ }
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from 'vitest'
1
+ import { describe, expect, it } from 'vite-plus/test'
2
2
 
3
3
  import { normaliseComicVineIssue, normaliseComicVineVolume } from './normalise'
4
4
  import type { ComicVineIssue, ComicVineVolume } from './types'
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from 'vitest'
1
+ import { describe, expect, it } from 'vite-plus/test'
2
2
 
3
3
  import { googleBooksCoverUrl, googleBooksMediaType } from './client'
4
4
 
@@ -1,4 +1,4 @@
1
- import { describe, expect, it, vi } from 'vitest'
1
+ import { describe, expect, it, vi } from 'vite-plus/test'
2
2
 
3
3
  import { googleBooksCoverUrl, googleBooksMediaType } from './client'
4
4
  import { normaliseGoogleBooksVolume } from './normalise'
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from 'vitest'
1
+ import { describe, expect, it } from 'vite-plus/test'
2
2
 
3
3
  import { openLibraryCoverUrl } from './client'
4
4
 
@@ -1,4 +1,4 @@
1
- import { describe, expect, it, vi } from 'vitest'
1
+ import { describe, expect, it, vi } from 'vite-plus/test'
2
2
 
3
3
  import { openLibraryCoverUrl } from './client'
4
4
  import {
@@ -1,4 +1,4 @@
1
- import { describe, expect, it, vi } from 'vitest'
1
+ import { describe, expect, it, vi } from 'vite-plus/test'
2
2
 
3
3
  vi.stubGlobal('useRuntimeConfig', () => ({
4
4
  metadataTmdb: {
@@ -1,4 +1,4 @@
1
- import { describe, expect, it, vi } from 'vitest'
1
+ import { describe, expect, it, vi } from 'vite-plus/test'
2
2
 
3
3
  import { tmdbImageUrl } from './client'
4
4
  import { normaliseTmdbMovie, normaliseTmdbSearchResult, normaliseTmdbTV } from './normalise'
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from 'vitest'
1
+ import { describe, expect, it } from 'vite-plus/test'
2
2
 
3
3
  import { hashQuery, makeCacheKey } from './cache'
4
4
 
@@ -1,5 +1,5 @@
1
1
  import type { MetadataProvider } from '#layers/metadata/shared/types'
2
- import { describe, expect, it } from 'vitest'
2
+ import { describe, expect, it } from 'vite-plus/test'
3
3
 
4
4
  import { getProvider, getProviders, registerProvider } from './provider-registry'
5
5
 
@@ -3,7 +3,7 @@ import type {
3
3
  MetadataSearchInput,
4
4
  MetadataSearchResult,
5
5
  } from '#layers/metadata/shared/types'
6
- import { beforeEach, describe, expect, it, vi } from 'vitest'
6
+ import { beforeEach, describe, expect, it, vi } from 'vite-plus/test'
7
7
 
8
8
  import { hashQuery, makeCacheKey } from './cache'
9
9
 
@@ -0,0 +1,2 @@
1
+ export type * from './metadata'
2
+ export type * from './providers'
@@ -0,0 +1,59 @@
1
+ import type { MetadataProviderId } from './providers'
2
+
3
+ export type MetadataMediaType =
4
+ | 'book'
5
+ | 'comic'
6
+ | 'comic-series'
7
+ | 'manga'
8
+ | 'graphic-novel'
9
+ | 'collected-edition'
10
+ | 'movie'
11
+ | 'tv-show'
12
+
13
+ export type MetadataCreator = {
14
+ name: string
15
+ role?: string
16
+ providerId?: string
17
+ }
18
+
19
+ export type MetadataIdentifiers = {
20
+ isbn10?: string
21
+ isbn13?: string
22
+ comicVineId?: string
23
+ openLibraryId?: string
24
+ googleBooksId?: string
25
+ tmdbId?: string
26
+ imdbId?: string
27
+ }
28
+
29
+ export type MetadataRecord = {
30
+ id: string
31
+ provider: MetadataProviderId
32
+ providerId: string
33
+ mediaType: MetadataMediaType
34
+ title: string
35
+ subtitle?: string
36
+ description?: string
37
+ creators?: MetadataCreator[]
38
+ publisher?: string
39
+ publishedAt?: string
40
+ coverUrl?: string
41
+ identifiers?: MetadataIdentifiers
42
+ sourceUrl?: string
43
+ raw?: unknown
44
+ lastSyncedAt?: string
45
+ }
46
+
47
+ export type MetadataCacheRecord = {
48
+ id: string
49
+ provider: MetadataProviderId
50
+ providerId: string
51
+ resourceType: string
52
+ mediaType: MetadataMediaType
53
+ normalised: MetadataRecord
54
+ raw: unknown
55
+ createdAt: string
56
+ updatedAt: string
57
+ lastSyncedAt: string
58
+ expiresAt?: string
59
+ }
@@ -0,0 +1,36 @@
1
+ import type { MetadataMediaType, MetadataRecord } from './metadata'
2
+
3
+ export type MetadataProviderId = 'comicvine' | 'openlibrary' | 'google-books' | 'tmdb' | 'local'
4
+
5
+ export type MetadataSearchInput = {
6
+ query: string
7
+ mediaType?: MetadataMediaType
8
+ providers?: MetadataProviderId[]
9
+ limit?: number
10
+ }
11
+
12
+ export type MetadataLookupInput = {
13
+ provider: MetadataProviderId
14
+ providerId: string
15
+ resourceType?: string
16
+ }
17
+
18
+ export type MetadataSyncInput = {
19
+ provider: MetadataProviderId
20
+ providerId: string
21
+ resourceType?: string
22
+ force?: boolean
23
+ }
24
+
25
+ export type MetadataSearchResult = MetadataRecord & {
26
+ score?: number
27
+ }
28
+
29
+ export type MetadataProvider = {
30
+ id: MetadataProviderId
31
+ label: string
32
+ mediaTypes: MetadataMediaType[]
33
+ search: (input: MetadataSearchInput) => Promise<MetadataSearchResult[]>
34
+ lookup: (input: MetadataLookupInput) => Promise<MetadataRecord | null>
35
+ sync?: (input: MetadataSyncInput) => Promise<MetadataRecord>
36
+ }
@@ -17,42 +17,54 @@
17
17
  }
18
18
 
19
19
  .feed-badge-rss {
20
- background-color: color-mix(in srgb, var(--color-orange-500, #f97316) 12%, transparent);
21
- color: var(--color-orange-600, #ea580c);
20
+ background-color: color-mix(
21
+ in srgb,
22
+ var(--color-orange-500, oklch(70.49% 0.1867 47.6deg)) 12%,
23
+ transparent
24
+ );
25
+ color: var(--color-orange-600, oklch(64.61% 0.1943 41.12deg));
22
26
  }
23
27
 
24
28
  .feed-badge-atom {
25
- background-color: color-mix(in srgb, var(--color-purple-500, #a855f7) 12%, transparent);
26
- color: var(--color-purple-600, #9333ea);
29
+ background-color: color-mix(
30
+ in srgb,
31
+ var(--color-purple-500, oklch(62.68% 0.2325 303.9deg)) 12%,
32
+ transparent
33
+ );
34
+ color: var(--color-purple-600, oklch(55.75% 0.2525 302.32deg));
27
35
  }
28
36
 
29
37
  .feed-badge-json {
30
- background-color: color-mix(in srgb, var(--color-yellow-500, #eab308) 12%, transparent);
31
- color: var(--color-yellow-600, #ca8a04);
38
+ background-color: color-mix(
39
+ in srgb,
40
+ var(--color-yellow-500, oklch(79.52% 0.1617 86.05deg)) 12%,
41
+ transparent
42
+ );
43
+ color: var(--color-yellow-600, oklch(68.06% 0.1423 75.83deg));
32
44
  }
33
45
 
34
46
  @media (prefers-color-scheme: dark) {
35
47
  .feed-badge-rss {
36
- color: var(--color-orange-400, #fb923c);
48
+ color: var(--color-orange-400, oklch(75.76% 0.159 55.93deg));
37
49
  }
38
50
 
39
51
  .feed-badge-atom {
40
- color: var(--color-purple-400, #c084fc);
52
+ color: var(--color-purple-400, oklch(72.17% 0.1767 305.5deg));
41
53
  }
42
54
 
43
55
  .feed-badge-json {
44
- color: var(--color-yellow-400, #facc15);
56
+ color: var(--color-yellow-400, oklch(86.06% 0.1731 91.94deg));
45
57
  }
46
58
  }
47
59
 
48
60
  /* Feed preview code block — used in playground and consuming apps */
49
61
  .feed-preview {
50
62
  border-radius: var(--ui-radius, 0.375rem);
51
- background-color: var(--color-gray-950, #030712);
63
+ background-color: var(--color-gray-950, oklch(12.96% 0.0274 261.69deg));
52
64
  padding: 1rem;
53
65
  max-height: 24rem;
54
66
  overflow: auto;
55
- color: var(--color-gray-100, #f3f4f6);
67
+ color: var(--color-gray-100, oklch(96.7% 0.0029 264.54deg));
56
68
  font-size: 0.75rem;
57
69
  line-height: 1.6;
58
70
  font-family: var(--font-mono, ui-monospace, monospace);
@@ -25,10 +25,10 @@
25
25
  >
26
26
  <div class="pointer-events-none absolute inset-0">
27
27
  <div
28
- class="absolute -right-20 top-0 h-72 w-72 rounded-full bg-orange-400/20 blur-3xl dark:bg-orange-500/15"
28
+ class="absolute top-0 -right-20 h-72 w-72 rounded-full bg-orange-400/20 blur-3xl dark:bg-orange-500/15"
29
29
  />
30
30
  <div
31
- class="absolute -left-20 bottom-0 h-80 w-80 rounded-full bg-sky-400/12 blur-3xl dark:bg-sky-500/10"
31
+ class="absolute bottom-0 -left-20 h-80 w-80 rounded-full bg-sky-400/12 blur-3xl dark:bg-sky-500/10"
32
32
  />
33
33
  <div
34
34
  class="absolute inset-x-8 top-0 h-px bg-linear-to-r from-transparent via-orange-400/35 to-transparent"
@@ -39,7 +39,7 @@
39
39
  <div class="feeds-split grid gap-6">
40
40
  <div class="space-y-6">
41
41
  <div
42
- class="inline-flex items-center gap-2 rounded-full border border-amber-200/80 bg-white/80 px-3 py-1 text-[0.68rem] font-semibold uppercase tracking-[0.35em] text-amber-700 shadow-sm backdrop-blur dark:border-slate-700 dark:bg-slate-900/60 dark:text-amber-200"
42
+ class="inline-flex items-center gap-2 rounded-full border border-amber-200/80 bg-white/80 px-3 py-1 text-[0.68rem] font-semibold tracking-[0.35em] text-amber-700 uppercase shadow-sm backdrop-blur dark:border-slate-700 dark:bg-slate-900/60 dark:text-amber-200"
43
43
  >
44
44
  <UIcon name="i-lucide-rss" class="text-sm" />
45
45
  Feeds layer
@@ -63,7 +63,7 @@
63
63
  class="rounded-2xl border border-slate-200/80 bg-white/75 p-4 shadow-sm backdrop-blur dark:border-slate-800 dark:bg-slate-900/45"
64
64
  >
65
65
  <p
66
- class="text-[0.68rem] font-semibold uppercase tracking-[0.3em] text-slate-500 dark:text-slate-400"
66
+ class="text-[0.68rem] font-semibold tracking-[0.3em] text-slate-500 uppercase dark:text-slate-400"
67
67
  >
68
68
  Default
69
69
  </p>
@@ -79,7 +79,7 @@
79
79
  class="rounded-2xl border border-slate-200/80 bg-white/75 p-4 shadow-sm backdrop-blur dark:border-slate-800 dark:bg-slate-900/45"
80
80
  >
81
81
  <p
82
- class="text-[0.68rem] font-semibold uppercase tracking-[0.3em] text-slate-500 dark:text-slate-400"
82
+ class="text-[0.68rem] font-semibold tracking-[0.3em] text-slate-500 uppercase dark:text-slate-400"
83
83
  >
84
84
  Exposed
85
85
  </p>
@@ -95,7 +95,7 @@
95
95
  class="rounded-2xl border border-slate-200/80 bg-white/75 p-4 shadow-sm backdrop-blur dark:border-slate-800 dark:bg-slate-900/45"
96
96
  >
97
97
  <p
98
- class="text-[0.68rem] font-semibold uppercase tracking-[0.3em] text-slate-500 dark:text-slate-400"
98
+ class="text-[0.68rem] font-semibold tracking-[0.3em] text-slate-500 uppercase dark:text-slate-400"
99
99
  >
100
100
  Limit
101
101
  </p>
@@ -113,7 +113,7 @@
113
113
  class="flex flex-wrap items-center gap-3 rounded-2xl border border-slate-200/80 bg-white/70 px-4 py-3 text-sm shadow-sm backdrop-blur dark:border-slate-800 dark:bg-slate-900/45"
114
114
  >
115
115
  <span
116
- class="text-xs font-semibold uppercase tracking-[0.3em] text-slate-500 dark:text-slate-400"
116
+ class="text-xs font-semibold tracking-[0.3em] text-slate-500 uppercase dark:text-slate-400"
117
117
  >
118
118
  Site
119
119
  </span>
@@ -134,7 +134,7 @@
134
134
  >
135
135
  <div class="flex items-center justify-between gap-3">
136
136
  <p
137
- class="text-[0.68rem] font-semibold uppercase tracking-[0.35em] text-slate-500 dark:text-slate-400"
137
+ class="text-[0.68rem] font-semibold tracking-[0.35em] text-slate-500 uppercase dark:text-slate-400"
138
138
  >
139
139
  Global routes
140
140
  </p>
@@ -162,7 +162,7 @@
162
162
  <div class="flex flex-wrap items-end justify-between gap-4">
163
163
  <div>
164
164
  <p
165
- class="text-[0.68rem] font-semibold uppercase tracking-[0.35em] text-slate-500 dark:text-slate-400"
165
+ class="text-[0.68rem] font-semibold tracking-[0.35em] text-slate-500 uppercase dark:text-slate-400"
166
166
  >
167
167
  Collection routes
168
168
  </p>
@@ -190,7 +190,7 @@
190
190
  >
191
191
  <div>
192
192
  <p
193
- class="text-[0.68rem] font-semibold uppercase tracking-[0.35em] text-slate-500 dark:text-slate-400"
193
+ class="text-[0.68rem] font-semibold tracking-[0.35em] text-slate-500 uppercase dark:text-slate-400"
194
194
  >
195
195
  Collection
196
196
  </p>
@@ -50,7 +50,7 @@
50
50
  <UCard
51
51
  :to="route.path"
52
52
  :ui="{ body: 'p-0' }"
53
- class="group relative overflow-hidden border-slate-200/80 bg-white/90 shadow-none transition duration-200 hover:-translate-y-0.5 hover:border-slate-300 hover:shadow-md dark:border-slate-800 dark:bg-slate-950/55 dark:hover:border-slate-700 @container"
53
+ class="group @container relative overflow-hidden border-slate-200/80 bg-white/90 shadow-none transition duration-200 hover:-translate-y-0.5 hover:border-slate-300 hover:shadow-md dark:border-slate-800 dark:bg-slate-950/55 dark:hover:border-slate-700"
54
54
  >
55
55
  <div class="absolute inset-y-0 left-0 w-1" :class="routeTone.strip" />
56
56
  <div :class="compact ? 'flex items-center gap-3 p-3' : 'flex items-center gap-3 p-4'">
@@ -12,13 +12,13 @@
12
12
  }
13
13
 
14
14
  :root {
15
- --feed-bg: var(--ui-bg, #fff);
16
- --feed-bg-elevated: var(--ui-bg-elevated, #f8fafc);
17
- --feed-bg-accented: var(--ui-bg-accented, #f1f5f9);
18
- --feed-border: var(--ui-border, #e2e8f0);
19
- --feed-text: var(--ui-text, #0f172a);
20
- --feed-text-muted: var(--ui-text-muted, #64748b);
21
- --feed-text-toned: var(--ui-text-toned, #475569);
15
+ --feed-bg: var(--ui-bg, oklch(100% 0 0deg));
16
+ --feed-bg-elevated: var(--ui-bg-elevated, oklch(98.42% 0.0034 247.86deg));
17
+ --feed-bg-accented: var(--ui-bg-accented, oklch(96.83% 0.0069 247.9deg));
18
+ --feed-border: var(--ui-border, oklch(92.88% 0.0126 255.51deg));
19
+ --feed-text: var(--ui-text, oklch(20.77% 0.0398 265.75deg));
20
+ --feed-text-muted: var(--ui-text-muted, oklch(55.44% 0.0407 257.42deg));
21
+ --feed-text-toned: var(--ui-text-toned, oklch(44.55% 0.0374 257.28deg));
22
22
  --feed-primary: var(--ui-color-primary-500, oklch(62.3% 0.214 259.5deg));
23
23
  --feed-primary-text: var(--ui-color-primary-600, oklch(54.6% 0.214 259.5deg));
24
24
  --feed-primary-bg: var(--ui-color-primary-50, oklch(97% 0.03 259.5deg));
@@ -28,13 +28,13 @@
28
28
 
29
29
  @media (prefers-color-scheme: dark) {
30
30
  :root {
31
- --feed-bg: var(--ui-bg, #020617);
32
- --feed-bg-elevated: var(--ui-bg-elevated, #0f172a);
33
- --feed-bg-accented: var(--ui-bg-accented, #1e293b);
34
- --feed-border: var(--ui-border, #1e293b);
35
- --feed-text: var(--ui-text, #f8fafc);
36
- --feed-text-muted: var(--ui-text-muted, #94a3b8);
37
- --feed-text-toned: var(--ui-text-toned, #cbd5e1);
31
+ --feed-bg: var(--ui-bg, oklch(12.88% 0.0406 264.7deg));
32
+ --feed-bg-elevated: var(--ui-bg-elevated, oklch(20.77% 0.0398 265.75deg));
33
+ --feed-bg-accented: var(--ui-bg-accented, oklch(27.95% 0.0368 260.03deg));
34
+ --feed-border: var(--ui-border, oklch(27.95% 0.0368 260.03deg));
35
+ --feed-text: var(--ui-text, oklch(98.42% 0.0034 247.86deg));
36
+ --feed-text-muted: var(--ui-text-muted, oklch(71.07% 0.0351 256.79deg));
37
+ --feed-text-toned: var(--ui-text-toned, oklch(86.9% 0.0198 252.89deg));
38
38
  --feed-primary: var(--ui-color-primary-400, oklch(71% 0.18 259.5deg));
39
39
  --feed-primary-text: var(--ui-color-primary-400, oklch(71% 0.18 259.5deg));
40
40
  --feed-primary-bg: var(--ui-color-primary-950, oklch(22% 0.06 259.5deg));
@@ -160,7 +160,7 @@ a:hover {
160
160
  border-radius: var(--feed-radius);
161
161
  background-color: var(--feed-primary);
162
162
  padding: 0.5rem 1.125rem;
163
- color: #fff;
163
+ color: oklch(100% 0 0deg);
164
164
  font-weight: 600;
165
165
  font-size: 0.875rem;
166
166
  line-height: 1;
@@ -169,7 +169,7 @@ a:hover {
169
169
 
170
170
  .feed-subscribe:hover {
171
171
  opacity: 0.85;
172
- color: #fff;
172
+ color: oklch(100% 0 0deg);
173
173
  text-decoration: none;
174
174
  }
175
175
 
@@ -1,5 +1,5 @@
1
1
  import type { H3Event } from 'h3'
2
- import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import { beforeEach, describe, expect, it, vi } from 'vite-plus/test'
3
3
 
4
4
  import { getContentFeedItems } from './content-adapter'
5
5
 
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from 'vitest'
1
+ import { describe, expect, it } from 'vite-plus/test'
2
2
 
3
3
  import { resolveFeedAuthor, resolveFeedDate } from './feed-author'
4
4
 
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from 'vitest'
1
+ import { describe, expect, it } from 'vite-plus/test'
2
2
 
3
3
  import {
4
4
  createFeedConfig,
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from 'vitest'
1
+ import { describe, expect, it } from 'vite-plus/test'
2
2
 
3
3
  import { toAtom } from './formats/atom'
4
4
  import { toJSONFeed } from './formats/json'
@@ -10,7 +10,9 @@
10
10
  },
11
11
  },
12
12
  setup(props) {
13
- if (import.meta.server) return () => null
13
+ // Render the custom element on server AND client. Returning null on the server
14
+ // produced a comment node there but an element on the client (hydration mismatch);
15
+ // an unregistered custom element is inert until the client script upgrades it.
14
16
  return () => h('baseline-status', { featureId: props.featureId })
15
17
  },
16
18
  })
@@ -1,5 +1,5 @@
1
1
  import type { SiteConfig } from '#layers/core/app/types/site'
2
- import { describe, expect, it } from 'vitest'
2
+ import { describe, expect, it } from 'vite-plus/test'
3
3
 
4
4
  import { resolveSeoConfig } from './seoConfig'
5
5
 
@@ -36,7 +36,6 @@
36
36
 
37
37
  [data-theme-transparency='reduced'] :where([class*='backdrop-blur']) {
38
38
  backdrop-filter: none !important;
39
- backdrop-filter: none !important;
40
39
  }
41
40
 
42
41
  [data-theme-transparency='reduced'] :where([class*='bg-default/'], [class*='bg-elevated/']) {
@@ -21,8 +21,8 @@
21
21
  class="size-8 rounded-full ring-1 ring-offset-2 transition-shadow duration-200"
22
22
  :class="[
23
23
  active
24
- ? 'ring-2 ring-primary ring-offset-bg shadow-lg'
25
- : 'ring-transparent hover:ring-muted ring-offset-bg hover:shadow-md',
24
+ ? 'ring-primary ring-offset-bg shadow-lg ring-2'
25
+ : 'hover:ring-muted ring-offset-bg ring-transparent hover:shadow-md',
26
26
  ]"
27
27
  :style="bgStyle"
28
28
  :aria-label="`Set accent color to ${color}`"
@@ -34,7 +34,7 @@
34
34
  <li class="flex w-full flex-row items-center justify-between py-1">
35
35
  <div>
36
36
  <h2 class="text-lg font-semibold">High Contrast</h2>
37
- <p v-if="contrastOverride === 'system'" class="text-sm text-muted">
37
+ <p v-if="contrastOverride === 'system'" class="text-muted text-sm">
38
38
  Following system
39
39
  </p>
40
40
  </div>
@@ -49,7 +49,7 @@
49
49
  <li class="flex w-full flex-row items-center justify-between py-1">
50
50
  <div>
51
51
  <h2 class="text-lg font-semibold">Reduce Motion</h2>
52
- <p v-if="motionOverride === 'system'" class="text-sm text-muted">
52
+ <p v-if="motionOverride === 'system'" class="text-muted text-sm">
53
53
  Following system
54
54
  </p>
55
55
  </div>
@@ -64,7 +64,7 @@
64
64
  <li class="flex w-full flex-row items-center justify-between py-1">
65
65
  <div>
66
66
  <h2 class="text-lg font-semibold">Reduce Transparency</h2>
67
- <p v-if="transparencyOverride === 'system'" class="text-sm text-muted">
67
+ <p v-if="transparencyOverride === 'system'" class="text-muted text-sm">
68
68
  Following system
69
69
  </p>
70
70
  </div>
@@ -13,7 +13,10 @@ export const useAccentColor = createSharedComposable(() => {
13
13
  // theme.client.ts reads localStorage after hydration and calls setAccent.
14
14
  const accent = useState<AccentColor>('theme-accent', () => defaultAccent)
15
15
 
16
- const pageAccent = ref<AccentColor | null>(null)
16
+ // Page-scoped override. Must be useState (not a module-level ref): pages call
17
+ // setPageAccent() during setup, and a plain ref is not carried in the SSR payload,
18
+ // so the server rendered the default accent while the client had the page's.
19
+ const pageAccent = useState<AccentColor | null>('theme-page-accent', () => null)
17
20
  const activeAccent = computed<AccentColor>(() => pageAccent.value ?? accent.value)
18
21
 
19
22
  function setAccent(color: AccentColor) {