feat: redesign cloud features section with new UI and content

- Replaced mobile phone mockups with full-width desktop screenshots for cloud platform features
- Updated feature descriptions to focus on cloud/Kubernetes capabilities instead of network features
- Changed section layout to improve desktop view with left-aligned feature tabs
- Removed unused phone frame component and related mobile UI elements
- Updated image assets from jpg to png format and reorganized image paths
- Reordered page
This commit is contained in:
2025-10-31 04:40:14 +01:00
parent 26ae2f156a
commit bfe3c1e4bd
8 changed files with 61 additions and 55 deletions

View File

Before

Width:  |  Height:  |  Size: 878 KiB

After

Width:  |  Height:  |  Size: 878 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

View File

@@ -11,7 +11,6 @@ import {
} from 'framer-motion' } from 'framer-motion'
import { useDebouncedCallback } from 'use-debounce' import { useDebouncedCallback } from 'use-debounce'
import { AppScreen } from '../network/AppScreen'
import { import {
Eyebrow, Eyebrow,
FeatureDescription, FeatureDescription,
@@ -23,10 +22,9 @@ import {
import { CircleBackground } from '@/components/CircleBackground' import { CircleBackground } from '@/components/CircleBackground'
import { Container } from '@/components/Container' import { Container } from '@/components/Container'
import connectorImg from '@/images/connector.png' import reservenodeimg from '/images/cloud/reserve.png'
import peersImg from '@/images/peers.png' import billingImg from '/images/cloud/billing.png'
import settingImg from '@/images/setting.png' import kubeconfigImg from '/images/cloud/kubeconfig.png'
import { PhoneFrame } from '@/components/PhoneFrame'
interface CustomAnimationProps { interface CustomAnimationProps {
@@ -36,25 +34,25 @@ interface CustomAnimationProps {
const features = [ const features = [
{ {
name: 'Mycelium Connector', name: 'Decentralized Kubernetes',
description: description:
"Start (and stop) your Mycelium connector to gain access to sites, apps, and workloads available exclusively on the Mycelium Network. View statistics around peers and traffic.", "Reserve a node and deploy sovereign Kubernetes clusters on decentralized infrastructure.",
icon: DeviceUserIcon, icon: DeviceUserIcon,
screen: InviteScreen, screen: ReserveNodeScreen,
}, },
{ {
name: 'Mycelium Peers', name: 'Manage Your Cluster',
description: description:
'Search and discover active peers on the Mycelium Network, or add your own.', 'Manage your cluster with ease, with a simple and intuitive interface.',
icon: DeviceNotificationIcon, icon: DeviceNotificationIcon,
screen: StocksScreen, screen: ManageClusterScreen,
}, },
{ {
name: 'Network Setting', name: 'Personalised Billings & Accounts',
description: description:
'Find version and network information and trigger light or dark mode.', 'Easily manage your cluster billing and accounts with personalised configurations.',
icon: DeviceTouchIcon, icon: DeviceTouchIcon,
screen: InvestScreen, screen: PersonalisedBillingsScreen,
}, },
] ]
@@ -182,28 +180,40 @@ const bodyAnimation: MotionProps = {
} }
function InviteScreen() { function ReserveNodeScreen() {
return ( return (
<AppScreen className="w-full"> <img
<img src={connectorImg} alt="Mycelium Connector" width="366" height="732" className="-mt-8" /> src={reservenodeimg}
</AppScreen> alt="Mycelium Reserve Node"
) width={2432}
height={1442}
className="w-4xl max-w-none rounded-xl shadow-xl ring-1 ring-gray-400/10 sm:w-240 md:-ml-4 lg:ml-0"
/>
);
} }
function StocksScreen() { function ManageClusterScreen() {
return ( return (
<AppScreen className="w-full"> <img
<img src={peersImg} alt="Mycelium Peers" width="366" height="732" className="-mt-8" /> src={kubeconfigImg}
</AppScreen> alt="Mycelium Kubeconfig"
) width={2432}
height={1442}
className="w-4xl max-w-none rounded-xl shadow-xl ring-1 ring-gray-400/10 sm:w-240 md:-ml-4 lg:ml-0"
/>
);
} }
function InvestScreen() { function PersonalisedBillingsScreen() {
return ( return (
<AppScreen className="w-full"> <img
<img src={settingImg} alt="Mycelium Settings" width="366" height="732" className="-mt-8" /> src={billingImg}
</AppScreen> alt="Mycelium Billing"
) width={2432}
height={1442}
className="w-4xl max-w-none rounded-xl shadow-xl ring-1 ring-gray-400/10 sm:w-240 md:-ml-4 lg:ml-0"
/>
);
} }
function usePrevious<T>(value: T) { function usePrevious<T>(value: T) {
@@ -216,7 +226,7 @@ function usePrevious<T>(value: T) {
return ref.current return ref.current
} }
function FeaturesDesktop() { function CloudFeaturesDesktop() {
let [changeCount, setChangeCount] = useState(0) let [changeCount, setChangeCount] = useState(0)
let [selectedIndex, setSelectedIndex] = useState(0) let [selectedIndex, setSelectedIndex] = useState(0)
let prevIndex = usePrevious(selectedIndex) let prevIndex = usePrevious(selectedIndex)
@@ -238,12 +248,12 @@ function FeaturesDesktop() {
onChange={onChange} onChange={onChange}
vertical vertical
> >
<TabList className="z-10 order-last col-span-6 space-y-6"> <TabList className="z-10 col-span-6 space-y-6 pl-4 sm:pl-6 lg:pl-8">
{features.map((feature, featureIndex) => ( {features.map((feature, featureIndex) => (
<div <div
key={feature.name} key={feature.name}
className={clsx( className={clsx(
'relative rounded-2xl outline-2 transition-all duration-300 ease-in-out hover:scale-105 hover:bg-gray-800/30', 'relative rounded-2xl outline-2 transition-all duration-300 ease-in-out hover:scale-105 hover:bg-gray-800/30 ml-16',
selectedIndex === featureIndex selectedIndex === featureIndex
? 'outline-cyan-500' ? 'outline-cyan-500'
: 'outline-transparent hover:outline-cyan-500', : 'outline-transparent hover:outline-cyan-500',
@@ -252,7 +262,7 @@ function FeaturesDesktop() {
{featureIndex === selectedIndex && ( {featureIndex === selectedIndex && (
<motion.div <motion.div
layoutId="activeBackground" layoutId="activeBackground"
className="absolute inset-0 bg-gray-800" className="absolute inset-0 bg-gray-800 "
initial={{ borderRadius: 16 }} initial={{ borderRadius: 16 }}
/> />
)} )}
@@ -271,11 +281,11 @@ function FeaturesDesktop() {
</div> </div>
))} ))}
</TabList> </TabList>
<div className="relative col-span-6"> <div className="relative col-span-6 overflow-hidden">
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"> <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
<CircleBackground id="primaryfeatures_desktop_circle" color="#13B5C8" className="animate-spin-slower" /> <CircleBackground id="primaryfeatures_desktop_circle" color="#13B5C8" className="animate-spin-slower" />
</div> </div>
<PhoneFrame className="z-10 mx-auto w-full max-w-[366px]"> <div className="z-10 mx-auto w-full max-w-[366px]">
<TabPanels as={Fragment}> <TabPanels as={Fragment}>
<AnimatePresence <AnimatePresence
initial={false} initial={false}
@@ -296,13 +306,13 @@ function FeaturesDesktop() {
)} )}
</AnimatePresence> </AnimatePresence>
</TabPanels> </TabPanels>
</PhoneFrame> </div>
</div> </div>
</TabGroup> </TabGroup>
) )
} }
function FeaturesMobile() { function CloudFeaturesMobile() {
let [activeIndex, setActiveIndex] = useState(0) let [activeIndex, setActiveIndex] = useState(0)
let slideContainerRef = useRef<React.ElementRef<'div'>>(null) let slideContainerRef = useRef<React.ElementRef<'div'>>(null)
let slideRefs = useRef<Array<React.ElementRef<'div'>>>([]) let slideRefs = useRef<Array<React.ElementRef<'div'>>>([])
@@ -361,9 +371,9 @@ function FeaturesMobile() {
className={featureIndex % 2 === 1 ? 'rotate-180' : undefined} className={featureIndex % 2 === 1 ? 'rotate-180' : undefined}
/> />
</div> </div>
<PhoneFrame className="relative mx-auto w-full max-w-[366px]"> <div className="relative mx-auto w-full max-w-[366px]">
<feature.screen /> <feature.screen />
</PhoneFrame> </div>
<div className="absolute inset-x-0 bottom-0 bg-gray-800/95 p-6 backdrop-blur-sm sm:p-10"> <div className="absolute inset-x-0 bottom-0 bg-gray-800/95 p-6 backdrop-blur-sm sm:p-10">
<feature.icon className="h-8 w-8" /> <feature.icon className="h-8 w-8" />
<MobileFeatureTitle color="white" className="mt-6"> <MobileFeatureTitle color="white" className="mt-6">
@@ -405,29 +415,27 @@ function FeaturesMobile() {
export function CloudFeatures() { export function CloudFeatures() {
return ( return (
<section <section
id="howitworks" id="overview"
aria-label="Features for investing all your money" aria-label="Features for investing all your money"
className="bg-gray-900 py-20 sm:py-32" className="bg-gray-900 py-20 sm:py-32"
> >
<Container> <Container>
<div className="mx-auto max-w-2xl lg:mx-0 lg:max-w-3xl"> <div className="mx-auto max-w-2xl lg:mx-0 lg:max-w-none">
<Eyebrow color="accent">How It Works</Eyebrow> <Eyebrow color="accent">Platform Overview</Eyebrow>
<SectionHeader color="white" className="mt-2"> <SectionHeader color="white" className="mt-2">
How Mycelium Operates A Decentralized Cloud that Operates Itself
</SectionHeader> </SectionHeader>
<P color="light" className="mt-6"> <P color="light" className="mt-6">
Mycelium, like its natural namesake, thrives on decentralization, Mycelium Cloud orchestrates Kubernetes clusters on the ThreeFold Grid with cryptographic certainty. Networking, storage, and orchestration are all built-in so developers can deploy critical workloads without wrestling infrastructure.
efficiency, and security, making it a truly powerful force in the world
of decentralized networks.
</P> </P>
</div> </div>
</Container> </Container>
<div className="hidden md:mt-20 md:block">
<CloudFeaturesDesktop />
</div>
<div className="mt-16 md:hidden"> <div className="mt-16 md:hidden">
<FeaturesMobile /> <CloudFeaturesMobile />
</div> </div>
<Container className="hidden md:mt-20 md:block">
<FeaturesDesktop />
</Container>
</section> </section>
) )
} }

View File

@@ -1,5 +1,4 @@
import { AnimatedSection } from '../../components/AnimatedSection' import { AnimatedSection } from '../../components/AnimatedSection'
import { CloudHero } from './CloudHero'
import { CloudOverview } from './CloudOverview' import { CloudOverview } from './CloudOverview'
import { CloudArchitecture } from './CloudArchitecture' import { CloudArchitecture } from './CloudArchitecture'
import { CloudFeatures } from './CloudFeatures' import { CloudFeatures } from './CloudFeatures'
@@ -17,14 +16,13 @@ export default function CloudPage() {
<CloudHeroNew /> <CloudHeroNew />
</AnimatedSection> </AnimatedSection>
<AnimatedSection> <AnimatedSection>
<CloudOverview /> <CloudFeatures />
</AnimatedSection> </AnimatedSection>
<AnimatedSection> <AnimatedSection>
<CloudArchitecture /> <CloudArchitecture />
</AnimatedSection> </AnimatedSection>
<AnimatedSection>
<CloudFeatures />
</AnimatedSection>
<AnimatedSection> <AnimatedSection>
<CloudGettingStarted /> <CloudGettingStarted />
</AnimatedSection> </AnimatedSection>