forked from emre/www_projectmycelium_com
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:
250
src/pages/cloud/CloudFeaturesLight.tsx
Normal file
250
src/pages/cloud/CloudFeaturesLight.tsx
Normal 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 don’t 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user