forked from emre/www_projectmycelium_com
feat: add architecture section with animated illustrations
- Created HomeArchitecture component showcasing decentralized network principles with SVG animations - Added animated illustrations for mesh networking, no extraction, no control, and no central servers - Enhanced CallToAction with cyan radial glow effect for visual emphasis - Fixed audience gallery image paths to use correct /images/audiences/ directory
This commit is contained in:
@@ -3,48 +3,74 @@ import { Button } from '@/components/Button'
|
||||
|
||||
export function CallToAction() {
|
||||
return (
|
||||
<section className='relative overflow-hidden bg-[#121212]'>
|
||||
{/* ✅ Top horizontal line with spacing */}
|
||||
<div className="max-w-7xl bg-[#121212] mx-auto py-6 border border-t-0 border-b-0 border-gray-800"></div>
|
||||
{/* === Content === */}
|
||||
<div className="w-full border-t border-l border-r border-gray-800 " />
|
||||
<div
|
||||
id="get-started"
|
||||
className="py-18 max-w-7xl mx-auto border-t-0 border-b-0 border bg-[#111111] border-gray-800">
|
||||
<Container className="relative">
|
||||
<div className="mx-auto max-w-2xl text-center ">
|
||||
<h2 className="text-3xl lg:text-4xl font-medium tracking-tight text-white sm:text-4xl">
|
||||
A Living Network
|
||||
<section className="relative overflow-hidden bg-[#121212]">
|
||||
{/* ✅ Top horizontal line with spacing */}
|
||||
<div className="max-w-7xl bg-[#121212] mx-auto py-6 border border-t-0 border-b-0 border-gray-800"></div>
|
||||
|
||||
</h2>
|
||||
<p className="mt-6 text-lg text-gray-300">
|
||||
Mycelium isn’t a platform.
|
||||
It’s the soil where a new internet grows — open, resilient, and alive.
|
||||
|
||||
</p>
|
||||
<p className="mt-4 text-lg text-gray-300">
|
||||
The self-sovereign network powering the next internet.
|
||||
</p>
|
||||
<div className="mt-10 flex flex-wrap justify-center gap-x-6 gap-y-4">
|
||||
<Button to="/cloud" variant="solid" color="cyan">
|
||||
Join Mycelium
|
||||
</Button>
|
||||
<Button
|
||||
to="https://threefold.info/mycelium_network/docs/"
|
||||
as="a"
|
||||
target="_blank"
|
||||
variant="outline"
|
||||
color="white"
|
||||
>
|
||||
Join Early Cloud Access
|
||||
</Button>
|
||||
{/* === Content === */}
|
||||
<div className="w-full border-t border-l border-r border-gray-800" />
|
||||
|
||||
<div
|
||||
id="get-started"
|
||||
className="py-18 max-w-7xl mx-auto border-t-0 border-b-0 border bg-[#111111] border-gray-800 relative overflow-hidden"
|
||||
>
|
||||
{/* ✅ Cyan Radial Glow */}
|
||||
<svg
|
||||
viewBox="0 0 1024 1024"
|
||||
aria-hidden="true"
|
||||
className="absolute top-full left-1/2 w-7xl h-320 -translate-x-1/2 -translate-y-1/2 mask-image mask-[radial-gradient(circle,white,transparent)]"
|
||||
>
|
||||
<circle
|
||||
r={512}
|
||||
cx={512}
|
||||
cy={512}
|
||||
fill="url(#mycelium-cyan-glow)"
|
||||
fillOpacity="0.2"
|
||||
/>
|
||||
<defs>
|
||||
<radialGradient id="mycelium-cyan-glow">
|
||||
<stop stopColor="#00e5ff" />
|
||||
<stop offset="1" stopColor="transparent" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<Container className="relative">
|
||||
<div className="mx-auto max-w-2xl text-center">
|
||||
<h2 className="text-3xl lg:text-4xl font-medium tracking-tight text-white sm:text-4xl">
|
||||
A Living Network
|
||||
</h2>
|
||||
|
||||
<p className="mt-6 text-lg text-gray-300">
|
||||
Mycelium isn’t a platform.
|
||||
It’s the soil where a new internet grows — open, resilient, and alive.
|
||||
</p>
|
||||
|
||||
<p className="mt-4 text-lg text-gray-300">
|
||||
The self-sovereign network powering the next internet.
|
||||
</p>
|
||||
|
||||
<div className="mt-10 flex flex-wrap justify-center gap-x-6 gap-y-4">
|
||||
<Button to="/cloud" variant="solid" color="cyan">
|
||||
Join Mycelium
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
to="https://threefold.info/mycelium_network/docs/"
|
||||
as="a"
|
||||
target="_blank"
|
||||
variant="outline"
|
||||
color="white"
|
||||
>
|
||||
Join Early Cloud Access
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
|
||||
</div>
|
||||
{/* ✅ Bottom horizontal line with spacing */}
|
||||
<div className="w-full border-b border-gray-800" />
|
||||
</Container>
|
||||
</div>
|
||||
|
||||
{/* ✅ Bottom horizontal line */}
|
||||
<div className="w-full border-b border-gray-800" />
|
||||
<div className="max-w-7xl bg-transparent mx-auto py-6 border border-t-0 border-b-0 border-gray-800"></div>
|
||||
</section>
|
||||
)
|
||||
|
||||
126
src/pages/home/HomeArchitecture.tsx
Normal file
126
src/pages/home/HomeArchitecture.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
"use client";
|
||||
|
||||
import { Eyebrow, H3, P } from "@/components/Texts";
|
||||
import MeshNetworking from "./animations/Meshnetworking";
|
||||
import NoExtraction from "./animations/NoExtraction";
|
||||
import NoControl from "./animations/NoControl";
|
||||
import NoCentral from "./animations/NoCentral";
|
||||
|
||||
const deterministicCards = [
|
||||
{
|
||||
id: "core",
|
||||
eyebrow: "Why It Matters",
|
||||
title: "Built for a Sovereign Digital World",
|
||||
description:
|
||||
"The current internet is a rent-seeking one. Mycelium builds one that belongs to everyone — where infrastructure, data, and intelligence stay with the people and organizations who create them.",
|
||||
animation: null,
|
||||
colSpan: "lg:col-span-3",
|
||||
rowSpan: "lg:row-span-1",
|
||||
custom: true,
|
||||
noBorder: true,
|
||||
},
|
||||
{
|
||||
id: "crypto",
|
||||
title: "No central servers.",
|
||||
description:
|
||||
"Your devices form a distributed network, eliminating reliance on centralized data centers.",
|
||||
animation: <NoCentral className="lg:-mt-12" />, // ✅ NEW
|
||||
colSpan: "lg:col-span-3",
|
||||
rowSpan: "lg:row-span-1",
|
||||
rounded: "lg:rounded-tr-4xl max-lg:rounded-t-4xl",
|
||||
innerRounded: "lg:rounded-tr-[calc(2rem+1px)] max-lg:rounded-t-[calc(2rem+1px)]",
|
||||
},
|
||||
{
|
||||
id: "stateless",
|
||||
title: "No data extraction.",
|
||||
description:
|
||||
"You own your data. Run services and AI models on your own devices, ensuring privacy and control.",
|
||||
animation: <NoExtraction className="lg:-mt-12" />, // ✅ NEW
|
||||
colSpan: "lg:col-span-3",
|
||||
rowSpan: "lg:row-span-1",
|
||||
rounded: "lg:rounded-bl-4xl max-lg:rounded-b-4xl",
|
||||
innerRounded: "lg:rounded-bl-[calc(2rem+1px)] max-lg:rounded-b-[calc(2rem+1px)]",
|
||||
},
|
||||
{
|
||||
id: "healing",
|
||||
title: "No single point of control.",
|
||||
description:
|
||||
"No single entity can dictate or censor your online experience.",
|
||||
animation: <NoControl />, // ✅ NEW
|
||||
colSpan: "lg:col-span-3",
|
||||
rowSpan: "lg:row-span-1",
|
||||
rounded: "lg:rounded-br-4xl max-lg:rounded-b-4xl",
|
||||
innerRounded: "lg:rounded-br-[calc(2rem+1px)] max-lg:rounded-b-[calc(2rem+1px)]",
|
||||
},
|
||||
];
|
||||
|
||||
export function HomeArchitecture() {
|
||||
return (
|
||||
<section className="relative w-full bg-[#121212] overflow-hidden">
|
||||
{/* ✅ Top horizontal line */}
|
||||
<div className="max-w-7xl bg-[#121212] mx-auto py-6 border border-t-0 border-b-0 border-gray-800"></div>
|
||||
<div className="w-full border-t border-l border-r border-gray-800" />
|
||||
|
||||
<div className="mx-auto bg-[#111111] max-w-2xl px-6 lg:max-w-7xl lg:px-10 border border-t-0 border-b-0 border-gray-800">
|
||||
<div className="grid grid-cols-1 gap-6 pt-6 lg:grid-cols-6 lg:grid-rows-2 pb-6">
|
||||
{deterministicCards.map((card) => (
|
||||
<div
|
||||
key={card.id}
|
||||
className={`relative ${card.colSpan} ${card.rowSpan} transition-transform duration-300 hover:scale-102 group`}
|
||||
>
|
||||
{/* ✅ Disable wrapper on first card */}
|
||||
{!card.noBorder && (
|
||||
<div
|
||||
className={`absolute inset-0 rounded-md border border-gray-800 bg-[#111212] ${card.rounded} group-hover:bg-linear-to-br from-gray-900 to-gray-800`}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={`relative flex lg:h-90 flex-col overflow-hidden rounded-[calc(var(--radius-lg)+1px)] ${card.innerRounded}`}
|
||||
>
|
||||
{/* ✅ SVG Animation instead of images */}
|
||||
{card.animation ? (
|
||||
<div className="lg:h-64 h-48 w-full overflow-hidden bg-transparent flex items-center">
|
||||
<div className="w-full h-full">
|
||||
{card.animation}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-48 w-full flex items-center justify-center bg-transparent" />
|
||||
)}
|
||||
|
||||
<div className="px-8 pt-4 pb-6">
|
||||
{card.custom ? (
|
||||
<>
|
||||
{card.eyebrow && <Eyebrow>{card.eyebrow}</Eyebrow>}
|
||||
<H3 className="mt-2 text-white">{card.title}</H3>
|
||||
<P className="mt-4 max-w-lg text-gray-200">{card.description}</P>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="mt-1 text-lg font-medium lg:text-xl tracking-tight text-white">
|
||||
{card.title}
|
||||
</p>
|
||||
<p className="mt-1 max-w-lg text-sm/6 text-gray-200">
|
||||
{card.description}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!card.noBorder && (
|
||||
<div
|
||||
className={`pointer-events-none absolute inset-0 rounded-lg shadow-sm outline outline-black/5 ${card.rounded}`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full border-b border-gray-800" />
|
||||
<div className="max-w-7xl mx-auto py-6 border-x border-gray-800 border-t-0 border-b-0" />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -17,14 +17,14 @@ const rotating = [
|
||||
|
||||
// ✅ Use local image files (1–8)
|
||||
const gallery = [
|
||||
"/images/audience/1.jpg",
|
||||
"/images/audience/2.jpg",
|
||||
"/images/audience/3.jpg",
|
||||
"/images/audience/4.jpg",
|
||||
"/images/audience/5.jpg",
|
||||
"/images/audience/6.jpg",
|
||||
"/images/audience/7.jpg",
|
||||
"/images/audience/8.jpg",
|
||||
"/images/audiences/1.jpg",
|
||||
"/images/audiences/2.jpg",
|
||||
"/images/audiences/3.jpg",
|
||||
"/images/audiences/4.jpg",
|
||||
"/images/audiences/5.jpg",
|
||||
"/images/audiences/6.jpg",
|
||||
"/images/audiences/7.jpg",
|
||||
"/images/audiences/8.jpg",
|
||||
];
|
||||
|
||||
export function HomeAudience() {
|
||||
|
||||
@@ -17,7 +17,7 @@ export function HomeBlink({ onGetStartedClick }: { onGetStartedClick: () => void
|
||||
<Spotlight className="-top-40 left-0 md:-top-20 md:left-60" />
|
||||
|
||||
<div className="relative z-10 mx-auto w-full max-w-7xl p-4 md:pt-0">
|
||||
<H1 className="text-black text-center font-bold">
|
||||
<H1 className="text-black text-center font-bold tracking-wide">
|
||||
MYCELIUM
|
||||
</H1>
|
||||
<H4 className="text-center mt-4">The Living Network of the Next Internet</H4>
|
||||
|
||||
@@ -3,10 +3,11 @@ import { useRef } from 'react'
|
||||
import { AnimatedSection } from '../../components/AnimatedSection'
|
||||
import { CallToAction } from './CallToAction'
|
||||
import { HomeTab } from './HomeTab'
|
||||
import { HomeWhy} from './HomeWhy'
|
||||
import { HomeMap } from './HomeMap'
|
||||
import { HomeAudience } from './HomeAudience'
|
||||
import { HomeBlink } from './HomeBlink'
|
||||
import { HomeArchitecture } from './HomeArchitecture'
|
||||
s
|
||||
|
||||
|
||||
|
||||
@@ -23,7 +24,7 @@ export default function HomePage() {
|
||||
</AnimatedSection>
|
||||
|
||||
<AnimatedSection>
|
||||
<HomeWhy />
|
||||
<HomeArchitecture/>
|
||||
</AnimatedSection>
|
||||
|
||||
<AnimatedSection>
|
||||
|
||||
189
src/pages/home/animations/Deterministic.tsx
Normal file
189
src/pages/home/animations/Deterministic.tsx
Normal file
@@ -0,0 +1,189 @@
|
||||
"use client";
|
||||
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
import clsx from "clsx";
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
accent?: string;
|
||||
gridStroke?: string;
|
||||
};
|
||||
|
||||
const W = 760;
|
||||
const H = 420;
|
||||
|
||||
export default function Deterministic({
|
||||
className,
|
||||
accent = "#00b8db",
|
||||
gridStroke = "#2b2a2a",
|
||||
}: Props) {
|
||||
const prefers = useReducedMotion();
|
||||
|
||||
const stages = [
|
||||
{ x: 180, y: 180, w: 120, h: 80, label: "Build" },
|
||||
{ x: 330, y: 180, w: 120, h: 80, label: "Package" },
|
||||
{ x: 480, y: 180, w: 120, h: 80, label: "Deploy" },
|
||||
];
|
||||
|
||||
// Packet path (deterministic flow)
|
||||
const packetPath = `M ${stages[0].x + 120} ${stages[0].y + 40}
|
||||
L ${stages[1].x + 0} ${stages[1].y + 40}
|
||||
L ${stages[1].x + 120} ${stages[1].y + 40}
|
||||
L ${stages[2].x + 0} ${stages[2].y + 40}`;
|
||||
|
||||
// tiny arrow for each transition
|
||||
const arrows = [
|
||||
`M ${stages[0].x + 120} ${stages[0].y + 40} L ${stages[1].x + 6} ${stages[1].y + 40}`,
|
||||
`M ${stages[1].x + 120} ${stages[1].y + 40} L ${stages[2].x + 6} ${stages[2].y + 40}`
|
||||
];
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx("relative overflow-hidden", className)}
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
aria-label="Deterministic orchestration: predictable deployments"
|
||||
style={{ background: "transparent" }}
|
||||
>
|
||||
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full">
|
||||
|
||||
{/* BACKGROUND GRID */}
|
||||
<defs>
|
||||
<pattern id="grid-orch" width="28" height="28" patternUnits="userSpaceOnUse">
|
||||
<path d="M 28 0 L 0 0 0 28"
|
||||
fill="none"
|
||||
stroke={gridStroke}
|
||||
strokeWidth="1"
|
||||
opacity="0.45"
|
||||
/>
|
||||
</pattern>
|
||||
|
||||
{/* Soft glow for highlight */}
|
||||
<filter id="orch-glow">
|
||||
<feGaussianBlur stdDeviation="4" result="blur" />
|
||||
<feMerge>
|
||||
<feMergeNode in="blur" />
|
||||
<feMergeNode in="SourceGraphic" />
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<rect width={W} height={H} fill="url(#grid-orch)" />
|
||||
|
||||
{/* STAGE BOXES */}
|
||||
{stages.map((s, i) => (
|
||||
<motion.rect
|
||||
key={`stage-${i}`}
|
||||
x={s.x}
|
||||
y={s.y}
|
||||
width={s.w}
|
||||
height={s.h}
|
||||
rx={14}
|
||||
fill="#0d0d0d"
|
||||
stroke="#1a1a1a"
|
||||
strokeWidth={2}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 0.9 }}
|
||||
transition={{ duration: 0.6 + i * 0.1 }}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Stage labels (subtle, not text-heavy) */}
|
||||
{stages.map((s, i) => (
|
||||
<motion.text
|
||||
key={`label-${i}`}
|
||||
x={s.x + s.w / 2}
|
||||
y={s.y + s.h / 2 + 6}
|
||||
fill="#9ca3af"
|
||||
textAnchor="middle"
|
||||
fontSize="14"
|
||||
fontFamily="Inter, sans-serif"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 0.9 }}
|
||||
transition={{ delay: 0.1 * i, duration: 0.6 }}
|
||||
>
|
||||
{s.label}
|
||||
</motion.text>
|
||||
))}
|
||||
|
||||
{/* CONSISTENT ORCHESTRATION LINES */}
|
||||
{arrows.map((d, i) => (
|
||||
<motion.path
|
||||
key={`arrow-${i}`}
|
||||
d={d}
|
||||
stroke="#3a3a3a"
|
||||
strokeWidth={4}
|
||||
strokeLinecap="round"
|
||||
fill="none"
|
||||
initial={{ pathLength: 0, opacity: 0 }}
|
||||
animate={{ pathLength: 1, opacity: 0.8 }}
|
||||
transition={{ delay: 0.15 * i, duration: 0.8, ease: [0.22, 1, 0.36, 1] }}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* CYAN ACCENT OVERLAY ON LINES (predictable updates) */}
|
||||
{arrows.map((d, i) => (
|
||||
<motion.path
|
||||
key={`arrow-accent-${i}`}
|
||||
d={d}
|
||||
stroke={accent}
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeDasharray="10"
|
||||
fill="none"
|
||||
initial={{ pathLength: 0, opacity: 0 }}
|
||||
animate={{ pathLength: 1, opacity: 1 }}
|
||||
transition={{
|
||||
delay: 0.25 * i,
|
||||
duration: 0.8,
|
||||
ease: [0.22, 1, 0.36, 1]
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* MOVING PACKET SHOWING DETERMINISTIC FLOW */}
|
||||
{!prefers && (
|
||||
<motion.circle
|
||||
r={6}
|
||||
fill={accent}
|
||||
filter="url(#orch-glow)"
|
||||
style={{
|
||||
offsetPath: `path('${packetPath}')`,
|
||||
}}
|
||||
initial={{ offsetDistance: "0%", opacity: 0 }}
|
||||
animate={{
|
||||
offsetDistance: ["0%", "100%"],
|
||||
opacity: [0.2, 1, 0.2],
|
||||
}}
|
||||
transition={{
|
||||
duration: 2.3,
|
||||
repeat: Infinity,
|
||||
repeatType: "loop",
|
||||
ease: "linear",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* FINAL CONFIRMATION PULSE AT DEPLOY STAGE */}
|
||||
{!prefers && (
|
||||
<motion.circle
|
||||
cx={stages[2].x + stages[2].w / 2}
|
||||
cy={stages[2].y + stages[2].h / 2}
|
||||
r={24}
|
||||
fill={accent}
|
||||
opacity={0.1}
|
||||
initial={{ scale: 0.9, opacity: 0 }}
|
||||
animate={{ scale: [1, 1.15, 1], opacity: [0.05, 0.3, 0.05] }}
|
||||
transition={{
|
||||
duration: 1.8,
|
||||
repeat: Infinity,
|
||||
repeatType: "mirror",
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
filter="url(#orch-glow)"
|
||||
/>
|
||||
)}
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
151
src/pages/home/animations/Meshnetworking.tsx
Normal file
151
src/pages/home/animations/Meshnetworking.tsx
Normal file
@@ -0,0 +1,151 @@
|
||||
"use client";
|
||||
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
import clsx from "clsx";
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
accent?: string;
|
||||
stroke?: string;
|
||||
};
|
||||
|
||||
const W = 760;
|
||||
const H = 420;
|
||||
|
||||
export default function MeshNetworking({
|
||||
className,
|
||||
accent = "#00b8db",
|
||||
stroke = "#4B5563",
|
||||
}: Props) {
|
||||
const prefersReduced = useReducedMotion();
|
||||
|
||||
// Nodes in a real mesh (hex pattern)
|
||||
const nodes = [
|
||||
{ x: 200, y: 120 },
|
||||
{ x: 380, y: 100 },
|
||||
{ x: 560, y: 120 },
|
||||
|
||||
{ x: 130, y: 240 },
|
||||
{ x: 320, y: 240 },
|
||||
{ x: 540, y: 240 },
|
||||
{ x: 630, y: 240 },
|
||||
|
||||
{ x: 260, y: 340 },
|
||||
{ x: 440, y: 340 },
|
||||
];
|
||||
|
||||
// All connected pairs (mesh links)
|
||||
const links = [
|
||||
[0,1],[1,2],
|
||||
[0,3],[1,4],[2,5],
|
||||
[3,4],[4,5],[5,6],
|
||||
[3,7],[4,7],[4,8],[5,8],
|
||||
[7,8]
|
||||
];
|
||||
|
||||
const drawLine = (i: number, j: number) => {
|
||||
const a = nodes[i];
|
||||
const b = nodes[j];
|
||||
return `M ${a.x} ${a.y} L ${b.x} ${b.y}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx("relative overflow-hidden", className)}
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
aria-label="Mesh networking topology"
|
||||
style={{ background: "transparent" }} // ✅ transparent background
|
||||
>
|
||||
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full">
|
||||
|
||||
{/* ✅ Subtle dark grid */}
|
||||
<defs>
|
||||
<pattern id="mesh-grid" width="28" height="28" patternUnits="userSpaceOnUse">
|
||||
<path d="M 28 0 L 0 0 0 28" fill="none" stroke="#2b2a2a" strokeWidth="1" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width={W} height={H} fill="url(#mesh-grid)" />
|
||||
|
||||
{/* ✅ Gray baseline mesh connections */}
|
||||
{links.map(([i, j], idx) => (
|
||||
<motion.path
|
||||
key={`base-${idx}`}
|
||||
d={drawLine(i, j)}
|
||||
stroke={stroke}
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
fill="none"
|
||||
initial={{ pathLength: 0 }}
|
||||
animate={{ pathLength: 1, opacity: 0.4 }}
|
||||
transition={{
|
||||
delay: 0.05 * idx,
|
||||
duration: 0.6,
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* ✅ Cyan signal traveling across mesh diagonally */}
|
||||
{!prefersReduced &&
|
||||
links.map(([i, j], idx) => {
|
||||
const path = drawLine(i, j);
|
||||
return (
|
||||
<motion.circle
|
||||
key={`signal-${idx}`}
|
||||
r={4}
|
||||
fill={accent}
|
||||
style={{ offsetPath: `path('${path}')` }}
|
||||
initial={{ offsetDistance: "0%", opacity: 0 }}
|
||||
animate={{
|
||||
offsetDistance: ["0%", "100%"],
|
||||
opacity: [0, 1, 0],
|
||||
}}
|
||||
transition={{
|
||||
delay: idx * 0.15,
|
||||
duration: 1.8,
|
||||
repeat: Infinity,
|
||||
repeatType: "loop",
|
||||
ease: "linear",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* ✅ Nodes with soft glow */}
|
||||
{nodes.map((n, idx) => (
|
||||
<g key={`node-${idx}`}>
|
||||
<motion.circle
|
||||
cx={n.x}
|
||||
cy={n.y}
|
||||
r={18}
|
||||
fill="#0d0d0d"
|
||||
stroke="#111"
|
||||
strokeWidth={2}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 0.7 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
/>
|
||||
<motion.circle
|
||||
cx={n.x}
|
||||
cy={n.y}
|
||||
r={10}
|
||||
fill={accent}
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
scale: prefersReduced ? 1 : [1, 1.08, 1],
|
||||
}}
|
||||
transition={{
|
||||
duration: 1.6,
|
||||
repeat: prefersReduced ? 0 : Infinity,
|
||||
repeatType: "mirror",
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
/>
|
||||
</g>
|
||||
))}
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
238
src/pages/home/animations/NoCentral.tsx
Normal file
238
src/pages/home/animations/NoCentral.tsx
Normal file
@@ -0,0 +1,238 @@
|
||||
"use client";
|
||||
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
import clsx from "clsx";
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
accent?: string;
|
||||
bg?: string;
|
||||
};
|
||||
|
||||
const W = 760;
|
||||
const H = 420;
|
||||
|
||||
const Node = ({
|
||||
x,
|
||||
y,
|
||||
r = 10,
|
||||
accent = "#00b8db",
|
||||
pulse = false,
|
||||
delay = 0,
|
||||
}: {
|
||||
x: number;
|
||||
y: number;
|
||||
r?: number;
|
||||
accent?: string;
|
||||
pulse?: boolean;
|
||||
delay?: number;
|
||||
}) => {
|
||||
const prefers = useReducedMotion();
|
||||
return (
|
||||
<>
|
||||
<motion.circle
|
||||
cx={x}
|
||||
cy={y}
|
||||
r={r + 8}
|
||||
stroke="#1F2937"
|
||||
strokeWidth={2}
|
||||
fill="none"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 0.8 }}
|
||||
transition={{ delay, duration: 0.6 }}
|
||||
/>
|
||||
<motion.circle
|
||||
cx={x}
|
||||
cy={y}
|
||||
r={r}
|
||||
fill={accent}
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
scale: pulse && !prefers ? [1, 1.1, 1] : 1,
|
||||
}}
|
||||
transition={{
|
||||
delay,
|
||||
duration: pulse && !prefers ? 1.6 : 0.6,
|
||||
repeat: pulse && !prefers ? Infinity : 0,
|
||||
repeatType: "mirror",
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Packet = ({
|
||||
path,
|
||||
delay = 0,
|
||||
accent = "#00b8db",
|
||||
duration = 2.4,
|
||||
}: {
|
||||
path: string;
|
||||
delay?: number;
|
||||
accent?: string;
|
||||
duration?: number;
|
||||
}) => {
|
||||
const prefers = useReducedMotion();
|
||||
return (
|
||||
<motion.circle
|
||||
r={4}
|
||||
fill={accent}
|
||||
initial={{ offsetDistance: "0%", opacity: 0 }}
|
||||
animate={{
|
||||
offsetDistance: ["0%", "100%"],
|
||||
opacity: [0, 1, 0],
|
||||
}}
|
||||
transition={{
|
||||
delay,
|
||||
duration,
|
||||
repeat: !prefers ? Infinity : 0,
|
||||
repeatType: "loop",
|
||||
ease: "linear",
|
||||
}}
|
||||
style={{ offsetPath: `path('${path}')` }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default function NoCentral({
|
||||
className,
|
||||
accent = "#00b8db",
|
||||
bg = "#0a0a0a",
|
||||
}: Props) {
|
||||
const center = { x: 380, y: 210 };
|
||||
const nodes = [
|
||||
{ x: 160, y: 100 },
|
||||
{ x: 270, y: 70 },
|
||||
{ x: 500, y: 90 },
|
||||
{ x: 620, y: 150 },
|
||||
{ x: 220, y: 300 },
|
||||
{ x: 360, y: 340 },
|
||||
{ x: 530, y: 290 },
|
||||
];
|
||||
|
||||
const links = [
|
||||
[nodes[0], nodes[1]],
|
||||
[nodes[1], nodes[2]],
|
||||
[nodes[2], nodes[3]],
|
||||
[nodes[0], nodes[4]],
|
||||
[nodes[4], nodes[5]],
|
||||
[nodes[5], nodes[6]],
|
||||
[nodes[1], nodes[5]],
|
||||
[nodes[3], nodes[6]],
|
||||
];
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx("relative overflow-hidden", className)}
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
aria-label="Distributed network illustration"
|
||||
style={{ background: bg }}
|
||||
>
|
||||
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full">
|
||||
{/* Background grid */}
|
||||
<defs>
|
||||
<pattern id="grid-dark" width="28" height="28" patternUnits="userSpaceOnUse">
|
||||
<path d="M 28 0 L 0 0 0 28" fill="none" stroke="#0f0f0f" strokeWidth="1" />
|
||||
</pattern>
|
||||
<radialGradient id="glow" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stopColor={accent} stopOpacity="0.12" />
|
||||
<stop offset="100%" stopColor={accent} stopOpacity="0" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<rect width={W} height={H} fill="url(#grid-dark)" />
|
||||
|
||||
{/* Cyan glow field */}
|
||||
<circle cx={center.x} cy={center.y} r={200} fill="url(#glow)" opacity={0.4} />
|
||||
|
||||
{/* Static connection lines */}
|
||||
{links.map(([a, b], i) => (
|
||||
<motion.path
|
||||
key={`line-${i}`}
|
||||
d={`M ${a.x} ${a.y} L ${b.x} ${b.y}`}
|
||||
stroke="#1F2937"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
fill="none"
|
||||
initial={{ pathLength: 0, opacity: 0 }}
|
||||
animate={{ pathLength: 1, opacity: 0.4 }}
|
||||
transition={{
|
||||
delay: 0.05 * i,
|
||||
duration: 0.8,
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Cyan animated signal lines */}
|
||||
{links.slice(0, 5).map(([a, b], i) => (
|
||||
<motion.path
|
||||
key={`signal-${i}`}
|
||||
d={`M ${a.x} ${a.y} L ${b.x} ${b.y}`}
|
||||
stroke={accent}
|
||||
strokeWidth={2.5}
|
||||
strokeDasharray="10 10"
|
||||
strokeLinecap="round"
|
||||
fill="none"
|
||||
animate={{ opacity: [0.3, 0.8, 0.3] }}
|
||||
transition={{
|
||||
duration: 2,
|
||||
repeat: Infinity,
|
||||
delay: i * 0.2,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Circulating packets on random links */}
|
||||
{links.map(([a, b], i) => (
|
||||
<Packet
|
||||
key={`pkt-${i}`}
|
||||
path={`M ${a.x} ${a.y} L ${b.x} ${b.y}`}
|
||||
delay={i * 0.4}
|
||||
accent={accent}
|
||||
duration={3}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Pulsing nodes */}
|
||||
{nodes.map((n, i) => (
|
||||
<Node
|
||||
key={`node-${i}`}
|
||||
x={n.x}
|
||||
y={n.y}
|
||||
r={10}
|
||||
accent={accent}
|
||||
pulse={i % 2 === 0}
|
||||
delay={i * 0.1}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Faded red “no central” mark at middle */}
|
||||
<motion.circle
|
||||
cx={center.x}
|
||||
cy={center.y}
|
||||
r={18}
|
||||
fill="none"
|
||||
stroke="#FF4D4D"
|
||||
strokeWidth={3}
|
||||
strokeDasharray="6 4"
|
||||
initial={{ scale: 0, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 0.7 }}
|
||||
transition={{ delay: 1, duration: 0.8 }}
|
||||
/>
|
||||
<motion.path
|
||||
d={`M ${center.x - 10} ${center.y - 10} L ${center.x + 10} ${center.y + 10} M ${center.x - 10} ${center.y + 10} L ${center.x + 10} ${center.y - 10}`}
|
||||
stroke="#FF4D4D"
|
||||
strokeWidth={3}
|
||||
strokeLinecap="round"
|
||||
initial={{ opacity: 0, scale: 0 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ delay: 1.2, duration: 0.6 }}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
175
src/pages/home/animations/NoControl.tsx
Normal file
175
src/pages/home/animations/NoControl.tsx
Normal file
@@ -0,0 +1,175 @@
|
||||
"use client";
|
||||
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
import clsx from "clsx";
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
accent?: string;
|
||||
bg?: string;
|
||||
};
|
||||
|
||||
const W = 760;
|
||||
const H = 420;
|
||||
|
||||
/** Node component */
|
||||
const Node = ({
|
||||
x,
|
||||
y,
|
||||
r = 14,
|
||||
accent = "#00b8db",
|
||||
pulse = false,
|
||||
faded = false,
|
||||
}: {
|
||||
x: number;
|
||||
y: number;
|
||||
r?: number;
|
||||
accent?: string;
|
||||
pulse?: boolean;
|
||||
faded?: boolean;
|
||||
}) => {
|
||||
const prefers = useReducedMotion();
|
||||
return (
|
||||
<>
|
||||
<motion.circle
|
||||
cx={x}
|
||||
cy={y}
|
||||
r={r + 10}
|
||||
stroke="#1F2937"
|
||||
strokeWidth={2}
|
||||
fill="none"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: faded ? 0.3 : 0.9 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
/>
|
||||
<motion.circle
|
||||
cx={x}
|
||||
cy={y}
|
||||
r={r}
|
||||
fill={accent}
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{
|
||||
opacity: faded ? 0.3 : 1,
|
||||
scale: pulse && !prefers ? [1, 1.12, 1] : 1,
|
||||
}}
|
||||
transition={{
|
||||
duration: pulse && !prefers ? 1.8 : 0.6,
|
||||
repeat: pulse && !prefers ? Infinity : 0,
|
||||
repeatType: "mirror",
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/** Moving packet along a path */
|
||||
const Packet = ({
|
||||
path,
|
||||
delay = 0,
|
||||
accent = "#00b8db",
|
||||
}: {
|
||||
path: string;
|
||||
delay?: number;
|
||||
accent?: string;
|
||||
}) => {
|
||||
const prefers = useReducedMotion();
|
||||
return (
|
||||
<motion.circle
|
||||
r={5}
|
||||
fill={accent}
|
||||
initial={{ offsetDistance: "0%", opacity: 0 }}
|
||||
animate={{
|
||||
offsetDistance: ["0%", "100%"],
|
||||
opacity: [0, 1, 0],
|
||||
}}
|
||||
transition={{
|
||||
delay,
|
||||
duration: 1.6,
|
||||
repeat: !prefers ? Infinity : 0,
|
||||
repeatType: "loop",
|
||||
ease: "linear",
|
||||
}}
|
||||
style={{
|
||||
offsetPath: `path('${path}')`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default function NoControl({
|
||||
className,
|
||||
accent = "#00b8db",
|
||||
bg = "#0a0a0a",
|
||||
}: Props) {
|
||||
const center = { x: 380, y: 210 };
|
||||
const outer = [
|
||||
{ x: 160, y: 120 },
|
||||
{ x: 600, y: 120 },
|
||||
{ x: 160, y: 300 },
|
||||
{ x: 600, y: 300 },
|
||||
];
|
||||
|
||||
const link = (a: any, b: any) => `M ${a.x} ${a.y} L ${b.x} ${b.y}`;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx("relative overflow-hidden", className)}
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
aria-label="No single point of control illustration"
|
||||
style={{ background: bg }}
|
||||
>
|
||||
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full">
|
||||
{/* Background grid */}
|
||||
<defs>
|
||||
<pattern id="grid-dark" width="28" height="28" patternUnits="userSpaceOnUse">
|
||||
<path d="M 28 0 L 0 0 0 28" fill="none" stroke="#0f0f0f" strokeWidth="1" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width={W} height={H} fill="url(#grid-dark)" />
|
||||
|
||||
{/* Connections between outer nodes (decentralized ring) */}
|
||||
<motion.path
|
||||
d={`M ${outer[0].x} ${outer[0].y}
|
||||
L ${outer[1].x} ${outer[1].y}
|
||||
L ${outer[3].x} ${outer[3].y}
|
||||
L ${outer[2].x} ${outer[2].y} Z`}
|
||||
stroke="#1F2937"
|
||||
strokeWidth={3}
|
||||
strokeLinecap="round"
|
||||
fill="none"
|
||||
initial={{ pathLength: 0, opacity: 0.3 }}
|
||||
animate={{ pathLength: 1, opacity: 0.6 }}
|
||||
transition={{ duration: 1.2, ease: [0.22, 1, 0.36, 1] }}
|
||||
/>
|
||||
|
||||
{/* Cyan pulsing signals around the ring */}
|
||||
{outer.map((o, i) => {
|
||||
const next = outer[(i + 1) % outer.length];
|
||||
const p = link(o, next);
|
||||
return <Packet key={`p-${i}`} path={p} delay={i * 0.4} accent={accent} />;
|
||||
})}
|
||||
|
||||
{/* Faded center node (represents the “former” single point) */}
|
||||
<Node x={center.x} y={center.y} r={18} accent={accent} faded pulse={false} />
|
||||
|
||||
{/* Outer sovereign nodes */}
|
||||
{outer.map((n, i) => (
|
||||
<Node key={i} x={n.x} y={n.y} r={14} accent={accent} pulse />
|
||||
))}
|
||||
|
||||
{/* Cross mark over center node (control denied) */}
|
||||
<motion.path
|
||||
d={`M ${center.x - 12} ${center.y - 12} L ${center.x + 12} ${center.y + 12} M ${center.x - 12} ${center.y + 12} L ${center.x + 12} ${center.y - 12}`}
|
||||
stroke="#FF4D4D"
|
||||
strokeWidth={3}
|
||||
strokeLinecap="round"
|
||||
initial={{ opacity: 0, scale: 0 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ delay: 0.8, duration: 0.6 }}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
247
src/pages/home/animations/NoExtraction.tsx
Normal file
247
src/pages/home/animations/NoExtraction.tsx
Normal file
@@ -0,0 +1,247 @@
|
||||
"use client";
|
||||
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
import clsx from "clsx";
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
accent?: string;
|
||||
bg?: string;
|
||||
};
|
||||
|
||||
const W = 760;
|
||||
const H = 420;
|
||||
|
||||
/** Node = local data cluster */
|
||||
const Node = ({
|
||||
x,
|
||||
y,
|
||||
r = 10,
|
||||
accent = "#00b8db",
|
||||
pulse = false,
|
||||
delay = 0,
|
||||
}: {
|
||||
x: number;
|
||||
y: number;
|
||||
r?: number;
|
||||
accent?: string;
|
||||
pulse?: boolean;
|
||||
delay?: number;
|
||||
}) => {
|
||||
const prefers = useReducedMotion();
|
||||
return (
|
||||
<>
|
||||
{/* outer glow */}
|
||||
<motion.circle
|
||||
cx={x}
|
||||
cy={y}
|
||||
r={r + 8}
|
||||
stroke="#1F2937"
|
||||
strokeWidth={2}
|
||||
fill="none"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 0.9 }}
|
||||
transition={{ delay, duration: 0.6 }}
|
||||
/>
|
||||
{/* data core */}
|
||||
<motion.circle
|
||||
cx={x}
|
||||
cy={y}
|
||||
r={r}
|
||||
fill={accent}
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
scale: pulse && !prefers ? [1, 1.15, 1] : 1,
|
||||
}}
|
||||
transition={{
|
||||
delay,
|
||||
duration: pulse && !prefers ? 1.6 : 0.6,
|
||||
repeat: pulse && !prefers ? Infinity : 0,
|
||||
repeatType: "mirror",
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/** A data particle traveling along a given path */
|
||||
const Particle = ({
|
||||
path,
|
||||
delay = 0,
|
||||
accent = "#00b8db",
|
||||
duration = 2,
|
||||
reverse = false,
|
||||
}: {
|
||||
path: string;
|
||||
delay?: number;
|
||||
accent?: string;
|
||||
duration?: number;
|
||||
reverse?: boolean;
|
||||
}) => {
|
||||
const prefers = useReducedMotion();
|
||||
return (
|
||||
<motion.circle
|
||||
r={4}
|
||||
fill={accent}
|
||||
initial={{ offsetDistance: reverse ? "100%" : "0%", opacity: 0 }}
|
||||
animate={{
|
||||
offsetDistance: reverse ? ["100%", "0%"] : ["0%", "100%"],
|
||||
opacity: [0, 1, 0],
|
||||
}}
|
||||
transition={{
|
||||
delay,
|
||||
duration,
|
||||
repeat: !prefers ? Infinity : 0,
|
||||
repeatType: "mirror",
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
style={{
|
||||
offsetPath: `path('${path}')`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default function NoExtraction({
|
||||
className,
|
||||
accent = "#00b8db",
|
||||
bg = "#0a0a0a",
|
||||
}: Props) {
|
||||
const center = { x: 380, y: 210 };
|
||||
const WBOX = 360;
|
||||
const HBOX = 220;
|
||||
const boxX = center.x - WBOX / 2;
|
||||
const boxY = center.y - HBOX / 2;
|
||||
|
||||
// local nodes within boundary
|
||||
const nodes = [
|
||||
{ x: center.x - 80, y: center.y - 40 },
|
||||
{ x: center.x + 60, y: center.y - 50 },
|
||||
{ x: center.x, y: center.y + 50 },
|
||||
{ x: center.x - 50, y: center.y + 30 },
|
||||
];
|
||||
|
||||
// internal circulation paths
|
||||
const internalPaths = [
|
||||
`M ${center.x - 80} ${center.y - 40} Q ${center.x} ${center.y - 80} ${center.x + 60} ${center.y - 50}`,
|
||||
`M ${center.x - 50} ${center.y + 30} Q ${center.x} ${center.y + 70} ${center.x} ${center.y + 50}`,
|
||||
];
|
||||
|
||||
// escape attempt path
|
||||
const attemptPath = `M ${center.x} ${center.y} Q ${center.x + 200} ${center.y - 50} ${center.x + 130} ${center.y}`;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx("relative overflow-hidden", className)}
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
aria-label="No data extraction illustration"
|
||||
style={{ background: bg }}
|
||||
>
|
||||
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full">
|
||||
{/* Background grid */}
|
||||
<defs>
|
||||
<pattern id="grid-dark" width="28" height="28" patternUnits="userSpaceOnUse">
|
||||
<path d="M 28 0 L 0 0 0 28" fill="none" stroke="#0f0f0f" strokeWidth="1" />
|
||||
</pattern>
|
||||
|
||||
{/* Cyan glow gradient */}
|
||||
<radialGradient id="glow" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stopColor={accent} stopOpacity="0.15" />
|
||||
<stop offset="100%" stopColor={accent} stopOpacity="0" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<rect width={W} height={H} fill="url(#grid-dark)" />
|
||||
|
||||
{/* Cyan ambient glow field */}
|
||||
<circle
|
||||
cx={center.x}
|
||||
cy={center.y}
|
||||
r={160}
|
||||
fill="url(#glow)"
|
||||
opacity={0.3}
|
||||
/>
|
||||
|
||||
{/* Secure boundary box */}
|
||||
<motion.rect
|
||||
x={boxX}
|
||||
y={boxY}
|
||||
width={WBOX}
|
||||
height={HBOX}
|
||||
rx={16}
|
||||
stroke="#1F2937"
|
||||
strokeWidth={3}
|
||||
fill="none"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
/>
|
||||
|
||||
{/* Cyan encryption border */}
|
||||
<motion.rect
|
||||
x={boxX + 4}
|
||||
y={boxY + 4}
|
||||
width={WBOX - 8}
|
||||
height={HBOX - 8}
|
||||
rx={14}
|
||||
stroke={accent}
|
||||
strokeWidth={2}
|
||||
strokeDasharray="10 6"
|
||||
fill="none"
|
||||
animate={{ opacity: [0.4, 0.9, 0.4] }}
|
||||
transition={{
|
||||
duration: 2,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Internal data circulation */}
|
||||
{internalPaths.map((p, i) => (
|
||||
<Particle
|
||||
key={`int-${i}`}
|
||||
path={p}
|
||||
delay={i * 0.6}
|
||||
accent={accent}
|
||||
duration={3}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Local data nodes */}
|
||||
{nodes.map((n, i) => (
|
||||
<Node
|
||||
key={i}
|
||||
x={n.x}
|
||||
y={n.y}
|
||||
r={10}
|
||||
accent={accent}
|
||||
pulse={i === 2}
|
||||
delay={i * 0.2}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Data packet attempting to leave */}
|
||||
<Particle path={attemptPath} delay={0.5} accent={accent} duration={2.2} />
|
||||
|
||||
{/* Impact glow at boundary */}
|
||||
<motion.circle
|
||||
cx={center.x + 130}
|
||||
cy={center.y - 10}
|
||||
r={10}
|
||||
fill="#FF4D4D"
|
||||
initial={{ scale: 0, opacity: 0 }}
|
||||
animate={{ scale: [0, 1.2, 0.8], opacity: [0, 1, 0] }}
|
||||
transition={{
|
||||
delay: 0.9,
|
||||
duration: 1.4,
|
||||
repeat: Infinity,
|
||||
repeatDelay: 1.5,
|
||||
ease: "easeOut",
|
||||
}}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
236
src/pages/home/animations/SovereignCompute.tsx
Normal file
236
src/pages/home/animations/SovereignCompute.tsx
Normal file
@@ -0,0 +1,236 @@
|
||||
"use client";
|
||||
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
import clsx from "clsx";
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
accent?: string; // cyan highlight
|
||||
gridStroke?: string; // grid color (default #2b2a2a as requested)
|
||||
};
|
||||
|
||||
const W = 760;
|
||||
const H = 420;
|
||||
|
||||
const Server = ({
|
||||
x,
|
||||
y,
|
||||
w = 140,
|
||||
h = 90,
|
||||
rows = 3,
|
||||
}: {
|
||||
x: number;
|
||||
y: number;
|
||||
w?: number;
|
||||
h?: number;
|
||||
rows?: number;
|
||||
}) => {
|
||||
const rowH = (h - 24) / rows;
|
||||
|
||||
return (
|
||||
<g>
|
||||
{/* chassis */}
|
||||
<rect x={x} y={y} width={w} height={h} rx={12} fill="#0d0d0d" stroke="#1a1a1a" strokeWidth={2} />
|
||||
{/* bays */}
|
||||
{Array.from({ length: rows }).map((_, i) => (
|
||||
<g key={i}>
|
||||
<rect
|
||||
x={x + 12}
|
||||
y={y + 12 + i * rowH}
|
||||
width={w - 24}
|
||||
height={rowH - 10}
|
||||
rx={8}
|
||||
fill="#111111"
|
||||
stroke="#1f1f1f"
|
||||
strokeWidth={1}
|
||||
/>
|
||||
{/* bay indicators */}
|
||||
<rect x={x + 20} y={y + 22 + i * rowH} width={10} height={6} rx={2} fill="#16a34a" opacity={0.8} />
|
||||
<rect x={x + 36} y={y + 22 + i * rowH} width={10} height={6} rx={2} fill="#9ca3af" opacity={0.6} />
|
||||
<rect x={x + 52} y={y + 22 + i * rowH} width={10} height={6} rx={2} fill="#9ca3af" opacity={0.6} />
|
||||
</g>
|
||||
))}
|
||||
{/* subtle top highlight */}
|
||||
<rect x={x + 2} y={y + 2} width={w - 4} height={10} rx={5} fill="#0f0f0f" />
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
export default function SovereignCompute({
|
||||
className,
|
||||
accent = "#00b8db",
|
||||
gridStroke = "#2b2a2a",
|
||||
}: Props) {
|
||||
const prefers = useReducedMotion();
|
||||
|
||||
// Positions
|
||||
const left = { x: 140, y: 120 };
|
||||
const mid = { x: 310, y: 150 };
|
||||
const right= { x: 500, y: 120 };
|
||||
|
||||
// Shield position (trust boundary)
|
||||
const shield = { cx: 600, cy: 250, r: 38 };
|
||||
|
||||
// Attestation paths from racks to shield
|
||||
const pathFromLeft = `M ${left.x + 140} ${left.y + 45} C 330 150, 470 200, ${shield.cx - 50} ${shield.cy}`;
|
||||
const pathFromMid = `M ${mid.x + 140} ${mid.y + 45} C 420 190, 500 215, ${shield.cx - 50} ${shield.cy}`;
|
||||
const pathFromRight = `M ${right.x + 140} ${right.y + 45} C 520 180, 560 220, ${shield.cx - 50} ${shield.cy}`;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx("relative overflow-hidden", className)}
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
aria-label="Sovereign compute: execution only on hardware you control"
|
||||
style={{ background: "transparent" }}
|
||||
>
|
||||
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full">
|
||||
{/* GRID (transparent bg, subtle dark grid) */}
|
||||
<defs>
|
||||
<pattern id="grid-secure" width="28" height="28" patternUnits="userSpaceOnUse">
|
||||
<path d="M 28 0 L 0 0 0 28" fill="none" stroke={gridStroke} strokeWidth="1" opacity="0.45" />
|
||||
</pattern>
|
||||
|
||||
{/* soft glow filter for shield */}
|
||||
<filter id="glow">
|
||||
<feGaussianBlur stdDeviation="6" result="coloredBlur" />
|
||||
<feMerge>
|
||||
<feMergeNode in="coloredBlur" />
|
||||
<feMergeNode in="SourceGraphic" />
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<rect width={W} height={H} fill="url(#grid-secure)" />
|
||||
|
||||
{/* RACKS (hardware you control) */}
|
||||
<Server x={left.x} y={left.y} />
|
||||
<Server x={mid.x} y={mid.y} />
|
||||
<Server x={right.x} y={right.y} />
|
||||
|
||||
{/* BASELINES for attestation links */}
|
||||
{[pathFromLeft, pathFromMid, pathFromRight].map((d, i) => (
|
||||
<motion.path
|
||||
key={`base-${i}`}
|
||||
d={d}
|
||||
fill="none"
|
||||
stroke="#303030"
|
||||
strokeWidth={3}
|
||||
strokeLinecap="round"
|
||||
initial={{ pathLength: 0, opacity: 0 }}
|
||||
animate={{ pathLength: 1, opacity: 0.6 }}
|
||||
transition={{ delay: 0.15 * i, duration: 0.8, ease: [0.22, 1, 0.36, 1] }}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* MOVING ATTESTATION TOKENS (signatures/hashes) */}
|
||||
{!prefers && [pathFromLeft, pathFromMid, pathFromRight].map((d, i) => (
|
||||
<motion.circle
|
||||
key={`token-${i}`}
|
||||
r={5}
|
||||
fill={accent}
|
||||
style={{ offsetPath: `path('${d}')` }}
|
||||
initial={{ offsetDistance: "0%", opacity: 0 }}
|
||||
animate={{ offsetDistance: ["0%", "100%"], opacity: [0, 1, 0] }}
|
||||
transition={{
|
||||
delay: 0.25 * i,
|
||||
duration: 2.0,
|
||||
repeat: Infinity,
|
||||
repeatType: "loop",
|
||||
ease: "linear",
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* TRUST BOUNDARY + SHIELD (hardware attestation target) */}
|
||||
<motion.circle
|
||||
cx={shield.cx}
|
||||
cy={shield.cy}
|
||||
r={shield.r + 18}
|
||||
fill="none"
|
||||
stroke="#1f1f1f"
|
||||
strokeWidth={2}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 0.9 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
/>
|
||||
|
||||
{/* cyan halo */}
|
||||
{!prefers && (
|
||||
<motion.circle
|
||||
cx={shield.cx}
|
||||
cy={shield.cy}
|
||||
r={shield.r + 6}
|
||||
fill={accent}
|
||||
opacity={0.12}
|
||||
initial={{ scale: 0.95, opacity: 0 }}
|
||||
animate={{ scale: [1, 1.12, 1], opacity: [0.1, 0.35, 0.1] }}
|
||||
transition={{ duration: 1.8, repeat: Infinity, repeatType: "mirror", ease: [0.22, 1, 0.36, 1] }}
|
||||
filter="url(#glow)"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Shield outline */}
|
||||
<motion.path
|
||||
d={`M ${shield.cx} ${shield.cy - 30}
|
||||
L ${shield.cx + 28} ${shield.cy - 15}
|
||||
L ${shield.cx + 22} ${shield.cy + 24}
|
||||
L ${shield.cx} ${shield.cy + 34}
|
||||
L ${shield.cx - 22} ${shield.cy + 24}
|
||||
L ${shield.cx - 28} ${shield.cy - 15}
|
||||
Z`}
|
||||
fill="none"
|
||||
stroke={accent}
|
||||
strokeWidth={3}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
initial={{ pathLength: 0, opacity: 0 }}
|
||||
animate={{ pathLength: 1, opacity: 1 }}
|
||||
transition={{ duration: 0.9, ease: [0.22, 1, 0.36, 1] }}
|
||||
filter="url(#glow)"
|
||||
/>
|
||||
|
||||
{/* Check mark (verified hardware) */}
|
||||
<motion.path
|
||||
d={`M ${shield.cx - 14} ${shield.cy + 6} L ${shield.cx - 2} ${shield.cy + 18} L ${shield.cx + 18} ${shield.cy - 6}`}
|
||||
fill="none"
|
||||
stroke={accent}
|
||||
strokeWidth={4}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
initial={{ pathLength: 0 }}
|
||||
animate={{ pathLength: 1 }}
|
||||
transition={{ duration: 0.9, delay: 0.2, ease: [0.22, 1, 0.36, 1] }}
|
||||
filter="url(#glow)"
|
||||
/>
|
||||
|
||||
{/* LOCKED EXECUTION BOUNDARY (subtle arc) */}
|
||||
<motion.path
|
||||
d={`M ${shield.cx - 70} ${shield.cy + 46} Q ${shield.cx} ${shield.cy + 76} ${shield.cx + 70} ${shield.cy + 46}`}
|
||||
fill="none"
|
||||
stroke="#2e2e2e"
|
||||
strokeWidth={2}
|
||||
initial={{ pathLength: 0, opacity: 0 }}
|
||||
animate={{ pathLength: 1, opacity: 0.6 }}
|
||||
transition={{ duration: 0.8, delay: 0.3 }}
|
||||
/>
|
||||
|
||||
{/* Cyan confirmation pulses emanating out (execution allowed) */}
|
||||
{!prefers && [0, 1].map((i) => (
|
||||
<motion.circle
|
||||
key={`emit-${i}`}
|
||||
cx={shield.cx}
|
||||
cy={shield.cy}
|
||||
r={shield.r + 12}
|
||||
fill="none"
|
||||
stroke={accent}
|
||||
strokeWidth={2}
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: [0.0, 0.5, 0.0], scale: [1, 1.15, 1.3] }}
|
||||
transition={{ duration: 1.8, delay: i * 0.3, repeat: Infinity, ease: [0.22, 1, 0.36, 1] }}
|
||||
/>
|
||||
))}
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user