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:
77
components/BackgroundBlobs.tsx
Normal file
77
components/BackgroundBlobs.tsx
Normal 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
33
components/GooFilter.tsx
Normal 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>
|
||||
);
|
||||
5
components/LiquidCursor.tsx
Normal file
5
components/LiquidCursor.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
'use client';
|
||||
|
||||
export const LiquidCursor = () => {
|
||||
return null;
|
||||
};
|
||||
19
components/LiquidHeading.tsx
Normal file
19
components/LiquidHeading.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user