more frontend fixes
This commit is contained in:
@ -50,6 +50,10 @@ class Crossword:
|
|||||||
def current_grid(self) -> List[List[Optional[str]]]:
|
def current_grid(self) -> List[List[Optional[str]]]:
|
||||||
return self._current_grid
|
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]:
|
def get_words_by_y_x_position(self, x, y) -> List[CrosswordWord]:
|
||||||
"""Get the list of CrosswordWord objects that start at position (x, y)."""
|
"""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)
|
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(
|
cw = Crossword(
|
||||||
dictionary=dictionary,
|
dictionary=dictionary,
|
||||||
grid=final_step.grid,
|
grid=final_step.grid,
|
||||||
|
solution_word_positions=solution_word_positions
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.debug("Generated Crossword: \n\n%s", cw)
|
logger.debug("Generated Crossword: \n\n%s", cw)
|
||||||
|
|
||||||
|
|
||||||
return cw
|
return cw
|
||||||
|
|
||||||
|
|
||||||
@ -139,7 +192,19 @@ class Crossword:
|
|||||||
grid: List[List[Optional[str]]],
|
grid: List[List[Optional[str]]],
|
||||||
current_grid: Optional[List[List[Optional[str]]]] = None,
|
current_grid: Optional[List[List[Optional[str]]]] = None,
|
||||||
words: Optional[List[CrosswordWord]] = 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._dictionary = dictionary
|
||||||
self._solved_grid = grid
|
self._solved_grid = grid
|
||||||
self._words: List[CrosswordWord] = []
|
self._words: List[CrosswordWord] = []
|
||||||
@ -147,6 +212,8 @@ class Crossword:
|
|||||||
self._horizontal_words_by_y_x_position = {}
|
self._horizontal_words_by_y_x_position = {}
|
||||||
self._vertical_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:
|
if current_grid is not None:
|
||||||
self._current_grid = current_grid
|
self._current_grid = current_grid
|
||||||
|
|
||||||
|
|||||||
@ -30,9 +30,10 @@ class SendFullSessionStateServerMessage(ServerMessageBase):
|
|||||||
grid: list[list[str]] # 2D array representing the current grid state
|
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_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
|
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_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 (col, row) 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 (col, row) positions that are solved
|
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):
|
class LetterUpdateBroadcastServerMessage(ServerMessageBase):
|
||||||
type: str = "letter_update"
|
type: str = "letter_update"
|
||||||
|
|||||||
@ -224,6 +224,11 @@ class WebsocketCrosswordServer(object):
|
|||||||
|
|
||||||
solved_positions = list(solved_positions)
|
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(
|
response = server_messages.SendFullSessionStateServerMessage(
|
||||||
session_id=session.session_id,
|
session_id=session.session_id,
|
||||||
grid=grid_state,
|
grid=grid_state,
|
||||||
@ -232,6 +237,7 @@ class WebsocketCrosswordServer(object):
|
|||||||
clue_positions_across=clue_positions_across,
|
clue_positions_across=clue_positions_across,
|
||||||
clue_positions_down=clue_positions_down,
|
clue_positions_down=clue_positions_down,
|
||||||
solved_positions=solved_positions,
|
solved_positions=solved_positions,
|
||||||
|
solution_word_positions=solution_word_positions,
|
||||||
)
|
)
|
||||||
# register the client to the session
|
# register the client to the session
|
||||||
session.add_client(handler)
|
session.add_client(handler)
|
||||||
|
|||||||
@ -15,8 +15,11 @@ export class ClueArea extends LitElement {
|
|||||||
selectedCol: { type: Number },
|
selectedCol: { type: Number },
|
||||||
selectedMode: { type: String }, // 'horizontal' or 'vertical'
|
selectedMode: { type: String }, // 'horizontal' or 'vertical'
|
||||||
grid: { type: Array }, // 2D grid from server (needed to find walls)
|
grid: { type: Array }, // 2D grid from server (needed to find walls)
|
||||||
|
gridData: { type: Object }, // { rows, cols, walls, solvedCells }
|
||||||
_showAllCluesAcross: { state: true },
|
_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.selectedCol = 0;
|
||||||
this.selectedMode = 'horizontal';
|
this.selectedMode = 'horizontal';
|
||||||
this.grid = [];
|
this.grid = [];
|
||||||
|
this.gridData = { rows: 0, cols: 0, walls: new Set(), solvedCells: new Set() };
|
||||||
this._showAllCluesAcross = false;
|
this._showAllCluesAcross = false;
|
||||||
this._showAllCluesDown = 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;
|
this._showAllCluesAcross = !this._showAllCluesAcross;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,6 +192,143 @@ export class ClueArea extends LitElement {
|
|||||||
this._showAllCluesDown = !this._showAllCluesDown;
|
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() {
|
render() {
|
||||||
const currentClue = this._getCurrentClue();
|
const currentClue = this._getCurrentClue();
|
||||||
|
|
||||||
@ -195,15 +338,15 @@ export class ClueArea extends LitElement {
|
|||||||
<div class="clue-area">
|
<div class="clue-area">
|
||||||
<div class="clue-header">
|
<div class="clue-header">
|
||||||
<h3>Across Clues</h3>
|
<h3>Across Clues</h3>
|
||||||
<button class="clue-toggle" @click="${this._toggleShowAllClues}">
|
<button class="clue-toggle" @click="${this._toggleShowAllCluesAcross}">
|
||||||
<span class="chevron">◀</span>
|
<span class="chevron">✕</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="clue-list-container">
|
<div class="clue-list-container">
|
||||||
<div class="clue-list">
|
<div class="clue-list">
|
||||||
${Object.entries(this.cluesAcross).map(([num, text]) => html`
|
${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-number">${num}.</span>
|
||||||
<span class="clue-text">${text}</span>
|
<span class="clue-text">${text}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -221,14 +364,14 @@ export class ClueArea extends LitElement {
|
|||||||
<div class="clue-header">
|
<div class="clue-header">
|
||||||
<h3>Down Clues</h3>
|
<h3>Down Clues</h3>
|
||||||
<button class="clue-toggle" @click="${this._toggleShowAllCluesDown}">
|
<button class="clue-toggle" @click="${this._toggleShowAllCluesDown}">
|
||||||
<span class="chevron">◀</span>
|
<span class="chevron">✕</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="clue-list-container">
|
<div class="clue-list-container">
|
||||||
<div class="clue-list">
|
<div class="clue-list">
|
||||||
${Object.entries(this.cluesDown).map(([num, text]) => html`
|
${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-number">${num}.</span>
|
||||||
<span class="clue-text">${text}</span>
|
<span class="clue-text">${text}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -253,11 +396,13 @@ export class ClueArea extends LitElement {
|
|||||||
`}
|
`}
|
||||||
|
|
||||||
<div class="clue-toggle-group">
|
<div class="clue-toggle-group">
|
||||||
<button class="clue-toggle" @click="${this._toggleShowAllClues}" title="Show all across clues">
|
<div class="clue-text empty">Show all clues:</div>
|
||||||
<span class="chevron">▶ A</span>
|
|
||||||
|
<button class="clue-toggle" @click="${this._toggleShowAllCluesAcross}" title="Show all across clues">
|
||||||
|
<span class="chevron">▶</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="clue-toggle" @click="${this._toggleShowAllCluesDown}" title="Show all down clues">
|
<button class="clue-toggle" @click="${this._toggleShowAllCluesDown}" title="Show all down clues">
|
||||||
<span class="chevron">▼ D</span>
|
<span class="chevron">▼</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -18,6 +18,10 @@ export class CrosswordGrid extends LitElement {
|
|||||||
_inputMode: { state: true }, // 'horizontal' or 'vertical'
|
_inputMode: { state: true }, // 'horizontal' or 'vertical'
|
||||||
_solvedCells: { state: true }, // tracks which cells are solved
|
_solvedCells: { state: true }, // tracks which cells are solved
|
||||||
_clueNumbers: { state: true }, // map of "row,col" -> { across: number, down: number }
|
_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
|
// styles moved to webui/styles.css; render into light DOM so external CSS applies
|
||||||
@ -31,6 +35,10 @@ export class CrosswordGrid extends LitElement {
|
|||||||
this._inputMode = 'horizontal'; // default input mode
|
this._inputMode = 'horizontal'; // default input mode
|
||||||
this._solvedCells = new Set(); // set of "r,c" strings for solved cells
|
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._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
|
this.sessionId = null; // Session ID for sending updates to server
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,9 +81,32 @@ export class CrosswordGrid extends LitElement {
|
|||||||
this._ensureGrid();
|
this._ensureGrid();
|
||||||
// set CSS variables for cell-size and column count; layout done in external stylesheet
|
// set CSS variables for cell-size and column count; layout done in external stylesheet
|
||||||
return html`
|
return html`
|
||||||
|
<div class="grid-container ${this._isSolutionWordComplete() ? 'complete' : ''}">
|
||||||
<div class="grid" style="--cell-size: ${this._cellSize}px; --cols: ${this.cols};">
|
<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()}
|
${this._grid.map((row, r) => row.map((cell, c) => this._renderCell(r, c, cell))).flat()}
|
||||||
</div>
|
</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>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,11 +168,18 @@ export class CrosswordGrid extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get solution index for this cell
|
||||||
|
const solutionIndex = this._solutionIndices.get(cellKey);
|
||||||
|
|
||||||
const cellContent = clueNumberDisplay
|
const cellContent = clueNumberDisplay
|
||||||
? html`<span class="clue-number">${clueNumberDisplay}</span><span class="cell-letter">${value}</span>`
|
? html`<span class="clue-number">${clueNumberDisplay}</span><span class="cell-letter">${value}</span>`
|
||||||
: html`<span class="cell-letter">${value}</span>`;
|
: html`<span class="cell-letter">${value}</span>`;
|
||||||
|
|
||||||
return html`<div class="${classes.join(' ')}" @click=${() => this._onCellClick(r, c)} data-r="${r}" data-c="${c}">${cellContent}</div>`;
|
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>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -204,6 +242,14 @@ export class CrosswordGrid extends LitElement {
|
|||||||
return end - start + 1;
|
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
|
* 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)
|
* (i.e., same column and not blocked by walls above/below this cell)
|
||||||
@ -228,14 +274,23 @@ export class CrosswordGrid extends LitElement {
|
|||||||
return r >= start && r <= end;
|
return r >= start && r <= end;
|
||||||
}
|
}
|
||||||
|
|
||||||
_onCellClick(r, c) {
|
_onCellClick(r, c, preferredMode = null) {
|
||||||
// if same cell is clicked again, toggle the input mode
|
// if same cell is clicked again, toggle the input mode
|
||||||
if (this._selected.r === r && this._selected.c === c) {
|
if (this._selected.r === r && this._selected.c === c) {
|
||||||
|
// If a preferred mode is provided, use it (don't toggle)
|
||||||
|
if (preferredMode) {
|
||||||
|
this._inputMode = preferredMode;
|
||||||
|
} else {
|
||||||
this._inputMode = this._inputMode === 'horizontal' ? 'vertical' : 'horizontal';
|
this._inputMode = this._inputMode === 'horizontal' ? 'vertical' : 'horizontal';
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// select a new cell
|
// select a new cell
|
||||||
this._selected = { r, c };
|
this._selected = { 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
|
// auto-select mode based on line lengths
|
||||||
const horizontalLength = this._getHorizontalLineLength(r, c);
|
const horizontalLength = this._getHorizontalLineLength(r, c);
|
||||||
const verticalLength = this._getVerticalLineLength(r, c);
|
const verticalLength = this._getVerticalLineLength(r, c);
|
||||||
@ -248,12 +303,19 @@ export class CrosswordGrid extends LitElement {
|
|||||||
}
|
}
|
||||||
// otherwise keep current mode (both >1 or both =1)
|
// otherwise keep current mode (both >1 or both =1)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
this.dispatchEvent(new CustomEvent('cell-selected', { detail: { row: r, col: c, mode: this._inputMode }, bubbles: true, composed: true }));
|
this.dispatchEvent(new CustomEvent('cell-selected', { detail: { row: r, col: c, mode: this._inputMode }, bubbles: true, composed: true }));
|
||||||
// focus the element so keyboard input goes to the grid
|
// focus the element so keyboard input goes to the grid
|
||||||
this.focus();
|
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) {
|
_onKeydown(e) {
|
||||||
// Only handle keys when the grid has focus
|
// Only handle keys when the grid has focus
|
||||||
// Map letters, arrows and backspace to our handlers
|
// Map letters, arrows and backspace to our handlers
|
||||||
@ -483,7 +545,44 @@ export class CrosswordGrid extends LitElement {
|
|||||||
this._solvedCells.delete(cellKey);
|
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();
|
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})`);
|
console.log(`Letter update from server: [${row}, ${col}] = "${letter}" (solved: ${is_solved})`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -522,6 +621,49 @@ export class CrosswordGrid extends LitElement {
|
|||||||
|
|
||||||
this.requestUpdate();
|
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);
|
customElements.define('crossword-grid', CrosswordGrid);
|
||||||
@ -125,8 +125,15 @@
|
|||||||
gridContainer.innerHTML = `
|
gridContainer.innerHTML = `
|
||||||
<div class="game-header">
|
<div class="game-header">
|
||||||
<h2>Crossword</h2>
|
<h2>Crossword</h2>
|
||||||
|
<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>
|
<button class="close-game-btn" aria-label="Close game">✕</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="game-content">
|
<div class="game-content">
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@ -163,6 +170,7 @@
|
|||||||
for (let r = 0; r < gridRows; r++) {
|
for (let r = 0; r < gridRows; r++) {
|
||||||
for (let c = 0; c < gridCols; c++) {
|
for (let c = 0; c < gridCols; c++) {
|
||||||
const cell = message.grid[r][c];
|
const cell = message.grid[r][c];
|
||||||
|
|
||||||
// Skip walls and empty cells
|
// Skip walls and empty cells
|
||||||
if (cell !== '#' && cell !== '') {
|
if (cell !== '#' && cell !== '') {
|
||||||
gridElement._grid[r][c] = cell;
|
gridElement._grid[r][c] = cell;
|
||||||
@ -181,6 +189,11 @@
|
|||||||
// Populate clue numbers for display
|
// Populate clue numbers for display
|
||||||
gridElement.populateClueNumbers(message.clue_positions_across, message.clue_positions_down);
|
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();
|
gridElement.requestUpdate();
|
||||||
|
|
||||||
console.log(`Grid created: ${gridRows}x${gridCols} with ${wallPositions.length} walls`);
|
console.log(`Grid created: ${gridRows}x${gridCols} with ${wallPositions.length} walls`);
|
||||||
@ -188,6 +201,9 @@
|
|||||||
if (message.solved_positions) {
|
if (message.solved_positions) {
|
||||||
console.log(`Solved positions: ${message.solved_positions.length}`);
|
console.log(`Solved positions: ${message.solved_positions.length}`);
|
||||||
}
|
}
|
||||||
|
if (message.solution_word_positions) {
|
||||||
|
console.log(`Solution word positions: ${message.solution_word_positions.length}`);
|
||||||
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
// Create and add clue area
|
// Create and add clue area
|
||||||
@ -197,9 +213,36 @@
|
|||||||
clueArea.cluePositionsAcross = message.clue_positions_across;
|
clueArea.cluePositionsAcross = message.clue_positions_across;
|
||||||
clueArea.cluePositionsDown = message.clue_positions_down;
|
clueArea.cluePositionsDown = message.clue_positions_down;
|
||||||
clueArea.grid = message.grid; // Pass grid for dimension calculation
|
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.selectedRow = 0;
|
||||||
clueArea.selectedCol = 0;
|
clueArea.selectedCol = 0;
|
||||||
clueArea.selectedMode = 'horizontal';
|
clueArea.selectedMode = 'horizontal';
|
||||||
|
|
||||||
|
// Update solved clues initially
|
||||||
|
clueArea._updateSolvedClues();
|
||||||
|
|
||||||
document.body.insertBefore(clueArea, document.body.firstChild);
|
document.body.insertBefore(clueArea, document.body.firstChild);
|
||||||
|
|
||||||
// Listen for cell selection changes
|
// Listen for cell selection changes
|
||||||
@ -210,12 +253,157 @@
|
|||||||
clueArea.requestUpdate();
|
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
|
// Close button handler
|
||||||
closeBtn.addEventListener('click', closeGame);
|
closeBtn.addEventListener('click', closeGame);
|
||||||
|
|
||||||
|
const shareBtn = gridContainer.querySelector('.share-game-btn');
|
||||||
|
shareBtn.addEventListener('click', shareGame);
|
||||||
|
|
||||||
notificationManager.success('Game loaded successfully');
|
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 to close game and return to menu
|
||||||
function closeGame() {
|
function closeGame() {
|
||||||
console.log('Closing game');
|
console.log('Closing game');
|
||||||
|
|||||||
@ -33,8 +33,8 @@ export class MobileKeyboard extends LitElement {
|
|||||||
'zxcvbnm'.split(''),
|
'zxcvbnm'.split(''),
|
||||||
];
|
];
|
||||||
|
|
||||||
// compute the maximum number of columns across rows (account for backspace in first row)
|
// compute the maximum number of columns across rows (account for backspace in second row now)
|
||||||
const counts = rows.map((r, idx) => r.length + (idx === 0 ? 1 : 0));
|
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 arrowCols = 3; // reserve 3 columns on the right for [left][down][right]
|
||||||
const baseMax = Math.max(...counts, 10);
|
const baseMax = Math.max(...counts, 10);
|
||||||
const maxCols = baseMax;
|
const maxCols = baseMax;
|
||||||
@ -53,7 +53,7 @@ export class MobileKeyboard extends LitElement {
|
|||||||
return html`<div class="${rowClasses}" style="--cols:${maxCols-idx}; --arrow-cols:${arrowCols};">
|
return html`<div class="${rowClasses}" style="--cols:${maxCols-idx}; --arrow-cols:${arrowCols};">
|
||||||
<div class="keys">
|
<div class="keys">
|
||||||
${r.map(l => html`<button @click=${() => this._emitLetter(l)}>${l}</button>`) }
|
${r.map(l => html`<button @click=${() => this._emitLetter(l)}>${l}</button>`) }
|
||||||
${idx === 0 ? html`<button class="backspace" @click=${() => this._emit({ type: 'backspace' })}>⌫</button>` : ''}
|
${idx === 1 ? html`<button class="backspace" @click=${() => this._emitBackspace()}>⌫</button>` : ''}
|
||||||
</div>
|
</div>
|
||||||
<div class="arrows">
|
<div class="arrows">
|
||||||
${Array.from({ length: arrowCols }).map((_, i) => {
|
${Array.from({ length: arrowCols }).map((_, i) => {
|
||||||
@ -67,7 +67,7 @@ export class MobileKeyboard extends LitElement {
|
|||||||
<!-- spacebar row -->
|
<!-- spacebar row -->
|
||||||
<div class="row" style="--cols:${maxCols};">
|
<div class="row" style="--cols:${maxCols};">
|
||||||
<!-- spacebar spans all but the right arrow columns -->
|
<!-- spacebar spans all but the right arrow columns -->
|
||||||
<button class="space" @click=${() => this._emit({ type: 'letter', value: '' })}>␣</button>
|
<button class="space" @click=${() => this._emitSpace()}>␣</button>
|
||||||
<!-- arrow columns: left, down, right (will occupy the last 3 columns) -->
|
<!-- 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('left')}>◀</button>
|
||||||
<button class="nav" @click=${() => this._emitNavigate('down')}>▼</button>
|
<button class="nav" @click=${() => this._emitNavigate('down')}>▼</button>
|
||||||
@ -77,20 +77,45 @@ export class MobileKeyboard extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
} _emitLetter(l) {
|
||||||
|
this._vibrate();
|
||||||
_emitLetter(l) {
|
|
||||||
this._emit({ type: 'letter', value: l });
|
this._emit({ type: 'letter', value: l });
|
||||||
}
|
}
|
||||||
|
|
||||||
_emitNavigate(dir) {
|
_emitNavigate(dir) {
|
||||||
|
this._vibrate();
|
||||||
this._emit({ type: 'navigate', value: dir });
|
this._emit({ type: 'navigate', value: dir });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_emitBackspace() {
|
||||||
|
this._vibrate();
|
||||||
|
this._emit({ type: 'backspace' });
|
||||||
|
}
|
||||||
|
|
||||||
|
_emitSpace() {
|
||||||
|
this._vibrate();
|
||||||
|
this._emit({ type: 'letter', value: '' });
|
||||||
|
}
|
||||||
|
|
||||||
_emit(detail) {
|
_emit(detail) {
|
||||||
window.dispatchEvent(new CustomEvent('key-press', { 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() {
|
connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
window.addEventListener('resize', this._onResize);
|
window.addEventListener('resize', this._onResize);
|
||||||
|
|||||||
@ -20,11 +20,17 @@ html, body { -webkit-text-size-adjust: 100%; }
|
|||||||
body {
|
body {
|
||||||
font-family: 'Segoe UI', 'Helvetica Neue', system-ui, Roboto, Arial;
|
font-family: 'Segoe UI', 'Helvetica Neue', system-ui, Roboto, Arial;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background: #0a0805;
|
background:
|
||||||
background-image:
|
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(255,255,255,.02) 3px, rgba(255,255,255,.02) 5px),
|
||||||
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(255,255,255,.04) 2px, rgba(255,255,255,.04) 4px),
|
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(255,255,255,.015) 4px, rgba(255,255,255,.015) 6px),
|
||||||
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(255,255,255,.04) 2px, rgba(255,255,255,.04) 4px),
|
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(0,0,0,.02) 3px, rgba(0,0,0,.02) 5px),
|
||||||
repeating-linear-gradient(0deg, transparent, transparent 3px, rgba(255,255,255,.02) 3px, rgba(255,255,255,.02) 6px);
|
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);
|
color: var(--ink-dark);
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
@ -61,22 +67,36 @@ main {
|
|||||||
}
|
}
|
||||||
crossword-grid { display: block; margin: 0 auto; }
|
crossword-grid { display: block; margin: 0 auto; }
|
||||||
mobile-keyboard { display: block; }
|
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; margin: 0 auto; }
|
crossword-grid { display: block; margin: 0 auto; }
|
||||||
|
|
||||||
|
|
||||||
|
.grid-container {
|
||||||
|
display: inline-block;
|
||||||
|
gap: 0;
|
||||||
|
overflow: visible;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.grid {
|
.grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 0;
|
gap: 0;
|
||||||
background: var(--ink-dark);
|
background: transparent;
|
||||||
background-image:
|
padding: 0;
|
||||||
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;
|
|
||||||
grid-template-columns: repeat(var(--cols), var(--cell-size));
|
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 {
|
.cell {
|
||||||
@ -86,14 +106,19 @@ crossword-grid { display: block; margin: 0 auto; }
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background:
|
background:
|
||||||
repeating-linear-gradient(45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
|
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(0,0,0,.03) 3px, rgba(0,0,0,.03) 5px),
|
||||||
repeating-linear-gradient(-45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
|
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(0,0,0,.025) 4px, rgba(0,0,0,.025) 6px),
|
||||||
repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
|
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(255,255,255,.06) 3px, rgba(255,255,255,.06) 5px),
|
||||||
repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
|
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(255,255,255,.04) 4px, rgba(255,255,255,.04) 6px),
|
||||||
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
|
repeating-radial-gradient(circle at 12% 18%, rgba(0,0,0,.03) 1px, transparent 1px),
|
||||||
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
|
repeating-radial-gradient(circle at 67% 77%, rgba(255,255,255,.05) 1px, transparent 1px),
|
||||||
radial-gradient(ellipse 800px 600px at 30% 40%, rgba(255,255,255,.4) 0%, transparent 40%),
|
repeating-radial-gradient(circle at 34% 51%, rgba(0,0,0,.02) 1.5px, transparent 1.5px),
|
||||||
radial-gradient(ellipse 600px 500px at 70% 60%, rgba(0,0,0,.02) 0%, transparent 50%),
|
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%);
|
linear-gradient(135deg, #fefdfb 0%, #f9f7f3 100%);
|
||||||
color: var(--ink-dark);
|
color: var(--ink-dark);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@ -119,11 +144,18 @@ crossword-grid { display: block; margin: 0 auto; }
|
|||||||
|
|
||||||
.cell.wall {
|
.cell.wall {
|
||||||
background:
|
background:
|
||||||
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(-45deg, transparent, transparent 2px, rgba(0,0,0,.15) 2px, rgba(0,0,0,.15) 4px),
|
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(255,255,255,.015) 4px, rgba(255,255,255,.015) 6px),
|
||||||
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(0,0,0,.05) 2px, rgba(0,0,0,.05) 4px),
|
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(0,0,0,.025) 3px, rgba(0,0,0,.025) 5px),
|
||||||
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(0,0,0,.05) 2px, rgba(0,0,0,.05) 4px),
|
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(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%);
|
linear-gradient(135deg, #3a3530 0%, #2a2520 100%);
|
||||||
color: transparent;
|
color: transparent;
|
||||||
border-color: var(--wall-dark);
|
border-color: var(--wall-dark);
|
||||||
@ -134,19 +166,34 @@ crossword-grid { display: block; margin: 0 auto; }
|
|||||||
inset 1px 1px 2px rgba(255,255,255,0.05);
|
inset 1px 1px 2px rgba(255,255,255,0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cell.wall .cell-letter {
|
||||||
|
font-size: 0;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
.cell.wall.selected {
|
.cell.wall.selected {
|
||||||
border-color: var(--accent-wall);
|
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);
|
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:
|
background:
|
||||||
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(-45deg, transparent, transparent 2px, rgba(0,0,0,.15) 2px, rgba(0,0,0,.15) 4px),
|
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%);
|
linear-gradient(135deg, #3a3530 0%, #2a2520 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cell.wall.mode-highlighted {
|
.cell.wall.mode-highlighted {
|
||||||
background:
|
background:
|
||||||
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(-45deg, transparent, transparent 2px, rgba(0,0,0,.15) 2px, rgba(0,0,0,.15) 4px),
|
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%);
|
linear-gradient(135deg, #3a3530 0%, #2a2520 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,17 +215,58 @@ crossword-grid { display: block; margin: 0 auto; }
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 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 {
|
.cell.mode-highlighted {
|
||||||
background:
|
background:
|
||||||
repeating-linear-gradient(45deg, transparent, transparent 1px, rgba(100,180,200,.2) 1px, rgba(100,180,200,.2) 2px),
|
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(100,180,200,.15) 3px, rgba(100,180,200,.15) 5px),
|
||||||
repeating-linear-gradient(-45deg, transparent, transparent 1px, rgba(100,180,200,.2) 1px, rgba(100,180,200,.2) 2px),
|
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(100,180,200,.1) 4px, rgba(100,180,200,.1) 6px),
|
||||||
repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(100,180,200,.1) 1px, rgba(100,180,200,.1) 2px),
|
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(100,180,200,.08) 3px, rgba(100,180,200,.08) 5px),
|
||||||
repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(100,180,200,.1) 1px, rgba(100,180,200,.1) 2px),
|
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(100,180,200,.05) 4px, rgba(100,180,200,.05) 6px),
|
||||||
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(100,180,200,.05) 2px, rgba(100,180,200,.05) 4px),
|
repeating-radial-gradient(circle at 12% 18%, rgba(100,180,200,.07) 1px, transparent 1px),
|
||||||
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(100,180,200,.05) 2px, rgba(100,180,200,.05) 4px),
|
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(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%);
|
linear-gradient(135deg, #f0fafb 0%, #e8f4f8 100%);
|
||||||
box-shadow:
|
box-shadow:
|
||||||
inset 0 1px 2px rgba(255,255,255,0.9),
|
inset 0 1px 2px rgba(255,255,255,0.9),
|
||||||
@ -190,15 +278,44 @@ crossword-grid { display: block; margin: 0 auto; }
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Solved cells - green background and not editable */
|
/* 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 {
|
.cell.solved {
|
||||||
background:
|
background:
|
||||||
repeating-linear-gradient(45deg, transparent, transparent 1px, rgba(100,200,100,.15) 1px, rgba(100,200,100,.15) 2px),
|
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(100,200,100,.1) 3px, rgba(100,200,100,.1) 5px),
|
||||||
repeating-linear-gradient(-45deg, transparent, transparent 1px, rgba(100,200,100,.15) 1px, rgba(100,200,100,.15) 2px),
|
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(100,200,100,.08) 4px, rgba(100,200,100,.08) 6px),
|
||||||
repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(100,200,100,.1) 1px, rgba(100,200,100,.1) 2px),
|
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(100,200,100,.06) 3px, rgba(100,200,100,.06) 5px),
|
||||||
repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(100,200,100,.1) 1px, rgba(100,200,100,.1) 2px),
|
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(100,200,100,.04) 4px, rgba(100,200,100,.04) 6px),
|
||||||
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(100,200,100,.05) 2px, rgba(100,200,100,.05) 4px),
|
repeating-radial-gradient(circle at 12% 18%, rgba(100,200,100,.06) 1px, transparent 1px),
|
||||||
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(100,200,100,.05) 2px, rgba(100,200,100,.05) 4px),
|
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(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%);
|
linear-gradient(135deg, #d4f4d4 0%, #c8ead4 100%);
|
||||||
box-shadow:
|
box-shadow:
|
||||||
inset 0 1px 2px rgba(255,255,255,0.8),
|
inset 0 1px 2px rgba(255,255,255,0.8),
|
||||||
@ -206,21 +323,58 @@ crossword-grid { display: block; margin: 0 auto; }
|
|||||||
0 0.5px 1px rgba(0,0,0,0.05),
|
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(100,200,100,0.08),
|
||||||
inset 1px 1px 2px rgba(255,255,255,0.3);
|
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 {
|
.cell.selected {
|
||||||
outline: none;
|
outline: none;
|
||||||
background:
|
background:
|
||||||
repeating-linear-gradient(45deg, transparent, transparent 1px, rgba(255,200,0,.18) 1px, rgba(255,200,0,.18) 2px),
|
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(255,200,0,.15) 3px, rgba(255,200,0,.15) 5px),
|
||||||
repeating-linear-gradient(-45deg, transparent, transparent 1px, rgba(255,200,0,.18) 1px, rgba(255,200,0,.18) 2px),
|
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(255,200,0,.1) 4px, rgba(255,200,0,.1) 6px),
|
||||||
repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(255,200,0,.09) 1px, rgba(255,200,0,.09) 2px),
|
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(255,200,0,.08) 3px, rgba(255,200,0,.08) 5px),
|
||||||
repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(255,200,0,.09) 1px, rgba(255,200,0,.09) 2px),
|
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(255,200,0,.05) 4px, rgba(255,200,0,.05) 6px),
|
||||||
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(255,200,0,.05) 2px, rgba(255,200,0,.05) 4px),
|
repeating-radial-gradient(circle at 12% 18%, rgba(255,200,0,.07) 1px, transparent 1px),
|
||||||
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(255,200,0,.05) 2px, rgba(255,200,0,.05) 4px),
|
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(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%);
|
linear-gradient(135deg, #fffef9 0%, #fff8e6 100%);
|
||||||
border-color: var(--ink-dark);
|
border-color: var(--ink-dark);
|
||||||
box-shadow:
|
box-shadow:
|
||||||
@ -233,13 +387,18 @@ crossword-grid { display: block; margin: 0 auto; }
|
|||||||
|
|
||||||
.cell.selected.mode-highlighted {
|
.cell.selected.mode-highlighted {
|
||||||
background:
|
background:
|
||||||
repeating-linear-gradient(45deg, transparent, transparent 1px, rgba(200,150,0,.2) 1px, rgba(200,150,0,.2) 2px),
|
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(200,150,0,.15) 3px, rgba(200,150,0,.15) 5px),
|
||||||
repeating-linear-gradient(-45deg, transparent, transparent 1px, rgba(200,150,0,.2) 1px, rgba(200,150,0,.2) 2px),
|
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(200,150,0,.1) 4px, rgba(200,150,0,.1) 6px),
|
||||||
repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(200,150,0,.1) 1px, rgba(200,150,0,.1) 2px),
|
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(200,150,0,.08) 3px, rgba(200,150,0,.08) 5px),
|
||||||
repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(200,150,0,.1) 1px, rgba(200,150,0,.1) 2px),
|
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(200,150,0,.05) 4px, rgba(200,150,0,.05) 6px),
|
||||||
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(200,150,0,.05) 2px, rgba(200,150,0,.05) 4px),
|
repeating-radial-gradient(circle at 12% 18%, rgba(200,150,0,.07) 1px, transparent 1px),
|
||||||
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(200,150,0,.05) 2px, rgba(200,150,0,.05) 4px),
|
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(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%);
|
linear-gradient(135deg, #fffaf0 0%, #fff5d6 100%);
|
||||||
box-shadow:
|
box-shadow:
|
||||||
inset 0 1px 2px rgba(255,255,255,0.9),
|
inset 0 1px 2px rgba(255,255,255,0.9),
|
||||||
@ -263,7 +422,7 @@ mobile-keyboard {
|
|||||||
z-index: 2000;
|
z-index: 2000;
|
||||||
font-size: clamp(0.95rem, 2.4vw, 1.15rem);
|
font-size: clamp(0.95rem, 2.4vw, 1.15rem);
|
||||||
--key-width: clamp(1.7rem, 5.5vw, 2.1rem);
|
--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: 0.4;
|
||||||
--stagger-factor-deep: 0.8;
|
--stagger-factor-deep: 0.8;
|
||||||
--up-arrow-offset: calc(0 - var(--key-width) * var(--stagger-factor-deep));
|
--up-arrow-offset: calc(0 - var(--key-width) * var(--stagger-factor-deep));
|
||||||
@ -291,11 +450,17 @@ mobile-keyboard .keyboard {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.35rem;
|
gap: 0.35rem;
|
||||||
padding: 0.75em;
|
padding: 0.75em;
|
||||||
background: #0a0805;
|
background:
|
||||||
background-image:
|
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(255,255,255,.02) 3px, rgba(255,255,255,.02) 5px),
|
||||||
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(255,255,255,.04) 2px, rgba(255,255,255,.04) 4px),
|
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(255,255,255,.015) 4px, rgba(255,255,255,.015) 6px),
|
||||||
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(255,255,255,.04) 2px, rgba(255,255,255,.04) 4px),
|
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(0,0,0,.02) 3px, rgba(0,0,0,.02) 5px),
|
||||||
repeating-linear-gradient(0deg, transparent, transparent 3px, rgba(255,255,255,.02) 3px, rgba(255,255,255,.02) 6px);
|
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;
|
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);
|
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;
|
margin: 0 auto;
|
||||||
@ -340,13 +505,18 @@ mobile-keyboard button {
|
|||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
border: none;
|
border: none;
|
||||||
background:
|
background:
|
||||||
repeating-linear-gradient(45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
|
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(0,0,0,.02) 3px, rgba(0,0,0,.02) 5px),
|
||||||
repeating-linear-gradient(-45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
|
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(0,0,0,.015) 4px, rgba(0,0,0,.015) 6px),
|
||||||
repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
|
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(255,255,255,.05) 3px, rgba(255,255,255,.05) 5px),
|
||||||
repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
|
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(255,255,255,.03) 4px, rgba(255,255,255,.03) 6px),
|
||||||
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
|
repeating-radial-gradient(circle at 12% 18%, rgba(0,0,0,.02) 1px, transparent 1px),
|
||||||
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
|
repeating-radial-gradient(circle at 67% 77%, rgba(255,255,255,.04) 1px, transparent 1px),
|
||||||
radial-gradient(ellipse 800px 600px at 30% 40%, rgba(255,255,255,.4) 0%, transparent 40%),
|
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%);
|
linear-gradient(180deg, #fffef9 0%, #f5f1ed 100%);
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@ -371,42 +541,57 @@ mobile-keyboard button:active {
|
|||||||
|
|
||||||
mobile-keyboard button:hover {
|
mobile-keyboard button:hover {
|
||||||
background:
|
background:
|
||||||
repeating-linear-gradient(45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
|
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(0,0,0,.025) 3px, rgba(0,0,0,.025) 5px),
|
||||||
repeating-linear-gradient(-45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
|
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(0,0,0,.018) 4px, rgba(0,0,0,.018) 6px),
|
||||||
repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
|
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(255,255,255,.06) 3px, rgba(255,255,255,.06) 5px),
|
||||||
repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
|
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(255,255,255,.04) 4px, rgba(255,255,255,.04) 6px),
|
||||||
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
|
repeating-radial-gradient(circle at 12% 18%, rgba(0,0,0,.025) 1px, transparent 1px),
|
||||||
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
|
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(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%);
|
linear-gradient(180deg, #fffbf5 0%, #f8f4ef 100%);
|
||||||
transform: scale(1.05);
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
mobile-keyboard button.backspace {
|
mobile-keyboard button.backspace {
|
||||||
width: calc(var(--key-width) * 0.8);
|
width: calc(var(--key-width) * 1.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
mobile-keyboard button[aria-pressed="true"] {
|
mobile-keyboard button[aria-pressed="true"] {
|
||||||
background:
|
background:
|
||||||
repeating-linear-gradient(45deg, transparent, transparent 1px, rgba(255,200,0,.18) 1px, rgba(255,200,0,.18) 2px),
|
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(0,0,0,.03) 3px, rgba(0,0,0,.03) 5px),
|
||||||
repeating-linear-gradient(-45deg, transparent, transparent 1px, rgba(255,200,0,.18) 1px, rgba(255,200,0,.18) 2px),
|
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(0,0,0,.02) 4px, rgba(0,0,0,.02) 6px),
|
||||||
repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(255,200,0,.09) 1px, rgba(255,200,0,.09) 2px),
|
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(255,200,0,.12) 3px, rgba(255,200,0,.12) 5px),
|
||||||
repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(255,200,0,.09) 1px, rgba(255,200,0,.09) 2px),
|
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(255,200,0,.08) 4px, rgba(255,200,0,.08) 6px),
|
||||||
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(255,200,0,.05) 2px, rgba(255,200,0,.05) 4px),
|
repeating-radial-gradient(circle at 12% 18%, rgba(0,0,0,.025) 1px, transparent 1px),
|
||||||
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(255,200,0,.05) 2px, rgba(255,200,0,.05) 4px),
|
repeating-radial-gradient(circle at 67% 77%, rgba(255,200,0,.08) 1px, transparent 1px),
|
||||||
radial-gradient(ellipse 800px 600px at 30% 40%, rgba(255,255,255,.4) 0%, transparent 40%),
|
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%);
|
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);
|
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 {
|
mobile-keyboard .nav {
|
||||||
background:
|
background:
|
||||||
repeating-linear-gradient(45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
|
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(255,255,255,.05) 3px, rgba(255,255,255,.05) 5px),
|
||||||
repeating-linear-gradient(-45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
|
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(255,255,255,.03) 4px, rgba(255,255,255,.03) 6px),
|
||||||
repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
|
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(0,0,0,.08) 3px, rgba(0,0,0,.08) 5px),
|
||||||
repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
|
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(0,0,0,.05) 4px, rgba(0,0,0,.05) 6px),
|
||||||
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
|
repeating-radial-gradient(circle at 12% 18%, rgba(255,255,255,.04) 1px, transparent 1px),
|
||||||
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
|
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%);
|
linear-gradient(180deg, #5a8ca4 0%, #4a7c94 100%);
|
||||||
color: #f0f8ff;
|
color: #f0f8ff;
|
||||||
width: var(--key-width);
|
width: var(--key-width);
|
||||||
@ -418,12 +603,17 @@ mobile-keyboard .nav {
|
|||||||
|
|
||||||
mobile-keyboard .nav:hover {
|
mobile-keyboard .nav:hover {
|
||||||
background:
|
background:
|
||||||
repeating-linear-gradient(45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
|
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(255,255,255,.06) 3px, rgba(255,255,255,.06) 5px),
|
||||||
repeating-linear-gradient(-45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
|
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(255,255,255,.04) 4px, rgba(255,255,255,.04) 6px),
|
||||||
repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
|
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(0,0,0,.1) 3px, rgba(0,0,0,.1) 5px),
|
||||||
repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
|
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(0,0,0,.06) 4px, rgba(0,0,0,.06) 6px),
|
||||||
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
|
repeating-radial-gradient(circle at 12% 18%, rgba(255,255,255,.05) 1px, transparent 1px),
|
||||||
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
|
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%);
|
linear-gradient(180deg, #6a9cb4 0%, #5a8ca4 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -435,13 +625,18 @@ mobile-keyboard .space {
|
|||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
border: none;
|
border: none;
|
||||||
background:
|
background:
|
||||||
repeating-linear-gradient(45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
|
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(0,0,0,.02) 3px, rgba(0,0,0,.02) 5px),
|
||||||
repeating-linear-gradient(-45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
|
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(0,0,0,.015) 4px, rgba(0,0,0,.015) 6px),
|
||||||
repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
|
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(255,255,255,.05) 3px, rgba(255,255,255,.05) 5px),
|
||||||
repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
|
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(255,255,255,.03) 4px, rgba(255,255,255,.03) 6px),
|
||||||
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
|
repeating-radial-gradient(circle at 12% 18%, rgba(0,0,0,.02) 1px, transparent 1px),
|
||||||
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
|
repeating-radial-gradient(circle at 67% 77%, rgba(255,255,255,.04) 1px, transparent 1px),
|
||||||
radial-gradient(ellipse 800px 600px at 30% 40%, rgba(255,255,255,.4) 0%, transparent 40%),
|
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%);
|
linear-gradient(180deg, #fffef9 0%, #f5f1ed 100%);
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@ -466,11 +661,14 @@ mobile-keyboard .handle {
|
|||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
background:
|
background:
|
||||||
repeating-linear-gradient(45deg, transparent, transparent 1px, rgba(0,0,0,.15) 1px, rgba(0,0,0,.15) 2px),
|
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(255,255,255,.02) 3px, rgba(255,255,255,.02) 5px),
|
||||||
repeating-linear-gradient(-45deg, transparent, transparent 1px, rgba(0,0,0,.15) 1px, rgba(0,0,0,.15) 2px),
|
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(255,255,255,.015) 4px, rgba(255,255,255,.015) 6px),
|
||||||
repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(0,0,0,.08) 1px, rgba(0,0,0,.08) 2px),
|
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(0,0,0,.02) 3px, rgba(0,0,0,.02) 5px),
|
||||||
repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(0,0,0,.08) 1px, rgba(0,0,0,.08) 2px),
|
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(0,0,0,.015) 4px, rgba(0,0,0,.015) 6px),
|
||||||
#0a0805;
|
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;
|
color: #a89f99;
|
||||||
padding: 0.55rem 0.9rem;
|
padding: 0.55rem 0.9rem;
|
||||||
border-radius: 0.25rem 0.25rem 0 0;
|
border-radius: 0.25rem 0.25rem 0 0;
|
||||||
@ -501,32 +699,39 @@ crossword-menu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.menu-container {
|
.menu-container {
|
||||||
background: #0a0805;
|
background:
|
||||||
background-image:
|
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(255,255,255,.02) 3px, rgba(255,255,255,.02) 5px),
|
||||||
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(255,255,255,.04) 2px, rgba(255,255,255,.04) 4px),
|
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(255,255,255,.015) 4px, rgba(255,255,255,.015) 6px),
|
||||||
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(255,255,255,.04) 2px, rgba(255,255,255,.04) 4px),
|
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(0,0,0,.02) 3px, rgba(0,0,0,.02) 5px),
|
||||||
repeating-linear-gradient(0deg, transparent, transparent 3px, rgba(255,255,255,.02) 3px, rgba(255,255,255,.02) 6px);
|
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;
|
min-height: 100vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 2rem;
|
padding: 1rem;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu {
|
.menu {
|
||||||
background: #f9f7f3;
|
background: #f9f7f3;
|
||||||
background-image:
|
background-image:
|
||||||
repeating-linear-gradient(45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
|
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(0,0,0,.02) 3px, rgba(0,0,0,.02) 5px),
|
||||||
repeating-linear-gradient(-45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
|
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(0,0,0,.015) 4px, rgba(0,0,0,.015) 6px),
|
||||||
repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
|
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(255,255,255,.05) 3px, rgba(255,255,255,.05) 5px),
|
||||||
repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
|
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(255,255,255,.03) 4px, rgba(255,255,255,.03) 6px),
|
||||||
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
|
repeating-radial-gradient(circle at 12% 18%, rgba(0,0,0,.02) 1px, transparent 1px),
|
||||||
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
|
repeating-radial-gradient(circle at 67% 77%, rgba(255,255,255,.04) 1px, transparent 1px),
|
||||||
radial-gradient(ellipse 800px 600px at 30% 40%, rgba(255,255,255,.4) 0%, transparent 40%),
|
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%),
|
radial-gradient(ellipse 600px 500px at 70% 60%, rgba(0,0,0,.02) 0%, transparent 50%),
|
||||||
linear-gradient(135deg, #fefdfb 0%, #f9f7f3 100%);
|
linear-gradient(135deg, #fefdfb 0%, #f9f7f3 100%);
|
||||||
padding: 3rem;
|
padding: 2rem;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 10px 40px rgba(0,0,0,0.3),
|
0 10px 40px rgba(0,0,0,0.3),
|
||||||
@ -545,21 +750,21 @@ crossword-menu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.form-group {
|
.form-group {
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group label {
|
.form-group label {
|
||||||
display: block;
|
display: block;
|
||||||
color: #1a1815;
|
color: #1a1815;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.3rem;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group select,
|
.form-group select,
|
||||||
.form-group input[type="text"] {
|
.form-group input[type="text"] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.75rem;
|
padding: 0.6rem;
|
||||||
border: 1px solid #c0bbb5;
|
border: 1px solid #c0bbb5;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
@ -579,15 +784,21 @@ crossword-menu {
|
|||||||
|
|
||||||
.menu button {
|
.menu button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.75rem;
|
padding: 0.6rem;
|
||||||
|
margin-bottom: 0.7rem;
|
||||||
background:
|
background:
|
||||||
repeating-linear-gradient(45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
|
repeating-linear-gradient(87deg, transparent, transparent 3px, rgba(0,0,0,.02) 3px, rgba(0,0,0,.02) 5px),
|
||||||
repeating-linear-gradient(-45deg, transparent, transparent 1px, rgba(0,0,0,.06) 1px, rgba(0,0,0,.06) 2px),
|
repeating-linear-gradient(22deg, transparent, transparent 4px, rgba(0,0,0,.015) 4px, rgba(0,0,0,.015) 6px),
|
||||||
repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
|
repeating-linear-gradient(59deg, transparent, transparent 3px, rgba(255,255,255,.05) 3px, rgba(255,255,255,.05) 5px),
|
||||||
repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(0,0,0,.035) 1px, rgba(0,0,0,.035) 2px),
|
repeating-linear-gradient(-11deg, transparent, transparent 4px, rgba(255,255,255,.03) 4px, rgba(255,255,255,.03) 6px),
|
||||||
repeating-linear-gradient(67deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
|
repeating-radial-gradient(circle at 12% 18%, rgba(0,0,0,.02) 1px, transparent 1px),
|
||||||
repeating-linear-gradient(23deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
|
repeating-radial-gradient(circle at 67% 77%, rgba(255,255,255,.04) 1px, transparent 1px),
|
||||||
radial-gradient(ellipse 800px 600px at 30% 40%, rgba(255,255,255,.4) 0%, transparent 40%),
|
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%);
|
linear-gradient(180deg, #fffef9 0%, #f5f1ed 100%);
|
||||||
color: #1a1815;
|
color: #1a1815;
|
||||||
border: 1px solid #c0bbb5;
|
border: 1px solid #c0bbb5;
|
||||||
@ -655,12 +866,20 @@ crossword-menu {
|
|||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
|
||||||
animation: slideIn 0.3s ease-out;
|
animation: slideIn 0.3s ease-out;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
max-width: 350px;
|
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 {
|
@keyframes slideIn {
|
||||||
@ -785,9 +1004,71 @@ crossword-menu {
|
|||||||
flex-grow: 1;
|
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 {
|
.close-game-btn {
|
||||||
background: none;
|
background:
|
||||||
border: 1px solid rgba(245, 241, 237, 0.3);
|
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;
|
color: #f5f1ed;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -801,11 +1082,25 @@ crossword-menu {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
line-height: 1;
|
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 {
|
.close-game-btn:hover {
|
||||||
background: rgba(245, 241, 237, 0.1);
|
background:
|
||||||
border-color: rgba(245, 241, 237, 0.6);
|
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 {
|
.close-game-btn:active {
|
||||||
@ -827,7 +1122,6 @@ crossword-grid {
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
min-width: fit-content; /* Ensure grid takes its full needed width */
|
min-width: fit-content; /* Ensure grid takes its full needed width */
|
||||||
border-radius: 0px;
|
border-radius: 0px;
|
||||||
background: var(--ink-dark);
|
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -837,7 +1131,17 @@ crossword-grid {
|
|||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 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);
|
backdrop-filter: blur(8px);
|
||||||
border-bottom: 2px solid rgba(245, 241, 237, 0.2);
|
border-bottom: 2px solid rgba(245, 241, 237, 0.2);
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
@ -879,8 +1183,20 @@ crossword-grid {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.clue-toggle {
|
.clue-toggle {
|
||||||
background: none;
|
background:
|
||||||
border: 1px solid rgba(245, 241, 237, 0.3);
|
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;
|
color: #f5f1ed;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -893,11 +1209,25 @@ crossword-grid {
|
|||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
padding: 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 {
|
.clue-toggle:hover {
|
||||||
background: rgba(245, 241, 237, 0.1);
|
background:
|
||||||
border-color: rgba(245, 241, 237, 0.6);
|
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 {
|
.clue-toggle:active {
|
||||||
@ -920,7 +1250,7 @@ crossword-grid {
|
|||||||
|
|
||||||
.clue-list-container {
|
.clue-list-container {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr;
|
||||||
gap: 1.5rem;
|
gap: 1.5rem;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
@ -960,6 +1290,11 @@ crossword-grid {
|
|||||||
background: rgba(255, 255, 255, 0.05);
|
background: rgba(255, 255, 255, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.clue-item.solved .clue-text {
|
||||||
|
text-decoration: line-through;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
.clue-item .clue-number {
|
.clue-item .clue-number {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
min-width: 2rem;
|
min-width: 2rem;
|
||||||
|
|||||||
Reference in New Issue
Block a user