anvilsign in

collin/mahjong · 3d11fb14

Fullscreen button and a two-row portrait hand for the phone controller

Collin Richards · 2026-08-20 09:47 UTC · 3d11fb14098574cfcf5c07b388cc74baf935dcc5 · parent e58f5c3c · browse files

modifiedsrc/App.tsx+1 −1
⋯ 227 unchanged lines
228228 // ---- routing ------------------------------------------------------------
229229 // A phone at the party table holds a hand, not the table.
230230 if (session && netMode === 'party' && !isScreen) {
231- return <Controller session={session} />;
231+ return <Controller session={session} prefs={prefs} onPrefs={setPrefs} />;
232232 }
233233 if (session && netMode === 'online' && !session.started) {
234234 return <OnlineLobby session={session} prefs={prefs} />;
⋯ 188 unchanged lines
modifiedsrc/styles.css+17 −0
⋯ 2158 unchanged lines
21592159 .ctl-hand .hand {
21602160 flex-wrap: nowrap;
21612161 }
2162+/* Held upright, seventeen tiles will never make one row. Break into two and
2163+ size the tiles off half the count instead of shrinking them past reading.
2164+ The row gap leaves room for the 摸 tag over a tile in the lower row. */
2165+@media (orientation: portrait) {
2166+ .ctl {
2167+ --tile-w: clamp(34px, calc((100vw - 44px) / 9.4), 76px);
2168+ }
2169+ .ctl-hand .hand {
2170+ flex-wrap: wrap;
2171+ justify-content: center;
2172+ /* Bounded, or it stays one long row: a `flex: none` item is happy to
2173+ overflow its container rather than wrap inside it. */
2174+ width: 100%;
2175+ height: auto;
2176+ row-gap: calc(var(--tile-w) * 0.4);
2177+ }
2178+}
21622179 /* The tile in the fingers once it has left the row, and its exit: flung, it
21632180 keeps going up off the screen — the common screen takes it from here. */
21642181 .ctl-held {
⋯ 72 unchanged lines
modifiedsrc/ui/Controller.tsx+19 −5
11 import { useEffect, useState } from 'react';
22 import { pendingOptions } from '../game/ctl';
3+import type { Prefs } from '../game/prefs';
34 import { labelOf, WIND_EN, WIND_NAMES, type Tile } from '../game/tiles';
45 import type { SeatId } from '../game/types';
56 import type { NetSession } from '../net/session';
67 import { seatFromUrl } from '../net/protocol';
78 import { Bi, CLAIM_LABEL } from './Bi';
9+import { FullscreenToggle } from './FullscreenToggle';
810 import { Hand, type Throw } from './Hand';
911 import { TileView } from './TileView';
1012
1113 const SEATS: SeatId[] = [0, 1, 2, 3];
1214
15+interface CtlProps {
16+ session: NetSession;
17+ /** For the fullscreen button — a phone's address bar is a sixth of the
18+ * height this screen has to hold a hand in. */
19+ prefs: Prefs;
20+ onPrefs: (p: Prefs) => void;
21+}
22+
1323 /**
1424 * The phone at the party table: just your own tiles, held in landscape, with
1525 * the common screen doing all the talking. What it keeps of the full table is
⋯ 2 unchanged lines
1828 * here, crosses the wire, and comes flying in from your edge of the shared
1929 * screen.
2030 */
21-export function Controller({ session }: { session: NetSession }) {
31+export function Controller({ session, prefs, onPrefs }: CtlProps) {
2232 const seat = session.mySeat;
2333
2434 // The QR carries which seat it was stuck to; a phone that arrived some other
⋯ 8 unchanged lines
3343 }
3444 }, [session, seat, asked]);
3545
36- if (seat === null) return <SeatPick session={session} joining={asked} />;
37- return <SeatStrip session={session} seat={seat} />;
46+ if (seat === null) {
47+ return <SeatPick session={session} joining={asked} prefs={prefs} onPrefs={onPrefs} />;
48+ }
49+ return <SeatStrip session={session} seat={seat} prefs={prefs} onPrefs={onPrefs} />;
3850 }
3951
40-function SeatPick({ session, joining }: { session: NetSession; joining?: boolean }) {
52+function SeatPick({ session, joining, prefs, onPrefs }: CtlProps & { joining?: boolean }) {
4153 const seats = session.seats;
4254 const s = session.table.state;
4355 return (
⋯ 18 unchanged lines
6274 );
6375 })}
6476 </div>
77+ <FullscreenToggle prefs={prefs} onPrefs={onPrefs} />
6578 </div>
6679 );
6780 }
⋯ 8 unchanged lines
7689 flung: boolean;
7790 }
7891
79-function SeatStrip({ session, seat }: { session: NetSession; seat: SeatId }) {
92+function SeatStrip({ session, seat, prefs, onPrefs }: CtlProps & { seat: SeatId }) {
8093 const table = session.table;
8194 const s = table.state;
8295 const p = s.players[seat];
⋯ 74 unchanged lines
157170 <button className="btn ghost sort" onClick={() => table.sortHand(seat)}>
158171 理牌
159172 </button>
173+ <FullscreenToggle prefs={prefs} onPrefs={onPrefs} compact />
160174 </div>
161175
162176 <div className="ctl-actions">
⋯ 112 unchanged lines