Skip to content

feat: inspira #252

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
247 changes: 247 additions & 0 deletions packages/frontendmu-nuxt/components/inspira/Vortex.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
<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()
}

function handleResize() {
const canvas = canvasRef.value
const ctx = canvas?.getContext('2d')

if (canvas && ctx) {
resize(canvas, ctx)
}
}

onMounted(() => {
setup()
window.addEventListener('resize', handleResize)
})

onUnmounted(() => {
window.removeEventListener('resize', handleResize)
})
</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