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
This commit is contained in:
2025-10-24 21:16:41 +02:00
parent 68b2119c76
commit 8844ec8a63
9 changed files with 540 additions and 23 deletions

View File

@@ -26,11 +26,11 @@ const CubeSvg: React.FC<React.SVGProps<SVGSVGElement> & { index: number }> = ({
<path
fill={`url(#cube-gradient-${index})`}
d="M491.651 144.747L287.198 227.339C265.219 236.22 241.783 236.22 219.802 227.339L15.3486 144.747C-5.11621 136.479 -5.11621 97.5191 15.3486 89.2539L219.802 6.65884C241.783 -2.21961 265.219 -2.21961 287.198 6.65884L491.651 89.2539C512.116 97.5191 512.116 136.479 491.651 144.747Z"
stroke="rgba(0,255,255,0.25)"
stroke="rgba(59, 130, 246, 0.25)"
strokeWidth="0.5"
/>
<defs>
{/* Cyan-white soft gradient */}
{/* Blue-white soft gradient */}
<linearGradient
id={`cube-gradient-${index}`}
x1="185.298"
@@ -39,8 +39,8 @@ const CubeSvg: React.FC<React.SVGProps<SVGSVGElement> & { index: number }> = ({
y2="206.448"
gradientUnits="userSpaceOnUse"
>
<stop offset="0%" stopColor="#DFFFFF" stopOpacity="0.75" />
<stop offset="40%" stopColor="#A5F4FF" stopOpacity="0.8" />
<stop offset="0%" stopColor="#EBF8FF" stopOpacity="0.75" />
<stop offset="40%" stopColor="#BEE3F8" stopOpacity="0.8" />
<stop offset="100%" stopColor="#FFFFFF" stopOpacity="0.9" />
</linearGradient>
</defs>
@@ -79,8 +79,8 @@ export function CubeLight({
<div
className={`absolute inset-0 blur-3xl rounded-2xl transition-all duration-500 ${
isActive
? "bg-cyan-400/40 opacity-70"
: "bg-cyan-200/20 opacity-40"
? "bg-blue-400/40 opacity-70"
: "bg-blue-200/20 opacity-40"
}`}
/>
@@ -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 */}
<div className="absolute inset-0 flex items-center justify-center">
<h3
className="text-cyan-900 text-sm lg:text-base font-medium text-center px-4"
className="text-blue-900 text-sm lg:text-base font-medium text-center px-4"
style={{
textShadow:
"0 0 15px rgba(255,255,255,0.8), 0 0 25px rgba(0,255,255,0.5)",
"0 0 15px rgba(255,255,255,0.8), 0 0 25px rgba(59, 130, 246, 0.5)",
}}
>
{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"
/>

View File

@@ -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<HTMLDivElement>(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 (
<CarouselContext.Provider
value={{ onCardClose: handleCardClose, currentIndex }}
>
<div className="relative w-full">
<div
className="flex w-full overflow-x-scroll overscroll-x-auto scroll-smooth py-10 [scrollbar-width:none] md:py-20"
ref={carouselRef}
onScroll={checkScrollability}
>
<div
className={cn(
"absolute right-0 z-[1000] h-auto w-[5%] overflow-hidden bg-gradient-to-l",
)}
></div>
<div
className={cn(
"flex flex-row justify-start gap-4 pl-4",
"mx-auto max-w-7xl", // remove max-w-4xl if you want the carousel to span the full width of its container
)}
>
{items.map((item, index) => (
<motion.div
initial={{
opacity: 0,
y: 20,
}}
animate={{
opacity: 1,
y: 0,
transition: {
duration: 0.5,
delay: 0.2 * index,
ease: "easeOut",
},
}}
key={"card" + index}
className="rounded-3xl last:pr-[5%] md:last:pr-[33%]"
>
{item}
</motion.div>
))}
</div>
</div>
<div className="mr-10 flex justify-end gap-2">
<button
className="relative z-40 flex h-10 w-10 items-center justify-center rounded-full bg-neutral-800 disabled:opacity-50"
onClick={scrollLeft}
disabled={!canScrollLeft}
>
<IconArrowNarrowLeft className="h-6 w-6 text-white" />
</button>
<button
className="relative z-40 flex h-10 w-10 items-center justify-center rounded-full bg-neutral-800 disabled:opacity-50"
onClick={scrollRight}
disabled={!canScrollRight}
>
<IconArrowNarrowRight className="h-6 w-6 text-white" />
</button>
</div>
</div>
</CarouselContext.Provider>
);
};
export const Card = ({
card,
index,
layout = false,
}: {
card: Card;
index: number;
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 (
<>
<AnimatePresence>
{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}
onClick={handleOpen}
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"
>
<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">
<motion.p
layoutId={layout ? `category-${card.category}` : undefined}
className="text-left font-sans text-sm font-medium text-white md:text-base"
>
{card.category}
</motion.p>
<motion.p
layoutId={layout ? `title-${card.title}` : undefined}
className="mt-2 max-w-xs text-left font-sans text-xl font-semibold [text-wrap:balance] text-white md:text-3xl"
>
{card.title}
</motion.p>
</div>
<BlurImage
src={card.src}
alt={card.title}
className="absolute inset-0 z-10 h-full w-full object-cover"
/>
</motion.button>
</>
);
};
export const BlurImage = ({
src,
className,
width,
height,
alt,
...rest
}: React.ImgHTMLAttributes<HTMLImageElement>) => {
const [isLoading, setLoading] = useState(true);
return (
<img
className={cn(
"h-full w-full transition duration-300",
isLoading ? "blur-sm" : "blur-0",
className,
)}
onLoad={() => setLoading(false)}
src={src as string}
width={width}
height={height}
loading="lazy"
decoding="async"
alt={alt ? alt : "Background of a beautiful view"}
{...rest}
/>
);
};

View File

@@ -0,0 +1,42 @@
"use client";
import { cn } from "@/lib/utils";
import { type HTMLAttributes, useEffect, useState } from "react";
interface SonOfAGlitchProps extends HTMLAttributes<HTMLHeadingElement> {
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 (
<h1 className={cn("relative font-mono", className)}>
<span
className={cn(
"absolute top-0 left-0 w-full h-full bg-transparent",
isGlitching &&
"before:content-[attr(data-text)] before:absolute before:top-0 before:w-full before:h-full before:bg-transparent before:left-[-2px] before:text-red-500 before:overflow-hidden before:animate-glitch-1",
isGlitching &&
"after:content-[attr(data-text)] after:absolute after:top-0 after:w-full after:h-full after:bg-transparent after:left-[2px] after:text-blue-500 after:overflow-hidden after:animate-glitch-2",
glitchClassName,
)}
data-text={text}
></span>
<span className={cn(textClassName)}>{text}</span>
</h1>
);
};

View File

@@ -0,0 +1,23 @@
import React, { useEffect } from "react";
export const useOutsideClick = (
ref: React.RefObject<HTMLDivElement>,
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]);
};

View File

@@ -0,0 +1,24 @@
import React, { useEffect } from "react";
export const useOutsideClick = (
ref: React.RefObject<HTMLDivElement>,
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]);
};

View File

@@ -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() {
<AnimatedSection>
<HomeFeaturesDark />
<HomeSlider />
</AnimatedSection>
<AnimatedSection>

View File

@@ -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) => (
<Card key={card.src} card={card} index={index} />
));
return (
<div className="w-full h-full py-20 bg-black">
<div className="max-w-7xl mx-auto pl-4">
<H3 className="text-left text-white">
Discover the Mycelium Ecosystem
</H3>
<div className="mt-4 max-w-3xl">
<P className="text-left text-neutral-400">
From compute and networking to intelligent automation, these components work together to empower users, developers, and organizations to build freely, without intermediaries.
</P>
</div>
</div>
<Carousel items={cards} />
</div>
);
}
const DummyContent = () => {
return (
<div className="bg-neutral-800 p-8 md:p-14 rounded-3xl mb-4 max-w-3xl">
<P className="text-neutral-400 text-base md:text-2xl font-sans mx-auto">
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.
</P>
<img
src="/images/gallery/1.webp"
alt="Macbook mockup from Aceternity UI"
height="500"
width="500"
className="md:w-1/2 md:h-1/2 h-full w-full mx-auto object-cover"
/>
</div>
);
};
const data = [
{
category: "DePIN",
title: "Mycelium Network",
src: "/images/gallery/9.webp",
content: <DummyContent />,
},
{
category: "AI Agent",
title: "Mycelium Agent",
src: "/images/gallery/2.webp",
content: <DummyContent />,
},
{
category: "Cloud",
title: "Mycelium Cloud",
src: "/images/gallery/3.webp",
content: <DummyContent />,
},
{
category: "GPU",
title: "Mycelium GPU",
src: "/images/gallery/4.webp",
content: <DummyContent />,
},
{
category: "Compute",
title: "Mycelium Compute",
src: "/images/gallery/5.webp",
content: <DummyContent />,
},
{
category: "Storage",
title: "Mycelium Storage",
src: "/images/gallery/6.webp",
content: <DummyContent />,
},
];

View File

@@ -7,35 +7,53 @@ import { FadeIn } from "@/components/ui/FadeIn";
export function StackSectionLight() {
return (
<section className="relative w-full overflow-hidden py-24 lg:py-40">
<section className="relative w-full overflow-hidden py-24 lg:py-40 isolate">
{/* === Background Layer === */}
<div className="absolute inset-0 -z-10 bg-transparent">
{/* === Center Radial Glow Aura === */}
<div className="absolute inset-0 z-0 bg-transparent">
{/* Central main aura */}
<motion.div
className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[1200px] h-[1200px] rounded-full pointer-events-none"
style={{
background:
"radial-gradient(circle, rgba(173,255,255,0.5) 0%, rgba(0,220,255,0.3) 30%, rgba(255,255,255,0) 70%)",
filter: "blur(120px)",
"radial-gradient(circle, rgba(180,255,255,0.55) 0%, rgba(0,210,255,0.35) 35%, rgba(255,255,255,0) 55%)",
filter: "blur(140px)",
}}
animate={{
opacity: [0.6, 0.8, 0.6],
opacity: [0.5, 0.8, 0.5],
scale: [1, 1.05, 1],
}}
transition={{
duration: 8,
duration: 9,
repeat: Infinity,
ease: "easeInOut",
}}
/>
{/* Faint cyan mist in the back for depth */}
<motion.div
className="absolute left-[70%] top-[30%] -translate-x-1/2 -translate-y-1/2 w-[1600px] h-[1600px] rounded-full pointer-events-none"
style={{
background:
"radial-gradient(circle, rgba(100,220,255,0.25) 0%, rgba(200,255,255,0.15) 50%, rgba(255,255,255,0) 90%)",
filter: "blur(200px)",
}}
animate={{
opacity: [0.2, 0.35, 0.2],
scale: [1, 1.1, 1],
}}
transition={{
duration: 12,
repeat: Infinity,
ease: "easeInOut",
delay: 3,
}}
/>
</div>
{/* === Content === */}
<div className="relative mx-auto max-w-7xl px-6 lg:px-8 grid grid-cols-1 lg:grid-cols-3 gap-16 items-center">
{/* Left Column - Text */}
<div className="text-center lg:text-left">
<div className="text-center lg:text-left z-10">
<FadeIn>
<Eyebrow color="accent">Technology Layers</Eyebrow>
<SectionHeader color="dark" className="text-4xl sm:text-5xl font-semibold">
@@ -53,7 +71,7 @@ export function StackSectionLight() {
</div>
{/* Right Column - Animated Stack */}
<div className="lg:col-span-2 flex items-center justify-center lg:justify-start relative">
<div className="lg:col-span-2 flex items-center justify-center lg:justify-start relative z-10">
<motion.div
initial={{ y: 30, opacity: 0 }}
whileInView={{ y: 0, opacity: 1 }}

32
tailwind.config.js Normal file
View File

@@ -0,0 +1,32 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
keyframes: {
'glitch-1': {
'0%': { transform: 'none' },
'25%': { transform: 'skew(-0.5deg, -0.5deg)' },
'50%': { transform: 'none' },
'75%': { transform: 'skew(0.5deg, 0.5deg)' },
'100%': { transform: 'none' },
},
'glitch-2': {
'0%': { transform: 'none' },
'25%': { transform: 'skew(-0.5deg, -0.5deg)' },
'50%': { transform: 'none' },
'75%': { transform: 'skew(0.5deg, 0.5deg)' },
'100%': { transform: 'none' },
},
},
animation: {
'glitch-1': 'glitch-1 1s infinite',
'glitch-2': 'glitch-2 1s infinite',
},
},
},
plugins: [],
}