diff --git a/fleet/web/app/_wall/PlayDrawer.tsx b/fleet/web/app/_wall/PlayDrawer.tsx
index 20a625a92..ebaee9714 100644
--- a/fleet/web/app/_wall/PlayDrawer.tsx
+++ b/fleet/web/app/_wall/PlayDrawer.tsx
@@ -1,12 +1,15 @@
"use client";
-// The drawer: directing the scene (choosing the camera's next stop, writing a word, dropping a
-// light), how the streams arrive, the stats overlay, and what the page is (the
-// counters, recent actions, how a square is isolated). Every
-// label comes from the view model; every action goes through WallClient.act,
-// which holds it until the Turnstile check passes when the coordinator asks for one.
+// The drawer shell and its three panels. "play" directs the scene (choosing the camera's next
+// stop, writing a word, dropping a light, how the streams arrive, the stats overlay). "about"
+// is what the page is (title, sentence, counters, recent actions). "isolation" is how a square
+// is isolated. Each header button opens its own panel in the same shell, so they share the
+// close button, Escape and focus handling, and each opens at its top. On a phone the header has
+// no room for a third icon, so About carries the isolation section too (`isolationInAbout`). Every label comes from
+// the view model; every action goes through WallClient.act, which holds it until the Turnstile
+// check passes when the coordinator asks for one.
-import { useEffect, useRef, useState } from "react";
+import { useEffect, useRef } from "react";
import { wordArgs, type WallView } from "../../core/index.ts";
import { TurnstileWidget } from "./Turnstile.tsx";
import type { WallClient } from "./wall-client.ts";
@@ -28,36 +31,49 @@ export function VerifyBox({ client, view }: { client: WallClient; view: WallView
);
}
-export function PlayDrawer({ client, view, onClose, section }: { client: WallClient; view: WallView; onClose: () => void; section: string | null }) {
+export type DrawerPanel = "play" | "about" | "isolation";
+
+export function PlayDrawer({ client, view, onClose, panel, isolationInAbout, word, setWord }: { client: WallClient; view: WallView; onClose: () => void; panel: DrawerPanel; isolationInAbout: boolean; word: string; setWord: (word: string) => void }) {
const p = view.play!;
- const [word, setWord] = useState("");
- const panel = useRef(null);
- const iso = view.isolation;
+ const shell = useRef(null);
+ const title = panel === "about" ? view.title : panel === "isolation" ? view.isolation.title : p.title;
useEffect(() => {
- const target = section ? panel.current?.querySelector(`#${section}-title`) : null;
- if (target) {
- target.scrollIntoView({ block: "start" });
- target.focus();
- } else {
- panel.current?.querySelector("button, input")?.focus();
- }
+ // Every panel opens at its top: focus without scrolling, then pin the scroll to 0.
+ const target = panel === "play" ? shell.current?.querySelector("button, input") : shell.current?.querySelector(`#${panel}-title`);
+ target?.focus({ preventScroll: true });
+ if (shell.current) shell.current.scrollTop = 0;
const onKey = (e: KeyboardEvent) => {
if (e.key === "Escape") onClose();
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
- }, [onClose, section]);
+ }, [onClose, panel]);
return (
-