diff --git a/public/images/networkhero.png b/public/images/networkhero.png new file mode 100644 index 0000000..b3a72e3 Binary files /dev/null and b/public/images/networkhero.png differ diff --git a/src/components/Texts.tsx b/src/components/Texts.tsx index a442771..e31782e 100644 --- a/src/components/Texts.tsx +++ b/src/components/Texts.tsx @@ -160,4 +160,4 @@ export const DownloadCardDescription = createTextComponent( ) export const CT = createTextComponent('span', 'text-lg lg:text-xl font-semibold') -export const CP = createTextComponent('p', 'text-sm lg:text-sm leading-[1.525] font-light') +export const CP = createTextComponent('p', 'text-sm lg:text-sm tracking-wide leading-[1.525] font-light') diff --git a/src/components/magicui/magic-card.tsx b/src/components/magicui/magic-card.tsx new file mode 100644 index 0000000..f0beaf5 --- /dev/null +++ b/src/components/magicui/magic-card.tsx @@ -0,0 +1,63 @@ +'use client'; + +import { cn } from '@/lib/utils'; +import { type ReactNode, useEffect, useRef, useState } from 'react'; + +interface MagicCardProps { + children: ReactNode; + className?: string; + gradientSize?: number; + gradientColor?: string; +} + +export const MagicCard = ({ children, className, gradientSize = 200, gradientColor = '#262626' }: MagicCardProps) => { + const mouseX = useRef(0); + const mouseY = useRef(0); + const cardRef = useRef(null); + const [isHovering, setIsHovering] = useState(false); + + useEffect(() => { + const handleMouseMove = (e: MouseEvent) => { + if (cardRef.current) { + const rect = cardRef.current.getBoundingClientRect(); + mouseX.current = e.clientX - rect.left; + mouseY.current = e.clientY - rect.top; + cardRef.current.style.setProperty('--mouse-x', `${mouseX.current}px`); + cardRef.current.style.setProperty('--mouse-y', `${mouseY.current}px`); + } + }; + + const currentCardRef = cardRef.current; + if (currentCardRef) { + currentCardRef.addEventListener('mousemove', handleMouseMove); + } + + return () => { + if (currentCardRef) { + currentCardRef.removeEventListener('mousemove', handleMouseMove); + } + }; + }, []); + + return ( +
setIsHovering(true)} + onMouseLeave={() => setIsHovering(false)} + className={cn( + 'relative w-full h-full p-px rounded-2xl transition-all duration-300 ease-in-out', + 'bg-neutral-900 hover:bg-neutral-800', + className + )} + style={{ + background: isHovering + ? `radial-gradient(var(--gradient-size, ${gradientSize}px) circle at var(--mouse-x) var(--mouse-y), var(--gradient-color, ${gradientColor}), transparent 100%)` + : 'transparent', + }} + > +
+ {children} +
+
+ ); +}; diff --git a/src/components/ui/CubeLight.tsx b/src/components/ui/CubeLight.tsx index 556d2f2..c5b6bb8 100644 --- a/src/components/ui/CubeLight.tsx +++ b/src/components/ui/CubeLight.tsx @@ -26,8 +26,11 @@ const CubeSvg: React.FC & { index: number }> = ({ + {/* Cyan-white soft gradient */} & { index: number }> = ({ y2="206.448" gradientUnits="userSpaceOnUse" > - - + + + ); -export function CubeLight({ title, descriptionTitle, description, isActive, index, onHover, onLeave, onClick }: CubeProps) { +export function CubeLight({ + title, + descriptionTitle, + description, + isActive, + index, + onHover, + onLeave, + onClick, +}: CubeProps) { return (
+ {/* Glow aura behind cube */} +
+ {/* SVG Cube */} - - + {/* Title overlay */}
-

{title} @@ -106,26 +131,24 @@ export function CubeLight({ title, descriptionTitle, description, isActive, inde y1="1" x2="120" y2="1" - stroke="black" + stroke="rgba(0,255,255,0.6)" strokeWidth="1" - opacity="0.6" + opacity="0.8" /> - + {/* Description text */}
-

+

{descriptionTitle}

-

+

{description}

)} - - {/* Description for Mobile - Below cube */} - +

); } diff --git a/src/components/ui/StackedCubesLight.tsx b/src/components/ui/StackedCubesLight.tsx index 03c2ce5..cbd001a 100644 --- a/src/components/ui/StackedCubesLight.tsx +++ b/src/components/ui/StackedCubesLight.tsx @@ -2,13 +2,14 @@ import { useState } from "react"; import { motion } from "framer-motion"; -import { CubeLight } from "@/components/ui/CubeLight" +import { CubeLight } from "@/components/ui/CubeLight"; const stackData = [ { id: "agent", title: "Agent Layer", - descriptionTitle: "Your sovereign agent with private memory and permissioned data access—always under your control.", + descriptionTitle: + "Your sovereign agent with private memory and permissioned data access—always under your control.", description: "Choose from a wide library of open-source LLMs, paired with built-in semantic search and retrieval.\nIt coordinates across people, apps, and other agents to plan, create, and execute.\nIt operates inside a compliant legal & financial sandbox, ready for real-world transactions and operations.\nMore than just an assistant—an intelligent partner that learns and does your way.", position: "top", @@ -16,7 +17,8 @@ const stackData = [ { id: "network", title: "Network Layer", - descriptionTitle: "A global, end-to-end encrypted overlay that simply doesn’t break.", + descriptionTitle: + "A global, end-to-end encrypted overlay that simply doesn’t break.", description: "Shortest-path routing moves your traffic the fastest way, every time.\nInstant discovery with integrated DNS, semantic search, and indexing.\nA distributed CDN and edge delivery keep content available and tamper-resistant worldwide.\nBuilt-in tool services and secure coding sandboxes—seamless on phones, desktops, and edge.", position: "middle", @@ -24,7 +26,8 @@ const stackData = [ { id: "cloud", title: "Cloud Layer", - descriptionTitle: "An autonomous, stateless OS that enforces pre-deterministic deployments you define.", + descriptionTitle: + "An autonomous, stateless OS that enforces pre-deterministic deployments you define.", description: "Workloads are cryptographically bound to your private key—location and access are yours.\nNo cloud vendor or middleman in the path: end-to-end ownership and isolation by default.\nGeo-aware placement delivers locality, compliance, and ultra-low latency where it matters.\nEncrypted, erasure-coded storage, decentralized compute and GPU on demand—including LLMs.", position: "bottom", @@ -36,60 +39,74 @@ export function StackedCubesLight() { const [selectedForMobile, setSelectedForMobile] = useState("agent"); const handleCubeClick = (id: string) => { - setSelectedForMobile(prev => (prev === id ? null : id)); + setSelectedForMobile((prev) => (prev === id ? null : id)); }; - const selectedMobileLayer = stackData.find(layer => layer.id === selectedForMobile); + const selectedMobileLayer = stackData.find( + (layer) => layer.id === selectedForMobile + ); return ( -
-
setActive("agent")} - > - + {/* ✨ Ambient cyan-white gradient background */} +
+
setActive("agent")} > - {stackData.map((layer, index) => ( -
- setActive(layer.id)} - onLeave={() => {}} - onClick={() => handleCubeClick(layer.id)} - /> -
- ))} - -
- {selectedMobileLayer && ( -
-

- {selectedMobileLayer.descriptionTitle} -

-

- {selectedMobileLayer.description} -

+ + {stackData.map((layer, index) => ( +
+ {/* 🌫 subtle glow behind each cube */} +
+ setActive(layer.id)} + onLeave={() => {}} + onClick={() => handleCubeClick(layer.id)} + /> +
+ ))} +
- )} + + {/* Mobile layer description */} + {selectedMobileLayer && ( +
+

+ {selectedMobileLayer.descriptionTitle} +

+

+ {selectedMobileLayer.description} +

+
+ )}
); } diff --git a/src/components/ui/glowing-effect.tsx b/src/components/ui/glowing-effect.tsx new file mode 100644 index 0000000..25c4c41 --- /dev/null +++ b/src/components/ui/glowing-effect.tsx @@ -0,0 +1,190 @@ +"use client"; + +import { memo, useCallback, useEffect, useRef } from "react"; +import { cn } from "@/lib/utils"; +import { animate } from "motion/react"; + +interface GlowingEffectProps { + blur?: number; + inactiveZone?: number; + proximity?: number; + spread?: number; + variant?: "default" | "white"; + glow?: boolean; + className?: string; + disabled?: boolean; + movementDuration?: number; + borderWidth?: number; +} +const GlowingEffect = memo( + ({ + blur = 0, + inactiveZone = 0.7, + proximity = 0, + spread = 20, + variant = "default", + glow = false, + className, + movementDuration = 2, + borderWidth = 1, + disabled = true, + }: GlowingEffectProps) => { + const containerRef = useRef(null); + const lastPosition = useRef({ x: 0, y: 0 }); + const animationFrameRef = useRef(0); + + const handleMove = useCallback( + (e?: MouseEvent | { x: number; y: number }) => { + if (!containerRef.current) return; + + if (animationFrameRef.current) { + cancelAnimationFrame(animationFrameRef.current); + } + + animationFrameRef.current = requestAnimationFrame(() => { + const element = containerRef.current; + if (!element) return; + + const { left, top, width, height } = element.getBoundingClientRect(); + const mouseX = e?.x ?? lastPosition.current.x; + const mouseY = e?.y ?? lastPosition.current.y; + + if (e) { + lastPosition.current = { x: mouseX, y: mouseY }; + } + + const center = [left + width * 0.5, top + height * 0.5]; + const distanceFromCenter = Math.hypot( + mouseX - center[0], + mouseY - center[1] + ); + const inactiveRadius = 0.5 * Math.min(width, height) * inactiveZone; + + if (distanceFromCenter < inactiveRadius) { + element.style.setProperty("--active", "0"); + return; + } + + const isActive = + mouseX > left - proximity && + mouseX < left + width + proximity && + mouseY > top - proximity && + mouseY < top + height + proximity; + + element.style.setProperty("--active", isActive ? "1" : "0"); + + if (!isActive) return; + + const currentAngle = + parseFloat(element.style.getPropertyValue("--start")) || 0; + let targetAngle = + (180 * Math.atan2(mouseY - center[1], mouseX - center[0])) / + Math.PI + + 90; + + const angleDiff = ((targetAngle - currentAngle + 180) % 360) - 180; + const newAngle = currentAngle + angleDiff; + + animate(currentAngle, newAngle, { + duration: movementDuration, + ease: [0.16, 1, 0.3, 1], + onUpdate: (value) => { + element.style.setProperty("--start", String(value)); + }, + }); + }); + }, + [inactiveZone, proximity, movementDuration] + ); + + useEffect(() => { + if (disabled) return; + + const handleScroll = () => handleMove(); + const handlePointerMove = (e: PointerEvent) => handleMove(e); + + window.addEventListener("scroll", handleScroll, { passive: true }); + document.body.addEventListener("pointermove", handlePointerMove, { + passive: true, + }); + + return () => { + if (animationFrameRef.current) { + cancelAnimationFrame(animationFrameRef.current); + } + window.removeEventListener("scroll", handleScroll); + document.body.removeEventListener("pointermove", handlePointerMove); + }; + }, [handleMove, disabled]); + + return ( + <> + diff --git a/src/pages/home/StackSection.tsx b/src/pages/home/StackSection.tsx index 2ecd54f..7a23998 100644 --- a/src/pages/home/StackSection.tsx +++ b/src/pages/home/StackSection.tsx @@ -4,25 +4,32 @@ import { motion } from "framer-motion"; import { StackedCubesLight } from "@/components/ui/StackedCubesLight"; import { P, SectionHeader, Eyebrow } from "@/components/Texts"; import { FadeIn } from "@/components/ui/FadeIn"; -import { DottedGlowBackground } from '@/components/ui/dotted-glow-background'; export function StackSectionLight() { return (
{/* === Background Layer === */}
- {/* Dotted Glow Background */} - - {/* Faint 3D grid floor */} -
-
-
{/* === Content === */}