feat: Matrix click effect and updated focus areas
- Replace mycelial cursor with matrix character burst on click - Characters explode outward with rotation and gravity - Uses katakana, numbers, and symbols in warm gold color - Updated hero section with focused domains: mycoeconomics, psilocybernetics, post-capitalism, collective intelligence, regenerative systems, the undernet 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
b344d256d3
commit
033a38a312
|
|
@ -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<HTMLCanvasElement>(null)
|
||||
const sporesRef = useRef<Spore[]>([])
|
||||
const allPointsRef = useRef<Point[]>([])
|
||||
const charsRef = useRef<MatrixChar[]>([])
|
||||
const frameRef = useRef<number>(0)
|
||||
const lastSpawnRef = useRef<number>(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() {
|
|||
<canvas
|
||||
ref={canvasRef}
|
||||
className="fixed inset-0 pointer-events-none z-0"
|
||||
style={{ opacity: 0.8 }}
|
||||
style={{ opacity: 0.9 }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<section className="min-h-screen flex items-center justify-center px-6">
|
||||
<div className="max-w-2xl">
|
||||
<h1 className="text-4xl md:text-5xl font-light mb-8">
|
||||
<section className="min-h-screen flex items-center justify-center px-6 py-20">
|
||||
<div className="max-w-2xl w-full">
|
||||
<h1 className="text-4xl md:text-6xl font-light mb-6">
|
||||
Jeff Emmett
|
||||
</h1>
|
||||
|
||||
<p className="text-xl md:text-2xl text-muted-foreground mb-4">
|
||||
<p className="text-xl md:text-2xl text-muted-foreground mb-6">
|
||||
Exploring{" "}
|
||||
<span className="text-primary">
|
||||
{typedText}
|
||||
|
|
@ -59,20 +60,52 @@ export function HeroSection() {
|
|||
</p>
|
||||
|
||||
<p className="text-muted-foreground mb-12 leading-relaxed">
|
||||
Building tools for coordination and collective flourishing.
|
||||
Mycelial explorer of alternative economic potentialities.
|
||||
Designing tools for coordination and collective flourishing.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-wrap gap-6 text-sm">
|
||||
{/* Main project */}
|
||||
<div className="mb-10">
|
||||
<a
|
||||
href="https://mycofi.earth"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-block text-2xl md:text-3xl font-light hover:opacity-70 transition-opacity"
|
||||
>
|
||||
MycoFi →
|
||||
</a>
|
||||
<p className="text-muted-foreground text-sm mt-2">
|
||||
Mycelial design patterns for Web3 and beyond
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Links */}
|
||||
<div className="flex flex-wrap gap-6 text-sm mb-10">
|
||||
<a href="https://commonsstack.org" target="_blank" rel="noopener noreferrer">
|
||||
Commons Stack
|
||||
</a>
|
||||
<a href="https://mycofi.earth" target="_blank" rel="noopener noreferrer">
|
||||
MycoFi
|
||||
<a href="https://block.science" target="_blank" rel="noopener noreferrer">
|
||||
BlockScience
|
||||
</a>
|
||||
<a href="https://medium.com/@jeffemmett" target="_blank" rel="noopener noreferrer">
|
||||
Writing
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* Social */}
|
||||
<div className="pt-6 border-t border-border">
|
||||
<div className="flex flex-wrap gap-6 text-sm text-muted-foreground">
|
||||
<a href="https://twitter.com/JeffEmmett" target="_blank" rel="noopener noreferrer">
|
||||
Twitter
|
||||
</a>
|
||||
<a href="https://bsky.app/profile/jeffemmett.bsky.social" target="_blank" rel="noopener noreferrer">
|
||||
Bluesky
|
||||
</a>
|
||||
<a href="https://github.com/Jeff-Emmett" target="_blank" rel="noopener noreferrer">
|
||||
GitHub
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue