|
|
@@ -2,72 +2,96 @@
|
|
|
import {PUBLIC_API_URL} from "$env/static/public";
|
|
|
import {toast} from "$lib/toast.svelte.js";
|
|
|
import {goto} from "$app/navigation";
|
|
|
+ import Game from "./Game.svelte";
|
|
|
+ import Character from "./Character.svelte";
|
|
|
+
|
|
|
+ let step = $state(1);
|
|
|
|
|
|
let title = $state("");
|
|
|
let gameContext = $state("");
|
|
|
|
|
|
- const submit = async ()=>{
|
|
|
- const body = {
|
|
|
- title: title,
|
|
|
- game_context: gameContext
|
|
|
- };
|
|
|
+ let characterName = $state("");
|
|
|
+ let characterDescription = $state("");
|
|
|
+
|
|
|
+ const nextStep = (e) => {
|
|
|
+ e.preventDefault();
|
|
|
+ step = 2;
|
|
|
+ };
|
|
|
+
|
|
|
+ const submit = async (e) => {
|
|
|
+ e.preventDefault();
|
|
|
|
|
|
- const response = await fetch(`${PUBLIC_API_URL}/game`, {
|
|
|
+ const gameRes = await fetch(`${PUBLIC_API_URL}/game`, {
|
|
|
method: "POST",
|
|
|
headers: {"Content-Type": "application/json"},
|
|
|
credentials: "include",
|
|
|
- body: JSON.stringify(body)
|
|
|
+ body: JSON.stringify({ title, game_context: gameContext })
|
|
|
});
|
|
|
|
|
|
- const data = await response.json();
|
|
|
- if(!response.ok){
|
|
|
- toast(data.error.message, "error");
|
|
|
- if(response.status === 401){
|
|
|
- goto("/");
|
|
|
- }
|
|
|
- }else{
|
|
|
- toast("Game created");
|
|
|
- goto(`/game/${data.id}/character/new`);
|
|
|
+ const gameData = await gameRes.json();
|
|
|
+ if (!gameRes.ok) {
|
|
|
+ toast(gameData.error.message, "error");
|
|
|
+ if (gameRes.status === 401) goto("/");
|
|
|
+ return;
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- const autofocus = (node)=>{
|
|
|
- node.focus();
|
|
|
- }
|
|
|
+ const charRes = await fetch(`${PUBLIC_API_URL}/game/${gameData.id}/character`, {
|
|
|
+ method: "POST",
|
|
|
+ headers: {"Content-Type": "application/json"},
|
|
|
+ credentials: "include",
|
|
|
+ body: JSON.stringify({ name: characterName, description: characterDescription })
|
|
|
+ });
|
|
|
+
|
|
|
+ const charData = await charRes.json();
|
|
|
+ if (!charRes.ok) {
|
|
|
+ toast(charData.error.message, "error");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ toast("Adventure created!");
|
|
|
+ goto(`/game/${gameData.id}`);
|
|
|
+ };
|
|
|
</script>
|
|
|
|
|
|
<div class="page">
|
|
|
<header>
|
|
|
- <a href="/game" class="back">
|
|
|
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
|
- <path d="M19 12H5M12 5l-7 7 7 7"/>
|
|
|
- </svg>
|
|
|
- My Games
|
|
|
- </a>
|
|
|
+ {#if step === 1}
|
|
|
+ <a href="/game" class="back">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
|
+ <path d="M19 12H5M12 5l-7 7 7 7"/>
|
|
|
+ </svg>
|
|
|
+ My Games
|
|
|
+ </a>
|
|
|
+ {:else}
|
|
|
+ <button class="back" onclick={() => step = 1}>
|
|
|
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
|
+ <path d="M19 12H5M12 5l-7 7 7 7"/>
|
|
|
+ </svg>
|
|
|
+ Back
|
|
|
+ </button>
|
|
|
+ {/if}
|
|
|
</header>
|
|
|
|
|
|
<main>
|
|
|
- <div class="heading">
|
|
|
- <h1>New Adventure</h1>
|
|
|
- <p>Set the stage for your story</p>
|
|
|
+ <div class="step-indicator">
|
|
|
+ <div class="step-node" class:active={step >= 1} class:done={step > 1}>
|
|
|
+ {#if step > 1}
|
|
|
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
|
|
|
+ <path d="M20 6L9 17l-5-5"/>
|
|
|
+ </svg>
|
|
|
+ {:else}
|
|
|
+ 1
|
|
|
+ {/if}
|
|
|
+ </div>
|
|
|
+ <div class="step-line" class:active={step > 1}></div>
|
|
|
+ <div class="step-node" class:active={step >= 2}>2</div>
|
|
|
</div>
|
|
|
|
|
|
- <form class="standardForm" onsubmit={submit}>
|
|
|
- <label>Title
|
|
|
- <input bind:value={title} type="text" placeholder="e.g. The Lost Kingdom of Arath" required use:autofocus>
|
|
|
- </label>
|
|
|
-
|
|
|
- <label>Game Information
|
|
|
- <textarea
|
|
|
- bind:value={gameContext}
|
|
|
- placeholder="Describe the world, setting, tone, or any context you want the AI to know about your adventure…"
|
|
|
- rows="6"
|
|
|
- required
|
|
|
- ></textarea>
|
|
|
- </label>
|
|
|
-
|
|
|
- <button type="submit">Begin Adventure</button>
|
|
|
- </form>
|
|
|
+ {#if step === 1}
|
|
|
+ <Game bind:title bind:gameContext onNext={nextStep} />
|
|
|
+ {:else}
|
|
|
+ <Character bind:name={characterName} bind:description={characterDescription} onSubmit={submit} />
|
|
|
+ {/if}
|
|
|
</main>
|
|
|
</div>
|
|
|
|
|
|
@@ -92,6 +116,11 @@
|
|
|
font-weight: 500;
|
|
|
color: #6b6b7a;
|
|
|
text-decoration: none;
|
|
|
+ background: none;
|
|
|
+ border: none;
|
|
|
+ padding: 0;
|
|
|
+ cursor: pointer;
|
|
|
+ font-family: inherit;
|
|
|
transition: color 0.15s;
|
|
|
}
|
|
|
|
|
|
@@ -107,40 +136,51 @@
|
|
|
gap: 2rem;
|
|
|
}
|
|
|
|
|
|
- .heading {
|
|
|
- text-align: center;
|
|
|
- max-width: 480px;
|
|
|
+ .step-indicator {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .step-node {
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ border-radius: 50%;
|
|
|
+ border: 2px solid #2a2a38;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 0.8rem;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #44445a;
|
|
|
+ background: #0f0f18;
|
|
|
+ transition: border-color 0.3s, color 0.3s, background 0.3s;
|
|
|
+ flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
- h1 {
|
|
|
- font-size: clamp(1.8rem, 4vw, 2.4rem);
|
|
|
- font-weight: 800;
|
|
|
- letter-spacing: -1.5px;
|
|
|
- color: #e5e5ea;
|
|
|
- margin-bottom: 0.5rem;
|
|
|
+ .step-node.active {
|
|
|
+ border-color: #c084fc;
|
|
|
+ color: #c084fc;
|
|
|
}
|
|
|
|
|
|
- .heading p {
|
|
|
- font-size: 0.95rem;
|
|
|
- color: #6b6b7a;
|
|
|
+ .step-node.done {
|
|
|
+ background: #c084fc;
|
|
|
+ border-color: #c084fc;
|
|
|
+ color: #0b0b10;
|
|
|
}
|
|
|
|
|
|
- .standardForm {
|
|
|
- width: 100%;
|
|
|
- max-width: 780px;
|
|
|
- padding: 3rem;
|
|
|
+ .step-line {
|
|
|
+ width: 80px;
|
|
|
+ height: 2px;
|
|
|
+ background: #2a2a38;
|
|
|
+ transition: background 0.3s;
|
|
|
}
|
|
|
|
|
|
- textarea {
|
|
|
- resize: vertical;
|
|
|
- min-height: 280px;
|
|
|
- line-height: 1.6;
|
|
|
- font-size: 1rem;
|
|
|
+ .step-line.active {
|
|
|
+ background: #c084fc;
|
|
|
}
|
|
|
|
|
|
@media (max-width: 700px) {
|
|
|
- .standardForm { padding: 1.75rem; }
|
|
|
- textarea { min-height: 160px; }
|
|
|
main { padding: 2rem 1.25rem 4rem; }
|
|
|
+ .step-line { width: 48px; }
|
|
|
}
|
|
|
</style>
|