3 Commits

Author SHA1 Message Date
f015a0d892 feat: implement dark theme with white logo and updated text effects
- Added white logo variant and updated header to use dark theme styling
- Split text hover effect component into two variants (cursor-following and auto-animated)
- Updated button and dropdown components to support transparent backgrounds and white text styling
2025-11-12 17:59:54 +01:00
954d51dcaa Merge branch 'development' into development_sasha 2025-11-12 16:54:05 +01:00
3ac2f8ede7 refactor: remove comparison table section from home page 2025-11-07 12:13:12 +01:00
172 changed files with 1401 additions and 7888 deletions

View File

@@ -1 +0,0 @@
google-site-verification: google5dd3a8b700455c0e.html

View File

@@ -4,10 +4,8 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="google-site-verification" content="rRrZkMEhdC4yFe_BrENEzYmy2bRfD-VE6RTRiDJNLkg" />
<title>Project Mycelium - Built for Digital Sovereignty</title>
<meta name="description" content="Discover Project Mycelium. A sovereign peer-to-peer network for private communication, storage, and compute. Build and run your digital environment on infrastructure you control." />
<meta name="keywords" content="Project Mycelium, Mycelium, digital sovereignty, decentralized network, peer-to-peer infrastructure, private storage, secure compute, sovereign cloud, edge cloud" />
<title>Project Mycelium - Unleash the Power of Decentralized Networks</title>
<meta name="description" content="Project Mycelium's technology enables anyone to deploy their own Internet infrastructure, anywhere." />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Mulish:wght@400;500;700&display=swap" rel="stylesheet" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 618 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 508 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 793 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 801 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 792 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 66 KiB

BIN
public/images/logowhite.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 990 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

BIN
public/videos/agent.mp4 Normal file

Binary file not shown.

BIN
public/videos/benefits.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

BIN
public/videos/benefits.mp4 Normal file

Binary file not shown.

BIN
public/videos/chip_vid.mp4 Normal file

Binary file not shown.

BIN
public/videos/cloud.mp4 Normal file

Binary file not shown.

BIN
public/videos/cta.mp4 Normal file

Binary file not shown.

Binary file not shown.

BIN
public/videos/fungistor.mp4 Normal file

Binary file not shown.

BIN
public/videos/herodb.mp4 Normal file

Binary file not shown.

BIN
public/videos/mesh.mp4 Normal file

Binary file not shown.

BIN
public/videos/mhero.mp4 Normal file

Binary file not shown.

BIN
public/videos/mycelium2.mp4 Normal file

Binary file not shown.

BIN
public/videos/sandbox.mp4 Normal file

Binary file not shown.

BIN
public/videos/universal.mp4 Normal file

Binary file not shown.

View File

@@ -1,6 +1,6 @@
import { HashRouter, Routes, Route, useLocation } from 'react-router-dom';
import { HashRouter, Routes, Route } from 'react-router-dom';
import { Layout } from './components/Layout';
import { lazy, Suspense, useEffect } from 'react';
import { lazy, Suspense } from 'react';
const HomePage = lazy(() => import('./pages/home/HomePage'));
const CloudPage = lazy(() => import('./pages/cloud/CloudPage'));
@@ -11,31 +11,10 @@ const ComputePage = lazy(() => import('./pages/compute/ComputePage'));
const StoragePage = lazy(() => import('./pages/storage/StoragePage'));
const GpuPage = lazy(() => import('./pages/gpu/GpuPage'));
const PodsPage = lazy(() => import('./pages/pods/PodsPage'));
const NodesPage = lazy(() => import('./pages/nodes/NodesPage'));
function ScrollToTop() {
const { pathname, hash } = useLocation();
useEffect(() => {
if (hash) {
const id = hash.replace('#', '');
const element = document.getElementById(id);
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
return;
}
}
window.scrollTo({ top: 0, left: 0, behavior: 'auto' });
}, [pathname, hash]);
return null;
}
function App() {
return (
<HashRouter>
<ScrollToTop />
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Layout />}>
@@ -48,7 +27,6 @@ function App() {
<Route path="storage" element={<StoragePage />} />
<Route path="gpu" element={<GpuPage />} />
<Route path="pods" element={<PodsPage />} />
<Route path="nodes" element={<NodesPage />} />
</Route>
</Routes>
</Suspense>

View File

@@ -3,30 +3,23 @@ import clsx from 'clsx'
const baseStyles = {
solid:
'inline-flex justify-center rounded-full py-2 px-5 text-sm md:text-base font-semibold transition-colors',
'inline-flex justify-center rounded-full py-2 px-5 text-base font-semibold transition-colors',
outline:
'inline-flex justify-center bg-transparent font-semibold rounded-full border border-2 py-[calc(--spacing(2)-1px)] px-[calc(--spacing(5)-1px)] text-sm md:text-base transition-colors',
link:
'inline-flex items-center text-sm md:text-base font-semibold transition-colors',
'inline-flex justify-center bg-transparent rounded-full border py-[calc(--spacing(2)-1px)] px-[calc(--spacing(5)-1px)] text-base transition-colors',
}
const variantStyles = {
solid: {
cyan: 'relative overflow-hidden bg-cyan-500 text-white before:absolute before:inset-0 hover:bg-cyan-400 active:before:bg-transparent hover:before:bg-white/10 active:bg-cyan-500 active:text-white/80 before:transition-colors',
cyan: 'relative overflow-hidden bg-cyan-500 text-white before:absolute before:inset-0 active:before:bg-transparent hover:before:bg-white/10 active:bg-cyan-600 active:text-white/80 before:transition-colors',
white:
'bg-white text-cyan-900 hover:bg-white/90 active:bg-white/90 active:text-cyan-900/70',
gray: 'bg-gray-800 text-white hover:bg-gray-900 active:bg-gray-800 active:text-white/80',
green: 'bg-green-500 text-white hover:bg-green-600',
},
outline: {
cyan: 'border-cyan-500 text-cyan-500 hover:border-cyan-400 hover:text-cyan-400 active:border-cyan-400',
gray: 'border-gray-200 text-gray-600 hover:text-cyan-400 hover:border-cyan-400 active:border-cyan-400',
white: 'border-gray-300 text-white hover:text-cyan-400 hover:border-cyan-400 active:border-cyan-400',
},
link: {
cyan: 'text-cyan-400 underline hover:text-cyan-300',
white: 'text-white underline hover:text-cyan-300',
dark: 'text-gray-900 underline hover:text-cyan-400',
cyan: 'border-cyan-500 text-cyan-500',
gray: 'border-gray-300 text-white hover:text-cyan-500 hover:border-cyan-500 active:border-cyan-500',
white: 'border-gray-300 text-white hover:border-cyan-500 active:border-cyan-500',
},
}
@@ -37,11 +30,7 @@ type ButtonProps = (
}
| {
variant: 'outline'
color?: keyof typeof variantStyles.outline
}
| {
variant: 'link'
color?: keyof typeof variantStyles.link
color?: (keyof typeof variantStyles.outline) | 'cyan'
}
) &
(
@@ -54,33 +43,16 @@ type ButtonProps = (
)
export function Button({ className, as, ...props }: ButtonProps) {
// Set safe defaults per variant so color always matches a valid palette key
if (!props.variant) {
props.variant = 'solid'
}
if (!props.color) {
if (props.variant === 'solid') {
props.color = 'gray'
} else if (props.variant === 'outline') {
props.color = 'gray'
} else if (props.variant === 'link') {
props.color = 'cyan'
}
}
const variant = props.variant
const color = props.color as string
props.variant ??= 'solid'
props.color ??= 'gray'
className = clsx(
baseStyles[variant],
variant === 'outline'
? variantStyles.outline[color as keyof typeof variantStyles.outline]
: variant === 'solid'
? variantStyles.solid[color as keyof typeof variantStyles.solid]
: variant === 'link'
? variantStyles.link[color as keyof typeof variantStyles.link]
: undefined,
baseStyles[props.variant],
props.variant === 'outline'
? variantStyles.outline[props.color]
: props.variant === 'solid'
? variantStyles.solid[props.color]
: undefined,
className,
)

View File

@@ -8,27 +8,27 @@ export function Footer() {
<div className="flex flex-col items-start justify-between gap-y-12 pt-16 pb-6 lg:flex-row lg:items-center lg:py-8">
<div>
<div className="flex items-center text-gray-900">
<img src="/images/logomark.svg" alt="Mycelium Logomark" className="h-20 w-20 flex-none" />
<div className="">
<p className="text-base lg:text-lg font-semibold">Project Mycelium</p>
<p className="mt-1 text-sm">Built for Digital Sovereignty</p>
<img src="/images/logomark.svg" alt="Mycelium Logomark" className="h-15 w-15 flex-none" />
<div className="ml-4">
<p className="text-base font-semibold">Project Mycelium</p>
<p className="mt-1 text-sm">Unleash the Power of Decentralization</p>
</div>
</div>
<nav className="mt-10 flex gap-8">
<Link to="/network" className="text-sm text-gray-700 hover:text-cyan-500 transition-colors">
Network
<Link to="/" className="text-sm text-gray-700 hover:text-cyan-500 transition-colors">
Home
</Link>
<Link to="/cloud" className="text-sm text-gray-700 hover:text-cyan-500 transition-colors">
Cloud
</Link>
<Link to="/pods" className="text-sm text-gray-700 hover:text-cyan-500 transition-colors">
Pods
<Link to="/network" className="text-sm text-gray-700 hover:text-cyan-500 transition-colors">
Network
</Link>
<Link to="/agents" className="text-sm text-gray-700 hover:text-cyan-500 transition-colors">
Agents
</Link>
<Link to="/nodes" className="text-sm text-gray-700 hover:text-cyan-500 transition-colors">
Nodes
<Link to="/pods" className="text-sm text-gray-700 hover:text-cyan-500 transition-colors">
Pods
</Link>
</nav>
</div>
@@ -49,11 +49,11 @@ export function Footer() {
</div>
</div>
</div>
<div className="flex flex-col items-center border-t border-gray-100 py-8 md:flex-row-reverse md:justify-between md:pt-6">
<div className="flex flex-col items-center border-t border-gray-100 pt-8 pb-12 md:flex-row-reverse md:justify-between md:pt-6">
<p className="mt-6 text-sm text-gray-500 md:mt-0">
&copy; Copyright{' '}
<a href="https://ourworld.tf/" target="_blank" rel="noopener noreferrer" className="font-semibold hover:text-cyan-500 transition-colors">
OurWorld
<a href="https://www.threefold.io" target="_blank" rel="noopener noreferrer" className="hover:text-cyan-500 transition-colors">
ThreeFold
</a>{' '}
{new Date().getFullYear()}. All rights reserved.
</p>

View File

@@ -1,14 +1,32 @@
import { useState } from 'react'
import { Link } from 'react-router-dom'
import { Link, useLocation } from 'react-router-dom'
import { Dropdown } from './ui/Dropdown'
import { ChevronDownIcon } from '@heroicons/react/20/solid'
import { Container } from './Container'
import { Button } from './Button'
import pmyceliumLogo from '../images/logos/mainlogo.svg'
import pmyceliumLogo from '../images/logos/logo_1.png'
import { Dialog } from '@headlessui/react'
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() {
const location = useLocation()
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 (
<header className="bg-white">
<nav className="border-b border-gray-100">
@@ -18,24 +36,28 @@ export function Header() {
<img src={pmyceliumLogo} alt="Mycelium" className="h-8 w-auto" />
</Link>
<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
to="/network"
className="text-base/7 tracking-tight text-gray-700 hover:text-cyan-500 transition-colors"
>
Network
</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
to="/agents"
className="text-base/7 tracking-tight text-gray-700 hover:text-cyan-500 transition-colors"
@@ -43,10 +65,10 @@ export function Header() {
Agents
</Link>
<Link
to="/nodes"
to="/pods"
className="text-base/7 tracking-tight text-gray-700 hover:text-cyan-500 transition-colors"
>
Nodes
Pods
</Link>
</div>
</div>
@@ -102,6 +124,16 @@ export function Header() {
<div className="mt-6 flow-root">
<div className="-my-6 divide-y divide-gray-500/10">
<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
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"
@@ -109,20 +141,6 @@ export function Header() {
>
Network
</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
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"
@@ -131,11 +149,11 @@ export function Header() {
Agents
</Link>
<Link
to="/nodes"
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)}
>
Nodes
Pods
</Link>
</div>
<div className="py-6">

View File

@@ -1,41 +1,64 @@
import { useState } from 'react'
import { Link } from 'react-router-dom'
import { Link, useLocation } from 'react-router-dom'
import { Dropdown } from './ui/Dropdown'
import { ChevronDownIcon } from '@heroicons/react/20/solid'
import { Container } from './Container'
import { Button } from './Button'
import pmyceliumLogo from '../images/logos/mainlogo.svg'
import pmyceliumLogo from '../images/logos/logowhite.png'
import { Dialog } from '@headlessui/react'
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 HeaderDark() {
const location = useLocation()
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 (
<header className="bg-[#111111]">
<nav className="border-b border-gray-800">
<nav className="">
<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">
<Dropdown
buttonClassName="bg-transparent"
buttonContent={
<>
{['Compute', 'Storage', 'GPU'].includes(getCurrentPageName()) ? (
<>
<span className="text-white bg-[#111111]">Cloud {' >'} </span>
<span className="text-white bg-[#111111]">{getCurrentPageName()}</span>
</>
) : (
<span className="text-white">Cloud</span>
)}
<ChevronDownIcon className="h-5 w-5 text-white" aria-hidden="true" />
</>
}
items={cloudNavItems}
/>
<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"
@@ -43,10 +66,10 @@ export function HeaderDark() {
Agents
</Link>
<Link
to="/nodes"
to="/pods"
className="text-base/7 tracking-tight text-gray-300 hover:text-cyan-400 transition-colors"
>
Nodes
Pods
</Link>
</div>
</div>
@@ -102,6 +125,16 @@ export function HeaderDark() {
<div className="mt-6 flow-root">
<div className="-my-6 divide-y divide-gray-500/20">
<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-white hover:bg-gray-800"
onClick={() => setMobileMenuOpen(false)}
>
{item.name}
</Link>
))}
<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"
@@ -109,20 +142,6 @@ export function HeaderDark() {
>
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"
@@ -131,11 +150,11 @@ export function HeaderDark() {
Agents
</Link>
<Link
to="/nodes"
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)}
>
Nodes
Pods
</Link>
</div>
<div className="py-6">

View File

@@ -1,9 +1,9 @@
import { TextHoverEffect } from "@/components/ui/text-hover-effect";
import { TextHoverEffects } from "@/components/ui/text-hover-effects";
export function HomeHeadline() {
return (
<div className="flex items-center justify-center h-auto max-h-[200px]">
<TextHoverEffect text="MYCELIUM" />
<TextHoverEffects text="MYCELIUM" />
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { TextHoverEffect } from "@/components/ui/text-hover-effect";
export function HomeHeadlineDark() {
return (
<div className="flex items-center justify-center h-auto max-h-[200px]">
<TextHoverEffect text="MYCELIUM" />
</div>
);
}

View File

@@ -1,47 +1,13 @@
import { useEffect } from 'react'
import { Outlet } from 'react-router-dom'
import { Footer } from './Footer'
import { Header } from './Header'
import { HeaderDark } from './HeaderDark'
export function Layout() {
useEffect(() => {
if (typeof window === 'undefined') return
if (document.getElementById('mailerlite-universal')) return
const script = document.createElement('script')
script.id = 'mailerlite-universal'
script.innerHTML = `
(function(m,a,i,l,e,r){
m['MailerLiteObject']=e;
function f(){
var c={a:arguments,q:[]};
var r=this.push(c);
return "number"!=typeof r?r:f.bind(c.q);
}
f.q=f.q||[];
m[e]=m[e]||f.bind(f.q);
m[e].q=m[e].q||f.q;
r=a.createElement(i);
var _=a.getElementsByTagName(i)[0];
r.async=1;
r.src=l+'?v'+(~~(new Date().getTime()/1000000));
_.parentNode.insertBefore(r,_);
})(window, document, 'script', 'https://static.mailerlite.com/js/universal.js', 'ml');
window.ml_account = ml('accounts', '1778010', 'x2d3d9f8n1', 'load');
`
document.body.appendChild(script)
return () => {
script.remove()
}
}, [])
return (
<div className="bg-[#fdfdfd] antialiased relative" style={{ fontFamily: 'var(--font-inter)' }}>
<div className="relative z-10">
<Header />
<HeaderDark />
<main>
<Outlet />
</main>

View File

@@ -12,8 +12,8 @@ const colorVariants = {
primary: 'text-gray-900',
secondary: 'text-gray-600',
light: 'text-gray-50',
accent: 'text-cyan-400',
cyan: 'text-cyan-400',
accent: 'text-cyan-500',
cyan: 'text-cyan-50',
white: 'text-white',
dark: 'text-gray-950',
tertiary: 'text-gray-700',
@@ -162,5 +162,5 @@ export const DownloadCardDescription = createTextComponent(
'text-base/7 leading-normal tracking-normal'
)
export const CT = createTextComponent('span', 'text-base lg:text-lg leading-normal font-medium')
export const CP = createTextComponent('p', 'text-sm lg:text-base tracking-wide leading-normal font-light')
export const CT = createTextComponent('span', 'text-base lg:text-lg font-medium')
export const CP = createTextComponent('p', 'text-sm lg:text-base tracking-wide leading-tight font-light')

View File

@@ -1,12 +1,12 @@
"use client";
import { cn } from "@/lib/utils";
import React, { useEffect, useRef, useState } from "react";
import React, { useCallback, useEffect, useState } from "react";
export const InfiniteMovingCards = ({
items,
direction = "left",
speed = "slow",
speed = "fast",
pauseOnHover = true,
className,
}: {
@@ -15,39 +15,43 @@ export const InfiniteMovingCards = ({
speed?: "fast" | "normal" | "slow";
pauseOnHover?: boolean;
className?: string;
}) => {
const containerRef = useRef<HTMLDivElement>(null);
const scrollerRef = useRef<HTMLUListElement>(null);
const [isReady, setIsReady] = useState(false);
}): JSX.Element => {
const containerRef = React.useRef<HTMLDivElement>(null);
const scrollerRef = React.useRef<HTMLUListElement>(null);
const [start, setStart] = useState(false);
const getSpeed = useCallback(() => {
if (containerRef.current) {
if (speed === "fast") {
containerRef.current.style.setProperty("--animation-duration", "20s");
} else if (speed === "normal") {
containerRef.current.style.setProperty("--animation-duration", "40s");
} else {
containerRef.current.style.setProperty("--animation-duration", "80s");
}
}
}, [speed]);
const addAnimation = useCallback(() => {
if (containerRef.current && scrollerRef.current) {
const scrollerContent = Array.from(scrollerRef.current.children);
scrollerContent.forEach((item) => {
const duplicatedItem = item.cloneNode(true);
if (scrollerRef.current) {
scrollerRef.current.appendChild(duplicatedItem);
}
});
getSpeed();
setStart(true);
}
}, [getSpeed]);
useEffect(() => {
if (!scrollerRef.current) return;
const children = Array.from(scrollerRef.current.children);
// duplicate each item ONCE
children.forEach((item) => {
const clone = item.cloneNode(true);
scrollerRef.current!.appendChild(clone);
});
// set speed variable
const duration =
speed === "fast" ? "20s" : speed === "normal" ? "40s" : "80s";
containerRef.current?.style.setProperty(
"--animation-duration",
duration
);
// set direction variable
containerRef.current?.style.setProperty(
"--animation-direction",
direction === "left" ? "forwards" : "reverse"
);
setIsReady(true);
}, [direction, speed]);
addAnimation();
}, [addAnimation]);
return (
<div
@@ -57,16 +61,16 @@ export const InfiniteMovingCards = ({
<ul
ref={scrollerRef}
className={cn(
"flex w-max shrink-0 gap-16 py-4",
isReady &&
(direction === "left"
? "animate-infinite-scroll"
: "animate-infinite-scroll-right"),
pauseOnHover && "hover:[animation-play-state:paused]"
"flex min-w-full shrink-0 gap-16 py-4 w-max flex-nowrap",
start && "animate-scroll",
pauseOnHover && "hover:[animation-play-state:paused]",
)}
style={{
"--animation-direction": direction === "left" ? "forwards" : "reverse",
} as React.CSSProperties}
>
{items.map((item, i) => (
<li key={i} className="flex-shrink-0">
{items.map((item, idx) => (
<li className="relative flex-shrink-0" key={idx}>
{item}
</li>
))}

View File

@@ -10,13 +10,14 @@ interface DropdownItem {
interface DropdownProps {
buttonContent: React.ReactNode
items: DropdownItem[]
buttonClassName?: string
}
export function Dropdown({ buttonContent, items }: DropdownProps) {
export function Dropdown({ buttonContent, items, buttonClassName }: DropdownProps) {
return (
<Menu as="div" className="relative inline-block text-left">
<div>
<Menu.Button className="inline-flex w-full justify-center items-center gap-x-1.5 rounded-md bg-white text-base/7 tracking-tight text-gray-700 hover:text-cyan-500 transition-colors">
<Menu.Button className={`inline-flex w-full justify-center items-center gap-x-1.5 rounded-md text-base/7 tracking-tight text-gray-700 hover:text-cyan-500 transition-colors ${buttonClassName}`}>
{buttonContent}
</Menu.Button>
</div>

View File

@@ -1,122 +0,0 @@
import { useState, useEffect } from 'react';
import WorldMap from './world-map';
import { motion } from 'framer-motion';
// Interface for the simplified data passed to WorldMap
interface GeoNode {
lat: number;
lng: number;
label?: string;
color?: string;
}
// Interface for the raw data structure expected from the gridproxy API
interface RawNode {
node_id: number;
location: {
latitude: string; // API often returns these as strings
longitude: string; // API often returns these as strings
city: string;
country: string;
};
// ... other raw fields you don't need
}
const clusterNodes = (nodeList: GeoNode[], cellSize = 2) => {
const buckets = new Map<
string,
{ latSum: number; lngSum: number; count: number }
>();
nodeList.forEach((node) => {
const latBucket = Math.round(node.lat / cellSize) * cellSize;
const lngBucket = Math.round(node.lng / cellSize) * cellSize;
const key = `${latBucket}|${lngBucket}`;
const bucket = buckets.get(key);
if (bucket) {
bucket.latSum += node.lat;
bucket.lngSum += node.lng;
bucket.count += 1;
} else {
buckets.set(key, {
latSum: node.lat,
lngSum: node.lng,
count: 1,
});
}
});
return Array.from(buckets.values()).map((bucket) => {
const avgLat = bucket.latSum / bucket.count;
const avgLng = bucket.lngSum / bucket.count;
const count = bucket.count;
let color = "#06b6d4";
if (count > 20) {
color = "#0891b2";
} else if (count > 5) {
color = "#22d3ee";
}
return {
lat: avgLat,
lng: avgLng,
color,
label: `${count} nodes`,
};
});
};
function DynamicMapContainer() {
const [loading, setLoading] = useState(true);
const [nodes, setNodes] = useState<GeoNode[]>([]);
const API_URL = "https://gridproxy.grid.tf/nodes?healthy=true&size=500";
useEffect(() => {
async function fetchNodeData() {
try {
const response = await fetch(API_URL);
const data: RawNode[] = await response.json();
const geoNodes: GeoNode[] = data
.filter((node: RawNode) => node.location && node.location.latitude && node.location.longitude)
.map((node: RawNode) => ({
lat: parseFloat(node.location.latitude),
lng: parseFloat(node.location.longitude),
label: `${node.location.city}, ${node.location.country} (${node.node_id})`,
}));
const clusteredNodes = clusterNodes(geoNodes);
setNodes(clusteredNodes);
} catch (error) {
console.error("Failed to fetch node data:", error);
} finally {
setLoading(false);
}
}
fetchNodeData();
}, []);
// While fetching, show a loading state
if (loading) {
return (
<div className="flex justify-center items-center w-full aspect-[2/1] bg-[#111111] rounded-lg text-cyan-500">
<motion.span
animate={{ rotate: 360 }}
transition={{ duration: 1, repeat: Infinity, ease: "linear" }}
className="text-4xl"
>
🌎
</motion.span>
<p className="ml-4">Loading nodes...</p>
</div>
);
}
// After data is loaded, render the map
return <WorldMap nodes={nodes} />;
}
export default DynamicMapContainer;

View File

@@ -1,35 +1,31 @@
"use client";
import { useRef, useEffect, useState } from "react";
import { motion, useAnimation } from "motion/react";
import React, { useRef, useEffect, useState } from "react";
import { motion } from "motion/react";
export const TextHoverEffect = ({
text,
duration = 6, // loop duration
duration,
}: {
text: string;
duration?: number;
automatic?: boolean;
}) => {
const svgRef = useRef<SVGSVGElement>(null);
const controls = useAnimation();
const [hovered, setHovered] = useState(false);
const [cursor, setCursor] = useState({ x: 0, y: 0 });
const [hovered, setHovered] = useState(false);
const [maskPosition, setMaskPosition] = useState({ cx: "50%", cy: "50%" });
// ✅ 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]);
if (svgRef.current && cursor.x !== null && cursor.y !== null) {
const svgRect = svgRef.current.getBoundingClientRect();
const cxPercentage = ((cursor.x - svgRect.left) / svgRect.width) * 100;
const cyPercentage = ((cursor.y - svgRect.top) / svgRect.height) * 100;
setMaskPosition({
cx: `${cxPercentage}%`,
cy: `${cyPercentage}%`,
});
}
}, [cursor]);
return (
<svg
@@ -40,97 +36,96 @@ export const TextHoverEffect = ({
xmlns="http://www.w3.org/2000/svg"
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
className="select-none"
onMouseMove={(e) => setCursor({ x: e.clientX, y: e.clientY })}
className="select-none"
>
<defs>
{/* ✅ Softer cyan gradient */}
<linearGradient id="textGradient" gradientUnits="userSpaceOnUse">
{hovered ? (
<linearGradient
id="textGradient"
gradientUnits="userSpaceOnUse"
cx="50%"
cy="50%"
r="25%"
>
{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" />
<stop offset="0%" stopColor="#eab308" />
<stop offset="25%" stopColor="#ef4444" />
<stop offset="50%" stopColor="#3b82f6" />
<stop offset="75%" stopColor="#06b6d4" />
<stop offset="100%" stopColor="#8b5cf6" />
</>
)}
</linearGradient>
{/* ✅ Mask with autoplay motion */}
<motion.radialGradient
id="revealMask"
gradientUnits="userSpaceOnUse"
r="45%"
animate={controls}
r="20%"
initial={{ cx: "50%", cy: "50%" }}
animate={maskPosition}
transition={{ duration: duration ?? 0, ease: "easeOut" }}
// example for a smoother animation below
// transition={{
// type: "spring",
// stiffness: 300,
// damping: 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)" />
<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 }}
strokeWidth="0.3"
className="fill-transparent stroke-neutral-200 font-[helvetica] text-7xl font-bold dark:stroke-neutral-800"
style={{ opacity: hovered ? 0.7 : 0 }}
>
{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 }}
strokeWidth="0.3"
className="fill-transparent stroke-neutral-200 font-[helvetica] text-7xl font-bold dark:stroke-neutral-800"
initial={{ strokeDashoffset: 1000, strokeDasharray: 1000 }}
animate={{
strokeDashoffset: 0,
strokeDasharray: 600,
strokeDasharray: 1000,
}}
transition={{
duration: 2.2,
duration: 4,
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"
strokeWidth="0.3"
mask="url(#textMask)"
filter="url(#glow)"
className="font-[helvetica] text-7xl font-bold fill-[url(#textGradient)]"
className="fill-transparent font-[helvetica] text-7xl font-bold"
>
{text}
</text>

View File

@@ -0,0 +1,139 @@
"use client";
import { useRef, useEffect, useState } from "react";
import { motion, useAnimation } from "motion/react";
export const TextHoverEffects = ({
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>
);
};

View File

@@ -1,7 +1,7 @@
"use client";
import { useRef } from "react";
import { motion } from "framer-motion";
import { motion } from "motion/react";
import DottedMap from "dotted-map";
interface MapProps {
@@ -9,39 +9,34 @@ interface MapProps {
start: { lat: number; lng: number; label?: string };
end: { lat: number; lng: number; label?: string };
}>;
// New prop for dynamic standalone nodes
nodes?: Array<{ lat: number; lng: number; label?: string; color?: string }>;
lineColor?: string;
}
export default function WorldMap({
dots = [],
nodes = [],
lineColor = "#06b6d4",
lineColor = "#06b6d4", // cyan-500
}: MapProps) {
const svgRef = useRef<SVGSVGElement>(null);
// ✅ Force dark-dotted map theme
// ✅ Force dark-dotted map theme
const map = new DottedMap({ height: 100, grid: "diagonal" });
const svgMap = map.getSVG({
radius: 0.22,
color: "#06b6d480",
color: "#06b6d480", // cyan-500 at 50% opacity
shape: "circle",
backgroundColor: "#111111",
});
// ✅ Point projection stays the same
// Projects lat/lng to the SVG's 800x400 viewBox coordinates
// ✅ Point projection stays the same
const projectPoint = (lat: number, lng: number) => {
const x = (lng + 180) * (800 / 360);
const y = (90 - lat) * (400 / 180) + 45;
const y = (90 - lat) * (400 / 180);
return { x, y };
};
const createCurvedPath = (start: any, end: any) => {
const midX = (start.x + end.x) / 2;
// Creates an arc that bows upward by 50 units
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}`;
};
@@ -54,53 +49,13 @@ export default function WorldMap({
draggable={false}
/>
{/* ✅ Lines + points + new standalone nodes */}
{/* ✅ Lines + points */}
<svg
ref={svgRef}
viewBox="0 0 800 400"
className="w-full h-full absolute inset-0 pointer-events-none select-none"
>
{/* Glowing path gradient DEFS */}
<defs>
<linearGradient id="path-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stopColor="black" stopOpacity="0" />
<stop offset="5%" stopColor={lineColor} stopOpacity="1" />
<stop offset="95%" stopColor={lineColor} stopOpacity="1" />
<stop offset="100%" stopColor="black" stopOpacity="0" />
</linearGradient>
</defs>
{/* ✅ DYNAMIC STANDALONE NODE DOTS (New Section) */}
{nodes.map((node, i) => {
const p = projectPoint(node.lat, node.lng);
const dotColor = node.color || lineColor;
return (
<g key={`node-${i}`}>
{/* Outer pulsing circle */}
<circle cx={p.x} cy={p.y} r="2" fill={dotColor} opacity="0.5">
<animate
attributeName="r"
from="2"
to="7"
dur="1.4s"
repeatCount="indefinite"
/>
<animate
attributeName="opacity"
from="0.6"
to="0"
dur="1.4s"
repeatCount="indefinite"
/>
</circle>
{/* Inner fixed circle */}
<circle cx={p.x} cy={p.y} r="2" fill={dotColor} />
</g>
);
})}
{/* ✅ Animated curved travel lines (Existing Logic) */}
{/* ✅ animated curved travel lines */}
{dots.map((dot, i) => {
const startPoint = projectPoint(dot.start.lat, dot.start.lng);
const endPoint = projectPoint(dot.end.lat, dot.end.lng);
@@ -123,7 +78,17 @@ export default function WorldMap({
);
})}
{/* ✅ Start & end points with pulsing cyan glow (Existing Logic) */}
{/* ✅ glowing path gradient */}
<defs>
<linearGradient id="path-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stopColor="black" stopOpacity="0" />
<stop offset="5%" stopColor={lineColor} stopOpacity="1" />
<stop offset="95%" stopColor={lineColor} stopOpacity="1" />
<stop offset="100%" stopColor="black" stopOpacity="0" />
</linearGradient>
</defs>
{/* ✅ start & end points with pulsing cyan glow */}
{dots.map((dot, i) => {
const s = projectPoint(dot.start.lat, dot.start.lng);
const e = projectPoint(dot.end.lat, dot.end.lng);
@@ -157,4 +122,4 @@ export default function WorldMap({
</svg>
</div>
);
}
}

View File

@@ -0,0 +1,181 @@
import { useState } from 'react'
import { Link, useLocation } from 'react-router-dom'
import { Dropdown } from './ui/Dropdown'
import { ChevronDownIcon } from '@heroicons/react/20/solid'
import { Container } from './Container'
import { Button } from './Button'
import pmyceliumLogo from '../images/logos/logowhite.png'
import { Dialog } from '@headlessui/react'
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 HeaderDark() {
const location = useLocation()
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 (
<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">
<Dropdown
buttonContent={
<>
{['Compute', 'Storage', 'GPU'].includes(getCurrentPageName()) ? (
<>
<span className="text-gray-400">Cloud {' >'} </span>
<span className="text-white">{getCurrentPageName()}</span>
</>
) : (
<span className="text-white">Cloud</span>
)}
<ChevronDownIcon className="h-5 w-5 text-white" aria-hidden="true" />
</>
}
items={cloudNavItems}
/>
<Link
to="/network"
className="text-base/7 tracking-tight text-gray-300 hover:text-cyan-400 transition-colors"
>
Network
</Link>
<Link
to="/agents"
className="text-base/7 tracking-tight text-gray-300 hover:text-cyan-400 transition-colors"
>
Agents
</Link>
<Link
to="/pods"
className="text-base/7 tracking-tight text-gray-300 hover:text-cyan-400 transition-colors"
>
Pods
</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">
{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-white hover:bg-gray-800"
onClick={() => setMobileMenuOpen(false)}
>
{item.name}
</Link>
))}
<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="/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="/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>
</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>
)
}

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="77" zoomAndPan="magnify" viewBox="0 0 57.75 54" height="72" preserveAspectRatio="xMidYMid meet" version="1.0"><defs><clipPath id="6b820d2194"><path d="M 0.402344 0 L 57.101562 0 L 57.101562 6 L 0.402344 6 Z M 0.402344 0 " clip-rule="nonzero"/></clipPath><clipPath id="3794aac157"><path d="M 0.402344 23 L 57 23 L 57 30 L 0.402344 30 Z M 0.402344 23 " clip-rule="nonzero"/></clipPath><clipPath id="a8068b094c"><path d="M 0.402344 46 L 57.101562 46 L 57.101562 53 L 0.402344 53 Z M 0.402344 46 " clip-rule="nonzero"/></clipPath></defs><g clip-path="url(#6b820d2194)"><path stroke-linecap="butt" transform="matrix(0.736364, 0, 0, 0.736364, 0.402273, 0.00000196364)" fill="none" stroke-linejoin="miter" d="M 0.000096737 3.999805 L 76.537522 3.999805 " stroke="#43d7ff" stroke-width="8" stroke-opacity="1" stroke-miterlimit="4"/></g><g clip-path="url(#3794aac157)"><path fill="#43d7ff" d="M 0.402344 23.136719 L 35.746094 23.136719 L 35.746094 29.027344 L 0.402344 29.027344 M 41.636719 23.136719 L 56.761719 23.136719 L 56.761719 29.027344 L 41.636719 29.027344 " fill-opacity="1" fill-rule="nonzero"/></g><g clip-path="url(#a8068b094c)"><path stroke-linecap="butt" transform="matrix(0.736364, 0, 0, 0.736364, 0.402273, 46.951043)" fill="none" stroke-linejoin="miter" d="M 0.000096737 4.002635 L 76.537522 4.002635 " stroke="#43d7ff" stroke-width="8" stroke-opacity="1" stroke-miterlimit="4"/></g></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -1,32 +1,15 @@
"use client";
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: {
id: string;
eyebrow?: string;
title: string;
subtitle?: string;
description: string;
animation: React.ComponentType | null;
colSpan: string;
rowSpan: string;
custom?: boolean;
noBorder?: boolean;
}[] = [
const bentos = [
{
id: "core",
eyebrow: "ARCHITECTURE",
title: "Intelligence Fabric",
title: "Deterministic by Design",
description:
"The sovereign substrate for autonomous AI. Stateless, geo-aware, end-to-end encrypted—and verifiable from intent to execution.",
animation: null,
"Every workload runs exactly as declared: no drift, no hidden state, no surprises.",
video: null,
colSpan: "lg:col-span-3",
rowSpan: "lg:row-span-1",
custom: true,
@@ -40,7 +23,7 @@ const bentos: {
subtitle: "Long-Term AI Memory",
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.",
animation: Fungistor,
video: "/videos/fungistor.mp4",
colSpan: "lg:col-span-3",
rowSpan: "lg:row-span-1",
},
@@ -50,7 +33,7 @@ const bentos: {
subtitle: "Active AI Memory",
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.",
animation: Herodb,
video: "/videos/herodb.mp4",
colSpan: "lg:col-span-3",
rowSpan: "lg:row-span-1",
},
@@ -60,7 +43,7 @@ const bentos: {
subtitle: "Secure Agent Workspaces",
description:
"Attested, signed workspaces spin up ≈5s worldwide—ready to execute. Hardware isolation and scoped egress: run hard, tear down clean, zero residue.",
animation: MOSSandboxes,
video: "/videos/herodb.mp4",
colSpan: "lg:col-span-3",
rowSpan: "lg:row-span-1",
},
@@ -70,7 +53,7 @@ const bentos: {
subtitle: "Secure Communication Network",
description:
"A private, public-key fabric with self-healing multi-path routing. Glides through NATs and firewalls—direct, low-latency, no middlemen.",
animation: MyceliumMesh,
video: "/videos/mesh.mp4",
colSpan: "lg:col-span-2",
rowSpan: "lg:row-span-1",
},
@@ -80,7 +63,7 @@ const bentos: {
subtitle: "Verifiable Code Execution",
description:
"Declare intent, get a hash; remote attestation proves that is what runs. Reproducible builds, signed artifacts, immutable logs—supply chain, sealed.",
animation: DeterministicExecution,
video: "/videos/deterministic.mp4",
colSpan: "lg:col-span-2",
rowSpan: "lg:row-span-1",
},
@@ -90,7 +73,7 @@ const bentos: {
subtitle: "Sovereign Workflow Management",
description:
"Your private agent conducts swarms of specialists in parallel. Policies fan out work; human checkpoints keep you in command.",
animation: AgentCoordination,
video: "/videos/agent.mp4",
colSpan: "lg:col-span-2",
rowSpan: "lg:row-span-1",
},
@@ -118,12 +101,20 @@ export function AgentBento() {
<div
className={`relative flex lg:h-90 flex-col overflow-hidden rounded-[calc(var(--radius-lg)+1px)] `}
>
{card.animation ? (
<div className="lg:h-64 h-48 w-full overflow-hidden bg-transparent flex items-center justify-center">
<div className="w-full h-full object-cover"><card.animation /></div>
{/* ✅ VIDEO instead of animation */}
{card.video ? (
<div className="lg:h-64 h-48 w-full overflow-hidden bg-transparent flex items-center">
<video
src={card.video}
autoPlay
loop
muted
playsInline
className="w-full h-full object-cover"
/>
</div>
) : (
<div className="hidden md:flex md:h-48 w-full items-center justify-center bg-transparent" />
<div className="h-48 w-full flex items-center justify-center bg-transparent" />
)}
<div className="px-8 pt-4 pb-6">
@@ -136,7 +127,7 @@ export function AgentBento() {
) : (
<>
{/* ✅ NEW SUBTITLE */}
<p className="text-sm text-cyan-500">{card.subtitle}</p>
<p className="text-sm text-cyan-400">{card.subtitle}</p>
<p className="mt-1 text-lg font-medium lg:text-xl tracking-tight text-white">
{card.title}
@@ -161,9 +152,6 @@ export function AgentBento() {
))}
</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>
</section>
);
}

View File

@@ -1,56 +0,0 @@
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 py-10 px-4 border border-gray-100 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 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>
)
}

View File

@@ -1,38 +1,36 @@
'use client'
import { Eyebrow, H3, P } from '@/components/Texts'
import { Button } from '@/components/Button'
import { Eyebrow, H3 } from '@/components/Texts'
export function AgentHeroAlt() {
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 md:bg-[url('/images/agents.webp')] md:bg-contain md:bg-right md:bg-no-repeat"
className="relative mx-auto max-w-7xl border border-t-0 border-b-0 border-gray-100 bg-white overflow-hidden bg-contain bg-right bg-no-repeat"
style={{ backgroundImage: "url('/images/agents.webp')", backgroundSize: "contain" }}
>
{/* Inner padding */}
<div className="px-6 pt-4 pb-12 lg:py-24">
{/* Mobile-only hero image */}
<img
src="/images/mobile/agents.jpg"
alt="Mycelium Agents visual"
className="mb-8 w-full object-cover md:hidden"
/>
<div className="max-w-xl lg:pl-6">
<Eyebrow>MYCELIUM AGENTS - COMING IN 2026</Eyebrow>
<div className="px-6 py-16 lg:py-16">
<div className="max-w-2xl lg:pl-6">
<Eyebrow>MYCELIUM AGENTS</Eyebrow>
<H3 as="h1" className="mt-4">
Private, Sovereign and Distributed AI You Control
Sovereign AI Agents, Coming Soon.
</H3>
<P className="mt-6 text-gray-600">
Mycelium Agents let you deploy and run intelligent systems on your own infrastructure.
</P>
<P className="mt-4 text-gray-600">
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>
<p className="mt-6 text-lg">
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.
</p>
<p className="mt-4 lg:text-base italic text-gray-600 text-sm">
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">
{/* TODO: Hero CTAs (Follow Development / Explore Docs) to be added when links are ready.
Previously two Buttons here with href="#". */}
<Button href="#" variant="solid" color="cyan">
Follow Deployment
</Button>
<Button href="#" variant="outline">
Explore Docs <span aria-hidden="true"></span>
</Button>
</div>
</div>
</div>

View File

@@ -1,90 +0,0 @@
import { Small } from "@/components/Texts";
import { Eyebrow, H3, P } from "@/components/Texts";
const highlights = [
{
label: "Local Execution",
title: "Agents run entirely inside your environment.",
description:
"Models, logic, and memory stay within your own trusted hardware, never behind third-party APIs.",
},
{
label: "Mesh Connectivity",
title: "They communicate peer-to-peer across trusted nodes.",
description:
"Agents form direct encrypted paths between environments, without relays or central servers.",
},
{
label: "Private Data Access",
title: "They use your data without sending it elsewhere.",
description:
"Your datasets, embeddings, and context never leave your boundaries. Processing stays local.",
},
{
label: "Portability",
title: "They move with you, not with a cloud provider.",
description:
"Agents follow your devices, networks, and workflows, remaining sovereign across every location.",
},
];
export function AgentPro() {
return (
<section className="relative w-full bg-[#FDFDFD] overflow-hidden">
{/* Top spacing line */}
<div className="max-w-7xl bg-[#FDFDFD] mx-auto py-6 border border-t-0 border-b-0 border-gray-100"></div>
<div className="w-full border-t border-l border-r border-gray-100" />
{/* Intro Block */}
<div className="bg-white w-full max-w-7xl mx-auto border border-t-0 border-b-0 border-gray-100">
<div className="px-8 py-12 max-w-4xl mx-auto flex flex-col items-center justify-center min-h-[220px] text-center">
<Eyebrow className="text-cyan-500">
Advantages
</Eyebrow>
<H3 className="mt-4 text-black">
Why Its Different
</H3>
<P className="mt-4 text-gray-700 text-base leading-relaxed">
Most AI systems run on centralized clouds, where the models, data, and
logic operate behind third-party APIs. Mycelium Agents flip that
architecture, it runs entirely inside your environment so control,
privacy, and autonomy stay with you.
</P>
</div>
{/* Grid */}
<div className="grid lg:grid-cols-4">
{highlights.map((item) => (
<div
key={item.title}
className="group relative overflow-hidden border border-gray-100 bg-white p-8 transition hover:border-cyan-400/40 hover:bg-white"
>
{/* Glow */}
<div className="absolute inset-0 bg-linear-to-br from-cyan-200/0 via-cyan-100/20 to-cyan-300/20 opacity-0 transition group-hover:opacity-100" />
<div className="relative">
<Small className="text-xs uppercase tracking-[0.16em] text-cyan-600">
{item.label}
</Small>
<h3 className="mt-4 text-lg font-semibold leading-tight text-black">
{item.title}
</h3>
<p className="mt-4 text-base leading-relaxed text-gray-600">
{item.description}
</p>
</div>
</div>
))}
</div>
</div>
{/* Bottom spacing */}
<div className="w-full border-b border-gray-100 bg-[#FDFDFD]" />
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-100" />
</section>
);
}

View File

@@ -1,120 +0,0 @@
"use client";
import { Eyebrow, P, CT, CP, H3} from "@/components/Texts";
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 with 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,
},
{
title: "Blend local + remote intelligence",
description:
"Let lightweight agents run locally while offloading heavy tasks to trusted nodes, maintaining privacy and performance balance.",
icon: CpuChipIcon,
},
];
export function AgentUsecase() {
return (
<section className="w-full max-w-8xl mx-auto bg-transparent">
{/* Top horizontal spacing line */}
<div className="max-w-7xl 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 framed section */}
<div className="max-w-7xl bg-white mx-auto py-12 border border-t-0 border-b-0 border-gray-100">
<div className="mx-auto max-w-3xl sm:text-center px-6 lg:px-8">
{/* Intro block (from isIntro item) */}
{networkUseCases[0].isIntro && (
<>
<Eyebrow className="text-cyan-500">{networkUseCases[0].eyebrow}</Eyebrow>
<H3
className="mt-4 text-gray-900"
>
{networkUseCases[0].title}
</H3>
<P className="mt-4 text-lg text-gray-600">
{networkUseCases[0].description}
</P>
</>
)}
</div>
{/* Grid of features (excluding intro) */}
<ul
role="list"
className="mx-auto mt-8 max-w-6xl grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 md:gap-y-10 px-6"
>
{networkUseCases.slice(1).map((item, idx) => (
<li
key={idx}
className="rounded-2xl border border-gray-200 p-8 transition-all duration-300 ease-in-out hover:scale-[1.03] hover:border-cyan-500 hover:shadow-lg hover:shadow-cyan-500/20 bg-white"
>
{/* Icon */}
{item.icon && (
<div className="h-10 w-10 mb-2 flex items-center justify-center rounded-xl bg-gray-100">
<item.icon className="h-6 w-6 text-cyan-500" />
</div>
)}
{/* Title */}
<CT className="leading-normal text-gray-900">
{item.title}
</CT>
{/* Description */}
<CP className="mt-2 text-gray-600 text-sm leading-snug">
{item.description}
</CP>
</li>
))}
</ul>
</div>
{/* Bottom horizontal line */}
<div className="w-full border-b border-gray-100" />
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-100"></div>
</section>
);
}

View File

@@ -1,12 +1,10 @@
import { AnimatedSection } from '../../components/AnimatedSection'
import { DeploySection } from './DeploySection'
import { GallerySection } from './GallerySection'
import { Companies } from './Companies'
import { AgentBento } from './AgentBento'
import { AgentHeroAlt } from './AgentHeroAlt'
import { CallToAction } from './CallToAction'
import { AgentUsecase } from './AgentUseCase'
import { AgentPro } from './AgentPro'
export default function AgentsPage() {
return (
@@ -24,17 +22,13 @@ export default function AgentsPage() {
</AnimatedSection>
<AnimatedSection>
<AgentUsecase />
<GallerySection />
</AnimatedSection>
<AnimatedSection>
<AgentBento />
</AnimatedSection>
<AnimatedSection>
<AgentPro />
</AnimatedSection>
<AnimatedSection>
<CallToAction />
</AnimatedSection>

View File

@@ -13,70 +13,37 @@ export function CallToAction() {
{/* ✅ Main boxed area */}
<div
id="get-started"
className="relative py-18 max-w-7xl mx-auto overflow-hidden bg-[#111111] border border-t-0 border-b-0 border-gray-800"
className="relative py-18 max-w-7xl mx-auto bg-[#111111] border border-t-0 border-b-0 border-gray-800"
>
{/* ✅ 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.3"
/>
<defs>
<radialGradient id="mycelium-cyan-glow">
<stop stopColor="#00e5ff" />
<stop offset="1" stopColor="transparent" />
</radialGradient>
</defs>
</svg>
<Container className="relative">
<div className="mx-auto max-w-3xl text-center">
<h2 className="text-3xl lg:text-4xl font-medium tracking-tight text-white sm:text-4xl">
Start with Mycelium Today
Start Building the Future of Sovereign AI
</h2>
<p className="mt-6 lg:text-lg text-base leading-normal text-gray-300">
The Agent Framework launches in H1 2026, but the foundation is ready now.
</p>
<p className="mt-2 lg:text-lg text-base leading-normal text-gray-300">
Use todays components models, storage, compute, and network to deploy workloads, connect nodes, and prepare for the next generation of distributed AI.
<p className="mt-6 text-lg text-gray-300">
Use todays components models, storage, compute, mesh and step into agents as they arrive.
</p>
{/* ✅ Button row same structure as homepage CTA */}
<div className="mt-10 flex flex-wrap justify-center items-center gap-x-6 gap-y-4">
<Button
as="a"
to="https://myceliumcloud.tf"
variant="solid"
color="cyan"
target="_blank"
rel="noopener noreferrer"
>
Deploy a Model
</Button>
{/* ✅ 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="/deploy" variant="solid" color="cyan" className="mt-4">
Deploy a Model
</Button>
</div>
<Button to="/nodes" variant="outline" color="white">
Host a Node
</Button>
<div className="flex flex-col items-center text-center max-w-xs">
<Button to="/host" as="a" variant="outline" color="white" className="mt-4">
Host a Node
</Button>
</div>
<Button
as="a"
to="https://threefold.info/mycelium_network/docs/"
variant="link"
color="white"
className="inline-flex items-center gap-1.5"
target="_blank"
rel="noopener noreferrer"
>
Follow Development
<span aria-hidden="true"></span>
</Button>
<div className="flex flex-col items-center text-center max-w-xs">
<Button to="https://threefold.info/mycelium_network/docs/" as="a" target="_blank" variant="outline" color="white" className="mt-4">
Follow Development
</Button>
</div>
</div>
</div>
</Container>

View File

@@ -37,7 +37,7 @@ const row2 = logos.slice(6);
export function Companies() {
return (
<div id="companies" className="relative bg-[#121212] flex flex-col items-center justify-center w-full overflow-hidden antialiased py-4">
<div id="companies" className="relative bg-[#121212] flex flex-col items-center justify-center w-full overflow-hidden antialiased py-4 mb-12">
<div className="relative z-10 mx-auto w-full max-w-7xl p-4">
{/* Logos grid */}
<div className="flex flex-col items-center gap-y-6 text-white ">

View File

@@ -4,6 +4,7 @@ import { useEffect, useMemo, useState } from 'react'
import { useResponsiveCarousel } from '@/hooks/useResponsiveCarousel';
import { motion, AnimatePresence } from 'framer-motion'
import { wrap } from 'popmotion'
import { Button } from '@/components/Button';
import { SectionHeader, P, Eyebrow, CP } from '@/components/Texts';
import { TypeAnimation } from 'react-type-animation'
import { FadeIn } from '@/components/FadeIn';
@@ -146,8 +147,9 @@ export function GallerySection() {
repeat={0}
/>
</CP>
{/* TODO: Desktop CTA (Start) button to be added when link target is defined.
Previously: <Button href="#" color="cyan">Start</Button> */}
<Button href="#" color="cyan" className="text-sm px-4 py-2 lg:text-base whitespace-nowrap">
Start
</Button>
</div>
</div>
</section>
@@ -164,8 +166,9 @@ export function GallerySection() {
repeat={0}
/>
</CP>
{/* TODO: Mobile CTA (Start) button to be added when link target is defined.
Previously: <Button href="#" color="cyan">Start</Button> */}
<Button href="#" color="cyan" className="text-xs px-3 py-1.5 whitespace-nowrap">
Start
</Button>
</div>
</div>
</FadeIn>

View File

@@ -1,264 +0,0 @@
"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 lg:-translate-y-6" 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>
);
}

View File

@@ -1,263 +0,0 @@
"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 lg:-translate-y-6" 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>
);
}

View File

@@ -1,238 +0,0 @@
"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, a distributed long-term AI memory"
style={{ background: bg }}
>
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full lg:-translate-y-16" 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>
);
}

View File

@@ -1,286 +0,0 @@
"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 lg:-translate-y-18" 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>
);
}

View File

@@ -1,231 +0,0 @@
"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 lg:-translate-y-16" 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>
);
}

View File

@@ -1,231 +0,0 @@
"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, a secure communication network"
style={{ background: bg }}
>
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full lg:-translate-y-4" 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>
);
}

View File

@@ -13,28 +13,8 @@ export function CallToAction() {
{/* ✅ Main boxed area */}
<div
id="get-started"
className="relative py-18 max-w-7xl overflow-hidden mx-auto bg-[#111111] border border-t-0 border-b-0 border-gray-800"
className="relative py-18 max-w-7xl mx-auto bg-[#111111] border border-t-0 border-b-0 border-gray-800"
>
{/* ✅ 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.3"
/>
<defs>
<radialGradient id="mycelium-cyan-glow">
<stop stopColor="#00e5ff" />
<stop offset="1" stopColor="transparent" />
</radialGradient>
</defs>
</svg>
<Container className="relative">
@@ -52,21 +32,13 @@ export function CallToAction() {
{/* ✅ 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="/nodes" variant="solid" color="cyan" className="mt-4">
<Button to="/host" variant="solid" color="cyan" className="mt-4">
Host a Node
</Button>
</div>
<div className="flex flex-col items-center text-center max-w-xs">
<Button
as="a"
to="https://myceliumcloud.tf"
variant="outline"
color="white"
className="mt-4"
target="_blank"
rel="noopener noreferrer"
>
<Button to="/cloud" variant="outline" color="white" className="mt-4">
Start Deploying
</Button>
</div>

View File

@@ -40,7 +40,7 @@ export function CloudArchitecture() {
<div className="relative mx-auto max-w-7xl border border-t-0 border-b-0 border-gray-800 bg-[#111111] py-12">
<Container>
<div className="mx-auto max-w-4xl sm:text-center">
<Eyebrow className="">ARCHITECTURE</Eyebrow>
<Eyebrow className="text-cyan-400">ARCHITECTURE</Eyebrow>
<H3 className="text-3xl lg:text-4xl font-medium tracking-tight text-white">
How Mycelium Cloud Works

View File

@@ -20,14 +20,14 @@ export function CloudBluePrint() {
<div className="max-w-7xl bg-white mx-auto py-12 border border-t-0 border-b-0 border-gray-100">
<Container>
<div className="mx-auto max-w-4xl sm:text-center">
<Eyebrow className="text-cyan-400">Featured Blueprint</Eyebrow>
<Eyebrow className="text-cyan-500">Featured Blueprint</Eyebrow>
<H3 className="text-3xl lg:text-4xl font-medium tracking-tight text-gray-900 mt-2">
Your Personal Sovereign Cloud Workspace
</H3>
<P className="mt-6 text-lg text-gray-600">
Digital Me is an example environment built to demonstrate whats possible on top of the Mycelium Stack, which is a full personal cloud you can deploy, customize, or extend. Your files, communication, apps, and optional AI agent, all running privately on infrastructure you choose.
Digital Me is an example environment built to demonstrate whats possible on top of the Mycelium Stack a full personal cloud you can deploy, customize, or extend. Your files, communication, apps, and optional AI agent, all running privately on infrastructure you choose.
</P>
</div>

View File

@@ -1,112 +0,0 @@
"use client";
import { useState } from "react";
const files = [
{
id: "kube",
label: "kubernetes.yaml",
code: `apiVersion: apps/v1
kind: Deployment
metadata:
name: mycelium-app
spec:
replicas: 3
selector:
matchLabels:
app: mycelium-app
template:
metadata:
labels:
app: mycelium-app`,
},
{
id: "vdc",
label: "vdc.tf",
code: `provider "mycelium" {
identity = "~/.mycelium/id"
}
resource "mycelium_vdc" "production" {
name = "prod-vdc"
region = "eu-central"
nodes = 6
cpu_cores = 24
ram_gb = 128
storage = "10TB"
network_policies = ["private", "encrypted"]
}`,
},
{
id: "qsfs",
label: "qsfs.py",
code: `from qsfs import QSFS
# mount encrypted distributed filesystem
fs = QSFS.mount("/mnt/secure", key="my-private-key")
# write protected research data
with fs.open("dataset/raw-images/img001.png", "wb") as f:
f.write(b"...binary data...")
# list stored files via S3/IPFS/WebDAV compatibility layer
files = fs.list("dataset/raw-images/")
print("Stored files:", files)`,
},
];
export function CloudCodeTabs() {
const [active, setActive] = useState("kube");
const file = files.find((f) => f.id === active)!;
return (
<div className="sm:px-6 lg:px-0">
<div className="relative isolate overflow-hidden bg-cyan-600 px-6 pt-8 sm:mx-auto sm:max-w-2xl sm:rounded-md sm:pt-16 sm:pr-0 sm:pl-16 lg:mx-0 lg:max-w-none">
{/* Cyan skew background */}
<div
aria-hidden="true"
className="absolute -inset-y-px -left-3 -z-10 w-full origin-bottom-left skew-x-[-30deg] bg-cyan-500 opacity-20 ring-1 ring-white ring-inset"
/>
<div className="mx-auto max-w-2xl sm:mx-0 sm:max-w-none">
<div className="w-screen overflow-hidden rounded-tl-xl bg-[#121212] ring-1 ring-white/10">
{/* File Tabs */}
<div className="flex bg-gray-800/40 ring-1 ring-white/5">
<div className="-mb-px flex text-sm font-medium text-gray-400">
{files.map((f) => (
<button
key={f.id}
onClick={() => setActive(f.id)}
className={`px-4 py-2 border-r border-white/10 transition ${
active === f.id
? "border-b border-b-white/20 bg-white/5 text-white"
: "hover:text-white"
}`}
>
{f.label}
</button>
))}
</div>
</div>
{/* Code Block */}
<div className="px-6 pt-6 pb-14 font-mono text-xs leading-relaxed text-gray-200 whitespace-pre overflow-x-auto">
{file.code}
</div>
</div>
</div>
{/* Outer ring */}
<div
aria-hidden="true"
className="pointer-events-none absolute inset-0 ring-1 ring-white/10 ring-inset sm:rounded-3xl"
/>
</div>
</div>
);
}

View File

@@ -254,8 +254,8 @@ function CloudFeaturesDesktop() {
className={clsx(
'relative rounded-2xl outline-2 transition-all duration-300 ease-in-out hover:scale-105 hover:bg-gray-800/30 ml-16',
selectedIndex === featureIndex
? 'outline-cyan-400'
: 'outline-transparent hover:outline-cyan-400',
? 'outline-cyan-500'
: 'outline-transparent hover:outline-cyan-500',
)}
>
{featureIndex === selectedIndex && (

View File

@@ -153,8 +153,8 @@ function CloudFeaturesDesktop() {
className={clsx(
'relative rounded-2xl outline-2 transition-all duration-300 ease-in-out hover:scale-105 hover:bg-gray-100',
selectedIndex === featureIndex
? 'outline-cyan-400'
: 'outline-transparent hover:outline-cyan-400',
? 'outline-cyan-500'
: 'outline-transparent hover:outline-cyan-500',
)}
>
{featureIndex === selectedIndex && (
@@ -257,7 +257,7 @@ export function CloudFeaturesLight() {
</SectionHeader>
<P className="mt-6 text-gray-600">
Mycelium Cloud runs Kubernetes on a sovereign, self-healing network
with compute, storage, and networking built in, so you dont need
with compute, storage, and networking built in so you dont need
external cloud dependencies.
</P>
</div>

View File

@@ -1,59 +1,49 @@
import { H3, Eyebrow, P } from "@/components/Texts"
import { H3, Eyebrow } from "@/components/Texts"
import { Button } from "@/components/Button"
export function CloudHeroNew() {
export function CloudHeroNew({ onGetStartedClick = () => {} }: { onGetStartedClick?: () => void }) {
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 md:bg-[url('/images/cloudhero4.webp')] md:bg-contain md:bg-right md:bg-no-repeat"
className="relative mx-auto max-w-7xl border border-t-0 border-b-0 border-gray-100 bg-white overflow-hidden bg-contain bg-right bg-no-repeat"
style={{ backgroundImage: "url('/images/cloudhero4.webp')", backgroundSize: "contain" }}
>
{/* Inner padding */}
<div className="px-6 pt-4 pb-12 lg:py-24">
{/* Mobile-only hero image */}
<img
src="/images/mobile/cloudpage.jpg"
alt="Mycelium Cloud page visual"
className="mb-8 w-full object-cover md:hidden"
/>
<div className="px-6 py-16 lg:py-16">
<div className="max-w-2xl lg:pl-6">
<Eyebrow>
MYCELIUM CLOUD
Mycelium Cloud
</Eyebrow>
<H3 className="mt-4">
Sovereign Edge Cloud Infrastructure
Run Kubernetes on the Sovereign Agentic Cloud
</H3>
<P className="mt-6 text-gray-600">
Run compute, storage, and AI resources on infrastructure you control.
The Mycelium Cloud runs on a distributed grid of independent nodes,
delivering secure, scalable performance wherever your users or data live.
</P>
<p className="mt-6 text-lg">
Deploy K3s clusters on a global, self-healing mesh network.
Your workloads run on sovereign, encrypted infrastructure, without centralized cloud control.
</p>
<p className="mt-4 lg:text-base italic text-gray-600 text-sm">
Works Alone. Works Together.
Mycelium Cloud can run on any network fabric, or pair with Mycelium Network
for sovereign connectivity.
</p>
<div className="mt-10 flex items-center gap-x-6">
<Button
as="a"
to="https://myceliumcloud.tf"
onClick={onGetStartedClick}
variant="solid"
color="cyan"
target="_blank"
rel="noopener noreferrer"
>
Deploy Workloads
Get started
</Button>
<Button
as="a"
to="https://myceliumcloud.tf/docs"
variant="outline"
target="_blank"
rel="noopener noreferrer"
>
Explore Docs <span aria-hidden="true"></span>
<Button to="#" variant="outline">
Documentation <span aria-hidden="true"></span>
</Button>
</div>
</div>
</div>
</div>
{/* Bottom horizontal line with spacing */}
{/* 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>

View File

@@ -13,6 +13,7 @@ import { Eyebrow, H3, H4 } from "@/components/Texts"
const product = {
subtitle: 'capabilities',
name: 'What You Can Run on Mycelium Cloud',
description: '<p>Host nodes, deploy workloads, or build private AI systems, all on infrastructure you own and control.</p>',
details: [
{
name: 'Kubernetes Clusters',
@@ -63,6 +64,9 @@ export function CloudHostingNew() {
<div className="mt-4 text-gray-300 text-xl"
dangerouslySetInnerHTML={{ __html: product.description }}
/>
{/* ✅ Details accordion */}

View File

@@ -1,214 +0,0 @@
"use client";
import { useState } from "react";
import { Eyebrow, H3, P } from "@/components/Texts";
import { Button } from "@/components/Button";
const tabs = [
{
id: "kubernetes",
label: "Managed Kubernetes",
content: {
item: "Managed Kubernetes",
desc:
"Create and manage clusters across distributed environments using standard Kubernetes tools.",
bullets: [
"Create and manage clusters on distributed nodes",
"Run workloads at the edge or across enterprise sites",
"Keep full ownership of data and orchestration",
"Use the Kubernetes ecosystem without modification",
],
},
},
{
id: "vdc",
label: "Virtual Data Centers",
content: {
item: "Virtual Data Centers",
desc:
"Provision and manage full cloud environments without owning or maintaining servers.",
bullets: [
"Create dedicated environments for applications, databases, and internal services",
"Add or remove compute and storage resources instantly",
"Migrate workloads from cloud or on-prem systems",
"Meet compliance requirements by selecting where data resides",
"Benefit from continuous monitoring and automated recovery",
],
},
},
{
id: "qsfs",
label: "Quantum Safe File System (QSFS)",
content: {
item: "Quantum Safe File System (QSFS)",
desc:
"Encrypted, redundant storage designed for high-security and high-availability workloads. Data is distributed across independent nodes and remains accessible even during failures or outages.",
bullets: [
"Secure file storage with quantum-safe encryption",
"Distributed replication for durability",
"Standard protocol support: S3, IPFS, WebDAV",
"Automatic scaling as data grows",
"Consistent performance for research, enterprise, and AI workloads",
],
},
},
];
const tabButtons = {
kubernetes: {
primary: "Deploy a Cluster",
secondary: "Learn More",
},
vdc: {
primary: "Follow Development",
secondary: "Learn More",
},
qsfs: {
primary: "View Docs",
secondary: "Explore Integration",
},
} as const;
export function CloudIntro() {
const [active, setActive] = useState("kubernetes");
const current = tabs.find((t) => t.id === active)!.content;
const currentButtons = tabButtons[active as keyof typeof tabButtons];
const primaryLinks: Record<string, string | undefined> = {
kubernetes: "https://myceliumcloud.tf",
vdc: undefined,
qsfs: undefined,
};
const secondaryLinks: Record<string, string | undefined> = {
kubernetes: "https://myceliumcloud.tf/docs",
vdc: "https://threefold.info/mycelium_vdc/docs/",
qsfs: undefined,
};
return (
<section className="relative w-full bg-[#121212] overflow-hidden">
{/* Top Spacing Border */}
<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="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">
{/* ================================
Header
================================= */}
<div className="mb-16">
<Eyebrow color="accent">Capabilities</Eyebrow>
<H3 color="white">What You Can Run on Mycelium Cloud</H3>
<P className="max-w-3xl text-gray-200 mt-6">
Host nodes, deploy workloads, or build private AI systems all on
infrastructure you own and control. Mycelium gives you scalable compute,
secure storage, and sovereign orchestration without depending on
hyperscalers.
</P>
</div>
{/* ================================
Two-column layout
================================= */}
<div className="flex flex-col lg:flex-row gap-16">
{/* Left: Code UI */}
<div className="w-full lg:w-1/2">
<img
src="/images/cloud/reserve.png"
alt="Mycelium Cloud reserve"
className="w-full h-auto rounded-xl border border-white/10 object-cover"
/>
</div>
{/* Right: Tabs */}
<div className="w-full lg:w-1/2 text-white">
{/* Tabs Navigation */}
<div className="flex gap-6 border-b border-white/10 pb-2">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActive(tab.id)}
className={`text-sm font-medium tracking-wide leading-tight pb-2 ${
active === tab.id
? "border-b-2 border-cyan-500 text-white"
: "text-gray-400 hover:text-white"
}`}
>
{tab.label}
</button>
))}
</div>
{/* Tab Content */}
<div className="mt-6 space-y-6">
<div>
<p className="text-lg lg:text-xl font-medium text-white">{current.item}</p>
<p className="mt-2 text-base text-gray-300 leading-relaxed">
{current.desc}
</p>
</div>
<div className="mt-4 space-y-2">
<p className="text-sm uppercase tracking-wide text-cyan-500 font-semibold">
Key capabilities
</p>
<ul className="space-y-2">
{current.bullets.map((b, i) => (
<li key={i} className="text-base text-gray-400 flex gap-2">
<span className="text-cyan-500"></span>
{b}
</li>
))}
</ul>
</div>
{currentButtons && (
<div className="mt-8 flex flex-wrap gap-4">
<Button
as={primaryLinks[active] ? "a" : undefined}
to={primaryLinks[active] ?? "#"}
variant="solid"
color="cyan"
{...(primaryLinks[active]
? { target: "_blank", rel: "noopener noreferrer" }
: {})}
>
{currentButtons.primary}
</Button>
<Button
as={secondaryLinks[active] ? "a" : undefined}
to={secondaryLinks[active] ?? "#"}
variant="outline"
color="white"
{...(secondaryLinks[active]
? { target: "_blank", rel: "noopener noreferrer" }
: {})}
>
{currentButtons.secondary}
</Button>
</div>
)}
</div>
</div>
</div>
</div>
{/* Bottom Borders */}
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-800" />
<div className="w-full border-b border-gray-800" />
</section>
);
}

View File

@@ -1,8 +1,11 @@
import { AnimatedSection } from '../../components/AnimatedSection'
import { CloudArchitecture } from './CloudArchitecture'
import { CloudUseCases } from './CloudUseCases'
import { CloudHeroNew } from './CloudHeroNew'
import { CloudBluePrint } from './CloudBluePrint'
import { CallToAction } from './CalltoAction'
import { CloudIntro } from './CloudIntro'
import { CloudPros } from './CloudPros'
import { CloudHostingNew } from './CloudHostingNew'
import { CloudFeaturesLight } from './CloudFeaturesLight'
export default function CloudPage() {
@@ -14,11 +17,23 @@ export default function CloudPage() {
</AnimatedSection>
<AnimatedSection>
<CloudIntro />
<CloudHostingNew />
</AnimatedSection>
<AnimatedSection>
<CloudPros />
<CloudFeaturesLight />
</AnimatedSection>
<AnimatedSection>
<CloudArchitecture />
</AnimatedSection>
<AnimatedSection>
<CloudUseCases />
</AnimatedSection>
<AnimatedSection>
<CloudBluePrint />
</AnimatedSection>
<AnimatedSection>

View File

@@ -1,89 +0,0 @@
import { H3, P, Eyebrow, Small } from "@/components/Texts";
const highlights = [
{
label: "Local Execution",
title: "Agents run entirely inside your environment.",
description:
"Models, logic, and memory stay within your own trusted hardware—never behind third-party APIs.",
},
{
label: "Mesh Connectivity",
title: "They communicate peer-to-peer across trusted nodes.",
description:
"Agents form direct encrypted paths between environments, without relays or central servers.",
},
{
label: "Private Data Access",
title: "They use your data without sending it elsewhere.",
description:
"Your datasets, embeddings, and context never leave your boundaries—processing stays local.",
},
{
label: "Portability",
title: "They move with you, not with a cloud provider.",
description:
"Agents follow your devices, networks, and workflows, remaining sovereign across every location.",
},
];
export function CloudPros() {
return (
<section className="relative w-full bg-[#FDFDFD] overflow-hidden">
{/* Top spacing line */}
<div className="max-w-7xl bg-[#FDFDFD] mx-auto py-6 border border-t-0 border-b-0 border-gray-100"></div>
<div className="w-full border-t border-l border-r border-gray-100" />
{/* Intro Block */}
<div className="bg-white w-full max-w-7xl mx-auto border border-t-0 border-b-0 border-gray-100">
<div className="px-8 py-12 max-w-4xl">
<Eyebrow className="uppercase tracking-[0.16em] text-cyan-400">
Cloud Advantages
</Eyebrow>
<H3 className="mt-4 text-black">
Why Its Different
</H3>
<P className="mt-4 text-gray-700 leading-relaxed">
Most AI systems run on centralized clouds, where the models, data, and
logic operate behind third-party APIs. Mycelium Agents flip that
architecture, running entirely inside your environment so control,
privacy, and autonomy stay with you.
</P>
</div>
{/* Grid */}
<div className="grid lg:grid-cols-4">
{highlights.map((item) => (
<div
key={item.title}
className="group relative overflow-hidden border border-gray-100 bg-white p-8 transition hover:border-cyan-400/40 hover:bg-white"
>
{/* Glow */}
<div className="absolute inset-0 bg-linear-to-br from-cyan-200/0 via-cyan-100/20 to-cyan-300/20 opacity-0 transition group-hover:opacity-100" />
<div className="relative">
<Small className="text-xs uppercase tracking-[0.16em] text-cyan-600">
{item.label}
</Small>
<h3 className="mt-4 text-lg font-semibold leading-tight text-black">
{item.title}
</h3>
<p className="mt-4 text-base leading-relaxed text-gray-600">
{item.description}
</p>
</div>
</div>
))}
</div>
</div>
{/* Bottom spacing */}
<div className="w-full border-b border-gray-100 bg-[#FDFDFD]" />
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-100" />
</section>
);
}

View File

@@ -48,7 +48,7 @@ export function CloudUseCases() {
<div className="max-w-7xl bg-white mx-auto py-12 border border-t-0 border-b-0 border-gray-100">
<Container>
<div className="mx-auto max-w-4xl sm:text-center">
<Eyebrow className="text-cyan-400">USE CASES</Eyebrow>
<Eyebrow className="text-cyan-500">USE CASES</Eyebrow>
<H3 className="text-3xl lg:text-4xl font-medium tracking-tight text-gray-900">
Built for intelligent workloads across every edge.

View File

@@ -13,28 +13,8 @@ export function CallToAction() {
{/* ✅ Main boxed area */}
<div
id="get-started"
className="relative py-18 max-w-7xl overflow-hidden mx-auto bg-[#111111] border border-t-0 border-b-0 border-gray-800"
className="relative py-18 max-w-7xl mx-auto bg-[#111111] border border-t-0 border-b-0 border-gray-800"
>
{/* ✅ 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.3"
/>
<defs>
<radialGradient id="mycelium-cyan-glow">
<stop stopColor="#00e5ff" />
<stop offset="1" stopColor="transparent" />
</radialGradient>
</defs>
</svg>
<Container className="relative">
@@ -43,7 +23,7 @@ export function CallToAction() {
Choose How You Want to Start
</h2>
<p className="mt-6 lg:text-lg text-base leading-normal text-gray-300">
<p className="mt-6 text-lg text-gray-300">
Host your own node to contribute capacity or deploy workloads using the Mycelium Cloud.
You dont need to host before deploying, and you dont need to deploy before hosting.

View File

@@ -2,6 +2,7 @@
import { Container } from '@/components/Container'
import { Eyebrow, H3, P } from '@/components/Texts'
import { Button } from '@/components/Button'
const capabilities = [
{
@@ -75,8 +76,14 @@ export function ComputeCapabilities() {
</ul>
{/* Button section */}
{/* TODO: CTA buttons (Get Started / Explore Docs) to be re-enabled when real links are available.
Previously rendered here as two Buttons with href="#". */}
<div className="mx-auto mt-12 flex justify-center gap-6">
<Button variant="solid" color="cyan" href="#">
Get Started
</Button>
<Button variant="outline" color="white" href="#">
Explore Docs
</Button>
</div>
</Container>
</div>

View File

@@ -78,8 +78,12 @@ export function ComputeCapabilitiesNew() {
{/* Arrows inside first card */}
<div className="flex items-center gap-x-4 mt-2">
{/* TODO: Intro card CTA (Learn more) to be added here when destination is defined.
Previously: <a href="#" className="inline-flex items-center ...">Learn more →</a> */}
<a
href="#"
className="inline-flex items-center gap-1 text-cyan-400 hover:text-cyan-300 text-sm font-medium mr-auto"
>
Learn more
</a>
<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"

View File

@@ -33,7 +33,7 @@ export function ComputeFeatures() {
</P>
<P className="mt-3 text-lg text-gray-600">
Each component, from message passing to content distribution works in harmony
Each component from message passing to content distribution works in harmony
to create a fully self-healing, self-optimizing data mesh.
</P>
</div>

View File

@@ -35,8 +35,9 @@ export function ComputeHero({ onGetStartedClick = () => {} }: { onGetStartedClic
>
Get started
</Button>
{/* TODO: Secondary CTA (Documentation) link to be wired when docs route is ready.
Previously: <Button to="#" variant="outline">Documentation →</Button> */}
<Button to="#" variant="outline">
Documentation <span aria-hidden="true"></span>
</Button>
</div>
</div>
</div>

View File

@@ -34,11 +34,11 @@ const tabs = [
},
{
item: "Zero cloud lock-in",
desc: "Deploy containers, VMs, or full Kubernetes clusters, migrate off AWS/GCP/Azure with no code changes.",
desc: "Deploy containers, VMs, or full Kubernetes clusters migrate off AWS/GCP/Azure with no code changes.",
},
{
item: "Encrypted networking",
desc: "All services communicate through Mycelium Mesh without VPNs, no exposed ports.",
desc: "All services communicate through Mycelium Mesh — no VPNs, no exposed ports.",
},
],
},
@@ -48,11 +48,11 @@ const tabs = [
content: [
{
item: "Distributed workloads",
desc: "Run compute where data lives; homes, factories, hospitals, or remote regions.",
desc: "Run compute where data lives homes, factories, hospitals, or remote regions.",
},
{
item: "Offline-first resilience",
desc: "Nodes keep working even with weak internet or outages, ideal for mission-critical edge.",
desc: "Nodes keep working even with weak internet or outages ideal for mission-critical edge.",
},
{
item: "Global deployment, local data",
@@ -85,7 +85,7 @@ export function ComputeUseCases() {
<P className="max-w-3xl text-gray-400 mt-6">
Mycelium Compute is a decentralized physical infrastructure network
(DePIN) for high-performance workloads. Run reproducible AI/ML
pipelines, host self-healing applications, or deploy to the edge, all
pipelines, host self-healing applications, or deploy to the edge all
on a fabric thats more resilient and private than the cloud.
</P>
</div>

View File

@@ -36,12 +36,8 @@ const features = [
export function DevHub() {
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-6 py-12 bg-[#111111] border border-t-0 border-b-0 border-gray-800 max-w-7xl mx-auto">
<div className="bg-[#121212] py-24 sm:py-32">
<div className="mx-auto max-w-7xl px-6 lg:px-8">
<div className="mx-auto grid max-w-2xl grid-cols-1 gap-x-8 gap-y-16 sm:gap-y-20 lg:mx-0 lg:max-w-none lg:grid-cols-5">
<div className="col-span-2">
<h2 className="mb-2 text-base font-semibold leading-7 text-cyan-500">Get Started</h2>
@@ -67,11 +63,7 @@ export function DevHub() {
))}
</dl>
</div>
</div>
{/* Bottom horizontal line with spacing (match NetworkDownload) */}
<div className="w-full border-b border-gray-800" />
<div className="max-w-7xl bg-transparent mx-auto py-6 border border-t-0 border-b-0 border-gray-800"></div>
</section>
</div>
</div>
)
}

View File

@@ -40,14 +40,14 @@ const features = [
export function DownloadHero() {
return (
<section id="download" className="w-full max-w-8xl mx-auto bg-transparent">
<div className="mx-auto max-w-7xl px-6 lg:px-8 bg-white pt-24 pb-12 border border-t-0 border-b-0 border-gray-100">
<div className="py-16 sm:py-32">
<div className="mx-auto max-w-7xl px-6 lg:px-8">
<div className="mx-auto max-w-2xl lg:mx-0">
<motion.h2
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="text-4xl font-medium tracking-tight text-gray-900 lg:text-5xl"
className="text-5xl font-medium tracking-tight text-gray-900 lg:text-6xl"
>
Download Mycelium Network
</motion.h2>
@@ -55,7 +55,7 @@ export function DownloadHero() {
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.2 }}
className="mt-8 text-lg text-gray-600 lg:leading-8"
className="mt-6 text-lg text-gray-600 lg:leading-8"
>
Get Mycelium Network for Android, Windows, macOS, and iOS to securely connect, store, and interact with the decentralized networkseamlessly and efficiently. Not sure how it works?{' '}
<a
@@ -68,9 +68,8 @@ export function DownloadHero() {
</a>
</motion.p>
</div>
<div className="mx-auto mt-8 max-w-2xl lg:mt-12 lg:max-w-none">
<dl className="grid max-w-xl grid-cols-1 gap-x-8 lg:gap-y-16 gap-y-8 md:grid-cols-2 lg:max-w-none lg:grid-cols-4">
<div className="mx-auto mt-16 max-w-2xl sm:mt-20 lg:mt-24 lg:max-w-none">
<dl className="grid max-w-xl grid-cols-1 gap-x-8 gap-y-16 md:grid-cols-2 lg:max-w-none lg:grid-cols-4">
{features.map((feature) => (
<div
key={feature.name}
@@ -100,10 +99,6 @@ export function DownloadHero() {
</dl>
</div>
</div>
{/* Bottom horizontal line with spacing (match NetworkDownload) */}
<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>
</div>
)
}

View File

@@ -13,28 +13,8 @@ export function CallToAction() {
{/* ✅ Main boxed area */}
<div
id="get-started"
className="relative py-18 max-w-7xl overflow-hidden mx-auto bg-[#111111] border border-t-0 border-b-0 border-gray-800"
className="relative py-18 max-w-7xl mx-auto bg-[#111111] border border-t-0 border-b-0 border-gray-800"
>
{/* ✅ 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.3"
/>
<defs>
<radialGradient id="mycelium-cyan-glow">
<stop stopColor="#00e5ff" />
<stop offset="1" stopColor="transparent" />
</radialGradient>
</defs>
</svg>
<Container className="relative">
@@ -43,7 +23,7 @@ export function CallToAction() {
Choose How You Want to Start
</h2>
<p className="mt-6 lg:text-lg text-base leading-normal text-gray-300">
<p className="mt-6 text-lg text-gray-300">
Use GPUs through Mycelium Cloud, or contribute GPU nodes to the mesh and run your own workloads.
</p>

View File

@@ -40,7 +40,7 @@ export function GpuArchitecture() {
Sovereign Compute Nodes
</h3>
<p className="mt-2 text-gray-600 max-w-2xl">
GPUs run only on hardware you control, eliminating reliance on centralized clouds.
GPUs run only on hardware you control eliminating reliance on centralized clouds.
</p>
<div className="mt-8 h-px w-full bg-cyan-500/50" />
</div>
@@ -52,7 +52,7 @@ export function GpuArchitecture() {
Encrypted Mesh Networking
</h3>
<p className="mt-2 text-gray-600 max-w-2xl">
Nodes form private, encrypted tunnels to workloads, no public exposure required.
Nodes form private, encrypted tunnels to workloads no public exposure required.
</p>
<div className="mt-8 h-px w-full bg-cyan-500/50" />
</div>

Some files were not shown because too many files have changed in this diff Show More