Compare commits

..

2 Commits

Author SHA1 Message Date
372e246124 more frontend fixes 2025-11-14 15:17:31 +01:00
9f02bc1888 more frontend fixes 2025-11-13 18:41:34 +01:00
9 changed files with 1254 additions and 241 deletions

View File

@ -50,6 +50,10 @@ class Crossword:
def current_grid(self) -> List[List[Optional[str]]]:
return self._current_grid
@property
def solution_word_positions(self) -> Optional[List[Tuple[int, int]]]:
return self._solution_word_positions
def get_words_by_y_x_position(self, x, y) -> List[CrosswordWord]:
"""Get the list of CrosswordWord objects that start at position (x, y)."""
@ -123,13 +127,62 @@ class Crossword:
logger.info("Crossword generated successfully for grid size %dx%d with block ratio %.2f", grid_width, grid_height, grid_block_ratio)
# Build letter position index for efficient lookup
letter_to_positions: Dict[str, List[Tuple[int, int]]] = {}
for r in range(len(final_step.grid)):
for c in range(len(final_step.grid[0])):
cell = final_step.grid[r][c]
if cell and cell != '#':
if cell not in letter_to_positions:
letter_to_positions[cell] = []
letter_to_positions[cell].append((r, c))
# Now find a solution word: generate a random word from the dictionary (with 10-20 letters)
# and try to find the necessary letter positions in the grid. if we fail we repeat with another word (max 10 tries)
solution_word_positions: Optional[List[Tuple[int, int]]] = None
max_solution_word_attempts = 10
for _ in range(max_solution_word_attempts):
random_length = random.randint(10, 20)
possible_words = dictionary.find_by_pattern('*' * random_length)
if not possible_words:
continue
chosen_word = random.choice(possible_words)
letter_positions = []
used_positions = set()
for letter in chosen_word.word:
if letter not in letter_to_positions:
letter_positions = []
break
# Pick random position for this letter that's not already used
available = [p for p in letter_to_positions[letter] if p not in used_positions]
if not available:
letter_positions = []
break
chosen_position = random.choice(available)
letter_positions.append(chosen_position)
used_positions.add(chosen_position)
if len(letter_positions) == random_length:
solution_word_positions = letter_positions
break
if solution_word_positions is None:
logger.warning("Failed to find a solution word for the generated crossword after %d attempts", max_solution_word_attempts)
return None
cw = Crossword(
dictionary=dictionary,
grid=final_step.grid,
solution_word_positions=solution_word_positions
)
logger.debug("Generated Crossword: \n\n%s", cw)
return cw
@ -139,7 +192,19 @@ class Crossword:
grid: List[List[Optional[str]]],
current_grid: Optional[List[List[Optional[str]]]] = None,
words: Optional[List[CrosswordWord]] = None,
solution_word_positions: Optional[List[Tuple[int, int]]] = None,
):
"""
Initialize a Crossword object.
Args:
dictionary (Dictionary): The dictionary containing words and hints.
grid (List[List[Optional[str]]]): The solved crossword grid.
current_grid (Optional[List[List[Optional[str]]]]): The current state of the crossword grid.
words (Optional[List[CrosswordWord]]): Pre-extracted list of CrosswordWord objects.
solution_word_positions (Optional[List[Tuple[int, int]]]): Positions of letters building the solution word.
"""
self._dictionary = dictionary
self._solved_grid = grid
self._words: List[CrosswordWord] = []
@ -147,6 +212,8 @@ class Crossword:
self._horizontal_words_by_y_x_position = {}
self._vertical_words_by_y_x_position = {}
self._solution_word_positions = solution_word_positions
if current_grid is not None:
self._current_grid = current_grid

View File

@ -6,7 +6,7 @@ DEFAULT_WEBSOCKET_PORT = 8765
DEFAULT_MIN_GRID_SIZE = 10
DEFAULT_MAX_GRID_SIZE = 30
DEFAULT_GRID_BLOCK_RATIO = 0.38
DEFAULT_GRID_BLOCK_RATIO = 0.39
DEFAULT_MAX_SESSION_IDLE_TIME_SECONDS = 3600 * 48 # 2 days

View File

@ -30,9 +30,10 @@ class SendFullSessionStateServerMessage(ServerMessageBase):
grid: list[list[str]] # 2D array representing the current grid state
clues_across: dict[str, str] # mapping from clue number to clue text for across clues
clues_down: dict[str, str] # mapping from clue number to clue text for down clues
clue_positions_across: dict[str, tuple[int, int]] # mapping from clue number to its (col, row) position
clue_positions_down: dict[str, tuple[int, int]] # mapping from clue number to its (col, row) position
solved_positions: list[tuple[int, int]] # list of (col, row) positions that are solved
clue_positions_across: dict[str, tuple[int, int]] # mapping from clue number to its (row, col) position
clue_positions_down: dict[str, tuple[int, int]] # mapping from clue number to its (row, col) position
solved_positions: list[tuple[int, int]] # list of (row, col) positions that are solved
solution_word_positions: list[tuple[int, int]] # list of (row, col) positions that are part of solution word
class LetterUpdateBroadcastServerMessage(ServerMessageBase):
type: str = "letter_update"

View File

@ -224,6 +224,11 @@ class WebsocketCrosswordServer(object):
solved_positions = list(solved_positions)
solution_word_positions = []
positions = session.crossword.solution_word_positions
for pos in positions:
solution_word_positions.append((pos[1], pos[0])) # convert (row, col) to (col, row)
response = server_messages.SendFullSessionStateServerMessage(
session_id=session.session_id,
grid=grid_state,
@ -232,6 +237,7 @@ class WebsocketCrosswordServer(object):
clue_positions_across=clue_positions_across,
clue_positions_down=clue_positions_down,
solved_positions=solved_positions,
solution_word_positions=solution_word_positions,
)
# register the client to the session
session.add_client(handler)

View File

@ -15,8 +15,11 @@ export class ClueArea extends LitElement {
selectedCol: { type: Number },
selectedMode: { type: String }, // 'horizontal' or 'vertical'
grid: { type: Array }, // 2D grid from server (needed to find walls)
gridData: { type: Object }, // { rows, cols, walls, solvedCells }
_showAllCluesAcross: { state: true },
_showAllCluesDown: { state: true }
_showAllCluesDown: { state: true },
_solvedCluesAcross: { state: true }, // Set of solved clue numbers
_solvedCluesDown: { state: true } // Set of solved clue numbers
};
}
@ -30,8 +33,11 @@ export class ClueArea extends LitElement {
this.selectedCol = 0;
this.selectedMode = 'horizontal';
this.grid = [];
this.gridData = { rows: 0, cols: 0, walls: new Set(), solvedCells: new Set() };
this._showAllCluesAcross = false;
this._showAllCluesDown = false;
this._solvedCluesAcross = new Set();
this._solvedCluesDown = new Set();
}
/**
@ -178,7 +184,7 @@ export class ClueArea extends LitElement {
}
}
_toggleShowAllClues() {
_toggleShowAllCluesAcross() {
this._showAllCluesAcross = !this._showAllCluesAcross;
}
@ -186,6 +192,143 @@ export class ClueArea extends LitElement {
this._showAllCluesDown = !this._showAllCluesDown;
}
/**
* Find the starting row,col of an across clue by clue number
*/
_getAcrossClueStart(clueNum) {
const position = this.cluePositionsAcross[clueNum];
if (!position) return null;
// Server sends (x, y) = (col, row)
const col = position[0];
const row = position[1];
return { row, col };
}
/**
* Find the starting row,col of a down clue by clue number
*/
_getDownClueStart(clueNum) {
const position = this.cluePositionsDown[clueNum];
if (!position) return null;
// Server sends (x, y) = (col, row)
const col = position[0];
const row = position[1];
return { row, col };
}
/**
* Get all cells that belong to an across clue
*/
_getAcrossCluesCells(clueNum) {
const startPos = this._getAcrossClueStart(clueNum);
if (!startPos) return [];
const { row, col } = startPos;
const cells = [];
// Expand right until we hit a wall
for (let c = col; c < this.gridData.cols; c++) {
if (this.gridData.walls.has(`${row},${c}`)) {
break;
}
cells.push({ row, col: c });
}
return cells;
}
/**
* Get all cells that belong to a down clue
*/
_getDownCluesCells(clueNum) {
const startPos = this._getDownClueStart(clueNum);
if (!startPos) return [];
const { row, col } = startPos;
const cells = [];
// Expand down until we hit a wall
for (let r = row; r < this.gridData.rows; r++) {
if (this.gridData.walls.has(`${r},${col}`)) {
break;
}
cells.push({ row: r, col });
}
return cells;
}
/**
* Check if a clue is fully solved
*/
_isCluesSolved(clueNum, direction) {
const cells = direction === 'across'
? this._getAcrossCluesCells(clueNum)
: this._getDownCluesCells(clueNum);
return cells.length > 0 && cells.every(cell =>
this.gridData.solvedCells.has(`${cell.row},${cell.col}`)
);
}
/**
* Update which clues are solved
*/
_updateSolvedClues() {
this._solvedCluesAcross = new Set();
this._solvedCluesDown = new Set();
for (const clueNum of Object.keys(this.cluesAcross)) {
if (this._isCluesSolved(clueNum, 'across')) {
this._solvedCluesAcross.add(clueNum);
}
}
for (const clueNum of Object.keys(this.cluesDown)) {
if (this._isCluesSolved(clueNum, 'down')) {
this._solvedCluesDown.add(clueNum);
}
}
}
/**
* Handle clue item click - focus the cell and set orientation
*/
_onClueItemClick(clueNum, direction) {
let startPos;
let mode;
if (direction === 'across') {
startPos = this._getAcrossClueStart(clueNum);
mode = 'horizontal';
} else {
startPos = this._getDownClueStart(clueNum);
mode = 'vertical';
}
if (!startPos) return;
// Update selected cell and mode in parent grid
this.selectedRow = startPos.row;
this.selectedCol = startPos.col;
this.selectedMode = mode;
// Dispatch event to notify grid component
this.dispatchEvent(new CustomEvent('clue-selected', {
detail: {
row: startPos.row,
col: startPos.col,
mode: mode
},
bubbles: true,
composed: true
}));
// Close the all-clues view and return to default view
this._showAllCluesAcross = false;
this._showAllCluesDown = false;
}
render() {
const currentClue = this._getCurrentClue();
@ -195,15 +338,15 @@ export class ClueArea extends LitElement {
<div class="clue-area">
<div class="clue-header">
<h3>Across Clues</h3>
<button class="clue-toggle" @click="${this._toggleShowAllClues}">
<span class="chevron"></span>
<button class="clue-toggle" @click="${this._toggleShowAllCluesAcross}">
<span class="chevron"></span>
</button>
</div>
<div class="clue-list-container">
<div class="clue-list">
${Object.entries(this.cluesAcross).map(([num, text]) => html`
<div class="clue-item">
<div class="clue-item ${this._solvedCluesAcross.has(num) ? 'solved' : ''}" @click="${() => this._onClueItemClick(num, 'across')}" style="cursor: pointer;">
<span class="clue-number">${num}.</span>
<span class="clue-text">${text}</span>
</div>
@ -221,14 +364,14 @@ export class ClueArea extends LitElement {
<div class="clue-header">
<h3>Down Clues</h3>
<button class="clue-toggle" @click="${this._toggleShowAllCluesDown}">
<span class="chevron"></span>
<span class="chevron"></span>
</button>
</div>
<div class="clue-list-container">
<div class="clue-list">
${Object.entries(this.cluesDown).map(([num, text]) => html`
<div class="clue-item">
<div class="clue-item ${this._solvedCluesDown.has(num) ? 'solved' : ''}" @click="${() => this._onClueItemClick(num, 'down')}" style="cursor: pointer;">
<span class="clue-number">${num}.</span>
<span class="clue-text">${text}</span>
</div>
@ -253,11 +396,13 @@ export class ClueArea extends LitElement {
`}
<div class="clue-toggle-group">
<button class="clue-toggle" @click="${this._toggleShowAllClues}" title="Show all across clues">
<span class="chevron">▶ A</span>
<div class="clue-text empty">Show all clues:</div>
<button class="clue-toggle" @click="${this._toggleShowAllCluesAcross}" title="Show all across clues">
<span class="chevron">▶</span>
</button>
<button class="clue-toggle" @click="${this._toggleShowAllCluesDown}" title="Show all down clues">
<span class="chevron">▼ D</span>
<span class="chevron">▼</span>
</button>
</div>
</div>

View File

@ -17,6 +17,11 @@ export class CrosswordGrid extends LitElement {
_selected: { state: true },
_inputMode: { state: true }, // 'horizontal' or 'vertical'
_solvedCells: { state: true }, // tracks which cells are solved
_clueNumbers: { state: true }, // map of "row,col" -> { across: number, down: number }
_solutionIndices: { state: true }, // map of "row,col" -> solution index
_solutionWordPositions: { state: true }, // list of [col, row] positions for solution word
_solutionWordValues: { state: true }, // map of index -> letter for solution word
_solutionWordSolved: { state: true }, // set of solution word indices that are solved
};
// styles moved to webui/styles.css; render into light DOM so external CSS applies
@ -29,6 +34,11 @@ export class CrosswordGrid extends LitElement {
this._selected = { r: 0, c: 0 };
this._inputMode = 'horizontal'; // default input mode
this._solvedCells = new Set(); // set of "r,c" strings for solved cells
this._clueNumbers = new Map(); // map of "row,col" -> { across: number, down: number }
this._solutionIndices = new Map(); // map of "row,col" -> solution index (1-indexed)
this._solutionWordPositions = []; // list of [col, row] positions
this._solutionWordValues = new Map(); // map of index -> letter
this._solutionWordSolved = new Set(); // set of solution word indices that are solved
this.sessionId = null; // Session ID for sending updates to server
}
@ -71,8 +81,31 @@ export class CrosswordGrid extends LitElement {
this._ensureGrid();
// set CSS variables for cell-size and column count; layout done in external stylesheet
return html`
<div class="grid" style="--cell-size: ${this._cellSize}px; --cols: ${this.cols};">
${this._grid.map((row, r) => row.map((cell, c) => this._renderCell(r, c, cell))).flat()}
<div class="grid-container ${this._isSolutionWordComplete() ? 'complete' : ''}">
<div class="grid" style="--cell-size: ${this._cellSize}px; --cols: ${this.cols};">
${this._grid.map((row, r) => row.map((cell, c) => this._renderCell(r, c, cell))).flat()}
</div>
</div>
${this._solutionWordPositions.length > 0 ? html`
<h3 style="margin-top: 2rem;">Solution Word</h3>
<div class="grid solution-word-grid" style="--cell-size: 40px; --cols: ${this._solutionWordPositions.length};">
${this._solutionWordPositions.map((pos, i) => this._renderSolutionCell(i, pos))}
</div>
` : ''}
`;
}
_renderSolutionCell(index, position) {
const letter = this._solutionWordValues.get(index) || '';
const isSolved = this._solutionWordSolved.has(index);
const classes = ['cell'];
if (isSolved) classes.push('solved');
return html`
<div class="${classes.join(' ')}" data-solution-index="${index}" data-row="${position[1]}" data-col="${position[0]}" @click=${() => this._onSolutionCellClick(index, position)}>
<div class="solution-circle"></div>
<span class="solution-index">${index + 1}</span>
<span class="cell-letter">${letter}</span>
</div>
`;
}
@ -119,7 +152,34 @@ export class CrosswordGrid extends LitElement {
}
}
return html`<div class="${classes.join(' ')}" @click=${() => this._onCellClick(r, c)} data-r="${r}" data-c="${c}">${value}</div>`;
// Get clue numbers for this cell
const clueInfo = this._clueNumbers.get(cellKey);
let clueNumberDisplay = '';
if (clueInfo) {
if (clueInfo.across !== null && clueInfo.down !== null) {
// Both across and down clues: show "across/down" format
clueNumberDisplay = `${clueInfo.across}/${clueInfo.down}`;
} else if (clueInfo.across !== null) {
// Only across clue
clueNumberDisplay = String(clueInfo.across);
} else if (clueInfo.down !== null) {
// Only down clue
clueNumberDisplay = String(clueInfo.down);
}
}
// Get solution index for this cell
const solutionIndex = this._solutionIndices.get(cellKey);
const cellContent = clueNumberDisplay
? html`<span class="clue-number">${clueNumberDisplay}</span><span class="cell-letter">${value}</span>`
: html`<span class="cell-letter">${value}</span>`;
const cellHTML = solutionIndex !== undefined
? html`${cellContent}<div class="solution-circle"></div><span class="solution-index">${solutionIndex}</span>`
: cellContent;
return html`<div class="${classes.join(' ')}" @click=${() => this._onCellClick(r, c)} data-r="${r}" data-c="${c}">${cellHTML}</div>`;
}
/**
@ -182,6 +242,14 @@ export class CrosswordGrid extends LitElement {
return end - start + 1;
}
/**
* Check if the entire solution word is solved
*/
_isSolutionWordComplete() {
if (this._solutionWordPositions.length === 0) return false;
return this._solutionWordPositions.every((_, i) => this._solutionWordSolved.has(i));
}
/**
* Check if cell (r, c) is part of the vertical line from the selected cell
* (i.e., same column and not blocked by walls above/below this cell)
@ -206,25 +274,35 @@ export class CrosswordGrid extends LitElement {
return r >= start && r <= end;
}
_onCellClick(r, c) {
_onCellClick(r, c, preferredMode = null) {
// if same cell is clicked again, toggle the input mode
if (this._selected.r === r && this._selected.c === c) {
this._inputMode = this._inputMode === 'horizontal' ? 'vertical' : 'horizontal';
// If a preferred mode is provided, use it (don't toggle)
if (preferredMode) {
this._inputMode = preferredMode;
} else {
this._inputMode = this._inputMode === 'horizontal' ? 'vertical' : 'horizontal';
}
} else {
// select a new cell
this._selected = { r, c };
// auto-select mode based on line lengths
const horizontalLength = this._getHorizontalLineLength(r, c);
const verticalLength = this._getVerticalLineLength(r, c);
// Use preferred mode if provided, otherwise auto-select based on line lengths
if (preferredMode) {
this._inputMode = preferredMode;
} else {
// auto-select mode based on line lengths
const horizontalLength = this._getHorizontalLineLength(r, c);
const verticalLength = this._getVerticalLineLength(r, c);
// if one mode only has 1 cell but the other has multiple, use the one with multiple
if (horizontalLength === 1 && verticalLength > 1) {
this._inputMode = 'vertical';
} else if (verticalLength === 1 && horizontalLength > 1) {
this._inputMode = 'horizontal';
// if one mode only has 1 cell but the other has multiple, use the one with multiple
if (horizontalLength === 1 && verticalLength > 1) {
this._inputMode = 'vertical';
} else if (verticalLength === 1 && horizontalLength > 1) {
this._inputMode = 'horizontal';
}
// otherwise keep current mode (both >1 or both =1)
}
// otherwise keep current mode (both >1 or both =1)
}
this.requestUpdate();
this.dispatchEvent(new CustomEvent('cell-selected', { detail: { row: r, col: c, mode: this._inputMode }, bubbles: true, composed: true }));
@ -232,6 +310,12 @@ export class CrosswordGrid extends LitElement {
this.focus();
}
_onSolutionCellClick(index, position) {
// When clicking a solution word cell, select the corresponding grid cell
const [col, row] = position;
this._onCellClick(row, col);
}
_onKeydown(e) {
// Only handle keys when the grid has focus
// Map letters, arrows and backspace to our handlers
@ -461,10 +545,125 @@ export class CrosswordGrid extends LitElement {
this._solvedCells.delete(cellKey);
}
// Update solution word if this position is part of it
for (let i = 0; i < this._solutionWordPositions.length; i++) {
const [col_sw, row_sw] = this._solutionWordPositions[i];
if (row === row_sw && col === col_sw) {
this._solutionWordValues.set(i, letter);
// Mark solution word cell as solved
if (is_solved) {
this._solutionWordSolved.add(i);
} else {
this._solutionWordSolved.delete(i);
}
break;
}
}
this.requestUpdate();
// Trigger animation if solution word just completed
if (this._isSolutionWordComplete()) {
this.updateComplete.then(() => {
const gridContainer = this.querySelector('.solution-word-grid');
if (gridContainer) {
// Force reflow to trigger animation
gridContainer.offsetHeight;
gridContainer.classList.remove('complete');
gridContainer.offsetHeight;
gridContainer.classList.add('complete');
}
});
}
// Emit a letter-changed event so solution word can update
this.dispatchEvent(new CustomEvent('letter-changed', {
detail: { row, col, letter, is_solved },
bubbles: true,
composed: true
}));
console.log(`Letter update from server: [${row}, ${col}] = "${letter}" (solved: ${is_solved})`);
}
}
/**
* Populate clue numbers from server data
* @param {Object} cluePositionsAcross - dict of clue_number -> [col, row]
* @param {Object} cluePositionsDown - dict of clue_number -> [col, row]
*/
populateClueNumbers(cluePositionsAcross = {}, cluePositionsDown = {}) {
this._clueNumbers.clear();
// Add across clues
for (const [clueNum, position] of Object.entries(cluePositionsAcross)) {
const [col, row] = position;
const cellKey = `${row},${col}`;
if (!this._clueNumbers.has(cellKey)) {
this._clueNumbers.set(cellKey, { across: null, down: null });
}
this._clueNumbers.get(cellKey).across = parseInt(clueNum);
}
// Add down clues
for (const [clueNum, position] of Object.entries(cluePositionsDown)) {
const [col, row] = position;
const cellKey = `${row},${col}`;
if (!this._clueNumbers.has(cellKey)) {
this._clueNumbers.set(cellKey, { across: null, down: null });
}
this._clueNumbers.get(cellKey).down = parseInt(clueNum);
}
this.requestUpdate();
}
/**
* Populate solution word indices from server data
* @param {Array} solutionPositions - list of [col, row] positions in order
*/
populateSolutionIndices(solutionPositions = []) {
this._solutionIndices.clear();
this._solutionWordPositions = solutionPositions;
this._solutionWordValues.clear();
this._solutionWordSolved.clear();
for (let i = 0; i < solutionPositions.length; i++) {
const [col, row] = solutionPositions[i];
const cellKey = `${row},${col}`;
this._solutionIndices.set(cellKey, i + 1); // 1-indexed
// Initialize solution word value with current grid letter
const letter = this._grid[row][col] || '';
this._solutionWordValues.set(i, letter);
// Check if this position is already solved
if (this._solvedCells.has(cellKey)) {
this._solutionWordSolved.add(i);
}
}
console.log('Solution word initialized. Solved:', this._solutionWordSolved.size, 'Total:', this._solutionWordPositions.length);
this.requestUpdate();
// Trigger animation on init if already complete
if (this._isSolutionWordComplete()) {
this.updateComplete.then(() => {
const gridContainer = this.querySelector('.solution-word-grid');
if (gridContainer) {
// Force reflow to trigger animation
gridContainer.offsetHeight;
gridContainer.classList.remove('complete');
gridContainer.offsetHeight;
gridContainer.classList.add('complete');
}
});
}
}
}
customElements.define('crossword-grid', CrosswordGrid);

View File

@ -125,7 +125,14 @@
gridContainer.innerHTML = `
<div class="game-header">
<h2>Crossword</h2>
<button class="close-game-btn" aria-label="Close game">✕</button>
<div class="header-buttons">
<button class="share-game-btn" aria-label="Share game">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor">
<path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.15c.52.47 1.2.77 1.96.77 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.44 9.31 6.77 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.77 0 1.44-.3 1.96-.77l7.12 4.16c-.057.21-.087.43-.087.66 0 1.61 1.31 2.92 2.92 2.92 1.61 0 2.92-1.31 2.92-2.92s-1.31-2.92-2.92-2.92z"/>
</svg>
</button>
<button class="close-game-btn" aria-label="Close game">✕</button>
</div>
</div>
<div class="game-content">
</div>
@ -163,6 +170,7 @@
for (let r = 0; r < gridRows; r++) {
for (let c = 0; c < gridCols; c++) {
const cell = message.grid[r][c];
// Skip walls and empty cells
if (cell !== '#' && cell !== '') {
gridElement._grid[r][c] = cell;
@ -178,6 +186,14 @@
}
}
// Populate clue numbers for display
gridElement.populateClueNumbers(message.clue_positions_across, message.clue_positions_down);
// Populate solution word
if (message.solution_word_positions) {
gridElement.populateSolutionIndices(message.solution_word_positions);
}
gridElement.requestUpdate();
console.log(`Grid created: ${gridRows}x${gridCols} with ${wallPositions.length} walls`);
@ -185,6 +201,9 @@
if (message.solved_positions) {
console.log(`Solved positions: ${message.solved_positions.length}`);
}
if (message.solution_word_positions) {
console.log(`Solution word positions: ${message.solution_word_positions.length}`);
}
}, 0);
// Create and add clue area
@ -194,9 +213,36 @@
clueArea.cluePositionsAcross = message.clue_positions_across;
clueArea.cluePositionsDown = message.clue_positions_down;
clueArea.grid = message.grid; // Pass grid for dimension calculation
// Setup gridData for solved clue tracking
const walls = new Set();
for (let r = 0; r < gridRows; r++) {
for (let c = 0; c < gridCols; c++) {
if (message.grid[r][c] === '#') {
walls.add(`${r},${c}`);
}
}
}
const solvedCells = new Set();
if (message.solved_positions) {
for (const [col, row] of message.solved_positions) {
solvedCells.add(`${row},${col}`);
}
}
clueArea.gridData = {
rows: gridRows,
cols: gridCols,
walls: walls,
solvedCells: solvedCells
};
clueArea.selectedRow = 0;
clueArea.selectedCol = 0;
clueArea.selectedMode = 'horizontal';
// Update solved clues initially
clueArea._updateSolvedClues();
document.body.insertBefore(clueArea, document.body.firstChild);
// Listen for cell selection changes
@ -207,12 +253,157 @@
clueArea.requestUpdate();
});
// Listen for clue clicks to navigate grid
clueArea.addEventListener('clue-selected', (e) => {
const { row, col, mode } = e.detail;
// Call _onCellClick with preferred mode from the clue
gridElement._onCellClick(row, col, mode);
gridElement.focus();
});
// Listen for letter updates to update solved clues
gridElement.addEventListener('letter-changed', (e) => {
const { row, col, is_solved } = e.detail;
const cellKey = `${row},${col}`;
if (is_solved) {
clueArea.gridData.solvedCells.add(cellKey);
} else {
clueArea.gridData.solvedCells.delete(cellKey);
}
clueArea._updateSolvedClues();
clueArea.requestUpdate();
});
// Close button handler
closeBtn.addEventListener('click', closeGame);
const shareBtn = gridContainer.querySelector('.share-game-btn');
shareBtn.addEventListener('click', shareGame);
notificationManager.success('Game loaded successfully');
});
// Function to share game
function shareGame() {
console.log('Sharing game with session ID:', currentSessionId);
// Build URL with session ID
const url = `${window.location.origin}${window.location.pathname}?session_id=${currentSessionId}`;
// Try native share API first (mobile)
if (navigator.share) {
navigator.share({
title: 'Join my Crossword!',
text: 'Play crossword with me!',
url: url
}).then(() => {
console.log('Share successful');
}).catch(err => {
if (err.name !== 'AbortError') {
console.error('Error sharing:', err);
showShareDialog(url);
}
});
} else {
// Fallback: show dialog with link
showShareDialog(url);
}
}
// Function to show share dialog with copy option
function showShareDialog(url) {
console.log('Showing share dialog with URL:', url);
// Create modal dialog
const dialog = document.createElement('div');
dialog.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
`;
const content = document.createElement('div');
content.style.cssText = `
background: #2a2520;
padding: 1.5rem;
border-radius: 0.5rem;
max-width: 90%;
width: 100%;
max-width: 400px;
box-shadow: 0 10px 40px rgba(0,0,0,0.5);
`;
content.innerHTML = `
<h3 style="margin: 0 0 1rem 0; color: #f5f1ed; font-size: 1.2rem;">Share Game Link</h3>
<p style="margin: 0 0 1rem 0; color: #d4cdc5; font-size: 0.9rem;">Copy this link and send it to friends:</p>
<input type="text" value="${url}" readonly style="
width: 100%;
padding: 0.75rem;
background: #1a1511;
color: #f5f1ed;
border: 1px solid #5a4a4a;
border-radius: 0.25rem;
font-family: monospace;
font-size: 0.85rem;
box-sizing: border-box;
margin-bottom: 1rem;
" id="share-url-input" />
<div style="display: flex; gap: 0.5rem;">
<button id="copy-btn" style="
flex: 1;
padding: 0.75rem;
background: #4a7a9e;
color: #f5f1ed;
border: none;
border-radius: 0.25rem;
cursor: pointer;
font-weight: 600;
">Copy</button>
<button id="close-share-btn" style="
flex: 1;
padding: 0.75rem;
background: #5a4a4a;
color: #f5f1ed;
border: none;
border-radius: 0.25rem;
cursor: pointer;
font-weight: 600;
">Close</button>
</div>
`;
dialog.appendChild(content);
document.body.appendChild(dialog);
// Copy button
document.getElementById('copy-btn').addEventListener('click', () => {
const input = document.getElementById('share-url-input');
input.select();
document.execCommand('copy');
notificationManager.success('Link copied!');
dialog.remove();
});
// Close button
document.getElementById('close-share-btn').addEventListener('click', () => {
dialog.remove();
});
// Close on background click
dialog.addEventListener('click', (e) => {
if (e.target === dialog) {
dialog.remove();
}
});
}
// Function to close game and return to menu
function closeGame() {
console.log('Closing game');

View File

@ -25,72 +25,97 @@ export class MobileKeyboard extends LitElement {
createRenderRoot() { return this; }
render() {
// simple QWERTY-like rows
const rows = [
'qwertyuiop'.split(''),
'asdfghjkl'.split(''),
'zxcvbnm'.split(''),
];
render() {
// simple QWERTY-like rows
const rows = [
'qwertyuiop'.split(''),
'asdfghjkl'.split(''),
'zxcvbnm'.split(''),
];
// compute the maximum number of columns across rows (account for backspace in first row)
const counts = rows.map((r, idx) => r.length + (idx === 0 ? 1 : 0));
const arrowCols = 3; // reserve 3 columns on the right for [left][down][right]
const baseMax = Math.max(...counts, 10);
const maxCols = baseMax;
// compute the maximum number of columns across rows (account for backspace in second row now)
const counts = rows.map((r, idx) => r.length + (idx === 1 ? 1 : 0));
const arrowCols = 3; // reserve 3 columns on the right for [left][down][right]
const baseMax = Math.max(...counts, 10);
const maxCols = baseMax;
return html`
<div class="keyboard-container">
${html`<div class="handle" @click=${this._toggleCollapse}>${this.collapsed ? '▲' : '▼'}</div>`}
<div class="keyboard-wrapper">
<div class="keyboard">
${rows.map((r, idx) => {
// center the letter keys leaving the rightmost `arrowCols` for the arrow block
return html`
<div class="keyboard-container">
${html`<div class="handle" @click=${this._toggleCollapse}>${this.collapsed ? '▲' : '▼'}</div>`}
<div class="keyboard-wrapper">
<div class="keyboard">
${rows.map((r, idx) => {
// center the letter keys leaving the rightmost `arrowCols` for the arrow block
let rowClasses = 'row';
if (idx === 1) rowClasses += ' stagger'; // A row
if (idx === 2) rowClasses += ' stagger-deep'; // Z row needs a larger indent
return html`<div class="${rowClasses}" style="--cols:${maxCols-idx}; --arrow-cols:${arrowCols};">
<div class="keys">
${r.map(l => html`<button @click=${() => this._emitLetter(l)}>${l}</button>`) }
${idx === 0 ? html`<button class="backspace" @click=${() => this._emit({ type: 'backspace' })}>⌫</button>` : ''}
</div>
<div class="arrows">
${Array.from({ length: arrowCols }).map((_, i) => {
if (idx === 2 && i === 1) return html`<button class="nav" @click=${() => this._emitNavigate('up')}>▲</button>`;
return html`<div class="key-spacer"></div>`;
})}
</div>
</div>`;
})}
let rowClasses = 'row';
if (idx === 1) rowClasses += ' stagger'; // A row
if (idx === 2) rowClasses += ' stagger-deep'; // Z row needs a larger indent
return html`<div class="${rowClasses}" style="--cols:${maxCols-idx}; --arrow-cols:${arrowCols};">
<div class="keys">
${r.map(l => html`<button @click=${() => this._emitLetter(l)}>${l}</button>`) }
${idx === 1 ? html`<button class="backspace" @click=${() => this._emitBackspace()}>⌫</button>` : ''}
</div>
<div class="arrows">
${Array.from({ length: arrowCols }).map((_, i) => {
if (idx === 2 && i === 1) return html`<button class="nav" @click=${() => this._emitNavigate('up')}>▲</button>`;
return html`<div class="key-spacer"></div>`;
})}
</div>
</div>`;
})}
<!-- spacebar row -->
<div class="row" style="--cols:${maxCols};">
<!-- spacebar spans all but the right arrow columns -->
<button class="space" @click=${() => this._emit({ type: 'letter', value: '' })}></button>
<!-- arrow columns: left, down, right (will occupy the last 3 columns) -->
<button class="nav" @click=${() => this._emitNavigate('left')}></button>
<button class="nav" @click=${() => this._emitNavigate('down')}></button>
<button class="nav" @click=${() => this._emitNavigate('right')}></button>
</div>
<!-- spacebar row -->
<div class="row" style="--cols:${maxCols};">
<!-- spacebar spans all but the right arrow columns -->
<button class="space" @click=${() => this._emitSpace()}></button>
<!-- arrow columns: left, down, right (will occupy the last 3 columns) -->
<button class="nav" @click=${() => this._emitNavigate('left')}></button>
<button class="nav" @click=${() => this._emitNavigate('down')}></button>
<button class="nav" @click=${() => this._emitNavigate('right')}></button>
</div>
</div>
</div>
`;
}
_emitLetter(l) {
</div>
`;
} _emitLetter(l) {
this._vibrate();
this._emit({ type: 'letter', value: l });
}
_emitNavigate(dir) {
this._vibrate();
this._emit({ type: 'navigate', value: dir });
}
_emitBackspace() {
this._vibrate();
this._emit({ type: 'backspace' });
}
_emitSpace() {
this._vibrate();
this._emit({ type: 'letter', value: '' });
}
_emit(detail) {
window.dispatchEvent(new CustomEvent('key-press', { detail }));
}
_vibrate() {
// Use Vibration API for haptic feedback
try {
console.log('Attempting vibration... navigator.vibrate:', typeof navigator.vibrate);
if (navigator.vibrate) {
navigator.vibrate(10); // 10ms short buzz
console.log('Vibration sent!');
} else {
console.log('Vibration API not available on this device');
}
} catch (e) {
console.warn('Vibration API error:', e);
}
}
connectedCallback() {
super.connectedCallback();
window.addEventListener('resize', this._onResize);

View File

@ -20,11 +20,17 @@ html, body { -webkit-text-size-adjust: 100%; }
body {
font-family: 'Segoe UI', 'Helvetica Neue', system-ui, Roboto, Arial;
margin: 0;
background: #0a0805;
background-image:
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(255,255,255,.04) 2px, rgba(255,255,255,.04) 4px),
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(255,255,255,.04) 2px, rgba(255,255,255,.04) 4px),
repeating-linear-gradient(0deg, transparent, transparent 3px, rgba(255,255,255,.02) 3px, rgba(255,255,255,.02) 6px);
background:
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(255,255,255,.02) 3px, rgba(255,255,255,.02) 5px),
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(255,255,255,.015) 4px, rgba(255,255,255,.015) 6px),
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(0,0,0,.02) 3px, rgba(0,0,0,.02) 5px),
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(0,0,0,.015) 4px, rgba(0,0,0,.015) 6px),
repeating-radial-gradient(circle at 12% 18%, rgba(255,255,255,.02) 1px, transparent 1px),
repeating-radial-gradient(circle at 67% 77%, rgba(0,0,0,.03) 1px, transparent 1px),
repeating-radial-gradient(circle at 34% 51%, rgba(255,255,255,.015) 1.5px, transparent 1.5px),
radial-gradient(ellipse 900px 700px at 25% 35%, rgba(0,0,0,.3) 0%, transparent 45%),
radial-gradient(ellipse 700px 600px at 75% 65%, rgba(255,255,255,.02) 0%, transparent 50%),
linear-gradient(135deg, #0a0805 0%, #0f0c09 100%);
color: var(--ink-dark);
-webkit-font-smoothing: antialiased;
font-size: 100%;
@ -36,36 +42,61 @@ main {
justify-content: center;
align-items: flex-start;
padding: var(--page-padding);
padding-bottom: var(--keyboard-space);
padding-bottom: 0; /* Remove keyboard space by default (desktop) */
min-height: calc(100vh - 0px);
box-sizing: border-box;
}
.container { width: 100%; max-width: var(--max-container-width); }
/* Add keyboard space on mobile devices */
@media (max-width: 900px) {
main {
padding-bottom: var(--keyboard-space);
}
}
.container {
width: fit-content; /* Size exactly to content */
max-width: 100%; /* But don't exceed viewport */
}
#grid-container {
width: 100%;
overflow-x: auto;
overflow-y: auto;
display: flex;
justify-content: center; /* Center small grids */
}
crossword-grid { display: block; margin: 0 auto; }
mobile-keyboard { display: block; }
h2, h3 { margin: 0.5rem 0; color: #f5f1ed; text-align: center; text-shadow: 0 2px 4px rgba(0,0,0,0.7); font-weight: 600; }
h2, h3 {
margin: 0.5rem 0;
color: #f5f1ed;
text-align: center;
font-weight: 600;
letter-spacing: 0.01em;
}
crossword-grid {
display: block;
max-width: 100%;
margin: 0 auto;
crossword-grid { display: block; margin: 0 auto; }
border-radius: 0px;
background: var(--ink-dark);
position: relative;
.grid-container {
display: inline-block;
gap: 0;
overflow: visible;
padding: 20px;
}
.grid {
display: grid;
gap: 0;
background: var(--ink-dark);
background-image:
repeating-linear-gradient(45deg, transparent, transparent 1px, rgba(0,0,0,.025) 1px, rgba(0,0,0,.025) 2px),
repeating-linear-gradient(-45deg, transparent, transparent 1px, rgba(0,0,0,.025) 1px, rgba(0,0,0,.025) 2px);
padding: 0.8rem;
background: transparent;
padding: 0;
grid-template-columns: repeat(var(--cols), var(--cell-size));
border: 2px solid var(--ink-dark);
border: none;
}
.grid-container.complete .grid {
animation: grid-glow 2s ease-in-out infinite;
will-change: box-shadow;
}
.cell {
@ -75,14 +106,19 @@ crossword-grid {
align-items: center;
justify-content: center;
background:
repeating-linear-gradient(45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
repeating-linear-gradient(-45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
radial-gradient(ellipse 800px 600px at 30% 40%, rgba(255,255,255,.4) 0%, transparent 40%),
radial-gradient(ellipse 600px 500px at 70% 60%, rgba(0,0,0,.02) 0%, transparent 50%),
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(0,0,0,.03) 3px, rgba(0,0,0,.03) 5px),
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(0,0,0,.025) 4px, rgba(0,0,0,.025) 6px),
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(255,255,255,.06) 3px, rgba(255,255,255,.06) 5px),
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(255,255,255,.04) 4px, rgba(255,255,255,.04) 6px),
repeating-radial-gradient(circle at 12% 18%, rgba(0,0,0,.03) 1px, transparent 1px),
repeating-radial-gradient(circle at 67% 77%, rgba(255,255,255,.05) 1px, transparent 1px),
repeating-radial-gradient(circle at 34% 51%, rgba(0,0,0,.02) 1.5px, transparent 1.5px),
repeating-radial-gradient(circle at 23% 67%, rgba(0,0,0,.015) 0.8px, transparent 0.8px),
repeating-radial-gradient(circle at 78% 22%, rgba(255,255,255,.03) 0.8px, transparent 0.8px),
radial-gradient(ellipse 900px 700px at 25% 35%, rgba(255,255,255,.35) 0%, transparent 45%),
radial-gradient(ellipse 700px 600px at 75% 65%, rgba(0,0,0,.03) 0%, transparent 50%),
radial-gradient(circle at 0% 0%, rgba(0,0,0,.025) 0%, transparent 70%),
radial-gradient(circle at 100% 100%, rgba(0,0,0,.025) 0%, transparent 70%),
linear-gradient(135deg, #fefdfb 0%, #f9f7f3 100%);
color: var(--ink-dark);
font-weight: 700;
@ -108,11 +144,18 @@ crossword-grid {
.cell.wall {
background:
repeating-linear-gradient(45deg, transparent, transparent 2px, rgba(0,0,0,.15) 2px, rgba(0,0,0,.15) 4px),
repeating-linear-gradient(-45deg, transparent, transparent 2px, rgba(0,0,0,.15) 2px, rgba(0,0,0,.15) 4px),
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(0,0,0,.05) 2px, rgba(0,0,0,.05) 4px),
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(0,0,0,.05) 2px, rgba(0,0,0,.05) 4px),
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(255,255,255,.02) 3px, rgba(255,255,255,.02) 5px),
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(255,255,255,.015) 4px, rgba(255,255,255,.015) 6px),
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(0,0,0,.025) 3px, rgba(0,0,0,.025) 5px),
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(0,0,0,.02) 4px, rgba(0,0,0,.02) 6px),
repeating-radial-gradient(circle at 12% 18%, rgba(255,255,255,.025) 1px, transparent 1px),
repeating-radial-gradient(circle at 67% 77%, rgba(0,0,0,.035) 1px, transparent 1px),
repeating-radial-gradient(circle at 34% 51%, rgba(255,255,255,.02) 1.5px, transparent 1.5px),
repeating-radial-gradient(circle at 23% 67%, rgba(255,255,255,.015) 0.8px, transparent 0.8px),
repeating-radial-gradient(circle at 78% 22%, rgba(0,0,0,.025) 0.8px, transparent 0.8px),
radial-gradient(ellipse 800px 600px at 30% 40%, rgba(0,0,0,.15) 0%, transparent 50%),
radial-gradient(circle at 0% 0%, rgba(0,0,0,.03) 0%, transparent 70%),
radial-gradient(circle at 100% 100%, rgba(0,0,0,.03) 0%, transparent 70%),
linear-gradient(135deg, #3a3530 0%, #2a2520 100%);
color: transparent;
border-color: var(--wall-dark);
@ -123,31 +166,107 @@ crossword-grid {
inset 1px 1px 2px rgba(255,255,255,0.05);
}
.cell.wall .cell-letter {
font-size: 0;
text-shadow: none;
}
.cell.wall.selected {
border-color: var(--accent-wall);
box-shadow: inset 0 1px 2px rgba(255,255,255,0.1), inset 0 0 0 1px var(--accent-wall), 0 0 6px rgba(212,105,107,0.3);
background:
repeating-linear-gradient(45deg, transparent, transparent 2px, rgba(0,0,0,.15) 2px, rgba(0,0,0,.15) 4px),
repeating-linear-gradient(-45deg, transparent, transparent 2px, rgba(0,0,0,.15) 2px, rgba(0,0,0,.15) 4px),
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(255,255,255,.02) 3px, rgba(255,255,255,.02) 5px),
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(255,255,255,.015) 4px, rgba(255,255,255,.015) 6px),
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(0,0,0,.02) 3px, rgba(0,0,0,.02) 5px),
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(0,0,0,.015) 4px, rgba(0,0,0,.015) 6px),
repeating-radial-gradient(circle at 12% 18%, rgba(255,255,255,.02) 1px, transparent 1px),
repeating-radial-gradient(circle at 67% 77%, rgba(0,0,0,.03) 1px, transparent 1px),
repeating-radial-gradient(circle at 34% 51%, rgba(255,255,255,.015) 1.5px, transparent 1.5px),
linear-gradient(135deg, #3a3530 0%, #2a2520 100%);
}
.cell.wall.mode-highlighted {
background:
repeating-linear-gradient(45deg, transparent, transparent 2px, rgba(0,0,0,.15) 2px, rgba(0,0,0,.15) 4px),
repeating-linear-gradient(-45deg, transparent, transparent 2px, rgba(0,0,0,.15) 2px, rgba(0,0,0,.15) 4px),
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(255,255,255,.02) 3px, rgba(255,255,255,.02) 5px),
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(255,255,255,.015) 4px, rgba(255,255,255,.015) 6px),
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(0,0,0,.02) 3px, rgba(0,0,0,.02) 5px),
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(0,0,0,.015) 4px, rgba(0,0,0,.015) 6px),
repeating-radial-gradient(circle at 12% 18%, rgba(255,255,255,.02) 1px, transparent 1px),
repeating-radial-gradient(circle at 67% 77%, rgba(0,0,0,.03) 1px, transparent 1px),
repeating-radial-gradient(circle at 34% 51%, rgba(255,255,255,.015) 1.5px, transparent 1.5px),
linear-gradient(135deg, #3a3530 0%, #2a2520 100%);
}
/* Clue number styling - only in grid cells */
.cell .clue-number {
position: absolute;
top: 2px;
left: 2px;
font-size: 0.55rem;
font-weight: 700;
line-height: 1;
color: #000000 !important;
pointer-events: none;
}
.cell-letter {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
filter: contrast(1.05);
letter-spacing: 0.02em;
}
.solution-index {
position: absolute;
bottom: 1px;
right: 2px;
font-size: 0.5rem;
font-weight: 700;
line-height: 1;
color: #000000 !important;
pointer-events: none;
}
.solution-circle {
position: absolute;
width: calc(var(--cell-size) * 0.75);
height: calc(var(--cell-size) * 0.75);
top: 50%;
left: 50%;
transform: translate(calc(-50% + var(--cell-size) * 0.0625), calc(-50% + var(--cell-size) * 0.0625));
border: 1.5px solid rgba(26, 24, 21, 0.7);
border-radius: 50%;
background: transparent;
pointer-events: none;
/* Hide bottom-right quarter with clip-path for 270 degree arc */
clip-path: polygon(0 0, 100% 0, 100% 50%, 50% 50%, 50% 100%, 0 100%);
}
.solution-word-grid .solution-circle {
display: block;
}
.grid .cell .solution-circle {
display: block;
}
.cell.mode-highlighted {
background:
repeating-linear-gradient(45deg, transparent, transparent 1px, rgba(100,180,200,.2) 1px, rgba(100,180,200,.2) 2px),
repeating-linear-gradient(-45deg, transparent, transparent 1px, rgba(100,180,200,.2) 1px, rgba(100,180,200,.2) 2px),
repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(100,180,200,.1) 1px, rgba(100,180,200,.1) 2px),
repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(100,180,200,.1) 1px, rgba(100,180,200,.1) 2px),
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(100,180,200,.05) 2px, rgba(100,180,200,.05) 4px),
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(100,180,200,.05) 2px, rgba(100,180,200,.05) 4px),
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(100,180,200,.15) 3px, rgba(100,180,200,.15) 5px),
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(100,180,200,.1) 4px, rgba(100,180,200,.1) 6px),
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(100,180,200,.08) 3px, rgba(100,180,200,.08) 5px),
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(100,180,200,.05) 4px, rgba(100,180,200,.05) 6px),
repeating-radial-gradient(circle at 12% 18%, rgba(100,180,200,.07) 1px, transparent 1px),
repeating-radial-gradient(circle at 67% 77%, rgba(100,180,200,.09) 1px, transparent 1px),
repeating-radial-gradient(circle at 34% 51%, rgba(100,180,200,.04) 1.5px, transparent 1.5px),
repeating-radial-gradient(circle at 23% 67%, rgba(100,180,200,.02) 0.8px, transparent 0.8px),
repeating-radial-gradient(circle at 78% 22%, rgba(255,255,255,.04) 0.8px, transparent 0.8px),
radial-gradient(ellipse 800px 600px at 30% 40%, rgba(255,255,255,.3) 0%, transparent 40%),
radial-gradient(circle at 0% 0%, rgba(100,180,200,.02) 0%, transparent 70%),
radial-gradient(circle at 100% 100%, rgba(100,180,200,.02) 0%, transparent 70%),
linear-gradient(135deg, #f0fafb 0%, #e8f4f8 100%);
box-shadow:
inset 0 1px 2px rgba(255,255,255,0.9),
@ -159,15 +278,44 @@ crossword-grid {
}
/* Solved cells - green background and not editable */
@keyframes cell-bounce {
0%, 100% {
transform: scale(1);
}
25% {
transform: scale(1.15);
}
50% {
transform: scale(1.25);
}
75% {
transform: scale(1.15);
}
}
@keyframes grid-glow {
0%, 100% {
box-shadow: 0 0 20px rgba(100, 200, 100, 0.3), inset 0 0 20px rgba(100, 200, 100, 0.1);
}
50% {
box-shadow: 0 0 40px rgba(100, 200, 100, 0.6), inset 0 0 40px rgba(100, 200, 100, 0.2);
}
}
.cell.solved {
background:
repeating-linear-gradient(45deg, transparent, transparent 1px, rgba(100,200,100,.15) 1px, rgba(100,200,100,.15) 2px),
repeating-linear-gradient(-45deg, transparent, transparent 1px, rgba(100,200,100,.15) 1px, rgba(100,200,100,.15) 2px),
repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(100,200,100,.1) 1px, rgba(100,200,100,.1) 2px),
repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(100,200,100,.1) 1px, rgba(100,200,100,.1) 2px),
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(100,200,100,.05) 2px, rgba(100,200,100,.05) 4px),
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(100,200,100,.05) 2px, rgba(100,200,100,.05) 4px),
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(100,200,100,.1) 3px, rgba(100,200,100,.1) 5px),
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(100,200,100,.08) 4px, rgba(100,200,100,.08) 6px),
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(100,200,100,.06) 3px, rgba(100,200,100,.06) 5px),
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(100,200,100,.04) 4px, rgba(100,200,100,.04) 6px),
repeating-radial-gradient(circle at 12% 18%, rgba(100,200,100,.06) 1px, transparent 1px),
repeating-radial-gradient(circle at 67% 77%, rgba(100,200,100,.07) 1px, transparent 1px),
repeating-radial-gradient(circle at 34% 51%, rgba(100,200,100,.03) 1.5px, transparent 1.5px),
repeating-radial-gradient(circle at 23% 67%, rgba(100,200,100,.015) 0.8px, transparent 0.8px),
repeating-radial-gradient(circle at 78% 22%, rgba(255,255,255,.03) 0.8px, transparent 0.8px),
radial-gradient(ellipse 800px 600px at 30% 40%, rgba(255,255,255,.2) 0%, transparent 40%),
radial-gradient(circle at 0% 0%, rgba(100,200,100,.015) 0%, transparent 70%),
radial-gradient(circle at 100% 100%, rgba(100,200,100,.015) 0%, transparent 70%),
linear-gradient(135deg, #d4f4d4 0%, #c8ead4 100%);
box-shadow:
inset 0 1px 2px rgba(255,255,255,0.8),
@ -175,21 +323,58 @@ crossword-grid {
0 0.5px 1px rgba(0,0,0,0.05),
inset -1px -1px 2px rgba(100,200,100,0.08),
inset 1px 1px 2px rgba(255,255,255,0.3);
border-color: #90c890;
cursor: not-allowed;
opacity: 0.9;
}
@keyframes cell-bounce {
0%, 100% {
transform: scale(1);
}
25% {
transform: scale(1.15);
}
50% {
transform: scale(1.25);
}
75% {
transform: scale(1.15);
}
}
.solution-word-grid.complete .cell {
animation: cell-bounce 0.8s ease-in-out;
}
.solution-word-grid.complete .cell:nth-child(1) { animation-delay: 0s; }
.solution-word-grid.complete .cell:nth-child(2) { animation-delay: 0.1s; }
.solution-word-grid.complete .cell:nth-child(3) { animation-delay: 0.2s; }
.solution-word-grid.complete .cell:nth-child(4) { animation-delay: 0.3s; }
.solution-word-grid.complete .cell:nth-child(5) { animation-delay: 0.4s; }
.solution-word-grid.complete .cell:nth-child(6) { animation-delay: 0.5s; }
.solution-word-grid.complete .cell:nth-child(7) { animation-delay: 0.6s; }
.solution-word-grid.complete .cell:nth-child(8) { animation-delay: 0.7s; }
.solution-word-grid.complete .cell:nth-child(9) { animation-delay: 0.8s; }
.solution-word-grid.complete .cell:nth-child(10) { animation-delay: 0.9s; }
.solution-word-grid.complete .cell:nth-child(11) { animation-delay: 1.0s; }
.solution-word-grid.complete .cell:nth-child(12) { animation-delay: 1.1s; }
.solution-word-grid.complete .cell:nth-child(13) { animation-delay: 1.2s; }
.solution-word-grid.complete .cell:nth-child(14) { animation-delay: 1.3s; }
.solution-word-grid.complete .cell:nth-child(15) { animation-delay: 1.4s; }
.cell.selected {
outline: none;
background:
repeating-linear-gradient(45deg, transparent, transparent 1px, rgba(255,200,0,.18) 1px, rgba(255,200,0,.18) 2px),
repeating-linear-gradient(-45deg, transparent, transparent 1px, rgba(255,200,0,.18) 1px, rgba(255,200,0,.18) 2px),
repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(255,200,0,.09) 1px, rgba(255,200,0,.09) 2px),
repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(255,200,0,.09) 1px, rgba(255,200,0,.09) 2px),
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(255,200,0,.05) 2px, rgba(255,200,0,.05) 4px),
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(255,200,0,.05) 2px, rgba(255,200,0,.05) 4px),
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(255,200,0,.15) 3px, rgba(255,200,0,.15) 5px),
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(255,200,0,.1) 4px, rgba(255,200,0,.1) 6px),
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(255,200,0,.08) 3px, rgba(255,200,0,.08) 5px),
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(255,200,0,.05) 4px, rgba(255,200,0,.05) 6px),
repeating-radial-gradient(circle at 12% 18%, rgba(255,200,0,.07) 1px, transparent 1px),
repeating-radial-gradient(circle at 67% 77%, rgba(255,200,0,.09) 1px, transparent 1px),
repeating-radial-gradient(circle at 34% 51%, rgba(255,200,0,.04) 1.5px, transparent 1.5px),
repeating-radial-gradient(circle at 23% 67%, rgba(255,200,0,.02) 0.8px, transparent 0.8px),
repeating-radial-gradient(circle at 78% 22%, rgba(255,255,255,.04) 0.8px, transparent 0.8px),
radial-gradient(ellipse 800px 600px at 30% 40%, rgba(255,255,255,.4) 0%, transparent 40%),
radial-gradient(circle at 0% 0%, rgba(255,200,0,.02) 0%, transparent 70%),
radial-gradient(circle at 100% 100%, rgba(255,200,0,.02) 0%, transparent 70%),
linear-gradient(135deg, #fffef9 0%, #fff8e6 100%);
border-color: var(--ink-dark);
box-shadow:
@ -202,13 +387,18 @@ crossword-grid {
.cell.selected.mode-highlighted {
background:
repeating-linear-gradient(45deg, transparent, transparent 1px, rgba(200,150,0,.2) 1px, rgba(200,150,0,.2) 2px),
repeating-linear-gradient(-45deg, transparent, transparent 1px, rgba(200,150,0,.2) 1px, rgba(200,150,0,.2) 2px),
repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(200,150,0,.1) 1px, rgba(200,150,0,.1) 2px),
repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(200,150,0,.1) 1px, rgba(200,150,0,.1) 2px),
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(200,150,0,.05) 2px, rgba(200,150,0,.05) 4px),
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(200,150,0,.05) 2px, rgba(200,150,0,.05) 4px),
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(200,150,0,.15) 3px, rgba(200,150,0,.15) 5px),
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(200,150,0,.1) 4px, rgba(200,150,0,.1) 6px),
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(200,150,0,.08) 3px, rgba(200,150,0,.08) 5px),
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(200,150,0,.05) 4px, rgba(200,150,0,.05) 6px),
repeating-radial-gradient(circle at 12% 18%, rgba(200,150,0,.07) 1px, transparent 1px),
repeating-radial-gradient(circle at 67% 77%, rgba(200,150,0,.09) 1px, transparent 1px),
repeating-radial-gradient(circle at 34% 51%, rgba(200,150,0,.04) 1.5px, transparent 1.5px),
repeating-radial-gradient(circle at 23% 67%, rgba(200,150,0,.02) 0.8px, transparent 0.8px),
repeating-radial-gradient(circle at 78% 22%, rgba(255,255,255,.04) 0.8px, transparent 0.8px),
radial-gradient(ellipse 800px 600px at 30% 40%, rgba(255,255,255,.4) 0%, transparent 40%),
radial-gradient(circle at 0% 0%, rgba(200,150,0,.02) 0%, transparent 70%),
radial-gradient(circle at 100% 100%, rgba(200,150,0,.02) 0%, transparent 70%),
linear-gradient(135deg, #fffaf0 0%, #fff5d6 100%);
box-shadow:
inset 0 1px 2px rgba(255,255,255,0.9),
@ -232,7 +422,7 @@ mobile-keyboard {
z-index: 2000;
font-size: clamp(0.95rem, 2.4vw, 1.15rem);
--key-width: clamp(1.7rem, 5.5vw, 2.1rem);
--key-height: calc(var(--key-width) * 1.2);
--key-height: calc(var(--key-width) * 1.4);
--stagger-factor: 0.4;
--stagger-factor-deep: 0.8;
--up-arrow-offset: calc(0 - var(--key-width) * var(--stagger-factor-deep));
@ -260,11 +450,17 @@ mobile-keyboard .keyboard {
flex-direction: column;
gap: 0.35rem;
padding: 0.75em;
background: #0a0805;
background-image:
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(255,255,255,.04) 2px, rgba(255,255,255,.04) 4px),
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(255,255,255,.04) 2px, rgba(255,255,255,.04) 4px),
repeating-linear-gradient(0deg, transparent, transparent 3px, rgba(255,255,255,.02) 3px, rgba(255,255,255,.02) 6px);
background:
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(255,255,255,.02) 3px, rgba(255,255,255,.02) 5px),
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(255,255,255,.015) 4px, rgba(255,255,255,.015) 6px),
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(0,0,0,.02) 3px, rgba(0,0,0,.02) 5px),
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(0,0,0,.015) 4px, rgba(0,0,0,.015) 6px),
repeating-radial-gradient(circle at 12% 18%, rgba(255,255,255,.02) 1px, transparent 1px),
repeating-radial-gradient(circle at 67% 77%, rgba(0,0,0,.03) 1px, transparent 1px),
repeating-radial-gradient(circle at 34% 51%, rgba(255,255,255,.015) 1.5px, transparent 1.5px),
radial-gradient(ellipse 900px 700px at 25% 35%, rgba(0,0,0,.3) 0%, transparent 45%),
radial-gradient(ellipse 700px 600px at 75% 65%, rgba(255,255,255,.02) 0%, transparent 50%),
linear-gradient(135deg, #0a0805 0%, #0f0c09 100%);
border-top: 2px solid #3a3530;
box-shadow: 0 -0.6rem 1.4rem rgba(0,0,0,0.6), inset 0 1px 0 rgba(255,255,255,0.05);
margin: 0 auto;
@ -309,13 +505,18 @@ mobile-keyboard button {
border-radius: 0.5rem;
border: none;
background:
repeating-linear-gradient(45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
repeating-linear-gradient(-45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
radial-gradient(ellipse 800px 600px at 30% 40%, rgba(255,255,255,.4) 0%, transparent 40%),
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(0,0,0,.02) 3px, rgba(0,0,0,.02) 5px),
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(0,0,0,.015) 4px, rgba(0,0,0,.015) 6px),
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(255,255,255,.05) 3px, rgba(255,255,255,.05) 5px),
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(255,255,255,.03) 4px, rgba(255,255,255,.03) 6px),
repeating-radial-gradient(circle at 12% 18%, rgba(0,0,0,.02) 1px, transparent 1px),
repeating-radial-gradient(circle at 67% 77%, rgba(255,255,255,.04) 1px, transparent 1px),
repeating-radial-gradient(circle at 34% 51%, rgba(0,0,0,.015) 1.5px, transparent 1.5px),
repeating-radial-gradient(circle at 23% 67%, rgba(0,0,0,.01) 0.8px, transparent 0.8px),
repeating-radial-gradient(circle at 78% 22%, rgba(255,255,255,.03) 0.8px, transparent 0.8px),
radial-gradient(ellipse 800px 600px at 30% 40%, rgba(255,255,255,.35) 0%, transparent 40%),
radial-gradient(circle at 0% 0%, rgba(0,0,0,.02) 0%, transparent 70%),
radial-gradient(circle at 100% 100%, rgba(0,0,0,.02) 0%, transparent 70%),
linear-gradient(180deg, #fffef9 0%, #f5f1ed 100%);
font-family: inherit;
font-weight: 600;
@ -340,42 +541,57 @@ mobile-keyboard button:active {
mobile-keyboard button:hover {
background:
repeating-linear-gradient(45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
repeating-linear-gradient(-45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(0,0,0,.025) 3px, rgba(0,0,0,.025) 5px),
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(0,0,0,.018) 4px, rgba(0,0,0,.018) 6px),
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(255,255,255,.06) 3px, rgba(255,255,255,.06) 5px),
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(255,255,255,.04) 4px, rgba(255,255,255,.04) 6px),
repeating-radial-gradient(circle at 12% 18%, rgba(0,0,0,.025) 1px, transparent 1px),
repeating-radial-gradient(circle at 67% 77%, rgba(255,255,255,.05) 1px, transparent 1px),
repeating-radial-gradient(circle at 34% 51%, rgba(0,0,0,.02) 1.5px, transparent 1.5px),
repeating-radial-gradient(circle at 23% 67%, rgba(0,0,0,.015) 0.8px, transparent 0.8px),
repeating-radial-gradient(circle at 78% 22%, rgba(255,255,255,.04) 0.8px, transparent 0.8px),
radial-gradient(ellipse 800px 600px at 30% 40%, rgba(255,255,255,.45) 0%, transparent 40%),
radial-gradient(circle at 0% 0%, rgba(0,0,0,.02) 0%, transparent 70%),
radial-gradient(circle at 100% 100%, rgba(0,0,0,.02) 0%, transparent 70%),
linear-gradient(180deg, #fffbf5 0%, #f8f4ef 100%);
transform: scale(1.05);
}
mobile-keyboard button.backspace {
width: calc(var(--key-width) * 0.8);
width: calc(var(--key-width) * 1.3);
}
mobile-keyboard button[aria-pressed="true"] {
background:
repeating-linear-gradient(45deg, transparent, transparent 1px, rgba(255,200,0,.18) 1px, rgba(255,200,0,.18) 2px),
repeating-linear-gradient(-45deg, transparent, transparent 1px, rgba(255,200,0,.18) 1px, rgba(255,200,0,.18) 2px),
repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(255,200,0,.09) 1px, rgba(255,200,0,.09) 2px),
repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(255,200,0,.09) 1px, rgba(255,200,0,.09) 2px),
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(255,200,0,.05) 2px, rgba(255,200,0,.05) 4px),
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(255,200,0,.05) 2px, rgba(255,200,0,.05) 4px),
radial-gradient(ellipse 800px 600px at 30% 40%, rgba(255,255,255,.4) 0%, transparent 40%),
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(0,0,0,.03) 3px, rgba(0,0,0,.03) 5px),
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(0,0,0,.02) 4px, rgba(0,0,0,.02) 6px),
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(255,200,0,.12) 3px, rgba(255,200,0,.12) 5px),
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(255,200,0,.08) 4px, rgba(255,200,0,.08) 6px),
repeating-radial-gradient(circle at 12% 18%, rgba(0,0,0,.025) 1px, transparent 1px),
repeating-radial-gradient(circle at 67% 77%, rgba(255,200,0,.08) 1px, transparent 1px),
repeating-radial-gradient(circle at 34% 51%, rgba(0,0,0,.02) 1.5px, transparent 1.5px),
repeating-radial-gradient(circle at 23% 67%, rgba(255,200,0,.05) 0.8px, transparent 0.8px),
repeating-radial-gradient(circle at 78% 22%, rgba(255,200,0,.06) 0.8px, transparent 0.8px),
radial-gradient(ellipse 800px 600px at 30% 40%, rgba(255,255,255,.3) 0%, transparent 40%),
radial-gradient(circle at 0% 0%, rgba(0,0,0,.02) 0%, transparent 70%),
radial-gradient(circle at 100% 100%, rgba(0,0,0,.02) 0%, transparent 70%),
linear-gradient(180deg, #fffef5 0%, #fffbf0 100%);
box-shadow: 0 3px 8px rgba(0,0,0,0.3), inset 0 0 0 2px #ffc107, inset 0 1px 0 rgba(255,255,255,0.8);
}
mobile-keyboard .nav {
background:
repeating-linear-gradient(45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
repeating-linear-gradient(-45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(255,255,255,.05) 3px, rgba(255,255,255,.05) 5px),
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(255,255,255,.03) 4px, rgba(255,255,255,.03) 6px),
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(0,0,0,.08) 3px, rgba(0,0,0,.08) 5px),
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(0,0,0,.05) 4px, rgba(0,0,0,.05) 6px),
repeating-radial-gradient(circle at 12% 18%, rgba(255,255,255,.04) 1px, transparent 1px),
repeating-radial-gradient(circle at 67% 77%, rgba(0,0,0,.06) 1px, transparent 1px),
repeating-radial-gradient(circle at 34% 51%, rgba(255,255,255,.03) 1.5px, transparent 1.5px),
repeating-radial-gradient(circle at 23% 67%, rgba(255,255,255,.02) 0.8px, transparent 0.8px),
repeating-radial-gradient(circle at 78% 22%, rgba(0,0,0,.04) 0.8px, transparent 0.8px),
radial-gradient(circle at 0% 0%, rgba(0,0,0,.03) 0%, transparent 70%),
radial-gradient(circle at 100% 100%, rgba(0,0,0,.03) 0%, transparent 70%),
linear-gradient(180deg, #5a8ca4 0%, #4a7c94 100%);
color: #f0f8ff;
width: var(--key-width);
@ -387,12 +603,17 @@ mobile-keyboard .nav {
mobile-keyboard .nav:hover {
background:
repeating-linear-gradient(45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
repeating-linear-gradient(-45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(255,255,255,.06) 3px, rgba(255,255,255,.06) 5px),
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(255,255,255,.04) 4px, rgba(255,255,255,.04) 6px),
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(0,0,0,.1) 3px, rgba(0,0,0,.1) 5px),
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(0,0,0,.06) 4px, rgba(0,0,0,.06) 6px),
repeating-radial-gradient(circle at 12% 18%, rgba(255,255,255,.05) 1px, transparent 1px),
repeating-radial-gradient(circle at 67% 77%, rgba(0,0,0,.08) 1px, transparent 1px),
repeating-radial-gradient(circle at 34% 51%, rgba(255,255,255,.04) 1.5px, transparent 1.5px),
repeating-radial-gradient(circle at 23% 67%, rgba(255,255,255,.025) 0.8px, transparent 0.8px),
repeating-radial-gradient(circle at 78% 22%, rgba(0,0,0,.05) 0.8px, transparent 0.8px),
radial-gradient(circle at 0% 0%, rgba(0,0,0,.04) 0%, transparent 70%),
radial-gradient(circle at 100% 100%, rgba(0,0,0,.04) 0%, transparent 70%),
linear-gradient(180deg, #6a9cb4 0%, #5a8ca4 100%);
}
@ -404,13 +625,18 @@ mobile-keyboard .space {
border-radius: 0.5rem;
border: none;
background:
repeating-linear-gradient(45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
repeating-linear-gradient(-45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
radial-gradient(ellipse 800px 600px at 30% 40%, rgba(255,255,255,.4) 0%, transparent 40%),
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(0,0,0,.02) 3px, rgba(0,0,0,.02) 5px),
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(0,0,0,.015) 4px, rgba(0,0,0,.015) 6px),
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(255,255,255,.05) 3px, rgba(255,255,255,.05) 5px),
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(255,255,255,.03) 4px, rgba(255,255,255,.03) 6px),
repeating-radial-gradient(circle at 12% 18%, rgba(0,0,0,.02) 1px, transparent 1px),
repeating-radial-gradient(circle at 67% 77%, rgba(255,255,255,.04) 1px, transparent 1px),
repeating-radial-gradient(circle at 34% 51%, rgba(0,0,0,.015) 1.5px, transparent 1.5px),
repeating-radial-gradient(circle at 23% 67%, rgba(0,0,0,.01) 0.8px, transparent 0.8px),
repeating-radial-gradient(circle at 78% 22%, rgba(255,255,255,.03) 0.8px, transparent 0.8px),
radial-gradient(ellipse 800px 600px at 30% 40%, rgba(255,255,255,.35) 0%, transparent 40%),
radial-gradient(circle at 0% 0%, rgba(0,0,0,.02) 0%, transparent 70%),
radial-gradient(circle at 100% 100%, rgba(0,0,0,.02) 0%, transparent 70%),
linear-gradient(180deg, #fffef9 0%, #f5f1ed 100%);
font-family: inherit;
font-weight: 600;
@ -435,11 +661,14 @@ mobile-keyboard .handle {
left: 50%;
transform: translateX(-50%);
background:
repeating-linear-gradient(45deg, transparent, transparent 1px, rgba(0,0,0,.15) 1px, rgba(0,0,0,.15) 2px),
repeating-linear-gradient(-45deg, transparent, transparent 1px, rgba(0,0,0,.15) 1px, rgba(0,0,0,.15) 2px),
repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(0,0,0,.08) 1px, rgba(0,0,0,.08) 2px),
repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(0,0,0,.08) 1px, rgba(0,0,0,.08) 2px),
#0a0805;
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(255,255,255,.02) 3px, rgba(255,255,255,.02) 5px),
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(255,255,255,.015) 4px, rgba(255,255,255,.015) 6px),
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(0,0,0,.02) 3px, rgba(0,0,0,.02) 5px),
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(0,0,0,.015) 4px, rgba(0,0,0,.015) 6px),
repeating-radial-gradient(circle at 12% 18%, rgba(255,255,255,.02) 1px, transparent 1px),
repeating-radial-gradient(circle at 67% 77%, rgba(0,0,0,.03) 1px, transparent 1px),
repeating-radial-gradient(circle at 34% 51%, rgba(255,255,255,.015) 1.5px, transparent 1.5px),
linear-gradient(135deg, #0a0805 0%, #0f0c09 100%);
color: #a89f99;
padding: 0.55rem 0.9rem;
border-radius: 0.25rem 0.25rem 0 0;
@ -470,32 +699,39 @@ crossword-menu {
}
.menu-container {
background: #0a0805;
background-image:
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(255,255,255,.04) 2px, rgba(255,255,255,.04) 4px),
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(255,255,255,.04) 2px, rgba(255,255,255,.04) 4px),
repeating-linear-gradient(0deg, transparent, transparent 3px, rgba(255,255,255,.02) 3px, rgba(255,255,255,.02) 6px);
background:
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(255,255,255,.02) 3px, rgba(255,255,255,.02) 5px),
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(255,255,255,.015) 4px, rgba(255,255,255,.015) 6px),
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(0,0,0,.02) 3px, rgba(0,0,0,.02) 5px),
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(0,0,0,.015) 4px, rgba(0,0,0,.015) 6px),
repeating-radial-gradient(circle at 12% 18%, rgba(255,255,255,.02) 1px, transparent 1px),
repeating-radial-gradient(circle at 67% 77%, rgba(0,0,0,.03) 1px, transparent 1px),
repeating-radial-gradient(circle at 34% 51%, rgba(255,255,255,.015) 1.5px, transparent 1.5px),
radial-gradient(ellipse 900px 700px at 25% 35%, rgba(0,0,0,.3) 0%, transparent 45%),
radial-gradient(ellipse 700px 600px at 75% 65%, rgba(255,255,255,.02) 0%, transparent 50%),
linear-gradient(135deg, #0a0805 0%, #0f0c09 100%);
min-height: 100vh;
display: flex;
align-items: center;
align-items: flex-start;
justify-content: center;
padding: 2rem;
padding: 1rem;
box-sizing: border-box;
}
.menu {
background: #f9f7f3;
background-image:
repeating-linear-gradient(45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
repeating-linear-gradient(-45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
radial-gradient(ellipse 800px 600px at 30% 40%, rgba(255,255,255,.4) 0%, transparent 40%),
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(0,0,0,.02) 3px, rgba(0,0,0,.02) 5px),
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(0,0,0,.015) 4px, rgba(0,0,0,.015) 6px),
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(255,255,255,.05) 3px, rgba(255,255,255,.05) 5px),
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(255,255,255,.03) 4px, rgba(255,255,255,.03) 6px),
repeating-radial-gradient(circle at 12% 18%, rgba(0,0,0,.02) 1px, transparent 1px),
repeating-radial-gradient(circle at 67% 77%, rgba(255,255,255,.04) 1px, transparent 1px),
repeating-radial-gradient(circle at 34% 51%, rgba(0,0,0,.015) 1.5px, transparent 1.5px),
radial-gradient(ellipse 800px 600px at 30% 40%, rgba(255,255,255,.35) 0%, transparent 40%),
radial-gradient(ellipse 600px 500px at 70% 60%, rgba(0,0,0,.02) 0%, transparent 50%),
linear-gradient(135deg, #fefdfb 0%, #f9f7f3 100%);
padding: 3rem;
padding: 2rem;
border-radius: 0.5rem;
box-shadow:
0 10px 40px rgba(0,0,0,0.3),
@ -514,21 +750,21 @@ crossword-menu {
}
.form-group {
margin-bottom: 1.5rem;
margin-bottom: 1rem;
}
.form-group label {
display: block;
color: #1a1815;
font-weight: 600;
margin-bottom: 0.5rem;
margin-bottom: 0.3rem;
font-size: 0.95rem;
}
.form-group select,
.form-group input[type="text"] {
width: 100%;
padding: 0.75rem;
padding: 0.6rem;
border: 1px solid #c0bbb5;
border-radius: 0.25rem;
font-size: 1rem;
@ -548,15 +784,21 @@ crossword-menu {
.menu button {
width: 100%;
padding: 0.75rem;
padding: 0.6rem;
margin-bottom: 0.7rem;
background:
repeating-linear-gradient(45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
repeating-linear-gradient(-45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
radial-gradient(ellipse 800px 600px at 30% 40%, rgba(255,255,255,.4) 0%, transparent 40%),
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(0,0,0,.02) 3px, rgba(0,0,0,.02) 5px),
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(0,0,0,.015) 4px, rgba(0,0,0,.015) 6px),
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(255,255,255,.05) 3px, rgba(255,255,255,.05) 5px),
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(255,255,255,.03) 4px, rgba(255,255,255,.03) 6px),
repeating-radial-gradient(circle at 12% 18%, rgba(0,0,0,.02) 1px, transparent 1px),
repeating-radial-gradient(circle at 67% 77%, rgba(255,255,255,.04) 1px, transparent 1px),
repeating-radial-gradient(circle at 34% 51%, rgba(0,0,0,.015) 1.5px, transparent 1.5px),
repeating-radial-gradient(circle at 23% 67%, rgba(0,0,0,.01) 0.8px, transparent 0.8px),
repeating-radial-gradient(circle at 78% 22%, rgba(255,255,255,.03) 0.8px, transparent 0.8px),
radial-gradient(ellipse 800px 600px at 30% 40%, rgba(255,255,255,.35) 0%, transparent 40%),
radial-gradient(circle at 0% 0%, rgba(0,0,0,.02) 0%, transparent 70%),
radial-gradient(circle at 100% 100%, rgba(0,0,0,.02) 0%, transparent 70%),
linear-gradient(180deg, #fffef9 0%, #f5f1ed 100%);
color: #1a1815;
border: 1px solid #c0bbb5;
@ -624,12 +866,20 @@ crossword-menu {
border-radius: 0.375rem;
font-size: 0.95rem;
font-weight: 500;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
animation: slideIn 0.3s ease-out;
pointer-events: auto;
gap: 0.75rem;
max-width: 350px;
backdrop-filter: blur(4px);
background:
repeating-linear-gradient(87deg, transparent, transparent 2px, rgba(0,0,0,.015) 2px, rgba(0,0,0,.015) 3px),
repeating-linear-gradient(22deg, transparent, transparent 3px, rgba(0,0,0,.01) 3px, rgba(0,0,0,.01) 4px),
repeating-linear-gradient(59deg, transparent, transparent 2px, rgba(255,255,255,.03) 2px, rgba(255,255,255,.03) 3px),
repeating-linear-gradient(-11deg, transparent, transparent 3px, rgba(255,255,255,.02) 3px, rgba(255,255,255,.02) 4px),
repeating-radial-gradient(circle at 12% 18%, rgba(0,0,0,.01) 0.8px, transparent 0.8px),
repeating-radial-gradient(circle at 67% 77%, rgba(255,255,255,.02) 0.8px, transparent 0.8px),
radial-gradient(circle at 0% 0%, rgba(0,0,0,.02) 0%, transparent 70%),
radial-gradient(circle at 100% 100%, rgba(0,0,0,.02) 0%, transparent 70%);
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
}
@keyframes slideIn {
@ -754,9 +1004,71 @@ crossword-menu {
flex-grow: 1;
}
.header-buttons {
display: flex;
gap: 0.5rem;
}
.share-game-btn {
background:
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(100,180,255,.04) 3px, rgba(100,180,255,.04) 5px),
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(100,180,255,.025) 4px, rgba(100,180,255,.025) 6px),
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(100,180,255,.03) 3px, rgba(100,180,255,.03) 5px),
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(100,180,255,.02) 4px, rgba(100,180,255,.02) 6px),
repeating-radial-gradient(circle at 12% 18%, rgba(100,180,255,.025) 1px, transparent 1px),
repeating-radial-gradient(circle at 67% 77%, rgba(100,180,255,.02) 1px, transparent 1px),
repeating-radial-gradient(circle at 34% 51%, rgba(100,180,255,.02) 1.5px, transparent 1.5px),
repeating-radial-gradient(circle at 23% 67%, rgba(100,180,255,.01) 0.8px, transparent 0.8px),
repeating-radial-gradient(circle at 78% 22%, rgba(100,180,255,.015) 0.8px, transparent 0.8px),
radial-gradient(circle at 0% 0%, rgba(0,0,0,.03) 0%, transparent 70%),
radial-gradient(circle at 100% 100%, rgba(0,0,0,.03) 0%, transparent 70%),
linear-gradient(180deg, #4a5a6a 0%, #3a4a5a 100%);
border: 1px solid rgba(100, 180, 255, 0.3);
color: #f5f1ed;
cursor: pointer;
width: 2.5rem;
height: 2.5rem;
display: flex;
align-items: center;
justify-content: center;
border-radius: 0.25rem;
transition: all 0.2s ease;
flex-shrink: 0;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255,255,255,0.08);
}
.share-game-btn:hover {
background:
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(100,180,255,.05) 3px, rgba(100,180,255,.05) 5px),
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(100,180,255,.03) 4px, rgba(100,180,255,.03) 6px),
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(100,180,255,.04) 3px, rgba(100,180,255,.04) 5px),
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(100,180,255,.025) 4px, rgba(100,180,255,.025) 6px),
repeating-radial-gradient(circle at 12% 18%, rgba(100,180,255,.03) 1px, transparent 1px),
repeating-radial-gradient(circle at 67% 77%, rgba(100,180,255,.025) 1px, transparent 1px),
repeating-radial-gradient(circle at 34% 51%, rgba(100,180,255,.025) 1.5px, transparent 1.5px),
repeating-radial-gradient(circle at 23% 67%, rgba(100,180,255,.015) 0.8px, transparent 0.8px),
repeating-radial-gradient(circle at 78% 22%, rgba(100,180,255,.02) 0.8px, transparent 0.8px),
radial-gradient(circle at 0% 0%, rgba(100,180,255,.05) 0%, transparent 70%),
radial-gradient(circle at 100% 100%, rgba(100,180,255,.05) 0%, transparent 70%),
linear-gradient(180deg, #5a6a7a 0%, #4a5a6a 100%);
border-color: rgba(100, 180, 255, 0.5);
}
.close-game-btn {
background: none;
border: 1px solid rgba(245, 241, 237, 0.3);
background:
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(255,255,255,.04) 3px, rgba(255,255,255,.04) 5px),
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(255,255,255,.025) 4px, rgba(255,255,255,.025) 6px),
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(180,80,80,.03) 3px, rgba(180,80,80,.03) 5px),
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(180,80,80,.02) 4px, rgba(180,80,80,.02) 6px),
repeating-radial-gradient(circle at 12% 18%, rgba(255,255,255,.025) 1px, transparent 1px),
repeating-radial-gradient(circle at 67% 77%, rgba(180,80,80,.02) 1px, transparent 1px),
repeating-radial-gradient(circle at 34% 51%, rgba(255,255,255,.02) 1.5px, transparent 1.5px),
repeating-radial-gradient(circle at 23% 67%, rgba(180,80,80,.01) 0.8px, transparent 0.8px),
repeating-radial-gradient(circle at 78% 22%, rgba(180,80,80,.015) 0.8px, transparent 0.8px),
radial-gradient(circle at 0% 0%, rgba(0,0,0,.03) 0%, transparent 70%),
radial-gradient(circle at 100% 100%, rgba(0,0,0,.03) 0%, transparent 70%),
linear-gradient(180deg, #5a4a4a 0%, #4a3a3a 100%);
border: 1px solid rgba(160, 90, 90, 0.3);
color: #f5f1ed;
font-size: 1.5rem;
cursor: pointer;
@ -770,11 +1082,25 @@ crossword-menu {
flex-shrink: 0;
font-weight: 300;
line-height: 1;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255,255,255,0.08);
}
.close-game-btn:hover {
background: rgba(245, 241, 237, 0.1);
border-color: rgba(245, 241, 237, 0.6);
background:
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(255,255,255,.045) 3px, rgba(255,255,255,.045) 5px),
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(255,255,255,.03) 4px, rgba(255,255,255,.03) 6px),
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(180,80,80,.04) 3px, rgba(180,80,80,.04) 5px),
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(180,80,80,.025) 4px, rgba(180,80,80,.025) 6px),
repeating-radial-gradient(circle at 12% 18%, rgba(255,255,255,.03) 1px, transparent 1px),
repeating-radial-gradient(circle at 67% 77%, rgba(180,80,80,.03) 1px, transparent 1px),
repeating-radial-gradient(circle at 34% 51%, rgba(255,255,255,.025) 1.5px, transparent 1.5px),
repeating-radial-gradient(circle at 23% 67%, rgba(180,80,80,.015) 0.8px, transparent 0.8px),
repeating-radial-gradient(circle at 78% 22%, rgba(180,80,80,.02) 0.8px, transparent 0.8px),
radial-gradient(circle at 0% 0%, rgba(0,0,0,.035) 0%, transparent 70%),
radial-gradient(circle at 100% 100%, rgba(0,0,0,.035) 0%, transparent 70%),
linear-gradient(180deg, #6a5a5a 0%, #5a4a4a 100%);
border-color: rgba(160, 90, 90, 0.4);
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.35), inset 0 1px 0 rgba(255,255,255,0.1);
}
.close-game-btn:active {
@ -783,8 +1109,20 @@ crossword-menu {
}
.game-content {
display: flex;
justify-content: center;
display: block;
overflow-y: auto;
overflow-x: auto;
max-height: calc(100vh - 200px); /* Leave space for header, clue area, and keyboard */
width: 100%;
padding: 1rem 0; /* Add some padding for breathing room */
text-align: center; /* Center inline-block children */
}
crossword-grid {
display: inline-block;
min-width: fit-content; /* Ensure grid takes its full needed width */
border-radius: 0px;
position: relative;
}
/* Clue Area */
@ -793,7 +1131,17 @@ crossword-menu {
top: 0;
left: 0;
right: 0;
background: rgba(26, 24, 21, 0.95);
background:
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(255,255,255,.02) 3px, rgba(255,255,255,.02) 5px),
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(255,255,255,.015) 4px, rgba(255,255,255,.015) 6px),
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(0,0,0,.02) 3px, rgba(0,0,0,.02) 5px),
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(0,0,0,.015) 4px, rgba(0,0,0,.015) 6px),
repeating-radial-gradient(circle at 12% 18%, rgba(255,255,255,.02) 1px, transparent 1px),
repeating-radial-gradient(circle at 67% 77%, rgba(0,0,0,.03) 1px, transparent 1px),
repeating-radial-gradient(circle at 34% 51%, rgba(255,255,255,.015) 1.5px, transparent 1.5px),
radial-gradient(ellipse 900px 700px at 25% 35%, rgba(0,0,0,.3) 0%, transparent 45%),
radial-gradient(ellipse 700px 600px at 75% 65%, rgba(255,255,255,.02) 0%, transparent 50%),
linear-gradient(135deg, #0a0805 0%, #0f0c09 100%);
backdrop-filter: blur(8px);
border-bottom: 2px solid rgba(245, 241, 237, 0.2);
z-index: 1000;
@ -835,8 +1183,20 @@ crossword-menu {
}
.clue-toggle {
background: none;
border: 1px solid rgba(245, 241, 237, 0.3);
background:
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(255,255,255,.04) 3px, rgba(255,255,255,.04) 5px),
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(255,255,255,.025) 4px, rgba(255,255,255,.025) 6px),
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(100,130,180,.03) 3px, rgba(100,130,180,.03) 5px),
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(100,130,180,.02) 4px, rgba(100,130,180,.02) 6px),
repeating-radial-gradient(circle at 12% 18%, rgba(255,255,255,.025) 1px, transparent 1px),
repeating-radial-gradient(circle at 67% 77%, rgba(100,130,180,.02) 1px, transparent 1px),
repeating-radial-gradient(circle at 34% 51%, rgba(255,255,255,.02) 1.5px, transparent 1.5px),
repeating-radial-gradient(circle at 23% 67%, rgba(100,130,180,.01) 0.8px, transparent 0.8px),
repeating-radial-gradient(circle at 78% 22%, rgba(100,130,180,.015) 0.8px, transparent 0.8px),
radial-gradient(circle at 0% 0%, rgba(0,0,0,.03) 0%, transparent 70%),
radial-gradient(circle at 100% 100%, rgba(0,0,0,.03) 0%, transparent 70%),
linear-gradient(180deg, #3a4a5a 0%, #2a3a4a 100%);
border: 1px solid rgba(100, 130, 180, 0.3);
color: #f5f1ed;
font-size: 1rem;
cursor: pointer;
@ -849,11 +1209,25 @@ crossword-menu {
transition: all 0.2s ease;
flex-shrink: 0;
padding: 0;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255,255,255,0.08);
}
.clue-toggle:hover {
background: rgba(245, 241, 237, 0.1);
border-color: rgba(245, 241, 237, 0.6);
background:
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(255,255,255,.045) 3px, rgba(255,255,255,.045) 5px),
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(255,255,255,.03) 4px, rgba(255,255,255,.03) 6px),
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(100,130,180,.04) 3px, rgba(100,130,180,.04) 5px),
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(100,130,180,.025) 4px, rgba(100,130,180,.025) 6px),
repeating-radial-gradient(circle at 12% 18%, rgba(255,255,255,.03) 1px, transparent 1px),
repeating-radial-gradient(circle at 67% 77%, rgba(100,130,180,.03) 1px, transparent 1px),
repeating-radial-gradient(circle at 34% 51%, rgba(255,255,255,.025) 1.5px, transparent 1.5px),
repeating-radial-gradient(circle at 23% 67%, rgba(100,130,180,.015) 0.8px, transparent 0.8px),
repeating-radial-gradient(circle at 78% 22%, rgba(100,130,180,.02) 0.8px, transparent 0.8px),
radial-gradient(circle at 0% 0%, rgba(0,0,0,.035) 0%, transparent 70%),
radial-gradient(circle at 100% 100%, rgba(0,0,0,.035) 0%, transparent 70%),
linear-gradient(180deg, #4a5a6a 0%, #3a4a5a 100%);
border-color: rgba(100, 130, 180, 0.4);
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.35), inset 0 1px 0 rgba(255,255,255,0.1);
}
.clue-toggle:active {
@ -876,7 +1250,7 @@ crossword-menu {
.clue-list-container {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-columns: 1fr;
gap: 1.5rem;
margin-top: 1rem;
}
@ -916,6 +1290,11 @@ crossword-menu {
background: rgba(255, 255, 255, 0.05);
}
.clue-item.solved .clue-text {
text-decoration: line-through;
opacity: 0.6;
}
.clue-item .clue-number {
flex-shrink: 0;
min-width: 2rem;