From 8844ec8a63bd093f6d9bccc345e285498a6f4472 Mon Sep 17 00:00:00 2001 From: sasha-astiadi Date: Fri, 24 Oct 2025 21:16:41 +0200 Subject: [PATCH] feat: update cube component color scheme from cyan to blue - Changed cube gradient colors from cyan to blue for better visual consistency - Updated glow effects and shadows to use blue (rgba(59, 130, 246)) instead of cyan - Modified background aura gradients in StackSection for enhanced depth perception - Replaced HomeFeaturesDark component with new HomeSlider in HomePage layout - Added isolate property to StackSection to prevent gradient bleeding - Enhanced background layer in StackSection with additional --- src/components/ui/CubeLight.tsx | 22 +- src/components/ui/apple-cards-carousel.tsx | 293 +++++++++++++++++++++ src/components/ui/son-of-a-glitch-text.tsx | 42 +++ src/hooks/use-outside-click.ts | 23 ++ src/hooks/use-outside-click.tsx | 24 ++ src/pages/home/HomePage.tsx | 3 +- src/pages/home/HomeSlider.tsx | 84 ++++++ src/pages/home/StackSection.tsx | 40 ++- tailwind.config.js | 32 +++ 9 files changed, 540 insertions(+), 23 deletions(-) create mode 100644 src/components/ui/apple-cards-carousel.tsx create mode 100644 src/components/ui/son-of-a-glitch-text.tsx create mode 100644 src/hooks/use-outside-click.ts create mode 100644 src/hooks/use-outside-click.tsx create mode 100644 src/pages/home/HomeSlider.tsx create mode 100644 tailwind.config.js diff --git a/src/components/ui/CubeLight.tsx b/src/components/ui/CubeLight.tsx index c5b6bb8..fd1b8ab 100644 --- a/src/components/ui/CubeLight.tsx +++ b/src/components/ui/CubeLight.tsx @@ -26,11 +26,11 @@ const CubeSvg: React.FC & { index: number }> = ({ - {/* Cyan-white soft gradient */} + {/* Blue-white soft gradient */} & { index: number }> = ({ y2="206.448" gradientUnits="userSpaceOnUse" > - - + + @@ -79,8 +79,8 @@ export function CubeLight({
@@ -90,8 +90,8 @@ export function CubeLight({ className="w-48 sm:w-64 lg:w-80 h-auto relative" style={{ filter: isActive - ? "drop-shadow(0 0 25px rgba(0,255,255,0.4)) brightness(1.1)" - : "drop-shadow(0 0 10px rgba(0,255,255,0.15)) brightness(1)", + ? "drop-shadow(0 0 25px rgba(59, 130, 246, 0.4)) brightness(1.1)" + : "drop-shadow(0 0 10px rgba(59, 130, 246, 0.15)) brightness(1)", transition: "all 0.4s ease", }} /> @@ -99,10 +99,10 @@ export function CubeLight({ {/* Title overlay */}

{title} @@ -131,7 +131,7 @@ export function CubeLight({ y1="1" x2="120" y2="1" - stroke="rgba(0,255,255,0.6)" + stroke="rgba(59, 130, 246, 0.6)" strokeWidth="1" opacity="0.8" /> diff --git a/src/components/ui/apple-cards-carousel.tsx b/src/components/ui/apple-cards-carousel.tsx new file mode 100644 index 0000000..5c89960 --- /dev/null +++ b/src/components/ui/apple-cards-carousel.tsx @@ -0,0 +1,293 @@ +"use client"; +import React, { + useEffect, + useRef, + useState, + createContext, + useContext, +} from "react"; +import { + IconArrowNarrowLeft, + IconArrowNarrowRight, + IconX, +} from "@tabler/icons-react"; +import { cn } from "@/lib/utils"; +import { AnimatePresence, motion } from "motion/react"; +import { useOutsideClick } from "@/hooks/use-outside-click"; + +interface CarouselProps { + items: JSX.Element[]; + initialScroll?: number; +} + +type Card = { + src: string; + title: string; + category: string; + content: React.ReactNode; +}; + +export const CarouselContext = createContext<{ + onCardClose: (index: number) => void; + currentIndex: number; +}>({ + onCardClose: () => {}, + currentIndex: 0, +}); + +export const Carousel = ({ items, initialScroll = 0 }: CarouselProps) => { + const carouselRef = React.useRef(null); + const [canScrollLeft, setCanScrollLeft] = React.useState(false); + const [canScrollRight, setCanScrollRight] = React.useState(true); + const [currentIndex, setCurrentIndex] = useState(0); + + useEffect(() => { + if (carouselRef.current) { + carouselRef.current.scrollLeft = initialScroll; + checkScrollability(); + } + }, [initialScroll]); + + const checkScrollability = () => { + if (carouselRef.current) { + const { scrollLeft, scrollWidth, clientWidth } = carouselRef.current; + setCanScrollLeft(scrollLeft > 0); + setCanScrollRight(scrollLeft < scrollWidth - clientWidth); + } + }; + + const scrollLeft = () => { + if (carouselRef.current) { + carouselRef.current.scrollBy({ left: -300, behavior: "smooth" }); + } + }; + + const scrollRight = () => { + if (carouselRef.current) { + carouselRef.current.scrollBy({ left: 300, behavior: "smooth" }); + } + }; + + const handleCardClose = (index: number) => { + if (carouselRef.current) { + const cardWidth = isMobile() ? 230 : 384; // (md:w-96) + const gap = isMobile() ? 4 : 8; + const scrollPosition = (cardWidth + gap) * (index + 1); + carouselRef.current.scrollTo({ + left: scrollPosition, + behavior: "smooth", + }); + setCurrentIndex(index); + } + }; + + const isMobile = () => { + return window && window.innerWidth < 768; + }; + + return ( + +
+
+
+ +
+ {items.map((item, index) => ( + + {item} + + ))} +
+
+
+ + +
+
+
+ ); +}; + +export const Card = ({ + card, + index, + layout = false, +}: { + card: Card; + index: number; + layout?: boolean; +}) => { + const [open, setOpen] = useState(false); + const containerRef = useRef(null); + const { onCardClose } = useContext(CarouselContext); + + useEffect(() => { + function onKeyDown(event: KeyboardEvent) { + if (event.key === "Escape") { + handleClose(); + } + } + + if (open) { + document.body.style.overflow = "hidden"; + } else { + document.body.style.overflow = "auto"; + } + + window.addEventListener("keydown", onKeyDown); + return () => window.removeEventListener("keydown", onKeyDown); + }, [open]); + + useOutsideClick(containerRef, () => handleClose()); + + const handleOpen = () => { + setOpen(true); + }; + + const handleClose = () => { + setOpen(false); + onCardClose(index); + }; + + return ( + <> + + {open && ( +
+ + + + + {card.category} + + + {card.title} + +
{card.content}
+
+
+ )} +
+ +
+
+ + {card.category} + + + {card.title} + +
+ + + + ); +}; + +export const BlurImage = ({ + src, + className, + width, + height, + alt, + ...rest +}: React.ImgHTMLAttributes) => { + const [isLoading, setLoading] = useState(true); + return ( + setLoading(false)} + src={src as string} + width={width} + height={height} + loading="lazy" + decoding="async" + alt={alt ? alt : "Background of a beautiful view"} + {...rest} + /> + ); +}; diff --git a/src/components/ui/son-of-a-glitch-text.tsx b/src/components/ui/son-of-a-glitch-text.tsx new file mode 100644 index 0000000..e901d71 --- /dev/null +++ b/src/components/ui/son-of-a-glitch-text.tsx @@ -0,0 +1,42 @@ +"use client"; + +import { cn } from "@/lib/utils"; +import { type HTMLAttributes, useEffect, useState } from "react"; + +interface SonOfAGlitchProps extends HTMLAttributes { + text: string; + textClassName?: string; + glitchClassName?: string; + showGlitch?: boolean; +} + +export const SonOfAGlitch = ({ + text, + className, + textClassName, + glitchClassName, + showGlitch = true, +}: SonOfAGlitchProps) => { + const [isGlitching, setIsGlitching] = useState(showGlitch); + + useEffect(() => { + setIsGlitching(showGlitch); + }, [showGlitch]); + + return ( +

+ + {text} +

+ ); +}; diff --git a/src/hooks/use-outside-click.ts b/src/hooks/use-outside-click.ts new file mode 100644 index 0000000..efb1010 --- /dev/null +++ b/src/hooks/use-outside-click.ts @@ -0,0 +1,23 @@ +import React, { useEffect } from "react"; + +export const useOutsideClick = ( + ref: React.RefObject, + callback: Function +) => { + useEffect(() => { + const listener = (event: any) => { + if (!ref.current || ref.current.contains(event.target)) { + return; + } + callback(event); + }; + + document.addEventListener("mousedown", listener); + document.addEventListener("touchstart", listener); + + return () => { + document.removeEventListener("mousedown", listener); + document.removeEventListener("touchstart", listener); + }; + }, [ref, callback]); +}; diff --git a/src/hooks/use-outside-click.tsx b/src/hooks/use-outside-click.tsx new file mode 100644 index 0000000..697ad2a --- /dev/null +++ b/src/hooks/use-outside-click.tsx @@ -0,0 +1,24 @@ +import React, { useEffect } from "react"; + +export const useOutsideClick = ( + ref: React.RefObject, + callback: Function +) => { + useEffect(() => { + const listener = (event: any) => { + // DO NOTHING if the element being clicked is the target element or their children + if (!ref.current || ref.current.contains(event.target)) { + return; + } + callback(event); + }; + + document.addEventListener("mousedown", listener); + document.addEventListener("touchstart", listener); + + return () => { + document.removeEventListener("mousedown", listener); + document.removeEventListener("touchstart", listener); + }; + }, [ref, callback]); +}; diff --git a/src/pages/home/HomePage.tsx b/src/pages/home/HomePage.tsx index 07cda4d..fa3546a 100644 --- a/src/pages/home/HomePage.tsx +++ b/src/pages/home/HomePage.tsx @@ -5,6 +5,7 @@ import { StackSectionLight } from './StackSection' import { WorldMap } from './HomeGlobe' import { HomeBenefits } from './HomeBenefits' import { CallToAction } from './CallToAction' +import { HomeSlider } from './HomeSlider' export default function HomePage() { @@ -24,7 +25,7 @@ export default function HomePage() { - + diff --git a/src/pages/home/HomeSlider.tsx b/src/pages/home/HomeSlider.tsx new file mode 100644 index 0000000..1796af9 --- /dev/null +++ b/src/pages/home/HomeSlider.tsx @@ -0,0 +1,84 @@ +"use client"; + +import React from "react"; +import { Carousel, Card } from "@/components/ui/apple-cards-carousel"; +import { H2, H3, P } from "@/components/Texts"; + +export function HomeSlider() { + const cards = data.map((card, index) => ( + + )); + + return ( +
+
+

+ Discover the Mycelium Ecosystem +

+
+

+ From compute and networking to intelligent automation, these components work together to empower users, developers, and organizations to build freely, without intermediaries. +

+
+
+ +
+ ); +} + +const DummyContent = () => { + return ( +
+

+ Mycelium is a decentralized compute network that allows you to run AI models and other software on a global network of devices. Mycelium is built on top of the Mycelium Network, a decentralized network of devices that are connected to each other and to the internet. +

+ Macbook mockup from Aceternity UI +
+ ); +}; + +const data = [ + { + category: "DePIN", + title: "Mycelium Network", + src: "/images/gallery/9.webp", + content: , + }, + { + category: "AI Agent", + title: "Mycelium Agent", + src: "/images/gallery/2.webp", + content: , + }, + { + category: "Cloud", + title: "Mycelium Cloud", + src: "/images/gallery/3.webp", + content: , + }, + + { + category: "GPU", + title: "Mycelium GPU", + src: "/images/gallery/4.webp", + content: , + }, + { + category: "Compute", + title: "Mycelium Compute", + src: "/images/gallery/5.webp", + content: , + }, + { + category: "Storage", + title: "Mycelium Storage", + src: "/images/gallery/6.webp", + content: , + }, +]; diff --git a/src/pages/home/StackSection.tsx b/src/pages/home/StackSection.tsx index 7a23998..9fab282 100644 --- a/src/pages/home/StackSection.tsx +++ b/src/pages/home/StackSection.tsx @@ -7,35 +7,53 @@ import { FadeIn } from "@/components/ui/FadeIn"; export function StackSectionLight() { return ( -
+
{/* === Background Layer === */} -
- - - {/* === Center Radial Glow Aura === */} +
+ {/* Central main aura */} + + {/* Faint cyan mist in the back for depth */} +
{/* === Content === */}
{/* Left Column - Text */} -
+
Technology Layers @@ -53,7 +71,7 @@ export function StackSectionLight() {
{/* Right Column - Animated Stack */} -
+