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,57 +1,77 @@
|
||||
'use client';
|
||||
|
||||
import { Container } from '@/components/Container'
|
||||
import { Eyebrow, H3, P } from '@/components/Texts'
|
||||
import { Button } from '@/components/Button'
|
||||
import { MeshNetworkIcon } from './animations/MeshNetworkIcon'
|
||||
import { SovereignComputer } from './animations/SovereignComputer'
|
||||
import { DeterministicOrchestration } from './animations/DeterministicOrchestration'
|
||||
|
||||
const architecture = [
|
||||
{
|
||||
title: 'Mesh Networking Layer',
|
||||
description:
|
||||
'Every node receives a cryptographic network identity and secure routing path.',
|
||||
icon: <MeshNetworkIcon className="mb-4" />, // ✅ stored as const JSX
|
||||
},
|
||||
{
|
||||
title: 'Sovereign Compute Layer',
|
||||
description:
|
||||
'Workloads run on hardware you authorize, no shared control, no exposed surfaces.',
|
||||
icon: <SovereignComputer className="mb-4" />,
|
||||
},
|
||||
{
|
||||
title: 'Deterministic Orchestration',
|
||||
description:
|
||||
'K3s clusters deploy predictably, verifiably, and remain drift-free.',
|
||||
icon: <DeterministicOrchestration className="mb-4" />,
|
||||
},
|
||||
]
|
||||
|
||||
export function CloudArchitecture() {
|
||||
return (
|
||||
<section className="bg-white py-24 lg:py-32">
|
||||
<section className="bg-[#121212] w-full max-w-8xl mx-auto">
|
||||
|
||||
{/* ✅ Top horizontal spacer like HomeHosting */}
|
||||
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-800 bg-transparent" />
|
||||
<div className="w-full border-t border-l border-r border-gray-800" />
|
||||
|
||||
{/* ✅ Boxed container with matching spacing */}
|
||||
<div className="relative mx-auto max-w-7xl border border-t-0 border-b-0 border-gray-800 bg-[#111111] py-12">
|
||||
<Container>
|
||||
<div className="mx-auto max-w-4xl text-center">
|
||||
<Eyebrow>ARCHITECTURE</Eyebrow>
|
||||
<H3 className="mt-6 text-gray-900">
|
||||
<div className="mx-auto max-w-4xl sm:text-center">
|
||||
<Eyebrow className="text-cyan-400">ARCHITECTURE</Eyebrow>
|
||||
|
||||
<H3 className="text-3xl lg:text-4xl font-medium tracking-tight text-white">
|
||||
How Mycelium Cloud Works
|
||||
</H3>
|
||||
<P className="mt-6 text-gray-600">
|
||||
|
||||
<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>
|
||||
|
||||
<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">
|
||||
{architecture.map((layer) => (
|
||||
<div
|
||||
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"
|
||||
{/* ✅ 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"
|
||||
>
|
||||
<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>
|
||||
{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>
|
||||
))}
|
||||
</div>
|
||||
</ul>
|
||||
|
||||
<div className="mx-auto mt-16 flex justify-center gap-4">
|
||||
{/* ✅ 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>
|
||||
@@ -60,6 +80,11 @@ export function CloudArchitecture() {
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
||||
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