mirror of
https://github.com/Such-Software/such.software.git
synced 2026-10-17 05:32:05 +02:00
fix: globalize nebula background, refine positioning and mute animations
This commit is contained in:
@@ -6,8 +6,7 @@ import { FaMapMarkerAlt, FaPhone, FaBuilding, FaExternalLinkAlt } from "react-ic
|
|||||||
|
|
||||||
export default function AboutPage() {
|
export default function AboutPage() {
|
||||||
return (
|
return (
|
||||||
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground overflow-hidden">
|
<main className="relative flex flex-col items-center overflow-hidden">
|
||||||
<DynamicBackground />
|
|
||||||
|
|
||||||
<header className="z-50 w-full max-w-7xl mx-auto flex justify-between items-center p-6 bg-background/50 backdrop-blur-sm sticky top-0">
|
<header className="z-50 w-full max-w-7xl mx-auto flex justify-between items-center p-6 bg-background/50 backdrop-blur-sm sticky top-0">
|
||||||
<div className="flex items-center gap-6">
|
<div className="flex items-center gap-6">
|
||||||
|
|||||||
@@ -3,13 +3,11 @@ import { Inter } from "next/font/google";
|
|||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import { ThemeProvider } from "@/components/theme-provider";
|
import { ThemeProvider } from "@/components/theme-provider";
|
||||||
import { Footer } from "@/components/layout/footer";
|
import { Footer } from "@/components/layout/footer";
|
||||||
|
import { DynamicBackground } from "@/components/landing/dynamic-background";
|
||||||
|
|
||||||
const inter = Inter({ subsets: ["latin"] });
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
// ... (metadata omitted)
|
||||||
title: "Such Software LLC",
|
|
||||||
description: "Next-Generation Monorepo Platform",
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
@@ -25,8 +23,11 @@ export default function RootLayout({
|
|||||||
enableSystem
|
enableSystem
|
||||||
disableTransitionOnChange
|
disableTransitionOnChange
|
||||||
>
|
>
|
||||||
{children}
|
<DynamicBackground />
|
||||||
<Footer />
|
<div className="relative z-10 flex flex-col min-h-screen">
|
||||||
|
{children}
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -9,11 +9,9 @@ import { Button } from "@repo/ui/components/button";
|
|||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<main className="relative min-h-screen flex flex-col items-center justify-between overflow-hidden bg-background text-foreground transition-colors duration-500">
|
<main className="relative flex flex-col items-center justify-between overflow-hidden">
|
||||||
|
|
||||||
<DynamicBackground />
|
<header className="z-50 w-full max-w-7xl mx-auto flex justify-between items-center p-6 pb-2">
|
||||||
|
|
||||||
<header className="z-50 w-full max-w-7xl mx-auto flex justify-between items-center p-6">
|
|
||||||
<div className="flex items-center gap-6">
|
<div className="flex items-center gap-6">
|
||||||
<h1 className="text-2xl font-bold tracking-tighter">Such Software</h1>
|
<h1 className="text-2xl font-bold tracking-tighter">Such Software</h1>
|
||||||
<nav className="hidden md:flex gap-4 text-sm font-medium">
|
<nav className="hidden md:flex gap-4 text-sm font-medium">
|
||||||
|
|||||||
@@ -25,10 +25,10 @@ const StreamLine = ({ d, themeMode, onHit, width }: StreamLineProps) => {
|
|||||||
let isMounted = true;
|
let isMounted = true;
|
||||||
const animate = async () => {
|
const animate = async () => {
|
||||||
// Very short initial delay for first streamers
|
// Very short initial delay for first streamers
|
||||||
await new Promise(resolve => setTimeout(resolve, Math.random() * 1000));
|
await new Promise(resolve => setTimeout(resolve, Math.random() * 800));
|
||||||
|
|
||||||
while (isMounted) {
|
while (isMounted) {
|
||||||
const duration = 7 + Math.random() * 5; // Faster, snappier
|
const duration = 6 + Math.random() * 4; // Snappier, more frequent small impacts
|
||||||
|
|
||||||
controls.set({ pathLength: 0, pathOffset: 0, opacity: 0 });
|
controls.set({ pathLength: 0, pathOffset: 0, opacity: 0 });
|
||||||
|
|
||||||
@@ -37,18 +37,18 @@ const StreamLine = ({ d, themeMode, onHit, width }: StreamLineProps) => {
|
|||||||
}, duration * 750);
|
}, duration * 750);
|
||||||
|
|
||||||
await controls.start({
|
await controls.start({
|
||||||
pathLength: [0, 0.25, 0.25, 0],
|
pathLength: [0, 0.20, 0.20, 0],
|
||||||
pathOffset: [0, 0, 0.75, 1],
|
pathOffset: [0, 0, 0.75, 0.98], // Avoid overshooting center too much
|
||||||
opacity: [0, 0.4, 0.4, 0], // Muted streamers
|
opacity: [0, 0.3, 0.3, 0], // Muted streamers
|
||||||
transition: {
|
transition: {
|
||||||
duration,
|
duration,
|
||||||
ease: "linear",
|
ease: "linear",
|
||||||
times: [0, 0.25, 0.75, 1]
|
times: [0, 0.2, 0.8, 1]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
clearTimeout(hitTimeout);
|
clearTimeout(hitTimeout);
|
||||||
await new Promise(resolve => setTimeout(resolve, 500 + Math.random() * 1500));
|
await new Promise(resolve => setTimeout(resolve, 300 + Math.random() * 1000));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
animate();
|
animate();
|
||||||
@@ -65,7 +65,7 @@ const StreamLine = ({ d, themeMode, onHit, width }: StreamLineProps) => {
|
|||||||
animate={controls}
|
animate={controls}
|
||||||
className={cn(
|
className={cn(
|
||||||
"transition-colors duration-1000",
|
"transition-colors duration-1000",
|
||||||
themeMode === 'light' ? "text-slate-300" : "text-emerald-500/50"
|
themeMode === 'light' ? "text-slate-300" : "text-emerald-500/30"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -74,20 +74,24 @@ const StreamLine = ({ d, themeMode, onHit, width }: StreamLineProps) => {
|
|||||||
export const NebulaField = ({ className, density = 8, themeMode = 'dark' }: NebulaProps) => {
|
export const NebulaField = ({ className, density = 8, themeMode = 'dark' }: NebulaProps) => {
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
const monitorControls = useAnimation();
|
const monitorControls = useAnimation();
|
||||||
const [monitorColor, setMonitorColor] = useState(themeMode === 'light' ? "text-slate-400" : "text-emerald-500");
|
const [monitorColor, setMonitorColor] = useState(themeMode === 'light' ? "text-slate-400" : "text-emerald-500/60");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Sync color with theme if no impacts have happened yet
|
// Sync color with theme if no impacts have happened yet
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMonitorColor(themeMode === 'light' ? "text-slate-300" : "text-emerald-500/50");
|
setMonitorColor(themeMode === 'light' ? "text-slate-300" : "text-emerald-500/40");
|
||||||
}, [themeMode]);
|
}, [themeMode]);
|
||||||
|
|
||||||
// Parallax Scroll for Computer Icon
|
// Parallax Scroll for Computer Icon
|
||||||
const { scrollY } = useScroll();
|
const { scrollY } = useScroll();
|
||||||
const yRange = useTransform(scrollY, [0, 1000], [0, 200]);
|
const yRange = useTransform(scrollY, [0, 1000], [0, 250]);
|
||||||
|
|
||||||
// Configuration for convergence point
|
// Configuration for convergence point
|
||||||
const targetX = 25;
|
const targetX = 10; // Even further left to clear centered H1s
|
||||||
const targetY = 35; // Moved down slightly more
|
const targetY = 18; // High up near the title line
|
||||||
|
|
||||||
const paths = useMemo(() => {
|
const paths = useMemo(() => {
|
||||||
return Array.from({ length: density }).map(() => {
|
return Array.from({ length: density }).map(() => {
|
||||||
@@ -101,37 +105,35 @@ export const NebulaField = ({ className, density = 8, themeMode = 'dark' }: Nebu
|
|||||||
case 3: startX = -20; startY = Math.random() * 100; break;
|
case 3: startX = -20; startY = Math.random() * 100; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const endX = targetX + (Math.random() - 0.5) * 2; // Tighter hit box
|
const endX = targetX + (Math.random() - 0.5) * 1.5; // Even tighter
|
||||||
const endY = targetY + (Math.random() - 0.5) * 2; // Tighter hit box
|
const endY = targetY + (Math.random() - 0.5) * 1.5;
|
||||||
const cpX = (startX + endX) / 2 + (Math.random() - 0.5) * 50;
|
const cpX = (startX + endX) / 2 + (Math.random() - 0.5) * 40;
|
||||||
const cpY = (startY + endY) / 2 + (Math.random() - 0.5) * 50;
|
const cpY = (startY + endY) / 2 + (Math.random() - 0.5) * 40;
|
||||||
|
|
||||||
return `M ${startX} ${startY} Q ${cpX} ${cpY} ${endX} ${endY}`;
|
return `M ${startX} ${startY} Q ${cpX} ${cpY} ${endX} ${endY}`;
|
||||||
});
|
});
|
||||||
}, [density, themeMode, targetX, targetY]); // Added dependencies for stability
|
}, [density, themeMode, targetX, targetY]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setMounted(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleHit = useCallback(() => {
|
const handleHit = useCallback(() => {
|
||||||
const colors = themeMode === 'light'
|
const colors = themeMode === 'light'
|
||||||
? ["text-slate-400", "text-blue-400", "text-indigo-400", "text-emerald-400", "text-cyan-400"]
|
? ["text-slate-400", "text-blue-400", "text-indigo-400", "text-emerald-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"];
|
: ["text-emerald-500/50", "text-blue-500/50", "text-purple-500/50", "text-cyan-500/50", "text-teal-500/50"];
|
||||||
|
|
||||||
setMonitorColor(colors[Math.floor(Math.random() * colors.length)]);
|
setMonitorColor(colors[Math.floor(Math.random() * colors.length)]);
|
||||||
|
|
||||||
monitorControls.start({
|
monitorControls.start({
|
||||||
scale: [1, 1.08, 1], // Less jarring scale
|
scale: [1, 1.05, 1], // Very subtle
|
||||||
filter: [
|
filter: [
|
||||||
"brightness(1) drop-shadow(0 0 0px rgba(0,0,0,0))",
|
"brightness(1) drop-shadow(0 0 0px rgba(0,0,0,0))",
|
||||||
"brightness(1.2) drop-shadow(0 0 15px currentColor)", // Less brightness
|
"brightness(1.1) drop-shadow(0 0 8px currentColor)", // Muted glow
|
||||||
"brightness(1) drop-shadow(0 0 0px rgba(0,0,0,0))"
|
"brightness(1) drop-shadow(0 0 0px rgba(0,0,0,0))"
|
||||||
],
|
],
|
||||||
transition: { duration: 3, ease: "easeOut" } // Slower and smoother
|
transition: { duration: 2.5, ease: "easeOut" }
|
||||||
});
|
});
|
||||||
}, [monitorControls, themeMode]);
|
}, [monitorControls, themeMode]);
|
||||||
|
|
||||||
|
if (!mounted) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("absolute inset-0 overflow-hidden pointer-events-none z-0", className)}>
|
<div className={cn("absolute inset-0 overflow-hidden pointer-events-none z-0", className)}>
|
||||||
|
|
||||||
@@ -139,10 +141,10 @@ export const NebulaField = ({ className, density = 8, themeMode = 'dark' }: Nebu
|
|||||||
className="absolute -translate-x-1/2 -translate-y-1/2 z-20"
|
className="absolute -translate-x-1/2 -translate-y-1/2 z-20"
|
||||||
initial={{ opacity: 0, scale: 0.8 }}
|
initial={{ opacity: 0, scale: 0.8 }}
|
||||||
animate={{
|
animate={{
|
||||||
opacity: themeMode === 'light' ? 0.3 : 0.5,
|
opacity: themeMode === 'light' ? 0.3 : 0.4,
|
||||||
scale: 1
|
scale: 1
|
||||||
}}
|
}}
|
||||||
transition={{ duration: 1 }}
|
transition={{ duration: 1.5 }}
|
||||||
style={{
|
style={{
|
||||||
left: `${targetX}%`,
|
left: `${targetX}%`,
|
||||||
top: `${targetY}%`,
|
top: `${targetY}%`,
|
||||||
@@ -151,7 +153,7 @@ export const NebulaField = ({ className, density = 8, themeMode = 'dark' }: Nebu
|
|||||||
>
|
>
|
||||||
<motion.div animate={monitorControls}>
|
<motion.div animate={monitorControls}>
|
||||||
<Monitor
|
<Monitor
|
||||||
size={64}
|
size={40} // Smaller, more subtle icon
|
||||||
className={cn(
|
className={cn(
|
||||||
"transition-colors duration-2000",
|
"transition-colors duration-2000",
|
||||||
monitorColor
|
monitorColor
|
||||||
@@ -163,8 +165,8 @@ export const NebulaField = ({ className, density = 8, themeMode = 'dark' }: Nebu
|
|||||||
<motion.svg
|
<motion.svg
|
||||||
viewBox="0 0 100 100"
|
viewBox="0 0 100 100"
|
||||||
preserveAspectRatio="none"
|
preserveAspectRatio="none"
|
||||||
className="w-full h-full opacity-40 dark:opacity-70 absolute inset-0"
|
className="w-full h-full opacity-30 dark:opacity-50 absolute inset-0"
|
||||||
style={{ y: yRange }} // SVG follows computer parallax
|
style={{ y: yRange }}
|
||||||
>
|
>
|
||||||
{paths.map((d: string, i: number) => (
|
{paths.map((d: string, i: number) => (
|
||||||
<StreamLine
|
<StreamLine
|
||||||
@@ -172,7 +174,7 @@ export const NebulaField = ({ className, density = 8, themeMode = 'dark' }: Nebu
|
|||||||
d={d}
|
d={d}
|
||||||
themeMode={themeMode}
|
themeMode={themeMode}
|
||||||
onHit={handleHit}
|
onHit={handleHit}
|
||||||
width={themeMode === 'light' ? 0.08 : 0.12} // slightly thinner for less jarring feel
|
width={themeMode === 'light' ? 0.06 : 0.1}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</motion.svg>
|
</motion.svg>
|
||||||
|
|||||||
Reference in New Issue
Block a user