feat: add activity feed and background effects

- Implemented ActivityFeed component to display real-time user activity including coding, music, and chat interactions.
- Added GooFilter and BackgroundBlobs components for enhanced visual effects.
- Updated layout to include new components and ensure proper stacking context.
- Enhanced Tailwind CSS configuration with new color and font settings.
- Created API route to mock activity data from n8n.
- Refactored main page structure to streamline component rendering.
This commit is contained in:
2026-01-06 20:10:00 +01:00
parent e74f85da41
commit 4dc727fcd6
16 changed files with 801 additions and 1172 deletions

View File

@@ -0,0 +1,77 @@
'use client';
import { motion, useMotionValue, useTransform, useSpring } from 'framer-motion';
import { useEffect, useState } from 'react';
export const BackgroundBlobs = () => {
const mouseX = useMotionValue(0);
const mouseY = useMotionValue(0);
const springConfig = { damping: 50, stiffness: 200 };
const springX = useSpring(mouseX, springConfig);
const springY = useSpring(mouseY, springConfig);
// Parallax offsets
const x1 = useTransform(springX, (value) => value / 20);
const y1 = useTransform(springY, (value) => value / 20);
const x2 = useTransform(springX, (value) => value / -15);
const y2 = useTransform(springY, (value) => value / -15);
const x3 = useTransform(springX, (value) => value / 10);
const y3 = useTransform(springY, (value) => value / 10);
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
// Center the coordinate system
const { innerWidth, innerHeight } = window;
const x = e.clientX - innerWidth / 2;
const y = e.clientY - innerHeight / 2;
mouseX.set(x);
mouseY.set(y);
};
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, [mouseX, mouseY]);
// Prevent hydration mismatch
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) return null;
return (
<div className="fixed inset-0 overflow-hidden pointer-events-none z-0 liquid-container">
<motion.div
className="absolute top-[-10%] left-[-10%] w-[40vw] h-[40vw] bg-liquid-mint/40 rounded-full blur-[80px] mix-blend-multiply opacity-70"
style={{ x: x1, y: y1 }}
animate={{
scale: [1, 1.2, 1],
rotate: [0, 90, 0],
}}
transition={{ duration: 25, repeat: Infinity, ease: "linear" }}
/>
<motion.div
className="absolute top-[20%] right-[-10%] w-[35vw] h-[35vw] bg-liquid-lavender/40 rounded-full blur-[80px] mix-blend-multiply opacity-70"
style={{ x: x2, y: y2 }}
animate={{
scale: [1, 1.1, 1],
rotate: [0, -60, 0],
}}
transition={{ duration: 30, repeat: Infinity, ease: "linear" }}
/>
<motion.div
className="absolute bottom-[-10%] left-[20%] w-[45vw] h-[45vw] bg-liquid-rose/30 rounded-full blur-[80px] mix-blend-multiply opacity-70"
style={{ x: x3, y: y3 }}
animate={{
scale: [1, 1.3, 1],
rotate: [0, 45, 0]
}}
transition={{ duration: 35, repeat: Infinity, ease: "linear" }}
/>
</div>
);
};

33
components/GooFilter.tsx Normal file
View File

@@ -0,0 +1,33 @@
export const GooFilter = () => (
<svg
style={{ position: 'fixed', top: 0, left: 0, width: 0, height: 0, pointerEvents: 'none', zIndex: -1 }}
xmlns="http://www.w3.org/2000/svg"
version="1.1"
>
<defs>
{/* Global subtle filter */}
<filter id="goo">
<feGaussianBlur in="SourceGraphic" stdDeviation="2" result="blur" />
<feColorMatrix
in="blur"
mode="matrix"
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 19 -9"
result="goo"
/>
<feComposite in="SourceGraphic" in2="goo" operator="atop" />
</filter>
{/* Stronger filter specifically for LiquidHeading interaction */}
<filter id="goo-text">
<feGaussianBlur in="SourceGraphic" stdDeviation="2" result="blur" />
<feColorMatrix
in="blur"
mode="matrix"
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 25 -10"
result="goo"
/>
<feComposite in="SourceGraphic" in2="goo" operator="atop" />
</filter>
</defs>
</svg>
);

View File

@@ -0,0 +1,5 @@
'use client';
export const LiquidCursor = () => {
return null;
};

View File

@@ -0,0 +1,19 @@
'use client';
import clsx from 'clsx';
interface LiquidHeadingProps {
text: string;
className?: string;
level?: 1 | 2 | 3 | 4;
}
export const LiquidHeading = ({ text, className, level = 1 }: LiquidHeadingProps) => {
const Tag = `h${level}` as keyof JSX.IntrinsicElements;
return (
<Tag className={clsx("font-bold tracking-tight text-stone-800", className)}>
{text}
</Tag>
);
};