kmcom-nuxt-layers 4.0.1 → 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 (97) 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/metadata/providers/comicvine/server/utils/metadata-comicvine/normalise.test.ts +1 -1
  38. package/layers/data/metadata/providers/google-books/server/utils/metadata-google-books/client.test.ts +1 -1
  39. package/layers/data/metadata/providers/google-books/server/utils/metadata-google-books/normalise.test.ts +1 -1
  40. package/layers/data/metadata/providers/openlibrary/server/utils/metadata-openlibrary/client.test.ts +1 -1
  41. package/layers/data/metadata/providers/openlibrary/server/utils/metadata-openlibrary/normalise.test.ts +1 -1
  42. package/layers/data/metadata/providers/themoviedb/server/utils/metadata-tmdb/client.test.ts +1 -1
  43. package/layers/data/metadata/providers/themoviedb/server/utils/metadata-tmdb/normalise.test.ts +1 -1
  44. package/layers/data/metadata/server/utils/metadata/cache.test.ts +1 -1
  45. package/layers/data/metadata/server/utils/metadata/provider-registry.test.ts +1 -1
  46. package/layers/data/metadata/server/utils/metadata/search.test.ts +1 -1
  47. package/layers/delivery/feeds/app/assets/css/feeds.css +23 -11
  48. package/layers/delivery/feeds/app/components/Feeds/Index.vue +10 -10
  49. package/layers/delivery/feeds/app/components/Feeds/RouteCard.vue +1 -1
  50. package/layers/delivery/feeds/public/feed/style.css +16 -16
  51. package/layers/delivery/feeds/server/utils/content-adapter.test.ts +1 -1
  52. package/layers/delivery/feeds/server/utils/feed-author.test.ts +1 -1
  53. package/layers/delivery/feeds/server/utils/feed-config.test.ts +1 -1
  54. package/layers/delivery/feeds/server/utils/formats.test.ts +1 -1
  55. package/layers/delivery/scripts/app/components/BaselineStatus.vue +3 -1
  56. package/layers/delivery/seo/app/utils/seoConfig.test.ts +1 -1
  57. package/layers/design-system/theming/app/assets/css/theme.css +0 -1
  58. package/layers/design-system/theming/app/components/ThemePicker/AccentButton.vue +2 -2
  59. package/layers/design-system/theming/app/components/ThemePicker/Menu.vue +3 -3
  60. package/layers/design-system/theming/app/composables/useAccentColor.ts +4 -1
  61. package/layers/design-system/theming/app/plugins/theme.client.ts +15 -13
  62. package/layers/design-system/theming/app/utils/themeAdapter.ts +1 -1
  63. package/layers/design-system/theming/nuxt.config.ts +1 -1
  64. package/layers/design-system/theming/server/utils/accent-css.test.ts +1 -1
  65. package/layers/design-system/theming/server/utils/fouc-config.test.ts +1 -1
  66. package/layers/design-system/typography/app/components/Typography/TextStroke.vue +1 -1
  67. package/layers/design-system/visual/app/assets/css/visual.css +0 -1
  68. package/layers/design-system/visual/app/components/Accent/Scene.vue +2 -2
  69. package/layers/design-system/visual/app/components/Tint/Overlay.vue +1 -1
  70. package/layers/design-system/visual/app/utils/colorTokens.test.ts +1 -1
  71. package/layers/design-system/visual/app/utils/gradientStyle.test.ts +1 -1
  72. package/layers/design-system/visual/app/utils/responsiveSizes.test.ts +1 -1
  73. package/layers/motion/animations/app/components/Motion/Marquee.vue +1 -1
  74. package/layers/motion/animations/app/components/Motion/MarqueeText.vue +3 -3
  75. package/layers/motion/scroll/app/components/Motion/HorizontalScroll.vue +1 -1
  76. package/layers/motion/scroll/app/components/Motion/PinnedSection.vue +2 -2
  77. package/layers/motion/scroll/app/components/Motion/ScrollProgress.vue +2 -2
  78. package/layers/motion/scroll/app/components/Motion/ScrollStats.vue +6 -6
  79. package/layers/motion/transitions/app/assets/css/transitions.css +12 -12
  80. package/layers/render/canvas/package.json +2 -1
  81. package/layers/render/shader/app/assets/css/shader.css +5 -5
  82. package/layers/render/shader/app/shaders/common/grain.ts +1 -1
  83. package/layers/render/shader/app/types/tsl.ts +1 -1
  84. package/layers/render/shader/app/utils/tsl/index.ts +2 -0
  85. package/layers/structure/grid/app/assets/css/grid.css +2 -0
  86. package/layers/structure/grid/app/composables/useGridTracks.test.ts +1 -1
  87. package/layers/structure/grid/app/types/tracks.test.ts +1 -1
  88. package/layers/structure/grid/app/utils/placement.test.ts +1 -1
  89. package/layers/structure/layout/app/components/Layout/Grid/Debug.vue +1 -1
  90. package/layers/structure/layout/app/components/Layout/Page/Header.vue +3 -3
  91. package/layers/structure/layout/app/components/Layout/Page/index.vue +16 -11
  92. package/layers/structure/layout/app/utils/gridPlacementStyle.test.ts +1 -1
  93. package/layers/structure/navigation/app/components/Mast/Nav.vue +1 -1
  94. package/layers/structure/navigation/app/components/Mast/NavModal.vue +2 -2
  95. package/layers/structure/navigation/app/utils/site.test.ts +1 -1
  96. package/layers/structure/routing/app/utils/resolveRoute.test.ts +1 -1
  97. package/package.json +20 -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 {
@@ -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
 
@@ -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) {
@@ -10,19 +10,21 @@ export default defineNuxtPlugin({
10
10
  const { setTransparencyOverride, effectiveReducedTransparency } = useThemeTransparency()
11
11
 
12
12
  // Restore persisted preferences. useState defaults were serialized from the
13
- // server, so hydration matches. We update to the stored values here, after
14
- // hydration completes, to avoid any mismatch.
15
- const storedAccent = localStorage.getItem('theme-colour')
16
- if (storedAccent) setAccent(storedAccent as AccentColor)
17
-
18
- const storedContrast = localStorage.getItem('theme-contrast')
19
- if (storedContrast) setContrastOverride(storedContrast as PreferenceOverride)
20
-
21
- const storedMotion = localStorage.getItem('theme-motion')
22
- if (storedMotion) setMotionOverride(storedMotion as PreferenceOverride)
23
-
24
- const storedTransparency = localStorage.getItem('theme-transparency')
25
- if (storedTransparency) setTransparencyOverride(storedTransparency as PreferenceOverride)
13
+ // server, so hydration matches only if client state is still at those defaults
14
+ // while hydrating. Plugin setup runs BEFORE hydration, so the stored values are
15
+ // applied on `app:mounted` (after hydration) instead of here.
16
+ useNuxtApp().hook('app:mounted', () => {
17
+ const restorers: Array<[key: string, apply: (value: string) => void]> = [
18
+ ['theme-colour', (v) => setAccent(v as AccentColor)],
19
+ ['theme-contrast', (v) => setContrastOverride(v as PreferenceOverride)],
20
+ ['theme-motion', (v) => setMotionOverride(v as PreferenceOverride)],
21
+ ['theme-transparency', (v) => setTransparencyOverride(v as PreferenceOverride)],
22
+ ]
23
+ for (const [key, apply] of restorers) {
24
+ const stored = localStorage.getItem(key)
25
+ if (stored) apply(stored)
26
+ }
27
+ })
26
28
 
27
29
  watch(
28
30
  activeAccent,
@@ -1,4 +1,4 @@
1
- import { ACCENT_PALETTES, type AccentName } from '../../server/utils/accent-css'
1
+ import { ACCENT_PALETTES, type AccentName } from '../../server/utils/accent-css.ts'
2
2
 
3
3
  /**
4
4
  * Derives Nuxt UI's `ui.colors` shape from the same accent-relationship table
@@ -1,5 +1,5 @@
1
1
  // Theme Layer - Color mode, accent colors, accessibility preferences
2
- import { resolveUiColors } from './app/utils/themeAdapter'
2
+ import { resolveUiColors } from './app/utils/themeAdapter.ts'
3
3
 
4
4
  declare module '@nuxt/schema' {
5
5
  interface PublicRuntimeConfig {
@@ -1,5 +1,5 @@
1
1
  import twColors from 'tailwindcss/colors'
2
- import { describe, expect, it } from 'vitest'
2
+ import { describe, expect, it } from 'vite-plus/test'
3
3
 
4
4
  import { buildAccentCSS } from './accent-css'
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 { buildThemeInitScript } from './fouc-config'
4
4
 
@@ -31,7 +31,7 @@
31
31
  v-bind="$attrs"
32
32
  >
33
33
  <!-- Sizer: transparent text for natural sizing + selectable -->
34
- <span class="text-stroke-sizer text-transparent whitespace-nowrap" aria-hidden="true">{{
34
+ <span class="text-stroke-sizer whitespace-nowrap text-transparent" aria-hidden="true">{{
35
35
  text
36
36
  }}</span>
37
37
 
@@ -1,5 +1,4 @@
1
1
  .gradient-text {
2
- background-clip: text;
3
2
  background-clip: text;
4
3
  color: transparent;
5
4
  }
@@ -25,8 +25,8 @@
25
25
  </script>
26
26
 
27
27
  <template>
28
- <component :is="tag" class="relative overflow-clip isolate">
29
- <div class="absolute inset-0 pointer-events-none" aria-hidden="true">
28
+ <component :is="tag" class="relative isolate overflow-clip">
29
+ <div class="pointer-events-none absolute inset-0" aria-hidden="true">
30
30
  <AccentBlob
31
31
  v-for="(blob, i) in resolvedBlobs"
32
32
  :key="i"
@@ -20,6 +20,6 @@
20
20
  <template>
21
21
  <component :is="tag" class="relative">
22
22
  <slot />
23
- <span class="absolute inset-0 pointer-events-none" :style aria-hidden="true" />
23
+ <span class="pointer-events-none absolute inset-0" :style aria-hidden="true" />
24
24
  </component>
25
25
  </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 { resolveUiColorToken } from './colorTokens'
4
4