|
@ -1,35 +1,30 @@ |
|
|
import React, { useState, useEffect, useRef, useLayoutEffect } from "react"; |
|
|
import React, { useState, useEffect, useRef, useLayoutEffect } from "react"; |
|
|
import { css, cx } from "@emotion/css"; |
|
|
import { css, cx } from "@emotion/css"; |
|
|
import { useNavigate } from "react-router-dom"; |
|
|
import useLocation from "wouter/use-location"; |
|
|
|
|
|
|
|
|
import { ReactComponent as Logo } from "../assets/logo.svg"; |
|
|
import { ReactComponent as Logo } from "../assets/logo.svg"; |
|
|
import { ReactComponent as Right } from "../assets/arrow-right.svg"; |
|
|
import { ReactComponent as Right } from "../assets/arrow-right.svg"; |
|
|
import { getTimeout } from "../util"; |
|
|
import { get, getTimeout } from "../util"; |
|
|
import Menu from "./Menu"; |
|
|
import Menu, { MenuEntries } from "./Menu"; |
|
|
import useMediaQuery from "../util/useMediaQuery"; |
|
|
import useMediaQuery from "../util/useMediaQuery"; |
|
|
|
|
|
|
|
|
const [timer, clear] = getTimeout(); |
|
|
const [timer, clear] = getTimeout(); |
|
|
|
|
|
|
|
|
const Container: React.FC<{ |
|
|
const Container: React.FC<{ |
|
|
children: (string | React.DetailedReactHTMLElement<any, HTMLElement> | React.ReactElement)[]; |
|
|
children: ( |
|
|
|
|
|
| string |
|
|
|
|
|
| React.DetailedReactHTMLElement<any, HTMLElement> |
|
|
|
|
|
| React.ReactElement |
|
|
|
|
|
)[]; |
|
|
hideNav?: boolean; |
|
|
hideNav?: boolean; |
|
|
arrowReversed?: boolean; |
|
|
|
|
|
next?: string; |
|
|
|
|
|
className?: string; |
|
|
className?: string; |
|
|
}> = ({ |
|
|
}> = ({ children: _children, hideNav = false, className, ...props }) => { |
|
|
children: _children, |
|
|
const [location, navigate] = useLocation(); |
|
|
hideNav = false, |
|
|
|
|
|
arrowReversed = false, |
|
|
|
|
|
next, |
|
|
|
|
|
className, |
|
|
|
|
|
...props |
|
|
|
|
|
}) => { |
|
|
|
|
|
const navigate = useNavigate(); |
|
|
|
|
|
|
|
|
|
|
|
const mobile = useMediaQuery("(max-width: 50rem)"); |
|
|
const mobile = useMediaQuery("(max-width: 50rem)"); |
|
|
|
|
|
|
|
|
const logoContainer = useRef<HTMLButtonElement>(null); |
|
|
const logoContainer = useRef<HTMLButtonElement>(null); |
|
|
const highlightCircle = useRef<HTMLSpanElement>(null); |
|
|
const highlightCircle = useRef<HTMLButtonElement>(null); |
|
|
const containerChild = useRef<HTMLDivElement>(null); |
|
|
const containerChild = useRef<HTMLDivElement>(null); |
|
|
const nextBtn = useRef<HTMLButtonElement>(null); |
|
|
const nextBtn = useRef<HTMLButtonElement>(null); |
|
|
|
|
|
|
|
@ -37,11 +32,20 @@ const Container: React.FC<{ |
|
|
|
|
|
|
|
|
const children = React.Children.map( |
|
|
const children = React.Children.map( |
|
|
_children, |
|
|
_children, |
|
|
(child: string | React.DetailedReactHTMLElement<any, HTMLElement> | React.ReactElement) => |
|
|
( |
|
|
|
|
|
child: |
|
|
|
|
|
| string |
|
|
|
|
|
| React.DetailedReactHTMLElement<any, HTMLElement> |
|
|
|
|
|
| React.ReactElement, |
|
|
|
|
|
) => |
|
|
!child || typeof child === "string" |
|
|
!child || typeof child === "string" |
|
|
? child |
|
|
? child |
|
|
: React.cloneElement(child, { |
|
|
: React.cloneElement(child, { |
|
|
style: { opacity: 0, transform: "translateY(3rem)", transition: "all 300ms" }, |
|
|
style: { |
|
|
|
|
|
opacity: 0, |
|
|
|
|
|
transform: "translateY(3rem)", |
|
|
|
|
|
transition: "all 300ms", |
|
|
|
|
|
}, |
|
|
}), |
|
|
}), |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
@ -52,14 +56,19 @@ const Container: React.FC<{ |
|
|
if (highlightCircle.current) { |
|
|
if (highlightCircle.current) { |
|
|
highlightCircle.current.classList.add("highlight"); |
|
|
highlightCircle.current.classList.add("highlight"); |
|
|
timer( |
|
|
timer( |
|
|
() => highlightCircle.current && highlightCircle.current.classList.remove("highlight"), |
|
|
() => |
|
|
|
|
|
highlightCircle.current && |
|
|
|
|
|
highlightCircle.current.classList.remove("highlight"), |
|
|
1500, |
|
|
1500, |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (nextBtn.current) { |
|
|
if (nextBtn.current) { |
|
|
nextBtn.current.style.width = "4rem"; |
|
|
nextBtn.current.style.width = "4rem"; |
|
|
timer(() => nextBtn.current && (nextBtn.current.style.right = "10vw"), 300); |
|
|
timer( |
|
|
|
|
|
() => nextBtn.current && (nextBtn.current.style.right = "10vw"), |
|
|
|
|
|
300, |
|
|
|
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (containerChild.current) { |
|
|
if (containerChild.current) { |
|
@ -86,7 +95,9 @@ const Container: React.FC<{ |
|
|
|
|
|
|
|
|
const handleResize = () => { |
|
|
const handleResize = () => { |
|
|
if (containerChild.current && logoContainer.current) |
|
|
if (containerChild.current && logoContainer.current) |
|
|
logoContainer.current.style.left = `${containerChild.current.getBoundingClientRect().x}px`; |
|
|
logoContainer.current.style.left = `${ |
|
|
|
|
|
containerChild.current.getBoundingClientRect().x |
|
|
|
|
|
}px`;
|
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
useEffect(() => { |
|
@ -96,33 +107,92 @@ const Container: React.FC<{ |
|
|
return () => window.removeEventListener("resize", handleResize); |
|
|
return () => window.removeEventListener("resize", handleResize); |
|
|
}, []); |
|
|
}, []); |
|
|
|
|
|
|
|
|
// on first render
|
|
|
type MouseKb = React.MouseEvent | React.KeyboardEvent | KeyboardEvent; |
|
|
useLayoutEffect(handleResize, []); |
|
|
|
|
|
|
|
|
|
|
|
const handleNext: React.MouseEventHandler<HTMLButtonElement> = e => { |
|
|
const animateArrow = (e: MouseKb) => { |
|
|
if (containerChild.current) { |
|
|
if (containerChild.current) { |
|
|
([...containerChild.current.children] as (HTMLElement | SVGElement)[]).forEach(child => { |
|
|
( |
|
|
|
|
|
[...containerChild.current.children] as (HTMLElement | SVGElement)[] |
|
|
|
|
|
).forEach(child => { |
|
|
child.style.marginBottom = "2rem"; |
|
|
child.style.marginBottom = "2rem"; |
|
|
child.style.opacity = "0"; |
|
|
child.style.opacity = "0"; |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
document.body.style.maxHeight = "100vh"; |
|
|
document.body.style.maxHeight = "100vh"; |
|
|
document.body.style.overflow = "hidden"; |
|
|
document.body.style.overflow = "hidden"; |
|
|
e.currentTarget.style.width = "0"; |
|
|
try { |
|
|
|
|
|
const target = e.currentTarget! as HTMLButtonElement; |
|
|
|
|
|
target.style.width = "0"; |
|
|
|
|
|
} catch {} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const current = MenuEntries.findIndex(([, path]) => location === path); |
|
|
|
|
|
const next = get.next(MenuEntries, current)[1]; |
|
|
|
|
|
const prev = get.prev(MenuEntries, current)[1]; |
|
|
|
|
|
const end = current === MenuEntries.length - 1; |
|
|
|
|
|
|
|
|
|
|
|
const handlePrev = (e: MouseKb) => { |
|
|
|
|
|
animateArrow(e); |
|
|
|
|
|
timer(() => prev && navigate(prev), 300); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const handleNext = (e: MouseKb) => { |
|
|
|
|
|
animateArrow(e); |
|
|
timer(() => next && navigate(next), 300); |
|
|
timer(() => next && navigate(next), 300); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
function kbnav(e: KeyboardEvent) { |
|
|
|
|
|
switch (e.key) { |
|
|
|
|
|
case "ArrowLeft": |
|
|
|
|
|
return handlePrev(e); |
|
|
|
|
|
case "ArrowRight": |
|
|
|
|
|
return handleNext(e); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
|
window.addEventListener("keydown", kbnav); |
|
|
|
|
|
|
|
|
|
|
|
// cleanup
|
|
|
|
|
|
return () => window.removeEventListener("keydown", kbnav); |
|
|
|
|
|
}, []); |
|
|
|
|
|
|
|
|
|
|
|
// on first render
|
|
|
|
|
|
useLayoutEffect(handleResize, []); |
|
|
|
|
|
|
|
|
return ( |
|
|
return ( |
|
|
<div |
|
|
<div |
|
|
className={css` |
|
|
className={css` |
|
|
background: var(--background-colour); |
|
|
background: var(--background-colour); |
|
|
padding: 15rem calc(100vw / 8) 8rem calc(100vw / 8); |
|
|
padding-block-start: 15rem; |
|
|
|
|
|
padding-block-end: 8rem; |
|
|
|
|
|
padding-inline: calc(100vw / 8); |
|
|
overflow-x: hidden; |
|
|
overflow-x: hidden; |
|
|
min-height: 100vh; |
|
|
min-height: 100vh; |
|
|
position: relative; |
|
|
position: relative; |
|
|
`}>
|
|
|
`}>
|
|
|
|
|
|
<div |
|
|
|
|
|
className={cx( |
|
|
|
|
|
"fog", |
|
|
|
|
|
css` |
|
|
|
|
|
position: fixed; |
|
|
|
|
|
width: 100vw; |
|
|
|
|
|
left: 0; |
|
|
|
|
|
bottom: 0; |
|
|
|
|
|
height: 10rem; |
|
|
|
|
|
background: rgb(0, 0, 0); |
|
|
|
|
|
background: linear-gradient( |
|
|
|
|
|
180deg, |
|
|
|
|
|
rgba(0, 0, 0, 0) 0%, |
|
|
|
|
|
rgba(0, 0, 0, 1) 100% |
|
|
|
|
|
); |
|
|
|
|
|
z-index: 1000; |
|
|
|
|
|
pointer-events: none; |
|
|
|
|
|
`,
|
|
|
|
|
|
)} |
|
|
|
|
|
/> |
|
|
{!hideNav && ( |
|
|
{!hideNav && ( |
|
|
<button |
|
|
<span |
|
|
ref={logoContainer} |
|
|
ref={logoContainer} |
|
|
className={css` |
|
|
className={css` |
|
|
position: absolute; |
|
|
position: absolute; |
|
@ -134,7 +204,7 @@ const Container: React.FC<{ |
|
|
`}
|
|
|
`}
|
|
|
onMouseOver={() => !mobile && setShowMenu(true)} |
|
|
onMouseOver={() => !mobile && setShowMenu(true)} |
|
|
onMouseOut={() => !mobile && setShowMenu(false)}> |
|
|
onMouseOut={() => !mobile && setShowMenu(false)}> |
|
|
<span |
|
|
<button |
|
|
ref={highlightCircle} |
|
|
ref={highlightCircle} |
|
|
className={cx( |
|
|
className={cx( |
|
|
css` |
|
|
css` |
|
@ -143,7 +213,9 @@ const Container: React.FC<{ |
|
|
height: 5rem; |
|
|
height: 5rem; |
|
|
width: 5rem; |
|
|
width: 5rem; |
|
|
border-radius: 100%; |
|
|
border-radius: 100%; |
|
|
box-shadow: 0px 0px 50px 0px rgba(100, 100, 100, 0.65); |
|
|
border: 0; |
|
|
|
|
|
background: none; |
|
|
|
|
|
box-shadow: 0 0 1rem 0 rgba(100, 100, 100, 0.5); |
|
|
cursor: pointer; |
|
|
cursor: pointer; |
|
|
|
|
|
|
|
|
& > svg { |
|
|
& > svg { |
|
@ -152,11 +224,13 @@ const Container: React.FC<{ |
|
|
position: absolute; |
|
|
position: absolute; |
|
|
inset: 0; |
|
|
inset: 0; |
|
|
z-index: 1; |
|
|
z-index: 1; |
|
|
|
|
|
outline: 0; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
&::before { |
|
|
&::before { |
|
|
content: ""; |
|
|
content: ""; |
|
|
position: absolute; |
|
|
position: absolute; |
|
|
|
|
|
top: 0.5rem; |
|
|
left: -0.1rem; |
|
|
left: -0.1rem; |
|
|
width: 5rem; |
|
|
width: 5rem; |
|
|
height: 5rem; |
|
|
height: 5rem; |
|
@ -174,11 +248,13 @@ const Container: React.FC<{ |
|
|
opacity: 1; |
|
|
opacity: 1; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
&:hover::before { |
|
|
&:hover::before, |
|
|
|
|
|
&:focus::before { |
|
|
width: 5.2rem; |
|
|
width: 5.2rem; |
|
|
height: 5.2rem; |
|
|
height: 5.2rem; |
|
|
top: -0.05rem; |
|
|
top: -0.1rem; |
|
|
left: -0.1rem; |
|
|
left: -0.1rem; |
|
|
|
|
|
outline: none; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
&.highlight::before { |
|
|
&.highlight::before { |
|
@ -193,49 +269,48 @@ const Container: React.FC<{ |
|
|
viewBox="0 0 264 264" |
|
|
viewBox="0 0 264 264" |
|
|
onClick={() => (mobile ? setShowMenu(true) : navigate("/"))} |
|
|
onClick={() => (mobile ? setShowMenu(true) : navigate("/"))} |
|
|
/> |
|
|
/> |
|
|
</span> |
|
|
</button> |
|
|
<Menu show={showMenu} setShowMenu={setShowMenu} /> |
|
|
<Menu show={showMenu} setShowMenu={setShowMenu} /> |
|
|
</button> |
|
|
</span> |
|
|
)} |
|
|
)} |
|
|
{next && ( |
|
|
<button |
|
|
<button |
|
|
onClick={handleNext} |
|
|
onClick={handleNext} |
|
|
ref={nextBtn} |
|
|
ref={nextBtn} |
|
|
title={end ? "Back to start" : "Next page"} |
|
|
|
|
|
className={css` |
|
|
|
|
|
position: fixed; |
|
|
|
|
|
right: 14vw; |
|
|
|
|
|
bottom: 10vh; |
|
|
|
|
|
z-index: 500; |
|
|
|
|
|
background: none; |
|
|
|
|
|
padding: 0; |
|
|
|
|
|
font-weight: 500; |
|
|
|
|
|
cursor: pointer; |
|
|
|
|
|
letter-spacing: 0.2rem; |
|
|
|
|
|
border: none; |
|
|
|
|
|
overflow: hidden; |
|
|
|
|
|
width: 0; |
|
|
|
|
|
transition: all 300ms; |
|
|
|
|
|
overflow: hidden; |
|
|
|
|
|
|
|
|
|
|
|
${end ? "rotate: 180deg;" : ""} |
|
|
|
|
|
|
|
|
|
|
|
&:hover * { |
|
|
|
|
|
fill: var(--primary-colour); |
|
|
|
|
|
} |
|
|
|
|
|
`}>
|
|
|
|
|
|
<Right |
|
|
className={css` |
|
|
className={css` |
|
|
position: fixed; |
|
|
height: "2rem"; |
|
|
right: 14vw; |
|
|
width: "2rem"; |
|
|
bottom: 10vh; |
|
|
`}
|
|
|
z-index: 500; |
|
|
/> |
|
|
background: none; |
|
|
</button> |
|
|
padding: 0; |
|
|
|
|
|
font-weight: 500; |
|
|
|
|
|
cursor: pointer; |
|
|
|
|
|
letter-spacing: 0.2rem; |
|
|
|
|
|
border: none; |
|
|
|
|
|
overflow: hidden; |
|
|
|
|
|
width: 0; |
|
|
|
|
|
transition: all 300ms; |
|
|
|
|
|
overflow: hidden; |
|
|
|
|
|
|
|
|
|
|
|
${arrowReversed ? "rotate: 180deg;" : ""} |
|
|
|
|
|
|
|
|
|
|
|
&:hover * { |
|
|
|
|
|
fill: var(--primary-colour); |
|
|
|
|
|
} |
|
|
|
|
|
`}>
|
|
|
|
|
|
<Right |
|
|
|
|
|
className={css` |
|
|
|
|
|
height: "2rem"; |
|
|
|
|
|
width: "2rem"; |
|
|
|
|
|
`}
|
|
|
|
|
|
/> |
|
|
|
|
|
</button> |
|
|
|
|
|
)} |
|
|
|
|
|
<div |
|
|
<div |
|
|
className={cx( |
|
|
className={cx( |
|
|
css` |
|
|
css` |
|
|
width: 100%; |
|
|
width: 100%; |
|
|
max-width: 60rem; |
|
|
max-width: 62rem; |
|
|
min-height: 100%; |
|
|
min-height: 100%; |
|
|
margin: auto; |
|
|
margin: auto; |
|
|
|
|
|
|
|
|