website/src/routes/+layout.svelte
2024-03-14 15:17:44 +00:00

355 lines
8.8 KiB
Svelte

<script lang="ts">
import "../app.pcss";
import { onMount, onDestroy } from "svelte";
import Logo from "../components/LogoWithTextHorizontal.svelte";
import { CodeBranchOutline, DiscordSolid, BarsSolid, CloseSolid } from "flowbite-svelte-icons";
import { browser } from "$app/environment";
import { writable } from "svelte/store";
import { setContext } from "svelte";
import type { TransitionConfig } from "svelte/transition";
import type { PageData } from "./$types";
import { bounceOut } from "svelte/easing";
import { generateTransition, transition } from "$lib/util/animation";
export let data: PageData;
// TODO: refactor
interface NavItem {
name: string;
href: string;
}
const token = writable("");
function transitionIn(node: HTMLElement, { duration = 360 }: TransitionConfig) {
const UA = navigator.userAgent;
const ff = UA.indexOf("Firefox") > -1;
if (!dropdownCloseFinished) {
node.animate(
[
{
top: "160px",
opacity: "0",
filter: ff ? "" : "blur(20px)",
},
{
top: "0",
opacity: "1",
filter: ff ? "" : "blur(0px)",
},
],
{
easing: transition,
duration,
},
);
return {
duration: 0,
};
}
// FUCK YOUR DEFAULT SYSTEM, SVELTEKIT!!!
node.animate(
[
{
top: "-240px",
opacity: "0",
filter: ff ? "" : "blur(20px)",
},
{
top: "0",
opacity: "1",
filter: ff ? "" : "blur(0px)",
},
],
{
easing: transition,
duration,
},
);
return {
duration,
};
}
function transitionOut(node: HTMLElement, { duration = 360 }: TransitionConfig) {
if (!dropdownCloseFinished)
return {
duration: 0,
};
const UA = navigator.userAgent;
const ff = UA.indexOf("Firefox") > -1;
node.animate(
[
{
top: "0",
opacity: "1",
filter: ff ? "" : "blur(0px)",
},
{
top: "240px",
opacity: "0",
filter: ff ? "" : "blur(80px)",
},
],
{
easing: transition,
duration: duration,
},
);
return {
duration,
};
}
let dropdownOpen = false;
let dropdownCloseFinished = true;
let dropdownOpenFinished = false;
// let dropdownOpen = true;
// let dropdownCloseFinished = false;
// let dropdownOpenFinished = true;
let scrolled = false;
let cookies: {
[key: string]: string;
} = {};
const navItems: NavItem[] = [
{
name: "Blog",
href: "/blog",
},
{
name: "Docs",
href: "/coming-soon",
},
{
name: "FAQ",
href: "/coming-soon",
},
{
name: "Discord",
href: "https://discord.gg/suyu",
},
{
name: "GitLab",
href: "https://gitlab.com/suyu-emu/",
},
];
$: {
if (browser) {
const html = document.querySelector("html")!;
if (!dropdownOpen) {
dropdownCloseFinished = false;
setTimeout(() => {
html.style.overflowY = "auto";
dropdownCloseFinished = true;
}, 360);
} else {
html.style.overflowY = "hidden";
dropdownOpenFinished = false;
setTimeout(() => {
dropdownOpenFinished = true;
}, 360);
}
}
}
function getTransition(i: number) {
return `${((i + 1) / 4) * 75}ms`;
}
if (browser) {
cookies = Object.fromEntries(
document.cookie.split("; ").map((c) => {
const [key, value] = c.split("=");
return [key, value];
}),
);
if (cookies.token) {
$token = cookies.token;
}
}
function toggleDropdown() {
dropdownOpen = !dropdownOpen;
}
setContext("token", token);
onMount(() => {
const handleScroll = () => {
scrolled = window.scrollY > 0;
};
handleScroll(); // we can't guarantee that the page starts at the top
cookies = Object.fromEntries(
document.cookie.split("; ").map((c) => {
const [key, value] = c.split("=");
return [key, value];
}),
);
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
});
</script>
<main
class={`min-h-full w-full ${dropdownOpen || !dropdownCloseFinished ? "overflow-hidden" : ""}`}
>
<header
style="transition: 180ms ease border;"
class={`${
scrolled
? "fixed top-0 z-[9999] w-full border-b-2 border-b-[#ffffff11] bg-[#131215d0]"
: "fixed top-0 z-[9999] w-full border-b-0 border-b-[transparent]"
} pl-[calc(100vw-100%)]`}
>
<nav
style="transition: 180ms ease;"
class={scrolled
? "mx-auto flex h-[72px] w-full max-w-[1300px] flex-row items-center justify-between px-8 backdrop-blur-xl"
: "mx-auto flex h-[120px] w-full max-w-[1300px] flex-row items-center justify-between px-8"}
>
<div class="flex w-full flex-row items-center justify-start gap-8">
<a
href="/"
on:click={() => {
if (dropdownOpen && window.innerWidth < 625) toggleDropdown();
}}
>
<Logo size={28} />
</a>
</div>
<div
class="flex w-full flex-row items-center justify-center gap-2 text-sm font-medium text-[#A6A5A7] max-[625px]:hidden"
>
<a href="/blog" class="px-5 py-3 transition hover:text-white">Blog</a>
<a href="/coming-soon" class="px-5 py-3 transition hover:text-white">Docs</a>
<a href="/coming-soon" class="px-5 py-3 transition hover:text-white">FAQ</a>
</div>
<div class="flex w-full flex-row items-center justify-end text-[#A6A5A7]">
<div class="flex flex-row gap-4 max-[625px]:hidden">
<a
class="p-2 transition hover:text-white"
href="https://gitlab.com/suyu-emu/suyu"
rel="noreferrer noopener"
target="_blank"
>
<CodeBranchOutline />
</a>
<a
class="p-2 transition hover:text-white"
href="https://discord.gg/suyu"
rel="noreferrer noopener"
target="_blank"
>
<DiscordSolid />
</a>
<!-- <a href={$token ? "/account" : "/signup"} class="button-sm"
>{$token ? "Account" : "Sign up"}</a
> -->
</div>
<div class="relative mr-4 hidden flex-row gap-4 max-[625px]:flex">
<button
aria-label={dropdownOpen ? "Close navigation" : "Open navigation"}
aria-expanded={dropdownOpen}
on:click={toggleDropdown}
class="-mr-4 p-4"
>
<div
style="transition: 180ms; transition-property: opacity transform;"
class={`absolute ${dropdownOpen ? "rotate-45 opacity-0" : "opacity-1"}`}
>
<BarsSolid />
</div>
<div
style="transition: 180ms; transition-property: opacity transform;"
class={dropdownOpen
? "opacity-1 rotate-0"
: "rotate-[-45deg] opacity-0"}
>
<CloseSolid />
</div>
</button>
</div>
</div>
</nav>
</header>
<div
style="transition: 180ms ease;"
aria-hidden={!dropdownOpenFinished && !dropdownOpen}
class={`fixed left-0 z-10 h-screen w-full bg-black p-9 pt-[120px] ${dropdownOpen ? "pointer-events-auto visible opacity-100" : "pointer-events-none opacity-0"} ${!dropdownOpen && dropdownCloseFinished ? "invisible" : ""}`}
>
<div class={`flex flex-col gap-8`}>
<!-- <a href="##"><h1 class="w-full text-5xl">Blog</h1></a>
<a href="##"><h1 class="w-full text-5xl">Docs</h1></a>
<a href="##"><h1 class="w-full text-5xl">FAQ</h1></a> -->
{#each navItems as item, i}
<a
style={`transition: ${
dropdownOpen
? generateTransition([
// {
// duration: 600,
// delay: (i + 1) / 4,
// property: "transform",
// timingFunction: transition,
// },
// {
// duration: 500,
// delay: i * 1.25,
// property: "filter",
// timingFunction: transition,
// },
// {
// duration: 400,
// delay: (i + 1) / 4,
// property: "opacity",
// timingFunction: transition,
// },
{
duration: 450,
delay: i * 0.6,
property: ["transform", "opacity", "filter"],
timingFunction: transition,
},
])
: generateTransition([
{
duration: 450,
delay: 0,
property: ["transform", "opacity", "filter"],
timingFunction: transition,
},
])
}`}
class="{dropdownOpen
? 'translate-y-0 opacity-100 filter-none'
: '-translate-y-24 opacity-0 blur-md'} "
href={item.href}
on:click={() => toggleDropdown()}
>
<h1 class="w-full text-5xl">{item.name}</h1>
</a>
{/each}
</div>
</div>
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
{#key data.url}
<div
in:transitionIn={{ duration: 500 }}
out:transitionOut={{ duration: 500 }}
style="transition: 360ms {transition}; transition-property: opacity, transform;"
aria-hidden={dropdownOpenFinished && dropdownOpen}
tabindex={dropdownOpen ? 0 : -1}
class={`absolute left-[50%] z-50 mx-auto flex w-screen max-w-[1300px] translate-x-[-50%] flex-col px-8 pb-12 pt-[120px] ${dropdownOpen ? "pointer-events-none translate-y-[25vh] opacity-0" : ""} ${dropdownOpenFinished && dropdownOpen ? "invisible" : ""}`}
>
<slot />
</div>
{/key}
</main>