vert/src/routes/+layout.svelte

308 lines
8.0 KiB
Svelte

<script lang="ts">
import { page } from "$app/state";
import { beforeNavigate, goto } from "$app/navigation";
import { PUB_HOSTNAME, PUB_PLAUSIBLE_URL } from "$env/static/public";
import { duration, fly } from "$lib/animation";
import VertVBig from "$lib/assets/vert-bg.svg?component";
import featuredImage from "$lib/assets/VERT_Feature.webp";
import Navbar from "$lib/components/functional/Navbar.svelte";
import Footer from "$lib/components/visual/Footer.svelte";
import Logo from "$lib/components/visual/svg/Logo.svelte";
import { fade } from "$lib/animation";
import {
files,
gradientColor,
isMobile,
motion,
showGradient,
theme,
} from "$lib/store/index.svelte";
import {
InfoIcon,
RefreshCw,
SettingsIcon,
UploadIcon,
} from "lucide-svelte";
import { onMount } from "svelte";
import { quintOut } from "svelte/easing";
import "../app.scss";
import { writable } from "svelte/store";
let { children } = $props();
let shouldGoBack = writable(false);
let navbar = $state<HTMLDivElement>();
let hover = $state(false);
const items = $derived<
{
name: string;
url: string;
activeMatch: (pathname: string) => boolean;
icon: any;
badge?: number;
}[]
>([
{
name: "Upload",
url: "/",
activeMatch: (pathname) => pathname === "/",
icon: UploadIcon,
},
{
name: "Convert",
url: "/convert",
activeMatch: (pathname) => pathname === "/convert",
icon: RefreshCw,
badge: files.files.length,
},
{
name: "Settings",
url: "/settings",
activeMatch: (pathname) => pathname.startsWith("/settings"),
icon: SettingsIcon,
},
{
name: "About",
url: "/about",
activeMatch: (pathname) => pathname.startsWith("/about"),
icon: InfoIcon,
},
]);
const maybeNavToHome = (e: DragEvent) => {
if (e.dataTransfer?.types.includes("Files")) {
e.preventDefault();
goto("/");
}
};
onMount(() => {
const mouseEnter = () => {
hover = true;
};
const mouseLeave = () => {
hover = false;
};
navbar?.addEventListener("mouseenter", mouseEnter);
navbar?.addEventListener("mouseleave", mouseLeave);
isMobile.set(window.innerWidth <= 768);
window.addEventListener("resize", () => {
isMobile.set(window.innerWidth <= 768);
});
motion.set(localStorage.getItem("motion") !== "false"); // defaults to true if not set
theme.set(
(localStorage.getItem("theme") as "light" | "dark") || "light",
);
});
let goingLeft = $state(false);
beforeNavigate((e) => {
const oldIndex = items.findIndex((i) =>
i.activeMatch(e.from?.url.pathname || ""),
);
const newIndex = items.findIndex((i) =>
i.activeMatch(e.to?.url.pathname || ""),
);
if (newIndex < oldIndex) {
goingLeft = true;
} else {
goingLeft = false;
}
});
</script>
<svelte:head>
<title>VERT.sh</title>
<meta name="theme-color" content="#F2ABEE" />
<meta
name="title"
content="VERT.sh — Free, fast, and awesome file convert"
/>
<meta
name="description"
content="With VERT you can convert image and audio files to and from PNG, JPG, WEBP, MP3, WAV, FLAC, and more. No ads, no tracking, open source, and all processing is done on your device."
/>
<meta property="og:type" content="website" />
<meta
property="og:title"
content="VERT.sh — Free, fast, and awesome file convert"
/>
<meta
property="og:description"
content="With VERT you can convert image and audio files to and from PNG, JPG, WEBP, MP3, WAV, FLAC, and more. No ads, no tracking, open source, and all processing is done on your device."
/>
<meta property="og:image" content={featuredImage} />
<meta property="twitter:card" content="summary_large_image" />
<meta
property="twitter:title"
content="VERT.sh — Free, fast, and awesome file convert"
/>
<meta
property="twitter:description"
content="With VERT you can convert image and audio files to and from PNG, JPG, WEBP, MP3, WAV, FLAC, and more. No ads, no tracking, open source, and all processing is done on your device."
/>
<meta property="twitter:image" content={featuredImage} />
{#if PUB_PLAUSIBLE_URL}<script
defer
data-domain={PUB_HOSTNAME || "vert.sh"}
src="{PUB_PLAUSIBLE_URL}/js/script.pageview-props.tagged-events.js"
></script>{/if}
<script src="/coi-serviceworker.min.js"></script>
</svelte:head>
<div class="flex flex-col min-h-screen h-full">
<!-- FIXME: if user resizes between desktop/mobile, highlight of page disappears (only shows on original size) -->
<div>
<!-- Mobile logo -->
<div class="flex md:hidden justify-center items-center p-8">
<a
class="flex items-center justify-center bg-panel p-2 rounded-[20px] shadow-panel"
href="/"
>
<div
class="h-14 bg-accent rounded-[14px] flex items-center justify-center"
>
<div class="w-28 h-5">
<Logo />
</div>
</div>
</a>
</div>
<!-- Desktop navbar -->
<div class="hidden md:flex p-8 w-screen justify-center z-50">
<Navbar {items} />
</div>
</div>
<div class="grid grid-rows-1 grid-cols-1 h-full flex-grow">
{#key page.url.pathname}
<div
class="row-start-1 col-start-1"
in:fly={{
x: goingLeft ? -window.innerWidth : window.innerWidth,
duration,
easing: quintOut,
delay: 25,
}}
out:fly={{
x: goingLeft ? window.innerWidth : -window.innerWidth,
duration,
easing: quintOut,
}}
>
<div
class="flex flex-col h-full pb-36 md:pb-0"
in:fade={{
duration,
easing: quintOut,
delay: $isMobile ? 0 : 100,
}}
out:fade={{
duration,
easing: quintOut,
delay: $isMobile ? 0 : 200,
}}
>
{@render children()}
</div>
</div>
{/key}
</div>
<div>
<div
class="hidden md:block w-full h-14 border-t border-separator relative mt-12"
>
<Footer
class="w-full h-full"
items={{
//"Privacy policy": "#",
"Source code": "https://github.com/not-nullptr/VERT",
"Discord server": "https://discord.gg/kqevGxYPak",
}}
/>
</div>
<!-- Mobile navbar -->
<div
class="fixed md:hidden bottom-0 left-0 w-screen p-8 justify-center z-50"
>
<div class="flex flex-col justify-center items-center">
<Navbar {items} />
</div>
</div>
</div>
</div>
<!-- Gradients placed here to prevent it overlapping in transitions -->
{#if page.url.pathname === "/"}
<div
class="fixed -z-30 top-0 left-0 w-screen h-screen flex items-center justify-center overflow-hidden"
>
<VertVBig
class="fill-[--fg] opacity-10 dynadark:opacity-5 scale-[200%] md:scale-[80%]"
/>
</div>
<div
id="gradient-bg"
class="fixed top-0 left-0 w-screen h-screen -z-40 pointer-events-none"
style="background: var(--bg-gradient);"
></div>
{:else if page.url.pathname === "/convert" && $showGradient}
<div
id="gradient-bg"
class="fixed top-0 left-0 w-screen h-screen -z-40 pointer-events-none"
style="background: var(--bg-gradient-{$gradientColor || 'pink'});"
></div>
{:else if page.url.pathname === "/convert" && files.files.length === 1 && files.files[0].blobUrl}
<div
class="fixed w-screen h-screen opacity-75 overflow-hidden top-0 left-0 -z-50 pointer-events-none grid grid-cols-1 grid-rows-1 scale-105"
>
<div
class="w-full relative"
transition:fade={{
duration,
easing: quintOut,
}}
>
<img
class="object-cover w-full h-full blur-md"
src={files.files[0].blobUrl}
alt={files.files[0].name}
/>
<div
class="absolute top-0 left-0 w-full h-full"
style="background: var(--bg-gradient-image);"
></div>
<!-- <div class="absolute bottom-0 left-0 w-full h-full">
<ProgressiveBlur
direction="bottom"
endIntensity={256}
iterations={8}
fadeTo="var(--bg)"
/>
</div> -->
</div>
</div>
{:else if page.url.pathname === "/settings"}
<div
id="gradient-bg"
class="fixed top-0 left-0 w-screen h-screen -z-40 pointer-events-none"
style="background: var(--bg-gradient-blue);"
></div>
{:else if page.url.pathname === "/about"}
<div
id="gradient-bg"
class="fixed top-0 left-0 w-screen h-screen -z-40 pointer-events-none"
style="background: var(--bg-gradient-pink);"
></div>
{/if}