Skip to content

Commit f2f9320

Browse files
committed
feat: add sparkles
1 parent 0d28ec8 commit f2f9320

File tree

4 files changed

+4178
-2393
lines changed

4 files changed

+4178
-2393
lines changed

packages/frontendmu-nuxt/components/home/Hero.vue

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,22 @@
55
<div class="homepage-container -mt-32 pt-16 md:pt-0">
66
<div class="homepage-wrapper">
77
<main>
8+
<InspiraVortex />
89
<div class="relative z-0 w-full contain py-[200px] flex flex-col gap-32">
910
<div class="relative z-20 flex flex-col-reverse md:flex-row h-full justify-between">
1011
<div class="flex flex-col justify-center text-center md:text-left gap-10 md:w-2/3">
1112
<h1
12-
class="font-heading dark:text-white font-black text-4xl leading-tight sm:text-5xl lg:text-5xl xl:text-7xl"
13-
>
13+
class="font-heading dark:text-white font-black text-4xl leading-tight sm:text-5xl lg:text-5xl xl:text-7xl">
1414
Frontend Coders<br>
1515
<span
16-
class="accent font-bold uppercase sm:text-4xl lg:text-4xl xl:text-6xl inline-block"
17-
>Mauritius</span>
16+
class="accent font-bold uppercase sm:text-4xl lg:text-4xl xl:text-6xl inline-block">Mauritius</span>
1817
</h1>
1918
<p class="text-md md:text-lg dark:text-gray-300 md:w-5/6">
2019
{{ useAppConfig().description }}
2120
</p>
2221
<div class="grid place-items-center md:place-items-start">
23-
<NuxtLink
24-
href="/meetups"
25-
class="bg-verse-500 hover:bg-verse-600 transition-colors duration-200 text-md block w-48 rounded-full px-4 py-4 text-center font-medium text-white md:w-52 md:px-6 md:text-lg"
26-
>
22+
<NuxtLink href="/meetups"
23+
class="bg-verse-500 hover:bg-verse-600 transition-colors duration-200 text-md block w-48 rounded-full px-4 py-4 text-center font-medium text-white md:w-52 md:px-6 md:text-lg">
2724
View all meetups
2825
</NuxtLink>
2926
</div>
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
<script setup lang="ts">
2+
import { createNoise3D } from 'simplex-noise'
3+
import { cn } from '@/lib/utils'
4+
5+
const props = withDefaults(defineProps<VortexProps>(), {
6+
particleCount: 700,
7+
rangeY: 100,
8+
baseSpeed: 0.0,
9+
rangeSpeed: 1.5,
10+
baseRadius: 1,
11+
rangeRadius: 2,
12+
baseHue: 220,
13+
backgroundColor: 'transparent',
14+
})
15+
// All constants
16+
const TAU = 2 * Math.PI
17+
const baseTTL = 50
18+
const rangeTTL = 150
19+
const particlePropCount = 9
20+
const rangeHue = 100
21+
const noiseSteps = 3
22+
const xOff = 0.00125
23+
const yOff = 0.00125
24+
const zOff = 0.0005
25+
let tick = 0
26+
27+
interface VortexProps {
28+
class?: string
29+
containerClass?: string
30+
particleCount?: number
31+
rangeY?: number
32+
baseHue?: number
33+
baseSpeed?: number
34+
rangeSpeed?: number
35+
baseRadius?: number
36+
rangeRadius?: number
37+
backgroundColor?: string
38+
}
39+
40+
const canvasRef = useTemplateRef<HTMLCanvasElement | null>('canvasRef')
41+
const containerRef = useTemplateRef<HTMLElement | null>('containerRef')
42+
43+
const particlePropsLength = props.particleCount * particlePropCount
44+
45+
const noise3D = createNoise3D()
46+
let particleProps = new Float32Array(particlePropsLength)
47+
const center: [number, number] = [0, 0]
48+
49+
function rand(n: number): number {
50+
return n * Math.random()
51+
}
52+
53+
function randRange(n: number): number {
54+
return n - rand(2 * n)
55+
}
56+
57+
function fadeInOut(t: number, m: number): number {
58+
const hm = 0.5 * m
59+
return Math.abs(((t + hm) % m) - hm) / hm
60+
}
61+
62+
function lerp(n1: number, n2: number, speed: number): number {
63+
return (1 - speed) * n1 + speed * n2
64+
}
65+
66+
function setup() {
67+
const canvas = canvasRef.value
68+
const container = containerRef.value
69+
if (canvas && container) {
70+
const ctx = canvas.getContext('2d')
71+
if (ctx) {
72+
resize(canvas, ctx)
73+
initParticles()
74+
draw(canvas, ctx)
75+
}
76+
}
77+
}
78+
79+
function initParticles() {
80+
tick = 0
81+
particleProps = new Float32Array(particlePropsLength)
82+
for (let i = 0; i < particlePropsLength; i += particlePropCount) {
83+
initParticle(i)
84+
}
85+
}
86+
87+
function initParticle(i: number) {
88+
const canvas = canvasRef.value
89+
if (!canvas)
90+
return
91+
92+
const x = rand(canvas.width)
93+
const y = center[1] + randRange(props.rangeY)
94+
const vx = 0
95+
const vy = 0
96+
const life = 0
97+
const ttl = baseTTL + rand(rangeTTL)
98+
const speed = props.baseSpeed + rand(props.rangeSpeed)
99+
const radius = props.baseRadius + rand(props.rangeRadius)
100+
const hue = props.baseHue + rand(rangeHue)
101+
102+
particleProps.set([x, y, vx, vy, life, ttl, speed, radius, hue], i)
103+
}
104+
105+
function draw(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) {
106+
tick++
107+
ctx.clearRect(0, 0, canvas.width, canvas.height)
108+
109+
ctx.fillStyle = props.backgroundColor
110+
ctx.fillRect(0, 0, canvas.width, canvas.height)
111+
112+
drawParticles(ctx)
113+
renderGlow(canvas, ctx)
114+
renderToScreen(canvas, ctx)
115+
116+
requestAnimationFrame(() => draw(canvas, ctx))
117+
}
118+
119+
function drawParticles(ctx: CanvasRenderingContext2D) {
120+
for (let i = 0; i < particlePropsLength; i += particlePropCount) {
121+
updateParticle(i, ctx)
122+
}
123+
}
124+
125+
function updateParticle(i: number, ctx: CanvasRenderingContext2D) {
126+
const canvas = canvasRef.value
127+
if (!canvas)
128+
return
129+
130+
const [x, y, vx, vy, life, ttl, speed, radius, hue] = [
131+
particleProps[i],
132+
particleProps[i + 1],
133+
particleProps[i + 2],
134+
particleProps[i + 3],
135+
particleProps[i + 4],
136+
particleProps[i + 5],
137+
particleProps[i + 6],
138+
particleProps[i + 7],
139+
particleProps[i + 8],
140+
]
141+
142+
const n = noise3D(x * xOff, y * yOff, tick * zOff) * noiseSteps * TAU
143+
const nextVx = lerp(vx, Math.cos(n), 0.5)
144+
const nextVy = lerp(vy, Math.sin(n), 0.5)
145+
146+
drawParticle(x, y, x + nextVx * speed, y + nextVy * speed, life, ttl, radius, hue, ctx)
147+
148+
particleProps[i] = x + nextVx * speed
149+
particleProps[i + 1] = y + nextVy * speed
150+
particleProps[i + 2] = nextVx
151+
particleProps[i + 3] = nextVy
152+
particleProps[i + 4] = life + 1
153+
154+
if (checkBounds(x, y, canvas) || life > ttl) {
155+
initParticle(i)
156+
}
157+
}
158+
159+
function drawParticle(
160+
x: number,
161+
y: number,
162+
x2: number,
163+
y2: number,
164+
life: number,
165+
ttl: number,
166+
radius: number,
167+
hue: number,
168+
ctx: CanvasRenderingContext2D,
169+
) {
170+
ctx.save()
171+
ctx.lineCap = 'round'
172+
ctx.lineWidth = radius
173+
ctx.strokeStyle = `hsla(${hue},100%,60%,${fadeInOut(life, ttl)})`
174+
ctx.beginPath()
175+
ctx.moveTo(x, y)
176+
ctx.lineTo(x2, y2)
177+
ctx.stroke()
178+
ctx.closePath()
179+
ctx.restore()
180+
}
181+
182+
function checkBounds(x: number, y: number, canvas: HTMLCanvasElement) {
183+
return x > canvas.width || x < 0 || y > canvas.height || y < 0
184+
}
185+
186+
function resize(canvas: HTMLCanvasElement, ctx?: CanvasRenderingContext2D) {
187+
const { innerWidth, innerHeight } = window
188+
canvas.width = innerWidth
189+
canvas.height = innerHeight
190+
center[0] = 0.5 * canvas.width
191+
center[1] = 0.5 * canvas.height
192+
}
193+
194+
function renderGlow(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) {
195+
ctx.save()
196+
ctx.filter = 'blur(8px) brightness(200%)'
197+
ctx.globalCompositeOperation = 'lighter'
198+
ctx.drawImage(canvas, 0, 0)
199+
ctx.restore()
200+
201+
ctx.save()
202+
ctx.filter = 'blur(4px) brightness(200%)'
203+
ctx.globalCompositeOperation = 'lighter'
204+
ctx.drawImage(canvas, 0, 0)
205+
ctx.restore()
206+
}
207+
208+
function renderToScreen(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) {
209+
ctx.save()
210+
ctx.globalCompositeOperation = 'lighter'
211+
ctx.drawImage(canvas, 0, 0)
212+
ctx.restore()
213+
}
214+
215+
onMounted(() => {
216+
setup()
217+
window.addEventListener('resize', () => {
218+
const canvas = canvasRef.value
219+
const ctx = canvas?.getContext('2d')
220+
if (canvas && ctx) {
221+
resize(canvas, ctx)
222+
}
223+
})
224+
})
225+
226+
onUnmounted(() => {
227+
window.removeEventListener('resize', () => { })
228+
})
229+
</script>
230+
231+
<template>
232+
<div :class="cn('relative h-full w-full', props.containerClass)">
233+
<div ref="containerRef" v-motion :initial="{ opacity: 0 }" :enter="{ opacity: 1 }"
234+
class="absolute inset-0 z-0 flex size-full items-center justify-center bg-transparent">
235+
<canvas ref="canvasRef" />
236+
</div>
237+
238+
<div :class="cn('relative z-10', props.class)">
239+
<slot />
240+
</div>
241+
</div>
242+
</template>

packages/frontendmu-nuxt/package.json

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,33 @@
1414
"codegen": "playwright codegen"
1515
},
1616
"dependencies": {
17-
"@nuxt/content": "^2.13.2",
18-
"@nuxt/fonts": "^0.7.1",
19-
"@nuxt/icon": "^1.4.5",
20-
"@nuxt/image": "^1.7.0",
21-
"@nuxtjs/color-mode": "^3.4.2",
17+
"@nuxt/content": "^2.13.4",
18+
"@nuxt/fonts": "^0.7.2",
19+
"@nuxt/icon": "^1.8.2",
20+
"@nuxt/image": "^1.8.1",
21+
"@nuxtjs/color-mode": "^3.5.2",
2222
"@nuxtjs/seo": "2.0.0-rc.16",
23-
"@vueuse/core": "^10.11.0",
23+
"@vueuse/core": "^10.11.1",
2424
"class-variance-authority": "^0.7.0",
2525
"clsx": "^2.1.1",
26-
"embla-carousel": "^8.1.8",
27-
"embla-carousel-vue": "^8.1.8",
26+
"embla-carousel": "^8.4.0",
27+
"embla-carousel-vue": "^8.4.0",
2828
"lucide-vue-next": "^0.378.0",
29-
"nuxt": "^3.12.4",
30-
"radix-vue": "^1.9.2",
29+
"nuxt": "^3.14.1592",
30+
"radix-vue": "^1.9.10",
3131
"shadcn-nuxt": "^0.10.4",
32-
"tailwind-merge": "^2.4.0",
32+
"simplex-noise": "^4.0.3",
33+
"tailwind-merge": "^2.5.4",
3334
"tailwindcss-animate": "^1.0.7",
34-
"vue": "^3.4.35",
35-
"vue-router": "^4.4.2"
35+
"vue": "^3.5.13",
36+
"vue-router": "^4.4.5"
3637
},
3738
"devDependencies": {
38-
"@antfu/eslint-config": "^2.24.1",
39+
"@antfu/eslint-config": "^2.27.3",
3940
"@nuxt/eslint": "^0.4.0",
40-
"@nuxtjs/tailwindcss": "^6.12.1",
41-
"@playwright/test": "^1.40.1",
42-
"eslint": "^9.8.0",
43-
"typescript": "^5.5.4"
41+
"@nuxtjs/tailwindcss": "^6.12.2",
42+
"@playwright/test": "^1.49.0",
43+
"eslint": "^9.15.0",
44+
"typescript": "^5.6.3"
4445
}
4546
}

0 commit comments

Comments
 (0)