kmcom-nuxt-layers 2.2.12 → 2.2.13

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 (98) hide show
  1. package/docs/FEEDS.md +1 -2
  2. package/layers/animations/app/composables/useMagneticElement.ts +11 -9
  3. package/layers/animations/app/composables/useTiltEffect.ts +11 -9
  4. package/layers/animations/app/utils/pointerMotion.ts +31 -0
  5. package/layers/canvas/app/components/ShaderCanvas.vue +2 -2
  6. package/layers/content/app/composables/useCollectionItems.ts +28 -0
  7. package/layers/content/app/composables/useGalleryItems.ts +8 -14
  8. package/layers/content/app/composables/usePortfolioItems.ts +10 -18
  9. package/layers/core/app/composables/useBrowser.ts +9 -82
  10. package/layers/core/app/composables/useFeatures.ts +3 -27
  11. package/layers/core/app/plugins/init.ts +157 -135
  12. package/layers/core/app/utils/browserInfo.ts +115 -0
  13. package/layers/core/app/utils/featureClasses.ts +40 -0
  14. package/layers/core/app/utils/helpers.test.ts +51 -0
  15. package/layers/feeds/app/components/Feeds/Index.vue +1 -1
  16. package/layers/feeds/app/components/Feeds/RouteCard.vue +3 -9
  17. package/layers/feeds/app/utils/feed-catalog.ts +9 -4
  18. package/layers/feeds/nuxt.config.ts +0 -1
  19. package/layers/feeds/server/utils/content-adapter.test.ts +68 -0
  20. package/layers/feeds/server/utils/content-adapter.ts +2 -22
  21. package/layers/feeds/server/utils/feed-author.ts +32 -0
  22. package/layers/feeds/server/utils/feed-config.ts +88 -0
  23. package/layers/feeds/server/utils/feed-service.ts +11 -30
  24. package/layers/feeds/server/utils/feed-xml.ts +26 -0
  25. package/layers/feeds/server/utils/formats/rss.ts +10 -15
  26. package/layers/feeds/server/utils/formats.test.ts +71 -0
  27. package/layers/forms/app/components/Form/Field.vue +42 -30
  28. package/layers/forms/app/utils/fieldProps.ts +65 -0
  29. package/layers/layout/app/components/Layout/Grid/Item.vue +29 -146
  30. package/layers/layout/app/utils/gridPlacementStyle.ts +195 -0
  31. package/layers/mailer/app/types/mailer.ts +7 -25
  32. package/layers/mailer/server/utils/email.ts +28 -13
  33. package/layers/mailer/server/utils/hooks.ts +1 -20
  34. package/layers/navigation/app/composables/useSite.ts +2 -9
  35. package/layers/navigation/app/utils/site.ts +26 -0
  36. package/layers/routing/app/utils/resolveRoute.test.ts +47 -0
  37. package/layers/routing/app/utils/resolveRoute.ts +19 -10
  38. package/layers/scripts/app/composables/useAnalytics.ts +8 -41
  39. package/layers/scripts/app/composables/useGtm.ts +6 -13
  40. package/layers/scripts/app/utils/scriptClients.ts +70 -0
  41. package/layers/scroll/app/composables/useSmoothScroll.ts +9 -43
  42. package/layers/scroll/app/utils/scroll.ts +103 -0
  43. package/layers/seo/app/composables/useSeoConfig.ts +3 -9
  44. package/layers/seo/app/utils/seoConfig.ts +38 -0
  45. package/layers/shader/app/components/Material/AmbientAurora.client.vue +11 -33
  46. package/layers/shader/app/components/Material/AmbientFlow.client.vue +10 -37
  47. package/layers/shader/app/components/Material/AmbientGradientMesh.client.vue +10 -37
  48. package/layers/shader/app/components/Material/AmbientNebula.client.vue +12 -37
  49. package/layers/shader/app/components/Material/AmbientOcean.client.vue +9 -33
  50. package/layers/shader/app/components/Material/Gradient.client.vue +25 -46
  51. package/layers/shader/app/components/Material/Image.client.vue +10 -55
  52. package/layers/shader/app/components/Material/Node.client.vue +18 -5
  53. package/layers/shader/app/components/Material/Noise.client.vue +9 -43
  54. package/layers/shader/app/components/Preset/ThemeBubble.client.vue +2 -1
  55. package/layers/shader/app/components/Preset/ThemeFlow.client.vue +2 -1
  56. package/layers/shader/app/components/Preset/ThemeGradient.client.vue +2 -1
  57. package/layers/shader/app/components/Preset/ThemeLavaLamp.client.vue +2 -1
  58. package/layers/shader/app/components/Preset/ThemePlasma.client.vue +2 -1
  59. package/layers/shader/app/components/Preset/ThemeWave.client.vue +2 -1
  60. package/layers/shader/app/components/Shader/Background.client.vue +44 -24
  61. package/layers/shader/app/composables/useAmbientMaterials.ts +5 -1
  62. package/layers/shader/app/composables/useShader.ts +38 -23
  63. package/layers/shader/app/composables/useShaderGraph.ts +11 -6
  64. package/layers/shader/app/composables/useShaderMixBlend.ts +4 -4
  65. package/layers/shader/app/composables/useShaderRuntime.ts +0 -1
  66. package/layers/shader/app/composables/useShaderVec2.ts +2 -4
  67. package/layers/shader/app/composables/useThemePreset.ts +34 -8
  68. package/layers/shader/app/composables/useUniformWatchers.ts +15 -0
  69. package/layers/shader/app/composables/useUniforms.ts +0 -1
  70. package/layers/shader/app/shaders/common/blend.ts +4 -4
  71. package/layers/shader/app/shaders/common/effects.ts +38 -21
  72. package/layers/shader/app/shaders/common/grain.ts +46 -49
  73. package/layers/shader/app/shaders/common/lighting.ts +17 -15
  74. package/layers/shader/app/shaders/common/math.ts +2 -4
  75. package/layers/shader/app/shaders/common/nodes.ts +17 -0
  76. package/layers/shader/app/shaders/common/palette.ts +21 -11
  77. package/layers/shader/app/shaders/common/patterns.ts +25 -14
  78. package/layers/shader/app/shaders/common/shapes.ts +97 -88
  79. package/layers/shader/app/shaders/common/uv.ts +33 -34
  80. package/layers/shader/app/shaders/createMaterial.ts +92 -78
  81. package/layers/shader/app/shaders/layers/paperShading.ts +22 -10
  82. package/layers/shader/app/shaders/layers/shaderGradient.ts +46 -21
  83. package/layers/shader/app/utils/tsl/tween.ts +2 -4
  84. package/layers/shader/package.json +5 -1
  85. package/layers/theme/app/components/ThemePicker/Menu.vue +3 -25
  86. package/layers/theme/app/composables/useThemePreferenceModels.ts +39 -0
  87. package/layers/theme/server/plugins/theme-fouc.ts +1 -92
  88. package/layers/theme/server/utils/accent-css.ts +75 -0
  89. package/layers/typography/app/composables/typography.ts +3 -7
  90. package/layers/visual/app/composables/accent.ts +2 -9
  91. package/layers/visual/app/composables/gradient.ts +33 -46
  92. package/layers/visual/app/composables/picture.ts +2 -79
  93. package/layers/visual/app/utils/colorTokens.ts +23 -0
  94. package/layers/visual/app/utils/gradientStyle.ts +41 -0
  95. package/layers/visual/app/utils/responsiveSizes.ts +49 -0
  96. package/package.json +15 -4
  97. package/layers/feeds/app/utils/feed-catalog.test.ts +0 -71
  98. package/layers/feeds/server/routes/feed/discovery.get.ts +0 -31
@@ -20,6 +20,7 @@
20
20
 
21
21
  import { simplexNoise2D } from '../../shaders/common/noise'
22
22
  import type { TSLNode } from '../../shaders/types'
23
+ import { watchUniformProp } from '#layers/shader/app/composables/useUniformWatchers'
23
24
 
24
25
  const {
25
26
  speed = 1.0,
@@ -53,39 +54,14 @@
53
54
  const color2Uniform: TSLNode = uniform(new Color(color2))
54
55
  const color3Uniform: TSLNode = uniform(new Color(color3))
55
56
 
56
- // Watch prop changes
57
- watch(
58
- () => speed,
59
- (val) => (speedUniform.value = val)
60
- )
61
- watch(
62
- () => intensity,
63
- (val) => (intensityUniform.value = val)
64
- )
65
- watch(
66
- () => mouseX,
67
- (val) => (mouseXUniform.value = val)
68
- )
69
- watch(
70
- () => mouseY,
71
- (val) => (mouseYUniform.value = val)
72
- )
73
- watch(
74
- () => mouseStrength,
75
- (val) => (mouseStrengthUniform.value = val)
76
- )
77
- watch(
78
- () => color1,
79
- (val) => (color1Uniform.value = new Color(val))
80
- )
81
- watch(
82
- () => color2,
83
- (val) => (color2Uniform.value = new Color(val))
84
- )
85
- watch(
86
- () => color3,
87
- (val) => (color3Uniform.value = new Color(val))
88
- )
57
+ watchUniformProp(() => speed, speedUniform)
58
+ watchUniformProp(() => intensity, intensityUniform)
59
+ watchUniformProp(() => mouseX, mouseXUniform)
60
+ watchUniformProp(() => mouseY, mouseYUniform)
61
+ watchUniformProp(() => mouseStrength, mouseStrengthUniform)
62
+ watchUniformProp(() => color1, color1Uniform, (val) => new Color(val))
63
+ watchUniformProp(() => color2, color2Uniform, (val) => new Color(val))
64
+ watchUniformProp(() => color3, color3Uniform, (val) => new Color(val))
89
65
 
90
66
  const material = computed(() => {
91
67
  const mat = new MeshBasicNodeMaterial()
@@ -4,6 +4,7 @@
4
4
  import { MeshBasicNodeMaterial } from 'three/webgpu'
5
5
 
6
6
  import type { TSLNode } from '../../types'
7
+ import { watchUniformProp } from '#layers/shader/app/composables/useUniformWatchers'
7
8
 
8
9
  const {
9
10
  colors = ['#4f46e5', '#7c3aed', '#ec4899'],
@@ -49,37 +50,11 @@
49
50
  uniform(new Color(colors[4] || '#ec4899')),
50
51
  ]
51
52
 
52
- // Watch prop changes and update uniforms
53
- watch(
54
- () => angle,
55
- (val) => {
56
- angleUniform.value = val
57
- }
58
- )
59
- watch(
60
- () => speed,
61
- (val) => {
62
- speedUniform.value = val
63
- }
64
- )
65
- watch(
66
- () => mouseX,
67
- (val) => {
68
- mouseXUniform.value = val
69
- }
70
- )
71
- watch(
72
- () => mouseY,
73
- (val) => {
74
- mouseYUniform.value = val
75
- }
76
- )
77
- watch(
78
- () => mouseStrength,
79
- (val) => {
80
- mouseStrengthUniform.value = val
81
- }
82
- )
53
+ watchUniformProp(() => angle, angleUniform)
54
+ watchUniformProp(() => speed, speedUniform)
55
+ watchUniformProp(() => mouseX, mouseXUniform)
56
+ watchUniformProp(() => mouseY, mouseYUniform)
57
+ watchUniformProp(() => mouseStrength, mouseStrengthUniform)
83
58
 
84
59
  watch(
85
60
  () => colors,
@@ -93,14 +68,10 @@
93
68
  { deep: true }
94
69
  )
95
70
 
96
- const material = computed(() => {
97
- const mat = new MeshBasicNodeMaterial()
98
- const colorCount = colors.length
99
-
100
- // Get UV coordinates
71
+ // fallow-ignore-next-line complexity
72
+ function resolveGradientT() {
101
73
  let t: TSLNode = uv().y
102
74
 
103
- // Apply angle rotation for linear gradient (using uniform for reactivity)
104
75
  if (type === 'linear') {
105
76
  const rad = angleUniform.mul(Math.PI / 180)
106
77
  const cosVal = rad.cos()
@@ -108,10 +79,8 @@
108
79
  t = uv().x.mul(sinVal).add(uv().y.mul(cosVal))
109
80
  }
110
81
 
111
- // Radial gradient uses distance from center
112
82
  if (type === 'radial') {
113
83
  let center = uv().sub(0.5)
114
- // Add mouse interaction offset
115
84
  if (mouseInteraction) {
116
85
  const mouseOffset = vec3(
117
86
  mouseXUniform.sub(0.5).mul(mouseStrengthUniform),
@@ -123,18 +92,20 @@
123
92
  t = center.length().mul(2)
124
93
  }
125
94
 
126
- // Add mouse interaction for linear gradients
127
95
  if (type === 'linear' && mouseInteraction) {
128
96
  t = t.add(mouseXUniform.sub(0.5).mul(mouseStrengthUniform))
129
97
  }
130
98
 
131
- // Add animation
132
99
  if (animated) {
133
100
  t = t.add(sin(tslTime.mul(speedUniform)).mul(0.2))
134
101
  }
135
102
 
136
- // Build gradient from colors using uniforms
137
- // Use type assertion since we know the array is initialized
103
+ return t
104
+ }
105
+
106
+ // fallow-ignore-next-line complexity
107
+ function buildGradientColorNode(t: TSLNode) {
108
+ const colorCount = colors.length
138
109
  const c0 = colorUniforms[0]
139
110
  const c1 = colorUniforms[1]
140
111
  const c2 = colorUniforms[2]
@@ -144,9 +115,10 @@
144
115
  let colorNode: TSLNode = vec3(c0)
145
116
 
146
117
  if (colorCount === 2) {
147
- colorNode = mix(vec3(c0), vec3(c1), t)
148
- } else if (colorCount >= 3) {
149
- // Multi-stop gradient using pre-extracted uniforms
118
+ return mix(vec3(c0), vec3(c1), t)
119
+ }
120
+
121
+ if (colorCount >= 3) {
150
122
  const uniforms = [c0, c1, c2, c3, c4]
151
123
  const segments = colorCount - 1
152
124
 
@@ -167,6 +139,13 @@
167
139
  }
168
140
  }
169
141
 
142
+ return colorNode
143
+ }
144
+
145
+ const material = computed(() => {
146
+ const mat = new MeshBasicNodeMaterial()
147
+
148
+ const colorNode = buildGradientColorNode(resolveGradientT())
170
149
  mat.colorNode = colorNode
171
150
  mat.transparent = transparent
172
151
  mat.side = DoubleSide
@@ -17,6 +17,7 @@
17
17
 
18
18
  import type { TSLNode } from '../../types'
19
19
  import { simplexNoise2D } from '../../utils/tsl/noise'
20
+ import { watchUniformProp } from '#layers/shader/app/composables/useUniformWatchers'
20
21
 
21
22
  const {
22
23
  src,
@@ -76,61 +77,15 @@
76
77
  const rgbShiftUniform = uniform(rgbShift)
77
78
  const vignetteIntensityUniform = uniform(vignetteIntensity)
78
79
 
79
- // Watch all prop changes for reactivity
80
- watch(
81
- () => mouseX,
82
- (val) => {
83
- mouseXUniform.value = val
84
- }
85
- )
86
- watch(
87
- () => mouseY,
88
- (val) => {
89
- mouseYUniform.value = val
90
- }
91
- )
92
- watch(
93
- () => distortion,
94
- (val) => {
95
- distortionUniform.value = val
96
- }
97
- )
98
- watch(
99
- () => distortionSpeed,
100
- (val) => {
101
- distortionSpeedUniform.value = val
102
- }
103
- )
104
- watch(
105
- () => rippleStrength,
106
- (val) => {
107
- rippleStrengthUniform.value = val
108
- }
109
- )
110
- watch(
111
- () => zoom,
112
- (val) => {
113
- zoomUniform.value = val
114
- }
115
- )
116
- watch(
117
- () => grayscale,
118
- (val) => {
119
- grayscaleUniform.value = val
120
- }
121
- )
122
- watch(
123
- () => rgbShift,
124
- (val) => {
125
- rgbShiftUniform.value = val
126
- }
127
- )
128
- watch(
129
- () => vignetteIntensity,
130
- (val) => {
131
- vignetteIntensityUniform.value = val
132
- }
133
- )
80
+ watchUniformProp(() => mouseX, mouseXUniform)
81
+ watchUniformProp(() => mouseY, mouseYUniform)
82
+ watchUniformProp(() => distortion, distortionUniform)
83
+ watchUniformProp(() => distortionSpeed, distortionSpeedUniform)
84
+ watchUniformProp(() => rippleStrength, rippleStrengthUniform)
85
+ watchUniformProp(() => zoom, zoomUniform)
86
+ watchUniformProp(() => grayscale, grayscaleUniform)
87
+ watchUniformProp(() => rgbShift, rgbShiftUniform)
88
+ watchUniformProp(() => vignetteIntensity, vignetteIntensityUniform)
134
89
 
135
90
  // Load texture
136
91
  watch(
@@ -42,10 +42,12 @@
42
42
  double: DoubleSide,
43
43
  }
44
44
 
45
- const material = computed(() => {
46
- const mat = type === 'standard' ? new MeshStandardNodeMaterial() : new MeshBasicNodeMaterial()
45
+ function createMaterialInstance() {
46
+ return type === 'standard' ? new MeshStandardNodeMaterial() : new MeshBasicNodeMaterial()
47
+ }
47
48
 
48
- // Apply node properties
49
+ // fallow-ignore-next-line complexity
50
+ function applyNodeProperties(mat: MeshBasicNodeMaterial | MeshStandardNodeMaterial) {
49
51
  if (colorNode) mat.colorNode = colorNode
50
52
  if (opacityNode) {
51
53
  mat.opacityNode = opacityNode
@@ -53,22 +55,33 @@
53
55
  }
54
56
  if (normalNode) mat.normalNode = normalNode
55
57
  if (positionNode) mat.positionNode = positionNode
58
+ }
56
59
 
57
- // Standard material specific
60
+ // fallow-ignore-next-line complexity
61
+ function applyStandardProperties(mat: MeshBasicNodeMaterial | MeshStandardNodeMaterial) {
58
62
  if (mat instanceof MeshStandardNodeMaterial) {
59
63
  if (emissiveNode) mat.emissiveNode = emissiveNode
60
64
  if (metalnessNode) mat.metalnessNode = metalnessNode
61
65
  if (roughnessNode) mat.roughnessNode = roughnessNode
62
66
  mat.flatShading = flatShading
63
67
  }
68
+ }
64
69
 
65
- // Standard properties
70
+ function applySharedProperties(mat: MeshBasicNodeMaterial | MeshStandardNodeMaterial) {
66
71
  mat.transparent = transparent || !!opacityNode
67
72
  mat.side = sideMap[side]
68
73
  mat.wireframe = wireframe
69
74
  mat.depthTest = depthTest
70
75
  mat.depthWrite = depthWrite
71
76
 
77
+ return mat
78
+ }
79
+
80
+ const material = computed(() => {
81
+ const mat = createMaterialInstance()
82
+ applyNodeProperties(mat)
83
+ applyStandardProperties(mat)
84
+ applySharedProperties(mat)
72
85
  return mat
73
86
  })
74
87
 
@@ -5,6 +5,7 @@
5
5
 
6
6
  import type { TSLNode } from '../../shaders/types'
7
7
  import { fbm, simplexNoise2D } from '../../utils/tsl/noise'
8
+ import { watchUniformProp } from '#layers/shader/app/composables/useUniformWatchers'
8
9
 
9
10
  const {
10
11
  scale = 3,
@@ -45,49 +46,14 @@
45
46
  const baseColorUniform: TSLNode = uniform(new Color(baseColor))
46
47
  const peakColorUniform: TSLNode = uniform(new Color(peakColor))
47
48
 
48
- // Watch prop changes
49
- watch(
50
- () => scale,
51
- (val) => {
52
- scaleUniform.value = val
53
- }
54
- )
55
- watch(
56
- () => speed,
57
- (val) => {
58
- speedUniform.value = val
59
- }
60
- )
61
- watch(
62
- () => mouseX,
63
- (val) => {
64
- mouseXUniform.value = val
65
- }
66
- )
67
- watch(
68
- () => mouseY,
69
- (val) => {
70
- mouseYUniform.value = val
71
- }
72
- )
73
- watch(
74
- () => mouseStrength,
75
- (val) => {
76
- mouseStrengthUniform.value = val
77
- }
78
- )
79
- watch(
80
- () => baseColor,
81
- (val) => {
82
- baseColorUniform.value = new Color(val)
83
- }
84
- )
85
- watch(
86
- () => peakColor,
87
- (val) => {
88
- peakColorUniform.value = new Color(val)
89
- }
90
- )
49
+ // fallow-ignore-next-line code-duplication
50
+ watchUniformProp(() => scale, scaleUniform)
51
+ watchUniformProp(() => speed, speedUniform)
52
+ watchUniformProp(() => mouseX, mouseXUniform)
53
+ watchUniformProp(() => mouseY, mouseYUniform)
54
+ watchUniformProp(() => mouseStrength, mouseStrengthUniform)
55
+ watchUniformProp(() => baseColor, baseColorUniform, (val) => new Color(val))
56
+ watchUniformProp(() => peakColor, peakColorUniform, (val) => new Color(val))
91
57
 
92
58
  const material = computed(() => {
93
59
  const mat = new MeshBasicNodeMaterial()
@@ -1,8 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  import { createThemeBubbleColorNode } from '#layers/shader/app/composables/useAmbientMaterials'
3
- import { type ThemePresetProps } from '#layers/shader/app/composables/useThemePreset'
4
3
  import type { TSLNode } from '#layers/shader/app/types/tsl'
5
4
 
5
+ import type { ThemePresetProps } from '../../composables/useThemePreset'
6
+
6
7
  const {
7
8
  speed = 1.0,
8
9
  intensity = 1.0,
@@ -1,8 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  import { createThemeFlowColorNode } from '#layers/shader/app/composables/useAmbientMaterials'
3
- import { type ThemePresetProps } from '#layers/shader/app/composables/useThemePreset'
4
3
  import type { TSLNode } from '#layers/shader/app/types/tsl'
5
4
 
5
+ import type { ThemePresetProps } from '../../composables/useThemePreset'
6
+
6
7
  const {
7
8
  speed = 1.0,
8
9
  intensity = 1.0,
@@ -1,8 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  import { createThemeGradientColorNode } from '#layers/shader/app/composables/useAmbientMaterials'
3
- import { type ThemePresetProps } from '#layers/shader/app/composables/useThemePreset'
4
3
  import type { TSLNode } from '#layers/shader/app/types/tsl'
5
4
 
5
+ import type { ThemePresetProps } from '../../composables/useThemePreset'
6
+
6
7
  const {
7
8
  speed = 1.0,
8
9
  intensity = 1.0,
@@ -1,8 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  import { createThemeLavaLampColorNode } from '#layers/shader/app/composables/useAmbientMaterials'
3
- import { type ThemePresetProps } from '#layers/shader/app/composables/useThemePreset'
4
3
  import type { TSLNode } from '#layers/shader/app/types/tsl'
5
4
 
5
+ import type { ThemePresetProps } from '../../composables/useThemePreset'
6
+
6
7
  const {
7
8
  speed = 1.0,
8
9
  intensity = 1.0,
@@ -1,8 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  import { createThemePlasmaColorNode } from '#layers/shader/app/composables/useAmbientMaterials'
3
- import { type ThemePresetProps } from '#layers/shader/app/composables/useThemePreset'
4
3
  import type { TSLNode } from '#layers/shader/app/types/tsl'
5
4
 
5
+ import type { ThemePresetProps } from '../../composables/useThemePreset'
6
+
6
7
  const {
7
8
  speed = 1.0,
8
9
  intensity = 1.0,
@@ -1,8 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  import { createThemeWaveColorNode } from '#layers/shader/app/composables/useAmbientMaterials'
3
- import { type ThemePresetProps } from '#layers/shader/app/composables/useThemePreset'
4
3
  import type { TSLNode } from '#layers/shader/app/types/tsl'
5
4
 
5
+ import type { ThemePresetProps } from '../../composables/useThemePreset'
6
+
6
7
  const {
7
8
  speed = 1.0,
8
9
  intensity = 1.0,
@@ -14,7 +14,8 @@
14
14
  SRGBColorSpace,
15
15
  type ToneMapping,
16
16
  } from 'three'
17
- import { MeshBasicNodeMaterial, WebGPURenderer } from 'three/webgpu'
17
+ import * as ThreeWebGPU from 'three/webgpu'
18
+ import { MeshBasicNodeMaterial } from 'three/webgpu'
18
19
 
19
20
  const {
20
21
  material = null,
@@ -42,13 +43,13 @@
42
43
  }>()
43
44
 
44
45
  const emit = defineEmits<{
45
- ready: [renderer: WebGPURenderer]
46
+ ready: [renderer: ThreeWebGPU.WebGPURenderer]
46
47
  }>()
47
48
 
48
49
  const canvasRef = useTemplateRef<HTMLCanvasElement>('canvasRef')
49
50
  const { width, height } = useWindowSize()
50
51
 
51
- let renderer: WebGPURenderer | undefined
52
+ let renderer: ThreeWebGPU.WebGPURenderer | undefined
52
53
  let scene: Scene
53
54
  let camera: PerspectiveCamera
54
55
  let planeMesh: Mesh
@@ -86,6 +87,38 @@
86
87
  }
87
88
  }
88
89
 
90
+ function createRenderer(canvas: HTMLCanvasElement) {
91
+ const nextRenderer = new ThreeWebGPU.WebGPURenderer({ canvas, antialias: antialias })
92
+ nextRenderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
93
+ nextRenderer.setSize(width.value, height.value)
94
+ nextRenderer.setClearColor(new Color(clearColor))
95
+ nextRenderer.toneMapping = toneMappingMap[toneMapping] ?? ACESFilmicToneMapping
96
+ nextRenderer.outputColorSpace = SRGBColorSpace
97
+ return nextRenderer
98
+ }
99
+
100
+ function createSceneAndCamera() {
101
+ const nextScene = new Scene()
102
+ const nextCamera = new PerspectiveCamera(75, width.value / (height.value || 1), 0.1, 100)
103
+ nextCamera.position.set(0, 0, 1)
104
+ return { nextScene, nextCamera }
105
+ }
106
+
107
+ function createPlaneMesh() {
108
+ const { width: pw, height: ph } = getPlaneSize(width.value, height.value)
109
+ const geometry = new PlaneGeometry(pw, ph)
110
+ return new Mesh(geometry, material ?? new MeshBasicMaterial({ color: 0x000000 }))
111
+ }
112
+
113
+ // fallow-ignore-next-line complexity
114
+ async function compileMaterialIfReady() {
115
+ if (!renderer || !scene || !camera || !material?.colorNode) return
116
+ await renderer.compileAsync(scene, camera)
117
+ if (disposed && renderer) {
118
+ renderer.dispose()
119
+ }
120
+ }
121
+
89
122
  function animate() {
90
123
  // Bail if the component unmounted — guards against a frame scheduled just
91
124
  // before teardown rendering on a disposed renderer.
@@ -102,12 +135,7 @@
102
135
 
103
136
  try {
104
137
  // WebGPURenderer falls back to WebGL2 if WebGPU is unavailable
105
- renderer = new WebGPURenderer({ canvas, antialias: antialias })
106
- renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
107
- renderer.setSize(width.value, height.value)
108
- renderer.setClearColor(new Color(clearColor))
109
- renderer.toneMapping = toneMappingMap[toneMapping] ?? ACESFilmicToneMapping
110
- renderer.outputColorSpace = SRGBColorSpace
138
+ renderer = createRenderer(canvas)
111
139
 
112
140
  // Required async init for WebGPU context setup
113
141
  await renderer.init()
@@ -121,13 +149,10 @@
121
149
  return
122
150
  }
123
151
 
124
- scene = new Scene()
125
- camera = new PerspectiveCamera(75, width.value / (height.value || 1), 0.1, 100)
126
- camera.position.set(0, 0, 1)
127
-
128
- const { width: pw, height: ph } = getPlaneSize(width.value, height.value)
129
- const geometry = new PlaneGeometry(pw, ph)
130
- planeMesh = new Mesh(geometry, material ?? new MeshBasicMaterial({ color: 0x000000 }))
152
+ const { nextScene, nextCamera } = createSceneAndCamera()
153
+ scene = nextScene
154
+ camera = nextCamera
155
+ planeMesh = createPlaneMesh()
131
156
  scene.add(planeMesh)
132
157
 
133
158
  // Pre-compile the shader pipeline only when the material's colorNode is already
@@ -136,18 +161,13 @@
136
161
  // won't reliably trigger a GPU pipeline rebuild in all drivers.
137
162
  // When colorNode is absent we skip pre-compilation and let the first render()
138
163
  // call compile lazily — the browser may stutter once, but the output is correct.
139
- if (material?.colorNode) {
140
- await renderer.compileAsync(scene, camera)
141
- if (disposed) {
142
- renderer.dispose()
143
- return
144
- }
145
- }
164
+ await compileMaterialIfReady()
165
+ if (disposed) return
146
166
 
147
167
  initialized = true
148
168
  emit('ready', renderer)
149
169
  } catch (e) {
150
- console.error('[ShaderBackground] init() failed:', e)
170
+ void e
151
171
  }
152
172
 
153
173
  // Always start the animation loop even if pre-compilation failed — Three.js
@@ -35,7 +35,11 @@ import type { ColorUniform, FloatUniform, TSLNode, Vec3Uniform } from '../shader
35
35
  // ============================================
36
36
 
37
37
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
- function buildFlowWarpedUV(uvCoord: any, t: any, mouseOffset: any): {
38
+ function buildFlowWarpedUV(
39
+ uvCoord: any,
40
+ t: any,
41
+ mouseOffset: any
42
+ ): {
39
43
  warpedUV: TSLNode
40
44
  n1: TSLNode
41
45
  n2: TSLNode
@@ -11,6 +11,42 @@ export type UseShaderOptions = {
11
11
  autoDispose?: boolean
12
12
  }
13
13
 
14
+ function assignIfDefined<T extends object, K extends keyof T>(
15
+ target: T,
16
+ key: K,
17
+ value: T[K] | undefined
18
+ ) {
19
+ if (value !== undefined) {
20
+ target[key] = value
21
+ }
22
+ }
23
+
24
+ function applyStandardMaterialConfig(
25
+ material: MeshStandardNodeMaterial,
26
+ config: NodeMaterialConfig
27
+ ) {
28
+ assignIfDefined(material, 'emissiveNode', config.emissiveNode)
29
+ assignIfDefined(material, 'metalnessNode', config.metalnessNode)
30
+ assignIfDefined(material, 'roughnessNode', config.roughnessNode)
31
+ assignIfDefined(material, 'flatShading', config.flatShading)
32
+ }
33
+
34
+ function applyCommonMaterialConfig(
35
+ material: MeshBasicNodeMaterial | MeshStandardNodeMaterial,
36
+ config: NodeMaterialConfig
37
+ ) {
38
+ assignIfDefined(material, 'colorNode', config.colorNode)
39
+ assignIfDefined(material, 'opacityNode', config.opacityNode)
40
+ assignIfDefined(material, 'normalNode', config.normalNode)
41
+ assignIfDefined(material, 'positionNode', config.positionNode)
42
+ assignIfDefined(material, 'transparent', config.transparent)
43
+ assignIfDefined(material, 'side', config.side)
44
+ assignIfDefined(material, 'blending', config.blending)
45
+ assignIfDefined(material, 'depthTest', config.depthTest)
46
+ assignIfDefined(material, 'depthWrite', config.depthWrite)
47
+ assignIfDefined(material, 'wireframe', config.wireframe)
48
+ }
49
+
14
50
  /**
15
51
  * Main shader management composable
16
52
  */
@@ -39,30 +75,9 @@ export function useShader(options: UseShaderOptions = {}) {
39
75
  if (!material.value) return
40
76
 
41
77
  const mat = material.value
42
-
43
- if (newConfig.colorNode !== undefined) mat.colorNode = newConfig.colorNode
44
- if (newConfig.opacityNode !== undefined) mat.opacityNode = newConfig.opacityNode
45
- if (newConfig.normalNode !== undefined) mat.normalNode = newConfig.normalNode
46
-
78
+ applyCommonMaterialConfig(mat, newConfig)
47
79
  if (mat instanceof MeshStandardNodeMaterial) {
48
- if (newConfig.emissiveNode !== undefined) mat.emissiveNode = newConfig.emissiveNode
49
- if (newConfig.metalnessNode !== undefined) mat.metalnessNode = newConfig.metalnessNode
50
- if (newConfig.roughnessNode !== undefined) mat.roughnessNode = newConfig.roughnessNode
51
- }
52
-
53
- if (newConfig.positionNode !== undefined) mat.positionNode = newConfig.positionNode
54
-
55
- // Standard material properties
56
- if (newConfig.transparent !== undefined) mat.transparent = newConfig.transparent
57
- if (newConfig.side !== undefined) mat.side = newConfig.side
58
- if (newConfig.blending !== undefined) mat.blending = newConfig.blending
59
- if (newConfig.depthTest !== undefined) mat.depthTest = newConfig.depthTest
60
- if (newConfig.depthWrite !== undefined) mat.depthWrite = newConfig.depthWrite
61
- if (newConfig.wireframe !== undefined) mat.wireframe = newConfig.wireframe
62
-
63
- // flatShading only exists on MeshStandardNodeMaterial
64
- if (mat instanceof MeshStandardNodeMaterial && newConfig.flatShading !== undefined) {
65
- mat.flatShading = newConfig.flatShading
80
+ applyStandardMaterialConfig(mat, newConfig)
66
81
  }
67
82
  }
68
83