Files
buzz-sheet/app/api/teams/[teamId]/image/route.tsx
T
gunshiz 17214a3625
CI / Verify (push) Successful in 1m6s
CI / Build immutable images and deploy (push) Successful in 2m19s
feat(guides): organize schemas and export teams
2026-08-29 18:52:01 +00:00

125 lines
4.9 KiB
TypeScript

/* eslint-disable @next/next/no-img-element */
import { ImageResponse } from "next/og";
import { getPublicTeamExport } from "@/lib/guides/queries";
const anuphanFont = fetch(
"https://raw.githubusercontent.com/google/fonts/main/ofl/anuphan/Anuphan%5Bwght%5D.ttf",
)
.then((response) => (response.ok ? response.arrayBuffer() : null))
.catch(() => null);
function amount(value: number): string {
return new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }).format(value);
}
export async function GET(
_request: Request,
context: RouteContext<"/api/teams/[teamId]/image">,
) {
const { teamId } = await context.params;
const team = await getPublicTeamExport(teamId);
if (!team) return new Response("Team not found", { status: 404 });
const total = team.members.reduce(
(sum, member) => sum + Number(member.damage),
0,
);
const seconds = Number(team.rotationSeconds);
const dps = seconds > 0 ? total / seconds : 0;
const font = await anuphanFont;
return new ImageResponse(
<div
style={{
width: "100%",
height: "100%",
display: "flex",
flexDirection: "column",
color: "#f4fbfa",
background:
"radial-gradient(circle at 50% 0%, #16454a 0%, #0a2429 38%, #06171b 100%)",
border: "2px solid #3aaaa2",
padding: "34px 36px",
fontFamily: "Anuphan",
}}
>
<div style={{ display: "flex", flexDirection: "column", textAlign: "center" }}>
<div style={{ display: "flex", justifyContent: "center", fontSize: 42, fontWeight: 700 }}>
{team.guideName}
</div>
<div style={{ display: "flex", justifyContent: "center", color: "#82d6ce", fontSize: 20 }}>
{team.title} · {seconds.toLocaleString(undefined, { maximumFractionDigits: 2 })}s rotation
</div>
</div>
<div style={{ display: "flex", marginTop: 30, color: "#a6d8d4", fontSize: 22, fontWeight: 600 }}>
Damage Share
</div>
<div style={{ display: "flex", flexDirection: "column", gap: 16, marginTop: 10 }}>
{team.members.map((member) => {
const damage = Number(member.damage);
const percent = total > 0 ? (damage / total) * 100 : 0;
return (
<div key={member.position} style={{ display: "flex", alignItems: "center", gap: 14 }}>
<img
src={member.character.imageUrl}
alt=""
width="78"
height="78"
style={{ borderRadius: 16, border: "2px solid #78cbc4", objectFit: "cover" }}
/>
<div style={{ display: "flex", flex: 1, flexDirection: "column", gap: 6 }}>
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
<div style={{ display: "flex", fontSize: 20, fontWeight: 600 }}>{member.character.name}</div>
<div style={{ display: "flex", color: "#d9f6f2", fontSize: 18 }}>{amount(damage)}</div>
</div>
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
<div style={{ display: "flex", flex: 1, height: 24, overflow: "hidden", borderRadius: 8, background: "#17383d" }}>
<div
style={{
display: "flex",
width: `${Math.max(2, Math.min(100, percent))}%`,
height: "100%",
borderRadius: 8,
background: "linear-gradient(90deg, #55b7ae 0%, #8be0d7 100%)",
}}
/>
</div>
<div style={{ display: "flex", width: 66, justifyContent: "flex-end", fontSize: 20, fontWeight: 700 }}>
{percent.toFixed(1)}%
</div>
</div>
</div>
</div>
);
})}
</div>
<div style={{ display: "flex", flex: 1 }} />
<div style={{ display: "flex", justifyContent: "center", gap: 16, borderTop: "1px solid #28555a", paddingTop: 20, fontSize: 26, fontWeight: 700 }}>
<span style={{ color: "#f0c95f" }}>DPS: {amount(dps)}</span>
<span style={{ color: "#688c90" }}>|</span>
<span>DPR: {amount(total)}</span>
</div>
<div style={{ display: "flex", justifyContent: "center", marginTop: 14, textAlign: "center", color: "#d1e9e6", fontSize: 18, lineHeight: 1.45 }}>
{team.combo}
</div>
<div style={{ display: "flex", justifyContent: "center", marginTop: 18, color: "#5c9995", fontSize: 14 }}>
BUZZ SHEET
</div>
</div>,
{
width: 550,
height: 776,
fonts: font
? [{ name: "Anuphan", data: font, weight: 400, style: "normal" }]
: undefined,
headers: {
"Cache-Control": "no-store",
"Content-Disposition": `attachment; filename="buzz-team-${team.id}.png"`,
},
},
);
}