diff --git a/assets/react/controllers/ActorPopover.jsx b/assets/react/controllers/ActorPopover.jsx
index 61e3022..3941c4c 100644
--- a/assets/react/controllers/ActorPopover.jsx
+++ b/assets/react/controllers/ActorPopover.jsx
@@ -8,7 +8,7 @@ export default function ActorPopover({ actorName }) {
open: isOpen,
onOpenChange: setIsOpen,
middleware: [offset(8), flip(), shift()],
- placement: 'top',
+ placement: 'left',
});
const click = useClick(context);
diff --git a/assets/react/controllers/GameGrid.jsx b/assets/react/controllers/GameGrid.jsx
index 73cb81a..5c91d9d 100644
--- a/assets/react/controllers/GameGrid.jsx
+++ b/assets/react/controllers/GameGrid.jsx
@@ -5,15 +5,34 @@ export default function GameGrid({ grid, width, middle }) {
return (
- {grid.map((row, rowIndex) => (
-
- ))}
+ {grid.map((row, rowIndex) => {
+ if (row.separator !== undefined) {
+ return (
+
+ |
+ {Array.from({ length: middle }, (_, i) => (
+ |
+ ))}
+
+ {row.separator === ' ' ? '' : row.separator}
+ |
+ {Array.from({ length: width - middle }, (_, i) => (
+ |
+ ))}
+
+ );
+ }
+
+ return (
+
+ );
+ })}
);
diff --git a/assets/react/controllers/GameRow.jsx b/assets/react/controllers/GameRow.jsx
index 6caa8e0..8373562 100644
--- a/assets/react/controllers/GameRow.jsx
+++ b/assets/react/controllers/GameRow.jsx
@@ -1,19 +1,31 @@
-import React, { useRef, useCallback } from 'react';
+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 }) {
const inputRefs = useRef([]);
+ const letters = actorName.split('');
+
+ const letterIndices = useMemo(
+ () => letters.reduce((acc, ch, i) => { if (isLetter(ch)) acc.push(i); return acc; }, []),
+ [actorName]
+ );
const setInputRef = useCallback((index) => (el) => {
inputRefs.current[index] = el;
}, []);
- const focusInput = useCallback((index) => {
- inputRefs.current[index]?.focus();
- }, []);
-
- const letters = actorName.split('');
+ const focusNextInput = useCallback((charIndex, direction) => {
+ const currentPos = letterIndices.indexOf(charIndex);
+ const nextPos = currentPos + direction;
+ if (nextPos >= 0 && nextPos < letterIndices.length) {
+ inputRefs.current[letterIndices[nextPos]]?.focus();
+ }
+ }, [letterIndices]);
return (
@@ -28,13 +40,23 @@ export default function GameRow({ actorName, pos, colStart, totalWidth }) {
return | ;
}
+ const ch = letters[charIndex];
+
+ if (!isLetter(ch)) {
+ return (
+
+ {ch}
+ |
+ );
+ }
+
return (
focusInput(charIndex + 1)}
- onPrev={() => focusInput(charIndex - 1)}
+ onNext={() => focusNextInput(charIndex, 1)}
+ onPrev={() => focusNextInput(charIndex, -1)}
/>
);
})}
diff --git a/assets/styles/app.css b/assets/styles/app.css
index cd0252d..26fefe7 100644
--- a/assets/styles/app.css
+++ b/assets/styles/app.css
@@ -75,6 +75,27 @@ body {
color: var(--orange);
}
+.letter-static {
+ width: 38px;
+ height: 38px;
+ text-align: center;
+ font-family: 'Inter', sans-serif;
+ font-size: 15px;
+ font-weight: 700;
+ text-transform: uppercase;
+ color: var(--text);
+ vertical-align: middle;
+}
+
+.separator-row td {
+ height: 12px;
+ padding: 0;
+}
+
+.separator-char {
+ height: 12px;
+}
+
/* ── Popover ── */
.popover-trigger {
diff --git a/src/Service/GameGridGenerator.php b/src/Service/GameGridGenerator.php
index 564069f..d13cb57 100644
--- a/src/Service/GameGridGenerator.php
+++ b/src/Service/GameGridGenerator.php
@@ -71,7 +71,25 @@ class GameGridGenerator
$rightSize = 0;
$grid = [];
- foreach ($game->getRows() as $row) {
+ $mainActorChars = str_split($game->getMainActor()->getName());
+ $rows = $game->getRows()->toArray();
+ $rowIndex = 0;
+
+ foreach ($mainActorChars as $char) {
+ if (!preg_match('/[a-zA-Z]/', $char)) {
+ $grid[] = [
+ 'separator' => $char,
+ ];
+ continue;
+ }
+
+ $row = $rows[$rowIndex] ?? null;
+ ++$rowIndex;
+
+ if ($row === null) {
+ continue;
+ }
+
$actor = $row->getActor();
$pos = $row->getPosition();