Upload files to "/"

Word Search Stories

At Eduspire, we believe learning English should be fun, interactive, and creative. That’s why we developed Word Search Stories – a playful and educational game designed for students from grades 3 to 6. This game helps children enhance their vocabulary, spelling, reading comprehension, and pattern recognition while enjoying engaging, themed stories.

In Word Search Stories, students first select their grade level and a theme, such as Farm Life, Animals, or Space Adventure. The game then presents a colorful and interactive letter grid where children must find hidden words related to the selected theme. Words can appear horizontally, vertically, or diagonally, making the game challenging yet fun. A real-time progress tracker shows how many words have been found, while the Hint button allows students to briefly highlight one of the remaining words, encouraging learning without frustration.

Once all words are found, a short story related to the theme is unlocked. This story is presented in a playful pop-up animation, helping students connect the vocabulary they discovered with real language in context. The game is designed to be vibrant, visually appealing, and intuitive, featuring colorful cells, easy drag-and-select controls, and responsive layout that works on both desktop and touch devices.

Tech Stack:

HTML – structures the game, grid, buttons, and story popups.

CSS – adds vibrant colors, animations, and kid-friendly design.

JavaScript – handles word placement, grid logic, selection tracking, hints, and story reveal.

Optional Enhancements: Three.js for 3D effects, Web Audio API for future sound and voice-over support.

The game is fully iframe compatible, meaning it can easily be embedded into any website or digital learning platform. Its modular design separates structure (index.html), styles (style.css), and logic (script.js) for maintainability and easy collaboration.
This commit is contained in:
2025-09-09 17:57:03 +00:00
commit dc9985bbc8
3 changed files with 1336 additions and 0 deletions

104
index.html Normal file
View File

@@ -0,0 +1,104 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Story Word-Search — Learn English (Grades 36)</title>
<!-- CSS file -->
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<div class="app" role="application" aria-label="Word search story game">
<!-- HEADER with title and controls -->
<header>
<h1>📚 Story Word-Search — Grades 36</h1>
<div class="controls">
<!-- Grade dropdown -->
<div style="display:flex;gap:8px;align-items:center">
<label class="label">Grade</label>
<select id="gradeSelect" aria-label="grade choice">
<option value="3">Grade 3</option>
<option value="4">Grade 4</option>
<option value="5">Grade 5</option>
<option value="6" selected>Grade 6</option>
</select>
</div>
<!-- New Game button -->
<button id="newBtn" class="btn" title="Generate new puzzle">🔁 New Game</button>
</div>
</header>
<!-- LEFT PANEL (words list + settings) -->
<div class="left panel">
<div style="display:flex;justify-content:space-between;align-items:center">
<strong>Words to find</strong>
<span id="progress">0 / 0</span>
</div>
<div class="wordlist" id="wordList" aria-live="polite"></div>
<!-- Hint + Show Story -->
<div style="display:flex;gap:8px;margin-top:8px">
<button id="hintBtn" class="small">💡 Hint</button>
<button id="showStoryBtn" class="small">📖 Show Story</button>
</div>
<!-- Settings (theme, grid size, sound) -->
<div style="margin-top:12px">
<div class="settings panel">
<div class="row"><span class="label">Theme</span>
<select id="themeSelect" aria-label="theme">
<option value="farm">Farm Friends</option>
<option value="space">Space Adventure</option>
<option value="forest">Forest Tale</option>
</select>
</div>
<div class="row"><span class="label">Grid size</span>
<select id="sizeSelect">
<option>10</option>
<option>12</option>
<option selected>14</option>
<option>16</option>
</select>
</div>
<div class="row"><span class="label">Sound</span>
<input id="soundToggle" type="checkbox" checked>
</div>
</div>
</div>
</div>
<!-- RIGHT PANEL (grid) -->
<div class="panel" style="padding:14px;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
<strong>Find the words in the grid!</strong>
<div style="font-size:13px;color:#666">Tap and drag to select — works with mouse/touch.</div>
</div>
<div class="gridwrap" id="gridWrap" aria-hidden="false">
<div id="grid" class="grid" role="grid" aria-label="letter grid"></div>
</div>
</div>
<div class="footer">Made with ❤️ for curious kids — find all words to unlock the story and hear it aloud.</div>
</div>
<!-- STORY POPUP (shown after game success) -->
<div id="overlay" class="overlay" role="dialog" aria-hidden="true">
<canvas id="confetti" class="confetti-canvas"></canvas>
<div class="storybox panel" role="document" aria-live="assertive">
<div style="display:flex;justify-content:space-between;align-items:center">
<h2 id="storyTitle" style="margin:0">Story</h2>
<button id="closeStory" class="small"></button>
</div>
<p class="story-text" id="storyText"></p>
<div style="display:flex;gap:8px;justify-content:center">
<button id="replayVoice" class="btn">🔊 Replay Voice</button>
<button id="againBtn" class="small">Play Again</button>
</div>
</div>
</div>
<!-- JavaScript file -->
<script src="script.js"></script>
</body>
</html>

221
script.js Normal file
View File

@@ -0,0 +1,221 @@
/* ======================
SCRIPT.JS
- Working Word Search Game with HINT button
====================== */
/* ---------- Data: words & stories ---------- */
const data = {
farm: {
3: {
words: ["COW", "HEN", "BARN", "MILK", "EGGS"],
storyTitle: "Maya and the Friendly Cow",
story: "Maya went to the barn. She saw a cow and a hen. The hen gave eggs. Maya drank warm milk and smiled."
},
4: {
words: ["FARMER", "TRACTOR", "FIELD", "CORN", "SHEEP"],
storyTitle: "The Busy Farmer",
story: "The farmer drove the tractor to the field. The sheep grazed while the farmer planted corn. Everyone helped in the barn."
}
}
};
/* ---------- Global Variables ---------- */
let gridSize = 14;
let grid = [];
let placements = [];
let wordsToFind = [];
let foundWords = new Set();
let selecting = false;
let selectedCells = [];
const gridEl = document.getElementById('grid');
const wordListEl = document.getElementById('wordList');
const progressEl = document.getElementById('progress');
const overlay = document.getElementById('overlay');
const storyText = document.getElementById('storyText');
const storyTitle = document.getElementById('storyTitle');
/* ---------- Helper: random letter ---------- */
function randLetter() {
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
return letters[Math.floor(Math.random() * letters.length)];
}
/* ---------- Grid Setup ---------- */
function makeEmptyGrid(size) {
grid = Array(size).fill(null).map(() => Array(size).fill(""));
}
function placeWord(word, size) {
const dirs = [
[1, 0], [0, 1], [1, 1], [-1, 1]
];
for (let tries = 0; tries < 200; tries++) {
const dir = dirs[Math.floor(Math.random() * dirs.length)];
const row = Math.floor(Math.random() * size);
const col = Math.floor(Math.random() * size);
let r = row, c = col, ok = true;
for (let ch of word) {
if (r < 0 || r >= size || c < 0 || c >= size || (grid[r][c] && grid[r][c] !== ch)) {
ok = false; break;
}
r += dir[0]; c += dir[1];
}
if (!ok) continue;
r = row; c = col;
for (let ch of word) {
grid[r][c] = ch;
r += dir[0]; c += dir[1];
}
placements.push({ word, row, col, dir });
return true;
}
return false;
}
function fillRandom(size) {
for (let r = 0; r < size; r++) {
for (let c = 0; c < size; c++) {
if (!grid[r][c]) grid[r][c] = randLetter();
}
}
}
function renderGrid(size) {
gridEl.style.gridTemplateColumns = `repeat(${size}, 1fr)`;
gridEl.innerHTML = "";
for (let r = 0; r < size; r++) {
for (let c = 0; c < size; c++) {
const cell = document.createElement("div");
cell.className = "cell";
cell.textContent = grid[r][c];
cell.dataset.row = r;
cell.dataset.col = c;
gridEl.appendChild(cell);
}
}
}
/* ---------- Word List UI ---------- */
function updateWordListUI() {
wordListEl.innerHTML = "";
wordsToFind.forEach(w => {
const el = document.createElement("div");
el.className = "worditem";
el.textContent = w;
if (foundWords.has(w)) el.classList.add("done");
wordListEl.appendChild(el);
});
progressEl.textContent = `${foundWords.size} / ${wordsToFind.length}`;
}
/* ---------- Selection Handling ---------- */
function attachSelectionHandlers() {
gridEl.addEventListener("mousedown", e => {
if (!e.target.classList.contains("cell")) return;
selecting = true;
selectedCells = [e.target];
e.target.classList.add("sel");
});
gridEl.addEventListener("mouseover", e => {
if (!selecting || !e.target.classList.contains("cell")) return;
if (!selectedCells.includes(e.target)) {
selectedCells.push(e.target);
e.target.classList.add("sel");
}
});
document.addEventListener("mouseup", () => {
if (!selecting) return;
finalizeSelection();
selecting = false;
});
}
function finalizeSelection() {
const letters = selectedCells.map(c => c.textContent).join("");
const reverse = [...letters].reverse().join("");
const match = wordsToFind.find(w => w === letters || w === reverse);
if (match && !foundWords.has(match)) {
foundWords.add(match);
selectedCells.forEach(c => c.classList.add("found"));
updateWordListUI();
if (foundWords.size === wordsToFind.length) {
revealStory();
}
}
selectedCells.forEach(c => c.classList.remove("sel"));
selectedCells = [];
}
/* ---------- Story Popup ---------- */
function revealStory() {
overlay.classList.add("show");
storyTitle.textContent = currentStory.storyTitle;
storyText.textContent = currentStory.story;
}
function closeStory() {
overlay.classList.remove("show");
}
/* ---------- Hint Feature ---------- */
function giveHint() {
// Get words that are not yet found
const remaining = wordsToFind.filter(w => !foundWords.has(w));
if (remaining.length === 0) return;
const hintWord = remaining[Math.floor(Math.random() * remaining.length)];
const placement = placements.find(p => p.word === hintWord);
if (!placement) return;
// Highlight cells temporarily
let r = placement.row, c = placement.col;
const cells = [];
for (let ch of placement.word) {
const cell = [...gridEl.children].find(
el => parseInt(el.dataset.row) === r && parseInt(el.dataset.col) === c
);
if (cell) {
cell.classList.add("sel");
cells.push(cell);
}
r += placement.dir[0];
c += placement.dir[1];
}
// Remove highlight after 1 second
setTimeout(() => {
cells.forEach(cell => cell.classList.remove("sel"));
}, 1000);
}
/* ---------- Game Init ---------- */
let currentStory;
function generate() {
gridSize = parseInt(document.getElementById("sizeSelect").value) || 14;
const grade = parseInt(document.getElementById("gradeSelect").value);
const theme = document.getElementById("themeSelect").value;
const storyObj = data[theme]?.[grade];
if (!storyObj) {
alert("No story data for this grade & theme!");
return;
}
currentStory = storyObj;
wordsToFind = storyObj.words;
foundWords.clear();
placements = [];
makeEmptyGrid(gridSize);
wordsToFind.forEach(w => placeWord(w, gridSize));
fillRandom(gridSize);
renderGrid(gridSize);
updateWordListUI();
}
/* ---------- Button Events ---------- */
document.getElementById('newBtn').addEventListener('click', generate);
document.getElementById('hintBtn').addEventListener('click', giveHint);
document.getElementById('closeStory').addEventListener('click', closeStory);
document.getElementById('againBtn').addEventListener('click', () => { closeStory(); generate(); });
/* ---------- Init ---------- */
attachSelectionHandlers();
generate();

1011
styles.css Normal file

File diff suppressed because it is too large Load Diff