feat: add hide-on-scroll header and redesign hero with video background
This commit is contained in:
BIN
public/videos/cloud.mp4
Normal file
BIN
public/videos/cloud.mp4
Normal file
Binary file not shown.
BIN
public/videos/cloud2.mp4
Normal file
BIN
public/videos/cloud2.mp4
Normal file
Binary file not shown.
@@ -6,7 +6,7 @@ import { HomeHero } from '@/components/HomeHero'
|
||||
import { HomeHeroLight } from '@/components/HomeHeroLight'
|
||||
import { HomeHeroLight2 } from '@/components/HomeHeroLight2'
|
||||
import { HomeAbout } from '@/components/HomeAbout'
|
||||
import { ClickableGallery } from '@/components/ClickableGallery'
|
||||
import { ClickableGalleryLight } from '@/components/ClickableGalleryLight'
|
||||
import { StackSectionLight } from '@/components/StackSectionLight'
|
||||
import { Companies } from '@/components/Companies'
|
||||
import { CallToAction } from '@/components/CallToAction'
|
||||
@@ -36,7 +36,7 @@ export default function Home() {
|
||||
<Companies />
|
||||
</section>
|
||||
<section id="clickable-gallery">
|
||||
<ClickableGallery />
|
||||
<ClickableGalleryLight />
|
||||
</section>
|
||||
<section id="bento-reviews">
|
||||
<BentoReviews />
|
||||
|
||||
179
src/components/ClickableGalleryLight.tsx
Normal file
179
src/components/ClickableGalleryLight.tsx
Normal 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 intelligence—yours.
|
||||
</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>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useRef, useState } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { AnimatePresence, motion } from 'framer-motion'
|
||||
import clsx from 'clsx'
|
||||
@@ -60,11 +60,39 @@ function NavLinks() {
|
||||
}
|
||||
|
||||
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 (
|
||||
<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">
|
||||
<NavLinks />
|
||||
</div>
|
||||
</header>
|
||||
</motion.header>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,77 +1,58 @@
|
||||
'use client'
|
||||
|
||||
import { useState } 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'
|
||||
|
||||
import { useRef, useEffect } from 'react'
|
||||
|
||||
export function HomeHeroLight2() {
|
||||
const videoRef = useRef<HTMLVideoElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (videoRef.current) videoRef.current.playbackRate = 0.4
|
||||
}, [])
|
||||
|
||||
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">
|
||||
<div className="mx-auto max-w-7xl px-6 lg:px-8 grid grid-cols-1">
|
||||
<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>
|
||||
{/* Global soft wash + blur */}
|
||||
<div className="absolute inset-0 bg-white opacity-30 backdrop-blur-md z-0" />
|
||||
|
||||
<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>
|
||||
<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>
|
||||
{/* Center “halo” for text legibility */}
|
||||
<div
|
||||
className="absolute inset-0 z-0"
|
||||
style={{
|
||||
background:
|
||||
'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%)'
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
{/* 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
|
||||
alt="App screenshot"
|
||||
src="/images/mchip.webp"
|
||||
width={2432}
|
||||
height={1442}
|
||||
className="col-start-1 row-start-1 relative top-12"
|
||||
/>
|
||||
</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"
|
||||
/>
|
||||
<p
|
||||
className="mt-8 text-lg sm:text-xl font-medium text-gray-800/90 leading-relaxed"
|
||||
style={{ textShadow: '0 1px 4px rgba(0,0,0,0.06)' }}
|
||||
>
|
||||
Mycelium's advancements in Agentic infrastructure support private, secure, and
|
||||
autonomous Agents that connect, learn, and grow with you.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ export function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<HeaderLight />
|
||||
{children}
|
||||
<main className="flex-auto">{children}</main>
|
||||
<Footer />
|
||||
</>
|
||||
|
||||
@@ -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">
|
||||
<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
|
||||
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) */}
|
||||
<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>
|
||||
<H2 className="" color="dark">
|
||||
The Mycelium Stack
|
||||
|
||||
@@ -30,7 +30,7 @@ export function Steps() {
|
||||
const isInView = useInView(ref, { once: true });
|
||||
|
||||
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">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
|
||||
Reference in New Issue
Block a user