Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: inspira #252

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions packages/frontendmu-nuxt/components/home/Hero.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,22 @@
<div class="homepage-container -mt-32 pt-16 md:pt-0">
<div class="homepage-wrapper">
<main>
<InspiraVortex />
<div class="relative z-0 w-full contain py-[200px] flex flex-col gap-32">
<div class="relative z-20 flex flex-col-reverse md:flex-row h-full justify-between">
<div class="flex flex-col justify-center text-center md:text-left gap-10 md:w-2/3">
<h1
class="font-heading dark:text-white font-black text-4xl leading-tight sm:text-5xl lg:text-5xl xl:text-7xl"
>
class="font-heading dark:text-white font-black text-4xl leading-tight sm:text-5xl lg:text-5xl xl:text-7xl">
Frontend Coders<br>
<span
class="accent font-bold uppercase sm:text-4xl lg:text-4xl xl:text-6xl inline-block"
>Mauritius</span>
class="accent font-bold uppercase sm:text-4xl lg:text-4xl xl:text-6xl inline-block">Mauritius</span>
</h1>
<p class="text-md md:text-lg dark:text-gray-300 md:w-5/6">
{{ useAppConfig().description }}
</p>
<div class="grid place-items-center md:place-items-start">
<NuxtLink
href="/meetups"
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"
>
<NuxtLink href="/meetups"
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">
View all meetups
</NuxtLink>
</div>
Expand Down
242 changes: 242 additions & 0 deletions packages/frontendmu-nuxt/components/inspira/Vortex.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
<script setup lang="ts">
import { createNoise3D } from 'simplex-noise'
import { cn } from '@/lib/utils'

const props = withDefaults(defineProps<VortexProps>(), {
particleCount: 700,
rangeY: 100,
baseSpeed: 0.0,
rangeSpeed: 1.5,
baseRadius: 1,
rangeRadius: 2,
baseHue: 220,
backgroundColor: 'transparent',
})
// All constants
const TAU = 2 * Math.PI
const baseTTL = 50
const rangeTTL = 150
const particlePropCount = 9
const rangeHue = 100
const noiseSteps = 3
const xOff = 0.00125
const yOff = 0.00125
const zOff = 0.0005
let tick = 0

interface VortexProps {
class?: string
containerClass?: string
particleCount?: number
rangeY?: number
baseHue?: number
baseSpeed?: number
rangeSpeed?: number
baseRadius?: number
rangeRadius?: number
backgroundColor?: string
}

const canvasRef = useTemplateRef<HTMLCanvasElement | null>('canvasRef')
const containerRef = useTemplateRef<HTMLElement | null>('containerRef')

const particlePropsLength = props.particleCount * particlePropCount

const noise3D = createNoise3D()
let particleProps = new Float32Array(particlePropsLength)
const center: [number, number] = [0, 0]

function rand(n: number): number {
return n * Math.random()
}

function randRange(n: number): number {
return n - rand(2 * n)
}

function fadeInOut(t: number, m: number): number {
const hm = 0.5 * m
return Math.abs(((t + hm) % m) - hm) / hm
}

function lerp(n1: number, n2: number, speed: number): number {
return (1 - speed) * n1 + speed * n2
}

function setup() {
const canvas = canvasRef.value
const container = containerRef.value
if (canvas && container) {
const ctx = canvas.getContext('2d')
if (ctx) {
resize(canvas, ctx)
initParticles()
draw(canvas, ctx)
}
}
}

function initParticles() {
tick = 0
particleProps = new Float32Array(particlePropsLength)
for (let i = 0; i < particlePropsLength; i += particlePropCount) {
initParticle(i)
}
}

function initParticle(i: number) {
const canvas = canvasRef.value
if (!canvas)
return

const x = rand(canvas.width)
const y = center[1] + randRange(props.rangeY)
const vx = 0
const vy = 0
const life = 0
const ttl = baseTTL + rand(rangeTTL)
const speed = props.baseSpeed + rand(props.rangeSpeed)
const radius = props.baseRadius + rand(props.rangeRadius)
const hue = props.baseHue + rand(rangeHue)

particleProps.set([x, y, vx, vy, life, ttl, speed, radius, hue], i)
}

function draw(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) {
tick++
ctx.clearRect(0, 0, canvas.width, canvas.height)

ctx.fillStyle = props.backgroundColor
ctx.fillRect(0, 0, canvas.width, canvas.height)

drawParticles(ctx)
renderGlow(canvas, ctx)
renderToScreen(canvas, ctx)

requestAnimationFrame(() => draw(canvas, ctx))
}

function drawParticles(ctx: CanvasRenderingContext2D) {
for (let i = 0; i < particlePropsLength; i += particlePropCount) {
updateParticle(i, ctx)
}
}

function updateParticle(i: number, ctx: CanvasRenderingContext2D) {
const canvas = canvasRef.value
if (!canvas)
return

const [x, y, vx, vy, life, ttl, speed, radius, hue] = [
particleProps[i],
particleProps[i + 1],
particleProps[i + 2],
particleProps[i + 3],
particleProps[i + 4],
particleProps[i + 5],
particleProps[i + 6],
particleProps[i + 7],
particleProps[i + 8],
]

const n = noise3D(x * xOff, y * yOff, tick * zOff) * noiseSteps * TAU
const nextVx = lerp(vx, Math.cos(n), 0.5)
const nextVy = lerp(vy, Math.sin(n), 0.5)

drawParticle(x, y, x + nextVx * speed, y + nextVy * speed, life, ttl, radius, hue, ctx)

particleProps[i] = x + nextVx * speed
particleProps[i + 1] = y + nextVy * speed
particleProps[i + 2] = nextVx
particleProps[i + 3] = nextVy
particleProps[i + 4] = life + 1

if (checkBounds(x, y, canvas) || life > ttl) {
initParticle(i)
}
}

function drawParticle(
x: number,
y: number,
x2: number,
y2: number,
life: number,
ttl: number,
radius: number,
hue: number,
ctx: CanvasRenderingContext2D,
) {
ctx.save()
ctx.lineCap = 'round'
ctx.lineWidth = radius
ctx.strokeStyle = `hsla(${hue},100%,60%,${fadeInOut(life, ttl)})`
ctx.beginPath()
ctx.moveTo(x, y)
ctx.lineTo(x2, y2)
ctx.stroke()
ctx.closePath()
ctx.restore()
}

function checkBounds(x: number, y: number, canvas: HTMLCanvasElement) {
return x > canvas.width || x < 0 || y > canvas.height || y < 0
}

function resize(canvas: HTMLCanvasElement, ctx?: CanvasRenderingContext2D) {
const { innerWidth, innerHeight } = window
canvas.width = innerWidth
canvas.height = innerHeight
center[0] = 0.5 * canvas.width
center[1] = 0.5 * canvas.height
}

function renderGlow(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) {
ctx.save()
ctx.filter = 'blur(8px) brightness(200%)'
ctx.globalCompositeOperation = 'lighter'
ctx.drawImage(canvas, 0, 0)
ctx.restore()

ctx.save()
ctx.filter = 'blur(4px) brightness(200%)'
ctx.globalCompositeOperation = 'lighter'
ctx.drawImage(canvas, 0, 0)
ctx.restore()
}

function renderToScreen(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) {
ctx.save()
ctx.globalCompositeOperation = 'lighter'
ctx.drawImage(canvas, 0, 0)
ctx.restore()
}

onMounted(() => {
setup()
window.addEventListener('resize', () => {
const canvas = canvasRef.value
const ctx = canvas?.getContext('2d')
if (canvas && ctx) {
resize(canvas, ctx)
}
})
})
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle cleanup of animation frames on component unmounting.

The animation loop initiated by requestAnimationFrame in the draw function continues indefinitely. Ensure that the animation loop is stopped when the component is unmounted to prevent memory leaks.

Implement a mechanism to cancel the animation frame on unmount:

+let animationFrameId: number

 function draw(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) {
     tick++
     ctx.clearRect(0, 0, canvas.width, canvas.height)

     ctx.fillStyle = props.backgroundColor
     ctx.fillRect(0, 0, canvas.width, canvas.height)

     drawParticles(ctx)
     renderGlow(canvas, ctx)
     renderToScreen(canvas, ctx)

-    requestAnimationFrame(() => draw(canvas, ctx))
+    animationFrameId = requestAnimationFrame(() => draw(canvas, ctx))
 }

 onUnmounted(() => {
     window.removeEventListener('resize', resizeListener)
+    cancelAnimationFrame(animationFrameId)
 })

Committable suggestion skipped: line range outside the PR's diff.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MrSunshyne draw seems to be called recursively. Are you aware if it's ok to leave it as is or should there be some kind of conditional return to break out of the function?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!


onUnmounted(() => {
window.removeEventListener('resize', () => { })
})
n-d-r-d-g marked this conversation as resolved.
Show resolved Hide resolved
</script>

<template>
<div :class="cn('relative h-full w-full', props.containerClass)">
<div ref="containerRef" v-motion :initial="{ opacity: 0 }" :enter="{ opacity: 1 }"
class="absolute inset-0 z-0 flex size-full items-center justify-center bg-transparent">
<canvas ref="canvasRef" />
</div>

<div :class="cn('relative z-10', props.class)">
<slot />
</div>
</div>
</template>
1 change: 1 addition & 0 deletions packages/frontendmu-nuxt/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export default defineNuxtConfig({
'shadcn-nuxt',
'@nuxt/image',
'@nuxt/content',
'@vueuse/motion/nuxt',
],

eslint: {
Expand Down
45 changes: 24 additions & 21 deletions packages/frontendmu-nuxt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,35 @@
"codegen": "playwright codegen"
},
"dependencies": {
"@nuxt/content": "^2.13.2",
"@nuxt/fonts": "^0.7.1",
"@nuxt/icon": "^1.4.5",
"@nuxt/image": "^1.7.0",
"@nuxtjs/color-mode": "^3.4.2",
"@nuxt/content": "^2.13.4",
"@nuxt/fonts": "^0.7.2",
"@nuxt/icon": "^1.8.2",
"@nuxt/image": "^1.8.1",
"@nuxtjs/color-mode": "^3.5.2",
"@nuxtjs/seo": "2.0.0-rc.16",
"@vueuse/core": "^10.11.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"embla-carousel": "^8.1.8",
"embla-carousel-vue": "^8.1.8",
"@vueuse/core": "^10.11.1",
"@vueuse/motion": "^2.2.6",
"embla-carousel": "^8.4.0",
"embla-carousel-vue": "^8.4.0",
"lucide-vue-next": "^0.378.0",
"nuxt": "^3.12.4",
"radix-vue": "^1.9.2",
"nuxt": "^3.14.1592",
"radix-vue": "^1.9.10",
"shadcn-nuxt": "^0.10.4",
"tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7",
"vue": "^3.4.35",
"vue-router": "^4.4.2"
"simplex-noise": "^4.0.3",
"vue": "^3.5.13",
"vue-router": "^4.4.5"
},
"devDependencies": {
"@antfu/eslint-config": "^2.24.1",
"@antfu/eslint-config": "^2.27.3",
"@inspira-ui/plugins": "^0.0.1",
"@nuxt/eslint": "^0.4.0",
"@nuxtjs/tailwindcss": "^6.12.1",
"@playwright/test": "^1.40.1",
"eslint": "^9.8.0",
"typescript": "^5.5.4"
"@nuxtjs/tailwindcss": "^6.12.2",
"@playwright/test": "^1.49.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"eslint": "^9.15.0",
"tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7",
"typescript": "^5.6.3"
}
}
2 changes: 2 additions & 0 deletions packages/frontendmu-nuxt/tailwind.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const animate = require('tailwindcss-animate')
const setupInspiraUI = require('@inspira-ui/plugins')

/** @type {import('tailwindcss').Config} */
module.exports = {
Expand Down Expand Up @@ -145,5 +146,6 @@ module.exports = {
plugins: [
require('@tailwindcss/typography'),
animate,
setupInspiraUI,
],
}
Loading
Loading