diff --git a/frontend/src/components/GuessControls.tsx b/frontend/src/components/GuessControls.tsx index ff4a607..12bcc87 100644 --- a/frontend/src/components/GuessControls.tsx +++ b/frontend/src/components/GuessControls.tsx @@ -1,5 +1,6 @@ import { emit } from '../lib/socket'; import { forbiddenGuess } from '../lib/gameRules'; +import { GUESS_BUTTON } from '../lib/boardTheme'; interface Props { cardsInRound: number; @@ -7,15 +8,16 @@ interface Props { myOrder: number; activePlayer: number; activePlayerName: string; + desktop?: boolean; } -export default function GuessControls({ cardsInRound, guesses, myOrder, activePlayer, activePlayerName }: Props) { +export default function GuessControls({ cardsInRound, guesses, myOrder, activePlayer, activePlayerName, desktop = true }: Props) { const isMyTurn = activePlayer === myOrder; const forbidden = forbiddenGuess(cardsInRound, guesses); if (!isMyTurn) { return ( -

+

Čaká sa na tip:{' '} {activePlayerName}

@@ -23,6 +25,7 @@ export default function GuessControls({ cardsInRound, guesses, myOrder, activePl } const options = Array.from({ length: cardsInRound + 1 }, (_, i) => i); + const btn = desktop ? GUESS_BUTTON.desktop : GUESS_BUTTON.mobile; return (
@@ -36,8 +39,9 @@ export default function GuessControls({ cardsInRound, guesses, myOrder, activePl disabled={isForbidden} onClick={() => emit.addGuess(n)} title={isForbidden ? 'Zakázaná hodnota (súčet = počet kopiek)' : undefined} + style={{ width: btn.size, height: btn.size, fontSize: btn.font }} className={[ - 'w-11 h-11 rounded-full font-serif text-lg border-2 transition-colors', + 'rounded-full font-serif border-2 transition-colors', isForbidden ? 'border-[#5a2a2a] text-[#7a4040] opacity-40 cursor-not-allowed' : 'border-gold/50 text-gold hover:bg-gold hover:text-table hover:border-gold active:scale-95', diff --git a/frontend/src/components/PlayerCircle.tsx b/frontend/src/components/PlayerCircle.tsx index da82c35..b474342 100644 --- a/frontend/src/components/PlayerCircle.tsx +++ b/frontend/src/components/PlayerCircle.tsx @@ -12,6 +12,11 @@ interface Props { export default function PlayerCircle({ name, won, guess, active, size = 52 }: Props) { const nameFont = Math.max(9, Math.round(size * 0.17)); const valueFont = Math.round(size * 0.32); + // Bid status at a glance: terracotta once the player has taken more tricks + // than bid (the bid is already lost), gold while exactly on target, cream + // while still chasing. Scoring: exact match = 10 + bid, anything else = 0. + const guessColor = + guess === null ? '#d8cba6' : won > guess ? '#e08066' : won === guess ? '#e8c14a' : '#d8cba6'; // Oval: height a touch shorter than size so it reads as an ellipse. Width // starts at `size` (a circle for short names) but grows with the name via // fit-content + padding, up to a cap beyond which the name is ellipsised @@ -58,7 +63,10 @@ export default function PlayerCircle({ name, won, guess, active, size = 52 }: Pr }} > {won} - /{guess ?? '?'} + {/* The bid matters as much as the tricks won — keep it nearly as big + and in readable cream; only the slash separator stays muted. */} + / + {guess ?? '?'}
); diff --git a/frontend/src/components/Standings.tsx b/frontend/src/components/Standings.tsx index 23d894a..7c38cd0 100644 --- a/frontend/src/components/Standings.tsx +++ b/frontend/src/components/Standings.tsx @@ -2,6 +2,7 @@ import { useState } from 'react'; import type { PlayerInfo } from '../types'; import { computeTotal } from '../lib/standings'; import { displayName } from '../lib/names'; +import { STANDINGS_FONT } from '../lib/boardTheme'; interface Props { standings: number[][][]; @@ -40,14 +41,8 @@ export default function Standings({ standings, guesses = [], players, myOrder, d const ROUNDS_PER_SERIES = 8; // Bigger, more legible type on the wide desktop sidebar; compact on mobile. - const fz = { - head: desktop ? 11 : 10, - idx: desktop ? 13 : 9, - cell: desktop ? 19 : 14, - dot: desktop ? 18 : 13, - sigma: desktop ? 13 : 9, - total: desktop ? 20 : 18, - }; + // Sizes live in one place (boardTheme.ts) alongside the rest of the board. + const fz = desktop ? STANDINGS_FONT.desktop : STANDINGS_FONT.mobile; const gridCols = { gridTemplateColumns: `28px repeat(${cols.length}, 1fr)` }; diff --git a/frontend/src/lib/boardTheme.ts b/frontend/src/lib/boardTheme.ts new file mode 100644 index 0000000..3b3611d --- /dev/null +++ b/frontend/src/lib/boardTheme.ts @@ -0,0 +1,39 @@ +// Single source of truth for the game board's fixed pixel sizes. +// +// The board deliberately uses fixed px (not rem) so it stays pixel-precise and +// scales as one unit via `useFitScale` (desktop) — see index.css for why the +// board opts out of the global rem type lever. These constants keep those px +// values in one place instead of scattered as inline literals across the board +// components (Standings / GameTable / …). +// +// PlayerCircle derives its own font/padding as ratios of the `size` it gets, so +// its internal ratios stay local to that component; only the per-seat `size` +// inputs live here. + +/** Player-circle diameter per seat, by viewport. */ +export const SEAT_SIZE = { + desktop: { top: 64, side: 64, me: 70 }, + mobile: { top: 52, side: 48, me: 60 }, +} as const; + +/** Score list (Standings) type sizes. */ +export const STANDINGS_FONT = { + desktop: { head: 12, idx: 13, cell: 18, dot: 18, sigma: 18, total: 20 }, + mobile: { head: 11, idx: 9, cell: 16, dot: 13, sigma: 15, total: 18 }, +} as const; + +/** Header / score totals row (GameTable). The value size differs between the + * compact desktop header and the mobile header. */ +export const TOTALS_FONT = { + label: 12, + valueDesktop: 18, + valueMobile: 17, +} as const; + +/** Guess (tip) picker buttons (GuessControls): round button diameter + label + * size, in px — kept fixed like the rest of the board (not rem Tailwind classes) + * so they don't drift with the global font-size lever. */ +export const GUESS_BUTTON = { + desktop: { size: 48, font: 22 }, + mobile: { size: 40, font: 18 }, +} as const;