kmcom-nuxt-layers 2.2.13 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/layers/starter/app/components/StarterDesignSystem.vue +1913 -0
- package/layers/starter/app/components/StarterHome.vue +407 -0
- package/layers/starter/nuxt.config.ts +15 -0
- package/layers/starter/package.json +10 -0
- package/layers/theme/nuxt.config.ts +13 -0
- package/layers/theme/server/plugins/theme-fouc.ts +7 -3
- package/package.json +3 -2
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
useSeoMeta({
|
|
3
|
+
title: 'kmcom-nuxt-layers Starter',
|
|
4
|
+
description:
|
|
5
|
+
'A production-ready Nuxt starter powered by kmcom-nuxt-layers. Composable, modular, and ready to ship.',
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
const { gsap, ScrollTrigger } = useGsap()
|
|
9
|
+
gsap.registerPlugin(ScrollTrigger)
|
|
10
|
+
|
|
11
|
+
const heroRef = ref<HTMLElement | null>(null)
|
|
12
|
+
const sectionsRef = ref<HTMLElement | null>(null)
|
|
13
|
+
|
|
14
|
+
onMounted(() => {
|
|
15
|
+
if (heroRef.value) {
|
|
16
|
+
gsap.from(Array.from(heroRef.value.children), {
|
|
17
|
+
y: 24,
|
|
18
|
+
opacity: 0,
|
|
19
|
+
duration: 0.7,
|
|
20
|
+
stagger: 0.1,
|
|
21
|
+
ease: 'power3.out',
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (sectionsRef.value) {
|
|
26
|
+
gsap.from(sectionsRef.value.querySelectorAll('.layer-card'), {
|
|
27
|
+
scrollTrigger: {
|
|
28
|
+
trigger: sectionsRef.value,
|
|
29
|
+
start: 'top 85%',
|
|
30
|
+
toggleActions: 'play none none none',
|
|
31
|
+
},
|
|
32
|
+
y: 32,
|
|
33
|
+
opacity: 0,
|
|
34
|
+
scale: 0.97,
|
|
35
|
+
duration: 0.5,
|
|
36
|
+
stagger: { amount: 0.5 },
|
|
37
|
+
ease: 'power3.out',
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
const appConfig = useAppConfig()
|
|
43
|
+
const activeLayers = appConfig.layers as Record<string, boolean>
|
|
44
|
+
|
|
45
|
+
type LayerDef = {
|
|
46
|
+
name: string
|
|
47
|
+
key: string
|
|
48
|
+
description: string
|
|
49
|
+
icon: string
|
|
50
|
+
features: string[]
|
|
51
|
+
borderColor: string
|
|
52
|
+
bgColor: string
|
|
53
|
+
iconBg: string
|
|
54
|
+
iconColor: string
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const coreLayers: LayerDef[] = [
|
|
58
|
+
{
|
|
59
|
+
name: 'Core',
|
|
60
|
+
key: 'core',
|
|
61
|
+
description: 'Foundation utilities, error handling, device detection, PWA support',
|
|
62
|
+
icon: 'i-lucide-box',
|
|
63
|
+
features: ['Browser Detection', 'Screen Info', 'Network Status', 'Loading States', 'PWA'],
|
|
64
|
+
borderColor: 'border-blue-500/30',
|
|
65
|
+
bgColor: 'bg-blue-500/5',
|
|
66
|
+
iconBg: 'bg-blue-500/10',
|
|
67
|
+
iconColor: 'text-blue-500',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: 'UI',
|
|
71
|
+
key: 'ui',
|
|
72
|
+
description: 'Typography, navigation, visual primitives and the design system',
|
|
73
|
+
icon: 'i-lucide-palette',
|
|
74
|
+
features: ['Typography', 'Color Tokens', 'Navigation', 'Visual Primitives', 'Toast', 'Modal'],
|
|
75
|
+
borderColor: 'border-pink-500/30',
|
|
76
|
+
bgColor: 'bg-pink-500/5',
|
|
77
|
+
iconBg: 'bg-pink-500/10',
|
|
78
|
+
iconColor: 'text-pink-500',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'Layout',
|
|
82
|
+
key: 'layout',
|
|
83
|
+
description: 'Swiss Grid system, sections, and page containers',
|
|
84
|
+
icon: 'i-lucide-layout',
|
|
85
|
+
features: ['18-Col Grid', 'Hero Section', 'Split Section', 'Gallery Section', 'Grid Debug'],
|
|
86
|
+
borderColor: 'border-amber-500/30',
|
|
87
|
+
bgColor: 'bg-amber-500/5',
|
|
88
|
+
iconBg: 'bg-amber-500/10',
|
|
89
|
+
iconColor: 'text-amber-500',
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'Motion',
|
|
93
|
+
key: 'motion',
|
|
94
|
+
description: 'GSAP, Lenis smooth scroll, page transitions and animation effects',
|
|
95
|
+
icon: 'i-lucide-sparkles',
|
|
96
|
+
features: ['GSAP', 'ScrollTrigger', 'Lenis', 'Marquee', 'Magnetic', 'Tilt', 'Cursor'],
|
|
97
|
+
borderColor: 'border-emerald-500/30',
|
|
98
|
+
bgColor: 'bg-emerald-500/5',
|
|
99
|
+
iconBg: 'bg-emerald-500/10',
|
|
100
|
+
iconColor: 'text-emerald-500',
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: 'Forms',
|
|
104
|
+
key: 'forms',
|
|
105
|
+
description: 'Config-driven form fields with Zod validation and type inference',
|
|
106
|
+
icon: 'i-lucide-file-input',
|
|
107
|
+
features: ['Dynamic Fields', 'Zod Validation', 'Type Inference', 'Auto Icons', 'Contact Form'],
|
|
108
|
+
borderColor: 'border-cyan-500/30',
|
|
109
|
+
bgColor: 'bg-cyan-500/5',
|
|
110
|
+
iconBg: 'bg-cyan-500/10',
|
|
111
|
+
iconColor: 'text-cyan-500',
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: 'Theme',
|
|
115
|
+
key: 'theme',
|
|
116
|
+
description: 'Dark mode, accent color palettes and design token system',
|
|
117
|
+
icon: 'i-lucide-swatch-book',
|
|
118
|
+
features: ['Dark Mode', 'Accent Colors', 'Color Tokens', 'Color Mode'],
|
|
119
|
+
borderColor: 'border-neutral-400/30',
|
|
120
|
+
bgColor: 'bg-neutral-400/5',
|
|
121
|
+
iconBg: 'bg-neutral-400/10',
|
|
122
|
+
iconColor: 'text-neutral-400',
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: 'Content',
|
|
126
|
+
key: 'content',
|
|
127
|
+
description: 'Markdown CMS with typed collections via @nuxt/content',
|
|
128
|
+
icon: 'i-lucide-file-text',
|
|
129
|
+
features: ['Markdown', 'Collections', 'Frontmatter', 'Content API', 'Zod Schemas'],
|
|
130
|
+
borderColor: 'border-rose-500/30',
|
|
131
|
+
bgColor: 'bg-rose-500/5',
|
|
132
|
+
iconBg: 'bg-rose-500/10',
|
|
133
|
+
iconColor: 'text-rose-500',
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: 'Scripts',
|
|
137
|
+
key: 'scripts',
|
|
138
|
+
description: 'Third-party script management with performance-safe loading',
|
|
139
|
+
icon: 'i-lucide-code',
|
|
140
|
+
features: ['Lazy Loading', 'Analytics', 'Consent-Aware', 'Performance-Safe'],
|
|
141
|
+
borderColor: 'border-indigo-500/30',
|
|
142
|
+
bgColor: 'bg-indigo-500/5',
|
|
143
|
+
iconBg: 'bg-indigo-500/10',
|
|
144
|
+
iconColor: 'text-indigo-500',
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: 'Routing',
|
|
148
|
+
key: 'routing',
|
|
149
|
+
description: 'Advanced routing utilities, typed middleware and navigation helpers',
|
|
150
|
+
icon: 'i-lucide-route',
|
|
151
|
+
features: ['Typed Routes', 'Middleware', 'Route Guards', 'Navigation Utils'],
|
|
152
|
+
borderColor: 'border-teal-500/30',
|
|
153
|
+
bgColor: 'bg-teal-500/5',
|
|
154
|
+
iconBg: 'bg-teal-500/10',
|
|
155
|
+
iconColor: 'text-teal-500',
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: 'SEO',
|
|
159
|
+
key: 'seo',
|
|
160
|
+
description: 'Meta tags, Open Graph, JSON-LD, sitemap and robots.txt via @nuxtjs/seo',
|
|
161
|
+
icon: 'i-lucide-search',
|
|
162
|
+
features: ['Meta Tags', 'Open Graph', 'JSON-LD', 'Sitemap', 'Robots.txt', 'Schema.org'],
|
|
163
|
+
borderColor: 'border-sky-500/30',
|
|
164
|
+
bgColor: 'bg-sky-500/5',
|
|
165
|
+
iconBg: 'bg-sky-500/10',
|
|
166
|
+
iconColor: 'text-sky-500',
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: 'Feeds',
|
|
170
|
+
key: 'feeds',
|
|
171
|
+
description: 'RSS and feed aggregation with display components',
|
|
172
|
+
icon: 'i-lucide-rss',
|
|
173
|
+
features: ['RSS Feeds', 'Feed Display', 'Feed Parser'],
|
|
174
|
+
borderColor: 'border-orange-500/30',
|
|
175
|
+
bgColor: 'bg-orange-500/5',
|
|
176
|
+
iconBg: 'bg-orange-500/10',
|
|
177
|
+
iconColor: 'text-orange-500',
|
|
178
|
+
},
|
|
179
|
+
]
|
|
180
|
+
|
|
181
|
+
const addonLayers: LayerDef[] = [
|
|
182
|
+
{
|
|
183
|
+
name: 'Canvas',
|
|
184
|
+
key: 'canvas',
|
|
185
|
+
description: 'WebGL/WebGPU canvas foundation required by the shader layer',
|
|
186
|
+
icon: 'i-lucide-monitor',
|
|
187
|
+
features: ['WebGL', 'WebGPU', 'Canvas Composables', '3D Foundation'],
|
|
188
|
+
borderColor: 'border-purple-500/30',
|
|
189
|
+
bgColor: 'bg-purple-500/5',
|
|
190
|
+
iconBg: 'bg-purple-500/10',
|
|
191
|
+
iconColor: 'text-purple-500',
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: 'Shader',
|
|
195
|
+
key: 'shader',
|
|
196
|
+
description: 'Three.js + TresJS shader system with TSL, noise and post-processing',
|
|
197
|
+
icon: 'i-lucide-shapes',
|
|
198
|
+
features: ['TSL Shaders', 'Noise', 'Gradients', 'Fresnel', 'Post-Processing', 'WebGPU'],
|
|
199
|
+
borderColor: 'border-violet-500/30',
|
|
200
|
+
bgColor: 'bg-violet-500/5',
|
|
201
|
+
iconBg: 'bg-violet-500/10',
|
|
202
|
+
iconColor: 'text-violet-500',
|
|
203
|
+
},
|
|
204
|
+
]
|
|
205
|
+
|
|
206
|
+
const marqueeItems = [
|
|
207
|
+
'Core', '◆', 'UI', '◆', 'Layout', '◆', 'Motion', '◆', 'Forms', '◆',
|
|
208
|
+
'Theme', '◆', 'Content', '◆', 'SEO', '◆', 'Scripts', '◆', 'Routing', '◆', 'Feeds', '◆',
|
|
209
|
+
]
|
|
210
|
+
</script>
|
|
211
|
+
|
|
212
|
+
<template>
|
|
213
|
+
<div class="min-h-screen">
|
|
214
|
+
<UContainer class="px-8 pb-4 pt-8">
|
|
215
|
+
<!-- Hero -->
|
|
216
|
+
<div ref="heroRef" class="space-y-6 py-8 text-center">
|
|
217
|
+
<UBadge variant="subtle" color="primary" size="lg" icon="i-lucide-layers">
|
|
218
|
+
kmcom-nuxt-layers
|
|
219
|
+
</UBadge>
|
|
220
|
+
<h1 class="text-highlighted text-4xl font-bold sm:text-5xl lg:text-6xl">
|
|
221
|
+
Nuxt Layer Architecture
|
|
222
|
+
</h1>
|
|
223
|
+
<p class="text-muted mx-auto max-w-2xl text-lg">
|
|
224
|
+
A production-ready starter built on composable Nuxt layers. Toggle layers in
|
|
225
|
+
<code class="text-primary">layers.config.ts</code> to enable or disable capabilities.
|
|
226
|
+
</p>
|
|
227
|
+
<div class="flex flex-wrap justify-center gap-3">
|
|
228
|
+
<UButton
|
|
229
|
+
to="/design-system"
|
|
230
|
+
size="xl"
|
|
231
|
+
icon="i-lucide-layout-template"
|
|
232
|
+
trailing-icon="i-lucide-arrow-right"
|
|
233
|
+
>
|
|
234
|
+
Design System
|
|
235
|
+
</UButton>
|
|
236
|
+
<UButton
|
|
237
|
+
to="https://github.com/kieranmansfield/starter-template"
|
|
238
|
+
target="_blank"
|
|
239
|
+
size="xl"
|
|
240
|
+
icon="i-simple-icons-github"
|
|
241
|
+
color="neutral"
|
|
242
|
+
variant="subtle"
|
|
243
|
+
>
|
|
244
|
+
View on GitHub
|
|
245
|
+
</UButton>
|
|
246
|
+
</div>
|
|
247
|
+
</div>
|
|
248
|
+
</UContainer>
|
|
249
|
+
|
|
250
|
+
<!-- Marquee divider -->
|
|
251
|
+
<div class="border-default overflow-hidden border-y py-3">
|
|
252
|
+
<MotionMarquee
|
|
253
|
+
:speed="45"
|
|
254
|
+
:pause-on-hover="false"
|
|
255
|
+
gap="3rem"
|
|
256
|
+
velocity-based
|
|
257
|
+
:velocity-sensitivity="0.6"
|
|
258
|
+
>
|
|
259
|
+
<span
|
|
260
|
+
v-for="item in marqueeItems"
|
|
261
|
+
:key="item"
|
|
262
|
+
class="text-muted whitespace-nowrap text-xs font-semibold uppercase tracking-widest"
|
|
263
|
+
>{{ item }}</span>
|
|
264
|
+
</MotionMarquee>
|
|
265
|
+
</div>
|
|
266
|
+
|
|
267
|
+
<div ref="sectionsRef">
|
|
268
|
+
<UContainer class="px-8 pb-8 pt-10">
|
|
269
|
+
<div class="space-y-12">
|
|
270
|
+
|
|
271
|
+
<!-- Core Layers -->
|
|
272
|
+
<section>
|
|
273
|
+
<div class="mb-6">
|
|
274
|
+
<h2 class="text-highlighted text-xl font-semibold">Core Layers</h2>
|
|
275
|
+
<p class="text-muted mt-1 text-sm">
|
|
276
|
+
The solid foundation — enabled by default, no extra dependencies required.
|
|
277
|
+
</p>
|
|
278
|
+
</div>
|
|
279
|
+
<div class="grid gap-5 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
|
280
|
+
<div
|
|
281
|
+
v-for="layer in coreLayers"
|
|
282
|
+
:key="layer.key"
|
|
283
|
+
class="layer-card rounded-xl border-2 p-5"
|
|
284
|
+
:class="[layer.borderColor, layer.bgColor]"
|
|
285
|
+
>
|
|
286
|
+
<div class="flex h-full flex-col">
|
|
287
|
+
<div class="mb-4 flex items-center gap-3">
|
|
288
|
+
<div
|
|
289
|
+
class="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg"
|
|
290
|
+
:class="layer.iconBg"
|
|
291
|
+
>
|
|
292
|
+
<UIcon :name="layer.icon" class="text-lg" :class="layer.iconColor" />
|
|
293
|
+
</div>
|
|
294
|
+
<div>
|
|
295
|
+
<h3 class="text-highlighted text-sm font-semibold leading-tight">
|
|
296
|
+
{{ layer.name }}
|
|
297
|
+
</h3>
|
|
298
|
+
<UBadge
|
|
299
|
+
:color="activeLayers[layer.key] ? 'success' : 'neutral'"
|
|
300
|
+
variant="subtle"
|
|
301
|
+
size="sm"
|
|
302
|
+
class="mt-0.5"
|
|
303
|
+
>
|
|
304
|
+
{{ activeLayers[layer.key] ? 'Active' : 'Disabled' }}
|
|
305
|
+
</UBadge>
|
|
306
|
+
</div>
|
|
307
|
+
</div>
|
|
308
|
+
<p class="text-muted mb-4 grow text-xs leading-relaxed">
|
|
309
|
+
{{ layer.description }}
|
|
310
|
+
</p>
|
|
311
|
+
<div class="flex flex-wrap gap-1">
|
|
312
|
+
<UBadge
|
|
313
|
+
v-for="feature in layer.features"
|
|
314
|
+
:key="feature"
|
|
315
|
+
variant="subtle"
|
|
316
|
+
color="neutral"
|
|
317
|
+
size="sm"
|
|
318
|
+
>
|
|
319
|
+
{{ feature }}
|
|
320
|
+
</UBadge>
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
</div>
|
|
325
|
+
</section>
|
|
326
|
+
|
|
327
|
+
<!-- Add-on Layers -->
|
|
328
|
+
<section>
|
|
329
|
+
<div class="mb-6">
|
|
330
|
+
<h2 class="text-highlighted text-xl font-semibold">Add-on Layers</h2>
|
|
331
|
+
<p class="text-muted mt-1 text-sm">
|
|
332
|
+
Optional capabilities. Enable in <code class="text-primary">layers.config.ts</code>
|
|
333
|
+
and install the required peer dependencies.
|
|
334
|
+
</p>
|
|
335
|
+
</div>
|
|
336
|
+
<div class="grid gap-5 md:grid-cols-2 lg:grid-cols-3">
|
|
337
|
+
<div
|
|
338
|
+
v-for="layer in addonLayers"
|
|
339
|
+
:key="layer.key"
|
|
340
|
+
class="layer-card rounded-xl border-2 p-5 opacity-70 transition-opacity"
|
|
341
|
+
:class="[
|
|
342
|
+
layer.borderColor,
|
|
343
|
+
layer.bgColor,
|
|
344
|
+
activeLayers[layer.key] ? 'opacity-100' : 'opacity-60',
|
|
345
|
+
]"
|
|
346
|
+
>
|
|
347
|
+
<div class="flex h-full flex-col">
|
|
348
|
+
<div class="mb-4 flex items-center gap-3">
|
|
349
|
+
<div
|
|
350
|
+
class="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg"
|
|
351
|
+
:class="layer.iconBg"
|
|
352
|
+
>
|
|
353
|
+
<UIcon :name="layer.icon" class="text-lg" :class="layer.iconColor" />
|
|
354
|
+
</div>
|
|
355
|
+
<div>
|
|
356
|
+
<h3 class="text-highlighted text-sm font-semibold leading-tight">
|
|
357
|
+
{{ layer.name }}
|
|
358
|
+
</h3>
|
|
359
|
+
<UBadge
|
|
360
|
+
:color="activeLayers[layer.key] ? 'success' : 'neutral'"
|
|
361
|
+
variant="subtle"
|
|
362
|
+
size="sm"
|
|
363
|
+
class="mt-0.5"
|
|
364
|
+
>
|
|
365
|
+
{{ activeLayers[layer.key] ? 'Active' : 'Disabled' }}
|
|
366
|
+
</UBadge>
|
|
367
|
+
</div>
|
|
368
|
+
</div>
|
|
369
|
+
<p class="text-muted mb-4 grow text-xs leading-relaxed">
|
|
370
|
+
{{ layer.description }}
|
|
371
|
+
</p>
|
|
372
|
+
<div class="flex flex-wrap gap-1">
|
|
373
|
+
<UBadge
|
|
374
|
+
v-for="feature in layer.features"
|
|
375
|
+
:key="feature"
|
|
376
|
+
variant="subtle"
|
|
377
|
+
color="neutral"
|
|
378
|
+
size="sm"
|
|
379
|
+
>
|
|
380
|
+
{{ feature }}
|
|
381
|
+
</UBadge>
|
|
382
|
+
</div>
|
|
383
|
+
</div>
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
</section>
|
|
387
|
+
|
|
388
|
+
<!-- Quick Links -->
|
|
389
|
+
<div class="flex flex-wrap justify-center gap-4 pt-2">
|
|
390
|
+
<UButton to="/design-system" variant="outline" icon="i-lucide-layout-template">
|
|
391
|
+
Design System Reference
|
|
392
|
+
</UButton>
|
|
393
|
+
<UButton
|
|
394
|
+
to="https://github.com/kieranmansfield/starter-template"
|
|
395
|
+
target="_blank"
|
|
396
|
+
variant="outline"
|
|
397
|
+
icon="i-simple-icons-github"
|
|
398
|
+
>
|
|
399
|
+
View Source
|
|
400
|
+
</UButton>
|
|
401
|
+
</div>
|
|
402
|
+
|
|
403
|
+
</div>
|
|
404
|
+
</UContainer>
|
|
405
|
+
</div>
|
|
406
|
+
</div>
|
|
407
|
+
</template>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Starter layer — showcase components for the boilerplate starter template
|
|
2
|
+
export default defineNuxtConfig({
|
|
3
|
+
$meta: {
|
|
4
|
+
name: 'starter',
|
|
5
|
+
},
|
|
6
|
+
|
|
7
|
+
extends: ['../core', '../ui', '../layout', '../motion'],
|
|
8
|
+
|
|
9
|
+
compatibilityDate: '2026-06-20',
|
|
10
|
+
|
|
11
|
+
typescript: {
|
|
12
|
+
typeCheck: false,
|
|
13
|
+
strict: true,
|
|
14
|
+
},
|
|
15
|
+
})
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
// Theme Layer - Color mode, accent colors, accessibility preferences
|
|
2
|
+
|
|
3
|
+
declare module '@nuxt/schema' {
|
|
4
|
+
interface PublicRuntimeConfig {
|
|
5
|
+
themeDefaultAccent: string
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
2
9
|
export default defineNuxtConfig({
|
|
3
10
|
$meta: {
|
|
4
11
|
name: 'theme',
|
|
@@ -6,6 +13,12 @@ export default defineNuxtConfig({
|
|
|
6
13
|
|
|
7
14
|
extends: ['../core'],
|
|
8
15
|
|
|
16
|
+
runtimeConfig: {
|
|
17
|
+
public: {
|
|
18
|
+
themeDefaultAccent: 'blue',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
|
|
9
22
|
alias: {
|
|
10
23
|
'#layers/theme': import.meta.dirname,
|
|
11
24
|
'#layers/theme/types': `${import.meta.dirname}/app/types`,
|
|
@@ -28,11 +28,12 @@ const accentCSS = buildAccentCSS()
|
|
|
28
28
|
// Written as a self-invoking function to avoid polluting the global scope.
|
|
29
29
|
// JSON.parse handles the quoted string that useLocalStorage writes.
|
|
30
30
|
// Note: theme-mode is stored as a raw string by Nuxt Color Mode (no JSON.stringify).
|
|
31
|
-
|
|
31
|
+
function buildInitScript(defaultAccent: string) {
|
|
32
|
+
return `(function(){
|
|
32
33
|
try{
|
|
33
34
|
var h=document.documentElement;
|
|
34
35
|
var c=localStorage.getItem('theme-colour');
|
|
35
|
-
h.setAttribute('data-theme-colour',c?JSON.parse(c):'
|
|
36
|
+
h.setAttribute('data-theme-colour',c?JSON.parse(c):'${defaultAccent}');
|
|
36
37
|
var ct=localStorage.getItem('theme-contrast');
|
|
37
38
|
var ctv=ct?JSON.parse(ct):'system';
|
|
38
39
|
if(ctv==='on'){h.setAttribute('data-theme-contrast','high')}
|
|
@@ -55,9 +56,12 @@ const initScript = `(function(){
|
|
|
55
56
|
else{h.setAttribute('data-theme-mode',(window.matchMedia&&window.matchMedia('(prefers-color-scheme:dark)').matches)?'dark':'light')}
|
|
56
57
|
}catch(e){}
|
|
57
58
|
})()`.replace(/\n\s*/g, '')
|
|
59
|
+
}
|
|
58
60
|
|
|
59
61
|
export default defineNitroPlugin((nitroApp) => {
|
|
60
|
-
nitroApp.hooks.hook('render:html', (html) => {
|
|
62
|
+
nitroApp.hooks.hook('render:html', (html, { event }) => {
|
|
63
|
+
const config = useRuntimeConfig(event)
|
|
64
|
+
const initScript = buildInitScript(config.public.themeDefaultAccent || 'blue')
|
|
61
65
|
html.head.unshift(
|
|
62
66
|
`<style id="theme-accent-css">${accentCSS}</style><script>${initScript}</script>`
|
|
63
67
|
)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kmcom-nuxt-layers",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.3.1",
|
|
5
5
|
"description": "Composable Nuxt 4 layers for building scalable Vue applications",
|
|
6
6
|
"exports": {
|
|
7
7
|
"./layers/core": "./layers/core/nuxt.config.ts",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"./layers/theme": "./layers/theme/nuxt.config.ts",
|
|
25
25
|
"./layers/content": "./layers/content/nuxt.config.ts",
|
|
26
26
|
"./layers/feeds": "./layers/feeds/nuxt.config.ts",
|
|
27
|
-
"./layers/routing": "./layers/routing/nuxt.config.ts"
|
|
27
|
+
"./layers/routing": "./layers/routing/nuxt.config.ts",
|
|
28
|
+
"./layers/starter": "./layers/starter/nuxt.config.ts"
|
|
28
29
|
},
|
|
29
30
|
"files": [
|
|
30
31
|
"layers/*/nuxt.config.ts",
|