forked from emre/www_projectmycelium_com
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:
49
src/pages/nodes/CallToAction.tsx
Normal file
49
src/pages/nodes/CallToAction.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
"use client";
|
||||
|
||||
import { Container } from "@/components/Container";
|
||||
import { Button } from "@/components/Button";
|
||||
|
||||
export function CallToAction() {
|
||||
return (
|
||||
<section className="relative overflow-hidden bg-[#121212]">
|
||||
<div className="max-w-7xl bg-[#111111] mx-auto py-6 border border-t-0 border-b-0 border-gray-800"></div>
|
||||
<div className="w-full border-t border-l border-r border-gray-800" />
|
||||
|
||||
<div
|
||||
id="get-started"
|
||||
className="relative py-18 max-w-7xl mx-auto bg-[#111111] border border-t-0 border-b-0 border-gray-800"
|
||||
>
|
||||
<Container className="relative">
|
||||
<div className="mx-auto max-w-3xl text-center">
|
||||
<h2 className="text-3xl lg:text-4xl font-medium tracking-tight text-white sm:text-4xl">
|
||||
Join Mycelium Grid
|
||||
</h2>
|
||||
|
||||
<p className="mt-6 text-lg text-gray-300">
|
||||
Be part of a global network that powers private, distributed
|
||||
infrastructure. Host a node, contribute resources, and earn rewards
|
||||
while expanding the sovereign digital grid.
|
||||
</p>
|
||||
|
||||
<div className="mt-10 flex flex-wrap justify-center gap-x-10 gap-y-8">
|
||||
<div className="flex flex-col items-center text-center max-w-xs">
|
||||
<Button to="/host" variant="solid" color="cyan" className="mt-4">
|
||||
Join Mycelium
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center text-center max-w-xs">
|
||||
<Button to="/docs" variant="outline" color="white" className="mt-4">
|
||||
Explore docs
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
|
||||
<div className="w-full border-b border-gray-800" />
|
||||
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-800 bg-transparent" />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
115
src/pages/nodes/NodeBenefits.tsx
Normal file
115
src/pages/nodes/NodeBenefits.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { SectionHeader, P, Eyebrow, CT, CP } from "@/components/Texts";
|
||||
import type { ComponentPropsWithoutRef } from "react";
|
||||
|
||||
type CircleIconProps = ComponentPropsWithoutRef<"svg">;
|
||||
|
||||
const CircleNumber1Icon = (props: CircleIconProps) => (
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" {...props}>
|
||||
<path d="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2zm.994 5.886c-.83-.777-1.755-.394-1.701.404l-.006.114v8l.007.117a1 1 0 001.986 0l.007-.117V9l-.006-.114z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const CircleNumber2Icon = (props: CircleIconProps) => (
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" {...props}>
|
||||
<path d="M12 2a10 10 0 110 20 10 10 0 010-20zm1 5h-3a1 1 0 100 2h3v2h-2a2 2 0 00-2 2v2a2 2 0 002 2h3a1 1 0 100-2h-3v-2h2a2 2 0 002-2V9a2 2 0 00-2-2z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const CircleNumber3Icon = (props: CircleIconProps) => (
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" {...props}>
|
||||
<path d="M12 2a10 10 0 110 20 10 10 0 010-20zm1 5h-2a2 2 0 00-2 2 1 1 0 102 0h2v2h-2c-1.39 0-2.103 1.67-1.11 2.588l.11.098h3v2h-2a1 1 0 100 2h2a2 2 0 002-2v-2a2 2 0 00-.25-.97l-.068-.115.068-.115A1.99 1.99 0 0015 11V9a2 2 0 00-2-2z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const features = [
|
||||
{
|
||||
name: "Build Your Private Setup",
|
||||
description:
|
||||
"Run a node in your home or office and use it to host private workloads, models, and data. Experiment, deploy, or tinker locally, then offset your costs by sharing unused capacity with the network.",
|
||||
icon: CircleNumber1Icon,
|
||||
},
|
||||
{
|
||||
name: "Strengthen the Network",
|
||||
description:
|
||||
"Every node adds bandwidth, compute, and resilience to the Mycelium Grid. Together, hosts form the physical foundation for decentralized communication, storage, and AI.",
|
||||
icon: CircleNumber2Icon,
|
||||
},
|
||||
{
|
||||
name: "Earn Rewards",
|
||||
description:
|
||||
"Share storage, CPU, GPU, and bandwidth. When your resources are used, you earn network rewards based on real demand.",
|
||||
icon: CircleNumber3Icon,
|
||||
},
|
||||
];
|
||||
|
||||
export function NodeBenefits() {
|
||||
return (
|
||||
<section className="bg-[#121212] w-full max-w-8xl mx-auto">
|
||||
{/* Top spacing + border */}
|
||||
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-800" />
|
||||
<div className="w-full border-t border-l border-r border-gray-800" />
|
||||
|
||||
<div className="relative px-6 lg:px-12 py-12 bg-[#111111] border border-t-0 border-b-0 border-gray-800 max-w-7xl mx-auto">
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, amount: 0.3 }}
|
||||
transition={{ duration: 0.6, ease: "easeOut", delay: 0.1 }}
|
||||
className="mx-auto max-w-5xl text-center"
|
||||
>
|
||||
<Eyebrow color="accent">Host</Eyebrow>
|
||||
<SectionHeader
|
||||
className="text-3xl font-medium tracking-tight"
|
||||
color="light"
|
||||
>
|
||||
Benefits of Hosting Nodes
|
||||
</SectionHeader>
|
||||
|
||||
<P className="mt-6" color="light">
|
||||
Hosting a node gives you private compute, contributes to the global
|
||||
Mycelium infrastructure, and unlocks ways to earn from real network
|
||||
usage — all while keeping sovereignty and control.
|
||||
</P>
|
||||
</motion.div>
|
||||
|
||||
{/* Features */}
|
||||
<motion.ul
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true, amount: 0.2 }}
|
||||
transition={{ duration: 0.5, delay: 0.2 }}
|
||||
className="mx-auto mt-8 grid max-w-2xl grid-cols-1 gap-x-8 gap-y-8 text-base/7 sm:grid-cols-2 sm:gap-y-16 lg:mx-12 lg:mt-12 lg:max-w-7xl lg:grid-cols-3"
|
||||
>
|
||||
{features.map((feature, index) => (
|
||||
<motion.li
|
||||
key={feature.name}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true, amount: 0.2 }}
|
||||
transition={{
|
||||
duration: 0.45,
|
||||
delay: 0.3 + index * 0.15,
|
||||
ease: "easeOut",
|
||||
}}
|
||||
className="rounded-2xl border border-gray-300 bg-white/5 p-8 transition-all duration-300 ease-in-out hover:scale-105 hover:border-cyan-500 hover:shadow-lg hover:shadow-cyan-500/20 backdrop-blur-md"
|
||||
>
|
||||
<feature.icon className="mb-4 h-8 w-8 text-white" />
|
||||
<CT as="span" className="text-left font-semibold" color="light">
|
||||
{feature.name}
|
||||
</CT>
|
||||
<CP className="mt-2 text-left text-sm" color="light">
|
||||
{feature.description}
|
||||
</CP>
|
||||
</motion.li>
|
||||
))}
|
||||
</motion.ul>
|
||||
</div>
|
||||
|
||||
<div className="w-full border-b border-gray-800 bg-[#121212]" />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
40
src/pages/nodes/NodeHero.tsx
Normal file
40
src/pages/nodes/NodeHero.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
'use client'
|
||||
|
||||
import { Button } from '@/components/Button'
|
||||
import { Eyebrow, H3, P } from '@/components/Texts'
|
||||
|
||||
export function NodeHero() {
|
||||
return (
|
||||
<div className="">
|
||||
{/* Boxed container */}
|
||||
<div
|
||||
className="relative mx-auto max-w-7xl border border-t-0 border-b-0 border-gray-100 bg-white overflow-hidden"
|
||||
>
|
||||
{/* Inner padding */}
|
||||
<div className="px-6 py-16 lg:py-16">
|
||||
<div className="max-w-3xl mx-auto text-center">
|
||||
<Eyebrow>MYCELIUM NODES</Eyebrow>
|
||||
<H3 as="h1" className="mt-4">
|
||||
Host a Node. Power the Network.
|
||||
</H3>
|
||||
<P className="mt-6 text-gray-800">
|
||||
The Mycelium Network runs on nodes hosted by people and organizations around the world. Each node adds capacity, resilience, and sovereignty, expanding a global network for private, distributed compute and AI.
|
||||
</P>
|
||||
|
||||
<div className="mt-10 flex items-center justify-center gap-x-6">
|
||||
<Button href="/host" variant="solid" color="cyan">
|
||||
Host a Node
|
||||
</Button>
|
||||
<Button href="#" variant="outline">
|
||||
Learn More
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* ✅ Bottom horizontal line with spacing */}
|
||||
<div className="w-full border-b border-gray-100" />
|
||||
<div className="max-w-7xl bg-transparent mx-auto py-6 border border-t-0 border-b-0 border-gray-100"></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
228
src/pages/nodes/NodeProducts.tsx
Normal file
228
src/pages/nodes/NodeProducts.tsx
Normal file
@@ -0,0 +1,228 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { Eyebrow, SectionHeader, P } from "@/components/Texts";
|
||||
import {
|
||||
QuestionMarkCircleIcon,
|
||||
ShieldCheckIcon,
|
||||
CheckIcon,
|
||||
} from "@heroicons/react/20/solid";
|
||||
|
||||
/* ------------------------------------------ */
|
||||
/* PRODUCT DATA */
|
||||
/* ------------------------------------------ */
|
||||
|
||||
const nodes = {
|
||||
ainode: {
|
||||
id: "ainode",
|
||||
name: "Edge AI Node",
|
||||
subtitle: "Based on Ryzen AI MAX+ 395 platform",
|
||||
description:
|
||||
"A compact AI-ready node designed for local inference, agent hosting, and sovereign edge compute. Equipped with a dedicated AI accelerator and optimized thermals.",
|
||||
image: "/images/ainode.png",
|
||||
features: [
|
||||
"Run local AI and cloud workloads",
|
||||
"Host Mycelium Slices and earn SPORE",
|
||||
"Experiment with decentralized apps and LLM agents",
|
||||
"Participate in the global Mycelium Network",
|
||||
],
|
||||
buyUrl:
|
||||
"https://www.gmktec.com/products/amd-ryzen%E2%84%A2-ai-max-395-evo-x2-ai-mini-pc?variant=6f7af17b-b907-4a9d-9c7e-afecfb41ed98",
|
||||
learnUrl:
|
||||
"https://threefold.info/mycelium_economics/docs/recommended_nodes/edge_ai_node",
|
||||
},
|
||||
|
||||
edgenode: {
|
||||
id: "edgenode",
|
||||
name: "Edge Compute Node",
|
||||
subtitle: "Based on GMKtec NUCBox M6 Ultra (Ryzen 5 7640HS)",
|
||||
description:
|
||||
"High-performance edge compute for networking, local models, and multiple agents. Excellent balance of efficiency and compute density.",
|
||||
image: "/images/edgenode.png",
|
||||
features: [
|
||||
"Efficient 6-core / 12-thread Zen 4 architecture at up to 5.0 GHz boost. (CPU Monkey)",
|
||||
"Low power consumption (35 W class HS chip) conducive to continuous operation. (LaptopMedia)",
|
||||
"Compact size → easier placement, less cooling overhead.",
|
||||
"Full node owner flexibility: use it for private workloads, host slices, or a hybrid of both.",
|
||||
],
|
||||
buyUrl:
|
||||
"https://www.gmktec.com/products/amd-ryzen-5-7640hs-mini-pc-nucbox-m6-ultra?spm=..product_ba613c14-a120-431b-af10-c5c5ca575d55.header_1.1&variant=35ad4a9a-3f31-490c-a2d1-ef9ea3773fe9",
|
||||
learnUrl:
|
||||
"https://threefold.info/mycelium_economics/docs/recommended_nodes/edge_node",
|
||||
},
|
||||
};
|
||||
|
||||
const configOptions = [
|
||||
{
|
||||
id: "ainode",
|
||||
name: "EDGE AI Node",
|
||||
description: "Optimized for local inference + AI acceleration",
|
||||
},
|
||||
{
|
||||
id: "edgenode",
|
||||
name: "EDGE Compute Node",
|
||||
description: "Optimized for general-purpose compute + agent workloads",
|
||||
},
|
||||
];
|
||||
|
||||
/* ------------------------------------------ */
|
||||
/* MAIN COMPONENT */
|
||||
/* ------------------------------------------ */
|
||||
|
||||
export function NodeProducts() {
|
||||
const [selectedNode, setSelectedNode] = useState(nodes.ainode);
|
||||
|
||||
return (
|
||||
<section className="bg-[#121212] w-full max-w-8xl mx-auto">
|
||||
|
||||
{/* Top spacing + border */}
|
||||
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-800" />
|
||||
<div className="w-full border-t border-l border-r border-gray-800" />
|
||||
|
||||
{/* MAIN SECTION */}
|
||||
<div className="relative px-6 lg:px-12 py-16 bg-[#111111] border border-t-0 border-b-0 border-gray-800 max-w-7xl mx-auto">
|
||||
|
||||
{/* --------------------------------------- */}
|
||||
{/* SECTION TITLE + INTRO PARAGRAPH */}
|
||||
{/* --------------------------------------- */}
|
||||
<div className="text-center max-w-3xl mx-auto mb-16">
|
||||
<Eyebrow color="accent">Hardware</Eyebrow>
|
||||
<SectionHeader className="text-3xl font-medium" color="light">
|
||||
Recommended Nodes
|
||||
</SectionHeader>
|
||||
<P className="mt-4 text-gray-300">
|
||||
Below are some of the best-performing and most commonly recommended nodes
|
||||
for hosting agents, Mycelium workloads, and contributing compute to the network.
|
||||
</P>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-16 lg:gap-24 max-w-6xl mx-auto">
|
||||
|
||||
{/* ------------------------------ */}
|
||||
{/* LEFT — TEXT AREA */}
|
||||
{/* ------------------------------ */}
|
||||
<motion.div
|
||||
key={selectedNode.id}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.4 }}
|
||||
className="flex flex-col justify-center"
|
||||
>
|
||||
<h1 className="text-3xl font-semibold text-white">
|
||||
{selectedNode.name}
|
||||
</h1>
|
||||
|
||||
<p className="mt-1 text-gray-400 text-base">
|
||||
{selectedNode.subtitle}
|
||||
</p>
|
||||
|
||||
{/* Description */}
|
||||
<p className="mt-6 text-gray-300 text-base leading-relaxed">
|
||||
{selectedNode.description}
|
||||
</p>
|
||||
|
||||
{/* FEATURES */}
|
||||
<ul className="mt-6 space-y-2">
|
||||
{selectedNode.features.map((f, i) => (
|
||||
<li key={i} className="flex items-start">
|
||||
<CheckIcon className="w-5 h-5 text-green-500 mt-0.5" />
|
||||
<p className="ml-2 text-sm text-gray-300 leading-relaxed">
|
||||
{f}
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{/* CONFIG SELECTOR */}
|
||||
<fieldset className="mt-10">
|
||||
<legend className="text-sm font-medium text-gray-200">
|
||||
Configuration
|
||||
</legend>
|
||||
|
||||
<div className="mt-3 grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
{configOptions.map((opt) => (
|
||||
<label
|
||||
key={opt.id}
|
||||
className={`group relative flex flex-col border rounded-xl p-4 cursor-pointer transition
|
||||
${
|
||||
selectedNode.id === opt.id
|
||||
? "border-cyan-500 bg-white/5"
|
||||
: "border-gray-700 hover:border-gray-500"
|
||||
}`}
|
||||
onClick={() => setSelectedNode(nodes[opt.id as keyof typeof nodes])}
|
||||
>
|
||||
<span className="text-white font-medium">{opt.name}</span>
|
||||
<span className="mt-1 text-sm text-gray-400">{opt.description}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div className="mt-4">
|
||||
<a className="inline-flex text-sm text-gray-500 hover:text-gray-300 transition">
|
||||
What config should I choose?
|
||||
<QuestionMarkCircleIcon className="ml-1 w-5 h-5 text-gray-500" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* ------------------------ */}
|
||||
{/* BUTTONS AREA */}
|
||||
{/* ------------------------ */}
|
||||
<div className="mt-10 flex flex-col sm:flex-row gap-4">
|
||||
|
||||
{/* Outline Button */}
|
||||
<a
|
||||
href={selectedNode.learnUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex-1 sm:flex-none text-center border border-gray-700 hover:border-gray-500
|
||||
text-gray-300 hover:text-white px-8 py-3 rounded-lg transition"
|
||||
>
|
||||
Learn More
|
||||
</a>
|
||||
|
||||
{/* Solid Cyan Button */}
|
||||
<a
|
||||
href={selectedNode.buyUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex-1 sm:flex-none text-center bg-cyan-600 hover:bg-cyan-700
|
||||
text-white px-8 py-3 rounded-lg font-medium transition"
|
||||
>
|
||||
Purchase Node
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* Guarantee */}
|
||||
<div className="mt-6 flex items-center text-gray-400 text-sm">
|
||||
<ShieldCheckIcon className="w-6 h-6 text-gray-500 mr-2" />
|
||||
Lifetime Guarantee
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* ------------------------------ */}
|
||||
{/* RIGHT — IMAGE */}
|
||||
{/* ------------------------------ */}
|
||||
<motion.div
|
||||
key={selectedNode.image}
|
||||
initial={{ opacity: 0, scale: 0.92 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.35 }}
|
||||
className="flex justify-center"
|
||||
>
|
||||
<img
|
||||
src={selectedNode.image}
|
||||
alt={selectedNode.name}
|
||||
className="w-full max-w-md rounded-2xl border border-gray-800 object-cover"
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom border */}
|
||||
<div className="w-full border-b border-gray-800 bg-[#121212]" />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
97
src/pages/nodes/NodeSpecs.tsx
Normal file
97
src/pages/nodes/NodeSpecs.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
"use client";
|
||||
|
||||
import { Eyebrow, H3, CT, CP, P } from "@/components/Texts";
|
||||
|
||||
const nodeSpecs = [
|
||||
{
|
||||
id: "autonomous",
|
||||
eyebrow: "Self-Running",
|
||||
title: "Autonomous Operation",
|
||||
description: "Runs autonomously with no central control.",
|
||||
colSpan: "lg:col-span-3",
|
||||
rounded: "max-lg:rounded-t-4xl lg:rounded-tl-4xl",
|
||||
innerRounded: "max-lg:rounded-t-[calc(2rem+1px)] lg:rounded-tl-[calc(2rem+1px)]",
|
||||
},
|
||||
{
|
||||
id: "encrypted",
|
||||
eyebrow: "Security",
|
||||
title: "Encrypted by Default",
|
||||
description: "Fully encrypted and identity-based.",
|
||||
colSpan: "lg:col-span-3",
|
||||
rounded: "lg:rounded-tr-4xl",
|
||||
innerRounded: "lg:rounded-tr-[calc(2rem+1px)]",
|
||||
},
|
||||
{
|
||||
id: "efficient",
|
||||
eyebrow: "Performance",
|
||||
title: "Energy Efficient",
|
||||
description: "Energy-efficient and quiet, designed for 24/7 uptime.",
|
||||
colSpan: "lg:col-span-2",
|
||||
rounded: "lg:rounded-bl-4xl",
|
||||
innerRounded: "lg:rounded-bl-[calc(2rem+1px)]",
|
||||
},
|
||||
{
|
||||
id: "uptime",
|
||||
eyebrow: "Reliability",
|
||||
title: "Measured Uptime",
|
||||
description: "Automatically measures uptime and contribution.",
|
||||
colSpan: "lg:col-span-2",
|
||||
rounded: "",
|
||||
innerRounded: "",
|
||||
},
|
||||
{
|
||||
id: "fullstack",
|
||||
eyebrow: "Compatibility",
|
||||
title: "Full Mycelium Stack Support",
|
||||
description: "Supports Mycelium Network, Cloud, Pods, and Agents.",
|
||||
colSpan: "lg:col-span-2",
|
||||
rounded: "max-lg:rounded-b-4xl lg:rounded-br-4xl",
|
||||
innerRounded: "max-lg:rounded-b-[calc(2rem+1px)] lg:rounded-br-[calc(2rem+1px)]",
|
||||
},
|
||||
];
|
||||
|
||||
export function NodeSpecs() {
|
||||
return (
|
||||
<div className="bg-white py-24 sm:py-32">
|
||||
<div className="mx-auto max-w-2xl px-6 lg:max-w-7xl lg:px-8">
|
||||
<Eyebrow>Node Specifications</Eyebrow>
|
||||
<H3 className="mt-2">Built for Reliability and Control</H3>
|
||||
<P className="mt-6 max-w-xl">
|
||||
Each node strengthens the network and helps build a more open, sovereign and
|
||||
distributed internet.
|
||||
</P>
|
||||
|
||||
<div className="mt-10 grid grid-cols-1 gap-4 sm:mt-16 lg:grid-cols-6 lg:grid-rows-2">
|
||||
{nodeSpecs.map((item) => (
|
||||
<div key={item.id} className={`relative ${item.colSpan}`}>
|
||||
{/* BG LAYER */}
|
||||
<div
|
||||
className={`absolute inset-0 rounded-lg bg-white ${item.rounded}`}
|
||||
/>
|
||||
|
||||
{/* CONTENT LAYER */}
|
||||
<div
|
||||
className={`relative flex h-full flex-col overflow-hidden rounded-[calc(var(--radius-lg)+1px)] ${item.innerRounded}`}
|
||||
>
|
||||
<div className="p-10 pt-6">
|
||||
<h3 className="text-sm/4 font-semibold text-cyan-600">{item.eyebrow}</h3>
|
||||
<CT className="mt-2 text-lg font-medium tracking-tight text-gray-950">
|
||||
{item.title}
|
||||
</CT>
|
||||
<CP className="mt-2 max-w-lg text-sm/6 text-gray-600">
|
||||
{item.description}
|
||||
</CP>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* OUTLINE OVERLAY */}
|
||||
<div
|
||||
className={`pointer-events-none absolute inset-0 rounded-lg shadow-sm outline outline-black/5 ${item.rounded}`}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
87
src/pages/nodes/NodeSteps.tsx
Normal file
87
src/pages/nodes/NodeSteps.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
"use client";
|
||||
|
||||
import { Eyebrow, H3, P, CT, CP } from "@/components/Texts";
|
||||
import {
|
||||
CloudArrowDownIcon,
|
||||
CpuChipIcon,
|
||||
WalletIcon,
|
||||
BoltIcon,
|
||||
} from "@heroicons/react/24/solid";
|
||||
|
||||
const steps = [
|
||||
{
|
||||
name: "Buy a Node",
|
||||
description:
|
||||
"Choose your hardware setup. You can use your own device or one of the recommended models below.",
|
||||
icon: CpuChipIcon,
|
||||
},
|
||||
{
|
||||
name: "Download Mycelium OS",
|
||||
description:
|
||||
"Install the Mycelium OS to prepare your node for network access.",
|
||||
icon: CloudArrowDownIcon,
|
||||
},
|
||||
{
|
||||
name: "Link Your Wallet",
|
||||
description:
|
||||
"Connect your private key from your Mycelium account to enable rewards and authentication.",
|
||||
icon: WalletIcon,
|
||||
},
|
||||
{
|
||||
name: "Plug In and Go",
|
||||
description:
|
||||
"Connect to power and to the internet. Your node will join automatically and start contributing.",
|
||||
icon: BoltIcon,
|
||||
},
|
||||
];
|
||||
|
||||
export function NodeSteps() {
|
||||
return (
|
||||
<section className="w-full max-w-8xl mx-auto bg-transparent">
|
||||
{/* Top line */}
|
||||
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-100" />
|
||||
<div className="w-full border-t border-l border-r border-gray-100" />
|
||||
|
||||
<div className="max-w-7xl bg-white mx-auto py-16 border border-t-0 border-b-0 border-gray-100">
|
||||
<div className="mx-auto max-w-4xl sm:text-center">
|
||||
<Eyebrow className="text-cyan-500">HOW IT WORKS</Eyebrow>
|
||||
|
||||
<H3 className="text-3xl lg:text-4xl font-medium tracking-tight text-gray-900">
|
||||
Host a Node
|
||||
</H3>
|
||||
|
||||
<P className="mt-6 text-lg text-gray-600">
|
||||
Hosting a node takes minutes, and it runs automatically once online.
|
||||
</P>
|
||||
</div>
|
||||
|
||||
{/* 🔹 Horizontal Stepper */}
|
||||
<div className="relative mt-16 px-6 flex flex-col gap-8 lg:flex-row lg:items-start lg:justify-between">
|
||||
{steps.map((step, i) => (
|
||||
<div key={step.name} className="relative flex-1 px-4">
|
||||
{/* 🔹 Horizontal connector line (desktop only) */}
|
||||
{i < steps.length - 1 && (
|
||||
<div className="hidden lg:block absolute top-5 left-[55%] w-full h-[4px] bg-gray-400 -z-10" />
|
||||
)}
|
||||
|
||||
{/* 🔹 Step header with icon */}
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center justify-center w-8 h-8 rounded-full bg-cyan-100 text-cyan-600">
|
||||
<step.icon className="w-5 h-5" />
|
||||
</div>
|
||||
<CT>{step.name}</CT>
|
||||
</div>
|
||||
|
||||
{/* 🔹 Description */}
|
||||
<CP className="mt-3">{step.description}</CP>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{/* bottom line */}
|
||||
{/* ✅ Bottom horizontal line with spacing */}
|
||||
<div className="w-full border-b border-gray-100" />
|
||||
<div className="max-w-7xl bg-transparent mx-auto py-6 border border-t-0 border-b-0 border-gray-100"></div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
22
src/pages/nodes/NodesPage.tsx
Normal file
22
src/pages/nodes/NodesPage.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import { NodeHero } from './NodeHero';
|
||||
import { NodeBenefits } from './NodeBenefits';
|
||||
import { NodeSteps } from './NodeSteps';
|
||||
import { NodeProducts } from './NodeProducts';
|
||||
import { NodeSpecs } from './NodeSpecs';
|
||||
import { CallToAction } from './CallToAction';
|
||||
|
||||
const NodesPage: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<NodeHero />
|
||||
<NodeBenefits />
|
||||
<NodeSteps />
|
||||
<NodeProducts />
|
||||
<NodeSpecs />
|
||||
<CallToAction />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default NodesPage;
|
||||
Reference in New Issue
Block a user