feat: update card carousel to use direct background images
- Replaced BlurImage component with direct background image styling for better performance - Added new 'bg' property to Card type to support background image imports - Imported slider background images for each card category - Removed redundant background color class and BlurImage component - Updated card styling to maintain visual consistency with background images
@@ -1,19 +1,16 @@
 | 
				
			|||||||
"use client";
 | 
					"use client";
 | 
				
			||||||
import React, {
 | 
					import React, {
 | 
				
			||||||
  useEffect,
 | 
					  useEffect,
 | 
				
			||||||
  useRef,
 | 
					 | 
				
			||||||
  useState,
 | 
					  useState,
 | 
				
			||||||
  createContext,
 | 
					 | 
				
			||||||
  useContext,
 | 
					 | 
				
			||||||
} from "react";
 | 
					} from "react";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  IconArrowNarrowLeft,
 | 
					  IconArrowNarrowLeft,
 | 
				
			||||||
  IconArrowNarrowRight,
 | 
					  IconArrowNarrowRight,
 | 
				
			||||||
  IconX,
 | 
					  IconChevronRight,
 | 
				
			||||||
} from "@tabler/icons-react";
 | 
					} from "@tabler/icons-react";
 | 
				
			||||||
import { cn } from "@/lib/utils";
 | 
					import { cn } from "@/lib/utils";
 | 
				
			||||||
import { AnimatePresence, motion } from "motion/react";
 | 
					import { Link } from "react-router-dom";
 | 
				
			||||||
import { useOutsideClick } from "@/hooks/use-outside-click";
 | 
					import { motion } from "motion/react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface CarouselProps {
 | 
					interface CarouselProps {
 | 
				
			||||||
  items: JSX.Element[];
 | 
					  items: JSX.Element[];
 | 
				
			||||||
@@ -24,22 +21,15 @@ type Card = {
 | 
				
			|||||||
  src: string;
 | 
					  src: string;
 | 
				
			||||||
  title: string;
 | 
					  title: string;
 | 
				
			||||||
  category: string;
 | 
					  category: string;
 | 
				
			||||||
  content: React.ReactNode;
 | 
					  description: string;
 | 
				
			||||||
 | 
					  link: string;
 | 
				
			||||||
 | 
					  bg: any;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const CarouselContext = createContext<{
 | 
					 | 
				
			||||||
  onCardClose: (index: number) => void;
 | 
					 | 
				
			||||||
  currentIndex: number;
 | 
					 | 
				
			||||||
}>({
 | 
					 | 
				
			||||||
  onCardClose: () => {},
 | 
					 | 
				
			||||||
  currentIndex: 0,
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const Carousel = ({ items, initialScroll = 0 }: CarouselProps) => {
 | 
					export const Carousel = ({ items, initialScroll = 0 }: CarouselProps) => {
 | 
				
			||||||
  const carouselRef = React.useRef<HTMLDivElement>(null);
 | 
					  const carouselRef = React.useRef<HTMLDivElement>(null);
 | 
				
			||||||
  const [canScrollLeft, setCanScrollLeft] = React.useState(false);
 | 
					  const [canScrollLeft, setCanScrollLeft] = React.useState(false);
 | 
				
			||||||
  const [canScrollRight, setCanScrollRight] = React.useState(true);
 | 
					  const [canScrollRight, setCanScrollRight] = React.useState(true);
 | 
				
			||||||
  const [currentIndex, setCurrentIndex] = useState(0);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    if (carouselRef.current) {
 | 
					    if (carouselRef.current) {
 | 
				
			||||||
@@ -68,27 +58,12 @@ export const Carousel = ({ items, initialScroll = 0 }: CarouselProps) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  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 = () => {
 | 
					  const isMobile = () => {
 | 
				
			||||||
    return window && window.innerWidth < 768;
 | 
					    return window && window.innerWidth < 768;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <CarouselContext.Provider
 | 
					 | 
				
			||||||
      value={{ onCardClose: handleCardClose, currentIndex }}
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      <div className="relative w-full">
 | 
					      <div className="relative w-full">
 | 
				
			||||||
        <div
 | 
					        <div
 | 
				
			||||||
          className="flex w-full overflow-x-scroll overscroll-x-auto scroll-smooth py-10 [scrollbar-width:none] md:py-20"
 | 
					          className="flex w-full overflow-x-scroll overscroll-x-auto scroll-smooth py-10 [scrollbar-width:none] md:py-20"
 | 
				
			||||||
@@ -147,100 +122,30 @@ export const Carousel = ({ items, initialScroll = 0 }: CarouselProps) => {
 | 
				
			|||||||
          </button>
 | 
					          </button>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </CarouselContext.Provider>
 | 
					 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const Card = ({
 | 
					export const Card = ({
 | 
				
			||||||
  card,
 | 
					  card,
 | 
				
			||||||
  index,
 | 
					 | 
				
			||||||
  layout = false,
 | 
					  layout = false,
 | 
				
			||||||
}: {
 | 
					}: {
 | 
				
			||||||
  card: Card;
 | 
					  card: Card;
 | 
				
			||||||
  index: number;
 | 
					 | 
				
			||||||
  layout?: boolean;
 | 
					  layout?: boolean;
 | 
				
			||||||
}) => {
 | 
					}) => {
 | 
				
			||||||
  const [open, setOpen] = useState(false);
 | 
					 | 
				
			||||||
  const containerRef = useRef<HTMLDivElement>(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 (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <Link to={card.link}>
 | 
				
			||||||
      <AnimatePresence>
 | 
					      <motion.div
 | 
				
			||||||
        {open && (
 | 
					 | 
				
			||||||
          <div className="fixed inset-0 z-50 h-screen overflow-auto">
 | 
					 | 
				
			||||||
            <motion.div
 | 
					 | 
				
			||||||
              initial={{ opacity: 0 }}
 | 
					 | 
				
			||||||
              animate={{ opacity: 1 }}
 | 
					 | 
				
			||||||
              exit={{ opacity: 0 }}
 | 
					 | 
				
			||||||
              className="fixed inset-0 h-full w-full bg-black/80 backdrop-blur-lg"
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
            <motion.div
 | 
					 | 
				
			||||||
              initial={{ opacity: 0 }}
 | 
					 | 
				
			||||||
              animate={{ opacity: 1 }}
 | 
					 | 
				
			||||||
              exit={{ opacity: 0 }}
 | 
					 | 
				
			||||||
              ref={containerRef}
 | 
					 | 
				
			||||||
              layoutId={layout ? `card-${card.title}` : undefined}
 | 
					 | 
				
			||||||
              className="relative z-[60] mx-auto my-10 h-fit max-w-5xl rounded-3xl bg-neutral-900 p-4 font-sans md:p-10"
 | 
					 | 
				
			||||||
            >
 | 
					 | 
				
			||||||
              <button
 | 
					 | 
				
			||||||
                className="sticky top-4 right-0 ml-auto flex h-8 w-8 items-center justify-center rounded-full bg-black dark:bg-white"
 | 
					 | 
				
			||||||
                onClick={handleClose}
 | 
					 | 
				
			||||||
              >
 | 
					 | 
				
			||||||
                <IconX className="h-6 w-6 text-neutral-100 dark:text-neutral-900" />
 | 
					 | 
				
			||||||
              </button>
 | 
					 | 
				
			||||||
              <motion.p
 | 
					 | 
				
			||||||
                layoutId={layout ? `category-${card.title}` : undefined}
 | 
					 | 
				
			||||||
                className="text-base font-medium text-black dark:text-white"
 | 
					 | 
				
			||||||
              >
 | 
					 | 
				
			||||||
                {card.category}
 | 
					 | 
				
			||||||
              </motion.p>
 | 
					 | 
				
			||||||
              <motion.p
 | 
					 | 
				
			||||||
                layoutId={layout ? `title-${card.title}` : undefined}
 | 
					 | 
				
			||||||
                className="mt-4 text-2xl font-semibold text-neutral-700 md:text-5xl dark:text-white"
 | 
					 | 
				
			||||||
              >
 | 
					 | 
				
			||||||
                {card.title}
 | 
					 | 
				
			||||||
              </motion.p>
 | 
					 | 
				
			||||||
              <div className="py-10">{card.content}</div>
 | 
					 | 
				
			||||||
            </motion.div>
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        )}
 | 
					 | 
				
			||||||
      </AnimatePresence>
 | 
					 | 
				
			||||||
      <motion.button
 | 
					 | 
				
			||||||
        layoutId={layout ? `card-${card.title}` : undefined}
 | 
					        layoutId={layout ? `card-${card.title}` : undefined}
 | 
				
			||||||
        onClick={handleOpen}
 | 
					        className="relative z-10 flex h-60 w-56 flex-col items-start justify-start overflow-hidden rounded-3xl md:h-120 md:w-96 hover:scale-105 transition-transform duration-200"
 | 
				
			||||||
        className="relative z-10 flex h-60 w-56 flex-col items-start justify-start overflow-hidden rounded-3xl bg-neutral-900 md:h-120 md:w-96"
 | 
					        style={{
 | 
				
			||||||
 | 
					          backgroundImage: `url(${card.bg})`,
 | 
				
			||||||
 | 
					          backgroundSize: 'cover',
 | 
				
			||||||
 | 
					          backgroundPosition: 'center',
 | 
				
			||||||
 | 
					        }}
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <div className="pointer-events-none absolute inset-x-0 top-0 z-30 h-full bg-gradient-to-b from-black/50 via-transparent to-transparent" />
 | 
					        <div className="pointer-events-none absolute inset-x-0 top-0 z-30 h-full bg-gradient-to-b from-black/50 via-transparent to-transparent" />
 | 
				
			||||||
        <div className="relative z-40 p-8">
 | 
					        <div className="relative z-40 p-8 w-full">
 | 
				
			||||||
          <motion.p
 | 
					          <motion.p
 | 
				
			||||||
            layoutId={layout ? `category-${card.category}` : undefined}
 | 
					            layoutId={layout ? `category-${card.category}` : undefined}
 | 
				
			||||||
            className="text-left font-sans text-sm font-medium text-white md:text-base"
 | 
					            className="text-left font-sans text-sm font-medium text-white md:text-base"
 | 
				
			||||||
@@ -253,14 +158,17 @@ export const Card = ({
 | 
				
			|||||||
          >
 | 
					          >
 | 
				
			||||||
            {card.title}
 | 
					            {card.title}
 | 
				
			||||||
          </motion.p>
 | 
					          </motion.p>
 | 
				
			||||||
 | 
					          <div className="flex flex-row justify-between items-center w-full mt-4">
 | 
				
			||||||
 | 
					            <motion.p className="max-w-xs text-left font-sans text-sm text-neutral-300">
 | 
				
			||||||
 | 
					              {card.description}
 | 
				
			||||||
 | 
					            </motion.p>
 | 
				
			||||||
 | 
					            <div className="h-8 w-8 bg-[#212121] rounded-full flex items-center justify-center text-[#858585] shrink-0 hover:bg-[#262626] hover:text-white active:bg-[#262626] active:text-white transition-colors duration-200">
 | 
				
			||||||
 | 
					              <IconChevronRight className="h-6 w-6" />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <BlurImage
 | 
					      </motion.div>
 | 
				
			||||||
          src={card.src}
 | 
					    </Link>
 | 
				
			||||||
          alt={card.title}
 | 
					 | 
				
			||||||
          className="absolute inset-0 z-10 h-full w-full object-cover"
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </motion.button>
 | 
					 | 
				
			||||||
    </>
 | 
					 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										
											BIN
										
									
								
								src/images/agent1.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 27 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/images/slider/agent1.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 30 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/images/slider/cloud1.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 37 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/images/slider/compute1.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 36 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/images/slider/gpu1.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 35 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/images/slider/network1.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 26 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/images/slider/storage1.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 32 KiB  | 
@@ -10,7 +10,7 @@ export function HomeSlider() {
 | 
				
			|||||||
  ));
 | 
					  ));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className="w-full h-full py-20 bg-black">
 | 
					    <div className="w-full h-full py-20 bg-[#0b0b0b]">
 | 
				
			||||||
      <div className="max-w-7xl mx-auto pl-4">
 | 
					      <div className="max-w-7xl mx-auto pl-4">
 | 
				
			||||||
        <H3 className="text-left text-white">
 | 
					        <H3 className="text-left text-white">
 | 
				
			||||||
          Discover the Mycelium Ecosystem
 | 
					          Discover the Mycelium Ecosystem
 | 
				
			||||||
@@ -43,42 +43,67 @@ const DummyContent = () => {
 | 
				
			|||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import networkImage from "@/images/slider/network1.jpg";
 | 
				
			||||||
 | 
					import agentImage from "@/images/slider/agent1.jpg";
 | 
				
			||||||
 | 
					import cloudImage from "@/images/slider/cloud1.jpg";
 | 
				
			||||||
 | 
					import gpuImage from "@/images/slider/gpu1.jpg";
 | 
				
			||||||
 | 
					import computeImage from "@/images/slider/compute1.jpg";
 | 
				
			||||||
 | 
					import storageImage from "@/images/slider/storage1.jpg";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const data = [
 | 
					const data = [
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    category: "DePIN",
 | 
					    category: "DePIN",
 | 
				
			||||||
    title: "Mycelium Network",
 | 
					    title: "Mycelium Network",
 | 
				
			||||||
 | 
					    description: "A decentralized network for distributed computing.",
 | 
				
			||||||
    src: "/images/gallery/9.webp",
 | 
					    src: "/images/gallery/9.webp",
 | 
				
			||||||
    content: <DummyContent />,
 | 
					    content: <DummyContent />,
 | 
				
			||||||
 | 
					    bg: networkImage,
 | 
				
			||||||
 | 
					    link: "/network",
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    category: "AI Agent",
 | 
					    category: "AI Agent",
 | 
				
			||||||
    title: "Mycelium Agent",
 | 
					    title: "Mycelium Agent",
 | 
				
			||||||
 | 
					    description: "An intelligent agent for task automation.",
 | 
				
			||||||
    src: "/images/gallery/2.webp",
 | 
					    src: "/images/gallery/2.webp",
 | 
				
			||||||
    content: <DummyContent />,
 | 
					    content: <DummyContent />,
 | 
				
			||||||
 | 
					    bg: agentImage,
 | 
				
			||||||
 | 
					    link: "/agent",
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    category: "Cloud",
 | 
					    category: "Cloud",
 | 
				
			||||||
    title: "Mycelium Cloud",
 | 
					    title: "Mycelium Cloud",
 | 
				
			||||||
 | 
					    description: "Decentralized cloud storage and services.",
 | 
				
			||||||
    src: "/images/gallery/3.webp",
 | 
					    src: "/images/gallery/3.webp",
 | 
				
			||||||
    content: <DummyContent />,
 | 
					    content: <DummyContent />,
 | 
				
			||||||
 | 
					    bg: cloudImage,
 | 
				
			||||||
 | 
					    link: "/cloud",
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    category: "GPU",
 | 
					    category: "GPU",
 | 
				
			||||||
    title: "Mycelium GPU",
 | 
					    title: "Mycelium GPU",
 | 
				
			||||||
 | 
					    description: "Access to a global network of GPUs.",
 | 
				
			||||||
    src: "/images/gallery/4.webp",
 | 
					    src: "/images/gallery/4.webp",
 | 
				
			||||||
    content: <DummyContent />,
 | 
					    content: <DummyContent />,
 | 
				
			||||||
 | 
					    bg: gpuImage,
 | 
				
			||||||
 | 
					    link: "/gpu",
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    category: "Compute",
 | 
					    category: "Compute",
 | 
				
			||||||
    title: "Mycelium Compute",
 | 
					    title: "Mycelium Compute",
 | 
				
			||||||
 | 
					    description: "Run computations on a distributed network.",
 | 
				
			||||||
    src: "/images/gallery/5.webp",
 | 
					    src: "/images/gallery/5.webp",
 | 
				
			||||||
    content: <DummyContent />,
 | 
					    content: <DummyContent />,
 | 
				
			||||||
 | 
					    bg: computeImage,
 | 
				
			||||||
 | 
					    link: "/compute",
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    category: "Storage",
 | 
					    category: "Storage",
 | 
				
			||||||
    title: "Mycelium Storage",
 | 
					    title: "Mycelium Storage",
 | 
				
			||||||
 | 
					    description: "Secure and decentralized data storage.",
 | 
				
			||||||
    src: "/images/gallery/6.webp",
 | 
					    src: "/images/gallery/6.webp",
 | 
				
			||||||
    content: <DummyContent />,
 | 
					    content: <DummyContent />,
 | 
				
			||||||
 | 
					    bg: storageImage,
 | 
				
			||||||
 | 
					    link: "/storage",
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 
 | 
				
			|||||||