forked from emre/www_projectmycelium_com
feat: redesign CloudArchitecture section with dark theme and animated icons
- Added animated icon components (MeshNetworkIcon, SovereignComputer, DeterministicOrchestration) to architecture cards - Updated styling to match dark theme with bordered container layout consistent with HomeHosting section - Improved card layout with better spacing, hover effects, and visual hierarchy
This commit is contained in:
@@ -1,65 +1,90 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
import { Container } from '@/components/Container'
|
import { Container } from '@/components/Container'
|
||||||
import { Eyebrow, H3, P } from '@/components/Texts'
|
import { Eyebrow, H3, P } from '@/components/Texts'
|
||||||
import { Button } from '@/components/Button'
|
import { Button } from '@/components/Button'
|
||||||
|
import { MeshNetworkIcon } from './animations/MeshNetworkIcon'
|
||||||
|
import { SovereignComputer } from './animations/SovereignComputer'
|
||||||
|
import { DeterministicOrchestration } from './animations/DeterministicOrchestration'
|
||||||
|
|
||||||
const architecture = [
|
const architecture = [
|
||||||
{
|
{
|
||||||
title: 'Mesh Networking Layer',
|
title: 'Mesh Networking Layer',
|
||||||
description:
|
description:
|
||||||
'Every node receives a cryptographic network identity and secure routing path.',
|
'Every node receives a cryptographic network identity and secure routing path.',
|
||||||
|
icon: <MeshNetworkIcon className="mb-4" />, // ✅ stored as const JSX
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Sovereign Compute Layer',
|
title: 'Sovereign Compute Layer',
|
||||||
description:
|
description:
|
||||||
'Workloads run on hardware you authorize, no shared control, no exposed surfaces.',
|
'Workloads run on hardware you authorize, no shared control, no exposed surfaces.',
|
||||||
|
icon: <SovereignComputer className="mb-4" />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Deterministic Orchestration',
|
title: 'Deterministic Orchestration',
|
||||||
description:
|
description:
|
||||||
'K3s clusters deploy predictably, verifiably, and remain drift-free.',
|
'K3s clusters deploy predictably, verifiably, and remain drift-free.',
|
||||||
|
icon: <DeterministicOrchestration className="mb-4" />,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export function CloudArchitecture() {
|
export function CloudArchitecture() {
|
||||||
return (
|
return (
|
||||||
<section className="bg-white py-24 lg:py-32">
|
<section className="bg-[#121212] w-full max-w-8xl mx-auto">
|
||||||
<Container>
|
|
||||||
<div className="mx-auto max-w-4xl text-center">
|
|
||||||
<Eyebrow>ARCHITECTURE</Eyebrow>
|
|
||||||
<H3 className="mt-6 text-gray-900">
|
|
||||||
How Mycelium Cloud Works
|
|
||||||
</H3>
|
|
||||||
<P className="mt-6 text-gray-600">
|
|
||||||
Mycelium Cloud runs Kubernetes on a global encrypted mesh, with
|
|
||||||
identity, routing, and state verified at the protocol level.
|
|
||||||
</P>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mx-auto mt-16 max-w-4xl space-y-6 lg:space-y-0 lg:grid lg:grid-cols-3 lg:gap-8">
|
{/* ✅ Top horizontal spacer like HomeHosting */}
|
||||||
{architecture.map((layer) => (
|
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-800 bg-transparent" />
|
||||||
<div
|
<div className="w-full border-t border-l border-r border-gray-800" />
|
||||||
key={layer.title}
|
|
||||||
className="rounded-3xl border border-slate-200 bg-gray-50/40 p-8 shadow-sm transition hover:-translate-y-1 hover:border-cyan-300 hover:shadow-lg"
|
|
||||||
>
|
|
||||||
<h3 className="text-xl font-semibold text-gray-900">
|
|
||||||
{layer.title}
|
|
||||||
</h3>
|
|
||||||
<p className="mt-3 text-sm leading-relaxed text-gray-600">
|
|
||||||
{layer.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mx-auto mt-16 flex justify-center gap-4">
|
{/* ✅ Boxed container with matching spacing */}
|
||||||
<Button variant="solid" color="cyan" href="/start">
|
<div className="relative mx-auto max-w-7xl border border-t-0 border-b-0 border-gray-800 bg-[#111111] py-12">
|
||||||
Get Started
|
<Container>
|
||||||
</Button>
|
<div className="mx-auto max-w-4xl sm:text-center">
|
||||||
<Button variant="outline" color="gray" href="/docs">
|
<Eyebrow className="text-cyan-400">ARCHITECTURE</Eyebrow>
|
||||||
Explore Docs
|
|
||||||
</Button>
|
<H3 className="text-3xl lg:text-4xl font-medium tracking-tight text-white">
|
||||||
</div>
|
How Mycelium Cloud Works
|
||||||
</Container>
|
</H3>
|
||||||
|
|
||||||
|
<P className="mt-6 text-lg text-gray-300">
|
||||||
|
Mycelium Cloud runs Kubernetes on a global encrypted mesh, with
|
||||||
|
identity, routing, and state verified at the protocol level.
|
||||||
|
</P>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ✅ Card layout spacing & grid match HomeHosting */}
|
||||||
|
<ul
|
||||||
|
role="list"
|
||||||
|
className="mx-auto mt-12 grid max-w-2xl grid-cols-1 gap-6 text-sm
|
||||||
|
sm:grid-cols-2 lg:max-w-none lg:grid-cols-3 md:gap-y-10"
|
||||||
|
>
|
||||||
|
{architecture.map((layer) => (
|
||||||
|
<li
|
||||||
|
key={layer.title}
|
||||||
|
className="rounded-xl border border-gray-800 bg-[#111]/60 p-6"
|
||||||
|
>
|
||||||
|
{layer.icon} {/* ✅ this now works */}
|
||||||
|
<h3 className="text-lg font-semibold text-white">{layer.title}</h3>
|
||||||
|
<p className="mt-2 text-gray-400 leading-snug">{layer.description}</p>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{/* ✅ Matching button spacing and layout */}
|
||||||
|
<div className="mx-auto mt-12 flex justify-center gap-6">
|
||||||
|
<Button variant="solid" color="cyan" href="/start">
|
||||||
|
Get Started
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" color="gray" href="/docs">
|
||||||
|
Explore Docs
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ✅ bottom border + bottom spacer to match */}
|
||||||
|
<div className="w-full border-b border-gray-800" />
|
||||||
|
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-800 bg-transparent" />
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
103
src/pages/cloud/animations/DeterministicOrchestration.tsx
Normal file
103
src/pages/cloud/animations/DeterministicOrchestration.tsx
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { motion, useReducedMotion } from "framer-motion";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
export function DeterministicOrchestration({ className }: { className?: string }) {
|
||||||
|
const prefersReducedMotion = useReducedMotion();
|
||||||
|
|
||||||
|
// Hex coordinates (simple honeycomb layout)
|
||||||
|
const hexes = [
|
||||||
|
{ x: 60, y: 25 },
|
||||||
|
{ x: 100, y: 25 },
|
||||||
|
{ x: 140, y: 25 },
|
||||||
|
|
||||||
|
{ x: 40, y: 65 },
|
||||||
|
{ x: 80, y: 65 },
|
||||||
|
{ x: 120, y: 65 },
|
||||||
|
{ x: 160, y: 65 },
|
||||||
|
|
||||||
|
{ x: 60, y: 105 },
|
||||||
|
{ x: 100, y: 105 },
|
||||||
|
{ x: 140, y: 105 },
|
||||||
|
];
|
||||||
|
|
||||||
|
// simple hex path generator
|
||||||
|
const hexPath = (cx: number, cy: number, r = 14) => {
|
||||||
|
const p = (a: number) => ({
|
||||||
|
x: cx + r * Math.cos((Math.PI / 180) * a),
|
||||||
|
y: cy + r * Math.sin((Math.PI / 180) * a),
|
||||||
|
});
|
||||||
|
const pts = [30, 90, 150, 210, 270, 330].map((a) => p(a));
|
||||||
|
return `M ${pts[0].x} ${pts[0].y} L ${pts[1].x} ${pts[1].y} L ${pts[2].x} ${pts[2].y}
|
||||||
|
L ${pts[3].x} ${pts[3].y} L ${pts[4].x} ${pts[4].y} L ${pts[5].x} ${pts[5].y} Z`;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
"w-full h-24 flex items-center justify-center bg-[#0a0a0a]",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
aria-hidden="true"
|
||||||
|
role="img"
|
||||||
|
>
|
||||||
|
<svg viewBox="0 0 200 140" className="w-full h-full" fill="none">
|
||||||
|
{/* Hex outlines */}
|
||||||
|
{hexes.map((h, i) => (
|
||||||
|
<motion.path
|
||||||
|
key={i}
|
||||||
|
d={hexPath(h.x, h.y)}
|
||||||
|
stroke="#4B5563" // gray-600
|
||||||
|
strokeWidth={1.6}
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ duration: 0.6, delay: 0.03 * i }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Deterministic sequence highlight — hexes fill in predictable order */}
|
||||||
|
{hexes.map((h, i) => (
|
||||||
|
<motion.path
|
||||||
|
key={`fill-${i}`}
|
||||||
|
d={hexPath(h.x, h.y)}
|
||||||
|
fill="#00b8db"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{
|
||||||
|
opacity:
|
||||||
|
prefersReducedMotion
|
||||||
|
? 1
|
||||||
|
: [0, 1, 1, 0], // flash on, stay, fade
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 1.2,
|
||||||
|
delay: i * 0.12,
|
||||||
|
repeat: prefersReducedMotion ? 0 : Infinity,
|
||||||
|
repeatDelay: 0.6,
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Optional center stability indicator */}
|
||||||
|
<motion.circle
|
||||||
|
cx={100}
|
||||||
|
cy={65}
|
||||||
|
r={5}
|
||||||
|
fill="#00b8db"
|
||||||
|
initial={{ scale: 0.8, opacity: 0 }}
|
||||||
|
animate={{
|
||||||
|
opacity: 1,
|
||||||
|
scale: prefersReducedMotion ? 1 : [1, 1.15, 1],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 1.6,
|
||||||
|
repeat: prefersReducedMotion ? 0 : Infinity,
|
||||||
|
repeatType: "mirror",
|
||||||
|
ease: [0.22, 1, 0.36, 1],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
112
src/pages/cloud/animations/MeshNetworkIcon.tsx
Normal file
112
src/pages/cloud/animations/MeshNetworkIcon.tsx
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { motion, useReducedMotion } from "framer-motion";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
export function MeshNetworkIcon({ className }: { className?: string }) {
|
||||||
|
const prefersReducedMotion = useReducedMotion();
|
||||||
|
|
||||||
|
const nodes = [
|
||||||
|
{ x: 50, y: 25 },
|
||||||
|
{ x: 120, y: 25 },
|
||||||
|
{ x: 150, y: 65 },
|
||||||
|
{ x: 120, y: 100 },
|
||||||
|
{ x: 60, y: 100 },
|
||||||
|
{ x: 30, y: 65 },
|
||||||
|
{ x: 90, y: 55 }, // center-ish node
|
||||||
|
];
|
||||||
|
|
||||||
|
// Lines between nodes (pairs of indices)
|
||||||
|
const links: [number, number][] = [
|
||||||
|
[0, 6], [6, 1],
|
||||||
|
[1, 2], [2, 3],
|
||||||
|
[3, 6], [6, 4],
|
||||||
|
[4, 5], [5, 0],
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
"w-full h-24 flex items-center justify-center bg-[#0a0a0a]",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
aria-hidden="true"
|
||||||
|
role="img"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 180 120"
|
||||||
|
className="w-full h-full"
|
||||||
|
fill="none"
|
||||||
|
>
|
||||||
|
{/* Lines */}
|
||||||
|
{links.map(([a, b], i) => (
|
||||||
|
<motion.line
|
||||||
|
key={i}
|
||||||
|
x1={nodes[a].x}
|
||||||
|
y1={nodes[a].y}
|
||||||
|
x2={nodes[b].x}
|
||||||
|
y2={nodes[b].y}
|
||||||
|
stroke="#4B5563" /* gray-600 */
|
||||||
|
strokeWidth={1.5}
|
||||||
|
initial={{ pathLength: 0, opacity: 0 }}
|
||||||
|
animate={{ pathLength: 1, opacity: 1 }}
|
||||||
|
transition={{
|
||||||
|
delay: 0.1 * i,
|
||||||
|
duration: 0.5,
|
||||||
|
ease: [0.22, 1, 0.36, 1],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Nodes */}
|
||||||
|
{nodes.map((n, i) => (
|
||||||
|
<motion.circle
|
||||||
|
key={i}
|
||||||
|
cx={n.x}
|
||||||
|
cy={n.y}
|
||||||
|
r={6}
|
||||||
|
fill="#00b8db" // cyan nodes
|
||||||
|
initial={{ scale: 0.8, opacity: 0 }}
|
||||||
|
animate={{
|
||||||
|
opacity: 1,
|
||||||
|
scale:
|
||||||
|
!prefersReducedMotion && i !== 6 // central node pulses more
|
||||||
|
? [1, 1.12, 1]
|
||||||
|
: 1,
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 1.4,
|
||||||
|
repeat: i !== 6 && !prefersReducedMotion ? Infinity : 0,
|
||||||
|
repeatType: "mirror",
|
||||||
|
ease: [0.22, 1, 0.36, 1],
|
||||||
|
delay: 0.05 * i,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Center node extra pulse */}
|
||||||
|
<motion.circle
|
||||||
|
cx={nodes[6].x}
|
||||||
|
cy={nodes[6].y}
|
||||||
|
r={10}
|
||||||
|
stroke="#00b8db"
|
||||||
|
strokeWidth={1}
|
||||||
|
fill="none"
|
||||||
|
initial={{ scale: 0 }}
|
||||||
|
animate={{
|
||||||
|
scale:
|
||||||
|
prefersReducedMotion
|
||||||
|
? 0
|
||||||
|
: [0.6, 1.3, 0.6],
|
||||||
|
opacity: [0.3, 0.8, 0.3],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 1.8,
|
||||||
|
repeat: prefersReducedMotion ? 0 : Infinity,
|
||||||
|
repeatType: "mirror",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
113
src/pages/cloud/animations/SovereignComputer.tsx
Normal file
113
src/pages/cloud/animations/SovereignComputer.tsx
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { motion, useReducedMotion } from "framer-motion";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
export function SovereignComputer({ className }: { className?: string }) {
|
||||||
|
const prefersReducedMotion = useReducedMotion();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
"w-full h-24 flex items-center justify-center bg-[#0a0a0a]",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
aria-hidden="true"
|
||||||
|
role="img"
|
||||||
|
>
|
||||||
|
<svg viewBox="0 0 180 120" className="w-full h-full" fill="none">
|
||||||
|
{/* Outer secure boundary (shield / isolation) */}
|
||||||
|
<motion.rect
|
||||||
|
x={40}
|
||||||
|
y={20}
|
||||||
|
width={100}
|
||||||
|
height={80}
|
||||||
|
rx={14}
|
||||||
|
stroke="#4B5563"
|
||||||
|
strokeWidth={2}
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ duration: 0.6 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Inner compute chip */}
|
||||||
|
<motion.rect
|
||||||
|
x={70}
|
||||||
|
y={45}
|
||||||
|
width={40}
|
||||||
|
height={30}
|
||||||
|
rx={4}
|
||||||
|
fill="#00b8db"
|
||||||
|
initial={{ scale: 0.8, opacity: 0 }}
|
||||||
|
animate={{
|
||||||
|
opacity: 1,
|
||||||
|
scale:
|
||||||
|
!prefersReducedMotion
|
||||||
|
? [1, 1.12, 1]
|
||||||
|
: 1,
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 1.6,
|
||||||
|
repeat: prefersReducedMotion ? 0 : Infinity,
|
||||||
|
repeatType: "mirror",
|
||||||
|
ease: [0.22, 1, 0.36, 1],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Compute lines out → show isolation of data paths */}
|
||||||
|
{[
|
||||||
|
[90, 45, 90, 20],
|
||||||
|
[90, 75, 90, 100],
|
||||||
|
[70, 60, 40, 60],
|
||||||
|
[110, 60, 140, 60],
|
||||||
|
].map(([x1, y1, x2, y2], i) => (
|
||||||
|
<motion.line
|
||||||
|
key={i}
|
||||||
|
x1={x1}
|
||||||
|
y1={y1}
|
||||||
|
x2={x2}
|
||||||
|
y2={y2}
|
||||||
|
stroke="#4B5563"
|
||||||
|
strokeWidth={2}
|
||||||
|
initial={{ pathLength: 0, opacity: 0 }}
|
||||||
|
animate={{ pathLength: 1, opacity: 1 }}
|
||||||
|
transition={{
|
||||||
|
delay: 0.1 * i,
|
||||||
|
duration: 0.6,
|
||||||
|
ease: [0.22, 1, 0.36, 1],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Cyan data pulses traveling outward */}
|
||||||
|
{[
|
||||||
|
[90, 45, 90, 20],
|
||||||
|
[90, 75, 90, 100],
|
||||||
|
[70, 60, 40, 60],
|
||||||
|
[110, 60, 140, 60],
|
||||||
|
].map(([x1, y1, x2, y2], i) => (
|
||||||
|
<motion.circle
|
||||||
|
key={`pulse-${i}`}
|
||||||
|
cx={x1}
|
||||||
|
cy={y1}
|
||||||
|
r={3}
|
||||||
|
fill="#00b8db"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{
|
||||||
|
opacity: [0, 1, 0],
|
||||||
|
cx: [x1, x2],
|
||||||
|
cy: [y1, y2],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
delay: 0.15 * i,
|
||||||
|
duration: 1.4,
|
||||||
|
repeat: prefersReducedMotion ? 0 : Infinity,
|
||||||
|
repeatType: "loop",
|
||||||
|
ease: "linear",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user