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:
2025-11-14 13:03:06 +01:00
parent c44d9158f2
commit d8524ef181

View File

@@ -1,90 +1,222 @@
"use client"; "use client";
import { useState } from "react";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { Eyebrow, SectionHeader, P, CT, CP } from "@/components/Texts"; 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", name: "Edge AI Node",
subtitle: "Based on Ryzen AI MAX+ 395 platform", 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", 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", name: "Edge Compute Node",
subtitle: "Based on GMKtec NUCBox M6 Ultra (Ryzen 5 7640HS)", 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", 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() { export function NodeProducts() {
const [selectedNode, setSelectedNode] = useState(nodes.ainode);
return ( return (
<section className="bg-[#121212] w-full max-w-8xl mx-auto"> <section className="bg-[#121212] w-full max-w-8xl mx-auto">
{/* Top spacing + border */} {/* Top spacing + border */}
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-800" /> <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="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"> <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 {/* SECTION TITLE + INTRO PARAGRAPH */}
initial={{ opacity: 0, y: 20 }} {/* --------------------------------------- */}
whileInView={{ opacity: 1, y: 0 }} <div className="text-center max-w-3xl mx-auto mb-16">
viewport={{ once: true }} <Eyebrow color="accent">Hardware</Eyebrow>
transition={{ duration: 0.6, ease: "easeOut" }} <SectionHeader className="text-3xl font-medium" color="light">
className="mx-auto max-w-4xl text-center" Recommended Nodes
>
<Eyebrow color="accent">Recommended</Eyebrow>
<SectionHeader
className="text-3xl font-medium tracking-tight"
color="light"
>
Recommended Nodes to Buy
</SectionHeader> </SectionHeader>
<P className="mt-4 text-gray-300">
<P className="mt-6" color="light"> Below are some of the best-performing and most commonly recommended nodes
The best entry-level and performance-balanced hardware for hosting for hosting agents, Mycelium workloads, and contributing compute to the network.
Mycelium nodes, running agents, and contributing compute to the network.
</P> </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> </motion.div>
{/* Node cards */} {/* ------------------------------ */}
<div className="mx-auto mt-16 grid grid-cols-1 lg:grid-cols-2 gap-12 max-w-6xl"> {/* RIGHT — IMAGE */}
{/* ------------------------------ */}
{nodes.map((node, i) => (
<motion.div <motion.div
key={node.name} key={selectedNode.image}
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, scale: 0.92 }}
whileInView={{ opacity: 1, y: 0 }} animate={{ opacity: 1, scale: 1 }}
viewport={{ once: true }} transition={{ duration: 0.35 }}
transition={{ duration: 0.45, delay: 0.15 * i }} className="flex justify-center"
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"
> >
<img <img
src={node.image} src={selectedNode.image}
alt={node.name} alt={selectedNode.name}
className="w-full rounded-xl border border-gray-700 object-cover" 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> </motion.div>
))}
</div> </div>
</div> </div>