kmcom-nuxt-layers 1.6.40 → 1.6.43

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 (45) hide show
  1. package/layers/core/app/composables/useErrorLog.ts +6 -11
  2. package/layers/core/app/composables/useLoading.ts +15 -46
  3. package/layers/core/app/composables/useScrollGuard.ts +115 -129
  4. package/layers/forms/app/components/Form/Field.vue +2 -2
  5. package/layers/forms/app/composables/useFormSchema.ts +1 -3
  6. package/layers/forms/app/config/fields.ts +12 -6
  7. package/layers/forms/app/types/fields.ts +2 -20
  8. package/layers/forms/nuxt.config.ts +0 -10
  9. package/layers/motion/app/components/Motion/CountUp.vue +39 -0
  10. package/layers/motion/app/components/Motion/Cursor.vue +101 -0
  11. package/layers/motion/app/components/Motion/Magnetic.vue +22 -0
  12. package/layers/motion/app/components/Motion/Tilt.vue +22 -0
  13. package/layers/motion/app/components/Motion/VelocityEffect.vue +33 -71
  14. package/layers/motion/app/composables/useCountUp.ts +61 -0
  15. package/layers/motion/app/composables/useCursorFollower.ts +25 -0
  16. package/layers/motion/app/composables/useMagneticElement.ts +56 -0
  17. package/layers/motion/app/composables/useSmoothScroll.ts +11 -0
  18. package/layers/motion/app/composables/useTiltEffect.ts +58 -0
  19. package/layers/motion/app/plugins/locomotive-scroll.client.ts +1 -1
  20. package/layers/motion/app/types/app-config.d.ts +11 -2
  21. package/layers/routing/app/middleware/02.governance.global.ts +7 -18
  22. package/layers/routing/app/types/routing.ts +10 -0
  23. package/layers/routing/app/utils/resolveRoute.ts +31 -0
  24. package/layers/shader/package.json +2 -1
  25. package/layers/theme/app/composables/useAccentColor.ts +1 -12
  26. package/layers/theme/app/composables/useThemeContrast.ts +0 -11
  27. package/layers/theme/app/composables/useThemeMotion.ts +0 -11
  28. package/layers/theme/app/composables/useThemeTransparency.ts +0 -14
  29. package/layers/theme/app/plugins/theme.client.ts +20 -3
  30. package/layers/theme/app/types/app-config.d.ts +2 -2
  31. package/layers/ui/app/components/Base/Modal.vue +0 -1
  32. package/layers/ui/app/composables/color.ts +1 -32
  33. package/layers/ui/app/utils/createModal.ts +11 -2
  34. package/package.json +28 -26
  35. package/layers/content/app/.DS_Store +0 -0
  36. package/layers/content/app/pages/blog/[slug].vue +0 -19
  37. package/layers/content/app/pages/blog/index.vue +0 -22
  38. package/layers/core/app/.DS_Store +0 -0
  39. package/layers/core/app/assets/.DS_Store +0 -0
  40. package/layers/forms/app/.DS_Store +0 -0
  41. package/layers/layout/app/.DS_Store +0 -0
  42. package/layers/motion/app/.DS_Store +0 -0
  43. package/layers/shader/app/.DS_Store +0 -0
  44. package/layers/theme/app/.DS_Store +0 -0
  45. package/layers/ui/app/.DS_Store +0 -0
@@ -0,0 +1,22 @@
1
+ <script setup lang="ts">
2
+ const {
3
+ strength = 0.3,
4
+ radius = 100,
5
+ damping = 30,
6
+ stiffness = 300,
7
+ } = defineProps<{
8
+ strength?: number
9
+ radius?: number
10
+ damping?: number
11
+ stiffness?: number
12
+ }>()
13
+
14
+ const el = ref<HTMLElement | null>(null)
15
+ const { isActive } = useMagneticElement(el, { strength, radius, damping, stiffness })
16
+ </script>
17
+
18
+ <template>
19
+ <div ref="el" class="will-change-transform">
20
+ <slot :is-active="isActive" />
21
+ </div>
22
+ </template>
@@ -0,0 +1,22 @@
1
+ <script setup lang="ts">
2
+ const {
3
+ maxTilt = 15,
4
+ perspective = 800,
5
+ damping = 40,
6
+ stiffness = 250,
7
+ } = defineProps<{
8
+ maxTilt?: number
9
+ perspective?: number
10
+ damping?: number
11
+ stiffness?: number
12
+ }>()
13
+
14
+ const el = ref<HTMLElement | null>(null)
15
+ useTiltEffect(el, { maxTilt, perspective, damping, stiffness })
16
+ </script>
17
+
18
+ <template>
19
+ <div ref="el" class="will-change-transform" style="transform-style: preserve-3d">
20
+ <slot />
21
+ </div>
22
+ </template>
@@ -9,78 +9,43 @@ type EffectType =
9
9
  | 'translateY'
10
10
  | 'hueRotate'
11
11
 
12
- const props = withDefaults(
13
- defineProps<{
14
- /**
15
- * Type of velocity effect to apply
16
- */
17
- effect: EffectType
18
- /**
19
- * Intensity multiplier (default varies by effect type)
20
- */
21
- intensity?: number
22
- /**
23
- * Whether to use smoothed velocity (recommended)
24
- */
25
- smooth?: boolean
26
- /**
27
- * Lerp factor for smoothing (0.1 = very smooth, 0.3 = responsive)
28
- */
29
- smoothFactor?: number
30
- /**
31
- * Tag to render as
32
- */
33
- as?: string
34
- }>(),
35
- {
36
- smooth: true,
37
- smoothFactor: 0.15,
38
- as: 'div',
39
- }
40
- )
41
-
12
+ const {
13
+ effect,
14
+ intensity = undefined,
15
+ damping = 15,
16
+ stiffness = 150,
17
+ as = 'div',
18
+ } = defineProps<{
19
+ effect: EffectType
20
+ intensity?: number
21
+ damping?: number
22
+ stiffness?: number
23
+ as?: string
24
+ }>()
25
+
26
+ const { gsap } = useGsap()
42
27
  const { velocity } = useSmoothScroll()
43
28
 
44
- // Smoothed velocity for stable animations
45
29
  const smoothVelocity = ref(0)
46
30
  const smoothAbsVelocity = ref(0)
47
- let animationFrame: number | null = null
48
31
 
49
- function lerp(start: number, end: number, factor: number): number {
50
- return start + (end - start) * factor
51
- }
32
+ function tick() {
33
+ const d = damping / 1000
34
+ const s = stiffness / 1000
52
35
 
53
- function updateSmoothedValues() {
54
- smoothVelocity.value = lerp(smoothVelocity.value, velocity.value, props.smoothFactor)
55
- smoothAbsVelocity.value = lerp(
56
- smoothAbsVelocity.value,
57
- Math.abs(velocity.value),
58
- props.smoothFactor
59
- )
36
+ smoothVelocity.value += (velocity.value - smoothVelocity.value) * s
37
+ smoothVelocity.value *= 1 - d
38
+
39
+ smoothAbsVelocity.value += (Math.abs(velocity.value) - smoothAbsVelocity.value) * s
40
+ smoothAbsVelocity.value *= 1 - d
60
41
 
61
42
  if (Math.abs(smoothVelocity.value) < 0.01) smoothVelocity.value = 0
62
43
  if (smoothAbsVelocity.value < 0.01) smoothAbsVelocity.value = 0
63
-
64
- animationFrame = requestAnimationFrame(updateSmoothedValues)
65
44
  }
66
45
 
67
- onMounted(() => {
68
- if (props.smooth) {
69
- animationFrame = requestAnimationFrame(updateSmoothedValues)
70
- }
71
- })
72
-
73
- onUnmounted(() => {
74
- if (animationFrame) cancelAnimationFrame(animationFrame)
75
- })
76
-
77
- // Get the active velocity value
78
- const activeVelocity = computed(() => (props.smooth ? smoothVelocity.value : velocity.value))
79
- const activeAbsVelocity = computed(() =>
80
- props.smooth ? smoothAbsVelocity.value : Math.abs(velocity.value)
81
- )
46
+ onMounted(() => gsap.ticker.add(tick))
47
+ onUnmounted(() => gsap.ticker.remove(tick))
82
48
 
83
- // Default intensities per effect type
84
49
  const defaultIntensities: Record<EffectType, number> = {
85
50
  skew: 3,
86
51
  scale: 0.12,
@@ -92,15 +57,14 @@ const defaultIntensities: Record<EffectType, number> = {
92
57
  hueRotate: 45,
93
58
  }
94
59
 
95
- const intensity = computed(() => props.intensity ?? defaultIntensities[props.effect])
60
+ const activeIntensity = computed(() => intensity ?? defaultIntensities[effect])
96
61
 
97
- // Compute the style based on effect type
98
62
  const effectStyle = computed(() => {
99
- const vel = activeVelocity.value
100
- const absVel = activeAbsVelocity.value
101
- const int = intensity.value
63
+ const vel = smoothVelocity.value
64
+ const absVel = smoothAbsVelocity.value
65
+ const int = activeIntensity.value
102
66
 
103
- switch (props.effect) {
67
+ switch (effect) {
104
68
  case 'skew':
105
69
  return { transform: `skewX(${Math.max(-15, Math.min(15, vel * int))}deg)` }
106
70
  case 'scale':
@@ -122,13 +86,11 @@ const effectStyle = computed(() => {
122
86
  }
123
87
  })
124
88
 
125
- // Classes for performance optimization
126
89
  const effectClasses = computed(() => {
127
- const classes = []
128
- if (['skew', 'scale', 'rotate', 'translateY'].includes(props.effect)) {
129
- classes.push('will-change-transform')
90
+ if (['skew', 'scale', 'rotate', 'translateY'].includes(effect)) {
91
+ return ['will-change-transform']
130
92
  }
131
- return classes
93
+ return []
132
94
  })
133
95
  </script>
134
96
 
@@ -0,0 +1,61 @@
1
+ import type { MaybeRefOrGetter } from 'vue'
2
+ import type { ScrollTrigger as GSAPScrollTrigger } from 'gsap/ScrollTrigger'
3
+
4
+ interface UseCountUpOptions {
5
+ to: number
6
+ from?: number
7
+ duration?: number
8
+ ease?: string
9
+ format?: (n: number) => string
10
+ once?: boolean
11
+ }
12
+
13
+ export function useCountUp(elementRef: MaybeRefOrGetter<HTMLElement | null>, opts: UseCountUpOptions) {
14
+ const { gsap, ScrollTrigger } = useGsap()
15
+
16
+ const displayValue = ref(String(opts.from ?? 0))
17
+ const isComplete = ref(false)
18
+
19
+ const format = opts.format ?? ((n: number) => n.toLocaleString())
20
+ const counter = { value: opts.from ?? 0 }
21
+
22
+ let scrollTriggerInstance: GSAPScrollTrigger | null = null
23
+ let tween: gsap.core.Tween | null = null
24
+
25
+ function startCount() {
26
+ tween = gsap.to(counter, {
27
+ value: opts.to,
28
+ duration: opts.duration ?? 2,
29
+ ease: opts.ease ?? 'power2.out',
30
+ onUpdate: () => {
31
+ displayValue.value = format(Math.round(counter.value))
32
+ },
33
+ onComplete: () => {
34
+ isComplete.value = true
35
+ displayValue.value = format(opts.to)
36
+ },
37
+ })
38
+ }
39
+
40
+ onMounted(async () => {
41
+ await nextTick()
42
+ displayValue.value = format(opts.from ?? 0)
43
+
44
+ const el = toValue(elementRef)
45
+ if (!el) return
46
+
47
+ scrollTriggerInstance = ScrollTrigger.create({
48
+ trigger: el,
49
+ start: 'top 80%',
50
+ once: opts.once ?? true,
51
+ onEnter: startCount,
52
+ })
53
+ })
54
+
55
+ onUnmounted(() => {
56
+ tween?.kill()
57
+ scrollTriggerInstance?.kill()
58
+ })
59
+
60
+ return { displayValue, isComplete }
61
+ }
@@ -0,0 +1,25 @@
1
+ import type { MaybeRef } from 'vue'
2
+
3
+ export function useCursorFollower(opts: { smoothing?: MaybeRef<number> } = {}) {
4
+ const { gsap } = useGsap()
5
+ const { x: mouseX, y: mouseY } = useMouse({ type: 'client' })
6
+
7
+ const x = ref(0)
8
+ const y = ref(0)
9
+
10
+ function tick() {
11
+ const factor = unref(opts.smoothing) ?? 0.15
12
+ x.value += (mouseX.value - x.value) * factor
13
+ y.value += (mouseY.value - y.value) * factor
14
+ }
15
+
16
+ onMounted(() => {
17
+ x.value = mouseX.value
18
+ y.value = mouseY.value
19
+ gsap.ticker.add(tick)
20
+ })
21
+
22
+ onUnmounted(() => gsap.ticker.remove(tick))
23
+
24
+ return { x, y }
25
+ }
@@ -0,0 +1,56 @@
1
+ export function useMagneticElement(
2
+ elementRef: Ref<HTMLElement | null>,
3
+ opts: {
4
+ strength?: number
5
+ radius?: number
6
+ damping?: number
7
+ stiffness?: number
8
+ } = {},
9
+ ) {
10
+ const { gsap } = useGsap()
11
+ const { elementX, elementY, elementWidth, elementHeight } = useMouseInElement(elementRef)
12
+
13
+ const currentX = ref(0)
14
+ const currentY = ref(0)
15
+ const isActive = ref(false)
16
+
17
+ function tick() {
18
+ const strength = opts.strength ?? 0.3
19
+ const radius = opts.radius ?? 100
20
+ const damping = (opts.damping ?? 30) / 1000
21
+ const stiffness = (opts.stiffness ?? 300) / 1000
22
+
23
+ const dx = elementX.value - elementWidth.value / 2
24
+ const dy = elementY.value - elementHeight.value / 2
25
+ const dist = Math.sqrt(dx * dx + dy * dy)
26
+
27
+ let targetX = 0
28
+ let targetY = 0
29
+
30
+ if (dist < radius) {
31
+ isActive.value = true
32
+ targetX = dx * strength
33
+ targetY = dy * strength
34
+ } else {
35
+ isActive.value = false
36
+ }
37
+
38
+ currentX.value += (targetX - currentX.value) * stiffness
39
+ currentX.value *= 1 - damping
40
+ currentY.value += (targetY - currentY.value) * stiffness
41
+ currentY.value *= 1 - damping
42
+
43
+ if (elementRef.value) {
44
+ gsap.set(elementRef.value, { x: currentX.value, y: currentY.value })
45
+ }
46
+ }
47
+
48
+ onMounted(() => gsap.ticker.add(tick))
49
+
50
+ onUnmounted(() => {
51
+ gsap.ticker.remove(tick)
52
+ if (elementRef.value) gsap.set(elementRef.value, { x: 0, y: 0 })
53
+ })
54
+
55
+ return { isActive }
56
+ }
@@ -23,12 +23,19 @@ interface ScrollToOptions {
23
23
  */
24
24
  export function useSmoothScroll() {
25
25
  const nuxtApp = useNuxtApp()
26
+ const appConfig = useAppConfig()
26
27
 
27
28
  // Get the Locomotive Scroll instance from the reactive ref provided by the plugin
28
29
  const locomotiveScroll = computed<LocomotiveScroll | undefined>(
29
30
  () => (nuxtApp.$locomotiveScroll as Ref<LocomotiveScroll | null>)?.value ?? undefined
30
31
  )
31
32
 
33
+ // true if smooth scroll is configured to run (globally or per-route)
34
+ const isEnabled = computed(() => (appConfig.motion?.smoothScroll ?? true) !== false)
35
+
36
+ // true once the LocomotiveScroll instance is actually initialised on this route
37
+ const isReady = computed(() => locomotiveScroll.value != null)
38
+
32
39
  // Reactive scroll state from the plugin
33
40
  const scrollState = computed(
34
41
  () =>
@@ -110,6 +117,10 @@ export function useSmoothScroll() {
110
117
  // Locomotive Scroll instance
111
118
  locomotiveScroll,
112
119
 
120
+ // Scroll availability
121
+ isEnabled,
122
+ isReady,
123
+
113
124
  // Reactive scroll state
114
125
  scrollY,
115
126
  velocity,
@@ -0,0 +1,58 @@
1
+ export function useTiltEffect(
2
+ elementRef: Ref<HTMLElement | null>,
3
+ opts: {
4
+ maxTilt?: number
5
+ perspective?: number
6
+ damping?: number
7
+ stiffness?: number
8
+ } = {},
9
+ ) {
10
+ const { gsap } = useGsap()
11
+ const { elementX, elementY, elementWidth, elementHeight, isOutside } =
12
+ useMouseInElement(elementRef)
13
+
14
+ const currentRotateX = ref(0)
15
+ const currentRotateY = ref(0)
16
+
17
+ function tick() {
18
+ const maxTilt = opts.maxTilt ?? 15
19
+ const perspective = opts.perspective ?? 800
20
+ const damping = (opts.damping ?? 40) / 1000
21
+ const stiffness = (opts.stiffness ?? 250) / 1000
22
+
23
+ const width = elementWidth.value || 1
24
+ const height = elementHeight.value || 1
25
+
26
+ let targetRotateX = 0
27
+ let targetRotateY = 0
28
+
29
+ if (!isOutside.value) {
30
+ const nx = (elementX.value / width) * 2 - 1
31
+ const ny = (elementY.value / height) * 2 - 1
32
+ targetRotateY = nx * maxTilt
33
+ targetRotateX = -ny * maxTilt
34
+ }
35
+
36
+ currentRotateX.value += (targetRotateX - currentRotateX.value) * stiffness
37
+ currentRotateX.value *= 1 - damping
38
+ currentRotateY.value += (targetRotateY - currentRotateY.value) * stiffness
39
+ currentRotateY.value *= 1 - damping
40
+
41
+ if (elementRef.value) {
42
+ gsap.set(elementRef.value, {
43
+ rotateX: currentRotateX.value,
44
+ rotateY: currentRotateY.value,
45
+ transformPerspective: perspective,
46
+ })
47
+ }
48
+ }
49
+
50
+ onMounted(() => gsap.ticker.add(tick))
51
+
52
+ onUnmounted(() => {
53
+ gsap.ticker.remove(tick)
54
+ if (elementRef.value) gsap.set(elementRef.value, { rotateX: 0, rotateY: 0 })
55
+ })
56
+
57
+ return { rotateX: currentRotateX, rotateY: currentRotateY }
58
+ }
@@ -45,7 +45,7 @@ export default defineNuxtPlugin(() => {
45
45
  }
46
46
 
47
47
  const router = useRouter()
48
- const appConfig = useAppConfig() as any
48
+ const appConfig = useAppConfig()
49
49
  const smoothScroll: boolean | string[] = appConfig.motion?.smoothScroll ?? true
50
50
 
51
51
  if (smoothScroll === true) {
@@ -1,7 +1,16 @@
1
1
  declare module '@nuxt/schema' {
2
2
  interface AppConfigInput {
3
- motionLayer?: {
4
- name?: string
3
+ motion?: {
4
+ smoothScroll?: boolean | string[]
5
+ gsapScrollTrigger?: boolean
6
+ lenis?: {
7
+ duration?: number
8
+ orientation?: 'vertical' | 'horizontal'
9
+ gestureOrientation?: 'vertical' | 'horizontal'
10
+ smoothWheel?: boolean
11
+ smoothTouch?: boolean
12
+ touchMultiplier?: number
13
+ }
5
14
  }
6
15
  }
7
16
  }
@@ -1,27 +1,16 @@
1
1
  import { useFeatures } from '../composables/useFeatures'
2
+ import { resolveRoute } from '../utils/resolveRoute'
2
3
 
3
4
  export default defineNuxtRouteMiddleware((to) => {
4
- const { config, isStrictMode, isLayerDefaultDeny } = useRoutingConfig()
5
+ const { config } = useRoutingConfig()
5
6
  const { resolve } = useFeatures()
6
- const meta = to.meta
7
7
 
8
- if (config.debug) console.log('[routing] governance check', to.path, meta)
8
+ if (config.debug) console.log('[routing] governance check', to.path, to.meta)
9
9
 
10
- // strict default-deny: every route must declare a feature
11
- if (isStrictMode() && !meta.feature) {
12
- throw createError({ statusCode: 404 })
13
- }
10
+ const resolution = resolveRoute(to.meta, config, resolve)
14
11
 
15
- // layer default-deny: layer routes must declare a feature
16
- if (isLayerDefaultDeny() && meta.__fromLayer && !meta.feature) {
17
- throw createError({ statusCode: 404 })
18
- }
12
+ if (config.debug) console.log('[routing] resolution', resolution)
19
13
 
20
- if (!meta.feature) return
21
-
22
- const variant = resolve(meta.feature)
23
- if (config.debug) console.log(`[routing] feature "${meta.feature}" resolved to "${variant}"`)
24
-
25
- if (variant === 'disabled') throw createError({ statusCode: 404 })
26
- if (variant === 'beta' || variant === 'coming-soon') return navigateTo('/coming-soon')
14
+ if (resolution.outcome === 'deny') throw createError({ statusCode: 404 })
15
+ if (resolution.outcome === 'redirect') return navigateTo(resolution.to)
27
16
  })
@@ -1,10 +1,16 @@
1
1
  export type FeatureValue = 'enabled' | 'disabled' | 'beta' | 'coming-soon'
2
2
  export type RoutingPreset = 'simple' | 'marketing' | 'product' | 'enterprise'
3
3
 
4
+ export type RouteResolution =
5
+ | { outcome: 'allow' }
6
+ | { outcome: 'deny' }
7
+ | { outcome: 'redirect'; to: string }
8
+
4
9
  export interface RoutingLayerConfig {
5
10
  preset: RoutingPreset
6
11
  strictDefaultDeny: boolean
7
12
  layerDefaultDeny: boolean
13
+ betaRedirect: string
8
14
  runtimeFlags: boolean
9
15
  debug: boolean
10
16
  maintenance: { enabled: boolean; allowRoutes: string[] }
@@ -16,6 +22,7 @@ export const ROUTING_PRESETS: Record<RoutingPreset, Omit<RoutingLayerConfig, 'pr
16
22
  simple: {
17
23
  strictDefaultDeny: false,
18
24
  layerDefaultDeny: false,
25
+ betaRedirect: '/coming-soon',
19
26
  runtimeFlags: false,
20
27
  debug: false,
21
28
  maintenance: { enabled: false, allowRoutes: ['/maintenance'] },
@@ -24,6 +31,7 @@ export const ROUTING_PRESETS: Record<RoutingPreset, Omit<RoutingLayerConfig, 'pr
24
31
  marketing: {
25
32
  strictDefaultDeny: false,
26
33
  layerDefaultDeny: true,
34
+ betaRedirect: '/coming-soon',
27
35
  runtimeFlags: false,
28
36
  debug: false,
29
37
  maintenance: { enabled: true, allowRoutes: ['/maintenance'] },
@@ -32,6 +40,7 @@ export const ROUTING_PRESETS: Record<RoutingPreset, Omit<RoutingLayerConfig, 'pr
32
40
  product: {
33
41
  strictDefaultDeny: false,
34
42
  layerDefaultDeny: true,
43
+ betaRedirect: '/coming-soon',
35
44
  runtimeFlags: true,
36
45
  debug: false,
37
46
  maintenance: { enabled: true, allowRoutes: ['/maintenance'] },
@@ -40,6 +49,7 @@ export const ROUTING_PRESETS: Record<RoutingPreset, Omit<RoutingLayerConfig, 'pr
40
49
  enterprise: {
41
50
  strictDefaultDeny: true,
42
51
  layerDefaultDeny: true,
52
+ betaRedirect: '/coming-soon',
43
53
  runtimeFlags: true,
44
54
  debug: false,
45
55
  maintenance: { enabled: true, allowRoutes: ['/maintenance'] },
@@ -0,0 +1,31 @@
1
+ import type { FeatureValue, RouteResolution, RoutingLayerConfig } from '../types/routing'
2
+
3
+ interface RouteMeta {
4
+ feature?: string
5
+ __fromLayer?: boolean
6
+ }
7
+
8
+ export function resolveRoute(
9
+ meta: RouteMeta,
10
+ config: RoutingLayerConfig,
11
+ resolveFeature: (name: string) => FeatureValue,
12
+ ): RouteResolution {
13
+ if (config.strictDefaultDeny && !meta.feature) {
14
+ return { outcome: 'deny' }
15
+ }
16
+
17
+ if (config.layerDefaultDeny && meta.__fromLayer && !meta.feature) {
18
+ return { outcome: 'deny' }
19
+ }
20
+
21
+ if (!meta.feature) return { outcome: 'allow' }
22
+
23
+ const variant = resolveFeature(meta.feature)
24
+
25
+ if (variant === 'disabled') return { outcome: 'deny' }
26
+ if (variant === 'beta' || variant === 'coming-soon') {
27
+ return { outcome: 'redirect', to: config.betaRedirect }
28
+ }
29
+
30
+ return { outcome: 'allow' }
31
+ }
@@ -15,6 +15,7 @@
15
15
  "three": "^0.182.0"
16
16
  },
17
17
  "devDependencies": {
18
- "@types/three": "^0.182.0"
18
+ "@types/three": "^0.184.0",
19
+ "vite": "^7.0.0"
19
20
  }
20
21
  }
@@ -1,11 +1,10 @@
1
- // @ts-nocheck
2
1
  import { createSharedComposable, useLocalStorage } from '@vueuse/core'
3
2
  import type { AccentColor } from '#layers/theme/app/types/theme'
4
3
 
5
4
  export const useAccentColor = createSharedComposable(() => {
6
5
  const appConfig = useAppConfig()
7
6
 
8
- const defaultAccent = ((appConfig as any).themeLayer?.defaultAccent ?? 'blue') as AccentColor
7
+ const defaultAccent = (appConfig.themeLayer?.defaultAccent ?? 'blue') as AccentColor
9
8
  const accent = useLocalStorage<AccentColor>('theme-colour', defaultAccent)
10
9
 
11
10
  const pageAccent = ref<AccentColor | null>(null)
@@ -19,16 +18,6 @@ export const useAccentColor = createSharedComposable(() => {
19
18
  pageAccent.value = color
20
19
  }
21
20
 
22
- if (import.meta.client) {
23
- watch(
24
- activeAccent,
25
- (color) => {
26
- document.documentElement.setAttribute('data-theme-colour', color)
27
- },
28
- { immediate: true }
29
- )
30
- }
31
-
32
21
  return {
33
22
  accent: readonly(accent),
34
23
  setAccent,
@@ -1,4 +1,3 @@
1
- // @ts-nocheck
2
1
  import { createSharedComposable, useLocalStorage, usePreferredContrast } from '@vueuse/core'
3
2
  import type { PreferenceOverride } from '#layers/theme/app/types/theme'
4
3
 
@@ -16,16 +15,6 @@ export const useThemeContrast = createSharedComposable(() => {
16
15
  contrastOverride.value = value
17
16
  }
18
17
 
19
- if (import.meta.client) {
20
- watch(
21
- effectiveHighContrast,
22
- (high) => {
23
- document.documentElement.setAttribute('data-theme-contrast', high ? 'high' : 'standard')
24
- },
25
- { immediate: true }
26
- )
27
- }
28
-
29
18
  return {
30
19
  contrastOverride: readonly(contrastOverride),
31
20
  setContrastOverride,
@@ -1,4 +1,3 @@
1
- // @ts-nocheck
2
1
  import { createSharedComposable, useLocalStorage, usePreferredReducedMotion } from '@vueuse/core'
3
2
  import type { PreferenceOverride } from '#layers/theme/app/types/theme'
4
3
 
@@ -16,16 +15,6 @@ export const useThemeMotion = createSharedComposable(() => {
16
15
  motionOverride.value = value
17
16
  }
18
17
 
19
- if (import.meta.client) {
20
- watch(
21
- effectiveReducedMotion,
22
- (reduced) => {
23
- document.documentElement.setAttribute('data-theme-motion', reduced ? 'reduced' : 'full')
24
- },
25
- { immediate: true }
26
- )
27
- }
28
-
29
18
  return {
30
19
  motionOverride: readonly(motionOverride),
31
20
  setMotionOverride,
@@ -1,4 +1,3 @@
1
- // @ts-nocheck
2
1
  import {
3
2
  createSharedComposable,
4
3
  useLocalStorage,
@@ -20,19 +19,6 @@ export const useThemeTransparency = createSharedComposable(() => {
20
19
  transparencyOverride.value = value
21
20
  }
22
21
 
23
- if (import.meta.client) {
24
- watch(
25
- effectiveReducedTransparency,
26
- (reduced) => {
27
- document.documentElement.setAttribute(
28
- 'data-theme-transparency',
29
- reduced ? 'reduced' : 'full'
30
- )
31
- },
32
- { immediate: true }
33
- )
34
- }
35
-
36
22
  return {
37
23
  transparencyOverride: readonly(transparencyOverride),
38
24
  setTransparencyOverride,