diff --git a/viewer/.gitignore b/.gitignore
similarity index 100%
rename from viewer/.gitignore
rename to .gitignore
diff --git a/viewer/.npmrc b/.npmrc
similarity index 100%
rename from viewer/.npmrc
rename to .npmrc
diff --git a/Dockerfile b/Dockerfile
index b6706f9bc07cd11410e785c8727a8586203700b4..6a33d344d2fa17fb961562bc8e1f53265f65871d 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,11 +1,11 @@
 FROM node:alpine
 
 WORKDIR /app
-COPY viewer/package.json package.json
+COPY package.json package.json
 RUN npm install
 
-COPY viewer/ /app
+COPY / /app
 RUN npm run build
 
 EXPOSE 3000
-CMD ["npm", "start"]
\ No newline at end of file
+CMD ["npm", "start"]
diff --git a/viewer/package-lock.json b/package-lock.json
similarity index 99%
rename from viewer/package-lock.json
rename to package-lock.json
index 4a77b36ad89f7d59c979775593c7135341b4f817..8d1fc44ae20380126a21f14f30b88fce49432b75 100644
--- a/viewer/package-lock.json
+++ b/package-lock.json
@@ -16,6 +16,7 @@
                 "@sveltejs/adapter-auto": "^2.0.0",
                 "@sveltejs/adapter-node": "^1.3.1",
                 "@sveltejs/kit": "^1.20.4",
+                "carbon-icons-svelte": "^12.8.0",
                 "svelte": "^4.0.5",
                 "svelte-check": "^3.4.3",
                 "tslib": "^2.4.1",
@@ -829,6 +830,12 @@
                 "node": ">=6"
             }
         },
+        "node_modules/carbon-icons-svelte": {
+            "version": "12.8.0",
+            "resolved": "https://registry.npmjs.org/carbon-icons-svelte/-/carbon-icons-svelte-12.8.0.tgz",
+            "integrity": "sha512-ops12PG2NucXc4fWaDP9ZmsN4oA4ofjwQvd3yJAbrRzLfrUw9uMyC5u7FVSujS6gLkPRIbdd+LekfZt+1kY/zg==",
+            "dev": true
+        },
         "node_modules/chokidar": {
             "version": "3.5.3",
             "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
diff --git a/viewer/package.json b/package.json
similarity index 95%
rename from viewer/package.json
rename to package.json
index 3ed0f2c582f31a110a37bc8bdc42aebc63f683ef..584ab0cc0d48b440bdf56d412cc6d9621384bd10 100644
--- a/viewer/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
         "@sveltejs/adapter-auto": "^2.0.0",
         "@sveltejs/adapter-node": "^1.3.1",
         "@sveltejs/kit": "^1.20.4",
+        "carbon-icons-svelte": "^12.8.0",
         "svelte": "^4.0.5",
         "svelte-check": "^3.4.3",
         "tslib": "^2.4.1",
diff --git a/viewer/src/app.d.ts b/src/app.d.ts
similarity index 100%
rename from viewer/src/app.d.ts
rename to src/app.d.ts
diff --git a/viewer/src/app.html b/src/app.html
similarity index 100%
rename from viewer/src/app.html
rename to src/app.html
diff --git a/viewer/src/lib/index.ts b/src/lib/index.ts
similarity index 100%
rename from viewer/src/lib/index.ts
rename to src/lib/index.ts
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..0ef3fe92b2046c135e3ce30d599f84ea239b5915
--- /dev/null
+++ b/src/routes/+page.svelte
@@ -0,0 +1,62 @@
+<script lang="ts">
+    import Leaderboard from "./Leaderboard.svelte";
+    import ModelDetails from "./ModelDetails.svelte";
+    import Viewer from "./Viewer.svelte";
+
+    interface Scene {
+        name: string;
+        url: string;
+        thumbnail: string;
+    }
+
+    let currentView: "Leaderboard" | "Vote" | "ModelDetails" | "Viewer" = "Leaderboard";
+    let selectedEntry: { name: string; path: string } | null = null;
+    let selectedScene: Scene | null = null;
+
+    function goHome() {
+        window.location.href = "/";
+    }
+
+    function showModelDetails(entry: { name: string; path: string }) {
+        selectedEntry = entry;
+        currentView = "ModelDetails";
+    }
+
+    function showScene(scene: Scene) {
+        selectedScene = scene;
+        currentView = "Viewer";
+    }
+</script>
+
+<div class="container">
+    <div on:pointerdown={goHome} class="banner">
+        <h1>IGF</h1>
+        <p>Generative 3D Leaderboard</p>
+    </div>
+
+    {#if currentView === "Leaderboard" || currentView === "Vote"}
+        <div class="tabs">
+            <button on:click={() => (currentView = "Vote")} class={currentView === "Vote" ? "active" : ""}>Vote</button>
+            <button on:click={() => (currentView = "Leaderboard")} class={currentView === "Leaderboard" ? "active" : ""}
+                >Leaderboard</button
+            >
+        </div>
+    {/if}
+
+    {#if currentView === "Leaderboard"}
+        <Leaderboard onEntryClick={showModelDetails} />
+    {:else if currentView === "Vote"}
+        <div class="loading-container">
+            <div class="loading-text">Coming Soon</div>
+        </div>
+    {:else if currentView === "ModelDetails" && selectedEntry}
+        <ModelDetails
+            modelName={selectedEntry.name}
+            modelPath={selectedEntry.path}
+            onBack={() => (currentView = "Leaderboard")}
+            onSceneClick={showScene}
+        />
+    {:else if currentView === "Viewer" && selectedScene && selectedEntry}
+        <Viewer modelName={selectedEntry.name} scene={selectedScene} onBack={() => (currentView = "ModelDetails")} />
+    {/if}
+</div>
diff --git a/src/routes/Leaderboard.svelte b/src/routes/Leaderboard.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..5ffe015880453f515165aa762c51db26193a856c
--- /dev/null
+++ b/src/routes/Leaderboard.svelte
@@ -0,0 +1,58 @@
+<script lang="ts">
+    import { onMount } from "svelte";
+    import { ProgressBarRound } from "carbon-icons-svelte";
+
+    interface Entry {
+        name: string;
+        path: string;
+        thumbnail: string;
+    }
+
+    export let onEntryClick: (entry: Entry) => void;
+
+    let leaderboard: Entry[] = [];
+
+    const fetchLeaderboardData = async () => {
+        const baseUrl = "https://huggingface.co";
+        const repoId = "dylanebert/3d-arena";
+        const url = `${baseUrl}/api/datasets/${repoId}`;
+        const response = await fetch(url);
+        const data = await response.json();
+
+        const entries: Entry[] = [];
+
+        for (const item of data.siblings) {
+            const filename = item.rfilename;
+            if (!filename.endsWith(".json")) continue;
+
+            const directory = filename.split("/").slice(0, -1).join("/");
+            const name = directory.split("/").pop();
+            const thumbnail = `${baseUrl}/datasets/${repoId}/resolve/main/${directory}/thumbnail.png`;
+
+            entries.push({ name, path: filename, thumbnail });
+        }
+
+        leaderboard = entries;
+    };
+
+    onMount(async () => {
+        await fetchLeaderboardData();
+    });
+</script>
+
+{#if leaderboard.length > 0}
+    <div class="grid">
+        {#each leaderboard as entry, index}
+            <button class="grid-item" on:click={() => onEntryClick(entry)}>
+                <img src={entry.thumbnail} alt={entry.name} class="thumbnail" />
+                <div class="ranking">{index + 1}</div>
+                <div class="title">{entry.name}</div>
+            </button>
+        {/each}
+    </div>
+{:else}
+    <div class="loading-container">
+        <ProgressBarRound class="loading-icon" />
+        <div class="loading-text">Loading...</div>
+    </div>
+{/if}
diff --git a/src/routes/ModelDetails.svelte b/src/routes/ModelDetails.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..f18f9f4e3cfb58accf43302c2fddc9cbb60ee93b
--- /dev/null
+++ b/src/routes/ModelDetails.svelte
@@ -0,0 +1,73 @@
+<script lang="ts">
+    import { onMount } from "svelte";
+    import { ProgressBarRound, ArrowLeft } from "carbon-icons-svelte";
+
+    interface Scene {
+        name: string;
+        url: string;
+        thumbnail: string;
+    }
+
+    export let modelName: string;
+    export let modelPath: string;
+    export let onBack: () => void;
+    export let onSceneClick: (scene: Scene) => void;
+
+    let scenes: Scene[] = [];
+
+    async function fetchScenes() {
+        scenes = [];
+
+        const baseUrl = "https://huggingface.co";
+        const repoId = "dylanebert/3d-arena";
+        const url = `${baseUrl}/api/datasets/${repoId}`;
+        const response = await fetch(url);
+        const responseData = await response.json();
+
+        const extensions = ["obj", "glb", "ply", "splat"];
+        const directory = modelPath.split("/").slice(0, -1).join("/");
+
+        scenes = responseData.siblings
+            .filter((scene: any) => {
+                const fileExtension = scene.rfilename.split(".").pop();
+                return scene.rfilename.startsWith(directory) && extensions.includes(fileExtension);
+            })
+            .reduce((acc: Scene[], scene: any) => {
+                const name = scene.rfilename.split("/").pop().split(".").slice(0, -1).join(".");
+                const url = `${baseUrl}/datasets/${repoId}/resolve/main/${scene.rfilename}`;
+                const thumbnail = url.replace(/\.[^.]+$/, ".png");
+                acc.push({ name, url, thumbnail });
+                return acc;
+            }, []);
+
+        scenes = [...scenes];
+    }
+
+    onMount(fetchScenes);
+</script>
+
+<div class="header">
+    <div class="back" aria-label="Back" aria-hidden="true" on:click={onBack}>
+        <ArrowLeft size={24} />
+    </div>
+    <div class="spacer" />
+    <button class="title-button" on:click={fetchScenes}>
+        <h2 class="muted">{modelName}</h2>
+    </button>
+    <div class="spacer" />
+</div>
+{#if scenes.length > 0}
+    <div class="grid">
+        {#each scenes as scene}
+            <button class="grid-item" on:click={() => onSceneClick(scene)}>
+                <img src={scene.thumbnail} alt={scene.name} class="thumbnail" />
+                <div class="title">{modelName}</div>
+            </button>
+        {/each}
+    </div>
+{:else}
+    <div class="loading-container">
+        <ProgressBarRound class="loading-icon" />
+        <div class="loading-text">Loading...</div>
+    </div>
+{/if}
diff --git a/src/routes/Viewer.svelte b/src/routes/Viewer.svelte
new file mode 100644
index 0000000000000000000000000000000000000000..f0b79d73e1be82bead6f171b4d8d3ef0a1dc31af
--- /dev/null
+++ b/src/routes/Viewer.svelte
@@ -0,0 +1,76 @@
+<script lang="ts">
+    import { onMount, onDestroy } from "svelte";
+    import type { IViewer } from "./viewers/IViewer";
+    import { createViewer } from "./viewers/ViewerFactory";
+    import ArrowLeft from "carbon-icons-svelte/lib/ArrowLeft.svelte";
+
+    interface Scene {
+        name: string;
+        url: string;
+        thumbnail: string;
+    }
+
+    export let modelName: string;
+    export let scene: Scene;
+    export let onBack: () => void;
+
+    let container: HTMLDivElement;
+    let canvas: HTMLCanvasElement;
+
+    let viewer: IViewer;
+
+    async function loadScene() {
+        viewer = await createViewer(scene.url, canvas);
+        window.addEventListener("resize", handleResize);
+        window.addEventListener("keydown", handleKeyDown);
+        handleResize();
+    }
+
+    function handleResize() {
+        if (!canvas || !container) return;
+        const maxWidth = container.clientHeight * (16 / 9);
+        const maxHeight = container.clientWidth * (9 / 16);
+        canvas.width = Math.min(container.clientWidth, maxWidth);
+        canvas.height = Math.min(container.clientHeight, maxHeight);
+    }
+
+    function handleKeyDown(e: KeyboardEvent) {
+        if (e.code === "KeyP") {
+            capture();
+        }
+    }
+
+    async function capture() {
+        const data = await viewer.capture();
+        if (!data) {
+            console.error("Failed to capture screenshot");
+            return;
+        }
+        const a = document.createElement("a");
+        a.href = data;
+        a.download = "screenshot.png";
+        a.click();
+    }
+
+    onMount(loadScene);
+
+    onDestroy(() => {
+        viewer?.dispose();
+        window.removeEventListener("resize", handleResize);
+        window.removeEventListener("keydown", handleKeyDown);
+    });
+</script>
+
+<div class="header">
+    <div class="back" aria-label="Back" aria-hidden="true" on:click={onBack}>
+        <ArrowLeft size={24} />
+    </div>
+    <button class="title-button" on:click={loadScene}>
+        <!-- svelte-ignore a11y-click-events-have-key-events -->
+        <!-- svelte-ignore a11y-no-static-element-interactions -->
+        <h2><span class="muted" on:click={onBack}>{modelName}/</span>{scene.name}</h2>
+    </button>
+</div>
+<div class="canvas-container" bind:this={container}>
+    <canvas bind:this={canvas} width={800} height={600} />
+</div>
diff --git a/viewer/src/routes/store.js b/src/routes/store.js
similarity index 100%
rename from viewer/src/routes/store.js
rename to src/routes/store.js
diff --git a/viewer/src/routes/viewer/[slug]/BabylonViewer.ts b/src/routes/viewers/BabylonViewer.ts
similarity index 100%
rename from viewer/src/routes/viewer/[slug]/BabylonViewer.ts
rename to src/routes/viewers/BabylonViewer.ts
diff --git a/viewer/src/routes/viewer/[slug]/IViewer.ts b/src/routes/viewers/IViewer.ts
similarity index 100%
rename from viewer/src/routes/viewer/[slug]/IViewer.ts
rename to src/routes/viewers/IViewer.ts
diff --git a/viewer/src/routes/viewer/[slug]/SplatViewer.ts b/src/routes/viewers/SplatViewer.ts
similarity index 84%
rename from viewer/src/routes/viewer/[slug]/SplatViewer.ts
rename to src/routes/viewers/SplatViewer.ts
index e0721b8ca34334561c2fd65c3741b51d727336b2..1010c4294a7b2da297931b95a92e47773c8330b9 100644
--- a/viewer/src/routes/viewer/[slug]/SplatViewer.ts
+++ b/src/routes/viewers/SplatViewer.ts
@@ -18,14 +18,23 @@ export class SplatViewer implements IViewer {
         this.scene = new SPLAT.Scene();
         this.camera = new SPLAT.Camera();
         this.controls = new SPLAT.OrbitControls(this.camera, canvas);
+        this.controls.orbitSpeed = 3.0;
 
         this.handleResize = this.handleResize.bind(this);
     }
 
     async loadScene(url: string, loadingBarCallback?: (progress: number) => void) {
-        await SPLAT.Loader.LoadAsync(url, this.scene, (progress) => {
-            loadingBarCallback?.(progress);
-        });
+        if (url.endsWith(".splat")) {
+            await SPLAT.Loader.LoadAsync(url, this.scene, (progress) => {
+                loadingBarCallback?.(progress);
+            });
+        } else if (url.endsWith(".ply")) {
+            await SPLAT.PLYLoader.LoadAsync(url, this.scene, (progress) => {
+                loadingBarCallback?.(progress);
+            });
+        } else {
+            throw new Error("Unsupported file format");
+        }
 
         const frame = () => {
             this.controls.update();
diff --git a/src/routes/viewers/ViewerFactory.ts b/src/routes/viewers/ViewerFactory.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7d4218eebc861ccaa66b436304a30d3970e36b89
--- /dev/null
+++ b/src/routes/viewers/ViewerFactory.ts
@@ -0,0 +1,19 @@
+import type { IViewer } from "./IViewer";
+import { BabylonViewer } from "./BabylonViewer";
+import { SplatViewer } from "./SplatViewer";
+
+const meshFormats = ["obj", "stl", "gltf", "glb"];
+const splatFormats = ["splat", "ply"];
+
+export async function createViewer(url: string, canvas: HTMLCanvasElement): Promise<IViewer> {
+    let viewer: IViewer;
+    if (meshFormats.some((format) => url.endsWith(format))) {
+        viewer = new BabylonViewer(canvas);
+    } else if (splatFormats.some((format) => url.endsWith(format))) {
+        viewer = new SplatViewer(canvas);
+    } else {
+        throw new Error("Unsupported file format");
+    }
+    await viewer.loadScene(url);
+    return viewer;
+}
diff --git a/viewer/static/favicon.png b/static/favicon.png
similarity index 100%
rename from viewer/static/favicon.png
rename to static/favicon.png
diff --git a/viewer/static/global.css b/static/global.css
similarity index 52%
rename from viewer/static/global.css
rename to static/global.css
index 7fde503d1ee06102067befc5c162c167670c1e11..bd549bc907d67355c4803781f7d7f79969d982a6 100644
--- a/viewer/static/global.css
+++ b/static/global.css
@@ -1,6 +1,6 @@
 html,
 body {
-    font-family: 'Roboto', sans-serif;
+    font-family: "Roboto", sans-serif;
     background-color: #1a1b1e;
     color: white;
     margin: 0;
@@ -32,11 +32,45 @@ body {
     cursor: pointer;
 }
 
+.muted {
+    color: #aaa;
+}
+
+.loading-container {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    width: 100%;
+    height: 196px;
+}
+
+.loading-icon {
+    width: 32px;
+    height: 32px;
+    animation: spin 0.5s linear infinite;
+    color: #aaa;
+}
+
+.loading-text {
+    color: #aaa;
+    margin-top: 10px;
+}
+
+@keyframes spin {
+    from {
+        transform: rotate(0deg);
+    }
+    to {
+        transform: rotate(360deg);
+    }
+}
+
 .container {
     padding: 10px 15px 80px 15px;
     margin-left: auto;
     margin-right: auto;
-    max-height: 100vh;
+    height: 100vh;
     overflow-y: auto;
     position: relative;
     box-sizing: border-box;
@@ -58,31 +92,47 @@ body {
     }
 }
 
-.exit-button {
+.canvas-container {
+    position: relative;
+    box-sizing: border-box;
     display: flex;
+    flex-direction: column;
     justify-content: center;
     align-items: center;
+    width: 100%;
+    max-height: 50%;
+    overflow: hidden;
+}
+
+.header {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    position: relative;
+}
+
+.back {
     position: absolute;
-    top: 10px;
-    right: 16px;
-    width: 36px;
-    height: 36px;
-    color: #aaa;
-    font-size: 24px;
+    left: 0;
     cursor: pointer;
-    font-weight: bold;
-    transition: scale 0.2s ease;
+    transition: transform 0.1s;
+    padding: 16px;
 }
 
-.exit-button:hover {
-    scale: 1.1;
+.back:hover {
+    transform: scale(1.2);
 }
 
-.header {
-    padding: 20px 0;
-    font-size: 24px;
-    font-weight: bold;
-    color: #aaa;
+.spacer {
+    flex: 1;
+}
+
+.title-button {
+    background: none;
+    border: none;
+    font: inherit;
+    color: inherit;
+    cursor: pointer;
 }
 
 .viewer {
@@ -129,6 +179,12 @@ body {
     padding-top: 100%;
 }
 
+@media (max-width: 575px) {
+    .grid-item::before {
+        padding-top: 56.25%;
+    }
+}
+
 .grid-item:hover {
     cursor: pointer;
 }
@@ -153,18 +209,65 @@ body {
     height: 48px;
 }
 
-.thumbnail {
+.grid-item .ranking {
     position: absolute;
-    top: -10px;
-    ;
+    top: 0;
     left: 0;
-    width: 100%;
-    height: auto;
+    background-color: #1a1b1e;
+    color: #aaa;
+    padding: 10px;
+    box-sizing: border-box;
+    font-size: 16px;
+    overflow: hidden;
+    transition: background-color 0.2s ease;
+}
+
+.grid-item:hover .ranking {
+    background-color: #444;
+}
+
+.thumbnail {
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    width: auto;
+    height: 100%;
     overflow: hidden;
     scale: 1;
-    transition: scale 0.2s ease;
+    transform: translate(-50%, -50%);
+    transition: transform 0.2s ease;
 }
 
 .grid-item:hover .thumbnail {
-    scale: 1.1;
-}
\ No newline at end of file
+    transform: translate(-50%, -50%) scale(1.1);
+}
+
+.tabs {
+    display: flex;
+    justify-content: left;
+    margin-top: 10px;
+    margin-bottom: 10px;
+    gap: 10px;
+    width: 100%;
+}
+
+.tabs button {
+    background-color: #1a1b1e;
+    color: #ddd;
+    border: none;
+    outline: none;
+    padding: 10px 10px 10px 10px;
+    font-family: "Roboto", sans-serif;
+    font-size: 14px;
+    font-weight: 600;
+    transition: background-color 0.2s ease;
+}
+
+.tabs button:hover {
+    cursor: pointer;
+    background-color: #555;
+}
+
+.tabs button.active {
+    background-color: #444;
+}
diff --git a/viewer/svelte.config.js b/svelte.config.js
similarity index 100%
rename from viewer/svelte.config.js
rename to svelte.config.js
diff --git a/viewer/tsconfig.json b/tsconfig.json
similarity index 100%
rename from viewer/tsconfig.json
rename to tsconfig.json
diff --git a/viewer/README.md b/viewer/README.md
deleted file mode 100644
index 5c91169b0ca6508bb24301c957a9edea5abf2b01..0000000000000000000000000000000000000000
--- a/viewer/README.md
+++ /dev/null
@@ -1,38 +0,0 @@
-# create-svelte
-
-Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
-
-## Creating a project
-
-If you're seeing this, you've probably already done this step. Congrats!
-
-```bash
-# create a new project in the current directory
-npm create svelte@latest
-
-# create a new project in my-app
-npm create svelte@latest my-app
-```
-
-## Developing
-
-Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
-
-```bash
-npm run dev
-
-# or start the server and open the app in a new browser tab
-npm run dev -- --open
-```
-
-## Building
-
-To create a production version of your app:
-
-```bash
-npm run build
-```
-
-You can preview the production build with `npm run preview`.
-
-> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
diff --git a/viewer/src/lib/data/dataLoader.ts b/viewer/src/lib/data/dataLoader.ts
deleted file mode 100644
index 6ebbde32211a6ea478cdfa4afda14f41b2690a5b..0000000000000000000000000000000000000000
--- a/viewer/src/lib/data/dataLoader.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import scenes from "$lib/data/scenes.json";
-import models from "$lib/data/models.json";
-
-export async function getScenes() {
-    return scenes;
-}
-
-export async function getModels() {
-    return models;
-}
diff --git a/viewer/src/lib/data/models.json b/viewer/src/lib/data/models.json
deleted file mode 100644
index 2b5b6423c0ef6772dbd0f775f5bff7a47264d296..0000000000000000000000000000000000000000
--- a/viewer/src/lib/data/models.json
+++ /dev/null
@@ -1,137 +0,0 @@
-[
-    {
-        "slug": "3dgs",
-        "title": "3D Gaussian Splatting",
-        "paper": "https://huggingface.co/papers/2308.04079",
-        "project": "https://repo-sam.inria.fr/fungraph/3d-gaussian-splatting/",
-        "code": "https://github.com/graphdeco-inria/gaussian-splatting",
-        "spaces": []
-    },
-    {
-        "slug": "sync-dreamer",
-        "title": "SyncDreamer",
-        "paper": "https://huggingface.co/papers/2309.03453",
-        "project": "https://liuyuan-pal.github.io/SyncDreamer/",
-        "code": "https://github.com/liuyuan-pal/SyncDreamer",
-        "spaces": []
-    },
-    {
-        "slug": "shap-e",
-        "title": "shap-e",
-        "paper": "https://arxiv.org/abs/2305.02463",
-        "code": "https://github.com/openai/shap-e",
-        "spaces": [
-            "https://huggingface.co/spaces/hysts/Shap-E"
-        ]
-    },
-    {
-        "slug": "dreamfusion",
-        "title": "DreamFusion",
-        "paper": "https://arxiv.org/abs/2209.14988",
-        "project": "https://dreamfusion3d.github.io/index.html",
-        "spaces": []
-    },
-    {
-        "slug": "mv-dream",
-        "title": "MVDream",
-        "paper": "https://huggingface.co/papers/2308.16512",
-        "project": "https://mv-dream.github.io/",
-        "code": "https://github.com/bytedance/MVDream",
-        "spaces": []
-    },
-    {
-        "slug": "wonder3d",
-        "title": "Wonder3D",
-        "paper": "https://huggingface.co/papers/2310.15008",
-        "project": "https://www.xxlong.site/Wonder3D/",
-        "code": "https://github.com/xxlong0/Wonder3D",
-        "spaces": []
-    },
-    {
-        "slug": "gaussian-dreamer",
-        "title": "GaussianDreamer",
-        "paper": "https://huggingface.co/papers/2310.08529",
-        "project": "https://taoranyi.com/gaussiandreamer/",
-        "code": "https://github.com/hustvl/GaussianDreamer",
-        "spaces": []
-    },
-    {
-        "slug": "dream-gaussian",
-        "title": "DreamGaussian",
-        "paper": "https://huggingface.co/papers/2309.16653",
-        "project": "https://dreamgaussian.github.io/",
-        "code": "https://github.com/dreamgaussian/dreamgaussian",
-        "spaces": [
-            "https://huggingface.co/spaces/jiawei011/dreamgaussian"
-        ]
-    },
-    {
-        "slug": "panic3d-anime-reconstruction",
-        "title": "PAniC-3D",
-        "paper": "https://arxiv.org/abs/2303.14587",
-        "project": "",
-        "code": "https://github.com/ShuhongChen/panic3d-anime-reconstruction",
-        "spaces": []
-    },
-    {
-        "slug": "eg3d",
-        "title": "EG3D",
-        "paper": "https://arxiv.org/abs/2112.07945",
-        "project": "https://nvlabs.github.io/eg3d/",
-        "code": "https://github.com/NVlabs/eg3d",
-        "spaces": []
-    },
-    {
-        "slug": "ag3d",
-        "title": "AG3D",
-        "paper": "https://zj-dong.github.io/AG3D/assets/paper.pdf",
-        "project": "https://zj-dong.github.io/AG3D/",
-        "code": "https://github.com/zj-dong/AG3D",
-        "spaces": []
-    },
-    {
-        "slug": "scene-dreamer",
-        "title": "SceneDreamer",
-        "paper": "https://arxiv.org/abs/2302.01330",
-        "project": "https://scene-dreamer.github.io/",
-        "code": "https://github.com/FrozenBurning/SceneDreamer",
-        "spaces": ["https://huggingface.co/spaces/FrozenBurning/SceneDreamer"]
-    },
-    {
-        "slug": "ldm3d-vr",
-        "title": "LDM3D-VR",
-        "paper": "https://arxiv.org/abs/2311.03226",
-        "spaces": []
-    },
-    {
-        "slug": "d3ga",
-        "title": "D3GA",
-        "paper": "https://huggingface.co/papers/2311.08581",
-        "project": "https://zielon.github.io/d3ga/",
-        "spaces": []
-    },
-    {
-        "slug": "dmv3d",
-        "title": "DMV3D",
-        "paper": "https://huggingface.co/papers/2311.09217",
-        "project": "https://justimyhxu.github.io/projects/dmv3d/",
-        "spaces": []
-    },
-    {
-        "slug": "instant3d",
-        "title": "Instant3D",
-        "paper": "https://huggingface.co/papers/2311.08403",
-        "project": "https://instant-3d.github.io/",
-        "spaces": []
-    },
-    {
-        "slug": "one2345++",
-        "title": "One-2-3-45++",
-        "paper": "https://huggingface.co/papers/2311.07885",
-        "project": "https://one-2-3-45.github.io/",
-        "code": "https://github.com/One-2-3-45/One-2-3-45",
-        "spaces": [
-            "https://huggingface.co/spaces/One-2-3-45/One-2-3-45"
-        ]
-    }
-]
diff --git a/viewer/src/lib/data/scenes.json b/viewer/src/lib/data/scenes.json
deleted file mode 100644
index 5d9be8717c42b2d68021f51f930c564f84b4f5c8..0000000000000000000000000000000000000000
--- a/viewer/src/lib/data/scenes.json
+++ /dev/null
@@ -1,255 +0,0 @@
-[
-    {
-        "slug": "3dgs-bicycle",
-        "model": "3dgs",
-        "title": "Bicycle",
-        "type": "splat",
-        "url": "https://huggingface.co/datasets/dylanebert/3dgs/resolve/main/bicycle/bicycle-7k.splat",
-        "pipeline": [
-            "Gaussian Splatting"
-        ]
-    },
-    {
-        "slug": "3dgs-bonsai",
-        "model": "3dgs",
-        "title": "Bonsai",
-        "type": "splat",
-        "url": "https://huggingface.co/datasets/dylanebert/3dgs/resolve/main/bonsai/bonsai-7k.splat",
-        "pipeline": [
-            "Gaussian Splatting"
-        ]
-    },
-    {
-        "slug": "3dgs-counter",
-        "model": "3dgs",
-        "title": "Counter",
-        "type": "splat",
-        "url": "https://huggingface.co/datasets/dylanebert/3dgs/resolve/main/counter/counter-7k.splat",
-        "pipeline": [
-            "Gaussian Splatting"
-        ]
-    },
-    {
-        "slug": "3dgs-garden",
-        "model": "3dgs",
-        "title": "Garden",
-        "type": "splat",
-        "url": "https://huggingface.co/datasets/dylanebert/3dgs/resolve/main/garden/garden-7k.splat",
-        "pipeline": [
-            "Gaussian Splatting"
-        ]
-    },
-    {
-        "slug": "3dgs-kitchen",
-        "model": "3dgs",
-        "title": "Kitchen",
-        "type": "splat",
-        "url": "https://huggingface.co/datasets/dylanebert/3dgs/resolve/main/kitchen/kitchen-7k.splat",
-        "pipeline": [
-            "Gaussian Splatting"
-        ]
-    },
-    {
-        "slug": "3dgs-playroom",
-        "model": "3dgs",
-        "title": "Playroom",
-        "type": "splat",
-        "url": "https://huggingface.co/datasets/dylanebert/3dgs/resolve/main/playroom/playroom-7k.splat",
-        "pipeline": [
-            "Gaussian Splatting"
-        ]
-    },
-    {
-        "slug": "3dgs-room",
-        "model": "3dgs",
-        "title": "Room",
-        "type": "splat",
-        "url": "https://huggingface.co/datasets/dylanebert/3dgs/resolve/main/room/room-7k.splat",
-        "pipeline": [
-            "Gaussian Splatting"
-        ]
-    },
-    {
-        "slug": "3dgs-stump",
-        "model": "3dgs",
-        "title": "Stump",
-        "type": "splat",
-        "url": "https://huggingface.co/datasets/dylanebert/3dgs/resolve/main/stump/stump-7k.splat",
-        "pipeline": [
-            "Gaussian Splatting"
-        ]
-    },
-    {
-        "slug": "sync-dreamer-armor",
-        "model": "sync-dreamer",
-        "title": "armor",
-        "type": "mesh",
-        "url": "https://huggingface.co/datasets/dylanebert/igf-results/resolve/main/sync-dreamer/armor.glb",
-        "pipeline": [
-            "Multi-view Diffusion",
-            "NeuS"
-        ]
-    },
-    {
-        "slug": "sync-dreamer-deer",
-        "model": "sync-dreamer",
-        "title": "deer",
-        "type": "mesh",
-        "url": "https://huggingface.co/datasets/dylanebert/igf-results/resolve/main/sync-dreamer/deer.glb",
-        "pipeline": [
-            "Multi-view Diffusion",
-            "NeuS"
-        ]
-    },
-    {
-        "slug": "sync-dreamer-drum",
-        "model": "sync-dreamer",
-        "title": "drum",
-        "type": "mesh",
-        "url": "https://huggingface.co/datasets/dylanebert/igf-results/resolve/main/sync-dreamer/drum.glb",
-        "pipeline": [
-            "Multi-view Diffusion",
-            "NeuS"
-        ]
-    },
-    {
-        "slug": "sync-dreamer-forest",
-        "model": "sync-dreamer",
-        "title": "forest",
-        "type": "mesh",
-        "url": "https://huggingface.co/datasets/dylanebert/igf-results/resolve/main/sync-dreamer/forest.glb",
-        "pipeline": [
-            "Multi-view Diffusion",
-            "NeuS"
-        ]
-    },
-    {
-        "slug": "sync-dreamer-monkey",
-        "model": "sync-dreamer",
-        "title": "monkey",
-        "type": "mesh",
-        "url": "https://huggingface.co/datasets/dylanebert/igf-results/resolve/main/sync-dreamer/monkey.glb",
-        "pipeline": [
-            "Multi-view Diffusion",
-            "NeuS"
-        ]
-    },
-    {
-        "slug": "sync-dreamer-poro",
-        "model": "sync-dreamer",
-        "title": "poro",
-        "type": "mesh",
-        "url": "https://huggingface.co/datasets/dylanebert/igf-results/resolve/main/sync-dreamer/poro.glb",
-        "pipeline": [
-            "Multi-view Diffusion",
-            "NeuS"
-        ]
-    },
-    {
-        "slug": "sync-dreamer-train",
-        "model": "sync-dreamer",
-        "title": "train",
-        "type": "mesh",
-        "url": "https://huggingface.co/datasets/dylanebert/igf-results/resolve/main/sync-dreamer/train.glb",
-        "pipeline": [
-            "Multi-view Diffusion",
-            "NeuS"
-        ]
-    },
-    {
-        "slug": "dreamfusion-sweaterfrog",
-        "model": "dreamfusion",
-        "title": "sweater frog",
-        "type": "mesh",
-        "url": "https://dreamfusion3d.github.io/assets/meshes2/sweaterfrog_1step.glb",
-        "prompt": "frog wearing a sweater",
-        "pipeline": [
-            "Multi-view Diffusion",
-            "NeuS"
-        ]
-    },
-    {
-        "slug": "dreamfusion-chick",
-        "model": "dreamfusion",
-        "title": "chick",
-        "type": "mesh",
-        "url": "https://dreamfusion3d.github.io/assets/meshes2/44855521_sept18_hero16_047a_DSLR_photo_of_an_eggshell_broken_in_two_with_an_adorable_chick_standing_next_to_it_1step.glb",
-        "prompt": "eggshell broken in two with an adorable chick standing next to it",
-        "pipeline": [
-            "Multi-view Diffusion",
-            "Marching Cubes"
-        ]
-    },
-    {
-        "slug": "dreamfusion-ghost",
-        "model": "dreamfusion",
-        "title": "ghost",
-        "type": "mesh",
-        "url": "https://dreamfusion3d.github.io/assets/meshes2/44934035_sept18_hero19_113a_DSLR_photo_of_a_ghost_eating_a_hamburger_1step.glb",
-        "prompt": "ghost eating a hamburger",
-        "pipeline": [
-            "Multi-view Diffusion",
-            "Marching Cubes"
-        ]
-    },
-    {
-        "slug": "dreamfusion-pig",
-        "model": "dreamfusion",
-        "title": "pig",
-        "type": "mesh",
-        "url": "https://dreamfusion3d.github.io/assets/meshes2/44844973_sept18_hero14_076a_pig_wearing_a_backpack_1step.glb",
-        "prompt": "a pig wearing a backback",
-        "pipeline": [
-            "Multi-view Diffusion",
-            "Marching Cubes"
-        ]
-    },
-    {
-        "slug": "dreamfusion-eagle",
-        "model": "dreamfusion",
-        "title": "eagle",
-        "type": "mesh",
-        "url": "https://dreamfusion3d.github.io/assets/meshes2/44853505_sept18_hero15_145a_bald_eagle_carved_out_of_wood_1step.glb",
-        "prompt": "a bald eagle carved out of wood",
-        "pipeline": [
-            "Multi-view Diffusion",
-            "Marching Cubes"
-        ]
-    },
-    {
-        "slug": "dreamfusion-crab",
-        "model": "dreamfusion",
-        "title": "crab",
-        "type": "mesh",
-        "url": "https://dreamfusion3d.github.io/assets/meshes2/44930695_sept18_hero18_103a_crab,_low_poly_1step.glb",
-        "prompt": "a crab, low poly",
-        "pipeline": [
-            "Multi-view Diffusion",
-            "Marching Cubes"
-        ]
-    },
-    {
-        "slug": "dreamfusion-lemur",
-        "model": "dreamfusion",
-        "title": "lemur",
-        "type": "mesh",
-        "url": "https://dreamfusion3d.github.io/assets/meshes2/44853505_sept18_hero15_124a_lemur_taking_notes_in_a_journal_1step.glb",
-        "prompt": "a lemur taking notes in a journal",
-        "pipeline": [
-            "Multi-view Diffusion",
-            "Marching Cubes"
-        ]
-    },
-    {
-        "slug": "dreamfusion-corgi",
-        "model": "dreamfusion",
-        "title": "corgi",
-        "type": "mesh",
-        "url": "https://dreamfusion3d.github.io/assets/meshes2/44960400_sept18_hero20peter_117a_plush_toy_of_a_corgi_nurse_1step.glb",
-        "prompt": "a plush toy of a corgi nurse",
-        "pipeline": [
-            "Multi-view Diffusion",
-            "Marching Cubes"
-        ]
-    }
-]
\ No newline at end of file
diff --git a/viewer/src/lib/placeholder.png b/viewer/src/lib/placeholder.png
deleted file mode 100755
index fb66a22baba4329aa49598425c75ebd92eeaac95..0000000000000000000000000000000000000000
Binary files a/viewer/src/lib/placeholder.png and /dev/null differ
diff --git a/viewer/src/routes/+page.svelte b/viewer/src/routes/+page.svelte
deleted file mode 100644
index 25c761a2e7e69ef90776d60a562ecf1dfcbb5d56..0000000000000000000000000000000000000000
--- a/viewer/src/routes/+page.svelte
+++ /dev/null
@@ -1,57 +0,0 @@
-<script lang="ts">
-    import { activeTab } from "./store.js";
-    import ModelsView from "./components/ModelsView.svelte";
-    import ScenesView from "./components/ScenesView.svelte";
-
-    function goHome() {
-        window.location.href = "/";
-    }
-</script>
-
-<div class="container">
-    <div on:pointerdown={goHome} class="banner">
-        <h1>IGF</h1>
-        <p>Comparative 3D Research Browser</p>
-    </div>
-    <div class="tabs">
-        <button on:click={() => activeTab.set("Models")} class={$activeTab === "Models" ? "active" : ""}>Models</button>
-        <button on:click={() => activeTab.set("Scenes")} class={$activeTab === "Scenes" ? "active" : ""}>Scenes</button>
-    </div>
-    {#if $activeTab === "Models"}
-        <ModelsView />
-    {:else}
-        <ScenesView />
-    {/if}
-</div>
-
-<style>
-    .tabs {
-        display: flex;
-        justify-content: left;
-        margin-top: 10px;
-        margin-bottom: 10px;
-        gap: 10px;
-        width: 100%;
-    }
-
-    .tabs button {
-        background-color: #1a1b1e;
-        color: #ddd;
-        border: none;
-        outline: none;
-        padding: 10px 10px 10px 10px;
-        font-family: "Roboto", sans-serif;
-        font-size: 14px;
-        font-weight: 600;
-        transition: background-color 0.2s ease;
-    }
-
-    .tabs button:hover {
-        cursor: pointer;
-        background-color: #555;
-    }
-
-    .tabs button.active {
-        background-color: #444;
-    }
-</style>
diff --git a/viewer/src/routes/components/ModelsView.svelte b/viewer/src/routes/components/ModelsView.svelte
deleted file mode 100644
index 3e178a37dc5bd16f5357be30ffa58a1512014d8c..0000000000000000000000000000000000000000
--- a/viewer/src/routes/components/ModelsView.svelte
+++ /dev/null
@@ -1,42 +0,0 @@
-<script lang="ts">
-    import { onMount } from "svelte";
-    import { getModels, getScenes } from "$lib/data/dataLoader";
-    import placeholderImage from "$lib/placeholder.png";
-
-    let models: any[] = [];
-    let sceneMap: any = {};
-
-    onMount(async () => {
-        models = await getModels();
-        const scenes = await getScenes();
-        for (let model of models) {
-            for (let scene of scenes) {
-                if (scene.model === model.slug) {
-                    sceneMap[model.slug] = scene.slug;
-                    break;
-                }
-            }
-        }
-    });
-
-    function handleImageError(event: Event) {
-        const image = event.currentTarget as HTMLImageElement;
-        image.src = placeholderImage;
-    }
-</script>
-
-<div class="grid">
-    {#each models as model}
-        {#if sceneMap[model.slug] !== undefined}
-            <a href={`/models/${model.slug}`} class="grid-item">
-                <img
-                    src={`/thumbnails/${sceneMap[model.slug]}.png`}
-                    alt={model.title}
-                    class="thumbnail"
-                    on:error={(event) => handleImageError(event)}
-                />
-                <div class="title">{model.title}</div>
-            </a>
-        {/if}
-    {/each}
-</div>
diff --git a/viewer/src/routes/components/ScenesView.svelte b/viewer/src/routes/components/ScenesView.svelte
deleted file mode 100644
index 29b83c27926abbbe089b4182659f550030d313a6..0000000000000000000000000000000000000000
--- a/viewer/src/routes/components/ScenesView.svelte
+++ /dev/null
@@ -1,30 +0,0 @@
-<script lang="ts">
-    import { onMount } from "svelte";
-    import { getScenes } from "$lib/data/dataLoader";
-    import placeholderImage from "$lib/placeholder.png";
-
-    let scenes: any[] = [];
-
-    onMount(async () => {
-        scenes = await getScenes();
-    });
-
-    function handleImageError(event: Event) {
-        const image = event.currentTarget as HTMLImageElement;
-        image.src = placeholderImage;
-    }
-</script>
-
-<div class="grid">
-    {#each scenes as scene}
-        <a href={`/viewer/${scene.slug}`} class="grid-item">
-            <img
-                src={`/thumbnails/${scene.slug}.png`}
-                alt={scene.title}
-                class="thumbnail"
-                on:error={(event) => handleImageError(event)}
-            />
-            <div class="title">{scene.title}</div>
-        </a>
-    {/each}
-</div>
diff --git a/viewer/src/routes/models/[slug]/+page.server.ts b/viewer/src/routes/models/[slug]/+page.server.ts
deleted file mode 100644
index 7fa317fa2b9e5b0b8cc6f707da470e8360228077..0000000000000000000000000000000000000000
--- a/viewer/src/routes/models/[slug]/+page.server.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { getModels, getScenes } from "$lib/data/dataLoader";
-
-export async function load({ params }) {
-    const models = await getModels();
-    const scenes = await getScenes();
-
-    const model = models.find((model: any) => model.slug === params.slug);
-    const modelScenes = scenes.filter((scene: any) => scene.model === params.slug);
-
-    return {
-        model: model,
-        scenes: modelScenes,
-    };
-}
diff --git a/viewer/src/routes/models/[slug]/+page.svelte b/viewer/src/routes/models/[slug]/+page.svelte
deleted file mode 100644
index 474f8b44b706a58d613f76027dfe4f0810c5fddd..0000000000000000000000000000000000000000
--- a/viewer/src/routes/models/[slug]/+page.svelte
+++ /dev/null
@@ -1,182 +0,0 @@
-<script lang="ts">
-    import placeholderImage from "$lib/placeholder.png";
-
-    export let data: {
-        model: {
-            title: string;
-            paper: string;
-            project: string;
-            code: string;
-        };
-        scenes: {
-            slug: string;
-            title: string;
-        }[];
-    };
-
-    function handleImageError(event: Event) {
-        const image = event.currentTarget as HTMLImageElement;
-        image.src = placeholderImage;
-    }
-
-    function goHome() {
-        window.location.href = "/";
-    }
-</script>
-
-<div class="container">
-    <div on:pointerdown={goHome} class="banner">
-        <h1>IGF</h1>
-        <p>Comparative 3D Research Browser</p>
-    </div>
-    <div class="header">{data.model.title}</div>
-    <div class="model-container">
-        <div class="model-info">
-            <p class="model-header">Info</p>
-            <table class="table">
-                {#if data.model.paper}
-                    <tr>
-                        <td>Paper</td>
-                        <td><a href={data.model.paper} target="_blank">{data.model.paper}</a></td>
-                    </tr>
-                {/if}
-                {#if data.model.project}
-                    <tr>
-                        <td>Project</td>
-                        <td><a href={data.model.project} target="_blank">{data.model.project}</a></td>
-                    </tr>
-                {/if}
-                {#if data.model.code}
-                    <tr>
-                        <td>Code</td>
-                        <td><a href={data.model.code} target="_blank">{data.model.code}</a></td>
-                    </tr>
-                {/if}
-            </table>
-        </div>
-        <div class="grid-container">
-            {#if data.scenes.length > 0}
-                <div class="grid">
-                    {#each data.scenes as scene}
-                        <a href={`/viewer/${scene.slug}`} class="grid-item">
-                            <img
-                                src={`/thumbnails/${scene.slug}.png`}
-                                alt={scene.title}
-                                class="thumbnail"
-                                on:error={(event) => handleImageError(event)}
-                            />
-                            <div class="title">{scene.title}</div>
-                        </a>
-                    {/each}
-                </div>
-            {:else}
-                <div class="grid">
-                    <div class="warning">No scenes found</div>
-                </div>
-            {/if}
-        </div>
-    </div>
-</div>
-
-<style>
-    .model-header {
-        padding: 10px;
-        font-size: 16px;
-        color: #aaa;
-        margin: 0;
-    }
-
-    .model-info {
-        border: 1px solid #333;
-        box-sizing: border-box;
-        width: 100%;
-        margin: 0;
-
-        @media (min-width: 576px) {
-            width: 384px;
-        }
-    }
-
-    .table {
-        table-layout: fixed;
-        width: 100%;
-        margin: 0;
-        padding: 0;
-        border-collapse: collapse;
-    }
-
-    .table td {
-        width: 100%;
-        margin: 0;
-        padding: 10px;
-        border-top: 1px solid #333;
-        white-space: nowrap;
-    }
-
-    .table td:first-child {
-        width: 128px;
-        background-color: #222;
-        border-right: 1px solid #333;
-        font-size: 14px;
-        font-weight: bold;
-        color: #aaa;
-    }
-
-    .table td:last-child {
-        width: 100%;
-        font-size: 14px;
-        overflow: hidden;
-    }
-
-    .table a {
-        color: #6d90b6;
-    }
-
-    .model-container {
-        display: flex;
-        flex-wrap: wrap;
-        align-items: flex-start;
-    }
-
-    .grid-container {
-        flex: 1;
-        display: flex;
-        flex-wrap: wrap;
-        justify-content: center;
-    }
-
-    .grid {
-        margin-top: 10px;
-        margin-left: 0;
-
-        @media (min-width: 576px) {
-            margin-top: 0;
-            margin-left: 10px;
-        }
-    }
-
-    .grid-item {
-        @media (min-width: 576px) {
-            width: 100%;
-        }
-
-        @media (min-width: 768px) {
-            width: calc(50% - 10px);
-        }
-
-        @media (min-width: 992px) {
-            width: calc(33.333% - 10px);
-        }
-
-        @media (min-width: 1200px) {
-            width: calc(25% - 10px);
-        }
-    }
-
-    .warning {
-        width: 100%;
-        margin-top: 20px;
-        text-align: center;
-        color: #aaa;
-    }
-</style>
diff --git a/viewer/src/routes/viewer/[slug]/+page.server.ts b/viewer/src/routes/viewer/[slug]/+page.server.ts
deleted file mode 100644
index 9a58a3bb31bf472899f81aeca3313d8031b42bc1..0000000000000000000000000000000000000000
--- a/viewer/src/routes/viewer/[slug]/+page.server.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { error } from "@sveltejs/kit";
-import { getModels, getScenes } from "$lib/data/dataLoader";
-
-export async function load({ params }) {
-    const models = await getModels();
-    const scenes = await getScenes();
-
-    const scene = scenes.find((scene: any) => scene.slug === params.slug);
-    const model = models.find((model: any) => model.slug === scene!.model);
-
-    if (!scene) throw error(404);
-
-    return {
-        scene: scene,
-        model: model,
-    };
-}
diff --git a/viewer/src/routes/viewer/[slug]/+page.svelte b/viewer/src/routes/viewer/[slug]/+page.svelte
deleted file mode 100644
index 0c3c305d75a85a58e3ffeac724f8bed32180f9b7..0000000000000000000000000000000000000000
--- a/viewer/src/routes/viewer/[slug]/+page.svelte
+++ /dev/null
@@ -1,374 +0,0 @@
-<script lang="ts">
-    import { onMount, onDestroy } from "svelte";
-    import type { IViewer } from "./IViewer";
-    import { BabylonViewer } from "./BabylonViewer";
-    import { SplatViewer } from "./SplatViewer";
-
-    export let data: {
-        scene: {
-            title: string;
-            model: string;
-            type: string;
-            url: string;
-            prompt: string;
-            pipeline: string[];
-        };
-        model: {
-            title: string;
-        };
-    };
-
-    let stats: { name: string; value: any }[] = [];
-
-    let viewer: IViewer;
-    let overlay: HTMLDivElement;
-    let container: HTMLDivElement;
-    let hudToggleBtn: HTMLButtonElement;
-    let canvas: HTMLCanvasElement;
-    let loadingBarFill: HTMLDivElement;
-    let collapsed = false;
-
-    onMount(initViewer);
-    onDestroy(destroyViewer);
-
-    async function initViewer() {
-        document.body.classList.add("viewer");
-        if (data.scene.type == "mesh") {
-            viewer = new BabylonViewer(canvas);
-        } else if (data.scene.type == "splat") {
-            viewer = new SplatViewer(canvas);
-        } else {
-            console.error(`Unsupported scene type: ${data.scene.type}`);
-        }
-        handleMobileView();
-        await loadScene(data.scene.url);
-        window.addEventListener("resize", () => {
-            updateCanvasSize();
-        });
-        updateStats();
-        setInterval(updateStats, 1000);
-    }
-
-    function destroyViewer() {
-        document.body.classList.remove("viewer");
-        viewer.dispose();
-    }
-
-    function handleMobileView() {
-        const isMobile = window.innerWidth < 768;
-        if (isMobile) toggleHUD();
-    }
-
-    function toggleHUD() {
-        collapsed = !collapsed;
-        hudToggleBtn.textContent = collapsed ? ")" : "(";
-        if (collapsed) {
-            container.classList.remove("hud-expanded");
-        } else {
-            container.classList.add("hud-expanded");
-        }
-    }
-
-    function setRenderMode(event: PointerEvent) {
-        const babylonViewer = viewer as BabylonViewer;
-        if (!babylonViewer) {
-            console.error("Can only set render mode for BabylonViewer");
-            return;
-        }
-
-        document.querySelectorAll(".mode-item").forEach((item) => {
-            item.classList.remove("active");
-        });
-
-        const modeItem = event.currentTarget as HTMLElement;
-        modeItem.classList.add("active");
-
-        const mode = modeItem.dataset.mode as string;
-        babylonViewer.setRenderMode(mode);
-    }
-
-    async function loadScene(url: string) {
-        overlay.style.display = "flex";
-        await viewer.loadScene(url, (progress) => {
-            loadingBarFill.style.width = `${progress * 100}%`;
-        });
-        updateCanvasSize();
-        overlay.style.display = "none";
-    }
-
-    function updateCanvasSize() {
-        if (!canvas || !container) return;
-        canvas.width = container.clientWidth;
-        canvas.height = container.clientHeight;
-    }
-
-    async function capture() {
-        const data = await viewer.capture();
-        if (!data) {
-            console.error("Failed to capture screenshot");
-            return;
-        }
-        const a = document.createElement("a");
-        a.href = data;
-        a.download = "screenshot.png";
-        a.click();
-    }
-
-    function updateStats() {
-        stats = viewer.getStats();
-    }
-
-    function exit() {
-        window.history.back();
-    }
-</script>
-
-<div bind:this={container} class="canvas-container hud-expanded">
-    <div bind:this={overlay} class="loading-overlay">
-        <div class="loading-bar">
-            <div bind:this={loadingBarFill} class="loading-bar-fill" />
-        </div>
-    </div>
-    <canvas bind:this={canvas} width="512" height="512" />
-    <div class="exit-button" on:pointerdown={exit}>x</div>
-    <div class="hud" class:collapsed>
-        <button bind:this={hudToggleBtn} on:click={toggleHUD} class="hud-toggle-btn">(</button>
-        <div class="section">
-            <div class="title">{data.scene.title}</div>
-        </div>
-        <div class="section">
-            <div class="section-title">Model</div>
-            <div class="info-panel">
-                {#if data.scene.model}
-                    <a href={`/models/${data.scene.model}`} class="section-label">{data.model.title}</a>
-                    {#if data.scene.pipeline}
-                        <ol class="pipeline">
-                            {#each data.scene.pipeline as step}
-                                <li>{step}</li>
-                            {/each}
-                        </ol>
-                    {/if}
-                {:else}
-                    <div class="section-label">None</div>
-                {/if}
-            </div>
-        </div>
-        {#if data.scene.prompt}
-            <div class="section">
-                <div class="section-title">Prompt</div>
-                <div class="info-panel">
-                    <div class="section-label">{data.scene.prompt}</div>
-                </div>
-            </div>
-        {/if}
-        {#if stats.length > 0}
-            <div class="section">
-                <div class="section-title">Stats</div>
-                <div class="info-panel">
-                    {#each stats as stat}
-                        <div>{stat.name}: {stat.value}</div>
-                    {/each}
-                </div>
-            </div>
-        {/if}
-        {#if data.scene.type === "mesh"}
-            <div class="section">
-                <div class="section-title">Render Mode</div>
-                <div class="button-group mode-list">
-                    <div on:pointerdown={setRenderMode} class="hud-button mode-item active" data-mode="rendered">
-                        Rendered
-                    </div>
-                    <div on:pointerdown={setRenderMode} class="hud-button mode-item" data-mode="wireframe">
-                        Wireframe
-                    </div>
-                </div>
-            </div>
-        {/if}
-        <div class="section">
-            <div class="section-title">Actions</div>
-            <div class="button-group">
-                <div class="hud-button" on:pointerdown={capture}>Capture</div>
-            </div>
-        </div>
-    </div>
-</div>
-
-<style>
-    .canvas-container {
-        position: relative;
-        box-sizing: border-box;
-        transition: padding-left 0.2s ease;
-        display: flex;
-        flex-direction: column;
-        justify-content: center;
-        align-items: center;
-        width: 100wh;
-        height: 100vh;
-        overflow: hidden;
-    }
-
-    .canvas-container.hud-expanded {
-        padding-left: 256px;
-    }
-
-    .loading-overlay {
-        position: absolute;
-        top: 0;
-        left: 0;
-        width: 100%;
-        height: 100%;
-        background-color: #1a1b1e;
-        display: flex;
-        flex-direction: column;
-        justify-content: center;
-        align-items: center;
-        z-index: 100;
-        gap: 10px;
-    }
-
-    .loading-overlay::before {
-        content: "Loading...";
-        color: white;
-        font-size: 16px;
-    }
-
-    .loading-bar {
-        position: relative;
-        width: 256px;
-        height: 20px;
-        border: 2px solid #aaa;
-        background-color: #1a1b1e;
-    }
-
-    .loading-bar-fill {
-        position: absolute;
-        top: 0;
-        left: 0;
-        width: 0%;
-        height: 100%;
-        background-color: #555;
-        transition: width 0.2s ease;
-    }
-
-    canvas {
-        max-width: 100%;
-        max-height: 100%;
-    }
-
-    canvas:focus {
-        outline: none;
-    }
-
-    .hud {
-        position: absolute;
-        top: 0;
-        left: 0;
-        width: 286px;
-        height: 100%;
-        box-sizing: border-box;
-        font-size: 14px;
-        background-color: #1a1b1e;
-        box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.3);
-        transition: transform 0.2s ease;
-        margin: 0;
-        padding: 0 30px 0 0;
-        overflow-x: hidden;
-        overflow-y: auto;
-
-        @media (max-width: 768px) {
-            width: 100%;
-        }
-    }
-
-    .hud-toggle-btn {
-        position: absolute;
-        right: 0;
-        top: 50%;
-        transform: translateY(-50%);
-        background-color: #1a1b1e;
-        border: none;
-        color: #aaa;
-        font-size: 16px;
-        cursor: pointer;
-        outline: none;
-        width: 29px;
-        height: 100%;
-        box-sizing: border-box;
-        transition: background-color 0.2s ease;
-        border-left: 1px solid #444;
-    }
-
-    .hud-toggle-btn:hover {
-        background-color: #444;
-    }
-
-    .hud.collapsed {
-        transform: translateX(calc(-100% + 30px));
-    }
-
-    .section {
-        width: 100%;
-        padding: 10px;
-        box-sizing: border-box;
-    }
-
-    .title {
-        font-size: 16px;
-        color: #aaa;
-        font-weight: bold;
-        padding: 4px;
-        padding-top: 10px;
-    }
-
-    .section-title {
-        font-size: 11px;
-        font-weight: light;
-        text-transform: uppercase;
-        color: #aaa;
-        width: 100%;
-        padding: 4px;
-    }
-
-    .section-label {
-        font-size: 14px;
-        color: #ddd;
-    }
-
-    .info-panel {
-        padding: 6px 10px 0px 10px;
-        color: #ddd;
-    }
-
-    .pipeline {
-        margin: 0;
-        padding: 6px 10px 0px 20px;
-    }
-
-    .button-group {
-        border: none;
-        background-color: transparent;
-        box-sizing: border-box;
-    }
-
-    .hud-button {
-        padding: 10px 15px;
-        cursor: pointer;
-        background-color: #1a1b1e;
-        border-bottom: 1px solid #444;
-        transition: background-color 0.2s ease;
-        box-sizing: border-box;
-    }
-
-    .hud-button:last-child {
-        border-bottom: none;
-    }
-
-    .hud-button:hover {
-        background-color: #555;
-    }
-
-    .hud-button.active {
-        background-color: #444;
-        color: white;
-    }
-</style>
diff --git a/viewer/static/thumbnails/3dgs-bicycle.png b/viewer/static/thumbnails/3dgs-bicycle.png
deleted file mode 100644
index f7388bf7ba3028485b6471872f1ad43727d58fb9..0000000000000000000000000000000000000000
Binary files a/viewer/static/thumbnails/3dgs-bicycle.png and /dev/null differ
diff --git a/viewer/static/thumbnails/3dgs-bonsai.png b/viewer/static/thumbnails/3dgs-bonsai.png
deleted file mode 100644
index ee3f8c8a38506a3dcab0998f4d424dea72c19686..0000000000000000000000000000000000000000
Binary files a/viewer/static/thumbnails/3dgs-bonsai.png and /dev/null differ
diff --git a/viewer/static/thumbnails/3dgs-counter.png b/viewer/static/thumbnails/3dgs-counter.png
deleted file mode 100644
index e76b13d8b4ac9444110f28871ac1a12ad267706a..0000000000000000000000000000000000000000
Binary files a/viewer/static/thumbnails/3dgs-counter.png and /dev/null differ
diff --git a/viewer/static/thumbnails/3dgs-flowers.png b/viewer/static/thumbnails/3dgs-flowers.png
deleted file mode 100644
index 5eec184234037833d5adda804ceb5ae1e69dd26d..0000000000000000000000000000000000000000
Binary files a/viewer/static/thumbnails/3dgs-flowers.png and /dev/null differ
diff --git a/viewer/static/thumbnails/3dgs-garden.png b/viewer/static/thumbnails/3dgs-garden.png
deleted file mode 100644
index 5861944c94c6a0bc2ef921cac60f8706b531d63d..0000000000000000000000000000000000000000
Binary files a/viewer/static/thumbnails/3dgs-garden.png and /dev/null differ
diff --git a/viewer/static/thumbnails/3dgs-kitchen.png b/viewer/static/thumbnails/3dgs-kitchen.png
deleted file mode 100644
index 2b2b5a753dcb4db985e50a5c09a264663755c96e..0000000000000000000000000000000000000000
Binary files a/viewer/static/thumbnails/3dgs-kitchen.png and /dev/null differ
diff --git a/viewer/static/thumbnails/3dgs-playroom.png b/viewer/static/thumbnails/3dgs-playroom.png
deleted file mode 100644
index 4140f796348cd83f322763351760ce23280be740..0000000000000000000000000000000000000000
Binary files a/viewer/static/thumbnails/3dgs-playroom.png and /dev/null differ
diff --git a/viewer/static/thumbnails/3dgs-room.png b/viewer/static/thumbnails/3dgs-room.png
deleted file mode 100644
index 7b4b989b67a852c69ab30fe2f7fab1a8e1c34242..0000000000000000000000000000000000000000
Binary files a/viewer/static/thumbnails/3dgs-room.png and /dev/null differ
diff --git a/viewer/static/thumbnails/3dgs-stump.png b/viewer/static/thumbnails/3dgs-stump.png
deleted file mode 100644
index 2b8738cecb6eacc9097d6cfef1d85e2725507298..0000000000000000000000000000000000000000
Binary files a/viewer/static/thumbnails/3dgs-stump.png and /dev/null differ
diff --git a/viewer/static/thumbnails/3dgs-treehill.png b/viewer/static/thumbnails/3dgs-treehill.png
deleted file mode 100644
index 67a9a58186ca19c4ab5cf1d7ae88da9ec20b4c76..0000000000000000000000000000000000000000
Binary files a/viewer/static/thumbnails/3dgs-treehill.png and /dev/null differ
diff --git a/viewer/static/thumbnails/dreamfusion-chick.png b/viewer/static/thumbnails/dreamfusion-chick.png
deleted file mode 100755
index 1eb78ac69741ad627e948ea0a45aa66bf7edb4f0..0000000000000000000000000000000000000000
Binary files a/viewer/static/thumbnails/dreamfusion-chick.png and /dev/null differ
diff --git a/viewer/static/thumbnails/dreamfusion-corgi.png b/viewer/static/thumbnails/dreamfusion-corgi.png
deleted file mode 100755
index dcc4dd48c454c920cc95e8a692dbc90f35d739fa..0000000000000000000000000000000000000000
Binary files a/viewer/static/thumbnails/dreamfusion-corgi.png and /dev/null differ
diff --git a/viewer/static/thumbnails/dreamfusion-crab.png b/viewer/static/thumbnails/dreamfusion-crab.png
deleted file mode 100755
index 872d3df5455eae65b968d09471033f95eeefe55d..0000000000000000000000000000000000000000
Binary files a/viewer/static/thumbnails/dreamfusion-crab.png and /dev/null differ
diff --git a/viewer/static/thumbnails/dreamfusion-eagle.png b/viewer/static/thumbnails/dreamfusion-eagle.png
deleted file mode 100755
index 449502544db7674d6e5fb2bd363b846d88c63974..0000000000000000000000000000000000000000
Binary files a/viewer/static/thumbnails/dreamfusion-eagle.png and /dev/null differ
diff --git a/viewer/static/thumbnails/dreamfusion-ghost.png b/viewer/static/thumbnails/dreamfusion-ghost.png
deleted file mode 100755
index 830e77d8b99f5bbdb302d7fd1490eb56f4485872..0000000000000000000000000000000000000000
Binary files a/viewer/static/thumbnails/dreamfusion-ghost.png and /dev/null differ
diff --git a/viewer/static/thumbnails/dreamfusion-lemur.png b/viewer/static/thumbnails/dreamfusion-lemur.png
deleted file mode 100755
index 5699699fa60389f40d6aa04fdfb15b5427f36541..0000000000000000000000000000000000000000
Binary files a/viewer/static/thumbnails/dreamfusion-lemur.png and /dev/null differ
diff --git a/viewer/static/thumbnails/dreamfusion-pig.png b/viewer/static/thumbnails/dreamfusion-pig.png
deleted file mode 100755
index 1141d746d06db70edb85bf2a1fc7de12a969135e..0000000000000000000000000000000000000000
Binary files a/viewer/static/thumbnails/dreamfusion-pig.png and /dev/null differ
diff --git a/viewer/static/thumbnails/dreamfusion-sweaterfrog.png b/viewer/static/thumbnails/dreamfusion-sweaterfrog.png
deleted file mode 100755
index 3ce486afb77b3c5e7fa0f46957202590fd3a39cd..0000000000000000000000000000000000000000
Binary files a/viewer/static/thumbnails/dreamfusion-sweaterfrog.png and /dev/null differ
diff --git a/viewer/static/thumbnails/sync-dreamer-armor.png b/viewer/static/thumbnails/sync-dreamer-armor.png
deleted file mode 100755
index 0955e38286edd486e88ebd6da6f3ade39aed32cc..0000000000000000000000000000000000000000
Binary files a/viewer/static/thumbnails/sync-dreamer-armor.png and /dev/null differ
diff --git a/viewer/static/thumbnails/sync-dreamer-deer.png b/viewer/static/thumbnails/sync-dreamer-deer.png
deleted file mode 100755
index ec7825d799ce7ffadcccd5fc4ff03af6693265d3..0000000000000000000000000000000000000000
Binary files a/viewer/static/thumbnails/sync-dreamer-deer.png and /dev/null differ
diff --git a/viewer/static/thumbnails/sync-dreamer-drum.png b/viewer/static/thumbnails/sync-dreamer-drum.png
deleted file mode 100755
index 3827f01a9aa8d133843c821092f9f14e5e674ff6..0000000000000000000000000000000000000000
Binary files a/viewer/static/thumbnails/sync-dreamer-drum.png and /dev/null differ
diff --git a/viewer/static/thumbnails/sync-dreamer-forest.png b/viewer/static/thumbnails/sync-dreamer-forest.png
deleted file mode 100755
index 1b771914a1c71025f89851f7b8347008f19d8993..0000000000000000000000000000000000000000
Binary files a/viewer/static/thumbnails/sync-dreamer-forest.png and /dev/null differ
diff --git a/viewer/static/thumbnails/sync-dreamer-monkey.png b/viewer/static/thumbnails/sync-dreamer-monkey.png
deleted file mode 100755
index c87b5148825eb42c137ee7abf190a52a53b9054c..0000000000000000000000000000000000000000
Binary files a/viewer/static/thumbnails/sync-dreamer-monkey.png and /dev/null differ
diff --git a/viewer/static/thumbnails/sync-dreamer-poro.png b/viewer/static/thumbnails/sync-dreamer-poro.png
deleted file mode 100755
index 4a4e530d5c9ec3f7bd6fd4bc198c12b56ce60637..0000000000000000000000000000000000000000
Binary files a/viewer/static/thumbnails/sync-dreamer-poro.png and /dev/null differ
diff --git a/viewer/static/thumbnails/sync-dreamer-train.png b/viewer/static/thumbnails/sync-dreamer-train.png
deleted file mode 100755
index f5be286dace6e591fa8ed1ad30935775815f8513..0000000000000000000000000000000000000000
Binary files a/viewer/static/thumbnails/sync-dreamer-train.png and /dev/null differ
diff --git a/viewer/vite.config.ts b/vite.config.ts
similarity index 100%
rename from viewer/vite.config.ts
rename to vite.config.ts