Compare commits
23 Commits
developmen
...
d1fc11ce80
| Author | SHA1 | Date | |
|---|---|---|---|
| d1fc11ce80 | |||
| 48954151c9 | |||
| 61e368e27e | |||
| d8524ef181 | |||
| c44d9158f2 | |||
| 359afc3360 | |||
| 96a17a668a | |||
| c93974ea3e | |||
| f1f8f50871 | |||
| e4b2d66a76 | |||
| b208fe7f2a | |||
| 7b80ab84c9 | |||
| aa6f475050 | |||
| 5c2fcecbd9 | |||
| e5cf6ee362 | |||
| e9c1fd795e | |||
| be95ea97c4 | |||
| 9c47ea39fc | |||
| 7a0675a408 | |||
| 37d4371288 | |||
| dfe71dd4eb | |||
| eba1bb7047 | |||
| 0d9f357881 |
11
package-lock.json
generated
@@ -28,6 +28,7 @@
|
|||||||
"framer-motion": "^10.18.0",
|
"framer-motion": "^10.18.0",
|
||||||
"lucide-react": "^0.544.0",
|
"lucide-react": "^0.544.0",
|
||||||
"motion": "^12.23.24",
|
"motion": "^12.23.24",
|
||||||
|
"next-themes": "^0.4.6",
|
||||||
"popmotion": "^11.0.5",
|
"popmotion": "^11.0.5",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-countup": "^6.5.3",
|
"react-countup": "^6.5.3",
|
||||||
@@ -9417,6 +9418,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/next-themes": {
|
||||||
|
"version": "0.4.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
|
||||||
|
"integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-releases": {
|
"node_modules/node-releases": {
|
||||||
"version": "2.0.26",
|
"version": "2.0.26",
|
||||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz",
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz",
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
"framer-motion": "^10.18.0",
|
"framer-motion": "^10.18.0",
|
||||||
"lucide-react": "^0.544.0",
|
"lucide-react": "^0.544.0",
|
||||||
"motion": "^12.23.24",
|
"motion": "^12.23.24",
|
||||||
|
"next-themes": "^0.4.6",
|
||||||
"popmotion": "^11.0.5",
|
"popmotion": "^11.0.5",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-countup": "^6.5.3",
|
"react-countup": "^6.5.3",
|
||||||
|
|||||||
BIN
public/images/6b080b431e628e15126de0893db49e1a.webp
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
public/images/ainode.png
Normal file
|
After Width: | Height: | Size: 510 KiB |
BIN
public/images/audience/1.jpg
Normal file
|
After Width: | Height: | Size: 306 KiB |
BIN
public/images/audience/2.jpg
Normal file
|
After Width: | Height: | Size: 205 KiB |
BIN
public/images/audience/3.jpg
Normal file
|
After Width: | Height: | Size: 192 KiB |
BIN
public/images/audience/4.jpg
Normal file
|
After Width: | Height: | Size: 147 KiB |
BIN
public/images/audience/5.jpg
Normal file
|
After Width: | Height: | Size: 128 KiB |
BIN
public/images/audience/6.jpg
Normal file
|
After Width: | Height: | Size: 234 KiB |
BIN
public/images/audience/7.jpg
Normal file
|
After Width: | Height: | Size: 152 KiB |
BIN
public/images/audience/8.jpg
Normal file
|
After Width: | Height: | Size: 228 KiB |
BIN
public/images/audiences/1.jpg
Normal file
|
After Width: | Height: | Size: 229 KiB |
BIN
public/images/audiences/2.jpg
Normal file
|
After Width: | Height: | Size: 164 KiB |
BIN
public/images/audiences/3.jpg
Normal file
|
After Width: | Height: | Size: 147 KiB |
BIN
public/images/audiences/4.jpg
Normal file
|
After Width: | Height: | Size: 117 KiB |
BIN
public/images/audiences/5.jpg
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
public/images/audiences/6.jpg
Normal file
|
After Width: | Height: | Size: 184 KiB |
BIN
public/images/audiences/7.jpg
Normal file
|
After Width: | Height: | Size: 124 KiB |
BIN
public/images/audiences/8.jpg
Normal file
|
After Width: | Height: | Size: 182 KiB |
BIN
public/images/bento-03-mobile-friendly.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
public/images/bento-cloud2.png
Normal file
|
After Width: | Height: | Size: 208 KiB |
BIN
public/images/bento-network.jpg
Normal file
|
After Width: | Height: | Size: 259 KiB |
BIN
public/images/edgenode.png
Normal file
|
After Width: | Height: | Size: 796 KiB |
BIN
public/images/podsimg.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
@@ -14,6 +14,8 @@
|
|||||||
.logo:hover {
|
.logo:hover {
|
||||||
filter: drop-shadow(0 0 2em #646cffaa);
|
filter: drop-shadow(0 0 2em #646cffaa);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.logo.react:hover {
|
.logo.react:hover {
|
||||||
filter: drop-shadow(0 0 2em #61dafbaa);
|
filter: drop-shadow(0 0 2em #61dafbaa);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const ComputePage = lazy(() => import('./pages/compute/ComputePage'));
|
|||||||
const StoragePage = lazy(() => import('./pages/storage/StoragePage'));
|
const StoragePage = lazy(() => import('./pages/storage/StoragePage'));
|
||||||
const GpuPage = lazy(() => import('./pages/gpu/GpuPage'));
|
const GpuPage = lazy(() => import('./pages/gpu/GpuPage'));
|
||||||
const PodsPage = lazy(() => import('./pages/pods/PodsPage'));
|
const PodsPage = lazy(() => import('./pages/pods/PodsPage'));
|
||||||
|
const NodePage = lazy(() => import('./pages/node/NodePage'));
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
@@ -27,6 +28,7 @@ function App() {
|
|||||||
<Route path="storage" element={<StoragePage />} />
|
<Route path="storage" element={<StoragePage />} />
|
||||||
<Route path="gpu" element={<GpuPage />} />
|
<Route path="gpu" element={<GpuPage />} />
|
||||||
<Route path="pods" element={<PodsPage />} />
|
<Route path="pods" element={<PodsPage />} />
|
||||||
|
<Route path="nodes" element={<NodePage />} />
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|||||||
@@ -15,20 +15,20 @@ export function Footer() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<nav className="mt-10 flex gap-8">
|
<nav className="mt-10 flex gap-8">
|
||||||
<Link to="/" className="text-sm text-gray-700 hover:text-cyan-500 transition-colors">
|
<Link to="/network" className="text-sm text-gray-700 hover:text-cyan-500 transition-colors">
|
||||||
Home
|
Network
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="/cloud" className="text-sm text-gray-700 hover:text-cyan-500 transition-colors">
|
<Link to="/cloud" className="text-sm text-gray-700 hover:text-cyan-500 transition-colors">
|
||||||
Cloud
|
Cloud
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="/network" className="text-sm text-gray-700 hover:text-cyan-500 transition-colors">
|
<Link to="/pods" className="text-sm text-gray-700 hover:text-cyan-500 transition-colors">
|
||||||
Network
|
Pods
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="/agents" className="text-sm text-gray-700 hover:text-cyan-500 transition-colors">
|
<Link to="/agents" className="text-sm text-gray-700 hover:text-cyan-500 transition-colors">
|
||||||
Agents
|
Agents
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="/pods" className="text-sm text-gray-700 hover:text-cyan-500 transition-colors">
|
<Link to="/node" className="text-sm text-gray-700 hover:text-cyan-500 transition-colors">
|
||||||
Pods
|
Node
|
||||||
</Link>
|
</Link>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,32 +1,14 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { Link, useLocation } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { Dropdown } from './ui/Dropdown'
|
|
||||||
import { ChevronDownIcon } from '@heroicons/react/20/solid'
|
|
||||||
import { Container } from './Container'
|
import { Container } from './Container'
|
||||||
import { Button } from './Button'
|
import { Button } from './Button'
|
||||||
import pmyceliumLogo from '../images/logos/logo_1.png'
|
import pmyceliumLogo from '../images/logos/logo_1.png'
|
||||||
import { Dialog } from '@headlessui/react'
|
import { Dialog } from '@headlessui/react'
|
||||||
import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline'
|
import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline'
|
||||||
|
|
||||||
const cloudNavItems = [
|
|
||||||
{ name: 'Cloud', href: '/cloud' },
|
|
||||||
{ name: 'Compute', href: '/compute' },
|
|
||||||
{ name: 'Storage', href: '/storage' },
|
|
||||||
{ name: 'GPU', href: '/gpu' },
|
|
||||||
]
|
|
||||||
|
|
||||||
export function Header() {
|
export function Header() {
|
||||||
const location = useLocation()
|
|
||||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
||||||
|
|
||||||
const getCurrentPageName = () => {
|
|
||||||
const currentPath = location.pathname;
|
|
||||||
if (currentPath.startsWith('/compute')) return 'Compute';
|
|
||||||
if (currentPath.startsWith('/storage')) return 'Storage';
|
|
||||||
if (currentPath.startsWith('/gpu')) return 'GPU';
|
|
||||||
return 'Cloud';
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="bg-white">
|
<header className="bg-white">
|
||||||
<nav className="border-b border-gray-100">
|
<nav className="border-b border-gray-100">
|
||||||
@@ -36,28 +18,24 @@ export function Header() {
|
|||||||
<img src={pmyceliumLogo} alt="Mycelium" className="h-8 w-auto" />
|
<img src={pmyceliumLogo} alt="Mycelium" className="h-8 w-auto" />
|
||||||
</Link>
|
</Link>
|
||||||
<div className="hidden lg:flex lg:gap-10">
|
<div className="hidden lg:flex lg:gap-10">
|
||||||
<Dropdown
|
|
||||||
buttonContent={
|
|
||||||
<>
|
|
||||||
{['Compute', 'Storage', 'GPU'].includes(getCurrentPageName()) ? (
|
|
||||||
<>
|
|
||||||
<span className="text-gray-500">Cloud {' >'} </span>
|
|
||||||
<span>{getCurrentPageName()}</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
'Cloud'
|
|
||||||
)}
|
|
||||||
<ChevronDownIcon className="h-5 w-5" aria-hidden="true" />
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
items={cloudNavItems}
|
|
||||||
/>
|
|
||||||
<Link
|
<Link
|
||||||
to="/network"
|
to="/network"
|
||||||
className="text-base/7 tracking-tight text-gray-700 hover:text-cyan-500 transition-colors"
|
className="text-base/7 tracking-tight text-gray-700 hover:text-cyan-500 transition-colors"
|
||||||
>
|
>
|
||||||
Network
|
Network
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/cloud"
|
||||||
|
className="text-base/7 tracking-tight text-gray-700 hover:text-cyan-500 transition-colors"
|
||||||
|
>
|
||||||
|
Cloud
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/pods"
|
||||||
|
className="text-base/7 tracking-tight text-gray-700 hover:text-cyan-500 transition-colors"
|
||||||
|
>
|
||||||
|
Pods
|
||||||
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to="/agents"
|
to="/agents"
|
||||||
className="text-base/7 tracking-tight text-gray-700 hover:text-cyan-500 transition-colors"
|
className="text-base/7 tracking-tight text-gray-700 hover:text-cyan-500 transition-colors"
|
||||||
@@ -65,10 +43,10 @@ export function Header() {
|
|||||||
Agents
|
Agents
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to="/pods"
|
to="/node"
|
||||||
className="text-base/7 tracking-tight text-gray-700 hover:text-cyan-500 transition-colors"
|
className="text-base/7 tracking-tight text-gray-700 hover:text-cyan-500 transition-colors"
|
||||||
>
|
>
|
||||||
Pods
|
Node
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -124,16 +102,6 @@ export function Header() {
|
|||||||
<div className="mt-6 flow-root">
|
<div className="mt-6 flow-root">
|
||||||
<div className="-my-6 divide-y divide-gray-500/10">
|
<div className="-my-6 divide-y divide-gray-500/10">
|
||||||
<div className="space-y-2 py-6">
|
<div className="space-y-2 py-6">
|
||||||
{cloudNavItems.map((item) => (
|
|
||||||
<Link
|
|
||||||
key={item.name}
|
|
||||||
to={item.href}
|
|
||||||
className="-mx-3 block rounded-lg px-3 py-2 text-base font-semibold leading-7 text-gray-900 hover:bg-gray-50"
|
|
||||||
onClick={() => setMobileMenuOpen(false)}
|
|
||||||
>
|
|
||||||
{item.name}
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
<Link
|
<Link
|
||||||
to="/network"
|
to="/network"
|
||||||
className="-mx-3 block rounded-lg px-3 py-2 text-base font-semibold leading-7 text-gray-900 hover:bg-gray-50"
|
className="-mx-3 block rounded-lg px-3 py-2 text-base font-semibold leading-7 text-gray-900 hover:bg-gray-50"
|
||||||
@@ -141,6 +109,20 @@ export function Header() {
|
|||||||
>
|
>
|
||||||
Network
|
Network
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/cloud"
|
||||||
|
className="-mx-3 block rounded-lg px-3 py-2 text-base font-semibold leading-7 text-gray-900 hover:bg-gray-50"
|
||||||
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
|
>
|
||||||
|
Cloud
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/pods"
|
||||||
|
className="-mx-3 block rounded-lg px-3 py-2 text-base font-semibold leading-7 text-gray-900 hover:bg-gray-50"
|
||||||
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
|
>
|
||||||
|
Pods
|
||||||
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to="/agents"
|
to="/agents"
|
||||||
className="-mx-3 block rounded-lg px-3 py-2 text-base font-semibold leading-7 text-gray-900 hover:bg-gray-50"
|
className="-mx-3 block rounded-lg px-3 py-2 text-base font-semibold leading-7 text-gray-900 hover:bg-gray-50"
|
||||||
@@ -149,11 +131,11 @@ export function Header() {
|
|||||||
Agents
|
Agents
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to="/pods"
|
to="/node"
|
||||||
className="-mx-3 block rounded-lg px-3 py-2 text-base font-semibold leading-7 text-gray-900 hover:bg-gray-50"
|
className="-mx-3 block rounded-lg px-3 py-2 text-base font-semibold leading-7 text-gray-900 hover:bg-gray-50"
|
||||||
onClick={() => setMobileMenuOpen(false)}
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
>
|
>
|
||||||
Pods
|
Node
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="py-6">
|
<div className="py-6">
|
||||||
|
|||||||
163
src/components/HeaderDark.tsx
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { Container } from './Container'
|
||||||
|
import { Button } from './Button'
|
||||||
|
import pmyceliumLogo from '../images/logos/logo_1.png'
|
||||||
|
import { Dialog } from '@headlessui/react'
|
||||||
|
import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline'
|
||||||
|
|
||||||
|
export function HeaderDark() {
|
||||||
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header className="bg-[#111111]">
|
||||||
|
<nav className="border-b border-gray-800">
|
||||||
|
<Container className="flex bg-transparent justify-between py-4">
|
||||||
|
<div className="relative z-10 flex items-center gap-16">
|
||||||
|
<Link to="/" aria-label="Home">
|
||||||
|
<img src={pmyceliumLogo} alt="Mycelium" className="h-8 w-auto" />
|
||||||
|
</Link>
|
||||||
|
<div className="hidden lg:flex lg:gap-10">
|
||||||
|
<Link
|
||||||
|
to="/network"
|
||||||
|
className="text-base/7 tracking-tight text-gray-300 hover:text-cyan-400 transition-colors"
|
||||||
|
>
|
||||||
|
Network
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/cloud"
|
||||||
|
className="text-base/7 tracking-tight text-gray-300 hover:text-cyan-400 transition-colors"
|
||||||
|
>
|
||||||
|
Cloud
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/pods"
|
||||||
|
className="text-base/7 tracking-tight text-gray-300 hover:text-cyan-400 transition-colors"
|
||||||
|
>
|
||||||
|
Pods
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/agents"
|
||||||
|
className="text-base/7 tracking-tight text-gray-300 hover:text-cyan-400 transition-colors"
|
||||||
|
>
|
||||||
|
Agents
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/node"
|
||||||
|
className="text-base/7 tracking-tight text-gray-300 hover:text-cyan-400 transition-colors"
|
||||||
|
>
|
||||||
|
Node
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-6">
|
||||||
|
<div className="flex items-center gap-6 max-lg:hidden">
|
||||||
|
<Button
|
||||||
|
to="https://myceliumcloud.tf"
|
||||||
|
variant="outline"
|
||||||
|
as="a"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
Deploy Now
|
||||||
|
</Button>
|
||||||
|
<Button to="/download" variant="solid" color="cyan">
|
||||||
|
Get Mycelium Connector
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="lg:hidden">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="-m-2.5 inline-flex items-center justify-center rounded-md p-2.5 text-gray-300 hover:text-cyan-400 transition-colors"
|
||||||
|
onClick={() => setMobileMenuOpen(true)}
|
||||||
|
>
|
||||||
|
<span className="sr-only">Open main menu</span>
|
||||||
|
<Bars3Icon className="h-6 w-6" aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</nav>
|
||||||
|
<Dialog as="div" className="lg:hidden" open={mobileMenuOpen} onClose={setMobileMenuOpen}>
|
||||||
|
<div className="fixed inset-0 z-10" />
|
||||||
|
<Dialog.Panel className="fixed inset-y-0 right-0 z-10 w-full overflow-y-auto bg-[#111111] px-6 py-6 sm:max-w-sm sm:ring-1 sm:ring-gray-200/10">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Link to="#" className="-m-1.5 p-1.5">
|
||||||
|
<span className="sr-only">Mycelium</span>
|
||||||
|
<img
|
||||||
|
className="h-8 w-auto"
|
||||||
|
src={pmyceliumLogo}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="-m-2.5 rounded-md p-2.5 text-gray-300 hover:text-cyan-400 transition-colors"
|
||||||
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
|
>
|
||||||
|
<span className="sr-only">Close menu</span>
|
||||||
|
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="mt-6 flow-root">
|
||||||
|
<div className="-my-6 divide-y divide-gray-500/20">
|
||||||
|
<div className="space-y-2 py-6">
|
||||||
|
<Link
|
||||||
|
to="/network"
|
||||||
|
className="-mx-3 block rounded-lg px-3 py-2 text-base font-semibold leading-7 text-white hover:bg-gray-800"
|
||||||
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
|
>
|
||||||
|
Network
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/cloud"
|
||||||
|
className="-mx-3 block rounded-lg px-3 py-2 text-base font-semibold leading-7 text-white hover:bg-gray-800"
|
||||||
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
|
>
|
||||||
|
Cloud
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/pods"
|
||||||
|
className="-mx-3 block rounded-lg px-3 py-2 text-base font-semibold leading-7 text-white hover:bg-gray-800"
|
||||||
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
|
>
|
||||||
|
Pods
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/agents"
|
||||||
|
className="-mx-3 block rounded-lg px-3 py-2 text-base font-semibold leading-7 text-white hover:bg-gray-800"
|
||||||
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
|
>
|
||||||
|
Agents
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/node"
|
||||||
|
className="-mx-3 block rounded-lg px-3 py-2 text-base font-semibold leading-7 text-white hover:bg-gray-800"
|
||||||
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
|
>
|
||||||
|
Node
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="py-6">
|
||||||
|
<Button
|
||||||
|
to="https://myceliumcloud.tf"
|
||||||
|
variant="outline"
|
||||||
|
as="a"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="w-full"
|
||||||
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
|
>
|
||||||
|
Start Deployment
|
||||||
|
</Button>
|
||||||
|
<Button to="/download" variant="solid" color="cyan" className="mt-4 w-full" onClick={() => setMobileMenuOpen(false)}>
|
||||||
|
Get Mycelium Connector
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog.Panel>
|
||||||
|
</Dialog>
|
||||||
|
</header>
|
||||||
|
)
|
||||||
|
}
|
||||||
9
src/components/HomeHeadline.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { TextHoverEffect } from "@/components/ui/text-hover-effect";
|
||||||
|
|
||||||
|
export function HomeHeadline() {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center h-auto max-h-[200px]">
|
||||||
|
<TextHoverEffect text="MYCELIUM" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
64
src/components/ui/GridBlink.tsx
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
export function GridBlink() {
|
||||||
|
const svgRef = useRef<SVGSVGElement>(null);
|
||||||
|
const maxActive = 3; // ✅ limit active squares
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const svg = svgRef.current;
|
||||||
|
if (!svg) return;
|
||||||
|
|
||||||
|
const squares = Array.from(svg.querySelectorAll<SVGRectElement>(".blink-square"));
|
||||||
|
|
||||||
|
function scheduleBlink() {
|
||||||
|
// ✅ only blink if we have too few active
|
||||||
|
const currentlyActive = squares.filter(sq => sq.classList.contains("active"));
|
||||||
|
if (currentlyActive.length < maxActive) {
|
||||||
|
const sq = squares[Math.floor(Math.random() * squares.length)];
|
||||||
|
sq.classList.add("active");
|
||||||
|
|
||||||
|
const duration = 800 + Math.random() * 1000; // ✅ slower fade-out
|
||||||
|
setTimeout(() => {
|
||||||
|
sq.classList.remove("active");
|
||||||
|
}, duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ slower scheduling
|
||||||
|
setTimeout(scheduleBlink, 300 + Math.random() * 600);
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduleBlink();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const rows = 20;
|
||||||
|
const cols = 32;
|
||||||
|
const size = 40;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
ref={svgRef}
|
||||||
|
className="pointer-events-none absolute inset-0 z-0"
|
||||||
|
style={{ width: "100%", height: "100%" }}
|
||||||
|
>
|
||||||
|
{Array.from({ length: rows * cols }).map((_, i) => {
|
||||||
|
const x = (i % cols) * size;
|
||||||
|
const y = Math.floor(i / cols) * size;
|
||||||
|
return (
|
||||||
|
<rect
|
||||||
|
key={i}
|
||||||
|
className="blink-square"
|
||||||
|
x={x}
|
||||||
|
y={y}
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
fill="transparent"
|
||||||
|
stroke="#efefef"
|
||||||
|
strokeWidth="1"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
63
src/components/ui/spotlight.tsx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
type SpotlightProps = {
|
||||||
|
className?: string;
|
||||||
|
fill?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Spotlight = ({ className, fill }: SpotlightProps) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className={cn(
|
||||||
|
"animate-spotlight pointer-events-none absolute z-[1] h-[169%] w-[138%] lg:w-[84%] opacity-100 mix-blend-screen",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 3787 2842"
|
||||||
|
fill="none"
|
||||||
|
>
|
||||||
|
<g filter="url(#filter)">
|
||||||
|
<ellipse
|
||||||
|
cx="1924.71"
|
||||||
|
cy="273.501"
|
||||||
|
rx="1924.71"
|
||||||
|
ry="273.501"
|
||||||
|
transform="matrix(-0.822377 -0.568943 -0.568943 0.822377 3631.88 2291.09)"
|
||||||
|
fill={fill || "url(#spotlightGradient)"}
|
||||||
|
fillOpacity="1"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<defs>
|
||||||
|
{/* ✅ Cyan radial gradient */}
|
||||||
|
<radialGradient
|
||||||
|
id="spotlightGradient"
|
||||||
|
cx="0"
|
||||||
|
cy="0"
|
||||||
|
r="1"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="translate(2200 1600) rotate(90) scale(1800 2800)"
|
||||||
|
>
|
||||||
|
<stop offset="0%" stopColor="#00eaff" stopOpacity="0.95" />
|
||||||
|
<stop offset="40%" stopColor="#00eaff" stopOpacity="0.35" />
|
||||||
|
<stop offset="100%" stopColor="#00eaff" stopOpacity="0" />
|
||||||
|
</radialGradient>
|
||||||
|
|
||||||
|
<filter
|
||||||
|
id="filter"
|
||||||
|
x="0.860352"
|
||||||
|
y="0.838989"
|
||||||
|
width="3785.16"
|
||||||
|
height="2840.26"
|
||||||
|
filterUnits="userSpaceOnUse"
|
||||||
|
colorInterpolationFilters="sRGB"
|
||||||
|
>
|
||||||
|
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
||||||
|
<feGaussianBlur stdDeviation="151" result="effect1_foregroundBlur" />
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
139
src/components/ui/text-hover-effect.tsx
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
"use client";
|
||||||
|
import { useRef, useEffect, useState } from "react";
|
||||||
|
import { motion, useAnimation } from "motion/react";
|
||||||
|
|
||||||
|
export const TextHoverEffect = ({
|
||||||
|
text,
|
||||||
|
duration = 6, // loop duration
|
||||||
|
}: {
|
||||||
|
text: string;
|
||||||
|
duration?: number;
|
||||||
|
}) => {
|
||||||
|
const svgRef = useRef<SVGSVGElement>(null);
|
||||||
|
const controls = useAnimation();
|
||||||
|
const [hovered, setHovered] = useState(false);
|
||||||
|
|
||||||
|
// ✅ Animate mask looping automatically
|
||||||
|
useEffect(() => {
|
||||||
|
const loop = async () => {
|
||||||
|
while (true) {
|
||||||
|
await controls.start({
|
||||||
|
cx: ["20%", "80%", "50%"],
|
||||||
|
cy: ["20%", "80%", "50%"],
|
||||||
|
transition: {
|
||||||
|
duration,
|
||||||
|
ease: "easeInOut",
|
||||||
|
repeat: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loop();
|
||||||
|
}, [controls, duration]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
ref={svgRef}
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
viewBox="0 0 300 100"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
onMouseEnter={() => setHovered(true)}
|
||||||
|
onMouseLeave={() => setHovered(false)}
|
||||||
|
className="select-none"
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
{/* ✅ Softer cyan gradient */}
|
||||||
|
<linearGradient id="textGradient" gradientUnits="userSpaceOnUse">
|
||||||
|
{hovered ? (
|
||||||
|
<>
|
||||||
|
<stop offset="0%" stopColor="#7df3ff" />
|
||||||
|
<stop offset="40%" stopColor="#4adffa" />
|
||||||
|
<stop offset="70%" stopColor="#18c5e8" />
|
||||||
|
<stop offset="100%" stopColor="#0aaecb" />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<stop offset="0%" stopColor="#7df3ff33" />
|
||||||
|
<stop offset="100%" stopColor="#0aaecb33" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</linearGradient>
|
||||||
|
|
||||||
|
{/* ✅ Mask with autoplay motion */}
|
||||||
|
<motion.radialGradient
|
||||||
|
id="revealMask"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
r="45%"
|
||||||
|
animate={controls}
|
||||||
|
initial={{ cx: "50%", cy: "50%" }}
|
||||||
|
>
|
||||||
|
<stop offset="0%" stopColor="white" />
|
||||||
|
<stop offset="100%" stopColor="black" />
|
||||||
|
</motion.radialGradient>
|
||||||
|
|
||||||
|
{/* ✅ Glow */}
|
||||||
|
<filter id="glow">
|
||||||
|
<feGaussianBlur stdDeviation="3.2" result="blur" />
|
||||||
|
<feMerge>
|
||||||
|
<feMergeNode in="blur" />
|
||||||
|
<feMergeNode in="SourceGraphic" />
|
||||||
|
</feMerge>
|
||||||
|
</filter>
|
||||||
|
|
||||||
|
<mask id="textMask">
|
||||||
|
<rect x="0" y="0" width="100%" height="100%" fill="url(#revealMask)" />
|
||||||
|
</mask>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
{/* ✅ Background faint stroke */}
|
||||||
|
<text
|
||||||
|
x="50%"
|
||||||
|
y="50%"
|
||||||
|
textAnchor="middle"
|
||||||
|
dominantBaseline="middle"
|
||||||
|
strokeWidth="0.15"
|
||||||
|
className="fill-transparent stroke-neutral-300 dark:stroke-neutral-800 font-[helvetica] text-7xl font-bold"
|
||||||
|
style={{ opacity: hovered ? 0.25 : 0.1 }}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</text>
|
||||||
|
|
||||||
|
{/* ✅ Line drawing animation always plays too */}
|
||||||
|
<motion.text
|
||||||
|
x="50%"
|
||||||
|
y="50%"
|
||||||
|
textAnchor="middle"
|
||||||
|
dominantBaseline="middle"
|
||||||
|
strokeWidth="0.25"
|
||||||
|
className="fill-transparent stroke-cyan-300 font-[helvetica] text-7xl font-bold"
|
||||||
|
initial={{ strokeDashoffset: 600, strokeDasharray: 600 }}
|
||||||
|
animate={{
|
||||||
|
strokeDashoffset: 0,
|
||||||
|
strokeDasharray: 600,
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 2.2,
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</motion.text>
|
||||||
|
|
||||||
|
{/* ✅ Final filled glowing cyan text (mask reveals it) */}
|
||||||
|
<text
|
||||||
|
x="50%"
|
||||||
|
y="50%"
|
||||||
|
textAnchor="middle"
|
||||||
|
dominantBaseline="middle"
|
||||||
|
stroke="url(#textGradient)"
|
||||||
|
strokeWidth="1.1"
|
||||||
|
mask="url(#textMask)"
|
||||||
|
filter="url(#glow)"
|
||||||
|
className="font-[helvetica] text-7xl font-bold fill-[url(#textGradient)]"
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</text>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -14,152 +14,111 @@ interface MapProps {
|
|||||||
|
|
||||||
export default function WorldMap({
|
export default function WorldMap({
|
||||||
dots = [],
|
dots = [],
|
||||||
lineColor = "#06b6d4",
|
lineColor = "#06b6d4", // cyan-500
|
||||||
}: MapProps) {
|
}: MapProps) {
|
||||||
const svgRef = useRef<SVGSVGElement>(null);
|
const svgRef = useRef<SVGSVGElement>(null);
|
||||||
const map = new DottedMap({ height: 100, grid: "diagonal" });
|
|
||||||
|
|
||||||
|
// ✅ Force dark-dotted map theme
|
||||||
|
const map = new DottedMap({ height: 100, grid: "diagonal" });
|
||||||
const svgMap = map.getSVG({
|
const svgMap = map.getSVG({
|
||||||
radius: 0.22,
|
radius: 0.22,
|
||||||
color: "#FFFFFF40", // Hardcoded for dark theme
|
color: "#06b6d480", // cyan-500 at 50% opacity
|
||||||
shape: "circle",
|
shape: "circle",
|
||||||
backgroundColor: "black", // Hardcoded for dark theme
|
backgroundColor: "#111111",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ✅ Point projection stays the same
|
||||||
const projectPoint = (lat: number, lng: number) => {
|
const projectPoint = (lat: number, lng: number) => {
|
||||||
const x = (lng + 180) * (800 / 360);
|
const x = (lng + 180) * (800 / 360);
|
||||||
const y = (90 - lat) * (400 / 180);
|
const y = (90 - lat) * (400 / 180);
|
||||||
return { x, y };
|
return { x, y };
|
||||||
};
|
};
|
||||||
|
|
||||||
const createCurvedPath = (
|
const createCurvedPath = (start: any, end: any) => {
|
||||||
start: { x: number; y: number },
|
|
||||||
end: { x: number; y: number }
|
|
||||||
) => {
|
|
||||||
const midX = (start.x + end.x) / 2;
|
const midX = (start.x + end.x) / 2;
|
||||||
const midY = Math.min(start.y, end.y) - 50;
|
const midY = Math.min(start.y, end.y) - 50;
|
||||||
return `M ${start.x} ${start.y} Q ${midX} ${midY} ${end.x} ${end.y}`;
|
return `M ${start.x} ${start.y} Q ${midX} ${midY} ${end.x} ${end.y}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full aspect-[2/1] dark:bg-black bg-white rounded-lg relative font-sans">
|
<div className="w-full aspect-[2/1] bg-[#111111] rounded-lg relative font-sans">
|
||||||
<img
|
<img
|
||||||
src={`data:image/svg+xml;utf8,${encodeURIComponent(svgMap)}`}
|
src={`data:image/svg+xml;utf8,${encodeURIComponent(svgMap)}`}
|
||||||
className="h-full w-full [mask-image:linear-gradient(to_bottom,transparent,white_10%,white_90%,transparent)] pointer-events-none select-none"
|
className="h-full w-full pointer-events-none select-none opacity-[0.6]"
|
||||||
alt="world map"
|
alt="world map"
|
||||||
height="495"
|
|
||||||
width="1056"
|
|
||||||
draggable={false}
|
draggable={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* ✅ Lines + points */}
|
||||||
<svg
|
<svg
|
||||||
ref={svgRef}
|
ref={svgRef}
|
||||||
viewBox="0 0 800 400"
|
viewBox="0 0 800 400"
|
||||||
className="w-full h-full absolute inset-0 pointer-events-none select-none"
|
className="w-full h-full absolute inset-0 pointer-events-none select-none"
|
||||||
>
|
>
|
||||||
|
{/* ✅ animated curved travel lines */}
|
||||||
{dots.map((dot, i) => {
|
{dots.map((dot, i) => {
|
||||||
const startPoint = projectPoint(dot.start.lat, dot.start.lng);
|
const startPoint = projectPoint(dot.start.lat, dot.start.lng);
|
||||||
const endPoint = projectPoint(dot.end.lat, dot.end.lng);
|
const endPoint = projectPoint(dot.end.lat, dot.end.lng);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<g key={`path-group-${i}`}>
|
|
||||||
<motion.path
|
<motion.path
|
||||||
|
key={`path-${i}`}
|
||||||
d={createCurvedPath(startPoint, endPoint)}
|
d={createCurvedPath(startPoint, endPoint)}
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="url(#path-gradient)"
|
stroke="url(#path-gradient)"
|
||||||
strokeWidth="1"
|
strokeWidth="1"
|
||||||
initial={{
|
initial={{ pathLength: 0 }}
|
||||||
pathLength: 0,
|
animate={{ pathLength: 1 }}
|
||||||
}}
|
|
||||||
animate={{
|
|
||||||
pathLength: 1,
|
|
||||||
}}
|
|
||||||
transition={{
|
transition={{
|
||||||
duration: 1,
|
duration: 1,
|
||||||
delay: 0.5 * i,
|
delay: 0.5 * i,
|
||||||
ease: "easeOut",
|
ease: "easeOut",
|
||||||
}}
|
}}
|
||||||
key={`start-upper-${i}`}
|
/>
|
||||||
></motion.path>
|
|
||||||
</g>
|
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
{/* ✅ glowing path gradient */}
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="path-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
<linearGradient id="path-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
<stop offset="0%" stopColor="white" stopOpacity="0" />
|
<stop offset="0%" stopColor="black" stopOpacity="0" />
|
||||||
<stop offset="5%" stopColor={lineColor} stopOpacity="1" />
|
<stop offset="5%" stopColor={lineColor} stopOpacity="1" />
|
||||||
<stop offset="95%" stopColor={lineColor} stopOpacity="1" />
|
<stop offset="95%" stopColor={lineColor} stopOpacity="1" />
|
||||||
<stop offset="100%" stopColor="white" stopOpacity="0" />
|
<stop offset="100%" stopColor="black" stopOpacity="0" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
|
|
||||||
{dots.map((dot, i) => (
|
{/* ✅ start & end points with pulsing cyan glow */}
|
||||||
<g key={`points-group-${i}`}>
|
{dots.map((dot, i) => {
|
||||||
<g key={`start-${i}`}>
|
const s = projectPoint(dot.start.lat, dot.start.lng);
|
||||||
<circle
|
const e = projectPoint(dot.end.lat, dot.end.lng);
|
||||||
cx={projectPoint(dot.start.lat, dot.start.lng).x}
|
|
||||||
cy={projectPoint(dot.start.lat, dot.start.lng).y}
|
return (
|
||||||
r="2"
|
<g key={`points-${i}`}>
|
||||||
fill={lineColor}
|
{[s, e].map((p, idx) => (
|
||||||
/>
|
<g key={idx}>
|
||||||
<circle
|
<circle cx={p.x} cy={p.y} r="2" fill={lineColor} />
|
||||||
cx={projectPoint(dot.start.lat, dot.start.lng).x}
|
<circle cx={p.x} cy={p.y} r="2" fill={lineColor} opacity="0.5">
|
||||||
cy={projectPoint(dot.start.lat, dot.start.lng).y}
|
|
||||||
r="2"
|
|
||||||
fill={lineColor}
|
|
||||||
opacity="0.5"
|
|
||||||
>
|
|
||||||
<animate
|
<animate
|
||||||
attributeName="r"
|
attributeName="r"
|
||||||
from="2"
|
from="2"
|
||||||
to="8"
|
to="7"
|
||||||
dur="1.5s"
|
dur="1.4s"
|
||||||
begin="0s"
|
|
||||||
repeatCount="indefinite"
|
repeatCount="indefinite"
|
||||||
/>
|
/>
|
||||||
<animate
|
<animate
|
||||||
attributeName="opacity"
|
attributeName="opacity"
|
||||||
from="0.5"
|
from="0.6"
|
||||||
to="0"
|
to="0"
|
||||||
dur="1.5s"
|
dur="1.4s"
|
||||||
begin="0s"
|
|
||||||
repeatCount="indefinite"
|
repeatCount="indefinite"
|
||||||
/>
|
/>
|
||||||
</circle>
|
</circle>
|
||||||
</g>
|
</g>
|
||||||
<g key={`end-${i}`}>
|
|
||||||
<circle
|
|
||||||
cx={projectPoint(dot.end.lat, dot.end.lng).x}
|
|
||||||
cy={projectPoint(dot.end.lat, dot.end.lng).y}
|
|
||||||
r="2"
|
|
||||||
fill={lineColor}
|
|
||||||
/>
|
|
||||||
<circle
|
|
||||||
cx={projectPoint(dot.end.lat, dot.end.lng).x}
|
|
||||||
cy={projectPoint(dot.end.lat, dot.end.lng).y}
|
|
||||||
r="2"
|
|
||||||
fill={lineColor}
|
|
||||||
opacity="0.5"
|
|
||||||
>
|
|
||||||
<animate
|
|
||||||
attributeName="r"
|
|
||||||
from="2"
|
|
||||||
to="8"
|
|
||||||
dur="1.5s"
|
|
||||||
begin="0s"
|
|
||||||
repeatCount="indefinite"
|
|
||||||
/>
|
|
||||||
<animate
|
|
||||||
attributeName="opacity"
|
|
||||||
from="0.5"
|
|
||||||
to="0"
|
|
||||||
dur="1.5s"
|
|
||||||
begin="0s"
|
|
||||||
repeatCount="indefinite"
|
|
||||||
/>
|
|
||||||
</circle>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
))}
|
))}
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import { StrictMode } from 'react'
|
import { StrictMode } from 'react'
|
||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client'
|
||||||
|
import { ThemeProvider } from 'next-themes'
|
||||||
import './styles/tailwind.css'
|
import './styles/tailwind.css'
|
||||||
import App from './App'
|
import App from './App'
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
|
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem>
|
||||||
<App />
|
<App />
|
||||||
|
</ThemeProvider>
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,15 +1,32 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Eyebrow, H3, P } from "@/components/Texts";
|
import { Eyebrow, H3, P } from "@/components/Texts";
|
||||||
|
import AgentCoordination from "./animations/AgentCoordination";
|
||||||
|
import DeterministicExecution from "./animations/DeterministicExecution";
|
||||||
|
import Fungistor from "./animations/Fungistor";
|
||||||
|
import Herodb from "./animations/Herodb";
|
||||||
|
import MOSSandboxes from "./animations/MOSSandboxes";
|
||||||
|
import MyceliumMesh from "./animations/MyceliumMesh";
|
||||||
|
|
||||||
const bentos = [
|
const bentos: {
|
||||||
|
id: string;
|
||||||
|
eyebrow?: string;
|
||||||
|
title: string;
|
||||||
|
subtitle?: string;
|
||||||
|
description: string;
|
||||||
|
animation: React.ComponentType | null;
|
||||||
|
colSpan: string;
|
||||||
|
rowSpan: string;
|
||||||
|
custom?: boolean;
|
||||||
|
noBorder?: boolean;
|
||||||
|
}[] = [
|
||||||
{
|
{
|
||||||
id: "core",
|
id: "core",
|
||||||
eyebrow: "ARCHITECTURE",
|
eyebrow: "ARCHITECTURE",
|
||||||
title: "Deterministic by Design",
|
title: "Augmented Intelligence Fabric",
|
||||||
description:
|
description:
|
||||||
"Every workload runs exactly as declared: no drift, no hidden state, no surprises.",
|
"The sovereign substrate for autonomous AI. Stateless, geo-aware, end-to-end encrypted—and verifiable from intent to execution.",
|
||||||
video: null,
|
animation: null,
|
||||||
colSpan: "lg:col-span-3",
|
colSpan: "lg:col-span-3",
|
||||||
rowSpan: "lg:row-span-1",
|
rowSpan: "lg:row-span-1",
|
||||||
custom: true,
|
custom: true,
|
||||||
@@ -23,7 +40,7 @@ const bentos = [
|
|||||||
subtitle: "Long-Term AI Memory",
|
subtitle: "Long-Term AI Memory",
|
||||||
description:
|
description:
|
||||||
"Erasure coding + compression slash storage bloat by up to 10× vs basic replication. Source-encrypted shards are geo-dispersed—lose pieces, rebuild perfectly from a quorum.",
|
"Erasure coding + compression slash storage bloat by up to 10× vs basic replication. Source-encrypted shards are geo-dispersed—lose pieces, rebuild perfectly from a quorum.",
|
||||||
video: "/videos/fungistor.mp4",
|
animation: Fungistor,
|
||||||
colSpan: "lg:col-span-3",
|
colSpan: "lg:col-span-3",
|
||||||
rowSpan: "lg:row-span-1",
|
rowSpan: "lg:row-span-1",
|
||||||
},
|
},
|
||||||
@@ -33,7 +50,7 @@ const bentos = [
|
|||||||
subtitle: "Active AI Memory",
|
subtitle: "Active AI Memory",
|
||||||
description:
|
description:
|
||||||
"Multimodal vector+keyword retrieval makes RAG feel instant across text, image, audio. Time-aware, policy-guarded context keeps results fresh while access stays governed.",
|
"Multimodal vector+keyword retrieval makes RAG feel instant across text, image, audio. Time-aware, policy-guarded context keeps results fresh while access stays governed.",
|
||||||
video: "/videos/herodb.mp4",
|
animation: Herodb,
|
||||||
colSpan: "lg:col-span-3",
|
colSpan: "lg:col-span-3",
|
||||||
rowSpan: "lg:row-span-1",
|
rowSpan: "lg:row-span-1",
|
||||||
},
|
},
|
||||||
@@ -43,7 +60,7 @@ const bentos = [
|
|||||||
subtitle: "Secure Agent Workspaces",
|
subtitle: "Secure Agent Workspaces",
|
||||||
description:
|
description:
|
||||||
"Attested, signed workspaces spin up ≈5s worldwide—ready to execute. Hardware isolation and scoped egress: run hard, tear down clean, zero residue.",
|
"Attested, signed workspaces spin up ≈5s worldwide—ready to execute. Hardware isolation and scoped egress: run hard, tear down clean, zero residue.",
|
||||||
video: "/videos/herodb.mp4",
|
animation: MOSSandboxes,
|
||||||
colSpan: "lg:col-span-3",
|
colSpan: "lg:col-span-3",
|
||||||
rowSpan: "lg:row-span-1",
|
rowSpan: "lg:row-span-1",
|
||||||
},
|
},
|
||||||
@@ -53,7 +70,7 @@ const bentos = [
|
|||||||
subtitle: "Secure Communication Network",
|
subtitle: "Secure Communication Network",
|
||||||
description:
|
description:
|
||||||
"A private, public-key fabric with self-healing multi-path routing. Glides through NATs and firewalls—direct, low-latency, no middlemen.",
|
"A private, public-key fabric with self-healing multi-path routing. Glides through NATs and firewalls—direct, low-latency, no middlemen.",
|
||||||
video: "/videos/mesh.mp4",
|
animation: MyceliumMesh,
|
||||||
colSpan: "lg:col-span-2",
|
colSpan: "lg:col-span-2",
|
||||||
rowSpan: "lg:row-span-1",
|
rowSpan: "lg:row-span-1",
|
||||||
},
|
},
|
||||||
@@ -63,7 +80,7 @@ const bentos = [
|
|||||||
subtitle: "Verifiable Code Execution",
|
subtitle: "Verifiable Code Execution",
|
||||||
description:
|
description:
|
||||||
"Declare intent, get a hash; remote attestation proves that is what runs. Reproducible builds, signed artifacts, immutable logs—supply chain, sealed.",
|
"Declare intent, get a hash; remote attestation proves that is what runs. Reproducible builds, signed artifacts, immutable logs—supply chain, sealed.",
|
||||||
video: "/videos/deterministic.mp4",
|
animation: DeterministicExecution,
|
||||||
colSpan: "lg:col-span-2",
|
colSpan: "lg:col-span-2",
|
||||||
rowSpan: "lg:row-span-1",
|
rowSpan: "lg:row-span-1",
|
||||||
},
|
},
|
||||||
@@ -73,7 +90,7 @@ const bentos = [
|
|||||||
subtitle: "Sovereign Workflow Management",
|
subtitle: "Sovereign Workflow Management",
|
||||||
description:
|
description:
|
||||||
"Your private agent conducts swarms of specialists in parallel. Policies fan out work; human checkpoints keep you in command.",
|
"Your private agent conducts swarms of specialists in parallel. Policies fan out work; human checkpoints keep you in command.",
|
||||||
video: "/videos/agent.mp4",
|
animation: AgentCoordination,
|
||||||
colSpan: "lg:col-span-2",
|
colSpan: "lg:col-span-2",
|
||||||
rowSpan: "lg:row-span-1",
|
rowSpan: "lg:row-span-1",
|
||||||
},
|
},
|
||||||
@@ -101,17 +118,9 @@ export function AgentBento() {
|
|||||||
<div
|
<div
|
||||||
className={`relative flex lg:h-90 flex-col overflow-hidden rounded-[calc(var(--radius-lg)+1px)] `}
|
className={`relative flex lg:h-90 flex-col overflow-hidden rounded-[calc(var(--radius-lg)+1px)] `}
|
||||||
>
|
>
|
||||||
{/* ✅ VIDEO instead of animation */}
|
{card.animation ? (
|
||||||
{card.video ? (
|
|
||||||
<div className="lg:h-64 h-48 w-full overflow-hidden bg-transparent flex items-center">
|
<div className="lg:h-64 h-48 w-full overflow-hidden bg-transparent flex items-center">
|
||||||
<video
|
<card.animation />
|
||||||
src={card.video}
|
|
||||||
autoPlay
|
|
||||||
loop
|
|
||||||
muted
|
|
||||||
playsInline
|
|
||||||
className="w-full h-full object-cover"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="h-48 w-full flex items-center justify-center bg-transparent" />
|
<div className="h-48 w-full flex items-center justify-center bg-transparent" />
|
||||||
|
|||||||
56
src/pages/agents/AgentDesign.tsx
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import {
|
||||||
|
ComputerDesktopIcon,
|
||||||
|
ArrowsRightLeftIcon,
|
||||||
|
CircleStackIcon,
|
||||||
|
} from '@heroicons/react/24/solid'
|
||||||
|
|
||||||
|
const benefits = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: 'Each agent operates entirely inside your environment',
|
||||||
|
icon: ComputerDesktopIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: 'They communicate peer-to-peer across trusted nodes',
|
||||||
|
icon: ArrowsRightLeftIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
title: 'They access data locally without exposing it to external providers',
|
||||||
|
icon: CircleStackIcon,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export function AgentDesign() {
|
||||||
|
return (
|
||||||
|
<section className="w-full max-w-8xl mx-auto bg-transparent">
|
||||||
|
|
||||||
|
{/* ✅ Top horizontal line with spacing */}
|
||||||
|
<div className="max-w-7xl bg-transparent mx-auto py-6 border border-t-0 border-b-0 border-gray-100"></div>
|
||||||
|
<div className="w-full border border-l border-r border-gray-100" />
|
||||||
|
|
||||||
|
{/* ✅ Main content */}
|
||||||
|
<div className="mx-auto max-w-7xl border-gray-100">
|
||||||
|
<dl className="grid grid-cols-1 gap-4 lg:gap-6 lg:grid-cols-3 text-center ">
|
||||||
|
{benefits.map((item) => (
|
||||||
|
<div
|
||||||
|
key={item.id}
|
||||||
|
className="flex flex-col items-center bg-white dark:bg-black/40 py-10 px-4 border border-gray-100 dark:border-gray-800 lg:border-t-0 lg:border-b-0"
|
||||||
|
>
|
||||||
|
<item.icon className="h-10 w-10 text-cyan-500 mb-4" />
|
||||||
|
<h3 className="text-base font-medium text-black dark:text-white max-w-xs">
|
||||||
|
{item.title}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ✅ Bottom horizontal line with spacing */}
|
||||||
|
<div className="w-full border border-gray-100" />
|
||||||
|
<div className="max-w-7xl bg-transparent mx-auto py-6 border border-t-0 border-b-0 border-gray-100"></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { Button } from '@/components/Button'
|
import { Button } from '@/components/Button'
|
||||||
import { Eyebrow, H3 } from '@/components/Texts'
|
import { Eyebrow, H3, P } from '@/components/Texts'
|
||||||
|
|
||||||
export function AgentHeroAlt() {
|
export function AgentHeroAlt() {
|
||||||
return (
|
return (
|
||||||
@@ -14,19 +14,18 @@ export function AgentHeroAlt() {
|
|||||||
{/* Inner padding */}
|
{/* Inner padding */}
|
||||||
<div className="px-6 py-16 lg:py-16">
|
<div className="px-6 py-16 lg:py-16">
|
||||||
<div className="max-w-2xl lg:pl-6">
|
<div className="max-w-2xl lg:pl-6">
|
||||||
<Eyebrow>MYCELIUM AGENTS</Eyebrow>
|
<Eyebrow>MYCELIUM AGENTS - COMING IN 2026</Eyebrow>
|
||||||
<H3 as="h1" className="mt-4">
|
<H3 as="h1" className="mt-4">
|
||||||
Sovereign AI Agents, Coming Soon.
|
Private, Sovereign and Distributed AI You Control
|
||||||
</H3>
|
</H3>
|
||||||
<p className="mt-6 text-lg">
|
<P className="mt-6 text-gray-800">
|
||||||
The Agent layer will allow you to run autonomous, policy-governed AI that operates on infrastructure you control, with private memory, verifiable execution, and coordination across your personal or organizational environment.
|
Mycelium Agents let you deploy and run intelligent systems on your own infrastructure.
|
||||||
</p>
|
Private, local, and autonomous by design, they give you everything you need to build, host, and connect AI agents without relying on centralized clouds.
|
||||||
<p className="mt-4 lg:text-base italic text-gray-600 text-sm">
|
</P>
|
||||||
Works Alone. Works Together. Use Agents on top of any Mycelium Cloud deployment or pair them with the Mycelium Network for private, encrypted collaboration across users and systems.
|
|
||||||
</p>
|
|
||||||
<div className="mt-10 flex items-center gap-x-6">
|
<div className="mt-10 flex items-center gap-x-6">
|
||||||
<Button href="#" variant="solid" color="cyan">
|
<Button href="#" variant="solid" color="cyan">
|
||||||
Follow Deployment
|
Follow Development
|
||||||
</Button>
|
</Button>
|
||||||
<Button href="#" variant="outline">
|
<Button href="#" variant="outline">
|
||||||
Explore Docs <span aria-hidden="true">→</span>
|
Explore Docs <span aria-hidden="true">→</span>
|
||||||
|
|||||||
147
src/pages/agents/AgentUseCase.tsx
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useRef } from "react";
|
||||||
|
import { Eyebrow, SectionHeader, P } from "@/components/Texts";
|
||||||
|
import { IoArrowBackOutline, IoArrowForwardOutline } from "react-icons/io5";
|
||||||
|
|
||||||
|
import {
|
||||||
|
CpuChipIcon,
|
||||||
|
GlobeAltIcon,
|
||||||
|
LockClosedIcon,
|
||||||
|
ArrowPathIcon,
|
||||||
|
ShieldCheckIcon,
|
||||||
|
} from "@heroicons/react/24/solid";
|
||||||
|
|
||||||
|
const networkUseCases = [
|
||||||
|
{
|
||||||
|
isIntro: true,
|
||||||
|
eyebrow: "WHAT IT ENABLES",
|
||||||
|
title: "What It Enables",
|
||||||
|
description:
|
||||||
|
"The framework gives you full control over where agents live, how they connect, and what data they use.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Run agents on your own hardware",
|
||||||
|
description:
|
||||||
|
"Deploy AI processes on laptops, homelabs, edge nodes, or full clusters — no cloud dependency.",
|
||||||
|
icon: CpuChipIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Connect them over the secure Mycelium network",
|
||||||
|
description:
|
||||||
|
"Agents communicate privately across homes, clouds, countries, and environments in one address space.",
|
||||||
|
icon: GlobeAltIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Keep data and memory private by default",
|
||||||
|
description:
|
||||||
|
"Your datasets, tools, prompts, embeddings, and memory stay local unless you choose otherwise.",
|
||||||
|
icon: LockClosedIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Build workflows across cloud + edge",
|
||||||
|
description:
|
||||||
|
"Orchestrate multi-node jobs, pipelines, and real-time systems that live anywhere in your infrastructure.",
|
||||||
|
icon: ArrowPathIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Operate securely in regulated contexts",
|
||||||
|
description:
|
||||||
|
"Run agents in sectors requiring strict data residency, verified identity, and controlled connectivity.",
|
||||||
|
icon: ShieldCheckIcon,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function AgentUsecase() {
|
||||||
|
const sliderRef = useRef<HTMLUListElement>(null);
|
||||||
|
|
||||||
|
const scrollLeft = () =>
|
||||||
|
sliderRef.current?.scrollBy({ left: -400, behavior: "smooth" });
|
||||||
|
|
||||||
|
const scrollRight = () =>
|
||||||
|
sliderRef.current?.scrollBy({ left: 400, behavior: "smooth" });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="w-full max-w-8xl mx-auto bg-transparent">
|
||||||
|
|
||||||
|
{/* ✅ Top horizontal line with spacing */}
|
||||||
|
<div className="max-w-7xl bg-transparent 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" />
|
||||||
|
|
||||||
|
<div className="relative mx-auto max-w-7xl border border-t-0 border-b-0 border-slate-200 bg-white overflow-hidden">
|
||||||
|
<ul
|
||||||
|
ref={sliderRef}
|
||||||
|
className="flex overflow-x-auto snap-x snap-mandatory scroll-smooth no-scrollbar"
|
||||||
|
>
|
||||||
|
{networkUseCases.map((item, idx) => (
|
||||||
|
<li
|
||||||
|
key={idx}
|
||||||
|
className={`snap-start shrink-0 w-[85%] sm:w-[50%] lg:w-[33%]
|
||||||
|
border border-slate-200 px-10 py-12 relative
|
||||||
|
${item.isIntro ? "bg-gray-50/80" : "bg-white"}`}
|
||||||
|
>
|
||||||
|
{/* INTRO CARD */}
|
||||||
|
{item.isIntro ? (
|
||||||
|
<div className="flex flex-col justify-between h-full">
|
||||||
|
<div>
|
||||||
|
<Eyebrow>{item.eyebrow}</Eyebrow>
|
||||||
|
<SectionHeader
|
||||||
|
as="h3"
|
||||||
|
className="mt-4 text-gray-900 text-xl lg:text-2xl"
|
||||||
|
>
|
||||||
|
{item.title}
|
||||||
|
</SectionHeader>
|
||||||
|
<P className="mt-4 text-gray-600 text-sm lg:text-base">
|
||||||
|
{item.description}
|
||||||
|
</P>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* slider buttons */}
|
||||||
|
<div className="flex items-center gap-x-4 mt-6">
|
||||||
|
<button
|
||||||
|
onClick={scrollLeft}
|
||||||
|
className="h-8 w-8 flex items-center justify-center
|
||||||
|
border border-slate-300 rounded-md
|
||||||
|
hover:border-cyan-500 transition-colors"
|
||||||
|
>
|
||||||
|
<IoArrowBackOutline className="text-gray-600" size={16} />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={scrollRight}
|
||||||
|
className="h-8 w-8 flex items-center justify-center
|
||||||
|
border border-slate-300 rounded-md
|
||||||
|
hover:border-cyan-500 transition-colors"
|
||||||
|
>
|
||||||
|
<IoArrowForwardOutline className="text-gray-600" size={16} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
/* REGULAR CARD */
|
||||||
|
<div className="flex flex-col h-full">
|
||||||
|
{item.icon && (
|
||||||
|
<div className="h-12 w-12 flex items-center justify-center rounded-xl bg-gray-100 mb-6">
|
||||||
|
<item.icon className="h-6 w-6 text-cyan-600" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<p className="text-lg font-semibold text-gray-900">
|
||||||
|
{item.title}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p className="mt-2 text-gray-600 leading-snug">
|
||||||
|
{item.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{/* ✅ Bottom horizontal line with spacing */}
|
||||||
|
<div className="w-full border-b border-gray-100" />
|
||||||
|
<div className="max-w-7xl bg-transparent mx-auto py-6 border border-t-0 border-b-0 border-gray-100"></div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
import { AnimatedSection } from '../../components/AnimatedSection'
|
import { AnimatedSection } from '../../components/AnimatedSection'
|
||||||
import { DeploySection } from './DeploySection'
|
import { DeploySection } from './DeploySection'
|
||||||
import { GallerySection } from './GallerySection'
|
|
||||||
import { Companies } from './Companies'
|
import { Companies } from './Companies'
|
||||||
import { AgentBento } from './AgentBento'
|
import { AgentBento } from './AgentBento'
|
||||||
import { AgentHeroAlt } from './AgentHeroAlt'
|
import { AgentHeroAlt } from './AgentHeroAlt'
|
||||||
import { CallToAction } from './CallToAction'
|
import { CallToAction } from './CallToAction'
|
||||||
|
import { AgentUsecase } from './AgentUseCase'
|
||||||
|
import { AgentDesign } from './AgentDesign'
|
||||||
|
|
||||||
export default function AgentsPage() {
|
export default function AgentsPage() {
|
||||||
return (
|
return (
|
||||||
@@ -22,13 +23,17 @@ export default function AgentsPage() {
|
|||||||
</AnimatedSection>
|
</AnimatedSection>
|
||||||
|
|
||||||
<AnimatedSection>
|
<AnimatedSection>
|
||||||
<GallerySection />
|
<AgentUsecase />
|
||||||
</AnimatedSection>
|
</AnimatedSection>
|
||||||
|
|
||||||
<AnimatedSection>
|
<AnimatedSection>
|
||||||
<AgentBento />
|
<AgentBento />
|
||||||
</AnimatedSection>
|
</AnimatedSection>
|
||||||
|
|
||||||
|
<AnimatedSection>
|
||||||
|
<AgentDesign />
|
||||||
|
</AnimatedSection>
|
||||||
|
|
||||||
<AnimatedSection>
|
<AnimatedSection>
|
||||||
<CallToAction />
|
<CallToAction />
|
||||||
</AnimatedSection>
|
</AnimatedSection>
|
||||||
|
|||||||
@@ -18,11 +18,14 @@ export function CallToAction() {
|
|||||||
<Container className="relative">
|
<Container className="relative">
|
||||||
<div className="mx-auto max-w-3xl text-center">
|
<div className="mx-auto max-w-3xl text-center">
|
||||||
<h2 className="text-3xl lg:text-4xl font-medium tracking-tight text-white sm:text-4xl">
|
<h2 className="text-3xl lg:text-4xl font-medium tracking-tight text-white sm:text-4xl">
|
||||||
Start Building the Future of Sovereign AI
|
Start with Mycelium Today
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<p className="mt-6 text-lg text-gray-300">
|
<p className="mt-6 text-lg text-gray-300">
|
||||||
Use today’s components — models, storage, compute, mesh — and step into agents as they arrive.
|
The Agent Framework launches in H1 2026, but the foundation is ready now.
|
||||||
|
</p>
|
||||||
|
<p className="mt-6 text-lg text-gray-300">
|
||||||
|
Use today’s components —models, storage, compute, and network— to deploy workloads, connect nodes, and prepare for the next generation of distributed AI.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* ✅ Two cards, stacked center with spacing */}
|
{/* ✅ Two cards, stacked center with spacing */}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ const row2 = logos.slice(6);
|
|||||||
|
|
||||||
export function Companies() {
|
export function Companies() {
|
||||||
return (
|
return (
|
||||||
<div id="companies" className="relative bg-[#121212] flex flex-col items-center justify-center w-full overflow-hidden antialiased py-4 mb-12">
|
<div id="companies" className="relative bg-[#121212] flex flex-col items-center justify-center w-full overflow-hidden antialiased py-4">
|
||||||
<div className="relative z-10 mx-auto w-full max-w-7xl p-4">
|
<div className="relative z-10 mx-auto w-full max-w-7xl p-4">
|
||||||
{/* Logos grid */}
|
{/* Logos grid */}
|
||||||
<div className="flex flex-col items-center gap-y-6 text-white ">
|
<div className="flex flex-col items-center gap-y-6 text-white ">
|
||||||
|
|||||||
264
src/pages/agents/animations/AgentCoordination.tsx
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
"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 AgentNode = ({
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
r = 12,
|
||||||
|
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 + 10}
|
||||||
|
stroke="#1F2937"
|
||||||
|
strokeWidth={2}
|
||||||
|
fill="none"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 0.7 }}
|
||||||
|
transition={{ delay, duration: 0.6 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<motion.circle
|
||||||
|
cx={x}
|
||||||
|
cy={y}
|
||||||
|
r={r}
|
||||||
|
fill={accent}
|
||||||
|
initial={{ opacity: 0, scale: 0.85 }}
|
||||||
|
animate={{
|
||||||
|
opacity: 1,
|
||||||
|
scale: pulse && !prefers ? [1, 1.12, 1] : 1,
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
delay,
|
||||||
|
duration: pulse ? 1.8 : 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.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: "loop",
|
||||||
|
ease: "linear",
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
offsetPath: `path('${path}')`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default function AgentCoordination({
|
||||||
|
className,
|
||||||
|
accent = "#00b8db",
|
||||||
|
bg = "#0a0a0a",
|
||||||
|
}: Props) {
|
||||||
|
const center = { x: 380, y: 210 };
|
||||||
|
|
||||||
|
// Specialist agents placed in a controlled orbit
|
||||||
|
const agents = [
|
||||||
|
{ x: 200, y: 120 },
|
||||||
|
{ x: 560, y: 120 },
|
||||||
|
{ x: 620, y: 260 },
|
||||||
|
{ x: 500, y: 330 },
|
||||||
|
{ x: 260, y: 330 },
|
||||||
|
{ x: 140, y: 260 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const paths = agents.map(
|
||||||
|
(a) => `M ${center.x} ${center.y} Q ${(center.x + a.x) / 2} ${(center.y + a.y) / 2 - 40} ${a.x} ${a.y}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx("relative overflow-hidden", className)}
|
||||||
|
aria-hidden="true"
|
||||||
|
role="img"
|
||||||
|
aria-label="Agent coordination and sovereign workflow management"
|
||||||
|
style={{ background: bg }}
|
||||||
|
>
|
||||||
|
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full" preserveAspectRatio="xMidYMid slice">
|
||||||
|
|
||||||
|
{/* background */}
|
||||||
|
<defs>
|
||||||
|
<pattern id="grid-dark" width="28" height="28" patternUnits="userSpaceOnUse">
|
||||||
|
<path d="M 28 0 L 0 0 0 28" stroke="#0f0f0f" strokeWidth="1" />
|
||||||
|
</pattern>
|
||||||
|
|
||||||
|
<radialGradient id="glow" cx="50%" cy="50%" r="50%">
|
||||||
|
<stop offset="0%" stopColor={accent} stopOpacity="0.20" />
|
||||||
|
<stop offset="100%" stopColor={accent} stopOpacity="0" />
|
||||||
|
</radialGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<rect width={W} height={H} fill="url(#grid-dark)" />
|
||||||
|
|
||||||
|
{/* global glow */}
|
||||||
|
<circle cx={center.x} cy={center.y} r={240} fill="url(#glow)" opacity={0.45} />
|
||||||
|
|
||||||
|
{/* POLICY RING */}
|
||||||
|
<motion.circle
|
||||||
|
cx={center.x}
|
||||||
|
cy={center.y}
|
||||||
|
r={110}
|
||||||
|
stroke={accent}
|
||||||
|
strokeWidth={2}
|
||||||
|
fill="none"
|
||||||
|
strokeDasharray="14 10"
|
||||||
|
animate={{ opacity: [0.25, 0.7, 0.25] }}
|
||||||
|
transition={{
|
||||||
|
duration: 3,
|
||||||
|
repeat: Infinity,
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* CHECKPOINT GATE */}
|
||||||
|
<motion.rect
|
||||||
|
x={center.x - 50}
|
||||||
|
y={center.y - 130}
|
||||||
|
width={100}
|
||||||
|
height={22}
|
||||||
|
rx={6}
|
||||||
|
stroke={accent}
|
||||||
|
strokeWidth={2}
|
||||||
|
fill="none"
|
||||||
|
animate={{ opacity: [0.2, 0.7, 0.2] }}
|
||||||
|
transition={{
|
||||||
|
duration: 2.4,
|
||||||
|
repeat: Infinity,
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* central conductor agent */}
|
||||||
|
<motion.circle
|
||||||
|
cx={center.x}
|
||||||
|
cy={center.y}
|
||||||
|
r={26}
|
||||||
|
fill={accent}
|
||||||
|
initial={{ opacity: 0, scale: 0.85 }}
|
||||||
|
animate={{
|
||||||
|
opacity: 1,
|
||||||
|
scale: [1, 1.06, 1],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 1.8,
|
||||||
|
repeat: Infinity,
|
||||||
|
repeatType: "mirror",
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* inward → outward routing lines */}
|
||||||
|
{paths.map((p, i) => (
|
||||||
|
<motion.path
|
||||||
|
key={`p-${i}`}
|
||||||
|
d={p}
|
||||||
|
stroke="#1F2937"
|
||||||
|
strokeWidth={3}
|
||||||
|
strokeLinecap="round"
|
||||||
|
fill="none"
|
||||||
|
initial={{ pathLength: 0, opacity: 0 }}
|
||||||
|
animate={{ pathLength: 1, opacity: 0.4 }}
|
||||||
|
transition={{
|
||||||
|
delay: i * 0.1,
|
||||||
|
duration: 0.8,
|
||||||
|
ease: [0.22, 1, 0.36, 1],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* OUTBOUND tasks */}
|
||||||
|
{paths.map((p, i) => (
|
||||||
|
<Packet
|
||||||
|
key={`out-${i}`}
|
||||||
|
path={p}
|
||||||
|
delay={0.3 * i}
|
||||||
|
accent={accent}
|
||||||
|
duration={2}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* INBOUND results */}
|
||||||
|
{paths.map((p, i) => (
|
||||||
|
<Packet
|
||||||
|
key={`in-${i}`}
|
||||||
|
path={p}
|
||||||
|
reverse
|
||||||
|
delay={0.5 * i}
|
||||||
|
accent={accent}
|
||||||
|
duration={2.2}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* specialist agents */}
|
||||||
|
{agents.map((a, i) => (
|
||||||
|
<AgentNode
|
||||||
|
key={`agent-${i}`}
|
||||||
|
x={a.x}
|
||||||
|
y={a.y}
|
||||||
|
pulse={i % 2 === 0}
|
||||||
|
delay={i * 0.1}
|
||||||
|
accent={accent}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
263
src/pages/agents/animations/DeterministicExecution.tsx
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
"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 PulseCircle = ({
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
r,
|
||||||
|
accent,
|
||||||
|
delay = 0,
|
||||||
|
}: {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
r: number;
|
||||||
|
accent: string;
|
||||||
|
delay?: number;
|
||||||
|
}) => {
|
||||||
|
const prefers = useReducedMotion();
|
||||||
|
return (
|
||||||
|
<motion.circle
|
||||||
|
cx={x}
|
||||||
|
cy={y}
|
||||||
|
r={r}
|
||||||
|
stroke={accent}
|
||||||
|
strokeWidth={2}
|
||||||
|
fill="none"
|
||||||
|
initial={{ scale: 0.8, opacity: 0 }}
|
||||||
|
animate={{
|
||||||
|
scale: !prefers ? [1, 1.25, 1] : 1,
|
||||||
|
opacity: !prefers ? [0.4, 0.9, 0.4] : 1,
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
delay,
|
||||||
|
duration: 2.2,
|
||||||
|
repeat: !prefers ? Infinity : 0,
|
||||||
|
repeatType: "mirror",
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Packet = ({
|
||||||
|
path,
|
||||||
|
delay = 0,
|
||||||
|
duration = 2,
|
||||||
|
accent = "#00b8db",
|
||||||
|
}: {
|
||||||
|
path: string;
|
||||||
|
delay?: number;
|
||||||
|
duration?: number;
|
||||||
|
accent?: string;
|
||||||
|
}) => {
|
||||||
|
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 DeterministicExecution({
|
||||||
|
className,
|
||||||
|
accent = "#00b8db",
|
||||||
|
bg = "#0a0a0a",
|
||||||
|
}: Props) {
|
||||||
|
const left = { x: 200, y: 210 };
|
||||||
|
const center = { x: 380, y: 210 };
|
||||||
|
const right = { x: 560, y: 210 };
|
||||||
|
|
||||||
|
const leftToCenter = `M ${left.x} ${left.y} L ${center.x} ${center.y}`;
|
||||||
|
const centerToRight = `M ${center.x} ${center.y} L ${right.x} ${right.y}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx("relative overflow-hidden", className)}
|
||||||
|
aria-hidden="true"
|
||||||
|
role="img"
|
||||||
|
aria-label="Deterministic deployment and verifiable code execution"
|
||||||
|
style={{ background: bg }}
|
||||||
|
>
|
||||||
|
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full" preserveAspectRatio="xMidYMid slice">
|
||||||
|
|
||||||
|
{/* background grid */}
|
||||||
|
<defs>
|
||||||
|
<pattern id="grid-dark" width="28" height="28" patternUnits="userSpaceOnUse">
|
||||||
|
<path d="M 28 0 L 0 0 0 28" stroke="#0f0f0f" strokeWidth="1" />
|
||||||
|
</pattern>
|
||||||
|
|
||||||
|
<radialGradient id="glow" cx="50%" cy="50%" r="50%">
|
||||||
|
<stop offset="0%" stopColor={accent} stopOpacity="0.18" />
|
||||||
|
<stop offset="100%" stopColor={accent} stopOpacity="0" />
|
||||||
|
</radialGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<rect width={W} height={H} fill="url(#grid-dark)" />
|
||||||
|
|
||||||
|
{/* global glow */}
|
||||||
|
<circle cx={center.x} cy={center.y} r={230} fill="url(#glow)" opacity={0.4} />
|
||||||
|
|
||||||
|
{/* LEFT: Declare Intent block */}
|
||||||
|
<motion.rect
|
||||||
|
x={left.x - 60}
|
||||||
|
y={left.y - 40}
|
||||||
|
width={120}
|
||||||
|
height={80}
|
||||||
|
rx={10}
|
||||||
|
stroke="#1F2937"
|
||||||
|
strokeWidth={3}
|
||||||
|
fill="none"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 0.8 }}
|
||||||
|
transition={{ duration: 0.6 }}
|
||||||
|
/>
|
||||||
|
<motion.rect
|
||||||
|
x={left.x - 45}
|
||||||
|
y={left.y - 25}
|
||||||
|
width={90}
|
||||||
|
height={50}
|
||||||
|
rx={6}
|
||||||
|
fill={accent}
|
||||||
|
initial={{ opacity: 0, scale: 0.85 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
transition={{ duration: 0.8 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* CENTER: Cryptographic hash */}
|
||||||
|
<motion.rect
|
||||||
|
x={center.x - 40}
|
||||||
|
y={center.y - 40}
|
||||||
|
width={80}
|
||||||
|
height={80}
|
||||||
|
rx={12}
|
||||||
|
stroke={accent}
|
||||||
|
strokeWidth={2}
|
||||||
|
strokeDasharray="10 6"
|
||||||
|
fill="none"
|
||||||
|
animate={{ opacity: [0.4, 0.9, 0.4] }}
|
||||||
|
transition={{
|
||||||
|
duration: 2,
|
||||||
|
repeat: Infinity,
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* hash print */}
|
||||||
|
<motion.text
|
||||||
|
x={center.x}
|
||||||
|
y={center.y + 5}
|
||||||
|
textAnchor="middle"
|
||||||
|
fill={accent}
|
||||||
|
fontFamily="monospace"
|
||||||
|
fontSize="14"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 0.9 }}
|
||||||
|
transition={{ delay: 0.3 }}
|
||||||
|
>
|
||||||
|
9f1a..42c7
|
||||||
|
</motion.text>
|
||||||
|
|
||||||
|
{/* signature stamp */}
|
||||||
|
<motion.circle
|
||||||
|
cx={center.x + 55}
|
||||||
|
cy={center.y - 55}
|
||||||
|
r={14}
|
||||||
|
stroke={accent}
|
||||||
|
strokeWidth={2}
|
||||||
|
fill="none"
|
||||||
|
initial={{ scale: 0, opacity: 0 }}
|
||||||
|
animate={{ scale: 1, opacity: 1 }}
|
||||||
|
transition={{ delay: 0.4, duration: 0.6 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* RIGHT: Verified Execution Node */}
|
||||||
|
<motion.circle
|
||||||
|
cx={right.x}
|
||||||
|
cy={right.y}
|
||||||
|
r={24}
|
||||||
|
fill={accent}
|
||||||
|
initial={{ opacity: 0, scale: 0.8 }}
|
||||||
|
animate={{
|
||||||
|
opacity: 1,
|
||||||
|
scale: [1, 1.06, 1],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 1.8,
|
||||||
|
repeat: Infinity,
|
||||||
|
repeatType: "mirror",
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* outer attestation ring */}
|
||||||
|
<PulseCircle x={right.x} y={right.y} r={40} accent={accent} delay={0.2} />
|
||||||
|
|
||||||
|
{/* immutable logs ring */}
|
||||||
|
<motion.circle
|
||||||
|
cx={right.x}
|
||||||
|
cy={right.y}
|
||||||
|
r={70}
|
||||||
|
stroke={accent}
|
||||||
|
strokeWidth={2}
|
||||||
|
strokeDasharray="14 12"
|
||||||
|
fill="none"
|
||||||
|
animate={{ opacity: [0.2, 0.6, 0.2] }}
|
||||||
|
transition={{
|
||||||
|
duration: 3,
|
||||||
|
repeat: Infinity,
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* deterministic path lines */}
|
||||||
|
<motion.path
|
||||||
|
d={leftToCenter}
|
||||||
|
stroke="#1F2937"
|
||||||
|
strokeWidth={3}
|
||||||
|
fill="none"
|
||||||
|
initial={{ pathLength: 0 }}
|
||||||
|
animate={{ pathLength: 1 }}
|
||||||
|
transition={{ duration: 0.8 }}
|
||||||
|
/>
|
||||||
|
<motion.path
|
||||||
|
d={centerToRight}
|
||||||
|
stroke="#1F2937"
|
||||||
|
strokeWidth={3}
|
||||||
|
fill="none"
|
||||||
|
initial={{ pathLength: 0 }}
|
||||||
|
animate={{ pathLength: 1 }}
|
||||||
|
transition={{ duration: 0.8, delay: 0.2 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* active cryptographic pulses */}
|
||||||
|
<Packet path={leftToCenter} delay={0.4} accent={accent} duration={1.8} />
|
||||||
|
<Packet path={centerToRight} delay={0.8} accent={accent} duration={1.8} />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
238
src/pages/agents/animations/Fungistor.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 (
|
||||||
|
<>
|
||||||
|
{/* outer faint ring */}
|
||||||
|
<motion.circle
|
||||||
|
cx={x}
|
||||||
|
cy={y}
|
||||||
|
r={r + 10}
|
||||||
|
stroke="#1F2937"
|
||||||
|
strokeWidth={2}
|
||||||
|
fill="none"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 0.6 }}
|
||||||
|
transition={{ delay, duration: 0.6 }}
|
||||||
|
/>
|
||||||
|
{/* glowing shard node */}
|
||||||
|
<motion.circle
|
||||||
|
cx={x}
|
||||||
|
cy={y}
|
||||||
|
r={r}
|
||||||
|
fill={accent}
|
||||||
|
initial={{ opacity: 0, scale: 0.8 }}
|
||||||
|
animate={{
|
||||||
|
opacity: 1,
|
||||||
|
scale: pulse && !prefers ? [1, 1.12, 1] : 1,
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
delay,
|
||||||
|
duration: pulse && !prefers ? 1.8 : 0.6,
|
||||||
|
repeat: pulse && !prefers ? Infinity : 0,
|
||||||
|
repeatType: "mirror",
|
||||||
|
ease: [0.22, 1, 0.36, 1],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Shard = ({
|
||||||
|
path,
|
||||||
|
delay = 0,
|
||||||
|
accent = "#00b8db",
|
||||||
|
duration = 2,
|
||||||
|
}: {
|
||||||
|
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 FungiStor({
|
||||||
|
className,
|
||||||
|
accent = "#00b8db",
|
||||||
|
bg = "#0a0a0a",
|
||||||
|
}: Props) {
|
||||||
|
const center = { x: 380, y: 210 };
|
||||||
|
const shardNodes = [
|
||||||
|
{ x: 160, y: 100 },
|
||||||
|
{ x: 260, y: 70 },
|
||||||
|
{ x: 580, y: 100 },
|
||||||
|
{ x: 620, y: 250 },
|
||||||
|
{ x: 500, y: 330 },
|
||||||
|
{ x: 240, y: 320 },
|
||||||
|
];
|
||||||
|
|
||||||
|
// outgoing shard paths
|
||||||
|
const paths = shardNodes.map(
|
||||||
|
(n) => `M ${center.x} ${center.y} Q ${(n.x + center.x) / 2} ${(n.y + center.y) / 2 - 40} ${n.x} ${n.y}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx("relative overflow-hidden", className)}
|
||||||
|
aria-hidden="true"
|
||||||
|
role="img"
|
||||||
|
aria-label="FungiStor — distributed long-term AI memory"
|
||||||
|
style={{ background: bg }}
|
||||||
|
>
|
||||||
|
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full" preserveAspectRatio="xMidYMid slice">
|
||||||
|
{/* 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.15" />
|
||||||
|
<stop offset="100%" stopColor={accent} stopOpacity="0" />
|
||||||
|
</radialGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<rect width={W} height={H} fill="url(#grid-dark)" />
|
||||||
|
|
||||||
|
{/* Soft global network glow */}
|
||||||
|
<circle cx={center.x} cy={center.y} r={180} fill="url(#glow)" opacity={0.4} />
|
||||||
|
|
||||||
|
{/* Source data core */}
|
||||||
|
<motion.rect
|
||||||
|
x={center.x - 40}
|
||||||
|
y={center.y - 40}
|
||||||
|
width={80}
|
||||||
|
height={80}
|
||||||
|
rx={12}
|
||||||
|
fill="none"
|
||||||
|
stroke={accent}
|
||||||
|
strokeWidth={2}
|
||||||
|
strokeDasharray="10 6"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: [0.4, 0.9, 0.4] }}
|
||||||
|
transition={{ duration: 2, repeat: Infinity, ease: "easeInOut" }}
|
||||||
|
/>
|
||||||
|
<motion.rect
|
||||||
|
x={center.x - 20}
|
||||||
|
y={center.y - 20}
|
||||||
|
width={40}
|
||||||
|
height={40}
|
||||||
|
rx={6}
|
||||||
|
fill={accent}
|
||||||
|
initial={{ opacity: 0, scale: 0.8 }}
|
||||||
|
animate={{
|
||||||
|
opacity: 1,
|
||||||
|
scale: [1, 1.08, 1],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 1.8,
|
||||||
|
repeat: Infinity,
|
||||||
|
repeatType: "mirror",
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Outgoing shard connections */}
|
||||||
|
{paths.map((p, i) => (
|
||||||
|
<motion.path
|
||||||
|
key={`path-${i}`}
|
||||||
|
d={p}
|
||||||
|
stroke="#1F2937"
|
||||||
|
strokeWidth={2}
|
||||||
|
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],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Animated data shards traveling */}
|
||||||
|
{paths.map((p, i) => (
|
||||||
|
<Shard key={`shard-${i}`} path={p} delay={i * 0.4} accent={accent} duration={2.6} />
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Destination storage nodes */}
|
||||||
|
{shardNodes.map((n, i) => (
|
||||||
|
<Node
|
||||||
|
key={`node-${i}`}
|
||||||
|
x={n.x}
|
||||||
|
y={n.y}
|
||||||
|
accent={accent}
|
||||||
|
pulse={i % 2 === 0}
|
||||||
|
delay={i * 0.1}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* “Reconstruction glow” pulse ring */}
|
||||||
|
<motion.circle
|
||||||
|
cx={center.x}
|
||||||
|
cy={center.y}
|
||||||
|
r={110}
|
||||||
|
stroke={accent}
|
||||||
|
strokeWidth={2}
|
||||||
|
fill="none"
|
||||||
|
initial={{ scale: 0.9, opacity: 0 }}
|
||||||
|
animate={{
|
||||||
|
scale: [0.9, 1.2, 0.9],
|
||||||
|
opacity: [0.2, 0.6, 0.2],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 3.2,
|
||||||
|
repeat: Infinity,
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
286
src/pages/agents/animations/Herodb.tsx
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
"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 = 12,
|
||||||
|
accent = "#00b8db",
|
||||||
|
pulse = false,
|
||||||
|
delay = 0,
|
||||||
|
type = "dot",
|
||||||
|
}: {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
r?: number;
|
||||||
|
accent?: string;
|
||||||
|
pulse?: boolean;
|
||||||
|
delay?: number;
|
||||||
|
type?: "dot" | "text" | "image" | "audio";
|
||||||
|
}) => {
|
||||||
|
const prefers = useReducedMotion();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* faint ring */}
|
||||||
|
<motion.circle
|
||||||
|
cx={x}
|
||||||
|
cy={y}
|
||||||
|
r={r + 10}
|
||||||
|
stroke="#1F2937"
|
||||||
|
strokeWidth={2}
|
||||||
|
fill="none"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 0.7 }}
|
||||||
|
transition={{ delay, duration: 0.6 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* inner icon shape to represent modality */}
|
||||||
|
{type === "text" && (
|
||||||
|
<motion.rect
|
||||||
|
x={x - r}
|
||||||
|
y={y - r / 2}
|
||||||
|
width={r * 2}
|
||||||
|
height={r}
|
||||||
|
rx={3}
|
||||||
|
fill={accent}
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ delay }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{type === "image" && (
|
||||||
|
<motion.path
|
||||||
|
d={`M ${x - r} ${y + r/2} L ${x - r} ${y - r/2} L ${x + r} ${y - r/2} L ${x + r} ${y + r/2} Z`}
|
||||||
|
stroke={accent}
|
||||||
|
strokeWidth={2}
|
||||||
|
fill="none"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ delay }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{type === "audio" && (
|
||||||
|
<motion.path
|
||||||
|
d={`
|
||||||
|
M ${x - r/2} ${y - r/2}
|
||||||
|
L ${x - r/2} ${y + r/2}
|
||||||
|
M ${x} ${y - r/3}
|
||||||
|
L ${x} ${y + r/3}
|
||||||
|
M ${x + r/2} ${y - r/4}
|
||||||
|
L ${x + r/2} ${y + r/4}
|
||||||
|
`}
|
||||||
|
stroke={accent}
|
||||||
|
strokeWidth={2}
|
||||||
|
strokeLinecap="round"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ delay }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* standard pulsing circle fallback */}
|
||||||
|
{type === "dot" && (
|
||||||
|
<motion.circle
|
||||||
|
cx={x}
|
||||||
|
cy={y}
|
||||||
|
r={r}
|
||||||
|
fill={accent}
|
||||||
|
initial={{ opacity: 0, scale: 0.85 }}
|
||||||
|
animate={{
|
||||||
|
opacity: 1,
|
||||||
|
scale: pulse && !prefers ? [1, 1.12, 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],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// inward pulse particle
|
||||||
|
const Packet = ({
|
||||||
|
path,
|
||||||
|
delay = 0,
|
||||||
|
accent = "#00b8db",
|
||||||
|
duration = 1.8,
|
||||||
|
}: {
|
||||||
|
path: string;
|
||||||
|
delay?: number;
|
||||||
|
accent?: string;
|
||||||
|
duration?: number;
|
||||||
|
}) => {
|
||||||
|
const prefers = useReducedMotion();
|
||||||
|
return (
|
||||||
|
<motion.circle
|
||||||
|
r={4}
|
||||||
|
fill={accent}
|
||||||
|
initial={{ offsetDistance: "100%", opacity: 0 }}
|
||||||
|
animate={{
|
||||||
|
offsetDistance: ["100%", "0%"],
|
||||||
|
opacity: [0, 1, 0],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
delay,
|
||||||
|
duration,
|
||||||
|
repeat: !prefers ? Infinity : 0,
|
||||||
|
repeatType: "loop",
|
||||||
|
ease: "linear",
|
||||||
|
}}
|
||||||
|
style={{ offsetPath: `path('${path}')` }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Herodb({
|
||||||
|
className,
|
||||||
|
accent = "#00b8db",
|
||||||
|
bg = "#0a0a0a",
|
||||||
|
}: Props) {
|
||||||
|
const center = { x: 380, y: 210 };
|
||||||
|
|
||||||
|
const shardNodes = [
|
||||||
|
{ x: 160, y: 100, type: "text" },
|
||||||
|
{ x: 580, y: 120, type: "image" },
|
||||||
|
{ x: 620, y: 280, type: "audio" },
|
||||||
|
{ x: 420, y: 330, type: "text" },
|
||||||
|
{ x: 240, y: 320, type: "image" },
|
||||||
|
{ x: 150, y: 220, type: "dot" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const paths = shardNodes.map(
|
||||||
|
(n) =>
|
||||||
|
`M ${n.x} ${n.y} Q ${(n.x + center.x) / 2} ${(n.y + center.y) / 2 - 40} ${center.x} ${center.y}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx("relative overflow-hidden", className)}
|
||||||
|
aria-hidden="true"
|
||||||
|
role="img"
|
||||||
|
aria-label="HeroDB — active AI memory retrieval"
|
||||||
|
style={{ background: bg }}
|
||||||
|
>
|
||||||
|
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full" preserveAspectRatio="xMidYMid slice">
|
||||||
|
{/* 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.18" />
|
||||||
|
<stop offset="100%" stopColor={accent} stopOpacity="0" />
|
||||||
|
</radialGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<rect width={W} height={H} fill="url(#grid-dark)" />
|
||||||
|
|
||||||
|
{/* global halo */}
|
||||||
|
<circle
|
||||||
|
cx={center.x}
|
||||||
|
cy={center.y}
|
||||||
|
r={200}
|
||||||
|
fill="url(#glow)"
|
||||||
|
opacity={0.45}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* core retrieval sphere */}
|
||||||
|
<motion.circle
|
||||||
|
cx={center.x}
|
||||||
|
cy={center.y}
|
||||||
|
r={22}
|
||||||
|
fill={accent}
|
||||||
|
initial={{ opacity: 0, scale: 0.85 }}
|
||||||
|
animate={{
|
||||||
|
opacity: 1,
|
||||||
|
scale: [1, 1.05, 1],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 1.6,
|
||||||
|
repeat: Infinity,
|
||||||
|
repeatType: "mirror",
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* core aura ring */}
|
||||||
|
<motion.circle
|
||||||
|
cx={center.x}
|
||||||
|
cy={center.y}
|
||||||
|
r={40}
|
||||||
|
stroke={accent}
|
||||||
|
strokeWidth={2}
|
||||||
|
fill="none"
|
||||||
|
animate={{
|
||||||
|
opacity: [0.2, 0.7, 0.2],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 2.2,
|
||||||
|
repeat: Infinity,
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* inward paths */}
|
||||||
|
{paths.map((p, i) => (
|
||||||
|
<motion.path
|
||||||
|
key={`path-${i}`}
|
||||||
|
d={p}
|
||||||
|
stroke="#1F2937"
|
||||||
|
strokeWidth={2}
|
||||||
|
fill="none"
|
||||||
|
initial={{ pathLength: 0, opacity: 0 }}
|
||||||
|
animate={{ pathLength: 1, opacity: 0.4 }}
|
||||||
|
transition={{
|
||||||
|
delay: 0.05 * i,
|
||||||
|
duration: 0.7,
|
||||||
|
ease: [0.22, 1, 0.36, 1],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* packets flowing inward */}
|
||||||
|
{paths.map((p, i) => (
|
||||||
|
<Packet
|
||||||
|
key={`pkt-${i}`}
|
||||||
|
path={p}
|
||||||
|
delay={i * 0.3}
|
||||||
|
accent={accent}
|
||||||
|
duration={1.8}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* modality nodes */}
|
||||||
|
{shardNodes.map((n, i) => (
|
||||||
|
<Node
|
||||||
|
key={`node-${i}`}
|
||||||
|
x={n.x}
|
||||||
|
y={n.y}
|
||||||
|
type={n.type as any}
|
||||||
|
r={12}
|
||||||
|
accent={accent}
|
||||||
|
pulse={i % 2 === 0}
|
||||||
|
delay={i * 0.1}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
231
src/pages/agents/animations/MOSSandboxes.tsx
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
"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 PulseRing = ({
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
accent,
|
||||||
|
delay = 0,
|
||||||
|
}: {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
accent: string;
|
||||||
|
delay?: number;
|
||||||
|
}) => {
|
||||||
|
const prefers = useReducedMotion();
|
||||||
|
return (
|
||||||
|
<motion.circle
|
||||||
|
cx={x}
|
||||||
|
cy={y}
|
||||||
|
r={42}
|
||||||
|
stroke={accent}
|
||||||
|
strokeWidth={2}
|
||||||
|
fill="none"
|
||||||
|
initial={{ scale: 0.85, opacity: 0 }}
|
||||||
|
animate={{
|
||||||
|
scale: !prefers ? [1, 1.12, 1] : 1,
|
||||||
|
opacity: !prefers ? [0.15, 0.6, 0.15] : 0.4,
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
delay,
|
||||||
|
duration: 2,
|
||||||
|
repeat: !prefers ? Infinity : 0,
|
||||||
|
repeatType: "mirror",
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Egress = ({
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
delay = 0,
|
||||||
|
accent = "#00b8db",
|
||||||
|
}: {
|
||||||
|
from: { x: number; y: number };
|
||||||
|
to: { x: number; y: number };
|
||||||
|
delay?: number;
|
||||||
|
accent?: string;
|
||||||
|
}) => {
|
||||||
|
const path = `M ${from.x} ${from.y} L ${to.x} ${to.y}`;
|
||||||
|
const prefers = useReducedMotion();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<motion.path
|
||||||
|
d={path}
|
||||||
|
stroke="#1F2937"
|
||||||
|
strokeWidth={3}
|
||||||
|
strokeLinecap="round"
|
||||||
|
fill="none"
|
||||||
|
initial={{ pathLength: 0, opacity: 0 }}
|
||||||
|
animate={{ pathLength: 1, opacity: 0.4 }}
|
||||||
|
transition={{
|
||||||
|
delay,
|
||||||
|
duration: 0.8,
|
||||||
|
ease: [0.22, 1, 0.36, 1],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<motion.circle
|
||||||
|
r={4}
|
||||||
|
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 MOSSandboxes({
|
||||||
|
className,
|
||||||
|
accent = "#00b8db",
|
||||||
|
bg = "#0a0a0a",
|
||||||
|
}: Props) {
|
||||||
|
const center = { x: 380, y: 210 };
|
||||||
|
|
||||||
|
// scoped egress ports
|
||||||
|
const egress = [
|
||||||
|
{ from: center, to: { x: 520, y: 140 } },
|
||||||
|
{ from: center, to: { x: 520, y: 280 } },
|
||||||
|
{ from: center, to: { x: 260, y: 320 } },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx("relative overflow-hidden", className)}
|
||||||
|
aria-hidden="true"
|
||||||
|
role="img"
|
||||||
|
aria-label="MOS Secure Agent Sandboxes"
|
||||||
|
style={{ background: bg }}
|
||||||
|
>
|
||||||
|
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full" preserveAspectRatio="xMidYMid slice">
|
||||||
|
{/* BACKGROUND GRID */}
|
||||||
|
<defs>
|
||||||
|
<pattern id="grid-dark" width="28" height="28" patternUnits="userSpaceOnUse">
|
||||||
|
<path d="M 28 0 L 0 0 0 28" stroke="#0f0f0f" strokeWidth="1" />
|
||||||
|
</pattern>
|
||||||
|
|
||||||
|
<radialGradient id="glow" cx="50%" cy="50%" r="50%">
|
||||||
|
<stop offset="0%" stopColor={accent} stopOpacity="0.20" />
|
||||||
|
<stop offset="100%" stopColor={accent} stopOpacity="0" />
|
||||||
|
</radialGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<rect width={W} height={H} fill="url(#grid-dark)" />
|
||||||
|
|
||||||
|
{/* GLOBAL GLOW */}
|
||||||
|
<circle cx={center.x} cy={center.y} r={200} fill="url(#glow)" opacity={0.45} />
|
||||||
|
|
||||||
|
{/* SANDBOX OUTER ENCLAVE */}
|
||||||
|
<motion.rect
|
||||||
|
x={center.x - 90}
|
||||||
|
y={center.y - 60}
|
||||||
|
width={180}
|
||||||
|
height={120}
|
||||||
|
rx={16}
|
||||||
|
stroke="#1F2937"
|
||||||
|
strokeWidth={3}
|
||||||
|
fill="none"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 0.7 }}
|
||||||
|
transition={{ duration: 0.6 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* ATTESTATION RING */}
|
||||||
|
<PulseRing x={center.x} y={center.y} accent={accent} delay={0.3} />
|
||||||
|
|
||||||
|
{/* SIGNED WORKSPACE CORE */}
|
||||||
|
<motion.rect
|
||||||
|
x={center.x - 40}
|
||||||
|
y={center.y - 30}
|
||||||
|
width={80}
|
||||||
|
height={60}
|
||||||
|
rx={10}
|
||||||
|
stroke={accent}
|
||||||
|
strokeWidth={2}
|
||||||
|
strokeDasharray="10 6"
|
||||||
|
fill="none"
|
||||||
|
initial={{ opacity: 0, scale: 0.8 }}
|
||||||
|
animate={{
|
||||||
|
opacity: [0.4, 0.9, 0.4],
|
||||||
|
scale: [1, 1.06, 1],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 2,
|
||||||
|
repeat: Infinity,
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* SANDBOX ACTIVE PAYLOAD */}
|
||||||
|
<motion.circle
|
||||||
|
cx={center.x}
|
||||||
|
cy={center.y}
|
||||||
|
r={18}
|
||||||
|
fill={accent}
|
||||||
|
initial={{ scale: 0.6, opacity: 0 }}
|
||||||
|
animate={{
|
||||||
|
opacity: 1,
|
||||||
|
scale: [1, 1.1, 1],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 1.8,
|
||||||
|
repeat: Infinity,
|
||||||
|
repeatType: "mirror",
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* EGRESS PATHS */}
|
||||||
|
{egress.map((e, i) => (
|
||||||
|
<Egress key={i} from={e.from} to={e.to} delay={i * 0.2} accent={accent} />
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* “TEAR DOWN” FADE — ephemeral sandbox lifecycle */}
|
||||||
|
<motion.rect
|
||||||
|
x={center.x - 90}
|
||||||
|
y={center.y - 60}
|
||||||
|
width={180}
|
||||||
|
height={120}
|
||||||
|
rx={16}
|
||||||
|
fill={accent}
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{
|
||||||
|
opacity: [0, 0, 0.12, 0],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 4,
|
||||||
|
repeat: Infinity,
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
231
src/pages/agents/animations/MyceliumMesh.tsx
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
"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 = 12,
|
||||||
|
accent = "#00b8db",
|
||||||
|
pulse = false,
|
||||||
|
delay = 0,
|
||||||
|
}: {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
r?: number;
|
||||||
|
accent?: string;
|
||||||
|
pulse?: boolean;
|
||||||
|
delay?: number;
|
||||||
|
}) => {
|
||||||
|
const prefers = useReducedMotion();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Outer ring */}
|
||||||
|
<motion.circle
|
||||||
|
cx={x}
|
||||||
|
cy={y}
|
||||||
|
r={r + 8}
|
||||||
|
stroke="#1F2937"
|
||||||
|
strokeWidth={2}
|
||||||
|
fill="none"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 0.8 }}
|
||||||
|
transition={{ duration: 0.6, delay }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Core node */}
|
||||||
|
<motion.circle
|
||||||
|
cx={x}
|
||||||
|
cy={y}
|
||||||
|
r={r}
|
||||||
|
fill={accent}
|
||||||
|
initial={{ opacity: 0, scale: 0.85 }}
|
||||||
|
animate={{
|
||||||
|
opacity: 1,
|
||||||
|
scale: pulse && !prefers ? [1, 1.1, 1] : 1,
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: pulse ? 1.6 : 0.6,
|
||||||
|
repeat: pulse && !prefers ? Infinity : 0,
|
||||||
|
repeatType: "mirror",
|
||||||
|
ease: [0.22, 1, 0.36, 1],
|
||||||
|
delay,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Animated encrypted packet */
|
||||||
|
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={5}
|
||||||
|
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 MyceliumMesh({
|
||||||
|
className,
|
||||||
|
accent = "#00b8db",
|
||||||
|
bg = "#0a0a0a",
|
||||||
|
}: Props) {
|
||||||
|
const center = { x: 380, y: 210 };
|
||||||
|
|
||||||
|
// Peer nodes forming a mesh
|
||||||
|
const nodes = [
|
||||||
|
{ x: 180, y: 120 },
|
||||||
|
{ x: 580, y: 100 },
|
||||||
|
{ x: 620, y: 260 },
|
||||||
|
{ x: 460, y: 330 },
|
||||||
|
{ x: 240, y: 320 },
|
||||||
|
{ x: 140, y: 240 },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Multi-path routing (3 routes to illustrate "self-healing")
|
||||||
|
const routes = [
|
||||||
|
[nodes[0], nodes[1], nodes[2]], // path A→B→C
|
||||||
|
[nodes[0], nodes[5], nodes[4], nodes[3]], // path A→F→E→D
|
||||||
|
[nodes[1], nodes[4], nodes[3]], // path B→E→D
|
||||||
|
];
|
||||||
|
|
||||||
|
// Convert list of nodes → SVG path
|
||||||
|
const toPath = (list: any[]) =>
|
||||||
|
list
|
||||||
|
.map((p: any, i: number) =>
|
||||||
|
i === 0 ? `M ${p.x} ${p.y}` : `L ${p.x} ${p.y}`
|
||||||
|
)
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
"relative overflow-hidden",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
aria-hidden="true"
|
||||||
|
role="img"
|
||||||
|
aria-label="Mycelium Mesh — secure communication network"
|
||||||
|
style={{ background: bg }}
|
||||||
|
>
|
||||||
|
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full" preserveAspectRatio="xMidYMid slice">
|
||||||
|
{/* Background grid */}
|
||||||
|
<defs>
|
||||||
|
<pattern id="grid-dark" width="28" height="28" patternUnits="userSpaceOnUse">
|
||||||
|
<path d="M 28 0 L 0 0 0 28" stroke="#0f0f0f" strokeWidth="1" />
|
||||||
|
</pattern>
|
||||||
|
|
||||||
|
<radialGradient id="glow" cx="50%" cy="50%" r="50%">
|
||||||
|
<stop offset="0%" stopColor={accent} stopOpacity="0.18" />
|
||||||
|
<stop offset="100%" stopColor={accent} stopOpacity="0" />
|
||||||
|
</radialGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<rect width={W} height={H} fill="url(#grid-dark)" />
|
||||||
|
|
||||||
|
{/* Wide ambient glow */}
|
||||||
|
<circle cx={center.x} cy={center.y} r={240} fill="url(#glow)" opacity={0.45} />
|
||||||
|
|
||||||
|
{/* Multi-path routing lines */}
|
||||||
|
{routes.map((pathNodes, i) => (
|
||||||
|
<motion.path
|
||||||
|
key={`line-${i}`}
|
||||||
|
d={toPath(pathNodes)}
|
||||||
|
stroke="#1F2937"
|
||||||
|
strokeWidth={3}
|
||||||
|
fill="none"
|
||||||
|
strokeLinecap="round"
|
||||||
|
initial={{ pathLength: 0, opacity: 0 }}
|
||||||
|
animate={{ pathLength: 1, opacity: 0.4 }}
|
||||||
|
transition={{
|
||||||
|
delay: 0.2 * i,
|
||||||
|
duration: 1,
|
||||||
|
ease: [0.22, 1, 0.36, 1],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Cyan “active” encrypted routing */}
|
||||||
|
{routes.map((pathNodes, i) => (
|
||||||
|
<motion.path
|
||||||
|
key={`signal-${i}`}
|
||||||
|
d={toPath(pathNodes)}
|
||||||
|
stroke={accent}
|
||||||
|
strokeWidth={2.5}
|
||||||
|
strokeDasharray="12 10"
|
||||||
|
strokeLinecap="round"
|
||||||
|
fill="none"
|
||||||
|
animate={{ opacity: [0.25, 0.8, 0.25] }}
|
||||||
|
transition={{
|
||||||
|
duration: 2.4,
|
||||||
|
repeat: Infinity,
|
||||||
|
delay: i * 0.3,
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Moving encrypted packets */}
|
||||||
|
{routes.map((pathNodes, i) => (
|
||||||
|
<Packet
|
||||||
|
key={`pkt-${i}`}
|
||||||
|
path={toPath(pathNodes)}
|
||||||
|
delay={i * 0.5}
|
||||||
|
duration={2.6}
|
||||||
|
accent={accent}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Pulse nodes */}
|
||||||
|
{nodes.map((n, i) => (
|
||||||
|
<Node
|
||||||
|
key={`node-${i}`}
|
||||||
|
x={n.x}
|
||||||
|
y={n.y}
|
||||||
|
r={12}
|
||||||
|
accent={accent}
|
||||||
|
pulse={i % 2 === 0}
|
||||||
|
delay={i * 0.15}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,46 +1,77 @@
|
|||||||
import { Container } from '@/components/Container'
|
import { Container } from '@/components/Container'
|
||||||
import { Button } from '@/components/Button'
|
import { Button } from '@/components/Button'
|
||||||
|
import { H3, P } from '@/components/Texts'
|
||||||
|
|
||||||
export function CallToAction() {
|
export function CallToAction() {
|
||||||
return (
|
return (
|
||||||
<section className='relative overflow-hidden bg-[#121212]'>
|
<section className="relative overflow-hidden bg-[#121212]">
|
||||||
{/* ✅ Top horizontal line with spacing */}
|
{/* ✅ 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>
|
<div className="max-w-7xl bg-[#121212] mx-auto py-6 border border-t-0 border-b-0 border-gray-800"></div>
|
||||||
|
|
||||||
{/* === Content === */}
|
{/* === Content === */}
|
||||||
<div className="w-full border-t border-l border-r border-gray-800" />
|
<div className="w-full border-t border-l border-r border-gray-800" />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
id="get-started"
|
id="get-started"
|
||||||
className="py-18 max-w-7xl mx-auto border-t-0 border-b-0 border bg-[#111111] border-gray-800">
|
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">
|
<Container className="relative">
|
||||||
<div className="mx-auto max-w-2xl text-center ">
|
<div className="mx-auto max-w-3xl text-center">
|
||||||
<h2 className="text-3xl lg:text-4xl font-medium tracking-tight text-white sm:text-4xl">
|
<H3 className=" text-white ">
|
||||||
Use the Mycelium Stack Your Way
|
Use the Mycelium Stack Your Way
|
||||||
</h2>
|
</H3>
|
||||||
<p className="mt-6 text-lg text-gray-300">
|
|
||||||
Run workloads, connect environments, host nodes, and build agentic systems, all on one sovereign, self-healing network.
|
<P className="mt-6 text-gray-300">
|
||||||
</p>
|
Deploy infrastructure, run workloads, connect environments, and build distributed AI systems, all on one network designed for autonomy and control.
|
||||||
<p className="mt-4 text-lg text-gray-300">
|
</P>
|
||||||
Start wherever you are. Scale however you choose.
|
|
||||||
</p>
|
<P className="mt-4 text-gray-300">
|
||||||
<div className="mt-10 flex flex-wrap justify-center gap-x-6 gap-y-4">
|
Start wherever you are. Scale on your own terms.
|
||||||
<Button to="/cloud" variant="solid" color="cyan">
|
</P>
|
||||||
Get Started
|
|
||||||
|
<div className="mt-10 flex flex-wrap justify-center items-center gap-x-6 gap-y-4">
|
||||||
|
<Button to="/network" variant="solid" color="cyan">
|
||||||
|
Join the Network
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
to="https://threefold.info/mycelium_network/docs/"
|
to="/cloud"
|
||||||
as="a"
|
|
||||||
target="_blank"
|
|
||||||
variant="outline"
|
variant="outline"
|
||||||
color="white"
|
color="white"
|
||||||
>
|
>
|
||||||
Explore Docs
|
Deploy in Cloud
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<a href="/node" className="text-cyan-400 hover:text-cyan-300 transition-colors">
|
||||||
|
Host a Node →
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{/* ✅ Bottom horizontal line with spacing */}
|
|
||||||
|
{/* ✅ Bottom horizontal line */}
|
||||||
<div className="w-full border-b border-gray-800" />
|
<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>
|
<div className="max-w-7xl bg-transparent mx-auto py-6 border border-t-0 border-b-0 border-gray-800"></div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
136
src/pages/home/HomeArchitecture.tsx
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Eyebrow, H3, P } from "@/components/Texts";
|
||||||
|
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 modern internet still runs on centralized platforms that own the servers, shape the rules, and extract the data. Mycelium gives you a way out. You operate the infrastructure. You keep the data. You decide the boundaries.",
|
||||||
|
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-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: "healing",
|
||||||
|
title: "No single point of failure.",
|
||||||
|
description:
|
||||||
|
"No single entity can dictate or censor your online experience.",
|
||||||
|
animation: <NoControl />, // ✅ NEW
|
||||||
|
colSpan: "lg:col-span-2",
|
||||||
|
rowSpan: "lg:row-span-1",
|
||||||
|
rounded: "",
|
||||||
|
innerRounded: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "control",
|
||||||
|
title: "No single point of control.",
|
||||||
|
description:
|
||||||
|
"Infrastructure that moves with its operators, not a corporation.",
|
||||||
|
animation: <NoControl />, // ✅ NEW
|
||||||
|
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 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
109
src/pages/home/HomeAudience.tsx
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
"use client";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { motion, AnimatePresence } from "motion/react";
|
||||||
|
import { H3, P } from "@/components/Texts";
|
||||||
|
|
||||||
|
|
||||||
|
const rotating = [
|
||||||
|
"Communities",
|
||||||
|
"Integrators",
|
||||||
|
"Builders",
|
||||||
|
"Enterprises",
|
||||||
|
"Institutions",
|
||||||
|
"Creators",
|
||||||
|
"Researchers",
|
||||||
|
"Individuals",
|
||||||
|
];
|
||||||
|
|
||||||
|
// ✅ Use local image files (1–8)
|
||||||
|
const gallery = [
|
||||||
|
"/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() {
|
||||||
|
const [index, setIndex] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
setIndex((prev) => (prev + 1) % rotating.length);
|
||||||
|
}, 3200);
|
||||||
|
return () => clearInterval(timer);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{/* ✅ Top horizontal line + container border */}
|
||||||
|
<div className="max-w-7xl bg-transparent 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 content */}
|
||||||
|
<div className="mx-auto max-w-7xl bg-white border border-t-0 border-b-0 border-gray-100">
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2">
|
||||||
|
|
||||||
|
{/* ✅ LEFT — Text & rotating headline */}
|
||||||
|
<div className="px-6 py-14 sm:px-10 lg:px-14 flex flex-col justify-center">
|
||||||
|
<H3 className="text-black">
|
||||||
|
Sovereign Infrastructure for{" "}
|
||||||
|
<span className="inline-block text-black font-semibold relative ml-2 h-[1.2em]">
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
<motion.span
|
||||||
|
key={rotating[index]}
|
||||||
|
initial={{ opacity: 0, y: 8 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, y: -8 }}
|
||||||
|
transition={{ duration: 0.35 }}
|
||||||
|
className="absolute left-0 top-0"
|
||||||
|
>
|
||||||
|
{rotating[index]}
|
||||||
|
</motion.span>
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
|
{/* Invisible placeholder to avoid layout jump */}
|
||||||
|
<span className="opacity-0">{rotating[index]}</span>
|
||||||
|
</span>
|
||||||
|
</H3>
|
||||||
|
|
||||||
|
<P className="text-gray-800 mt-4">
|
||||||
|
The internet wasn’t built for sovereignty. Today, data, AI models, and identity
|
||||||
|
live on centralized clouds and owned by a few.
|
||||||
|
</P>
|
||||||
|
<P className="text-gray-800 mt-4">
|
||||||
|
Mycelium brings infrastructure back
|
||||||
|
to people, communities, and nations: private, resilient, and cryptographically yours.
|
||||||
|
</P>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ✅ RIGHT — Landscape image gallery synced with title */}
|
||||||
|
<div className="relative h-64 sm:h-96 lg:h-full w-full overflow-hidden">
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
<motion.img
|
||||||
|
key={gallery[index]}
|
||||||
|
src={gallery[index]}
|
||||||
|
alt=""
|
||||||
|
initial={{ opacity: 0, scale: 1.02 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
exit={{ opacity: 0, scale: 1.02 }}
|
||||||
|
transition={{ duration: 0.6 }}
|
||||||
|
className="absolute inset-0 w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ✅ Bottom border */}
|
||||||
|
<div className="w-full border-b border-gray-100" />
|
||||||
|
<div className="max-w-7xl bg-transparent mx-auto py-6 border border-t-0 border-b-0 border-gray-100" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { H1, H5 } from "@/components/Texts"
|
import { H1, H4, H5 } from "@/components/Texts"
|
||||||
import { Button } from "@/components/Button"
|
import { Button } from "@/components/Button"
|
||||||
|
|
||||||
export function HomeAurora({ onGetStartedClick }: { onGetStartedClick: () => void }) {
|
export function HomeAurora({ onGetStartedClick }: { onGetStartedClick: () => void }) {
|
||||||
@@ -6,7 +6,7 @@ export function HomeAurora({ onGetStartedClick }: { onGetStartedClick: () => voi
|
|||||||
<div className="px-4">
|
<div className="px-4">
|
||||||
{/* Boxed container */}
|
{/* Boxed container */}
|
||||||
<div
|
<div
|
||||||
className="relative mx-auto max-w-7xl border border-t-0 border-gray-100 bg-white overflow-hidden bg-contain bg-right bg-no-repeat"
|
className="relative mx-auto max-w-7xl border border-t-0 border-gray-100 bg-white overflow-hidden bg-size-[65%] bg-right bg-no-repeat"
|
||||||
style={{ backgroundImage: "url('/images/hero11.webp')" }}
|
style={{ backgroundImage: "url('/images/hero11.webp')" }}
|
||||||
>
|
>
|
||||||
{/* Inner padding */}
|
{/* Inner padding */}
|
||||||
@@ -23,12 +23,19 @@ export function HomeAurora({ onGetStartedClick }: { onGetStartedClick: () => voi
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<H1 className="mt-8">
|
<H1 className="mt-8">
|
||||||
The Sovereign Agentic Cloud
|
MYCELIUM
|
||||||
</H1>
|
</H1>
|
||||||
|
|
||||||
|
<H4 className="mt-8">
|
||||||
|
Private, distributed infrastructure built
|
||||||
|
for digital sovereignty
|
||||||
|
|
||||||
|
|
||||||
|
</H4>
|
||||||
|
|
||||||
<H5 className="mt-8 text-lg text-gray-600">
|
<H5 className="mt-8 text-lg text-gray-600">
|
||||||
Host nodes, deploy workloads, or build private AI systems,
|
|
||||||
all on infrastructure you own and control.
|
Run your apps, data, and intelligence on infrastructure that belongs to you
|
||||||
</H5>
|
</H5>
|
||||||
|
|
||||||
<div className="mt-10 flex items-center gap-x-6">
|
<div className="mt-10 flex items-center gap-x-6">
|
||||||
|
|||||||
60
src/pages/home/HomeBlink.tsx
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button } from "@/components/Button";
|
||||||
|
import { Spotlight } from "@/components/ui/spotlight";
|
||||||
|
import { H4, H5 } from "@/components/Texts";
|
||||||
|
import { HomeHeadline } from "@/components/HomeHeadline";
|
||||||
|
|
||||||
|
export function HomeBlink({ onGetStartedClick }: { onGetStartedClick: () => void }) {
|
||||||
|
return (
|
||||||
|
<div className="px-4">
|
||||||
|
<div className="relative mx-auto max-w-7xl border border-t-0 border-gray-100 bg-white overflow-hidden py-24 lg:py-32">
|
||||||
|
|
||||||
|
{/* ✅ Cyan Radial Glow */}
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
aria-hidden="true"
|
||||||
|
className="absolute top-full left-1/2 w-7xl h-720 -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>
|
||||||
|
|
||||||
|
{/* ✅ Cyan Spotlight */}
|
||||||
|
<Spotlight className="-top-40 left-0 md:-top-20 md:left-60" />
|
||||||
|
|
||||||
|
<div className="relative z-10 mx-auto w-full max-w-7xl py-4 md:pt-0">
|
||||||
|
<HomeHeadline />
|
||||||
|
<H4 className="text-center mt-8">The Living Network of the Next Internet</H4>
|
||||||
|
|
||||||
|
<H5 className="mx-auto mt-6 max-w-4xl text-center font-normal text-neutral-500">
|
||||||
|
A new internet is emerging — private, distributed, and self-sovereign.
|
||||||
|
Mycelium is the living network that makes it possible.
|
||||||
|
A peer-to-peer foundation where people, data, and intelligence connect
|
||||||
|
directly — without intermediaries, without compromise.
|
||||||
|
</H5>
|
||||||
|
|
||||||
|
<div className="mt-8 flex justify-center gap-6">
|
||||||
|
<Button variant="solid" color="cyan" onClick={onGetStartedClick}>
|
||||||
|
Enter the Network
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" color="gray" onClick={onGetStartedClick}>
|
||||||
|
Explore Docs
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
} from '@heroicons/react/24/outline'
|
} from '@heroicons/react/24/outline'
|
||||||
import { CP, CT, Eyebrow, H3, P } from '@/components/Texts'
|
import { CP, CT, Eyebrow, H3, P } from '@/components/Texts'
|
||||||
import { DarkCard } from '@/components/ui/cards'
|
import { DarkCard } from '@/components/ui/cards'
|
||||||
|
import { HeaderDark } from '@/components/HeaderDark'
|
||||||
|
|
||||||
const features = [
|
const features = [
|
||||||
{
|
{
|
||||||
@@ -32,6 +33,8 @@ const features = [
|
|||||||
|
|
||||||
export function HomeHostingDark() {
|
export function HomeHostingDark() {
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<HeaderDark />
|
||||||
<div className="relative py-24 bg-[#111111] lg:py-32">
|
<div className="relative py-24 bg-[#111111] lg:py-32">
|
||||||
<div className="mx-auto max-w-md px-6 text-center sm:max-w-3xl lg:max-w-7xl lg:px-8">
|
<div className="mx-auto max-w-md px-6 text-center sm:max-w-3xl lg:max-w-7xl lg:px-8">
|
||||||
<Eyebrow>DEPLOY</Eyebrow>
|
<Eyebrow>DEPLOY</Eyebrow>
|
||||||
@@ -60,5 +63,6 @@ export function HomeHostingDark() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
78
src/pages/home/HomeMap.tsx
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
"use client";
|
||||||
|
import WorldMap from "@/components/ui/world-map";
|
||||||
|
import { Eyebrow, H3, P } from "@/components/Texts";
|
||||||
|
|
||||||
|
const stats = [
|
||||||
|
{ id: 1, name: 'CORES', value: '54,958', description: 'Total Central Processing Unit cores available on the grid.' },
|
||||||
|
{ id: 2, name: 'NODES', value: '1,493', description: 'Total number of nodes on the grid.' },
|
||||||
|
{ id: 3, name: 'SSD CAPACITY', value: '5,388,956', description: 'Total GB of storage (SSD, HDD, & RAM) on the grid.' },
|
||||||
|
{ id: 4, name: 'COUNTRIES', value: '44', description: 'Total number of countries with active nodes.' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export function HomeMap() {
|
||||||
|
return (
|
||||||
|
<div className="bg-[#121212] w-full">
|
||||||
|
{/* ✅ Top horizontal line with spacing */}
|
||||||
|
<div className="max-w-7xl bg-transparent 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="max-w-7xl mx-auto text-center pt-12 border border-t-0 border-b-0 border-gray-800">
|
||||||
|
<Eyebrow>PROJECT MYCELIUM IS LIVE. </Eyebrow>
|
||||||
|
<H3 className="text-white">Host a Node, Grow the Network</H3>
|
||||||
|
<P className="text-sm md:text-lg text-gray-200 max-w-3xl mx-auto py-4">
|
||||||
|
Mycelium runs on nodes hosted by people and organizations around the world.
|
||||||
|
Each node adds compute, storage, and bandwidth, expanding the network’s capacity and resilience.
|
||||||
|
</P>
|
||||||
|
<P className="text-sm md:text-lg text-gray-200 max-w-3xl mx-auto py-4">
|
||||||
|
You can share your idle resources and earn rewards when they are used.
|
||||||
|
Configure it once. Your node takes over from there.
|
||||||
|
</P>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className="max-w-7xl mx-auto border border-t-0 border-b-0 border-gray-800 ">
|
||||||
|
{/* ✅ Match same side margins */}
|
||||||
|
<div className="max-w-5xl mx-auto px-6 ">
|
||||||
|
<WorldMap
|
||||||
|
dots={[
|
||||||
|
{ start: { lat: 64.2008, lng: -149.4937 }, end: { lat: 34.0522, lng: -118.2437 } }, // Alaska → LA
|
||||||
|
{ start: { lat: 64.2008, lng: -149.4937 }, end: { lat: -15.7975, lng: -47.8919 } }, // Alaska → Brasília
|
||||||
|
{ start: { lat: -15.7975, lng: -47.8919 }, end: { lat: 38.7223, lng: -9.1393 } }, // Brasília → Lisbon
|
||||||
|
{ start: { lat: 51.5074, lng: -0.1278 }, end: { lat: 28.6139, lng: 77.209 } }, // London → New Delhi
|
||||||
|
{ start: { lat: 28.6139, lng: 77.209 }, end: { lat: 43.1332, lng: 131.9113 } }, // New Delhi → Vladivostok
|
||||||
|
{ start: { lat: 28.6139, lng: 77.209 }, end: { lat: -1.2921, lng: 36.8219 } }, // New Delhi → Nairobi
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mx-auto max-w-7xl px-6 lg:px-8 border border-t-0 border-b-0 border-gray-800 pb-12">
|
||||||
|
|
||||||
|
<dl className="pt-6 grid grid-cols-1 gap-0.5 overflow-hidden rounded-md text-center sm:grid-cols-2 lg:grid-cols-4">
|
||||||
|
{stats.map((stat) => (
|
||||||
|
<div
|
||||||
|
key={stat.id}
|
||||||
|
className="flex flex-col bg-white/1 p-8"
|
||||||
|
>
|
||||||
|
<dt className="text-sm/6 font-semibold text-gray-300">
|
||||||
|
{stat.name}
|
||||||
|
</dt>
|
||||||
|
|
||||||
|
<dd className="order-first text-3xl font-semibold tracking-tight text-white">
|
||||||
|
{stat.value}
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<p className="mt-2 text-sm text-gray-400">
|
||||||
|
{stat.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ✅ Bottom horizontal line with spacing */}
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
|
|
||||||
import { useRef } from 'react'
|
import { useRef } from 'react'
|
||||||
import { AnimatedSection } from '../../components/AnimatedSection'
|
import { AnimatedSection } from '../../components/AnimatedSection'
|
||||||
import { StackSectionDark } from './StackSectionDark'
|
|
||||||
import { WorldMap } from './HomeGlobe'
|
|
||||||
import { CallToAction } from './CallToAction'
|
import { CallToAction } from './CallToAction'
|
||||||
import { HomeHosting } from './HomeHosting'
|
|
||||||
import { HomeAurora } from './HomeAurora'
|
|
||||||
import { HomeTab } from './HomeTab'
|
import { HomeTab } from './HomeTab'
|
||||||
import { HomeBenefits } from './HomeBenefits'
|
import { HomeMap } from './HomeMap'
|
||||||
|
import { HomeAudience } from './HomeAudience'
|
||||||
|
import { HomeAurora } from './HomeAurora'
|
||||||
|
import { HomeArchitecture } from './HomeArchitecture';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
@@ -22,23 +22,20 @@ export default function HomePage() {
|
|||||||
<HomeAurora onGetStartedClick={handleScrollToSlider} />
|
<HomeAurora onGetStartedClick={handleScrollToSlider} />
|
||||||
</AnimatedSection>
|
</AnimatedSection>
|
||||||
|
|
||||||
<AnimatedSection id="next-section">
|
|
||||||
<WorldMap />
|
|
||||||
</AnimatedSection>
|
|
||||||
|
|
||||||
<AnimatedSection>
|
<AnimatedSection>
|
||||||
<HomeHosting />
|
<HomeArchitecture/>
|
||||||
</AnimatedSection>
|
|
||||||
|
|
||||||
<AnimatedSection>
|
|
||||||
<StackSectionDark />
|
|
||||||
</AnimatedSection>
|
</AnimatedSection>
|
||||||
|
|
||||||
<AnimatedSection>
|
<AnimatedSection>
|
||||||
<HomeTab />
|
<HomeTab />
|
||||||
</AnimatedSection>
|
</AnimatedSection>
|
||||||
|
|
||||||
<AnimatedSection>
|
<AnimatedSection>
|
||||||
<HomeBenefits />
|
<HomeMap />
|
||||||
|
</AnimatedSection>
|
||||||
|
|
||||||
|
<AnimatedSection>
|
||||||
|
<HomeAudience />
|
||||||
</AnimatedSection>
|
</AnimatedSection>
|
||||||
|
|
||||||
<AnimatedSection>
|
<AnimatedSection>
|
||||||
|
|||||||
55
src/pages/home/HomeSpotlight.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button } from "@/components/Button";
|
||||||
|
import { Spotlight } from "@/components/ui/spotlight";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { H1, H4, H5 } from "@/components/Texts";
|
||||||
|
|
||||||
|
export function HomeSpotlight({
|
||||||
|
onGetStartedClick,
|
||||||
|
}: {
|
||||||
|
onGetStartedClick: () => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="px-4">
|
||||||
|
{/* Boxed container */}
|
||||||
|
<div className="relative mx-auto max-w-7xl border border-t-0 border-gray-100 bg-white overflow-hidden py-24 lg:py-32">
|
||||||
|
|
||||||
|
{/* ✅ Grid background */}
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"pointer-events-none absolute inset-0 select-none [background-size:40px_40px]",
|
||||||
|
"[background-image:linear-gradient(to_right,#f4f4f4_1px,transparent_1px),linear-gradient(to_bottom,#f4f4f4_1px,transparent_1px)]"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* ✅ Cyan Spotlight */}
|
||||||
|
<Spotlight className="-top-40 left-0 md:-top-20 md:left-60" />
|
||||||
|
|
||||||
|
{/* ✅ Foreground content */}
|
||||||
|
<div className="relative z-10 mx-auto w-full max-w-7xl p-4 md:pt-0">
|
||||||
|
<H1 className="bg-opacity-50 bg-gradient-to-b from-neutral-50 to-black bg-clip-text text-center font-bold text-transparent ">
|
||||||
|
MYCELIUM
|
||||||
|
</H1>
|
||||||
|
<H4 className="text-center mt-4">The Living Network of the Next Internet</H4>
|
||||||
|
|
||||||
|
<H5 className="mx-auto mt-6 max-w-4xl text-center font-normal text-neutral-500">
|
||||||
|
A new internet is emerging — private, distributed, and self-sovereign.
|
||||||
|
Mycelium is the living network that makes it possible.
|
||||||
|
A peer-to-peer foundation where people, data, and intelligence connect
|
||||||
|
directly — without intermediaries, without compromise.
|
||||||
|
</H5>
|
||||||
|
|
||||||
|
<div className="mt-8 flex justify-center gap-6">
|
||||||
|
<Button variant="solid" color="cyan" onClick={onGetStartedClick}>
|
||||||
|
Enter the Network
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" color="gray" onClick={onGetStartedClick}>
|
||||||
|
Explore Docs
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,123 +3,136 @@
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { CP, CT, Eyebrow, H3, P } from "@/components/Texts";
|
import { CP, CT, Eyebrow, H3, P } from "@/components/Texts";
|
||||||
|
|
||||||
const bentoCards = [
|
|
||||||
{
|
|
||||||
id: 'network',
|
|
||||||
title: 'Mycelium Network',
|
|
||||||
eyebrow: 'Network',
|
|
||||||
description: 'Encrypted peer-to-peer mesh networking across the globe.',
|
|
||||||
image: '/images/bento-network.png',
|
|
||||||
link: '/network',
|
|
||||||
colSpan: 'lg:col-span-3',
|
|
||||||
rowSpan: 'lg:row-span-1',
|
|
||||||
rounded: 'lg:rounded-tl-4xl max-lg:rounded-t-4xl',
|
|
||||||
innerRounded: 'lg:rounded-tl-[calc(2rem+1px)] max-lg:rounded-t-[calc(2rem+1px)]'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'agents',
|
|
||||||
title: 'Mycelium Agents',
|
|
||||||
eyebrow: 'Agents',
|
|
||||||
description: 'Private, programmable AI systems that run on your hardware.',
|
|
||||||
image: '/images/bento-agent.jpg',
|
|
||||||
link: '/agents',
|
|
||||||
colSpan: 'lg:col-span-3',
|
|
||||||
rowSpan: 'lg:row-span-1',
|
|
||||||
rounded: 'lg:rounded-tr-4xl',
|
|
||||||
innerRounded: 'lg:rounded-tr-[calc(2rem+1px)]'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'cloud',
|
|
||||||
title: 'Mycelium Cloud',
|
|
||||||
eyebrow: 'Cloud',
|
|
||||||
description: 'Deploy Kubernetes clusters on sovereign infrastructure.',
|
|
||||||
image: '/images/bento-cloud.jpg',
|
|
||||||
link: '/cloud',
|
|
||||||
colSpan: 'lg:col-span-6',
|
|
||||||
rowSpan: 'lg:row-span-1',
|
|
||||||
rounded: 'rounded-lg',
|
|
||||||
innerRounded: 'rounded-[calc(var(--radius-lg)+1px)]'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'compute',
|
|
||||||
title: 'Mycelium Compute',
|
|
||||||
eyebrow: 'Compute',
|
|
||||||
description: 'The Compute resource layers powering the stack.',
|
|
||||||
image: '/images/bento-compute.png',
|
|
||||||
link: '/compute',
|
|
||||||
colSpan: 'lg:col-span-2',
|
|
||||||
rowSpan: 'lg:row-span-1',
|
|
||||||
rounded: 'lg:rounded-bl-4xl',
|
|
||||||
innerRounded: 'lg:rounded-bl-[calc(2rem+1px)]'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'storage',
|
|
||||||
title: 'Mycelium Storage',
|
|
||||||
eyebrow: 'Storage',
|
|
||||||
description: 'The Storage resource layers powering the stack.',
|
|
||||||
image: '/images/bento-storage.png',
|
|
||||||
link: '/storage',
|
|
||||||
colSpan: 'lg:col-span-2',
|
|
||||||
rowSpan: 'lg:row-span-1',
|
|
||||||
rounded: 'rounded-lg',
|
|
||||||
innerRounded: 'rounded-[calc(var(--radius-lg)+1px)]'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'gpu',
|
|
||||||
title: 'Mycelium GPU',
|
|
||||||
eyebrow: 'GPU',
|
|
||||||
description: 'The GPU resource layers powering the stack.',
|
|
||||||
image: '/images/bento-gpu.jpg',
|
|
||||||
link: '/gpu',
|
|
||||||
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 HomeTab() {
|
export function HomeTab() {
|
||||||
return (
|
return (
|
||||||
<section className="w-full max-w-8xl mx-auto bg-transparent">
|
<section className="w-full max-w-8xl mx-auto bg-transparent">
|
||||||
|
|
||||||
{/* ✅ Top spacer + full-width line */}
|
{/* Top section separators */}
|
||||||
<div className="max-w-7xl mx-auto py-6 border-x border-gray-100 bg-white border-t-0 border-b-0" />
|
<div className="max-w-7xl mx-auto py-6 border-x border-gray-100 bg-white border-t-0 border-b-0" />
|
||||||
<div className="w-full border-t border-l border-r border-gray-100" />
|
<div className="w-full border-t border-l border-r border-gray-100" />
|
||||||
|
|
||||||
|
{/* Main content */}
|
||||||
{/* ✅ Section with vertical borders */}
|
|
||||||
<div className="mx-auto bg-white max-w-2xl px-6 lg:max-w-7xl lg:px-10 border border-t-0 border-b-0 border-gray-100">
|
<div className="mx-auto bg-white max-w-2xl px-6 lg:max-w-7xl lg:px-10 border border-t-0 border-b-0 border-gray-100">
|
||||||
<Eyebrow className="pt-12 ">Deploy faster</Eyebrow>
|
<Eyebrow className="pt-12">Components</Eyebrow>
|
||||||
<H3 className="mt-2">Mycelium Components</H3>
|
<H3 className="mt-2">Explore the Stack</H3>
|
||||||
<P className="mt-6 max-w-lg">
|
<P className="mt-6 max-w-4xl">
|
||||||
Each component can be used on its own or combined into a fully sovereign cloud.
|
Mycelium’s technology stack gives you everything you need to build and run applications
|
||||||
|
on a distributed network, from connectivity and compute to personal environments and AI.
|
||||||
</P>
|
</P>
|
||||||
|
|
||||||
<div className="mt-8 grid grid-cols-1 gap-6 sm:mt-10 lg:grid-cols-6 lg:grid-rows-3 pb-12">
|
{/* BENTO GRID LAYOUT — EXACT MATCH */}
|
||||||
{bentoCards.map((card) => (
|
<div className="mt-10 grid gap-4 sm:mt-16 lg:grid-cols-3 lg:grid-rows-2 pb-12">
|
||||||
<Link to={card.link} key={card.id} className={`relative ${card.colSpan} ${card.rowSpan} transition-transform duration-300 hover:scale-102 cursor-pointer`}>
|
|
||||||
<div className={`absolute inset-0 rounded-md bg-white ${card.rounded}`} />
|
{/* ------------------ LEFT TALL CARD ------------------ */}
|
||||||
<div className={`relative flex h-full flex-col overflow-hidden rounded-[calc(var(--radius-lg)+1px)] ${card.innerRounded}`}>
|
<Link to="/network" className="relative lg:row-span-2 cursor-pointer">
|
||||||
<img
|
<div className="absolute inset-px rounded-lg bg-white lg:rounded-l-4xl" />
|
||||||
alt={card.title}
|
<div className="relative flex h-full flex-col overflow-hidden rounded-[calc(var(--radius-lg)+1px)] lg:rounded-l-[calc(2rem+1px)]">
|
||||||
src={card.image}
|
|
||||||
className="h-50 object-cover object-center"
|
<div className="px-8 pt-8 pb-3 sm:px-10 sm:pt-10 sm:pb-0">
|
||||||
/>
|
<h3 className="text-sm/4 font-semibold text-cyan-500">LIVE</h3>
|
||||||
<div className="px-8 pt-4 pb-6">
|
<CT className="mt-2 text-lg font-medium tracking-tight text-gray-950">Mycelium Network</CT>
|
||||||
<h3 className="text-sm/4 font-semibold text-cyan-500">{card.eyebrow}</h3>
|
<CP className="mt-2 max-w-lg text-sm/6 text-gray-600">
|
||||||
<CT className="mt-2 text-lg lg:text-xl tracking-tight text-gray-950">{card.title}</CT>
|
Peer-to-peer connectivity between users, nodes, and devices. The foundation for secure
|
||||||
<CP className="mt-1 max-w-lg text-gray-600">
|
communication and collaboration.
|
||||||
{card.description}
|
|
||||||
</CP>
|
</CP>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Tall image container */}
|
||||||
|
<div className="@container relative min-h-120 w-full grow max-lg:mx-auto max-lg:max-w-sm">
|
||||||
|
<div className="absolute inset-x-10 top-10 bottom-0 overflow-hidden
|
||||||
|
rounded-t-[12cqw] border-x-[3cqw] border-t-[3cqw]
|
||||||
|
border-gray-700 bg-gray-900 shadow-2xl">
|
||||||
|
<img
|
||||||
|
src="/images/bento-network.jpg"
|
||||||
|
className="size-full object-cover object-top"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={`pointer-events-none absolute inset-0 rounded-lg shadow-sm outline outline-black/5 ${card.rounded}`} />
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ✅ Bottom full-width line + spacer */}
|
<div className="pointer-events-none absolute inset-px rounded-lg shadow-sm outline outline-black/5 lg:rounded-l-4xl" />
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* ------------------ MIDDLE TOP ------------------ */}
|
||||||
|
<Link to="/pods" className="relative cursor-pointer max-lg:row-start-1">
|
||||||
|
<div className="absolute inset-px rounded-lg bg-white max-lg:rounded-t-4xl" />
|
||||||
|
<div className="relative flex h-full flex-col overflow-hidden rounded-[calc(var(--radius-lg)+1px)] max-lg:rounded-t-[calc(2rem+1px)]">
|
||||||
|
|
||||||
|
<div className="px-8 pt-8 sm:px-10 sm:pt-10">
|
||||||
|
<h3 className="text-sm/4 font-semibold text-cyan-500">Coming Soon</h3>
|
||||||
|
<CT className="mt-2 text-lg font-medium tracking-tight text-gray-950">Mycelium Pods</CT>
|
||||||
|
<CP className="mt-2 max-w-lg text-sm/6 text-gray-600">
|
||||||
|
Personal digital environments that never reset and stay under your control. Build, deploy,
|
||||||
|
and connect on your own terms.
|
||||||
|
</CP>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-1 items-center justify-center px-8 max-lg:pt-10 sm:px-10 lg:pb-2">
|
||||||
|
<img
|
||||||
|
src="/images/bento-gpu.jpg"
|
||||||
|
className="w-full max-lg:max-w-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pointer-events-none absolute inset-px rounded-lg shadow-sm outline outline-black/5 max-lg:rounded-t-4xl" />
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* ------------------ MIDDLE BOTTOM ------------------ */}
|
||||||
|
<Link to="/agents" className="relative cursor-pointer max-lg:row-start-3 lg:col-start-2 lg:row-start-2">
|
||||||
|
<div className="absolute inset-px rounded-lg bg-white" />
|
||||||
|
<div className="relative flex h-full flex-col overflow-hidden rounded-[calc(var(--radius-lg)+1px)]">
|
||||||
|
|
||||||
|
<div className="px-8 pt-8 sm:px-10 sm:pt-10">
|
||||||
|
<h3 className="text-sm/4 font-semibold text-cyan-500">Q1 2026</h3>
|
||||||
|
<CT className="mt-2 text-lg font-medium tracking-tight text-gray-950">Mycelium Agents</CT>
|
||||||
|
<CP className="mt-2 max-w-lg text-sm/6 text-gray-600">
|
||||||
|
Peer-to-peer AI that serves you, not your data. Train, deploy, and own your AI end-to-end.
|
||||||
|
</CP>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="@container flex flex-1 items-center justify-center max-lg:py-6 lg:pb-2">
|
||||||
|
<img
|
||||||
|
src="/images/bento-agent.jpg"
|
||||||
|
className="h-[min(200px,40cqw)] object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pointer-events-none absolute inset-px rounded-lg shadow-sm outline outline-black/5" />
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* ------------------ RIGHT TALL ------------------ */}
|
||||||
|
<Link to="/cloud" className="relative lg:row-span-2 cursor-pointer">
|
||||||
|
<div className="absolute inset-px rounded-lg bg-white max-lg:rounded-b-4xl lg:rounded-r-4xl" />
|
||||||
|
<div className="relative flex h-full flex-col overflow-hidden rounded-[calc(var(--radius-lg)+1px)] max-lg:rounded-b-[calc(2rem+1px)] lg:rounded-r-[calc(2rem+1px)]">
|
||||||
|
|
||||||
|
<div className="px-8 pt-8 pb-3 sm:px-10 sm:pt-10 sm:pb-0">
|
||||||
|
<h3 className="text-sm/4 font-semibold text-cyan-500">Early Access</h3>
|
||||||
|
<CT className="mt-2 text-lg font-medium tracking-tight text-gray-950">Mycelium Cloud</CT>
|
||||||
|
<CP className="mt-2 max-w-lg text-sm/6 text-gray-600">
|
||||||
|
Decentralized compute, storage, and orchestration. A full cloud service without a central operator.
|
||||||
|
</CP>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative min-h-120 w-full grow">
|
||||||
|
<div className="absolute top-10 right-0 bottom-0 left-10 overflow-hidden
|
||||||
|
rounded-tl-xl bg-gray-900 shadow-2xl outline outline-white/10">
|
||||||
|
<img
|
||||||
|
src="/images/cloud/reserve.png"
|
||||||
|
className="size-full object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pointer-events-none absolute inset-px rounded-lg shadow-sm outline outline-black/5 max-lg:rounded-b-4xl lg:rounded-r-4xl" />
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bottom separators */}
|
||||||
<div className="w-full border-b border-gray-100" />
|
<div className="w-full border-b border-gray-100" />
|
||||||
<div className="max-w-7xl mx-auto py-6 border-x border-gray-100 border-t-0 border-b-0" />
|
<div className="max-w-7xl mx-auto py-6 border-x border-gray-100 border-t-0 border-b-0" />
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
72
src/pages/home/HomeWhy.tsx
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import {
|
||||||
|
GlobeAltIcon,
|
||||||
|
KeyIcon,
|
||||||
|
ServerStackIcon,
|
||||||
|
ShieldCheckIcon,
|
||||||
|
} from '@heroicons/react/24/outline'
|
||||||
|
import { CP, CT, Eyebrow, H3, P } from '@/components/Texts'
|
||||||
|
import { DarkCard } from '@/components/ui/cards'
|
||||||
|
|
||||||
|
const features = [
|
||||||
|
{
|
||||||
|
name: 'No central servers.',
|
||||||
|
description: 'Your devices form a distributed network, eliminating reliance on centralized data centers.',
|
||||||
|
icon: GlobeAltIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'No data extraction.',
|
||||||
|
description: 'You own your data. Run services and AI models on your own devices, ensuring privacy and control.',
|
||||||
|
icon: KeyIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'No single point of control.',
|
||||||
|
description: 'A decentralized architecture means no single entity can dictate or censor your online experience.',
|
||||||
|
icon: ServerStackIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Mesh VPN & Zero-Trust Networking',
|
||||||
|
description: 'Create a secure, private network between your devices, accessible from anywhere.',
|
||||||
|
icon: ShieldCheckIcon,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export function HomeWhy() {
|
||||||
|
return (
|
||||||
|
<div className="relative bg-[#121212]">
|
||||||
|
{/* ✅ Top horizontal line with spacing */}
|
||||||
|
<div className="max-w-7xl bg-transparent 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=" py-12 mx-auto bg-[#111111] max-w-md px-6 text-center sm:max-w-3xl lg:max-w-7xl lg:px-8 border border-t-0 border-b-0 border-gray-800">
|
||||||
|
<Eyebrow>Why It Matters</Eyebrow>
|
||||||
|
<H3 className="mt-2 text-gray-200">Why Mycelium?</H3>
|
||||||
|
<P className="mx-auto mt-5 max-w-prose text-gray-400">
|
||||||
|
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.
|
||||||
|
|
||||||
|
</P>
|
||||||
|
<div className="mt-16">
|
||||||
|
<div className="grid grid-cols-1 gap-6 lg:grid-cols-4">
|
||||||
|
{features.map((feature) => (
|
||||||
|
<div key={feature.name} className="relative">
|
||||||
|
<DarkCard className="flex h-full flex-col pt-8">
|
||||||
|
<span className="absolute -top-6 left-1/2 -translate-x-1/2 transform rounded-md bg-cyan-600 hover:bg-cyan-500 p-2 shadow-lg">
|
||||||
|
<feature.icon aria-hidden="true" className="size-8 text-white" />
|
||||||
|
</span>
|
||||||
|
<CT as="h3" className="mt-4 text-gray-200">
|
||||||
|
{feature.name}
|
||||||
|
</CT>
|
||||||
|
<CP color="secondary" className="mt-2 text-gray-400">
|
||||||
|
{feature.description}
|
||||||
|
</CP>
|
||||||
|
</DarkCard>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* ✅ Bottom horizontal line with spacing */}
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
49
src/pages/node/CallToAction.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Container } from "@/components/Container";
|
||||||
|
import { Button } from "@/components/Button";
|
||||||
|
|
||||||
|
export function CallToAction() {
|
||||||
|
return (
|
||||||
|
<section className="relative overflow-hidden bg-[#121212]">
|
||||||
|
<div className="max-w-7xl bg-[#111111] 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
|
||||||
|
id="get-started"
|
||||||
|
className="relative py-18 max-w-7xl mx-auto bg-[#111111] border border-t-0 border-b-0 border-gray-800"
|
||||||
|
>
|
||||||
|
<Container className="relative">
|
||||||
|
<div className="mx-auto max-w-3xl text-center">
|
||||||
|
<h2 className="text-3xl lg:text-4xl font-medium tracking-tight text-white sm:text-4xl">
|
||||||
|
Join Mycelium Grid
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p className="mt-6 text-lg text-gray-300">
|
||||||
|
Be part of a global network that powers private, distributed
|
||||||
|
infrastructure. Host a node, contribute resources, and earn rewards
|
||||||
|
while expanding the sovereign digital grid.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="mt-10 flex flex-wrap justify-center gap-x-10 gap-y-8">
|
||||||
|
<div className="flex flex-col items-center text-center max-w-xs">
|
||||||
|
<Button to="/host" variant="solid" color="cyan" className="mt-4">
|
||||||
|
Join Mycelium
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col items-center text-center max-w-xs">
|
||||||
|
<Button to="/docs" variant="outline" color="white" className="mt-4">
|
||||||
|
Explore docs
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full border-b border-gray-800" />
|
||||||
|
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-800 bg-transparent" />
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
115
src/pages/node/NodeBenefits.tsx
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { SectionHeader, P, Eyebrow, CT, CP } from "@/components/Texts";
|
||||||
|
import type { ComponentPropsWithoutRef } from "react";
|
||||||
|
|
||||||
|
type CircleIconProps = ComponentPropsWithoutRef<"svg">;
|
||||||
|
|
||||||
|
const CircleNumber1Icon = (props: CircleIconProps) => (
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor" {...props}>
|
||||||
|
<path d="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2zm.994 5.886c-.83-.777-1.755-.394-1.701.404l-.006.114v8l.007.117a1 1 0 001.986 0l.007-.117V9l-.006-.114z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
const CircleNumber2Icon = (props: CircleIconProps) => (
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor" {...props}>
|
||||||
|
<path d="M12 2a10 10 0 110 20 10 10 0 010-20zm1 5h-3a1 1 0 100 2h3v2h-2a2 2 0 00-2 2v2a2 2 0 002 2h3a1 1 0 100-2h-3v-2h2a2 2 0 002-2V9a2 2 0 00-2-2z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
const CircleNumber3Icon = (props: CircleIconProps) => (
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor" {...props}>
|
||||||
|
<path d="M12 2a10 10 0 110 20 10 10 0 010-20zm1 5h-2a2 2 0 00-2 2 1 1 0 102 0h2v2h-2c-1.39 0-2.103 1.67-1.11 2.588l.11.098h3v2h-2a1 1 0 100 2h2a2 2 0 002-2v-2a2 2 0 00-.25-.97l-.068-.115.068-.115A1.99 1.99 0 0015 11V9a2 2 0 00-2-2z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
const features = [
|
||||||
|
{
|
||||||
|
name: "Build Your Private Setup",
|
||||||
|
description:
|
||||||
|
"Run a node in your home or office and use it to host private workloads, models, and data. Experiment, deploy, or tinker locally, then offset your costs by sharing unused capacity with the network.",
|
||||||
|
icon: CircleNumber1Icon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Strengthen the Network",
|
||||||
|
description:
|
||||||
|
"Every node adds bandwidth, compute, and resilience to the Mycelium Grid. Together, hosts form the physical foundation for decentralized communication, storage, and AI.",
|
||||||
|
icon: CircleNumber2Icon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Earn Rewards",
|
||||||
|
description:
|
||||||
|
"Share storage, CPU, GPU, and bandwidth. When your resources are used, you earn network rewards based on real demand.",
|
||||||
|
icon: CircleNumber3Icon,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function NodeBenefits() {
|
||||||
|
return (
|
||||||
|
<section className="bg-[#121212] w-full max-w-8xl mx-auto">
|
||||||
|
{/* Top spacing + border */}
|
||||||
|
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-800" />
|
||||||
|
<div className="w-full border-t border-l border-r border-gray-800" />
|
||||||
|
|
||||||
|
<div className="relative px-6 lg:px-12 py-12 bg-[#111111] border border-t-0 border-b-0 border-gray-800 max-w-7xl mx-auto">
|
||||||
|
{/* Header */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true, amount: 0.3 }}
|
||||||
|
transition={{ duration: 0.6, ease: "easeOut", delay: 0.1 }}
|
||||||
|
className="mx-auto max-w-5xl text-center"
|
||||||
|
>
|
||||||
|
<Eyebrow color="accent">Host</Eyebrow>
|
||||||
|
<SectionHeader
|
||||||
|
className="text-3xl font-medium tracking-tight"
|
||||||
|
color="light"
|
||||||
|
>
|
||||||
|
Benefits of Hosting Nodes
|
||||||
|
</SectionHeader>
|
||||||
|
|
||||||
|
<P className="mt-6" color="light">
|
||||||
|
Hosting a node gives you private compute, contributes to the global
|
||||||
|
Mycelium infrastructure, and unlocks ways to earn from real network
|
||||||
|
usage — all while keeping sovereignty and control.
|
||||||
|
</P>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Features */}
|
||||||
|
<motion.ul
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
whileInView={{ opacity: 1 }}
|
||||||
|
viewport={{ once: true, amount: 0.2 }}
|
||||||
|
transition={{ duration: 0.5, delay: 0.2 }}
|
||||||
|
className="mx-auto mt-8 grid max-w-2xl grid-cols-1 gap-x-8 gap-y-8 text-base/7 sm:grid-cols-2 sm:gap-y-16 lg:mx-12 lg:mt-12 lg:max-w-7xl lg:grid-cols-3"
|
||||||
|
>
|
||||||
|
{features.map((feature, index) => (
|
||||||
|
<motion.li
|
||||||
|
key={feature.name}
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true, amount: 0.2 }}
|
||||||
|
transition={{
|
||||||
|
duration: 0.45,
|
||||||
|
delay: 0.3 + index * 0.15,
|
||||||
|
ease: "easeOut",
|
||||||
|
}}
|
||||||
|
className="rounded-2xl border border-gray-300 bg-white/5 p-8 transition-all duration-300 ease-in-out hover:scale-105 hover:border-cyan-500 hover:shadow-lg hover:shadow-cyan-500/20 backdrop-blur-md"
|
||||||
|
>
|
||||||
|
<feature.icon className="mb-4 h-8 w-8 text-white" />
|
||||||
|
<CT as="span" className="text-left font-semibold" color="light">
|
||||||
|
{feature.name}
|
||||||
|
</CT>
|
||||||
|
<CP className="mt-2 text-left text-sm" color="light">
|
||||||
|
{feature.description}
|
||||||
|
</CP>
|
||||||
|
</motion.li>
|
||||||
|
))}
|
||||||
|
</motion.ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full border-b border-gray-800 bg-[#121212]" />
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
40
src/pages/node/NodeHero.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { Button } from '@/components/Button'
|
||||||
|
import { Eyebrow, H3, P } from '@/components/Texts'
|
||||||
|
|
||||||
|
export function NodeHero() {
|
||||||
|
return (
|
||||||
|
<div className="">
|
||||||
|
{/* Boxed container */}
|
||||||
|
<div
|
||||||
|
className="relative mx-auto max-w-7xl border border-t-0 border-b-0 border-gray-100 bg-white overflow-hidden"
|
||||||
|
>
|
||||||
|
{/* Inner padding */}
|
||||||
|
<div className="px-6 py-16 lg:py-16">
|
||||||
|
<div className="max-w-3xl mx-auto text-center">
|
||||||
|
<Eyebrow>MYCELIUM NODES</Eyebrow>
|
||||||
|
<H3 as="h1" className="mt-4">
|
||||||
|
Host a Node. Power the Network.
|
||||||
|
</H3>
|
||||||
|
<P className="mt-6 text-gray-800">
|
||||||
|
The Mycelium Network runs on nodes hosted by people and organizations around the world. Each node adds capacity, resilience, and sovereignty, expanding a global network for private, distributed compute and AI.
|
||||||
|
</P>
|
||||||
|
|
||||||
|
<div className="mt-10 flex items-center justify-center gap-x-6">
|
||||||
|
<Button href="/host" variant="solid" color="cyan">
|
||||||
|
Host a Node
|
||||||
|
</Button>
|
||||||
|
<Button href="#" variant="outline">
|
||||||
|
Learn More
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* ✅ Bottom horizontal line with spacing */}
|
||||||
|
<div className="w-full border-b border-gray-100" />
|
||||||
|
<div className="max-w-7xl bg-transparent mx-auto py-6 border border-t-0 border-b-0 border-gray-100"></div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
22
src/pages/node/NodePage.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { NodeHero } from './NodeHero';
|
||||||
|
import { NodeBenefits } from './NodeBenefits';
|
||||||
|
import { NodeSteps } from './NodeSteps';
|
||||||
|
import { NodeProducts } from './NodeProducts';
|
||||||
|
import { NodeSpecs } from './NodeSpecs';
|
||||||
|
import { CallToAction } from './CallToAction';
|
||||||
|
|
||||||
|
const NodePage: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<NodeHero />
|
||||||
|
<NodeBenefits />
|
||||||
|
<NodeSteps />
|
||||||
|
<NodeProducts />
|
||||||
|
<NodeSpecs />
|
||||||
|
<CallToAction />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NodePage;
|
||||||
228
src/pages/node/NodeProducts.tsx
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { Eyebrow, SectionHeader, P } from "@/components/Texts";
|
||||||
|
import {
|
||||||
|
QuestionMarkCircleIcon,
|
||||||
|
ShieldCheckIcon,
|
||||||
|
CheckIcon,
|
||||||
|
} from "@heroicons/react/20/solid";
|
||||||
|
|
||||||
|
/* ------------------------------------------ */
|
||||||
|
/* PRODUCT DATA */
|
||||||
|
/* ------------------------------------------ */
|
||||||
|
|
||||||
|
const nodes = {
|
||||||
|
ainode: {
|
||||||
|
id: "ainode",
|
||||||
|
name: "Edge AI Node",
|
||||||
|
subtitle: "Based on Ryzen AI MAX+ 395 platform",
|
||||||
|
description:
|
||||||
|
"A compact AI-ready node designed for local inference, agent hosting, and sovereign edge compute. Equipped with a dedicated AI accelerator and optimized thermals.",
|
||||||
|
image: "/images/ainode.png",
|
||||||
|
features: [
|
||||||
|
"Run local AI and cloud workloads",
|
||||||
|
"Host Mycelium Slices and earn SPORE",
|
||||||
|
"Experiment with decentralized apps and LLM agents",
|
||||||
|
"Participate in the global Mycelium Network",
|
||||||
|
],
|
||||||
|
buyUrl:
|
||||||
|
"https://www.gmktec.com/products/amd-ryzen%E2%84%A2-ai-max-395-evo-x2-ai-mini-pc?variant=6f7af17b-b907-4a9d-9c7e-afecfb41ed98",
|
||||||
|
learnUrl:
|
||||||
|
"https://threefold.info/mycelium_economics/docs/recommended_nodes/edge_ai_node",
|
||||||
|
},
|
||||||
|
|
||||||
|
edgenode: {
|
||||||
|
id: "edgenode",
|
||||||
|
name: "Edge Compute Node",
|
||||||
|
subtitle: "Based on GMKtec NUCBox M6 Ultra (Ryzen 5 7640HS)",
|
||||||
|
description:
|
||||||
|
"High-performance edge compute for networking, local models, and multiple agents. Excellent balance of efficiency and compute density.",
|
||||||
|
image: "/images/edgenode.png",
|
||||||
|
features: [
|
||||||
|
"Efficient 6-core / 12-thread Zen 4 architecture at up to 5.0 GHz boost. (CPU Monkey)",
|
||||||
|
"Low power consumption (35 W class HS chip) conducive to continuous operation. (LaptopMedia)",
|
||||||
|
"Compact size → easier placement, less cooling overhead.",
|
||||||
|
"Full node owner flexibility: use it for private workloads, host slices, or a hybrid of both.",
|
||||||
|
],
|
||||||
|
buyUrl:
|
||||||
|
"https://www.gmktec.com/products/amd-ryzen-5-7640hs-mini-pc-nucbox-m6-ultra?spm=..product_ba613c14-a120-431b-af10-c5c5ca575d55.header_1.1&variant=35ad4a9a-3f31-490c-a2d1-ef9ea3773fe9",
|
||||||
|
learnUrl:
|
||||||
|
"https://threefold.info/mycelium_economics/docs/recommended_nodes/edge_node",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const configOptions = [
|
||||||
|
{
|
||||||
|
id: "ainode",
|
||||||
|
name: "EDGE AI Node",
|
||||||
|
description: "Optimized for local inference + AI acceleration",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "edgenode",
|
||||||
|
name: "EDGE Compute Node",
|
||||||
|
description: "Optimized for general-purpose compute + agent workloads",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/* ------------------------------------------ */
|
||||||
|
/* MAIN COMPONENT */
|
||||||
|
/* ------------------------------------------ */
|
||||||
|
|
||||||
|
export function NodeProducts() {
|
||||||
|
const [selectedNode, setSelectedNode] = useState(nodes.ainode);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="bg-[#121212] w-full max-w-8xl mx-auto">
|
||||||
|
|
||||||
|
{/* Top spacing + border */}
|
||||||
|
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-800" />
|
||||||
|
<div className="w-full border-t border-l border-r border-gray-800" />
|
||||||
|
|
||||||
|
{/* MAIN SECTION */}
|
||||||
|
<div className="relative px-6 lg:px-12 py-16 bg-[#111111] border border-t-0 border-b-0 border-gray-800 max-w-7xl mx-auto">
|
||||||
|
|
||||||
|
{/* --------------------------------------- */}
|
||||||
|
{/* SECTION TITLE + INTRO PARAGRAPH */}
|
||||||
|
{/* --------------------------------------- */}
|
||||||
|
<div className="text-center max-w-3xl mx-auto mb-16">
|
||||||
|
<Eyebrow color="accent">Hardware</Eyebrow>
|
||||||
|
<SectionHeader className="text-3xl font-medium" color="light">
|
||||||
|
Recommended Nodes
|
||||||
|
</SectionHeader>
|
||||||
|
<P className="mt-4 text-gray-300">
|
||||||
|
Below are some of the best-performing and most commonly recommended nodes
|
||||||
|
for hosting agents, Mycelium workloads, and contributing compute to the network.
|
||||||
|
</P>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 lg:gap-24 max-w-6xl mx-auto">
|
||||||
|
|
||||||
|
{/* ------------------------------ */}
|
||||||
|
{/* LEFT — TEXT AREA */}
|
||||||
|
{/* ------------------------------ */}
|
||||||
|
<motion.div
|
||||||
|
key={selectedNode.id}
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.4 }}
|
||||||
|
className="flex flex-col justify-center"
|
||||||
|
>
|
||||||
|
<h1 className="text-3xl font-semibold text-white">
|
||||||
|
{selectedNode.name}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p className="mt-1 text-gray-400 text-base">
|
||||||
|
{selectedNode.subtitle}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Description */}
|
||||||
|
<p className="mt-6 text-gray-300 text-base leading-relaxed">
|
||||||
|
{selectedNode.description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* FEATURES */}
|
||||||
|
<ul className="mt-6 space-y-2">
|
||||||
|
{selectedNode.features.map((f, i) => (
|
||||||
|
<li key={i} className="flex items-start">
|
||||||
|
<CheckIcon className="w-5 h-5 text-green-500 mt-0.5" />
|
||||||
|
<p className="ml-2 text-sm text-gray-300 leading-relaxed">
|
||||||
|
{f}
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{/* CONFIG SELECTOR */}
|
||||||
|
<fieldset className="mt-10">
|
||||||
|
<legend className="text-sm font-medium text-gray-200">
|
||||||
|
Configuration
|
||||||
|
</legend>
|
||||||
|
|
||||||
|
<div className="mt-3 grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
{configOptions.map((opt) => (
|
||||||
|
<label
|
||||||
|
key={opt.id}
|
||||||
|
className={`group relative flex flex-col border rounded-xl p-4 cursor-pointer transition
|
||||||
|
${
|
||||||
|
selectedNode.id === opt.id
|
||||||
|
? "border-cyan-500 bg-white/5"
|
||||||
|
: "border-gray-700 hover:border-gray-500"
|
||||||
|
}`}
|
||||||
|
onClick={() => setSelectedNode(nodes[opt.id as keyof typeof nodes])}
|
||||||
|
>
|
||||||
|
<span className="text-white font-medium">{opt.name}</span>
|
||||||
|
<span className="mt-1 text-sm text-gray-400">{opt.description}</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<div className="mt-4">
|
||||||
|
<a className="inline-flex text-sm text-gray-500 hover:text-gray-300 transition">
|
||||||
|
What config should I choose?
|
||||||
|
<QuestionMarkCircleIcon className="ml-1 w-5 h-5 text-gray-500" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ------------------------ */}
|
||||||
|
{/* BUTTONS AREA */}
|
||||||
|
{/* ------------------------ */}
|
||||||
|
<div className="mt-10 flex flex-col sm:flex-row gap-4">
|
||||||
|
|
||||||
|
{/* Outline Button */}
|
||||||
|
<a
|
||||||
|
href={selectedNode.learnUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="flex-1 sm:flex-none text-center border border-gray-700 hover:border-gray-500
|
||||||
|
text-gray-300 hover:text-white px-8 py-3 rounded-lg transition"
|
||||||
|
>
|
||||||
|
Learn More
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{/* Solid Cyan Button */}
|
||||||
|
<a
|
||||||
|
href={selectedNode.buyUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="flex-1 sm:flex-none text-center bg-cyan-600 hover:bg-cyan-700
|
||||||
|
text-white px-8 py-3 rounded-lg font-medium transition"
|
||||||
|
>
|
||||||
|
Purchase Node
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Guarantee */}
|
||||||
|
<div className="mt-6 flex items-center text-gray-400 text-sm">
|
||||||
|
<ShieldCheckIcon className="w-6 h-6 text-gray-500 mr-2" />
|
||||||
|
Lifetime Guarantee
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* ------------------------------ */}
|
||||||
|
{/* RIGHT — IMAGE */}
|
||||||
|
{/* ------------------------------ */}
|
||||||
|
<motion.div
|
||||||
|
key={selectedNode.image}
|
||||||
|
initial={{ opacity: 0, scale: 0.92 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
transition={{ duration: 0.35 }}
|
||||||
|
className="flex justify-center"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={selectedNode.image}
|
||||||
|
alt={selectedNode.name}
|
||||||
|
className="w-full max-w-md rounded-2xl border border-gray-800 object-cover"
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bottom border */}
|
||||||
|
<div className="w-full border-b border-gray-800 bg-[#121212]" />
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
97
src/pages/node/NodeSpecs.tsx
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Eyebrow, H3, CT, CP, P } from "@/components/Texts";
|
||||||
|
|
||||||
|
const nodeSpecs = [
|
||||||
|
{
|
||||||
|
id: "autonomous",
|
||||||
|
eyebrow: "Self-Running",
|
||||||
|
title: "Autonomous Operation",
|
||||||
|
description: "Runs autonomously with no central control.",
|
||||||
|
colSpan: "lg:col-span-3",
|
||||||
|
rounded: "max-lg:rounded-t-4xl lg:rounded-tl-4xl",
|
||||||
|
innerRounded: "max-lg:rounded-t-[calc(2rem+1px)] lg:rounded-tl-[calc(2rem+1px)]",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "encrypted",
|
||||||
|
eyebrow: "Security",
|
||||||
|
title: "Encrypted by Default",
|
||||||
|
description: "Fully encrypted and identity-based.",
|
||||||
|
colSpan: "lg:col-span-3",
|
||||||
|
rounded: "lg:rounded-tr-4xl",
|
||||||
|
innerRounded: "lg:rounded-tr-[calc(2rem+1px)]",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "efficient",
|
||||||
|
eyebrow: "Performance",
|
||||||
|
title: "Energy Efficient",
|
||||||
|
description: "Energy-efficient and quiet, designed for 24/7 uptime.",
|
||||||
|
colSpan: "lg:col-span-2",
|
||||||
|
rounded: "lg:rounded-bl-4xl",
|
||||||
|
innerRounded: "lg:rounded-bl-[calc(2rem+1px)]",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "uptime",
|
||||||
|
eyebrow: "Reliability",
|
||||||
|
title: "Measured Uptime",
|
||||||
|
description: "Automatically measures uptime and contribution.",
|
||||||
|
colSpan: "lg:col-span-2",
|
||||||
|
rounded: "",
|
||||||
|
innerRounded: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "fullstack",
|
||||||
|
eyebrow: "Compatibility",
|
||||||
|
title: "Full Mycelium Stack Support",
|
||||||
|
description: "Supports Mycelium Network, Cloud, Pods, and Agents.",
|
||||||
|
colSpan: "lg:col-span-2",
|
||||||
|
rounded: "max-lg:rounded-b-4xl lg:rounded-br-4xl",
|
||||||
|
innerRounded: "max-lg:rounded-b-[calc(2rem+1px)] lg:rounded-br-[calc(2rem+1px)]",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function NodeSpecs() {
|
||||||
|
return (
|
||||||
|
<div className="bg-white py-24 sm:py-32">
|
||||||
|
<div className="mx-auto max-w-2xl px-6 lg:max-w-7xl lg:px-8">
|
||||||
|
<Eyebrow>Node Specifications</Eyebrow>
|
||||||
|
<H3 className="mt-2">Built for Reliability and Control</H3>
|
||||||
|
<P className="mt-6 max-w-xl">
|
||||||
|
Each node strengthens the network and helps build a more open, sovereign and
|
||||||
|
distributed internet.
|
||||||
|
</P>
|
||||||
|
|
||||||
|
<div className="mt-10 grid grid-cols-1 gap-4 sm:mt-16 lg:grid-cols-6 lg:grid-rows-2">
|
||||||
|
{nodeSpecs.map((item) => (
|
||||||
|
<div key={item.id} className={`relative ${item.colSpan}`}>
|
||||||
|
{/* BG LAYER */}
|
||||||
|
<div
|
||||||
|
className={`absolute inset-0 rounded-lg bg-white ${item.rounded}`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* CONTENT LAYER */}
|
||||||
|
<div
|
||||||
|
className={`relative flex h-full flex-col overflow-hidden rounded-[calc(var(--radius-lg)+1px)] ${item.innerRounded}`}
|
||||||
|
>
|
||||||
|
<div className="p-10 pt-6">
|
||||||
|
<h3 className="text-sm/4 font-semibold text-cyan-600">{item.eyebrow}</h3>
|
||||||
|
<CT className="mt-2 text-lg font-medium tracking-tight text-gray-950">
|
||||||
|
{item.title}
|
||||||
|
</CT>
|
||||||
|
<CP className="mt-2 max-w-lg text-sm/6 text-gray-600">
|
||||||
|
{item.description}
|
||||||
|
</CP>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* OUTLINE OVERLAY */}
|
||||||
|
<div
|
||||||
|
className={`pointer-events-none absolute inset-0 rounded-lg shadow-sm outline outline-black/5 ${item.rounded}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
87
src/pages/node/NodeSteps.tsx
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Eyebrow, H3, P, CT, CP } from "@/components/Texts";
|
||||||
|
import {
|
||||||
|
CloudArrowDownIcon,
|
||||||
|
CpuChipIcon,
|
||||||
|
WalletIcon,
|
||||||
|
BoltIcon,
|
||||||
|
} from "@heroicons/react/24/solid";
|
||||||
|
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
name: "Buy a Node",
|
||||||
|
description:
|
||||||
|
"Choose your hardware setup. You can use your own device or one of the recommended models below.",
|
||||||
|
icon: CpuChipIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Download Mycelium OS",
|
||||||
|
description:
|
||||||
|
"Install the Mycelium OS to prepare your node for network access.",
|
||||||
|
icon: CloudArrowDownIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Link Your Wallet",
|
||||||
|
description:
|
||||||
|
"Connect your private key from your Mycelium account to enable rewards and authentication.",
|
||||||
|
icon: WalletIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Plug In and Go",
|
||||||
|
description:
|
||||||
|
"Connect to power and to the internet. Your node will join automatically and start contributing.",
|
||||||
|
icon: BoltIcon,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function NodeSteps() {
|
||||||
|
return (
|
||||||
|
<section className="w-full max-w-8xl mx-auto bg-transparent">
|
||||||
|
{/* Top line */}
|
||||||
|
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-100" />
|
||||||
|
<div className="w-full border-t border-l border-r border-gray-100" />
|
||||||
|
|
||||||
|
<div className="max-w-7xl bg-white mx-auto py-16 border border-t-0 border-b-0 border-gray-100">
|
||||||
|
<div className="mx-auto max-w-4xl sm:text-center">
|
||||||
|
<Eyebrow className="text-cyan-500">HOW IT WORKS</Eyebrow>
|
||||||
|
|
||||||
|
<H3 className="text-3xl lg:text-4xl font-medium tracking-tight text-gray-900">
|
||||||
|
Host a Node
|
||||||
|
</H3>
|
||||||
|
|
||||||
|
<P className="mt-6 text-lg text-gray-600">
|
||||||
|
Hosting a node takes minutes, and it runs automatically once online.
|
||||||
|
</P>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 🔹 Horizontal Stepper */}
|
||||||
|
<div className="relative mt-16 px-6 flex flex-col gap-8 lg:flex-row lg:items-start lg:justify-between">
|
||||||
|
{steps.map((step, i) => (
|
||||||
|
<div key={step.name} className="relative flex-1 px-4">
|
||||||
|
{/* 🔹 Horizontal connector line (desktop only) */}
|
||||||
|
{i < steps.length - 1 && (
|
||||||
|
<div className="hidden lg:block absolute top-5 left-[55%] w-full h-[4px] bg-gray-400 -z-10" />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 🔹 Step header with icon */}
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="flex items-center justify-center w-8 h-8 rounded-full bg-cyan-100 text-cyan-600">
|
||||||
|
<step.icon className="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
<CT>{step.name}</CT>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 🔹 Description */}
|
||||||
|
<CP className="mt-3">{step.description}</CP>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* bottom line */}
|
||||||
|
{/* ✅ Bottom horizontal line with spacing */}
|
||||||
|
<div className="w-full border-b border-gray-100" />
|
||||||
|
<div className="max-w-7xl bg-transparent mx-auto py-6 border border-t-0 border-b-0 border-gray-100"></div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
51
src/pages/pods/CallToAction.tsx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button } from "@/components/Button";
|
||||||
|
import { Container } from "@/components/Container";
|
||||||
|
|
||||||
|
export function CallToAction() {
|
||||||
|
return (
|
||||||
|
<section className="relative overflow-hidden bg-[#121212] ">
|
||||||
|
{/* ✅ Top horizontal line with spacing */}
|
||||||
|
<div className="max-w-7xl bg-[#111111] 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" />
|
||||||
|
|
||||||
|
{/* ✅ Main boxed area */}
|
||||||
|
<div
|
||||||
|
id="get-started"
|
||||||
|
className="relative py-18 max-w-7xl mx-auto bg-[#111111] border border-t-0 border-b-0 border-gray-800"
|
||||||
|
>
|
||||||
|
<Container className="relative">
|
||||||
|
<div className="mx-auto max-w-3xl text-center">
|
||||||
|
<h2 className="text-3xl lg:text-4xl font-medium tracking-tight text-white sm:text-4xl">
|
||||||
|
Join the First Wave
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p className="mt-6 text-lg text-gray-300">
|
||||||
|
Pods are launching soon. Be among the first to claim your private space in the new internet.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* ✅ Two cards, stacked center with spacing */}
|
||||||
|
<div className="mt-10 flex flex-wrap justify-center gap-x-10 gap-y-8">
|
||||||
|
<div className="flex flex-col items-center text-center max-w-xs">
|
||||||
|
<Button to="#" variant="solid" color="cyan" className="mt-4">
|
||||||
|
Join the Waitlist
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col items-center text-center max-w-xs">
|
||||||
|
<Button to="#" variant="outline" color="white" className="mt-4">
|
||||||
|
Deploy Pods in Your Community
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ✅ Bottom horizontal line with spacing */}
|
||||||
|
<div className="w-full border-b border-gray-800" />
|
||||||
|
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-800 bg-transparent" />
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { H3, Eyebrow } from "@/components/Texts"
|
import { H3, Eyebrow, P } from "@/components/Texts"
|
||||||
|
|
||||||
export default function Homepod() {
|
export default function Homepod() {
|
||||||
return (
|
return (
|
||||||
@@ -17,11 +17,11 @@ export default function Homepod() {
|
|||||||
<H3 className="mt-4">
|
<H3 className="mt-4">
|
||||||
Your Private Space in the New Internet
|
Your Private Space in the New Internet
|
||||||
</H3>
|
</H3>
|
||||||
<p className="mt-6 text-lg">
|
<P className="mt-6 text-gray-800">
|
||||||
Imagine having your own corner of the internet — private, secure, and always online.
|
Imagine having your own corner of the internet — private, secure, and always online.
|
||||||
A Pod is your personal digital space on the Mycelium Network.
|
A Pod is your personal digital space on the Mycelium Network.
|
||||||
It’s where your conversations, files, and digital tools live — owned by you, connected to others directly.
|
It’s where your conversations, files, and digital tools live — owned by you, connected to others directly.
|
||||||
</p>
|
</P>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
151
src/pages/pods/PodCapabilities.tsx
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useRef } from "react";
|
||||||
|
import { Eyebrow, CP, CT, H4 } from "@/components/Texts";
|
||||||
|
import { IoArrowBackOutline, IoArrowForwardOutline } from "react-icons/io5";
|
||||||
|
|
||||||
|
import {
|
||||||
|
HomeModernIcon,
|
||||||
|
CpuChipIcon,
|
||||||
|
ServerStackIcon,
|
||||||
|
ShieldCheckIcon,
|
||||||
|
} from "@heroicons/react/24/solid";
|
||||||
|
|
||||||
|
const capabilities = [
|
||||||
|
{
|
||||||
|
isIntro: true,
|
||||||
|
eyebrow: "CAPABILITIES",
|
||||||
|
title: "What is a Pod?",
|
||||||
|
description: "",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: "Your private digital home on the decentralized internet",
|
||||||
|
description:
|
||||||
|
"Your Pod is a private digital home where apps, data, and identity live independently of Big Tech and central servers.",
|
||||||
|
icon: (
|
||||||
|
<div className="flex items-start justify-start">
|
||||||
|
<HomeModernIcon className="h-12 w-12 text-cyan-400" />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: "An always-on space you fully control",
|
||||||
|
description:
|
||||||
|
"A dedicated, always-on environment you fully command — your own sovereign slice of the network that never goes offline.",
|
||||||
|
icon: (
|
||||||
|
<div className="flex items-start justify-start">
|
||||||
|
<CpuChipIcon className="h-12 w-12 text-cyan-400" />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: "Runs communication, storage, and collaboration tools",
|
||||||
|
description:
|
||||||
|
"Runs your communication, storage, and collaboration tools in a secure local environment without reliance on outside platforms.",
|
||||||
|
icon: (
|
||||||
|
<div className="flex items-start justify-start">
|
||||||
|
<ServerStackIcon className="h-12 w-12 text-cyan-400" />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: "Fully encrypted, federated peer-to-peer network",
|
||||||
|
description:
|
||||||
|
"Encrypted, federated peer-to-peer networking that links your Pod directly with trusted devices without intermediaries.",
|
||||||
|
icon: (
|
||||||
|
<div className="flex items-start justify-start">
|
||||||
|
<ShieldCheckIcon className="h-12 w-12 text-cyan-400" />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function PodCapabilities() {
|
||||||
|
const sliderRef = useRef<HTMLUListElement>(null);
|
||||||
|
|
||||||
|
const scrollLeft = () =>
|
||||||
|
sliderRef.current?.scrollBy({ left: -400, behavior: "smooth" });
|
||||||
|
|
||||||
|
const scrollRight = () =>
|
||||||
|
sliderRef.current?.scrollBy({ left: 400, behavior: "smooth" });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="bg-[#121212] w-full max-w-8xl mx-auto">
|
||||||
|
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-800" />
|
||||||
|
<div className="w-full border-t border-l border-r border-gray-800" />
|
||||||
|
|
||||||
|
<div className="relative mx-auto max-w-7xl border border-t-0 border-b-0 border-gray-800 bg-[#111111] overflow-hidden">
|
||||||
|
{/* Horizontal Slider */}
|
||||||
|
<ul
|
||||||
|
ref={sliderRef}
|
||||||
|
className="flex overflow-x-auto snap-x snap-mandatory scroll-smooth no-scrollbar"
|
||||||
|
>
|
||||||
|
{capabilities.map((item, idx) => (
|
||||||
|
<li
|
||||||
|
key={idx}
|
||||||
|
className={`snap-start shrink-0 w-[85%] sm:w-[50%] lg:w-[33%] border border-gray-800 p-10 relative ${
|
||||||
|
item.isIntro ? "" : "bg-[#111]/60"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{/* INTRO CARD */}
|
||||||
|
{item.isIntro ? (
|
||||||
|
<div className="flex flex-col justify-between h-full">
|
||||||
|
<div>
|
||||||
|
<Eyebrow>{item.eyebrow}</Eyebrow>
|
||||||
|
<H4 className="text-white mt-0 max-w-lg">{item.title}</H4>
|
||||||
|
<p className="text-gray-400 lg:text-lg text-sm leading-relaxed">
|
||||||
|
{item.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Arrow controls */}
|
||||||
|
<div className="flex items-center gap-x-4 mt-2">
|
||||||
|
<button
|
||||||
|
onClick={scrollLeft}
|
||||||
|
className="h-8 w-8 flex items-center justify-center border border-gray-800 rounded-md hover:border-cyan-500 transition-colors"
|
||||||
|
>
|
||||||
|
<IoArrowBackOutline className="text-gray-300" size={16} />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={scrollRight}
|
||||||
|
className="h-8 w-8 flex items-center justify-center border border-gray-800 rounded-md hover:border-cyan-500 transition-colors"
|
||||||
|
>
|
||||||
|
<IoArrowForwardOutline
|
||||||
|
className="text-gray-300"
|
||||||
|
size={16}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{/* LEFT-ALIGNED ICON */}
|
||||||
|
{item.icon}
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
{/* LEFT-ALIGNED TEXT */}
|
||||||
|
<CT className="font-semibold leading-tight text-white text-left">
|
||||||
|
{item.title}
|
||||||
|
</CT>
|
||||||
|
|
||||||
|
<CP className="mt-2 text-gray-400 text-left">
|
||||||
|
{item.description}
|
||||||
|
</CP>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full border-b border-gray-800" />
|
||||||
|
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-800" />
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
74
src/pages/pods/PodsBenefits.tsx
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import { Small } from '@/components/Texts'
|
||||||
|
|
||||||
|
const highlights = [
|
||||||
|
{
|
||||||
|
label: 'Identity',
|
||||||
|
title: 'Identity built on cryptographic keys',
|
||||||
|
description:
|
||||||
|
'combined with familiar logins for everyday access',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Network',
|
||||||
|
title: 'Runs on the Mycelium Network',
|
||||||
|
description:
|
||||||
|
'not on centralized cloud servers',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Privacy',
|
||||||
|
title: 'No data collection or tracking',
|
||||||
|
description:
|
||||||
|
'Your Pod decides what to share',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Resilience',
|
||||||
|
title: 'No single point of failure',
|
||||||
|
description:
|
||||||
|
'If one node goes offline, others keep you connected',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Scalability',
|
||||||
|
title: 'Expandable architecture',
|
||||||
|
description:
|
||||||
|
'Each Pod can host your personal AI Agent and scale with your needs',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export function PodsBenefits() {
|
||||||
|
return (
|
||||||
|
<section className="bg-[#121212] w-full max-w-8xl mx-auto">
|
||||||
|
{/* Top line */}
|
||||||
|
<div className="max-w-7xl py-6 mx-auto border border-t-0 border-b-0 border-gray-800" />
|
||||||
|
<div className="w-full border-t border-l border-r border-gray-800" />
|
||||||
|
|
||||||
|
<div className="bg-[#121212] w-full max-w-7xl mx-auto border border-t-0 border-b-0 border-gray-800">
|
||||||
|
<div className="grid lg:grid-cols-5">
|
||||||
|
{highlights.map((item) => (
|
||||||
|
<div
|
||||||
|
key={item.title}
|
||||||
|
className="group relative overflow-hidden border border-white/10 bg-white/4 p-8 backdrop-blur-sm transition hover:border-cyan-300/50 hover:bg-white/8"
|
||||||
|
>
|
||||||
|
<div className="absolute inset-0 bg-linear-to-br from-cyan-500/0 via-white/5 to-cyan-300/20 opacity-0 transition group-hover:opacity-100" />
|
||||||
|
|
||||||
|
<div className="relative">
|
||||||
|
<Small className="text-xs uppercase tracking-[0.16em] text-cyan-200">
|
||||||
|
{item.label}
|
||||||
|
</Small>
|
||||||
|
|
||||||
|
<h3 className="mt-4 text-lg font-semibold text-white">
|
||||||
|
{item.title}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p className="mt-4 text-sm leading-relaxed text-gray-300">
|
||||||
|
{item.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full border-b border-gray-800 bg-[#121212]" />
|
||||||
|
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-800" />
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
94
src/pages/pods/PodsComing.tsx
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import {
|
||||||
|
ChatBubbleLeftRightIcon,
|
||||||
|
CalendarDaysIcon,
|
||||||
|
UsersIcon,
|
||||||
|
CpuChipIcon,
|
||||||
|
} from '@heroicons/react/24/solid'
|
||||||
|
import { Eyebrow, H4, P } from '@/components/Texts'
|
||||||
|
|
||||||
|
export function PodsComing() {
|
||||||
|
return (
|
||||||
|
<section className="w-full max-w-8xl mx-auto bg-white">
|
||||||
|
|
||||||
|
{/* Top horizontal line */}
|
||||||
|
<div className="max-w-7xl bg-transparent mx-auto py-6 border border-t-0 border-b-0 border-gray-200" />
|
||||||
|
<div className="w-full border-t border-l border-r border-gray-200" />
|
||||||
|
|
||||||
|
{/* Main content */}
|
||||||
|
<div className="mx-auto max-w-7xl px-6 bg-white lg:px-8 grid grid-cols-1 lg:grid-cols-2 gap-20 py-12 border border-t-0 border-b-0 border-gray-200">
|
||||||
|
|
||||||
|
{/* LEFT SIDE – Title + Intro */}
|
||||||
|
<div className="max-w-xl">
|
||||||
|
<Eyebrow className="text-cyan-600">COMING SOON</Eyebrow>
|
||||||
|
|
||||||
|
<H4 className="mt-6 text-gray-900">
|
||||||
|
Be Among The First
|
||||||
|
</H4>
|
||||||
|
|
||||||
|
<P className="mt-6 text-gray-700">
|
||||||
|
The first Pods are launching soon.
|
||||||
|
10,000 early Pods will be available for early adopters. Next, Pods will support peer-to-peer AI Agents that live inside your environment.
|
||||||
|
Your own AI, powered by your data without any data leaks.
|
||||||
|
</P>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* RIGHT SIDE — 4 upcoming features */}
|
||||||
|
<div className="space-y-10">
|
||||||
|
|
||||||
|
{/* 1 — Private Chat & Calls */}
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold text-gray-900 flex items-center">
|
||||||
|
<ChatBubbleLeftRightIcon className="h-6 w-6 text-cyan-600 mr-3" />
|
||||||
|
Private Chat & Calls
|
||||||
|
</h3>
|
||||||
|
<p className="mt-2 text-gray-700 max-w-2xl">
|
||||||
|
Peer-to-peer conversations and calling routed directly between Pods.
|
||||||
|
</p>
|
||||||
|
<div className="mt-8 h-px w-full bg-cyan-200" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 2 — Calendar & File Sync */}
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold text-gray-900 flex items-center">
|
||||||
|
<CalendarDaysIcon className="h-6 w-6 text-cyan-600 mr-3" />
|
||||||
|
Calendar & File Sync
|
||||||
|
</h3>
|
||||||
|
<p className="mt-2 text-gray-700 max-w-2xl">
|
||||||
|
Your schedules, documents, and files — synced across your Pods with no central cloud.
|
||||||
|
</p>
|
||||||
|
<div className="mt-8 h-px w-full bg-cyan-200" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 3 — Secure Team Spaces */}
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold text-gray-900 flex items-center">
|
||||||
|
<UsersIcon className="h-6 w-6 text-cyan-600 mr-3" />
|
||||||
|
Secure Team Spaces
|
||||||
|
</h3>
|
||||||
|
<p className="mt-2 text-gray-700 max-w-2xl">
|
||||||
|
Create shared Pods for teams, communities, or groups — fully encrypted, fully sovereign.
|
||||||
|
</p>
|
||||||
|
<div className="mt-8 h-px w-full bg-cyan-200" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 4 — Early AI Agent Integration */}
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold text-gray-900 flex items-center">
|
||||||
|
<CpuChipIcon className="h-6 w-6 text-cyan-600 mr-3" />
|
||||||
|
Early AI Agent Integration
|
||||||
|
<span className="ml-2 text-xs text-gray-400">🕒</span>
|
||||||
|
</h3>
|
||||||
|
<p className="mt-2 text-gray-700 max-w-2xl">
|
||||||
|
Host your personal AI agent inside your Pod — private, local-first, and fully under your control.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bottom horizontal line */}
|
||||||
|
<div className="w-full border-b border-gray-200" />
|
||||||
|
<div className="max-w-7xl bg-transparent mx-auto py-6 border border-t-0 border-b-0 border-gray-200" />
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
121
src/pages/pods/PodsDesign.tsx
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import {
|
||||||
|
Disclosure,
|
||||||
|
DisclosureButton,
|
||||||
|
DisclosurePanel,
|
||||||
|
} from '@headlessui/react'
|
||||||
|
import { MinusIcon, PlusIcon } from '@heroicons/react/24/outline'
|
||||||
|
|
||||||
|
import { Eyebrow, H3, H4 } from "@/components/Texts"
|
||||||
|
|
||||||
|
|
||||||
|
const product = {
|
||||||
|
subtitle: "Federation",
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
`,
|
||||||
|
|
||||||
|
details: [
|
||||||
|
{
|
||||||
|
name: "Your Data Lives on Your Pods",
|
||||||
|
description:
|
||||||
|
"Full control of where your data is stored and how it’s shared.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Direct Pod-to-Pod Networking",
|
||||||
|
description:
|
||||||
|
"Direct connections between Pods for faster, private communication.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "No One Can Spy or Shut You Down",
|
||||||
|
description:
|
||||||
|
"Independence from corporate servers or cloud outages.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Resilient Even if Nodes Disconnect",
|
||||||
|
description:
|
||||||
|
"Continuous availability even if one node disconnects.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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-200" />
|
||||||
|
<div className="w-full border-t border-l border-r border-gray-200" />
|
||||||
|
|
||||||
|
<main className="mx-auto max-w-7xl px-6 lg:px-12 py-12 border border-t-0 border-b-0 border-gray-200">
|
||||||
|
<div className="mx-auto max-w-2xl lg:max-w-none">
|
||||||
|
|
||||||
|
<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/cloudhosting.webp"
|
||||||
|
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">
|
||||||
|
{product.subtitle}
|
||||||
|
</Eyebrow>
|
||||||
|
|
||||||
|
<H4 className="text-gray-900">
|
||||||
|
{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>
|
||||||
|
</DisclosurePanel>
|
||||||
|
</Disclosure>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{/* BOTTOM LINE */}
|
||||||
|
<div className="w-full border-b border-gray-200" />
|
||||||
|
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-200" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
141
src/pages/pods/PodsFeatures.tsx
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Container } from "@/components/Container";
|
||||||
|
import { Small } from "@/components/Texts";
|
||||||
|
|
||||||
|
const useCases = [
|
||||||
|
{
|
||||||
|
title: "Private Messaging & Calling",
|
||||||
|
description:
|
||||||
|
"Communicate directly Pod-to-Pod with no centralized routing.",
|
||||||
|
bullets: [
|
||||||
|
"End-to-end encrypted messaging and voice calling.",
|
||||||
|
"No intermediaries — connections flow directly between Pods.",
|
||||||
|
"Zero metadata profiling, tracking, or data resale.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Safe File Storage & Sharing",
|
||||||
|
description:
|
||||||
|
"Store and share files securely without exposing data to third parties.",
|
||||||
|
bullets: [
|
||||||
|
"Files remain private with no platform-level scanning or analysis.",
|
||||||
|
"Share documents and media directly with trusted contacts.",
|
||||||
|
"Full ownership of your content — no cloud vendor dependencies.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Personal Calendar & Meetings",
|
||||||
|
description:
|
||||||
|
"Manage your schedule within your own sovereign digital space.",
|
||||||
|
bullets: [
|
||||||
|
"Keep events, appointments, and reminders fully private.",
|
||||||
|
"Coordinate calls and meetings entirely inside your Pod.",
|
||||||
|
"No syncing with external servers or corporate platforms.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Private Communities & Teams",
|
||||||
|
description:
|
||||||
|
"Create groups or collaborative spaces without platform ownership.",
|
||||||
|
bullets: [
|
||||||
|
"Form teams, friendship circles, and micro-communities.",
|
||||||
|
"All interactions occur directly Pod-to-Pod.",
|
||||||
|
"No hosting company or service provider controls your group data.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Quantum Safe File System (QSFS)",
|
||||||
|
description:
|
||||||
|
"Store data using a self-healing, encrypted, quantum-resistant system.",
|
||||||
|
bullets: [
|
||||||
|
"Files are encrypted, split, and distributed across trusted nodes.",
|
||||||
|
"Designed to withstand future quantum-level attacks.",
|
||||||
|
"Automatic repair and reconstruction of data fragments.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Access From Any Device",
|
||||||
|
description:
|
||||||
|
"Your Pod travels with you — always accessible, always yours.",
|
||||||
|
bullets: [
|
||||||
|
"Use your Pod from phones, laptops, tablets, or shared machines.",
|
||||||
|
"Your identity, apps, and files follow you securely.",
|
||||||
|
"No syncing or duplicated copies — direct access to your environment.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function PodsFeatures() {
|
||||||
|
return (
|
||||||
|
<section className="w-full max-w-8xl mx-auto bg-transparent">
|
||||||
|
{/* Top horizontal spacing */}
|
||||||
|
<div className="max-w-7xl bg-transparent 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" />
|
||||||
|
|
||||||
|
<Container className="py-12 border border-t-0 border-b-0 border-gray-100">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="mx-auto max-w-4xl sm:text-center">
|
||||||
|
<h2 className="text-base/7 font-semibold text-cyan-500">
|
||||||
|
WHAT YOU CAN DO
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p className="text-3xl lg:text-4xl font-medium tracking-tight text-gray-900">
|
||||||
|
A Fully Personal, Sovereign Digital Environment
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p className="mt-6 text-lg text-gray-600">
|
||||||
|
Pods use open standard protocols like Matrix and Nostr. Everything runs directly through the Mycelium Network.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Cards */}
|
||||||
|
<ul
|
||||||
|
role="list"
|
||||||
|
className="mx-auto mt-12 grid max-w-2xl grid-cols-1 gap-6
|
||||||
|
sm:grid-cols-2 lg:max-w-none lg:grid-cols-3 md:gap-y-10"
|
||||||
|
>
|
||||||
|
{useCases.map((useCase) => (
|
||||||
|
<li
|
||||||
|
key={useCase.title}
|
||||||
|
className="rounded-md border border-gray-100 bg-white p-6 transition-all duration-300
|
||||||
|
hover:scale-105 hover:border-cyan-500 hover:shadow-lg hover:shadow-cyan-500/20 flex flex-col"
|
||||||
|
>
|
||||||
|
{/* Title + label */}
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h3 className="font-semibold text-gray-900">
|
||||||
|
{useCase.title}
|
||||||
|
</h3>
|
||||||
|
<Small className="uppercase tracking-[0.25em] text-cyan-500">
|
||||||
|
Feature
|
||||||
|
</Small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Short description */}
|
||||||
|
<p className="mt-4 text-gray-700 leading-snug">
|
||||||
|
{useCase.description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Bullet list */}
|
||||||
|
<ul className="mt-6 space-y-3 text-sm text-gray-600">
|
||||||
|
{useCase.bullets.map((bullet) => (
|
||||||
|
<li
|
||||||
|
key={bullet}
|
||||||
|
className="flex items-start gap-3 rounded-2xl border border-cyan-100 bg-cyan-50/60 p-3 leading-relaxed"
|
||||||
|
>
|
||||||
|
<span className="mt-1 inline-block size-2 rounded-full bg-cyan-500" />
|
||||||
|
<span>{bullet}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</Container>
|
||||||
|
|
||||||
|
{/* Bottom spacing */}
|
||||||
|
<div className="w-full border-b border-gray-100" />
|
||||||
|
<div className="max-w-7xl bg-transparent mx-auto py-6 border border-t-0 border-b-0 border-gray-100"></div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Eyebrow, H3, P } from "@/components/Texts";
|
import { Eyebrow, H3, P } from "@/components/Texts";
|
||||||
import CloudPods from "./animations/CloudPods";
|
import PodsFlow from "./animations/PodsFlow";
|
||||||
|
|
||||||
export function PodsHow() {
|
export function PodsHow() {
|
||||||
return (
|
return (
|
||||||
@@ -13,34 +13,40 @@ export function PodsHow() {
|
|||||||
<div className="max-w-7xl mx-auto px-6 lg:px-8 py-12 border border-t-0 border-b-0 border-gray-800 bg-[#111111] overflow-hidden">
|
<div className="max-w-7xl mx-auto px-6 lg:px-8 py-12 border border-t-0 border-b-0 border-gray-800 bg-[#111111] overflow-hidden">
|
||||||
|
|
||||||
{/* ✅ Two-column layout */}
|
{/* ✅ Two-column layout */}
|
||||||
<div className="flex flex-col lg:flex-row-reverse gap-16">
|
<div className="flex flex-col lg:flex-row-reverse gap-8">
|
||||||
|
|
||||||
{/* ✅ Right side animation */}
|
{/* ✅ Right side animation */}
|
||||||
<div className="w-full lg:w-1/2">
|
<div className="w-full lg:w-4/9">
|
||||||
<CloudPods />
|
<PodsFlow />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ✅ Left side content */}
|
{/* ✅ Left side content */}
|
||||||
<div className="w-full lg:w-1/2 text-white">
|
<div className="w-full lg:w-5/9 text-white">
|
||||||
<Eyebrow color="accent" className="">
|
<Eyebrow color="accent" className="">
|
||||||
How it works
|
How it works
|
||||||
</Eyebrow>
|
</Eyebrow>
|
||||||
<H3 color="white" className="mt-6">
|
<H3 color="white" className="mt-6">
|
||||||
What Living in a Pod Feels Like
|
A Pod in Action
|
||||||
</H3>
|
</H3>
|
||||||
<P className="max-w-3xl text-gray-400 mt-6">
|
<P className="max-w-4xl text-gray-400 mt-6">
|
||||||
When you use Mycelium, everything — your messages, calls, files — runs directly from your Pod.
|
When you use Mycelium, everything runs directly from your Pod.
|
||||||
</P>
|
</P>
|
||||||
|
<ul className="max-w-4xl text-gray-400 mt-6 space-y-2 ml-6">
|
||||||
|
<li className="flex items-start gap-2">
|
||||||
|
<span className="mt-1 inline-block size-2 rounded-full bg-cyan-400" />
|
||||||
|
<span>When you message someone, it goes Pod to Pod, not through a central server.</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex items-start gap-3">
|
||||||
|
<span className="mt-1 inline-block size-2 rounded-full bg-cyan-400" />
|
||||||
|
<span>When you host a call, it runs on your Pod — no third-party data centers.</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex items-start gap-3">
|
||||||
|
<span className="mt-1 inline-block size-2 rounded-full bg-cyan-400" />
|
||||||
|
<span>When you save a file, it stays on your Pod, encrypted and always available.</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
<P className="max-w-3xl text-gray-400 mt-4">
|
<P className="max-w-3xl text-gray-400 mt-4">
|
||||||
It’s your personal digital hub:
|
|
||||||
When you message someone, it goes Pod to Pod, not through a central server.
|
|
||||||
When you host a call, it runs on your Pod — no third-party data centers.
|
|
||||||
</P>
|
|
||||||
<P className="max-w-3xl text-gray-400 mt-4">
|
|
||||||
When you save a file, it stays on your Pod, encrypted and always available.
|
|
||||||
No one else can read it, rent it, or switch it off.
|
No one else can read it, rent it, or switch it off.
|
||||||
</P>
|
|
||||||
<P className="max-w-3xl text-gray-400 mt-4">
|
|
||||||
You don’t log in to the internet — you are part of it.
|
You don’t log in to the internet — you are part of it.
|
||||||
</P>
|
</P>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,13 +1,23 @@
|
|||||||
import Homepod from './Homepod';
|
import Homepod from './Homepod';
|
||||||
import { PodsCapabilities } from './PodsCapabilities';
|
import { PodCapabilities } from './PodCapabilities';
|
||||||
import { PodsHow } from './PodsHow';
|
import { PodsHow } from './PodsHow';
|
||||||
|
import { PodsFeatures } from './PodsFeatures';
|
||||||
|
import { PodsDesign } from './PodsDesign';
|
||||||
|
import { PodsBenefits } from './PodsBenefits';
|
||||||
|
import { PodsComing } from './PodsComing';
|
||||||
|
import { CallToAction } from './CallToAction';
|
||||||
|
|
||||||
const PodsPage = () => {
|
const PodsPage = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Homepod />
|
<Homepod />
|
||||||
<PodsCapabilities />
|
<PodCapabilities />
|
||||||
|
<PodsFeatures />
|
||||||
<PodsHow />
|
<PodsHow />
|
||||||
|
<PodsDesign />
|
||||||
|
<PodsBenefits />
|
||||||
|
<PodsComing />
|
||||||
|
<CallToAction />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
143
src/pages/pods/PodsPricing.tsx
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
import { CheckIcon } from "@heroicons/react/20/solid";
|
||||||
|
import { Eyebrow, H3 } from "@/components/Texts";
|
||||||
|
|
||||||
|
const tiers = [
|
||||||
|
{
|
||||||
|
name: "Pod Basic",
|
||||||
|
id: "pod-basic",
|
||||||
|
href: "#",
|
||||||
|
priceMonthly: "€3",
|
||||||
|
description:
|
||||||
|
"A sovereign starter Pod with private communication, storage, and local-first identity.",
|
||||||
|
features: [
|
||||||
|
"1 private space (chat, calls, storage)",
|
||||||
|
"Local-first identity (no sign-up)",
|
||||||
|
"Peer-to-peer encrypted networking",
|
||||||
|
],
|
||||||
|
mostPopular: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Pod Plus",
|
||||||
|
id: "pod-plus",
|
||||||
|
href: "#",
|
||||||
|
priceMonthly: "€5",
|
||||||
|
description:
|
||||||
|
"A multi-device Pod with team spaces and expanded tools for encrypted collaboration.",
|
||||||
|
features: [
|
||||||
|
"Team spaces & multi-device sync",
|
||||||
|
"Shared encrypted folders",
|
||||||
|
"Priority relay paths for roaming devices",
|
||||||
|
],
|
||||||
|
mostPopular: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Pod Pro",
|
||||||
|
id: "pod-pro",
|
||||||
|
href: "#",
|
||||||
|
priceMonthly: "€8",
|
||||||
|
description:
|
||||||
|
"Advanced Pod with AI Agent support and the upcoming private automation layer.",
|
||||||
|
features: [
|
||||||
|
"AI Agent layer (coming soon)",
|
||||||
|
"Federated compute support",
|
||||||
|
"Private AI memory stored on your Pod",
|
||||||
|
],
|
||||||
|
mostPopular: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function PodsPricing() {
|
||||||
|
return (
|
||||||
|
<div className="bg-[#121212] text-white ">
|
||||||
|
|
||||||
|
{/* Top horizontal line */}
|
||||||
|
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-800" />
|
||||||
|
<div className="w-full border-t border-l border-r border-gray-800" />
|
||||||
|
|
||||||
|
<div className="mx-auto max-w-7xl px-6 lg:px-8 py-12 border border-t-0 border-b-0 border-gray-800">
|
||||||
|
|
||||||
|
{/* Header */}
|
||||||
|
<div className="mx-auto max-w-4xl text-center">
|
||||||
|
<Eyebrow>PLANS</Eyebrow>
|
||||||
|
<H3 className="mt-2 text-white">
|
||||||
|
Choose your Pod
|
||||||
|
</H3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="mx-auto mt-6 max-w-2xl text-center text-lg text-gray-400 sm:text-xl">
|
||||||
|
Personal, sovereign cloud environments—priced for everyone.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Pricing grid */}
|
||||||
|
<div className="isolate mx-auto mt-16 grid max-w-md grid-cols-1 gap-y-8
|
||||||
|
sm:mt-20 lg:max-w-none lg:grid-cols-3 lg:mx-0">
|
||||||
|
|
||||||
|
{tiers.map((tier, i) => (
|
||||||
|
<div
|
||||||
|
key={tier.id}
|
||||||
|
className={`flex flex-col justify-between rounded-3xl p-8 xl:p-10
|
||||||
|
bg-[#1a1a1a]/60 border border-gray-800 hover:border-cyan-500 transition
|
||||||
|
${tier.mostPopular ? "lg:z-10" : "lg:mt-8"}
|
||||||
|
${i === 0 ? "lg:rounded-r-none" : ""}
|
||||||
|
${i === tiers.length - 1 ? "lg:rounded-l-none" : ""}`}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h3
|
||||||
|
id={tier.id}
|
||||||
|
className={`text-lg font-semibold ${
|
||||||
|
tier.mostPopular ? "text-cyan-500" : "text-white"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{tier.name}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
{tier.mostPopular && (
|
||||||
|
<p className="rounded-full bg-cyan-500/10 px-2.5 py-1 text-xs font-semibold text-cyan-500">
|
||||||
|
Most popular
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="mt-4 text-sm text-gray-300">{tier.description}</p>
|
||||||
|
|
||||||
|
<p className="mt-6 flex items-baseline gap-x-1">
|
||||||
|
<span className="text-4xl font-semibold">{tier.priceMonthly}</span>
|
||||||
|
<span className="text-sm font-semibold text-gray-400">/month</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul className="mt-8 space-y-3 text-sm text-gray-300">
|
||||||
|
{tier.features.map((feature) => (
|
||||||
|
<li key={feature} className="flex gap-x-3">
|
||||||
|
<CheckIcon className="h-5 w-5 flex-none text-cyan-500" />
|
||||||
|
{feature}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* CTA */}
|
||||||
|
<a
|
||||||
|
href={tier.href}
|
||||||
|
aria-describedby={tier.id}
|
||||||
|
className={`mt-8 block rounded-md px-3 py-2 text-center text-sm font-semibold
|
||||||
|
focus-visible:outline-2 focus-visible:outline-offset-2
|
||||||
|
${
|
||||||
|
tier.mostPopular
|
||||||
|
? "bg-cyan-500 text-white hover:bg-cyan-400 focus-visible:outline-cyan-500"
|
||||||
|
: "bg-white/10 text-white hover:bg-white/20 focus-visible:outline-cyan-500"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Choose Pod
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bottom horizontal line */}
|
||||||
|
<div className="w-full border-b border-gray-800" />
|
||||||
|
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-800" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
192
src/pages/pods/animations/PodsFlow.tsx
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string;
|
||||||
|
accent?: string;
|
||||||
|
gridStroke?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const W = 760;
|
||||||
|
const H = 420;
|
||||||
|
|
||||||
|
export default function PodsFlow({
|
||||||
|
className,
|
||||||
|
accent = "#00b8db",
|
||||||
|
gridStroke = "#2b2a2a",
|
||||||
|
}: Props) {
|
||||||
|
const pods = [
|
||||||
|
{ x: 100, y: 180, label: "Pod 1" },
|
||||||
|
{ x: 260, y: 180, label: "Pod 2" },
|
||||||
|
{ x: 420, y: 180, label: "Pod 3" },
|
||||||
|
{ x: 580, y: 180, label: "Pod 4" },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Pulse path
|
||||||
|
const path = `
|
||||||
|
M ${pods[0].x + 80} ${pods[0].y + 40}
|
||||||
|
L ${pods[1].x - 10} ${pods[1].y + 40}
|
||||||
|
L ${pods[1].x + 80} ${pods[1].y + 40}
|
||||||
|
L ${pods[2].x - 10} ${pods[2].y + 40}
|
||||||
|
L ${pods[2].x + 80} ${pods[2].y + 40}
|
||||||
|
L ${pods[3].x - 10} ${pods[3].y + 40}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Arrow segments
|
||||||
|
const arrows = [
|
||||||
|
{
|
||||||
|
d: `M ${pods[0].x + 80} ${pods[0].y + 40} L ${pods[1].x - 6} ${pods[1].y + 40}`,
|
||||||
|
end: { x: pods[1].x - 6, y: pods[1].y + 40 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
d: `M ${pods[1].x + 80} ${pods[1].y + 40} L ${pods[2].x - 6} ${pods[2].y + 40}`,
|
||||||
|
end: { x: pods[2].x - 6, y: pods[2].y + 40 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
d: `M ${pods[2].x + 80} ${pods[2].y + 40} L ${pods[3].x - 6} ${pods[3].y + 40}`,
|
||||||
|
end: { x: pods[3].x - 6, y: pods[3].y + 40 },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx("relative overflow-hidden", className)}
|
||||||
|
aria-hidden="true"
|
||||||
|
role="img"
|
||||||
|
aria-label="Pod-to-Pod signal transfer animation"
|
||||||
|
style={{ background: "transparent" }}
|
||||||
|
>
|
||||||
|
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full">
|
||||||
|
{/* GRID BG */}
|
||||||
|
<defs>
|
||||||
|
<pattern id="pods-grid" width="28" height="28" patternUnits="userSpaceOnUse">
|
||||||
|
<path d="M 28 0 L 0 0 0 28" fill="none" stroke={gridStroke} strokeWidth="1" opacity="0.6" />
|
||||||
|
</pattern>
|
||||||
|
|
||||||
|
<filter id="pods-glow">
|
||||||
|
<feGaussianBlur stdDeviation="4" result="blur" />
|
||||||
|
<feMerge>
|
||||||
|
<feMergeNode in="blur" />
|
||||||
|
<feMergeNode in="SourceGraphic" />
|
||||||
|
</feMerge>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<rect width={W} height={H} fill="url(#pods-grid)" />
|
||||||
|
|
||||||
|
{/* POD BOXES */}
|
||||||
|
{pods.map((p, i) => (
|
||||||
|
<motion.rect
|
||||||
|
key={i}
|
||||||
|
x={p.x}
|
||||||
|
y={p.y}
|
||||||
|
width={80}
|
||||||
|
height={80}
|
||||||
|
rx={14}
|
||||||
|
fill="#0d0d0d"
|
||||||
|
stroke="#1a1a1a"
|
||||||
|
strokeWidth={2}
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 0.9 }}
|
||||||
|
transition={{ duration: 0.5 + i * 0.15 }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* POD LABELS */}
|
||||||
|
{pods.map((p, i) => (
|
||||||
|
<motion.text
|
||||||
|
key={i}
|
||||||
|
x={p.x + 40}
|
||||||
|
y={p.y + 50}
|
||||||
|
textAnchor="middle"
|
||||||
|
fontSize="14"
|
||||||
|
fontFamily="Inter, sans-serif"
|
||||||
|
fill="#9ca3af"
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 0.9 }}
|
||||||
|
transition={{ delay: 0.1 + i * 0.1, duration: 0.6 }}
|
||||||
|
>
|
||||||
|
{p.label}
|
||||||
|
</motion.text>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* GREY LINES */}
|
||||||
|
{arrows.map((a, i) => (
|
||||||
|
<motion.path
|
||||||
|
key={`grey-${i}`}
|
||||||
|
d={a.d}
|
||||||
|
stroke="#333"
|
||||||
|
strokeWidth={4}
|
||||||
|
strokeLinecap="round"
|
||||||
|
fill="none"
|
||||||
|
initial={{ pathLength: 0, opacity: 0 }}
|
||||||
|
animate={{ pathLength: 1, opacity: 0.8 }}
|
||||||
|
transition={{ delay: 0.2 * i, duration: 0.7 }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* CYAN LINES */}
|
||||||
|
{arrows.map((a, i) => (
|
||||||
|
<motion.path
|
||||||
|
key={`cyan-${i}`}
|
||||||
|
d={a.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.7 }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* NEW: CYAN ENDPOINT PULSES */}
|
||||||
|
{arrows.map((a, i) => (
|
||||||
|
<motion.circle
|
||||||
|
key={`endpoint-${i}`}
|
||||||
|
cx={a.end.x}
|
||||||
|
cy={a.end.y}
|
||||||
|
r={10}
|
||||||
|
fill={accent}
|
||||||
|
opacity={0.12}
|
||||||
|
filter="url(#pods-glow)"
|
||||||
|
initial={{ scale: 0.8, opacity: 0 }}
|
||||||
|
animate={{
|
||||||
|
scale: [1, 1.2, 1],
|
||||||
|
opacity: [0.05, 0.25, 0.05],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 1.5,
|
||||||
|
delay: i * 0.2,
|
||||||
|
repeat: Infinity,
|
||||||
|
repeatType: "mirror",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* MAIN MOVING CYAN PULSE */}
|
||||||
|
<motion.circle
|
||||||
|
r={8}
|
||||||
|
fill={accent}
|
||||||
|
filter="url(#pods-glow)"
|
||||||
|
style={{
|
||||||
|
offsetPath: `path('${path.replace(/\s+/g, " ")}')`,
|
||||||
|
}}
|
||||||
|
initial={{ offsetDistance: "0%", opacity: 0.4 }}
|
||||||
|
animate={{
|
||||||
|
offsetDistance: ["0%", "100%"],
|
||||||
|
opacity: [0.4, 1, 0.4],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 2.6,
|
||||||
|
repeat: Infinity,
|
||||||
|
ease: "linear",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -221,3 +221,10 @@
|
|||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.blink-square.active {
|
||||||
|
fill: #79f4ff;
|
||||||
|
opacity: 0.15;
|
||||||
|
filter: drop-shadow(0 0 6px #90f6ff);
|
||||||
|
transition: opacity 1s ease-out;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
export default {
|
export default {
|
||||||
|
darkMode: 'class',
|
||||||
content: [
|
content: [
|
||||||
"./index.html",
|
"./index.html",
|
||||||
"./src/**/*.{js,ts,jsx,tsx}",
|
"./src/**/*.{js,ts,jsx,tsx}",
|
||||||
|
|||||||