Merge pull request 'development' (#1) from sashaastiadi/www_mycelium_net:development into development

Reviewed-on: #1
This commit is contained in:
2025-10-15 11:51:08 +00:00
46 changed files with 1504 additions and 449 deletions

BIN
connector.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@@ -0,0 +1,46 @@
'use client';
import { useState, useEffect } from 'react';
export type ScrollDirection = 'up' | 'down';
/**
* A hook to detect the scroll direction.
* It uses requestAnimationFrame for performance, comparing the current scroll position
* with the previous one to determine if the user is scrolling up or down.
*
* @returns {ScrollDirection | null} The current scroll direction ('up' or 'down'), or null on the initial render.
*/
export function useScrollDirection(): ScrollDirection | null {
const [scrollDirection, setScrollDirection] = useState<ScrollDirection | null>(null);
useEffect(() => {
let lastScrollY = window.pageYOffset;
let ticking = false;
const updateScrollDirection = () => {
const scrollY = window.pageYOffset;
if (Math.abs(scrollY - lastScrollY) < 10) {
ticking = false;
return;
}
setScrollDirection(scrollY > lastScrollY ? 'down' : 'up');
lastScrollY = scrollY > 0 ? scrollY : 0;
ticking = false;
};
const onScroll = () => {
if (!ticking) {
window.requestAnimationFrame(updateScrollDirection);
ticking = true;
}
};
window.addEventListener('scroll', onScroll);
return () => window.removeEventListener('scroll', onScroll);
}, []);
return scrollDirection;
}

BIN
peers.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
setting.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

View File

@@ -1,49 +0,0 @@
import { type Metadata } from 'next'
import Link from 'next/link'
import { AuthLayout } from '@/components/AuthLayout'
import { Button } from '@/components/Button'
import { TextField } from '@/components/Fields'
export const metadata: Metadata = {
title: 'Sign In',
}
export default function Login() {
return (
<AuthLayout
title="Sign in to account"
subtitle={
<>
Dont have an account?{' '}
<Link href="/register" className="text-cyan-600">
Sign up
</Link>{' '}
for a free trial.
</>
}
>
<form>
<div className="space-y-6">
<TextField
label="Email address"
name="email"
type="email"
autoComplete="email"
required
/>
<TextField
label="Password"
name="password"
type="password"
autoComplete="current-password"
required
/>
</div>
<Button type="submit" color="cyan" className="mt-8 w-full">
Sign in to account
</Button>
</form>
</AuthLayout>
)
}

View File

@@ -1,75 +0,0 @@
import { type Metadata } from 'next'
import Link from 'next/link'
import { AuthLayout } from '@/components/AuthLayout'
import { Button } from '@/components/Button'
import { SelectField, TextField } from '@/components/Fields'
export const metadata: Metadata = {
title: 'Sign Up',
}
export default function Register() {
return (
<AuthLayout
title="Sign up for an account"
subtitle={
<>
Already registered?{' '}
<Link href="/login" className="text-cyan-600">
Sign in
</Link>{' '}
to your account.
</>
}
>
<form>
<div className="grid grid-cols-2 gap-6">
<TextField
label="First name"
name="first_name"
type="text"
autoComplete="given-name"
required
/>
<TextField
label="Last name"
name="last_name"
type="text"
autoComplete="family-name"
required
/>
<TextField
className="col-span-full"
label="Email address"
name="email"
type="email"
autoComplete="email"
required
/>
<TextField
className="col-span-full"
label="Password"
name="password"
type="password"
autoComplete="new-password"
required
/>
<SelectField
className="col-span-full"
label="How did you hear about us?"
name="referral_source"
>
<option>AltaVista search</option>
<option>Super Bowl commercial</option>
<option>Our route 34 city bus ad</option>
<option>The Never Use This podcast</option>
</SelectField>
</div>
<Button type="submit" color="cyan" className="mt-8 w-full">
Get started today
</Button>
</form>
</AuthLayout>
)
}

View File

@@ -0,0 +1,20 @@
import { AnimatedSection } from '@/components/AnimatedSection'
import DownloadHero from '@/components/DownloadHero'
import { DevHub } from '@/components/DevHub'
import { Faqs } from '@/components/Faqs'
export default function Download() {
return (
<>
<AnimatedSection>
<DownloadHero />
</AnimatedSection>
<AnimatedSection>
<DevHub />
</AnimatedSection>
<AnimatedSection>
<Faqs />
</AnimatedSection>
</>
)
}

View File

@@ -1,24 +1,38 @@
import { AnimatedSection } from '@/components/AnimatedSection'
import { CallToAction } from '@/components/CallToAction'
import { Faqs } from '@/components/Faqs'
import { Hero } from '@/components/Hero'
import { Pricing } from '@/components/Pricing'
import { PrimaryFeatures } from '@/components/PrimaryFeatures'
import { UseCases } from '@/components/UseCases'
import { SecondaryFeatures } from '@/components/SecondaryFeatures'
import { Benefits } from '@/components/Benefits'
import { About } from '@/components/About'
import { Features } from '@/components/Features'
export default function Home() {
return (
<>
<Hero />
<About />
<Benefits />
<PrimaryFeatures />
<UseCases />
<CallToAction />
<SecondaryFeatures />
<Faqs />
<AnimatedSection>
<Hero />
</AnimatedSection>
<AnimatedSection>
<About />
</AnimatedSection>
<AnimatedSection>
<Features />
</AnimatedSection>
<AnimatedSection>
<PrimaryFeatures />
</AnimatedSection>
<AnimatedSection>
<SecondaryFeatures />
</AnimatedSection>
<AnimatedSection>
<CallToAction />
</AnimatedSection>
<AnimatedSection>
<Faqs />
</AnimatedSection>
</>
)
}

View File

@@ -9,7 +9,7 @@ export default function NotFound() {
<Container className="relative isolate flex h-full flex-col items-center justify-center py-20 text-center sm:py-32">
<CirclesBackground className="absolute top-1/2 left-1/2 -z-10 mt-44 w-272.5 -translate-x-1/2 -translate-y-1/2 mask-[linear-gradient(to_bottom,white_20%,transparent_75%)] stroke-gray-300/30" />
<p className="text-sm font-semibold text-gray-900">404</p>
<h1 className="mt-2 text-3xl font-medium tracking-tight text-gray-900">
<h1 className="mt-2 text-3xl lg:text-4xl font-medium tracking-tight text-gray-900">
Page not found
</h1>
<p className="mt-2 text-lg text-gray-600">

View File

@@ -1,4 +1,5 @@
import { AppStoreLink } from '@/components/AppStoreLink'
import { Button } from '@/components/Button'
import { CircleBackground } from '@/components/CircleBackground'
import { Container } from '@/components/Container'
@@ -6,16 +7,17 @@ export function About() {
return (
<section
id="about"
className="relative overflow-hidden bg-gray-900 py-20 sm:py-28"
className="relative overflow-hidden bg-gray-900 py-20 lg:py-32 lg:top-0 top-0"
>
<div className="absolute top-1/2 left-20 -translate-y-1/2 sm:left-1/2 sm:-translate-x-1/2">
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
<CircleBackground color="#fff" className="animate-spin-slower" />
</div>
<Container className="relative">
<div className="mx-auto max-w-3xl sm:text-center">
<h2 className="text-3xl font-medium tracking-tight text-white sm:text-4xl">
<div className="mx-auto max-w-3xl text-center">
<h2 className="text-base/7 font-semibold text-cyan-500">Our Mission</h2>
<p className="text-3xl lg:text-4xl font-medium tracking-tight text-white sm:text-4xl">
Discover Mycelium
</h2>
</p>
<p className="mt-6 text-lg text-gray-300">
Mycelium is an unbreakable network, always finding the shortest path and providing 100% secure, peer-to-peer communication. But this is just the beginning.
</p>
@@ -23,7 +25,14 @@ export function About() {
Our mission is to create a sustainable digital ecosystem where communication is seamless, data is secure, and scalability knows no bounds.
</p>
<div className="mt-8 flex justify-center">
<AppStoreLink color="white" />
<Button
href="https://docs.ourworld.tf/mycelium_cloud/docs/"
target="_blank"
variant="outline"
color="white"
>
Learn More
</Button>
</div>
</div>
</Container>

View File

@@ -0,0 +1,23 @@
'use client'
import { useRef } from 'react'
import { motion, useInView } from 'framer-motion'
export function AnimatedSection({ children }: { children: React.ReactNode }) {
const ref = useRef(null)
const isInView = useInView(ref, { once: true, margin: '-20% 0px -20% 0px' })
return (
<motion.section
ref={ref}
initial={{ opacity: 0, y: 50 }}
animate={{
opacity: isInView ? 1 : 0,
y: isInView ? 0 : 50,
}}
transition={{ duration: 0.5 }}
>
{children}
</motion.section>
)
}

View File

@@ -51,7 +51,7 @@ export function AppScreen({
}: React.ComponentPropsWithoutRef<'div'>) {
return (
<div className={clsx('flex flex-col', className)} {...props}>
<div className="flex justify-between px-4 pt-4">
<div className="flex justify-between px-4 pt-0">
<MenuIcon className="h-6 w-6 flex-none" />
<Logo className="h-6 flex-none" />
<UserIcon className="h-6 w-6 flex-none" />

View File

@@ -28,10 +28,10 @@ const features = [
export function Benefits() {
return (
<section id="benefits" className="bg-white py-24 sm:py-32 dark:bg-gray-900">
<section id="howitworks" className="bg-white py-24 sm:py-32 dark:bg-gray-900">
<div className="mx-auto max-w-7xl px-6 lg:px-8">
<div className="mx-auto max-w-5xl lg:mx-0">
<h2 className="text-3xl font-medium tracking-tight text-gray-900">
<h2 className="text-3xl lg:text-4xl font-medium tracking-tight text-gray-900">
Nature's Blueprint for Digital Connectivity
</h2>
<p className="mt-6 text-lg text-gray-600">

View File

@@ -16,7 +16,8 @@ const variantStyles = {
gray: 'bg-gray-800 text-white hover:bg-gray-900 active:bg-gray-800 active:text-white/80',
},
outline: {
gray: 'border-gray-300 text-gray-700 hover:border-gray-400 active:bg-gray-100 active:text-gray-700/80',
gray: 'border-gray-300 text-gray-700 hover:text-gray-500 hover:border-gray-400 active:bg-gray-100 active:text-gray-700/80',
white: 'border-gray-300 text-white hover:text-gray-200 hover:border-gray-400 active:bg-gray-100 active:text-gray-700/80',
},
}

View File

@@ -11,18 +11,18 @@ export function CallToAction() {
id="get-free-shares-today"
className="relative overflow-hidden bg-gray-900 py-20 sm:py-28"
>
<div className="absolute top-1/2 left-20 -translate-y-1/2 sm:left-1/2 sm:-translate-x-1/2">
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
<CircleBackground color="#fff" className="animate-spin-slower" />
</div>
<Container className="relative">
<div className="mx-auto max-w-2xl sm:text-center">
<h2 className="text-3xl font-medium tracking-tight text-white sm:text-4xl">
<h2 className="text-3xl lg:text-4xl font-medium tracking-tight text-white sm:text-4xl">
Get Started Today
</h2>
<p className="mt-6 text-lg text-gray-300">
Download the Mycelium app and step into the future of secure, peer-to-peer networking; fast, private, and decentralized.
</p>
<div className="mt-8 flex justify-center gap-4">
<div className="mt-8 grid grid-cols-2 justify-items-center gap-4 sm:flex sm:justify-center">
<AppStoreLink color="white" />
<WindowsLink color="white" />
<AndroidLink color="white" />

View File

@@ -6,7 +6,7 @@ export function Container({
}: React.ComponentPropsWithoutRef<'div'>) {
return (
<div
className={clsx('mx-auto max-w-7xl px-4 sm:px-6 lg:px-8', className)}
className={clsx('mx-auto max-w-7xl px-6 lg:px-8', className)}
{...props}
/>
)

View File

@@ -0,0 +1,189 @@
'use client';
import * as React from 'react';
import { motion, useReducedMotion } from 'framer-motion';
type Props = {
className?: string; // e.g. "w-full h-80"
bg?: string; // default white
};
/** Palette */
const ACCENT = '#00b8db';
const STROKE = '#111827';
const GRAY = '#9CA3AF';
const GRAY_LT = '#E5E7EB';
/* ---------- small generic icons (no brands) ---------- */
const IconSquare = () => (
<rect x={-14} y={-14} width={28} height={28} rx={6} fill={ACCENT} stroke={STROKE} strokeWidth={3} />
);
const IconTriangle = () => (
<path d="M 0 -15 L 14 12 L -14 12 Z" fill="#fff" stroke={STROKE} strokeWidth={3} />
);
const IconHex = () => (
<path
d="M 0 -15 L 13 -7 L 13 7 L 0 15 L -13 7 L -13 -7 Z"
fill="#fff"
stroke={STROKE}
strokeWidth={3}
/>
);
const IconBolt = () => (
<path d="M -5 -14 L 4 -2 L -1 -2 L 5 14 L -6 1 L -1 1 Z" fill={ACCENT} stroke={STROKE} strokeWidth={3} />
);
const IconPlay = () => (
<circle r={15} fill="#fff" stroke={STROKE} strokeWidth={3} />
);
const IconDB = () => (
<>
<ellipse cx={0} cy={-10} rx={16} ry={8} fill="#fff" stroke={STROKE} strokeWidth={3} />
<rect x={-16} y={-10} width={32} height={20} fill="#fff" stroke={STROKE} strokeWidth={3} />
<ellipse cx={0} cy={10} rx={16} ry={8} fill="#fff" stroke={STROKE} strokeWidth={3} />
</>
);
/* icon inside white circular badge */
function Badge({ children }: { children: React.ReactNode }) {
return (
<>
<circle r={26} fill="#fff" stroke={GRAY_LT} strokeWidth={3} />
<g>{children}</g>
<filter id="shadow" x="-200%" y="-200%" width="400%" height="400%">
<feDropShadow dx="0" dy="2" stdDeviation="2" floodColor={GRAY} floodOpacity="0.25" />
</filter>
</>
);
}
/* ---------- central cloud ---------- */
function Cloud({ pulse = true }: { pulse?: boolean }) {
const prefersReduced = useReducedMotion();
return (
<g>
<g fill={STROKE}>
<circle cx={-18} cy={0} r={14} />
<circle cx={0} cy={-10} r={18} />
<circle cx={18} cy={0} r={16} />
<rect x={-30} y={0} width={54} height={16} rx={8} />
</g>
{/* subtle accent aura */}
<motion.circle
r={36}
fill="none"
stroke={ACCENT}
strokeWidth={4}
initial={{ opacity: 0.15, scale: 0.9 }}
animate={pulse && !prefersReduced ? { opacity: [0.15, 0.35, 0.15], scale: [0.9, 1.05, 0.9] } : {}}
transition={{ duration: 1.8, repeat: Infinity }}
/>
</g>
);
}
/* a small packet line from center to a node */
function Beam({
x2,
y2,
delay = 0,
}: {
x2: number;
y2: number;
delay?: number;
}) {
const prefersReduced = useReducedMotion();
return (
<motion.line
x1={0}
y1={0}
x2={x2}
y2={y2}
stroke={ACCENT}
strokeWidth={4}
strokeLinecap="round"
initial={{ pathLength: 0, opacity: 0.0 }}
animate={{ pathLength: 1, opacity: 0.9 }}
transition={{
duration: prefersReduced ? 0.01 : 0.9,
delay,
repeat: prefersReduced ? 0 : Infinity,
repeatDelay: 1.2,
repeatType: 'reverse',
ease: [0.22, 1, 0.36, 1],
}}
/>
);
}
export default function ContentDistribution({ className, bg = '#ffffff' }: Props) {
const W = 900;
const H = 560;
// ring radii
const rings = [110, 190, 270];
// positions (angle degrees) for badges on rings
const layout = [
{ r: rings[1], a: -20, icon: <IconSquare /> },
{ r: rings[2], a: 20, icon: <IconTriangle /> },
{ r: rings[0], a: 155, icon: <IconHex /> },
{ r: rings[2], a: -145, icon: <IconBolt /> },
{ r: rings[1], a: 210, icon: <IconDB /> },
{ r: rings[0], a: 60, icon: <IconPlay /> },
];
const prefersReduced = useReducedMotion();
return (
<div className={className} aria-hidden="true" role="img" style={{ background: bg }}>
<svg viewBox={`0 0 ${W} ${H}`} width="100%" height="100%">
{/* subtle radial background + rings */}
<defs>
<radialGradient id="fade" cx="50%" cy="50%" r="60%">
<stop offset="0%" stopColor="#ffffff" />
<stop offset="100%" stopColor="#ffffff" />
</radialGradient>
</defs>
<rect width={W} height={H} fill="url(#fade)" />
<g transform={`translate(${W / 2}, ${H / 2})`}>
{rings.map((r, i) => (
<circle key={i} r={r} fill="none" stroke={GRAY_LT} strokeWidth={2} />
))}
{/* central cloud */}
<Cloud />
{/* rotating layer with badges and beams */}
<motion.g
initial={{ rotate: 0 }}
animate={{ rotate: prefersReduced ? 0 : 360 }}
transition={{ duration: 40, ease: 'linear', repeat: prefersReduced ? 0 : Infinity }}
>
{/* Beams */}
{layout.map((n, i) => {
const rad = (n.a * Math.PI) / 180;
const x = n.r * Math.cos(rad);
const y = n.r * Math.sin(rad);
return <Beam key={`beam-${i}`} x2={x} y2={y} delay={i * 0.15} />;
})}
{/* Badges */}
{layout.map((n, i) => {
const rad = (n.a * Math.PI) / 180;
const x = n.r * Math.cos(rad);
const y = n.r * Math.sin(rad);
return (
<g key={`badge-${i}`} transform={`translate(${x}, ${y})`} filter="url(#shadow)">
<circle r={34} fill="#fff" stroke={GRAY_LT} strokeWidth={3} />
<g transform="scale(1)">
{n.icon}
</g>
</g>
);
})}
</motion.g>
</g>
</svg>
</div>
);
}

46
src/components/DevHub.tsx Normal file
View File

@@ -0,0 +1,46 @@
import { CheckIcon } from '@heroicons/react/20/solid'
const features = [
{
name: 'Documentation',
description: 'Documentation for Mycelium.',
},
{ name: 'Support', description: 'Talk to an expert.' },
{
name: 'Forum',
description: 'Forum for all your questions.',
},
{ name: 'Community', description: 'Join our Developers community on telegram.' },
]
export function DevHub() {
return (
<div className="bg-gray-900 py-24 sm:py-32">
<div className="mx-auto max-w-7xl px-6 lg:px-8">
<div className="mx-auto grid max-w-2xl grid-cols-1 gap-x-8 gap-y-16 sm:gap-y-20 lg:mx-0 lg:max-w-none lg:grid-cols-5">
<div className="col-span-2">
<h2 className="text-base/7 font-semibold text-cyan-500 mb-2">Get Started</h2>
<p className="text-4xl font-semibold tracking-tight text-pretty text-white sm:text-5xl">
Developer Hub
</p>
<p className="mt-6 text-base/7 text-gray-300">
Our Developer Hub is a resource center for developers looking to build on top of Mycelium. Join our Developers community on telegram to get started.
</p>
</div>
<dl className="col-span-3 grid grid-cols-1 gap-x-8 gap-y-10 text-base/7 text-gray-400 sm:grid-cols-2 lg:gap-y-16">
{features.map((feature) => (
<div key={feature.name} className="relative pl-9">
<dt className="font-semibold text-white">
<CheckIcon aria-hidden="true" className="absolute top-1 left-0 size-5 text-indigo-400" />
{feature.name}
</dt>
<dd className="mt-2">{feature.description}</dd>
</div>
))}
</dl>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,77 @@
import Image from 'next/image';
import appleIcon from '@/images/apple.svg';
import windowsIcon from '@/images/windows.svg';
import androidIcon from '@/images/android.svg';
import linuxIcon from '@/images/linux.svg';
const features = [
{
name: 'Download for iOS & MacOS',
description: 'Download Mycelium App from the Apple Store.',
href: 'https://apps.apple.com/us/app/mycelium-network/id6504277565',
icon: appleIcon,
},
{
name: 'Download for Windows',
description: 'Download the Mycelium App for Windows directly from its Github repository.',
href: 'https://github.com/threefoldtech/myceliumflut/releases',
icon: windowsIcon,
},
{
name: 'Download for Android',
description: 'Download Mycelium from the Google Play Store.',
href: 'https://play.google.com/store/apps/details?id=tech.threefold.mycelium&pli=1',
icon: androidIcon,
},
{
name: 'Download for Linux',
description: 'Download the Mycelium binary for Linux directly from its Github repository.',
href: 'https://github.com/threefoldtech/mycelium/releases/tag/v0.6.1',
icon: linuxIcon,
},
];
export default function DownloadHero() {
return (
<div className=" py-16 sm:py-32">
<div className="mx-auto max-w-7xl px-6 lg:px-8">
<div className="mx-auto max-w-2xl lg:mx-0">
<h2 className="text-5xl lg:text-6xl font-medium tracking-tight text-gray-900">
Download Mycelium
</h2>
<p className="mt-6 text-lg/8 text-gray-600">
Get Mycelium for Android, Windows, macOS, and iOS to securely connect, store, and interact with the decentralized networkseamlessly and efficiently. Not sure how it works?{' '}
<a href="https://docs.ourworld.tf/mycelium_cloud/docs/" className="text-cyan-500 hover:text-cyan-600 font-semibold underline">
Read the manual.
</a>
</p>
</div>
<div className="mx-auto mt-16 max-w-2xl sm:mt-20 lg:mt-24 lg:max-w-none">
<dl className="grid max-w-xl grid-cols-1 gap-x-8 gap-y-16 lg:max-w-none md:grid-cols-2 lg:grid-cols-4">
{features.map((feature) => (
<div
key={feature.name}
className="flex flex-col rounded-lg border border-gray-200 p-8 shadow-sm transition-all duration-300 ease-in-out hover:bg-gray-50 hover:shadow-md hover:scale-105"
>
<dt className="text-base/7 font-semibold text-gray-900">
<div className="mb-6 flex h-10 w-10 items-center justify-center">
<Image src={feature.icon} alt="" className="h-10 w-10" />
</div>
{feature.name}
</dt>
<dd className="mt-1 flex flex-auto flex-col text-base/7 text-gray-600">
<p className="flex-auto">{feature.description}</p>
<p className="mt-6">
<a href={feature.href} className="text-sm/6 font-semibold text-cyan-500 hover:text-cyan-500">
Download Now <span aria-hidden="true"></span>
</a>
</p>
</dd>
</div>
))}
</dl>
</div>
</div>
</div>
);
}

View File

@@ -4,9 +4,11 @@ import { ArrowDownTrayIcon } from '@heroicons/react/24/solid'
export function DownloadLink() {
return (
<Link
href="#"
href="https://github.com/threefoldtech/mycelium/releases"
aria-label="Download Mycelium"
className="inline-flex items-center rounded-lg bg-gray-800 px-4 py-2 text-sm font-semibold text-white hover:bg-gray-900 transition-colors"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center rounded-lg bg-cyan-500 px-4 py-2 text-sm font-semibold text-white hover:bg-cyan-600 transition-colors"
>
<ArrowDownTrayIcon className="h-5 w-5 mr-2" />
Get Mycelium

View File

@@ -60,7 +60,7 @@ export function Faqs() {
<div className="mx-auto max-w-2xl lg:mx-0">
<h2
id="faqs-title"
className="text-3xl font-medium tracking-tight text-gray-900"
className="text-3xl lg:text-4xl font-medium tracking-tight text-gray-900"
>
Frequently asked questions
</h2>

111
src/components/Features.tsx Normal file
View File

@@ -0,0 +1,111 @@
import Pathfinding from '@/components/Pathfinding'
import MessageBus from '@/components/MessageBus'
import ProxyDetection from '@/components/ProxyDetection'
import ProxyForwarding from '@/components/ProxyForwarding'
import ContentDistribution from '@/components/ContentDistribution'
export function Features() {
return (
<section id="features" className=" py-24">
<div className="mx-auto max-w-2xl px-6 lg:max-w-7xl lg:px-8">
<h2 className="text-base/7 font-semibold text-cyan-500">Core Components</h2>
<p className="mt-2 max-w-4xl text-3xl lg:text-4xl font-medium tracking-tight text-pretty text-gray-950">
Network Capabilities
</p>
<p className="mt-4 max-w-xl text-lg text-gray-600">
Built for resilience and autonomy, the Mycelium Network dynamically connects nodes through intelligent routing, proxy discovery, and decentralized delivery.
</p>
<p className="mt-2 max-w-xl text-lg text-gray-600">
Each component from message passing to content distribution works in harmony to create a fully self-healing, self-optimizing data mesh.
</p>
<div className="mt-10 grid grid-cols-1 gap-x-4 gap-y-8 sm:mt-16 lg:grid-cols-6 lg:grid-rows-2">
<div className="relative lg:col-span-3 transition-all duration-300 ease-in-out hover:scale-105">
<div className="absolute inset-0 rounded-lg bg-white max-lg:rounded-t-4xl lg:rounded-tl-4xl" />
<div className="relative flex h-full flex-col overflow-hidden rounded-[calc(var(--radius-lg)+1px)] max-lg:rounded-t-[calc(2rem+1px)] lg:rounded-tl-[calc(2rem+1px)]">
<Pathfinding />
<div className="p-10 pt-4">
<h3 className="text-sm/4 font-semibold text-cyan-500">Routing</h3>
<p className="mt-2 text-lg font-medium tracking-tight text-gray-950">
Automatic pathfinding
</p>
<p className="mt-2 max-w-lg text-sm/6 text-gray-600">
The Mycelium Network automatically discovers the shortest and fastest routes between nodes,
ensuring optimal data flow and network efficiency without manual configuration.
</p>
</div>
</div>
<div className="pointer-events-none absolute inset-0 rounded-lg shadow-sm outline outline-black/5 max-lg:rounded-t-4xl lg:rounded-tl-4xl" />
</div>
<div className="relative lg:col-span-3 transition-all duration-300 ease-in-out hover:scale-105">
<div className="absolute inset-0 rounded-lg bg-white lg:rounded-tr-4xl" />
<div className="relative flex h-full flex-col overflow-hidden rounded-[calc(var(--radius-lg)+1px)] lg:rounded-tr-[calc(2rem+1px)]">
<MessageBus />
<div className="p-10 pt-4">
<h3 className="text-sm/4 font-semibold text-cyan-500">Communication</h3>
<p className="mt-2 text-lg font-medium tracking-tight text-gray-950">
Distributed message bus
</p>
<p className="mt-2 max-w-lg text-sm/6 text-gray-600">
Acts as a global message layer that lets nodes exchange information seamlessly.
Enables resilient, asynchronous communication across the entire decentralized mesh.
</p>
</div>
</div>
<div className="pointer-events-none absolute inset-0 rounded-lg shadow-sm outline outline-black/5 lg:rounded-tr-4xl" />
</div>
<div className="relative lg:col-span-2 transition-all duration-300 ease-in-out hover:scale-105">
<div className="absolute inset-0 rounded-lg bg-white lg:rounded-bl-4xl" />
<div className="relative flex h-full flex-col overflow-hidden rounded-[calc(var(--radius-lg)+1px)] lg:rounded-bl-[calc(2rem+1px)]">
<ProxyDetection className="h-80" />
<div className="p-10 pt-4">
<h3 className="text-sm/4 font-semibold text-cyan-500">Discovery</h3>
<p className="mt-2 text-lg font-medium tracking-tight text-gray-950">
Automatic proxy detection
</p>
<p className="mt-2 max-w-lg text-sm/6 text-gray-600">
The system continuously scans for open SOCKS5 proxies within the network,
making it effortless to find available connection points without manual setup.
</p>
</div>
</div>
<div className="pointer-events-none absolute inset-0 rounded-lg shadow-sm outline outline-black/5 lg:rounded-bl-4xl" />
</div>
<div className="relative lg:col-span-2 transition-all duration-300 ease-in-out hover:scale-105">
<div className="absolute inset-0 rounded-lg bg-white" />
<div className="relative flex h-full flex-col overflow-hidden rounded-[calc(var(--radius-lg)+1px)]">
<ProxyForwarding className="h-80" />
<div className="p-10 pt-4">
<h3 className="text-sm/4 font-semibold text-cyan-500">Connectivity</h3>
<p className="mt-2 text-lg font-medium tracking-tight text-gray-950">
Seamless proxy forwarding
</p>
<p className="mt-2 max-w-lg text-sm/6 text-gray-600">
Local SOCKS5 connections can be forwarded through nearby nodes or remote proxies.
When browsers use the local proxy, traffic moves securely through the meshlike a built-in VPN.
</p>
</div>
</div>
<div className="pointer-events-none absolute inset-0 rounded-lg shadow-sm outline outline-black/5" />
</div>
<div className="relative lg:col-span-2 transition-all duration-300 ease-in-out hover:scale-105">
<div className="absolute inset-0 rounded-lg bg-white max-lg:rounded-b-4xl lg:rounded-br-4xl" />
<div className="relative flex h-full flex-col overflow-hidden rounded-[calc(var(--radius-lg)+1px)] max-lg:rounded-b-[calc(2rem+1px)] lg:rounded-br-[calc(2rem+1px)]">
<ContentDistribution className="h-80" />
<div className="p-10 pt-4">
<h3 className="text-sm/4 font-semibold text-cyan-500">Delivery</h3>
<p className="mt-2 text-lg font-medium tracking-tight text-gray-950">
Decentralized content distribution
</p>
<p className="mt-2 max-w-lg text-sm/6 text-gray-600">
Mycelium can serve data from distributed 0-DBs, creating a CDN-like layer that delivers
content faster and more reliablywithout relying on centralized servers.
</p>
</div>
</div>
<div className="pointer-events-none absolute inset-0 rounded-lg shadow-sm outline outline-black/5 max-lg:rounded-b-4xl lg:rounded-br-4xl" />
</div>
</div>
</div>
</section>
)
}

View File

@@ -4,30 +4,17 @@ import Link from 'next/link'
import { Button } from '@/components/Button'
import { Container } from '@/components/Container'
import { TextField } from '@/components/Fields'
import { Logomark } from '@/components/Logo'
import { NavLinks } from '@/components/NavLinks'
import qrCode from '@/images/qr-code.svg'
function QrCodeBorder(props: React.ComponentPropsWithoutRef<'svg'>) {
return (
<svg viewBox="0 0 96 96" fill="none" aria-hidden="true" {...props}>
<path
d="M1 17V9a8 8 0 0 1 8-8h8M95 17V9a8 8 0 0 0-8-8h-8M1 79v8a8 8 0 0 0 8 8h8M95 79v8a8 8 0 0 1-8 8h-8"
strokeWidth="2"
strokeLinecap="round"
/>
</svg>
)
}
import github from '@/images/github.svg'
export function Footer() {
return (
<footer className="border-t border-gray-200">
<Container>
<div className="flex flex-col items-start justify-between gap-y-12 pt-16 pb-6 lg:flex-row lg:items-center lg:py-16">
<div className="flex flex-col items-start justify-between gap-y-12 pt-16 pb-6 lg:flex-row lg:items-center lg:py-8">
<div>
<div className="flex items-center text-gray-900">
<Logomark className="h-10 w-10 flex-none fill-cyan-500" />
<Image src="/images/logo.svg" alt="Mycelium Logomark" width={60} height={60} className="h-20 w-20 flex-none" />
<div className="ml-4">
<p className="text-base font-semibold">Mycelium</p>
<p className="mt-1 text-sm">Unleash the Power of Decentralized Networks</p>
@@ -38,19 +25,18 @@ export function Footer() {
</nav>
</div>
<div className="group relative -mx-4 flex items-center self-stretch p-4 transition-colors hover:bg-gray-100 sm:self-auto sm:rounded-2xl lg:mx-0 lg:self-auto lg:p-6">
<div className="relative flex h-24 w-24 flex-none items-center justify-center">
<QrCodeBorder className="absolute inset-0 h-full w-full stroke-gray-300 transition-colors group-hover:stroke-cyan-500" />
<Image src={qrCode} alt="" unoptimized />
<div className="relative flex h-16 w-16 flex-none items-center justify-center">
<Image src={github} alt="GitHub" unoptimized />
</div>
<div className="ml-8 lg:w-64">
<div className="ml-4 lg:w-72">
<p className="text-base font-semibold text-gray-900">
<Link href="#">
<Link href="https://github.com/threefoldtech/mycelium/releases/" target='_blank'>
<span className="absolute inset-0 sm:rounded-2xl" />
Download the app
Download Mycelium
</Link>
</p>
<p className="mt-1 text-sm text-gray-700">
Scan the QR code to download the app from the App Store.
Head to the GitHub to access the latest Mycelium builds for your devices.
</p>
</div>
</div>

View File

@@ -107,26 +107,11 @@ export function Header() {
}}
className="absolute inset-x-0 top-0 z-0 origin-top rounded-b-2xl bg-gray-50 px-6 pt-32 pb-6 shadow-2xl shadow-gray-900/20"
>
<div className="space-y-4">
<MobileNavLink href="/#about">
About
</MobileNavLink>
<MobileNavLink href="/#benefits">
Benefits
</MobileNavLink>
<MobileNavLink href="/#features">
Features
</MobileNavLink>
<MobileNavLink href="/#usecases">
Use Cases
</MobileNavLink>
<MobileNavLink href="/#faqs">FAQs</MobileNavLink>
</div>
<div className="mt-8 flex flex-col gap-4">
<Button href="https://docs.ourworld.tf/mycelium_cloud/docs/" variant="outline">
<div className="mt-6 flex flex-col gap-4">
<Button href="https://docs.ourworld.tf/mycelium_cloud/docs/" variant="outline" target="_blank" rel="noopener noreferrer">
Docs
</Button>
<Button href="https://www.mycelium.threefold.io/download/">Get Mycelium</Button>
<Button variant="solid" color="cyan" href="/download/">Get Mycelium</Button>
</div>
</PopoverPanel>
</>
@@ -136,10 +121,10 @@ export function Header() {
)}
</Popover>
<div className="flex items-center gap-6 max-lg:hidden">
<Button href="https://docs.ourworld.tf/mycelium_cloud/docs/" variant="outline">
<Button href="https://docs.ourworld.tf/mycelium_cloud/docs/" variant="outline" target="_blank" rel="noopener noreferrer">
Docs
</Button>
<Button href="https://www.mycelium.threefold.io/download/">Get Mycelium</Button>
<Button href="/download/" variant="solid" color="cyan">Get Mycelium</Button>
</div>
</div>
</Container>

View File

@@ -2,11 +2,9 @@ import { useId } from 'react'
import Image from 'next/image'
import clsx from 'clsx'
import { AppDemo } from '@/components/AppDemo'
import { DownloadLink } from '@/components/DownloadLink'
import { Button } from '@/components/Button'
import { Container } from '@/components/Container'
import { PhoneFrame } from '@/components/PhoneFrame'
import logoBbc from '@/images/logos/bbc.svg'
import logoCbs from '@/images/logos/cbs.svg'
import logoCnn from '@/images/logos/cnn.svg'
@@ -100,20 +98,20 @@ function PlayIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
export function Hero() {
return (
<div className="overflow-hidden py-20 sm:py-32 lg:pb-32 xl:pb-36">
<div className="overflow-hidden lg:py-32 lg:pb-0 pb-24">
<Container>
<div className="lg:grid lg:grid-cols-12 lg:gap-x-8 lg:gap-y-20">
<div className="flex flex-col-reverse gap-y-16 lg:grid lg:grid-cols-12 lg:gap-x-8 lg:gap-y-20">
<div className="relative z-10 mx-auto max-w-2xl lg:col-span-7 lg:max-w-none lg:pt-6 xl:col-span-6">
<h1 className="text-4xl font-medium tracking-tight text-gray-900">
<h1 className="text-4xl lg:text-6xl font-medium tracking-tight text-gray-900">
Mycelium
</h1>
<h2 className="mt-6 text-2xl tracking-tight text-gray-600">
Unleashing the Power of Decentralized Networks
<h2 className="mt-6 lg:text-2xl text-xl tracking-tight leading-normal text-gray-600">
Unleashing the Power of Decentralized Networks
</h2>
<p className="mt-6 text-lg text-gray-600">
<p className="mt-6 lg:text-xl text-lg text-gray-600 lg:leading-normal leading-tight">
Discover Mycelium, an end-to-end encrypted IPv6 overlay network. The future of secure, efficient, and scalable networking.
</p>
<p className="mt-6 text-lg text-gray-600">
<p className="mt-6 text-lg text-gray-600 ">
Coming Soon: New Decentralized Features
</p>
<div className="mt-8 flex flex-wrap gap-x-6 gap-y-4">
@@ -127,15 +125,20 @@ export function Hero() {
</Button>
</div>
</div>
<div className="relative mt-10 sm:mt-20 lg:col-span-5 lg:row-span-2 lg:mt-0 xl:col-span-6">
<BackgroundIllustration className="absolute top-4 left-1/2 h-[1026px] w-[1026px] -translate-x-1/3 mask-[linear-gradient(to_bottom,white_20%,transparent_75%)] stroke-gray-300/70 sm:top-16 sm:-translate-x-1/2 lg:-top-16 lg:ml-12 xl:-top-14 xl:ml-0" />
<div className="-mx-4 h-[448px] mask-[linear-gradient(to_bottom,white_60%,transparent)] px-9 sm:mx-0 lg:absolute lg:-inset-x-10 lg:-top-10 lg:-bottom-20 lg:h-auto lg:px-0 lg:pt-10 xl:-bottom-32">
<PhoneFrame className="mx-auto max-w-[366px]" priority>
<AppDemo />
</PhoneFrame>
<div className="relative lg:mt-10 mt-0 lg:col-span-5 lg:row-span-2 xl:col-span-6">
<BackgroundIllustration className="absolute top-4 left-1/2 h-[1026px] w-[1026px] -translate-x-1/2 stroke-gray-300/70 sm:top-16 lg:-top-12 lg:ml-12 ml-0" />
<div className="mx-auto h-[448px] mask-[linear-gradient(to_bottom,white_60%,transparent)] lg:px-0 lg:absolute lg:-inset-x-10 lg:-top-24 lg:h-auto lg:pt-10 xl:-bottom-32">
<Image
src="/images/phoneframe.png"
alt="Mycelium application demo"
className="mx-auto max-w-[366px]"
width={366}
height={729}
priority
/>
</div>
</div>
<div className="relative -mt-4 lg:col-span-7 lg:mt-0 xl:col-span-6">
{/* <div className="relative -mt-4 lg:col-span-7 lg:mt-0 xl:col-span-6">
<p className="text-center text-sm font-semibold text-gray-900 lg:text-left">
As featured in
</p>
@@ -158,7 +161,7 @@ export function Hero() {
</li>
))}
</ul>
</div>
</div> */}
</div>
</Container>
</div>

View File

@@ -8,7 +8,7 @@ export function LinuxLink({
}) {
return (
<Link
href="#"
href="https://github.com/threefoldtech/mycelium/releases"
aria-label="Download for Linux"
className={clsx(
'flex items-center rounded-lg transition-colors px-4 py-2',

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,150 @@
'use client';
import * as React from 'react';
import { motion, useReducedMotion } from 'framer-motion';
type Props = {
className?: string; // e.g. "w-full h-72"
bg?: string; // default white
};
/** Palette (gray/black + accent only) */
const ACCENT = '#00b8db';
const STROKE = '#111827'; // black-ish
const GRAY = '#9CA3AF';
const GRAY_LT = '#E5E7EB';
function Envelope({
x, y, w = 88, h = 56, fill = GRAY_LT, accent = false, delay = 0, duration = 1.6,
path = 'none', // 'left1' | 'left2' | 'rightTop' | 'rightBottom' | 'none'
reverse = false,
}: {
x: number; y: number; w?: number; h?: number; fill?: string; accent?: boolean;
delay?: number; duration?: number; path?: 'left1'|'left2'|'rightTop'|'rightBottom'|'none'; reverse?: boolean;
}) {
const prefersReduced = useReducedMotion();
// simple keyframe paths (straight line segments)
const paths: Record<string, { x: number[]; y: number[] }> = {
left1: { x: [x, 380], y: [y, 220] },
left2: { x: [x, 380], y: [y, 220] },
rightTop: { x: [380, 720], y: [220, 150] },
rightBottom: { x: [380, 720], y: [220, 290] },
none: { x: [x], y: [y] },
};
const k = paths[path];
return (
<motion.g
initial={{ opacity: 0, scale: 0.98 }}
animate={{
opacity: 1,
scale: 1,
x: prefersReduced ? 0 : (reverse ? [...k.x].reverse() : k.x),
y: prefersReduced ? 0 : (reverse ? [...k.y].reverse() : k.y),
}}
transition={{
delay,
duration: prefersReduced ? 0.01 : duration,
ease: [0.22, 1, 0.36, 1],
repeat: prefersReduced ? 0 : Infinity,
repeatDelay: 0.6,
}}
>
{/* envelope body */}
<rect x={-w / 2} y={-h / 2} width={w} height={h} rx={8} fill={fill} stroke={STROKE} strokeWidth={3} />
{/* flap */}
<path
d={`M ${-w/2+4} ${-h/2+6} L 0 ${-h/2+26} L ${w/2-4} ${-h/2+6}`}
fill="none"
stroke={accent ? ACCENT : STROKE}
strokeWidth={4}
strokeLinecap="round"
strokeLinejoin="round"
/>
</motion.g>
);
}
export default function MessageBus({ className, bg = '#ffffff' }: Props) {
const W = 900;
const H = 460;
return (
<div className={className} aria-hidden="true" role="img" style={{ background: bg }}>
<svg viewBox={`0 0 ${W} ${H}`} width="100%" height="100%">
{/* subtle grid */}
<defs>
<pattern id="grid" width="24" height="24" patternUnits="userSpaceOnUse">
<path d="M 24 0 L 0 0 0 24" fill="none" stroke={GRAY_LT} strokeWidth="1" />
</pattern>
</defs>
<rect width={W} height={H} fill="url(#grid)" />
{/* producers (left) */}
{[{cx:140,cy:120},{cx:140,cy:340}].map((n,i)=>(
<g key={i}>
<circle cx={n.cx} cy={n.cy} r={44} fill="#fff" stroke={STROKE} strokeWidth={4}/>
{/* arrows toward queue */}
<motion.path
d={`M ${n.cx+48} ${n.cy} L 320 ${n.cy>200?260:180}`}
fill="none" stroke={STROKE} strokeWidth={4} strokeLinecap="round"
initial={{ pathLength: 0, opacity: 0.3 }}
animate={{ pathLength: 1, opacity: 1 }}
transition={{ duration: 0.9, delay: 0.1 + i*0.1, ease: [0.22,1,0.36,1] }}
/>
</g>
))}
{/* consumers (right) */}
{[{cx:760,cy:120},{cx:760,cy:340}].map((n,i)=>(
<g key={i}>
<circle cx={n.cx} cy={n.cy} r={44} fill="#fff" stroke={STROKE} strokeWidth={4}/>
{/* arrows out from queue */}
<motion.path
d={`M 560 ${i===0?180:260} L ${n.cx-48} ${n.cy}`}
fill="none" stroke={STROKE} strokeWidth={4} strokeLinecap="round"
initial={{ pathLength: 0, opacity: 0.3 }}
animate={{ pathLength: 1, opacity: 1 }}
transition={{ duration: 0.9, delay: 0.2 + i*0.1, ease: [0.22,1,0.36,1] }}
/>
</g>
))}
{/* central queue container */}
<rect x={330} y={150} width={240} height={140} rx={24} fill="#fff" stroke={STROKE} strokeWidth={4} />
{/* inner slots (visual only) */}
{[0,1,2].map(i=>(
<rect key={i} x={350 + i*76} y={170} width={64} height={100} rx={12} fill="none" stroke={GRAY} strokeWidth={3}/>
))}
{/* inbound envelopes (from left nodes to queue) */}
<Envelope x={200} y={120} accent fill="#fff" path="left1" delay={0.0} duration={2.0}/>
<Envelope x={200} y={340} fill={GRAY_LT} path="left2" delay={0.4} duration={2.2}/>
<Envelope x={200} y={340} accent fill="#fff" path="left2" delay={0.9} duration={2.0}/>
{/* queue “processing” envelopes (pulse inside slots) */}
{[0,1,2].map((i)=>(
<motion.g key={i} transform={`translate(${382 + i*76} 220)`}>
<motion.rect
x={-28} y={-18} width={56} height={36} rx={8}
fill={i===2 ? ACCENT : GRAY_LT}
stroke={STROKE} strokeWidth={3}
initial={{ opacity: 0.6 }}
animate={{ opacity: [0.6, 1, 0.6] }}
transition={{ duration: 1.8, repeat: Infinity, delay: i*0.2 }}
/>
<path d="M -24 -12 L 0 0 L 24 -12" fill="none" stroke={i===2 ? STROKE : STROKE} strokeWidth={4} strokeLinecap="round" />
</motion.g>
))}
{/* outbound envelopes (from queue to right nodes) */}
<Envelope x={560} y={180} accent fill="#fff" path="rightTop" delay={0.6} duration={2.1}/>
<Envelope x={560} y={260} fill={GRAY_LT} path="rightBottom" delay={1.0} duration={2.3}/>
<Envelope x={560} y={260} accent fill="#fff" path="rightBottom" delay={1.5} duration={2.0}/>
</svg>
</div>
);
}

View File

@@ -10,9 +10,9 @@ export function NavLinks() {
return [
['About', '/#about'],
['Benefits', '/#benefits'],
['Features', '/#features'],
['Use Cases', '/#usecases'],
['How it Works', '/#howitworks'],
['Coming Soon', '/#comingsoon'],
['FAQs', '/#faqs'],
].map(([label, href], index) => (
<Link
@@ -28,7 +28,15 @@ export function NavLinks() {
onMouseLeave={() => {
timeoutRef.current = window.setTimeout(() => {
setHoveredIndex(null)
}, 200)
}, 50)
}}
onClick={(e) => {
e.preventDefault()
const targetId = href.substring(2)
const targetElement = document.getElementById(targetId)
if (targetElement) {
targetElement.scrollIntoView({ behavior: 'smooth' })
}
}}
>
<AnimatePresence>

View File

@@ -0,0 +1,233 @@
// pathfinding.tsx
// Animated SVG illustrating "Automatic pathfinding"
// - Central hub + surrounding nodes
// - Arrows fade/slide in
// - Shortest path highlights on loop
// - Respects prefers-reduced-motion
'use client';
import * as React from 'react';
import { motion, useReducedMotion } from 'framer-motion';
import clsx from 'clsx';
type Props = {
className?: string; // e.g. "w-full h-64"
accent?: string; // main accent color
stroke?: string; // neutral stroke color
bg?: string; // background color
};
const Node = ({
cx,
cy,
r = 16,
fill = "#00b8db",
ring = "#E5E7EB",
pulse = false,
rMotion = 2,
}: {
cx: number;
cy: number;
r?: number;
fill?: string;
ring?: string;
pulse?: boolean;
rMotion?: number;
}) => {
const prefersReduced = useReducedMotion();
return (
<>
{/* outer ring */}
<motion.circle
cx={cx}
cy={cy}
r={r + 14}
fill="none"
stroke={ring}
strokeWidth={2}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.6 }}
/>
{/* core node */}
<motion.circle
cx={cx}
cy={cy}
r={r}
fill={fill}
initial={{ opacity: 0, scale: 0.8 }}
animate={{
opacity: 1,
scale: pulse && !prefersReduced ? [1, 1 + rMotion / 16, 1] : 1,
}}
transition={{
duration: pulse && !prefersReduced ? 1.8 : 0.6,
repeat: pulse && !prefersReduced ? Infinity : 0,
repeatType: "loop",
ease: [0.22, 1, 0.36, 1],
}}
/>
</>
);
};
const Arrow = ({
d,
color = "#111827",
delay = 0,
}: {
d: string;
color?: string;
delay?: number;
}) => (
<motion.path
d={d}
fill="none"
stroke={color}
strokeWidth={3}
strokeLinecap="round"
strokeLinejoin="round"
initial={{ pathLength: 0, opacity: 0 }}
animate={{ pathLength: 1, opacity: 1 }}
transition={{
delay,
duration: 0.8,
ease: [0.22, 1, 0.36, 1],
}}
/>
);
const DashedPath = ({
d,
color = "#9CA3AF",
dash = 6,
delay = 0,
loop = false,
}: {
d: string;
color?: string;
dash?: number;
delay?: number;
loop?: boolean;
}) => {
const prefersReduced = useReducedMotion();
return (
<motion.path
d={d}
fill="none"
stroke={color}
strokeWidth={3}
strokeDasharray={dash}
strokeLinecap="round"
initial={{ pathLength: 0, opacity: 0.4 }}
animate={{
pathLength: 1,
opacity: 1,
}}
transition={{
delay,
duration: 0.9,
ease: [0.22, 1, 0.36, 1],
repeat: !prefersReduced && loop ? Infinity : 0,
repeatDelay: 1.2,
repeatType: "reverse",
}}
/>
);
};
export default function Pathfinding({
className,
accent = "#00b8db", // indigo-800 vibe
stroke = "#111827", // gray-900
bg = "#FFFFFF",
}: Props) {
// Canvas
const W = 760;
const H = 420;
// Layout (simple radial)
const center = { x: 380, y: 210 };
const nodes = [
{ x: 130, y: 210 }, // left
{ x: 670, y: 210 }, // right
{ x: 380, y: 70 }, // top
{ x: 280, y: 340 }, // bottom-left
{ x: 500, y: 340 }, // bottom-right
];
// Helper to make arrow path with a small head
const arrowTo = (from: { x: number; y: number }, to: { x: number; y: number }) => {
const dx = to.x - from.x;
const dy = to.y - from.y;
const len = Math.hypot(dx, dy);
const ux = dx / len;
const uy = dy / len;
const end = { x: to.x - ux * 18, y: to.y - uy * 18 }; // inset a bit
const headL = {
x: end.x - uy * 8 - ux * 6,
y: end.y + ux * 8 - uy * 6,
};
const headR = {
x: end.x + uy * 8 - ux * 6,
y: end.y - ux * 8 - uy * 6,
};
return `M ${from.x} ${from.y} L ${end.x} ${end.y} M ${headL.x} ${headL.y} L ${end.x} ${end.y} L ${headR.x} ${headR.y}`;
};
// "Shortest" highlighted route: left -> center -> bottom-right
const highlightA = `M ${nodes[0].x} ${nodes[0].y} L ${center.x} ${center.y}`;
const highlightB = `M ${center.x} ${center.y} L ${nodes[4].x} ${nodes[4].y}`;
// Faint alternative routes
const alt1 = `M ${nodes[2].x} ${nodes[2].y} L ${center.x} ${center.y}`;
const alt2 = `M ${nodes[3].x} ${nodes[3].y} L ${center.x} ${center.y}`;
const alt3 = `M ${center.x} ${center.y} L ${nodes[1].x} ${nodes[1].y}`;
return (
<div
className={clsx(
"relative overflow-hidden",
className
)}
aria-hidden="true"
role="img"
aria-label="Automatic pathfinding between nodes"
style={{ background: bg }}
>
<svg viewBox={`0 0 ${W} ${H}`} className="w-full h-full">
{/* background subtle grid */}
<defs>
<pattern id="grid" width="24" height="24" patternUnits="userSpaceOnUse">
<path d="M 24 0 L 0 0 0 24" fill="none" stroke="#F3F4F6" strokeWidth="1" />
</pattern>
</defs>
<rect width={W} height={H} fill="url(#grid)" />
{/* faint alternative connections */}
<DashedPath d={alt1} color="#E5E7EB" dash={5} delay={0.1} />
<DashedPath d={alt2} color="#E5E7EB" dash={5} delay={0.2} />
<DashedPath d={alt3} color="#E5E7EB" dash={5} delay={0.3} />
{/* highlighted “shortest path” (animates / pulses) */}
<DashedPath d={highlightA} color={accent} dash={8} delay={0.2} loop />
<DashedPath d={highlightB} color={accent} dash={8} delay={0.4} loop />
{/* directional arrows toward the center (auto routing) */}
<Arrow d={arrowTo(nodes[0], center)} color={stroke} delay={0.1} />
<Arrow d={arrowTo(nodes[2], center)} color={stroke} delay={0.2} />
<Arrow d={arrowTo(nodes[3], center)} color={stroke} delay={0.25} />
<Arrow d={arrowTo(nodes[1], center)} color={stroke} delay={0.3} />
{/* nodes */}
<Node cx={center.x} cy={center.y} r={18} fill={accent} ring="#E5E7EB" pulse />
{nodes.map((n, i) => (
<Node key={i} cx={n.x} cy={n.y} r={14} fill="#FFFFFF" ring="#E5E7EB" />
))}
</svg>
</div>
);
}

View File

@@ -1,22 +1,6 @@
import Image from 'next/image'
import clsx from 'clsx'
import frame from '@/images/phone-frame.svg'
function PlaceholderFrame(props: React.ComponentPropsWithoutRef<'svg'>) {
return (
<svg viewBox="0 0 366 729" aria-hidden="true" {...props}>
<path
fill="#F2F2F2"
fillRule="evenodd"
clipRule="evenodd"
d="M300.092 1c41.22 0 63.223 21.99 63.223 63.213V184.94c-.173.184-.329.476-.458.851.188-.282.404-.547.647-.791.844-.073 2.496.257 2.496 2.157V268.719c-.406 2.023-2.605 2.023-2.605 2.023a7.119 7.119 0 0 1-.08-.102v394.462c0 41.213-22.001 63.212-63.223 63.212h-95.074c-.881-.468-2.474-.795-4.323-.838l-33.704-.005-.049.001h-.231l-.141-.001c-2.028 0-3.798.339-4.745.843H66.751c-41.223 0-63.223-21.995-63.223-63.208V287.739c-.402-.024-2.165-.23-2.524-2.02v-.973A2.039 2.039 0 0 1 1 284.62v-47.611c0-.042.001-.084.004-.126v-.726c0-1.9 1.652-2.23 2.496-2.157l.028.028v-16.289c-.402-.024-2.165-.23-2.524-2.02v-.973A2.039 2.039 0 0 1 1 214.62v-47.611c0-.042.001-.084.004-.126v-.726c0-1.9 1.652-2.23 2.496-2.157l.028.028v-26.041a2.26 2.26 0 0 0 .093-.236l-.064-.01a3.337 3.337 0 0 1-.72-.12l-.166-.028A2 2 0 0 1 1 135.62v-24.611a2 2 0 0 1 1.671-1.973l.857-.143v-44.68C3.528 22.99 25.53 1 66.75 1h233.341ZM3.952 234.516a5.481 5.481 0 0 0-.229-.278c.082.071.159.163.228.278Zm89.99-206.304A4.213 4.213 0 0 0 89.727 24H56.864C38.714 24 24 38.708 24 56.852v618.296C24 693.292 38.714 708 56.864 708h250.272c18.15 0 32.864-14.708 32.864-32.852V56.852C340 38.708 325.286 24 307.136 24h-32.864a4.212 4.212 0 0 0-4.213 4.212v2.527c0 10.235-8.3 18.532-18.539 18.532H112.48c-10.239 0-18.539-8.297-18.539-18.532v-2.527Z"
/>
<rect x="154" y="29" width="56" height="5" rx="2.5" fill="#D4D4D4" />
</svg>
)
}
export function PhoneFrame({
className,
children,
@@ -24,19 +8,17 @@ export function PhoneFrame({
...props
}: React.ComponentPropsWithoutRef<'div'> & { priority?: boolean }) {
return (
<div className={clsx('relative aspect-366/729', className)} {...props}>
<div className="absolute inset-y-[calc(1/729*100%)] right-[calc(5/729*100%)] left-[calc(7/729*100%)] rounded-[calc(58/366*100%)/calc(58/729*100%)] shadow-2xl" />
<div className="absolute top-[calc(23/729*100%)] left-[calc(23/366*100%)] grid h-[calc(686/729*100%)] w-[calc(318/366*100%)] transform grid-cols-1 overflow-hidden bg-gray-900 pt-[calc(23/318*100%)]">
{children}
</div>
<PlaceholderFrame className="pointer-events-none absolute inset-0 h-full w-full fill-gray-100" />
<div className={clsx('relative aspect-[366/729]', className)} {...props}>
<Image
src={frame}
src="/images/phone-frame.svg"
alt=""
className="pointer-events-none absolute inset-0 h-full w-full"
unoptimized
className="pointer-events-none absolute inset-0"
fill
priority={priority}
/>
<div className="absolute inset-x-[6.3%] top-[3.15%] bottom-[2.75%] rounded-3xl overflow-y-auto bg-gray-900">
{children}
</div>
</div>
)
}

View File

@@ -132,7 +132,7 @@ function Plan({
</h3>
<p
className={clsx(
'relative mt-5 flex text-3xl tracking-tight',
'relative mt-5 flex text-3xl lg:text-4xl tracking-tight',
featured ? 'text-white' : 'text-gray-900',
)}
>
@@ -221,7 +221,7 @@ export function Pricing() {
<div className="mx-auto max-w-2xl text-center">
<h2
id="pricing-title"
className="text-3xl font-medium tracking-tight text-gray-900"
className="text-3xl lg:text-4xl font-medium tracking-tight text-gray-900"
>
Flat pricing, no management fees.
</h2>

View File

@@ -15,6 +15,7 @@ import { useDebouncedCallback } from 'use-debounce'
import { AppScreen } from '@/components/AppScreen'
import { CircleBackground } from '@/components/CircleBackground'
import { Container } from '@/components/Container'
import Image from 'next/image'
import { PhoneFrame } from '@/components/PhoneFrame'
import {
DiageoLogo,
@@ -37,23 +38,23 @@ interface CustomAnimationProps {
const features = [
{
name: 'Decentralized Nodes',
name: 'Mycelium Connector',
description:
"Mycelium operates through a network of decentralized nodes, similar to how nature's mycelium forms a decentralized network of threads. Each node acts as a connection point in the overall digital ecosystem.",
"Start (and stop) your Mycelium connector to gain access to sites, apps, and workloads available exclusively on the Mycelium Network. View statistics around peers and traffic.",
icon: DeviceUserIcon,
screen: InviteScreen,
},
{
name: 'Efficient Data Routing',
name: 'Mycelium Peers',
description:
'Mycelium optimizes data routing by choosing the most efficient path for communication. Data travels along the shortest path in terms of latency, ensuring that information reaches its destination swiftly.',
'Search and discover active peers on the Mycelium Network, or add your own.',
icon: DeviceNotificationIcon,
screen: StocksScreen,
},
{
name: 'End-to-End Encryption',
name: 'Network Setting',
description:
'Each node in the system is identified by a unique key pair. Data between nodes is encrypted using secret keys derived from these pairs. This ensures that data remains confidential, enhancing the privacy of the network.',
'Find version and network information and trigger light or dark mode.',
icon: DeviceTouchIcon,
screen: InvestScreen,
},
@@ -193,35 +194,7 @@ type ScreenProps =
function InviteScreen(props: ScreenProps) {
return (
<AppScreen className="w-full">
<MotionAppScreenHeader {...(props.animated ? headerAnimation : {})}>
<AppScreen.Title>Invite people</AppScreen.Title>
<AppScreen.Subtitle>
Get tips <span className="text-white">5s sooner</span> for every
invite.
</AppScreen.Subtitle>
</MotionAppScreenHeader>
<MotionAppScreenBody
{...(props.animated ? { ...bodyAnimation, custom: props.custom } : {})}
>
<div className="px-4 py-6">
<div className="space-y-6">
{[
{ label: 'Full name', value: 'Albert H. Wiggin' },
{ label: 'Email address', value: 'awiggin@chase.com' },
].map((field) => (
<div key={field.label}>
<div className="text-sm text-gray-500">{field.label}</div>
<div className="mt-2 border-b border-gray-200 pb-2 text-sm text-gray-900">
{field.value}
</div>
</div>
))}
</div>
<div className="mt-6 rounded-lg bg-cyan-500 px-3 py-2 text-center text-sm font-semibold text-white">
Invite person
</div>
</div>
</MotionAppScreenBody>
<Image src="/images/connector.png" alt="Mycelium Connector" width={366} height={732} className="mt-[-2rem]" />
</AppScreen>
)
}
@@ -229,101 +202,7 @@ function InviteScreen(props: ScreenProps) {
function StocksScreen(props: ScreenProps) {
return (
<AppScreen className="w-full">
<MotionAppScreenHeader {...(props.animated ? headerAnimation : {})}>
<AppScreen.Title>Stocks</AppScreen.Title>
<AppScreen.Subtitle>March 9, 2022</AppScreen.Subtitle>
</MotionAppScreenHeader>
<MotionAppScreenBody
{...(props.animated ? { ...bodyAnimation, custom: props.custom } : {})}
>
<div className="divide-y divide-gray-100">
{[
{
name: 'Laravel',
price: '4,098.01',
change: '+4.98%',
color: '#F9322C',
logo: LaravelLogo,
},
{
name: 'Tuple',
price: '5,451.10',
change: '-3.38%',
color: '#5A67D8',
logo: TupleLogo,
},
{
name: 'Transistor',
price: '4,098.41',
change: '+6.25%',
color: '#2A5B94',
logo: TransistorLogo,
},
{
name: 'Diageo',
price: '250.65',
change: '+1.25%',
color: '#3320A7',
logo: DiageoLogo,
},
{
name: 'StaticKit',
price: '250.65',
change: '-3.38%',
color: '#2A3034',
logo: StaticKitLogo,
},
{
name: 'Statamic',
price: '5,040.85',
change: '-3.11%',
color: '#0EA5E9',
logo: StatamicLogo,
},
{
name: 'Mirage',
price: '140.44',
change: '+9.09%',
color: '#16A34A',
logo: MirageLogo,
},
{
name: 'Reversable',
price: '550.60',
change: '-1.25%',
color: '#8D8D8D',
logo: ReversableLogo,
},
].map((stock) => (
<div key={stock.name} className="flex items-center gap-4 px-4 py-3">
<div
className="flex-none rounded-full"
style={{ backgroundColor: stock.color }}
>
<stock.logo className="h-10 w-10" />
</div>
<div className="flex-auto text-sm text-gray-900">
{stock.name}
</div>
<div className="flex-none text-right">
<div className="text-sm font-medium text-gray-900">
{stock.price}
</div>
<div
className={clsx(
'text-xs/5',
stock.change.startsWith('+')
? 'text-cyan-500'
: 'text-gray-500',
)}
>
{stock.change}
</div>
</div>
</div>
))}
</div>
</MotionAppScreenBody>
<Image src="/images/peers.png" alt="Mycelium Peers" width={366} height={732} className="mt-[-2rem]" />
</AppScreen>
)
}
@@ -331,54 +210,7 @@ function StocksScreen(props: ScreenProps) {
function InvestScreen(props: ScreenProps) {
return (
<AppScreen className="w-full">
<MotionAppScreenHeader {...(props.animated ? headerAnimation : {})}>
<AppScreen.Title>Buy $LA</AppScreen.Title>
<AppScreen.Subtitle>
<span className="text-white">$34.28</span> per share
</AppScreen.Subtitle>
</MotionAppScreenHeader>
<MotionAppScreenBody
{...(props.animated ? { ...bodyAnimation, custom: props.custom } : {})}
>
<div className="px-4 py-6">
<div className="space-y-4">
{[
{ label: 'Number of shares', value: '100' },
{
label: 'Current market price',
value: (
<div className="flex">
$34.28
<svg viewBox="0 0 24 24" fill="none" className="h-6 w-6">
<path
d="M17 15V7H9M17 7 7 17"
stroke="#06B6D4"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</div>
),
},
{ label: 'Estimated cost', value: '$3,428.00' },
].map((item) => (
<div
key={item.label}
className="flex justify-between border-b border-gray-100 pb-4"
>
<div className="text-sm text-gray-500">{item.label}</div>
<div className="text-sm font-semibold text-gray-900">
{item.value}
</div>
</div>
))}
<div className="rounded-lg bg-cyan-500 px-3 py-2 text-center text-sm font-semibold text-white">
Buy shares
</div>
</div>
</div>
</MotionAppScreenBody>
<Image src="/images/setting.png" alt="Mycelium Settings" width={366} height={732} className="mt-[-2rem]" />
</AppScreen>
)
}
@@ -410,7 +242,7 @@ function FeaturesDesktop() {
return (
<TabGroup
className="grid grid-cols-12 items-center gap-8 lg:gap-16 xl:gap-24"
className="grid grid-cols-12 items-center gap-8 lg:gap-16"
selectedIndex={selectedIndex}
onChange={onChange}
vertical
@@ -419,7 +251,7 @@ function FeaturesDesktop() {
{features.map((feature, featureIndex) => (
<div
key={feature.name}
className="relative rounded-2xl transition-colors hover:bg-gray-800/30"
className="relative rounded-2xl transition-all duration-300 ease-in-out hover:scale-105 hover:bg-gray-800/30"
>
{featureIndex === selectedIndex && (
<motion.div
@@ -517,7 +349,7 @@ function FeaturesMobile() {
<div
key={featureIndex}
ref={(ref) => ref && (slideRefs.current[featureIndex] = ref)}
className="w-full flex-none snap-center px-4 sm:px-6"
className="w-full flex-none snap-center px-4 sm:px-6 transition-all duration-300 ease-in-out hover:scale-105"
>
<div className="relative transform overflow-hidden rounded-2xl bg-gray-800 px-5 py-6">
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
@@ -570,15 +402,16 @@ function FeaturesMobile() {
export function PrimaryFeatures() {
return (
<section
id="features"
id="howitworks"
aria-label="Features for investing all your money"
className="bg-gray-900 py-20 sm:py-32"
>
<Container>
<div className="mx-auto max-w-2xl lg:mx-0 lg:max-w-3xl">
<h2 className="text-3xl font-medium tracking-tight text-white">
<h2 className="text-base/7 font-semibold text-cyan-500">How It Works</h2>
<p className="text-3xl lg:text-4xl font-medium tracking-tight text-white">
How Mycelium Operates
</h2>
</p>
<p className="mt-6 text-lg text-gray-300">
Mycelium, like its natural namesake, thrives on decentralization, efficiency, and security, making it a truly powerful force in the world of decentralized networks.
</p>

View File

@@ -0,0 +1,194 @@
'use client';
import * as React from 'react';
import { motion, useReducedMotion } from 'framer-motion';
type Props = {
className?: string; // e.g. "w-full h-64"
bg?: string; // defaults to white
};
// Palette (only these)
const ACCENT = '#00b8db';
const STROKE = '#111827';
const GRAY = '#9CA3AF';
const GRAY_LT = '#E5E7EB';
function Magnifier({
x = 0,
y = 0,
flip = false,
delay = 0,
duration = 3,
}: {
x?: number;
y?: number;
flip?: boolean; // rotate handle direction
delay?: number;
duration?: number;
}) {
const prefersReduced = useReducedMotion();
return (
<motion.g
initial={{ x: 0 }}
animate={{ x: [0, 520] }}
transition={{
delay,
duration: prefersReduced ? 0.01 : duration,
ease: [0.22, 1, 0.36, 1],
repeat: prefersReduced ? 0 : Infinity,
repeatType: 'reverse',
repeatDelay: 0.4,
}}
transform={`translate(${x}, ${y})`}
>
{/* glass */}
<circle cx={0} cy={0} r={38} fill="#fff" stroke={STROKE} strokeWidth={6} />
{/* subtle scanning pulse inside the glass */}
<motion.circle
cx={0}
cy={0}
r={26}
fill="none"
stroke={ACCENT}
strokeWidth={4}
initial={{ opacity: 0.15, scale: 0.8 }}
animate={{ opacity: [0.15, 0.35, 0.15], scale: [0.8, 1.05, 0.8] }}
transition={{ duration: 1.6, repeat: Infinity }}
/>
{/* handle */}
<g transform={`rotate(${flip ? 40 : -40}) translate(35, 10)`}>
<rect x={0} y={-6} width={80} height={12} rx={6} fill={STROKE} />
<rect x={0} y={-12} width={14} height={24} rx={6} fill={GRAY} />
</g>
</motion.g>
);
}
function ServerBox({
x,
y,
w = 88,
h = 50,
delay = 0,
accentPulse = false,
}: {
x: number;
y: number;
w?: number;
h?: number;
delay?: number;
accentPulse?: boolean;
}) {
const prefersReduced = useReducedMotion();
return (
<motion.g
transform={`translate(${x}, ${y})`}
initial={{ opacity: 0.6 }}
animate={{
opacity: 1,
}}
transition={{ delay, duration: 0.4 }}
>
{/* outer box */}
<rect
x={-w / 2}
y={-h / 2}
width={w}
height={h}
rx={10}
fill="#fff"
stroke={STROKE}
strokeWidth={3}
/>
{/* top bar */}
<rect
x={-w / 2 + 6}
y={-h / 2 + 8}
width={w - 12}
height={12}
rx={6}
fill={GRAY_LT}
/>
{/* activity line */}
<motion.rect
x={-w / 2 + 10}
y={-h / 2 + 26}
width={w - 20}
height={10}
rx={5}
fill={GRAY_LT}
initial={{ width: w * 0.2 }}
animate={{ width: [w * 0.2, w - 20, w * 0.2] }}
transition={{
delay,
duration: prefersReduced ? 0.01 : 1.8,
repeat: prefersReduced ? 0 : Infinity,
ease: [0.22, 1, 0.36, 1],
}}
/>
{/* “detected” indicator */}
<motion.circle
cx={w / 2 - 14}
cy={h / 2 - 14}
r={6}
fill={accentPulse ? ACCENT : GRAY}
initial={{ scale: 0.9, opacity: 0.8 }}
animate={
accentPulse && !prefersReduced
? { scale: [0.9, 1.15, 0.9], opacity: [0.8, 1, 0.8] }
: { scale: 1, opacity: 0.9 }
}
transition={{ duration: 1.4, repeat: accentPulse && !prefersReduced ? Infinity : 0 }}
/>
</motion.g>
);
}
export default function ProxyDetection({ className, bg = '#ffffff' }: Props) {
// Canvas
const W = 900;
const H = 180;
// Layout: a row of proxy servers
const rowY = H / 2;
const xs = [180, 320, 460, 600, 740];
// Sequence timings so boxes light up as magnifier passes
const delays = [0.8, 0.6, 0.4, 0.2, 0.0];
return (
<div
className={className}
aria-hidden="true"
role="img"
style={{ background: bg }}
>
<svg viewBox={`0 0 ${W} ${H}`} width="100%" height="100%">
{/* subtle grid */}
<defs>
<pattern id="grid" width="24" height="24" patternUnits="userSpaceOnUse">
<path d="M 24 0 L 0 0 0 24" fill="none" stroke={GRAY_LT} strokeWidth="1" />
</pattern>
</defs>
<rect width={W} height={H} fill="url(#grid)" />
{/* Server row (right -> left sweep) */}
{xs.map((x, i) => (
<ServerBox
key={`b-${i}`}
x={x}
y={rowY}
delay={delays[i]}
accentPulse
/>
))}
{/* Magnifier scanning across the row (opposite direction) */}
<Magnifier x={120} y={rowY} flip={true} delay={0.25} duration={3.2} />
</svg>
</div>
);
}

View File

@@ -0,0 +1,175 @@
'use client';
import * as React from 'react';
import { motion, useReducedMotion } from 'framer-motion';
type Props = {
className?: string; // e.g. "w-full h-72"
bg?: string; // default white
};
/** Palette */
const ACCENT = '#00b8db';
const STROKE = '#111827'; // black-ish
const GRAY = '#9CA3AF';
const GRAY_LT = '#E5E7EB';
function Laptop({ x, y }: { x: number; y: number }) {
return (
<g transform={`translate(${x}, ${y})`}>
{/* screen */}
<rect x={-48} y={-32} width={96} height={64} rx={8} fill="#fff" stroke={STROKE} strokeWidth={3} />
<rect x={-44} y={-28} width={88} height={40} rx={6} fill={GRAY_LT} />
{/* base */}
<rect x={-56} y={32} width={112} height={10} rx={5} fill={STROKE} />
</g>
);
}
function ServerStack({ x, y }: { x: number; y: number }) {
return (
<g transform={`translate(${x}, ${y})`}>
{[0, 1, 2].map((i) => (
<g key={i} transform={`translate(0, ${-38 + i * 28})`}>
<rect x={-56} y={-12} width={112} height={24} rx={8} fill="#fff" stroke={STROKE} strokeWidth={3} />
<rect x={-46} y={-6} width={56} height={12} rx={6} fill={GRAY_LT} />
<circle cx={20} cy={0} r={4} fill={GRAY} />
<circle cx={30} cy={0} r={4} fill={ACCENT} />
<circle cx={40} cy={0} r={4} fill={GRAY} />
</g>
))}
{/* tiny rack base */}
<rect x={-18} y={48} width={36} height={6} rx={3} fill={GRAY} />
<rect x={-10} y={54} width={20} height={6} rx={3} fill={ACCENT} />
</g>
);
}
function Cloud({ x, y }: { x: number; y: number }) {
return (
<g transform={`translate(${x}, ${y})`} fill={STROKE}>
<circle cx={-30} cy={0} r={18} fill={STROKE} />
<circle cx={-8} cy={-10} r={22} fill={STROKE} />
<circle cx={16} cy={0} r={20} fill={STROKE} />
<rect x={-40} y={0} width={72} height={20} rx={10} fill={STROKE} />
<rect x={-46} y={18} width={88} height={6} rx={3} fill={STROKE} />
</g>
);
}
function Arrow({ d, delay = 0 }: { d: string; delay?: number }) {
return (
<motion.path
d={d}
fill="none"
stroke={STROKE}
strokeWidth={4}
strokeLinecap="round"
initial={{ pathLength: 0, opacity: 0.3 }}
animate={{ pathLength: 1, opacity: 1 }}
transition={{ duration: 0.8, delay, ease: [0.22, 1, 0.36, 1] }}
/>
);
}
/** Small packet traveling along keyframe x/y arrays */
function Packet({
xs,
ys,
delay = 0,
color = ACCENT,
duration = 2.2,
}: {
xs: number[];
ys: number[];
delay?: number;
color?: string;
duration?: number;
}) {
const prefersReduced = useReducedMotion();
return (
<motion.circle
r={6}
fill={color}
initial={{ x: xs[0], y: ys[0], opacity: 0 }}
animate={{
x: prefersReduced ? xs[0] : xs,
y: prefersReduced ? ys[0] : ys,
opacity: 1,
}}
transition={{
delay,
duration: prefersReduced ? 0.01 : duration,
ease: [0.22, 1, 0.36, 1],
repeat: prefersReduced ? 0 : Infinity,
repeatDelay: 0.6,
}}
stroke="#fff"
strokeWidth={2}
/>
);
}
export default function ProxyForwarding({ className, bg = '#ffffff' }: Props) {
const W = 1000;
const H = 420;
// Key points
const C1 = { x: 140, y: 90 };
const C2 = { x: 140, y: 210 };
const C3 = { x: 140, y: 330 };
const PROXY = { x: 420, y: 210 };
const CLOUD = { x: 640, y: 210 };
const DEST = { x: 860, y: 210 };
return (
<div className={className} aria-hidden="true" role="img" style={{ background: bg }}>
<svg viewBox={`0 0 ${W} ${H}`} width="100%" height="100%">
{/* subtle grid bg */}
<defs>
<pattern id="grid" width="24" height="24" patternUnits="userSpaceOnUse">
<path d="M 24 0 L 0 0 0 24" fill="none" stroke={GRAY_LT} strokeWidth="1" />
</pattern>
</defs>
<rect width={W} height={H} fill="url(#grid)" />
{/* Clients */}
<Laptop x={C1.x} y={C1.y} />
<Laptop x={C2.x} y={C2.y} />
<Laptop x={C3.x} y={C3.y} />
{/* Proxy (stack) */}
<ServerStack x={PROXY.x} y={PROXY.y} />
{/* Cloud / Internet */}
<Cloud x={CLOUD.x} y={CLOUD.y} />
{/* Destination servers */}
<ServerStack x={DEST.x} y={DEST.y} />
{/* Arrows: clients -> proxy */}
<Arrow d={`M ${C1.x + 70} ${C1.y} C 260 ${C1.y}, 320 150, ${PROXY.x - 80} 170`} delay={0.05} />
<Arrow d={`M ${C2.x + 70} ${C2.y} L ${PROXY.x - 80} ${PROXY.y}`} delay={0.1} />
<Arrow d={`M ${C3.x + 70} ${C3.y} C 260 ${C3.y}, 320 270, ${PROXY.x - 80} 250`} delay={0.15} />
{/* Arrow: proxy -> cloud -> destination */}
<Arrow d={`M ${PROXY.x + 80} ${PROXY.y} L ${CLOUD.x - 60} ${CLOUD.y}`} delay={0.2} />
<Arrow d={`M ${CLOUD.x + 60} ${CLOUD.y} L ${DEST.x - 80} ${DEST.y}`} delay={0.25} />
{/* Packets flowing from clients to proxy */}
<Packet xs={[C1.x + 70, PROXY.x - 80]} ys={[C1.y, 170]} delay={0.0} />
<Packet xs={[C2.x + 70, PROXY.x - 80]} ys={[C2.y, PROXY.y]} delay={0.3} color={GRAY} />
<Packet xs={[C3.x + 70, PROXY.x - 80]} ys={[C3.y, 250]} delay={0.6} />
{/* Packets moving through proxy to cloud */}
<Packet xs={[PROXY.x + 80, CLOUD.x - 60]} ys={[PROXY.y, CLOUD.y]} delay={0.4} />
<Packet xs={[PROXY.x + 80, CLOUD.x - 60]} ys={[PROXY.y, CLOUD.y]} delay={0.9} color={GRAY} />
{/* Packets leaving cloud to destination */}
<Packet xs={[CLOUD.x + 60, DEST.x - 80]} ys={[CLOUD.y, DEST.y]} delay={0.7} />
<Packet xs={[CLOUD.x + 60, DEST.x - 80]} ys={[CLOUD.y, DEST.y]} delay={1.1} color={GRAY} />
</svg>
</div>
);
}

View File

@@ -189,15 +189,16 @@ function DeviceChartIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
export function SecondaryFeatures() {
return (
<section
id="secondary-features"
id="comingsoon"
aria-label="Features for building a portfolio"
className="py-20 sm:py-32"
>
<Container>
<div className="mx-auto max-w-4xl sm:text-center">
<h2 className="text-3xl font-medium tracking-tight text-gray-900">
<h2 className="text-base/7 font-semibold text-cyan-500">Roadmap</h2>
<p className="text-3xl lg:text-4xl font-medium tracking-tight text-gray-900">
Coming Soon: The Future of Mycelium
</h2>
</p>
<p className="mt-6 text-lg text-gray-600">
Mycelium is evolving to bring even more powerful decentralized features, designed to enhance your experience and expand possibilities. Be the first to explore what's coming next by staying connected with our latest updates.
</p>
@@ -209,7 +210,7 @@ export function SecondaryFeatures() {
{features.map((feature) => (
<li
key={feature.name}
className="rounded-2xl border border-gray-200 p-8"
className="rounded-2xl border border-gray-200 p-8 transition-all duration-300 ease-in-out hover:scale-105"
>
<feature.icon className="h-8 w-8" />
<h3 className="mt-6 font-semibold text-gray-900">

View File

@@ -359,7 +359,7 @@ export function UseCases() {
<div className="mx-auto max-w-2xl lg:max-w-5xl">
<h2
id="usecases-title"
className="text-3xl font-medium tracking-tight text-gray-900 sm:text-center"
className="text-3xl lg:text-4xl font-medium tracking-tight text-gray-900 sm:text-center"
>
Powering Secure & Decentralized Connectivity
</h2>

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="80" zoomAndPan="magnify" viewBox="0 0 60 60" height="80" preserveAspectRatio="xMidYMid meet" version="1.0"><defs><clipPath id="3ce6073860"><path d="M 1.566406 25 L 8 25 L 8 43 L 1.566406 43 Z M 1.566406 25 " clip-rule="nonzero"/></clipPath><clipPath id="225ba38cc1"><path d="M 10 25 L 34 25 L 34 53.640625 L 10 53.640625 Z M 10 25 " clip-rule="nonzero"/></clipPath></defs><g clip-path="url(#3ce6073860)"><path fill="#000000" d="M 4.445312 25.296875 C 2.855469 25.296875 1.5625 26.582031 1.5625 28.164062 L 1.5625 39.636719 C 1.5625 41.21875 2.855469 42.503906 4.445312 42.503906 C 6.035156 42.503906 7.324219 41.21875 7.324219 39.636719 L 7.324219 28.164062 C 7.324219 26.582031 6.035156 25.296875 4.445312 25.296875 Z M 4.445312 25.296875 " fill-opacity="1" fill-rule="nonzero"/></g><path fill="#000000" d="M 39.015625 25.296875 C 37.425781 25.296875 36.132812 26.582031 36.132812 28.164062 L 36.132812 39.636719 C 36.132812 41.21875 37.425781 42.503906 39.015625 42.503906 C 40.605469 42.503906 41.894531 41.21875 41.894531 39.636719 L 41.894531 28.164062 C 41.894531 26.582031 40.605469 25.296875 39.015625 25.296875 Z M 39.015625 25.296875 " fill-opacity="1" fill-rule="nonzero"/><g clip-path="url(#225ba38cc1)"><path fill="#000000" d="M 10.207031 42.667969 C 10.207031 44.253906 11.496094 45.535156 13.085938 45.535156 L 13.085938 51.105469 C 13.085938 52.6875 14.378906 53.972656 15.96875 53.972656 C 17.558594 53.972656 18.847656 52.6875 18.847656 51.105469 L 18.847656 45.535156 L 24.609375 45.535156 L 24.609375 51.105469 C 24.609375 52.6875 25.902344 53.972656 27.492188 53.972656 C 29.082031 53.972656 30.371094 52.6875 30.371094 51.105469 L 30.371094 45.535156 C 31.960938 45.535156 33.253906 44.253906 33.253906 42.667969 L 33.253906 25.464844 L 10.207031 25.464844 Z M 10.207031 42.667969 " fill-opacity="1" fill-rule="nonzero"/></g><path fill="#000000" d="M 28.921875 13.53125 L 31.484375 10.4375 C 31.992188 9.824219 31.90625 8.921875 31.292969 8.417969 C 30.675781 7.914062 29.769531 8 29.261719 8.609375 L 26.460938 11.992188 C 25.015625 11.339844 23.421875 10.957031 21.730469 10.957031 C 20.015625 10.957031 18.402344 11.355469 16.941406 12.023438 L 14.214844 8.628906 C 13.714844 8.011719 12.808594 7.910156 12.1875 8.40625 C 11.570312 8.902344 11.46875 9.804688 11.964844 10.421875 L 14.492188 13.570312 C 11.898438 15.671875 10.207031 18.839844 10.207031 22.429688 L 33.253906 22.429688 C 33.253906 18.816406 31.542969 15.632812 28.921875 13.53125 Z M 18.847656 18.128906 C 18.054688 18.128906 17.410156 17.484375 17.410156 16.695312 C 17.410156 15.902344 18.054688 15.261719 18.847656 15.261719 C 19.644531 15.261719 20.289062 15.902344 20.289062 16.695312 C 20.289062 17.484375 19.644531 18.128906 18.847656 18.128906 Z M 24.609375 18.128906 C 23.816406 18.128906 23.171875 17.484375 23.171875 16.695312 C 23.171875 15.902344 23.816406 15.261719 24.609375 15.261719 C 25.40625 15.261719 26.050781 15.902344 26.050781 16.695312 C 26.050781 17.484375 25.40625 18.128906 24.609375 18.128906 Z M 24.609375 18.128906 " fill-opacity="1" fill-rule="nonzero"/></svg>
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" width="80" height="80"><defs><clipPath clipPathUnits="userSpaceOnUse" id="cp1"><path d="m2.09 33.33h8.58v24h-8.58z"/></clipPath><clipPath clipPathUnits="userSpaceOnUse" id="cp2"><path d="m13.33 33.33h32v38.19h-32z"/></clipPath></defs><style>.a{fill:#00b8db}</style><g clip-path="url(#cp1)"><path class="a" d="m5.9 33.7c-2.1 0-3.8 1.7-3.8 3.9v15.2c0 2.2 1.7 3.9 3.8 3.9 2.1 0 3.9-1.7 3.9-3.9v-15.2c0-2.2-1.8-3.9-3.9-3.9z"/></g><path class="a" d="m52 33.7c-2.1 0-3.8 1.7-3.8 3.9v15.2c0 2.2 1.7 3.9 3.8 3.9 2.1 0 3.9-1.7 3.9-3.9v-15.2c0-2.2-1.8-3.9-3.9-3.9z"/><g clip-path="url(#cp2)"><path class="a" d="m13.6 56.9c0 2.1 1.7 3.8 3.8 3.8v7.4c0 2.2 1.8 3.9 3.9 3.9 2.1 0 3.8-1.7 3.8-3.9v-7.4h7.7v7.4c0 2.2 1.7 3.9 3.9 3.9 2.1 0 3.8-1.7 3.8-3.9v-7.4c2.1 0 3.8-1.7 3.8-3.8v-22.9h-30.7z"/></g><path class="a" d="m38.6 18l3.4-4.1c0.7-0.8 0.5-2-0.3-2.7-0.8-0.6-2-0.5-2.7 0.3l-3.7 4.5c-1.9-0.9-4.1-1.4-6.3-1.4-2.3 0-4.5 0.5-6.4 1.4l-3.6-4.5c-0.7-0.8-1.9-1-2.8-0.3-0.8 0.7-0.9 1.9-0.2 2.7l3.3 4.2c-3.4 2.8-5.7 7-5.7 11.8h30.7c0-4.8-2.2-9.1-5.7-11.9zm-13.5 6.2c-1 0-1.9-0.9-1.9-1.9 0-1.1 0.9-2 1.9-2 1.1 0 2 0.9 2 2 0 1-0.9 1.9-2 1.9zm7.7 0c-1 0-1.9-0.9-1.9-1.9 0-1.1 0.9-2 1.9-2 1.1 0 1.9 0.9 1.9 2 0 1-0.8 1.9-1.9 1.9z"/></svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

1
src/images/apple.svg Normal file
View File

@@ -0,0 +1 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" width="80" height="80"><defs><clipPath clipPathUnits="userSpaceOnUse" id="cp1"><path d="m1.33 25.33h50.59v46.27h-50.59z"/></clipPath><clipPath clipPathUnits="userSpaceOnUse" id="cp2"><path d="m28 7.6h13.33v13.73h-13.33z"/></clipPath></defs><style>.a{fill:#00b8db}</style><g clip-path="url(#cp1)"><path fill-rule="evenodd" class="a" d="m12.8 27c-4.8 1.5-8.5 5.4-10 10.3-1.1 3.8-1.9 10.2 1.3 18.8 0 0.1 3.1 8.6 10.8 13.2 3.7 2.2 8.5 2.1 12-0.3l0.1-0.1q0.2-0.1 0.4-0.3 0.3-0.2 0.7-0.2 0.3 0 0.6 0.2 0.3 0.2 0.5 0.4h0.1c3.5 2.4 8.2 2.5 11.9 0.2 6.9-4.1 10.1-11.2 10.7-12.8q-0.2 0-0.4 0c-3.7 0-7.1-1.4-9.7-3.9-2.6-2.6-4-6-4-9.6 0-6.4 4.5-11.8 10.6-13.2-1.5-1.2-3.2-2.2-5-2.8-8.1-2.6-13.9 3-14.6 3.6v0.1c-0.4 0.4-1 0.4-1.4 0l-0.1-0.1c-0.6-0.6-6.4-6.2-14.5-3.5z"/></g><g clip-path="url(#cp2)"><path fill-rule="evenodd" class="a" d="m31.3 10.9c-2.2 2.2-3.3 5.4-3.1 9.1 3.8 0.2 7.1-0.9 9.2-3.1 2.2-2.1 3.3-5.3 3.1-9-3.8-0.2-7 0.8-9.2 3z"/></g></svg>

After

Width:  |  Height:  |  Size: 1014 B

BIN
src/images/connector.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

4
src/images/github.svg Normal file
View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" aria-label="GitHub" viewBox="0 0 512 512" id="github">
<rect width="512" height="512" fill="#1B1817" rx="15%"></rect>
<path fill="#fff" d="M335 499c14 0 12 17 12 17H165s-2-17 12-17c13 0 16-6 16-12l-1-50c-71 16-86-28-86-28-12-30-28-37-28-37-24-16 1-16 1-16 26 2 40 26 40 26 22 39 59 28 74 22 2-17 9-28 16-35-57-6-116-28-116-126 0-28 10-51 26-69-3-6-11-32 3-67 0 0 21-7 70 26 42-12 86-12 128 0 49-33 70-26 70-26 14 35 6 61 3 67 16 18 26 41 26 69 0 98-60 120-117 126 10 8 18 24 18 48l-1 70c0 6 3 12 16 12z"></path>
</svg>

After

Width:  |  Height:  |  Size: 563 B

View File

@@ -1,3 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 266 312">
<path d="M128.6640625 79.2793c0 1-1 1-1 1h-1c-1 0-1-1-2-2 0 0-1-1-1-2s0-1 1-1l2 1c1 1 2 2 2 3m-18-10c0-5-2-8-5-8 0 0 0 1-1 1v2h3c0 2 1 3 1 5h2m35-5c2 0 3 2 4 5h2c-1-1-1-2-1-3s0-2-1-3-2-2-3-2c0 0-1 1-2 1 0 1 1 1 1 2m-30 16c-1 0-1 0-1-1s0-2 1-3c2 0 3-1 3-1 1 0 1 1 1 1 0 1-1 2-3 4h-1m-11-1c-4-2-5-5-5-10 0-3 0-5 2-7 1-2 3-3 5-3s3 1 5 3c1 3 2 6 2 9v2h1v-1c1 0 1-2 1-6 0-3 0-6-2-9s-4-5-8-5c-3 0-6 2-7 5-2 4-2.4 7-2.4 12 0 4 1.4 8 5.4 12 1-1 2-1 3-2m125 141c1 0 1-.4 1-1.3 0-2.2-1-4.8-4-7.7-3-3-8-4.9-14-5.7-1-.1-2-.1-2-.1-1-.2-1-.2-2-.2-1-.1-3-.3-4-.5 3-9.3 4-17.5 4-24.7 0-10-2-17-6-23s-8-9-13-10c-1 1-1 1-1 2 5 2 10 6 13 12 3 7 4 13 4 20 0 5.6-1 13.9-5 24.5-4 1.6-8 5.3-11 11.1 0 .9 0 1.4 1 1.4 0 0 1-.9 2-2.6 2-1.7 3-3.4 5-5.1 3-1.7 5-2.6 8-2.6 5 0 10 .7 13 2.1 4 1.3 6 2.7 7 4.3 1 1.5 2 2.9 3 4.2 0 1.3 1 1.9 1 1.9m-92-145c-1-1-1-3-1-5 0-4 0-6 2-9 2-2 4-3 6-3 3 0 5 2 7 4 1 3 2 5 2 8 0 5-2 8-6 9 0 0 1 1 2 1 2 0 3 1 5 2 1-6 2-10 2-15 0-6-1-10-3-13-3-3-6-4-10-4-3 0-6 1-9 3-2 3-3 5-3 8 0 5 1 9 3 13 1 0 2 1 3 1m12 16c-13 9-23 13-31 13-7 0-14-3-20-8 1 2 2 4 3 5l6 6c4 4 9 6 14 6 7 0 15-4 25-11l9-6c2-2 4-4 4-7 0-1 0-2-1-2-1-2-6-5-16-8-9-4-16-6-20-6-3 0-8 2-15 6-6 4-10 8-10 12 0 0 1 1 2 3 6 5 12 8 18 8 8 0 18-4 31-14v2c1 0 1 1 1 1m23 202c4 7.52 11 11.3 19 11.3 2 0 4-.3 6-.9 2-.4 4-1.1 5-1.9 1-.7 2-1.4 3-2.2 2-.7 2-1.2 3-1.7l17-14.7c4-3.19 8-5.98 13-8.4 4-2.4 8-4 10-4.9 3-.8 5-2 7-3.6 1-1.5 2-3.4 2-5.8 0-2.9-2-5.1-4-6.7s-4-2.7-6-3.4-4-2.3-7-5c-2-2.6-4-6.2-5-10.9l-1-5.8c-1-2.7-1-4.7-2-5.8 0-.3 0-.4-1-.4s-3 .9-4 2.6c-2 1.7-4 3.6-6 5.6-1 2-4 3.8-6 5.5-3 1.7-6 2.6-8 2.6-8 0-12-2.2-15-6.5-2-3.2-3-6.9-4-11.1-2-1.7-3-2.6-5-2.6-5 0-7 5.2-7 15.7v31.1c0 .9-1 2.9-1 6-1 3.1-1 6.62-1 10.6l-2 11.1v.17m-145-5.29c9.3 1.36 20 4.27 32.1 8.71 12.1 4.4 19.5 6.7 22.2 6.7 7 0 12.8-3.1 17.6-9.09 1-1.94 1-4.22 1-6.84 0-9.45-5.7-21.4-17.1-35.9l-6.8-9.1c-1.4-1.9-3.1-4.8-5.3-8.7-2.1-3.9-4-6.9-5.5-9-1.3-2.3-3.4-4.6-6.1-6.9-2.6-2.3-5.6-3.8-8.9-4.6-4.2.8-7.1 2.2-8.5 4.1s-2.2 4-2.4 6.2c-.3 2.1-.9 3.5-1.9 4.2-1 .6-2.7 1.1-5 1.6-.5 0-1.4 0-2.7.1h-2.7c-5.3 0-8.9.6-10.8 1.6-2.5 2.9-3.8 6.2-3.8 9.7 0 1.6.4 4.3 1.2 8.1.8 3.7 1.2 6.7 1.2 8.8 0 4.1-1.2 8.2-3.7 12.3-2.5 4.3-3.8 7.5-3.8 9.78 1 3.88 7.6 6.61 19.7 8.21m33.3-90.9c0-6.9 1.8-14.5 5.5-23.5 3.6-9 7.2-15 10.7-19-.2-1-.7-1-1.5-1l-1-1c-2.9 3-6.4 10-10.6 20-4.2 9-6.4 17.3-6.4 23.4 0 4.5 1.1 8.4 3.1 11.8 2.2 3.3 7.5 8.1 15.9 14.2l10.6 6.9c11.3 9.8 17.3 16.6 17.3 20.6 0 2.1-1 4.2-4 6.5-2 2.4-4.7 3.6-7 3.6-.2 0-.3.2-.3.7 0 .1 1 2.1 3.1 6 4.2 5.7 13.2 8.5 25.2 8.5 22 0 39-9 52-27 0-5 0-8.1-1-9.4v-3.7c0-6.5 1-11.4 3-14.6s4-4.7 7-4.7c2 0 4 .7 6 2.2 1-7.7 1-14.4 1-20.4 0-9.1 0-16.6-2-23.6-1-6-3-11-5-15l-6-9c-2-3-3-6-5-9-1-4-2-7-2-12-3-5-5-10-8-15-2-5-4-10-6-14l-9 7c-10 7-18 10-25 10-6 0-11-1-14-5l-6-5c0 3-1 7-3 11l-6.3 12c-2.8 7-4.3 11-4.6 14-.4 2-.7 4-.9 4l-7.5 15c-8.1 15-12.2 28.9-12.2 40.4 0 2.3.2 4.7.6 7.1-4.5-3.1-6.7-7.4-6.7-13m71.6 94.6c-13 0-23 1.76-30 5.25v-.3c-5 6-10.6 9.1-18.4 9.1-4.9 0-12.6-1.9-23-5.7-10.5-3.6-19.8-6.36-27.9-8.18-.8-.23-2.6-.57-5.5-1.03-2.8-.45-5.4-.91-7.7-1.37-2.1-.45-4.5-1.13-7.1-2.05-2.5-.79-4.5-1.82-6-3.07-1.38-1.26-2.06-2.68-2.06-4.27 0-1.6.34-3.31 1.02-5.13.64-1.1 1.34-2.2 2.04-3.2.7-1.1 1.3-2.1 1.7-3.1.6-.9 1-1.8 1.4-2.8.4-.9.8-1.8 1-2.9.2-1 .4-2 .4-3s-.4-4-1.2-9.3c-.8-5.2-1.2-8.5-1.2-9.9 0-4.4 1-7.9 3.2-10.4s4.3-3.8 6.5-3.8h11.5c.9 0 2.3-.5 4.4-1.7.7-1.6 1.3-2.9 1.7-4.1.5-1.2.7-2.1.9-2.5.2-.6.4-1.2.6-1.7.4-.7.9-1.5 1.6-2.3-.8-1-1.2-2.3-1.2-3.9 0-1.1 0-2.1.2-2.7 0-3.6 1.7-8.7 5.3-15.4l3.5-6.3c2.9-5.4 5.1-9.4 6.7-13.4 1.7-4 3.5-10 5.5-18 1.6-7 5.4-14 11.4-21l7.5-9c5.2-6 8.6-11 10.5-15s2.9-9 2.9-13c0-2-.5-8-1.6-18-1-10-1.5-20-1.5-29 0-7 .6-12 1.9-17s3.6-10 7-14c3-4 7-8 13-10s13-3 21-3c3 0 6 0 9 1 3 0 7 1 12 3 4 2 8 4 11 7 4 3 7 8 10 13 2 6 4 12 5 20 1 5 1 10 2 17 0 6 1 10 1 13 1 3 1 7 2 12 1 4 2 8 4 11 2 4 4 8 7 12 3 5 7 10 11 16 9 10 16 21 20 32 5 10 8 23 8 36.9 0 6.9-1 13.6-3 20.1 2 0 3 .8 4 2.2s2 4.4 3 9.1l1 7.4c1 2.2 2 4.3 5 6.1 2 1.8 4 3.3 7 4.5 2 1 5 2.4 7 4.2 2 2 3 4.1 3 6.3 0 3.4-1 5.9-3 7.7-2 2-4 3.4-7 4.3-2 1-6 3-12 5.82-5 2.96-10 6.55-15 10.8l-10 8.51c-4 3.9-8 6.7-11 8.4-3 1.8-7 2.7-11 2.7l-7-.8c-8-2.1-13-6.1-16-12.2-16-1.94-29-2.9-37-2.9"/>
</svg>
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 266 312" width="266" height="312"><style>.a{fill:#00b8db}</style><path class="a" d="m132.6 291.6q-19.5 0-30 5.3v-0.3c-5 6-10.6 9.1-18.4 9.1-4.9 0-12.6-1.9-23-5.7-10.5-3.6-19.8-6.4-27.9-8.2-0.8-0.2-2.6-0.6-5.5-1-2.8-0.5-5.4-0.9-7.7-1.4-2.1-0.5-4.5-1.1-7.1-2.1q-3.8-1.1-6-3-2.1-1.9-2.1-4.3 0-2.4 1-5.1c0.7-1.1 1.4-2.2 2.1-3.2 0.7-1.1 1.3-2.1 1.7-3.1 0.6-0.9 1-1.8 1.4-2.8 0.4-0.9 0.8-1.8 1-2.9 0.2-1 0.4-2 0.4-3 0-1-0.4-4-1.2-9.3q-1.2-7.8-1.2-9.9c0-4.4 1-7.9 3.2-10.4 2.2-2.5 4.3-3.8 6.5-3.8h11.5c0.9 0 2.3-0.5 4.4-1.7 0.7-1.6 1.3-2.9 1.7-4.1 0.5-1.2 0.7-2.1 0.9-2.5 0.2-0.6 0.4-1.2 0.6-1.7 0.4-0.7 0.9-1.5 1.6-2.3q-1.2-1.5-1.2-3.9c0-1.1 0-2.1 0.2-2.7 0-3.6 1.7-8.7 5.3-15.4l3.5-6.3c2.9-5.4 5.1-9.4 6.7-13.4 1.7-4 3.5-10 5.5-18q2.4-10.5 11.4-21l7.5-9c5.2-6 8.6-11 10.5-15 1.9-4 2.9-9 2.9-13 0-2-0.5-8-1.6-18-1-10-1.5-20-1.5-29 0-7 0.6-12 1.9-17 1.3-5 3.6-10 7-14 3-4 7-8 13-10q9-3 21-3c3 0 6 0 9 1q4.5 0 12 3c4 2 8 4 11 7 4 3 7 8 10 13 2 6 4 12 5 20 1 5 1 10 2 17 0 6 1 10 1 13 1 3 1 7 2 12 1 4 2 8 4 11 2 4 4 8 7 12 3 5 7 10 11 16 9 10 16 21 20 32 5 10 8 23 8 36.9q0 10.3-3 20.1c2 0 3 0.8 4 2.2 1 1.4 2 4.4 3 9.1l1 7.4c1 2.2 2 4.3 5 6.1 2 1.8 4 3.3 7 4.5 2 1 5 2.4 7 4.2q3 3 3 6.3c0 3.4-1 5.9-3 7.7-2 2-4 3.4-7 4.3-2 1-6 3-12 5.8q-7.5 4.4-15 10.8l-10 8.5c-4 3.9-8 6.7-11 8.4-3 1.8-7 2.7-11 2.7l-7-0.8c-8-2.1-13-6.1-16-12.2-16-1.9-29-2.9-37-2.9m-27.9-212.3c-4-2-5-5-5-10 0-3 0-5 2-7 1-2 3-3 5-3 2 0 3 1 5 3 1 3 2 6 2 9v2h1v-1c1 0 1-2 1-6 0-3 0-6-2-9-2-3-4-5-8-5-3 0-6 2-7 5-2 4-2.4 7-2.4 12 0 4 1.4 8 5.4 12 1-1 2-1 3-2zm33-4c-1-1-1-3-1-5 0-4 0-6 2-9q3-3 6-3c3 0 5 2 7 4 1 3 2 5 2 8q0 7.5-6 9c0 0 1 1 2 1 2 0 3 1 5 2 1-6 2-10 2-15 0-6-1-10-3-13-3-3-6-4-10-4q-4.5 0-9 3c-2 3-3 5-3 8 0 5 1 9 3 13 1 0 2 1 3 1zm12 16c-13 9-23 13-31 13-7 0-14-3-20-8 1 2 2 4 3 5l6 6c4 4 9 6 14 6 7 0 15-4 25-11l9-6c2-2 4-4 4-7 0-1 0-2-1-2-1-2-6-5-16-8-9-4-16-6-20-6q-4.5 0-15 6c-6 4-10 8-10 12 0 0 1 1 2 3 6 5 12 8 18 8 8 0 18-4 31-14v2c1 0 1 1 1 1zm-39-22c0-5-2-8-5-8 0 0 0 1-1 1v2h3c0 2 1 3 1 5zm119 151c1 0 1-0.4 1-1.3 0-2.2-1-4.8-4-7.7-3-3-8-4.9-14-5.7-1-0.1-2-0.1-2-0.1-1-0.2-1-0.2-2-0.2-1-0.1-3-0.3-4-0.5 3-9.3 4-17.5 4-24.7 0-10-2-17-6-23-4-6-8-9-13-10-1 1-1 1-1 2 5 2 10 6 13 12 3 7 4 13 4 20 0 5.6-1 13.9-5 24.5-4 1.6-8 5.3-11 11.1 0 0.9 0 1.4 1 1.4 0 0 1-0.9 2-2.6 2-1.7 3-3.4 5-5.1 3-1.7 5-2.6 8-2.6 5 0 10 0.7 13 2.1 4 1.3 6 2.7 7 4.3q1.5 2.2 3 4.2c0 1.3 1 1.9 1 1.9zm-84-156c2 0 3 2 4 5h2c-1-1-1-2-1-3 0-1 0-2-1-3-1-1-2-2-3-2 0 0-1 1-2 1 0 1 1 1 1 2zm-17 15c0 1-1 1-1 1h-1c-1 0-1-1-2-2 0 0-1-1-1-2 0-1 0-1 1-1l2 1c1 1 2 2 2 3zm44 214c4 7.5 11 11.3 19 11.3q3 0 6-0.9c2-0.4 4-1.1 5-1.9 1-0.7 2-1.4 3-2.2 2-0.7 2-1.2 3-1.7l17-14.7c4-3.2 8-6 13-8.4 4-2.4 8-4 10-4.9 3-0.8 5-2 7-3.6 1-1.5 2-3.4 2-5.8 0-2.9-2-5.1-4-6.7-2-1.6-4-2.7-6-3.4-2-0.7-4-2.3-7-5-2-2.6-4-6.2-5-10.9l-1-5.8c-1-2.7-1-4.7-2-5.8 0-0.3 0-0.4-1-0.4-1 0-3 0.9-4 2.6-2 1.7-4 3.6-6 5.6-1 2-4 3.8-6 5.5-3 1.7-6 2.6-8 2.6-8 0-12-2.2-15-6.5-2-3.2-3-6.9-4-11.1-2-1.7-3-2.6-5-2.6-5 0-7 5.2-7 15.7v31.1c0 0.9-1 2.9-1 6-1 3.1-1 6.6-1 10.6l-2 11.1v0.1m-145-5.2q13.9 2 32.1 8.7c12.1 4.4 19.5 6.7 22.2 6.7 7 0 12.8-3.1 17.6-9.1 1-2 1-4.2 1-6.9q0-14.1-17.1-35.9l-6.8-9.1c-1.4-1.9-3.1-4.8-5.3-8.7-2.1-3.9-4-6.9-5.5-9-1.3-2.3-3.4-4.6-6.1-6.9-2.6-2.3-5.6-3.8-8.9-4.6-4.2 0.8-7.1 2.2-8.5 4.1-1.4 1.9-2.2 4-2.4 6.2-0.3 2.1-0.9 3.5-1.9 4.2-1 0.6-2.7 1.1-5 1.6-0.5 0-1.4 0-2.7 0.1h-2.7c-5.3 0-8.9 0.6-10.8 1.6-2.5 2.9-3.8 6.2-3.8 9.7q0 2.4 1.2 8.1c0.8 3.7 1.2 6.7 1.2 8.8 0 4.1-1.2 8.2-3.7 12.3-2.5 4.3-3.8 7.5-3.8 9.8 1 3.9 7.6 6.6 19.7 8.2m33.3-90.9c0-6.9 1.8-14.5 5.5-23.5 3.6-9 7.2-15 10.7-19-0.2-1-0.7-1-1.5-1l-1-1c-2.9 3-6.4 10-10.6 20-4.2 9-6.4 17.3-6.4 23.4 0 4.5 1.1 8.4 3.1 11.8 2.2 3.3 7.5 8.1 15.9 14.2l10.6 6.9c11.3 9.8 17.3 16.6 17.3 20.6 0 2.1-1 4.2-4 6.5-2 2.4-4.7 3.6-7 3.6-0.2 0-0.3 0.2-0.3 0.7 0 0.1 1 2.1 3.1 6 4.2 5.7 13.2 8.5 25.2 8.5 22 0 39-9 52-27 0-5 0-8.1-1-9.4v-3.7c0-6.5 1-11.4 3-14.6 2-3.2 4-4.7 7-4.7 2 0 4 0.7 6 2.2 1-7.7 1-14.4 1-20.4 0-9.1 0-16.6-2-23.6-1-6-3-11-5-15l-6-9c-2-3-3-6-5-9-1-4-2-7-2-12-3-5-5-10-8-15-2-5-4-10-6-14l-9 7c-10 7-18 10-25 10-6 0-11-1-14-5l-6-5c0 3-1 7-3 11l-6.3 12c-2.8 7-4.3 11-4.6 14-0.4 2-0.7 4-0.9 4l-7.5 15c-8.1 15-12.2 28.9-12.2 40.4 0 2.3 0.2 4.7 0.6 7.1-4.5-3.1-6.7-7.4-6.7-13zm54.7-116.7c-1 0-1 0-1-1 0-1 0-2 1-3 2 0 3-1 3-1 1 0 1 1 1 1 0 1-1 2-3 4z"/></svg>

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
src/images/peers.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

BIN
src/images/phoneframe.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 KiB

BIN
src/images/setting.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

1
src/images/windows.svg Normal file
View File

@@ -0,0 +1 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" width="80" height="80"><defs><clipPath clipPathUnits="userSpaceOnUse" id="cp1"><path d="m2.92 11.31h25.08v26.02h-25.08z"/></clipPath><clipPath clipPathUnits="userSpaceOnUse" id="cp2"><path d="m34.67 11.31h25.25v26.02h-25.25z"/></clipPath><clipPath clipPathUnits="userSpaceOnUse" id="cp3"><path d="m2.92 42.67h25.08v25.64h-25.08z"/></clipPath><clipPath clipPathUnits="userSpaceOnUse" id="cp4"><path d="m34.67 42.67h25.25v25.64h-25.25z"/></clipPath></defs><style>.a{fill:#00b8db}</style><g clip-path="url(#cp1)"><path class="a" d="m26.9 11.3h-22.8c-0.7 0-1.2 0.5-1.2 1.2v22.8c0 0.6 0.5 1.1 1.2 1.1h22.8c0.6 0 1.1-0.5 1.1-1.1v-22.8c0-0.7-0.5-1.2-1.1-1.2z"/></g><g clip-path="url(#cp2)"><path class="a" d="m58.8 11.3h-22.8c-0.6 0-1.2 0.5-1.2 1.2v22.8c0 0.6 0.6 1.1 1.2 1.1h22.8c0.6 0 1.1-0.5 1.1-1.1v-22.8c0-0.7-0.5-1.2-1.1-1.2z"/></g><g clip-path="url(#cp3)"><path class="a" d="m26.9 43.2h-22.8c-0.7 0-1.2 0.5-1.2 1.2v22.8c0 0.6 0.5 1.1 1.2 1.1h22.8c0.6 0 1.1-0.5 1.1-1.1v-22.8c0-0.7-0.5-1.2-1.1-1.2z"/></g><g clip-path="url(#cp4)"><path class="a" d="m58.8 43.2h-22.8c-0.6 0-1.2 0.5-1.2 1.2v22.8c0 0.6 0.6 1.1 1.2 1.1h22.8c0.6 0 1.1-0.5 1.1-1.1v-22.8c0-0.7-0.5-1.2-1.1-1.2z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB