mirror of https://github.com/VERT-sh/VERT.git
feat: page transitions
This commit is contained in:
parent
08353cf8ff
commit
38709395d4
|
@ -18,5 +18,5 @@
|
|||
}
|
||||
|
||||
body {
|
||||
@apply text-foreground bg-background font-body;
|
||||
@apply text-foreground bg-background font-body overflow-x-hidden;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
import type { EasingFunction, TransitionConfig } from "svelte/transition";
|
||||
|
||||
export const transition =
|
||||
"linear(0,0.006,0.025 2.8%,0.101 6.1%,0.539 18.9%,0.721 25.3%,0.849 31.5%,0.937 38.1%,0.968 41.8%,0.991 45.7%,1.006 50.1%,1.015 55%,1.017 63.9%,1.001)";
|
||||
|
||||
export const duration = 500;
|
||||
|
||||
const remap = (
|
||||
value: number,
|
||||
low1: number,
|
||||
high1: number,
|
||||
low2: number,
|
||||
high2: number,
|
||||
) => low2 + ((high2 - low2) * (value - low1)) / (high1 - low1);
|
||||
|
||||
const choose = (
|
||||
direction: "in" | "out" | "both",
|
||||
defaultValue: number,
|
||||
inValue?: number,
|
||||
outValue?: number,
|
||||
) =>
|
||||
direction !== "out"
|
||||
? typeof inValue === "number"
|
||||
? inValue
|
||||
: defaultValue
|
||||
: typeof outValue === "number"
|
||||
? outValue
|
||||
: defaultValue;
|
||||
|
||||
export const blur = (
|
||||
_: HTMLElement,
|
||||
config:
|
||||
| Partial<{
|
||||
blurMultiplier: number;
|
||||
duration: number;
|
||||
easing: EasingFunction;
|
||||
scale: {
|
||||
start: number;
|
||||
end: number;
|
||||
};
|
||||
x: {
|
||||
start: number;
|
||||
end: number;
|
||||
};
|
||||
y: {
|
||||
start: number;
|
||||
end: number;
|
||||
};
|
||||
delay: number;
|
||||
opacity: boolean;
|
||||
}>
|
||||
| undefined,
|
||||
dir: {
|
||||
direction: "in" | "out" | "both";
|
||||
},
|
||||
): TransitionConfig => {
|
||||
const prefersReducedMotion = window.matchMedia(
|
||||
"(prefers-reduced-motion: reduce)",
|
||||
).matches;
|
||||
if (typeof config?.opacity === "undefined" && config) config.opacity = true;
|
||||
return {
|
||||
delay: config?.delay || 0,
|
||||
duration: prefersReducedMotion ? 0 : config?.duration || 300,
|
||||
css: (t) =>
|
||||
prefersReducedMotion
|
||||
? ""
|
||||
: `filter: blur(${(1 - t) * (config?.blurMultiplier || 1)}px); opacity: ${config?.opacity ? t : 1}; transform: translate(${remap(
|
||||
t,
|
||||
0,
|
||||
1,
|
||||
choose(
|
||||
dir.direction,
|
||||
0,
|
||||
config?.x?.start,
|
||||
config?.x?.end,
|
||||
),
|
||||
choose(
|
||||
dir.direction,
|
||||
0,
|
||||
config?.x?.end,
|
||||
config?.x?.start,
|
||||
),
|
||||
)}px, ${remap(
|
||||
t,
|
||||
0,
|
||||
1,
|
||||
choose(
|
||||
dir.direction,
|
||||
0,
|
||||
config?.y?.start,
|
||||
config?.y?.end,
|
||||
),
|
||||
choose(
|
||||
dir.direction,
|
||||
0,
|
||||
config?.y?.end,
|
||||
config?.y?.start,
|
||||
),
|
||||
)}px) scale(${remap(
|
||||
t,
|
||||
0,
|
||||
1,
|
||||
choose(
|
||||
dir.direction,
|
||||
0.9,
|
||||
config?.scale?.start,
|
||||
config?.scale?.end,
|
||||
),
|
||||
choose(
|
||||
dir.direction,
|
||||
1,
|
||||
config?.scale?.end,
|
||||
config?.scale?.start,
|
||||
),
|
||||
)});`,
|
||||
easing: config?.easing,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,11 @@
|
|||
class Files {
|
||||
public files = $state<File[]>([]);
|
||||
public conversionTypes = $state<string[]>([]);
|
||||
public downloadFns = $state<(() => void)[]>([]);
|
||||
public beenToConverterPage = $state(false);
|
||||
public shouldShowAlert = $derived(
|
||||
!this.beenToConverterPage && this.files.length > 0,
|
||||
);
|
||||
}
|
||||
|
||||
export const files = new Files();
|
|
@ -1,7 +1,105 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import "../app.css";
|
||||
let { children } = $props();
|
||||
import { goto } from "$app/navigation";
|
||||
import clsx from "clsx";
|
||||
import { blur, duration, transition } from "$lib/animation";
|
||||
import { quintOut } from "svelte/easing";
|
||||
import { files } from "$lib/store/index.svelte";
|
||||
let { children, data } = $props();
|
||||
|
||||
let navWidth = $state(1);
|
||||
let shouldGoBack = $state(false);
|
||||
|
||||
const links = $derived<{
|
||||
[key: string]: {
|
||||
href: string;
|
||||
alert?: boolean;
|
||||
};
|
||||
}>({
|
||||
Upload: { href: "/" },
|
||||
[files.files.length > 0
|
||||
? `Convert ${files.files.length} file${files.files.length > 1 ? "s" : ""}`
|
||||
: `Convert`]: { href: "/convert", alert: files.shouldShowAlert },
|
||||
});
|
||||
|
||||
const linkCount = $derived(Object.keys(links).length);
|
||||
const linkIndex = $derived(
|
||||
Object.keys(links).findIndex(
|
||||
(link) => links[link].href === data.pathname,
|
||||
),
|
||||
);
|
||||
</script>
|
||||
|
||||
{@render children()}
|
||||
<div class="w-full h-full flex items-center pt-72 flex-col gap-4">
|
||||
<div
|
||||
bind:clientWidth={navWidth}
|
||||
class="bg-background relative w-full h-16 max-w-screen-lg border-2 p-1 border-solid border-foreground-muted-alt rounded-2xl flex"
|
||||
>
|
||||
<div
|
||||
class="absolute pointer-events-none top-1 bg-foreground h-[calc(100%-8px)] rounded-xl"
|
||||
style="width: {navWidth / linkCount - 8}px; left: {(navWidth /
|
||||
linkCount) *
|
||||
linkIndex +
|
||||
4}px; transition: {duration - 200}ms ease left;"
|
||||
></div>
|
||||
{#each Object.entries(links) as [name, link] (link)}
|
||||
<button
|
||||
class="w-1/2 h-full flex items-center justify-center rounded-xl relative"
|
||||
onclick={() => {
|
||||
const keys = Object.keys(links);
|
||||
const currentIndex = keys.findIndex(
|
||||
(key) => links[key].href === data.pathname,
|
||||
);
|
||||
const nextIndex = keys.findIndex(
|
||||
(key) => links[key] === link,
|
||||
);
|
||||
shouldGoBack = nextIndex < currentIndex;
|
||||
console.log({ shouldGoBack });
|
||||
goto(link.href);
|
||||
}}
|
||||
>
|
||||
<span class="mix-blend-difference invert">
|
||||
{name}
|
||||
</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="w-full grid grid-cols-1 grid-rows-1 relative">
|
||||
{#key data.pathname}
|
||||
<div class="w-full">
|
||||
<div
|
||||
class="absolute top-0 left-0 w-full h-full flex justify-center"
|
||||
in:blur={{
|
||||
duration,
|
||||
easing: quintOut,
|
||||
blurMultiplier: 12,
|
||||
x: {
|
||||
start: !shouldGoBack ? 250 : -250,
|
||||
end: 0,
|
||||
},
|
||||
scale: {
|
||||
start: 0.75,
|
||||
end: 1,
|
||||
},
|
||||
}}
|
||||
out:blur={{
|
||||
duration,
|
||||
easing: quintOut,
|
||||
blurMultiplier: 12,
|
||||
x: {
|
||||
start: 0,
|
||||
end: !shouldGoBack ? -250 : 250,
|
||||
},
|
||||
scale: {
|
||||
start: 1,
|
||||
end: 0.75,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{@render children()}
|
||||
</div>
|
||||
</div>
|
||||
{/key}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
export const load = ({ url }) => {
|
||||
const { pathname } = url;
|
||||
return {
|
||||
pathname,
|
||||
};
|
||||
};
|
|
@ -1,18 +1,15 @@
|
|||
<script lang="ts">
|
||||
import Uploader from "$lib/components/visual/Uploader.svelte";
|
||||
import { converters } from "$lib/converters";
|
||||
|
||||
let conversionTypes = $state<string[]>([]);
|
||||
let downloadFns = $state<(() => void)[]>([]);
|
||||
let files = $state<File[]>();
|
||||
import { files } from "$lib/store/index.svelte";
|
||||
|
||||
$effect(() => {
|
||||
$inspect(files);
|
||||
});
|
||||
|
||||
const convertAllFiles = async () => {
|
||||
const promises = files?.map(async (file, i) => {
|
||||
let conversionType = conversionTypes[i];
|
||||
const promises = files.files?.map(async (file, i) => {
|
||||
let conversionType = files.conversionTypes[i];
|
||||
const converter = converters[0];
|
||||
const convertedFile = await converter.convert(
|
||||
{
|
||||
|
@ -21,7 +18,7 @@
|
|||
},
|
||||
conversionType,
|
||||
);
|
||||
downloadFns[i] = () => {
|
||||
files.downloadFns[i] = () => {
|
||||
const url = URL.createObjectURL(
|
||||
new Blob([convertedFile.buffer]),
|
||||
);
|
||||
|
@ -39,9 +36,7 @@
|
|||
};
|
||||
</script>
|
||||
|
||||
<div class="w-full h-full flex items-center justify-center">
|
||||
<Uploader bind:files />
|
||||
</div>
|
||||
<Uploader bind:files={files.files} />
|
||||
|
||||
<style>
|
||||
/* for this page specifically */
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
<script>
|
||||
import { converters } from "$lib/converters";
|
||||
import { files } from "$lib/store/index.svelte";
|
||||
|
||||
if (files.files.length > 0) files.beenToConverterPage = true;
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 w-full items-center">
|
||||
{#if files.files.length === 0}
|
||||
<p class="text-foreground-muted">
|
||||
No files uploaded. Head to the Upload tab to begin!
|
||||
</p>
|
||||
{/if}
|
||||
{#each files.files as file, i}
|
||||
<div
|
||||
class="flex items-center w-full max-w-screen-lg border-2 border-solid border-foreground-muted-alt rounded-xl px-4 py-2"
|
||||
>
|
||||
<div class="flex items-center flex-grow">
|
||||
{file.name}
|
||||
</div>
|
||||
<div class="flex gap-4 flex-shrink-0">
|
||||
<select
|
||||
class="border-2 border-solid border-foreground-muted-alt rounded-xl px-4 py-2 focus:!outline-none"
|
||||
bind:value={files.conversionTypes[i]}
|
||||
>
|
||||
{#each converters[0].supportedFormats as conversionType}
|
||||
<option value={conversionType}>{conversionType}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
|
@ -4,16 +4,17 @@ export default {
|
|||
content: ["./src/**/*.{html,js,svelte,ts}"],
|
||||
|
||||
theme: {
|
||||
extend: {},
|
||||
colors: {
|
||||
background: "var(--bg)",
|
||||
foreground: "var(--fg)",
|
||||
"foreground-muted": "var(--fg-muted)",
|
||||
"foreground-muted-alt": "var(--fg-muted-alt)",
|
||||
},
|
||||
fontFamily: {
|
||||
display: "var(--font-display)",
|
||||
body: "var(--font-body)",
|
||||
extend: {
|
||||
colors: {
|
||||
background: "var(--bg)",
|
||||
foreground: "var(--fg)",
|
||||
"foreground-muted": "var(--fg-muted)",
|
||||
"foreground-muted-alt": "var(--fg-muted-alt)",
|
||||
},
|
||||
fontFamily: {
|
||||
display: "var(--font-display)",
|
||||
body: "var(--font-body)",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
|
|
Loading…
Reference in New Issue