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:
2025-11-06 22:08:00 +01:00
parent 5af349ad4a
commit f796ec1218
4 changed files with 389 additions and 36 deletions

View File

@@ -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>
) )
} }

View 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>
);
}

View 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>
);
}

View 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>
);
}