Files
buzz-sheet/lib/image-export/templates/team.tsx
T

280 lines
8.7 KiB
TypeScript

import { Image, Link, Kbd, ArrowUpRightIcon } from "./primitives";
import { cn } from "@/lib/utils";
import { rarityGradientClass } from "@/lib/catalog/rarity";
import { elementBackgroundColor } from "@/lib/catalog/element";
import { isCustomTravelerKey } from "@/lib/catalog/travelers";
import type { PublicGuide } from "@/lib/guides/queries";
type Team = PublicGuide["teams"][number];
type TeamArtifact = {
key: string;
name: string;
imageUrl: string;
rarity: number | null;
};
function ArtifactHalf({
artifact,
side,
eager,
linked,
sizes,
}: {
artifact: TeamArtifact;
side: "left" | "right";
eager: boolean;
linked: boolean;
sizes: string;
}) {
const content = (
<span className="relative block size-full">
<Image
src={artifact.imageUrl}
alt={linked ? artifact.name : ""}
fill
loading={eager ? "eager" : undefined}
sizes={sizes}
className="object-contain p-1"
/>
</span>
);
const className = cn(
"absolute inset-0 overflow-hidden",
side === "left"
? "[clip-path:polygon(0_0,0_100%,100%_100%)]"
: "[clip-path:polygon(0_0,100%_0,100%_100%)]",
rarityGradientClass(artifact.rarity),
);
return linked ? (
<Link
href={`https://lunaris.moe/artifact/${encodeURIComponent(artifact.key)}`}
target="_blank"
rel="noreferrer"
aria-label={`เปิดข้อมูลอาร์ติแฟกต์ ${artifact.name} บน Lunaris`}
className={className}
>
{content}
</Link>
) : (
<span className={className}>{content}</span>
);
}
export function TeamArtifactIcon({
first,
second,
eager = false,
linked = false,
className,
sizes,
}: {
first: TeamArtifact | null;
second: TeamArtifact | null;
eager?: boolean;
linked?: boolean;
className?: string;
sizes: string;
}) {
const primary = first ?? second;
const secondary = first ? second : null;
if (!primary) {
return (
<div
className={cn("aspect-square rounded-md border bg-muted", className)}
/>
);
}
if (!secondary) {
const content = (
<>
<Image
src={primary.imageUrl}
alt={linked ? primary.name : ""}
fill
loading={eager ? "eager" : undefined}
sizes={sizes}
className="object-contain p-1"
/>
{linked ? (
<Kbd className="absolute top-0 right-0 h-3 min-w-3 bg-transparent p-0 text-white">
<ArrowUpRightIcon aria-hidden="true" />
</Kbd>
) : null}
</>
);
const artifactClassName = cn(
"relative aspect-square scale-110 overflow-hidden rounded-md bg-muted",
rarityGradientClass(primary.rarity),
className,
);
return linked ? (
<Link
href={`https://lunaris.moe/artifact/${encodeURIComponent(primary.key)}`}
target="_blank"
rel="noreferrer"
aria-label={`เปิดข้อมูลอาร์ติแฟกต์ ${primary.name} บน Lunaris`}
className={artifactClassName}
>
{content}
</Link>
) : (
<div className={artifactClassName}>{content}</div>
);
}
return (
<div
className={cn(
"relative aspect-square scale-110 overflow-hidden rounded-md",
className,
)}
>
<ArtifactHalf
artifact={primary}
side="left"
eager={eager}
linked={linked}
sizes={sizes}
/>
<ArtifactHalf
artifact={secondary}
side="right"
eager={eager}
linked={linked}
sizes={sizes}
/>
<span
aria-hidden="true"
className="pointer-events-none absolute top-1/2 left-1/2 h-px w-[142%] -translate-x-1/2 -translate-y-1/2 rotate-45 bg-white/30"
/>
{linked ? (
<Kbd className="pointer-events-none absolute top-0 right-0 h-3 min-w-3 bg-transparent p-0 text-white">
<ArrowUpRightIcon aria-hidden="true" />
</Kbd>
) : null}
</div>
);
}
export function TeamExportImage({ team }: { team: Team }) {
const total = team.members.reduce(
(sum, member) => sum + Number(member.damage),
0,
);
const seconds = Number(team.rotationSeconds);
return (
<div className="flex min-h-337.5 w-270 flex-col bg-[#05060a] p-[48px_52px] font-heading text-[white]">
<header className="flex flex-col items-center text-center">
<h2 className="text-[64px] leading-tight font-bold">{team.title}</h2>
<p className="text-[32px] text-[#b9b9c2]">{`Rotation duration: ${seconds.toLocaleString("en-US", { maximumFractionDigits: 2 })} seconds`}</p>
</header>
<div className="mt-5 flex flex-col gap-4">
{team.members.map((member) => {
const damage = Number(member.damage);
const percent = total > 0 ? (damage / total) * 100 : 0;
return (
<div
key={member.position}
className="flex h-43.5 items-center gap-6 rounded-3xl p-3.5"
style={{
backgroundColor: elementBackgroundColor(
member.character.element,
),
}}
>
<div
className={cn(
"relative size-35 shrink-0 overflow-hidden rounded-3xl border-[3px] border-white/20",
rarityGradientClass(
isCustomTravelerKey(member.character.key)
? 5
: member.character.rarity,
),
)}
>
<Image
src={member.character.imageUrl}
alt=""
fill
loading="eager"
sizes="140px"
className="object-contain"
/>
</div>
<div className="grid w-44 shrink-0 grid-cols-2 gap-3">
{member.weapon ? (
<div
className={cn(
"relative aspect-square min-w-0 scale-110 overflow-hidden rounded-2xl",
rarityGradientClass(member.weapon.rarity),
)}
>
<Image
src={member.weapon.imageUrl}
alt=""
fill
loading="eager"
sizes="82px"
className="object-contain p-1"
/>
</div>
) : null}
<TeamArtifactIcon
first={member.artifact}
second={member.secondArtifact}
eager
sizes="82px"
className="min-w-0 rounded-2xl"
/>
</div>
<div className="flex min-w-0 flex-1 flex-col gap-3.5">
<div className="flex items-center justify-between gap-4">
<span className="flex min-w-0 items-baseline gap-3 text-[36px] font-semibold">
<span className="truncate">{member.character.name}</span>
<span className="shrink-0 text-[28px] text-[#f0c95f]">
C{member.constellation}R{member.refinement}
</span>
</span>
<span className="shrink-0 text-[36px]">
{damage.toLocaleString("en-US")}
</span>
</div>
<div className="flex items-center gap-5">
<div className="h-11 flex-1 overflow-hidden rounded-xl bg-[#272730]">
<div
className="h-full rounded-xl bg-linear-to-r from-[#b7791f] to-[#f0b72f]"
style={{
width: `${Math.max(2, Math.min(100, percent))}%`,
}}
/>
</div>
<span className="w-28 text-right text-[34px] font-bold">
{percent.toFixed(1)}%
</span>
</div>
</div>
</div>
);
})}
</div>
<div className="my-3 flex flex-1 items-center justify-center rounded-2xl bg-[#272730] p-4 text-center text-[28px] leading-[1.4]">
<p className="whitespace-pre-wrap">{team.combo}</p>
</div>
<div className="flex shrink-0 justify-center border-t-2 border-[#34343d] pt-6 text-[64px] font-bold text-[#f0c95f]">
DPS:{" "}
{seconds > 0
? (total / seconds).toLocaleString("en-US", {
maximumFractionDigits: 0,
})
: "0"}
</div>
<div className="mt-5 flex shrink-0 justify-center text-[40px] text-[#74747f]">
By YT Genshin Isn&apos;t Mobile Game
</div>
</div>
);
}