feat: expand to 1014 words, add title-case display and 2 new themes

- Move word data from hardcoded seed.py to data/words.json
- Expand from 210 words across 7 categories to 1014 words across 14
  categories (animals, food, colors, sports, space, nature, music,
  science, countries, occupations, ocean, weather, mythology, technology)
- Word list sidebar now shows title case (Tiger) while grid stays
  uppercase; text input remains case-insensitive
- Add Ocean theme (light, blue accents) and Forest theme (dark, amber
  accents on deep green)
- Replace hardcoded rgba values with --coral-bg CSS variable so
  selection highlighting adapts per theme

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Mathew Sir Guest the best
2026-05-07 02:43:43 -06:00
co-authored by Claude Opus 4.6
parent cd1dc9200b
commit 9319f08875
13 changed files with 728 additions and 236 deletions
+40 -3
View File
@@ -6,6 +6,10 @@
let selectedCells = [];
let feedbackTimeout = null;
function titleCase(word) {
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
}
// DOM refs
const setupDiv = document.getElementById("game-setup");
const boardDiv = document.getElementById("game-board");
@@ -26,6 +30,7 @@
const finalTime = document.getElementById("final-time");
const playAgainBtn = document.getElementById("play-again-btn");
const inputArea = document.getElementById("input-area");
const playerNameInput = document.getElementById("player-name");
// Load categories
async function loadCategories() {
@@ -59,10 +64,16 @@
const categoryId = parseInt(categorySelect.value);
const boardSize = parseInt(boardSizeInput.value);
const wordCount = parseInt(wordCountInput.value);
const playerName = playerNameInput.value.trim();
const resp = await fetch("/api/game/new", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ category_id: categoryId, board_size: boardSize, word_count: wordCount }),
body: JSON.stringify({
category_id: categoryId,
board_size: boardSize,
word_count: wordCount,
player_name: playerName,
}),
});
if (!resp.ok) {
const errData = await resp.json().catch(function () { return {}; });
@@ -110,7 +121,7 @@
gameState.words.forEach(function (w) {
const li = document.createElement("li");
li.className = "list-group-item";
li.textContent = w.text;
li.textContent = titleCase(w.text);
li.dataset.word = w.text;
if (w.found) {
li.classList.add("word-found");
@@ -255,12 +266,14 @@
if (result.correct) {
highlightFoundCells(result.cells);
renderWordList();
showFeedback("Found: " + result.word + "!", "text-success");
showFeedback("Found: " + titleCase(result.word) + "!", "text-success");
} else {
showFeedback("Not a match.", "text-danger");
}
if (result.status === "completed") {
gameState.completed_at = result.completed_at;
gameState.player_name = result.player_name;
onGameComplete(result.completed_at);
}
} catch (err) {
@@ -294,6 +307,30 @@
finalTime.textContent = timerEl.textContent;
playAgainBtn.classList.remove("d-none");
inputArea.classList.add("d-none");
var startedAt = new Date(gameState.started_at);
var endedAt = new Date(completedAt);
var seconds = Math.floor((endedAt.getTime() - startedAt.getTime()) / 1000);
saveRecentGame({
game_id: gameState.id,
player_name: gameState.player_name || "Anonymous",
category: gameState.category,
time_display: timerEl.textContent,
time_seconds: seconds,
board_size: gameState.board_size,
word_count: gameState.words.length,
completed_at: completedAt,
});
}
function saveRecentGame(entry) {
var KEY = "ws-recent-games";
try {
var games = JSON.parse(localStorage.getItem(KEY) || "[]");
games.unshift(entry);
if (games.length > 20) games = games.slice(0, 20);
localStorage.setItem(KEY, JSON.stringify(games));
} catch (e) { /* localStorage unavailable */ }
}
playAgainBtn.addEventListener("click", function () {