diff --git a/patch.js b/patch.js
new file mode 100644
index 0000000..e58ec7b
--- /dev/null
+++ b/patch.js
@@ -0,0 +1,78 @@
+const fs = require('fs');
+
+// Patch 1: scanner
+let scan = fs.readFileSync('src/lib/scanner/index.ts', 'utf8');
+const oldLoop = scan.substring(scan.indexOf('// 3. Find new and changed files'), scan.indexOf('// 4. Find removed files'));
+const newLoop = ` // 3. Find new and changed files
+ const newFiles = [];
+ for (const [relPath, file] of scannedFiles) {
+ const existing = dbPaths.get(relPath);
+ if (!existing) {
+ newFiles.push(file);
+ } else {
+ unchanged++;
+ dbPaths.delete(relPath);
+ }
+ }
+
+ const stableResults = await Promise.all(
+ newFiles.map(async (file) => {
+ const isStable = await checkFileStability(file.absolutePath);
+ return { file, isStable };
+ })
+ );
+
+ for (const { file, isStable } of stableResults) {
+ const relPath = file.relativePath;
+ if (!isStable) {
+ console.log('Skipping unstable file:', relPath);
+ continue;
+ }
+
+ try {
+ const mediaType = getMediaType(file.ext);
+ const mimeType = getMimeType(file.ext);
+
+ await prisma.mediaItem.create({
+ data: {
+ relativePath: relPath,
+ filename: file.filename,
+ directory: file.directory,
+ mediaType,
+ mimeType,
+ fileSize: file.size,
+ lastModified: file.mtime,
+ lastScanned: new Date(),
+ published: false,
+ },
+ });
+
+ await enqueueProcessing(relPath, mediaType);
+ added++;
+ console.log('Added:', relPath);
+ } catch (error) {
+ const msg = 'Failed to add ' + relPath + ': ' + (error instanceof Error ? error.message : String(error));
+ errors.push(msg);
+ console.error(msg);
+ }
+ }
+
+`;
+scan = scan.replace(oldLoop, newLoop);
+fs.writeFileSync('src/lib/scanner/index.ts', scan, 'utf8');
+
+// Patch 2: media API
+let mediaApi = fs.readFileSync('src/app/api/media/route.ts', 'utf8');
+mediaApi = mediaApi.replace(
+ 'return NextResponse.json({\n items,\n nextCursor,\n votingOpen: settings?.votingOpen ?? false,\n });',
+ 'return NextResponse.json({ items: JSON.parse(JSON.stringify(items, (k, v) => typeof v === \'bigint\' ? Number(v) : v)), nextCursor, votingOpen: settings?.votingOpen ?? false });'
+);
+fs.writeFileSync('src/app/api/media/route.ts', mediaApi, 'utf8');
+
+// Patch 3: media ID update API
+let mediaIdApi = fs.readFileSync('src/app/api/admin/media/[id]/route.ts', 'utf8');
+mediaIdApi = mediaIdApi.replace(
+ 'return NextResponse.json(updated);',
+ 'return NextResponse.json(JSON.parse(JSON.stringify(updated, (k, v) => typeof v === \'bigint\' ? Number(v) : v)));'
+);
+fs.writeFileSync('src/app/api/admin/media/[id]/route.ts', mediaIdApi, 'utf8');
diff --git a/public/favicon.svg b/public/favicon.svg
deleted file mode 100644
index 2184e45..0000000
--- a/public/favicon.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/restore.js b/restore.js
new file mode 100644
index 0000000..303d95a
--- /dev/null
+++ b/restore.js
@@ -0,0 +1,64 @@
+const fs = require('fs');
+
+const routeTsContent = `import { NextRequest, NextResponse } from 'next/server';
+import { prisma } from '@/lib/db';
+import { requireAuth } from '@/lib/auth/session';
+import { scheduleZipRebuild } from '@/lib/jobs/queue';
+
+export async function PATCH(
+ request: NextRequest,
+ { params }: { params: { id: string } }
+) {
+ const authError = await requireAuth(request);
+ if (authError) return authError;
+
+ const id = parseInt(params.id, 10);
+ if (isNaN(id)) {
+ return NextResponse.json({ error: 'Invalid ID' }, { status: 400 });
+ }
+
+ try {
+ const body = await request.json();
+ const {
+ published,
+ featured,
+ placementMode,
+ gridColumn,
+ gridRow,
+ gridWidth,
+ gridHeight,
+ sortOrder
+ } = body;
+
+ const currentItem = await prisma.mediaItem.findUnique({ where: { id } });
+ if (!currentItem) {
+ return NextResponse.json({ error: 'Not found' }, { status: 404 });
+ }
+
+ const updated = await prisma.mediaItem.update({
+ where: { id },
+ data: {
+ ...(published !== undefined && { published }),
+ ...(featured !== undefined && { featured }),
+ ...(placementMode !== undefined && { placementMode }),
+ ...(gridColumn !== undefined && { gridColumn }),
+ ...(gridRow !== undefined && { gridRow }),
+ ...(gridWidth !== undefined && { gridWidth }),
+ ...(gridHeight !== undefined && { gridHeight }),
+ ...(sortOrder !== undefined && { sortOrder }),
+ }
+ });
+
+ if (published !== undefined && published !== currentItem.published) {
+ await scheduleZipRebuild();
+ }
+
+ return NextResponse.json(JSON.parse(JSON.stringify(updated, (k, v) => typeof v === 'bigint' ? Number(v) : v)));
+ } catch (error) {
+ console.error('Error updating media item:', error);
+ return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
+ }
+}
+`;
+
+fs.writeFileSync('src/app/api/admin/media/[id]/route.ts', routeTsContent, 'utf8');
diff --git a/src/app/icon.svg b/src/app/icon.svg
new file mode 100644
index 0000000..6c132c3
--- /dev/null
+++ b/src/app/icon.svg
@@ -0,0 +1,6 @@
+