forked from emre/www_projectmycelium_com
refactor: redesign pods page with bento grid layout and improved benefits sections
- Increased CloudHeroNew vertical padding on large screens (lg:py-16 to lg:py-24) - Reduced spacing between description paragraphs in CloudHeroNew (mt-4 to mt-2) - Created PodsBento component with animated bento grid showcasing pod benefits - Added animations for data control, connectivity, security, and resilience features - Refactored PodsDesign from accordion layout to centered intro with 4-column grid - Create
This commit is contained in:
@@ -10,7 +10,7 @@ export function CloudHeroNew({ onGetStartedClick = () => {} }: { onGetStartedCli
|
||||
style={{ backgroundImage: "url('/images/cloudhero4.webp')", backgroundSize: "contain" }}
|
||||
>
|
||||
{/* Inner padding */}
|
||||
<div className="px-6 py-16 lg:py-16">
|
||||
<div className="px-6 py-16 lg:py-24">
|
||||
<div className="max-w-2xl lg:pl-6">
|
||||
<Eyebrow>
|
||||
MYCELIUM CLOUD
|
||||
@@ -21,7 +21,7 @@ export function CloudHeroNew({ onGetStartedClick = () => {} }: { onGetStartedCli
|
||||
<p className="mt-6 text-lg text-gray-600">
|
||||
Run compute, storage, and AI resources on infrastructure you control.
|
||||
</p>
|
||||
<p className="mt-4 text-lg text-gray-600">
|
||||
<p className="mt-2 text-lg text-gray-600">
|
||||
The Mycelium Cloud runs on a distributed grid of independent nodes,
|
||||
delivering secure, scalable performance wherever your users or data live.
|
||||
</p>
|
||||
|
||||
160
src/pages/pods/PodsBento.tsx
Normal file
160
src/pages/pods/PodsBento.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
"use client";
|
||||
|
||||
import { Eyebrow, H3, P } from "@/components/Texts";
|
||||
import NoExtraction from "./animations/NoExtraction";
|
||||
import NoControl from "./animations/NoControl";
|
||||
import NoCentral from "./animations/NoCentral";
|
||||
import NoSinglePoint from "./animations/NoSinglePoint";
|
||||
|
||||
const deterministicCards = [
|
||||
{
|
||||
id: "intro",
|
||||
eyebrow: "BENEFITS",
|
||||
title: "Runs on Your Own Infrastructure",
|
||||
description:
|
||||
"Each Pod lives on your own hardware or on trusted local nodes in the Mycelium Network. There is no central cloud and no company in the middle. You are not uploading your life to the cloud. You are running it yourself.",
|
||||
animation: null,
|
||||
colSpan: "lg:col-span-3",
|
||||
rowSpan: "lg:row-span-1",
|
||||
custom: true,
|
||||
noBorder: true,
|
||||
},
|
||||
|
||||
{
|
||||
id: "data",
|
||||
label: "Data Control",
|
||||
title: "Your Data Lives on Your Pods",
|
||||
description:
|
||||
"Full control of where your data is stored and how it’s shared.",
|
||||
animation: <NoCentral className="lg:-mt-12" />,
|
||||
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: "connectivity",
|
||||
label: "Connectivity",
|
||||
title: "Direct Pod-to-Pod Networking",
|
||||
description:
|
||||
"Direct connections between Pods for faster, private communication.",
|
||||
animation: <NoExtraction className="lg:-mt-12" />,
|
||||
colSpan: "lg:col-span-2",
|
||||
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: "security",
|
||||
label: "Security",
|
||||
title: "No One Can Spy or Shut You Down",
|
||||
description:
|
||||
"Independence from corporate servers or cloud outages.",
|
||||
animation: <NoSinglePoint />,
|
||||
colSpan: "lg:col-span-2",
|
||||
rowSpan: "lg:row-span-1",
|
||||
rounded: "",
|
||||
innerRounded: "",
|
||||
},
|
||||
|
||||
{
|
||||
id: "resilience",
|
||||
label: "Resilience",
|
||||
title: "Resilient Even if Nodes Disconnect",
|
||||
description:
|
||||
"Continuous availability even if one node disconnects.",
|
||||
animation: <NoControl />,
|
||||
colSpan: "lg:col-span-2",
|
||||
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 PodsBento() {
|
||||
return (
|
||||
<section className="relative w-full bg-[#121212] overflow-hidden">
|
||||
{/* TOP 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 flex flex-col ${card.colSpan} ${card.rowSpan} transition-transform duration-300 hover:scale-102 group`}
|
||||
>
|
||||
{/* DISABLE OUTER WRAPPER ON INTRO CARD */}
|
||||
{!card.noBorder && (
|
||||
<div
|
||||
className={`absolute inset-0 rounded-md border border-gray-800 bg-[#111111] ${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}`}
|
||||
>
|
||||
{/* ANIMATION */}
|
||||
{card.animation ? (
|
||||
<div className="lg:h-64 h-48 w-full overflow-hidden bg-transparent flex items-center justify-center">
|
||||
<div className="w-full h-full object-cover">
|
||||
{card.animation}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-48 w-full flex items-center justify-center bg-transparent" />
|
||||
)}
|
||||
|
||||
{/* TEXT AREA */}
|
||||
<div className="px-8 pt-4 pb-6">
|
||||
{card.custom ? (
|
||||
<>
|
||||
<Eyebrow className="text-cyan-500">
|
||||
{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>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{card.label && (
|
||||
<p className="text-xs uppercase tracking-[0.16em] text-cyan-500">
|
||||
{card.label}
|
||||
</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-300">
|
||||
{card.description}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* OUTLINE SHADOW */}
|
||||
{!card.noBorder && (
|
||||
<div
|
||||
className={`pointer-events-none absolute inset-0 rounded-lg shadow-sm outline outline-black/5 ${card.rounded}`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* BOTTOM LINE */}
|
||||
<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>
|
||||
);
|
||||
}
|
||||
@@ -1,42 +1,39 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
Disclosure,
|
||||
DisclosureButton,
|
||||
DisclosurePanel,
|
||||
} from '@headlessui/react'
|
||||
import { MinusIcon, PlusIcon } from '@heroicons/react/24/outline'
|
||||
|
||||
import { Eyebrow, H3, H4 } from "@/components/Texts"
|
||||
|
||||
import { Eyebrow, H3, P, Small } from "@/components/Texts"
|
||||
|
||||
const product = {
|
||||
subtitle: "Federation",
|
||||
subtitle: "BENEFITS",
|
||||
name: "Runs on Your Own Infrastructure",
|
||||
description: `
|
||||
<p>
|
||||
Each Pod lives on your own hardware or on trusted local nodes in the Mycelium Network.
|
||||
There is no central cloud and no company in the middle. You are not uploading your life to the cloud. You are running it yourself.
|
||||
There is no central cloud and no company in the middle. You are not uploading your life
|
||||
to the cloud. You are running it yourself.
|
||||
</p>
|
||||
`,
|
||||
|
||||
details: [
|
||||
{
|
||||
label: "Data Control",
|
||||
name: "Your Data Lives on Your Pods",
|
||||
description:
|
||||
"Full control of where your data is stored and how it’s shared.",
|
||||
},
|
||||
{
|
||||
label: "Connectivity",
|
||||
name: "Direct Pod-to-Pod Networking",
|
||||
description:
|
||||
"Direct connections between Pods for faster, private communication.",
|
||||
},
|
||||
{
|
||||
label: "Security",
|
||||
name: "No One Can Spy or Shut You Down",
|
||||
description:
|
||||
"Independence from corporate servers or cloud outages.",
|
||||
},
|
||||
{
|
||||
label: "Resilience",
|
||||
name: "Resilient Even if Nodes Disconnect",
|
||||
description:
|
||||
"Continuous availability even if one node disconnects.",
|
||||
@@ -44,78 +41,66 @@ There is no central cloud and no company in the middle. You are not uploading yo
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
export function PodsDesign() {
|
||||
return (
|
||||
<div className="bg-white text-gray-900">
|
||||
{/* TOP LINE */}
|
||||
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-100" />
|
||||
<section className="relative w-full bg-[#FDFDFD] overflow-hidden">
|
||||
{/* ▸ Top Spacing Line */}
|
||||
<div className="max-w-7xl bg-[#FDFDFD] mx-auto py-6 border border-t-0 border-b-0 border-gray-100"></div>
|
||||
<div className="w-full border-t border-l border-r border-gray-100" />
|
||||
|
||||
<main className="mx-auto max-w-7xl px-6 lg:px-12 py-12 border border-t-0 border-b-0 border-gray-100">
|
||||
<div className="mx-auto max-w-2xl lg:max-w-none">
|
||||
{/* ▸ Intro Section */}
|
||||
<div className="bg-[#FDFDFD] w-full max-w-7xl mx-auto border border-t-0 border-b-0 border-gray-100">
|
||||
<div className="px-8 py-12 max-w-4xl mx-auto flex flex-col items-center justify-center min-h-[220px] text-center">
|
||||
|
||||
<div className="lg:grid lg:grid-cols-5 lg:items-start lg:gap-x-8">
|
||||
|
||||
{/* IMAGE */}
|
||||
<div className="lg:col-span-2 lg:mt-8 mt-2">
|
||||
<img
|
||||
alt="Mycelium Federation"
|
||||
src="/images/pod1.png"
|
||||
className="aspect-square w-full object-cover rounded-md"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* PRODUCT INFO */}
|
||||
<div className="mt-8 px-4 sm:px-0 lg:mt-0 lg:col-span-3">
|
||||
|
||||
<Eyebrow className="text-cyan-600">
|
||||
<Eyebrow className="uppercase tracking-[0.16em] text-cyan-600">
|
||||
{product.subtitle}
|
||||
</Eyebrow>
|
||||
|
||||
<H4 className="text-gray-900">
|
||||
<H3 className="mt-4 text-black">
|
||||
{product.name}
|
||||
</H4>
|
||||
|
||||
<div
|
||||
className="mt-4 text-gray-700 text-xl"
|
||||
dangerouslySetInnerHTML={{ __html: product.description }}
|
||||
/>
|
||||
|
||||
{/* DETAILS ACCORDION */}
|
||||
<section className="mt-6">
|
||||
<div className="divide-y divide-gray-200 border-t border-cyan-600/60">
|
||||
{product.details.map((detail) => (
|
||||
<Disclosure key={detail.name} as="div">
|
||||
<H3>
|
||||
<DisclosureButton className="group flex w-full items-center justify-between py-6 text-left">
|
||||
<span className="text-lg font-medium text-gray-900">
|
||||
{detail.name}
|
||||
</span>
|
||||
<span className="ml-6 flex items-center">
|
||||
<PlusIcon className="block h-6 w-6 text-gray-500 group-open:hidden" />
|
||||
<MinusIcon className="hidden h-6 w-6 text-cyan-600 group-open:block" />
|
||||
</span>
|
||||
</DisclosureButton>
|
||||
</H3>
|
||||
|
||||
<DisclosurePanel className="pb-6">
|
||||
<p className="text-gray-600 text-base">
|
||||
{detail.description}
|
||||
<P
|
||||
className="mt-4 text-gray-700 text-base leading-relaxed"
|
||||
dangerouslySetInnerHTML={{ __html: product.description }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* ▸ 4-Column Highlights Grid */}
|
||||
<div className="grid lg:grid-cols-4">
|
||||
{product.details.map((item) => (
|
||||
<div
|
||||
key={item.name}
|
||||
className="group relative overflow-hidden border border-gray-100 bg-white p-8 transition hover:border-cyan-400/40 hover:bg-white"
|
||||
>
|
||||
{/* Hover Glow */}
|
||||
<div className="absolute inset-0 bg-linear-to-br from-cyan-200/0 via-cyan-100/20 to-cyan-300/20 opacity-0 transition group-hover:opacity-100" />
|
||||
|
||||
<div className="relative">
|
||||
|
||||
{item.label && (
|
||||
<Small className="text-xs uppercase tracking-[0.16em] text-cyan-600">
|
||||
{item.label}
|
||||
</Small>
|
||||
)}
|
||||
|
||||
<h3 className="mt-4 text-lg font-semibold leading-tight text-black">
|
||||
{item.name}
|
||||
</h3>
|
||||
|
||||
<p className="mt-4 text-sm leading-relaxed text-gray-600">
|
||||
{item.description}
|
||||
</p>
|
||||
</DisclosurePanel>
|
||||
</Disclosure>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* BOTTOM LINE */}
|
||||
<div className="w-full border-b border-gray-100" />
|
||||
{/* ▸ Bottom Spacing */}
|
||||
<div className="w-full border-b border-gray-100 bg-[#FDFDFD]" />
|
||||
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-100" />
|
||||
</div>
|
||||
)
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import Homepod from './Homepod';
|
||||
import { PodsHow } from './PodsHow';
|
||||
import { PodsFeatures } from './PodsFeatures';
|
||||
import { PodsDesign } from './PodsDesign';
|
||||
import { CallToAction } from './CallToAction';
|
||||
import { PodsWhat } from './PodsWhat';
|
||||
import { PodsPro } from './PodsPro';
|
||||
import { PodsBento } from './PodsBento';
|
||||
|
||||
const PodsPage = () => {
|
||||
return (
|
||||
@@ -12,7 +13,8 @@ const PodsPage = () => {
|
||||
<PodsWhat />
|
||||
<PodsFeatures />
|
||||
<PodsHow />
|
||||
<PodsDesign />
|
||||
<PodsBento />
|
||||
<PodsPro />
|
||||
<CallToAction />
|
||||
</>
|
||||
);
|
||||
|
||||
111
src/pages/pods/PodsPro.tsx
Normal file
111
src/pages/pods/PodsPro.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
'use client'
|
||||
|
||||
import { Eyebrow, H3, P, Small } from "@/components/Texts"
|
||||
|
||||
const product = {
|
||||
subtitle: "BENEFITS",
|
||||
name: "Why It’s Different",
|
||||
description: `
|
||||
<p>
|
||||
Pods combine self-hosting with the reach of the Mycelium Network.
|
||||
They run on the same system that powers Mycelium Cloud and AI Agents.
|
||||
</p>
|
||||
`,
|
||||
|
||||
details: [
|
||||
{
|
||||
label: "Identity",
|
||||
name: "Cryptographic identity with optional user logins",
|
||||
description:
|
||||
"Every Pod carries a verifiable identity, enabling secure interactions with optional login layers.",
|
||||
},
|
||||
{
|
||||
label: "Autonomy",
|
||||
name: "No reliance on centralized cloud platforms",
|
||||
description:
|
||||
"Pods run independently without AWS, Google, or corporate cloud systems.",
|
||||
},
|
||||
{
|
||||
label: "Privacy",
|
||||
name: "No data collection or hidden intermediaries",
|
||||
description:
|
||||
"Your data remains local—no telemetry, analytics pipelines, or behind-the-scenes tracking.",
|
||||
},
|
||||
{
|
||||
label: "Reliability",
|
||||
name: "High availability through distributed routing",
|
||||
description:
|
||||
"Pods communicate through resilient, multi-path routing across the Mycelium Network.",
|
||||
},
|
||||
{
|
||||
label: "Future-Proof",
|
||||
name: "Ready to host your personal AI Agent",
|
||||
description:
|
||||
"Pods are built to evolve as agent capabilities grow—deploy your own agent as the system expands.",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export function PodsPro() {
|
||||
return (
|
||||
<section className="relative w-full bg-[#FDFDFD] overflow-hidden">
|
||||
{/* ▸ Top Spacing Line */}
|
||||
<div className="max-w-7xl bg-[#FDFDFD] mx-auto py-6 border border-t-0 border-b-0 border-gray-100"></div>
|
||||
<div className="w-full border-t border-l border-r border-gray-100" />
|
||||
|
||||
{/* ▸ Intro Section */}
|
||||
<div className="bg-[#FDFDFD] w-full max-w-7xl mx-auto border border-t-0 border-b-0 border-gray-100">
|
||||
<div className="px-8 py-12 max-w-4xl mx-auto flex flex-col items-center justify-center min-h-[220px] text-center">
|
||||
|
||||
<Eyebrow className="uppercase tracking-[0.16em] text-cyan-600">
|
||||
{product.subtitle}
|
||||
</Eyebrow>
|
||||
|
||||
<H3 className="mt-4 text-black">
|
||||
{product.name}
|
||||
</H3>
|
||||
|
||||
<P
|
||||
className="mt-4 text-gray-700 text-base leading-relaxed"
|
||||
dangerouslySetInnerHTML={{ __html: product.description }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* ▸ Highlights Grid */}
|
||||
<div className="grid lg:grid-cols-5">
|
||||
{product.details.map((item) => (
|
||||
<div
|
||||
key={item.name}
|
||||
className="group relative overflow-hidden border border-gray-100 bg-white p-8 transition hover:border-cyan-400/40 hover:bg-white"
|
||||
>
|
||||
{/* Hover Glow */}
|
||||
<div className="absolute inset-0 bg-linear-to-br from-cyan-200/0 via-cyan-100/20 to-cyan-300/20 opacity-0 transition group-hover:opacity-100" />
|
||||
|
||||
<div className="relative">
|
||||
|
||||
{item.label && (
|
||||
<Small className="text-xs uppercase tracking-[0.16em] text-cyan-600">
|
||||
{item.label}
|
||||
</Small>
|
||||
)}
|
||||
|
||||
<h3 className="mt-4 text-lg font-semibold leading-tight text-black">
|
||||
{item.name}
|
||||
</h3>
|
||||
|
||||
<p className="mt-4 text-sm leading-relaxed text-gray-600">
|
||||
{item.description}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ▸ Bottom Spacing */}
|
||||
<div className="w-full border-b border-gray-100 bg-[#FDFDFD]" />
|
||||
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-100" />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
189
src/pages/pods/animations/Deterministic.tsx
Normal file
189
src/pages/pods/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/pods/animations/Meshnetworking.tsx
Normal file
151
src/pages/pods/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/pods/animations/NoCentral.tsx
Normal file
238
src/pages/pods/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/pods/animations/NoControl.tsx
Normal file
175
src/pages/pods/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/pods/animations/NoExtraction.tsx
Normal file
247
src/pages/pods/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>
|
||||
);
|
||||
}
|
||||
225
src/pages/pods/animations/NoSinglePoint.tsx
Normal file
225
src/pages/pods/animations/NoSinglePoint.tsx
Normal file
@@ -0,0 +1,225 @@
|
||||
"use client";
|
||||
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
import clsx from "clsx";
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
accent?: string; // cyan
|
||||
bg?: string; // solid dark background
|
||||
gridStroke?: string;
|
||||
};
|
||||
|
||||
const W = 720; // 4:3
|
||||
const H = 540; // 4:3
|
||||
|
||||
export default function NoSinglePoint({
|
||||
className,
|
||||
accent = "#00b8db",
|
||||
bg = "#0b0b0b",
|
||||
gridStroke = "#2b2a2a",
|
||||
}: Props) {
|
||||
const prefers = useReducedMotion();
|
||||
|
||||
// Nodes (left source, right dest, top hub, bottom hub, plus two relays)
|
||||
const nodes = {
|
||||
left: { x: 120, y: H / 2 },
|
||||
right: { x: W - 120, y: H / 2 },
|
||||
top: { x: W / 2, y: 160 },
|
||||
bot: { x: W / 2, y: H - 160 },
|
||||
tl: { x: 240, y: 200 },
|
||||
br: { x: W - 240, y: H - 200 },
|
||||
};
|
||||
|
||||
// Redundant paths from left → right
|
||||
const upperPath = `M ${nodes.left.x} ${nodes.left.y}
|
||||
L ${nodes.tl.x} ${nodes.tl.y}
|
||||
L ${nodes.top.x} ${nodes.top.y}
|
||||
L ${nodes.right.x} ${nodes.right.y}`;
|
||||
|
||||
const lowerPath = `M ${nodes.left.x} ${nodes.left.y}
|
||||
L ${nodes.bot.x} ${nodes.bot.y}
|
||||
L ${nodes.br.x} ${nodes.br.y}
|
||||
L ${nodes.right.x} ${nodes.right.y}`;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx("relative overflow-hidden", className)}
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
style={{ background: bg }}
|
||||
>
|
||||
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full">
|
||||
{/* Subtle dark grid */}
|
||||
<defs>
|
||||
<pattern id="grid-dark-4x3" width="28" height="28" patternUnits="userSpaceOnUse">
|
||||
<path d="M 28 0 L 0 0 0 28" fill="none" stroke={gridStroke} strokeWidth="1" opacity="0.35" />
|
||||
</pattern>
|
||||
<filter id="glow">
|
||||
<feGaussianBlur stdDeviation="3" result="b" />
|
||||
<feMerge>
|
||||
<feMergeNode in="b" />
|
||||
<feMergeNode in="SourceGraphic" />
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<rect width={W} height={H} fill="url(#grid-dark-4x3)" />
|
||||
|
||||
{/* Base links (all potential connectivity) */}
|
||||
{[
|
||||
`M ${nodes.left.x} ${nodes.left.y} L ${nodes.tl.x} ${nodes.tl.y}`,
|
||||
`M ${nodes.tl.x} ${nodes.tl.y} L ${nodes.top.x} ${nodes.top.y}`,
|
||||
`M ${nodes.top.x} ${nodes.top.y} L ${nodes.right.x} ${nodes.right.y}`,
|
||||
`M ${nodes.left.x} ${nodes.left.y} L ${nodes.bot.x} ${nodes.bot.y}`,
|
||||
`M ${nodes.bot.x} ${nodes.bot.y} L ${nodes.br.x} ${nodes.br.y}`,
|
||||
`M ${nodes.br.x} ${nodes.br.y} L ${nodes.right.x} ${nodes.right.y}`,
|
||||
// cross edges (mesh redundancy)
|
||||
`M ${nodes.tl.x} ${nodes.tl.y} L ${nodes.bot.x} ${nodes.bot.y}`,
|
||||
`M ${nodes.top.x} ${nodes.top.y} L ${nodes.br.x} ${nodes.br.y}`,
|
||||
].map((d, i) => (
|
||||
<motion.path
|
||||
key={`base-${i}`}
|
||||
d={d}
|
||||
stroke="#1e1e1e"
|
||||
strokeWidth={3}
|
||||
strokeLinecap="round"
|
||||
fill="none"
|
||||
initial={{ pathLength: 0, opacity: 0.2 }}
|
||||
animate={{ pathLength: 1, opacity: 0.5 }}
|
||||
transition={{ duration: 0.8, delay: i * 0.05, ease: [0.22, 1, 0.36, 1] }}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Highlight the two redundant main routes */}
|
||||
<motion.path
|
||||
d={upperPath}
|
||||
fill="none"
|
||||
stroke="#3a3a3a"
|
||||
strokeWidth={4}
|
||||
strokeLinecap="round"
|
||||
initial={{ pathLength: 0, opacity: 0.6 }}
|
||||
animate={{ pathLength: 1, opacity: 0.6 }}
|
||||
transition={{ duration: 0.9 }}
|
||||
/>
|
||||
<motion.path
|
||||
d={lowerPath}
|
||||
fill="none"
|
||||
stroke="#3a3a3a"
|
||||
strokeWidth={4}
|
||||
strokeLinecap="round"
|
||||
initial={{ pathLength: 0, opacity: 0.6 }}
|
||||
animate={{ pathLength: 1, opacity: 0.6 }}
|
||||
transition={{ duration: 0.9, delay: 0.1 }}
|
||||
/>
|
||||
|
||||
{/* Cyan accent dash showing “preferred/active” path(s) */}
|
||||
<motion.path
|
||||
d={upperPath}
|
||||
fill="none"
|
||||
stroke={accent}
|
||||
strokeWidth={2}
|
||||
strokeDasharray="10 8"
|
||||
initial={{ pathLength: 0, opacity: 0.8 }}
|
||||
animate={{ pathLength: 1, opacity: [0.8, 0.2, 0.8] }} // will fade as "blocked"
|
||||
transition={{ duration: 1.1, repeat: Infinity, repeatType: "reverse" }}
|
||||
filter="url(#glow)"
|
||||
/>
|
||||
<motion.path
|
||||
d={lowerPath}
|
||||
fill="none"
|
||||
stroke={accent}
|
||||
strokeWidth={2}
|
||||
strokeDasharray="10 8"
|
||||
initial={{ pathLength: 0, opacity: 1 }}
|
||||
animate={{ pathLength: 1, opacity: 1 }}
|
||||
transition={{ duration: 1 }}
|
||||
filter="url(#glow)"
|
||||
/>
|
||||
|
||||
{/* Moving packets: one tries upper (gets dimmed at top hub), one uses lower and continues */}
|
||||
{!prefers && (
|
||||
<>
|
||||
{/* Upper path packet (dims near top hub to suggest block/censor but NOT stopping overall flow) */}
|
||||
<motion.circle
|
||||
r={5}
|
||||
fill={accent}
|
||||
style={{ offsetPath: `path('${upperPath}')` }}
|
||||
initial={{ offsetDistance: "0%", opacity: 0.9 }}
|
||||
animate={{
|
||||
offsetDistance: ["0%", "100%"],
|
||||
opacity: [0.9, 0.4, 0.9], // subtle dimming cycle
|
||||
}}
|
||||
transition={{ duration: 3.0, repeat: Infinity, ease: "linear" }}
|
||||
filter="url(#glow)"
|
||||
/>
|
||||
|
||||
{/* Lower path packet (stable flow) */}
|
||||
<motion.circle
|
||||
r={6}
|
||||
fill={accent}
|
||||
style={{ offsetPath: `path('${lowerPath}')` }}
|
||||
initial={{ offsetDistance: "0%", opacity: 1 }}
|
||||
animate={{ offsetDistance: ["0%", "100%"] }}
|
||||
transition={{ duration: 2.4, repeat: Infinity, ease: "linear" }}
|
||||
filter="url(#glow)"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Nodes */}
|
||||
{Object.values(nodes).map((n, i) => (
|
||||
<g key={`node-${i}`}>
|
||||
<circle cx={n.x} cy={n.y} r={20} fill="#0f0f0f" stroke="#1f1f1f" strokeWidth={2} />
|
||||
<motion.circle
|
||||
cx={n.x}
|
||||
cy={n.y}
|
||||
r={8}
|
||||
fill={i === 2 ? "#0f0f0f" : accent} // top hub inner is dark (to hint “blocked” moment)
|
||||
stroke="#222"
|
||||
strokeWidth={2}
|
||||
animate={
|
||||
!prefers
|
||||
? i === 2 // top node (potential choke/attack point) pulses differently
|
||||
? { opacity: [0.5, 0.25, 0.5], scale: [1, 0.95, 1] }
|
||||
: { opacity: [0.9, 1, 0.9], scale: [1, 1.06, 1] }
|
||||
: { opacity: 1 }
|
||||
}
|
||||
transition={{
|
||||
duration: 2.2,
|
||||
repeat: prefers ? 0 : Infinity,
|
||||
ease: [0.22, 1, 0.36, 1],
|
||||
delay: i * 0.05,
|
||||
}}
|
||||
filter="url(#glow)"
|
||||
/>
|
||||
</g>
|
||||
))}
|
||||
|
||||
{/* A subtle “block” overlay on the top hub (appears/disappears) */}
|
||||
{!prefers && (
|
||||
<motion.g
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: [0, 0.7, 0] }}
|
||||
transition={{ duration: 3.2, repeat: Infinity, ease: "easeInOut", delay: 0.8 }}
|
||||
>
|
||||
<circle
|
||||
cx={nodes.top.x}
|
||||
cy={nodes.top.y}
|
||||
r={18}
|
||||
fill="none"
|
||||
stroke="#8b8b8b"
|
||||
strokeWidth={2}
|
||||
/>
|
||||
<path
|
||||
d={`M ${nodes.top.x - 10} ${nodes.top.y - 10} L ${nodes.top.x + 10} ${nodes.top.y + 10}
|
||||
M ${nodes.top.x + 10} ${nodes.top.y - 10} L ${nodes.top.x - 10} ${nodes.top.y + 10}`}
|
||||
stroke="#8b8b8b"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</motion.g>
|
||||
)}
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
236
src/pages/pods/animations/SovereignCompute.tsx
Normal file
236
src/pages/pods/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