feat: add hide-on-scroll header and redesign hero with video background

This commit is contained in:
2025-10-21 16:45:54 +02:00
parent f44662829d
commit b46df781f8
9 changed files with 286 additions and 72 deletions

BIN
public/videos/cloud.mp4 Normal file

Binary file not shown.

BIN
public/videos/cloud2.mp4 Normal file

Binary file not shown.

View File

@@ -6,7 +6,7 @@ import { HomeHero } from '@/components/HomeHero'
import { HomeHeroLight } from '@/components/HomeHeroLight' import { HomeHeroLight } from '@/components/HomeHeroLight'
import { HomeHeroLight2 } from '@/components/HomeHeroLight2' import { HomeHeroLight2 } from '@/components/HomeHeroLight2'
import { HomeAbout } from '@/components/HomeAbout' import { HomeAbout } from '@/components/HomeAbout'
import { ClickableGallery } from '@/components/ClickableGallery' import { ClickableGalleryLight } from '@/components/ClickableGalleryLight'
import { StackSectionLight } from '@/components/StackSectionLight' import { StackSectionLight } from '@/components/StackSectionLight'
import { Companies } from '@/components/Companies' import { Companies } from '@/components/Companies'
import { CallToAction } from '@/components/CallToAction' import { CallToAction } from '@/components/CallToAction'
@@ -36,7 +36,7 @@ export default function Home() {
<Companies /> <Companies />
</section> </section>
<section id="clickable-gallery"> <section id="clickable-gallery">
<ClickableGallery /> <ClickableGalleryLight />
</section> </section>
<section id="bento-reviews"> <section id="bento-reviews">
<BentoReviews /> <BentoReviews />

View File

@@ -0,0 +1,179 @@
'use client'
import { useEffect, useMemo, useState } from 'react'
import { useResponsiveCarousel } from '@/hooks/useResponsiveCarousel';
import Image from 'next/image'
import { motion, AnimatePresence } from 'framer-motion'
import { wrap } from 'popmotion'
import { Button } from '@/components/Button';
import { H2, P, CT } from '@/components/Texts';
import { TypeAnimation } from 'react-type-animation'
import { FadeIn } from './FadeIn';
const galleryItems = [
{ text: 'Navigate and interact with any web interface', image: '/images/gallery/interface.jpg', width: 448, height: 277 },
{ text: 'Process documents across all formats', image: '/images/gallery/docs.jpg', width: 448, height: 277 },
{ text: 'Execute multi-step workflows autonomously', image: '/images/gallery/flow.jpg', width: 448, height: 277 },
{ text: 'Manage calendars, emails, and tasks', image: '/images/gallery/calendar.jpg', width: 448, height: 277 },
{ text: 'Perform deep semantic search across all data sources', image: '/images/gallery/data.jpg', width: 448, height: 277 },
{ text: 'Identify patterns in complex datasets', image: '/images/gallery/datasets.jpg', width: 448, height: 277 },
{ text: 'Provide real-time market intelligence', image: '/images/gallery/market.jpg', width: 448, height: 277 },
{ text: 'Generate and debug code in multiple languages', image: '/images/gallery/code.jpg', width: 448, height: 277 },
{ text: 'Create consistent branded content', image: '/images/gallery/branding.jpg', width: 448, height: 277 },
{ text: 'Translate and localize materials', image: '/images/gallery/translate.jpg', width: 448, height: 277 },
{ text: 'Transform and migrate data structures', image: '/images/gallery/structure.jpg', width: 448, height: 277 },
]
// 🔧 Carousel Config
const VISIBLE = 4;
const AUTOPLAY_MS = 3200;
export function ClickableGalleryLight() {
const [active, setActive] = useState(0);
const [hovering, setHovering] = useState(false);
const { GAP, ROT_Y, DEPTH, SCALE_DROP } = useResponsiveCarousel();
// autoplay
useEffect(() => {
if (hovering) return
const id = setInterval(() => setActive((i) => wrap(0, galleryItems.length, i + 1)), AUTOPLAY_MS)
return () => clearInterval(id)
}, [hovering])
const indices = useMemo(
() => [...Array(VISIBLE * 2 + 1)].map((_, i) => wrap(0, galleryItems.length, active + i - VISIBLE)),
[active]
)
const next = () => setActive((i) => wrap(0, galleryItems.length, i + 1))
const prev = () => setActive((i) => wrap(0, galleryItems.length, i - 1))
return (
<div className="bg-white">
<div className="relative isolate pt-8 pb-0 text-center w-full">
<FadeIn transition={{ duration: 0.8, delay: 0.1 }}>
<div className="mx-auto max-w-5xl lg:mt-12">
<H2 className="text-center" color="dark">Agents with Endless Possibilities.</H2>
</div>
</FadeIn>
<FadeIn transition={{ duration: 0.8, delay: 0.2 }}>
<div className="mx-auto max-w-4xl mt-6 lg:px-0 px-4">
<P className="text-center" color="dark">
Your private agent coordinates a team of specialists that spin up on demand, collaborate across your world, and deliver end-to-end results.
Many agents, one intelligenceyours.
</P>
</div>
</FadeIn>
</div>
<FadeIn transition={{ duration: 1, delay: 0.4 }}>
<section
className="relative w-full flex items-center justify-center overflow-hidden -mt-8 pt-0 pb-0"
onMouseEnter={() => setHovering(true)}
onMouseLeave={() => setHovering(false)}
>
<div className="relative w-full max-w-[1800px] h-[300px] md:h-[500px]" style={{ perspective: '1600px' }}>
<div className="absolute inset-0" style={{ transformStyle: 'preserve-3d' }}>
<AnimatePresence initial={false}>
{indices.map((idx, i) => {
const distance = i - VISIBLE;
const item = galleryItems[idx];
const x = distance * GAP;
const z = -Math.abs(distance) * DEPTH;
const r = distance * ROT_Y;
const s = 1 - Math.abs(distance) * SCALE_DROP;
const o = distance === 0 ? 1 : 0.80;
const zIndex = 100 - Math.abs(distance);
return (
<motion.div
key={`${idx}-${i}`}
className={`absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 will-change-transform overflow-hidden ${distance === 0 ? 'rounded-xl' : ''}`}
initial={{ opacity: 0 }}
animate={{
transform: `translateX(${x}px) translateZ(${z}px) rotateY(${r}deg) scale(${s})`,
zIndex,
opacity: o,
boxShadow: distance === 0 ? '0 0 20px 5px rgba(0, 0, 0, 0.1)' : 'none',
}}
exit={{ opacity: 0 }}
transition={{ type: 'spring', stiffness: 220, damping: 26 }}
onClick={() => setActive(idx)}
>
<div className="relative bg-gray-100 flex items-center justify-center">
<Image
src={item.image}
alt={item.text}
width={item.width}
height={item.height}
className="object-contain"
priority={i === VISIBLE}
/>
</div>
</motion.div>
);
})}
</AnimatePresence>
</div>
</div>
{/* Arrows */}
<div className="absolute inset-y-0 left-8 hidden md:flex items-center z-50">
<button
onClick={prev}
className="bg-white/50 rounded-full p-2 shadow-lg backdrop-blur-md text-black"
aria-label="Previous"
>
<svg className="size-8" viewBox="0 0 24 24" fill="none" dangerouslySetInnerHTML={{ __html: '<path d="M15 19L8 12l7-7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>' }} />
</button>
</div>
<div className="absolute inset-y-0 right-8 hidden md:flex items-center z-50">
<button
onClick={next}
className="bg-white/50 rounded-full p-2 shadow-lg backdrop-blur-md text-black"
aria-label="Next"
>
<svg className="size-8" viewBox="0 0 24 24" fill="none" dangerouslySetInnerHTML={{ __html: '<path d="M9 5l7 7-7 7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>' }} />
</button>
</div>
{/* Foreground pill (Desktop) */}
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-[60] hidden md:block">
<div className="flex items-center justify-between w-[1040px] gap-6 rounded-2xl bg-gray-100/80 shadow-[0_8px_40px_rgba(0,0,0,0.15)] px-12 backdrop-blur">
<CT as="h4" className="max-w-[820px] h-[72px] flex items-center" color="dark">
<TypeAnimation
key={active}
sequence={[galleryItems[active].text]}
wrapper="span"
speed={50}
repeat={0}
/>
</CT>
<Button href="#" color="cyan" className="text-sm px-4 py-2 lg:text-base whitespace-nowrap">
Start
</Button>
</div>
</div>
</section>
{/* Text box (Mobile) */}
<div className="md:hidden w-full px-4 -mt-12 mb-16">
<div className="flex flex-row items-center justify-between w-full gap-x-4 rounded-2xl bg-gray-100/80 p-4 backdrop-blur-md">
<CT as="h4" className="w-full text-left h-[72px] leading-tight flex items-center" color="dark">
<TypeAnimation
key={active}
sequence={[galleryItems[active].text]}
wrapper="span"
speed={50}
repeat={0}
/>
</CT>
<Button href="#" color="cyan" className="text-xs px-3 py-1.5 whitespace-nowrap">
Start
</Button>
</div>
</div>
</FadeIn>
</div>
);
}

View File

@@ -1,6 +1,6 @@
'use client' 'use client'
import { useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import Link from 'next/link' import Link from 'next/link'
import { AnimatePresence, motion } from 'framer-motion' import { AnimatePresence, motion } from 'framer-motion'
import clsx from 'clsx' import clsx from 'clsx'
@@ -60,11 +60,39 @@ function NavLinks() {
} }
export function HeaderLight() { export function HeaderLight() {
const [isVisible, setIsVisible] = useState(true);
const [lastScrollY, setLastScrollY] = useState(0);
const controlHeader = () => {
if (typeof window !== 'undefined') {
if (window.scrollY > lastScrollY && window.scrollY > 100) { // Hides when scrolling down past 100px
setIsVisible(false);
} else { // Shows when scrolling up
setIsVisible(true);
}
setLastScrollY(window.scrollY);
}
};
useEffect(() => {
if (typeof window !== 'undefined') {
window.addEventListener('scroll', controlHeader);
return () => {
window.removeEventListener('scroll', controlHeader);
};
}
}, [lastScrollY]);
return ( return (
<header className="fixed top-4 left-0 right-0 z-50 flex justify-center"> <motion.header
className="fixed top-4 left-0 right-0 z-50 flex justify-center"
initial={{ y: 0, opacity: 1 }}
animate={{ y: isVisible ? 0 : -100, opacity: isVisible ? 1 : 0 }}
transition={{ duration: 0.3, ease: 'easeInOut' }}
>
<div className="rounded-full bg-gray-50/90 px-5 py-3 shadow-lg ring-1 ring-white/10 backdrop-blur-sm"> <div className="rounded-full bg-gray-50/90 px-5 py-3 shadow-lg ring-1 ring-white/10 backdrop-blur-sm">
<NavLinks /> <NavLinks />
</div> </div>
</header> </motion.header>
) )
} }

View File

@@ -1,77 +1,58 @@
'use client' 'use client'
import { useState } from 'react' import { useRef, useEffect } from 'react'
import { motion } from 'framer-motion'
import { TypeAnimation } from 'react-type-animation'
import { Dialog, DialogPanel } from '@headlessui/react'
import { Bars3Icon, XMarkIcon, ChevronDoubleDownIcon } from '@heroicons/react/24/outline'
import { H1, H2, PL } from '@/components/Texts'
import { ChevronRightIcon } from '@heroicons/react/20/solid'
export function HomeHeroLight2() { export function HomeHeroLight2() {
const videoRef = useRef<HTMLVideoElement>(null)
useEffect(() => {
if (videoRef.current) videoRef.current.playbackRate = 0.4
}, [])
return ( return (
<div> <section className="relative h-screen overflow-hidden">
{/* Background video */}
<video
ref={videoRef}
src="/videos/cloud.mp4"
autoPlay
loop
muted
playsInline
className="absolute inset-0 h-full w-full object-cover z-[-10]"
/>
<div className="pt-24 lg:pt-32 bg-white pb-12"> {/* Global soft wash + blur */}
<div className="mx-auto max-w-7xl px-6 lg:px-8 grid grid-cols-1"> <div className="absolute inset-0 bg-white opacity-30 backdrop-blur-md z-0" />
<div className="col-start-1 row-start-1 mx-auto max-w-4xl text-center z-10">
<div
aria-hidden="true"
className="absolute inset-x-0 top-[calc(100%-13rem)] -z-10 transform-gpu overflow-hidden blur-3xl sm:top-[calc(100%-30rem)]"
>
<div
style={{
clipPath:
'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)',
}}
className="relative left-[calc(50%+3rem)] aspect-1155/678 w-144.5 -translate-x-1/2 bg-linear-to-tr from-blue-300 to-blue-600 opacity-20 sm:left-[calc(50%+36rem)] sm:w-288.75"
/>
</div>
<div {/* Center “halo” for text legibility */}
aria-hidden="true" <div
className="absolute inset-x-0 bottom-[calc(100%-13rem)] -z-10 transform-gpu overflow-hidden blur-3xl sm:bottom-[calc(100%-30rem)]" className="absolute inset-0 z-0"
> style={{
<div background:
style={{ 'radial-gradient(ellipse at center, rgba(255,255,255,0.96) 0%, rgba(255,255,255,0.88) 15%, rgba(255,255,255,0.72) 35%, rgba(255,255,255,0.08) 75%)'
clipPath: }}
'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)', />
}}
className="relative left-[calc(30%-3rem)] aspect-1155/678 w-144.5 -translate-x-1/2 bg-linear-to-tr from-blue-200 to-blue-400 opacity-15 sm:left-[calc(50%-36rem)] sm:w-288.75"
/>
</div>
<h1 className="pt-8 text-5xl font-semibold tracking-tight text-balance text-gray-900 sm:text-7xl">
Decentralized Autonomous Agentic Cloud.
</h1>
<p className="mt-8 text-lg font-medium text-pretty text-gray-500 sm:text-xl/8">
Mycelium's advancements in Agentic infrastructure supports private, secure and autonomous Agents that connect, learn and grow with you.
</p>
</div>
{/* Content */}
<div className="relative z-10 h-full flex items-center justify-center">
<div className="mx-auto max-w-4xl text-center px-6 lg:px-8">
<h1
className="pt-6 text-5xl sm:text-7xl font-semibold tracking-tight leading-tight text-gray-900"
style={{ textShadow: '0 2px 8px rgba(0,0,0,0.08)' }}
>
Decentralized Autonomous Agentic Cloud.
</h1>
<img <p
alt="App screenshot" className="mt-8 text-lg sm:text-xl font-medium text-gray-800/90 leading-relaxed"
src="/images/mchip.webp" style={{ textShadow: '0 1px 4px rgba(0,0,0,0.06)' }}
width={2432} >
height={1442} Mycelium&apos;s advancements in Agentic infrastructure support private, secure, and
className="col-start-1 row-start-1 relative top-12" autonomous Agents that connect, learn, and grow with you.
/> </p>
</div>
</div>
<div
aria-hidden="true"
className="absolute inset-x-0 top-[calc(100%-13rem)] -z-10 transform-gpu overflow-hidden blur-3xl sm:top-[calc(100%-30rem)]"
>
<div
style={{
clipPath:
'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)',
}}
className="relative left-[calc(50%+3rem)] aspect-1155/678 w-144.5 -translate-x-1/2 bg-linear-to-tr from-[#ff80b5] to-[#9089fc] opacity-30 sm:left-[calc(50%+36rem)] sm:w-288.75"
/>
</div> </div>
</div> </div>
</section>
) )
} }

View File

@@ -5,6 +5,7 @@ export function Layout({ children }: { children: React.ReactNode }) {
return ( return (
<> <>
<HeaderLight /> <HeaderLight />
{children}
<main className="flex-auto">{children}</main> <main className="flex-auto">{children}</main>
<Footer /> <Footer />
</> </>

View File

@@ -10,8 +10,33 @@ export function StackSectionLight() {
<section className="w-full bg-white lg:px-0 py-12 lg:py-24 px-6 relative lg:pt-32"> <section className="w-full bg-white lg:px-0 py-12 lg:py-24 px-6 relative lg:pt-32">
<div className="mx-auto max-w-7xl"> <div className="mx-auto max-w-7xl">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4 lg:gap-16 items-center lg:items-center"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-4 lg:gap-16 items-center lg:items-center">
<div
aria-hidden="true"
className="absolute inset-x-0 top-[calc(100%-13rem)] -z-10 transform-gpu overflow-hidden blur-3xl sm:top-[calc(100%-30rem)]"
>
<div
style={{
clipPath:
'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)',
}}
className="relative left-[calc(50%+3rem)] aspect-1155/678 w-144.5 -translate-x-1/2 bg-linear-to-tr from-blue-300 to-blue-600 opacity-20 sm:left-[calc(50%+36rem)] sm:w-288.75"
/>
</div>
<div
aria-hidden="true"
className="absolute inset-x-0 bottom-[calc(100%-13rem)] -z-10 transform-gpu overflow-hidden blur-3xl sm:bottom-[calc(100%-30rem)]"
>
<div
style={{
clipPath:
'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)',
}}
className="relative left-[calc(30%-3rem)] aspect-1155/678 w-144.5 -translate-x-1/2 bg-linear-to-tr from-blue-200 to-blue-400 opacity-15 sm:left-[calc(50%-36rem)] sm:w-288.75"
/>
</div>
{/* Left Column - Text (1/3 width) */} {/* Left Column - Text (1/3 width) */}
<div className="text-center lg:text-left lg:col-span-1 order-1 lg:order-1 pt-12"> <div className="text-center lg:text-left lg:col-span-1 order-1 lg:order-1 pt-12">
<FadeIn> <FadeIn>
<H2 className="" color="dark"> <H2 className="" color="dark">
The Mycelium Stack The Mycelium Stack

View File

@@ -30,7 +30,7 @@ export function Steps() {
const isInView = useInView(ref, { once: true }); const isInView = useInView(ref, { once: true });
return ( return (
<section id="benefits" ref={ref} className="relative pt-12 pb-4 px-4 lg:px-12 text-white"> <section id="benefits" ref={ref} className="relative pt-12 lg:pt-24 pb-4 px-4 lg:px-12 text-white">
<div className="relative px-6 lg:px-12"> <div className="relative px-6 lg:px-12">
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}