forked from emre/www_projectmycelium_com
feat: redesign node products section with interactive configuration selector
- Replaced static grid layout with dynamic two-column design featuring live product switching - Added configuration selector allowing users to toggle between AI Node and Compute Node options - Enhanced product information with detailed features, descriptions, and direct purchase/learn more CTAs
This commit is contained in:
@@ -1,90 +1,222 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { Eyebrow, SectionHeader, P, CT, CP } from "@/components/Texts";
|
||||
import {
|
||||
QuestionMarkCircleIcon,
|
||||
ShieldCheckIcon,
|
||||
CheckIcon,
|
||||
} from "@heroicons/react/20/solid";
|
||||
|
||||
const nodes = [
|
||||
{
|
||||
/* ------------------------------------------ */
|
||||
/* 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 CONTAINER */}
|
||||
{/* 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">
|
||||
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.6, ease: "easeOut" }}
|
||||
className="mx-auto max-w-4xl text-center"
|
||||
>
|
||||
<Eyebrow color="accent">Recommended</Eyebrow>
|
||||
|
||||
<SectionHeader
|
||||
className="text-3xl font-medium tracking-tight"
|
||||
color="light"
|
||||
>
|
||||
Recommended Nodes to Buy
|
||||
{/* --------------------------------------- */}
|
||||
{/* 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-6" color="light">
|
||||
The best entry-level and performance-balanced hardware for hosting
|
||||
Mycelium nodes, running agents, and contributing compute to the network.
|
||||
<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])}
|
||||
>
|
||||
<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>
|
||||
|
||||
{/* Node cards */}
|
||||
<div className="mx-auto mt-16 grid grid-cols-1 lg:grid-cols-2 gap-12 max-w-6xl">
|
||||
|
||||
{nodes.map((node, i) => (
|
||||
{/* ------------------------------ */}
|
||||
{/* RIGHT — IMAGE */}
|
||||
{/* ------------------------------ */}
|
||||
<motion.div
|
||||
key={node.name}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.45, delay: 0.15 * i }}
|
||||
className="rounded-2xl border border-gray-800 bg-white/5 p-8 backdrop-blur-sm
|
||||
hover:border-cyan-400 hover:shadow-xl hover:shadow-cyan-500/20
|
||||
transition-all duration-300"
|
||||
key={selectedNode.image}
|
||||
initial={{ opacity: 0, scale: 0.92 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.35 }}
|
||||
className="flex justify-center"
|
||||
>
|
||||
<img
|
||||
src={node.image}
|
||||
alt={node.name}
|
||||
className="w-full rounded-xl border border-gray-700 object-cover"
|
||||
src={selectedNode.image}
|
||||
alt={selectedNode.name}
|
||||
className="w-full max-w-md rounded-2xl border border-gray-800 object-cover"
|
||||
/>
|
||||
|
||||
<CT as="h3" className="mt-6 font-semibold text-white">
|
||||
{node.name}
|
||||
</CT>
|
||||
|
||||
<CP className="mt-2 text-sm text-gray-300">
|
||||
{node.subtitle}
|
||||
</CP>
|
||||
|
||||
<button
|
||||
className="mt-6 w-full rounded-lg bg-indigo-600 hover:bg-indigo-700
|
||||
text-white text-sm font-medium py-3 transition"
|
||||
>
|
||||
View Specs
|
||||
</button>
|
||||
</motion.div>
|
||||
))}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user