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 {
|
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">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import "../app.css";
|
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>
|
</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">
|
<script lang="ts">
|
||||||
import Uploader from "$lib/components/visual/Uploader.svelte";
|
import Uploader from "$lib/components/visual/Uploader.svelte";
|
||||||
import { converters } from "$lib/converters";
|
import { converters } from "$lib/converters";
|
||||||
|
import { files } from "$lib/store/index.svelte";
|
||||||
let conversionTypes = $state<string[]>([]);
|
|
||||||
let downloadFns = $state<(() => void)[]>([]);
|
|
||||||
let files = $state<File[]>();
|
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
$inspect(files);
|
$inspect(files);
|
||||||
});
|
});
|
||||||
|
|
||||||
const convertAllFiles = async () => {
|
const convertAllFiles = async () => {
|
||||||
const promises = files?.map(async (file, i) => {
|
const promises = files.files?.map(async (file, i) => {
|
||||||
let conversionType = conversionTypes[i];
|
let conversionType = files.conversionTypes[i];
|
||||||
const converter = converters[0];
|
const converter = converters[0];
|
||||||
const convertedFile = await converter.convert(
|
const convertedFile = await converter.convert(
|
||||||
{
|
{
|
||||||
|
|
@ -21,7 +18,7 @@
|
||||||
},
|
},
|
||||||
conversionType,
|
conversionType,
|
||||||
);
|
);
|
||||||
downloadFns[i] = () => {
|
files.downloadFns[i] = () => {
|
||||||
const url = URL.createObjectURL(
|
const url = URL.createObjectURL(
|
||||||
new Blob([convertedFile.buffer]),
|
new Blob([convertedFile.buffer]),
|
||||||
);
|
);
|
||||||
|
|
@ -39,9 +36,7 @@
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="w-full h-full flex items-center justify-center">
|
<Uploader bind:files={files.files} />
|
||||||
<Uploader bind:files />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* for this page specifically */
|
/* 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}"],
|
content: ["./src/**/*.{html,js,svelte,ts}"],
|
||||||
|
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
background: "var(--bg)",
|
background: "var(--bg)",
|
||||||
foreground: "var(--fg)",
|
foreground: "var(--fg)",
|
||||||
"foreground-muted": "var(--fg-muted)",
|
"foreground-muted": "var(--fg-muted)",
|
||||||
"foreground-muted-alt": "var(--fg-muted-alt)",
|
"foreground-muted-alt": "var(--fg-muted-alt)",
|
||||||
},
|
},
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
display: "var(--font-display)",
|
display: "var(--font-display)",
|
||||||
body: "var(--font-body)",
|
body: "var(--font-body)",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue