import { useState } from "preact/hooks";
import { CLASSES, type ClassId } from "../lib/game.ts";
const ORDER: ClassId[] = ["maiden", "witch", "assassin"];
export default function CharacterCreation() {
const [klass, setKlass] = useState<ClassId | null>(null);
const [name, setName] = useState("");
const [busy, setBusy] = useState(false);
const [error, setError] = useState<string | null>(null);
const selected = klass ? CLASSES[klass] : null;
async function createHero() {
if (!klass) return;
setBusy(true);
setError(null);
try {
const res = await fetch("/api/hero", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ name: name.trim(), klass }),
});
const data = await res.json();
if (!res.ok) {
setError(data.error ?? "Something went wrong.");
return;
}
globalThis.location.reload();
} catch {
setError("Could not reach the city gates. Try again.");
} finally {
setBusy(false);
}
}
return (
<div class="tapestry margin-vines min-h-screen flex flex-col items-center px-4 py-10">
<header class="relative z-1 text-center mb-10">
<p class="text-xs uppercase tracking-[0.4em] text-base-content/60">
Beyond the veil, your legend awaits
</p>
<h1 class="illuminated font-gothic text-4xl md:text-5xl tracking-[0.12em] text-accent mt-4">
Choose Your Hero
</h1>
<div class="fleuron-rule mt-6 w-72 mx-auto">
<span aria-hidden="true">☾ ✦ ☽</span>
</div>
</header>
<div class="relative z-1 grid w-full max-w-5xl gap-6 md:grid-cols-3">
{ORDER.map((id) => {
const c = CLASSES[id];
const active = klass === id;
return (
<button
type="button"
key={id}
onClick={() => setKlass(id)}
class={`arch-panel vellum card text-left transition-all duration-300 hover:-translate-y-1 ${
active ? "plaque-lit" : ""
}`}
>
<figure
class={`px-6 pt-4 pb-2 ${active ? "candle-glow" : ""}`}
>
<span class="shield-mount relative z-1 w-32 mx-auto">
<img
src={c.art}
alt=""
class={`shield mystic-class-icon mystic-class-${id}`}
/>
</span>
</figure>
<div class="corner-fleurons card-body items-center text-center pt-2">
<h2 class="banner w-full text-base tracking-[0.2em]">
{c.name}
</h2>
<p class="text-[0.7rem] uppercase tracking-[0.3em] text-accent/80">
{c.title}
</p>
<p class="text-sm text-base-content/75 mt-1">{c.blurb}</p>
<div class="stats stats-vertical sm:stats-horizontal stats-sm mt-2 bg-transparent">
<div class="stat py-1 px-3">
<div class="stat-title text-[0.6rem]">Vigor</div>
<div class="stat-value text-primary text-lg">{c.maxHp}</div>
</div>
<div class="stat py-1 px-3">
<div class="stat-title text-[0.6rem]">Energy</div>
<div class="stat-value text-secondary text-lg">
{c.energy}
</div>
</div>
</div>
</div>
</button>
);
})}
</div>
<div class="relative z-1 mt-10 w-full max-w-md flex flex-col items-center gap-4">
<label class="form-control w-full">
<span class="label-text text-xs uppercase tracking-[0.3em] text-base-content/60 mb-2">
Her name
</span>
<input
type="text"
value={name}
maxLength={24}
placeholder="e.g. Aurelia"
onInput={(e) => setName((e.target as HTMLInputElement).value)}
class="input input-bordered w-full"
/>
</label>
{error && (
<div role="alert" class="alert alert-error alert-soft w-full">
<span>{error}</span>
</div>
)}
<div class="relative">
<button
type="button"
disabled={!selected || name.trim().length < 2 || busy}
onClick={createHero}
class="btn btn-primary iron-band btn-wide tracking-[0.25em] uppercase font-heading"
>
{busy ? <span class="loading loading-spinner loading-sm" /> : (
"Enter the city"
)}
</button>
<span
class="wax-seal wax-seal-sm wax-seal-affix"
aria-hidden="true"
>
✦
</span>
</div>
</div>
</div>
);
}