kmcom-nuxt-layers 1.3.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/layers/content/app/components/Blog/List.vue +5 -1
- package/layers/content/app/components/Gallery/AmbientImage.vue +5 -12
- package/layers/content/app/components/Gallery/Detail.vue +8 -6
- package/layers/content/app/components/Gallery/Grid.vue +11 -3
- package/layers/content/app/components/Portfolio/ColorPalette.vue +1 -4
- package/layers/content/app/components/Portfolio/Detail.vue +6 -1
- package/layers/content/app/components/Portfolio/List.vue +5 -1
- package/layers/content/app/components/content/Figure.vue +1 -7
- package/layers/content/package.json +5 -5
- package/layers/core/app/assets/css/main.css +5 -0
- package/layers/core/app/composables/useCache.ts +8 -4
- package/layers/core/app/composables/useErrorLog.ts +9 -5
- package/layers/core/app/composables/useScrollGuard.ts +4 -2
- package/layers/core/app/plugins/feature-detection.client.ts +1 -1
- package/layers/core/app/plugins/init.ts +2 -1
- package/layers/core/app/plugins/scroll-guard.client.ts +4 -1
- package/layers/core/app.config.ts +0 -9
- package/layers/forms/app/components/Form/Contact.vue +16 -7
- package/layers/forms/nuxt.config.ts +18 -0
- package/layers/forms/package.json +2 -0
- package/layers/layout/app/components/Layout/Container.vue +1 -4
- package/layers/layout/app/components/Layout/Grid/Debug.vue +0 -1
- package/layers/layout/app/components/Layout/Grid/Item.vue +12 -6
- package/layers/layout/app/components/Layout/Main.vue +1 -4
- package/layers/layout/app/components/Layout/Page/Container.vue +3 -1
- package/layers/layout/app/components/Layout/Page/Header.vue +16 -7
- package/layers/layout/app/components/Layout/Section/Grid.vue +1 -4
- package/layers/layout/app/components/Layout/Section/Sidebar.vue +6 -1
- package/layers/layout/app/components/Layout/Section/Stack.vue +1 -1
- package/layers/layout/app/composables/useGridConfig.ts +6 -1
- package/layers/motion/app/components/Motion/HorizontalScroll.vue +61 -0
- package/layers/motion/app/components/Motion/PinnedSection.vue +77 -0
- package/layers/motion/app/components/Motion/ScrollProgress.vue +8 -56
- package/layers/motion/app/components/Motion/ScrollScene.vue +121 -0
- package/layers/motion/app/components/Motion/ScrollStep.vue +45 -0
- package/layers/motion/app/components/Motion/TextReveal.vue +28 -63
- package/layers/motion/app/composables/useScrollSteps.ts +41 -0
- package/layers/motion/app/composables/useSectionProgress.ts +58 -0
- package/layers/motion/app/composables/useSmoothScroll.ts +3 -2
- package/layers/motion/app/plugins/locomotive-scroll.client.ts +6 -6
- package/layers/motion/nuxt.config.ts +6 -0
- package/layers/motion/package.json +2 -1
- package/layers/shader/app/components/Preset/ThemeAurora.client.vue +86 -0
- package/layers/shader/app/components/Preset/ThemeFlow.client.vue +86 -0
- package/layers/shader/app/components/Preset/ThemeGradient.client.vue +87 -0
- package/layers/shader/app/components/Shader/Background.client.vue +6 -0
- package/layers/shader/app/composables/useAmbientMaterials.ts +150 -0
- package/layers/shader/app/composables/useThemeColors.ts +43 -0
- package/layers/shader/app/utils/tsl/oklch.ts +12 -6
- package/layers/theme/app/assets/css/theme.css +19 -14
- package/layers/theme/app/components/ThemePicker/AccentButton.vue +2 -2
- package/layers/theme/app/components/ThemePicker/Colors.vue +2 -4
- package/layers/theme/app/components/ThemePicker/Menu.vue +4 -13
- package/layers/theme/app/components/ThemePicker/MenuButton.vue +1 -7
- package/layers/theme/app/composables/useAccentColor.ts +38 -0
- package/layers/theme/app/composables/useTheme.ts +14 -0
- package/layers/theme/app/composables/useThemeContrast.ts +34 -0
- package/layers/theme/app/composables/useThemeMotion.ts +34 -0
- package/layers/theme/app/composables/useThemePreferences.ts +3 -156
- package/layers/theme/app/composables/useThemeTransparency.ts +41 -0
- package/layers/theme/app/plugins/theme.client.ts +3 -3
- package/layers/theme/app/types/theme.ts +4 -0
- package/layers/theme/nuxt.config.ts +7 -0
- package/layers/ui/app/app.config.ts +44 -0
- package/layers/ui/app/assets/css/main.css +14 -0
- package/layers/ui/app/components/Accent/Blob.vue +29 -0
- package/layers/ui/app/components/Accent/Scene.vue +38 -0
- package/layers/ui/app/components/Gradient/Background.vue +22 -0
- package/layers/ui/app/components/Gradient/Text.vue +22 -0
- package/layers/ui/app/components/Progress/Bar.vue +25 -0
- package/layers/ui/app/components/Progress/Circular.vue +69 -0
- package/layers/ui/app/components/Tint/Overlay.vue +25 -0
- package/layers/ui/app/components/Typography/CodeBlock.vue +2 -1
- package/layers/ui/app/components/Typography/Headline.vue +2 -1
- package/layers/ui/app/components/Typography/QuoteBlock.vue +2 -1
- package/layers/ui/app/components/Typography/TextStroke.vue +18 -16
- package/layers/ui/app/composables/accent.ts +51 -0
- package/layers/ui/app/composables/gradient.ts +79 -0
- package/layers/ui/app/composables/tint.ts +20 -0
- package/layers/ui/app/types/accent.ts +17 -0
- package/layers/ui/app/types/gradient.ts +27 -0
- package/layers/ui/app/types/tint.ts +25 -0
- package/package.json +32 -30
- package/layers/motion/app/utils/gsapAnimations.ts +0 -122
- package/layers/ui/app.config.ts +0 -12
|
@@ -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,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>
|
|
@@ -118,6 +118,12 @@ async function init() {
|
|
|
118
118
|
planeMesh = new Mesh(geometry, props.material ?? new MeshBasicMaterial({ color: 0x000000 }))
|
|
119
119
|
scene.add(planeMesh)
|
|
120
120
|
|
|
121
|
+
// Pre-compile the shader pipeline asynchronously before starting the render loop.
|
|
122
|
+
// Without this, the first render() call uses device.createRenderPipeline() which
|
|
123
|
+
// blocks the main thread for 1-5s on first visit (cold WebGPU pipeline cache).
|
|
124
|
+
// compileAsync() uses device.createRenderPipelineAsync() instead, which is non-blocking.
|
|
125
|
+
await renderer.compileAsync(scene, camera)
|
|
126
|
+
|
|
121
127
|
initialized = true
|
|
122
128
|
emit('ready', renderer)
|
|
123
129
|
animate()
|
|
@@ -277,6 +277,156 @@ export function createGradientMeshColorNode(uniforms: AmbientUniforms): any {
|
|
|
277
277
|
})()
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
+
export interface ThemeColorUniforms {
|
|
281
|
+
color1: any // TSL uniform node wrapping a THREE.Color
|
|
282
|
+
color2: any
|
|
283
|
+
color3: any
|
|
284
|
+
color4: any
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function createThemeGradientColorNode(
|
|
288
|
+
uniforms: AmbientUniforms,
|
|
289
|
+
colors: ThemeColorUniforms,
|
|
290
|
+
): any {
|
|
291
|
+
const { speed: uSpeed, intensity: uIntensity,
|
|
292
|
+
mouseX: uMouseX, mouseY: uMouseY, mouseStrength: uMouseStrength } = uniforms
|
|
293
|
+
|
|
294
|
+
return Fn(() => {
|
|
295
|
+
const t = mul(time, uSpeed, 0.2)
|
|
296
|
+
const uvCoord = uv()
|
|
297
|
+
|
|
298
|
+
// Gentle UV shift for p2-p4 area
|
|
299
|
+
const mouseOffset = vec2(
|
|
300
|
+
mul(sub(uMouseX, 0.5), uMouseStrength, 0.05),
|
|
301
|
+
mul(sub(uMouseY, 0.5), uMouseStrength, 0.05),
|
|
302
|
+
)
|
|
303
|
+
const adjustedUV = add(uvCoord, mouseOffset)
|
|
304
|
+
|
|
305
|
+
// p1 attracted toward mouse cursor
|
|
306
|
+
const p1Base = vec2(add(0.2, mul(sin(mul(t, 0.5)), 0.15)), add(0.3, mul(sin(add(mul(t, 0.4), 1.57)), 0.15)))
|
|
307
|
+
const mousePos = vec2(uMouseX, uMouseY)
|
|
308
|
+
const p1 = mix(p1Base, mousePos, mul(uMouseStrength, 0.5))
|
|
309
|
+
|
|
310
|
+
const p2 = vec2(add(0.8, mul(sin(add(mul(t, 0.6), 1.57)), 0.1)), add(0.2, mul(sin(mul(t, 0.5)), 0.1)))
|
|
311
|
+
const p3 = vec2(add(0.3, mul(sin(mul(t, 0.7)), 0.15)), add(0.8, mul(sin(add(mul(t, 0.3), 1.57)), 0.1)))
|
|
312
|
+
const p4 = vec2(add(0.7, mul(sin(add(mul(t, 0.4), 1.57)), 0.15)), add(0.7, mul(sin(mul(t, 0.6)), 0.15)))
|
|
313
|
+
|
|
314
|
+
const nm1 = simplexNoise2D(add(mul(adjustedUV, 3.0), mul(t, 0.3))).mul(0.5).add(0.5)
|
|
315
|
+
const nm2 = simplexNoise2D(add(mul(adjustedUV, 4.5), mul(t, -0.2), 8.0)).mul(0.5).add(0.5)
|
|
316
|
+
const nm3 = fbm2D(add(adjustedUV, mul(t, 0.1)), { octaves: 3, frequency: 2.0 }).mul(0.5).add(0.5)
|
|
317
|
+
const nm4 = simplexNoise2D(add(mul(adjustedUV, 7.0), mul(t, 0.5))).mul(0.5).add(0.5)
|
|
318
|
+
|
|
319
|
+
const d1 = mul(sub(1.0, smoothstep(0.0, 0.7, length(sub(adjustedUV, p1)))), add(0.7, mul(nm1, 0.3)))
|
|
320
|
+
const d2 = mul(sub(1.0, smoothstep(0.0, 0.7, length(sub(adjustedUV, p2)))), add(0.7, mul(nm2, 0.3)))
|
|
321
|
+
const d3 = mul(sub(1.0, smoothstep(0.0, 0.7, length(sub(adjustedUV, p3)))), add(0.7, mul(nm3, 0.3)))
|
|
322
|
+
const d4 = mul(sub(1.0, smoothstep(0.0, 0.7, length(sub(adjustedUV, p4)))), add(0.7, mul(nm4, 0.3)))
|
|
323
|
+
|
|
324
|
+
let colorNode = mul(colors.color1, d1)
|
|
325
|
+
colorNode = add(colorNode, mul(colors.color2, d2))
|
|
326
|
+
colorNode = add(colorNode, mul(colors.color3, d3))
|
|
327
|
+
colorNode = add(colorNode, mul(colors.color4, d4))
|
|
328
|
+
|
|
329
|
+
// Normalise first, then vignette, then intensity
|
|
330
|
+
const totalWeight = add(d1, d2, d3, d4, 0.01)
|
|
331
|
+
colorNode = colorNode.div(totalWeight)
|
|
332
|
+
|
|
333
|
+
const dist = length(sub(uvCoord, 0.5))
|
|
334
|
+
colorNode = mul(colorNode, sub(1.0, mul(dist, 0.25)))
|
|
335
|
+
colorNode = mul(colorNode, uIntensity)
|
|
336
|
+
|
|
337
|
+
return colorNode
|
|
338
|
+
})()
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export function createThemeFlowColorNode(
|
|
342
|
+
uniforms: AmbientUniforms,
|
|
343
|
+
colors: ThemeColorUniforms,
|
|
344
|
+
): any {
|
|
345
|
+
const { speed: uSpeed, intensity: uIntensity, mouseX: uMouseX, mouseY: uMouseY, mouseStrength: uMouseStrength } = uniforms
|
|
346
|
+
|
|
347
|
+
return Fn(() => {
|
|
348
|
+
const t = mul(time, uSpeed, 0.15)
|
|
349
|
+
const uvCoord = uv()
|
|
350
|
+
|
|
351
|
+
const mouseOffset = vec2(
|
|
352
|
+
mul(sub(uMouseX, 0.5), uMouseStrength, 0.3),
|
|
353
|
+
mul(sub(uMouseY, 0.5), uMouseStrength, 0.3),
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
const warpCoarse1 = simplexNoise2D(add(mul(uvCoord, 1.5), t))
|
|
357
|
+
const warpCoarse2 = simplexNoise2D(add(mul(uvCoord, 1.5), mul(t, -0.5), 7.0))
|
|
358
|
+
const warpedUV1 = add(uvCoord, mul(vec2(warpCoarse1, warpCoarse2), 0.25), mouseOffset)
|
|
359
|
+
|
|
360
|
+
const warpMed1 = simplexNoise2D(add(mul(warpedUV1, 3.0), mul(t, 0.7)))
|
|
361
|
+
const warpMed2 = simplexNoise2D(add(mul(warpedUV1, 3.0), mul(t, -0.3), 15.0))
|
|
362
|
+
const warpedUV2 = add(warpedUV1, mul(vec2(warpMed1, warpMed2), 0.12))
|
|
363
|
+
|
|
364
|
+
const warpFine1 = simplexNoise2D(add(mul(warpedUV2, 5.0), mul(t, 1.2)))
|
|
365
|
+
const warpFine2 = simplexNoise2D(add(mul(warpedUV2, 5.0), mul(t, -0.8), 25.0))
|
|
366
|
+
const warpedUV = add(warpedUV2, mul(vec2(warpFine1, warpFine2), 0.05))
|
|
367
|
+
|
|
368
|
+
const n1 = fbm2D(warpedUV, { octaves: 5, frequency: 2.0 }).mul(0.5).add(0.5)
|
|
369
|
+
const n2 = ridgedFbm2d(warpedUV, { octaves: 4, frequency: 1.5 })
|
|
370
|
+
|
|
371
|
+
let colorNode = mix(colors.color1, colors.color2, n1)
|
|
372
|
+
colorNode = mix(colorNode, colors.color3, mul(n2, 0.5))
|
|
373
|
+
|
|
374
|
+
const iridescence = mix(colors.color3, colors.color4, add(mul(n1, 0.6), mul(n2, 0.4)))
|
|
375
|
+
colorNode = mix(colorNode, iridescence, 0.3)
|
|
376
|
+
colorNode = mix(colorNode, colors.color4, mul(smoothstep(0.6, 0.9, n2), 0.25))
|
|
377
|
+
|
|
378
|
+
const brightness = add(0.85, mul(0.15, sin(add(mul(n1, 6.28), t))))
|
|
379
|
+
colorNode = mul(colorNode, brightness, uIntensity)
|
|
380
|
+
|
|
381
|
+
const dist = length(sub(uvCoord, 0.5))
|
|
382
|
+
colorNode = mul(colorNode, sub(1.0, mul(dist, 0.3)))
|
|
383
|
+
|
|
384
|
+
return colorNode
|
|
385
|
+
})()
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export function createThemeAuroraColorNode(
|
|
389
|
+
uniforms: AmbientUniforms,
|
|
390
|
+
colors: ThemeColorUniforms,
|
|
391
|
+
): any {
|
|
392
|
+
const { speed: uSpeed, intensity: uIntensity, mouseX: uMouseX, mouseY: uMouseY, mouseStrength: uMouseStrength } = uniforms
|
|
393
|
+
|
|
394
|
+
return Fn(() => {
|
|
395
|
+
const t = mul(time, uSpeed, 0.2)
|
|
396
|
+
const uvCoord = uv()
|
|
397
|
+
|
|
398
|
+
const mouseOffset = vec2(
|
|
399
|
+
mul(sub(uMouseX, 0.5), uMouseStrength),
|
|
400
|
+
mul(sub(uMouseY, 0.5), uMouseStrength),
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
const curtainCoord = add(
|
|
404
|
+
vec2(mul(uvCoord.x, 3.0), mul(uvCoord.y, 0.5)),
|
|
405
|
+
vec2(t, mul(t, 0.3)),
|
|
406
|
+
mouseOffset,
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
const curtain1 = simplexNoise2D(curtainCoord).mul(0.5).add(0.5)
|
|
410
|
+
const curtain2 = simplexNoise2D(add(mul(curtainCoord, 1.5), vec2(mul(t, -0.2), 5.0))).mul(0.5).add(0.5)
|
|
411
|
+
const curtain3 = simplexNoise2D(add(mul(curtainCoord, 0.7), vec2(mul(t, 0.4), 12.0))).mul(0.5).add(0.5)
|
|
412
|
+
const detail = fbm2D(curtainCoord, { octaves: 4, frequency: 2.0 }).mul(0.5).add(0.5)
|
|
413
|
+
const curtain = mul(add(mul(curtain1, 0.4), mul(curtain2, 0.25), mul(curtain3, 0.2), mul(detail, 0.15)), 1.0)
|
|
414
|
+
|
|
415
|
+
const fade = mul(pow(sub(1.0, uvCoord.y), 1.2), smoothstep(0.0, 0.3, uvCoord.y))
|
|
416
|
+
const aurora = mul(smoothstep(0.2, 0.8, mul(curtain, fade)), uIntensity)
|
|
417
|
+
|
|
418
|
+
const shimmer = simplexNoise2D(add(mul(curtainCoord, 8.0), mul(t, 3.0))).mul(0.5).add(0.5)
|
|
419
|
+
|
|
420
|
+
const colorDriver = add(mul(curtain2, 0.6), mul(curtain3, 0.4))
|
|
421
|
+
const auroraColor = mix(colors.color1, colors.color2, colorDriver)
|
|
422
|
+
|
|
423
|
+
const skyColor = mul(colors.color1, 0.04)
|
|
424
|
+
const shimmerColor = mul(colors.color3, mul(shimmer, mul(aurora, 0.15)))
|
|
425
|
+
|
|
426
|
+
return add(mix(skyColor, auroraColor, aurora), shimmerColor)
|
|
427
|
+
})()
|
|
428
|
+
}
|
|
429
|
+
|
|
280
430
|
export function createOceanColorNode(uniforms: AmbientUniforms): any {
|
|
281
431
|
const { speed: uSpeed, intensity: uIntensity, mouseX: uMouseX, mouseY: uMouseY, mouseStrength: uMouseStrength } = uniforms
|
|
282
432
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { parseOKLCH, oklchToColor } from '../utils/tsl/oklch'
|
|
2
|
+
|
|
3
|
+
export function useThemeColors() {
|
|
4
|
+
const { activeAccent } = useAccentColor()
|
|
5
|
+
const colorMode = useColorMode()
|
|
6
|
+
|
|
7
|
+
const primaryHex = ref('#8b5cf6')
|
|
8
|
+
const secondaryHex = ref('#6366f1')
|
|
9
|
+
const infoHex = ref('#38bdf8')
|
|
10
|
+
const primaryLightHex = ref('#a78bfa')
|
|
11
|
+
|
|
12
|
+
const clearColor = computed(() =>
|
|
13
|
+
colorMode.value === 'dark' ? '#0a0a0a' : '#f8f8f8'
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
function cssVarToHex(varName: string): string {
|
|
17
|
+
const raw = getComputedStyle(document.documentElement)
|
|
18
|
+
.getPropertyValue(varName).trim()
|
|
19
|
+
try {
|
|
20
|
+
const [l, c, h] = parseOKLCH(raw)
|
|
21
|
+
return `#${oklchToColor(l, c, h).getHexString()}`
|
|
22
|
+
} catch {
|
|
23
|
+
return '#888888'
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
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
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (import.meta.client) {
|
|
38
|
+
watch(activeAccent, refresh, { immediate: true })
|
|
39
|
+
watch(() => colorMode.value, refresh)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return { primaryHex, secondaryHex, infoHex, primaryLightHex, clearColor, refresh }
|
|
43
|
+
}
|
|
@@ -23,15 +23,21 @@ export function oklchToLinearSRGB(l: TSLNode, c: TSLNode, h: TSLNode): TSLNode {
|
|
|
23
23
|
// OKLab -> linear sRGB (approximate matrix transform)
|
|
24
24
|
const l_ = l.add(mul(a, float(0.3963377774))).add(mul(b, float(0.2158037573)))
|
|
25
25
|
const m_ = l.sub(mul(a, float(0.1055613458))).sub(mul(b, float(0.0638541728)))
|
|
26
|
-
const s_ = l.sub(mul(a, float(0.0894841775))).sub(mul(b, float(1.
|
|
26
|
+
const s_ = l.sub(mul(a, float(0.0894841775))).sub(mul(b, float(1.291485548)))
|
|
27
27
|
|
|
28
28
|
const lCubed = pow(l_, 3)
|
|
29
29
|
const mCubed = pow(m_, 3)
|
|
30
30
|
const sCubed = pow(s_, 3)
|
|
31
31
|
|
|
32
|
-
const r = mul(lCubed, float(4.0767416621))
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
const r = mul(lCubed, float(4.0767416621))
|
|
33
|
+
.sub(mul(mCubed, float(3.3077115913)))
|
|
34
|
+
.add(mul(sCubed, float(0.2309699292)))
|
|
35
|
+
const g = mul(lCubed, float(-1.2684380046))
|
|
36
|
+
.add(mul(mCubed, float(2.6097574011)))
|
|
37
|
+
.sub(mul(sCubed, float(0.3413193965)))
|
|
38
|
+
const bOut = mul(lCubed, float(-0.0041960863))
|
|
39
|
+
.sub(mul(mCubed, float(0.7034186147)))
|
|
40
|
+
.add(mul(sCubed, float(1.707614701)))
|
|
35
41
|
|
|
36
42
|
return vec3(r, g, bOut)
|
|
37
43
|
}
|
|
@@ -70,7 +76,7 @@ export function oklchToColor(l: number, c: number, h: number): Color {
|
|
|
70
76
|
// OKLab to linear sRGB (approximate)
|
|
71
77
|
const l_ = l + a * 0.3963377774 + b * 0.2158037573
|
|
72
78
|
const m_ = l - a * 0.1055613458 - b * 0.0638541728
|
|
73
|
-
const s_ = l - a * 0.0894841775 - b * 1.
|
|
79
|
+
const s_ = l - a * 0.0894841775 - b * 1.291485548
|
|
74
80
|
|
|
75
81
|
const lCubed = l_ * l_ * l_
|
|
76
82
|
const mCubed = m_ * m_ * m_
|
|
@@ -78,7 +84,7 @@ export function oklchToColor(l: number, c: number, h: number): Color {
|
|
|
78
84
|
|
|
79
85
|
const r = lCubed * 4.0767416621 - mCubed * 3.3077115913 + sCubed * 0.2309699292
|
|
80
86
|
const g = lCubed * -1.2684380046 + mCubed * 2.6097574011 - sCubed * 0.3413193965
|
|
81
|
-
const bOut = lCubed * -0.0041960863 - mCubed * 0.7034186147 + sCubed * 1.
|
|
87
|
+
const bOut = lCubed * -0.0041960863 - mCubed * 0.7034186147 + sCubed * 1.707614701
|
|
82
88
|
|
|
83
89
|
// Clamp to [0,1]
|
|
84
90
|
const color = new Color()
|
|
@@ -1,47 +1,52 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Theme Layer CSS
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* The
|
|
6
|
-
*
|
|
4
|
+
* Accessibility attributes are applied to <html> by the individual composables.
|
|
5
|
+
* The dark mode @custom-variant lives in core/main.css so it is defined in the
|
|
6
|
+
* same Tailwind pass as all utility generation (avoids HMR regeneration shift).
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
/* --- Reduced Motion --- */
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
[data-theme-motion="reduced"] *,
|
|
11
|
+
[data-theme-motion="reduced"] *::before,
|
|
12
|
+
[data-theme-motion="reduced"] *::after {
|
|
13
13
|
animation-duration: 0.01ms !important;
|
|
14
14
|
animation-iteration-count: 1 !important;
|
|
15
15
|
transition-duration: 0.01ms !important;
|
|
16
16
|
scroll-behavior: auto !important;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
[data-theme-motion="reduced"] {
|
|
20
|
+
--duration-base: 0ms;
|
|
21
|
+
}
|
|
22
|
+
|
|
19
23
|
/* --- Reduced Transparency --- */
|
|
20
|
-
|
|
21
|
-
.reduce-transparency {
|
|
24
|
+
[data-theme-transparency="reduced"] {
|
|
22
25
|
--ui-bg-elevated: var(--ui-bg);
|
|
26
|
+
--opacity-glass: 1;
|
|
27
|
+
--backdrop-blur: 0px;
|
|
23
28
|
}
|
|
24
29
|
|
|
25
|
-
|
|
30
|
+
[data-theme-transparency="reduced"] :where([class*='backdrop-blur']) {
|
|
26
31
|
backdrop-filter: none !important;
|
|
27
32
|
-webkit-backdrop-filter: none !important;
|
|
28
33
|
}
|
|
29
34
|
|
|
30
|
-
|
|
35
|
+
[data-theme-transparency="reduced"] :where([class*='bg-default/'], [class*='bg-elevated/']) {
|
|
31
36
|
background-color: var(--ui-bg-elevated) !important;
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
/* --- High Contrast --- */
|
|
35
|
-
|
|
40
|
+
[data-theme-contrast="high"] {
|
|
36
41
|
--ui-border: var(--ui-border-accented);
|
|
37
42
|
}
|
|
38
43
|
|
|
39
|
-
|
|
44
|
+
[data-theme-contrast="high"] :where(button, a, [role='button']) {
|
|
40
45
|
outline-width: 2px;
|
|
41
46
|
}
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
|
|
48
|
+
[data-theme-contrast="high"] .text-muted,
|
|
49
|
+
[data-theme-contrast="high"] .text-dimmed {
|
|
45
50
|
opacity: 1;
|
|
46
51
|
filter: contrast(1.25);
|
|
47
52
|
}
|
|
@@ -40,8 +40,8 @@ const bgStyle = computed(() => ({
|
|
|
40
40
|
class="size-8 rounded-full ring-1 ring-offset-2 transition-shadow duration-200"
|
|
41
41
|
:class="[
|
|
42
42
|
active
|
|
43
|
-
? 'ring-2 ring-primary ring-offset-
|
|
44
|
-
: 'ring-transparent hover:ring-muted ring-offset-
|
|
43
|
+
? 'ring-2 ring-primary ring-offset-bg shadow-lg'
|
|
44
|
+
: 'ring-transparent hover:ring-muted ring-offset-bg hover:shadow-md',
|
|
45
45
|
]"
|
|
46
46
|
:style="bgStyle"
|
|
47
47
|
:aria-label="`Set accent color to ${color}`"
|
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
import type { AccentColor } from '#layers/theme/app/types/theme'
|
|
3
3
|
|
|
4
4
|
const appConfig = useAppConfig()
|
|
5
|
-
const { activeAccent, setAccent } =
|
|
5
|
+
const { activeAccent, setAccent } = useAccentColor()
|
|
6
6
|
|
|
7
|
-
const accents = computed(
|
|
8
|
-
() => (appConfig as any).themeLayer?.accents as AccentColor[] ?? [],
|
|
9
|
-
)
|
|
7
|
+
const accents = computed(() => ((appConfig as any).themeLayer?.accents as AccentColor[]) ?? [])
|
|
10
8
|
</script>
|
|
11
9
|
|
|
12
10
|
<template>
|
|
@@ -9,7 +9,7 @@ const {
|
|
|
9
9
|
effectiveHighContrast,
|
|
10
10
|
effectiveReducedMotion,
|
|
11
11
|
effectiveReducedTransparency,
|
|
12
|
-
} =
|
|
12
|
+
} = useTheme()
|
|
13
13
|
|
|
14
14
|
const contrastModel = computed({
|
|
15
15
|
get: () => effectiveHighContrast.value,
|
|
@@ -50,10 +50,7 @@ const transparencyModel = computed({
|
|
|
50
50
|
<li class="flex w-full flex-row items-center justify-between py-1">
|
|
51
51
|
<div>
|
|
52
52
|
<h2 class="text-lg font-semibold">High Contrast</h2>
|
|
53
|
-
<p
|
|
54
|
-
v-if="contrastOverride === 'system'"
|
|
55
|
-
class="text-sm text-muted"
|
|
56
|
-
>
|
|
53
|
+
<p v-if="contrastOverride === 'system'" class="text-sm text-muted">
|
|
57
54
|
Following system
|
|
58
55
|
</p>
|
|
59
56
|
</div>
|
|
@@ -68,10 +65,7 @@ const transparencyModel = computed({
|
|
|
68
65
|
<li class="flex w-full flex-row items-center justify-between py-1">
|
|
69
66
|
<div>
|
|
70
67
|
<h2 class="text-lg font-semibold">Reduce Motion</h2>
|
|
71
|
-
<p
|
|
72
|
-
v-if="motionOverride === 'system'"
|
|
73
|
-
class="text-sm text-muted"
|
|
74
|
-
>
|
|
68
|
+
<p v-if="motionOverride === 'system'" class="text-sm text-muted">
|
|
75
69
|
Following system
|
|
76
70
|
</p>
|
|
77
71
|
</div>
|
|
@@ -86,10 +80,7 @@ const transparencyModel = computed({
|
|
|
86
80
|
<li class="flex w-full flex-row items-center justify-between py-1">
|
|
87
81
|
<div>
|
|
88
82
|
<h2 class="text-lg font-semibold">Reduce Transparency</h2>
|
|
89
|
-
<p
|
|
90
|
-
v-if="transparencyOverride === 'system'"
|
|
91
|
-
class="text-sm text-muted"
|
|
92
|
-
>
|
|
83
|
+
<p v-if="transparencyOverride === 'system'" class="text-sm text-muted">
|
|
93
84
|
Following system
|
|
94
85
|
</p>
|
|
95
86
|
</div>
|