kmcom-nuxt-layers 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/layers/content/nuxt.config.ts +16 -5
- package/layers/routing/app/app.config.ts +20 -0
- package/layers/routing/app/composables/useFeatures.ts +12 -0
- package/layers/routing/app/composables/useMaintenance.ts +7 -0
- package/layers/routing/app/composables/useRoutingConfig.ts +20 -0
- package/layers/routing/app/middleware/01.maintenance.global.ts +6 -0
- package/layers/routing/app/middleware/02.governance.global.ts +25 -0
- package/layers/routing/app/plugins/feature-flags.client.ts +15 -0
- package/layers/routing/app/plugins/scroll-routing.client.ts +21 -0
- package/layers/routing/app/types/route-meta.d.ts +6 -0
- package/layers/routing/app/types/routing.ts +48 -0
- package/layers/routing/nuxt.config.ts +27 -0
- package/layers/routing/package.json +6 -0
- package/layers/shader/app/components/Preset/ThemeBubble.client.vue +87 -0
- package/layers/shader/app/components/Preset/ThemeLavaLamp.client.vue +86 -0
- package/layers/shader/app/components/Preset/ThemePlasma.client.vue +86 -0
- package/layers/shader/app/components/Preset/ThemeWave.client.vue +86 -0
- package/layers/shader/app/components/Shader/Background.client.vue +9 -0
- package/layers/shader/app/composables/useAmbientMaterials.ts +156 -0
- package/layers/shader/app/composables/useThemeColors.ts +16 -7
- package/package.json +6 -2
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
// Conditionally load nuxt-studio if installed (optional peer dependency)
|
|
1
|
+
// Conditionally load nuxt-studio if installed (optional peer dependency) and in development
|
|
2
2
|
const studioModule: string[] = []
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
if (process.env.NODE_ENV === 'development') {
|
|
4
|
+
try {
|
|
5
|
+
await import('nuxt-studio')
|
|
6
|
+
studioModule.push('nuxt-studio')
|
|
7
|
+
} catch {}
|
|
8
|
+
}
|
|
7
9
|
|
|
8
10
|
// https://nuxt.com/docs/api/configuration/nuxt-config
|
|
9
11
|
export default defineNuxtConfig({
|
|
@@ -20,6 +22,15 @@ export default defineNuxtConfig({
|
|
|
20
22
|
|
|
21
23
|
modules: ['@nuxt/ui', '@nuxt/content', ...studioModule],
|
|
22
24
|
|
|
25
|
+
// Configure @nuxt/content for production/Netlify builds
|
|
26
|
+
content: {
|
|
27
|
+
// Disable local database for production builds to avoid better-sqlite3 native module issues
|
|
28
|
+
// database: process.env.NETLIFY || process.env.NODE_ENV === 'production' ? false : {},
|
|
29
|
+
experimental: {
|
|
30
|
+
nativeSqlite: true,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
|
|
23
34
|
css: ['#layers/content/app/assets/css/main.css'],
|
|
24
35
|
|
|
25
36
|
compatibilityDate: '2026-01-24',
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { RoutingLayerConfig } from './types/routing'
|
|
2
|
+
|
|
3
|
+
export default defineAppConfig({
|
|
4
|
+
routingLayer: {
|
|
5
|
+
preset: 'simple',
|
|
6
|
+
strictDefaultDeny: false,
|
|
7
|
+
layerDefaultDeny: false,
|
|
8
|
+
runtimeFlags: false,
|
|
9
|
+
debug: false,
|
|
10
|
+
maintenance: { enabled: false, allowRoutes: ['/maintenance'] },
|
|
11
|
+
scrollRouting: { enabled: false, mode: 'replace' },
|
|
12
|
+
features: {},
|
|
13
|
+
} satisfies RoutingLayerConfig,
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
declare module '@nuxt/schema' {
|
|
17
|
+
interface AppConfigInput {
|
|
18
|
+
routingLayer?: Partial<import('./types/routing').RoutingLayerConfig>
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FeatureValue } from '../types/routing'
|
|
2
|
+
|
|
3
|
+
export function useFeatures() {
|
|
4
|
+
const { config } = useRoutingConfig()
|
|
5
|
+
const runtimeFlags = useState<Record<string, FeatureValue>>('routing:flags', () => ({}))
|
|
6
|
+
|
|
7
|
+
function resolve(name: string): FeatureValue {
|
|
8
|
+
return runtimeFlags.value[name] ?? config.features[name] ?? 'disabled'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return { resolve, runtimeFlags }
|
|
12
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defu } from 'defu'
|
|
2
|
+
import { ROUTING_PRESETS } from '../types/routing'
|
|
3
|
+
import type { RoutingLayerConfig, FeatureValue } from '../types/routing'
|
|
4
|
+
|
|
5
|
+
export function useRoutingConfig() {
|
|
6
|
+
const appConfig = useAppConfig()
|
|
7
|
+
const user = appConfig.routingLayer as Partial<RoutingLayerConfig>
|
|
8
|
+
const preset = ROUTING_PRESETS[user.preset ?? 'simple']
|
|
9
|
+
const config = defu(user, preset) as RoutingLayerConfig
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
config,
|
|
13
|
+
isStrictMode: () => config.strictDefaultDeny,
|
|
14
|
+
isLayerDefaultDeny: () => config.layerDefaultDeny,
|
|
15
|
+
getFeatureVariant: (name: string): FeatureValue =>
|
|
16
|
+
config.features[name] ?? 'disabled',
|
|
17
|
+
isFeatureEnabled: (name: string) =>
|
|
18
|
+
config.features[name] === 'enabled',
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export default defineNuxtRouteMiddleware((to) => {
|
|
2
|
+
const { config, isStrictMode, isLayerDefaultDeny } = useRoutingConfig()
|
|
3
|
+
const { resolve } = useFeatures()
|
|
4
|
+
const meta = to.meta
|
|
5
|
+
|
|
6
|
+
if (config.debug) console.log('[routing] governance check', to.path, meta)
|
|
7
|
+
|
|
8
|
+
// strict default-deny: every route must declare a feature
|
|
9
|
+
if (isStrictMode() && !meta.feature) {
|
|
10
|
+
throw createError({ statusCode: 404 })
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// layer default-deny: layer routes must declare a feature
|
|
14
|
+
if (isLayerDefaultDeny() && meta.__fromLayer && !meta.feature) {
|
|
15
|
+
throw createError({ statusCode: 404 })
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!meta.feature) return
|
|
19
|
+
|
|
20
|
+
const variant = resolve(meta.feature)
|
|
21
|
+
if (config.debug) console.log(`[routing] feature "${meta.feature}" resolved to "${variant}"`)
|
|
22
|
+
|
|
23
|
+
if (variant === 'disabled') throw createError({ statusCode: 404 })
|
|
24
|
+
if (variant === 'beta' || variant === 'coming-soon') return navigateTo('/coming-soon')
|
|
25
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { FeatureValue } from '../types/routing'
|
|
2
|
+
|
|
3
|
+
export default defineNuxtPlugin(async () => {
|
|
4
|
+
const { config } = useRoutingConfig()
|
|
5
|
+
if (!config.runtimeFlags) return
|
|
6
|
+
|
|
7
|
+
const { runtimeFlags } = useFeatures()
|
|
8
|
+
try {
|
|
9
|
+
const data = await $fetch<Record<string, FeatureValue>>('/api/feature-flags')
|
|
10
|
+
runtimeFlags.value = data
|
|
11
|
+
}
|
|
12
|
+
catch (e) {
|
|
13
|
+
if (config.debug) console.warn('[routing] Failed to fetch feature flags', e)
|
|
14
|
+
}
|
|
15
|
+
})
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export default defineNuxtPlugin(() => {
|
|
2
|
+
const { config } = useRoutingConfig()
|
|
3
|
+
if (!config.scrollRouting.enabled) return
|
|
4
|
+
|
|
5
|
+
const router = useRouter()
|
|
6
|
+
const observer = new IntersectionObserver(
|
|
7
|
+
(entries) => {
|
|
8
|
+
const visible = entries.find(e => e.isIntersecting)
|
|
9
|
+
if (!visible) return
|
|
10
|
+
const id = visible.target.getAttribute('data-section')
|
|
11
|
+
if (!id) return
|
|
12
|
+
const method = config.scrollRouting.mode === 'push' ? 'push' : 'replace'
|
|
13
|
+
router[method]({ hash: `#${id}` })
|
|
14
|
+
},
|
|
15
|
+
{ threshold: 0.5 },
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
onNuxtReady(() => {
|
|
19
|
+
document.querySelectorAll('[data-section]').forEach(el => observer.observe(el))
|
|
20
|
+
})
|
|
21
|
+
})
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type FeatureValue = 'enabled' | 'disabled' | 'beta' | 'coming-soon'
|
|
2
|
+
export type RoutingPreset = 'simple' | 'marketing' | 'product' | 'enterprise'
|
|
3
|
+
|
|
4
|
+
export interface RoutingLayerConfig {
|
|
5
|
+
preset: RoutingPreset
|
|
6
|
+
strictDefaultDeny: boolean
|
|
7
|
+
layerDefaultDeny: boolean
|
|
8
|
+
runtimeFlags: boolean
|
|
9
|
+
debug: boolean
|
|
10
|
+
maintenance: { enabled: boolean; allowRoutes: string[] }
|
|
11
|
+
scrollRouting: { enabled: boolean; mode: 'replace' | 'push' }
|
|
12
|
+
features: Record<string, FeatureValue>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const ROUTING_PRESETS: Record<RoutingPreset, Omit<RoutingLayerConfig, 'preset' | 'features'>> = {
|
|
16
|
+
simple: {
|
|
17
|
+
strictDefaultDeny: false,
|
|
18
|
+
layerDefaultDeny: false,
|
|
19
|
+
runtimeFlags: false,
|
|
20
|
+
debug: false,
|
|
21
|
+
maintenance: { enabled: false, allowRoutes: ['/maintenance'] },
|
|
22
|
+
scrollRouting: { enabled: false, mode: 'replace' },
|
|
23
|
+
},
|
|
24
|
+
marketing: {
|
|
25
|
+
strictDefaultDeny: false,
|
|
26
|
+
layerDefaultDeny: true,
|
|
27
|
+
runtimeFlags: false,
|
|
28
|
+
debug: false,
|
|
29
|
+
maintenance: { enabled: true, allowRoutes: ['/maintenance'] },
|
|
30
|
+
scrollRouting: { enabled: true, mode: 'replace' },
|
|
31
|
+
},
|
|
32
|
+
product: {
|
|
33
|
+
strictDefaultDeny: false,
|
|
34
|
+
layerDefaultDeny: true,
|
|
35
|
+
runtimeFlags: true,
|
|
36
|
+
debug: false,
|
|
37
|
+
maintenance: { enabled: true, allowRoutes: ['/maintenance'] },
|
|
38
|
+
scrollRouting: { enabled: false, mode: 'replace' },
|
|
39
|
+
},
|
|
40
|
+
enterprise: {
|
|
41
|
+
strictDefaultDeny: true,
|
|
42
|
+
layerDefaultDeny: true,
|
|
43
|
+
runtimeFlags: true,
|
|
44
|
+
debug: false,
|
|
45
|
+
maintenance: { enabled: true, allowRoutes: ['/maintenance'] },
|
|
46
|
+
scrollRouting: { enabled: false, mode: 'replace' },
|
|
47
|
+
},
|
|
48
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { NuxtPage } from '@nuxt/schema'
|
|
2
|
+
|
|
3
|
+
export default defineNuxtConfig({
|
|
4
|
+
$meta: { name: 'routing' },
|
|
5
|
+
|
|
6
|
+
alias: {
|
|
7
|
+
'#layers/routing': import.meta.dirname,
|
|
8
|
+
},
|
|
9
|
+
|
|
10
|
+
compatibilityDate: '2026-01-30',
|
|
11
|
+
|
|
12
|
+
hooks: {
|
|
13
|
+
'pages:extend'(pages) {
|
|
14
|
+
const cwd = process.cwd()
|
|
15
|
+
const tag = (list: NuxtPage[]) => {
|
|
16
|
+
for (const page of list) {
|
|
17
|
+
if (page.file && !page.file.startsWith(cwd)) {
|
|
18
|
+
page.meta ??= {}
|
|
19
|
+
page.meta.__fromLayer = true
|
|
20
|
+
}
|
|
21
|
+
if (page.children) tag(page.children)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
tag(pages)
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
})
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// @ts-nocheck - TSL types
|
|
3
|
+
import {
|
|
4
|
+
createAmbientUniforms,
|
|
5
|
+
createThemeBubbleColorNode,
|
|
6
|
+
} from '#layers/shader/app/composables/useAmbientMaterials'
|
|
7
|
+
|
|
8
|
+
const props = withDefaults(
|
|
9
|
+
defineProps<{
|
|
10
|
+
speed?: number
|
|
11
|
+
intensity?: number
|
|
12
|
+
mouseInteraction?: boolean
|
|
13
|
+
mouseStrength?: number
|
|
14
|
+
color1?: string
|
|
15
|
+
color2?: string
|
|
16
|
+
color3?: string
|
|
17
|
+
color4?: string
|
|
18
|
+
}>(),
|
|
19
|
+
{
|
|
20
|
+
speed: 1.0,
|
|
21
|
+
intensity: 1.0,
|
|
22
|
+
mouseInteraction: true,
|
|
23
|
+
mouseStrength: 0.3,
|
|
24
|
+
color1: '#8b5cf6',
|
|
25
|
+
color2: '#6366f1',
|
|
26
|
+
color3: '#a78bfa',
|
|
27
|
+
color4: '#38bdf8',
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
const emit = defineEmits<{
|
|
32
|
+
node: [colorNode: any]
|
|
33
|
+
}>()
|
|
34
|
+
|
|
35
|
+
const uniforms = createAmbientUniforms({
|
|
36
|
+
speed: props.speed,
|
|
37
|
+
intensity: props.intensity,
|
|
38
|
+
mouseInteraction: props.mouseInteraction,
|
|
39
|
+
})
|
|
40
|
+
if (props.mouseInteraction) {
|
|
41
|
+
uniforms.mouseStrength.value = props.mouseStrength
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const c1 = useShaderColor(props.color1)
|
|
45
|
+
const c2 = useShaderColor(props.color2)
|
|
46
|
+
const c3 = useShaderColor(props.color3)
|
|
47
|
+
const c4 = useShaderColor(props.color4)
|
|
48
|
+
|
|
49
|
+
// color3/color4 accepted for API consistency but Bubble only uses color1+color2
|
|
50
|
+
const colorNode = createThemeBubbleColorNode(uniforms, {
|
|
51
|
+
color1: c1.node,
|
|
52
|
+
color2: c2.node,
|
|
53
|
+
color3: c3.node,
|
|
54
|
+
color4: c4.node,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
watch(() => props.color1, (hex) => c1.tweenTo(hex, 0.8))
|
|
58
|
+
watch(() => props.color2, (hex) => c2.tweenTo(hex, 0.8))
|
|
59
|
+
watch(() => props.color3, (hex) => c3.tweenTo(hex, 0.8))
|
|
60
|
+
watch(() => props.color4, (hex) => c4.tweenTo(hex, 0.8))
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const runtime = useShaderRuntimeContext()
|
|
64
|
+
watch(
|
|
65
|
+
() => [runtime.mouse.mouseX.value, runtime.mouse.mouseY.value],
|
|
66
|
+
([mx, my]) => {
|
|
67
|
+
uniforms.mouseX.value = mx
|
|
68
|
+
uniforms.mouseY.value = my
|
|
69
|
+
},
|
|
70
|
+
{ immediate: true }
|
|
71
|
+
)
|
|
72
|
+
} catch {
|
|
73
|
+
// No runtime context
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
watch(() => props.speed, (v) => { uniforms.speed.value = v })
|
|
77
|
+
watch(() => props.intensity, (v) => { uniforms.intensity.value = v })
|
|
78
|
+
watch(() => props.mouseInteraction, (v) => { uniforms.mouseStrength.value = v ? props.mouseStrength : 0 })
|
|
79
|
+
watch(() => props.mouseStrength, (v) => { if (props.mouseInteraction) uniforms.mouseStrength.value = v })
|
|
80
|
+
|
|
81
|
+
emit('node', colorNode)
|
|
82
|
+
defineExpose({ uniforms, colorNode })
|
|
83
|
+
</script>
|
|
84
|
+
|
|
85
|
+
<template>
|
|
86
|
+
<slot />
|
|
87
|
+
</template>
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// @ts-nocheck - TSL types
|
|
3
|
+
import {
|
|
4
|
+
createAmbientUniforms,
|
|
5
|
+
createThemeLavaLampColorNode,
|
|
6
|
+
} from '#layers/shader/app/composables/useAmbientMaterials'
|
|
7
|
+
|
|
8
|
+
const props = withDefaults(
|
|
9
|
+
defineProps<{
|
|
10
|
+
speed?: number
|
|
11
|
+
intensity?: number
|
|
12
|
+
mouseInteraction?: boolean
|
|
13
|
+
mouseStrength?: number
|
|
14
|
+
color1?: string
|
|
15
|
+
color2?: string
|
|
16
|
+
color3?: string
|
|
17
|
+
color4?: string
|
|
18
|
+
}>(),
|
|
19
|
+
{
|
|
20
|
+
speed: 1.0,
|
|
21
|
+
intensity: 1.0,
|
|
22
|
+
mouseInteraction: true,
|
|
23
|
+
mouseStrength: 0.3,
|
|
24
|
+
color1: '#8b5cf6',
|
|
25
|
+
color2: '#6366f1',
|
|
26
|
+
color3: '#a78bfa',
|
|
27
|
+
color4: '#38bdf8',
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
const emit = defineEmits<{
|
|
32
|
+
node: [colorNode: any]
|
|
33
|
+
}>()
|
|
34
|
+
|
|
35
|
+
const uniforms = createAmbientUniforms({
|
|
36
|
+
speed: props.speed,
|
|
37
|
+
intensity: props.intensity,
|
|
38
|
+
mouseInteraction: props.mouseInteraction,
|
|
39
|
+
})
|
|
40
|
+
if (props.mouseInteraction) {
|
|
41
|
+
uniforms.mouseStrength.value = props.mouseStrength
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const c1 = useShaderColor(props.color1)
|
|
45
|
+
const c2 = useShaderColor(props.color2)
|
|
46
|
+
const c3 = useShaderColor(props.color3)
|
|
47
|
+
const c4 = useShaderColor(props.color4)
|
|
48
|
+
|
|
49
|
+
const colorNode = createThemeLavaLampColorNode(uniforms, {
|
|
50
|
+
color1: c1.node,
|
|
51
|
+
color2: c2.node,
|
|
52
|
+
color3: c3.node,
|
|
53
|
+
color4: c4.node,
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
watch(() => props.color1, (hex) => c1.tweenTo(hex, 0.8))
|
|
57
|
+
watch(() => props.color2, (hex) => c2.tweenTo(hex, 0.8))
|
|
58
|
+
watch(() => props.color3, (hex) => c3.tweenTo(hex, 0.8))
|
|
59
|
+
watch(() => props.color4, (hex) => c4.tweenTo(hex, 0.8))
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const runtime = useShaderRuntimeContext()
|
|
63
|
+
watch(
|
|
64
|
+
() => [runtime.mouse.mouseX.value, runtime.mouse.mouseY.value],
|
|
65
|
+
([mx, my]) => {
|
|
66
|
+
uniforms.mouseX.value = mx
|
|
67
|
+
uniforms.mouseY.value = my
|
|
68
|
+
},
|
|
69
|
+
{ immediate: true }
|
|
70
|
+
)
|
|
71
|
+
} catch {
|
|
72
|
+
// No runtime context
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
watch(() => props.speed, (v) => { uniforms.speed.value = v })
|
|
76
|
+
watch(() => props.intensity, (v) => { uniforms.intensity.value = v })
|
|
77
|
+
watch(() => props.mouseInteraction, (v) => { uniforms.mouseStrength.value = v ? props.mouseStrength : 0 })
|
|
78
|
+
watch(() => props.mouseStrength, (v) => { if (props.mouseInteraction) uniforms.mouseStrength.value = v })
|
|
79
|
+
|
|
80
|
+
emit('node', colorNode)
|
|
81
|
+
defineExpose({ uniforms, colorNode })
|
|
82
|
+
</script>
|
|
83
|
+
|
|
84
|
+
<template>
|
|
85
|
+
<slot />
|
|
86
|
+
</template>
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// @ts-nocheck - TSL types
|
|
3
|
+
import {
|
|
4
|
+
createAmbientUniforms,
|
|
5
|
+
createThemePlasmaColorNode,
|
|
6
|
+
} from '#layers/shader/app/composables/useAmbientMaterials'
|
|
7
|
+
|
|
8
|
+
const props = withDefaults(
|
|
9
|
+
defineProps<{
|
|
10
|
+
speed?: number
|
|
11
|
+
intensity?: number
|
|
12
|
+
mouseInteraction?: boolean
|
|
13
|
+
mouseStrength?: number
|
|
14
|
+
color1?: string
|
|
15
|
+
color2?: string
|
|
16
|
+
color3?: string
|
|
17
|
+
color4?: string
|
|
18
|
+
}>(),
|
|
19
|
+
{
|
|
20
|
+
speed: 1.0,
|
|
21
|
+
intensity: 1.0,
|
|
22
|
+
mouseInteraction: true,
|
|
23
|
+
mouseStrength: 0.3,
|
|
24
|
+
color1: '#8b5cf6',
|
|
25
|
+
color2: '#6366f1',
|
|
26
|
+
color3: '#a78bfa',
|
|
27
|
+
color4: '#38bdf8',
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
const emit = defineEmits<{
|
|
32
|
+
node: [colorNode: any]
|
|
33
|
+
}>()
|
|
34
|
+
|
|
35
|
+
const uniforms = createAmbientUniforms({
|
|
36
|
+
speed: props.speed,
|
|
37
|
+
intensity: props.intensity,
|
|
38
|
+
mouseInteraction: props.mouseInteraction,
|
|
39
|
+
})
|
|
40
|
+
if (props.mouseInteraction) {
|
|
41
|
+
uniforms.mouseStrength.value = props.mouseStrength
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const c1 = useShaderColor(props.color1)
|
|
45
|
+
const c2 = useShaderColor(props.color2)
|
|
46
|
+
const c3 = useShaderColor(props.color3)
|
|
47
|
+
const c4 = useShaderColor(props.color4)
|
|
48
|
+
|
|
49
|
+
const colorNode = createThemePlasmaColorNode(uniforms, {
|
|
50
|
+
color1: c1.node,
|
|
51
|
+
color2: c2.node,
|
|
52
|
+
color3: c3.node,
|
|
53
|
+
color4: c4.node,
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
watch(() => props.color1, (hex) => c1.tweenTo(hex, 0.8))
|
|
57
|
+
watch(() => props.color2, (hex) => c2.tweenTo(hex, 0.8))
|
|
58
|
+
watch(() => props.color3, (hex) => c3.tweenTo(hex, 0.8))
|
|
59
|
+
watch(() => props.color4, (hex) => c4.tweenTo(hex, 0.8))
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const runtime = useShaderRuntimeContext()
|
|
63
|
+
watch(
|
|
64
|
+
() => [runtime.mouse.mouseX.value, runtime.mouse.mouseY.value],
|
|
65
|
+
([mx, my]) => {
|
|
66
|
+
uniforms.mouseX.value = mx
|
|
67
|
+
uniforms.mouseY.value = my
|
|
68
|
+
},
|
|
69
|
+
{ immediate: true }
|
|
70
|
+
)
|
|
71
|
+
} catch {
|
|
72
|
+
// No runtime context
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
watch(() => props.speed, (v) => { uniforms.speed.value = v })
|
|
76
|
+
watch(() => props.intensity, (v) => { uniforms.intensity.value = v })
|
|
77
|
+
watch(() => props.mouseInteraction, (v) => { uniforms.mouseStrength.value = v ? props.mouseStrength : 0 })
|
|
78
|
+
watch(() => props.mouseStrength, (v) => { if (props.mouseInteraction) uniforms.mouseStrength.value = v })
|
|
79
|
+
|
|
80
|
+
emit('node', colorNode)
|
|
81
|
+
defineExpose({ uniforms, colorNode })
|
|
82
|
+
</script>
|
|
83
|
+
|
|
84
|
+
<template>
|
|
85
|
+
<slot />
|
|
86
|
+
</template>
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// @ts-nocheck - TSL types
|
|
3
|
+
import {
|
|
4
|
+
createAmbientUniforms,
|
|
5
|
+
createThemeWaveColorNode,
|
|
6
|
+
} from '#layers/shader/app/composables/useAmbientMaterials'
|
|
7
|
+
|
|
8
|
+
const props = withDefaults(
|
|
9
|
+
defineProps<{
|
|
10
|
+
speed?: number
|
|
11
|
+
intensity?: number
|
|
12
|
+
mouseInteraction?: boolean
|
|
13
|
+
mouseStrength?: number
|
|
14
|
+
color1?: string
|
|
15
|
+
color2?: string
|
|
16
|
+
color3?: string
|
|
17
|
+
color4?: string
|
|
18
|
+
}>(),
|
|
19
|
+
{
|
|
20
|
+
speed: 1.0,
|
|
21
|
+
intensity: 1.0,
|
|
22
|
+
mouseInteraction: true,
|
|
23
|
+
mouseStrength: 0.3,
|
|
24
|
+
color1: '#8b5cf6',
|
|
25
|
+
color2: '#6366f1',
|
|
26
|
+
color3: '#a78bfa',
|
|
27
|
+
color4: '#38bdf8',
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
const emit = defineEmits<{
|
|
32
|
+
node: [colorNode: any]
|
|
33
|
+
}>()
|
|
34
|
+
|
|
35
|
+
const uniforms = createAmbientUniforms({
|
|
36
|
+
speed: props.speed,
|
|
37
|
+
intensity: props.intensity,
|
|
38
|
+
mouseInteraction: props.mouseInteraction,
|
|
39
|
+
})
|
|
40
|
+
if (props.mouseInteraction) {
|
|
41
|
+
uniforms.mouseStrength.value = props.mouseStrength
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const c1 = useShaderColor(props.color1)
|
|
45
|
+
const c2 = useShaderColor(props.color2)
|
|
46
|
+
const c3 = useShaderColor(props.color3)
|
|
47
|
+
const c4 = useShaderColor(props.color4)
|
|
48
|
+
|
|
49
|
+
const colorNode = createThemeWaveColorNode(uniforms, {
|
|
50
|
+
color1: c1.node,
|
|
51
|
+
color2: c2.node,
|
|
52
|
+
color3: c3.node,
|
|
53
|
+
color4: c4.node,
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
watch(() => props.color1, (hex) => c1.tweenTo(hex, 0.8))
|
|
57
|
+
watch(() => props.color2, (hex) => c2.tweenTo(hex, 0.8))
|
|
58
|
+
watch(() => props.color3, (hex) => c3.tweenTo(hex, 0.8))
|
|
59
|
+
watch(() => props.color4, (hex) => c4.tweenTo(hex, 0.8))
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const runtime = useShaderRuntimeContext()
|
|
63
|
+
watch(
|
|
64
|
+
() => [runtime.mouse.mouseX.value, runtime.mouse.mouseY.value],
|
|
65
|
+
([mx, my]) => {
|
|
66
|
+
uniforms.mouseX.value = mx
|
|
67
|
+
uniforms.mouseY.value = my
|
|
68
|
+
},
|
|
69
|
+
{ immediate: true }
|
|
70
|
+
)
|
|
71
|
+
} catch {
|
|
72
|
+
// No runtime context
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
watch(() => props.speed, (v) => { uniforms.speed.value = v })
|
|
76
|
+
watch(() => props.intensity, (v) => { uniforms.intensity.value = v })
|
|
77
|
+
watch(() => props.mouseInteraction, (v) => { uniforms.mouseStrength.value = v ? props.mouseStrength : 0 })
|
|
78
|
+
watch(() => props.mouseStrength, (v) => { if (props.mouseInteraction) uniforms.mouseStrength.value = v })
|
|
79
|
+
|
|
80
|
+
emit('node', colorNode)
|
|
81
|
+
defineExpose({ uniforms, colorNode })
|
|
82
|
+
</script>
|
|
83
|
+
|
|
84
|
+
<template>
|
|
85
|
+
<slot />
|
|
86
|
+
</template>
|
|
@@ -138,6 +138,15 @@ watch(
|
|
|
138
138
|
}
|
|
139
139
|
)
|
|
140
140
|
|
|
141
|
+
watch(
|
|
142
|
+
() => props.clearColor,
|
|
143
|
+
(color) => {
|
|
144
|
+
if (renderer && initialized) {
|
|
145
|
+
renderer.setClearColor(new Color(color))
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
)
|
|
149
|
+
|
|
141
150
|
watch([width, height], ([w, h]) => {
|
|
142
151
|
if (!initialized) return
|
|
143
152
|
camera.aspect = w / (h || 1)
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
mix,
|
|
11
11
|
mul,
|
|
12
12
|
pow,
|
|
13
|
+
sign,
|
|
13
14
|
sin,
|
|
14
15
|
smoothstep,
|
|
15
16
|
sub,
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
import { MeshBasicNodeMaterial } from 'three/webgpu'
|
|
23
24
|
import {
|
|
24
25
|
simplexNoise2D,
|
|
26
|
+
simplexNoise3d,
|
|
25
27
|
fbm2D,
|
|
26
28
|
fbm3dSimplex,
|
|
27
29
|
ridgedFbm2d,
|
|
@@ -427,6 +429,160 @@ export function createThemeAuroraColorNode(
|
|
|
427
429
|
})()
|
|
428
430
|
}
|
|
429
431
|
|
|
432
|
+
export function createThemeWaveColorNode(
|
|
433
|
+
uniforms: AmbientUniforms,
|
|
434
|
+
colors: ThemeColorUniforms,
|
|
435
|
+
): any {
|
|
436
|
+
const { speed: uSpeed, intensity: uIntensity, mouseX: uMouseX, mouseY: uMouseY, mouseStrength: uMouseStrength } = uniforms
|
|
437
|
+
|
|
438
|
+
return Fn(() => {
|
|
439
|
+
const t = mul(time, uSpeed, 0.1)
|
|
440
|
+
const mouseOff = vec2(
|
|
441
|
+
mul(sub(uMouseX, 0.5), uMouseStrength),
|
|
442
|
+
mul(sub(uMouseY, 0.5), uMouseStrength),
|
|
443
|
+
)
|
|
444
|
+
const uvCoord = add(sub(uv(), 0.5), mouseOff)
|
|
445
|
+
|
|
446
|
+
// Noise-driven rotation
|
|
447
|
+
const degree = simplexNoise2D(vec2(t, uvCoord.x.mul(uvCoord.y))).mul(0.5).add(0.5)
|
|
448
|
+
const angle = degree.sub(0.5).mul(720.0 * Math.PI / 180.0).add(Math.PI)
|
|
449
|
+
const cosA = angle.cos()
|
|
450
|
+
const sinA = angle.sin()
|
|
451
|
+
const rx = uvCoord.x.mul(cosA).sub(uvCoord.y.mul(sinA))
|
|
452
|
+
const ry = uvCoord.x.mul(sinA).add(uvCoord.y.mul(cosA))
|
|
453
|
+
|
|
454
|
+
// Wave warp (order matters: warped x feeds into y)
|
|
455
|
+
const waveSpeed = mul(time, uSpeed, 2.0)
|
|
456
|
+
const wx = rx.add(sin(ry.mul(5.0).add(waveSpeed)).div(30.0))
|
|
457
|
+
const wy = ry.add(sin(wx.mul(7.5).add(waveSpeed)).div(15.0))
|
|
458
|
+
|
|
459
|
+
// -5° rotation for layer blend
|
|
460
|
+
const COS5 = Math.cos(-5 * Math.PI / 180)
|
|
461
|
+
const SIN5 = Math.sin(-5 * Math.PI / 180)
|
|
462
|
+
const rotated5x = wx.mul(COS5).sub(wy.mul(SIN5))
|
|
463
|
+
|
|
464
|
+
const layer1 = mix(colors.color1, colors.color2, smoothstep(-0.3, 0.2, rotated5x))
|
|
465
|
+
const layer2 = mix(colors.color3, colors.color4, smoothstep(-0.3, 0.2, rotated5x))
|
|
466
|
+
|
|
467
|
+
return mix(layer1, layer2, smoothstep(0.5, -0.3, wy)).mul(uIntensity)
|
|
468
|
+
})()
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
export function createThemeLavaLampColorNode(
|
|
472
|
+
uniforms: AmbientUniforms,
|
|
473
|
+
colors: ThemeColorUniforms,
|
|
474
|
+
): any {
|
|
475
|
+
const { speed: uSpeed, intensity: uIntensity, mouseX: uMouseX, mouseY: uMouseY, mouseStrength: uMouseStrength } = uniforms
|
|
476
|
+
|
|
477
|
+
return Fn(() => {
|
|
478
|
+
const t = mul(time, uSpeed, 0.2)
|
|
479
|
+
const mouseOff = vec2(
|
|
480
|
+
mul(sub(uMouseX, 0.5), uMouseStrength),
|
|
481
|
+
mul(sub(uMouseY, 0.5), uMouseStrength),
|
|
482
|
+
)
|
|
483
|
+
const uvCoord = add(sub(uv(), 0.5), mouseOff)
|
|
484
|
+
|
|
485
|
+
// 4 blobs anchored to quadrants, oscillation wide enough to cross center and intersect
|
|
486
|
+
const b1 = vec2(sin(mul(t, 0.9)).mul(0.22).sub(0.20), sin(mul(t, 0.7)).mul(0.22).sub(0.18))
|
|
487
|
+
const b2 = vec2(sin(mul(t, 0.8).add(2.1)).mul(0.22).add(0.20), sin(mul(t, 1.0).add(1.5)).mul(0.22).sub(0.18))
|
|
488
|
+
const b3 = vec2(sin(mul(t, 0.6).add(4.2)).mul(0.22).sub(0.20), sin(mul(t, 0.9).add(3.1)).mul(0.22).add(0.18))
|
|
489
|
+
const b4 = vec2(sin(mul(t, 1.1).add(1.0)).mul(0.22).add(0.20), sin(mul(t, 0.5).add(2.8)).mul(0.22).add(0.18))
|
|
490
|
+
|
|
491
|
+
// Per-blob breathing k: oscillates between -3.5 (large) and -6.5 (tight)
|
|
492
|
+
const k1 = sin(mul(t, 0.4)).mul(1.5).sub(5.0)
|
|
493
|
+
const k2 = sin(add(mul(t, 0.35), 2.1)).mul(1.5).sub(5.0)
|
|
494
|
+
const k3 = sin(add(mul(t, 0.45), 4.2)).mul(1.5).sub(5.0)
|
|
495
|
+
const k4 = sin(add(mul(t, 0.3), 1.0)).mul(1.5).sub(5.0)
|
|
496
|
+
|
|
497
|
+
const w1 = exp(length(sub(uvCoord, b1)).mul(k1))
|
|
498
|
+
const w2 = exp(length(sub(uvCoord, b2)).mul(k2))
|
|
499
|
+
const w3 = exp(length(sub(uvCoord, b3)).mul(k3))
|
|
500
|
+
const w4 = exp(length(sub(uvCoord, b4)).mul(k4))
|
|
501
|
+
const wTotal = add(w1, w2, w3, w4)
|
|
502
|
+
|
|
503
|
+
// Weighted colour blend
|
|
504
|
+
const colorNode = add(
|
|
505
|
+
mul(colors.color1, w1),
|
|
506
|
+
mul(colors.color2, w2),
|
|
507
|
+
mul(colors.color3, w3),
|
|
508
|
+
mul(colors.color4, w4),
|
|
509
|
+
).div(wTotal.add(0.001))
|
|
510
|
+
|
|
511
|
+
// Darken space between blobs
|
|
512
|
+
const blobStrength = smoothstep(0.05, 0.9, wTotal)
|
|
513
|
+
const bg = mul(mix(colors.color1, colors.color2, 0.3), 0.15)
|
|
514
|
+
return mix(bg, colorNode, blobStrength).mul(uIntensity)
|
|
515
|
+
})()
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
export function createThemeBubbleColorNode(
|
|
519
|
+
uniforms: AmbientUniforms,
|
|
520
|
+
colors: ThemeColorUniforms,
|
|
521
|
+
): any {
|
|
522
|
+
const { speed: uSpeed, intensity: uIntensity, mouseX: uMouseX, mouseY: uMouseY, mouseStrength: uMouseStrength } = uniforms
|
|
523
|
+
|
|
524
|
+
return Fn(() => {
|
|
525
|
+
const t = mul(time, uSpeed, 0.06)
|
|
526
|
+
const mouseOff = vec2(
|
|
527
|
+
mul(sub(uMouseX, 0.5), uMouseStrength),
|
|
528
|
+
mul(sub(uMouseY, 0.5), uMouseStrength),
|
|
529
|
+
)
|
|
530
|
+
const uvCoord = add(uv(), mouseOff)
|
|
531
|
+
|
|
532
|
+
const n1 = simplexNoise3d(vec3(uvCoord.x.mul(1.5), uvCoord.y.mul(1.5), t)).mul(0.5).add(0.5)
|
|
533
|
+
const n2 = simplexNoise3d(vec3(
|
|
534
|
+
uvCoord.x.mul(2.5).add(1.3), uvCoord.y.mul(2.5).add(0.7), t.mul(0.8),
|
|
535
|
+
)).mul(0.5).add(0.5)
|
|
536
|
+
|
|
537
|
+
const blend1 = mix(colors.color1, colors.color2, smoothstep(0.3, 0.7, n1))
|
|
538
|
+
const blend2 = mix(colors.color3, colors.color4, smoothstep(0.3, 0.7, n2))
|
|
539
|
+
return mix(blend1, blend2, smoothstep(0.35, 0.65, n2)).mul(uIntensity)
|
|
540
|
+
})()
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
export function createThemePlasmaColorNode(
|
|
544
|
+
uniforms: AmbientUniforms,
|
|
545
|
+
colors: ThemeColorUniforms,
|
|
546
|
+
): any {
|
|
547
|
+
const { speed: uSpeed, intensity: uIntensity, mouseX: uMouseX, mouseY: uMouseY, mouseStrength: uMouseStrength } = uniforms
|
|
548
|
+
|
|
549
|
+
return Fn(() => {
|
|
550
|
+
const t = mul(time, uSpeed, 0.03)
|
|
551
|
+
const mouseOff = vec2(
|
|
552
|
+
mul(sub(uMouseX, 0.5), uMouseStrength, 0.2),
|
|
553
|
+
mul(sub(uMouseY, 0.5), uMouseStrength, 0.2),
|
|
554
|
+
)
|
|
555
|
+
const uvMut = add(sub(uv(), 0.5).mul(1.2), mouseOff).toVar()
|
|
556
|
+
|
|
557
|
+
const h = simplexNoise3d(vec3(uvMut.x.mul(2.0), uvMut.y.mul(2.0), t)).toVar()
|
|
558
|
+
|
|
559
|
+
// Unrolled distortion loop (n = 1..4)
|
|
560
|
+
for (let n = 1; n < 5; n++) {
|
|
561
|
+
const i = float(n)
|
|
562
|
+
uvMut.subAssign(vec2(
|
|
563
|
+
float(0.7).div(i).mul(sin(i.mul(uvMut.y).add(i).add(t.mul(1.5)).add(h.mul(i)))),
|
|
564
|
+
float(0.4).div(i).mul(sin(uvMut.x.add(4.0).sub(i).add(h).add(t.mul(1.5)).add(i.mul(0.3)))),
|
|
565
|
+
))
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Final UV shift
|
|
569
|
+
uvMut.subAssign(vec2(
|
|
570
|
+
float(1.2).mul(sin(uvMut.x.add(t).add(h))),
|
|
571
|
+
float(0.4).mul(sin(uvMut.y.add(t).add(h.mul(0.3)))),
|
|
572
|
+
))
|
|
573
|
+
|
|
574
|
+
const cx = sin(uvMut.x.mul(2.0)).mul(0.5).add(0.5)
|
|
575
|
+
const cxy = sin(uvMut.x.add(uvMut.y).mul(1.5)).mul(0.5).add(0.5)
|
|
576
|
+
const cy = sin(uvMut.y.mul(2.0)).mul(0.5).add(0.5)
|
|
577
|
+
|
|
578
|
+
const t12 = mix(colors.color1, colors.color2, cx)
|
|
579
|
+
const t34 = mix(colors.color3, colors.color4, cy)
|
|
580
|
+
const vivid = mix(t12, t34, cxy)
|
|
581
|
+
// Mix against near-black base so black/gray reads through between colour bands
|
|
582
|
+
return mix(vec3(0.03, 0.03, 0.03), vivid, float(0.65)).mul(uIntensity)
|
|
583
|
+
})()
|
|
584
|
+
}
|
|
585
|
+
|
|
430
586
|
export function createOceanColorNode(uniforms: AmbientUniforms): any {
|
|
431
587
|
const { speed: uSpeed, intensity: uIntensity, mouseX: uMouseX, mouseY: uMouseY, mouseStrength: uMouseStrength } = uniforms
|
|
432
588
|
|
|
@@ -24,19 +24,28 @@ export function useThemeColors() {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
function applyColors() {
|
|
28
|
+
const isDark = colorMode.value === 'dark'
|
|
29
|
+
const primary = cssVarToHex('--ui-color-primary-500')
|
|
30
|
+
// CSS vars not ready yet (returns fallback) — retry next frame
|
|
31
|
+
if (primary === '#888888') {
|
|
32
|
+
requestAnimationFrame(applyColors)
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
primaryHex.value = primary
|
|
36
|
+
secondaryHex.value = isDark ? cssVarToHex('--ui-color-neutral-700') : cssVarToHex('--ui-color-secondary-500')
|
|
37
|
+
primaryLightHex.value = isDark ? cssVarToHex('--ui-color-neutral-900') : cssVarToHex('--ui-color-primary-300')
|
|
38
|
+
infoHex.value = isDark ? cssVarToHex('--ui-color-secondary-500') : cssVarToHex('--ui-color-neutral-300')
|
|
39
|
+
}
|
|
40
|
+
|
|
27
41
|
function refresh() {
|
|
28
|
-
nextTick(
|
|
29
|
-
const isDark = colorMode.value === 'dark'
|
|
30
|
-
primaryHex.value = cssVarToHex(isDark ? '--ui-color-primary-500' : '--ui-color-primary-600')
|
|
31
|
-
secondaryHex.value = cssVarToHex(isDark ? '--ui-color-secondary-500' : '--ui-color-secondary-700')
|
|
32
|
-
primaryLightHex.value = cssVarToHex(isDark ? '--ui-color-primary-300' : '--ui-color-primary-400')
|
|
33
|
-
infoHex.value = cssVarToHex(isDark ? '--ui-color-info-500' : '--ui-color-info-600')
|
|
34
|
-
})
|
|
42
|
+
nextTick(applyColors)
|
|
35
43
|
}
|
|
36
44
|
|
|
37
45
|
if (import.meta.client) {
|
|
38
46
|
watch(activeAccent, refresh, { immediate: true })
|
|
39
47
|
watch(() => colorMode.value, refresh)
|
|
48
|
+
onMounted(applyColors)
|
|
40
49
|
}
|
|
41
50
|
|
|
42
51
|
return { primaryHex, secondaryHex, infoHex, primaryLightHex, clearColor, refresh }
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kmcom-nuxt-layers",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.5.0",
|
|
5
5
|
"description": "Composable Nuxt 4 layers for building scalable Vue applications",
|
|
6
6
|
"files": [
|
|
7
7
|
"layers/*/nuxt.config.ts",
|
|
@@ -159,6 +159,9 @@
|
|
|
159
159
|
"dependencies": {
|
|
160
160
|
"skills": "^1.4.3"
|
|
161
161
|
},
|
|
162
|
+
"engines": {
|
|
163
|
+
"node": ">=18 <21"
|
|
164
|
+
},
|
|
162
165
|
"scripts": {
|
|
163
166
|
"dev": "pnpm -F playground dev",
|
|
164
167
|
"dev:core": "PLAYGROUND_LAYERS=core pnpm -F playground dev",
|
|
@@ -197,6 +200,7 @@
|
|
|
197
200
|
"layer:rebuild:ui": "pnpm build:ui && pnpm layer:generate:ui && pnpm layer:ui",
|
|
198
201
|
"layer:rebuild:layout": "pnpm build:layout && pnpm layer:generate:layout && pnpm layer:layout",
|
|
199
202
|
"layer:rebuild:motion": "pnpm build:motion && pnpm layer:generate:motion && pnpm layer:motion",
|
|
200
|
-
"browserlist": "npx update-browserslist-db@latest"
|
|
203
|
+
"browserlist": "npx update-browserslist-db@latest",
|
|
204
|
+
"deploy": "turbo run build --filter playground"
|
|
201
205
|
}
|
|
202
206
|
}
|