From 7c84dad8b181b38d0657d040b3000720cbce5bae Mon Sep 17 00:00:00 2001 From: therealwolf42 <76-therealwolf42@users.noreply.gitlab.syncad.com> Date: Wed, 19 Nov 2025 17:17:25 +0100 Subject: [PATCH 1/6] inprogress changes --- apps/web/src/app/[locale]/page.tsx | 104 +++++++++++++++++- apps/web/src/app/globals.css | 4 +- apps/web/src/components/hero/DynamicHero.tsx | 65 +++++++---- .../components/navigation/NavigationItem.tsx | 3 +- apps/web/src/hooks/useBlockchainActivity.ts | 7 +- packages/hive-lib/src/activity.ts | 11 +- 6 files changed, 159 insertions(+), 35 deletions(-) diff --git a/apps/web/src/app/[locale]/page.tsx b/apps/web/src/app/[locale]/page.tsx index 2c37c3f..4515aa5 100644 --- a/apps/web/src/app/[locale]/page.tsx +++ b/apps/web/src/app/[locale]/page.tsx @@ -8,14 +8,31 @@ import { ScrollIndicator } from '@/components/ScrollIndicator'; import { RootEco } from '@/components/root/RootEco'; import { useAssets } from '@/hooks/useAssets'; import { EXCHANGES } from '@/lib/data/var'; +import { useState, useRef, useEffect } from 'react'; // Live Activity Components import { DynamicHero } from '@/components/hero/DynamicHero'; +interface MoneyParticle { + id: number; + x: number; + y: number; + vx: number; + vy: number; + rotation: number; + rotationSpeed: number; +} + export default function HomePage() { const router = useRouter(); const t = useTranslations(); const { getImage } = useAssets(); + const [particles, setParticles] = useState([]); + const defiCardRef = useRef(null); + const particleIdRef = useRef(0); + const animationFrameRef = useRef(); + const isHoveringRef = useRef(false); + const lastParticleTimeRef = useRef(0); const go = (link: string) => { window.open(link, '_blank'); @@ -25,6 +42,62 @@ export default function HomePage() { return getImage(`exchanges/${image}`); }; + // Particle animation loop for DeFi card + useEffect(() => { + const animate = () => { + const updateParticles = (prev: MoneyParticle[]) => { + return prev + .map((p) => ({ + ...p, + x: p.x + p.vx, + y: p.y + p.vy, + vy: p.vy + 0.5, // gravity + rotation: p.rotation + p.rotationSpeed, + })) + .filter((p) => p.y < window.innerHeight + 100); // remove off-screen particles + }; + + setParticles(updateParticles); + + animationFrameRef.current = requestAnimationFrame(animate); + }; + + animate(); + return () => { + if (animationFrameRef.current) { + cancelAnimationFrame(animationFrameRef.current); + } + }; + }, []); + + // Mouse move handler for DeFi card (HBD particles) + const handleMouseMove = (e: React.MouseEvent) => { + if (!isHoveringRef.current || !defiCardRef.current) return; + + // Throttle particle creation - only create one every 50ms + const now = Date.now(); + if (now - lastParticleTimeRef.current < 50) return; + lastParticleTimeRef.current = now; + + // Create particle at cursor position + const newParticle: MoneyParticle = { + id: particleIdRef.current++, + x: e.clientX, + y: e.clientY, + vx: (Math.random() - 0.5) * 4, + vy: -Math.random() * 3 - 2, + rotation: Math.random() * 360, + rotationSpeed: (Math.random() - 0.5) * 10, + }; + + setParticles((prev) => [...prev, newParticle]); + + // Limit particles + if (particles.length > 40) { + setParticles((prev) => prev.slice(-40)); + } + }; + return (
{/* Feature 3: DeFi Made Simple */} -
+
{ isHoveringRef.current = true; }} + onMouseLeave={() => { isHoveringRef.current = false; }} + onMouseMove={handleMouseMove} + >
HBD DeFi
@@ -121,6 +200,29 @@ export default function HomePage() {
+ + {/* HBD Money Fountain Particles */} + {particles.map((particle) => ( +
+ HBD +
+ ))}
); } diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index 2977d86..5917460 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -189,9 +189,11 @@ @keyframes fadeIn { from { opacity: 0; + transform: translateX(20px); } to { opacity: 1; + transform: translateX(0); } } @@ -205,7 +207,7 @@ } .animate-fade-in { - animation: fadeIn 0.3s ease-out forwards; + animation: fadeIn 1s ease-out forwards; } .animate-scroll-left { diff --git a/apps/web/src/components/hero/DynamicHero.tsx b/apps/web/src/components/hero/DynamicHero.tsx index c534fc6..6743433 100644 --- a/apps/web/src/components/hero/DynamicHero.tsx +++ b/apps/web/src/components/hero/DynamicHero.tsx @@ -2,20 +2,43 @@ import { useBlockchainActivity } from '@/hooks/useBlockchainActivity'; import { formatTimeAgo } from '@hiveio/hive-lib'; -import { useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; export function DynamicHero() { const [isVisible, setIsVisible] = useState(true); const containerRef = useRef(null); + const maxActivities = 4; const { activities, currentBlock, transactionCount } = useBlockchainActivity({ - maxActivities: 4, + maxActivities, enabled: isVisible }); const [animatingIds, setAnimatingIds] = useState>(new Set()); + const [finishedAnimatingIds, setFinishedAnimatingIds] = useState>(new Set()); + const [queuedIds, setQueuedIds] = useState([]); + const [exitingIds, setExitingIds] = useState>(new Set()); const prevActivitiesRef = useRef>(new Set()); - const timeoutRef = useRef(null); + + // Handle animation completion + const handleAnimationEnd = useCallback((activityId: string, event: React.AnimationEvent) => { + if (event.animationName === 'fadeIn') { + console.log('✅ Animation complete:', activityId); + + // Clear animating state + setAnimatingIds(new Set()); + + // Add to finished set + setFinishedAnimatingIds((prev) => new Set([...prev, activityId])); + + // Remove from queue (next animation will be started by the queue effect) + setQueuedIds((prev) => { + const remaining = prev.slice(1); + console.log('📋 Queue remaining:', remaining.length); + return remaining; + }); + } + }, []); // Observe visibility of the container useEffect(() => { @@ -49,21 +72,20 @@ export function DynamicHero() { prevActivitiesRef.current = currentIds; if (newIds.length > 0) { - // Clear any existing timeout - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - - // Add new IDs to animating set - setAnimatingIds(new Set(newIds)); - - // Remove animation class after animation completes (300ms) - timeoutRef.current = setTimeout(() => { - setAnimatingIds(new Set()); - }, 300); + console.log('➕ Adding to queue:', newIds); + setQueuedIds((prev) => [...prev, ...newIds]); } }, [activities]); + // Start first animation when queue goes from empty to having items + useEffect(() => { + if (queuedIds.length > 0 && animatingIds.size === 0) { + const firstId = queuedIds[0]; + console.log('🎬 Starting animation:', firstId); + setAnimatingIds(new Set([firstId])); + } + }, [queuedIds, animatingIds]); + return (
{/* Main Headlines */} @@ -110,17 +132,20 @@ export function DynamicHero() {
{/* Live Activities Feed */} -
+
- {[0, 1, 2, 3].map((index) => { - const activity = activities[index]; - if (!activity) return null; + {activities.map((activity) => { const isAnimating = animatingIds.has(activity.id); + const hasFinishedAnimating = finishedAnimatingIds.has(activity.id); + // Only set opacity if not currently animating + const opacity = isAnimating ? undefined : (hasFinishedAnimating ? 1 : 0); return (
handleAnimationEnd(activity.id, e)} >
{activity.avatarUrl ? ( diff --git a/apps/web/src/components/navigation/NavigationItem.tsx b/apps/web/src/components/navigation/NavigationItem.tsx index b6049ff..744932d 100644 --- a/apps/web/src/components/navigation/NavigationItem.tsx +++ b/apps/web/src/components/navigation/NavigationItem.tsx @@ -55,7 +55,8 @@ export const NavigationItem: React.FC = ({ return (