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:
Jeff Emmett 2025-12-27 02:07:30 -05:00
parent b344d256d3
commit 033a38a312
2 changed files with 108 additions and 159 deletions

View File

@ -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 }}
/>
)
}

View File

@ -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>
)