diff --git a/app/admin/generate/image/page.tsx b/app/admin/generate/image/page.tsx
new file mode 100644
index 0000000..f6e9145
--- /dev/null
+++ b/app/admin/generate/image/page.tsx
@@ -0,0 +1,56 @@
+import { redirect } from "next/navigation";
+import { connection } from "next/server";
+
+import { AdminHeader } from "@/components/admin/admin-header";
+import { OverviewImageGenerator } from "@/components/admin/overview-image-generator";
+import { getAdminSession } from "@/lib/auth/server";
+import { getActiveCatalog } from "@/lib/guides/queries";
+import { elementOrder } from "@/lib/overview-image";
+
+export default async function OverviewImagePage() {
+ await connection();
+ if (!(await getAdminSession())) redirect("/admin/login");
+
+ const catalog = await getActiveCatalog();
+ if (!catalog) redirect("/admin?sync=required");
+
+ const elementIconUrls = Object.fromEntries(
+ elementOrder.map((element) => [
+ element,
+ catalog.characters.find(
+ (character) =>
+ character.element === element && character.elementIconUrl,
+ )?.elementIconUrl ?? null,
+ ]),
+ );
+
+ return (
+
+
+
+
+ ({
+ key: character.key,
+ name: character.name,
+ aliases: character.aliases,
+ element: character.element,
+ rarity: character.rarity,
+ weaponType: character.weaponType,
+ imageUrl: character.imageUrl,
+ nameCardImageUrl: character.nameCardImageUrl,
+ }))}
+ elementIconUrls={elementIconUrls}
+ />
+
+
+ );
+}
diff --git a/app/globals.css b/app/globals.css
index ffed109..ded48a2 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -3,6 +3,12 @@
@import "tw-animate-css";
@import "shadcn/tailwind.css";
+@font-face {
+ font-family: "Genshin Overview";
+ src: url("/zh-cn.ttf") format("truetype");
+ font-display: swap;
+}
+
@custom-variant dark (&:is(.dark *));
@theme inline {
diff --git a/bun.lock b/bun.lock
index 118ba50..a04c6fa 100644
--- a/bun.lock
+++ b/bun.lock
@@ -9,6 +9,7 @@
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
+ "@origin-space/image-cropper": "^0.1.9",
"@tailwindcss/typography": "0.5.20",
"@tiptap/core": "3.31.3",
"@tiptap/extension-link": "3.31.3",
@@ -380,6 +381,8 @@
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.43.0", "", {}, "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg=="],
+ "@origin-space/image-cropper": ["@origin-space/image-cropper@0.1.9", "", { "peerDependencies": { "react": "^19.1.0", "react-dom": "^19.1.0" } }, "sha512-Ebl57I6beh7uKjSv8+r3VObEoOXIXhx4SicDUdxa9U7NVY18SzEGfqPDuhNyXuFIB5G/S8nYUToNlIRp9wwB9Q=="],
+
"@oxc-project/types": ["@oxc-project/types@0.147.0", "", {}, "sha512-IJ3s6ltHLp45S0bh7phkX+gJO7A1Wuz2EaqpAhb8WjqDwbzMiWKHhyyT42tskaWjEYXtHtVCPpnBJVT9+dcRLg=="],
"@radix-ui/primitive": ["@radix-ui/primitive@1.1.7", "", {}, "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q=="],
diff --git a/components/admin/admin-header.tsx b/components/admin/admin-header.tsx
index 5957447..79ac6e2 100644
--- a/components/admin/admin-header.tsx
+++ b/components/admin/admin-header.tsx
@@ -5,6 +5,7 @@ import Link from "next/link";
import { usePathname, useRouter } from "next/navigation";
import {
ExternalLinkIcon,
+ ImageIcon,
LayoutDashboardIcon,
BookOpenIcon,
LogOutIcon,
@@ -66,6 +67,17 @@ export function AdminHeader() {
New Guide
+ }
+ >
+
+ Image Generator
+