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:
2025-11-07 23:34:27 +01:00
parent 100cae988c
commit ee6b5458de
2 changed files with 119 additions and 53 deletions

View File

@@ -1,64 +1,125 @@
import { Container } from '@/components/Container'
import { Eyebrow, SectionHeader, P } from '@/components/Texts'
"use client";
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 = [
{
title: 'Secure Access to Self-Hosted Services',
isIntro: true,
eyebrow: "USE CASES",
title: "Private Connectivity for People, Services, and Infrastructures",
description:
'Access your services, VMs, and clusters remotely without VPNs, public IPs, or port forwarding.',
ideal:
'Ideal for: homelabs, personal clouds, long-running self-hosted stacks',
"Mycelium Network provides a secure, autonomous communication layer that works across homes, clouds, edge workloads, and global deployments.",
},
{
title: 'Service-to-Service Networking Across Environments',
title: "Secure Access to Self-Hosted Services",
description:
'Connect applications running across home labs, cloud regions, edge nodes, and data centers all on one address space.',
ideal:
'Ideal for: dev teams, distributed apps, container + K3s workloads',
"Access your services, VMs, and clusters remotely without VPNs, public IPs, or port forwarding.",
ideal: "Ideal for: homelabs, personal clouds, long-running self-hosted stacks",
icon: LockClosedIcon,
},
{
title: 'Resilient Connectivity Across Regions & Outages',
title: "Service-to-Service Networking Across Environments",
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',
"Connect applications running across home labs, cloud regions, edge nodes, and data centers all on one address space.",
ideal: "Ideal for: dev teams, distributed apps, container + K3s workloads",
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() {
return (
<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>
const sliderRef = useRef<HTMLUListElement>(null);
<div className="mx-auto mt-16 max-w-5xl grid gap-8 lg:grid-cols-3">
{networkUseCases.map((useCase) => (
<div
key={useCase.title}
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"
const scrollLeft = () =>
sliderRef.current?.scrollBy({ left: -400, behavior: "smooth" });
const scrollRight = () =>
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">
{useCase.title}
</h3>
<p className="mt-4 text-sm leading-relaxed text-gray-600">
{useCase.description}
</p>
<p className="mt-4 text-xs font-medium text-cyan-700">
{useCase.ideal}
</p>
</div>
{item.isIntro ? (
<div className="flex flex-col justify-between h-full">
<div>
<Eyebrow>{item.eyebrow}</Eyebrow>
<SectionHeader
as="h3"
className="mt-4 text-gray-900 text-xl lg:text-2xl"
>
{item.title}
</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>
</Container>
</ul>
</div>
</section>
)
);
}

View File

@@ -187,12 +187,14 @@ function DeviceChartIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
export function SecondaryFeatures() {
return (
<section
id="comingsoon"
aria-label="Features for building a portfolio"
className="py-20 sm:py-32"
>
<Container>
<section className="w-full max-w-8xl mx-auto bg-transparent">
{/* ✅ Top horizontal line with spacing */}
<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 className="py-12 border border-t-0 border-b-0 border-gray-100">
<div className="mx-auto max-w-4xl sm:text-center">
<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">
@@ -204,7 +206,7 @@ export function SecondaryFeatures() {
</div>
<ul
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) => (
<li
@@ -220,6 +222,9 @@ export function SecondaryFeatures() {
))}
</ul>
</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>
)
}