feat(ui): add theater mode visual hints (#350)
This commit is contained in:
1 parent
6b1e6274bd
commit
56e19139e6
4 files changed
+88
No files matched your search
@@ -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",
|
||||
];
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
Frame,
|
||||
LIVE,
|
||||
MINT,
|
||||
MiniTv,
|
||||
Pawn,
|
||||
RedX,
|
||||
SKY,
|
||||
@@ -311,6 +312,69 @@ ${rmBlock(
|
||||
</>
|
||||
);
|
||||
|
||||
function TheaterHint({
|
||||
theme,
|
||||
exit,
|
||||
}: {
|
||||
theme: Parameters<HintScene>[0]["theme"];
|
||||
exit: boolean;
|
||||
}) {
|
||||
const motion = exit ? "vlsThExit" : "vlsThEnter";
|
||||
return (
|
||||
<>
|
||||
<style>{`
|
||||
.vls-th-stage{transform-box:fill-box;transform-origin:center;animation:${motion} 3.2s cubic-bezier(.3,1.25,.5,1) infinite}
|
||||
.vls-th-arrows{animation:vlsThArrows 3.2s ease-in-out infinite}
|
||||
@keyframes vlsThEnter{0%,8%{transform:scale(.72)}28%,100%{transform:scale(1)}}
|
||||
@keyframes vlsThExit{0%,8%{transform:scale(1.28)}28%,100%{transform:scale(1)}}
|
||||
@keyframes vlsThArrows{0%,8%{opacity:0}20%,44%{opacity:1}58%,100%{opacity:.38}}
|
||||
${rmBlock(
|
||||
["vls-th-stage", "vls-th-arrows"],
|
||||
[
|
||||
[".vls-th-stage", "transform:none"],
|
||||
[".vls-th-arrows", "opacity:.38"],
|
||||
],
|
||||
)}
|
||||
`}</style>
|
||||
<Frame x={4} w={152} theme={theme} />
|
||||
<Frame x={164} w={152} theme={theme} accent={LIVE} />
|
||||
{exit ? (
|
||||
<>
|
||||
<MiniTv x={16} y={10} w={132} h={72} />
|
||||
<g className="vls-th-stage">
|
||||
<MiniTv x={204} y={22} w={72} h={44} />
|
||||
<path d="M190 72H290" stroke={INK} strokeWidth={2.5} strokeLinecap="round" />
|
||||
<rect x={218} y={73} width={44} height={9} rx={4.5} fill="var(--couch)" />
|
||||
</g>
|
||||
<g className="vls-th-arrows" stroke={LIVE} strokeWidth={2.5} strokeLinecap="round" strokeLinejoin="round" fill="none">
|
||||
<path d="M184 20l13 13m0-10v10h-10M296 20l-13 13m0-10v10h10M184 78l13-13m0 10V65h-10M296 78l-13-13m0 10V65h10" />
|
||||
</g>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<MiniTv x={44} y={22} w={72} h={44} />
|
||||
<path d="M30 72H130" stroke={INK} strokeWidth={2.5} strokeLinecap="round" />
|
||||
<rect x={58} y={73} width={44} height={9} rx={4.5} fill="var(--couch)" />
|
||||
<g className="vls-th-stage">
|
||||
<MiniTv x={176} y={10} w={132} h={72} />
|
||||
</g>
|
||||
<g className="vls-th-arrows" stroke={LIVE} strokeWidth={2.5} strokeLinecap="round" strokeLinejoin="round" fill="none">
|
||||
<path d="M204 34l-14-14m0 10V20h10M276 34l14-14m0 10V20h-10M204 62l-14 14m0-10v10h10M276 62l14 14m0-10v10h-10" />
|
||||
</g>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const HintTheater: HintScene = ({ theme }) => (
|
||||
<TheaterHint theme={theme} exit={false} />
|
||||
);
|
||||
|
||||
const HintTheaterExit: HintScene = ({ theme }) => (
|
||||
<TheaterHint theme={theme} exit />
|
||||
);
|
||||
|
||||
/* 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<Set4Kind, HintScene> = {
|
||||
"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,
|
||||
};
|
||||
@@ -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)}
|
||||
/>
|
||||
</div>
|
||||
@@ -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"
|
||||
|
||||
@@ -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],
|
||||
|
||||
Reference in new issue
Block a user