refactor: rename Node page to Nodes and reorganize network page sections

- Renamed NodePage component and directory to NodesPage/nodes for consistency
- Updated all navigation links from "Node" to "Nodes" across headers and footer
- Replaced anchor tags with React Router Link components for proper SPA navigation
- Reorganized NetworkPage component order and added NetworkPros section
- Converted NetworkUsecases from horizontal slider to responsive grid layout
- Added new use cases for adaptive mesh and compute fabric
- Update
This commit is contained in:
2025-11-14 17:01:29 +01:00
parent 3a656ef5e9
commit 326efc9fbd
20 changed files with 139 additions and 64 deletions

View File

@@ -0,0 +1,49 @@
"use client";
import { Container } from "@/components/Container";
import { Button } from "@/components/Button";
export function CallToAction() {
return (
<section className="relative overflow-hidden bg-[#121212]">
<div className="max-w-7xl bg-[#111111] mx-auto py-6 border border-t-0 border-b-0 border-gray-800"></div>
<div className="w-full border-t border-l border-r border-gray-800" />
<div
id="get-started"
className="relative py-18 max-w-7xl mx-auto bg-[#111111] border border-t-0 border-b-0 border-gray-800"
>
<Container className="relative">
<div className="mx-auto max-w-3xl text-center">
<h2 className="text-3xl lg:text-4xl font-medium tracking-tight text-white sm:text-4xl">
Join Mycelium Grid
</h2>
<p className="mt-6 text-lg text-gray-300">
Be part of a global network that powers private, distributed
infrastructure. Host a node, contribute resources, and earn rewards
while expanding the sovereign digital grid.
</p>
<div className="mt-10 flex flex-wrap justify-center gap-x-10 gap-y-8">
<div className="flex flex-col items-center text-center max-w-xs">
<Button to="/host" variant="solid" color="cyan" className="mt-4">
Join Mycelium
</Button>
</div>
<div className="flex flex-col items-center text-center max-w-xs">
<Button to="/docs" variant="outline" color="white" className="mt-4">
Explore docs
</Button>
</div>
</div>
</div>
</Container>
</div>
<div className="w-full border-b border-gray-800" />
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-800 bg-transparent" />
</section>
);
}

View File

@@ -0,0 +1,115 @@
"use client";
import { motion } from "framer-motion";
import { SectionHeader, P, Eyebrow, CT, CP } from "@/components/Texts";
import type { ComponentPropsWithoutRef } from "react";
type CircleIconProps = ComponentPropsWithoutRef<"svg">;
const CircleNumber1Icon = (props: CircleIconProps) => (
<svg viewBox="0 0 24 24" fill="currentColor" {...props}>
<path d="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2zm.994 5.886c-.83-.777-1.755-.394-1.701.404l-.006.114v8l.007.117a1 1 0 001.986 0l.007-.117V9l-.006-.114z" />
</svg>
);
const CircleNumber2Icon = (props: CircleIconProps) => (
<svg viewBox="0 0 24 24" fill="currentColor" {...props}>
<path d="M12 2a10 10 0 110 20 10 10 0 010-20zm1 5h-3a1 1 0 100 2h3v2h-2a2 2 0 00-2 2v2a2 2 0 002 2h3a1 1 0 100-2h-3v-2h2a2 2 0 002-2V9a2 2 0 00-2-2z" />
</svg>
);
const CircleNumber3Icon = (props: CircleIconProps) => (
<svg viewBox="0 0 24 24" fill="currentColor" {...props}>
<path d="M12 2a10 10 0 110 20 10 10 0 010-20zm1 5h-2a2 2 0 00-2 2 1 1 0 102 0h2v2h-2c-1.39 0-2.103 1.67-1.11 2.588l.11.098h3v2h-2a1 1 0 100 2h2a2 2 0 002-2v-2a2 2 0 00-.25-.97l-.068-.115.068-.115A1.99 1.99 0 0015 11V9a2 2 0 00-2-2z" />
</svg>
);
const features = [
{
name: "Build Your Private Setup",
description:
"Run a node in your home or office and use it to host private workloads, models, and data. Experiment, deploy, or tinker locally, then offset your costs by sharing unused capacity with the network.",
icon: CircleNumber1Icon,
},
{
name: "Strengthen the Network",
description:
"Every node adds bandwidth, compute, and resilience to the Mycelium Grid. Together, hosts form the physical foundation for decentralized communication, storage, and AI.",
icon: CircleNumber2Icon,
},
{
name: "Earn Rewards",
description:
"Share storage, CPU, GPU, and bandwidth. When your resources are used, you earn network rewards based on real demand.",
icon: CircleNumber3Icon,
},
];
export function NodeBenefits() {
return (
<section className="bg-[#121212] w-full max-w-8xl mx-auto">
{/* Top spacing + border */}
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-800" />
<div className="w-full border-t border-l border-r border-gray-800" />
<div className="relative px-6 lg:px-12 py-12 bg-[#111111] border border-t-0 border-b-0 border-gray-800 max-w-7xl mx-auto">
{/* Header */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.3 }}
transition={{ duration: 0.6, ease: "easeOut", delay: 0.1 }}
className="mx-auto max-w-5xl text-center"
>
<Eyebrow color="accent">Host</Eyebrow>
<SectionHeader
className="text-3xl font-medium tracking-tight"
color="light"
>
Benefits of Hosting Nodes
</SectionHeader>
<P className="mt-6" color="light">
Hosting a node gives you private compute, contributes to the global
Mycelium infrastructure, and unlocks ways to earn from real network
usage all while keeping sovereignty and control.
</P>
</motion.div>
{/* Features */}
<motion.ul
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true, amount: 0.2 }}
transition={{ duration: 0.5, delay: 0.2 }}
className="mx-auto mt-8 grid max-w-2xl grid-cols-1 gap-x-8 gap-y-8 text-base/7 sm:grid-cols-2 sm:gap-y-16 lg:mx-12 lg:mt-12 lg:max-w-7xl lg:grid-cols-3"
>
{features.map((feature, index) => (
<motion.li
key={feature.name}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.2 }}
transition={{
duration: 0.45,
delay: 0.3 + index * 0.15,
ease: "easeOut",
}}
className="rounded-2xl border border-gray-300 bg-white/5 p-8 transition-all duration-300 ease-in-out hover:scale-105 hover:border-cyan-500 hover:shadow-lg hover:shadow-cyan-500/20 backdrop-blur-md"
>
<feature.icon className="mb-4 h-8 w-8 text-white" />
<CT as="span" className="text-left font-semibold" color="light">
{feature.name}
</CT>
<CP className="mt-2 text-left text-sm" color="light">
{feature.description}
</CP>
</motion.li>
))}
</motion.ul>
</div>
<div className="w-full border-b border-gray-800 bg-[#121212]" />
</section>
);
}

View File

@@ -0,0 +1,40 @@
'use client'
import { Button } from '@/components/Button'
import { Eyebrow, H3, P } from '@/components/Texts'
export function NodeHero() {
return (
<div className="">
{/* Boxed container */}
<div
className="relative mx-auto max-w-7xl border border-t-0 border-b-0 border-gray-100 bg-white overflow-hidden"
>
{/* Inner padding */}
<div className="px-6 py-16 lg:py-16">
<div className="max-w-3xl mx-auto text-center">
<Eyebrow>MYCELIUM NODES</Eyebrow>
<H3 as="h1" className="mt-4">
Host a Node. Power the Network.
</H3>
<P className="mt-6 text-gray-800">
The Mycelium Network runs on nodes hosted by people and organizations around the world. Each node adds capacity, resilience, and sovereignty, expanding a global network for private, distributed compute and AI. 
</P>
<div className="mt-10 flex items-center justify-center gap-x-6">
<Button href="/host" variant="solid" color="cyan">
Host a Node
</Button>
<Button href="#" variant="outline">
Learn More
</Button>
</div>
</div>
</div>
</div>
{/* ✅ Bottom horizontal line with spacing */}
<div className="w-full border-b border-gray-100" />
<div className="max-w-7xl bg-transparent mx-auto py-6 border border-t-0 border-b-0 border-gray-100"></div>
</div>
)
}

View File

@@ -0,0 +1,228 @@
"use client";
import { useState } from "react";
import { motion } from "framer-motion";
import { Eyebrow, SectionHeader, P } from "@/components/Texts";
import {
QuestionMarkCircleIcon,
ShieldCheckIcon,
CheckIcon,
} from "@heroicons/react/20/solid";
/* ------------------------------------------ */
/* PRODUCT DATA */
/* ------------------------------------------ */
const nodes = {
ainode: {
id: "ainode",
name: "Edge AI Node",
subtitle: "Based on Ryzen AI MAX+ 395 platform",
description:
"A compact AI-ready node designed for local inference, agent hosting, and sovereign edge compute. Equipped with a dedicated AI accelerator and optimized thermals.",
image: "/images/ainode.png",
features: [
"Run local AI and cloud workloads",
"Host Mycelium Slices and earn SPORE",
"Experiment with decentralized apps and LLM agents",
"Participate in the global Mycelium Network",
],
buyUrl:
"https://www.gmktec.com/products/amd-ryzen%E2%84%A2-ai-max-395-evo-x2-ai-mini-pc?variant=6f7af17b-b907-4a9d-9c7e-afecfb41ed98",
learnUrl:
"https://threefold.info/mycelium_economics/docs/recommended_nodes/edge_ai_node",
},
edgenode: {
id: "edgenode",
name: "Edge Compute Node",
subtitle: "Based on GMKtec NUCBox M6 Ultra (Ryzen 5 7640HS)",
description:
"High-performance edge compute for networking, local models, and multiple agents. Excellent balance of efficiency and compute density.",
image: "/images/edgenode.png",
features: [
"Efficient 6-core / 12-thread Zen 4 architecture at up to 5.0 GHz boost. (CPU Monkey)",
"Low power consumption (35 W class HS chip) conducive to continuous operation. (LaptopMedia)",
"Compact size → easier placement, less cooling overhead.",
"Full node owner flexibility: use it for private workloads, host slices, or a hybrid of both.",
],
buyUrl:
"https://www.gmktec.com/products/amd-ryzen-5-7640hs-mini-pc-nucbox-m6-ultra?spm=..product_ba613c14-a120-431b-af10-c5c5ca575d55.header_1.1&variant=35ad4a9a-3f31-490c-a2d1-ef9ea3773fe9",
learnUrl:
"https://threefold.info/mycelium_economics/docs/recommended_nodes/edge_node",
},
};
const configOptions = [
{
id: "ainode",
name: "EDGE AI Node",
description: "Optimized for local inference + AI acceleration",
},
{
id: "edgenode",
name: "EDGE Compute Node",
description: "Optimized for general-purpose compute + agent workloads",
},
];
/* ------------------------------------------ */
/* MAIN COMPONENT */
/* ------------------------------------------ */
export function NodeProducts() {
const [selectedNode, setSelectedNode] = useState(nodes.ainode);
return (
<section className="bg-[#121212] w-full max-w-8xl mx-auto">
{/* Top spacing + border */}
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-800" />
<div className="w-full border-t border-l border-r border-gray-800" />
{/* MAIN SECTION */}
<div className="relative px-6 lg:px-12 py-16 bg-[#111111] border border-t-0 border-b-0 border-gray-800 max-w-7xl mx-auto">
{/* --------------------------------------- */}
{/* SECTION TITLE + INTRO PARAGRAPH */}
{/* --------------------------------------- */}
<div className="text-center max-w-3xl mx-auto mb-16">
<Eyebrow color="accent">Hardware</Eyebrow>
<SectionHeader className="text-3xl font-medium" color="light">
Recommended Nodes
</SectionHeader>
<P className="mt-4 text-gray-300">
Below are some of the best-performing and most commonly recommended nodes
for hosting agents, Mycelium workloads, and contributing compute to the network.
</P>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 lg:gap-24 max-w-6xl mx-auto">
{/* ------------------------------ */}
{/* LEFT — TEXT AREA */}
{/* ------------------------------ */}
<motion.div
key={selectedNode.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }}
className="flex flex-col justify-center"
>
<h1 className="text-3xl font-semibold text-white">
{selectedNode.name}
</h1>
<p className="mt-1 text-gray-400 text-base">
{selectedNode.subtitle}
</p>
{/* Description */}
<p className="mt-6 text-gray-300 text-base leading-relaxed">
{selectedNode.description}
</p>
{/* FEATURES */}
<ul className="mt-6 space-y-2">
{selectedNode.features.map((f, i) => (
<li key={i} className="flex items-start">
<CheckIcon className="w-5 h-5 text-green-500 mt-0.5" />
<p className="ml-2 text-sm text-gray-300 leading-relaxed">
{f}
</p>
</li>
))}
</ul>
{/* CONFIG SELECTOR */}
<fieldset className="mt-10">
<legend className="text-sm font-medium text-gray-200">
Configuration
</legend>
<div className="mt-3 grid grid-cols-1 sm:grid-cols-2 gap-4">
{configOptions.map((opt) => (
<label
key={opt.id}
className={`group relative flex flex-col border rounded-xl p-4 cursor-pointer transition
${
selectedNode.id === opt.id
? "border-cyan-500 bg-white/5"
: "border-gray-700 hover:border-gray-500"
}`}
onClick={() => setSelectedNode(nodes[opt.id as keyof typeof nodes])}
>
<span className="text-white font-medium">{opt.name}</span>
<span className="mt-1 text-sm text-gray-400">{opt.description}</span>
</label>
))}
</div>
</fieldset>
<div className="mt-4">
<a className="inline-flex text-sm text-gray-500 hover:text-gray-300 transition">
What config should I choose?
<QuestionMarkCircleIcon className="ml-1 w-5 h-5 text-gray-500" />
</a>
</div>
{/* ------------------------ */}
{/* BUTTONS AREA */}
{/* ------------------------ */}
<div className="mt-10 flex flex-col sm:flex-row gap-4">
{/* Outline Button */}
<a
href={selectedNode.learnUrl}
target="_blank"
rel="noopener noreferrer"
className="flex-1 sm:flex-none text-center border border-gray-700 hover:border-gray-500
text-gray-300 hover:text-white px-8 py-3 rounded-lg transition"
>
Learn More
</a>
{/* Solid Cyan Button */}
<a
href={selectedNode.buyUrl}
target="_blank"
rel="noopener noreferrer"
className="flex-1 sm:flex-none text-center bg-cyan-600 hover:bg-cyan-700
text-white px-8 py-3 rounded-lg font-medium transition"
>
Purchase Node
</a>
</div>
{/* Guarantee */}
<div className="mt-6 flex items-center text-gray-400 text-sm">
<ShieldCheckIcon className="w-6 h-6 text-gray-500 mr-2" />
Lifetime Guarantee
</div>
</motion.div>
{/* ------------------------------ */}
{/* RIGHT — IMAGE */}
{/* ------------------------------ */}
<motion.div
key={selectedNode.image}
initial={{ opacity: 0, scale: 0.92 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.35 }}
className="flex justify-center"
>
<img
src={selectedNode.image}
alt={selectedNode.name}
className="w-full max-w-md rounded-2xl border border-gray-800 object-cover"
/>
</motion.div>
</div>
</div>
{/* Bottom border */}
<div className="w-full border-b border-gray-800 bg-[#121212]" />
</section>
);
}

View File

@@ -0,0 +1,97 @@
"use client";
import { Eyebrow, H3, CT, CP, P } from "@/components/Texts";
const nodeSpecs = [
{
id: "autonomous",
eyebrow: "Self-Running",
title: "Autonomous Operation",
description: "Runs autonomously with no central control.",
colSpan: "lg:col-span-3",
rounded: "max-lg:rounded-t-4xl lg:rounded-tl-4xl",
innerRounded: "max-lg:rounded-t-[calc(2rem+1px)] lg:rounded-tl-[calc(2rem+1px)]",
},
{
id: "encrypted",
eyebrow: "Security",
title: "Encrypted by Default",
description: "Fully encrypted and identity-based.",
colSpan: "lg:col-span-3",
rounded: "lg:rounded-tr-4xl",
innerRounded: "lg:rounded-tr-[calc(2rem+1px)]",
},
{
id: "efficient",
eyebrow: "Performance",
title: "Energy Efficient",
description: "Energy-efficient and quiet, designed for 24/7 uptime.",
colSpan: "lg:col-span-2",
rounded: "lg:rounded-bl-4xl",
innerRounded: "lg:rounded-bl-[calc(2rem+1px)]",
},
{
id: "uptime",
eyebrow: "Reliability",
title: "Measured Uptime",
description: "Automatically measures uptime and contribution.",
colSpan: "lg:col-span-2",
rounded: "",
innerRounded: "",
},
{
id: "fullstack",
eyebrow: "Compatibility",
title: "Full Mycelium Stack Support",
description: "Supports Mycelium Network, Cloud, Pods, and Agents.",
colSpan: "lg:col-span-2",
rounded: "max-lg:rounded-b-4xl lg:rounded-br-4xl",
innerRounded: "max-lg:rounded-b-[calc(2rem+1px)] lg:rounded-br-[calc(2rem+1px)]",
},
];
export function NodeSpecs() {
return (
<div className="bg-white py-24 sm:py-32">
<div className="mx-auto max-w-2xl px-6 lg:max-w-7xl lg:px-8">
<Eyebrow>Node Specifications</Eyebrow>
<H3 className="mt-2">Built for Reliability and Control</H3>
<P className="mt-6 max-w-xl">
Each node strengthens the network and helps build a more open, sovereign and
distributed internet.
</P>
<div className="mt-10 grid grid-cols-1 gap-4 sm:mt-16 lg:grid-cols-6 lg:grid-rows-2">
{nodeSpecs.map((item) => (
<div key={item.id} className={`relative ${item.colSpan}`}>
{/* BG LAYER */}
<div
className={`absolute inset-0 rounded-lg bg-white ${item.rounded}`}
/>
{/* CONTENT LAYER */}
<div
className={`relative flex h-full flex-col overflow-hidden rounded-[calc(var(--radius-lg)+1px)] ${item.innerRounded}`}
>
<div className="p-10 pt-6">
<h3 className="text-sm/4 font-semibold text-cyan-600">{item.eyebrow}</h3>
<CT className="mt-2 text-lg font-medium tracking-tight text-gray-950">
{item.title}
</CT>
<CP className="mt-2 max-w-lg text-sm/6 text-gray-600">
{item.description}
</CP>
</div>
</div>
{/* OUTLINE OVERLAY */}
<div
className={`pointer-events-none absolute inset-0 rounded-lg shadow-sm outline outline-black/5 ${item.rounded}`}
/>
</div>
))}
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,87 @@
"use client";
import { Eyebrow, H3, P, CT, CP } from "@/components/Texts";
import {
CloudArrowDownIcon,
CpuChipIcon,
WalletIcon,
BoltIcon,
} from "@heroicons/react/24/solid";
const steps = [
{
name: "Buy a Node",
description:
"Choose your hardware setup. You can use your own device or one of the recommended models below.",
icon: CpuChipIcon,
},
{
name: "Download Mycelium OS",
description:
"Install the Mycelium OS to prepare your node for network access.",
icon: CloudArrowDownIcon,
},
{
name: "Link Your Wallet",
description:
"Connect your private key from your Mycelium account to enable rewards and authentication.",
icon: WalletIcon,
},
{
name: "Plug In and Go",
description:
"Connect to power and to the internet. Your node will join automatically and start contributing.",
icon: BoltIcon,
},
];
export function NodeSteps() {
return (
<section className="w-full max-w-8xl mx-auto bg-transparent">
{/* Top line */}
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-100" />
<div className="w-full border-t border-l border-r border-gray-100" />
<div className="max-w-7xl bg-white mx-auto py-16 border border-t-0 border-b-0 border-gray-100">
<div className="mx-auto max-w-4xl sm:text-center">
<Eyebrow className="text-cyan-500">HOW IT WORKS</Eyebrow>
<H3 className="text-3xl lg:text-4xl font-medium tracking-tight text-gray-900">
Host a Node
</H3>
<P className="mt-6 text-lg text-gray-600">
Hosting a node takes minutes, and it runs automatically once online.
</P>
</div>
{/* 🔹 Horizontal Stepper */}
<div className="relative mt-16 px-6 flex flex-col gap-8 lg:flex-row lg:items-start lg:justify-between">
{steps.map((step, i) => (
<div key={step.name} className="relative flex-1 px-4">
{/* 🔹 Horizontal connector line (desktop only) */}
{i < steps.length - 1 && (
<div className="hidden lg:block absolute top-5 left-[55%] w-full h-[4px] bg-gray-400 -z-10" />
)}
{/* 🔹 Step header with icon */}
<div className="flex items-center gap-3">
<div className="flex items-center justify-center w-8 h-8 rounded-full bg-cyan-100 text-cyan-600">
<step.icon className="w-5 h-5" />
</div>
<CT>{step.name}</CT>
</div>
{/* 🔹 Description */}
<CP className="mt-3">{step.description}</CP>
</div>
))}
</div>
</div>
{/* bottom line */}
{/* ✅ Bottom horizontal line with spacing */}
<div className="w-full border-b border-gray-100" />
<div className="max-w-7xl bg-transparent mx-auto py-6 border border-t-0 border-b-0 border-gray-100"></div>
</section>
);
}

View File

@@ -0,0 +1,22 @@
import React from 'react';
import { NodeHero } from './NodeHero';
import { NodeBenefits } from './NodeBenefits';
import { NodeSteps } from './NodeSteps';
import { NodeProducts } from './NodeProducts';
import { NodeSpecs } from './NodeSpecs';
import { CallToAction } from './CallToAction';
const NodesPage: React.FC = () => {
return (
<>
<NodeHero />
<NodeBenefits />
<NodeSteps />
<NodeProducts />
<NodeSpecs />
<CallToAction />
</>
);
};
export default NodesPage;