feat :idk
This commit is contained in:
@@ -92,5 +92,8 @@ export default async function Image({
|
|||||||
return generateOgImage({
|
return generateOgImage({
|
||||||
title: guide.name,
|
title: guide.name,
|
||||||
sub: `${sectionTitle(page, section.title)} · ${content.highlights.join(" · ") || content.description}`,
|
sub: `${sectionTitle(page, section.title)} · ${content.highlights.join(" · ") || content.description}`,
|
||||||
|
characterCard: true,
|
||||||
|
backgroundUrl: guide.character.nameCardImageUrl,
|
||||||
|
characterUrl: guide.character.coopImageUrl ?? guide.character.imageUrl,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,5 +18,11 @@ export default async function Image({
|
|||||||
await connection();
|
await connection();
|
||||||
const guide = await getCachedPublicGuide((await params).character);
|
const guide = await getCachedPublicGuide((await params).character);
|
||||||
if (!guide) return new Response("Not found", { status: 404 });
|
if (!guide) return new Response("Not found", { status: 404 });
|
||||||
return generateOgImage({ title: guide.name, sub: guide.overview });
|
return generateOgImage({
|
||||||
|
title: guide.name,
|
||||||
|
sub: guide.overview,
|
||||||
|
characterCard: true,
|
||||||
|
backgroundUrl: guide.character.nameCardImageUrl,
|
||||||
|
characterUrl: guide.character.coopImageUrl ?? guide.character.imageUrl,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -455,6 +455,8 @@ export const catalogCharacters = catalogSchema.table(
|
|||||||
weaponType: varchar("weapon_type", { length: 64 }),
|
weaponType: varchar("weapon_type", { length: 64 }),
|
||||||
imageKey: text("image_key").notNull(),
|
imageKey: text("image_key").notNull(),
|
||||||
elementIconKey: text("element_icon_key"),
|
elementIconKey: text("element_icon_key"),
|
||||||
|
coopImageKey: text("coop_image_key"),
|
||||||
|
nameCardImageKey: text("name_card_image_key"),
|
||||||
constellations: jsonb("constellations").$type<CatalogConstellation[]>().default([]).notNull(),
|
constellations: jsonb("constellations").$type<CatalogConstellation[]>().default([]).notNull(),
|
||||||
talents: jsonb("talents").$type<CatalogTalent[]>().default([]).notNull(),
|
talents: jsonb("talents").$type<CatalogTalent[]>().default([]).notNull(),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE "catalog"."catalog_character" ADD COLUMN "coop_image_key" text;--> statement-breakpoint
|
||||||
|
ALTER TABLE "catalog"."catalog_character" ADD COLUMN "name_card_image_key" text;
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -85,6 +85,13 @@
|
|||||||
"when": 1788107548585,
|
"when": 1788107548585,
|
||||||
"tag": "0011_nervous_quasimodo",
|
"tag": "0011_nervous_quasimodo",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 12,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1788177201633,
|
||||||
|
"tag": "0012_left_bishop",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,8 @@ import {
|
|||||||
characterDetailSchema,
|
characterDetailSchema,
|
||||||
cleanLunarisText,
|
cleanLunarisText,
|
||||||
constellationSnapshot,
|
constellationSnapshot,
|
||||||
|
nameCardImageName,
|
||||||
|
splashImageName,
|
||||||
supportsWeaponType,
|
supportsWeaponType,
|
||||||
talentSnapshot,
|
talentSnapshot,
|
||||||
} from "@/lib/catalog/lunaris";
|
} from "@/lib/catalog/lunaris";
|
||||||
@@ -16,6 +18,18 @@ describe("Lunaris character data", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("maps CardImg names to Lunaris character card assets", () => {
|
||||||
|
expect(splashImageName("UI_Gacha_AvatarIcon_Ayaka")).toBe(
|
||||||
|
"UI_Gacha_AvatarImg_Ayaka.png",
|
||||||
|
);
|
||||||
|
expect(nameCardImageName("UI_Gacha_AvatarIcon_Ayaka")).toBe(
|
||||||
|
"UI_NameCardPic_Ayaka_P.png",
|
||||||
|
);
|
||||||
|
expect(nameCardImageName("UI_Gacha_AvatarIcon_Qin")).toBe(
|
||||||
|
"UI_NameCardPic_Qin_P.png",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("removes display markup from constellation text", () => {
|
it("removes display markup from constellation text", () => {
|
||||||
expect(cleanLunarisText('"A {LINK#N1}<color=#fff>bright</color>{/LINK} morning"')).toBe("A bright morning");
|
expect(cleanLunarisText('"A {LINK#N1}<color=#fff>bright</color>{/LINK} morning"')).toBe("A bright morning");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -52,6 +52,18 @@ export function avatarIconName(value: string): string {
|
|||||||
return value.replace(/^UI_Gacha_AvatarIcon_/u, "UI_AvatarIcon_");
|
return value.replace(/^UI_Gacha_AvatarIcon_/u, "UI_AvatarIcon_");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function characterAssetName(value: string): string {
|
||||||
|
return avatarIconName(value).replace(/^UI_AvatarIcon_/u, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function splashImageName(value: string): string {
|
||||||
|
return `UI_Gacha_AvatarImg_${characterAssetName(value)}.png`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function nameCardImageName(value: string): string {
|
||||||
|
return `UI_NameCardPic_${characterAssetName(value)}_P.png`;
|
||||||
|
}
|
||||||
|
|
||||||
export function supportsWeaponType(
|
export function supportsWeaponType(
|
||||||
characterWeaponType: string | null | undefined,
|
characterWeaponType: string | null | undefined,
|
||||||
weaponType: string | null | undefined,
|
weaponType: string | null | undefined,
|
||||||
|
|||||||
+40
-3
@@ -15,6 +15,8 @@ import {
|
|||||||
avatarIconName,
|
avatarIconName,
|
||||||
characterDetailSchema,
|
characterDetailSchema,
|
||||||
constellationSnapshot,
|
constellationSnapshot,
|
||||||
|
nameCardImageName,
|
||||||
|
splashImageName,
|
||||||
talentSnapshot,
|
talentSnapshot,
|
||||||
} from "@/lib/catalog/lunaris";
|
} from "@/lib/catalog/lunaris";
|
||||||
import type { CatalogConstellation, CatalogTalent } from "@/lib/catalog/lunaris";
|
import type { CatalogConstellation, CatalogTalent } from "@/lib/catalog/lunaris";
|
||||||
@@ -56,7 +58,11 @@ interface SyncedCharacter {
|
|||||||
weaponType: string | null;
|
weaponType: string | null;
|
||||||
imageKey: string;
|
imageKey: string;
|
||||||
elementIconKey: string | null;
|
elementIconKey: string | null;
|
||||||
|
coopImageKey: string | null;
|
||||||
|
nameCardImageKey: string | null;
|
||||||
sourceImage: string | null;
|
sourceImage: string | null;
|
||||||
|
coopSourceImage: string | null;
|
||||||
|
nameCardSourceImage: string | null;
|
||||||
localImage: string | null;
|
localImage: string | null;
|
||||||
constellations: CatalogConstellation[];
|
constellations: CatalogConstellation[];
|
||||||
talents: CatalogTalent[];
|
talents: CatalogTalent[];
|
||||||
@@ -154,12 +160,18 @@ export async function processCatalogSyncJob(jobId: string): Promise<void> {
|
|||||||
const detailRaw = await getOptionalJson(`${API_ROOT}/${version}/en/char/${encodeURIComponent(key)}.json`);
|
const detailRaw = await getOptionalJson(`${API_ROOT}/${version}/en/char/${encodeURIComponent(key)}.json`);
|
||||||
const detail = detailRaw ? characterDetailSchema.parse(detailRaw) : null;
|
const detail = detailRaw ? characterDetailSchema.parse(detailRaw) : null;
|
||||||
const avatarIcon = webpName(avatarIconName(value.CardImg));
|
const avatarIcon = webpName(avatarIconName(value.CardImg));
|
||||||
|
const splashImage = splashImageName(value.CardImg);
|
||||||
|
const nameCardImage = nameCardImageName(value.CardImg);
|
||||||
const element = detail?.info?.element ?? value.element ?? null;
|
const element = detail?.info?.element ?? value.element ?? null;
|
||||||
return {
|
return {
|
||||||
version, key, name: value.enName, element, rarity: rarity(value.qualityType), weaponType: detail?.info?.weapon ?? value.weaponType ?? null,
|
version, key, name: value.enName, element, rarity: rarity(value.qualityType), weaponType: detail?.info?.weapon ?? value.weaponType ?? null,
|
||||||
imageKey: `catalog/${version}/characters/${avatarIcon}`,
|
imageKey: `catalog/${version}/characters/${avatarIcon}`,
|
||||||
elementIconKey: element ? `catalog/${version}/elements/${element.toLowerCase()}.png` : null,
|
elementIconKey: element ? `catalog/${version}/elements/${element.toLowerCase()}.png` : null,
|
||||||
|
coopImageKey: `catalog/${version}/characters/splash/${splashImage}`,
|
||||||
|
nameCardImageKey: `catalog/${version}/characters/namecard/${nameCardImage}`,
|
||||||
sourceImage: `${API_ROOT}/assets/avataricon/${encodeURIComponent(avatarIcon)}`,
|
sourceImage: `${API_ROOT}/assets/avataricon/${encodeURIComponent(avatarIcon)}`,
|
||||||
|
coopSourceImage: `${API_ROOT}/assets/gachaimg/${encodeURIComponent(splashImage)}`,
|
||||||
|
nameCardSourceImage: `${API_ROOT}/assets/namecardpic/${encodeURIComponent(nameCardImage)}`,
|
||||||
localImage: null,
|
localImage: null,
|
||||||
constellations: detail ? constellationSnapshot(detail, (icon) => `catalog/${version}/constellations/${webpName(icon)}`) : [],
|
constellations: detail ? constellationSnapshot(detail, (icon) => `catalog/${version}/constellations/${webpName(icon)}`) : [],
|
||||||
talents: detail ? talentSnapshot(detail, (icon) => `catalog/${version}/talents/${webpName(icon)}`) : [],
|
talents: detail ? talentSnapshot(detail, (icon) => `catalog/${version}/talents/${webpName(icon)}`) : [],
|
||||||
@@ -177,7 +189,11 @@ export async function processCatalogSyncJob(jobId: string): Promise<void> {
|
|||||||
weaponType: "WEAPON_SWORD_ONE_HAND",
|
weaponType: "WEAPON_SWORD_ONE_HAND",
|
||||||
imageKey: `catalog/${version}/characters/${traveler.asset}`,
|
imageKey: `catalog/${version}/characters/${traveler.asset}`,
|
||||||
elementIconKey: `catalog/${version}/elements/${traveler.element.toLowerCase()}.png`,
|
elementIconKey: `catalog/${version}/elements/${traveler.element.toLowerCase()}.png`,
|
||||||
|
coopImageKey: null,
|
||||||
|
nameCardImageKey: null,
|
||||||
sourceImage: null,
|
sourceImage: null,
|
||||||
|
coopSourceImage: null,
|
||||||
|
nameCardSourceImage: null,
|
||||||
localImage: traveler.asset,
|
localImage: traveler.asset,
|
||||||
constellations: constellationSnapshot(detail, (icon) => `catalog/${version}/constellations/${webpName(icon)}`),
|
constellations: constellationSnapshot(detail, (icon) => `catalog/${version}/constellations/${webpName(icon)}`),
|
||||||
talents: talentSnapshot(detail, (icon) => `catalog/${version}/talents/${webpName(icon)}`),
|
talents: talentSnapshot(detail, (icon) => `catalog/${version}/talents/${webpName(icon)}`),
|
||||||
@@ -200,6 +216,20 @@ export async function processCatalogSyncJob(jobId: string): Promise<void> {
|
|||||||
body: await readFile(join(process.cwd(), "lib/catalog/assets", character.localImage)),
|
body: await readFile(join(process.cwd(), "lib/catalog/assets", character.localImage)),
|
||||||
target: character.imageKey,
|
target: character.imageKey,
|
||||||
} : { source: character.sourceImage!, target: character.imageKey });
|
} : { source: character.sourceImage!, target: character.imageKey });
|
||||||
|
if (character.coopImageKey && character.coopSourceImage) {
|
||||||
|
uniqueAssets.set(character.coopImageKey, {
|
||||||
|
source: character.coopSourceImage,
|
||||||
|
target: character.coopImageKey,
|
||||||
|
type: "image/png",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (character.nameCardImageKey && character.nameCardSourceImage) {
|
||||||
|
uniqueAssets.set(character.nameCardImageKey, {
|
||||||
|
source: character.nameCardSourceImage,
|
||||||
|
target: character.nameCardImageKey,
|
||||||
|
type: "image/png",
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (const item of [...weapons, ...artifacts]) uniqueAssets.set(item.imageKey, { source: item.sourceImage, target: item.imageKey });
|
for (const item of [...weapons, ...artifacts]) uniqueAssets.set(item.imageKey, { source: item.sourceImage, target: item.imageKey });
|
||||||
for (const character of characters) {
|
for (const character of characters) {
|
||||||
@@ -225,13 +255,20 @@ export async function processCatalogSyncJob(jobId: string): Promise<void> {
|
|||||||
}
|
}
|
||||||
const missingAssets = await uploadAssets(jobId, [...uniqueAssets.values()]);
|
const missingAssets = await uploadAssets(jobId, [...uniqueAssets.values()]);
|
||||||
characters = characters.filter((item) => !missingAssets.has(item.imageKey));
|
characters = characters.filter((item) => !missingAssets.has(item.imageKey));
|
||||||
characters = characters.map((item) => ({ ...item, elementIconKey: item.elementIconKey && !missingAssets.has(item.elementIconKey) ? item.elementIconKey : null, constellations: item.constellations.map((constellation) => ({ ...constellation, iconKey: constellation.iconKey && !missingAssets.has(constellation.iconKey) ? constellation.iconKey : null })), talents: item.talents.map((talent) => ({ ...talent, iconKey: talent.iconKey && !missingAssets.has(talent.iconKey) ? talent.iconKey : null })) }));
|
characters = characters.map((item) => ({
|
||||||
|
...item,
|
||||||
|
elementIconKey: item.elementIconKey && !missingAssets.has(item.elementIconKey) ? item.elementIconKey : null,
|
||||||
|
coopImageKey: item.coopImageKey && !missingAssets.has(item.coopImageKey) ? item.coopImageKey : null,
|
||||||
|
nameCardImageKey: item.nameCardImageKey && !missingAssets.has(item.nameCardImageKey) ? item.nameCardImageKey : null,
|
||||||
|
constellations: item.constellations.map((constellation) => ({ ...constellation, iconKey: constellation.iconKey && !missingAssets.has(constellation.iconKey) ? constellation.iconKey : null })),
|
||||||
|
talents: item.talents.map((talent) => ({ ...talent, iconKey: talent.iconKey && !missingAssets.has(talent.iconKey) ? talent.iconKey : null })),
|
||||||
|
}));
|
||||||
weapons = weapons.filter((item) => !missingAssets.has(item.imageKey));
|
weapons = weapons.filter((item) => !missingAssets.has(item.imageKey));
|
||||||
artifacts = artifacts.filter((item) => !missingAssets.has(item.imageKey));
|
artifacts = artifacts.filter((item) => !missingAssets.has(item.imageKey));
|
||||||
|
|
||||||
const storage = await getMediaStorage();
|
const storage = await getMediaStorage();
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
storage.write(`catalog/${version}/characters.json`, JSON.stringify(characters.map((item) => ({ version: item.version, key: item.key, name: item.name, element: item.element, rarity: item.rarity, weaponType: item.weaponType, imageKey: item.imageKey, elementIconKey: item.elementIconKey, constellations: item.constellations, talents: item.talents }))), { type: "application/json", acl: "public-read" }),
|
storage.write(`catalog/${version}/characters.json`, JSON.stringify(characters.map((item) => ({ version: item.version, key: item.key, name: item.name, element: item.element, rarity: item.rarity, weaponType: item.weaponType, imageKey: item.imageKey, elementIconKey: item.elementIconKey, coopImageKey: item.coopImageKey, nameCardImageKey: item.nameCardImageKey, constellations: item.constellations, talents: item.talents }))), { type: "application/json", acl: "public-read" }),
|
||||||
storage.write(`catalog/${version}/weapons.json`, JSON.stringify(weapons.map((item) => ({ version: item.version, key: item.key, name: item.name, rarity: item.rarity, weaponType: item.weaponType, imageKey: item.imageKey }))), { type: "application/json", acl: "public-read" }),
|
storage.write(`catalog/${version}/weapons.json`, JSON.stringify(weapons.map((item) => ({ version: item.version, key: item.key, name: item.name, rarity: item.rarity, weaponType: item.weaponType, imageKey: item.imageKey }))), { type: "application/json", acl: "public-read" }),
|
||||||
storage.write(`catalog/${version}/artifacts.json`, JSON.stringify(artifacts.map((item) => ({ version: item.version, key: item.key, name: item.name, rarity: item.rarity, imageKey: item.imageKey }))), { type: "application/json", acl: "public-read" }),
|
storage.write(`catalog/${version}/artifacts.json`, JSON.stringify(artifacts.map((item) => ({ version: item.version, key: item.key, name: item.name, rarity: item.rarity, imageKey: item.imageKey }))), { type: "application/json", acl: "public-read" }),
|
||||||
]);
|
]);
|
||||||
@@ -241,7 +278,7 @@ export async function processCatalogSyncJob(jobId: string): Promise<void> {
|
|||||||
await tx.delete(catalogCharacters).where(eq(catalogCharacters.version, version));
|
await tx.delete(catalogCharacters).where(eq(catalogCharacters.version, version));
|
||||||
await tx.delete(catalogWeapons).where(eq(catalogWeapons.version, version));
|
await tx.delete(catalogWeapons).where(eq(catalogWeapons.version, version));
|
||||||
await tx.delete(catalogArtifacts).where(eq(catalogArtifacts.version, version));
|
await tx.delete(catalogArtifacts).where(eq(catalogArtifacts.version, version));
|
||||||
if (characters.length) await tx.insert(catalogCharacters).values(characters.map((item) => ({ version: item.version, key: item.key, name: item.name, element: item.element, rarity: item.rarity, weaponType: item.weaponType, imageKey: item.imageKey, elementIconKey: item.elementIconKey, constellations: item.constellations, talents: item.talents })));
|
if (characters.length) await tx.insert(catalogCharacters).values(characters.map((item) => ({ version: item.version, key: item.key, name: item.name, element: item.element, rarity: item.rarity, weaponType: item.weaponType, imageKey: item.imageKey, elementIconKey: item.elementIconKey, coopImageKey: item.coopImageKey, nameCardImageKey: item.nameCardImageKey, constellations: item.constellations, talents: item.talents })));
|
||||||
if (weapons.length) await tx.insert(catalogWeapons).values(weapons.map((item) => ({ version: item.version, key: item.key, name: item.name, rarity: item.rarity, weaponType: item.weaponType, imageKey: item.imageKey })));
|
if (weapons.length) await tx.insert(catalogWeapons).values(weapons.map((item) => ({ version: item.version, key: item.key, name: item.name, rarity: item.rarity, weaponType: item.weaponType, imageKey: item.imageKey })));
|
||||||
if (artifacts.length) await tx.insert(catalogArtifacts).values(artifacts.map((item) => ({ version: item.version, key: item.key, name: item.name, rarity: item.rarity, imageKey: item.imageKey })));
|
if (artifacts.length) await tx.insert(catalogArtifacts).values(artifacts.map((item) => ({ version: item.version, key: item.key, name: item.name, rarity: item.rarity, imageKey: item.imageKey })));
|
||||||
await tx.update(catalogVersions).set({ active: false });
|
await tx.update(catalogVersions).set({ active: false });
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ function catalogCharacterWithUrls(item: typeof catalogCharacters.$inferSelect) {
|
|||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
imageUrl: publicMediaUrl(item.imageKey),
|
imageUrl: publicMediaUrl(item.imageKey),
|
||||||
|
coopImageUrl: item.coopImageKey ? publicMediaUrl(item.coopImageKey) : null,
|
||||||
|
nameCardImageUrl: item.nameCardImageKey ? publicMediaUrl(item.nameCardImageKey) : null,
|
||||||
elementIconUrl: item.elementIconKey ? publicMediaUrl(item.elementIconKey) : null,
|
elementIconUrl: item.elementIconKey ? publicMediaUrl(item.elementIconKey) : null,
|
||||||
constellations: item.constellations.map((constellation) => ({
|
constellations: item.constellations.map((constellation) => ({
|
||||||
...constellation,
|
...constellation,
|
||||||
|
|||||||
+78
-7
@@ -4,12 +4,14 @@ import { readFile } from "node:fs/promises";
|
|||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
|
|
||||||
import { ImageResponse } from "next/og";
|
import { ImageResponse } from "next/og";
|
||||||
|
import sharp from "sharp";
|
||||||
|
|
||||||
export const ogImageSize = { width: 1222, height: 560 } as const;
|
export const ogImageSize = { width: 1222, height: 560 } as const;
|
||||||
export const ogImageContentType = "image/png";
|
export const ogImageContentType = "image/png";
|
||||||
|
|
||||||
let bannerDataPromise: Promise<string> | undefined;
|
let bannerDataPromise: Promise<string> | undefined;
|
||||||
let fontDataPromise: Promise<Buffer> | undefined;
|
let fontDataPromise: Promise<Buffer> | undefined;
|
||||||
|
const imageDataPromises = new Map<string, Promise<string | null>>();
|
||||||
|
|
||||||
function shorten(value: string, length: number): string {
|
function shorten(value: string, length: number): string {
|
||||||
const normalized = value.replace(/\s+/gu, " ").trim();
|
const normalized = value.replace(/\s+/gu, " ").trim();
|
||||||
@@ -31,14 +33,44 @@ function getFontData(): Promise<Buffer> {
|
|||||||
return fontDataPromise;
|
return fontDataPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getImageData(url: string): Promise<string | null> {
|
||||||
|
const cached = imageDataPromises.get(url);
|
||||||
|
if (cached) return cached;
|
||||||
|
|
||||||
|
const promise = fetch(url)
|
||||||
|
.then(async (response) => {
|
||||||
|
if (!response.ok) return null;
|
||||||
|
const png = await sharp(Buffer.from(await response.arrayBuffer()))
|
||||||
|
.png()
|
||||||
|
.toBuffer();
|
||||||
|
return `data:image/png;base64,${png.toString("base64")}`;
|
||||||
|
})
|
||||||
|
.catch(() => null);
|
||||||
|
imageDataPromises.set(url, promise);
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
export async function generateOgImage({
|
export async function generateOgImage({
|
||||||
title,
|
title,
|
||||||
sub,
|
sub,
|
||||||
|
characterCard = false,
|
||||||
|
backgroundUrl,
|
||||||
|
characterUrl,
|
||||||
}: {
|
}: {
|
||||||
title: string;
|
title: string;
|
||||||
sub: string;
|
sub: string;
|
||||||
|
characterCard?: boolean;
|
||||||
|
backgroundUrl?: string | null;
|
||||||
|
characterUrl?: string | null;
|
||||||
}) {
|
}) {
|
||||||
const [bannerSrc, kanit] = await Promise.all([getBannerData(), getFontData()]);
|
const [bannerSrc, kanit, characterBackgroundSrc, characterArtSrc] =
|
||||||
|
await Promise.all([
|
||||||
|
characterCard ? null : getBannerData(),
|
||||||
|
getFontData(),
|
||||||
|
characterCard && backgroundUrl ? getImageData(backgroundUrl) : null,
|
||||||
|
characterCard && characterUrl ? getImageData(characterUrl) : null,
|
||||||
|
]);
|
||||||
|
const backgroundSrc = characterCard ? characterBackgroundSrc : bannerSrc;
|
||||||
|
|
||||||
return new ImageResponse(
|
return new ImageResponse(
|
||||||
<div
|
<div
|
||||||
@@ -48,26 +80,65 @@ export async function generateOgImage({
|
|||||||
height: "560px",
|
height: "560px",
|
||||||
fontFamily: "Kanit",
|
fontFamily: "Kanit",
|
||||||
color: "#fff",
|
color: "#fff",
|
||||||
|
position: "relative",
|
||||||
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img src={bannerSrc} alt="Banner" width={1222} height={560} />
|
{backgroundSrc ? (
|
||||||
|
<img src={backgroundSrc} alt="" width={1222} height={560} style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover" }} />
|
||||||
|
) : (
|
||||||
|
<div style={{ position: "absolute", inset: 0, background: "#111827" }} />
|
||||||
|
)}
|
||||||
|
{characterCard && characterArtSrc ? (
|
||||||
|
<img
|
||||||
|
src={characterArtSrc}
|
||||||
|
alt=""
|
||||||
|
width={480}
|
||||||
|
height={360}
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
width: "58%",
|
||||||
|
height: "100%",
|
||||||
|
objectFit: "cover",
|
||||||
|
objectPosition: "center",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{characterCard ? (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
fontSize: 64,
|
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: "160px",
|
inset: 0,
|
||||||
|
background: "linear-gradient(90deg, rgba(3, 8, 18, 0.88) 0%, rgba(3, 8, 18, 0.62) 45%, rgba(3, 8, 18, 0.08) 100%)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: characterCard ? "52px" : "160px",
|
||||||
left: "60px",
|
left: "60px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
right: "450px",
|
right: characterCard ? "560px" : "450px",
|
||||||
bottom: "100px",
|
bottom: "100px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>{shorten(title, 24)}</div>
|
{characterCard ? (
|
||||||
|
<div style={{ display: "flex", color: "#d8f4ee", fontSize: 28 }}>
|
||||||
|
เกนชินไม่ใช่เกมมือถือไกด์
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div style={{ display: "flex", fontSize: 64, marginTop: characterCard ? "8px" : 0 }}>
|
||||||
|
{shorten(title, 24)}
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
display: "flex",
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
marginTop: "-20px",
|
marginTop: characterCard ? "8px" : "-20px",
|
||||||
color: "#aaa",
|
color: "#aaa",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user