bunch of fixes

This commit is contained in:
nate@necloud.us committed 2025-07-15 10:16:59 -04:00
1 parent 462c7908a1
commit 266a064330
14 files changed
+1211 -868

No files matched your search

+297 -193
View File
@@ -1,200 +1,304 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Spelling App with Roles</title>
<link href="styles.css" rel="stylesheet"/>
<!-- 🔧 Script Load Order -->
<script defer="" src="https://cdnjs.cloudflare.com/ajax/libs/jsSHA/2.4.2/sha.js"></script>
<script defer="" src="js/auth.js"></script>
<script defer="" src="js/ui.js"></script>
<script defer="" src="js/themes.js"></script>
<script src="js/game.js"></script>
<script defer="" src="js/admin.js"></script>
<script defer="" src="js/main.js"></script>
<script defer="" src="js/typing.js"></script>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>✨ Modern Spelling Practice</title>
<link href="styles.css" rel="stylesheet"/>
<!-- Scripts -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsSHA/2.4.2/sha.js"></script>
<script src="js/admin.js"></script>
<script src="js/auth.js"></script>
<script src="js/ui.js"></script>
<script src="js/themes.js"></script>
<script src="js/game.js"></script>
<script src="js/typing.js"></script>
<script src="js/main.js"></script>
</head>
<body>
<h1>Spelling Practice</h1>
<!-- 🏳️ Role Banner -->
<div class="banner hidden" id="roleBanner"></div>
<!-- 👤 Login Panel -->
<div id="loginPanel">
<h2>User Login</h2>
<form onsubmit="event.preventDefault(); verifyLogin();">
<input id="username" placeholder="Username" required="" type="text"/>
<input id="password" placeholder="Password" required="" type="password"/>
<button type="submit">Login</button>
</form>
<p id="loginMessage"></p>
</div>
<!-- 🔀 Role-Based Navigation -->
<div class="hidden" id="nav">
<p><strong id="userInfo"></strong></p>
<button class="hidden" id="adminBtn" onclick="showAdmin()">Admin Panel</button>
<button class="hidden" id="studentBtn" onclick="showStudent()">Student Panel</button>
<button id="logoutBtn" onclick="logoutUser()">Log Out</button>
</div>
<!-- 👨‍🏫 Admin Panel -->
<div class="hidden" id="adminPanel">
<h2>👨‍🏫 Admin Panel</h2>
<!-- 🔘 Tabs -->
<div id="adminTabs">
<button class="active" onclick="switchTab('tabUsers')">👤 Manage Users</button>
<button onclick="switchTab('tabWords')">📝 Word Lists</button>
<button onclick="switchTab('tabAnalytics')">📊 Analytics</button>
<button onclick="switchTab('tabResults')">📊 Results</button>
<button onclick="switchTab('tabReports')">📘 Reports</button>
<button onclick="switchTab('tabSessions')">📆 Sessions</button>
<button onclick="switchTab('tabBadges')">🎖 Badges</button>
<button onclick="switchTab('tabTheme')">🎨 Theme</button>
<button onclick="switchTab('tabTyping')">⌨️ Typing Practice</button>
</div>
<!-- 👥 Users Tab -->
<div class="adminTab" id="tabUsers">
<h3>Add New User</h3>
<form onsubmit="event.preventDefault(); addNewUser();">
<input id="newUsername" placeholder="Username" required="" type="text"/>
<input id="newPassword" placeholder="Password" required="" type="password"/>
<select id="newRole">
<option value="student">Student</option>
<option value="admin">Admin</option>
</select>
<button type="submit">Add User</button>
</form>
<h3>Delete Users</h3>
<select id="userDropdown" onchange="selectUserAction(this.value)">
<option value="">-- Select a user --</option>
</select>
</div>
<!-- 📝 Word List Tab -->
<div class="adminTab hidden" id="tabWords">
<h3>Edit Word List for:</h3>
<select class="word-user-dropdown" id="wordUserSelect" onchange="loadWordsForSelectedUser()">
<option value="">-- Select a user --</option>
</select>
<h2>Enter Spelling Words</h2>
<textarea id="wordInput" placeholder="Enter one word per line..."></textarea><br/>
<button onclick="saveWords()">Save Word List</button>
<button onclick="clearWords()">Clear Saved Words</button>
<h3>Upload a Word File</h3>
<input accept=".txt,.csv" onchange="handleFileUpload(event)" type="file"/>
<p><strong>Current Word List:</strong></p>
<ul id="wordListDisplay"></ul>
<h3>📋 View Word List by User:</h3>
<select class="word-user-dropdown" id="viewUserSelect" onchange="displayWordListForSelectedUser()">
<option value="">-- Select a user --</option>
</select>
<ul id="userWordListDisplay"></ul>
</div>
<!-- 📊 Results Tab -->
<div class="adminTab hidden" id="tabResults">
<h3>🧾 Student Results Overview</h3>
<button onclick="loadStudentResults()">Refresh Results</button>
<ul id="resultsList"></ul>
</div>
<!-- 📊 Analytics Tab -->
<div class="adminTab hidden" id="tabAnalytics">
<h3>Smart Analytics Overview</h3>
<ul id="analyticsSummary">
<li><strong>Average Score:</strong> –</li>
<li><strong>Total Completed Sessions:</strong> –</li>
<li><strong>Most Missed Word:</strong> –</li>
</ul>
<h4>Per-Student Breakdown</h4>
<ul id="studentBreakdown"></ul>
<button onclick="refreshAnalytics()">🔄 Refresh</button>
</div>
<!-- 🎖 Badges -->
<div class="adminTab hidden" id="tabBadges">
<h3>🎖 Badge Viewer</h3>
<ul id="badgeUserList"></ul>
<div class="hidden" id="badgeDetails">
<h4 id="badgeUserTitle"></h4>
<ul id="badgeUserBadges"></ul>
</div>
</div>
<!-- 🎨 Theme Tab -->
<div class="adminTab hidden" id="tabTheme">
<h3>🎨 Select Theme</h3>
<select id="themeSelect" onchange="changeTheme(this.value)">
<option value="playground">Playground</option>
<option value="chalkboard">Chalkboard</option>
<option value="galaxy">Galaxy</option>
<option value="nature">Nature</option>
</select>
</div>
</div>
<!-- ⌨️ Typing Practice Tab -->
<div class="adminTab hidden" id="tabTyping">
<h3>⌨️ Typing Challenge Preview</h3>
<label for="typingUserSelect">Select Student:</label>
<select class="word-user-dropdown" id="typingUserSelect" onchange="loadTypingWords(this.value)">
<option value="">-- Select a user --</option>
</select>
<button onclick="startTypingTest()">Start Typing Test</button>
<div class="hidden" id="typingTestArea">
<p><strong>Practice Sentence:</strong></p>
<p class="typing-sentence" id="typingPrompt"></p>
<input autocomplete="off" id="typingInput" placeholder="Type the spelling word here..." type="text"/>
<button onclick="submitTyping()">Submit</button>
<p id="typingFeedback"></p>
</div>
</div>
<!-- 🧾 Student Report Viewer -->
<div class="adminTab hidden" id="tabReports">
<h3>📘 Student Progress Report</h3>
<label for="reportUser">Select Student:</label>
<select id="reportUser" onchange="loadStudentReport(this.value)">
<option value="">-- Select --</option>
</select>
<div class="hidden" id="reportContent">
<h4 id="reportTitle"></h4>
<p><strong>Last Score:</strong> <span id="reportScore"></span></p>
<p><strong>Words Attempted:</strong></p>
<ul id="reportWords"></ul>
<p><strong>Badges Earned:</strong></p>
<ul id="reportBadges"></ul>
<button onclick="window.print()">🖨️ Print Report</button>
<button onclick="copyReportToClipboard()">📋 Copy to Clipboard</button>
</div>
</div>
<div class="adminTab hidden" id="tabSessions">
<h3>📘 Archived Sessions</h3>
<label for="sessionUserSelect">Select Student:</label>
<select id="sessionUserSelect" onchange="loadUserSessions(this.value)">
<option value="">-- Select --</option>
</select>
<ul id="userSessionList"></ul>
</div>
<!-- 👩‍🎓 Student Panel -->
<div class="hidden" id="studentPanel">
<h2>Student Dashboard</h2>
<div class="student-controls">
<button id="startGameBtn" onclick="startGame()">Start Spelling Game</button>
<button id="startTypingBtn" onclick="startTypingPractice()">Start Typing Practice</button>
</div>
<!-- Game Section -->
<div class="hidden" id="gameSection">
<h3>🎮 Spelling Game</h3>
<div class="word-box" id="wordBox"></div>
<div class="hidden" id="inputSection">
<input id="userInput" placeholder="Type the word you saw..." type="text"/>
<button onclick="submitAnswer()">Submit</button>
</div>
</div>
<!-- Typing Practice Section -->
<div class="hidden" id="typingSection">
<h3>Typing Practice</h3>
<p id="typingPrompt">Type the word...</p>
<input id="typingInput" type="text"/>
<button onclick="submitTyping()">Submit</button>
<p id="typingFeedback"></p>
</div>
</div>
<script>
<div class="container">
<h1>✨ Spelling Practice</h1>
<!-- Role Banner -->
<div class="banner hidden" id="roleBanner"></div>
<!-- Login Panel -->
<div id="loginPanel" class="card">
<div class="card-header">
<h2 class="text-center">Welcome Back</h2>
<p class="text-center" style="color: var(--text-secondary);">Sign in to continue your spelling practice</p>
</div>
<form onsubmit="event.preventDefault(); verifyLogin();">
<input id="username" placeholder="Username" required type="text"/>
<input id="password" placeholder="Password" required type="password"/>
<button type="submit">Sign In</button>
</form>
<p id="loginMessage" style="margin-top: 1rem; text-align: center;"></p>
</div>
<!-- Navigation -->
<div id="nav" class="hidden">
<p><strong id="userInfo"></strong></p>
<div class="flex gap-4 justify-center" style="margin-top: 1rem;">
<button class="hidden" id="adminBtn" onclick="showAdmin()">👨‍💼 Admin Panel</button>
<button class="hidden" id="studentBtn" onclick="showStudent()">🎓 Student Panel</button>
<button id="logoutBtn" onclick="logoutUser()" class="btn-secondary">Sign Out</button>
</div>
</div>
<!-- Admin Panel -->
<div class="hidden" id="adminPanel">
<div class="card-header">
<h2>👨‍💼 Admin Dashboard</h2>
<p style="color: var(--text-secondary);">Manage users, word lists, and view analytics</p>
</div>
<!-- Admin Tabs -->
<div id="adminTabs">
<button class="active" onclick="switchTab('tabUsers')">👤 Users</button>
<button onclick="switchTab('tabWords')">📝 Word Lists</button>
<button onclick="switchTab('tabAnalytics')">📊 Analytics</button>
<button onclick="switchTab('tabResults')">� Results</button>
<button onclick="switchTab('tabReports')">📘 Reports</button>
<button onclick="switchTab('tabSessions')">� Sessions</button>
<button onclick="switchTab('tabBadges')">� Badges</button>
<button onclick="switchTab('tabTheme')">🎨 Themes</button>
</div>
<!-- Users Tab -->
<div id="tabUsers" class="adminTab">
<h3>👤 User Management</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; margin-top: 1.5rem;">
<div class="card">
<h4>Add New User</h4>
<form onsubmit="event.preventDefault(); addNewUser();" style="display: flex; flex-direction: column; gap: 1rem;">
<input id="newUsername" placeholder="Username" required type="text"/>
<input id="newPassword" placeholder="Password" required type="password"/>
<select id="newRole">
<option value="student">🎓 Student</option>
<option value="admin">👨‍💼 Admin</option>
</select>
<button type="submit" class="btn-success">➕ Add User</button>
</form>
<p id="userAddMessage" style="margin-top: 1rem;"></p>
</div>
<div class="card">
<h4>Remove User</h4>
<select id="userDropdown" onchange="selectUserAction(this.value)" style="width: 100%;">
<option value="">-- Select a user to remove --</option>
</select>
</div>
</div>
</div>
<!-- Word Lists Tab -->
<div class="adminTab hidden" id="tabWords">
<h3>📝 Word List Management</h3>
<div style="display: grid; gap: 2rem;">
<div class="card">
<h4>Select Student</h4>
<select class="word-user-dropdown" id="wordUserSelect" onchange="loadWordsForSelectedUser()" style="width: 100%;">
<option value="">-- Select a student --</option>
</select>
</div>
<div class="card">
<h4>Manage Word List</h4>
<textarea id="wordInput" placeholder="Enter one word per line..."></textarea>
<div style="display: flex; gap: 1rem; margin-top: 1rem;">
<button onclick="saveWords()" class="btn-success">💾 Save Words</button>
<button onclick="clearWords()" class="btn-error">🗑️ Clear All</button>
</div>
<div style="margin-top: 1.5rem;">
<h5>Upload Word File</h5>
<input accept=".txt,.csv" onchange="handleFileUpload(event)" type="file" style="margin: 0.5rem 0;"/>
</div>
</div>
<div class="card">
<h4>Current Word List</h4>
<ul id="wordListDisplay" style="max-height: 200px; overflow-y: auto;"></ul>
</div>
<div class="card">
<h4>View by Student</h4>
<select class="word-user-dropdown" id="viewUserSelect" onchange="displayWordListForSelectedUser()" style="width: 100%;">
<option value="">-- Select a student --</option>
</select>
<ul id="userWordListDisplay" style="max-height: 200px; overflow-y: auto; margin-top: 1rem;"></ul>
</div>
</div>
</div>
<!-- Results Tab -->
<div class="adminTab hidden" id="tabResults">
<div class="card">
<div class="card-header">
<h3>📋 Student Results Overview</h3>
<button onclick="loadStudentResults()" class="btn-primary">🔄 Refresh Results</button>
</div>
<ul id="resultsList" style="max-height: 400px; overflow-y: auto;"></ul>
</div>
</div>
<!-- Analytics Tab -->
<div class="adminTab hidden" id="tabAnalytics">
<div class="card">
<div class="card-header">
<h3>📊 Performance Analytics</h3>
<button onclick="refreshAnalytics()" class="btn-primary">🔄 Refresh Analytics</button>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem;">
<div>
<h4>Summary Statistics</h4>
<ul id="analyticsSummary" style="background: var(--bg-secondary); border-radius: var(--radius); padding: 1rem;">
<li><strong>Average Score:</strong> –</li>
<li><strong>Total Sessions:</strong> –</li>
<li><strong>Most Missed Word:</strong> –</li>
</ul>
</div>
<div>
<h4>Student Breakdown</h4>
<ul id="studentBreakdown" style="max-height: 300px; overflow-y: auto;"></ul>
</div>
</div>
</div>
</div>
<!-- Badges Tab -->
<div class="adminTab hidden" id="tabBadges">
<div class="card">
<h3>� Student Badges</h3>
<div style="display: grid; grid-template-columns: 1fr 2fr; gap: 2rem;">
<div>
<h4>Select Student</h4>
<ul id="badgeUserList"></ul>
</div>
<div class="hidden" id="badgeDetails">
<h4 id="badgeUserTitle"></h4>
<ul id="badgeUserBadges"></ul>
</div>
</div>
</div>
</div>
<!-- Theme Tab -->
<div class="adminTab hidden" id="tabTheme">
<div class="card">
<h3>🎨 Application Themes</h3>
<p style="color: var(--text-secondary); margin-bottom: 1.5rem;">Choose a visual theme for the application</p>
<select id="themeSelect" onchange="changeTheme(this.value)" style="width: 100%; max-width: 300px;">
<option value="default">✨ Modern (Default)</option>
<option value="playground">🎪 Playground</option>
<option value="chalkboard">🎓 Chalkboard</option>
<option value="galaxy">🌌 Galaxy</option>
<option value="nature">🌿 Nature</option>
</select>
</div>
</div>
<!-- Reports Tab -->
<div class="adminTab hidden" id="tabReports">
<div class="card">
<h3>📘 Student Progress Reports</h3>
<div style="margin-bottom: 2rem;">
<label for="reportUser">Select Student:</label>
<select id="reportUser" onchange="loadStudentReport(this.value)" style="width: 100%; max-width: 300px;">
<option value="">-- Select a student --</option>
</select>
</div>
<div class="hidden" id="reportContent">
<div class="card" style="background: var(--bg-secondary);">
<h4 id="reportTitle"></h4>
<p><strong>Latest Score:</strong> <span id="reportScore"></span></p>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; margin-top: 1.5rem;">
<div>
<h5>Words Attempted</h5>
<ul id="reportWords" style="max-height: 200px; overflow-y: auto;"></ul>
</div>
<div>
<h5>Badges Earned</h5>
<ul id="reportBadges"></ul>
</div>
</div>
<div style="margin-top: 2rem; display: flex; gap: 1rem;">
<button onclick="window.print()" class="btn-secondary">🖨️ Print Report</button>
<button onclick="copyReportToClipboard()" class="btn-primary">📋 Copy to Clipboard</button>
</div>
</div>
</div>
</div>
</div>
<!-- Sessions Tab -->
<div class="adminTab hidden" id="tabSessions">
<div class="card">
<h3>� Session History</h3>
<div style="margin-bottom: 2rem;">
<label for="sessionUserSelect">Select Student:</label>
<select id="sessionUserSelect" onchange="loadUserSessions(this.value)" style="width: 100%; max-width: 300px;">
<option value="">-- Select a student --</option>
</select>
</div>
<ul id="userSessionList" style="max-height: 400px; overflow-y: auto;"></ul>
</div>
</div>
</div>
<!-- Student Panel -->
<div class="hidden" id="studentPanel">
<div class="card">
<div class="card-header">
<h2>🎓 Student Dashboard</h2>
<p style="color: var(--text-secondary);">Choose an activity to start practicing</p>
</div>
<div class="student-controls">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; max-width: 600px; margin: 0 auto;">
<button id="startGameBtn" onclick="startGame()" class="btn-success">
🎮 Spelling Game
</button>
<button id="startTypingBtn" onclick="startTypingPractice()" class="btn-warning">
⌨️ Typing Practice
</button>
</div>
</div>
<!-- Game Section -->
<div class="hidden" id="gameSection">
<h3>🎮 Spelling Challenge</h3>
<div class="word-box" id="wordBox"></div>
<div class="hidden" id="inputSection" style="text-align: center;">
<input id="userInput" placeholder="Type the word you saw..." type="text" style="max-width: 400px;"/>
<button onclick="submitAnswer()" class="btn-primary">Submit Answer</button>
</div>
<!-- Game Summary -->
<div class="hidden" id="summary" class="card"></div>
<!-- Badge Display -->
<div class="hidden" id="badgeDisplay" class="card">
<h3>🏅 Congratulations!</h3>
<p>You've earned new badges:</p>
<ul id="badgeList"></ul>
</div>
</div>
<!-- Typing Practice Section -->
<div class="hidden" id="typingSection">
<h3>⌨️ Typing Practice</h3>
<div id="typingPrompt">Get ready to type...</div>
<div style="text-align: center; margin: 2rem 0;">
<input id="typingInput" type="text" style="max-width: 500px;"/>
<button onclick="submitTyping()" class="btn-primary">Submit</button>
</div>
<p id="typingFeedback" style="text-align: center; font-weight: 600;"></p>
</div>
</div>
</div>
<!-- Toast notification -->
<div id="toast" class="toast"></div>
</div> <script>
// Initialize the application when DOM is loaded
window.addEventListener('DOMContentLoaded', () => {
const user = JSON.parse(localStorage.getItem('loggedInUser'));
if (user) {
@@ -206,5 +310,5 @@
}
});
</script>
<div id="spellingSection"><h3>Spelling Game</h3><p id="spellingPrompt">Word will appear here...</p></div></body>
</body>
</html>
-245
View File
@@ -1,245 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Spelling App with Roles</title>
<link rel="stylesheet" href="styles.css" />
<!-- 🔧 Script Load Order -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsSHA/2.4.2/sha.js" defer></script>
<script src="js/auth.js" defer></script>
<script src="js/ui.js" defer></script>
<script src="js/themes.js" defer></script>
<script src="js/game.js"></script>
<script src="js/admin.js" defer></script>
<script src="js/main.js" defer></script>
<script src="js/typing.js" defer></script>
</head>
<body>
<h1>Spelling Practice</h1>
<!-- 🏳️ Role Banner -->
<div id="roleBanner" class="banner hidden"></div>
<!-- 👤 Login Panel -->
<div id="loginPanel">
<h2>User Login</h2>
<form onsubmit="event.preventDefault(); verifyLogin();">
<input type="text" id="username" placeholder="Username" required />
<input type="password" id="password" placeholder="Password" required />
<button type="submit">Login</button>
</form>
<p id="loginMessage"></p>
</div>
<!-- 🔀 Role-Based Navigation -->
<div id="nav" class="hidden">
<p><strong id="userInfo"></strong></p>
<button onclick="showAdmin()" id="adminBtn" class="hidden">Admin Panel</button>
<button onclick="showStudent()" id="studentBtn" class="hidden">Student Panel</button>
<button onclick="logoutUser()" id="logoutBtn">Log Out</button>
</div>
<!-- 👨‍🏫 Admin Panel -->
<div id="adminPanel" class="hidden">
<h2>👨‍🏫 Admin Panel</h2>
<!-- 🔘 Tabs -->
<div id="adminTabs">
<button onclick="switchTab('tabUsers')" class="active">👤 Manage Users</button>
<button onclick="switchTab('tabWords')">📝 Word Lists</button>
<button onclick="switchTab('tabAnalytics')">📊 Analytics</button>
<button onclick="switchTab('tabResults')">📊 Results</button>
<button onclick="switchTab('tabReports')">📘 Reports</button>
<button onclick="switchTab('tabSessions')">📆 Sessions</button>
<button onclick="switchTab('tabBadges')">🎖 Badges</button>
<button onclick="switchTab('tabTheme')">🎨 Theme</button>
<button onclick="switchTab('tabTyping')">⌨️ Typing Practice</button>
</div>
<!-- 👥 Users Tab -->
<div id="tabUsers" class="adminTab">
<h3>Add New User</h3>
<form onsubmit="event.preventDefault(); addNewUser();">
<input type="text" id="newUsername" placeholder="Username" required />
<input type="password" id="newPassword" placeholder="Password" required />
<select id="newRole">
<option value="student">Student</option>
<option value="admin">Admin</option>
</select>
<button type="submit">Add User</button>
</form>
<h3>Delete Users</h3>
<select id="userDropdown" onchange="selectUserAction(this.value)">
<option value="">-- Select a user --</option>
</select>
</div>
<!-- 📝 Word List Tab -->
<div id="tabWords" class="adminTab hidden">
<h3>Edit Word List for:</h3>
<select id="wordUserSelect" class="word-user-dropdown" onchange="loadWordsForSelectedUser()">
<option value="">-- Select a user --</option>
</select>
<h2>Enter Spelling Words</h2>
<textarea id="wordInput" placeholder="Enter one word per line..."></textarea><br/>
<button onclick="saveWords()">Save Word List</button>
<button onclick="clearWords()">Clear Saved Words</button>
<h3>Upload a Word File</h3>
<input type="file" accept=".txt,.csv" onchange="handleFileUpload(event)" />
<p><strong>Current Word List:</strong></p>
<ul id="wordListDisplay"></ul>
<h3>📋 View Word List by User:</h3>
<select id="viewUserSelect" class="word-user-dropdown" onchange="displayWordListForSelectedUser()">
<option value="">-- Select a user --</option>
</select>
<ul id="userWordListDisplay"></ul>
</div>
<!-- 📊 Results Tab -->
<div id="tabResults" class="adminTab hidden">
<h3>🧾 Student Results Overview</h3>
<button onclick="loadStudentResults()">Refresh Results</button>
<ul id="resultsList"></ul>
</div>
<!-- 📊 Analytics Tab -->
<div id="tabAnalytics" class="adminTab hidden">
<h3>Smart Analytics Overview</h3>
<ul id="analyticsSummary">
<li><strong>Average Score:</strong> –</li>
<li><strong>Total Completed Sessions:</strong> –</li>
<li><strong>Most Missed Word:</strong> –</li>
</ul>
<h4>Per-Student Breakdown</h4>
<ul id="studentBreakdown"></ul>
<button onclick="refreshAnalytics()">🔄 Refresh</button>
</div>
<!-- 🎖 Badges -->
<div id="tabBadges" class="adminTab hidden">
<h3>🎖 Badge Viewer</h3>
<ul id="badgeUserList"></ul>
<div id="badgeDetails" class="hidden">
<h4 id="badgeUserTitle"></h4>
<ul id="badgeUserBadges"></ul>
</div>
</div>
<!-- 🎨 Theme Tab -->
<div id="tabTheme" class="adminTab hidden">
<h3>🎨 Select Theme</h3>
<select id="themeSelect" onchange="changeTheme(this.value)">
<option value="playground">Playground</option>
<option value="chalkboard">Chalkboard</option>
<option value="galaxy">Galaxy</option>
<option value="nature">Nature</option>
</select>
</div>
</div>
<!-- ⌨️ Typing Practice Tab -->
<div id="tabTyping" class="adminTab hidden">
<h3>⌨️ Typing Challenge Preview</h3>
<label for="typingUserSelect">Select Student:</label>
<select id="typingUserSelect" class="word-user-dropdown" onchange="loadTypingWords(this.value)">
<option value="">-- Select a user --</option>
</select>
<button onclick="startTypingTest()">Start Typing Test</button>
<div id="typingTestArea" class="hidden">
<p><strong>Practice Sentence:</strong></p>
<p id="typingPrompt" class="typing-sentence"></p>
<input type="text" id="typingInput" placeholder="Type the spelling word here..." autocomplete="off" />
<button onclick="submitTyping()">Submit</button>
<p id="typingFeedback"></p>
</div>
</div>
<!-- 🧾 Student Report Viewer -->
<div id="tabReports" class="adminTab hidden">
<h3>📘 Student Progress Report</h3>
<label for="reportUser">Select Student:</label>
<select id="reportUser" onchange="loadStudentReport(this.value)">
<option value="">-- Select --</option>
</select>
<div id="reportContent" class="hidden">
<h4 id="reportTitle"></h4>
<p><strong>Last Score:</strong> <span id="reportScore"></span></p>
<p><strong>Words Attempted:</strong></p>
<ul id="reportWords"></ul>
<p><strong>Badges Earned:</strong></p>
<ul id="reportBadges"></ul>
<button onclick="window.print()">🖨️ Print Report</button>
<button onclick="copyReportToClipboard()">📋 Copy to Clipboard</button>
</div>
</div>
<div id="tabSessions" class="adminTab hidden">
<h3>📘 Archived Sessions</h3>
<label for="sessionUserSelect">Select Student:</label>
<select id="sessionUserSelect" onchange="loadUserSessions(this.value)">
<option value="">-- Select --</option>
</select>
<ul id="userSessionList"></ul>
</div>
<!-- 👩‍🎓 Student Panel -->
<div id="studentPanel" class="hidden">
<h2>Student Dashboard</h2>
<div class="student-controls">
<button id="startGameBtn" onclick="startGame()">Start Spelling Game</button>
<button id="startTypingBtn" onclick="startTypingPractice()">Start Typing Practice</button>
</div>
<!-- Game Section -->
<div id="gameSection" class="hidden">
<h3>🎮 Spelling Game</h3>
<div id="wordBox" class="word-box"></div>
<div id="inputSection" class="hidden">
<input type="text" id="userInput" placeholder="Type the word you saw..." />
<button onclick="submitAnswer()">Submit</button>
</div>
</div>
<!-- Typing Practice Section -->
<div id="typingSection" class="hidden">
<h3>Typing Practice</h3>
<p id="typingPrompt">Type the word...</p>
<input type="text" id="typingInput" />
<button onclick="submitTyping()">Submit</button>
<p id="typingFeedback"></p>
</div>
</div>
<script>
window.addEventListener('DOMContentLoaded', () => {
const user = JSON.parse(localStorage.getItem('loggedInUser'));
if (user) {
if (user.role === 'admin') {
showAdmin();
} else {
showStudent();
}
}
});
</script>
</body>
</html>
+229 -86
View File
@@ -1,3 +1,84 @@
// Global variables for admin functionality
let allWords = [];
let adminUsers = [];
// 🧭 Admin tab switcher (make it globally accessible)
window.switchTab = function switchTab(targetId) {
console.log('🔄 Switching to tab:', targetId);
// Hide all tabs
document.querySelectorAll('.adminTab').forEach(tab => {
tab.classList.add('hidden');
});
// Remove active class from all buttons
document.querySelectorAll('#adminTabs button').forEach(btn => {
btn.classList.remove('active');
});
// Show the selected tab
const targetTab = document.getElementById(targetId);
if (targetTab) {
targetTab.classList.remove('hidden');
console.log('✅ Showing tab:', targetId);
} else {
console.error('❌ Tab not found:', targetId);
}
// Highlight the active button
document.querySelectorAll('#adminTabs button').forEach(btn => {
const onclick = btn.getAttribute('onclick');
if (onclick && onclick.includes(targetId)) {
btn.classList.add('active');
}
});
// 🔁 Trigger tab-specific logic
switch(targetId) {
case 'tabSessions':
if (typeof populateSessionUserDropdown === 'function') {
populateSessionUserDropdown();
}
const sessionList = document.getElementById("userSessionList");
if (sessionList) sessionList.innerHTML = "";
break;
case 'tabReports':
if (typeof loadStudentNamesForReport === 'function') {
loadStudentNamesForReport();
}
break;
case 'tabBadges':
if (typeof loadBadgeViewer === 'function') {
loadBadgeViewer();
}
break;
case 'tabAnalytics':
if (typeof refreshAnalytics === 'function') {
refreshAnalytics();
}
break;
case 'tabResults':
if (typeof loadStudentResults === 'function') {
loadStudentResults();
}
break;
case 'tabWords':
loadUserDropdowns();
break;
}
};
// Load users from server
async function loadUsers() {
try {
const res = await fetch('/getUsers');
adminUsers = await res.json();
} catch (err) {
console.error("Failed to load users:", err);
adminUsers = [];
}
}
// ➕ Add a new user
async function addNewUser() {
const username = document.getElementById("newUsername").value.trim();
@@ -26,8 +107,9 @@ async function addNewUser() {
document.getElementById("newPassword").value = '';
if (hashPreview) hashPreview.textContent = '[auto]';
await loadUsers();
displayUserDropdown();
await loadUsers();
displayUserDropdown();
populateWordUserDropdown();
} catch (e) {
msg.textContent = "❌ Failed to add user.";
console.error("Add user error:", e);
@@ -38,7 +120,7 @@ async function addNewUser() {
function displayUserDropdown() {
const dropdown = document.getElementById("userDropdown");
dropdown.innerHTML = `<option value="">-- Select a user --</option>`;
users.forEach(user => {
adminUsers.forEach(user => {
dropdown.innerHTML += `<option value="${user.username}">${user.username} (${user.role})</option>`;
});
}
@@ -104,8 +186,9 @@ async function loadWordsForSelectedUser() {
const selectedUser = document.getElementById("wordUserSelect").value;
if (!selectedUser) return;
const res = await fetch(`/getWordList?user=${encodeURIComponent(selectedUser)}`);
allWords = await res.json();
const res = await fetch(`/getWordList?username=${encodeURIComponent(selectedUser)}`);
const data = await res.json();
allWords = data.words || [];
displayWordList();
}
@@ -118,7 +201,7 @@ function populateWordUserDropdown() {
const selects = document.querySelectorAll(".word-user-dropdown");
selects.forEach(select => {
select.innerHTML = '<option value="">-- Select a user --</option>';
users.forEach(user => {
adminUsers.forEach(user => {
const option = document.createElement("option");
option.value = user.username;
option.textContent = user.username;
@@ -136,8 +219,9 @@ async function displayWordListForSelectedUser() {
if (!username) return;
try {
const res = await fetch(`/getWordList?user=${username}`);
const words = await res.json();
const res = await fetch(`/getWordList?username=${username}`);
const data = await res.json();
const words = data.words || [];
if (!words.length) {
list.innerHTML = "<li><em>No words found for this user.</em></li>";
return;
@@ -154,7 +238,7 @@ async function displayWordListForSelectedUser() {
}
// 📊 Student result viewer
async function loadStudentResults() {
window.loadStudentResults = async function() {
const list = document.getElementById("resultsList");
list.innerHTML = '';
@@ -167,19 +251,38 @@ async function loadStudentResults() {
return;
}
for (const [username, result] of Object.entries(results)) {
const li = document.createElement("li");
let answersHTML = '';
result.answers.forEach(entry => {
const status = entry.correct ? '✅' : '❌';
answersHTML += `${status} ${entry.word}<br>`;
});
for (const [username, userResults] of Object.entries(results)) {
// Handle array of results per user
if (Array.isArray(userResults) && userResults.length > 0) {
userResults.forEach((result, index) => {
const li = document.createElement("li");
let answersHTML = '';
// Check if result and answers exist and are valid
if (result && result.answers && Array.isArray(result.answers)) {
result.answers.forEach(entry => {
const status = entry.correct ? '✅' : '❌';
answersHTML += `${status} ${entry.word}<br>`;
});
} else {
answersHTML = '<em>No answer data available</em>';
}
li.innerHTML = `
<strong>${username}</strong> — Score: ${result.score}, Completed: ${result.completed ? '✅' : '❌'}<br>
<em>Answers:</em><br>${answersHTML}
`;
list.appendChild(li);
const timestamp = result.timestamp ? new Date(result.timestamp).toLocaleString() : 'Unknown time';
li.innerHTML = `
<strong>${username}</strong> (Session ${index + 1}) — Score: ${result?.score || 'N/A'}, Completed: ${result?.completed ? '✅' : '❌'}<br>
<em>Date:</em> ${timestamp}<br>
<em>Answers:</em><br>${answersHTML}
<hr style="margin: 1rem 0; opacity: 0.3;">
`;
list.appendChild(li);
});
} else {
// Fallback for single result format
const li = document.createElement("li");
li.innerHTML = `<strong>${username}</strong> — No valid results found`;
list.appendChild(li);
}
}
} catch (err) {
console.error("🔴 Failed to load results:", err);
@@ -187,40 +290,6 @@ async function loadStudentResults() {
}
}
// 🧭 Admin tab switcher
function switchTab(targetId) {
document.querySelectorAll('.adminTab').forEach(tab =>
tab.classList.add('hidden')
);
document.querySelectorAll('#adminTabs button').forEach(btn =>
btn.classList.remove('active')
);
// Show the selected tab
document.getElementById(targetId).classList.remove('hidden');
// Highlight the active button
document.querySelectorAll('#adminTabs button').forEach(btn => {
if (btn.getAttribute('onclick')?.includes(targetId)) {
btn.classList.add('active');
}
});
// 🔁 Trigger tab-specific logic
if (targetId === 'tabSessions') {
loadSessionHistory();
}
if (targetId === 'tabReports') {
loadStudentNamesForReport();
}
if (targetId === 'tabBadges') {
loadBadgeViewer();
}
if (targetId === 'tabSessions') {
populateSessionUserDropdown();
document.getElementById("userSessionList").innerHTML = "";
}
}
async function loadUserDropdowns() {
try {
const res = await fetch('/getUsers');
@@ -243,6 +312,8 @@ async function loadUserDropdowns() {
drop.appendChild(opt);
});
});
console.log('✅ User dropdowns loaded');
} catch (err) {
console.error("⚠️ Failed to load user list:", err);
}
@@ -352,44 +423,73 @@ async function loadStudentNamesForReport() {
});
}
async function loadStudentReport(username) {
window.loadStudentReport = async function(username) {
if (!username) return;
const res1 = await fetch('/getResults');
const results = await res1.json();
const res2 = await fetch('/getBadges');
const badges = await res2.json();
try {
const res1 = await fetch('/getResults');
const results = await res1.json();
const res2 = await fetch('/getBadges');
const badges = await res2.json();
const report = results[username];
const badgeList = badges[username] || [];
const userResults = results[username];
const badgeList = badges[username] || [];
document.getElementById("reportTitle").textContent = `Report for ${username}`;
document.getElementById("reportScore").textContent = `${report.score}/${report.answers.length} correct`;
const wordList = document.getElementById("reportWords");
wordList.innerHTML = "";
report.answers.forEach(({ word, correct }) => {
const li = document.createElement("li");
li.textContent = `${word} — ${correct ? '✅' : '❌'}`;
wordList.appendChild(li);
});
// Handle case where userResults might be an array or undefined
let latestReport = null;
if (Array.isArray(userResults) && userResults.length > 0) {
// Get the most recent result
latestReport = userResults[userResults.length - 1];
} else if (userResults && typeof userResults === 'object') {
// Handle single result object
latestReport = userResults;
}
const badgeContainer = document.getElementById("reportBadges");
badgeContainer.innerHTML = "";
if (badgeList.length) {
badgeList.forEach(b => {
const li = document.createElement("li");
li.textContent = `🏅 ${b}`;
badgeContainer.appendChild(li);
});
} else {
badgeContainer.innerHTML = "<li><em>No badges earned yet.</em></li>";
if (!latestReport) {
document.getElementById("reportContent").classList.add("hidden");
alert(`No results found for ${username}`);
return;
}
document.getElementById("reportTitle").textContent = `Report for ${username}`;
// Safe access to score and answers
const score = latestReport.score || 0;
const answers = latestReport.answers || [];
document.getElementById("reportScore").textContent = `${score}/${answers.length} correct`;
const wordList = document.getElementById("reportWords");
wordList.innerHTML = "";
if (answers.length > 0) {
answers.forEach(({ word, correct }) => {
const li = document.createElement("li");
li.textContent = `${word} — ${correct ? '✅' : '❌'}`;
wordList.appendChild(li);
});
} else {
wordList.innerHTML = "<li><em>No word data available.</em></li>";
}
const badgeContainer = document.getElementById("reportBadges");
badgeContainer.innerHTML = "";
if (badgeList.length) {
badgeList.forEach(b => {
const li = document.createElement("li");
li.textContent = `🏅 ${b}`;
badgeContainer.appendChild(li);
});
} else {
badgeContainer.innerHTML = "<li><em>No badges earned yet.</em></li>";
}
document.getElementById("reportContent").classList.remove("hidden");
} catch (error) {
console.error("Error loading student report:", error);
alert("Error loading student report. Please try again.");
}
document.getElementById("reportContent").classList.remove("hidden");
}
function copyReportToClipboard() {
window.copyReportToClipboard = function() {
const name = document.getElementById("reportTitle").textContent;
const score = document.getElementById("reportScore").textContent;
const words = Array.from(document.querySelectorAll("#reportWords li")).map(li => li.textContent).join("\n");
@@ -457,4 +557,47 @@ async function loadUserSessions(username) {
sessionItem.innerHTML = html;
list.appendChild(sessionItem);
});
}
}
// File upload handler for word lists
function handleFileUpload(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
const content = e.target.result;
const words = content.split(/\r?\n/).map(w => w.trim()).filter(Boolean);
document.getElementById('wordInput').value = words.join('\n');
showToast(`📁 Loaded ${words.length} words from file`);
};
reader.readAsText(file);
}
// Add event listeners for admin tabs as backup to onclick
document.addEventListener('DOMContentLoaded', () => {
// Setup tab click listeners
setTimeout(() => {
const tabButtons = document.querySelectorAll('#adminTabs button');
tabButtons.forEach(button => {
button.addEventListener('click', (e) => {
e.preventDefault();
const onclick = button.getAttribute('onclick');
if (onclick) {
const match = onclick.match(/switchTab\('(.+?)'\)/);
if (match) {
const tabId = match[1];
console.log('Tab clicked:', tabId);
if (typeof window.switchTab === 'function') {
window.switchTab(tabId);
} else {
console.error('switchTab function not available');
}
}
}
});
});
console.log('✅ Tab event listeners added to', tabButtons.length, 'buttons');
}, 300);
});
+92 -19
View File
@@ -1,8 +1,24 @@
// 🔐 Password hashing using jsSHA
function hashPassword(password) {
const shaObj = new jsSHA("SHA-256", "TEXT");
shaObj.update(password);
return shaObj.getHash("HEX");
try {
if (typeof jsSHA === 'undefined') {
console.error('jsSHA library not loaded');
throw new Error('jsSHA library not available');
}
const shaObj = new jsSHA("SHA-256", "TEXT");
shaObj.update(password);
return shaObj.getHash("HEX");
} catch (error) {
console.error('Error hashing password:', error);
// Fallback: simple hash (not secure, but for debugging)
let hash = 0;
for (let i = 0; i < password.length; i++) {
const char = password.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32bit integer
}
return Math.abs(hash).toString(16);
}
}
// 🌐 Load users from server
@@ -12,28 +28,43 @@ let users = [];
async function verifyLogin() {
const uname = document.getElementById('username').value.trim();
const pw = document.getElementById('password').value;
const hash = hashPassword(pw);
const msg = document.getElementById('loginMessage');
if (!uname || !pw) {
msg.textContent = "Please enter both username and password.";
msg.style.color = 'var(--error)';
return;
}
try {
console.log('Attempting login for:', uname);
const hash = hashPassword(pw);
console.log('Generated hash:', hash);
const res = await fetch('/verifyUser', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: uname, hash })
});
const responseText = await res.text();
console.log('Server response:', res.status, responseText);
if (!res.ok) {
msg.textContent = "Invalid username or password.";
msg.style.color = 'var(--error)';
return;
}
const user = await res.json();
const user = JSON.parse(responseText);
console.log('Login successful for user:', user);
localStorage.setItem('loggedInUser', JSON.stringify(user));
msg.textContent = '';
setupSession(user);
} catch (err) {
console.error("Login error:", err);
msg.textContent = "Login failed. Please try again.";
msg.style.color = 'var(--error)';
}
}
@@ -41,18 +72,60 @@ async function verifyLogin() {
function logoutUser() {
localStorage.removeItem('loggedInUser');
document.getElementById('adminPanel').classList.add('hidden');
document.getElementById('studentPanel').classList.add('hidden');
document.getElementById('nav').classList.add('hidden');
document.getElementById('loginPanel').classList.remove('hidden');
document.getElementById('username').value = '';
document.getElementById('password').value = '';
const roleBanner = document.getElementById('roleBanner');
roleBanner.classList.add('hidden');
roleBanner.classList.remove('admin', 'student');
roleBanner.textContent = '';
// Hide all panels
document.getElementById('adminPanel')?.classList.add('hidden');
document.getElementById('studentPanel')?.classList.add('hidden');
document.getElementById('nav')?.classList.add('hidden');
// Show login panel
const loginPanel = document.getElementById('loginPanel');
loginPanel?.classList.remove('hidden');
// Clear login form
const usernameField = document.getElementById('username');
const passwordField = document.getElementById('password');
const loginMessage = document.getElementById('loginMessage');
if (loginMessage) loginMessage.textContent = '';
}
if (usernameField) usernameField.value = '';
if (passwordField) passwordField.value = '';
if (loginMessage) {
loginMessage.textContent = '';
loginMessage.style.color = '';
}
// Reset banner
const roleBanner = document.getElementById('roleBanner');
if (roleBanner) {
roleBanner.classList.add('hidden');
roleBanner.classList.remove('admin', 'student');
roleBanner.textContent = '';
}
// Focus on username field
if (usernameField) {
setTimeout(() => usernameField.focus(), 100);
}
showToast('Logged out successfully');
}
// Add Enter key support for login form
document.addEventListener('DOMContentLoaded', () => {
const loginForm = document.querySelector('#loginPanel form');
const usernameField = document.getElementById('username');
const passwordField = document.getElementById('password');
if (loginForm && usernameField && passwordField) {
// Focus username field on page load
usernameField.focus();
// Add Enter key support
[usernameField, passwordField].forEach(field => {
field.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
verifyLogin();
}
});
});
}
});
+42 -2
View File
@@ -3,7 +3,46 @@ let words = [];
let currentWord = '';
let currentIndex = 0;
let results = [];
let typingWords = [];
// 📝 Check user's answer and move to next
window.submitAnswer = function() {
const input = document.getElementById('userInput').value.trim();
// Record the attempt
results.push({
word: currentWord,
attempt: input
});
if (input.toLowerCase() === currentWord.toLowerCase()) {
currentIndex++;
document.getElementById('inputSection').classList.add('hidden');
setTimeout(() => showWord(), 500); // Brief pause before next word
} else {
// Show feedback for incorrect answer
const inputEl = document.getElementById('userInput');
inputEl.style.borderColor = 'var(--error)';
inputEl.style.backgroundColor = '#fef2f2';
setTimeout(() => {
inputEl.style.borderColor = '';
inputEl.style.backgroundColor = '';
inputEl.focus();
}, 1000);
}
}
// Add Enter key support for game input
document.addEventListener('DOMContentLoaded', () => {
const gameInput = document.getElementById('userInput');
if (gameInput) {
gameInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
submitAnswer();
}
});
}
});[];
let currentSentence = '';
function showStudent() {
console.log("🔊 Showing student panel");
@@ -34,7 +73,7 @@ function showStudent() {
}
// 🚀 Begin game session
async function startGame() {
window.startGame = async function() {
console.log('🎮 Starting spelling game');
const user = JSON.parse(localStorage.getItem('loggedInUser'));
@@ -62,6 +101,7 @@ async function startGame() {
// Start game
words = [...data.words];
typingWords = [...data.words]; // Initialize typing words too
currentIndex = 0;
results = []; // Reset results array
showWord();
+46 -14
View File
@@ -1,22 +1,54 @@
document.addEventListener("DOMContentLoaded", async () => {
// 🌐 Load users.json for login validation
// await loadUsers();
displayUserDropdown();
populateWordUserDropdown();
// 🎨 Restore saved theme
applySavedTheme();
// 🔁 Restore session if user was logged in
const savedUser = localStorage.getItem("loggedInUser");
if (savedUser) {
setupSession(JSON.parse(savedUser));
console.log('🚀 Application starting...');
// Load users for admin functionality
try {
await loadUsers();
displayUserDropdown();
populateWordUserDropdown();
} catch (e) {
console.warn('Could not load users (normal if not admin):', e);
}
// 🧭 Tab switching for Admin Panel
// Restore saved theme
applySavedTheme();
// Restore session if user was logged in
const savedUser = localStorage.getItem("loggedInUser");
if (savedUser) {
try {
const user = JSON.parse(savedUser);
console.log('👤 Restoring session for:', user.username);
setupSession(user);
} catch (e) {
console.error('Error restoring session:', e);
localStorage.removeItem("loggedInUser");
}
} else {
console.log('👤 No saved session found');
// Focus on login form
const usernameField = document.getElementById('username');
if (usernameField) {
setTimeout(() => usernameField.focus(), 100);
}
}
// Set default admin tab
const defaultTab = document.querySelector('#adminTabs button.active');
if (defaultTab) {
const targetId = defaultTab.getAttribute("onclick")?.match(/'(.*?)'/)?.[1];
if (targetId) switchTab(targetId);
if (targetId) {
console.log('🔧 Setting default admin tab:', targetId);
// Use a small delay to ensure all scripts are loaded
setTimeout(() => {
if (typeof window.switchTab === 'function') {
window.switchTab(targetId);
} else {
console.warn('switchTab function not yet available');
}
}, 200);
}
}
console.log('✅ Application initialized');
});
+64 -5
View File
@@ -1,14 +1,73 @@
// 🎨 Apply saved theme on load
// Modern theme system
function applySavedTheme() {
const savedTheme = localStorage.getItem('theme') || 'playground';
document.body.className = savedTheme;
const savedTheme = localStorage.getItem('theme') || 'default';
applyTheme(savedTheme);
const themeSelector = document.getElementById('themeSelect');
if (themeSelector) themeSelector.value = savedTheme;
}
// 🎨 Change theme dynamically
function changeTheme(themeName) {
document.body.className = themeName;
applyTheme(themeName);
localStorage.setItem('theme', themeName);
showToast(`🎨 Theme changed to ${themeName}`);
}
function applyTheme(themeName) {
// Remove existing theme classes
document.body.className = '';
// Apply theme-specific styles
switch(themeName) {
case 'playground':
document.body.className = 'theme-playground';
document.documentElement.style.setProperty('--primary', '#ff6b6b');
document.documentElement.style.setProperty('--accent', '#4ecdc4');
document.documentElement.style.setProperty('--bg-primary', '#fff5f5');
document.documentElement.style.setProperty('--bg-secondary', '#ffebee');
break;
case 'chalkboard':
document.body.className = 'theme-chalkboard';
document.documentElement.style.setProperty('--primary', '#4ade80');
document.documentElement.style.setProperty('--accent', '#22d3ee');
document.documentElement.style.setProperty('--bg-primary', '#1f2937');
document.documentElement.style.setProperty('--bg-secondary', '#374151');
document.documentElement.style.setProperty('--text-primary', '#f9fafb');
document.documentElement.style.setProperty('--text-secondary', '#d1d5db');
document.documentElement.style.setProperty('--border', '#4b5563');
break;
case 'galaxy':
document.body.className = 'theme-galaxy';
document.documentElement.style.setProperty('--primary', '#8b5cf6');
document.documentElement.style.setProperty('--accent', '#06b6d4');
document.documentElement.style.setProperty('--bg-primary', '#0f0f23');
document.documentElement.style.setProperty('--bg-secondary', '#1a1a3f');
document.documentElement.style.setProperty('--text-primary', '#e0e7ff');
document.documentElement.style.setProperty('--text-secondary', '#a5b4fc');
document.documentElement.style.setProperty('--border', '#3730a3');
break;
case 'nature':
document.body.className = 'theme-nature';
document.documentElement.style.setProperty('--primary', '#059669');
document.documentElement.style.setProperty('--accent', '#84cc16');
document.documentElement.style.setProperty('--bg-primary', '#f0fdf4');
document.documentElement.style.setProperty('--bg-secondary', '#dcfce7');
document.documentElement.style.setProperty('--text-primary', '#14532d');
break;
default: // Modern default theme
document.body.className = 'theme-default';
// Reset to original CSS custom properties
document.documentElement.style.setProperty('--primary', '#3b82f6');
document.documentElement.style.setProperty('--accent', '#06b6d4');
document.documentElement.style.setProperty('--bg-primary', '#ffffff');
document.documentElement.style.setProperty('--bg-secondary', '#f8fafc');
document.documentElement.style.setProperty('--text-primary', '#1e293b');
document.documentElement.style.setProperty('--text-secondary', '#64748b');
document.documentElement.style.setProperty('--border', '#e2e8f0');
break;
}
}
+4 -9
View File
@@ -5,7 +5,8 @@ const typingState = {
currentWord: ''
};
async function startTypingPractice() {
// Make functions globally accessible
window.startTypingPractice = async function() {
console.log('Starting typing practice');
const user = JSON.parse(localStorage.getItem('loggedInUser'));
@@ -34,7 +35,7 @@ async function startTypingPractice() {
}
}
function showNextWord() {
window.showNextWord = function() {
const promptEl = document.getElementById('typingPrompt');
const inputEl = document.getElementById('typingInput');
if (!promptEl || !inputEl) return;
@@ -58,14 +59,8 @@ function showNextWord() {
inputEl.focus();
}
// Reset input
inputEl.value = '';
inputEl.disabled = false;
inputEl.focus();
function submitTyping() {
window.submitTyping = function() {
const input = document.getElementById('typingInput')?.value.trim() || '';
const feedback = document.getElementById('typingFeedback');
if (!feedback) return;
+51 -9
View File
@@ -46,16 +46,58 @@ function showToast(message, duration = 3000) {
toast.classList.remove("show");
}, duration);
}
function showAdmin() {
console.log("showAdmin fired");
const adminPanel = document.getElementById("adminPanel");
const studentPanel = document.getElementById("studentPanel");
function showStudent() {
console.log("🎓 Showing student panel");
loadUserDropdowns();
// Hide other panels
document.getElementById('adminPanel')?.classList.add('hidden');
document.getElementById('loginPanel')?.classList.add('hidden');
// Show student panel
const studentPanel = document.getElementById('studentPanel');
if (studentPanel) {
studentPanel.classList.remove('hidden');
// Ensure buttons are visible and functional
const gameBtn = document.getElementById('startGameBtn');
const typingBtn = document.getElementById('startTypingBtn');
if (gameBtn && typingBtn) {
gameBtn.classList.remove('hidden');
typingBtn.classList.remove('hidden');
console.log("✅ Student buttons are now visible");
}
}
}
adminPanel?.classList.remove("hidden");
studentPanel?.classList.add("hidden");
function showAdmin() {
console.log("👨‍💼 Showing admin panel");
switchTab("tabUsers"); // optional default tab
// Hide other panels
document.getElementById('studentPanel')?.classList.add('hidden');
document.getElementById('loginPanel')?.classList.add('hidden');
const adminPanel = document.getElementById("adminPanel");
if (adminPanel) {
adminPanel.classList.remove("hidden");
// Load user data for dropdowns
loadUserDropdowns().then(() => {
console.log("✅ User dropdowns loaded");
}).catch(e => {
console.warn("Could not load user dropdowns:", e);
});
// Set default tab - use a slight delay to ensure DOM is ready
setTimeout(() => {
if (typeof window.switchTab === 'function') {
window.switchTab("tabUsers");
console.log("✅ Default tab set");
} else {
console.error("switchTab function not available");
}
}, 100);
} else {
console.error("Admin panel not found");
}
}
+340 -262
View File
@@ -1,319 +1,397 @@
/* 📋 Global Defaults */
body {
font-family: sans-serif;
text-align: center;
padding: 2rem;
/* Modern Spelling App Styles */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
/* Reset and base styles */
* {
margin: 0;
}
ul {
list-style: none;
padding: 0;
}
li {
margin: 0.25rem 0;
box-sizing: border-box;
}
:root {
/* Modern color palette */
--primary: #3b82f6;
--primary-dark: #2563eb;
--secondary: #f1f5f9;
--accent: #06b6d4;
--success: #10b981;
--warning: #f59e0b;
--error: #ef4444;
--text-primary: #1e293b;
--text-secondary: #64748b;
--border: #e2e8f0;
--bg-primary: #ffffff;
--bg-secondary: #f8fafc;
--shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--radius: 0.75rem;
--transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background: linear-gradient(135deg, var(--bg-secondary) 0%, #ffffff 100%);
color: var(--text-primary);
line-height: 1.6;
min-height: 100vh;
padding: 2rem 1rem;
}
/* Container and layout */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
/* Typography */
h1 {
font-size: 3rem;
font-weight: 700;
text-align: center;
margin-bottom: 2rem;
background: linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
h2 {
font-size: 1.875rem;
font-weight: 600;
margin-bottom: 1.5rem;
color: var(--text-primary);
}
h3 {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 1rem;
color: var(--text-primary);
}
/* Form elements */
input, textarea, select {
font-size: 1rem;
padding: 0.5rem;
width: 100%;
max-width: 320px;
padding: 0.75rem 1rem;
margin: 0.5rem;
border: 2px solid #ccc;
border-radius: 5px;
border: 2px solid var(--border);
border-radius: var(--radius);
font-size: 1rem;
font-family: inherit;
background: var(--bg-primary);
color: var(--text-primary);
transition: var(--transition);
}
/* 🎨 Theme Styles */
body.playground {
background: #fef9e7;
color: #333;
font-family: 'Comic Sans MS', cursive, sans-serif;
}
body.chalkboard {
background: #222;
color: #f0f0f0;
font-family: 'Courier New', monospace;
}
body.galaxy {
background: radial-gradient(circle at center, #0d0d2b, #1a1a3f);
color: #b3e5fc;
font-family: 'Orbitron', sans-serif;
}
body.nature {
background: #e8f5e9;
color: #2e7d32;
font-family: 'Georgia', serif;
input:focus, textarea:focus, select:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgb(59 130 246 / 0.1);
}
/* 🔘 Buttons */
textarea {
min-height: 120px;
resize: vertical;
max-width: 500px;
}
/* Buttons */
button {
background-color: #4caf50;
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1.5rem;
margin: 0.25rem;
background: var(--primary);
color: white;
border: none;
border-radius: 8px;
padding: 0.6rem 1rem;
border-radius: var(--radius);
font-size: 1rem;
margin: 0.4rem;
font-weight: 500;
font-family: inherit;
cursor: pointer;
transition: transform 0.2s;
display: inline-block;
transition: var(--transition);
box-shadow: var(--shadow);
}
button:hover {
transform: scale(1.05);
background: var(--primary-dark);
transform: translateY(-1px);
box-shadow: var(--shadow-lg);
}
/* 🌈 Theme-specific Button Colors */
body.chalkboard button {
background-color: #555;
}
body.galaxy button {
background-color: #6200ea;
}
body.nature button {
background-color: #81c784;
button:active {
transform: translateY(0);
}
/* 🔳 Panels and Banners */
button:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
/* Button variants */
.btn-secondary {
background: var(--secondary);
color: var(--text-primary);
}
.btn-secondary:hover {
background: var(--border);
}
.btn-success {
background: var(--success);
}
.btn-success:hover {
background: #059669;
}
.btn-warning {
background: var(--warning);
}
.btn-warning:hover {
background: #d97706;
}
.btn-error {
background: var(--error);
}
.btn-error:hover {
background: #dc2626;
}
/* Cards and panels */
.card {
background: var(--bg-primary);
border-radius: var(--radius);
padding: 2rem;
margin: 1rem 0;
box-shadow: var(--shadow);
border: 1px solid var(--border);
}
.card-header {
padding-bottom: 1rem;
margin-bottom: 1rem;
border-bottom: 1px solid var(--border);
}
/* Layout utilities */
.hidden {
display: none;
display: none !important;
}
#wordBox {
font-size: 2em;
margin: 20px;
min-height: 60px;
padding: 20px;
background-color: #f5f5f5;
border-radius: 8px;
transition: opacity 0.3s ease;
.flex {
display: flex;
}
.word-box {
font-size: 3rem;
font-weight: bold;
margin-bottom: 2rem;
color: #2196f3;
text-shadow: 1px 1px #ccc;
.flex-col {
flex-direction: column;
}
.banner {
padding: 0.5em;
.items-center {
align-items: center;
}
.justify-center {
justify-content: center;
}
.gap-4 {
gap: 1rem;
}
.text-center {
text-align: center;
font-weight: bold;
color: white;
}
.banner.admin {
background-color: #b22222;
}
.banner.student {
background-color: #1e90ff;
}
/* 🔔 Toast Messages */
.toast {
position: fixed;
bottom: 20px;
right: 20px;
background: #333;
color: #fff;
padding: 0.75em 1.25em;
border-radius: 5px;
opacity: 0;
pointer-events: none;
transition: opacity 0.4s ease;
z-index: 999;
}
.toast.show {
opacity: 1;
pointer-events: auto;
/* Specific component styles */
#loginPanel {
max-width: 400px;
margin: 0 auto;
background: var(--bg-primary);
padding: 2rem;
border-radius: var(--radius);
box-shadow: var(--shadow-lg);
}
/* 🧭 Admin Tabs */
#adminTabs {
margin: 1em 0;
#loginPanel form {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
}
#adminTabs button {
margin-right: 8px;
padding: 10px 16px;
background-color: #f0f0f0;
border: 1px solid #ccc;
border-bottom: none;
border-radius: 6px 6px 0 0;
/* Banner */
.banner {
padding: 1rem;
text-align: center;
font-weight: 600;
color: #333;
color: white;
border-radius: var(--radius);
margin-bottom: 2rem;
box-shadow: var(--shadow);
}
.banner.admin {
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
}
.banner.student {
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
}
/* Navigation */
#nav {
text-align: center;
margin-bottom: 2rem;
padding: 1rem;
background: var(--bg-primary);
border-radius: var(--radius);
box-shadow: var(--shadow);
}
/* Admin tabs */
#adminTabs {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 2rem;
padding: 0.5rem;
background: var(--bg-secondary);
border-radius: var(--radius);
}
#adminTabs button {
flex: 1;
min-width: fit-content;
background: transparent;
color: var(--text-secondary);
border: none;
border-radius: calc(var(--radius) - 0.25rem);
padding: 0.75rem 1rem;
font-weight: 500;
transition: var(--transition);
box-shadow: none;
cursor: pointer;
transition: background-color 0.2s, color 0.2s;
}
#adminTabs button:hover {
background-color: #e0f2ff;
color: #000;
background: var(--bg-primary);
color: var(--text-primary);
transform: none;
}
#adminTabs button.active {
background-color: #ffffff;
color: #000;
border-bottom: 2px solid #fff;
box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.1);
background: var(--primary) !important;
color: white !important;
box-shadow: var(--shadow);
}
.adminTab {
display: none;
}
.adminTab:not(.hidden) {
display: block;
background: var(--bg-primary);
border-radius: var(--radius);
padding: 2rem;
box-shadow: var(--shadow);
border: 1px solid var(--border);
}
.typing-sentence {
font-size: 1.5em;
margin: 20px 0;
padding: 15px;
background-color: #f5f5f5;
border-radius: 8px;
border: 1px solid #ddd;
min-height: 50px;
display: block !important;
visibility: visible !important;
.adminTab.hidden {
display: none !important;
}
/* Remove these style classes if they exist */
.typing-section
.typing-container
.typing-prompt
.input-container
#typingInput
#typingFeedback
/* Fix for start buttons visibility */
#startGameBtn, #startTypingBtn {
display: inline-block;
margin: 10px;
padding: 12px 24px;
font-size: 1.1em;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
#startGameBtn:hover, #startTypingBtn:hover {
transform: scale(1.05);
background-color: #45a049;
}
/* Override any hidden class for these buttons */
#startGameBtn.hidden, #startTypingBtn.hidden {
display: none !important;
}
/* Button container for better spacing */
.button-container {
margin: 20px 0;
padding: 10px;
text-align: center;
/* Game elements */
#wordBox, .word-box, #typingPrompt {
font-size: 3rem;
font-weight: 700;
text-align: center;
padding: 3rem 2rem;
margin: 2rem 0;
background: linear-gradient(135deg, var(--bg-primary) 0%, var(--secondary) 100%);
border: 2px solid var(--border);
border-radius: var(--radius);
color: var(--primary);
box-shadow: var(--shadow-lg);
min-height: 120px;
display: flex;
align-items: center;
justify-content: center;
}
.student-controls {
display: block !important;
margin: 20px auto;
padding: 15px;
text-align: center;
visibility: visible !important;
text-align: center;
margin: 2rem 0;
padding: 2rem;
background: var(--bg-primary);
border-radius: var(--radius);
box-shadow: var(--shadow);
}
.student-btn {
display: inline-block !important;
visibility: visible !important;
background-color: #4CAF50;
color: white;
padding: 12px 24px;
margin: 10px;
border: none;
border-radius: 5px;
font-size: 16px;
cursor: pointer;
opacity: 1 !important;
/* Lists */
ul {
list-style: none;
}
/* Override any hiding classes for these specific buttons */
#startGameBtn:not(.hidden),
#startTypingBtn:not(.hidden) {
display: inline-block !important;
visibility: visible !important;
opacity: 1 !important;
li {
padding: 0.75rem 1rem;
margin: 0.5rem 0;
background: var(--bg-secondary);
border-radius: calc(var(--radius) - 0.25rem);
border: 1px solid var(--border);
}
/* Remove this if it exists as it might conflict */
.hidden {
display: none !important;
/* Toast notifications */
.toast {
position: fixed;
bottom: 2rem;
right: 2rem;
background: var(--text-primary);
color: white;
padding: 1rem 1.5rem;
border-radius: var(--radius);
opacity: 0;
transform: translateY(100%);
transition: var(--transition);
z-index: 1000;
box-shadow: var(--shadow-lg);
font-weight: 500;
}
/* Add this to ensure student panel shows properly */
#studentPanel:not(.hidden) {
display: block !important;
.toast.show {
opacity: 1;
transform: translateY(0);
}
/* 📝 Typing Practice Styles */
.typing-section {
max-width: 800px;
margin: 20px auto;
padding: 20px;
}
#typingPrompt {
font-size: 2em;
font-weight: bold;
text-align: center;
margin: 20px 0;
padding: 20px;
background: #f5f5f5;
border-radius: 8px;
min-height: 60px;
display: block !important;
visibility: visible !important;
}
#typingInput {
width: 300px;
padding: 10px;
font-size: 18px;
margin-right: 10px;
}
.typing-word {
font-size: 32px;
font-weight: bold;
text-align: center;
padding: 20px;
margin: 20px 0;
background: #f5f5f5;
border-radius: 8px;
min-height: 50px;
display: block !important;
visibility: visible !important;
}
.typing-input-container {
text-align: center;
margin: 20px 0;
}
/* Fix student panel buttons */
#startGameBtn, #startTypingBtn {
display: inline-block;
margin: 10px;
padding: 12px 24px;
font-size: 1.1em;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
visibility: visible !important;
}
/* Ensure student panel visibility */
#studentPanel:not(.hidden) {
display: block !important;
}
/* Remove any conflicting hidden styles */
#startGameBtn:not(.hidden),
#startTypingBtn:not(.hidden) {
display: inline-block !important;
visibility: visible !important;
/* Responsive design */
@media (max-width: 768px) {
body {
padding: 1rem 0.5rem;
}
h1 {
font-size: 2rem;
}
#adminTabs {
flex-direction: column;
}
#adminTabs button {
flex: none;
}
.card, .adminTab, #loginPanel {
padding: 1rem;
}
#wordBox, .word-box, #typingPrompt {
font-size: 2rem;
padding: 2rem 1rem;
}
}