forked from emre/www_projectmycelium_com
feat: redesign storage page with interactive components and dark theme
- Added interactive architecture section with tabbed navigation and smooth transitions - Implemented horizontal scrolling capabilities carousel with image backgrounds - Updated call-to-action section with bordered container layout and improved button styling - Replaced core value section with animated self-healing storage features - Applied consistent dark theme (#111111, #121212) with cyan accents across all storage components
This commit is contained in:
BIN
public/images/encrypted.png
Normal file
BIN
public/images/encrypted.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 888 KiB |
BIN
public/images/filesystem.png
Normal file
BIN
public/images/filesystem.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 910 KiB |
BIN
public/images/ipfs.png
Normal file
BIN
public/images/ipfs.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 834 KiB |
BIN
public/images/s3.png
Normal file
BIN
public/images/s3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1007 KiB |
@@ -106,7 +106,7 @@ export const H5 = createTextComponent(
|
|||||||
)
|
)
|
||||||
export const Eyebrow = createTextComponent(
|
export const Eyebrow = createTextComponent(
|
||||||
'h2',
|
'h2',
|
||||||
'text-base/7 font-semibold tracking-[0.18em] uppercase',
|
'text-base/7 font-semibold uppercase tracking-[0.16em]',
|
||||||
{ color: 'accent' }
|
{ color: 'accent' }
|
||||||
)
|
)
|
||||||
export const SectionHeader = createTextComponent(
|
export const SectionHeader = createTextComponent(
|
||||||
|
|||||||
@@ -1,48 +1,66 @@
|
|||||||
import { CircleBackground } from '../../components/CircleBackground'
|
"use client";
|
||||||
import { Container } from '@/components/Container'
|
|
||||||
import { Button } from '@/components/Button'
|
import { Container } from "@/components/Container";
|
||||||
|
import { Button } from "@/components/Button";
|
||||||
|
|
||||||
export function CallToAction() {
|
export function CallToAction() {
|
||||||
return (
|
return (
|
||||||
<section
|
<section className="relative overflow-hidden bg-gray-900">
|
||||||
id="get-started"
|
{/* ✅ Top horizontal line with spacing */}
|
||||||
className="relative overflow-hidden bg-gray-900 py-20 sm:py-28"
|
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-700"></div>
|
||||||
>
|
<div className="w-full border-t border-l border-r border-gray-700" />
|
||||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
|
|
||||||
<CircleBackground color="#06b6d4" className="animate-spin-slower" />
|
|
||||||
</div>
|
|
||||||
<Container className="relative">
|
|
||||||
<div className="mx-auto max-w-2xl text-center">
|
|
||||||
<h2 className="text-3xl lg:text-4xl font-medium tracking-tight text-white sm:text-4xl">
|
|
||||||
Choose How You Want to Start
|
|
||||||
</h2>
|
|
||||||
<p className="mt-6 text-lg text-gray-300">
|
|
||||||
Store data in your Mycelium Cloud environment
|
|
||||||
or host your own node for full sovereignty.
|
|
||||||
|
|
||||||
</p>
|
{/* ✅ Main boxed area */}
|
||||||
<div className="mt-10 flex flex-wrap justify-center gap-x-6 gap-y-4">
|
<div
|
||||||
<Button
|
id="get-started"
|
||||||
to="https://myceliumcloud.tf"
|
className="relative py-18 max-w-7xl mx-auto bg-[#090909] border border-t-0 border-b-0 border-gray-700"
|
||||||
as="a"
|
>
|
||||||
variant="solid"
|
<Container className="relative">
|
||||||
color="white"
|
<div className="mx-auto max-w-3xl text-center">
|
||||||
target="_blank"
|
<h2 className="text-3xl lg:text-4xl font-medium tracking-tight text-white sm:text-4xl">
|
||||||
rel="noreferrer"
|
Choose How You Want to Start
|
||||||
>
|
</h2>
|
||||||
Use Storage in Cloud
|
|
||||||
</Button>
|
<p className="mt-6 text-lg text-gray-300">
|
||||||
<Button
|
Store data in your Mycelium Cloud environment
|
||||||
to="#storage-developer-experience"
|
or host your own node for full sovereignty.
|
||||||
as="a"
|
</p>
|
||||||
variant="outline"
|
|
||||||
color="white"
|
{/* ✅ Two cards, stacked center with spacing */}
|
||||||
>
|
<div className="mt-8 flex flex-wrap justify-center gap-x-10 gap-y-8">
|
||||||
Host a Node
|
<div className="flex flex-col items-center text-center max-w-xs">
|
||||||
</Button>
|
<Button
|
||||||
|
to="https://myceliumcloud.tf"
|
||||||
|
as="a"
|
||||||
|
variant="solid"
|
||||||
|
color="cyan"
|
||||||
|
className="mt-4"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
Use Storage in Cloud
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col items-center text-center max-w-xs">
|
||||||
|
<Button
|
||||||
|
to="#storage-developer-experience"
|
||||||
|
as="a"
|
||||||
|
variant="outline"
|
||||||
|
color="white"
|
||||||
|
className="mt-4"
|
||||||
|
>
|
||||||
|
Host a Node
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Container>
|
||||||
</Container>
|
</div>
|
||||||
|
|
||||||
|
{/* ✅ Bottom horizontal line with spacing */}
|
||||||
|
<div className="w-full border-b border-gray-700" />
|
||||||
|
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-700 bg-transparent" />
|
||||||
</section>
|
</section>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,54 +1,81 @@
|
|||||||
import { Container } from '@/components/Container'
|
"use client";
|
||||||
import { Eyebrow, SectionHeader, P } from '@/components/Texts'
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Container } from "@/components/Container";
|
||||||
|
import { Eyebrow, SectionHeader, P, H3, H4, H5, CT, CP } from "@/components/Texts";
|
||||||
|
|
||||||
const architecture = [
|
const architecture = [
|
||||||
{
|
{
|
||||||
title: 'Encrypted Storage Substrate',
|
title: "Encrypted Storage Substrate",
|
||||||
description: 'Keeps data private and verifiable.',
|
description: "Keeps data private and verifiable.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Mesh Routing Layer',
|
title: "Mesh Routing Layer",
|
||||||
description: 'Connects clients and workloads securely, anywhere.',
|
description: "Connects clients and workloads securely, anywhere.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Protocol Gateway Layer',
|
title: "Protocol Gateway Layer",
|
||||||
description:
|
description:
|
||||||
'Serve the same dataset over S3, IPFS, WebDAV, or HTTP.',
|
"Serve the same dataset over S3, IPFS, WebDAV, or HTTP.",
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
|
|
||||||
export function StorageArchitecture() {
|
export function StorageArchitecture() {
|
||||||
|
const [active, setActive] = useState(0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="bg-gray-50 py-24 sm:py-32">
|
<section className="bg-[#121212] w-full max-w-8xl mx-auto">
|
||||||
<Container>
|
{/* ✅ Top horizontal line with spacing */}
|
||||||
|
<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" />
|
||||||
|
{/* ✅ Boxed container */}
|
||||||
|
<Container className="bg-[#111111] w-full max-w-7xl mx-auto py-12 border border-t-0 border-b-0 border-gray-800">
|
||||||
<div className="mx-auto max-w-3xl text-center">
|
<div className="mx-auto max-w-3xl text-center">
|
||||||
<Eyebrow>ARCHITECTURE</Eyebrow>
|
<Eyebrow>ARCHITECTURE</Eyebrow>
|
||||||
<SectionHeader as="h2" className="mt-6 text-gray-900">
|
<H3 className="mt-4 text-white">
|
||||||
HOW IT WORKS
|
How it Works
|
||||||
</SectionHeader>
|
</H3>
|
||||||
<P className="mt-6 text-gray-600">
|
<P className="mt-6 text-gray-400">
|
||||||
A layered design that encrypts, routes, and exposes storage through
|
A layered design that encrypts, routes, and exposes storage through
|
||||||
multiple protocols — without duplicating data or compromising
|
multiple protocols — without duplicating data or compromising
|
||||||
sovereignty.
|
sovereignty.
|
||||||
</P>
|
</P>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mx-auto mt-16 max-w-4xl space-y-6">
|
{/* ✅ New 2-column layout */}
|
||||||
{architecture.map((item) => (
|
<div className="mx-auto mt-8 grid max-w-5xl grid-cols-1 gap-2 lg:grid-cols-3 bg-[#121212] ">
|
||||||
|
{/* LEFT — 1 column (3 rows) */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
{architecture.map((item, index) => (
|
||||||
|
<button
|
||||||
|
key={item.title}
|
||||||
|
className={`w-full border bg-[#171717] text-left border-white/10 p-4 backdrop-blur-sm transition hover:-translate-y-1 hover:border-cyan-300/50 hover:bg-white/8
|
||||||
|
${active === index
|
||||||
|
? "border-cyan-400 shadow-md"
|
||||||
|
: "border-gray-800 hover:border-cyan-200 hover:shadow-sm"}`}
|
||||||
|
onClick={() => setActive(index)}
|
||||||
|
>
|
||||||
|
<CP className="text-white font-semibold">{item.title}</CP>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* RIGHT — 2 columns */}
|
||||||
|
<div className="lg:col-span-2 flex items-center justify-center border border-gray-800 bg-[#171717] p-10 backdrop-blur-sm transition hover:-translate-y-1 hover:border-cyan-300/50 hover:bg-white/8" >
|
||||||
<div
|
<div
|
||||||
key={item.title}
|
key={active} // ✅ force smooth transition
|
||||||
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"
|
className="transition-opacity duration-300 opacity-100 animate-fade"
|
||||||
>
|
>
|
||||||
<h3 className="text-xl font-semibold text-gray-900">
|
<H5 className="text-white">{architecture[active].title}</H5>
|
||||||
{item.title}
|
<P className="mt-2 text-gray-400 max-w-xl">
|
||||||
</h3>
|
{architecture[active].description}
|
||||||
<p className="mt-3 text-sm leading-relaxed text-gray-600">
|
</P>
|
||||||
{item.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
|
<div className="w-full border-b border-gray-800 bg-[#121212]" />
|
||||||
|
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-800" />
|
||||||
</section>
|
</section>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
110
src/pages/storage/StorageCapabilitiesNew.tsx
Normal file
110
src/pages/storage/StorageCapabilitiesNew.tsx
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useRef } from "react";
|
||||||
|
import { Eyebrow, CP, CT, H5 } from "@/components/Texts";
|
||||||
|
import { IoArrowBackOutline, IoArrowForwardOutline } from "react-icons/io5";
|
||||||
|
|
||||||
|
const capabilities = [
|
||||||
|
{
|
||||||
|
isIntro: true,
|
||||||
|
eyebrow: "CAPABILITIES",
|
||||||
|
title: "Flexible, Resilient, and Controllable Storage",
|
||||||
|
description:
|
||||||
|
"Mycelium Storage is designed for modern data workloads, providing a range of access methods and control over data placement.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "S3-Compatible Object Storage",
|
||||||
|
description: "Works with existing SDKs & tooling.",
|
||||||
|
imageUrl: "/images/s3.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "IPFS & Content-Addressed Access",
|
||||||
|
description: "Ideal for distributed and decentralized workloads.",
|
||||||
|
imageUrl: "/images/ipfs.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Filesystem Mounts (WebDAV / POSIX)",
|
||||||
|
description: "Mount storage directly into workflows and apps.",
|
||||||
|
imageUrl: "/images/filesystem.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Encrypted Replication & Placement Control",
|
||||||
|
description: "Choose data's ownership and locations.",
|
||||||
|
imageUrl: "/images/encrypted.png",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function StorageCapabilitiesNew() {
|
||||||
|
const sliderRef = useRef<HTMLUListElement>(null);
|
||||||
|
|
||||||
|
const scrollLeft = () => sliderRef.current?.scrollBy({ left: -400, behavior: "smooth" });
|
||||||
|
const scrollRight = () => sliderRef.current?.scrollBy({ left: 400, behavior: "smooth" });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="bg-[#121212] w-full max-w-8xl mx-auto">
|
||||||
|
<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 mx-auto max-w-7xl border border-t-0 border-b-0 border-gray-800 bg-[#111111] overflow-hidden">
|
||||||
|
|
||||||
|
{/* Horizontal Slider — shows part of next card */}
|
||||||
|
<ul
|
||||||
|
ref={sliderRef}
|
||||||
|
className="flex overflow-x-auto snap-x snap-mandatory scroll-smooth no-scrollbar"
|
||||||
|
>
|
||||||
|
{capabilities.map((item, idx) => (
|
||||||
|
<li
|
||||||
|
key={idx}
|
||||||
|
className={`snap-start shrink-0 w-[85%] sm:w-[50%] lg:w-[33%] border border-gray-800 relative ${item.isIntro ? ' p-10' : 'bg-cover bg-center'}`}
|
||||||
|
style={item.imageUrl ? { backgroundImage: `url(${item.imageUrl})` } : {}}
|
||||||
|
>
|
||||||
|
<div className={`relative z-10 flex flex-col h-full ${item.isIntro ? 'justify-between' : 'justify-end'}`}>
|
||||||
|
{/* First card with arrows */}
|
||||||
|
{item.isIntro ? (
|
||||||
|
<div className="flex flex-col justify-between h-full ">
|
||||||
|
<div>
|
||||||
|
<Eyebrow className="">{item.eyebrow}</Eyebrow>
|
||||||
|
<H5 className="text-white mt-4 lg:text-2xl text-xl">{item.title}</H5>
|
||||||
|
<p className="mt-4 text-gray-400 lg:text-lg text-sm leading-relaxed">{item.description}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Arrows inside first card */}
|
||||||
|
<div className="flex items-center gap-x-4 mt-2">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="inline-flex items-center gap-1 text-cyan-400 hover:text-cyan-300 text-sm font-medium mr-auto"
|
||||||
|
>
|
||||||
|
Learn more →
|
||||||
|
</a>
|
||||||
|
<button
|
||||||
|
onClick={scrollLeft}
|
||||||
|
className="h-8 w-8 flex items-center justify-center border border-gray-700 rounded-md hover:border-cyan-500 transition-colors"
|
||||||
|
>
|
||||||
|
<IoArrowBackOutline className="text-gray-300" size={16} />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={scrollRight}
|
||||||
|
className="h-8 w-8 flex items-center justify-center border border-gray-700 rounded-md hover:border-cyan-500 transition-colors"
|
||||||
|
>
|
||||||
|
<IoArrowForwardOutline className="text-gray-300" size={16} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="bg-[#111111] p-6 h-20 flex flex-col justify-center border-t border-b-0 border-gray-800">
|
||||||
|
<p className="text-base font-semibold text-white">{item.title}</p>
|
||||||
|
<p className="mt-2 text-gray-400 leading-snug">{item.description}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</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" />
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,21 +1,38 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Container } from "@/components/Container";
|
import { Container } from "@/components/Container";
|
||||||
import { H3, P, Eyebrow } from "@/components/Texts";
|
import { H3, P, Eyebrow, CT, CP } from "@/components/Texts";
|
||||||
|
import Encrypted from "./animation/Encrypted";
|
||||||
|
import SelfHealing from "./animation/SelfHealing";
|
||||||
|
import Residency from "./animation/Residency";
|
||||||
|
|
||||||
export function StorageCoreValue() {
|
export function StorageCoreValue() {
|
||||||
const logos = [
|
const values = [
|
||||||
{ src: '/images/logo/cryptpad.png', href: 'https://cryptpad.fr' },
|
{ id: "Encrypted",
|
||||||
{ src: '/images/logo/gitea.png', href: 'https://about.gitea.com' },
|
title: "Encrypted and verifiable at rest and in motion",
|
||||||
{ src: '/images/logo/lifekit.png', href: '#' }, // No link available
|
href: "#",
|
||||||
{ src: '/images/logo/matrix.png', href: 'https://matrix.org' },
|
animation: Encrypted,
|
||||||
{ src: '/images/logo/nextcloud.png', href: 'https://nextcloud.com' },
|
},
|
||||||
{ src: '/images/logo/stalwart.png', href: 'https://stalw.art' },
|
{ id: "SelfHealing",
|
||||||
];
|
title: "Self-healing replication and integrity checks",
|
||||||
|
href: "#",
|
||||||
|
animation: SelfHealing,
|
||||||
|
},
|
||||||
|
{ id: "Residency",
|
||||||
|
title: "Residency + governance policies you actually control",
|
||||||
|
href: "#",
|
||||||
|
animation: Residency,
|
||||||
|
},
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="w-full max-w-8xl mx-auto bg-transparent">
|
<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" />
|
||||||
|
|
||||||
{/* ✅ Boxed container */}
|
{/* ✅ Boxed container */}
|
||||||
<div className="max-w-7xl bg-white mx-auto py-12 border border-t-0 border-b-0 border-gray-100">
|
<div className="max-w-7xl bg-white mx-auto py-12 border border-t-0 border-b-0 border-gray-100">
|
||||||
<Container>
|
<Container>
|
||||||
@@ -23,31 +40,29 @@ export function StorageCoreValue() {
|
|||||||
<Eyebrow className="text-cyan-500">Featured Blueprint</Eyebrow>
|
<Eyebrow className="text-cyan-500">Featured Blueprint</Eyebrow>
|
||||||
|
|
||||||
<H3 className="text-3xl lg:text-4xl font-medium tracking-tight text-gray-900 mt-2">
|
<H3 className="text-3xl lg:text-4xl font-medium tracking-tight text-gray-900 mt-2">
|
||||||
Your Personal Sovereign Cloud Workspace
|
Sovereign Storage That Heals Itself
|
||||||
</H3>
|
</H3>
|
||||||
|
|
||||||
<P className="mt-6 text-lg text-gray-600">
|
<P className="mt-6 text-lg text-gray-600">
|
||||||
Digital Me is an example environment built to demonstrate what’s possible on top of the Mycelium Stack — a full personal cloud you can deploy, customize, or extend. Your files, communication, apps, and optional AI agent, all running privately on infrastructure you choose.
|
Mycelium Storage continuously verifies integrity and restores replicas automatically, so data stays available without operational overhead.
|
||||||
</P>
|
</P>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ✅ 3x2 logo grid */}
|
{/* ✅ 3x2 logo grid */}
|
||||||
<div className="mt-12 grid grid-cols-3 gap-x-8 gap-y-12">
|
<div className="mt-12 grid grid-cols-3 gap-x-8 gap-y-12">
|
||||||
{logos.map((logo, i) => (
|
{values.map((value, i) => (
|
||||||
<div key={i} className="flex justify-center">
|
<a
|
||||||
<a
|
key={i}
|
||||||
href={logo.href}
|
href={value.href}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="transition-transform duration-300 hover:scale-105"
|
className="flex flex-col items-center text-center p-6 border border-gray-200 rounded-lg transition-transform duration-300 hover:scale-105 hover:shadow-lg"
|
||||||
>
|
>
|
||||||
<img
|
<value.animation />
|
||||||
src={logo.src}
|
<CT className="text-gray-900 mt-4 font-semibold">
|
||||||
alt={`Logo ${i + 1}`}
|
{value.title}
|
||||||
className="max-h-12 w-auto object-contain"
|
</CT>
|
||||||
/>
|
</a>
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { Container } from '@/components/Container'
|
|
||||||
import { Eyebrow, SectionHeader, P, Small } from '@/components/Texts'
|
import { Eyebrow, SectionHeader, P, Small } from '@/components/Texts'
|
||||||
|
|
||||||
const highlights = [
|
const highlights = [
|
||||||
@@ -24,27 +23,17 @@ const highlights = [
|
|||||||
|
|
||||||
export function StorageOverview() {
|
export function StorageOverview() {
|
||||||
return (
|
return (
|
||||||
<section className="bg-gray-950 py-24 sm:py-32">
|
<section className="bg-[#121212] w-full max-w-8xl mx-auto">
|
||||||
<Container>
|
{/* ✅ Top horizontal line with spacing */}
|
||||||
|
<div className="max-w-7xl mx-auto border border-t-0 border-b-0 border-gray-800" />
|
||||||
<div className="mx-auto max-w-3xl text-center">
|
<div className="w-full border-t border-l border-r border-gray-800" />
|
||||||
<Eyebrow className="tracking-[0.32em] uppercase text-cyan-300">
|
|
||||||
Platform Overview
|
<div className="bg-[#121212] w-full max-w-7xl mx-auto border border-t-0 border-b-0 border-gray-800">
|
||||||
</Eyebrow>
|
<div className="grid lg:grid-cols-3">
|
||||||
<SectionHeader as="h2" color="light" className="mt-6 font-medium">
|
|
||||||
Core Features
|
|
||||||
</SectionHeader>
|
|
||||||
<P color="lightSecondary" className="mt-6">
|
|
||||||
Built on sovereign infrastructure, Mycelium Storage keeps data
|
|
||||||
resilient, verifiable, and instantly accessible. Encryption,
|
|
||||||
replication, and governance are woven directly into the substrate.
|
|
||||||
</P>
|
|
||||||
</div>
|
|
||||||
<div className="mt-16 grid gap-8 lg:grid-cols-3">
|
|
||||||
{highlights.map((item) => (
|
{highlights.map((item) => (
|
||||||
<div
|
<div
|
||||||
key={item.title}
|
key={item.title}
|
||||||
className="group relative overflow-hidden rounded-3xl border border-white/10 bg-white/4 p-8 backdrop-blur-sm transition hover:-translate-y-1 hover:border-cyan-300/50 hover:bg-white/8"
|
className="group relative overflow-hidden border border-white/10 bg-white/4 p-8 backdrop-blur-sm transition hover:border-cyan-300/50 hover:bg-white/8"
|
||||||
>
|
>
|
||||||
<div className="absolute inset-0 bg-linear-to-br from-cyan-500/0 via-white/5 to-cyan-300/20 opacity-0 transition group-hover:opacity-100" />
|
<div className="absolute inset-0 bg-linear-to-br from-cyan-500/0 via-white/5 to-cyan-300/20 opacity-0 transition group-hover:opacity-100" />
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
@@ -61,7 +50,9 @@ export function StorageOverview() {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</div>
|
||||||
|
<div className="w-full border-b border-gray-800 bg-[#121212]" />
|
||||||
|
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-800" />
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { StorageOverview } from './StorageOverview'
|
|||||||
import { StorageArchitecture } from './StorageArchitecture'
|
import { StorageArchitecture } from './StorageArchitecture'
|
||||||
import { StorageUseCases } from './StorageUseCases'
|
import { StorageUseCases } from './StorageUseCases'
|
||||||
import { CallToAction } from './CallToAction'
|
import { CallToAction } from './CallToAction'
|
||||||
import { StorageCapabilities } from './StorageCapabilities'
|
import { StorageCapabilitiesNew } from './StorageCapabilitiesNew'
|
||||||
import { StorageDesign } from './StorageDesign'
|
import { StorageCoreValue } from './StorageCoreValue'
|
||||||
|
|
||||||
export default function StoragePage() {
|
export default function StoragePage() {
|
||||||
return (
|
return (
|
||||||
@@ -15,11 +15,11 @@ export default function StoragePage() {
|
|||||||
</AnimatedSection>
|
</AnimatedSection>
|
||||||
|
|
||||||
<AnimatedSection>
|
<AnimatedSection>
|
||||||
<StorageCapabilities />
|
<StorageCapabilitiesNew />
|
||||||
</AnimatedSection>
|
</AnimatedSection>
|
||||||
|
|
||||||
<AnimatedSection>
|
<AnimatedSection>
|
||||||
<StorageDesign />
|
<StorageCoreValue />
|
||||||
</AnimatedSection>
|
</AnimatedSection>
|
||||||
|
|
||||||
<AnimatedSection>
|
<AnimatedSection>
|
||||||
|
|||||||
223
src/pages/storage/animation/Residency.tsx
Normal file
223
src/pages/storage/animation/Residency.tsx
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { motion, useReducedMotion } from "framer-motion";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string;
|
||||||
|
accent?: string;
|
||||||
|
gridStroke?: string;
|
||||||
|
stroke?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const W = 760;
|
||||||
|
const H = 420;
|
||||||
|
|
||||||
|
export default function Residency({
|
||||||
|
className,
|
||||||
|
accent = "#00b8db",
|
||||||
|
gridStroke = "#e5e7eb",
|
||||||
|
stroke = "#111111",
|
||||||
|
}: Props) {
|
||||||
|
const prefers = useReducedMotion();
|
||||||
|
|
||||||
|
// Layout: central governance node + 3 regional nodes
|
||||||
|
const center = { x: 380, y: 200 };
|
||||||
|
const regions = [
|
||||||
|
{ x: 220, y: 120 },
|
||||||
|
{ x: 540, y: 120 },
|
||||||
|
{ x: 380, y: 300 },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Path for data transfer (circular motion between regions)
|
||||||
|
const flowPath = `M ${regions[0].x} ${regions[0].y}
|
||||||
|
C 300 80, 460 80, ${regions[1].x} ${regions[1].y}
|
||||||
|
C 480 160, 420 260, ${regions[2].x} ${regions[2].y}
|
||||||
|
C 340 260, 280 160, ${regions[0].x} ${regions[0].y} Z`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx("relative overflow-hidden", className)}
|
||||||
|
aria-hidden="true"
|
||||||
|
role="img"
|
||||||
|
style={{ background: "transparent" }}
|
||||||
|
>
|
||||||
|
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full">
|
||||||
|
|
||||||
|
{/* ✅ Subtle light grid (same as Encrypted.tsx) */}
|
||||||
|
<defs>
|
||||||
|
<pattern id="grid-residency" width="28" height="28" patternUnits="userSpaceOnUse">
|
||||||
|
<path
|
||||||
|
d="M 28 0 L 0 0 0 28"
|
||||||
|
fill="none"
|
||||||
|
stroke={gridStroke}
|
||||||
|
strokeWidth="1"
|
||||||
|
opacity="0.4"
|
||||||
|
/>
|
||||||
|
</pattern>
|
||||||
|
|
||||||
|
<filter id="res-glow">
|
||||||
|
<feGaussianBlur stdDeviation="3" result="b" />
|
||||||
|
<feMerge>
|
||||||
|
<feMergeNode in="b" />
|
||||||
|
<feMergeNode in="SourceGraphic" />
|
||||||
|
</feMerge>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<rect width={W} height={H} fill="url(#grid-residency)" />
|
||||||
|
|
||||||
|
{/* ✅ Base dotted jurisdiction boundary */}
|
||||||
|
<circle
|
||||||
|
cx={center.x}
|
||||||
|
cy={center.y}
|
||||||
|
r={140}
|
||||||
|
fill="none"
|
||||||
|
stroke={stroke}
|
||||||
|
strokeWidth={2}
|
||||||
|
strokeDasharray="6 6"
|
||||||
|
opacity="0.3"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* ✅ Cyan policy ring expanding + fading */}
|
||||||
|
{!prefers && (
|
||||||
|
<motion.circle
|
||||||
|
cx={center.x}
|
||||||
|
cy={center.y}
|
||||||
|
r={140}
|
||||||
|
stroke={accent}
|
||||||
|
strokeWidth={2}
|
||||||
|
fill="none"
|
||||||
|
initial={{ scale: 0.9, opacity: 0 }}
|
||||||
|
animate={{
|
||||||
|
scale: [1, 1.15, 1.3],
|
||||||
|
opacity: [0.15, 0.4, 0],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 2.8,
|
||||||
|
repeat: Infinity,
|
||||||
|
ease: [0.22, 1, 0.36, 1],
|
||||||
|
}}
|
||||||
|
filter="url(#res-glow)"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* ✅ Cyan glow radius (policy control zone) */}
|
||||||
|
{!prefers && (
|
||||||
|
<motion.circle
|
||||||
|
cx={center.x}
|
||||||
|
cy={center.y}
|
||||||
|
r={60}
|
||||||
|
fill={accent}
|
||||||
|
opacity={0.08}
|
||||||
|
initial={{ scale: 1, opacity: 0.05 }}
|
||||||
|
animate={{
|
||||||
|
opacity: [0.05, 0.15, 0.05],
|
||||||
|
scale: [1, 1.05, 1],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 2,
|
||||||
|
repeat: Infinity,
|
||||||
|
ease: [0.22, 1, 0.36, 1],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* ✅ Central governance node */}
|
||||||
|
<circle
|
||||||
|
cx={center.x}
|
||||||
|
cy={center.y}
|
||||||
|
r={28}
|
||||||
|
fill="#fff"
|
||||||
|
stroke={stroke}
|
||||||
|
strokeWidth={2}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx={center.x}
|
||||||
|
cy={center.y}
|
||||||
|
r={12}
|
||||||
|
fill={accent}
|
||||||
|
stroke={stroke}
|
||||||
|
strokeWidth={2}
|
||||||
|
filter="url(#res-glow)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* ✅ Regional nodes */}
|
||||||
|
{regions.map((r, i) => (
|
||||||
|
<g key={i}>
|
||||||
|
<circle
|
||||||
|
cx={r.x}
|
||||||
|
cy={r.y}
|
||||||
|
r={22}
|
||||||
|
fill="#fff"
|
||||||
|
stroke={stroke}
|
||||||
|
strokeWidth={2}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx={r.x}
|
||||||
|
cy={r.y}
|
||||||
|
r={10}
|
||||||
|
fill="none"
|
||||||
|
stroke={stroke}
|
||||||
|
strokeWidth={1.5}
|
||||||
|
opacity="0.6"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* ✅ Data transfer flow (light dotted path) */}
|
||||||
|
<motion.path
|
||||||
|
d={flowPath}
|
||||||
|
fill="none"
|
||||||
|
stroke={stroke}
|
||||||
|
strokeWidth={1.5}
|
||||||
|
strokeDasharray="4 4"
|
||||||
|
opacity="0.3"
|
||||||
|
initial={{ pathLength: 0 }}
|
||||||
|
animate={{ pathLength: 1 }}
|
||||||
|
transition={{ duration: 1.4 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* ✅ Cyan packet traveling within jurisdiction */}
|
||||||
|
{!prefers && (
|
||||||
|
<motion.circle
|
||||||
|
r={6}
|
||||||
|
fill={accent}
|
||||||
|
style={{ offsetPath: `path('${flowPath}')` }}
|
||||||
|
initial={{ offsetDistance: "0%", opacity: 0 }}
|
||||||
|
animate={{
|
||||||
|
offsetDistance: ["0%", "100%"],
|
||||||
|
opacity: [0.3, 1, 0.3],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 4,
|
||||||
|
repeat: Infinity,
|
||||||
|
ease: "linear",
|
||||||
|
}}
|
||||||
|
filter="url(#res-glow)"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* ✅ Governance shield icon */}
|
||||||
|
<motion.path
|
||||||
|
d={`M ${center.x} ${center.y - 70}
|
||||||
|
L ${center.x + 20} ${center.y - 60}
|
||||||
|
L ${center.x + 16} ${center.y - 35}
|
||||||
|
L ${center.x} ${center.y - 25}
|
||||||
|
L ${center.x - 16} ${center.y - 35}
|
||||||
|
L ${center.x - 20} ${center.y - 60}
|
||||||
|
Z`}
|
||||||
|
fill="none"
|
||||||
|
stroke={accent}
|
||||||
|
strokeWidth={2}
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
initial={{ pathLength: 0 }}
|
||||||
|
animate={{ pathLength: 1 }}
|
||||||
|
transition={{ duration: 1.2, ease: [0.22, 1, 0.36, 1] }}
|
||||||
|
filter="url(#res-glow)"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
214
src/pages/storage/animation/SelfHealing.tsx
Normal file
214
src/pages/storage/animation/SelfHealing.tsx
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { motion, useReducedMotion } from "framer-motion";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string;
|
||||||
|
accent?: string;
|
||||||
|
gridStroke?: string;
|
||||||
|
stroke?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const W = 760;
|
||||||
|
const H = 420;
|
||||||
|
|
||||||
|
export default function SelfHealing({
|
||||||
|
className,
|
||||||
|
accent = "#00b8db",
|
||||||
|
gridStroke = "#e5e7eb",
|
||||||
|
stroke = "#111111",
|
||||||
|
}: Props) {
|
||||||
|
const prefers = useReducedMotion();
|
||||||
|
|
||||||
|
// diamond node layout
|
||||||
|
const nodes = [
|
||||||
|
{ x: 380, y: 130 }, // top
|
||||||
|
{ x: 240, y: 240 }, // left
|
||||||
|
{ x: 520, y: 240 }, // right
|
||||||
|
{ x: 380, y: 320 }, // bottom
|
||||||
|
];
|
||||||
|
|
||||||
|
// connection paths
|
||||||
|
const links = [
|
||||||
|
[0, 1],
|
||||||
|
[0, 2],
|
||||||
|
[1, 3],
|
||||||
|
[2, 3],
|
||||||
|
[1, 2],
|
||||||
|
];
|
||||||
|
|
||||||
|
// helper for path drawing
|
||||||
|
const drawLine = (i: number, j: number) => {
|
||||||
|
const a = nodes[i];
|
||||||
|
const b = nodes[j];
|
||||||
|
return `M ${a.x} ${a.y} L ${b.x} ${b.y}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx("relative overflow-hidden", className)}
|
||||||
|
aria-hidden="true"
|
||||||
|
role="img"
|
||||||
|
style={{ background: "transparent" }}
|
||||||
|
>
|
||||||
|
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full">
|
||||||
|
|
||||||
|
{/* ✅ Subtle grid (same as Encrypted.tsx) */}
|
||||||
|
<defs>
|
||||||
|
<pattern id="grid-heal" width="28" height="28" patternUnits="userSpaceOnUse">
|
||||||
|
<path
|
||||||
|
d="M 28 0 L 0 0 0 28"
|
||||||
|
fill="none"
|
||||||
|
stroke={gridStroke}
|
||||||
|
strokeWidth="1"
|
||||||
|
opacity="0.4"
|
||||||
|
/>
|
||||||
|
</pattern>
|
||||||
|
<filter id="glow">
|
||||||
|
<feGaussianBlur stdDeviation="3" result="b" />
|
||||||
|
<feMerge>
|
||||||
|
<feMergeNode in="b" />
|
||||||
|
<feMergeNode in="SourceGraphic" />
|
||||||
|
</feMerge>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<rect width={W} height={H} fill="url(#grid-heal)" />
|
||||||
|
|
||||||
|
{/* ✅ Static network links */}
|
||||||
|
{links.map(([i, j], idx) => (
|
||||||
|
<motion.path
|
||||||
|
key={idx}
|
||||||
|
d={drawLine(i, j)}
|
||||||
|
stroke={stroke}
|
||||||
|
strokeWidth={2}
|
||||||
|
strokeLinecap="round"
|
||||||
|
fill="none"
|
||||||
|
opacity="0.25"
|
||||||
|
initial={{ pathLength: 0 }}
|
||||||
|
animate={{ pathLength: 1 }}
|
||||||
|
transition={{ duration: 0.8, delay: idx * 0.1 }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* ✅ Circulating health-check pulse */}
|
||||||
|
{!prefers &&
|
||||||
|
[0, 1, 2, 3].map((i) => {
|
||||||
|
const next = (i + 1) % 4;
|
||||||
|
const path = drawLine(i, next);
|
||||||
|
return (
|
||||||
|
<motion.circle
|
||||||
|
key={`pulse-${i}`}
|
||||||
|
r={5}
|
||||||
|
fill={accent}
|
||||||
|
style={{ offsetPath: `path('${path}')` }}
|
||||||
|
initial={{ offsetDistance: "0%", opacity: 0 }}
|
||||||
|
animate={{
|
||||||
|
offsetDistance: ["0%", "100%"],
|
||||||
|
opacity: [0.1, 0.9, 0.1],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 2.8,
|
||||||
|
delay: i * 0.4,
|
||||||
|
repeat: Infinity,
|
||||||
|
ease: "linear",
|
||||||
|
}}
|
||||||
|
filter="url(#glow)"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{/* ✅ Nodes */}
|
||||||
|
{nodes.map((n, i) => (
|
||||||
|
<g key={`node-${i}`}>
|
||||||
|
<circle
|
||||||
|
cx={n.x}
|
||||||
|
cy={n.y}
|
||||||
|
r={26}
|
||||||
|
fill="none"
|
||||||
|
stroke={stroke}
|
||||||
|
strokeWidth={2}
|
||||||
|
opacity="0.8"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx={n.x}
|
||||||
|
cy={n.y}
|
||||||
|
r={12}
|
||||||
|
fill="#fff"
|
||||||
|
stroke={stroke}
|
||||||
|
strokeWidth={2}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* ✅ Simulated failure (bottom node flickers out, then heals) */}
|
||||||
|
{!prefers && (
|
||||||
|
<motion.circle
|
||||||
|
cx={nodes[3].x}
|
||||||
|
cy={nodes[3].y}
|
||||||
|
r={12}
|
||||||
|
fill="#fff"
|
||||||
|
stroke={stroke}
|
||||||
|
strokeWidth={2}
|
||||||
|
animate={{
|
||||||
|
opacity: [1, 0.2, 1, 1],
|
||||||
|
scale: [1, 0.9, 1, 1],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
duration: 4,
|
||||||
|
repeat: Infinity,
|
||||||
|
ease: "easeInOut",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* ✅ Healing pulse wave from neighbors → bottom node */}
|
||||||
|
{!prefers &&
|
||||||
|
[nodes[1], nodes[2]].map((n, i) => {
|
||||||
|
const path = `M ${n.x} ${n.y} L ${nodes[3].x} ${nodes[3].y}`;
|
||||||
|
return (
|
||||||
|
<motion.circle
|
||||||
|
key={`heal-${i}`}
|
||||||
|
r={5}
|
||||||
|
fill={accent}
|
||||||
|
style={{ offsetPath: `path('${path}')` }}
|
||||||
|
initial={{ offsetDistance: "0%", opacity: 0 }}
|
||||||
|
animate={{
|
||||||
|
offsetDistance: ["0%", "100%"],
|
||||||
|
opacity: [0, 1, 0],
|
||||||
|
}}
|
||||||
|
transition={{
|
||||||
|
delay: 1.5 + i * 0.2,
|
||||||
|
duration: 1.8,
|
||||||
|
repeat: Infinity,
|
||||||
|
repeatDelay: 2.2,
|
||||||
|
ease: "linear",
|
||||||
|
}}
|
||||||
|
filter="url(#glow)"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{/* ✅ Integrity halo on healed node */}
|
||||||
|
{!prefers && (
|
||||||
|
<motion.circle
|
||||||
|
cx={nodes[3].x}
|
||||||
|
cy={nodes[3].y}
|
||||||
|
r={32}
|
||||||
|
fill="none"
|
||||||
|
stroke={accent}
|
||||||
|
strokeWidth={2}
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: [0.15, 0.4, 0.15], scale: [1, 1.12, 1] }}
|
||||||
|
transition={{
|
||||||
|
duration: 2.8,
|
||||||
|
repeat: Infinity,
|
||||||
|
ease: [0.22, 1, 0.36, 1],
|
||||||
|
}}
|
||||||
|
filter="url(#glow)"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user