Files
www_projectmycelium_com/src/pages/cloud/CloudFeaturesLight.tsx

284 lines
8.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { Fragment, useEffect, useRef, useState } from 'react'
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from '@headlessui/react'
import clsx from 'clsx'
import {
type MotionProps,
type Variant,
AnimatePresence,
motion,
} from 'framer-motion'
import { useDebouncedCallback } from 'use-debounce'
import {
Eyebrow,
FeatureDescription,
FeatureTitle,
MobileFeatureTitle,
P,
SectionHeader,
} from '@/components/Texts'
import { Container } from '@/components/Container'
import reservenodeimg from '/images/cloud/reserve.png'
import billingImg from '/images/cloud/billing.png'
import kubeconfigImg from '/images/cloud/kubeconfig.png'
/* Feature Data */
const features = [
{
name: 'Decentralized Kubernetes',
description:
"Reserve a node and deploy sovereign Kubernetes clusters on decentralized infrastructure.",
screen: () => (
<img
src={reservenodeimg}
className="rounded-xl shadow-xl ring-1 ring-gray-300"
/>
),
},
{
name: 'Manage Your Cluster',
description:
'Manage your cluster with ease, with a simple and intuitive interface.',
screen: () => (
<img
src={kubeconfigImg}
className="rounded-xl shadow-xl ring-1 ring-gray-300"
/>
),
},
{
name: 'Personalised Billings & Accounts',
description:
'Easily manage your cluster billing and accounts with personalised configurations.',
screen: () => (
<img
src={billingImg}
className="rounded-xl shadow-xl ring-1 ring-gray-300"
/>
),
},
]
interface CustomAnimationProps {
isForwards: boolean
changeCount: number
}
const maxZIndex = 2147483647
const bodyVariantBackwards: Variant = {
opacity: 0.4,
scale: 0.8,
zIndex: 0,
filter: 'blur(4px)',
transition: { duration: 0.4 },
}
const bodyAnimation: MotionProps = {
initial: 'initial',
animate: 'animate',
exit: 'exit',
variants: {
initial: (custom: CustomAnimationProps) =>
custom.isForwards
? {
y: '100%',
zIndex: maxZIndex - custom.changeCount,
transition: { duration: 0.4 },
}
: bodyVariantBackwards,
animate: (custom: CustomAnimationProps) => ({
y: '0%',
opacity: 1,
scale: 1,
zIndex: maxZIndex / 2 - custom.changeCount,
filter: 'blur(0px)',
transition: { duration: 0.4 },
}),
exit: (custom: CustomAnimationProps) =>
custom.isForwards
? bodyVariantBackwards
: {
y: '100%',
zIndex: maxZIndex - custom.changeCount,
transition: { duration: 0.4 },
},
},
}
function usePrevious<T>(value: T) {
const ref = useRef<T>()
useEffect(() => {
ref.current = value
}, [value])
return ref.current
}
/* Desktop Component */
function CloudFeaturesDesktop() {
let [changeCount, setChangeCount] = useState(0)
let [selectedIndex, setSelectedIndex] = useState(0)
let prevIndex = usePrevious(selectedIndex)
let isForwards = prevIndex === undefined ? true : selectedIndex > prevIndex
let onChange = useDebouncedCallback(
(selectedIndex: number) => {
setSelectedIndex(selectedIndex)
setChangeCount((changeCount) => changeCount + 1)
},
100,
{ leading: true },
)
return (
<TabGroup
vertical
className="grid grid-cols-12 items-center gap-10"
selectedIndex={selectedIndex}
onChange={onChange}
>
<TabList className="col-span-6 space-y-6 pl-4 lg:pl-6">
{features.map((feature, featureIndex) => (
<div
key={feature.name}
className={clsx(
'relative rounded-2xl outline-2 transition-all duration-300 ease-in-out hover:scale-105 hover:bg-gray-100',
selectedIndex === featureIndex
? 'outline-cyan-500'
: 'outline-transparent hover:outline-cyan-500',
)}
>
{featureIndex === selectedIndex && (
<motion.div
layoutId="activeBackground"
className="absolute inset-0 bg-white shadow"
initial={{ borderRadius: 16 }}
/>
)}
<div className="relative z-10 p-8">
<FeatureTitle as="h3" className="text-gray-900">
<Tab className="text-left data-selected:not-data-focus:outline-hidden">
<span className="absolute inset-0 rounded-2xl" />
{feature.name}
</Tab>
</FeatureTitle>
<FeatureDescription className="mt-2 text-gray-600">
{feature.description}
</FeatureDescription>
</div>
</div>
))}
</TabList>
<div className="relative col-span-6">
<TabPanels as={Fragment}>
<AnimatePresence
initial={false}
custom={{ isForwards, changeCount }}
>
{features.map((feature, featureIndex) =>
selectedIndex === featureIndex ? (
<TabPanel
static
key={feature.name + changeCount}
className="col-start-1 row-start-1 flex focus:outline-offset-32 data-selected:not-data-focus:outline-hidden"
>
<motion.div
{...bodyAnimation}
custom={{ isForwards, changeCount }}
className="w-full flex justify-center"
>
<feature.screen />
</motion.div>
</TabPanel>
) : null,
)}
</AnimatePresence>
</TabPanels>
</div>
</TabGroup>
)
}
/* Mobile Version */
function CloudFeaturesMobile() {
return (
<>
<div className="flex snap-x overflow-x-auto space-x-4 pb-4 scrollbar-hide">
{features.map((feature, i) => (
<div key={i} className="w-full flex-none snap-center px-4">
<div className="rounded-2xl bg-white shadow ring-1 ring-gray-200 p-6">
<div className="w-full max-w-[366px] mx-auto">
<feature.screen />
</div>
<div className="mt-6">
<MobileFeatureTitle className="text-gray-900">
{feature.name}
</MobileFeatureTitle>
<FeatureDescription className="mt-2 text-gray-600">
{feature.description}
</FeatureDescription>
</div>
</div>
</div>
))}
</div>
</>
)
}
/* ✅ FINAL LIGHT MODE EXPORT — BOXED CONTAINER + BORDERS MATCHING CloudHeroNew */
export function CloudFeaturesLight() {
return (
<div className="">
{/* ✅ Top horizontal line with spacing */}
<div className="max-w-7xl bg-transparent mx-auto py-6 border border-t-0 border-b-0 border-gray-100"></div>
<div className="w-full border-t border-l border-r border-gray-100" />
{/* ✅ Boxed container (border-x only) */}
<div className="relative mx-auto max-w-7xl border border-t-0 border-b-0 border-gray-100 bg-white">
<section className="px-6 py-16 lg:py-16">
<Container>
<div className="max-w-4xl mx-auto items-center text-center">
<Eyebrow color="accent">Platform Overview</Eyebrow>
<SectionHeader className="mt-2 text-gray-900">
A Decentralized Cloud that Operates Itself
</SectionHeader>
<P className="mt-6 text-gray-600">
Mycelium Cloud runs Kubernetes on a sovereign, self-healing network
with compute, storage, and networking built in so you dont need
external cloud dependencies.
</P>
</div>
</Container>
<div className="hidden md:block mt-12">
<CloudFeaturesDesktop />
</div>
<div className="md:hidden mt-12">
<CloudFeaturesMobile />
</div>
</section>
</div>
{/* ✅ Bottom horizontal line */}
<div className="w-full border-b border-gray-100" />
{/* ✅ Bottom spacer matching hero */}
<div className="max-w-7xl bg-transparent mx-auto py-6 border border-t-0 border-b-0 border-gray-100"></div>
</div>
)
}