Compare commits
2 Commits
0837a8313c
...
26ae2f156a
| Author | SHA1 | Date | |
|---|---|---|---|
| 26ae2f156a | |||
| 33c940c604 |
BIN
public/images/cloudhero.webp
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
public/images/cloudhero2.webp
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
public/images/cloudhero3.webp
Normal file
|
After Width: | Height: | Size: 938 KiB |
BIN
public/images/cloudui/billing.jpg
Normal file
|
After Width: | Height: | Size: 878 KiB |
BIN
public/images/cloudui/kubeconfig.jpg
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
public/images/cloudui/reserve.jpg
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
public/images/k82.png
Normal file
|
After Width: | Height: | Size: 910 KiB |
@@ -13,6 +13,7 @@ const colorVariants = {
|
|||||||
secondary: 'text-gray-600',
|
secondary: 'text-gray-600',
|
||||||
light: 'text-gray-50',
|
light: 'text-gray-50',
|
||||||
accent: 'text-cyan-500',
|
accent: 'text-cyan-500',
|
||||||
|
cyan: 'text-cyan-50',
|
||||||
white: 'text-white',
|
white: 'text-white',
|
||||||
dark: 'text-gray-950',
|
dark: 'text-gray-950',
|
||||||
tertiary: 'text-gray-700',
|
tertiary: 'text-gray-700',
|
||||||
@@ -33,7 +34,8 @@ type PolymorphicProps<E extends React.ElementType, P> = P & {
|
|||||||
|
|
||||||
const createTextComponent = <DefaultElement extends React.ElementType>(
|
const createTextComponent = <DefaultElement extends React.ElementType>(
|
||||||
defaultElement: DefaultElement,
|
defaultElement: DefaultElement,
|
||||||
defaultClassName: string
|
defaultClassName: string,
|
||||||
|
defaultProps: Omit<TextOwnProps, 'className' | 'children'> = {}
|
||||||
) => {
|
) => {
|
||||||
type Props<E extends React.ElementType = DefaultElement> = PolymorphicProps<
|
type Props<E extends React.ElementType = DefaultElement> = PolymorphicProps<
|
||||||
E,
|
E,
|
||||||
@@ -41,9 +43,9 @@ const createTextComponent = <DefaultElement extends React.ElementType>(
|
|||||||
>
|
>
|
||||||
|
|
||||||
function Text<E extends React.ElementType = DefaultElement>({
|
function Text<E extends React.ElementType = DefaultElement>({
|
||||||
font = 'sans',
|
font = defaultProps.font || 'sans',
|
||||||
as,
|
as,
|
||||||
color = 'primary',
|
color = defaultProps.color || 'primary',
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
@@ -104,7 +106,8 @@ export const H5 = createTextComponent(
|
|||||||
)
|
)
|
||||||
export const Eyebrow = createTextComponent(
|
export const Eyebrow = createTextComponent(
|
||||||
'h2',
|
'h2',
|
||||||
'text-base/7 font-semibold tracking-wider uppercase'
|
'text-base/7 font-semibold tracking-[0.18em] uppercase',
|
||||||
|
{ color: 'accent' }
|
||||||
)
|
)
|
||||||
export const SectionHeader = createTextComponent(
|
export const SectionHeader = createTextComponent(
|
||||||
'p',
|
'p',
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export function CloudArchitecture() {
|
|||||||
<section className="bg-white py-24 sm:py-32">
|
<section className="bg-white py-24 sm:py-32">
|
||||||
<Container>
|
<Container>
|
||||||
<div className="mx-auto max-w-3xl text-center">
|
<div className="mx-auto max-w-3xl text-center">
|
||||||
<Eyebrow className="tracking-[0.32em] uppercase text-cyan-500">
|
<Eyebrow>
|
||||||
Technical Architecture
|
Technical Architecture
|
||||||
</Eyebrow>
|
</Eyebrow>
|
||||||
<SectionHeader as="h2" className="mt-6 text-gray-900">
|
<SectionHeader as="h2" className="mt-6 text-gray-900">
|
||||||
|
|||||||
@@ -1,124 +1,432 @@
|
|||||||
import { Container } from '../../components/Container'
|
'use client'
|
||||||
import { Eyebrow, SectionHeader, P, Small } from '../../components/Texts'
|
|
||||||
|
|
||||||
const featureSections = [
|
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 { AppScreen } from '../network/AppScreen'
|
||||||
|
import {
|
||||||
|
Eyebrow,
|
||||||
|
FeatureDescription,
|
||||||
|
FeatureTitle,
|
||||||
|
MobileFeatureTitle,
|
||||||
|
P,
|
||||||
|
SectionHeader,
|
||||||
|
} from '@/components/Texts'
|
||||||
|
import { CircleBackground } from '@/components/CircleBackground'
|
||||||
|
import { Container } from '@/components/Container'
|
||||||
|
|
||||||
|
import connectorImg from '@/images/connector.png'
|
||||||
|
import peersImg from '@/images/peers.png'
|
||||||
|
import settingImg from '@/images/setting.png'
|
||||||
|
import { PhoneFrame } from '@/components/PhoneFrame'
|
||||||
|
|
||||||
|
|
||||||
|
interface CustomAnimationProps {
|
||||||
|
isForwards: boolean
|
||||||
|
changeCount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const features = [
|
||||||
{
|
{
|
||||||
title: 'Mycelium Networking',
|
name: 'Mycelium Connector',
|
||||||
description:
|
description:
|
||||||
'Ultra-fast, decentralized networking inspired by nature to keep services reachable without exposing surfaces.',
|
"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.",
|
||||||
bullets: [
|
icon: DeviceUserIcon,
|
||||||
'End-to-end encrypted mesh connectivity between every node.',
|
screen: InviteScreen,
|
||||||
'Direct node communication without centralized intermediaries.',
|
|
||||||
'Self-optimizing routes that heal around failures automatically.',
|
|
||||||
'Secure peer-to-peer communication across the entire grid.',
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Zero-Image Technology',
|
name: 'Mycelium Peers',
|
||||||
description:
|
description:
|
||||||
'Metadata-first zero-images shrink artifacts up to 100x, reducing deployment time and bandwidth.',
|
'Search and discover active peers on the Mycelium Network, or add your own.',
|
||||||
bullets: [
|
icon: DeviceNotificationIcon,
|
||||||
'Deterministic deployments verified cryptographically.',
|
screen: StocksScreen,
|
||||||
'Run containers, VMs, and Linux workloads with secure boot.',
|
|
||||||
'Smart contract orchestration manages every workload lifecycle.',
|
|
||||||
'Minimal artifact footprint accelerates delivery everywhere.',
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Quantum-Safe Storage (QSS)',
|
name: 'Network Setting',
|
||||||
description:
|
description:
|
||||||
'Quantum-resistant encryption secures data beyond the application layer for complete ownership.',
|
'Find version and network information and trigger light or dark mode.',
|
||||||
bullets: [
|
icon: DeviceTouchIcon,
|
||||||
'Self-healing storage recovers instantly from corruption or failure.',
|
screen: InvestScreen,
|
||||||
'Serve the same data via IPFS, S3, WebDAV, HTTP, and native FS.',
|
|
||||||
'Geo-aware placement enforces residency and redundancy policies.',
|
|
||||||
'Autonomous replication keeps data resilient across the globe.',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Multi-Master Clusters',
|
|
||||||
description:
|
|
||||||
'High-availability Kubernetes with automatic failover and leadership orchestration.',
|
|
||||||
bullets: [
|
|
||||||
'Multi-master topologies orchestrated with zero downtime.',
|
|
||||||
'Automatic failover keeps control planes healthy and responsive.',
|
|
||||||
'HA operations managed without manual intervention or scripts.',
|
|
||||||
'Upgrades roll out seamlessly with continuous verification.',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Effortless Load Balancing & Scaling',
|
|
||||||
description:
|
|
||||||
'Adaptive automation balances traffic and scales workloads based on demand.',
|
|
||||||
bullets: [
|
|
||||||
'Built-in autoscaling that responds to real-time usage.',
|
|
||||||
'Native load balancing distributes traffic globally.',
|
|
||||||
'High availability delivered without custom controllers.',
|
|
||||||
'Elastic scaling keeps costs aligned with workload demand.',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Simple Web Gateway Access',
|
|
||||||
description:
|
|
||||||
'Expose services to the public web with declarative Kubernetes resources.',
|
|
||||||
bullets: [
|
|
||||||
'One resource publishes services without complex ingress rules.',
|
|
||||||
'Domain and prefix-based routing built into the platform.',
|
|
||||||
'No need to manage dedicated ingress controllers.',
|
|
||||||
'Consistent configuration across every cluster and region.',
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
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="#A3A3A3" fillOpacity={0.2} />
|
||||||
|
<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="#737373"
|
||||||
|
/>
|
||||||
|
<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="#A3A3A3"
|
||||||
|
/>
|
||||||
|
</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="#A3A3A3" fillOpacity={0.2} />
|
||||||
|
<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="#A3A3A3"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M9 8a2 2 0 012-2h10a2 2 0 012 2v2a2 2 0 01-2 2H11a2 2 0 01-2-2V8z"
|
||||||
|
fill="#737373"
|
||||||
|
/>
|
||||||
|
</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}
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
>
|
||||||
|
<stop stopColor="#737373" />
|
||||||
|
<stop offset={1} stopColor="#D4D4D4" stopOpacity={0} />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<circle cx={16} cy={16} r={16} fill="#A3A3A3" fillOpacity={0.2} />
|
||||||
|
<path
|
||||||
|
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"
|
||||||
|
fill="#A3A3A3"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M7 22c0-4.694 3.5-8 8-8"
|
||||||
|
stroke={`url(#${id}-gradient)`}
|
||||||
|
strokeWidth={2}
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="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="#A3A3A3"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 InviteScreen() {
|
||||||
|
return (
|
||||||
|
<AppScreen className="w-full">
|
||||||
|
<img src={connectorImg} alt="Mycelium Connector" width="366" height="732" className="-mt-8" />
|
||||||
|
</AppScreen>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function StocksScreen() {
|
||||||
|
return (
|
||||||
|
<AppScreen className="w-full">
|
||||||
|
<img src={peersImg} alt="Mycelium Peers" width="366" height="732" className="-mt-8" />
|
||||||
|
</AppScreen>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function InvestScreen() {
|
||||||
|
return (
|
||||||
|
<AppScreen className="w-full">
|
||||||
|
<img src={settingImg} alt="Mycelium Settings" width="366" height="732" className="-mt-8" />
|
||||||
|
</AppScreen>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function usePrevious<T>(value: T) {
|
||||||
|
const ref = useRef<T>()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
ref.current = value
|
||||||
|
}, [value])
|
||||||
|
|
||||||
|
return ref.current
|
||||||
|
}
|
||||||
|
|
||||||
|
function FeaturesDesktop() {
|
||||||
|
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
|
||||||
|
className="grid grid-cols-12 items-center gap-8 lg:gap-16"
|
||||||
|
selectedIndex={selectedIndex}
|
||||||
|
onChange={onChange}
|
||||||
|
vertical
|
||||||
|
>
|
||||||
|
<TabList className="z-10 order-last col-span-6 space-y-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-800/30',
|
||||||
|
selectedIndex === featureIndex
|
||||||
|
? 'outline-cyan-500'
|
||||||
|
: 'outline-transparent hover:outline-cyan-500',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{featureIndex === selectedIndex && (
|
||||||
|
<motion.div
|
||||||
|
layoutId="activeBackground"
|
||||||
|
className="absolute inset-0 bg-gray-800"
|
||||||
|
initial={{ borderRadius: 16 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className="relative z-10 p-8">
|
||||||
|
<feature.icon className="h-8 w-8" />
|
||||||
|
<FeatureTitle as="h3" color="white" className="mt-6">
|
||||||
|
<Tab className="text-left data-selected:not-data-focus:outline-hidden">
|
||||||
|
<span className="absolute inset-0 rounded-2xl" />
|
||||||
|
{feature.name}
|
||||||
|
</Tab>
|
||||||
|
</FeatureTitle>
|
||||||
|
<FeatureDescription color="secondary" className="mt-2">
|
||||||
|
{feature.description}
|
||||||
|
</FeatureDescription>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</TabList>
|
||||||
|
<div className="relative col-span-6">
|
||||||
|
<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" />
|
||||||
|
</div>
|
||||||
|
<PhoneFrame className="z-10 mx-auto w-full max-w-[366px]">
|
||||||
|
<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 }}>
|
||||||
|
<feature.screen />
|
||||||
|
</motion.div>
|
||||||
|
</TabPanel>
|
||||||
|
) : null,
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</TabPanels>
|
||||||
|
</PhoneFrame>
|
||||||
|
</div>
|
||||||
|
</TabGroup>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function FeaturesMobile() {
|
||||||
|
let [activeIndex, setActiveIndex] = useState(0)
|
||||||
|
let slideContainerRef = useRef<React.ElementRef<'div'>>(null)
|
||||||
|
let slideRefs = useRef<Array<React.ElementRef<'div'>>>([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let observer = new window.IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
for (let entry of entries) {
|
||||||
|
if (entry.isIntersecting && entry.target instanceof HTMLDivElement) {
|
||||||
|
setActiveIndex(slideRefs.current.indexOf(entry.target))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: slideContainerRef.current,
|
||||||
|
threshold: 0.6,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
for (let slide of slideRefs.current) {
|
||||||
|
if (slide) {
|
||||||
|
observer.observe(slide)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
observer.disconnect()
|
||||||
|
}
|
||||||
|
}, [slideContainerRef, slideRefs])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
ref={slideContainerRef}
|
||||||
|
className="-mb-4 flex snap-x snap-mandatory -space-x-4 overflow-x-auto overscroll-x-contain scroll-smooth pb-4 [scrollbar-width:none] sm:-space-x-6 [&::-webkit-scrollbar]:hidden"
|
||||||
|
>
|
||||||
|
{features.map((feature, featureIndex) => (
|
||||||
|
<div
|
||||||
|
key={featureIndex}
|
||||||
|
ref={(ref) => ref && (slideRefs.current[featureIndex] = ref)}
|
||||||
|
className="w-full flex-none snap-center px-4 sm:px-6 transition-all duration-300 ease-in-out hover:scale-105"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
'relative transform overflow-hidden rounded-2xl bg-gray-800 px-5 py-6 outline-2 transition-colors',
|
||||||
|
activeIndex === featureIndex
|
||||||
|
? 'outline-transparent' // Remove outline for active mobile slide
|
||||||
|
: 'outline-transparent hover:outline-cyan-500',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
|
||||||
|
<CircleBackground
|
||||||
|
id={`primaryfeatures_mobile_circle_${featureIndex}`}
|
||||||
|
color="#13B5C8"
|
||||||
|
className={featureIndex % 2 === 1 ? 'rotate-180' : undefined}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<PhoneFrame className="relative mx-auto w-full max-w-[366px]">
|
||||||
|
<feature.screen />
|
||||||
|
</PhoneFrame>
|
||||||
|
<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" />
|
||||||
|
<MobileFeatureTitle color="white" className="mt-6">
|
||||||
|
{feature.name}
|
||||||
|
</MobileFeatureTitle>
|
||||||
|
<FeatureDescription color="secondary" className="mt-2">
|
||||||
|
{feature.description}
|
||||||
|
</FeatureDescription>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="mt-6 flex justify-center gap-3">
|
||||||
|
{features.map((_, featureIndex) => (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
key={featureIndex}
|
||||||
|
className={clsx(
|
||||||
|
'relative h-0.5 w-4 rounded-full',
|
||||||
|
featureIndex === activeIndex ? 'bg-gray-300' : 'bg-gray-500',
|
||||||
|
)}
|
||||||
|
aria-label={`Go to slide ${featureIndex + 1}`}
|
||||||
|
onClick={() => {
|
||||||
|
slideRefs.current[featureIndex].scrollIntoView({
|
||||||
|
block: 'nearest',
|
||||||
|
inline: 'nearest',
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="absolute -inset-x-1.5 -inset-y-3" />
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function CloudFeatures() {
|
export function CloudFeatures() {
|
||||||
return (
|
return (
|
||||||
<section className="bg-gray-50 py-24 sm:py-32">
|
<section
|
||||||
|
id="howitworks"
|
||||||
|
aria-label="Features for investing all your money"
|
||||||
|
className="bg-gray-900 py-20 sm:py-32"
|
||||||
|
>
|
||||||
<Container>
|
<Container>
|
||||||
<div className="mx-auto max-w-3xl text-center">
|
<div className="mx-auto max-w-2xl lg:mx-0 lg:max-w-3xl">
|
||||||
<Eyebrow className="tracking-[0.32em] uppercase text-cyan-500">
|
<Eyebrow color="accent">How It Works</Eyebrow>
|
||||||
Core Features
|
<SectionHeader color="white" className="mt-2">
|
||||||
</Eyebrow>
|
How Mycelium Operates
|
||||||
<SectionHeader as="h2" className="mt-6 text-gray-900">
|
|
||||||
Infrastructure that verifies, heals, and scales itself.
|
|
||||||
</SectionHeader>
|
</SectionHeader>
|
||||||
<P className="mt-6 text-gray-600">
|
<P color="light" className="mt-6">
|
||||||
From mesh networking to quantum-safe storage, every capability in
|
Mycelium, like its natural namesake, thrives on decentralization,
|
||||||
Mycelium Cloud is designed for sovereign control and autonomous
|
efficiency, and security, making it a truly powerful force in the world
|
||||||
operations—so your teams focus on shipping workloads instead of
|
of decentralized networks.
|
||||||
wiring infrastructure.
|
|
||||||
</P>
|
</P>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-16 grid gap-8 md:grid-cols-2 xl:grid-cols-3">
|
</Container>
|
||||||
{featureSections.map((feature) => (
|
<div className="mt-16 md:hidden">
|
||||||
<div
|
<FeaturesMobile />
|
||||||
key={feature.title}
|
</div>
|
||||||
className="flex h-full flex-col rounded-3xl border border-slate-200 bg-white p-8 shadow-sm transition hover:-translate-y-1 hover:border-cyan-300 hover:shadow-lg"
|
<Container className="hidden md:mt-20 md:block">
|
||||||
>
|
<FeaturesDesktop />
|
||||||
<div>
|
|
||||||
<Small className="text-xs uppercase tracking-[0.3em] text-cyan-500">
|
|
||||||
Capability
|
|
||||||
</Small>
|
|
||||||
<h3 className="mt-4 text-xl font-semibold text-gray-900">
|
|
||||||
{feature.title}
|
|
||||||
</h3>
|
|
||||||
<p className="mt-4 text-sm leading-relaxed text-gray-600">
|
|
||||||
{feature.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<ul className="mt-6 space-y-3 text-sm text-gray-600">
|
|
||||||
{feature.bullets.map((bullet) => (
|
|
||||||
<li
|
|
||||||
key={bullet}
|
|
||||||
className="flex items-start gap-3 rounded-2xl border border-cyan-100 bg-cyan-50/60 p-3 leading-relaxed"
|
|
||||||
>
|
|
||||||
<span className="mt-1 inline-block size-2 rounded-full bg-cyan-500" />
|
|
||||||
<span>{bullet}</span>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</Container>
|
</Container>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
|||||||
44
src/pages/cloud/CloudHeroNew.tsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { H3, H5, Eyebrow } from "../../components/Texts"
|
||||||
|
import { Button } from "../../components/Button"
|
||||||
|
|
||||||
|
|
||||||
|
export function CloudHeroNew() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="relative bg-cover lg:bg-contain bg-right bg-no-repeat"
|
||||||
|
style={{ backgroundImage: "url('/images/cloudhero3.webp')" }}
|
||||||
|
>
|
||||||
|
<div className="mx-auto max-w-7xl lg:px-8">
|
||||||
|
<div className="px-6 pt-12 pb-24 sm:pb-32 lg:col-span-5 lg:px-0 lg:pt-40 lg:pb-48 xl:col-span-5">
|
||||||
|
<div className="mx-auto max-w-2xl lg:mx-0">
|
||||||
|
<Eyebrow>
|
||||||
|
Mycelium Cloud
|
||||||
|
</Eyebrow>
|
||||||
|
<H3 className="mt-4">
|
||||||
|
Deploy sovereign Kubernetes clusters on decentralized infrastructure.
|
||||||
|
</H3>
|
||||||
|
<H5 className="mt-8 text-lg text-gray-600">
|
||||||
|
Mycelium Cloud turns the ThreeFold Grid into a programmable substrate for K3s.
|
||||||
|
</H5>
|
||||||
|
<H5 className="mt-4 text-lg text-gray-600">
|
||||||
|
Launch verifiable clusters with nature-inspired networking, quantum-safe storage, and zero-image delivery that keeps every workload deterministic.
|
||||||
|
</H5>
|
||||||
|
<div className="mt-10 flex items-center gap-x-6">
|
||||||
|
<Button
|
||||||
|
to="#"
|
||||||
|
variant="solid"
|
||||||
|
className=""
|
||||||
|
color="cyan"
|
||||||
|
>
|
||||||
|
Get started
|
||||||
|
</Button>
|
||||||
|
<Button to="#" variant="outline">
|
||||||
|
Explore Docs <span aria-hidden="true"> →</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -39,7 +39,7 @@ export function CloudOverview() {
|
|||||||
</div>
|
</div>
|
||||||
<Container className="relative">
|
<Container className="relative">
|
||||||
<div className="mx-auto max-w-3xl text-center">
|
<div className="mx-auto max-w-3xl text-center">
|
||||||
<Eyebrow className="tracking-[0.32em] uppercase text-cyan-400">
|
<Eyebrow>
|
||||||
Platform Overview
|
Platform Overview
|
||||||
</Eyebrow>
|
</Eyebrow>
|
||||||
<SectionHeader as="h2" color="light" className="mt-6 font-medium">
|
<SectionHeader as="h2" color="light" className="mt-6 font-medium">
|
||||||
|
|||||||
@@ -7,12 +7,14 @@ import { CloudGettingStarted } from './CloudGettingStarted'
|
|||||||
import { CloudUseCases } from './CloudUseCases'
|
import { CloudUseCases } from './CloudUseCases'
|
||||||
import { SecurityPillars } from './SecurityPillars'
|
import { SecurityPillars } from './SecurityPillars'
|
||||||
import { CloudCTA } from './CloudCTA'
|
import { CloudCTA } from './CloudCTA'
|
||||||
|
import { CloudHeroNew } from './CloudHeroNew'
|
||||||
|
|
||||||
export default function CloudPage() {
|
export default function CloudPage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
||||||
<AnimatedSection>
|
<AnimatedSection>
|
||||||
<CloudHero />
|
<CloudHeroNew />
|
||||||
</AnimatedSection>
|
</AnimatedSection>
|
||||||
<AnimatedSection>
|
<AnimatedSection>
|
||||||
<CloudOverview />
|
<CloudOverview />
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export function CloudUseCases() {
|
|||||||
<section className="bg-white py-24 sm:py-32">
|
<section className="bg-white py-24 sm:py-32">
|
||||||
<Container>
|
<Container>
|
||||||
<div className="mx-auto max-w-3xl text-center">
|
<div className="mx-auto max-w-3xl text-center">
|
||||||
<Eyebrow className="tracking-[0.32em] uppercase text-cyan-500">
|
<Eyebrow>
|
||||||
Use Cases
|
Use Cases
|
||||||
</Eyebrow>
|
</Eyebrow>
|
||||||
<SectionHeader as="h2" className="mt-6 text-gray-900">
|
<SectionHeader as="h2" className="mt-6 text-gray-900">
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export function GpuArchitecture() {
|
|||||||
<section id="gpu-architecture" className="bg-white py-24 sm:py-32">
|
<section id="gpu-architecture" className="bg-white py-24 sm:py-32">
|
||||||
<Container>
|
<Container>
|
||||||
<div className="mx-auto max-w-3xl text-center">
|
<div className="mx-auto max-w-3xl text-center">
|
||||||
<Eyebrow className="tracking-[0.32em] uppercase text-cyan-500">
|
<Eyebrow>
|
||||||
Technical Architecture
|
Technical Architecture
|
||||||
</Eyebrow>
|
</Eyebrow>
|
||||||
<SectionHeader as="h2" className="mt-6 text-gray-900">
|
<SectionHeader as="h2" className="mt-6 text-gray-900">
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export function GpuUseCases() {
|
|||||||
<section className="bg-white py-24 sm:py-32">
|
<section className="bg-white py-24 sm:py-32">
|
||||||
<Container>
|
<Container>
|
||||||
<div className="mx-auto max-w-3xl text-center">
|
<div className="mx-auto max-w-3xl text-center">
|
||||||
<Eyebrow className="tracking-[0.32em] uppercase text-cyan-500">
|
<Eyebrow>
|
||||||
Use Cases
|
Use Cases
|
||||||
</Eyebrow>
|
</Eyebrow>
|
||||||
<SectionHeader as="h2" className="mt-6 text-gray-900">
|
<SectionHeader as="h2" className="mt-6 text-gray-900">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Carousel, Card } from "@/components/ui/apple-cards-carousel";
|
import { Carousel, Card } from "@/components/ui/apple-cards-carousel";
|
||||||
import { H3, P } from "@/components/Texts";
|
import { H3, P , Eyebrow} from "@/components/Texts";
|
||||||
|
|
||||||
export function HomeSlider() {
|
export function HomeSlider() {
|
||||||
const cards = data.map((card) => (
|
const cards = data.map((card) => (
|
||||||
@@ -11,8 +11,11 @@ export function HomeSlider() {
|
|||||||
return (
|
return (
|
||||||
<div className="w-full h-full py-20 bg-[#0b0b0b]">
|
<div className="w-full h-full py-20 bg-[#0b0b0b]">
|
||||||
<div className="max-w-7xl mx-auto pl-4">
|
<div className="max-w-7xl mx-auto pl-4">
|
||||||
|
<Eyebrow className="text-left">
|
||||||
|
Ecosystem
|
||||||
|
</Eyebrow>
|
||||||
<H3 className="text-left text-white">
|
<H3 className="text-left text-white">
|
||||||
Discover the Mycelium Ecosystem
|
Discover the Mycelium Components
|
||||||
</H3>
|
</H3>
|
||||||
<div className="mt-4 max-w-3xl">
|
<div className="mt-4 max-w-3xl">
|
||||||
<P className="text-left text-neutral-400">
|
<P className="text-left text-neutral-400">
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export function StorageArchitecture() {
|
|||||||
<section className="bg-gray-50 py-24 sm:py-32">
|
<section className="bg-gray-50 py-24 sm:py-32">
|
||||||
<Container>
|
<Container>
|
||||||
<div className="mx-auto max-w-3xl text-center">
|
<div className="mx-auto max-w-3xl text-center">
|
||||||
<Eyebrow className="tracking-[0.32em] uppercase text-cyan-500">
|
<Eyebrow>
|
||||||
Technical Architecture
|
Technical Architecture
|
||||||
</Eyebrow>
|
</Eyebrow>
|
||||||
<SectionHeader as="h2" className="mt-6 text-gray-900">
|
<SectionHeader as="h2" className="mt-6 text-gray-900">
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export function StorageFeatures() {
|
|||||||
<section id="storage-features" className="bg-white py-24 sm:py-32">
|
<section id="storage-features" className="bg-white py-24 sm:py-32">
|
||||||
<Container>
|
<Container>
|
||||||
<div className="mx-auto max-w-3xl text-center">
|
<div className="mx-auto max-w-3xl text-center">
|
||||||
<Eyebrow className="tracking-[0.32em] uppercase text-cyan-500">
|
<Eyebrow>
|
||||||
Core Features
|
Core Features
|
||||||
</Eyebrow>
|
</Eyebrow>
|
||||||
<SectionHeader as="h2" className="mt-6 text-gray-900">
|
<SectionHeader as="h2" className="mt-6 text-gray-900">
|
||||||
|
|||||||
@@ -43,11 +43,11 @@ export function StorageOverview() {
|
|||||||
{highlights.map((item) => (
|
{highlights.map((item) => (
|
||||||
<div
|
<div
|
||||||
key={item.title}
|
key={item.title}
|
||||||
className="group relative overflow-hidden rounded-3xl border border-white/10 bg-white/[0.04] p-8 backdrop-blur-sm transition hover:-translate-y-1 hover:border-cyan-300/50 hover:bg-white/[0.08]"
|
className="group relative overflow-hidden rounded-3xl border border-white/10 bg-white/4 p-8 backdrop-blur-sm transition hover:-translate-y-1 hover:border-cyan-300/50 hover:bg-white/8"
|
||||||
>
|
>
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-cyan-500/0 via-white/[0.05] to-cyan-300/20 opacity-0 transition group-hover:opacity-100" />
|
<div className="absolute inset-0 bg-linear-to-br from-cyan-500/0 via-white/5 to-cyan-300/20 opacity-0 transition group-hover:opacity-100" />
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Small className="text-xs uppercase tracking-[0.35em] text-cyan-200">
|
<Small className="text-xs uppercase tracking-[0.16em] text-cyan-200">
|
||||||
{item.label}
|
{item.label}
|
||||||
</Small>
|
</Small>
|
||||||
<h3 className="mt-4 text-lg font-semibold text-white">
|
<h3 className="mt-4 text-lg font-semibold text-white">
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export function StorageUseCases() {
|
|||||||
<section className="bg-white py-24 sm:py-32">
|
<section className="bg-white py-24 sm:py-32">
|
||||||
<Container>
|
<Container>
|
||||||
<div className="mx-auto max-w-3xl text-center">
|
<div className="mx-auto max-w-3xl text-center">
|
||||||
<Eyebrow className="tracking-[0.32em] uppercase text-cyan-500">
|
<Eyebrow>
|
||||||
Use Cases
|
Use Cases
|
||||||
</Eyebrow>
|
</Eyebrow>
|
||||||
<SectionHeader as="h2" className="mt-6 text-gray-900">
|
<SectionHeader as="h2" className="mt-6 text-gray-900">
|
||||||
|
|||||||