feat : big update
CI / Verify (push) Successful in 51s
CI / Build immutable images and deploy (push) Successful in 1m36s

This commit is contained in:
2026-08-30 10:52:37 +00:00 Unverified
parent 0a643dd60e
commit 273baa55d8
22 changed files with 4025 additions and 87 deletions
+59 -41
View File
@@ -1,6 +1,6 @@
import Image from "next/image";
import Link from "next/link";
import { ChevronLeftIcon, DownloadIcon } from "lucide-react";
import { ChevronLeftIcon, CrownIcon, DownloadIcon, HourglassIcon, WineIcon } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { buttonVariants } from "@/components/ui/button";
@@ -15,6 +15,7 @@ import {
import { Kbd } from "@/components/ui/kbd";
import { Separator } from "@/components/ui/separator";
import { SiteHeader } from "@/components/public/site-header";
import { ElementIcon } from "@/components/public/element-icon";
import { LiveRefresh } from "@/components/live-refresh";
import { rarityGradientClass, rarityTextClass } from "@/lib/catalog/rarity";
import type { PublicGuide } from "@/lib/guides/queries";
@@ -55,7 +56,7 @@ export function StructuredGuideShell({
<div className="min-h-svh bg-background">
<LiveRefresh endpoint={`/api/events/page/${guide.id}`} />
<SiteHeader />
<div className="sticky top-0 border-b bg-background/95 supports-backdrop-filter:backdrop-blur">
<div className="sticky top-0 z-10 border-b bg-background/95 supports-backdrop-filter:backdrop-blur">
<div className="mx-auto flex min-h-16 w-full max-w-7xl items-center gap-3 px-4 sm:px-6">
<Link
href="/"
@@ -78,13 +79,7 @@ export function StructuredGuideShell({
{guide.character.element ? (
<Badge>
{guide.character.elementIconUrl ? (
<Image
src={guide.character.elementIconUrl}
alt=""
width={16}
height={16}
className="brightness-0 invert"
/>
<ElementIcon element={guide.character.element} url={guide.character.elementIconUrl} />
) : null}
{guide.character.element}
</Badge>
@@ -202,9 +197,17 @@ function metric(value: string | null) {
function WeaponSection({ guide }: { guide: PublicGuide }) {
return (
<section className="flex flex-col gap-6">
<header className="text-center">
<header className="flex flex-col items-center gap-3 text-center">
<p className="text-sm font-medium text-primary">อาวุธที่แนะนำ</p>
<h1 className="font-heading text-4xl font-semibold">Weapons</h1>
<a
href={`/api/guides/${guide.id}/weapons/image`}
download
className={buttonVariants({ variant: "outline", size: "sm" })}
>
<DownloadIcon data-icon="inline-start" />
Export image
</a>
</header>
<div className="grid gap-3">
{guide.weapons.map((row, index) => (
@@ -333,16 +336,27 @@ function ArtifactSection({ guide }: { guide: PublicGuide }) {
))}
</div>
{profile ? (
<Card>
<CardHeader>
<CardTitle>สแตทที่แนะนำ</CardTitle>
</CardHeader>
<CardContent className="flex flex-col gap-5">
<StatLine label="นาฬิกา/Sands" values={profile.sands} />
<StatLine label="ถ้วย/Goblet" values={profile.goblet} />
<StatLine label="หมวก/Circlet" values={profile.circlet} />
<Separator />
<StatLine label="Substats" values={profile.substats} />
<div className="flex flex-col gap-5">
<Card className="overflow-hidden pt-0">
<CardHeader className="bg-primary py-4 text-center text-primary-foreground">
<CardTitle className="text-2xl">Artifact Stats</CardTitle>
</CardHeader>
<CardContent className="grid grid-cols-3 p-0">
<ArtifactStat icon={HourglassIcon} label="Sands" values={profile.sands} />
<ArtifactStat icon={WineIcon} label="Goblet" values={profile.goblet} />
<ArtifactStat icon={CrownIcon} label="Circlet" values={profile.circlet} />
</CardContent>
</Card>
{profile.substats.length ? (
<Card>
<CardContent className="py-6 text-center text-lg">
<strong>Stat Priority:</strong> {profile.substats.map(statLabel).join(" > ")}
</CardContent>
</Card>
) : null}
<Card>
<CardHeader><CardTitle>รายละเอียดเพิ่มเติม</CardTitle></CardHeader>
<CardContent className="flex flex-col gap-5">
{profile.targets.length ? (
<div>
<p className="mb-2 text-sm font-medium">Target Stats</p>
@@ -359,16 +373,19 @@ function ArtifactSection({ guide }: { guide: PublicGuide }) {
) : null}
{profile.talents.length ? (
<div>
<p className="mb-2 text-sm font-medium">ลำดับการอัพพรสรววค์</p>
<div className="flex gap-2">
<p className="mb-2 text-sm font-medium">ลำดับการอัพพรสวรรค์</p>
<div className="flex flex-wrap gap-3">
{[...profile.talents]
.sort((a, b) => a.priority - b.priority)
.map((talent) => (
<Badge key={talent.type} variant="secondary">
{talent.priority}. {talent.type}
{talent.level ? ` ${talent.level}` : ""}
</Badge>
))}
.map((talent) => {
const catalogTalent = guide.character.talents.find((item) => item.type === talent.type);
return catalogTalent?.iconUrl ? (
<div key={talent.type} className="relative size-14 overflow-hidden rounded-lg border bg-muted">
<Image src={catalogTalent.iconUrl} alt="" fill className="object-contain" />
<Kbd className="absolute top-0 right-0">{talent.priority}</Kbd>
</div>
) : <Badge key={talent.type} variant="secondary">{talent.priority}. {talent.type}</Badge>;
})}
</div>
</div>
) : null}
@@ -380,25 +397,26 @@ function ArtifactSection({ guide }: { guide: PublicGuide }) {
</p>
</div>
) : null}
</CardContent>
</Card>
</CardContent>
</Card>
</div>
) : null}
</div>
</section>
);
}
function StatLine({ label, values }: { label: string; values: string[] }) {
function statLabel(value: string): string {
return value === "EM" ? "Elemental Mastery" : value === "ER" ? "Energy Recharge" : value;
}
function ArtifactStat({ icon: Icon, label, values }: { icon: typeof HourglassIcon; label: string; values: string[] }) {
return (
<div>
<p className="text-sm text-muted-foreground">{label}</p>
<div className="mt-1 flex flex-wrap gap-2">
{values.map((value) => (
<Badge key={value} variant="outline">
{value}
</Badge>
))}
</div>
<div className="flex min-w-0 flex-col items-center gap-3 border-r p-3 text-center last:border-r-0 sm:p-5">
<Icon className="size-10" aria-hidden="true" />
<p className="font-semibold">{label}</p>
<Separator />
<p className="text-sm leading-6 sm:text-base">{values.length ? values.map(statLabel).join(" > ") : "—"}</p>
</div>
);
}
@@ -433,7 +451,7 @@ function ConstellationSection({ guide }: { guide: PublicGuide }) {
) : null}
</CardHeader>
<CardContent>
<p className="leading-7">{row.detail}</p>
<p className="whitespace-pre-line leading-7">{row.detail}</p>
</CardContent>
</Card>
))}