diff --git a/components/cursor-effect.tsx b/components/cursor-effect.tsx index a56891d..52f3bf1 100644 --- a/components/cursor-effect.tsx +++ b/components/cursor-effect.tsx @@ -2,34 +2,26 @@ import { useEffect, useRef } from 'react' -interface Point { +// Matrix-style characters: katakana, numbers, symbols +const MATRIX_CHARS = 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン0123456789@#$%^&*(){}[]|;:<>?/\\~`' + +interface MatrixChar { x: number y: number -} - -interface Tendril { - points: Point[] - angle: number - speed: number + char: string + vx: number + vy: number life: number maxLife: number - thickness: number - branched: boolean -} - -interface Spore { - x: number - y: number - tendrils: Tendril[] - age: number + size: number + rotation: number + rotationSpeed: number } export function CursorEffect() { const canvasRef = useRef(null) - const sporesRef = useRef([]) - const allPointsRef = useRef([]) + const charsRef = useRef([]) const frameRef = useRef(0) - const lastSpawnRef = useRef(0) useEffect(() => { const canvas = canvasRef.current @@ -45,154 +37,79 @@ export function CursorEffect() { resize() window.addEventListener('resize', resize) - const createTendril = (x: number, y: number, baseAngle: number): Tendril => { - const angleOffset = (Math.random() - 0.5) * Math.PI * 0.8 - return { - points: [{ x, y }], - angle: baseAngle + angleOffset, - speed: 1 + Math.random() * 2, - life: 1, - maxLife: 80 + Math.random() * 120, - thickness: 0.5 + Math.random() * 1.5, - branched: false, - } - } + const spawnChars = (x: number, y: number, count: number) => { + for (let i = 0; i < count; i++) { + const angle = (Math.PI * 2 * i) / count + (Math.random() - 0.5) * 0.5 + const speed = 2 + Math.random() * 4 - const spawnSpore = (x: number, y: number) => { - const tendrilCount = 3 + Math.floor(Math.random() * 4) - const tendrils: Tendril[] = [] - - for (let i = 0; i < tendrilCount; i++) { - const baseAngle = (i / tendrilCount) * Math.PI * 2 - tendrils.push(createTendril(x, y, baseAngle)) - } - - sporesRef.current.push({ - x, - y, - tendrils, - age: 0, - }) - } - - const findNearbyPoint = (x: number, y: number, radius: number): Point | null => { - for (const point of allPointsRef.current) { - const dx = point.x - x - const dy = point.y - y - const dist = Math.sqrt(dx * dx + dy * dy) - if (dist < radius && dist > 5) { - return point - } - } - return null - } - - const handleMouseMove = (e: MouseEvent) => { - const now = Date.now() - if (now - lastSpawnRef.current > 150) { - spawnSpore(e.clientX, e.clientY) - lastSpawnRef.current = now + charsRef.current.push({ + x, + y, + char: MATRIX_CHARS[Math.floor(Math.random() * MATRIX_CHARS.length)], + vx: Math.cos(angle) * speed, + vy: Math.sin(angle) * speed, + life: 1, + maxLife: 60 + Math.random() * 60, + size: 12 + Math.random() * 16, + rotation: Math.random() * Math.PI * 2, + rotationSpeed: (Math.random() - 0.5) * 0.2, + }) } } const handleClick = (e: MouseEvent) => { - // Spawn multiple spores on click - for (let i = 0; i < 3; i++) { - setTimeout(() => { - spawnSpore( - e.clientX + (Math.random() - 0.5) * 20, - e.clientY + (Math.random() - 0.5) * 20 - ) - }, i * 50) - } + // Burst of matrix characters on click + spawnChars(e.clientX, e.clientY, 8 + Math.floor(Math.random() * 8)) } - window.addEventListener('mousemove', handleMouseMove) window.addEventListener('click', handleClick) const animate = () => { - ctx.fillStyle = 'rgba(18, 16, 14, 0.03)' + // Clear with slight fade for trails + ctx.fillStyle = 'rgba(18, 16, 14, 0.15)' ctx.fillRect(0, 0, canvas.width, canvas.height) - const newAllPoints: Point[] = [] + charsRef.current = charsRef.current.filter(char => { + // Update position + char.x += char.vx + char.y += char.vy - sporesRef.current = sporesRef.current.filter(spore => { - spore.age++ - let hasActiveTendrils = false + // Gravity and friction + char.vy += 0.08 + char.vx *= 0.99 + char.vy *= 0.99 - spore.tendrils = spore.tendrils.filter(tendril => { - if (tendril.life <= 0) return false + // Update rotation + char.rotation += char.rotationSpeed - const lastPoint = tendril.points[tendril.points.length - 1] + // Decay life + char.life -= 1 / char.maxLife - // Random walk with slight curve - tendril.angle += (Math.random() - 0.5) * 0.3 + // Draw character + if (char.life > 0) { + ctx.save() + ctx.translate(char.x, char.y) + ctx.rotate(char.rotation) - const newX = lastPoint.x + Math.cos(tendril.angle) * tendril.speed - const newY = lastPoint.y + Math.sin(tendril.angle) * tendril.speed + const alpha = char.life * 0.9 + // Primary color (warm gold/amber matching the theme) + ctx.fillStyle = `rgba(200, 170, 120, ${alpha})` + ctx.font = `${char.size}px "JetBrains Mono", monospace` + ctx.textAlign = 'center' + ctx.textBaseline = 'middle' + ctx.fillText(char.char, 0, 0) - // Check for nearby existing points to connect - const nearbyPoint = findNearbyPoint(newX, newY, 30) + // Glow effect + ctx.fillStyle = `rgba(200, 170, 120, ${alpha * 0.3})` + ctx.font = `${char.size * 1.1}px "JetBrains Mono", monospace` + ctx.fillText(char.char, 0, 0) - if (nearbyPoint && tendril.points.length > 10) { - // Draw connection - ctx.beginPath() - ctx.moveTo(lastPoint.x, lastPoint.y) - ctx.lineTo(nearbyPoint.x, nearbyPoint.y) - ctx.strokeStyle = `rgba(180, 160, 140, ${tendril.life * 0.3})` - ctx.lineWidth = tendril.thickness * 0.5 - ctx.stroke() - tendril.life -= 0.1 - } + ctx.restore() + } - tendril.points.push({ x: newX, y: newY }) - newAllPoints.push({ x: newX, y: newY }) - - // Keep only recent points for memory - if (tendril.points.length > 50) { - tendril.points.shift() - } - - // Draw tendril segment - if (tendril.points.length >= 2) { - const p1 = tendril.points[tendril.points.length - 2] - const p2 = tendril.points[tendril.points.length - 1] - - ctx.beginPath() - ctx.moveTo(p1.x, p1.y) - ctx.lineTo(p2.x, p2.y) - - const alpha = tendril.life * 0.6 - ctx.strokeStyle = `rgba(180, 160, 140, ${alpha})` - ctx.lineWidth = tendril.thickness * tendril.life - ctx.lineCap = 'round' - ctx.stroke() - } - - // Branching - if (!tendril.branched && tendril.points.length > 20 && Math.random() < 0.02) { - tendril.branched = true - spore.tendrils.push(createTendril(newX, newY, tendril.angle)) - } - - // Decay - tendril.life -= 1 / tendril.maxLife - - // Boundary check - if (newX < 0 || newX > canvas.width || newY < 0 || newY > canvas.height) { - tendril.life -= 0.05 - } - - if (tendril.life > 0) hasActiveTendrils = true - return tendril.life > 0 - }) - - return hasActiveTendrils || spore.age < 10 + return char.life > 0 }) - // Keep a limited history of points for connection detection - allPointsRef.current = [...allPointsRef.current.slice(-500), ...newAllPoints] - frameRef.current = requestAnimationFrame(animate) } @@ -200,7 +117,6 @@ export function CursorEffect() { return () => { window.removeEventListener('resize', resize) - window.removeEventListener('mousemove', handleMouseMove) window.removeEventListener('click', handleClick) cancelAnimationFrame(frameRef.current) } @@ -210,7 +126,7 @@ export function CursorEffect() { ) } diff --git a/components/hero-section.tsx b/components/hero-section.tsx index 7434f8e..7d4e24b 100644 --- a/components/hero-section.tsx +++ b/components/hero-section.tsx @@ -3,11 +3,12 @@ import { useEffect, useState } from "react" const DOMAINS = [ - "token engineering", - "regenerative economics", - "commons governance", - "mycelial networks", + "mycoeconomics", + "psilocybernetics", + "post-capitalism", "collective intelligence", + "regenerative systems", + "the undernet", ] export function HeroSection() { @@ -44,13 +45,13 @@ export function HeroSection() { }, []) return ( -
-
-

+
+
+

Jeff Emmett

-

+

Exploring{" "} {typedText} @@ -59,20 +60,52 @@ export function HeroSection() {

- Building tools for coordination and collective flourishing. + Mycelial explorer of alternative economic potentialities. + Designing tools for coordination and collective flourishing.

-
+ {/* Main project */} +
+ + MycoFi → + +

+ Mycelial design patterns for Web3 and beyond +

+
+ + {/* Links */} + + + {/* Social */} +
)