feat: add light-themed cloud features section with boxed layout

- Created CloudFeaturesLight component with desktop/mobile responsive views
- Implemented tabbed interface for feature showcase with hover effects
- Added bordered container layout matching CloudHeroNew design system
This commit is contained in:
2025-11-06 21:39:14 +01:00
parent 15e81cb5cd
commit 2e22ed9683
2 changed files with 256 additions and 1 deletions

View File

@@ -0,0 +1,250 @@
'use client'
import { Fragment, useEffect, useId, 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'
/* ICONS */
function DeviceUserIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
return (
<svg viewBox="0 0 32 32" aria-hidden="true" {...props}>
<circle cx={16} cy={16} r={16} fill="#E5E7EB" />
<path
fillRule="evenodd"
clipRule="evenodd"
d="M16 23a3 3 0 100-6 3 3 0 000 6zm-1 2a4 4 0 00-4 4v1a2 2 0 002 2h6a2 2 0 002-2v-1a4 4 0 00-4-4h-2z"
fill="#6B7280"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M5 4a4 4 0 014-4h14a4 4 0 014 4v24a4.002 4.002 0 01-3.01 3.877c-.535.136-.99-.325-.99-.877s.474-.98.959-1.244A2 2 0 0025 28V4a2 2 0 00-2-2h-1.382a1 1 0 00-.894.553l-.448.894a1 1 0 01-.894.553h-6.764a1 1 0 01-.894-.553l-.448-.894A1 1 0 0010.382 2H9a2 2 0 00-2 2v24a2 2 0 001.041 1.756C8.525 30.02 9 30.448 9 31s-.455 1.013-.99.877A4.002 4.002 0 015 28V4z"
fill="#9CA3AF"
/>
</svg>
)
}
function DeviceNotificationIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
return (
<svg viewBox="0 0 32 32" aria-hidden="true" {...props}>
<circle cx={16} cy={16} r={16} fill="#E5E7EB" />
<path
fillRule="evenodd"
clipRule="evenodd"
d="M9 0a4 4 0 00-4 4v24a4 4 0 004 4h14a4 4 0 004-4V4a4 4 0 00-4-4H9zm0 2a2 2 0 00-2 2v24a2 2 0 002 2h14a2 2 0 002-2V4a2 2 0 00-2-2h-1.382a1 1 0 00-.894.553l-.448.894a1 1 0 01-.894.553h-6.764a1 1 0 01-.894-.553l-.448-.894A1 1 0 0010.382 2H9z"
fill="#9CA3AF"
/>
<path d="M9 8a2 2 0 012-2h10a2 2 0 012 2v2a2 2 0 01-2 2H11a2 2 0 01-2-2V8z" fill="#6B7280" />
</svg>
)
}
function DeviceTouchIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
let id = useId()
return (
<svg viewBox="0 0 32 32" fill="none" aria-hidden="true" {...props}>
<defs>
<linearGradient id={`${id}-gradient`} x1={14} y1={14.5} x2={7} y2={17}>
<stop stopColor="#6B7280" />
<stop offset={1} stopColor="#D1D5DB" stopOpacity={0} />
</linearGradient>
</defs>
<circle cx={16} cy={16} r={16} fill="#E5E7EB" />
<path
fill="#9CA3AF"
fillRule="evenodd"
clipRule="evenodd"
d="M5 4a4 4 0 014-4h14a4 4 0 014 4v13h-2V4a2 2 0 00-2-2h-1.382a1 1 0 00-.894.553l-.448.894a1 1 0 01-.894.553h-6.764a1 1 0 01-.894-.553l-.448-.894A1 1 0 0010.382 2H9a2 2 0 00-2 2v24a2 2 0 002 2h4v2H9a4 4 0 01-4-4V4z"
/>
<path d="M7 22c0-4.694 3.5-8 8-8" stroke={`url(#${id}-gradient)`} strokeWidth={2} strokeLinecap="round" />
<path
d="M21 20l.217-5.513a1.431 1.431 0 00-2.85-.226L17.5 21.5l-1.51-1.51a2.107 2.107 0 00-2.98 0 .024.024 0 00-.005.024l3.083 9.25A4 4 0 0019.883 32H25a4 4 0 004-4v-5a3 3 0 00-3-3h-5z"
fill="#9CA3AF"
/>
</svg>
)
}
/* Feature Data */
const features = [
{
name: 'Decentralized Kubernetes',
description:
"Reserve a node and deploy sovereign Kubernetes clusters on decentralized infrastructure.",
icon: DeviceUserIcon,
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.',
icon: DeviceNotificationIcon,
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.',
icon: DeviceTouchIcon,
screen: () => (
<img
src={billingImg}
className="rounded-xl shadow-xl ring-1 ring-gray-300"
/>
),
},
]
/* Desktop Component */
function CloudFeaturesDesktop() {
const [selectedIndex, setSelectedIndex] = useState(0)
return (
<TabGroup vertical className="grid grid-cols-12 gap-10">
<TabList className="col-span-6 space-y-6 pl-4 sm:pl-6 lg:pl-8">
{features.map((feature, i) => (
<div
key={feature.name}
className={clsx(
'relative rounded-2xl transition-all hover:scale-[1.02] hover:bg-gray-100',
selectedIndex === i
? 'ring-2 ring-cyan-500 bg-white shadow'
: 'ring-1 ring-transparent',
)}
>
<div className="p-8">
<feature.icon className="h-8 w-8" />
<FeatureTitle as="h3" className="mt-6 text-gray-900">
<Tab>{feature.name}</Tab>
</FeatureTitle>
<FeatureDescription className="mt-2 text-gray-600">
{feature.description}
</FeatureDescription>
</div>
</div>
))}
</TabList>
<div className="col-span-6">
<TabPanels as={Fragment}>
{features.map((feature, i) => (
<TabPanel key={feature.name}>
<div className="w-full flex justify-center">
<feature.screen />
</div>
</TabPanel>
))}
</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">
<feature.icon className="h-8 w-8" />
<MobileFeatureTitle className="mt-4 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-200"></div>
<div className="w-full border-t border-l border-r border-gray-200" />
{/* ✅ Boxed container (border-x only) */}
<div className="relative mx-auto max-w-7xl border border-t-0 border-b-0 border-gray-200 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-20">
<CloudFeaturesDesktop />
</div>
<div className="md:hidden mt-16">
<CloudFeaturesMobile />
</div>
</section>
</div>
{/* ✅ Bottom horizontal line */}
<div className="w-full border-b border-gray-200" />
{/* ✅ Bottom spacer matching hero */}
<div className="max-w-7xl bg-transparent mx-auto py-6 border border-t-0 border-b-0 border-gray-200"></div>
</div>
)
}

View File

@@ -3,10 +3,11 @@ import { CloudArchitecture } from './CloudArchitecture'
import { CloudFeatures } from './CloudFeatures' import { CloudFeatures } from './CloudFeatures'
import { CloudUseCases } from './CloudUseCases' import { CloudUseCases } from './CloudUseCases'
import { CloudHeroNew } from './CloudHeroNew' import { CloudHeroNew } from './CloudHeroNew'
import { CloudHosting } from './CloudHosting'
import { CloudBluePrint } from './CloudBluePrint' import { CloudBluePrint } from './CloudBluePrint'
import { CallToAction } from './CalltoAction' import { CallToAction } from './CalltoAction'
import { CloudHostingNew } from './CloudHostingNew' import { CloudHostingNew } from './CloudHostingNew'
import { CloudFeaturesLight } from './CloudFeaturesLight'
export default function CloudPage() { export default function CloudPage() {
return ( return (
@@ -20,6 +21,10 @@ export default function CloudPage() {
<CloudHostingNew /> <CloudHostingNew />
</AnimatedSection> </AnimatedSection>
<AnimatedSection>
<CloudFeaturesLight />
</AnimatedSection>
<AnimatedSection> <AnimatedSection>
<CloudFeatures /> <CloudFeatures />
</AnimatedSection> </AnimatedSection>