From 8942e7f6088cf60d1d29a519b0cbf045a5743635 Mon Sep 17 00:00:00 2001 From: thibaud-leclere Date: Tue, 31 Mar 2026 19:36:32 +0200 Subject: [PATCH] fix: integrate hint buttons into table for perfect row alignment and sticky scroll Move hint buttons from a separate flex column into the table as the first of each row, ensuring pixel-perfect alignment with grid rows. Use position:sticky with box-shadow to keep hints fixed on the left while scrolling horizontally. Co-Authored-By: Claude Opus 4.6 (1M context) --- assets/react/controllers/GameGrid.jsx | 77 ++++++++++++--------------- assets/react/controllers/GameRow.jsx | 6 ++- assets/styles/app.css | 38 ++++++------- 3 files changed, 54 insertions(+), 67 deletions(-) diff --git a/assets/react/controllers/GameGrid.jsx b/assets/react/controllers/GameGrid.jsx index b9de1c7..296dee9 100644 --- a/assets/react/controllers/GameGrid.jsx +++ b/assets/react/controllers/GameGrid.jsx @@ -4,52 +4,41 @@ import ActorPopover from './ActorPopover'; export default function GameGrid({ grid, width, middle }) { return ( -
-
- {grid.map((row, rowIndex) => { - if (row.separator !== undefined) { - return
; - } - return ( -
- -
- ); - })} -
-
- - - {grid.map((row, rowIndex) => { - if (row.separator !== undefined) { - return ( - - {Array.from({ length: middle }, (_, i) => ( - - {Array.from({ length: width - middle }, (_, i) => ( - - ); - } - +
+
- ))} - - {row.separator === ' ' ? '' : row.separator} - - ))} -
+ + {grid.map((row, rowIndex) => { + if (row.separator !== undefined) { return ( - + + + {Array.from({ length: width - middle }, (_, i) => ( + ); - })} - -
+ {Array.from({ length: middle }, (_, i) => ( + + ))} + + {row.separator === ' ' ? '' : row.separator} + + ))} +
-
+ } + + return ( + + ); + })} + +
); } diff --git a/assets/react/controllers/GameRow.jsx b/assets/react/controllers/GameRow.jsx index 3c505c5..29e86ad 100644 --- a/assets/react/controllers/GameRow.jsx +++ b/assets/react/controllers/GameRow.jsx @@ -1,11 +1,12 @@ import React, { useRef, useCallback, useMemo } from 'react'; import LetterInput from './LetterInput'; +import ActorPopover from './ActorPopover'; function isLetter(ch) { return /[a-zA-Z]/.test(ch); } -export default function GameRow({ actorName, pos, colStart, totalWidth }) { +export default function GameRow({ actorName, pos, colStart, totalWidth, hintType, hintText }) { const inputRefs = useRef([]); const letters = actorName.split(''); @@ -28,6 +29,9 @@ export default function GameRow({ actorName, pos, colStart, totalWidth }) { return ( + + + {Array.from({ length: totalWidth + 1 }, (_, colIndex) => { const charIndex = colIndex - colStart; const isInRange = charIndex >= 0 && charIndex < letters.length; diff --git a/assets/styles/app.css b/assets/styles/app.css index 61d7811..503e2b7 100644 --- a/assets/styles/app.css +++ b/assets/styles/app.css @@ -50,6 +50,7 @@ body { #actors td { width: var(--cell); height: var(--cell); + padding: 0; text-align: center; vertical-align: middle; } @@ -534,33 +535,26 @@ body { margin: 0; } -.game-grid-area { - display: flex; - align-items: flex-start; - margin-top: 16px; -} - -.hint-col { - display: flex; - flex-direction: column; - padding-top: 5px; - gap: 5px; - flex-shrink: 0; - padding-right: 12px; -} - .hint-cell { - height: var(--cell); - display: flex; - align-items: center; -} - -.hint-separator { - height: 12px; + padding: 0; + position: sticky; + left: 5px; + z-index: 1; + background: var(--surface); + box-shadow: + -5px 0 0 var(--surface), + 5px 0 0 var(--surface), + 0 -5px 0 var(--surface), + 0 5px 0 var(--surface), + -5px -5px 0 var(--surface), + 5px -5px 0 var(--surface), + -5px 5px 0 var(--surface), + 5px 5px 0 var(--surface); } .game-grid-scroll { overflow-x: auto; + margin-top: 16px; } /* ── Game actions ── */