From 29135a3a70c0b9d2a45d8bad467861c118cb0d09 Mon Sep 17 00:00:00 2001 From: jwinterm Date: Fri, 16 Jan 2026 10:49:33 -0500 Subject: [PATCH] style: mute nebula intensity, fix load visibility, and sync parallax --- .../components/landing/dynamic-background.tsx | 7 ++ packages/ui/src/procedural/nebula-field.tsx | 82 ++++++++----------- 2 files changed, 43 insertions(+), 46 deletions(-) diff --git a/apps/web/src/components/landing/dynamic-background.tsx b/apps/web/src/components/landing/dynamic-background.tsx index ded89dc..1339d9a 100644 --- a/apps/web/src/components/landing/dynamic-background.tsx +++ b/apps/web/src/components/landing/dynamic-background.tsx @@ -6,6 +6,13 @@ import { useEffect, useState } from "react"; export function DynamicBackground() { const { theme } = useTheme(); + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + }, []); + + if (!mounted) return null; return ( { useEffect(() => { let isMounted = true; const animate = async () => { - // Shorter start delay (0-2s) so it feels more reactive on load - await new Promise(resolve => setTimeout(resolve, Math.random() * 2000)); + // Very short initial delay for first streamers + await new Promise(resolve => setTimeout(resolve, Math.random() * 1000)); while (isMounted) { - const duration = 8 + Math.random() * 6; // slightly faster but still slow + const duration = 7 + Math.random() * 5; // Faster, snappier - // Ensure starting state is definitely 0 controls.set({ pathLength: 0, pathOffset: 0, opacity: 0 }); - // Trigger onHit when the tip reaches the end (at 75% of duration) const hitTimeout = setTimeout(() => { if (isMounted) onHit(); }, duration * 750); @@ -41,7 +39,7 @@ const StreamLine = ({ d, themeMode, onHit, width }: StreamLineProps) => { await controls.start({ pathLength: [0, 0.25, 0.25, 0], pathOffset: [0, 0, 0.75, 1], - opacity: [0, 0.5, 0.5, 0], + opacity: [0, 0.4, 0.4, 0], // Muted streamers transition: { duration, ease: "linear", @@ -50,9 +48,7 @@ const StreamLine = ({ d, themeMode, onHit, width }: StreamLineProps) => { }); clearTimeout(hitTimeout); - - // Continuous flow with small pause - await new Promise(resolve => setTimeout(resolve, 1000 + Math.random() * 2000)); + await new Promise(resolve => setTimeout(resolve, 500 + Math.random() * 1500)); } }; animate(); @@ -91,7 +87,7 @@ export const NebulaField = ({ className, density = 8, themeMode = 'dark' }: Nebu // Configuration for convergence point const targetX = 25; - const targetY = 30; // Lowered to hit center of computer better + const targetY = 35; // Moved down slightly more const paths = useMemo(() => { return Array.from({ length: density }).map(() => { @@ -105,76 +101,70 @@ export const NebulaField = ({ className, density = 8, themeMode = 'dark' }: Nebu case 3: startX = -20; startY = Math.random() * 100; break; } - const endX = targetX + (Math.random() - 0.5) * 4; - const endY = targetY + (Math.random() - 0.5) * 4; + const endX = targetX + (Math.random() - 0.5) * 2; // Tighter hit box + const endY = targetY + (Math.random() - 0.5) * 2; // Tighter hit box const cpX = (startX + endX) / 2 + (Math.random() - 0.5) * 50; const cpY = (startY + endY) / 2 + (Math.random() - 0.5) * 50; return `M ${startX} ${startY} Q ${cpX} ${cpY} ${endX} ${endY}`; }); - }, [density, themeMode]); // Regenerate on theme switch + }, [density, themeMode, targetX, targetY]); // Added dependencies for stability useEffect(() => { setMounted(true); }, []); - // Initial animation for the computer icon - useEffect(() => { - if (mounted) { - monitorControls.set({ opacity: 0, scale: 0.5 }); - monitorControls.start({ - opacity: themeMode === 'light' ? 0.4 : 0.6, - scale: 1, - transition: { duration: 2, ease: "easeOut" } - }); - } - }, [mounted, themeMode, monitorControls]); - const handleHit = useCallback(() => { - const colors = [ - "text-emerald-500", "text-blue-500", "text-purple-500", - "text-indigo-500", "text-cyan-500", "text-rose-500", "text-amber-500" - ]; + const colors = themeMode === 'light' + ? ["text-slate-400", "text-blue-400", "text-indigo-400", "text-emerald-400", "text-cyan-400"] + : ["text-emerald-400/60", "text-blue-400/60", "text-purple-400/60", "text-indigo-400/60", "text-cyan-400/60", "text-rose-400/60", "text-amber-400/60"]; + setMonitorColor(colors[Math.floor(Math.random() * colors.length)]); monitorControls.start({ - scale: [1, 1.2, 1], + scale: [1, 1.08, 1], // Less jarring scale filter: [ "brightness(1) drop-shadow(0 0 0px rgba(0,0,0,0))", - "brightness(1.5) drop-shadow(0 0 20px currentColor)", + "brightness(1.2) drop-shadow(0 0 15px currentColor)", // Less brightness "brightness(1) drop-shadow(0 0 0px rgba(0,0,0,0))" ], - transition: { duration: 4, ease: "easeInOut" } + transition: { duration: 3, ease: "easeOut" } // Slower and smoother }); - }, [monitorControls]); - - if (!mounted) return null; + }, [monitorControls, themeMode]); return (
- + + + - {paths.map((d: string, i: number) => ( ))} - +
); };