forked from emre/www_projectmycelium_com
feat: refactor pods page with simplified content and improved animations
- Added agent1.png image asset - Refactored InfiniteMovingCards component with cleaner animation logic and improved duplication handling - Changed default speed from "fast" to "slow" and simplified animation setup - Updated AgentBento title from "Augmented Intelligence Fabric" to "Intelligence Fabric" - Increased Homepod vertical padding on large screens (lg:py-16 to lg:py-24) - Removed "Feature" label from PodsFeatures use
This commit is contained in:
BIN
public/images/agent1.png
Normal file
BIN
public/images/agent1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 922 KiB |
@@ -1,12 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
|
||||
export const InfiniteMovingCards = ({
|
||||
items,
|
||||
direction = "left",
|
||||
speed = "fast",
|
||||
speed = "slow",
|
||||
pauseOnHover = true,
|
||||
className,
|
||||
}: {
|
||||
@@ -15,43 +15,39 @@ export const InfiniteMovingCards = ({
|
||||
speed?: "fast" | "normal" | "slow";
|
||||
pauseOnHover?: boolean;
|
||||
className?: string;
|
||||
}): JSX.Element => {
|
||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||
const scrollerRef = React.useRef<HTMLUListElement>(null);
|
||||
const [start, setStart] = useState(false);
|
||||
|
||||
|
||||
const getSpeed = useCallback(() => {
|
||||
if (containerRef.current) {
|
||||
if (speed === "fast") {
|
||||
containerRef.current.style.setProperty("--animation-duration", "20s");
|
||||
} else if (speed === "normal") {
|
||||
containerRef.current.style.setProperty("--animation-duration", "40s");
|
||||
} else {
|
||||
containerRef.current.style.setProperty("--animation-duration", "80s");
|
||||
}
|
||||
}
|
||||
}, [speed]);
|
||||
|
||||
const addAnimation = useCallback(() => {
|
||||
if (containerRef.current && scrollerRef.current) {
|
||||
const scrollerContent = Array.from(scrollerRef.current.children);
|
||||
|
||||
scrollerContent.forEach((item) => {
|
||||
const duplicatedItem = item.cloneNode(true);
|
||||
if (scrollerRef.current) {
|
||||
scrollerRef.current.appendChild(duplicatedItem);
|
||||
}
|
||||
});
|
||||
|
||||
getSpeed();
|
||||
setStart(true);
|
||||
}
|
||||
}, [getSpeed]);
|
||||
}) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const scrollerRef = useRef<HTMLUListElement>(null);
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
addAnimation();
|
||||
}, [addAnimation]);
|
||||
if (!scrollerRef.current) return;
|
||||
|
||||
const children = Array.from(scrollerRef.current.children);
|
||||
|
||||
// duplicate each item ONCE
|
||||
children.forEach((item) => {
|
||||
const clone = item.cloneNode(true);
|
||||
scrollerRef.current!.appendChild(clone);
|
||||
});
|
||||
|
||||
// set speed variable
|
||||
const duration =
|
||||
speed === "fast" ? "20s" : speed === "normal" ? "40s" : "80s";
|
||||
|
||||
containerRef.current?.style.setProperty(
|
||||
"--animation-duration",
|
||||
duration
|
||||
);
|
||||
|
||||
// set direction variable
|
||||
containerRef.current?.style.setProperty(
|
||||
"--animation-direction",
|
||||
direction === "left" ? "forwards" : "reverse"
|
||||
);
|
||||
|
||||
setIsReady(true);
|
||||
}, [direction, speed]);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -61,16 +57,13 @@ export const InfiniteMovingCards = ({
|
||||
<ul
|
||||
ref={scrollerRef}
|
||||
className={cn(
|
||||
"flex min-w-full shrink-0 gap-16 py-4 w-max flex-nowrap",
|
||||
start && "animate-scroll",
|
||||
pauseOnHover && "hover:[animation-play-state:paused]",
|
||||
"flex w-max shrink-0 gap-16 py-4",
|
||||
isReady && "animate-infinite-scroll",
|
||||
pauseOnHover && "hover:[animation-play-state:paused]"
|
||||
)}
|
||||
style={{
|
||||
"--animation-direction": direction === "left" ? "forwards" : "reverse",
|
||||
} as React.CSSProperties}
|
||||
>
|
||||
{items.map((item, idx) => (
|
||||
<li className="relative flex-shrink-0" key={idx}>
|
||||
{items.map((item, i) => (
|
||||
<li key={i} className="flex-shrink-0">
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
|
||||
@@ -23,7 +23,7 @@ const bentos: {
|
||||
{
|
||||
id: "core",
|
||||
eyebrow: "ARCHITECTURE",
|
||||
title: "Augmented Intelligence Fabric",
|
||||
title: "Intelligence Fabric",
|
||||
description:
|
||||
"The sovereign substrate for autonomous AI. Stateless, geo-aware, end-to-end encrypted—and verifiable from intent to execution.",
|
||||
animation: null,
|
||||
|
||||
@@ -13,7 +13,7 @@ export default function Homepod() {
|
||||
style={{ backgroundImage: "url('/images/computehero11.webp')", backgroundSize: "contain" }}
|
||||
>
|
||||
{/* Inner padding */}
|
||||
<div className="px-6 py-16 lg:py-16">
|
||||
<div className="px-6 py-16 lg:py-24">
|
||||
<div className="max-w-2xl lg:pl-6">
|
||||
<Eyebrow>
|
||||
MYCELIUM PODS
|
||||
|
||||
@@ -106,9 +106,6 @@ export function PodsFeatures() {
|
||||
<h3 className="font-semibold text-gray-900">
|
||||
{useCase.title}
|
||||
</h3>
|
||||
<Small className="uppercase tracking-[0.25em] text-cyan-500">
|
||||
Feature
|
||||
</Small>
|
||||
</div>
|
||||
|
||||
{/* Short description */}
|
||||
|
||||
@@ -1,57 +1,70 @@
|
||||
"use client";
|
||||
|
||||
import { Eyebrow, H3, P } from "@/components/Texts";
|
||||
import { Eyebrow, H4, H5 } from "@/components/Texts";
|
||||
import PodsFlow from "./animations/PodsFlow";
|
||||
import { useEffect, useState } from "react";
|
||||
import { IconClockHour5 } from "@tabler/icons-react";
|
||||
|
||||
const phrases = [
|
||||
"everything runs directly from your Pod.",
|
||||
"Messages travel from Pod to Pod, never through a server.",
|
||||
"Calls are hosted on your Pod, not in a data center.",
|
||||
"Files stay encrypted, available, and always yours.",
|
||||
];
|
||||
|
||||
export function PodsHow() {
|
||||
const [index, setIndex] = useState(0);
|
||||
const [fade, setFade] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setFade(false);
|
||||
setTimeout(() => {
|
||||
setIndex((prev) => (prev + 1) % phrases.length);
|
||||
setFade(true);
|
||||
}, 300);
|
||||
}, 3000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section className="relative w-full bg-[#121212] overflow-hidden">
|
||||
{/* ✅ Top horizontal line with spacing */}
|
||||
<div className="max-w-7xl bg-[#121212] mx-auto py-6 border border-t-0 border-b-0 border-gray-800"></div>
|
||||
<div className="w-full border-t border-l border-r border-gray-800" />
|
||||
|
||||
<div className="max-w-7xl mx-auto px-6 lg:px-8 py-12 border border-t-0 border-b-0 border-gray-800 bg-[#111111] overflow-hidden">
|
||||
|
||||
{/* ✅ Two-column layout */}
|
||||
{/* Two-column layout */}
|
||||
<div className="flex flex-col lg:flex-row-reverse gap-8">
|
||||
|
||||
{/* ✅ Right side animation */}
|
||||
{/* Right: Animation */}
|
||||
<div className="w-full lg:w-4/9">
|
||||
<PodsFlow />
|
||||
</div>
|
||||
|
||||
{/* ✅ Left side content */}
|
||||
<div className="w-full lg:w-5/9 text-white">
|
||||
<Eyebrow color="accent" className="">
|
||||
How it works
|
||||
</Eyebrow>
|
||||
<H3 color="white" className="mt-6">
|
||||
A Pod in Action
|
||||
</H3>
|
||||
<P className="max-w-4xl text-gray-400 mt-6">
|
||||
When you use Mycelium, everything runs directly from your Pod.
|
||||
</P>
|
||||
<ul className="max-w-4xl text-gray-400 mt-6 space-y-2 ml-6">
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="mt-1 inline-block size-2 rounded-full bg-cyan-400" />
|
||||
<span>When you message someone, it goes Pod to Pod, not through a central server.</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<span className="mt-1 inline-block size-2 rounded-full bg-cyan-400" />
|
||||
<span>When you host a call, it runs on your Pod. No third-party data centers involved.</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<span className="mt-1 inline-block size-2 rounded-full bg-cyan-400" />
|
||||
<span>When you save a file, it stays on your Pod, encrypted and always available.</span>
|
||||
</li>
|
||||
</ul>
|
||||
<P className="max-w-3xl text-gray-400 mt-4">
|
||||
No one else can read it, rent it, or switch it off.
|
||||
You don’t log in to the internet, you are part of it.
|
||||
</P>
|
||||
{/* Left: JUST the H3 with auto-changing sentence */}
|
||||
<div className="w-full lg:w-5/9 text-white flex items-center">
|
||||
|
||||
<div>
|
||||
<Eyebrow color="accent">How it works</Eyebrow>
|
||||
<H4 color="white">A Pod in Action</H4>
|
||||
<H5 color="white" className="mt-4">
|
||||
When you use Mycelium,
|
||||
<span
|
||||
className={`inline-block transition-opacity duration-300 ${
|
||||
fade ? "opacity-100" : "opacity-0"
|
||||
}`}
|
||||
>
|
||||
{phrases[index]}
|
||||
</span>
|
||||
</H5>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="max-w-7xl mx-auto py-6 border border-t-0 border-b-0 border-gray-800" />
|
||||
<div className="w-full border-b border-gray-800" />
|
||||
</section>
|
||||
|
||||
@@ -7,45 +7,27 @@ import {
|
||||
GlobeAltIcon,
|
||||
} from "@heroicons/react/24/solid";
|
||||
|
||||
import { Eyebrow, H3 } from "@/components/Texts";
|
||||
import { Eyebrow, H3, P } from "@/components/Texts";
|
||||
|
||||
const podCards = [
|
||||
{
|
||||
id: "intro",
|
||||
eyebrow: "Capabilities",
|
||||
title: "What is a Pod?",
|
||||
description: null,
|
||||
icon: null,
|
||||
custom: true,
|
||||
noBorder: true,
|
||||
colSpan: "lg:col-span-4",
|
||||
},
|
||||
{
|
||||
id: "home",
|
||||
title: "Your private digital home on the decentralized internet",
|
||||
description:
|
||||
"Your Pod is a private digital home where apps, data, and identity live independently of Big Tech and central servers.",
|
||||
id: "tools",
|
||||
title: "Runs communication, storage, and collaboration tools",
|
||||
icon: ServerIcon,
|
||||
},
|
||||
{
|
||||
id: "control",
|
||||
title: "An always-on space you fully control",
|
||||
description:
|
||||
"A dedicated, always-on environment you fully command. Your own sovereign slice of the network that never goes offline.",
|
||||
id: "p2p",
|
||||
title: "Operates peer to peer on the network",
|
||||
icon: ShieldCheckIcon,
|
||||
},
|
||||
{
|
||||
id: "tools",
|
||||
title: "Runs communication, storage, and collaboration tools",
|
||||
description:
|
||||
"Runs your communication, storage, and collaboration tools in a secure local environment without reliance on outside platforms.",
|
||||
id: "encrypted",
|
||||
title: "Uses encrypted identities and storage",
|
||||
icon: BoltIcon,
|
||||
},
|
||||
{
|
||||
id: "networking",
|
||||
title: "Fully encrypted, federated peer-to-peer network",
|
||||
description:
|
||||
"Encrypted, federated peer-to-peer networking that links your Pod directly with trusted devices without intermediaries.",
|
||||
id: "fabric",
|
||||
title: "Connects directly to other Pods through the network fabric",
|
||||
icon: GlobeAltIcon,
|
||||
},
|
||||
];
|
||||
@@ -60,38 +42,31 @@ export function PodsWhat() {
|
||||
{/* Content container */}
|
||||
<div className="mx-auto bg-[#111111] max-w-7xl px-6 lg:px-10 border border-t-0 border-b-0 border-gray-800">
|
||||
|
||||
{/* Intro heading */}
|
||||
<div className="pt-12 pb-10 max-w-3xl">
|
||||
<Eyebrow>What</Eyebrow>
|
||||
<H3 className="mt-2 text-white">What is a Pod?</H3>
|
||||
<P className="mt-4 text-gray-300">
|
||||
A Pod is an always-on digital environment that gives you a secure place to run your
|
||||
applications and data.
|
||||
</P>
|
||||
</div>
|
||||
|
||||
{/* 4-column grid */}
|
||||
<div className="grid grid-cols-1 gap-12 pt-12 lg:grid-cols-4 pb-20">
|
||||
<div className="grid grid-cols-1 gap-12 lg:grid-cols-4 pb-20">
|
||||
{podCards.map((card) => {
|
||||
const Icon = card.icon;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={card.id}
|
||||
className={`${card.colSpan || ""} flex flex-col ${
|
||||
card.custom ? "" : "transition-transform duration-300 hover:scale-[1.02]"
|
||||
}`}
|
||||
className="flex flex-col transition-transform duration-300 hover:scale-[1.02]"
|
||||
>
|
||||
{/* Custom Intro Card */}
|
||||
{card.custom ? (
|
||||
<>
|
||||
<Eyebrow>{card.eyebrow}</Eyebrow>
|
||||
<H3 className="mt-2 text-white">{card.title}</H3>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{/* TITLE WITH ICON (matching the TL example) */}
|
||||
<dt className="flex items-center gap-x-3 text-base font-semibold text-white">
|
||||
{Icon && <Icon className="h-6 w-6 text-cyan-500" aria-hidden="true" />}
|
||||
{card.title}
|
||||
</dt>
|
||||
|
||||
{/* DESCRIPTION */}
|
||||
<dd className="mt-4 text-base text-gray-300">
|
||||
{card.description}
|
||||
</dd>
|
||||
</>
|
||||
)}
|
||||
{/* TITLE WITH ICON */}
|
||||
<dt className="flex items-center gap-x-3 text-base font-semibold text-white">
|
||||
{Icon && <Icon className="h-6 w-6 text-cyan-500" aria-hidden="true" />}
|
||||
{card.title}
|
||||
</dt>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -9,22 +9,23 @@ type Props = {
|
||||
gridStroke?: string;
|
||||
};
|
||||
|
||||
// ▸ NEW: Cropped dimensions
|
||||
const W = 760;
|
||||
const H = 420;
|
||||
const H = 300; // was 420
|
||||
|
||||
export default function PodsFlow({
|
||||
className,
|
||||
accent = "#00b8db",
|
||||
gridStroke = "#2b2a2a",
|
||||
gridStroke = "#3a3a3a", // lighter grid stroke
|
||||
}: Props) {
|
||||
const pods = [
|
||||
{ x: 100, y: 180, label: "Pod 1" },
|
||||
{ x: 260, y: 180, label: "Pod 2" },
|
||||
{ x: 420, y: 180, label: "Pod 3" },
|
||||
{ x: 580, y: 180, label: "Pod 4" },
|
||||
{ x: 100, y: 120, label: "Pod 1" }, // moved slightly up for new height
|
||||
{ x: 260, y: 120, label: "Pod 2" },
|
||||
{ x: 420, y: 120, label: "Pod 3" },
|
||||
{ x: 580, y: 120, label: "Pod 4" },
|
||||
];
|
||||
|
||||
// Pulse path
|
||||
// Pulse path (unchanged)
|
||||
const path = `
|
||||
M ${pods[0].x + 80} ${pods[0].y + 40}
|
||||
L ${pods[1].x - 10} ${pods[1].y + 40}
|
||||
@@ -34,7 +35,7 @@ export default function PodsFlow({
|
||||
L ${pods[3].x - 10} ${pods[3].y + 40}
|
||||
`;
|
||||
|
||||
// Arrow segments
|
||||
// Line segments
|
||||
const arrows = [
|
||||
{
|
||||
d: `M ${pods[0].x + 80} ${pods[0].y + 40} L ${pods[1].x - 6} ${pods[1].y + 40}`,
|
||||
@@ -54,15 +55,13 @@ export default function PodsFlow({
|
||||
<div
|
||||
className={clsx("relative overflow-hidden", className)}
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
aria-label="Pod-to-Pod signal transfer animation"
|
||||
style={{ background: "transparent" }}
|
||||
>
|
||||
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full">
|
||||
{/* GRID BG */}
|
||||
{/* GRID BACKGROUND */}
|
||||
<defs>
|
||||
<pattern id="pods-grid" width="28" height="28" patternUnits="userSpaceOnUse">
|
||||
<path d="M 28 0 L 0 0 0 28" fill="none" stroke={gridStroke} strokeWidth="1" opacity="0.6" />
|
||||
<pattern id="pods-grid" width="26" height="26" patternUnits="userSpaceOnUse">
|
||||
<path d="M 26 0 L 0 0 0 26" fill="none" stroke={gridStroke} strokeWidth="1" opacity="0.9" />
|
||||
</pattern>
|
||||
|
||||
<filter id="pods-glow">
|
||||
@@ -85,8 +84,8 @@ export default function PodsFlow({
|
||||
width={80}
|
||||
height={80}
|
||||
rx={14}
|
||||
fill="#0d0d0d"
|
||||
stroke="#1a1a1a"
|
||||
fill="#0f0f0f"
|
||||
stroke="#fff" // brighter stroke
|
||||
strokeWidth={2}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 0.9 }}
|
||||
@@ -94,7 +93,7 @@ export default function PodsFlow({
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* POD LABELS */}
|
||||
{/* POD TEXT */}
|
||||
{pods.map((p, i) => (
|
||||
<motion.text
|
||||
key={i}
|
||||
@@ -103,26 +102,26 @@ export default function PodsFlow({
|
||||
textAnchor="middle"
|
||||
fontSize="14"
|
||||
fontFamily="Inter, sans-serif"
|
||||
fill="#9ca3af"
|
||||
fill="#fff" // lighter text
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 0.9 }}
|
||||
transition={{ delay: 0.1 + i * 0.1, duration: 0.6 }}
|
||||
animate={{ opacity: 0.95 }}
|
||||
transition={{ delay: 0.1 + i * 0.1 }}
|
||||
>
|
||||
{p.label}
|
||||
</motion.text>
|
||||
))}
|
||||
|
||||
{/* GREY LINES */}
|
||||
{/* GREY LINES (lighter) */}
|
||||
{arrows.map((a, i) => (
|
||||
<motion.path
|
||||
key={`grey-${i}`}
|
||||
d={a.d}
|
||||
stroke="#333"
|
||||
stroke="#444" // lighter grey
|
||||
strokeWidth={4}
|
||||
strokeLinecap="round"
|
||||
fill="none"
|
||||
initial={{ pathLength: 0, opacity: 0 }}
|
||||
animate={{ pathLength: 1, opacity: 0.8 }}
|
||||
animate={{ pathLength: 1, opacity: 0.9 }}
|
||||
transition={{ delay: 0.2 * i, duration: 0.7 }}
|
||||
/>
|
||||
))}
|
||||
@@ -143,7 +142,7 @@ export default function PodsFlow({
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* NEW: CYAN ENDPOINT PULSES */}
|
||||
{/* ENDPOINT PULSES */}
|
||||
{arrows.map((a, i) => (
|
||||
<motion.circle
|
||||
key={`endpoint-${i}`}
|
||||
@@ -151,23 +150,21 @@ export default function PodsFlow({
|
||||
cy={a.end.y}
|
||||
r={10}
|
||||
fill={accent}
|
||||
opacity={0.12}
|
||||
opacity={0.16} // slightly stronger
|
||||
filter="url(#pods-glow)"
|
||||
initial={{ scale: 0.8, opacity: 0 }}
|
||||
animate={{
|
||||
scale: [1, 1.2, 1],
|
||||
opacity: [0.05, 0.25, 0.05],
|
||||
opacity: [0.08, 0.28, 0.08],
|
||||
}}
|
||||
transition={{
|
||||
duration: 1.5,
|
||||
duration: 1.4,
|
||||
delay: i * 0.2,
|
||||
repeat: Infinity,
|
||||
repeatType: "mirror",
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* MAIN MOVING CYAN PULSE */}
|
||||
{/* PULSE DOT */}
|
||||
<motion.circle
|
||||
r={8}
|
||||
fill={accent}
|
||||
@@ -178,10 +175,10 @@ export default function PodsFlow({
|
||||
initial={{ offsetDistance: "0%", opacity: 0.4 }}
|
||||
animate={{
|
||||
offsetDistance: ["0%", "100%"],
|
||||
opacity: [0.4, 1, 0.4],
|
||||
opacity: [0.5, 1, 0.5],
|
||||
}}
|
||||
transition={{
|
||||
duration: 2.6,
|
||||
duration: 2.4,
|
||||
repeat: Infinity,
|
||||
ease: "linear",
|
||||
}}
|
||||
|
||||
@@ -228,3 +228,17 @@
|
||||
filter: drop-shadow(0 0 6px #90f6ff);
|
||||
transition: opacity 1s ease-out;
|
||||
}
|
||||
|
||||
@keyframes infinite-scroll {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-infinite-scroll {
|
||||
animation: infinite-scroll var(--animation-duration, 40s) linear infinite;
|
||||
animation-direction: var(--animation-direction, forwards);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user