Release v0.1.1 2026-07-10

This commit is contained in:
Warren H
2026-07-10 23:47:37 -04:00
parent 537a311f52
commit 6726c600e4
40 changed files with 2987 additions and 192 deletions
+43 -10
View File
@@ -1,4 +1,4 @@
import { useMemo, useState } from "react";
import { useMemo, useRef, useState } from "react";
import { MAX_HUNTER_NAME_LENGTH, MODE_COPY, normalizeHunterName, selectRandomBossPair } from "../frontend/data";
import { formatPlayTime, formatSaveTimestamp } from "../frontend/saveRepository";
import { useActiveHunter, useFrontendStore } from "../frontend/store";
@@ -51,15 +51,24 @@ function ControllerLegend({ back = false }: { back?: boolean }) {
function LoginScreen() {
const signIn = useFrontendStore((state) => state.signIn);
const createAccount = useFrontendStore((state) => state.createAccount);
const continueOffline = useFrontendStore((state) => state.continueOffline);
const notice = useFrontendStore((state) => state.notice);
const [hunterId, setHunterId] = useState("wayfinder");
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const usernameRef = useRef<HTMLInputElement>(null);
const passwordRef = useRef<HTMLInputElement>(null);
const actions = useMemo<MenuAction[]>(() => [
{ id: "sign-in", run: () => signIn(hunterId) },
{ id: "username", run: () => usernameRef.current?.focus() },
{ id: "password", run: () => passwordRef.current?.focus() },
{ id: "sign-in", run: () => { void signIn(username, password); } },
{ id: "create-account", run: () => { void createAccount(username, password); } },
{ id: "offline", run: continueOffline },
], [continueOffline, hunterId, signIn]);
], [continueOffline, createAccount, password, signIn, username]);
const controller = useMenuController(actions);
const submitSignIn = () => { void signIn(username, password); };
return (
<DualDisplayFrame
top={
@@ -71,17 +80,41 @@ function LoginScreen() {
<h1>Keep everyone standing.</h1>
<p>Your save always lives on this device. Sign in only when you want a second copy for PC AYN Thor handoff.</p>
</div>
<form className="login-panel" onSubmit={(event) => { event.preventDefault(); signIn(hunterId); }}>
<label htmlFor="hunter-id">Hunter ID</label>
<input id="hunter-id" value={hunterId} onChange={(event) => setHunterId(event.target.value)} autoComplete="username" />
<form className="login-panel" onSubmit={(event) => { event.preventDefault(); submitSignIn(); }}>
<label htmlFor="account-username">Username</label>
<input
ref={usernameRef}
id="account-username"
className={controller.focusedId === "username" ? "is-controller-focused" : ""}
value={username}
onChange={(event) => setUsername(event.target.value)}
onFocus={() => controller.focus("username")}
autoComplete="username"
required
/>
<label htmlFor="account-password">Password</label>
<input
ref={passwordRef}
id="account-password"
className={controller.focusedId === "password" ? "is-controller-focused" : ""}
type="password"
value={password}
onChange={(event) => setPassword(event.target.value)}
onFocus={() => controller.focus("password")}
autoComplete="current-password"
required
/>
<FocusButton id="sign-in" focusedId={controller.focusedId} focus={controller.focus} className="front-primary" type="submit">
<span>Sign in & sync</span><small>Online saves enabled</small>
</FocusButton>
<FocusButton id="create-account" focusedId={controller.focusedId} focus={controller.focus} className="front-secondary" type="button" onClick={() => { void createAccount(username, password); }}>
<span>Create account</span><small>Required for first sync</small>
</FocusButton>
<FocusButton id="offline" focusedId={controller.focusedId} focus={controller.focus} className="front-secondary" type="button" onClick={continueOffline}>
<span>Continue with offline save</span><small>No account required</small>
</FocusButton>
</form>
{notice && <div className="front-notice">{notice}</div>}
{notice && <div className="front-notice" role="status" aria-live="polite">{notice}</div>}
<ControllerLegend />
</FrontSurface>
}
@@ -92,8 +125,8 @@ function LoginScreen() {
<span className="context-kicker">How saving works</span>
<ol>
<li><b>01</b><span><strong>Play offline</strong><small>Every change writes to device storage first.</small></span></li>
<li><b>02</b><span><strong>Sync when ready</strong><small>Upload any slot after signing in.</small></span></li>
<li><b>03</b><span><strong>Move devices</strong><small>Download the online copy and overwrite local.</small></span></li>
<li><b>02</b><span><strong>Create or sign in</strong><small>Username and password unlock online sync.</small></span></li>
<li><b>03</b><span><strong>Move devices</strong><small>Sign in, then upload or download an online copy.</small></span></li>
</ol>
</div>
<div className="device-route"><span>PC</span><i></i><b>ONLINE COPY</b><i></i><span>THOR</span></div>