wordbuilder / index.html
AptlyDigital's picture
Update index.html
a041102 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Phonics Word Builder - Single Continuous Line</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary: #6a11cb;
--secondary: #2575fc;
--accent: #ff416c;
--success: #38ef7d;
--warning: #ffb347;
--light: #f8f9fa;
--dark: #212529;
--border-radius: 20px;
--shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
--transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
header {
text-align: center;
margin-bottom: 30px;
padding: 25px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: var(--border-radius);
box-shadow: var(--shadow);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.game-container {
display: grid;
grid-template-columns: 1fr 350px;
gap: 30px;
margin-bottom: 40px;
}
@media (max-width: 1024px) {
.game-container {
grid-template-columns: 1fr;
}
}
.main-game-area {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: var(--border-radius);
padding: 40px;
box-shadow: var(--shadow);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.word-display {
text-align: center;
margin-bottom: 50px;
padding: 30px;
background: rgba(0, 0, 0, 0.2);
border-radius: var(--border-radius);
}
.target-word {
font-size: 4rem;
font-weight: bold;
margin-bottom: 20px;
font-family: 'Comic Sans MS', 'Chalkboard SE', cursive;
text-shadow: 0 5px 15px rgba(0,0,0,0.3);
line-height: 1.5;
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
gap: 10px;
}
.word-part {
font-size: 4rem;
display: inline-block;
}
.continuous-blank {
display: inline-block;
min-width: 180px;
height: 70px;
margin: 0 10px;
position: relative;
vertical-align: middle;
}
.blank-line {
position: absolute;
top: 50%;
left: 0;
right: 0;
height: 8px;
background: linear-gradient(90deg, #ffd166, #ff9a00);
border-radius: 4px;
transform: translateY(-50%);
box-shadow: 0 4px 10px rgba(0,0,0,0.2);
}
.blank-line::after {
content: '';
position: absolute;
top: -5px;
bottom: -5px;
left: 0;
right: 0;
background: rgba(255, 209, 102, 0.1);
border-radius: 8px;
}
.pattern-options {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 20px;
margin-bottom: 40px;
}
.pattern-option {
background: rgba(255, 255, 255, 0.2);
padding: 25px;
border-radius: 15px;
text-align: center;
font-size: 2rem;
font-weight: bold;
cursor: pointer;
transition: var(--transition);
border: 2px solid transparent;
font-family: 'Comic Sans MS', 'Chalkboard SE', cursive;
}
.pattern-option:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-5px);
border-color: rgba(255, 255, 255, 0.5);
}
.pattern-option.selected {
background: var(--success);
color: white;
transform: scale(1.05);
border-color: white;
}
.pattern-option.wrong {
background: var(--accent);
color: white;
}
.game-controls {
display: flex;
justify-content: center;
gap: 20px;
margin-top: 30px;
flex-wrap: wrap;
}
.btn {
padding: 15px 30px;
border: none;
border-radius: 50px;
font-weight: bold;
font-size: 1.1rem;
cursor: pointer;
transition: var(--transition);
display: flex;
align-items: center;
gap: 10px;
background: white;
color: var(--dark);
}
.btn-primary {
background: linear-gradient(90deg, var(--success), #2ecc71);
color: white;
}
.btn-secondary {
background: rgba(255, 255, 255, 0.2);
color: white;
backdrop-filter: blur(10px);
}
.feedback {
text-align: center;
padding: 20px;
margin: 20px 0;
border-radius: var(--border-radius);
font-size: 1.2rem;
font-weight: bold;
display: none;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.feedback.correct {
background: rgba(56, 239, 125, 0.2);
border: 2px solid var(--success);
color: var(--success);
}
.feedback.incorrect {
background: rgba(255, 65, 108, 0.2);
border: 2px solid var(--accent);
color: var(--accent);
}
.sidebar {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: var(--border-radius);
padding: 25px;
box-shadow: var(--shadow);
border: 1px solid rgba(255, 255, 255, 0.2);
display: flex;
flex-direction: column;
}
.stat-box {
background: rgba(255, 255, 255, 0.15);
padding: 20px;
border-radius: 15px;
text-align: center;
margin-bottom: 25px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.stat-value {
font-size: 2.5rem;
font-weight: bold;
color: white;
text-shadow: 0 2px 10px rgba(0,0,0,0.2);
}
.stat-label {
font-size: 0.9rem;
opacity: 0.9;
}
.progress-bar {
height: 10px;
background: rgba(255, 255, 255, 0.2);
border-radius: 5px;
overflow: hidden;
margin: 15px 0;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--success), #2ecc71);
width: 0%;
transition: width 0.5s ease;
}
.history-container {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.history-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.history-list {
flex: 1;
overflow-y: auto;
background: rgba(0, 0, 0, 0.1);
border-radius: 10px;
padding: 15px;
}
.history-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 15px;
margin-bottom: 8px;
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
font-size: 0.9rem;
border-left: 4px solid transparent;
}
.history-item.correct {
border-left-color: var(--success);
}
.history-item.incorrect {
border-left-color: var(--accent);
}
.history-word {
font-weight: bold;
max-width: 100px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.history-pattern {
font-family: 'Comic Sans MS', cursive;
}
.history-result {
display: flex;
align-items: center;
gap: 5px;
}
.completed-word {
font-size: 1.5rem;
color: var(--success);
margin-top: 10px;
font-weight: bold;
}
.timer {
font-size: 1.5rem;
color: #ffd166;
font-weight: bold;
text-align: center;
margin-top: 20px;
}
.streak-display {
background: linear-gradient(45deg, #ff9a00, #ff0080);
padding: 10px 20px;
border-radius: 50px;
display: inline-flex;
align-items: center;
gap: 10px;
font-weight: bold;
margin: 10px auto;
}
.clear-history-btn {
padding: 8px 15px;
background: rgba(255, 65, 108, 0.2);
border: 1px solid rgba(255, 65, 108, 0.5);
border-radius: 20px;
color: white;
cursor: pointer;
font-size: 0.8rem;
transition: var(--transition);
}
.clear-history-btn:hover {
background: rgba(255, 65, 108, 0.4);
}
.export-btn {
padding: 8px 15px;
background: rgba(56, 239, 125, 0.2);
border: 1px solid rgba(56, 239, 125, 0.5);
border-radius: 20px;
color: white;
cursor: pointer;
font-size: 0.8rem;
transition: var(--transition);
margin-left: 10px;
}
.export-btn:hover {
background: rgba(56, 239, 125, 0.4);
}
.stats-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
margin-top: 20px;
}
.mini-stat {
background: rgba(0, 0, 0, 0.1);
padding: 15px;
border-radius: 10px;
text-align: center;
}
.mini-value {
font-size: 1.8rem;
font-weight: bold;
color: #ffd166;
}
.mini-label {
font-size: 0.8rem;
opacity: 0.8;
}
.word-hint {
font-size: 1.2rem;
margin-top: 15px;
opacity: 0.8;
font-style: italic;
}
.pattern-categories {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
justify-content: center;
}
.category-tag {
padding: 10px 20px;
background: rgba(255, 255, 255, 0.15);
border-radius: 50px;
cursor: pointer;
transition: var(--transition);
font-weight: 500;
}
.category-tag.active {
background: rgba(255, 255, 255, 0.3);
font-weight: bold;
border: 2px solid white;
}
.category-tag:hover {
background: rgba(255, 255, 255, 0.25);
}
</style>
</head>
<body>
<div class="container">
<header>
<h1><i class="fas fa-puzzle-piece"></i> Phonics Word Builder</h1>
<p>Complete the word by selecting the missing phonics pattern</p>
</header>
<div class="pattern-categories" id="patternCategories">
<!-- Category filters will be added here -->
</div>
<div class="game-container">
<div class="main-game-area">
<div class="word-display">
<div class="target-word" id="wordDisplay">
<!-- Word parts and continuous blank line will appear here -->
</div>
<div class="word-hint" id="wordHint">
Find the phonics pattern that completes this word
</div>
<div class="completed-word" id="completedWord"></div>
<div class="timer" id="timer">Time: 30s</div>
</div>
<div class="feedback" id="feedback"></div>
<div class="pattern-options" id="patternOptions">
<!-- Pattern options will appear here -->
</div>
<div class="game-controls">
<button class="btn btn-primary" id="submitBtn">
<i class="fas fa-check-circle"></i> Submit Answer
</button>
<button class="btn btn-secondary" id="nextBtn">
<i class="fas fa-forward"></i> Next Word
</button>
<button class="btn btn-secondary" id="hintBtn">
<i class="fas fa-lightbulb"></i> Show Hint
</button>
<button class="btn btn-secondary" id="resetBtn">
<i class="fas fa-redo"></i> New Game
</button>
</div>
</div>
<div class="sidebar">
<div class="stat-box">
<div class="stat-value" id="score">0</div>
<div class="stat-label">Score</div>
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
</div>
<div id="progressText">0% Complete</div>
</div>
<div class="stats-grid">
<div class="mini-stat">
<div class="mini-value" id="streak">0</div>
<div class="mini-label">Current Streak</div>
</div>
<div class="mini-stat">
<div class="mini-value" id="correctCount">0</div>
<div class="mini-label">Correct</div>
</div>
<div class="mini-stat">
<div class="mini-value" id="totalQuestions">0</div>
<div class="mini-label">Attempted</div>
</div>
<div class="mini-stat">
<div class="mini-value" id="accuracy">0%</div>
<div class="mini-label">Accuracy</div>
</div>
</div>
<div class="history-container">
<div class="history-header">
<h3><i class="fas fa-history"></i> Attempt History</h3>
<div>
<button class="clear-history-btn" id="clearHistoryBtn">Clear</button>
<button class="export-btn" id="exportBtn">Export</button>
</div>
</div>
<div class="history-list" id="historyList">
<!-- Word history will appear here -->
</div>
<div style="text-align: center; margin-top: 10px; font-size: 0.8rem; opacity: 0.7;">
<span id="historyCount">0</span> attempts recorded
</div>
</div>
</div>
</div>
</div>
<script>
// Word Bank - Updated with more words for each pattern
const wordBuilderData = [
// Consonant Digraphs (15 patterns)
{ pattern: "sh", pronunciation: "shh", category: "Consonant", words: ["ship", "wish", "shell", "fish", "brush", "crash", "flash", "trash", "splash", "shout"] },
{ pattern: "ch", pronunciation: "ch", category: "Consonant", words: ["chat", "chip", "lunch", "bench", "chest", "chop", "chick", "church", "cheer", "chase"] },
{ pattern: "th", pronunciation: "th (soft)", category: "Consonant", words: ["thin", "think", "bath", "math", "path", "thick", "thumb", "thunder", "thirsty", "thirteen"] },
{ pattern: "th", pronunciation: "th (hard)", category: "Consonant", words: ["this", "that", "mother", "father", "brother", "weather", "feather", "leather", "together", "other"] },
{ pattern: "wh", pronunciation: "wh", category: "Consonant", words: ["when", "whale", "whisper", "wheel", "whistle", "white", "what", "where", "why", "which"] },
{ pattern: "ph", pronunciation: "f", category: "Consonant", words: ["phone", "graph", "elephant", "alphabet", "photo", "phrase", "sphere", "dolphin", "orphan", "phantom"] },
{ pattern: "ng", pronunciation: "ng", category: "Consonant", words: ["song", "king", "ring", "long", "strong", "thing", "spring", "string", "wing", "sing"] },
{ pattern: "ck", pronunciation: "k", category: "Consonant", words: ["duck", "clock", "sock", "black", "stick", "chicken", "pocket", "rocket", "truck", "slick"] },
{ pattern: "tch", pronunciation: "ch", category: "Consonant", words: ["catch", "watch", "patch", "match", "hatch", "scratch", "stitch", "ditch", "witch", "kitchen"] },
{ pattern: "dge", pronunciation: "j", category: "Consonant", words: ["bridge", "fudge", "badge", "edge", "judge", "ledge", "dodge", "hedge", "pledge", "ridge"] },
{ pattern: "gh", pronunciation: "f", category: "Consonant", words: ["enough", "laugh", "rough", "tough", "cough", "trough", "laughter", "roughly", "toughen", "coughed"] },
{ pattern: "gn", pronunciation: "n", category: "Consonant", words: ["gnaw", "sign", "gnome", "design", "align", "foreign", "reign", "campaign", "gnat", "gnarl"] },
{ pattern: "kn", pronunciation: "n", category: "Consonant", words: ["knee", "know", "knock", "knife", "knot", "knit", "knight", "knob", "knowledge", "knock"] },
{ pattern: "wr", pronunciation: "r", category: "Consonant", words: ["write", "wrong", "wrap", "wrist", "wreath", "wreck", "wrench", "wrinkle", "wrote", "wrestle"] },
{ pattern: "mb", pronunciation: "m", category: "Consonant", words: ["comb", "thumb", "climb", "lamb", "dumb", "numb", "plumber", "crumb", "tomb", "womb"] },
// Vowel Teams (30 patterns)
{ pattern: "ai", pronunciation: "ay", category: "Vowels", words: ["rain", "train", "wait", "mail", "pain", "brain", "chain", "sail", "tail", "frail"] },
{ pattern: "ay", pronunciation: "ay", category: "Vowels", words: ["day", "play", "say", "way", "may", "pay", "stay", "gray", "clay", "spray"] },
{ pattern: "ee", pronunciation: "ee", category: "Vowels", words: ["tree", "see", "sleep", "bee", "feet", "green", "sheep", "three", "queen", "sweet"] },
{ pattern: "ea", pronunciation: "ee", category: "Vowels", words: ["eat", "sea", "team", "leaf", "meal", "seat", "beach", "peach", "teach", "cheap"] },
{ pattern: "ie", pronunciation: "ee", category: "Vowels", words: ["piece", "chief", "field", "believe", "grief", "relief", "thief", "yield", "shield", "niece"] },
{ pattern: "y", pronunciation: "ee", category: "Vowels", words: ["happy", "baby", "funny", "sunny", "candy", "city", "puppy", "lady", "penny", "cherry"] },
{ pattern: "y", pronunciation: "eye", category: "Vowels", words: ["cry", "fly", "my", "dry", "sky", "try", "why", "shy", "spy", "ply"] },
{ pattern: "igh", pronunciation: "eye", category: "Vowels", words: ["night", "light", "high", "right", "fight", "bright", "flight", "sight", "tight", "might"] },
{ pattern: "oa", pronunciation: "oh", category: "Vowels", words: ["boat", "coat", "road", "goat", "soap", "toast", "float", "throat", "coach", "roast"] },
{ pattern: "oe", pronunciation: "oh", category: "Vowels", words: ["toe", "hoe", "foe", "doe", "woe", "aloe", "canoe", "oboe", "roe", "shoe"] },
{ pattern: "ow", pronunciation: "oh", category: "Vowels", words: ["snow", "grow", "show", "bow", "row", "blow", "flow", "slow", "throw", "arrow"] },
{ pattern: "ou", pronunciation: "ow", category: "Vowels", words: ["out", "cloud", "found", "house", "mouse", "shout", "proud", "round", "sound", "count"] },
{ pattern: "ow", pronunciation: "ow", category: "Vowels", words: ["cow", "town", "down", "how", "now", "brown", "crowd", "flower", "power", "tower"] },
{ pattern: "oi", pronunciation: "oy", category: "Vowels", words: ["coin", "boil", "soil", "oil", "noise", "voice", "choice", "spoil", "join", "point"] },
{ pattern: "oy", pronunciation: "oy", category: "Vowels", words: ["toy", "boy", "enjoy", "joy", "soy", "annoy", "destroy", "employ", "loyal", "royal"] },
{ pattern: "oo", pronunciation: "oo (long)", category: "Vowels", words: ["moon", "spoon", "tooth", "food", "room", "boot", "root", "pool", "school", "cool"] },
{ pattern: "oo", pronunciation: "oo (short)", category: "Vowels", words: ["book", "look", "foot", "good", "hood", "wood", "stood", "cook", "hook", "shook"] },
{ pattern: "ew", pronunciation: "yoo", category: "Vowels", words: ["few", "dew", "new", "chew", "news", "jewel", "nephew", "stew", "view", "review"] },
{ pattern: "ue", pronunciation: "yoo", category: "Vowels", words: ["blue", "true", "clue", "glue", "value", "rescue", "argue", "venue", "statue", "virtue"] },
{ pattern: "au", pronunciation: "aw", category: "Vowels", words: ["haul", "autumn", "launch", "cause", "pause", "audio", "author", "sauce", "haunt", "fault"] },
{ pattern: "aw", pronunciation: "aw", category: "Vowels", words: ["saw", "draw", "claw", "law", "paw", "straw", "jaw", "lawn", "dawn", "yawn"] },
{ pattern: "ei", pronunciation: "ay", category: "Vowels", words: ["veil", "eight", "weigh", "neighbor", "reign", "freight", "vein", "beige", "sleigh", "rein"] },
{ pattern: "ey", pronunciation: "ay", category: "Vowels", words: ["they", "prey", "grey", "convey", "survey", "obey", "disobey", "hey", "whey", "abeyance"] },
{ pattern: "ei", pronunciation: "ee", category: "Vowels", words: ["receive", "ceiling", "deceive", "perceive", "conceive", "receipt", "seize", "leisure", "weird", "either"] },
{ pattern: "ey", pronunciation: "ee", category: "Vowels", words: ["key", "monkey", "honey", "money", "donkey", "turkey", "valley", "journey", "chimney", "kidney"] },
{ pattern: "ie", pronunciation: "eye", category: "Vowels", words: ["pie", "tie", "die", "lie", "fried", "cried", "tried", "dried", "spied", "supplied"] },
{ pattern: "ea", pronunciation: "eh", category: "Vowels", words: ["bread", "head", "ready", "heavy", "weather", "feather", "leather", "sweater", "treasure", "pleasure"] },
{ pattern: "ou", pronunciation: "oo", category: "Vowels", words: ["soup", "group", "youth", "coupon", "routine", "souvenir", "you", "through", "wound", "route"] },
{ pattern: "ui", pronunciation: "oo", category: "Vowels", words: ["fruit", "juice", "suit", "bruise", "cruise", "suitcase", "recruit", "biscuit", "circuit", "pursuit"] },
{ pattern: "ew", pronunciation: "oo", category: "Vowels", words: ["blew", "grew", "flew", "crew", "chew", "drew", "threw", "screw", "brew", "jewel"] },
// Word Endings (15 patterns)
{ pattern: "tion", pronunciation: "shun", category: "Endings", words: ["action", "station", "nation", "education", "vacation", "celebration", "information", "communication", "population", "attention"] },
{ pattern: "sion", pronunciation: "zhun", category: "Endings", words: ["vision", "decision", "confusion", "television", "division", "conclusion", "explosion", "invasion", "occasion", "persuasion"] },
{ pattern: "sion", pronunciation: "shun", category: "Endings", words: ["mission", "passion", "session", "expression", "discussion", "permission", "profession", "impression", "depression", "compression"] },
{ pattern: "cian", pronunciation: "shun", category: "Endings", words: ["musician", "magician", "physician", "politician", "electrician", "mathematician", "technician", "beautician", "dietician", "statistician"] },
{ pattern: "ture", pronunciation: "chur", category: "Endings", words: ["picture", "future", "nature", "creature", "adventure", "furniture", "temperature", "signature", "mixture", "literature"] },
{ pattern: "le", pronunciation: "ul", category: "Endings", words: ["table", "candle", "little", "apple", "bubble", "puzzle", "turtle", "castle", "bottle", "middle"] },
{ pattern: "ous", pronunciation: "us", category: "Endings", words: ["famous", "nervous", "dangerous", "enormous", "generous", "humorous", "mysterious", "numerous", "precious", "serious"] },
{ pattern: "cious", pronunciation: "shus", category: "Endings", words: ["delicious", "suspicious", "precious", "conscious", "vicious", "gracious", "spacious", "judicious", "malicious", "auspicious"] },
{ pattern: "tious", pronunciation: "shus", category: "Endings", words: ["ambitious", "cautious", "nutritious", "fictitious", "infectious", "superstitious", "contentious", "ostentatious", "pretentious", "conscientious"] },
{ pattern: "cial", pronunciation: "shul", category: "Endings", words: ["special", "social", "official", "commercial", "financial", "racial", "crucial", "judicial", "beneficial", "superficial"] },
{ pattern: "tial", pronunciation: "shul", category: "Endings", words: ["partial", "essential", "initial", "potential", "residential", "confidential", "substantial", "sequential", "influential", "presidential"] },
{ pattern: "age", pronunciation: "ij", category: "Endings", words: ["cage", "page", "stage", "message", "village", "damage", "manage", "package", "passage", "voyage"] },
{ pattern: "age", pronunciation: "azh", category: "Endings", words: ["garage", "massage", "mirage", "corsage", "camouflage", "espionage", "collage", "montage", "sabotage", "badinage"] },
{ pattern: "ine", pronunciation: "in", category: "Endings", words: ["engine", "imagine", "medicine", "discipline", "determine", "famine", "routine", "doctrine", "genuine", "vaccine"] },
{ pattern: "ine", pronunciation: "ine", category: "Endings", words: ["pine", "fine", "line", "mine", "nine", "vine", "wine", "shine", "whine", "divine"] }
];
// Game state
let currentPattern = null;
let currentWord = "";
let selectedOption = null;
let score = 0;
let streak = 0;
let correctCount = 0;
let totalQuestions = 0;
let patternsMastered = new Set();
let timer = 30;
let timerInterval = null;
let gameActive = false;
let wordHistory = [];
let currentCategory = "all";
// DOM Elements
const wordDisplay = document.getElementById('wordDisplay');
const wordHint = document.getElementById('wordHint');
const patternOptions = document.getElementById('patternOptions');
const feedback = document.getElementById('feedback');
const submitBtn = document.getElementById('submitBtn');
const nextBtn = document.getElementById('nextBtn');
const hintBtn = document.getElementById('hintBtn');
const resetBtn = document.getElementById('resetBtn');
const clearHistoryBtn = document.getElementById('clearHistoryBtn');
const exportBtn = document.getElementById('exportBtn');
const scoreEl = document.getElementById('score');
const streakEl = document.getElementById('streak');
const correctCountEl = document.getElementById('correctCount');
const totalQuestionsEl = document.getElementById('totalQuestions');
const accuracyEl = document.getElementById('accuracy');
const progressFill = document.getElementById('progressFill');
const progressText = document.getElementById('progressText');
const historyList = document.getElementById('historyList');
const historyCount = document.getElementById('historyCount');
const timerEl = document.getElementById('timer');
const completedWord = document.getElementById('completedWord');
const patternCategories = document.getElementById('patternCategories');
// Initialize game
function initGame() {
loadGameState();
setupEventListeners();
renderCategoryFilters();
generateNewWord();
updateStats();
updateHistory();
}
// Load game state from localStorage
function loadGameState() {
const savedState = JSON.parse(localStorage.getItem('wordBuilderState'));
if (savedState) {
score = savedState.score || 0;
streak = savedState.streak || 0;
correctCount = savedState.correctCount || 0;
totalQuestions = savedState.totalQuestions || 0;
patternsMastered = new Set(savedState.patternsMastered || []);
wordHistory = savedState.wordHistory || [];
currentCategory = savedState.currentCategory || "all";
}
}
// Save game state to localStorage
function saveGameState() {
const state = {
score,
streak,
correctCount,
totalQuestions,
patternsMastered: Array.from(patternsMastered),
wordHistory,
currentCategory,
timestamp: Date.now()
};
localStorage.setItem('wordBuilderState', JSON.stringify(state));
}
// Render category filters
function renderCategoryFilters() {
const categories = ['all', 'Consonant', 'Vowels', 'Endings'];
patternCategories.innerHTML = '';
categories.forEach(category => {
const count = category === 'all'
? wordBuilderData.length
: wordBuilderData.filter(p => p.category === category).length;
const tag = document.createElement('div');
tag.className = `category-tag ${currentCategory === category ? 'active' : ''}`;
tag.textContent = `${category} (${count})`;
tag.dataset.category = category;
tag.addEventListener('click', () => {
currentCategory = category;
renderCategoryFilters();
generateNewWord();
saveGameState();
});
patternCategories.appendChild(tag);
});
}
// Setup event listeners
function setupEventListeners() {
submitBtn.addEventListener('click', checkAnswer);
nextBtn.addEventListener('click', generateNewWord);
hintBtn.addEventListener('click', showHint);
resetBtn.addEventListener('click', resetGame);
clearHistoryBtn.addEventListener('click', clearHistory);
exportBtn.addEventListener('click', exportHistory);
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.key >= '1' && e.key <= '8') {
const index = parseInt(e.key) - 1;
const options = document.querySelectorAll('.pattern-option');
if (options[index]) {
options[index].click();
}
} else if (e.key === 'Enter') {
if (gameActive) {
checkAnswer();
} else {
generateNewWord();
}
} else if (e.key === ' ') {
e.preventDefault();
if (!gameActive) {
generateNewWord();
}
}
});
}
// Generate a new word with continuous blank line
function generateNewWord() {
// Stop any existing timer
if (timerInterval) clearInterval(timerInterval);
// Reset UI
feedback.style.display = 'none';
selectedOption = null;
completedWord.textContent = '';
// Filter patterns by category
let filteredPatterns = wordBuilderData;
if (currentCategory !== 'all') {
filteredPatterns = wordBuilderData.filter(p => p.category === currentCategory);
}
// Select random pattern
const randomIndex = Math.floor(Math.random() * filteredPatterns.length);
currentPattern = filteredPatterns[randomIndex];
// Select random word from pattern
const wordIndex = Math.floor(Math.random() * currentPattern.words.length);
currentWord = currentPattern.words[wordIndex];
// Find pattern in word (case insensitive)
const patternIndex = currentWord.toLowerCase().indexOf(currentPattern.pattern.toLowerCase());
if (patternIndex !== -1) {
const before = currentWord.substring(0, patternIndex);
const after = currentWord.substring(patternIndex + currentPattern.pattern.length);
// Display with continuous blank line
wordDisplay.innerHTML = `
${before ? `<span class="word-part">${before}</span>` : ''}
<div class="continuous-blank">
<div class="blank-line"></div>
</div>
${after ? `<span class="word-part">${after}</span>` : ''}
`;
// Update hint
wordHint.textContent = `Category: ${currentPattern.category} | Sound: "${currentPattern.pronunciation}"`;
} else {
// Fallback: show entire word as blank (shouldn't happen)
wordDisplay.innerHTML = `
<div class="continuous-blank" style="min-width: 250px;">
<div class="blank-line"></div>
</div>
`;
wordHint.textContent = "Find the phonics pattern that completes this word";
}
// Generate options
generateOptions();
// Start timer
timer = 30;
timerEl.textContent = `Time: ${timer}s`;
timerEl.style.color = "#ffd166";
gameActive = true;
timerInterval = setInterval(() => {
timer--;
timerEl.textContent = `Time: ${timer}s`;
if (timer <= 0) {
clearInterval(timerInterval);
gameActive = false;
feedback.textContent = "Time's up! The answer was: " + currentPattern.pattern;
feedback.className = "feedback incorrect";
feedback.style.display = "block";
addToHistory(false, false);
}
if (timer <= 10) {
timerEl.style.color = "#ff416c";
} else if (timer <= 20) {
timerEl.style.color = "#ffb347";
}
}, 1000);
// Update UI
patternOptions.querySelectorAll('.pattern-option').forEach(opt => {
opt.classList.remove('selected', 'wrong');
});
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="fas fa-check-circle"></i> Submit Answer';
}
// Generate pattern options
function generateOptions() {
// Clear existing options
patternOptions.innerHTML = '';
const options = [currentPattern.pattern];
// Get other patterns for wrong options
let allPatterns = [...new Set(wordBuilderData.map(p => p.pattern))];
// Filter by category if needed
if (currentCategory !== 'all') {
allPatterns = [...new Set(wordBuilderData
.filter(p => p.category === currentCategory)
.map(p => p.pattern))];
}
const wrongPatterns = allPatterns.filter(p => p !== currentPattern.pattern);
// Shuffle and select 7 wrong options (8 total)
shuffleArray(wrongPatterns);
for (let i = 0; i < 7; i++) {
if (wrongPatterns[i]) {
options.push(wrongPatterns[i]);
}
}
// Shuffle options
shuffleArray(options);
// Create option buttons
options.forEach((pattern, index) => {
const option = document.createElement('div');
option.className = 'pattern-option';
option.textContent = pattern;
option.dataset.pattern = pattern;
option.title = `Option ${index + 1} (Press ${index + 1})`;
option.addEventListener('click', () => {
if (!gameActive) return;
// Deselect all options
patternOptions.querySelectorAll('.pattern-option').forEach(opt => {
opt.classList.remove('selected');
});
// Select clicked option
option.classList.add('selected');
selectedOption = pattern;
});
patternOptions.appendChild(option);
});
}
// Check answer
function checkAnswer() {
if (!selectedOption || !gameActive) return;
clearInterval(timerInterval);
gameActive = false;
const isCorrect = selectedOption === currentPattern.pattern;
const timeBonus = Math.floor(timer / 5) * 10;
// Update score and streak
if (isCorrect) {
score += 10 + timeBonus;
streak++;
correctCount++;
patternsMastered.add(currentPattern.pattern);
feedback.textContent = `Correct! +${10 + timeBonus} points (${timeBonus} time bonus)`;
feedback.className = "feedback correct";
completedWord.textContent = `Complete word: ${currentWord}`;
} else {
streak = 0;
feedback.textContent = `Incorrect. The answer was: ${currentPattern.pattern}`;
feedback.className = "feedback incorrect";
// Highlight correct option
patternOptions.querySelectorAll('.pattern-option').forEach(opt => {
if (opt.dataset.pattern === currentPattern.pattern) {
opt.classList.add('selected');
} else if (opt.dataset.pattern === selectedOption) {
opt.classList.add('wrong');
}
});
}
totalQuestions++;
// Add to history
addToHistory(isCorrect, true);
// Update stats and save
updateStats();
saveGameState();
// Disable submit button
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fas fa-check"></i> Answered';
}
// Show hint
function showHint() {
// Already showing hint in wordHint
// Could add more specific hint here
if (currentPattern) {
const examples = currentPattern.words.slice(0, 3).join(", ");
alert(`Hint: The pattern "${currentPattern.pattern}" makes the sound "${currentPattern.pronunciation}"\n\nExamples: ${examples}`);
}
}
// Reset game
function resetGame() {
if (confirm("Start a new game? Your progress will be saved, but current streak will reset.")) {
streak = 0;
generateNewWord();
updateStats();
saveGameState();
}
}
// Add word to history
function addToHistory(isCorrect, attempted) {
const historyItem = {
word: currentWord,
pattern: currentPattern.pattern,
correct: isCorrect,
attempted: attempted, // false for timeout
timestamp: new Date().toISOString(),
time: new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}),
date: new Date().toLocaleDateString(),
category: currentPattern.category
};
wordHistory.unshift(historyItem);
// Keep only last 100 items
if (wordHistory.length > 100) {
wordHistory = wordHistory.slice(0, 100);
}
updateHistory();
}
// Update history display
function updateHistory() {
historyList.innerHTML = '';
if (wordHistory.length === 0) {
historyList.innerHTML = '<div style="text-align: center; padding: 20px; opacity: 0.7;">No attempts yet</div>';
historyCount.textContent = '0';
return;
}
wordHistory.forEach(item => {
const div = document.createElement('div');
div.className = `history-item ${item.correct ? 'correct' : 'incorrect'}`;
let statusIcon = item.correct ? '✓' : '✗';
let statusText = item.correct ? 'Correct' : 'Incorrect';
if (!item.attempted) {
statusIcon = '⏰';
statusText = 'Timeout';
}
div.innerHTML = `
<div class="history-word" title="${item.word}">${item.word}</div>
<div class="history-pattern">${item.pattern}</div>
<div class="history-result">
<span>${statusIcon}</span>
<span>${item.time}</span>
</div>
`;
div.title = `${item.word} - ${item.pattern} - ${item.category} - ${statusText} at ${item.time}`;
historyList.appendChild(div);
});
historyCount.textContent = wordHistory.length;
}
// Clear history
function clearHistory() {
if (confirm("Clear all attempt history? This cannot be undone.")) {
wordHistory = [];
updateHistory();
saveGameState();
}
}
// Export history
function exportHistory() {
if (wordHistory.length === 0) {
alert("No history to export.");
return;
}
let csvContent = "Word,Pattern,Category,Result,Date,Time\n";
wordHistory.forEach(item => {
const result = item.correct ? 'Correct' : (item.attempted ? 'Incorrect' : 'Timeout');
csvContent += `"${item.word}","${item.pattern}","${item.category}",${result},"${item.date}","${item.time}"\n`;
});
const blob = new Blob([csvContent], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `phonics-history-${new Date().toISOString().slice(0,10)}.csv`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
alert(`Exported ${wordHistory.length} attempts to CSV file.`);
}
// Update all stats
function updateStats() {
scoreEl.textContent = score;
streakEl.textContent = streak;
correctCountEl.textContent = correctCount;
totalQuestionsEl.textContent = totalQuestions;
const accuracy = totalQuestions > 0 ? Math.round((correctCount / totalQuestions) * 100) : 0;
accuracyEl.textContent = `${accuracy}%`;
const totalPatterns = currentCategory === 'all'
? wordBuilderData.length
: wordBuilderData.filter(p => p.category === currentCategory).length;
const progress = totalPatterns > 0 ? (patternsMastered.size / totalPatterns) * 100 : 0;
progressFill.style.width = `${progress}%`;
progressText.textContent = `${patternsMastered.size}/${totalPatterns} patterns (${Math.round(progress)}%)`;
}
// Utility: Shuffle array
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
// Initialize when page loads
document.addEventListener('DOMContentLoaded', initGame);
</script>
</body>
</html>