Spaces:
Running
Running
| <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> |