kmcom-nuxt-layers 1.3.1 → 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.
Files changed (102) hide show
  1. package/layers/content/app/components/Blog/List.vue +5 -1
  2. package/layers/content/app/components/Gallery/AmbientImage.vue +5 -12
  3. package/layers/content/app/components/Gallery/Detail.vue +8 -6
  4. package/layers/content/app/components/Gallery/Grid.vue +11 -3
  5. package/layers/content/app/components/Portfolio/ColorPalette.vue +1 -4
  6. package/layers/content/app/components/Portfolio/Detail.vue +6 -1
  7. package/layers/content/app/components/Portfolio/List.vue +5 -1
  8. package/layers/content/app/components/content/Figure.vue +1 -7
  9. package/layers/content/nuxt.config.ts +16 -5
  10. package/layers/content/package.json +5 -5
  11. package/layers/core/app/assets/css/main.css +5 -0
  12. package/layers/core/app/composables/useCache.ts +8 -4
  13. package/layers/core/app/composables/useErrorLog.ts +9 -5
  14. package/layers/core/app/composables/useScrollGuard.ts +4 -2
  15. package/layers/core/app/plugins/feature-detection.client.ts +1 -1
  16. package/layers/core/app/plugins/init.ts +2 -1
  17. package/layers/core/app/plugins/scroll-guard.client.ts +4 -1
  18. package/layers/core/app.config.ts +0 -9
  19. package/layers/forms/app/components/Form/Contact.vue +16 -7
  20. package/layers/forms/nuxt.config.ts +18 -0
  21. package/layers/forms/package.json +2 -0
  22. package/layers/layout/app/components/Layout/Container.vue +1 -4
  23. package/layers/layout/app/components/Layout/Grid/Debug.vue +0 -1
  24. package/layers/layout/app/components/Layout/Grid/Item.vue +12 -6
  25. package/layers/layout/app/components/Layout/Main.vue +1 -4
  26. package/layers/layout/app/components/Layout/Page/Container.vue +3 -1
  27. package/layers/layout/app/components/Layout/Page/Header.vue +16 -7
  28. package/layers/layout/app/components/Layout/Section/Grid.vue +1 -4
  29. package/layers/layout/app/components/Layout/Section/Sidebar.vue +6 -1
  30. package/layers/layout/app/components/Layout/Section/Stack.vue +1 -1
  31. package/layers/layout/app/composables/useGridConfig.ts +6 -1
  32. package/layers/motion/app/components/Motion/HorizontalScroll.vue +61 -0
  33. package/layers/motion/app/components/Motion/PinnedSection.vue +77 -0
  34. package/layers/motion/app/components/Motion/ScrollProgress.vue +8 -56
  35. package/layers/motion/app/components/Motion/ScrollScene.vue +121 -0
  36. package/layers/motion/app/components/Motion/ScrollStep.vue +45 -0
  37. package/layers/motion/app/components/Motion/TextReveal.vue +28 -63
  38. package/layers/motion/app/composables/useScrollSteps.ts +41 -0
  39. package/layers/motion/app/composables/useSectionProgress.ts +58 -0
  40. package/layers/motion/app/composables/useSmoothScroll.ts +3 -2
  41. package/layers/motion/app/plugins/locomotive-scroll.client.ts +6 -6
  42. package/layers/motion/nuxt.config.ts +6 -0
  43. package/layers/motion/package.json +2 -1
  44. package/layers/routing/app/app.config.ts +20 -0
  45. package/layers/routing/app/composables/useFeatures.ts +12 -0
  46. package/layers/routing/app/composables/useMaintenance.ts +7 -0
  47. package/layers/routing/app/composables/useRoutingConfig.ts +20 -0
  48. package/layers/routing/app/middleware/01.maintenance.global.ts +6 -0
  49. package/layers/routing/app/middleware/02.governance.global.ts +25 -0
  50. package/layers/routing/app/plugins/feature-flags.client.ts +15 -0
  51. package/layers/routing/app/plugins/scroll-routing.client.ts +21 -0
  52. package/layers/routing/app/types/route-meta.d.ts +6 -0
  53. package/layers/routing/app/types/routing.ts +48 -0
  54. package/layers/routing/nuxt.config.ts +27 -0
  55. package/layers/routing/package.json +6 -0
  56. package/layers/shader/app/components/Preset/ThemeAurora.client.vue +86 -0
  57. package/layers/shader/app/components/Preset/ThemeBubble.client.vue +87 -0
  58. package/layers/shader/app/components/Preset/ThemeFlow.client.vue +86 -0
  59. package/layers/shader/app/components/Preset/ThemeGradient.client.vue +87 -0
  60. package/layers/shader/app/components/Preset/ThemeLavaLamp.client.vue +86 -0
  61. package/layers/shader/app/components/Preset/ThemePlasma.client.vue +86 -0
  62. package/layers/shader/app/components/Preset/ThemeWave.client.vue +86 -0
  63. package/layers/shader/app/components/Shader/Background.client.vue +15 -0
  64. package/layers/shader/app/composables/useAmbientMaterials.ts +306 -0
  65. package/layers/shader/app/composables/useThemeColors.ts +52 -0
  66. package/layers/shader/app/utils/tsl/oklch.ts +12 -6
  67. package/layers/theme/app/assets/css/theme.css +19 -14
  68. package/layers/theme/app/components/ThemePicker/AccentButton.vue +2 -2
  69. package/layers/theme/app/components/ThemePicker/Colors.vue +2 -4
  70. package/layers/theme/app/components/ThemePicker/Menu.vue +4 -13
  71. package/layers/theme/app/components/ThemePicker/MenuButton.vue +1 -7
  72. package/layers/theme/app/composables/useAccentColor.ts +38 -0
  73. package/layers/theme/app/composables/useTheme.ts +14 -0
  74. package/layers/theme/app/composables/useThemeContrast.ts +34 -0
  75. package/layers/theme/app/composables/useThemeMotion.ts +34 -0
  76. package/layers/theme/app/composables/useThemePreferences.ts +3 -156
  77. package/layers/theme/app/composables/useThemeTransparency.ts +41 -0
  78. package/layers/theme/app/plugins/theme.client.ts +3 -3
  79. package/layers/theme/app/types/theme.ts +4 -0
  80. package/layers/theme/nuxt.config.ts +7 -0
  81. package/layers/ui/app/app.config.ts +44 -0
  82. package/layers/ui/app/assets/css/main.css +14 -0
  83. package/layers/ui/app/components/Accent/Blob.vue +29 -0
  84. package/layers/ui/app/components/Accent/Scene.vue +38 -0
  85. package/layers/ui/app/components/Gradient/Background.vue +22 -0
  86. package/layers/ui/app/components/Gradient/Text.vue +22 -0
  87. package/layers/ui/app/components/Progress/Bar.vue +25 -0
  88. package/layers/ui/app/components/Progress/Circular.vue +69 -0
  89. package/layers/ui/app/components/Tint/Overlay.vue +25 -0
  90. package/layers/ui/app/components/Typography/CodeBlock.vue +2 -1
  91. package/layers/ui/app/components/Typography/Headline.vue +2 -1
  92. package/layers/ui/app/components/Typography/QuoteBlock.vue +2 -1
  93. package/layers/ui/app/components/Typography/TextStroke.vue +18 -16
  94. package/layers/ui/app/composables/accent.ts +51 -0
  95. package/layers/ui/app/composables/gradient.ts +79 -0
  96. package/layers/ui/app/composables/tint.ts +20 -0
  97. package/layers/ui/app/types/accent.ts +17 -0
  98. package/layers/ui/app/types/gradient.ts +27 -0
  99. package/layers/ui/app/types/tint.ts +25 -0
  100. package/package.json +37 -31
  101. package/layers/motion/app/utils/gsapAnimations.ts +0 -122
  102. package/layers/ui/app.config.ts +0 -12
@@ -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,7 @@
1
+ export function useMaintenance() {
2
+ const { config } = useRoutingConfig()
3
+ return {
4
+ isEnabled: computed(() => config.maintenance.enabled),
5
+ allowRoutes: computed(() => config.maintenance.allowRoutes),
6
+ }
7
+ }
@@ -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,6 @@
1
+ export default defineNuxtRouteMiddleware((to) => {
2
+ const { isEnabled, allowRoutes } = useMaintenance()
3
+ if (!isEnabled.value) return
4
+ if (allowRoutes.value.some(r => to.path.startsWith(r))) return
5
+ return navigateTo(allowRoutes.value[0])
6
+ })
@@ -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,6 @@
1
+ declare module 'vue-router' {
2
+ interface RouteMeta {
3
+ feature?: string
4
+ __fromLayer?: boolean
5
+ }
6
+ }
@@ -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,6 @@
1
+ {
2
+ "name": "@layers/routing",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "./nuxt.config.ts"
6
+ }
@@ -0,0 +1,86 @@
1
+ <script setup lang="ts">
2
+ // @ts-nocheck - TSL types
3
+ import {
4
+ createAmbientUniforms,
5
+ createThemeAuroraColorNode,
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 = createThemeAuroraColorNode(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,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
+ createThemeFlowColorNode,
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 = createThemeFlowColorNode(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,87 @@
1
+ <script setup lang="ts">
2
+ // @ts-nocheck - TSL types
3
+ import {
4
+ createAmbientUniforms,
5
+ createThemeGradientColorNode,
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
+ // createAmbientUniforms hardcodes 0.5 — override with our prop value
41
+ if (props.mouseInteraction) {
42
+ uniforms.mouseStrength.value = props.mouseStrength
43
+ }
44
+
45
+ const c1 = useShaderColor(props.color1)
46
+ const c2 = useShaderColor(props.color2)
47
+ const c3 = useShaderColor(props.color3)
48
+ const c4 = useShaderColor(props.color4)
49
+
50
+ const colorNode = createThemeGradientColorNode(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>