kmcom-nuxt-layers 6.2.1 → 6.4.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 (77) hide show
  1. package/docs/MIGRATION-v6.3.0.md +234 -0
  2. package/docs/MIGRATION-v6.4.0.md +215 -0
  3. package/docs/MIGRATION.md +3 -0
  4. package/docs/adr/0003-modular-step-system-replaces-fluid-clamps.md +16 -0
  5. package/docs/component-levels.md +117 -0
  6. package/docs/layout-shift-causes.md +18 -0
  7. package/layers/content/app/components/Blog/ItemPage.vue +2 -7
  8. package/layers/content/app/components/Blog/List.vue +9 -2
  9. package/layers/content/app/components/Blog/Page.vue +9 -19
  10. package/layers/content/app/components/Gallery/Detail.vue +1 -0
  11. package/layers/content/app/components/Gallery/Grid.vue +9 -2
  12. package/layers/content/app/components/Gallery/ImageDetail.vue +0 -6
  13. package/layers/content/app/components/Gallery/ImagePage.vue +10 -6
  14. package/layers/content/app/components/Gallery/Page.vue +9 -19
  15. package/layers/content/app/components/Gallery/SlugPage.vue +2 -7
  16. package/layers/content/app/components/NuxtContent/Detail.vue +20 -11
  17. package/layers/content/app/components/NuxtContent/IndexTemplate.vue +21 -0
  18. package/layers/content/app/components/NuxtContent/List.vue +6 -1
  19. package/layers/content/app/components/Portfolio/ColorPalette.vue +2 -2
  20. package/layers/content/app/components/Portfolio/ItemPage.vue +2 -6
  21. package/layers/content/app/components/Portfolio/List.vue +19 -8
  22. package/layers/content/app/components/Portfolio/Page.vue +9 -19
  23. package/layers/content/app/composables/useContentItemSeo.ts +19 -0
  24. package/layers/content/app/composables/useContentSection.ts +9 -0
  25. package/layers/content/app/utils/galleryImageMeta.test.ts +30 -0
  26. package/layers/content/app/utils/galleryImageMeta.ts +11 -0
  27. package/layers/content/nuxt.config.ts +1 -1
  28. package/layers/core/app/app.config.ts +2 -2
  29. package/layers/core/app/app.vue +2 -1
  30. package/layers/core/app/assets/css/core.css +5 -11
  31. package/layers/core/app/assets/css/tokens/z-index.css +16 -0
  32. package/layers/core/app/components/DiagnosticsPage.vue +41 -41
  33. package/layers/core/app/components/LoadingScreen.vue +2 -0
  34. package/layers/core/app/utils/loading.test.ts +2 -2
  35. package/layers/core/app/utils/loading.ts +1 -1
  36. package/layers/delivery/feeds/app/components/Feeds/Index.vue +36 -75
  37. package/layers/delivery/feeds/app/components/Feeds/RouteCard.vue +6 -8
  38. package/layers/design-system/theming/nuxt.config.ts +1 -3
  39. package/layers/design-system/typography/app/assets/css/typography.css +41 -5
  40. package/layers/design-system/visual/app/components/Base/Modal.vue +34 -86
  41. package/layers/design-system/visual/app/components/HoverDimGroup.test.ts +38 -0
  42. package/layers/design-system/visual/app/components/HoverDimGroup.vue +54 -0
  43. package/layers/design-system/visual/app/components/Progress/Bar.vue +9 -29
  44. package/layers/motion/animations/app/components/Motion/Cursor.vue +1 -1
  45. package/layers/motion/animations/app/components/Motion/Marquee.vue +5 -0
  46. package/layers/motion/animations/app/components/Motion/MarqueeText.vue +2 -1
  47. package/layers/motion/animations/app/components/Motion/Staggered.test.ts +40 -0
  48. package/layers/motion/animations/app/components/Motion/Staggered.vue +37 -42
  49. package/layers/motion/animations/app/components/Motion/TextReveal.vue +8 -1
  50. package/layers/motion/animations/app/components/Motion/VelocityEffect.vue +6 -0
  51. package/layers/motion/animations/app/composables/useCountUp.ts +6 -0
  52. package/layers/motion/animations/app/composables/useCursorFollower.ts +4 -1
  53. package/layers/motion/animations/app/utils/pointerMotion.ts +7 -2
  54. package/layers/motion/scroll/app/components/Motion/HorizontalScroll.vue +9 -0
  55. package/layers/motion/scroll/app/components/Motion/Parallax.vue +13 -12
  56. package/layers/motion/scroll/app/components/Motion/PinnedSection.vue +16 -9
  57. package/layers/motion/scroll/app/composables/useReducedMotion.test.ts +23 -0
  58. package/layers/motion/scroll/app/composables/useReducedMotion.ts +14 -0
  59. package/layers/motion/scroll/app/utils/parallax.test.ts +24 -0
  60. package/layers/motion/scroll/app/utils/parallax.ts +16 -0
  61. package/layers/motion/scroll/nuxt.config.ts +1 -1
  62. package/layers/structure/layout/app/components/Layout/Grid/Debug.vue +1 -1
  63. package/layers/structure/layout/app/components/Layout/Page/index.vue +9 -10
  64. package/layers/structure/navigation/app/app.config.ts +37 -14
  65. package/layers/structure/navigation/app/components/Links/Group.vue +12 -38
  66. package/layers/structure/navigation/app/components/Links/Named.vue +9 -0
  67. package/layers/structure/navigation/app/components/Mast/Footer.vue +1 -1
  68. package/layers/structure/navigation/app/components/Mast/Nav.vue +14 -11
  69. package/layers/structure/navigation/app/components/Mast/NavModal.vue +95 -45
  70. package/layers/structure/navigation/app/components/Site/Logo.vue +35 -0
  71. package/layers/structure/navigation/app/components/Site/Subtitle.vue +10 -0
  72. package/layers/structure/navigation/app/components/Site/Title.vue +8 -0
  73. package/layers/structure/navigation/app/composables/mastNav.ts +38 -13
  74. package/layers/structure/navigation/app/types/nav.ts +34 -0
  75. package/layers/structure/navigation/nuxt.config.ts +7 -1
  76. package/package.json +1 -1
  77. /package/layers/{design-system/theming/app/assets/css/tokens.css → core/app/assets/css/tokens/elevation-motion.css} +0 -0
@@ -31,7 +31,7 @@
31
31
  <div class="space-y-8">
32
32
  <div class="text-center">
33
33
  <h1 class="mb-2 text-4xl font-bold">Core Layer Diagnostics</h1>
34
- <p class="text-gray-500 dark:text-gray-400">Feature detection and system information</p>
34
+ <p class="text-muted">Feature detection and system information</p>
35
35
  </div>
36
36
 
37
37
  <UCard>
@@ -40,33 +40,33 @@
40
40
  </template>
41
41
  <div class="diag-grid-9 gap-3">
42
42
  <div class="flex items-center gap-2">
43
- <span v-if="grid" class="text-green-500">✓</span>
44
- <span v-else class="text-red-500">✗</span>
43
+ <span v-if="grid" class="text-success">✓</span>
44
+ <span v-else class="text-error">✗</span>
45
45
  <span>CSS Grid</span>
46
46
  </div>
47
47
  <div class="flex items-center gap-2">
48
- <span v-if="subgrid" class="text-green-500">✓</span>
49
- <span v-else class="text-red-500">✗</span>
48
+ <span v-if="subgrid" class="text-success">✓</span>
49
+ <span v-else class="text-error">✗</span>
50
50
  <span>Subgrid</span>
51
51
  </div>
52
52
  <div class="flex items-center gap-2">
53
- <span v-if="containerQueries" class="text-green-500">✓</span>
54
- <span v-else class="text-red-500">✗</span>
53
+ <span v-if="containerQueries" class="text-success">✓</span>
54
+ <span v-else class="text-error">✗</span>
55
55
  <span>Container Queries</span>
56
56
  </div>
57
57
  <div class="flex items-center gap-2">
58
- <span v-if="has" class="text-green-500">✓</span>
59
- <span v-else class="text-red-500">✗</span>
58
+ <span v-if="has" class="text-success">✓</span>
59
+ <span v-else class="text-error">✗</span>
60
60
  <span>:has() Selector</span>
61
61
  </div>
62
62
  <div class="flex items-center gap-2">
63
- <span v-if="aspectRatio" class="text-green-500">✓</span>
64
- <span v-else class="text-red-500">✗</span>
63
+ <span v-if="aspectRatio" class="text-success">✓</span>
64
+ <span v-else class="text-error">✗</span>
65
65
  <span>Aspect Ratio</span>
66
66
  </div>
67
67
  <div class="flex items-center gap-2">
68
- <span v-if="backdropFilter" class="text-green-500">✓</span>
69
- <span v-else class="text-red-500">✗</span>
68
+ <span v-if="backdropFilter" class="text-success">✓</span>
69
+ <span v-else class="text-error">✗</span>
70
70
  <span>Backdrop Filter</span>
71
71
  </div>
72
72
  </div>
@@ -78,13 +78,13 @@
78
78
  </template>
79
79
  <div class="diag-grid-8 gap-3">
80
80
  <div class="flex items-center gap-2">
81
- <span v-if="webp" class="text-green-500">✓</span>
82
- <span v-else class="text-red-500">✗</span>
81
+ <span v-if="webp" class="text-success">✓</span>
82
+ <span v-else class="text-error">✗</span>
83
83
  <span>WebP</span>
84
84
  </div>
85
85
  <div class="flex items-center gap-2">
86
- <span v-if="avif" class="text-green-500">✓</span>
87
- <span v-else class="text-red-500">✗</span>
86
+ <span v-if="avif" class="text-success">✓</span>
87
+ <span v-else class="text-error">✗</span>
88
88
  <span>AVIF</span>
89
89
  </div>
90
90
  </div>
@@ -96,23 +96,23 @@
96
96
  </template>
97
97
  <div class="diag-grid-9 gap-3">
98
98
  <div class="flex items-center gap-2">
99
- <span v-if="intersectionObserver" class="text-green-500">✓</span>
100
- <span v-else class="text-red-500">✗</span>
99
+ <span v-if="intersectionObserver" class="text-success">✓</span>
100
+ <span v-else class="text-error">✗</span>
101
101
  <span>IntersectionObserver</span>
102
102
  </div>
103
103
  <div class="flex items-center gap-2">
104
- <span v-if="resizeObserver" class="text-green-500">✓</span>
105
- <span v-else class="text-red-500">✗</span>
104
+ <span v-if="resizeObserver" class="text-success">✓</span>
105
+ <span v-else class="text-error">✗</span>
106
106
  <span>ResizeObserver</span>
107
107
  </div>
108
108
  <div class="flex items-center gap-2">
109
- <span v-if="serviceWorker" class="text-green-500">✓</span>
110
- <span v-else class="text-red-500">✗</span>
109
+ <span v-if="serviceWorker" class="text-success">✓</span>
110
+ <span v-else class="text-error">✗</span>
111
111
  <span>Service Worker</span>
112
112
  </div>
113
113
  <div class="flex items-center gap-2">
114
- <span v-if="webGL" class="text-green-500">✓</span>
115
- <span v-else class="text-red-500">✗</span>
114
+ <span v-if="webGL" class="text-success">✓</span>
115
+ <span v-else class="text-error">✗</span>
116
116
  <span>WebGL</span>
117
117
  </div>
118
118
  </div>
@@ -139,8 +139,8 @@
139
139
  <div class="space-y-2">
140
140
  <div class="flex items-center gap-2">
141
141
  <span class="w-24 font-medium">Status:</span>
142
- <span v-if="isOnline" class="text-green-500">Online</span>
143
- <span v-else class="text-red-500">Offline</span>
142
+ <span v-if="isOnline" class="text-success">Online</span>
143
+ <span v-else class="text-error">Offline</span>
144
144
  </div>
145
145
  <div class="flex items-center gap-2">
146
146
  <span class="w-24 font-medium">Type:</span>
@@ -150,10 +150,10 @@
150
150
  <span class="w-24 font-medium">Quality:</span>
151
151
  <span
152
152
  :class="{
153
- 'text-green-500': connectionQuality === 'excellent',
154
- 'text-yellow-500': connectionQuality === 'good',
155
- 'text-red-500': connectionQuality === 'poor',
156
- 'text-gray-500': connectionQuality === 'offline',
153
+ 'text-success': connectionQuality === 'excellent',
154
+ 'text-warning': connectionQuality === 'good',
155
+ 'text-error': connectionQuality === 'poor',
156
+ 'text-muted': connectionQuality === 'offline',
157
157
  }"
158
158
  >
159
159
  {{ connectionQuality }}
@@ -161,13 +161,13 @@
161
161
  </div>
162
162
  <div class="flex items-center gap-2">
163
163
  <span class="w-24 font-medium">Speed:</span>
164
- <span v-if="isFast" class="text-green-500">Fast</span>
165
- <span v-else-if="isSlow" class="text-red-500">Slow</span>
164
+ <span v-if="isFast" class="text-success">Fast</span>
165
+ <span v-else-if="isSlow" class="text-error">Slow</span>
166
166
  <span v-else>Normal</span>
167
167
  </div>
168
168
  <div v-if="saveData" class="flex items-center gap-2">
169
169
  <span class="w-24 font-medium">Data Saver:</span>
170
- <span class="text-yellow-500">Enabled</span>
170
+ <span class="text-warning">Enabled</span>
171
171
  </div>
172
172
  </div>
173
173
  </UCard>
@@ -188,8 +188,8 @@
188
188
  </div>
189
189
  <div class="flex items-center gap-2">
190
190
  <span class="w-24 font-medium">Hydrated:</span>
191
- <span v-if="isHydrated" class="text-green-500">Yes</span>
192
- <span v-else class="text-yellow-500">No</span>
191
+ <span v-if="isHydrated" class="text-success">Yes</span>
192
+ <span v-else class="text-warning">No</span>
193
193
  </div>
194
194
  </div>
195
195
  </UCard>
@@ -201,17 +201,17 @@
201
201
  <div class="space-y-2">
202
202
  <div class="flex items-center gap-2">
203
203
  <span class="w-24 font-medium">Installed:</span>
204
- <span v-if="isInstalled" class="text-green-500">Yes</span>
205
- <span v-else class="text-gray-500">No</span>
204
+ <span v-if="isInstalled" class="text-success">Yes</span>
205
+ <span v-else class="text-muted">No</span>
206
206
  </div>
207
207
  <div class="flex items-center gap-2">
208
208
  <span class="w-24 font-medium">Installable:</span>
209
- <span v-if="canInstall" class="text-green-500">Yes</span>
210
- <span v-else class="text-gray-500">No</span>
209
+ <span v-if="canInstall" class="text-success">Yes</span>
210
+ <span v-else class="text-muted">No</span>
211
211
  </div>
212
212
  <div v-if="needRefresh" class="flex items-center gap-2">
213
213
  <span class="w-24 font-medium">Update:</span>
214
- <span class="text-yellow-500">Available</span>
214
+ <span class="text-warning">Available</span>
215
215
  </div>
216
216
  </div>
217
217
  </UCard>
@@ -92,6 +92,7 @@
92
92
  'Helvetica Neue',
93
93
  Arial,
94
94
  sans-serif;
95
+
95
96
  /* `backwards`, not `both` - a held end state would block the exit transition below */
96
97
  animation: loading-enter 0.9s cubic-bezier(0.16, 1, 0.3, 1) 0.1s backwards;
97
98
  }
@@ -110,6 +111,7 @@
110
111
  font-stretch: ultra-expanded;
111
112
  font-size: clamp(3rem, 35cqw + 1rem, 18rem);
112
113
  line-height: 0.8;
114
+
113
115
  /* Equal-width digits - the counter doesn't jitter as it ticks */
114
116
  font-variant-numeric: tabular-nums;
115
117
  }
@@ -6,9 +6,9 @@ const FRAME = 1 / 60
6
6
 
7
7
  describe('resolveLoadingConfig', () => {
8
8
  it('fills unset keys with defaults but keeps falsy values that were set', () => {
9
- expect(resolveLoadingConfig(undefined)).toMatchObject({ enabled: true, minDuration: 1500 })
9
+ expect(resolveLoadingConfig(undefined)).toMatchObject({ enabled: false, minDuration: 1500 })
10
10
  expect(resolveLoadingConfig({ loading: { minDuration: 0, label: '' } })).toMatchObject({
11
- enabled: true,
11
+ enabled: false,
12
12
  minDuration: 0,
13
13
  label: '',
14
14
  })
@@ -2,7 +2,7 @@ import type { LoadingConfig } from '../types/loading'
2
2
 
3
3
  /** Fallbacks for keys app.config leaves unset - core's app.config ships the same values. */
4
4
  const LOADING_DEFAULTS: LoadingConfig = {
5
- enabled: true,
5
+ enabled: false,
6
6
  minDuration: 1500,
7
7
  maxDuration: 3000,
8
8
  label: 'Loading',
@@ -20,18 +20,13 @@
20
20
 
21
21
  <template>
22
22
  <div
23
- class="feeds-shell relative overflow-hidden rounded-4xl border border-amber-200/70 bg-[linear-gradient(180deg,rgba(255,251,244,0.96),rgba(243,236,223,0.95))] shadow-[0_30px_100px_rgba(15,23,42,0.12)] dark:border-slate-800/80 dark:bg-[linear-gradient(180deg,rgba(2,6,23,0.96),rgba(3,7,18,0.98))]"
24
- style="container-type: inline-size"
23
+ class="feeds-shell border-default bg-muted @container relative overflow-hidden rounded-4xl border shadow-xl"
25
24
  >
26
25
  <div class="pointer-events-none absolute inset-0">
26
+ <div class="bg-primary/20 absolute top-0 -right-20 h-72 w-72 rounded-full blur-3xl" />
27
+ <div class="bg-secondary/10 absolute bottom-0 -left-20 h-80 w-80 rounded-full blur-3xl" />
27
28
  <div
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
- />
30
- <div
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
- />
33
- <div
34
- class="absolute inset-x-8 top-0 h-px bg-linear-to-r from-transparent via-orange-400/35 to-transparent"
29
+ class="via-primary/35 absolute inset-x-8 top-0 h-px bg-linear-to-r from-transparent to-transparent"
35
30
  />
36
31
  </div>
37
32
 
@@ -39,19 +34,17 @@
39
34
  <div class="feeds-split grid gap-6">
40
35
  <div class="space-y-6">
41
36
  <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 tracking-[0.35em] text-amber-700 uppercase shadow-sm backdrop-blur dark:border-slate-700 dark:bg-slate-900/60 dark:text-amber-200"
37
+ class="border-default bg-default/80 text-primary inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs font-semibold tracking-[0.35em] uppercase shadow-sm backdrop-blur"
43
38
  >
44
39
  <UIcon name="i-lucide-rss" class="text-sm" />
45
40
  Feeds layer
46
41
  </div>
47
42
 
48
43
  <div class="space-y-3">
49
- <h1
50
- class="feeds-h1 max-w-3xl font-semibold tracking-tight text-slate-950 dark:text-slate-50"
51
- >
44
+ <h1 class="feeds-h1 text-highlighted max-w-3xl font-semibold tracking-tight">
52
45
  Feed catalog for {{ catalog.site.title }}
53
46
  </h1>
54
- <p class="feeds-body max-w-2xl leading-7 text-slate-700 dark:text-slate-300">
47
+ <p class="feeds-body text-toned max-w-2xl leading-7">
55
48
  Configured in <code class="font-mono text-[0.9em]">app.config.ts</code>, validated
56
49
  against <code class="font-mono text-[0.9em]">content.config.ts</code>, and rendered as
57
50
  a human-readable index for readers and site owners.
@@ -60,82 +53,56 @@
60
53
 
61
54
  <div class="feeds-cards-grid gap-3">
62
55
  <div
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"
56
+ class="border-default bg-default/75 rounded-2xl border p-4 shadow-sm backdrop-blur"
64
57
  >
65
- <p
66
- class="text-[0.68rem] font-semibold tracking-[0.3em] text-slate-500 uppercase dark:text-slate-400"
67
- >
68
- Default
69
- </p>
70
- <p class="mt-2 text-lg font-semibold text-slate-950 dark:text-slate-100">
58
+ <p class="text-muted text-xs font-semibold tracking-[0.3em] uppercase">Default</p>
59
+ <p class="text-highlighted mt-2 text-lg font-semibold">
71
60
  {{ catalog.feed.defaultCollection }}
72
61
  </p>
73
- <p class="mt-1 text-xs text-slate-500 dark:text-slate-400">
74
- Shorthand routes point here.
75
- </p>
62
+ <p class="text-muted mt-1 text-xs">Shorthand routes point here.</p>
76
63
  </div>
77
64
 
78
65
  <div
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"
66
+ class="border-default bg-default/75 rounded-2xl border p-4 shadow-sm backdrop-blur"
80
67
  >
81
- <p
82
- class="text-[0.68rem] font-semibold tracking-[0.3em] text-slate-500 uppercase dark:text-slate-400"
83
- >
84
- Exposed
85
- </p>
86
- <p class="mt-2 text-lg font-semibold text-slate-950 dark:text-slate-100">
68
+ <p class="text-muted text-xs font-semibold tracking-[0.3em] uppercase">Exposed</p>
69
+ <p class="text-highlighted mt-2 text-lg font-semibold">
87
70
  {{ catalog.collectionGroups.length }} collections
88
71
  </p>
89
- <p class="mt-1 text-xs text-slate-500 dark:text-slate-400">
90
- Published in the feed catalog.
91
- </p>
72
+ <p class="text-muted mt-1 text-xs">Published in the feed catalog.</p>
92
73
  </div>
93
74
 
94
75
  <div
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"
76
+ class="border-default bg-default/75 rounded-2xl border p-4 shadow-sm backdrop-blur"
96
77
  >
97
- <p
98
- class="text-[0.68rem] font-semibold tracking-[0.3em] text-slate-500 uppercase dark:text-slate-400"
99
- >
100
- Limit
101
- </p>
102
- <p class="mt-2 text-lg font-semibold text-slate-950 dark:text-slate-100">
78
+ <p class="text-muted text-xs font-semibold tracking-[0.3em] uppercase">Limit</p>
79
+ <p class="text-highlighted mt-2 text-lg font-semibold">
103
80
  {{ catalog.feed.limit }} items
104
81
  </p>
105
- <p class="mt-1 text-xs text-slate-500 dark:text-slate-400">
106
- Applied to each feed response.
107
- </p>
82
+ <p class="text-muted mt-1 text-xs">Applied to each feed response.</p>
108
83
  </div>
109
84
  </div>
110
85
 
111
86
  <div
112
87
  v-if="catalog.site.url || catalog.site.description"
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"
88
+ class="border-default bg-default/70 flex flex-wrap items-center gap-3 rounded-2xl border px-4 py-3 text-sm shadow-sm backdrop-blur"
114
89
  >
115
- <span
116
- class="text-xs font-semibold tracking-[0.3em] text-slate-500 uppercase dark:text-slate-400"
117
- >
118
- Site
119
- </span>
120
- <span v-if="catalog.site.description" class="text-slate-700 dark:text-slate-300">
90
+ <span class="text-muted text-xs font-semibold tracking-[0.3em] uppercase"> Site </span>
91
+ <span v-if="catalog.site.description" class="text-toned">
121
92
  {{ catalog.site.description }}
122
93
  </span>
123
94
  <code
124
95
  v-if="catalog.site.url"
125
- class="rounded-full bg-slate-100 px-2 py-1 font-mono text-[0.72rem] text-slate-600 dark:bg-slate-800 dark:text-slate-300"
96
+ class="bg-elevated text-muted rounded-full px-2 py-1 font-mono text-xs"
126
97
  >
127
98
  {{ catalog.site.url }}
128
99
  </code>
129
100
  </div>
130
101
  </div>
131
102
 
132
- <aside
133
- class="rounded-[1.5rem] border border-slate-200/80 bg-white/75 p-4 shadow-sm backdrop-blur dark:border-slate-800 dark:bg-slate-900/45"
134
- >
103
+ <aside class="border-default bg-default/75 rounded-3xl border p-4 shadow-sm backdrop-blur">
135
104
  <div class="flex items-center justify-between gap-3">
136
- <p
137
- class="text-[0.68rem] font-semibold tracking-[0.35em] text-slate-500 uppercase dark:text-slate-400"
138
- >
105
+ <p class="text-muted text-xs font-semibold tracking-[0.35em] uppercase">
139
106
  Global routes
140
107
  </p>
141
108
  <UBadge color="neutral" variant="subtle" size="xs">
@@ -161,18 +128,14 @@
161
128
  <div class="space-y-4">
162
129
  <div class="flex flex-wrap items-end justify-between gap-4">
163
130
  <div>
164
- <p
165
- class="text-[0.68rem] font-semibold tracking-[0.35em] text-slate-500 uppercase dark:text-slate-400"
166
- >
131
+ <p class="text-muted text-xs font-semibold tracking-[0.35em] uppercase">
167
132
  Collection routes
168
133
  </p>
169
- <h2
170
- class="mt-2 text-2xl font-semibold tracking-tight text-slate-950 dark:text-slate-100"
171
- >
134
+ <h2 class="text-highlighted mt-2 text-2xl font-semibold tracking-tight">
172
135
  Each exposed collection gets RSS, Atom, and JSON.
173
136
  </h2>
174
137
  </div>
175
- <p class="max-w-lg text-sm text-slate-600 dark:text-slate-400">
138
+ <p class="text-muted max-w-lg text-sm">
176
139
  Add or remove collections in <code class="font-mono text-[0.9em]">app.config.ts</code>
177
140
  and the catalog updates automatically.
178
141
  </p>
@@ -183,21 +146,19 @@
183
146
  v-for="group in catalog.collectionGroups"
184
147
  :key="group.collection"
185
148
  :ui="{ body: 'p-0' }"
186
- class="overflow-hidden border-slate-200/80 bg-white/80 shadow-sm backdrop-blur dark:border-slate-800 dark:bg-slate-950/45"
149
+ class="border-default bg-default/80 overflow-hidden shadow-sm backdrop-blur"
187
150
  >
188
151
  <div
189
- class="flex flex-wrap items-start justify-between gap-4 border-b border-slate-200/80 px-5 py-4 dark:border-slate-800"
152
+ class="border-default flex flex-wrap items-start justify-between gap-4 border-b px-5 py-4"
190
153
  >
191
154
  <div>
192
- <p
193
- class="text-[0.68rem] font-semibold tracking-[0.35em] text-slate-500 uppercase dark:text-slate-400"
194
- >
155
+ <p class="text-muted text-xs font-semibold tracking-[0.35em] uppercase">
195
156
  Collection
196
157
  </p>
197
- <h3 class="mt-2 text-xl font-semibold text-slate-950 dark:text-slate-100">
158
+ <h3 class="text-highlighted mt-2 text-xl font-semibold">
198
159
  {{ group.label }}
199
160
  </h3>
200
- <p class="mt-1 font-mono text-sm text-slate-500 dark:text-slate-400">
161
+ <p class="text-muted mt-1 font-mono text-sm">
201
162
  {{ group.collection }}
202
163
  </p>
203
164
  </div>
@@ -215,13 +176,13 @@
215
176
  <UCard
216
177
  v-else
217
178
  :ui="{ body: 'p-0' }"
218
- class="border-dashed border-slate-300 bg-white/70 shadow-none dark:border-slate-700 dark:bg-slate-950/40"
179
+ class="border-accented bg-default/70 border-dashed shadow-none"
219
180
  >
220
181
  <div class="space-y-2 p-5">
221
- <p class="text-sm font-medium text-slate-950 dark:text-slate-100">
182
+ <p class="text-highlighted text-sm font-medium">
222
183
  No collection feeds are currently exposed.
223
184
  </p>
224
- <p class="text-sm text-slate-600 dark:text-slate-400">
185
+ <p class="text-muted text-sm">
225
186
  Add page collections to
226
187
  <code class="font-mono text-[0.9em]">feedsLayer.feed.collections</code>
227
188
  in <code class="font-mono text-[0.9em]">app.config.ts</code>.
@@ -16,8 +16,8 @@
16
16
  }
17
17
  > = {
18
18
  index: {
19
- strip: 'bg-slate-400',
20
- dot: 'bg-slate-500',
19
+ strip: 'bg-accented',
20
+ dot: 'bg-inverted/40',
21
21
  },
22
22
  rss: {
23
23
  strip: 'bg-orange-400',
@@ -50,23 +50,21 @@
50
50
  <UCard
51
51
  :to="route.path"
52
52
  :ui="{ body: 'p-0' }"
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"
53
+ class="group border-default bg-default/90 hover:border-accented @container relative overflow-hidden shadow-none transition duration-200 hover:-translate-y-0.5 hover:shadow-md"
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'">
57
57
  <span class="h-2.5 w-2.5 rounded-full" :class="routeTone.dot" />
58
58
  <div class="min-w-0 flex-1">
59
59
  <div class="flex items-start justify-between gap-3">
60
- <p class="font-medium text-slate-950 dark:text-slate-100">
60
+ <p class="text-highlighted font-medium">
61
61
  {{ route.label }}
62
62
  </p>
63
- <code
64
- class="shrink-0 rounded-full bg-slate-100 px-2 py-1 font-mono text-[0.72rem] text-slate-600 dark:bg-slate-800 dark:text-slate-300"
65
- >
63
+ <code class="bg-elevated text-muted shrink-0 rounded-full px-2 py-1 font-mono text-xs">
66
64
  {{ route.path }}
67
65
  </code>
68
66
  </div>
69
- <p class="mt-1 text-xs text-slate-500 dark:text-slate-400">
67
+ <p class="text-muted mt-1 text-xs">
70
68
  {{ getRouteDescription() }}
71
69
  </p>
72
70
  </div>
@@ -25,9 +25,7 @@ export default defineNuxtConfig({
25
25
  '#layers/theming/types': `${import.meta.dirname}/app/types`,
26
26
  },
27
27
 
28
- // tokens.css is NOT listed here — it defines a Tailwind `@theme static` block, so per the
29
- // CSS single-build-root rule (see .claude/rules/nuxt-layers.md) it must fold into core.css's
30
- // @import chain via relative path instead, or its @theme block is silently inert.
28
+ // Shadow/duration tokens live in core (core/app/assets/css/tokens/elevation-motion.css).
31
29
  css: ['#layers/theming/app/assets/css/theme.css'],
32
30
 
33
31
  plugins: ['#layers/theming/app/plugins/theme.client.ts'],
@@ -31,12 +31,25 @@
31
31
  --type-scale: 1.26;
32
32
  --type-snap: 0.0625rem; /* rounding interval: rem so it scales with the root; never px, never 0 */
33
33
 
34
- /* Fluid display step, 5 → 7 across 360 → 1280px. tan(atan2(length, 1rem)) casts the
35
- length to the unitless exponent pow() needs; normalised by 1rem (not 1px) so the
36
- exponent stays 5 → 7 whatever the user's root font size; the vw term is scaled by root / 16px so a
37
- larger root doesn't lower the exponent at a given width (h1 stays visibly larger). */
34
+ /* Fluid display step: --display-from at ≤360px → --display-to at ≥1280px (57.5 = 920px / 16).
35
+ tan(atan2(length, 1rem)) casts the length to the unitless exponent pow() needs; normalised
36
+ by 1rem (not 1px) so the exponent doesn't move with the user's root font size; the vw term
37
+ is scaled by root / 16px so a larger root doesn't lower it at a given width (h1 stays
38
+ visibly larger). Defaults reduce to clamp(5rem, 4.2176rem + 3.4784vw·k, 7rem).
39
+ --step-display resolves var() here at :root, so overriding --display-from/to on a subtree
40
+ only reaches elements that recompute it: text-step-display and h1 do. */
41
+ --display-from: 5;
42
+ --display-to: 7;
38
43
  --step-display: tan(
39
- atan2(clamp(5rem, 4.2176rem + 3.4784vw * tan(atan2(1rem, 16px)), 7rem), 1rem)
44
+ atan2(
45
+ clamp(
46
+ var(--display-from) * 1rem,
47
+ var(--display-from) * 1rem + (var(--display-to) - var(--display-from)) * (100vw - 360px) *
48
+ tan(atan2(1rem, 16px)) / 57.5,
49
+ var(--display-to) * 1rem
50
+ ),
51
+ 1rem
52
+ )
40
53
  );
41
54
  }
42
55
 
@@ -252,6 +265,18 @@
252
265
  }
253
266
 
254
267
  @utility text-step-display {
268
+ /* Recomputed here so [--display-from:4] [--display-to:9] on the element takes effect */
269
+ --step-display: tan(
270
+ atan2(
271
+ clamp(
272
+ var(--display-from) * 1rem,
273
+ var(--display-from) * 1rem + (var(--display-to) - var(--display-from)) * (100vw - 360px) *
274
+ tan(atan2(1rem, 16px)) / 57.5,
275
+ var(--display-to) * 1rem
276
+ ),
277
+ 1rem
278
+ )
279
+ );
255
280
  --step: var(--step-display);
256
281
 
257
282
  font-size: calc(var(--type-base) * pow(var(--type-scale), var(--step)));
@@ -283,6 +308,17 @@
283
308
 
284
309
  /* Same expressions as the text-step-* utilities. h1 uses the display step. */
285
310
  h1:where(:not([data-slot])) {
311
+ --step-display: tan(
312
+ atan2(
313
+ clamp(
314
+ var(--display-from) * 1rem,
315
+ var(--display-from) * 1rem + (var(--display-to) - var(--display-from)) * (100vw - 360px) *
316
+ tan(atan2(1rem, 16px)) / 57.5,
317
+ var(--display-to) * 1rem
318
+ ),
319
+ 1rem
320
+ )
321
+ );
286
322
  --step: var(--step-display);
287
323
 
288
324
  font-size: calc(var(--type-base) * pow(var(--type-scale), var(--step)));