diff --git a/src/lib/components/layout/Gradients.svelte b/src/lib/components/layout/Gradients.svelte
index 7a7d85b..f05271e 100644
--- a/src/lib/components/layout/Gradients.svelte
+++ b/src/lib/components/layout/Gradients.svelte
@@ -46,8 +46,8 @@
const color = $derived(
Object.values(colors).find((p) => p.matcher(page.url.pathname)) || {
matcher: () => false,
- color: "transparent",
- at: 0,
+ color: "var(--bg-gradient-from)",
+ at: 100,
},
);
@@ -64,9 +64,14 @@
const maskImage = $derived(
`linear-gradient(to top, transparent ${100 - at.current}%, black 100%)`,
);
+
+ const showLogo = $derived(
+ page.url.pathname === "/" ||
+ /^[\w-]+-[\w-]+$/.test(page.url.pathname.replace(/^\/|\/$/g, "")),
+ );
-{#if page.url.pathname === "/"}
+{#if showLogo}
pathname === "/",
+ activeMatch: (pathname) =>
+ pathname === "/" ||
+ /^[\w-]+-[\w-]+$/.test(pathname.replace(/^\/|\/$/g, "")),
icon: UploadIcon,
},
{
diff --git a/src/routes/[formats]/+page.server.ts b/src/routes/[formats]/+page.server.ts
new file mode 100644
index 0000000..5a351e4
--- /dev/null
+++ b/src/routes/[formats]/+page.server.ts
@@ -0,0 +1,96 @@
+import { converters } from "$lib/converters";
+import type { EntryGenerator } from "./$types";
+
+// generate conversion pairs at build time (e.g. mkv-mp4) for SEO
+export const entries: EntryGenerator = () => {
+ const seenPairs = new Set();
+
+ const addPair = (
+ fromName: string,
+ fromSupported: boolean,
+ toName: string,
+ toSupported: boolean,
+ ) => {
+ if (!fromSupported || !toSupported || fromName === toName) return;
+
+ const from = fromName.replace(".", "").toLowerCase();
+ const to = toName.replace(".", "").toLowerCase();
+ const slug = `${from}-${to}`;
+
+ if (!seenPairs.has(slug)) seenPairs.add(slug);
+ };
+
+ // check all conversions (same converter and cross-converter)
+ for (const fromConverter of converters) {
+ for (const toConverter of converters) {
+ const sameConverter = fromConverter.name === toConverter.name;
+
+ for (const fromFormat of fromConverter.supportedFormats) {
+ for (const toFormat of toConverter.supportedFormats) {
+ // skip if same converter and same format, or if different converter but formats are the same
+ if (sameConverter && fromFormat.name === toFormat.name)
+ continue;
+ if (!sameConverter && fromFormat.name === toFormat.name)
+ continue;
+
+ addPair(
+ fromFormat.name,
+ fromFormat.fromSupported,
+ toFormat.name,
+ toFormat.toSupported,
+ );
+ }
+ }
+ }
+ }
+
+ const result = Array.from(seenPairs).map((slug) => ({ formats: slug }));
+ console.log(`[SEO] generating ${result.length} format conversion routes`);
+ return result;
+};
+
+export const prerender = true;
+
+export const load = ({ params }) => {
+ const { formats } = params;
+
+ // parse the slug (e.g. { from: "mkv", to: "mp4" })
+ const parts = formats.split("-");
+
+ if (parts.length !== 2) {
+ return {
+ fromFormat: null,
+ toFormat: null,
+ error: "Invalid format slug",
+ };
+ }
+
+ const [from, to] = parts;
+ const fromFormat = `.${from}`;
+ const toFormat = `.${to}`;
+
+ let fromValid = false;
+ let toValid = false;
+
+ for (const converter of converters) {
+ for (const format of converter.supportedFormats) {
+ if (format.name === fromFormat && format.fromSupported)
+ fromValid = true;
+ if (format.name === toFormat && format.toSupported) toValid = true;
+ }
+ }
+
+ if (!fromValid || !toValid) {
+ return {
+ fromFormat: null,
+ toFormat: null,
+ error: "Invalid conversion pair",
+ };
+ }
+
+ return {
+ fromFormat,
+ toFormat,
+ error: null,
+ };
+};
diff --git a/src/routes/[formats]/+page.svelte b/src/routes/[formats]/+page.svelte
new file mode 100644
index 0000000..ae9f526
--- /dev/null
+++ b/src/routes/[formats]/+page.svelte
@@ -0,0 +1,35 @@
+
+
+
+ {#if data.fromFormat && data.toFormat}
+ Convert {data.fromFormat.replace(".", "").toUpperCase()} to {data.toFormat
+ .replace(".", "")
+ .toUpperCase()} - VERT
+
+ {/if}
+
+
+
+