justify-content: center;
width: 44px;
height: 44px;
+ color: #000;
background: #f5e6c8;
border: 2px solid #c9a96e;
border-radius: 4px;
}
/* Board grid */
+.board-and-rack {
+ display: inline-flex;
+ flex-direction: column;
+ max-width: 100%;
+}
+
.board-container {
margin-bottom: 1em;
overflow: auto;
max-width: 100%;
+ --cell-size: 48px;
}
.board {
background: #fff;
}
-.board-container {
- --cell-size: 48px;
-}
-
.cell {
width: var(--cell-size);
height: var(--cell-size);
cursor: grab;
}
-/* Blank tile modal */
+/* Blank tile modal — positioned relative to the board-and-rack
+ container so it centers over the grid, not the full viewport. */
+.board-and-rack {
+ position: relative;
+}
+
.blank-modal-overlay {
position: fixed;
top: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.4);
+ z-index: 100;
+}
+
+.blank-modal-anchor {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
display: flex;
align-items: center;
justify-content: center;
- z-index: 100;
+ z-index: 101;
}
.blank-modal {
.blank-modal .letter-btn {
width: 38px;
height: 38px;
- border: 1px solid #999;
- border-radius: 4px;
- background: #f5e6c8;
font-size: 18px;
- font-weight: bold;
cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
}
.blank-modal .letter-btn:hover {
function BlankTileModal(props) {
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
- return (
- <div className="blank-modal-overlay" onClick={props.onCancel}>
+ return [
+ <div key="overlay" className="blank-modal-overlay" onClick={props.onCancel} />,
+ <div key="anchor" className="blank-modal-anchor" onClick={props.onCancel}>
<div className="blank-modal" onClick={e => e.stopPropagation()}>
<h3>Choose a letter for blank tile</h3>
<div className="letter-grid">
{letters.map(l => (
<button key={l}
- className="letter-btn"
+ className="tile letter-btn"
onClick={() => props.onChoose(l)}>
{l}
</button>
</div>
</div>
</div>
- );
+ ];
}
function Tile(props) {
if (cell && cell.isBlank) {
const [r, c] = grid_key.split(",").map(Number);
this.setState({
- blank_pending: { r, c, tileIndex: cell.tileIndex }
+ blank_pending: { r, c, tileIndex: cell.tileIndex, reassign: true }
});
return;
}
</div>
) : null,
- state.joined ? this.render_board(analysis, invalid_cells, unconnected_cells) : null,
-
- state.joined ? this.render_rack(can_complete) : null,
-
- state.blank_pending ? (
- <BlankTileModal
- key="bm"
- onChoose={(l) => this.choose_blank_letter(l)}
- onCancel={() => {
- /* Put the blank tile back on the rack if cancelled. */
- const { tileIndex } = state.blank_pending;
- const new_rack = [...state.rack, tileIndex];
- this.setState({ blank_pending: null, rack: new_rack });
- }}
- />
- ) : null,
-
- state.joined && Object.keys(state.grid).length > 0 && !can_complete ? (
- <div key="status" className="status">
- {!analysis.all_valid ? "Some words are not valid." :
- !analysis.all_connected ? "All tiles must be connected." :
- state.rack.length > 0 ? "Place all your tiles to complete." :
- null}
+ state.joined ? (
+ <div key="board-rack" className="board-and-rack">
+ {this.render_board(analysis, invalid_cells, unconnected_cells)}
+ {this.render_rack(can_complete)}
+ {state.blank_pending ? (
+ <BlankTileModal
+ onChoose={(l) => this.choose_blank_letter(l)}
+ onCancel={() => {
+ /* Put the blank tile back on the rack if cancelled,
+ * but only if it was freshly placed (not a reassignment
+ * of a blank already on the grid). */
+ if (state.blank_pending.reassign) {
+ this.setState({ blank_pending: null });
+ } else {
+ const { tileIndex } = state.blank_pending;
+ const new_rack = [...state.rack, tileIndex];
+ this.setState({ blank_pending: null, rack: new_rack });
+ }
+ }}
+ />
+ ) : null}
+ {Object.keys(state.grid).length > 0 && !can_complete ? (
+ <div className="status">
+ {!analysis.all_valid ? "Some words are not valid." :
+ !analysis.all_connected ? "All tiles must be connected." :
+ state.rack.length > 0 ? "Place all your tiles to complete." :
+ null}
+ </div>
+ ) : null}
</div>
) : null
];
const cols = bounds.maxC - bounds.minC + 1;
return (
- <div key="board" className="board-container">
+ <div className="board-container">
<div className="board"
style={{ gridTemplateColumns: `repeat(${cols}, var(--cell-size))` }}>
{rows.flat()}
if (rack_drag_over) className += " drag-over";
return (
- <div key="rack"
- data-rack="true"
+ <div data-rack="true"
className={className}
onDragOver={(e) => this.on_rack_drag_over(e)}
onDragLeave={(e) => this.on_rack_drag_leave(e)}