@@ -541,6 +565,7 @@ const GamePage: FunctionComponent<{
{ name: bottomLocatedUser.name, stones: board.pits[bottomStore].stoneCount, isWinner: winnerSeat === getSeat(mancalaGame!, bottomLocatedUserId), isBottom: true },
{ name: topLocatedUser.name, stones: board.pits[topStore].stoneCount, isWinner: winnerSeat === getSeat(mancalaGame!, topLocatedUserId), isBottom: false },
]}
+ seriesScore={getSeriesScoreText()}
status={getRematchStatusText()}
waiting={rematch.status === "waiting"}
onClose={() => setGameOverClosed(true)}>
diff --git a/apps/frontend/src/styles/game.css b/apps/frontend/src/styles/game.css
index 07d46c0..3a094dd 100644
--- a/apps/frontend/src/styles/game.css
+++ b/apps/frontend/src/styles/game.css
@@ -13,6 +13,8 @@
.a-turn { grid-area: turn; min-width: 0; display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 8px; }
.a-me { grid-area: me; justify-self: end; min-width: 0; }
.a-board { grid-area: board; position: relative; min-width: 0; }
+.gl.has-series { grid-template-areas: "series series series" "opp turn me" "board board board" "moves moves moves"; }
+.a-series { grid-area: series; min-width: 0; }
.a-moves { grid-area: moves; width: 100%; max-width: 760px; justify-self: center; display: flex; flex-direction: column; gap: 16px; }
/* Players */
@@ -170,6 +172,30 @@
.ostatus.waiting { color: var(--muted); }
.ocard .row { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; margin-top: 6px; }
+/* Series strip: the score, then a box per game in the winner's colour, the game being played outlined. */
+
+.series { display: flex; align-items: center; justify-content: center; gap: 10px 16px; flex-wrap: wrap; min-width: 0; }
+.sscore { display: flex; align-items: baseline; gap: 8px; min-width: 0; font-size: 15px; white-space: nowrap; }
+.sscore b { font-size: 18px; font-weight: 800; font-variant-numeric: tabular-nums; }
+.slabel { font-size: 12px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
+.sname { max-width: 140px; overflow: hidden; text-overflow: ellipsis; font-weight: 700; }
+.sgames { display: flex; gap: 8px; max-width: 100%; margin: 0; padding: 2px; list-style: none; overflow-x: auto; scrollbar-width: none; }
+.sgames::-webkit-scrollbar { display: none; }
+.sg {
+ display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px;
+ min-width: 62px; height: 52px; padding: 0 8px; box-sizing: border-box; border-radius: 14px;
+ background: var(--surface2); color: var(--ink); text-decoration: none; font-variant-numeric: tabular-nums;
+}
+a.sg:hover { filter: brightness(1.08); }
+a.sg:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
+.sg.mine { background: var(--accent); color: var(--onAccent); }
+.sg.theirs { background: var(--ink); color: var(--bg); }
+.sg.now { background: transparent; box-shadow: inset 0 0 0 2px var(--accent); }
+.sg.more { color: var(--muted); font-weight: 800; }
+.sn { font-size: 11px; font-weight: 700; line-height: 1.2; opacity: .75; white-space: nowrap; }
+.ss { display: flex; align-items: center; gap: 3px; font-size: 15px; font-weight: 800; white-space: nowrap; }
+.sfirst { width: 5px; height: 5px; border-radius: 50%; background: currentColor; }
+
/* Tags of a move: extra turn, capture, stones to the store */
.tags { display: flex; gap: 6px; flex-wrap: wrap; }
@@ -179,6 +205,9 @@
@media (max-width: 640px) {
.gl { grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "opp turn" "board board" "me me" "moves moves"; gap: 16px 10px; }
+ .gl.has-series { grid-template-areas: "series series" "opp turn" "board board" "me me" "moves moves"; }
+ .series { flex-direction: column; gap: 8px; }
+ .sgames { justify-content: flex-start; }
.a-me { justify-self: start; }
.pl.r { flex-direction: row; text-align: left; }
.pl { gap: 8px; }
diff --git a/docs/specs/rematch/design.md b/docs/specs/rematch/design.md
index af79178..e1dd0df 100644
--- a/docs/specs/rematch/design.md
+++ b/docs/specs/rematch/design.md
@@ -111,10 +111,11 @@ The next game reaches both players as `on_game_start`, like a game from the lobb
- The final score as two columns, the bottom player's first: the name (small, muted) above the store count
(large), the winner's count in their colour (R3.8).
- The series score line once the series has more than one game (R3.6).
-- **Series strip** (`SeriesStrip`, R3): above the board in the game grid, shown from the second game of a series. The
+- **Series strip** (`SeriesStrip`, R3): the first row of the game grid, above the players and the board, shown from
+ the second game of a series. The
score, then one box per game: the winner's colour (`--accent` for the bottom player, the opponent's colour for the
top one, neutral for a draw), "30–18" from the bottom player's side, a small dot for the player who moved first, a
- small "time" or "left" tag. The current game's box is outlined and says "now". Finished boxes are links to
+ "time" or "left" after the game's number. The current game's box is outlined and says "now". Finished boxes are links to
`/game/` with `target="_blank"` (R3.4). At most 8 boxes; earlier games collapse into a "+N" box (R3.5). Below
600 px the strip scrolls sideways inside itself, never the page.
- Spectators: the strip uses the players' names instead of "You" (R3.7); they get no rematch buttons.
diff --git a/docs/specs/rematch/tasks.md b/docs/specs/rematch/tasks.md
index 25ddbfd..c195eef 100644
--- a/docs/specs/rematch/tasks.md
+++ b/docs/specs/rematch/tasks.md
@@ -12,7 +12,7 @@ Work through the tasks in order; each ends with its tests passing and a commit.
- [x] 5. **Core**: channel names, types, `getSeriesScore`, texts in English and Turkish; unit tests.
- [x] 6. **Web app, rematch**: the game page following `on_game_start` (`key={gameId}`), the game over card's rematch
states, "Play again" as a rematch, the score in two columns.
-- [ ] 7. **Web app, series strip**: `SeriesStrip`, the series score on the game over card, spectators.
+- [x] 7. **Web app, series strip**: `SeriesStrip`, the series score on the game over card, spectators.
- [ ] 8. **End to end and docs**: the Chrome tests from `design.md`; `AGENTS.md` (the specs list, rematch notes),
`TODO.md` (the out of scope items; remove "A rematch button" from Web redesign), the web redesign design's note
on the rematch button.