From 56e19139e6908b17bfe99f37ccb1fbf7b31b4812 Mon Sep 17 00:00:00 2001 From: AAA <35992542+TNTcraftHIM@users.noreply.github.com> Date: Mon, 31 Aug 2026 02:40:10 +0800 Subject: [PATCH] feat(ui): add theater mode visual hints (#350) --- src/client/components/living/hints/index.tsx | 4 ++ src/client/components/living/hints/set4.tsx | 66 ++++++++++++++++++++ src/client/pages/ViewerPage.tsx | 4 ++ tests/living-components.test.ts | 14 +++++ 4 files changed, 88 insertions(+) diff --git a/src/client/components/living/hints/index.tsx b/src/client/components/living/hints/index.tsx index a7a4984..b2cb740 100644 --- a/src/client/components/living/hints/index.tsx +++ b/src/client/components/living/hints/index.tsx @@ -48,6 +48,8 @@ export type Set4Kind = | "hint-rename" | "hint-theme" | "hint-join-go" + | "hint-theater" + | "hint-theater-exit" | "hint-route-p2p" | "hint-route-sfu"; @@ -82,6 +84,8 @@ export const HINT_KINDS: readonly HintKind[] = [ "hint-rename", "hint-theme", "hint-join-go", + "hint-theater", + "hint-theater-exit", "hint-route-p2p", "hint-route-sfu", ]; diff --git a/src/client/components/living/hints/set4.tsx b/src/client/components/living/hints/set4.tsx index da95264..1af1bd4 100644 --- a/src/client/components/living/hints/set4.tsx +++ b/src/client/components/living/hints/set4.tsx @@ -11,6 +11,7 @@ import { Frame, LIVE, MINT, + MiniTv, Pawn, RedX, SKY, @@ -311,6 +312,69 @@ ${rmBlock( ); +function TheaterHint({ + theme, + exit, +}: { + theme: Parameters[0]["theme"]; + exit: boolean; +}) { + const motion = exit ? "vlsThExit" : "vlsThEnter"; + return ( + <> + + + + {exit ? ( + <> + + + + + + + + + + + ) : ( + <> + + + + + + + + + + + )} + + ); +} + +const HintTheater: HintScene = ({ theme }) => ( + +); + +const HintTheaterExit: HintScene = ({ theme }) => ( + +); + /* hint-route-p2p: [two pawns apart] → [direct live-green arc pawn→pawn, live dot on the apex]. Loop 3.2s: arc draws 6-28%, both pawns hop 30-42%, panel-1 pair blinks at 64%; rest ~58%. */ @@ -436,6 +500,8 @@ export const SET4_SCENES: Record = { "hint-rename": HintRename, "hint-theme": HintTheme, "hint-join-go": HintJoinGo, + "hint-theater": HintTheater, + "hint-theater-exit": HintTheaterExit, "hint-route-p2p": HintRouteP2p, "hint-route-sfu": HintRouteSfu, }; diff --git a/src/client/pages/ViewerPage.tsx b/src/client/pages/ViewerPage.tsx index fde4ca5..e8c7edc 100644 --- a/src/client/pages/ViewerPage.tsx +++ b/src/client/pages/ViewerPage.tsx @@ -2517,6 +2517,7 @@ export function ViewerPage({ roomId, viewerGrant }: ViewerPageProps) { icon="contract" cap="viewer.theater.exit" title="viewer.theater.exit" + hint="hint-theater-exit" onClick={() => setTheaterMode(false)} /> @@ -2639,6 +2640,9 @@ export function ViewerPage({ roomId, viewerGrant }: ViewerPageProps) { title={ theaterMode ? "viewer.theater.exit" : "viewer.theater" } + hint={ + theaterMode ? "hint-theater-exit" : "hint-theater" + } tone={theaterMode ? "on" : undefined} pressed={theaterMode} controls="viewer-stage" diff --git a/tests/living-components.test.ts b/tests/living-components.test.ts index b51ddb5..48f0227 100644 --- a/tests/living-components.test.ts +++ b/tests/living-components.test.ts @@ -196,6 +196,20 @@ describe("living-room presentation", () => { expect(html).toContain("vlsPrivLock"); }); + it("uses opposite motion for entering and leaving theater mode", () => { + const entering = renderToStaticMarkup( + createElement(HintComic, { kind: "hint-theater", size: 240 }), + ); + const leaving = renderToStaticMarkup( + createElement(HintComic, { kind: "hint-theater-exit", size: 240 }), + ); + + expect(entering).toContain("animation:vlsThEnter"); + expect(leaving).toContain("animation:vlsThExit"); + expect(entering).toContain("vls-th-arrows"); + expect(leaving).toContain("vls-th-arrows"); + }); + it.each([ ["open", false, "host.policy.currentOpen", true], ["private", true, "host.policy.currentPassword", true],