forked from emre/www_projectmycelium_com
feat: convert network use cases to horizontal carousel with icons
- Replaced static grid layout with scrollable carousel supporting touch/mouse navigation - Added hero icons to each use case card for visual hierarchy - Introduced intro card with navigation controls and updated styling for better mobile experience
This commit is contained in:
@@ -1,64 +1,125 @@
|
|||||||
import { Container } from '@/components/Container'
|
"use client";
|
||||||
import { Eyebrow, SectionHeader, P } from '@/components/Texts'
|
|
||||||
|
import { useRef } from "react";
|
||||||
|
import { Eyebrow, SectionHeader, P, CT, CP } from "@/components/Texts";
|
||||||
|
import { IoArrowBackOutline, IoArrowForwardOutline } from "react-icons/io5";
|
||||||
|
import {
|
||||||
|
LockClosedIcon,
|
||||||
|
ArrowPathIcon,
|
||||||
|
GlobeAltIcon,
|
||||||
|
} from "@heroicons/react/24/solid";
|
||||||
|
|
||||||
const networkUseCases = [
|
const networkUseCases = [
|
||||||
{
|
{
|
||||||
title: 'Secure Access to Self-Hosted Services',
|
isIntro: true,
|
||||||
|
eyebrow: "USE CASES",
|
||||||
|
title: "Private Connectivity for People, Services, and Infrastructures",
|
||||||
description:
|
description:
|
||||||
'Access your services, VMs, and clusters remotely without VPNs, public IPs, or port forwarding.',
|
"Mycelium Network provides a secure, autonomous communication layer that works across homes, clouds, edge workloads, and global deployments.",
|
||||||
ideal:
|
|
||||||
'Ideal for: homelabs, personal clouds, long-running self-hosted stacks',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Service-to-Service Networking Across Environments',
|
title: "Secure Access to Self-Hosted Services",
|
||||||
description:
|
description:
|
||||||
'Connect applications running across home labs, cloud regions, edge nodes, and data centers all on one address space.',
|
"Access your services, VMs, and clusters remotely without VPNs, public IPs, or port forwarding.",
|
||||||
ideal:
|
ideal: "Ideal for: homelabs, personal clouds, long-running self-hosted stacks",
|
||||||
'Ideal for: dev teams, distributed apps, container + K3s workloads',
|
icon: LockClosedIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Resilient Connectivity Across Regions & Outages',
|
title: "Service-to-Service Networking Across Environments",
|
||||||
description:
|
description:
|
||||||
'Connect systems across countries, datacenters, edge locations, and remote deployments — with routing that automatically heals around ISP failures, censorship, and regional outages.',
|
"Connect applications running across home labs, cloud regions, edge nodes, and data centers all on one address space.",
|
||||||
ideal:
|
ideal: "Ideal for: dev teams, distributed apps, container + K3s workloads",
|
||||||
'Ideal for: research networks, cross-border orgs, distributed compute, off-grid / rural deployments',
|
icon: GlobeAltIcon,
|
||||||
},
|
},
|
||||||
]
|
{
|
||||||
|
title: "Resilient Connectivity Across Regions & Outages",
|
||||||
|
description:
|
||||||
|
"Connect systems across countries, datacenters, edge locations, and remote deployments — with routing that automatically heals around ISP failures, censorship, and regional outages.",
|
||||||
|
ideal:
|
||||||
|
"Ideal for: research networks, cross-border orgs, distributed compute, off-grid / rural deployments",
|
||||||
|
icon: ArrowPathIcon,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export function NetworkUsecases() {
|
export function NetworkUsecases() {
|
||||||
return (
|
const sliderRef = useRef<HTMLUListElement>(null);
|
||||||
<section className="bg-white py-24 sm:py-32">
|
|
||||||
<Container>
|
|
||||||
<div className="mx-auto max-w-3xl text-center">
|
|
||||||
<Eyebrow>USE CASES</Eyebrow>
|
|
||||||
<SectionHeader as="h2" className="mt-6 text-gray-900">
|
|
||||||
Private Connectivity for People, Services, and Infrastructures
|
|
||||||
</SectionHeader>
|
|
||||||
<P className="mt-6 text-gray-600">
|
|
||||||
Mycelium Network provides a secure, autonomous communication layer
|
|
||||||
that works across homes, clouds, edge workloads, and global deployments.
|
|
||||||
</P>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mx-auto mt-16 max-w-5xl grid gap-8 lg:grid-cols-3">
|
const scrollLeft = () =>
|
||||||
{networkUseCases.map((useCase) => (
|
sliderRef.current?.scrollBy({ left: -400, behavior: "smooth" });
|
||||||
<div
|
|
||||||
key={useCase.title}
|
const scrollRight = () =>
|
||||||
className="rounded-3xl border border-slate-200 bg-white p-8 shadow-sm transition hover:-translate-y-1 hover:border-cyan-300 hover:shadow-lg"
|
sliderRef.current?.scrollBy({ left: 400, behavior: "smooth" });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="bg-white w-full max-w-8xl mx-auto">
|
||||||
|
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-slate-200" />
|
||||||
|
<div className="w-full border-t border-l border-r border-slate-200" />
|
||||||
|
|
||||||
|
<div className="relative mx-auto max-w-7xl border border-t-0 border-b-0 border-slate-200 bg-white overflow-hidden">
|
||||||
|
<ul
|
||||||
|
ref={sliderRef}
|
||||||
|
className="flex overflow-x-auto snap-x snap-mandatory scroll-smooth no-scrollbar"
|
||||||
|
>
|
||||||
|
{networkUseCases.map((item, idx) => (
|
||||||
|
<li
|
||||||
|
key={idx}
|
||||||
|
className={`snap-start shrink-0 w-[85%] sm:w-[50%] lg:w-[33%] border border-slate-200 p-10 relative ${
|
||||||
|
item.isIntro ? "bg-gray-50/80" : "bg-white"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<h3 className="text-xl font-semibold text-gray-900">
|
{item.isIntro ? (
|
||||||
{useCase.title}
|
<div className="flex flex-col justify-between h-full">
|
||||||
</h3>
|
<div>
|
||||||
<p className="mt-4 text-sm leading-relaxed text-gray-600">
|
<Eyebrow>{item.eyebrow}</Eyebrow>
|
||||||
{useCase.description}
|
<SectionHeader
|
||||||
</p>
|
as="h3"
|
||||||
<p className="mt-4 text-xs font-medium text-cyan-700">
|
className="mt-4 text-gray-900 text-xl lg:text-2xl"
|
||||||
{useCase.ideal}
|
>
|
||||||
</p>
|
{item.title}
|
||||||
</div>
|
</SectionHeader>
|
||||||
|
<P className="mt-4 text-gray-600 text-sm lg:text-base">
|
||||||
|
{item.description}
|
||||||
|
</P>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-x-4 mt-2">
|
||||||
|
<button
|
||||||
|
onClick={scrollLeft}
|
||||||
|
className="h-8 w-8 flex items-center justify-center border border-slate-300 rounded-md hover:border-cyan-500 transition-colors"
|
||||||
|
>
|
||||||
|
<IoArrowBackOutline className="text-gray-600" size={16} />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={scrollRight}
|
||||||
|
className="h-8 w-8 flex items-center justify-center border border-slate-300 rounded-md hover:border-cyan-500 transition-colors"
|
||||||
|
>
|
||||||
|
<IoArrowForwardOutline className="text-gray-600" size={16} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{/* ✅ Icon above title */}
|
||||||
|
<div className="h-10 w-10 flex items-center justify-center rounded-xl bg-gray-100 mb-4">
|
||||||
|
<item.icon className="h-6 w-6 text-cyan-600" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CT className="text-lg font-semibold text-gray-900">
|
||||||
|
{item.title}
|
||||||
|
</CT>
|
||||||
|
|
||||||
|
<CP className="mt-2 text-gray-600 leading-snug">
|
||||||
|
{item.description}
|
||||||
|
</CP>
|
||||||
|
<CP className="mt-3 text-xs font-medium text-cyan-700">
|
||||||
|
{item.ideal}
|
||||||
|
</CP>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
))}
|
))}
|
||||||
</div>
|
</ul>
|
||||||
</Container>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -187,12 +187,14 @@ function DeviceChartIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
|
|||||||
|
|
||||||
export function SecondaryFeatures() {
|
export function SecondaryFeatures() {
|
||||||
return (
|
return (
|
||||||
<section
|
<section className="w-full max-w-8xl mx-auto bg-transparent">
|
||||||
id="comingsoon"
|
|
||||||
aria-label="Features for building a portfolio"
|
{/* ✅ Top horizontal line with spacing */}
|
||||||
className="py-20 sm:py-32"
|
<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-t border-l border-r border-gray-100" />
|
||||||
<Container>
|
|
||||||
|
|
||||||
|
<Container className="py-12 border border-t-0 border-b-0 border-gray-100">
|
||||||
<div className="mx-auto max-w-4xl sm:text-center">
|
<div className="mx-auto max-w-4xl sm:text-center">
|
||||||
<h2 className="text-base/7 font-semibold text-cyan-500">IN ACTIVE EVOLUTION</h2>
|
<h2 className="text-base/7 font-semibold text-cyan-500">IN ACTIVE EVOLUTION</h2>
|
||||||
<p className="text-3xl lg:text-4xl font-medium tracking-tight text-gray-900">
|
<p className="text-3xl lg:text-4xl font-medium tracking-tight text-gray-900">
|
||||||
@@ -204,7 +206,7 @@ export function SecondaryFeatures() {
|
|||||||
</div>
|
</div>
|
||||||
<ul
|
<ul
|
||||||
role="list"
|
role="list"
|
||||||
className="mx-auto mt-16 grid max-w-2xl grid-cols-1 gap-6 text-sm sm:mt-20 sm:grid-cols-2 md:gap-y-10 lg:max-w-none lg:grid-cols-3"
|
className="mx-auto mt-12 grid max-w-2xl grid-cols-1 gap-6 text-sm sm:grid-cols-2 md:gap-y-10 lg:max-w-none lg:grid-cols-3"
|
||||||
>
|
>
|
||||||
{features.map((feature) => (
|
{features.map((feature) => (
|
||||||
<li
|
<li
|
||||||
@@ -220,6 +222,9 @@ export function SecondaryFeatures() {
|
|||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</Container>
|
</Container>
|
||||||
|
{/* ✅ 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>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user