feat(frontend): let the board render at any width

Board components size themselves from a --board-unit CSS variable instead
of vw. By default it is 1vw, so the game page looks the same; with the new
width prop the same board renders small, e.g. in previews.
This commit is contained in:
Halit Aksoy 2026-10-16 00:21:53 +03:00
parent 85e3fb60f6
commit 9278ed7447
6 changed files with 29 additions and 22 deletions

View File

@ -14,12 +14,19 @@ const BoardView: FunctionComponent<{
boardViewModel: BoardViewModel; boardViewModel: BoardViewModel;
revert: boolean; revert: boolean;
onPitSelect: (index: number, pit: Pit) => void; onPitSelect: (index: number, pit: Pit) => void;
}> = ({ game, context, boardId, boardViewModel, revert, onPitSelect }) => { // Width in px, e.g. for previews; by default the board is as wide as the viewport.
width?: number;
}> = ({ game, context, boardId, boardViewModel, revert, onPitSelect, width }) => {
const mancalaGame = game?.mancalaGame; const mancalaGame = game?.mancalaGame;
const theme = context.themeManager.theme; const theme = context.themeManager.theme;
// Compute pit width based on viewport (each column is 11vw) // The board is 92 units wide (8 columns of 11 plus padding); all board parts are sized in units.
const pitWidth = typeof window !== "undefined" ? window.innerWidth * 0.11 : 100; const unit = width !== undefined ? width / 92 : (typeof window !== "undefined" ? window.innerWidth / 100 : 10);
const pitWidth = unit * 11;
const sizeVariables = {
"--board-unit": width !== undefined ? `${unit}px` : "1vw",
"--board-spacing": width !== undefined ? `${unit * 0.3}px` : "5px",
} as React.CSSProperties;
const createPitView = (key: any, pit: Pit, pitViewModel: PitViewModel) => ( const createPitView = (key: any, pit: Pit, pitViewModel: PitViewModel) => (
<PitView <PitView
@ -42,7 +49,7 @@ const BoardView: FunctionComponent<{
const player2BankViewModel = boardViewModel.pits[player2BankIndex]; const player2BankViewModel = boardViewModel.pits[player2BankIndex];
return ( return (
<div className="board" style={{ background: theme.boardColor }}> <div className="board" style={{ background: theme.boardColor, ...sizeVariables }}>
<StoreView <StoreView
context={context} context={context}
pitViewModel={revert ? player2BankViewModel : player1BankViewModel} pitViewModel={revert ? player2BankViewModel : player1BankViewModel}
@ -61,11 +68,11 @@ const BoardView: FunctionComponent<{
{revert ? player2Pits : player1Pits} {revert ? player2Pits : player1Pits}
<style jsx>{` <style jsx>{`
.board { .board {
padding: 2vw; padding: calc(2 * var(--board-unit));
display: grid; display: grid;
grid-template-columns: repeat(8, 11vw); grid-template-columns: repeat(8, calc(11 * var(--board-unit)));
grid-template-rows: repeat(2, 11vw); grid-template-rows: repeat(2, calc(11 * var(--board-unit)));
border-radius: 3vw; border-radius: calc(3 * var(--board-unit));
transition: background-color 0.5s; transition: background-color 0.5s;
} }
`}</style> `}</style>

View File

@ -53,9 +53,9 @@ const PitView: FunctionComponent<PitViewProps> = ({ pitViewModel, context, onCli
)} )}
<style jsx>{` <style jsx>{`
.pit { .pit {
margin: 5px; margin: var(--board-spacing);
padding: 5px; padding: var(--board-spacing);
border-radius: 10vw; border-radius: calc(10 * var(--board-unit));
transition: background-color 0.5s; transition: background-color 0.5s;
display: flex; display: flex;
align-items: center; align-items: center;
@ -67,11 +67,11 @@ const PitView: FunctionComponent<PitViewProps> = ({ pitViewModel, context, onCli
} }
.stone-count { .stone-count {
position: absolute; position: absolute;
bottom: 1.0vw; bottom: calc(1.0 * var(--board-unit));
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
font-family: monospace; font-family: monospace;
font-size: 1.5vw; font-size: calc(1.5 * var(--board-unit));
font-weight: bold; font-weight: bold;
pointer-events: none; pointer-events: none;
} }

View File

@ -7,9 +7,9 @@ const StoneView: FunctionComponent<{ color: string }> = ({ color }) => {
<style jsx>{` <style jsx>{`
.stone { .stone {
margin: 1px; margin: 1px;
width: 1vw; width: calc(1 * var(--board-unit));
height: 1vw; height: calc(1 * var(--board-unit));
border-radius: 10vw; border-radius: calc(10 * var(--board-unit));
transition: background-color 0.5s; transition: background-color 0.5s;
} }
`}</style> `}</style>

View File

@ -34,8 +34,8 @@ const StoreView: FunctionComponent<{
</span> </span>
<style jsx>{` <style jsx>{`
.store { .store {
margin: 5px; margin: var(--board-spacing);
border-radius: 10vw; border-radius: calc(10 * var(--board-unit));
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -46,10 +46,10 @@ const StoreView: FunctionComponent<{
} }
.store-stone-count-text { .store-stone-count-text {
position: absolute; position: absolute;
bottom: 2vw; bottom: calc(2 * var(--board-unit));
font-family: monospace; font-family: monospace;
font-weight: bold; font-weight: bold;
font-size: 2vw; font-size: calc(2 * var(--board-unit));
} }
`}</style> `}</style>
</div> </div>

View File

@ -166,7 +166,7 @@ not spectators (R4.5).
### Board size ### Board size
`BoardView` gets an optional `width` (px). All board components size themselves from a CSS variable `BoardView` gets an optional `width` (px). All board components size themselves from a CSS variable
`--board-unit` (default `1vw`, else `width / 100` px) instead of `vw`, so the same board renders at full width in a `--board-unit` (default `1vw`, else `width / 92` px, the board being 92 units wide) instead of `vw`, so the same board renders at full width in a
game and small in previews. game and small in previews.
### Home page (`routes/Home.tsx`) ### Home page (`routes/Home.tsx`)

View File

@ -17,7 +17,7 @@ Work through the tasks in order; each ends with its tests passing and a commit.
unit tests. unit tests.
- [x] 7. **API**: public ids, Bearer auth middleware, `/games/me/active`, `/games/me`, `/games/popular`, - [x] 7. **API**: public ids, Bearer auth middleware, `/games/me/active`, `/games/me`, `/games/popular`,
`/game/:id` for finished games, live games channel with throttled push; tests. `/game/:id` for finished games, live games channel with throttled push; tests.
- [ ] 8. **Board size**: `--board-unit` in `BoardView`, `PitView`, `StoreView`, `StoneView`; `width` prop. - [x] 8. **Board size**: `--board-unit` in `BoardView`, `PitView`, `StoreView`, `StoneView`; `width` prop.
- [ ] 9. **Home page**: `ActiveGameBanner`, `LiveGamesSection` with live updates, `RecentGamesSection`; lobby - [ ] 9. **Home page**: `ActiveGameBanner`, `LiveGamesSection` with live updates, `RecentGamesSection`; lobby
rejection message. rejection message.
- [ ] 10. **Live game history**: `MoveList`, review mode with `ReviewBanner` and keyboard, timeout countdown. - [ ] 10. **Live game history**: `MoveList`, review mode with `ReviewBanner` and keyboard, timeout countdown.