Initial commit

This commit is contained in:
Emre
2025-10-22 17:30:00 +03:00
commit 205c8fd0d9
134 changed files with 8080 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
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>
)
}

68
src/components/Button.tsx Normal file
View File

@@ -0,0 +1,68 @@
import { Link } from 'react-router-dom'
import clsx from 'clsx'
const baseStyles = {
solid:
'inline-flex justify-center rounded-lg py-2 px-3 text-sm font-semibold transition-colors',
outline:
'inline-flex justify-center rounded-lg border py-[calc(--spacing(2)-1px)] px-[calc(--spacing(3)-1px)] text-sm transition-colors',
}
const variantStyles = {
solid: {
cyan: 'relative overflow-hidden bg-cyan-500 text-white before:absolute before:inset-0 active:before:bg-transparent hover:before:bg-white/10 active:bg-cyan-600 active:text-white/80 before:transition-colors',
white:
'bg-white text-cyan-900 hover:bg-white/90 active:bg-white/90 active:text-cyan-900/70',
gray: 'bg-gray-800 text-white hover:bg-gray-900 active:bg-gray-800 active:text-white/80',
green: 'bg-green-500 text-white hover:bg-green-600',
},
outline: {
gray: 'border-gray-300 text-gray-700 hover:border-cyan-500 active:border-cyan-500',
white: 'border-gray-300 text-white hover:border-cyan-500 active:border-cyan-500',
},
}
type ButtonProps = (
| {
variant?: 'solid'
color?: keyof typeof variantStyles.solid
}
| {
variant: 'outline'
color?: keyof typeof variantStyles.outline
}
) &
(
| (Omit<React.ComponentPropsWithoutRef<typeof Link>, 'color'> & { to: string; as?: 'link' })
| (Omit<React.ComponentPropsWithoutRef<'a'>, 'color'> & { to: string; as: 'a' })
| (Omit<React.ComponentPropsWithoutRef<'button'>, 'color'> & {
to?: undefined
as?: undefined
})
)
export function Button({ className, as, ...props }: ButtonProps) {
props.variant ??= 'solid'
props.color ??= 'gray'
className = clsx(
baseStyles[props.variant],
props.variant === 'outline'
? variantStyles.outline[props.color]
: props.variant === 'solid'
? variantStyles.solid[props.color]
: undefined,
className,
)
if (typeof props.to === 'undefined') {
return <button className={className} {...props} />
}
if (as === 'a') {
const { to, variant, color, ...rest } = props as any
return <a className={className} href={to} {...rest} />
}
return <Link className={className} {...props} />
}

View File

@@ -0,0 +1,45 @@
import { useId } from 'react'
export function CircleBackground({
color,
...props
}: React.ComponentPropsWithoutRef<'svg'> & {
color: string
}) {
let id = useId()
return (
<svg
viewBox="0 0 558 558"
width="558"
height="558"
fill="none"
aria-hidden="true"
{...props}
>
<defs>
<linearGradient
id={id}
x1="79"
y1="16"
x2="105"
y2="237"
gradientUnits="userSpaceOnUse"
>
<stop stopColor={color} />
<stop offset="1" stopColor={color} stopOpacity="0" />
</linearGradient>
</defs>
<path
opacity=".2"
d="M1 279C1 125.465 125.465 1 279 1s278 124.465 278 278-124.465 278-278 278S1 432.535 1 279Z"
stroke={color}
/>
<path
d="M1 279C1 125.465 125.465 1 279 1"
stroke={`url(#${id})`}
strokeLinecap="round"
/>
</svg>
)
}

View File

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

View File

@@ -0,0 +1,17 @@
import CountUp from 'react-countup'
interface CountUpNumberProps {
end: number
className?: string
}
export function CountUpNumber({ end, className }: CountUpNumberProps) {
return (
<CountUp
end={end}
duration={2.5}
separator=","
className={className}
/>
)
}

61
src/components/Footer.tsx Normal file
View File

@@ -0,0 +1,61 @@
import { Link } from 'react-router-dom'
import { Container } from './Container'
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-8">
<div>
<div className="flex items-center text-gray-900">
<img src="/src/images/logomark.svg" alt="Mycelium Logomark" 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>
</div>
</div>
<nav className="mt-10 flex gap-8">
<Link to="/" className="text-sm text-gray-700 hover:text-cyan-500 transition-colors">
Home
</Link>
<Link to="/cloud" className="text-sm text-gray-700 hover:text-cyan-500 transition-colors">
Cloud
</Link>
<Link to="/network" className="text-sm text-gray-700 hover:text-cyan-500 transition-colors">
Network
</Link>
<Link to="/agents" className="text-sm text-gray-700 hover:text-cyan-500 transition-colors">
Agents
</Link>
</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-16 w-16 flex-none items-center justify-center">
<img src="/src/images/github.svg" alt="GitHub" className="h-16 w-16" />
</div>
<div className="ml-4 lg:w-72">
<p className="text-base font-semibold text-gray-900">
<a href="https://github.com/threefoldtech/mycelium/releases/" target="_blank" rel="noopener noreferrer">
<span className="absolute inset-0 sm:rounded-2xl" />
Download Mycelium
</a>
</p>
<p className="mt-1 text-sm text-gray-700">
Head to the GitHub to access the latest Mycelium builds for your devices.
</p>
</div>
</div>
</div>
<div className="flex flex-col items-center border-t border-gray-200 pt-8 pb-12 md:flex-row-reverse md:justify-between md:pt-6">
<p className="mt-6 text-sm text-gray-500 md:mt-0">
&copy; Copyright{' '}
<a href="https://www.threefold.io" target="_blank" rel="noopener noreferrer" className="hover:text-cyan-500 transition-colors">
ThreeFold
</a>{' '}
{new Date().getFullYear()}. All rights reserved.
</p>
</div>
</Container>
</footer>
)
}

61
src/components/Header.tsx Normal file
View File

@@ -0,0 +1,61 @@
import { Link } from 'react-router-dom'
import { Container } from './Container'
import { Button } from './Button'
export function Header() {
return (
<header>
<nav>
<Container className="relative z-50 flex justify-between py-8">
<div className="relative z-10 flex items-center gap-16">
<Link to="/" aria-label="Home">
<img src="/src/images/logomark.svg" alt="Mycelium" className="h-10 w-auto" />
</Link>
<div className="hidden lg:flex lg:gap-10">
<Link
to="/"
className="text-base/7 tracking-tight text-gray-700 hover:text-cyan-500 transition-colors"
>
Home
</Link>
<Link
to="/cloud"
className="text-base/7 tracking-tight text-gray-700 hover:text-cyan-500 transition-colors"
>
Cloud
</Link>
<Link
to="/network"
className="text-base/7 tracking-tight text-gray-700 hover:text-cyan-500 transition-colors"
>
Network
</Link>
<Link
to="/agents"
className="text-base/7 tracking-tight text-gray-700 hover:text-cyan-500 transition-colors"
>
Agents
</Link>
</div>
</div>
<div className="flex items-center gap-6">
<div className="flex items-center gap-6 max-lg:hidden">
<Button
to="https://threefold.info/mycelium_network/docs/"
variant="outline"
as="a"
target="_blank"
rel="noopener noreferrer"
>
Docs
</Button>
<Button to="/download" variant="solid" color="cyan">
Get Mycelium
</Button>
</div>
</div>
</Container>
</nav>
</header>
)
}

15
src/components/Layout.tsx Normal file
View File

@@ -0,0 +1,15 @@
import { Outlet } from 'react-router-dom'
import { Header } from './Header'
import { Footer } from './Footer'
export function Layout() {
return (
<div className="bg-gray-50 antialiased" style={{ fontFamily: 'var(--font-inter)' }}>
<Header />
<main>
<Outlet />
</main>
<Footer />
</div>
)
}

64
src/components/Logo.tsx Normal file
View File

@@ -0,0 +1,64 @@
export function Logomark(props: React.ComponentPropsWithoutRef<'svg'>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
width="81"
height="50"
version="1"
viewBox="0 0 60.75 37.5"
{...props}
>
<defs>
<filter id="a" width="100%" height="100%" x="0%" y="0%">
<feColorMatrix
colorInterpolationFilters="sRGB"
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"
/>
</filter>
<filter id="b" width="100%" height="100%" x="0%" y="0%">
<feColorMatrix
colorInterpolationFilters="sRGB"
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0.2126 0.7152 0.0722 0 0"
/>
</filter>
<clipPath id="c">
<path d="M.277.309H60V37H.277Zm0 0" />
</clipPath>
<mask id="d">
<g filter="url(#a)">
<g filter="url(#b)" transform="matrix(.08765 0 0 .08811 .276 .26)">
<image
xlinkHref="/src/images/logomark.svg"
width="684"
height="420"
/>
</g>
</g>
</mask>
</defs>
<g clipPath="url(#c)" mask="url(#d)">
<image
xlinkHref="/src/images/logomark.svg"
width="684"
height="420"
transform="matrix(.08765 0 0 .08811 .276 .26)"
/>
</g>
</svg>
)
}
export function Logo(props: React.ComponentPropsWithoutRef<'svg'>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="160"
height="40"
viewBox="0 0 120 30"
{...props}
>
<image xlinkHref="/src/images/mycelium.svg" width="120" height="30" />
</svg>
)
}

View File

@@ -0,0 +1,47 @@
import { useEffect, useRef } from 'react'
import createGlobe from 'cobe'
export function Globe({ className }: { className?: string }) {
const canvasRef = useRef<HTMLCanvasElement>(null)
useEffect(() => {
let phi = 0
if (!canvasRef.current) return
const globe = createGlobe(canvasRef.current, {
devicePixelRatio: 2,
width: 600 * 2,
height: 600 * 2,
phi: 0,
theta: 0,
dark: 0,
diffuse: 1.2,
mapSamples: 16000,
mapBrightness: 6,
baseColor: [0.3, 0.3, 0.3],
markerColor: [0.1, 0.8, 1],
glowColor: [1, 1, 1],
markers: [
{ location: [37.7595, -122.4367], size: 0.03 },
{ location: [40.7128, -74.006], size: 0.1 },
],
onRender: (state) => {
state.phi = phi
phi += 0.01
},
})
return () => {
globe.destroy()
}
}, [])
return (
<canvas
ref={canvasRef}
className={className}
style={{ width: '100%', height: '100%', maxWidth: '100%', aspectRatio: 1 }}
/>
)
}