Initial commit
This commit is contained in:
21
src/components/AnimatedSection.tsx
Normal file
21
src/components/AnimatedSection.tsx
Normal 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
68
src/components/Button.tsx
Normal 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} />
|
||||
}
|
||||
45
src/components/CircleBackground.tsx
Normal file
45
src/components/CircleBackground.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
13
src/components/Container.tsx
Normal file
13
src/components/Container.tsx
Normal 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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
17
src/components/CountUpNumber.tsx
Normal file
17
src/components/CountUpNumber.tsx
Normal 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
61
src/components/Footer.tsx
Normal 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">
|
||||
© 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
61
src/components/Header.tsx
Normal 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
15
src/components/Layout.tsx
Normal 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
64
src/components/Logo.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
47
src/components/ui/Globe.tsx
Normal file
47
src/components/ui/Globe.tsx
Normal 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 }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user