Initial Commit

This commit is contained in:
Nazareno Oviedo
2022-03-28 15:00:11 -03:00
commit 4aa3fdbc2a
147 changed files with 8710 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
# `common` dir
- Common components (maybe reused across the whole app)
- Not primitives
- Examples: `/media-card.tsx`, `/sidenav.tsx`, `/fade-in-box.tsx`, etc...
@@ -0,0 +1,8 @@
.transition {
position: fixed;
top: 100%;
left: 0;
z-index: 20;
pointer-events: none;
user-select: none;
}
@@ -0,0 +1,59 @@
import Image from 'next/image'
import * as React from 'react'
import { DURATION, gsap } from '~/lib/gsap'
import { usePageTransition } from '~/lib/gsap/page-transitions'
import cashSrc from '../../../../public/images/cash-transition.png'
import s from './cash-transition.module.scss'
export const CashTransition = React.memo(() => {
const { getTransitionSpace } = usePageTransition()
const cashRef = React.useRef<HTMLDivElement>(null)
React.useEffect(() => {
getTransitionSpace(async () => {
const tl = gsap.timeline({
onComplete: () => {
gsap.to(cashRef.current, {
top: '100%',
duration: DURATION * 2,
ease: 'power4.inOut'
})
}
})
const vw = window.innerWidth / 100
const vh = window.innerHeight / 100
const newTop = -1 * Math.max(vw * 12, vh * 20)
tl.to(cashRef.current, {
top: newTop,
duration: DURATION * 2.5,
ease: 'power4.inOut'
})
await tl
})
}, [getTransitionSpace])
return (
<div
ref={cashRef}
className={s['transition']}
style={{
width: 'max(100vw, 100vh)',
height: 'calc(120vh + max(12vh, 12vw))'
}}
>
<Image
src={cashSrc}
alt="cash"
objectFit="cover"
layout="fill"
objectPosition="top"
loading="eager"
/>
</div>
)
})
@@ -0,0 +1,14 @@
.cursor {
display: flex;
top: 0;
left: 0;
z-index: 50;
max-width: 35px;
pointer-events: none;
user-select: none;
> span {
position: relative;
display: flex;
}
}
+156
View File
@@ -0,0 +1,156 @@
import gsap from 'gsap'
import Head from 'next/head'
import * as React from 'react'
import { useDeviceDetect } from '~/hooks/use-device-detect'
import defaultSrc from '../../../../public/images/cursor/default.svg'
import defaultActiveSrc from '../../../../public/images/cursor/default-active.svg'
import pointerSrc from '../../../../public/images/cursor/pointer.svg'
import pointerActiveSrc from '../../../../public/images/cursor/pointer-active.svg'
import s from './cursor.module.scss'
type CursorType = 'pointer' | 'default' | undefined
const CursorContext = React.createContext<
{ setType: React.Dispatch<React.SetStateAction<CursorType>> } | undefined
>(undefined)
const Cursor = ({ children }: { children?: React.ReactNode }) => {
const cursorRef = React.useRef<HTMLDivElement>(null)
const [type, setType] = React.useState<CursorType>()
const { isMobile } = useDeviceDetect()
React.useEffect(() => {
if (!cursorRef.current) return
gsap.set(cursorRef.current, { xPercent: -50, yPercent: -50 })
const pos = { x: window.innerWidth / 2, y: window.innerHeight / 2 }
const mouse = { x: pos.x, y: pos.y }
const speed = 0.2
const xSet = gsap.quickSetter(cursorRef.current, 'x', 'px')
const ySet = gsap.quickSetter(cursorRef.current, 'y', 'px')
function handleMouseMove(e: MouseEvent) {
mouse.x = e.x
mouse.y = e.y
if (e.target instanceof HTMLElement || e.target instanceof SVGElement) {
if (e.target.dataset.cursor) {
setType(e.target.dataset.cursor as any)
return
}
if (e.target.closest('button') || e.target.closest('a')) {
setType('pointer')
return
} else if (
e.target.closest('p') ||
e.target.closest('span') ||
e.target.closest('h1') ||
e.target.closest('h2') ||
e.target.closest('h3') ||
e.target.closest('h4') ||
e.target.closest('h5') ||
e.target.closest('h5') ||
e.target.closest('input') ||
e.target.closest('textarea')
) {
setType('default') // this would be for text, if we'd have any text cursor
return
}
}
setType(undefined)
}
function handleTick() {
const dt = 1.0 - Math.pow(0.6 - speed, gsap.ticker.deltaRatio())
pos.x += (mouse.x - pos.x) * dt
pos.y += (mouse.y - pos.y) * dt
xSet(pos.x)
ySet(pos.y)
}
window.addEventListener('mousemove', handleMouseMove, { passive: true })
gsap.ticker.add(handleTick)
return () => {
window.removeEventListener('mousemove', handleMouseMove)
gsap.ticker.remove(handleTick)
}
}, [isMobile])
return (
<>
{isMobile === false && <CursorFollower ref={cursorRef} type={type} />}
<CursorContext.Provider value={{ setType }}>
{children}
</CursorContext.Provider>
</>
)
}
const CursorFollower = React.forwardRef<HTMLDivElement, { type: CursorType }>(
({ type }, ref) => {
const { src, adjustments } = React.useMemo(() => {
switch (type) {
case 'pointer':
return {
src: pointerSrc,
adjustments: { x: '4px', y: '22px' }
}
default:
return {
src: defaultSrc,
adjustments: { x: '10px', y: '17px' }
}
}
}, [type])
React.useEffect(() => {
document.documentElement.classList.add('has-custom-cursor')
return () => {
document.documentElement.classList.remove('has-custom-cursor')
}
}, [])
return (
<div ref={ref} className={s['cursor']}>
<Head>
{/* preload images */}
{[defaultSrc, defaultActiveSrc, pointerSrc, pointerActiveSrc].map(
(src) => {
return (
<link key={src.src} rel="preload" href={src.src} as="image" />
)
}
)}
</Head>
<span
style={{
transform: `translate(${adjustments.x}, ${adjustments.y})`
}}
>
<img
src={src.src}
alt={`cursor-${type}`}
width={src.width}
height={src.height}
loading="eager"
/>
</span>
</div>
)
}
)
export const useCursor = () => {
const context = React.useContext(CursorContext)
if (context === undefined) {
throw new Error('useCursor must be used within a CursorProvider')
}
return context
}
export default Cursor
@@ -0,0 +1,94 @@
import { forwardRef, useEffect, useRef, useState } from 'react'
import mergeRefs from 'react-merge-refs'
import { useAnimationContext } from '~/context/animation'
import { useIsomorphicLayoutEffect } from '~/hooks/use-isomorphic-layout-effect'
import { DURATION, gsap } from '~/lib/gsap'
type FadeInBoxProps = {
children: React.ReactNode
className?: string
id?: string
style?: React.CSSProperties
onFadeInCompleteTimeline?: GSAPTimeline
}
export const FadeInBox = forwardRef<HTMLDivElement, FadeInBoxProps>(
({ children, className, id, style, onFadeInCompleteTimeline }, ref) => {
const innerRef = useRef<HTMLDivElement>(null)
const { shouldAnimate } = useAnimationContext()
const [inView, setInView] = useState(false)
useEffect(() => {
const elementToObserve = innerRef.current
if (!elementToObserve) return
const handleObserve: IntersectionObserverCallback = ([element]) => {
if (element) {
setInView((p) => {
// trigger once
if (p === true) return true
else return element.isIntersecting
})
}
}
const observer = new IntersectionObserver(handleObserve, {
threshold: 0.14
})
observer.observe(elementToObserve)
return () => {
observer.disconnect()
}
}, [])
useIsomorphicLayoutEffect(() => {
if (!shouldAnimate || !inView) {
return
}
if (!innerRef.current) return
const tl = gsap.timeline({
paused: true,
smoothChildTiming: true,
defaults: { ease: 'slow', overwrite: true },
onComplete: () => {
if (
onFadeInCompleteTimeline &&
typeof onFadeInCompleteTimeline !== 'undefined'
) {
onFadeInCompleteTimeline.play()
}
}
})
tl.to(innerRef.current, {
autoAlpha: 1,
scale: 1,
duration: DURATION
})
tl.play()
return () => {
tl.kill()
}
}, [inView, shouldAnimate, onFadeInCompleteTimeline])
return (
<div
ref={mergeRefs([innerRef, ref])}
className={className}
id={id}
style={{
opacity: shouldAnimate ? 0 : undefined,
transform: shouldAnimate ? 'scale(0.9)' : undefined,
...style
}}
>
{children}
</div>
)
}
)
@@ -0,0 +1,117 @@
@import '~/css/helpers';
.footer {
position: relative;
z-index: 10;
.container {
display: flex;
justify-content: space-between;
padding-bottom: tovw(56px, 'default', 48px);
border-bottom: tovw(1px, 'default', 1px) solid var(--color-grey-light);
nav {
display: flex;
gap: tovw(88px, 'default', 64px);
@media screen and (max-width: 1024px) {
display: grid;
column-gap: tovw(97px, 'tablet', 97px);
grid-template-columns: repeat(2, 1fr);
grid-template-rows: auto;
row-gap: tovw(44px, 'tablet', 44px);
}
}
@media screen and (max-width: 1024px) {
flex-direction: column;
}
}
ul {
margin: 0;
padding: 0;
list-style-type: none;
> li {
line-height: 1.35;
&:first-of-type {
line-height: 1;
margin-bottom: tovw(12px, 'default', 10px);
a {
font-weight: 500;
}
}
}
}
}
.logo {
margin-right: tovw(88px, 'default', 64px);
@media screen and (max-width: 1024px) {
margin-right: 0;
margin-bottom: tovw(56px, 'tablet', 56px);
}
svg {
width: tovw(305px, 'default', 120px);
@media screen and (max-width: 1024px) {
width: 100%;
}
}
}
.connect__links {
> div {
display: grid;
align-content: flex-start;
gap: tovw(16px, 'default', 16px);
grid-template-columns: repeat(2, 1fr);
grid-template-rows: auto;
li {
width: tovw(24px, 'default', 24px);
height: tovw(24px, 'default', 24px);
}
}
@media screen and (max-width: 800px) {
display: none;
}
}
.sub__footer {
margin-top: tovw(18px, 'default', 18px);
margin-bottom: tovw(44px, 'default', 36px);
a,
p {
font-size: tovw(18px, 'default', 14px);
line-height: 1;
margin: 0;
color: var(--color-grey-light);
}
ul {
display: flex;
justify-content: space-between;
width: 100%;
@media screen and (max-width: 800px) {
align-items: center;
flex-direction: column;
justify-content: center;
li:last-of-type {
margin-bottom: 0;
}
}
div {
display: flex;
gap: tovw(37px, 'default', 24px);
@media screen and (max-width: 800px) {
margin-bottom: tovw(32px, 'tablet', 32px);
}
}
}
}
+51
View File
@@ -0,0 +1,51 @@
import {
Facebook,
Instagram,
Linkedin,
Reddit,
Telegram,
Twitter
} from '~/components/icons/socials'
export const DevelopersLinks = [
{ href: '/developers', title: 'Developers' },
{ href: '/github', title: 'Github' },
{ href: '/roadmap', title: 'Roadmap' },
{ href: '/chat', title: 'Chat' },
{ href: '/forum', title: 'Forum' }
]
export const ProductsLinks = [
{ href: '/products', title: 'Products' },
{ href: '/SDK', title: 'SDK' },
{ href: '/watchers', title: 'Watchers' },
{ href: '/network', title: 'Network' },
{ href: '/wallet', title: 'Wallet' },
{ href: '/token', title: 'Token' }
]
export const AboutLinks = [
{ href: '/about', title: 'About' },
{ href: '/faq', title: 'FAQ' },
{ href: '/team', title: 'Team' },
{ href: '/partners', title: 'Partners' },
{ href: '/newsroom', title: 'Newstoom' },
{ href: '/careers', title: 'Careers' },
{ href: '/contact', title: 'Contact' }
]
export const CommunityLinks = [
{ href: '/community', title: 'Community' },
{ href: '/validators', title: 'Validators' },
{ href: '/testnet', title: 'Testnet' },
{ href: '/insiders', title: 'Insiders' }
]
export const ConnectLinks = [
{ href: '/community', title: 'Twitter', logo: <Twitter /> },
{ href: '/validators', title: 'Telegram', logo: <Telegram /> },
{ href: '/testnet', title: 'Reddit', logo: <Reddit /> },
{ href: '/insiders', title: 'Linkedin', logo: <Linkedin /> },
{ href: '/insiders', title: 'Facebook', logo: <Facebook /> },
{ href: '/insiders', title: 'Instagram', logo: <Instagram /> }
]
+112
View File
@@ -0,0 +1,112 @@
import { LogoFooter } from '~/components/icons/logo'
import { Container } from '~/components/layout/container'
import Link from '~/components/primitives/link'
import {
AboutLinks,
CommunityLinks,
ConnectLinks,
DevelopersLinks,
ProductsLinks
} from './footer'
import s from './footer.module.scss'
export const Footer = () => {
return (
<footer className={s.footer}>
<Container className={s['container']}>
<div className={s['logo']}>
<Link variant="unstyled" href="/">
<LogoFooter />
</Link>
</div>
<nav>
<ul>
{DevelopersLinks.map((link) => {
return (
<li key={link.title}>
<Link href={link.href} variant="nav">
{link.title}
</Link>
</li>
)
})}
</ul>
<ul>
{ProductsLinks.map((link) => {
return (
<li key={link.title}>
<Link href={link.href} variant="nav">
{link.title}
</Link>
</li>
)
})}
</ul>
<ul>
{AboutLinks.map((link) => {
return (
<li key={link.title}>
<Link href={link.href} variant="nav">
{link.title}
</Link>
</li>
)
})}
</ul>
<ul>
{CommunityLinks.map((link) => {
return (
<li key={link.title}>
<Link href={link.href} variant="nav">
{link.title}
</Link>
</li>
)
})}
</ul>
<ul className={s['connect__links']}>
<li>
<Link href="/connect" variant="nav">
Connect
</Link>
</li>
<div>
{ConnectLinks.map((link) => {
return (
<li key={link.title}>
<Link href={link.href} variant="unstyled">
<span className="sr-only">{link.title}</span>
{link.logo && link.logo}
</Link>
</li>
)
})}
</div>
</ul>
</nav>
</Container>
<Container>
<nav className={s['sub__footer']}>
<ul>
<div>
<li>
<Link href="/privacy-policy" variant="nav">
Privacy Policy
</Link>
</li>
<li>
<Link href="/terms-of-use" variant="nav">
Terms of Use
</Link>
</li>
</div>
<li>
<p>Laconic, The Source of Proof</p>
</li>
</ul>
</nav>
</Container>
</footer>
)
}
@@ -0,0 +1,34 @@
@import '~/css/helpers';
.header {
position: fixed;
z-index: 10;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: tovw(14px, 'default', 14px) var(--main-padding-side);
background: rgb(4 4 4 / 0.01);
backdrop-filter: blur(20px);
nav {
display: flex;
align-items: center;
svg {
display: block;
}
}
ul {
display: flex;
margin: 0 0 0 tovw(122px, 'default', 90px);
padding: 0;
list-style-type: none;
gap: tovw(32px, 'default', 20px);
}
.burger {
width: tovw(22px, 'default', 22px);
}
}
+8
View File
@@ -0,0 +1,8 @@
export const defaultHeaderLinks = [
{ href: '/', title: 'Home' },
{ href: '/developers', title: 'Developers' },
{ href: '/products', title: 'Products' },
{ href: '/community', title: 'Community' },
{ href: '/about', title: 'About' },
{ href: '/blog', title: 'Blog' }
]
+38
View File
@@ -0,0 +1,38 @@
import clsx from 'clsx'
import NextLink from 'next/link'
import Burger from '~/components/icons/burguer'
import { Logo } from '~/components/icons/logo'
import { Button } from '~/components/primitives/button'
import Link from '~/components/primitives/link'
import { defaultHeaderLinks } from './header'
import s from './header.module.scss'
export const Header = () => {
return (
<header className={s.header}>
<nav>
<NextLink href="/">
<a>
<Logo />
</a>
</NextLink>
<ul className="hide-on-mobile">
{defaultHeaderLinks.length > 0 &&
defaultHeaderLinks.map((link, index) => (
<li key={index}>
<Link href={link.href} variant="nav">
{link.title}
</Link>
</li>
))}
</ul>
</nav>
<Button size="small" className="hide-on-mobile">
Get Started
</Button>
<Burger className={clsx(s['burger'], 'hide-on-desktop')} />
</header>
)
}
+96
View File
@@ -0,0 +1,96 @@
import { useRouter } from 'next/dist/client/router'
import NextHead from 'next/head'
import { NextSeo, NextSeoProps } from 'next-seo'
import * as React from 'react'
import { defaultMeta, siteOrigin } from '~/lib/constants'
type BasicMeta = {
colorScheme?: 'dark' | 'light'
description?: string
noIndex?: boolean
ogImage?: string
prefetch?: { href: string; as: string }[]
preload?: { href: string; as: string }[]
themeColor?: string
title?: string
}
export type MetaProps = BasicMeta & { rawNextSeoProps?: NextSeoProps }
export const Meta = (props: MetaProps) => {
const router = useRouter()
const nextSeoProps: NextSeoProps = React.useMemo(() => {
return {
title: props.title ?? defaultMeta.title,
description: props.description ?? defaultMeta.description,
canonical: `${siteOrigin}${router.pathname}`,
openGraph: {
images: [
{
url: props.ogImage ?? defaultMeta.ogImage,
alt: props.title ?? defaultMeta.title,
width: 1200,
height: 630,
type: 'image/jpeg'
}
]
},
twitter: {
cardType: 'summary_large_image',
handle: defaultMeta.twitter.handle,
site: defaultMeta.twitter.site
},
noindex: props.noIndex,
...props.rawNextSeoProps
}
}, [props, router.pathname])
return (
<>
<NextSeo {...nextSeoProps} />
<NextHead>
<meta charSet="utf-8" />
<meta
name="viewport"
content="minimum-scale=1, initial-scale=1, width=device-width"
/>
<meta name="theme-color" content={props.themeColor ?? '#FFB1E4'} />
<link
rel="apple-touch-icon"
sizes="180x180"
href="/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/favicon-16x16.png"
/>
<link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#000000" />
<meta name="msapplication-TileColor" content="#000000" />
<meta name="theme-color" content="#ffffff" />
{props.preload?.map(({ href, as }) => (
<link key={href} rel="preload" href={href} as={as} />
))}
{props.prefetch?.map(({ href, as }) => (
<link key={href} rel="prefetch" href={href} as={as} />
))}
</NextHead>
<style jsx global>{`
html {
color-scheme: ${props.colorScheme ?? 'light'};
}
`}</style>
</>
)
}
+44
View File
@@ -0,0 +1,44 @@
import clsx from 'clsx'
import { useDeviceDetect } from '~/hooks/use-device-detect'
import s from './noise.module.css'
export const Noise = ({
softLight = true,
colorBurn = true,
ignoreDevice = false,
absolute = false
}) => {
const { isSafari, isMobile, loaded } = useDeviceDetect()
if (!loaded) return null
return (
<>
{(ignoreDevice || (!isSafari && !isMobile)) && (
<>
{softLight && (
<div
className={clsx(
s.noise,
'noise',
absolute ? 'absolute' : 'fixed'
)}
aria-hidden
/>
)}
{colorBurn && (
<div
className={clsx(
s.noise2,
'noise',
absolute ? 'absolute' : 'fixed'
)}
aria-hidden
/>
)}
</>
)}
</>
)
}
@@ -0,0 +1,77 @@
.noise {
background-color: white;
background-image: url('/images/noise.png');
background-repeat: repeat;
background-size: auto;
z-index: 500;
width: 300%;
height: 300%;
left: -100%;
top: -100%;
pointer-events: none;
mix-blend-mode: soft-light;
opacity: 0.2;
}
.noise2 {
background-color: white;
background-image: url('/images/noise-2.png');
background-repeat: repeat;
background-size: auto;
z-index: 500;
width: 300%;
height: 300%;
left: -100%;
top: -100%;
pointer-events: none;
mix-blend-mode: color-burn;
opacity: 0.8;
will-change: transform;
animation: grain 16s steps(10) infinite;
}
@keyframes grain {
0% {
transform: translate(20%, -15%);
}
10% {
transform: translate(-20%, -15%);
}
20% {
transform: translate(20%, -5%);
}
30% {
transform: translate(-20%, -5%);
}
40% {
transform: translate(20%, 5%);
}
50% {
transform: translate(-20%, 5%);
}
60% {
transform: translate(20%, 15%);
}
70% {
transform: translate(-20%, 15%);
}
80% {
transform: translate(20%, 5%);
}
90% {
transform: translate(-20%, 5%);
}
100% {
transform: translate(20%, -5%);
}
}
+59
View File
@@ -0,0 +1,59 @@
const Arrow = ({ className, fill }: { className?: string; fill?: string }) => {
return (
<svg
className={className}
viewBox="0 0 23 23"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3.758 23H23V3.758l-2.523-.01.01 14.935L1.804 0 0 1.804l18.683 18.683-14.936-.01L3.758 23Z"
fill={fill || 'var(--color-white)'}
/>
</svg>
)
}
const ArrowDotted = ({
className,
fill
}: {
className?: string
fill?: string
}) => {
return (
<svg
className={className}
fill="none"
viewBox="0 0 42 14"
xmlns="http://www.w3.org/2000/svg"
>
<path d="m1 35 6 6 6-6" stroke={fill || '#fff'} />
<path d="M7 41V0" stroke={fill || '#fff'} strokeDasharray="2 2" />
</svg>
)
}
const ArrowLink = ({
className,
fill
}: {
className?: string
fill?: string
}) => {
return (
<svg
className={className}
fill="none"
viewBox="0 0 11 11"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1.455 10.557.427 9.53l7.648-7.662H2.167L2.181.443h8.347v8.36H9.091l.013-5.907-7.649 7.661Z"
fill={fill || 'var(--color-white)'}
/>
</svg>
)
}
export { Arrow, ArrowDotted, ArrowLink }
+18
View File
@@ -0,0 +1,18 @@
const Burger = ({ className, fill }: { className?: string; fill?: string }) => {
return (
<svg
className={className}
viewBox="0 0 22 11"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke={fill || '#fff'}
style={{ mixBlendMode: 'difference' }}
d="M0 .5h22M0 5.5h22M0 10.5h22"
/>
</svg>
)
}
export default Burger
+72
View File
@@ -0,0 +1,72 @@
const Isotype = ({
className,
fill
}: {
className?: string
fill?: string
}) => {
return (
<svg
className={className}
width="94"
height="95"
viewBox="0 0 94 95"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M15.86 49.967C26.8 38.911 33.57 23.643 33.568 6.785A60.4 60.4 0 0 0 33.198 0L0 .003l.001 64.465c-.003 7.814 2.945 15.631 8.842 21.59 5.897 5.96 13.637 8.942 21.37 8.938l-.002.002L94 95l-.002-33.556a60.653 60.653 0 0 0-6.714-.373c-16.676.002-31.784 6.845-42.724 17.9-7.96 7.844-20.724 7.845-28.586-.1-7.858-7.941-7.86-20.845-.114-28.904ZM87.114 6.976c-9.168-9.265-24.063-9.269-33.234 0-9.171 9.268-9.168 24.321 0 33.586 9.173 9.27 24.063 9.269 33.234 0 9.171-9.268 9.173-24.316 0-33.586Z"
fill={fill || 'var(--color-white)'}
/>
</svg>
)
}
const Logo = ({ className, fill }: { className?: string; fill?: string }) => {
return (
<svg
className={className}
width="133"
height="24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M37.761 22.302h9.246v-2.704h-6.155v-17.9h-3.09v20.604ZM59.314 1.697h-5.126l-5.357 20.605h3.194l1.34-5.151h6.618l1.34 5.151h3.348L59.314 1.697Zm-5.306 12.878 2.679-10.663h.103l2.575 10.663h-5.357ZM74.337 9.682h3.606c0-5.873-1.88-8.397-6.259-8.397-4.61 0-6.593 3.194-6.593 10.689 0 7.52 1.983 10.74 6.593 10.74 4.379 0 6.259-2.447 6.285-8.139h-3.606c-.026 4.456-.567 5.563-2.679 5.563-2.42 0-3.013-1.622-2.987-8.164 0-6.516.592-8.14 2.987-8.113 2.112 0 2.653 1.159 2.653 5.82ZM86.689 1.285c4.687.026 6.696 3.245 6.696 10.715 0 7.469-2.009 10.688-6.696 10.714-4.714.026-6.723-3.194-6.723-10.714 0-7.521 2.01-10.74 6.723-10.715ZM83.572 12c0 6.516.618 8.139 3.117 8.139 2.472 0 3.09-1.623 3.09-8.14 0-6.541-.618-8.164-3.09-8.138-2.499.026-3.117 1.648-3.117 8.139ZM99.317 22.276l-3.09.026V1.697h5.434l5.074 16.793h.052V1.697h3.09v20.605h-5.099l-5.409-18.08h-.052v18.054ZM116.615 1.697h-3.091v20.605h3.091V1.697ZM128.652 9.682h3.606c0-5.873-1.881-8.397-6.259-8.397-4.61 0-6.594 3.194-6.594 10.689 0 7.52 1.984 10.74 6.594 10.74 4.378 0 6.259-2.447 6.284-8.139h-3.605c-.026 4.456-.567 5.563-2.679 5.563-2.421 0-3.014-1.622-2.988-8.164 0-6.516.593-8.14 2.988-8.113 2.112 0 2.653 1.159 2.653 5.82Z"
fill={fill || 'var(--color-white)'}
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M4.05 12.623A15.378 15.378 0 0 0 8.57 1.714C8.573 1.136 8.54.564 8.477 0H0v16.287c0 1.974.752 3.949 2.258 5.454A7.69 7.69 0 0 0 7.714 24L24 24v-8.477a15.636 15.636 0 0 0-1.715-.095c-4.258 0-8.115 1.73-10.908 4.523-2.032 1.981-5.291 1.982-7.299-.026-2.006-2.006-2.007-5.266-.029-7.302Zm18.192-10.86a6.004 6.004 0 0 0-8.485 0 6.003 6.003 0 0 0 0 8.484 6.003 6.003 0 0 0 8.485 0 6.002 6.002 0 0 0 0-8.485Z"
fill={fill || 'var(--color-white)'}
/>
</svg>
)
}
const LogoFooter = ({
className,
fill
}: {
className?: string
fill?: string
}) => {
return (
<svg
className={className}
fill="none"
viewBox="0 0 305 70"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0 68.656h29.794v-8.834H9.96V1.35H0v67.306ZM69.388 1.35H52.873L35.61 68.656H45.9l4.316-16.826h21.33l4.315 16.826H86.65L69.388 1.35ZM52.292 43.416l8.631-34.83h.332l8.3 34.83H52.291ZM118.076 27.427h11.619C129.695 8.245 123.636 0 109.528 0 94.672 0 88.282 10.432 88.282 34.915c0 24.567 6.39 35.084 21.246 35.084 14.108 0 20.167-7.993 20.25-26.586h-11.619c-.083 14.555-1.826 18.173-8.631 18.173-7.802 0-9.71-5.3-9.627-26.67 0-21.286 1.908-26.587 9.627-26.503 6.805 0 8.548 3.786 8.548 19.014ZM158.069 0c15.105.085 21.578 10.601 21.578 35s-6.473 34.915-21.578 35c-15.188.084-21.661-10.433-21.661-35s6.473-35.084 21.661-35Zm-10.042 35c0 21.286 1.992 26.586 10.042 26.586 7.967 0 9.959-5.3 9.959-26.586 0-21.37-1.992-26.67-9.959-26.586-8.05.084-10.042 5.384-10.042 26.586ZM198.74 68.572l-9.959.084V1.35h17.511l16.35 54.855h.166V1.35h9.959v67.306h-16.432L198.906 9.595h-.166v58.977ZM254.391 1.35h-9.959v67.306h9.959V1.35ZM293.298 27.427h11.619C304.917 8.245 298.859 0 284.75 0c-14.856 0-21.246 10.432-21.246 34.915 0 24.567 6.39 35.084 21.246 35.084 14.109 0 20.167-7.993 20.25-26.586h-11.619c-.083 14.555-1.826 18.173-8.631 18.173-7.801 0-9.71-5.3-9.627-26.67 0-21.286 1.909-26.587 9.627-26.503 6.805 0 8.548 3.786 8.548 19.014Z"
fill={fill || 'var(--color-white)'}
/>
</svg>
)
}
export { Isotype, Logo, LogoFooter }
+228
View File
@@ -0,0 +1,228 @@
const Telegram = ({
className,
fill
}: {
className?: string
fill?: string
}) => {
return (
<svg
className={className}
fill="none"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
clipRule="evenodd"
d="M24 12c0 6.6274-5.3726 12-12 12-6.62742 0-12-5.3726-12-12C0 5.37258 5.37258 0 12 0c6.6274 0 12 5.37258 12 12ZM12.43 8.85893c-1.1672.48547-3.49986 1.49027-6.99811 3.01437-.56806.2259-.86563.4469-.89272.663-.04578.3652.41154.509 1.0343.7048.08471.0267.17248.0543.26247.0835.6127.1992 1.43689.4322 1.86535.4414.38865.0084.82244-.1518 1.30135-.4807 3.26856-2.2063 4.95576-3.32149 5.06166-3.34553.0747-.01696.1783-.03829.2485.02408.0701.06235.0632.18045.0558.21215-.0453.1931-1.8405 1.8621-2.7695 2.7258-.2896.2692-.495.4602-.537.5038-.0941.0977-.19.1902-.2821.279-.5692.5487-.99609.9602.0236 1.6322.49.3229.8822.5899 1.2733.8563.4273.291.8534.5812 1.4047.9426.1405.092.2746.1877.4053.2808.4972.3545.9438.6729 1.4957.6221.3206-.0295.6519-.331.8201-1.2302.3975-2.1253 1.1789-6.7299 1.3595-8.62743.0158-.16624-.0041-.379-.02-.4724-.016-.09339-.0494-.22646-.1708-.32497-.1438-.11666-.3658-.14126-.465-.13952-.4514.00795-1.1438.24874-4.4764 1.63485Z"
fill="url(#telegram)"
fillRule="evenodd"
/>
<defs>
<linearGradient
id="telegram"
x1="12"
y1="0"
x2="12"
y2="24"
gradientUnits="userSpaceOnUse"
>
<stop stopColor={fill || 'var(--color-white)'} />
<stop offset="1" stopColor="#DEDEDE" />
</linearGradient>
</defs>
</svg>
)
}
const Twitter = ({
className,
fill
}: {
className?: string
fill?: string
}) => {
return (
<svg
className={className}
fill="none"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M7.55016 21.3548c9.05434 0 14.00814-7.4471 14.00814-13.9033 0-.20936-.0047-.42337-.0141-.63273.9637-.69168 1.7953-1.54843 2.4558-2.52999-.8975.39631-1.8504.65515-2.8261.76765 1.0274-.61122 1.7966-1.57142 2.1652-2.7026-.9665.56851-2.0235.96954-3.1257 1.18591-.7426-.78315-1.7244-1.30169-2.7937-1.47544-1.0693-.17376-2.1665.00695-3.122.51417-.9554.50722-1.7159 1.31271-2.1639 2.29194-.4479.97922-.5584 2.07764-.3143 3.12543-1.95701-.09747-3.87156-.60206-5.61952-1.48104-1.74795-.87898-3.29029-2.11274-4.52701-3.62129-.62857 1.07562-.820913 2.34843-.53794 3.55975C1.418 7.66457 2.15506 8.7235 3.19641 9.41483c-.78178-.02463-1.54643-.23354-2.230785-.60947v.06048c-.0007 1.12879.392475 2.22296 1.112685 3.09656.72021.8736 1.72301 1.4727 2.83794 1.6955-.72419.1966-1.48427.2253-2.22141.0837.31461.9708.92673 1.8198 1.75093 2.4287.8242.6088 1.81935.9471 2.84657.9676-1.74392 1.3596-3.89817 2.0971-6.11578 2.0936C.783287 19.2309.390399 19.2069 0 19.1598c2.25286 1.4345 4.87353 2.1964 7.55016 2.195Z"
fill="url(#twitter)"
/>
<defs>
<linearGradient
id="twitter"
x1="12"
y1="2"
x2="12"
y2="21.3548"
gradientUnits="userSpaceOnUse"
>
<stop stopColor={fill || 'var(--color-white)'} />
<stop offset="1" stopColor="#DEDEDE" />
</linearGradient>
</defs>
</svg>
)
}
const Reddit = ({ className, fill }: { className?: string; fill?: string }) => {
return (
<svg
className={className}
fill="none"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M24 12c0 6.6274-5.3726 12-12 12-6.62742 0-12-5.3726-12-12C0 5.37258 5.37258 0 12 0c6.6274 0 12 5.37258 12 12Zm-5.7544-1.7544C19.214 10.2456 20 11.0316 20 12c0 .7158-.4351 1.3333-1.0105 1.614.0281.1684.0421.3369.0421.5193 0 2.6947-3.1298 4.8702-7.0035 4.8702-3.87371 0-7.00353-2.1755-7.00353-4.8702 0-.1824.01403-.3649.0421-.5333-.61754-.2807-1.03859-.8842-1.03859-1.6 0-.9684.78596-1.7544 1.75438-1.7544.46316 0 .89825.1965 1.20702.4912 1.20702-.88419 2.87719-1.43156 4.74382-1.4877l.8843-4.18246c.028-.08421.0701-.15438.1403-.19649.0702-.0421.1544-.05614.2386-.0421l2.9053.61754c.1965-.42105.6175-.70175 1.1087-.70175.6878 0 1.2492.5614 1.2492 1.24912s-.5614 1.24912-1.2492 1.24912c-.6736 0-1.221-.53333-1.2491-1.19298l-2.5965-.54737-.8 3.74737c1.8246.07018 3.4807.63158 4.6737 1.4877.3088-.3088.7298-.4912 1.207-.4912ZM9.24913 12c-.68772 0-1.24912.5614-1.24912 1.2491 0 .6877.5614 1.2491 1.24912 1.2491s1.24917-.5614 1.24917-1.2491c0-.6877-.56145-1.2491-1.24917-1.2491Zm2.76487 5.4596c.4772 0 2.1053-.0561 2.9614-.9123.1264-.1263.1264-.3228.0281-.4631-.1263-.1263-.3368-.1263-.4631 0-.5474.5333-1.6843.7298-2.5123.7298-.8281 0-1.979-.1965-2.5123-.7298-.12632-.1263-.33685-.1263-.46316 0-.12632.1263-.12632.3368 0 .4631.8421.8422 2.48416.9123 2.96136.9123Zm1.4878-4.2105c0 .6877.5614 1.2491 1.2491 1.2491.6877 0 1.2491-.5614 1.2491-1.2491C16 12.5614 15.4386 12 14.7509 12c-.6877 0-1.2491.5614-1.2491 1.2491Z"
fill="url(#reddit)"
/>
<defs>
<linearGradient
id="reddit"
x1="12"
y1="0"
x2="12"
y2="24"
gradientUnits="userSpaceOnUse"
>
<stop stopColor={fill || 'var(--color-white)'} />
<stop offset="1" stopColor="#DEDEDE" />
</linearGradient>
</defs>
</svg>
)
}
const Linkedin = ({
className,
fill
}: {
className?: string
fill?: string
}) => {
return (
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M3e-7 2.00509C3e-7 1.47331.21125.963308.587278.58728.963305.211253 1.47331.00000267 2.00509.00000267H21.9927c.2636-.00043042.5246.05112313.7682.15170933.2436.100586.4649.248229.6514.434476.1864.186247.3343.40744.4352.650912.1008.24348.1526.50446.1525.76799V21.9927c.0003.2636-.0514.5247-.1521.7683-.1007.2436-.2485.4649-.4348.6513-.1863.1865-.4076.3343-.6511.4352-.2436.1008-.5046.1526-.7682.1525H2.00509c-.2634 0-.52423-.0519-.76757-.1527-.243336-.1009-.464424-.2487-.650628-.435-.186204-.1863-.333875-.4074-.434576-.6508-.1007015-.2434-.15245901-.5043-.1523157-.7677V2.00509ZM9.49964 9.15055h3.24986v1.63195C13.2185 9.84437 14.4185 9 16.2218 9c3.4571 0 4.2764 1.8687 4.2764 5.2975v6.3512h-3.4986v-5.5702c0-1.9527-.4691-3.0545-1.6603-3.0545-1.6528 0-2.34 1.188-2.34 3.0545v5.5702H9.49964V9.15055Zm-6 11.34875h3.49963V9H3.49964v11.4993ZM7.5 5.24946c.0066.29964-.04672.59758-.15683.87634s-.27479.53271-.48437.74696c-.20959.21426-.45986.38449-.73612.5007-.27627.11622-.57296.17609-.87268.17609s-.59641-.05987-.87268-.17609c-.27626-.11621-.52653-.28644-.73612-.5007-.20958-.21425-.37426-.4682-.48437-.74696C3.04672 5.84704 2.9934 5.5491 3 5.24946c.01295-.58816.25569-1.14787.67624-1.55925.42054-.41139.98546-.64175 1.57376-.64175s1.15322.23036 1.57376.64175c.42055.41138.66329.97109.67624 1.55925Z"
fill={fill || 'var(--color-white)'}
/>
</svg>
)
}
const Facebook = ({
className,
fill
}: {
className?: string
fill?: string
}) => {
return (
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M24 12c0-6.62742-5.3726-12-12-12C5.37258 0 0 5.37258 0 12c0 5.9895 4.3882 10.954 10.125 11.8542v-8.3855H7.07812V12H10.125V9.35625c0-3.0075 1.7916-4.66875 4.5326-4.66875 1.3125 0 2.6862.23437 2.6862.23437V7.875h-1.5132c-1.4906 0-1.9556.92508-1.9556 1.875V12h3.3281l-.532 3.4687H13.875v8.3855C19.6118 22.954 24 17.9895 24 12Z"
fill="url(#facebook)"
/>
<defs>
<linearGradient
id="facebook"
x1="12"
y1="0"
x2="12"
y2="23.8542"
gradientUnits="userSpaceOnUse"
>
<stop stopColor={fill || 'var(--color-white)'} />
<stop offset="1" stopColor="#DEDEDE" />
</linearGradient>
</defs>
</svg>
)
}
const Instagram = ({
className,
fill
}: {
className?: string
fill?: string
}) => {
return (
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 2.16094c3.2063 0 3.5859.01406 4.8469.07031 1.1719.05156 1.8047.24844 2.2265.4125.5579.21563.961.47813 1.3782.89531.4218.42188.6797.82032.8953 1.37813.164.42187.3609 1.05937.4125 2.22656.0562 1.26562.0703 1.64531.0703 4.84685 0 3.2063-.0141 3.586-.0703 4.8469-.0516 1.1719-.2485 1.8047-.4125 2.2266-.2156.5578-.4782.9609-.8953 1.3781-.4219.4219-.8203.6797-1.3782.8953-.4218.1641-1.0593.3609-2.2265.4125-1.2656.0562-1.6453.0703-4.8469.0703-3.20625 0-3.58594-.0141-4.84687-.0703-1.17188-.0516-1.80469-.2484-2.22657-.4125-.55781-.2156-.96093-.4781-1.37812-.8953-.42188-.4219-.67969-.8203-.89531-1.3781-.16407-.4219-.36094-1.0594-.4125-2.2266-.05625-1.2656-.07032-1.6453-.07032-4.8469 0-3.20622.01407-3.58591.07032-4.84685.05156-1.17188.24843-1.80469.4125-2.22656.21562-.55781.47812-.96094.89531-1.37813.42187-.42187.82031-.67968 1.37812-.89531.42188-.16406 1.05938-.36094 2.22657-.4125C8.41406 2.175 8.79375 2.16094 12 2.16094ZM12 0C8.74219 0 8.33438.0140625 7.05469.0703125 5.77969.126563 4.90313.332812 4.14375.628125 3.35156.9375 2.68125 1.34531 2.01563 2.01562 1.34531 2.68125.9375 3.35156.628125 4.13906.332812 4.90313.126563 5.775.0703125 7.05.0140625 8.33437 0 8.74219 0 12c0 3.2578.0140625 3.6656.0703125 4.9453.0562505 1.275.2624995 2.1516.5578125 2.911.309375.7921.717185 1.4625 1.387505 2.1281.66562.6656 1.33593 1.0781 2.12343 1.3828.76407.2953 1.63594.5015 2.91094.5578 1.27969.0562 1.6875.0703 4.9453.0703 3.2578 0 3.6656-.0141 4.9453-.0703 1.275-.0563 2.1516-.2625 2.911-.5578.7875-.3047 1.4578-.7172 2.1234-1.3828.6656-.6656 1.0781-1.336 1.3828-2.1235.2953-.764.5016-1.6359.5578-2.9109.0563-1.2797.0703-1.6875.0703-4.9453 0-3.25782-.014-3.66564-.0703-4.94532-.0562-1.275-.2625-2.15157-.5578-2.91094-.2953-.79688-.7031-1.46719-1.3734-2.13282C21.3188 1.35 20.6484.9375 19.8609.632812 19.0969.3375 18.225.13125 16.95.075 15.6656.0140625 15.2578 0 12 0Z"
fill="url(#instagram-a)"
/>
<path
d="M12 5.83594c-3.40312 0-6.16406 2.76094-6.16406 6.16406 0 3.4031 2.76094 6.1641 6.16406 6.1641 3.4031 0 6.1641-2.761 6.1641-6.1641 0-3.40312-2.761-6.16406-6.1641-6.16406Zm0 10.16246c-2.20781 0-3.99844-1.7906-3.99844-3.9984 0-2.20781 1.79063-3.99844 3.99844-3.99844 2.2078 0 3.9984 1.79063 3.9984 3.99844 0 2.2078-1.7906 3.9984-3.9984 3.9984Z"
fill="url(#instagram-b)"
/>
<path
d="M19.8469 5.59238c0 .79688-.6469 1.43907-1.4391 1.43907-.7969 0-1.439-.64688-1.439-1.43907 0-.79687.6468-1.43906 1.439-1.43906s1.4391.64688 1.4391 1.43906Z"
fill="url(#instagram-c)"
/>
<defs>
<linearGradient
id="instagram-a"
x1="11.993"
y1="0"
x2="11.993"
y2="23.9953"
gradientUnits="userSpaceOnUse"
>
<stop stopColor={fill || 'var(--color-white)'} />
<stop offset="1" stopColor="#DEDEDE" />
</linearGradient>
<linearGradient
id="instagram-b"
x1="12"
y1="5.83594"
x2="12"
y2="18.1641"
gradientUnits="userSpaceOnUse"
>
<stop stopColor={fill || 'var(--color-white)'} />
<stop offset="1" stopColor="#DEDEDE" />
</linearGradient>
<linearGradient
id="instagram-c"
x1="18.4078"
y1="4.15332"
x2="18.4078"
y2="7.03145"
gradientUnits="userSpaceOnUse"
>
<stop stopColor={fill || 'var(--color-white)'} />
<stop offset="1" stopColor="#DEDEDE" />
</linearGradient>
</defs>
</svg>
)
}
export { Facebook, Instagram, Linkedin, Reddit, Telegram, Twitter }
@@ -0,0 +1,28 @@
.aspect-box {
position: relative;
}
@supports (aspect-ratio: var(--raw-ratio)) {
.aspect-box {
aspect-ratio: var(--raw-ratio);
position: relative;
}
}
@supports not (aspect-ratio: var(--raw-ratio)) {
.aspect-box::before {
content: '';
width: 1px;
margin-left: -1px;
float: left;
height: 0;
padding-top: var(--ratio);
}
.aspect-box::after {
/* to clear float */
content: '';
display: table;
clear: both;
}
}
@@ -0,0 +1,26 @@
import clsx from 'clsx'
import * as React from 'react'
import s from './aspect-box.module.css'
export const AspectBox = ({
ratio,
children,
className,
style,
...rest
}: { ratio: number } & JSX.IntrinsicElements['div']) => {
return (
<div
{...rest}
className={clsx(s['aspect-box'], className)}
style={{
...style,
['--ratio' as string]: `${100 / ratio}%`,
['--raw-ratio' as string]: ratio
}}
>
{children}
</div>
)
}
@@ -0,0 +1,12 @@
@import '~/css/helpers';
.container {
width: 100%;
max-width: tovw(1296px, 'default', 320px);
margin: 0 auto;
@media screen and (max-width: 800px) {
max-width: 100%;
padding: 0 tovw(27px, 'tablet', 27px);
}
}
+15
View File
@@ -0,0 +1,15 @@
import clsx from 'clsx'
import * as React from 'react'
import s from './container.module.scss'
export const Container = React.forwardRef<
HTMLDivElement,
JSX.IntrinsicElements['div']
>(({ className, ...props }, ref) => {
return (
<div {...props} className={clsx(s['container'], className)} ref={ref} />
)
})
export type ContainerProps = React.ComponentProps<typeof Container>
+34
View File
@@ -0,0 +1,34 @@
import * as React from 'react'
import { Footer } from '~/components/common/footer'
import { Header } from '~/components/common/header'
import { LocomotiveScrollProvider } from '~/lib/locomotive-scroll/provider'
type Props = {
children?: React.ReactNode
extras?: React.ReactNode
locomotiveScroll?: boolean
}
const ContentMemo = React.memo(({ children, extras }: Props) => {
return (
<>
{extras}
<Header />
<main>{children}</main>
<Footer />
</>
)
})
export const PageLayout = (props: Props) => {
if (props.locomotiveScroll) {
return (
<LocomotiveScrollProvider>
<ContentMemo {...props} />
</LocomotiveScrollProvider>
)
} else {
return <ContentMemo {...props} />
}
}
+101
View File
@@ -0,0 +1,101 @@
import clsx from 'clsx'
import gsap from 'gsap'
import * as React from 'react'
import mergeRefs from 'react-merge-refs'
import { useAnimationContext } from '~/context/animation'
import { useIsomorphicLayoutEffect } from '~/hooks/use-isomorphic-layout-effect'
import s from './section.module.scss'
type SectionProps = {
children: React.ReactNode
className?: string
disableFadeIn?: boolean
id?: string
onBlur?: React.FocusEventHandler
scrollSection?: boolean
timeline?: GSAPTimeline
variant?: 'default' | 'stripes' | 'stripes-center' | 'unstyled'
}
const Section = React.forwardRef<HTMLDivElement, SectionProps>(
(
{
children,
className,
disableFadeIn,
id,
onBlur,
scrollSection,
timeline,
variant
},
ref
) => {
const innerRef = React.useRef<HTMLDivElement>(null)
const { shouldAnimate } = useAnimationContext()
const [inView, setInView] = React.useState(false)
React.useEffect(() => {
const elementToObserve = innerRef.current
if (!elementToObserve) return
const handleObserve: IntersectionObserverCallback = ([element]) => {
if (element) {
setInView((p) => {
// trigger once
if (p === true) return true
else return element.isIntersecting
})
}
}
const observer = new IntersectionObserver(handleObserve, {
rootMargin: '8% 0%'
})
observer.observe(elementToObserve)
return () => {
observer.disconnect()
}
}, [])
useIsomorphicLayoutEffect(() => {
if (disableFadeIn || !shouldAnimate || !inView || !innerRef.current) {
return
}
const tl = gsap.timeline({
paused: true,
smoothChildTiming: true,
onComplete: () => {
if (timeline) {
timeline.play()
}
}
})
tl.from(innerRef.current, { autoAlpha: 0 })
tl.play()
return () => {
tl.kill()
}
}, [inView, shouldAnimate, timeline, disableFadeIn])
return (
<section
className={clsx(className, s.base, variant && s[`variant-${variant}`])}
id={id}
ref={mergeRefs([innerRef, ref])}
onBlur={onBlur}
{...(scrollSection ? { 'data-scroll-section': true } : {})}
>
{children}
</section>
)
}
)
export default Section
@@ -0,0 +1,30 @@
@import '~/css/helpers';
.base:not(.variant-unstyled) {
position: relative;
overflow: hidden;
}
.variant-stripes,
.variant-stripes-center {
background-color: theme('colors.pink');
overflow: hidden;
}
.stripesBase {
top: tovw(-524px);
left: tovw(-342px);
width: tovw(2605px, 'desktop-large');
height: tovw(2460px, 'desktop-large');
@media (prefers-reduced-motion: reduce) {
animation: none;
}
}
.children {
z-index: 1;
position: relative;
width: 100%;
height: 100%;
}
@@ -0,0 +1,42 @@
@import '~/css/helpers';
.button {
display: inline-block;
transition: all var(--normal-transition);
border: tovw(1px) solid var(--color-white);
border-radius: tovw(4px);
background: transparent;
cursor: pointer;
padding: tovw(16px, 'default', 10px) tovw(32px, 'default', 14px);
text-transform: uppercase;
line-height: 1;
color: var(--color-white);
font-family: var(--font-dm-mono);
font-size: tovw(18px, 'default', 14px);
appearance: none;
&:disabled {
pointer-events: none;
}
&:hover {
background: var(--color-white);
color: var(--color-black);
}
&--primary {
border-color: var(--color-accent);
box-shadow: 0 0 tovw(50px, 'default', 16px) var(--color-accent);
background: var(--color-accent);
&:hover {
box-shadow: 0 0 0 var(--color-accent);
background: var(--color-accent);
color: var(--color-white);
}
}
&--small {
padding: tovw(11.5px, 'default', 11.5px) tovw(24px, 'default', 10px);
}
}
@@ -0,0 +1,98 @@
import type * as Polymorphic from '@radix-ui/react-polymorphic'
import clsx from 'clsx'
import Link, { LinkProps } from 'next/link'
import * as React from 'react'
import { checkIsExternal } from '~/lib/utils/router'
import s from './button.module.scss'
export type ButtonProps = JSX.IntrinsicElements['button'] & {
size?: 'small' | 'medium' | 'large'
variant?: 'default' | 'primary'
}
export const Button = React.forwardRef(
(
{
as: Comp = 'button',
children,
className,
size = 'medium',
variant = 'default',
...rest
},
ref
) => {
return (
<Comp
className={clsx(
s.button,
className,
s[`button--${size}`],
s[`button--${variant}`]
)}
{...rest}
ref={ref}
>
{children}
</Comp>
)
}
) as Polymorphic.ForwardRefComponent<'button', ButtonProps>
type NextLinkProps = Pick<
LinkProps,
'href' | 'locale' | 'prefetch' | 'replace' | 'scroll' | 'shallow'
>
export type ButtonLinkProps = ButtonProps &
Omit<JSX.IntrinsicElements['a'], 'href'> &
NextLinkProps & { notExternal?: boolean }
export const ButtonLink = React.forwardRef<'a', ButtonLinkProps>(
(
{
// NextLinkProps
href,
replace,
scroll = false,
shallow,
prefetch,
locale,
// Rest
notExternal,
...props
},
ref
) => {
const externalProps = React.useMemo(() => {
const p = { target: '_blank', rel: 'noopener' }
if (typeof href === 'string') {
if (checkIsExternal(href)) return p
} else if (checkIsExternal(href.href ?? '')) return p
}, [href])
return (
<Link
href={href}
replace={replace}
scroll={scroll}
shallow={shallow}
prefetch={prefetch}
locale={locale}
passHref
>
<Button
{...(notExternal ? undefined : externalProps)}
{...props}
as="a"
// @ts-ignore
ref={ref}
/>
</Link>
)
}
)
export default Button
@@ -0,0 +1,51 @@
@import 'css/helpers';
.heading {
font-family: var(--font-heading);
font-weight: 400;
font-kerning: none;
&.centered {
text-align: center;
}
&--xl {
font-size: tovw(110px, 'default', 48px);
line-height: 1.1;
letter-spacing: tovw(-3px);
}
&--lg {
font-size: tovw(76px, 'default', 42px);
line-height: 1;
}
&--md {
font-size: tovw(58px, 'default', 42px);
line-height: 1.1;
}
&--sm {
font-size: tovw(40px, 'default', 36px);
line-height: 1;
}
&--xs {
font-size: tovw(12px, 'default', 12px);
line-height: 1.3;
letter-spacing: tovw(-0.5px);
text-transform: uppercase;
}
&--arthemys {
font-family: var(--font-arthemys);
}
&--tthoves {
font-family: var(--font-tt-hoves);
}
&--dmmono {
font-family: var(--font-dm-mono);
}
}
@@ -0,0 +1,47 @@
import type * as Polymorphic from '@radix-ui/react-polymorphic'
import clsx from 'clsx'
import * as React from 'react'
// Styles
import s from './heading.module.scss'
type HeadingProps = {
variant: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
font?: 'arthemys' | 'tthoves' | 'dmmono'
centered?: boolean
}
const DEFAULT_ELEMENT = 'h2'
const Heading = React.forwardRef(
(
{
as: Comp = DEFAULT_ELEMENT,
centered = false,
children,
className,
font,
variant,
...props
},
ref
) => {
return (
<Comp
className={clsx(
s.heading,
s[`heading--${variant}`],
s[`heading--${font}`],
{ [s['centered']]: centered },
className
)}
ref={ref}
{...props}
>
{children}
</Comp>
)
}
) as Polymorphic.ForwardRefComponent<typeof DEFAULT_ELEMENT, HeadingProps>
export default Heading
@@ -0,0 +1,3 @@
.highlight {
color: var(--color-accent);
}
@@ -0,0 +1,9 @@
import { FC } from 'react'
import s from './highlighted-text.module.scss'
const HighlightedText: FC = ({ children }) => {
return <span className={s['highlight']}>{children}</span>
}
export default HighlightedText
+64
View File
@@ -0,0 +1,64 @@
import clsx from 'clsx'
import { checkIsExternal } from 'lib/utils/router'
import NextLink, { LinkProps as NextLinkProps } from 'next/link'
import * as React from 'react'
import { ArrowLink } from '~/components/icons/arrow'
import s from './link.module.scss'
export type LinkProps = {
children?: React.ReactNode
variant?: 'default' | 'nav' | 'unstyled'
} & JSX.IntrinsicElements['a'] &
Omit<NextLinkProps, 'as' | 'passHref'>
const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
({ children, className, variant = 'default', ...restProps }, ref) => {
const {
href,
// NextLink Props
replace,
scroll,
shallow,
prefetch,
// Rest
...aProps
} = restProps
const isExternal = checkIsExternal(href)
return (
<NextLink
href={href}
replace={replace}
scroll={scroll}
shallow={shallow}
prefetch={prefetch}
passHref
>
<a
className={clsx(
variant !== 'unstyled' && s['link'],
variant !== 'unstyled' && s[`link--${variant}`],
className
)}
ref={ref}
rel={isExternal ? 'noopener' : undefined}
target={isExternal ? '_blank' : undefined}
{...aProps}
>
{children}
{variant === 'default' &&
(isExternal ? (
<ArrowLink className={s['icon']} />
) : (
<ArrowLink className={clsx(s['icon'], s['icon--rotated'])} />
))}
</a>
</NextLink>
)
}
)
export default Link
@@ -0,0 +1,161 @@
@import 'css/helpers';
.link {
font-family: var(--font-dm-mono);
font-size: tovw(18px, 'default', 14px);
font-weight: 400;
position: relative;
transition: color var(--duration-fast) var(--ease);
white-space: nowrap;
text-decoration: none;
text-transform: uppercase;
pointer-events: all;
color: currentcolor;
outline: none;
&--default,
&--nav {
.icon {
position: absolute;
right: tovw(-20px, 'default', -18px);
bottom: tovw(7px, 'default', 7px);
width: tovw(10px, 'default', 8px);
height: tovw(10px, 'default', 8px);
transition: transform var(--normal-transition);
vertical-align: middle;
&--rotated {
transform: rotate(45deg);
}
}
&::before {
position: absolute;
top: 100%;
left: 0;
width: 100%;
height: tovw(1px, 'default', 1px);
content: '';
animation: border var(--duration-normal) linear infinite;
pointer-events: none;
opacity: 0;
background-image: repeating-linear-gradient(
0deg,
currentcolor,
currentcolor 100%,
transparent 100%,
transparent 100%,
currentcolor 100%
),
repeating-linear-gradient(
90deg,
currentcolor,
currentcolor 100%,
transparent 100%,
transparent 100%,
currentcolor 100%
),
repeating-linear-gradient(
currentcolor,
currentcolor 100%,
transparent 100%,
transparent 100%,
currentcolor 100%
),
repeating-linear-gradient(
270deg,
currentcolor,
currentcolor 100%,
transparent 100%,
transparent 100%,
currentcolor 100%
);
background-repeat: no-repeat;
background-position: 0 0, 0 0, 100% 0, 0 100%;
background-size: 0 100%, 100% 0, 0 100%, 100% 2px;
}
&::after {
position: absolute;
top: 100%;
left: 0;
width: 100%;
height: tovw(1px);
content: '';
background: currentcolor;
}
&:hover {
.icon {
transform: rotate(45deg) scale(1.15);
}
&::after {
opacity: 0;
}
&::before {
opacity: 1;
background-image: repeating-linear-gradient(
0deg,
currentcolor,
currentcolor tovw(4px, 'default', 3px),
transparent tovw(4px, 'default', 3px),
transparent tovw(8px, 'default', 6px),
currentcolor tovw(8px, 'default', 6px)
),
repeating-linear-gradient(
90deg,
currentcolor,
currentcolor tovw(4px, 'default', 3px),
transparent tovw(4px, 'default', 3px),
transparent tovw(8px, 'default', 6px),
currentcolor tovw(8px, 'default', 6px)
),
repeating-linear-gradient(
currentcolor,
currentcolor tovw(4px, 'default', 3px),
transparent tovw(4px, 'default', 3px),
transparent tovw(8px, 'default', 6px),
currentcolor tovw(8px, 'default', 6px)
),
repeating-linear-gradient(
270deg,
currentcolor,
currentcolor tovw(4px, 'default', 3px),
transparent tovw(4px, 'default', 3px),
transparent tovw(8px, 'default', 6px),
currentcolor tovw(8px, 'default', 6px)
);
}
}
@keyframes border {
from {
background-position: 0 0, tovw(8px, 'default', 6px) 0,
100% tovw(8px, 'default', 6px), 0 100%;
}
to {
background-position: 0 tovw(8px, 'default', 6px), 0 0, 100% 0,
tovw(8px, 'default', 6px) 100%;
}
}
}
&--nav {
font-family: var(--font-tt-hoves);
font-size: tovw(16px, 'default', 14px);
line-height: 1;
letter-spacing: tovw(-0.5px, 'default', -0.5px);
text-transform: none;
&::after {
content: initial;
}
&::before {
top: 110%;
}
}
}
@@ -0,0 +1,51 @@
import Link from 'next/link'
import { forwardRef } from 'react'
import s from './nav-link.module.scss'
export const NavLink = forwardRef<
HTMLAnchorElement,
{
href: string
children?: React.ReactNode
title?: string
bg?: string
}
>(({ href, children, title, bg = '#FFE927', ...rest }, ref) => {
return (
<Link href={href} scroll={false}>
<a
title={title}
className={s.base}
style={{ ['--bg' as string]: bg }}
{...rest}
ref={ref}
>
{children}
</a>
</Link>
)
})
export const NavButton = forwardRef<
HTMLButtonElement,
{
children?: React.ReactNode
title?: string
bg?: string
onClick?: () => void
type?: 'button' | 'submit'
}
>(({ children, title, bg = '#FFE927', ...rest }, ref) => {
return (
<button
title={title}
className={s.base}
style={{ ['--bg' as string]: bg }}
{...rest}
ref={ref}
>
{children}
</button>
)
})
@@ -0,0 +1,29 @@
@import '~/css/helpers';
.base {
@apply section-borders;
--default-border-color: #{theme('colors.green')};
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: var(--default-border-radius);
text-transform: uppercase;
font-weight: 800;
font-size: tovw(18px, 'desktop-large', '12px');
padding: 0 1.3333em;
height: 2.5555em;
line-height: calc(22 / 18);
letter-spacing: -0.05em;
transition: background-color 0.15s ease-in-out;
appearance: none;
@screen tablet {
font-size: 12px;
}
&:hover {
background-color: var(--bg);
}
}
+5
View File
@@ -0,0 +1,5 @@
# `sections` dir
- Section components for specific sections in the site
- Generally nested by page
- Examples: `/home/hero.tsx`, `/about/founders.tsx`, `/about/team.tsx`, etc...
@@ -0,0 +1,42 @@
.section {
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
height: calc(var(--vh) * 118 - var(--header-height));
text-align: center;
&::after,
&::before {
position: absolute;
z-index: -1;
bottom: 0;
left: 0;
width: 100%;
height: calc(var(--vh) * 95);
content: '';
}
&::after {
background: radial-gradient(
circle,
rgb(0 0 0 / 0.5) 45%,
hsl(0deg 0% 100% / 0) 100%
);
}
&::before {
background: linear-gradient(0deg, #000 50%, hsl(0deg 0% 100% / 0));
}
.video {
position: absolute;
z-index: -2;
top: 0;
left: 0;
width: 100%;
height: 100%;
user-select: none;
pointer-events: none;
}
}
@@ -0,0 +1,37 @@
import clsx from 'clsx'
import Section from '~/components/layout/section'
import Heading from '~/components/primitives/heading'
import HighlightedText from '~/components/primitives/highlighted-text'
import s from './hero.module.scss'
const Hero = () => {
return (
<Section className={s['section']}>
<video
autoPlay
className={clsx('hide-on-mobile', s['video'])}
controls={false}
loop
muted
preload="true"
>
<source src="/videos/hero-grid.mp4" type="video/mp4" />
</video>
<Heading as="h1" variant="xl" centered>
The <HighlightedText>Multi-chain</HighlightedText> <br /> Verifable Data
Marketplace
</Heading>
<p>
From months to minutes. Laconic accelerates <br /> your DApp
development, interoperability, <br /> and user adoption.
</p>
<Heading as="h5" variant="xs" font="dmmono" centered>
Scroll Down
</Heading>
</Section>
)
}
export default Hero