major update
This commit is contained in:
1 parent
6029d6f244
commit
474235f94d
8 files changed
+892
-181
No files matched your search
@@ -1,109 +1,359 @@
|
|||||||
# 📝 Spelling App
|
# 📝 Modern Spelling & Typing Practice App
|
||||||
|
|
||||||
A lightweight, multi-user spelling practice app with admin controls, customizable themes, and per-user word list management. Built with **Node.js**, **Express**, and **Docker**.
|
A comprehensive educational application featuring spelling practice, typing exercises, and Bible verse typing with mode## ⚙️ Features Overview
|
||||||
---
|
|
||||||
## [Watch the video](https://www.youtube.com/watch?v=K29zCFZPEjE)
|
### 🎓 St### 👨💼 Admin Panel Featuresde---
|
||||||
[](https://www.youtube.com/watch?v=K29zCFZPEjE)
|
|
||||||
|
## 🔧 Configuration
|
||||||
|
|
||||||
|
## 🛡️ Security Features## Environment VariablestivitiesUI design. Built with **Node.js**, **Express**, and optimized for Docker deployment.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🚀 Quick Start (Docker Compose)
|
## [Watch the Demo Video](https://www.youtube.com/watch?v=K29zCFZPEjE)
|
||||||
|
[](https://www.youtube.com/watch?v=K29zCFZPEjE)
|
||||||
|
|
||||||
Clone and run from anywhere using Docker Compose:
|
---
|
||||||
|
|
||||||
|
## 🚀 Installation Options
|
||||||
|
|
||||||
|
### Option 1: Docker (Recommended)
|
||||||
|
|
||||||
|
#### Quick Start with Docker Compose
|
||||||
```bash
|
```bash
|
||||||
git clone https://git.necloud.us/nmemmert/Spelling.git
|
git clone https://git.necloud.us/nmemmert/Spelling.git
|
||||||
cd Spelling
|
cd Spelling
|
||||||
docker-compose up --build
|
docker-compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
Then visit: 👉 http://localhost:3000
|
#### Using Pre-built Container
|
||||||
|
```bash
|
||||||
|
# Pull and run the latest container
|
||||||
|
docker run -d \
|
||||||
|
--name spelling-app \
|
||||||
|
-p 3000:3000 \
|
||||||
|
-v ./data:/app/data \
|
||||||
|
--restart unless-stopped \
|
||||||
|
git.necloud.us/nmemmert/spelling:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Custom Docker Compose
|
||||||
|
```yaml
|
||||||
|
version: "3.9"
|
||||||
|
services:
|
||||||
|
spelling-app:
|
||||||
|
image: git.necloud.us/nmemmert/spelling:latest
|
||||||
|
container_name: spelling-app
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
volumes:
|
||||||
|
- ./data:/app/data
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
```
|
||||||
|
|
||||||
## 👥 Default Login
|
### Option 2: CasaOS Installation
|
||||||
|
|
||||||
| Username | Password | Role |
|
#### Method 1: One-Click Install (Recommended)
|
||||||
|-----------|----------|--------|
|
1. **Open CasaOS Dashboard** in your web browser
|
||||||
| admin1 | password | Admin |
|
2. Navigate to **App Store** → **Custom Install**
|
||||||
| student1 | 123456 | Student|
|
3. **Paste this complete Docker Compose configuration:**
|
||||||
|
|
||||||
> You can add new users from the Admin Panel.
|
```yaml
|
||||||
|
name: spelling-practice-app
|
||||||
|
services:
|
||||||
|
spelling-app:
|
||||||
|
image: git.necloud.us/nmemmert/spelling:latest
|
||||||
|
container_name: spelling-practice
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
volumes:
|
||||||
|
- /DATA/AppData/spelling:/app/data
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
labels:
|
||||||
|
- "casaos.name=Spelling Practice"
|
||||||
|
- "casaos.icon=📝"
|
||||||
|
- "casaos.category=Education"
|
||||||
|
- "casaos.description=Modern spelling and typing practice application"
|
||||||
|
|
||||||
## ⚙️ Features
|
x-casaos:
|
||||||
|
author: NMemmert
|
||||||
|
category: Education
|
||||||
|
description:
|
||||||
|
en_us: "A comprehensive educational application featuring spelling practice, typing exercises, and Bible verse typing with modern UI design."
|
||||||
|
developer: "NMemmert"
|
||||||
|
icon: "📝"
|
||||||
|
screenshot_link: ""
|
||||||
|
tagline:
|
||||||
|
en_us: "Modern Spelling & Typing Practice"
|
||||||
|
thumbnail: ""
|
||||||
|
title:
|
||||||
|
en_us: "Spelling Practice App"
|
||||||
|
tips: {}
|
||||||
|
port_map: "3000"
|
||||||
|
```
|
||||||
|
|
||||||
# 🎮 Student-Facing Features
|
4. **Click Install** and wait for deployment to complete
|
||||||
|
5. **Access the app** via CasaOS Dashboard or directly at `http://your-casaos-ip:3000`
|
||||||
|
|
||||||
- **Word Game Session**
|
> **💡 Tip**: The app will automatically appear in your CasaOS dashboard with the 📝 icon after installation
|
||||||
Timed word presentation with answer input and summary feedback
|
|
||||||
|
|
||||||
- **Retry Missed Words**
|
#### Method 2: Manual CasaOS Installation
|
||||||
Quick review round for any words answered incorrectly
|
1. **Access CasaOS Terminal**:
|
||||||
|
- Open CasaOS Dashboard
|
||||||
|
- Go to **Terminal** or **SSH** into your CasaOS system
|
||||||
|
|
||||||
- **Result Summary Panel**
|
2. **Create application directory:**
|
||||||
Shows score, accuracy percentage, and answers with color-coded feedback
|
```bash
|
||||||
|
mkdir -p /DATA/AppData/spelling
|
||||||
|
cd /DATA/AppData/spelling
|
||||||
|
```
|
||||||
|
|
||||||
- **🎖 Badge Display Section**
|
3. **Create docker-compose.yml file:**
|
||||||
Earned badges appear after each session based on milestone triggers
|
```bash
|
||||||
|
nano docker-compose.yml
|
||||||
|
```
|
||||||
|
|
||||||
- **Responsive Design**
|
4. **Add this configuration and save:**
|
||||||
Touch-friendly interface optimized across devices
|
```yaml
|
||||||
|
version: "3.9"
|
||||||
|
services:
|
||||||
|
spelling-app:
|
||||||
|
image: git.necloud.us/nmemmert/spelling:latest
|
||||||
|
container_name: spelling-practice
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
volumes:
|
||||||
|
- /DATA/AppData/spelling:/app/data
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Deploy the application:**
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Verify deployment:**
|
||||||
|
```bash
|
||||||
|
docker ps | grep spelling-practice
|
||||||
|
```
|
||||||
|
|
||||||
|
7. **Add to CasaOS Dashboard** (Optional):
|
||||||
|
- Go to CasaOS **Settings** → **Custom Apps**
|
||||||
|
- Click **Add Custom App**
|
||||||
|
- **Name**: "Spelling Practice"
|
||||||
|
- **URL**: `http://localhost:3000`
|
||||||
|
- **Icon**: 📝 (or upload custom icon)
|
||||||
|
- **Category**: Education
|
||||||
|
|
||||||
|
#### CasaOS-Specific Notes:
|
||||||
|
- **Data Location**: All user data is stored in `/DATA/AppData/spelling/`
|
||||||
|
- **Automatic Restart**: Container will restart automatically with CasaOS
|
||||||
|
- **Port Access**: App will be accessible on port 3000
|
||||||
|
- **Updates**: Use CasaOS container management to update the image
|
||||||
|
- **Backups**: Data directory can be backed up through CasaOS file manager
|
||||||
|
|
||||||
|
#### Troubleshooting CasaOS Installation:
|
||||||
|
```bash
|
||||||
|
# Check if container is running
|
||||||
|
docker ps
|
||||||
|
|
||||||
|
# View container logs
|
||||||
|
docker logs spelling-practice
|
||||||
|
|
||||||
|
# Restart container if needed
|
||||||
|
docker restart spelling-practice
|
||||||
|
|
||||||
|
# Check port availability
|
||||||
|
netstat -tlnp | grep :3000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 3: Manual Installation
|
||||||
|
```bash
|
||||||
|
# Clone repository
|
||||||
|
git clone https://git.necloud.us/nmemmert/Spelling.git
|
||||||
|
cd Spelling
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Start application
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# 📋 Admin Panel Features
|
## 🌐 Access & Default Credentials
|
||||||
|
|
||||||
- **User Management**
|
After installation, access the application at:
|
||||||
Add and remove student accounts from the system
|
- **Local**: http://localhost:3000
|
||||||
|
- **CasaOS**: http://your-casaos-ip:3000
|
||||||
|
|
||||||
- **Word List Editor**
|
### Default Login Credentials
|
||||||
Customize spelling lists per student
|
|
||||||
|
|
||||||
- **Game Preview & Play Trigger**
|
| Username | Password | Role | Description |
|
||||||
Admins can test gameplay directly
|
|-----------|----------|---------|-------------|
|
||||||
|
| admin1 | password | Admin | Full access to admin panel |
|
||||||
|
| student1 | 123456 | Student | Access to practice activities |
|
||||||
|
|
||||||
- **Analytics Tab**
|
> 🔐 **Important**: Change default passwords immediately after first login via Admin Panel → Password Management
|
||||||
Aggregate stats like average scores, most missed words, and per-student breakdown
|
|
||||||
|
|
||||||
- **Badge Viewer Tab**
|
|
||||||
Shows every user's earned badges and lets admins inspect progress visually
|
|
||||||
|
|
||||||
- **Reports Tab**
|
|
||||||
Print-friendly HTML summaries per student with words, scores, and badges
|
|
||||||
|
|
||||||
- **Clipboard Copy Button**
|
|
||||||
Quick export for emailing or recordkeeping
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# 🔄 Server-Side Logic
|
## ⚙️ Features Overview
|
||||||
|
|
||||||
- **Structured Result Saving**
|
### � Student Activities
|
||||||
Stores user sessions with score, completion status, and answer accuracy
|
|
||||||
|
|
||||||
- **Badge Awarding & Storage**
|
#### 📝 **Spelling Practice**
|
||||||
Server tracks earned achievements in `badges.json`
|
- Interactive word-by-word spelling challenges
|
||||||
|
- Real-time feedback with visual indicators
|
||||||
|
- Customizable word lists per student
|
||||||
|
- Progress tracking and accuracy scoring
|
||||||
|
- Retry system for missed words
|
||||||
|
|
||||||
- **Safe Data Handling**
|
#### ⌨️ **Typing Practice**
|
||||||
Validates all inputs and formats before writing JSON
|
- Speed and accuracy typing exercises
|
||||||
|
- WPM (Words Per Minute) calculation
|
||||||
|
- Error highlighting and correction
|
||||||
|
- Progressive difficulty levels
|
||||||
|
- Performance analytics
|
||||||
|
|
||||||
|
#### 📖 **Bible Typing (ESV)**
|
||||||
|
- 15 popular Bible verses for typing practice
|
||||||
|
- Verse-by-verse comparison and accuracy analysis
|
||||||
|
- Word-level error detection and highlighting
|
||||||
|
- Spiritual growth through Scripture memorization
|
||||||
|
- Progress tracking for Bible study
|
||||||
|
|
||||||
|
### 🛠️ **Student Features**
|
||||||
|
- **Theme Selection**: Choose from multiple UI themes
|
||||||
|
- **Progress Dashboard**: View personal statistics and achievements
|
||||||
|
- **Badge System**: Earn badges for milestones and consistent practice
|
||||||
|
- **Responsive Design**: Optimized for desktop, tablet, and mobile
|
||||||
|
- **Clean Interface**: Modern, distraction-free learning environment
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# 🚀 Upcoming Features (in progress or proposed)
|
### 👨� Admin Panel Features
|
||||||
|
|
||||||
- **Multi-Session History Tracking**
|
#### 📊 **User Management**
|
||||||
Store multiple results per user for trends and performance graphs
|
- Add, edit, and remove student accounts
|
||||||
|
- Role-based access control (Admin/Student)
|
||||||
|
- Password management and reset capabilities
|
||||||
|
- User activity monitoring
|
||||||
|
|
||||||
- **Performance Trends Viewer**
|
#### 📝 **Content Management**
|
||||||
Line/bar charts to show score progression over time
|
- **Word List Editor**: Create and customize spelling lists per student
|
||||||
|
- **Bulk Import**: Upload word lists from text/CSV files
|
||||||
|
- **Category Organization**: Organize words by topics or difficulty
|
||||||
|
- **Preview Mode**: Test activities before assigning to students
|
||||||
|
|
||||||
- **Practice Streak Badges**
|
#### 📈 **Analytics & Reporting**
|
||||||
Awarded for consistent use across days
|
- **Performance Analytics**: Track student progress and trends
|
||||||
|
- **Detailed Reports**: Generate comprehensive progress reports
|
||||||
|
- **Export Options**: Download data for external analysis
|
||||||
|
- **Visual Charts**: Graphical representation of student performance
|
||||||
|
|
||||||
- **Thematic Word Lists**
|
#### 🏆 **Badge Management**
|
||||||
Organize challenges by topic (e.g. animals, tech, holidays)
|
- **Achievement System**: Configure milestone badges
|
||||||
|
- **Progress Tracking**: Monitor student achievements
|
||||||
|
- **Custom Badges**: Create custom rewards for specific goals
|
||||||
|
- **Badge Viewer**: Visual display of all earned achievements
|
||||||
|
|
||||||
- **Email Export Button**
|
#### ⚙️ **System Administration**
|
||||||
Opens a pre-filled `mailto:` with progress summary text
|
- **Password Management**: Change admin and student passwords
|
||||||
|
- **Security Controls**: Manage access permissions
|
||||||
|
- **Data Backup**: Export/import user data and settings
|
||||||
|
- **System Monitoring**: View application health and usage
|
||||||
|
|
||||||
- **PDF Export Option**
|
---
|
||||||
Convert report data into downloadable documents
|
|
||||||
|
## � Configuration
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
```bash
|
||||||
|
# Optional environment variables
|
||||||
|
NODE_ENV=production # Set to production for deployment
|
||||||
|
PORT=3000 # Application port (default: 3000)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Data Persistence
|
||||||
|
The application stores data in JSON files:
|
||||||
|
- `data/users.json` - User accounts and authentication
|
||||||
|
- `data/wordlists.json` - Custom word lists per student
|
||||||
|
- `data/results.json` - Student progress and results
|
||||||
|
- `data/badges.json` - Achievement and badge data
|
||||||
|
|
||||||
|
### Volume Mapping
|
||||||
|
For Docker installations, map the data directory:
|
||||||
|
```bash
|
||||||
|
-v /host/data/path:/app/data
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## �️ Security Features
|
||||||
|
|
||||||
|
- **Password Hashing**: SHA-256 encryption for all passwords
|
||||||
|
- **Role-Based Access**: Separate admin and student interfaces
|
||||||
|
- **Session Management**: Secure login/logout functionality
|
||||||
|
- **Input Validation**: Protection against common security vulnerabilities
|
||||||
|
- **Data Isolation**: Per-user data segregation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 Updates & Maintenance
|
||||||
|
|
||||||
|
### Updating the Application
|
||||||
|
```bash
|
||||||
|
# Pull latest image
|
||||||
|
docker pull git.necloud.us/nmemmert/spelling:latest
|
||||||
|
|
||||||
|
# Restart container
|
||||||
|
docker-compose down && docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backup Data
|
||||||
|
```bash
|
||||||
|
# Backup user data
|
||||||
|
docker cp spelling-app:/app/data ./backup-$(date +%Y%m%d)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logs & Troubleshooting
|
||||||
|
```bash
|
||||||
|
# View application logs
|
||||||
|
docker logs spelling-app
|
||||||
|
|
||||||
|
# Follow live logs
|
||||||
|
docker logs -f spelling-app
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤝 Support & Contributing
|
||||||
|
|
||||||
|
- **Issues**: Report bugs and feature requests
|
||||||
|
- **Documentation**: Comprehensive setup guides
|
||||||
|
- **Community**: Educational technology discussions
|
||||||
|
|
||||||
|
### Technical Specifications
|
||||||
|
- **Backend**: Node.js 18+ with Express.js
|
||||||
|
- **Frontend**: Vanilla JavaScript with modern CSS
|
||||||
|
- **Storage**: JSON-based file system
|
||||||
|
- **Container**: Multi-stage Docker build (293MB)
|
||||||
|
- **Security**: SHA-256 password hashing, role-based access
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
This project is designed for educational use and community deployment.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Built with ❤️ for educators and students everywhere*
|
||||||
+62
-1
@@ -13,6 +13,7 @@
|
|||||||
<script src="js/themes.js"></script>
|
<script src="js/themes.js"></script>
|
||||||
<script src="js/game.js"></script>
|
<script src="js/game.js"></script>
|
||||||
<script src="js/typing.js"></script>
|
<script src="js/typing.js"></script>
|
||||||
|
<script src="js/bible.js"></script>
|
||||||
<script src="js/main.js"></script>
|
<script src="js/main.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -313,13 +314,16 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="student-controls">
|
<div class="student-controls">
|
||||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; max-width: 600px; margin: 0 auto;">
|
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 1rem; max-width: 900px; margin: 0 auto;">
|
||||||
<button id="startGameBtn" onclick="startGame()" class="btn-success">
|
<button id="startGameBtn" onclick="startGame()" class="btn-success">
|
||||||
🎮 Spelling Game
|
🎮 Spelling Game
|
||||||
</button>
|
</button>
|
||||||
<button id="startTypingBtn" onclick="startTypingPractice()" class="btn-warning">
|
<button id="startTypingBtn" onclick="startTypingPractice()" class="btn-warning">
|
||||||
⌨️ Typing Practice
|
⌨️ Typing Practice
|
||||||
</button>
|
</button>
|
||||||
|
<button id="startBibleBtn" onclick="startBibleTyping()" class="btn-primary">
|
||||||
|
📖 Bible Typing
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -368,6 +372,63 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Bible Typing Section -->
|
||||||
|
<div class="hidden" id="bibleSection">
|
||||||
|
<div class="card">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
|
||||||
|
<h3>📖 Bible Typing (ESV)</h3>
|
||||||
|
<button onclick="backToStudentDashboard()" class="btn-secondary" style="padding: 0.5rem 1rem;">
|
||||||
|
← Back to Dashboard
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Verse Selection -->
|
||||||
|
<div style="margin-bottom: 2rem;">
|
||||||
|
<h4>Select a Bible Verse:</h4>
|
||||||
|
<select id="verseSelect" onchange="loadSelectedVerse()" style="width: 100%; max-width: 500px; margin-bottom: 1rem;">
|
||||||
|
<option value="">-- Choose a verse --</option>
|
||||||
|
</select>
|
||||||
|
<button onclick="getRandomVerse()" class="btn-secondary" style="margin-left: 1rem;">🎲 Random Verse</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Verse Display -->
|
||||||
|
<div id="bibleVerseDisplay" style="font-size: 1.2rem; line-height: 1.8; padding: 2rem; background: var(--bg-secondary); border-radius: var(--radius); margin-bottom: 2rem; min-height: 100px; display: flex; align-items: center; justify-content: center; text-align: center;">
|
||||||
|
Select a verse to begin typing practice
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Typing Area -->
|
||||||
|
<div style="text-align: center; margin: 2rem 0;">
|
||||||
|
<textarea id="bibleInput" placeholder="Type the verse here..." style="width: 100%; max-width: 700px; min-height: 120px; font-size: 1.1rem; line-height: 1.6;"></textarea>
|
||||||
|
<div style="margin-top: 1rem;">
|
||||||
|
<button onclick="checkBibleVerse()" class="btn-primary">✓ Check Typing</button>
|
||||||
|
<button onclick="clearBibleInput()" class="btn-secondary">🗑️ Clear</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Feedback -->
|
||||||
|
<div id="bibleFeedback" style="text-align: center; margin-top: 2rem;"></div>
|
||||||
|
|
||||||
|
<!-- Progress -->
|
||||||
|
<div id="bibleProgress" style="margin-top: 2rem;">
|
||||||
|
<h4>📊 Typing Progress</h4>
|
||||||
|
<div id="bibleStats" style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 1rem; margin-top: 1rem;">
|
||||||
|
<div style="text-align: center; padding: 1rem; background: var(--bg-secondary); border-radius: var(--radius);">
|
||||||
|
<div style="font-size: 2rem; font-weight: bold; color: var(--primary);" id="accuracyPercent">0%</div>
|
||||||
|
<div>Accuracy</div>
|
||||||
|
</div>
|
||||||
|
<div style="text-align: center; padding: 1rem; background: var(--bg-secondary); border-radius: var(--radius);">
|
||||||
|
<div style="font-size: 2rem; font-weight: bold; color: var(--success);" id="correctWords">0</div>
|
||||||
|
<div>Correct Words</div>
|
||||||
|
</div>
|
||||||
|
<div style="text-align: center; padding: 1rem; background: var(--bg-secondary); border-radius: var(--radius);">
|
||||||
|
<div style="font-size: 2rem; font-weight: bold; color: var(--error);" id="incorrectWords">0</div>
|
||||||
|
<div>Errors</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Toast notification -->
|
<!-- Toast notification -->
|
||||||
<div id="toast" class="toast"></div>
|
<div id="toast" class="toast"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,300 @@
|
|||||||
|
// Bible Typing Practice Module
|
||||||
|
// ESV Bible verses for typing practice
|
||||||
|
|
||||||
|
// Popular Bible verses for typing practice
|
||||||
|
const bibleVerses = [
|
||||||
|
{
|
||||||
|
reference: "John 3:16",
|
||||||
|
text: "For God so loved the world, that he gave his only Son, that whoever believes in him should not perish but have eternal life."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reference: "Philippians 4:13",
|
||||||
|
text: "I can do all things through him who strengthens me."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reference: "Romans 8:28",
|
||||||
|
text: "And we know that for those who love God all things work together for good, for those who are called according to his purpose."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reference: "Jeremiah 29:11",
|
||||||
|
text: "For I know the plans I have for you, declares the Lord, plans for welfare and not for evil, to give you a future and a hope."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reference: "Proverbs 3:5-6",
|
||||||
|
text: "Trust in the Lord with all your heart, and do not lean on your own understanding. In all your ways acknowledge him, and he will make straight your paths."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reference: "Psalm 23:1",
|
||||||
|
text: "The Lord is my shepherd; I shall not want."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reference: "Isaiah 40:31",
|
||||||
|
text: "But they who wait for the Lord shall renew their strength; they shall mount up with wings like eagles; they shall run and not be weary; they shall walk and not faint."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reference: "Matthew 28:19-20",
|
||||||
|
text: "Go therefore and make disciples of all nations, baptizing them in the name of the Father and of the Son and of the Holy Spirit, teaching them to observe all that I have commanded you. And behold, I am with you always, to the end of the age."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reference: "1 Corinthians 13:4-5",
|
||||||
|
text: "Love is patient and kind; love does not envy or boast; it is not arrogant or rude. It does not insist on its own way; it is not irritable or resentful."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reference: "Ephesians 2:8-9",
|
||||||
|
text: "For by grace you have been saved through faith. And this is not your own doing; it is the gift of God, not a result of works, so that no one may boast."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reference: "Psalm 119:105",
|
||||||
|
text: "Your word is a lamp to my feet and a light to my path."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reference: "Romans 12:2",
|
||||||
|
text: "Do not be conformed to this world, but be transformed by the renewal of your mind, that by testing you may discern what is the will of God, what is good and acceptable and perfect."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reference: "2 Timothy 3:16",
|
||||||
|
text: "All Scripture is breathed out by God and profitable for teaching, for reproof, for correction, and for training in righteousness."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reference: "Psalm 46:10",
|
||||||
|
text: "Be still, and know that I am God. I will be exalted among the nations, I will be exalted in the earth!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reference: "Matthew 6:33",
|
||||||
|
text: "But seek first the kingdom of God and his righteousness, and all these things will be added to you."
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Global state for Bible typing
|
||||||
|
let currentVerse = null;
|
||||||
|
let typingStartTime = null;
|
||||||
|
|
||||||
|
// Initialize Bible typing
|
||||||
|
window.startBibleTyping = function() {
|
||||||
|
console.log('📖 Starting Bible typing practice');
|
||||||
|
|
||||||
|
const user = JSON.parse(localStorage.getItem('loggedInUser'));
|
||||||
|
if (!user) {
|
||||||
|
alert('Please log in first');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide other sections
|
||||||
|
document.getElementById('studentPanel').style.display = 'none';
|
||||||
|
document.getElementById('gameSection').style.display = 'none';
|
||||||
|
document.getElementById('typingSection').style.display = 'none';
|
||||||
|
|
||||||
|
// Show Bible section
|
||||||
|
document.getElementById('bibleSection').classList.remove('hidden');
|
||||||
|
document.getElementById('bibleSection').style.display = 'block';
|
||||||
|
|
||||||
|
// Populate verse dropdown
|
||||||
|
populateVerseDropdown();
|
||||||
|
|
||||||
|
// Reset the interface
|
||||||
|
resetBibleInterface();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate the verse selection dropdown
|
||||||
|
function populateVerseDropdown() {
|
||||||
|
const select = document.getElementById('verseSelect');
|
||||||
|
select.innerHTML = '<option value="">-- Choose a verse --</option>';
|
||||||
|
|
||||||
|
bibleVerses.forEach((verse, index) => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = index;
|
||||||
|
option.textContent = verse.reference;
|
||||||
|
select.appendChild(option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load selected verse
|
||||||
|
window.loadSelectedVerse = function() {
|
||||||
|
const select = document.getElementById('verseSelect');
|
||||||
|
const index = parseInt(select.value);
|
||||||
|
|
||||||
|
if (isNaN(index) || index < 0 || index >= bibleVerses.length) {
|
||||||
|
resetBibleInterface();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentVerse = bibleVerses[index];
|
||||||
|
displayVerse();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get random verse
|
||||||
|
window.getRandomVerse = function() {
|
||||||
|
const randomIndex = Math.floor(Math.random() * bibleVerses.length);
|
||||||
|
currentVerse = bibleVerses[randomIndex];
|
||||||
|
|
||||||
|
// Update dropdown
|
||||||
|
document.getElementById('verseSelect').value = randomIndex;
|
||||||
|
|
||||||
|
displayVerse();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display the selected verse
|
||||||
|
function displayVerse() {
|
||||||
|
if (!currentVerse) return;
|
||||||
|
|
||||||
|
const display = document.getElementById('bibleVerseDisplay');
|
||||||
|
display.innerHTML = `
|
||||||
|
<div>
|
||||||
|
<div style="font-weight: bold; color: var(--primary); margin-bottom: 1rem; font-size: 1.3rem;">
|
||||||
|
${currentVerse.reference}
|
||||||
|
</div>
|
||||||
|
<div style="font-style: italic;">
|
||||||
|
"${currentVerse.text}"
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Clear input and reset stats
|
||||||
|
document.getElementById('bibleInput').value = '';
|
||||||
|
document.getElementById('bibleFeedback').innerHTML = '';
|
||||||
|
resetStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check typed verse against original
|
||||||
|
window.checkBibleVerse = function() {
|
||||||
|
if (!currentVerse) {
|
||||||
|
alert('Please select a verse first');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const typedText = document.getElementById('bibleInput').value.trim();
|
||||||
|
if (!typedText) {
|
||||||
|
alert('Please type something first');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalText = currentVerse.text;
|
||||||
|
const feedback = document.getElementById('bibleFeedback');
|
||||||
|
|
||||||
|
// Calculate accuracy
|
||||||
|
const { accuracy, correctWords, totalWords, errors } = calculateAccuracy(originalText, typedText);
|
||||||
|
|
||||||
|
// Update stats
|
||||||
|
updateStats(accuracy, correctWords, errors);
|
||||||
|
|
||||||
|
// Show feedback
|
||||||
|
if (accuracy === 100) {
|
||||||
|
feedback.innerHTML = `
|
||||||
|
<div style="color: var(--success); font-size: 1.2rem; font-weight: bold;">
|
||||||
|
🎉 Perfect! You typed the verse correctly!
|
||||||
|
</div>
|
||||||
|
<div style="margin-top: 1rem;">
|
||||||
|
<button onclick="getRandomVerse()" class="btn-primary">📖 Try Another Verse</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
feedback.innerHTML = `
|
||||||
|
<div style="color: var(--warning); font-size: 1.1rem; font-weight: bold;">
|
||||||
|
📝 ${accuracy.toFixed(1)}% Accuracy - Keep practicing!
|
||||||
|
</div>
|
||||||
|
<div style="margin-top: 1rem; color: var(--text-secondary);">
|
||||||
|
<div>Correct words: ${correctWords} / ${totalWords}</div>
|
||||||
|
<div>Errors: ${errors}</div>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top: 1rem;">
|
||||||
|
<button onclick="clearBibleInput()" class="btn-secondary">🔄 Try Again</button>
|
||||||
|
<button onclick="showDifferences()" class="btn-primary">👀 Show Differences</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate typing accuracy
|
||||||
|
function calculateAccuracy(original, typed) {
|
||||||
|
const originalWords = original.toLowerCase().split(/\s+/);
|
||||||
|
const typedWords = typed.toLowerCase().split(/\s+/);
|
||||||
|
|
||||||
|
let correctWords = 0;
|
||||||
|
let errors = 0;
|
||||||
|
const maxLength = Math.max(originalWords.length, typedWords.length);
|
||||||
|
|
||||||
|
for (let i = 0; i < maxLength; i++) {
|
||||||
|
const originalWord = originalWords[i] || '';
|
||||||
|
const typedWord = typedWords[i] || '';
|
||||||
|
|
||||||
|
if (originalWord === typedWord) {
|
||||||
|
correctWords++;
|
||||||
|
} else {
|
||||||
|
errors++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const accuracy = originalWords.length > 0 ? (correctWords / originalWords.length) * 100 : 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
accuracy,
|
||||||
|
correctWords,
|
||||||
|
totalWords: originalWords.length,
|
||||||
|
errors
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update statistics display
|
||||||
|
function updateStats(accuracy, correctWords, errors) {
|
||||||
|
document.getElementById('accuracyPercent').textContent = accuracy.toFixed(1) + '%';
|
||||||
|
document.getElementById('correctWords').textContent = correctWords;
|
||||||
|
document.getElementById('incorrectWords').textContent = errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset statistics
|
||||||
|
function resetStats() {
|
||||||
|
document.getElementById('accuracyPercent').textContent = '0%';
|
||||||
|
document.getElementById('correctWords').textContent = '0';
|
||||||
|
document.getElementById('incorrectWords').textContent = '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show word-by-word differences
|
||||||
|
window.showDifferences = function() {
|
||||||
|
if (!currentVerse) return;
|
||||||
|
|
||||||
|
const typedText = document.getElementById('bibleInput').value.trim();
|
||||||
|
const originalWords = currentVerse.text.split(/\s+/);
|
||||||
|
const typedWords = typedText.split(/\s+/);
|
||||||
|
|
||||||
|
let comparisonHTML = '<div style="text-align: left; margin-top: 1rem; padding: 1rem; background: var(--bg-secondary); border-radius: var(--radius);">';
|
||||||
|
comparisonHTML += '<h4>Word-by-word comparison:</h4><div style="line-height: 2;">';
|
||||||
|
|
||||||
|
const maxLength = Math.max(originalWords.length, typedWords.length);
|
||||||
|
|
||||||
|
for (let i = 0; i < maxLength; i++) {
|
||||||
|
const originalWord = originalWords[i] || '';
|
||||||
|
const typedWord = typedWords[i] || '';
|
||||||
|
|
||||||
|
if (originalWord.toLowerCase() === typedWord.toLowerCase()) {
|
||||||
|
comparisonHTML += `<span style="background: var(--success); color: white; padding: 2px 6px; margin: 2px; border-radius: 4px;">${originalWord}</span> `;
|
||||||
|
} else if (typedWord === '') {
|
||||||
|
comparisonHTML += `<span style="background: var(--error); color: white; padding: 2px 6px; margin: 2px; border-radius: 4px; text-decoration: line-through;">${originalWord}</span> `;
|
||||||
|
} else {
|
||||||
|
comparisonHTML += `<span style="background: var(--warning); color: white; padding: 2px 6px; margin: 2px; border-radius: 4px;">${originalWord} (typed: ${typedWord})</span> `;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
comparisonHTML += '</div></div>';
|
||||||
|
|
||||||
|
document.getElementById('bibleFeedback').innerHTML += comparisonHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear Bible input
|
||||||
|
window.clearBibleInput = function() {
|
||||||
|
document.getElementById('bibleInput').value = '';
|
||||||
|
document.getElementById('bibleFeedback').innerHTML = '';
|
||||||
|
resetStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset Bible interface
|
||||||
|
function resetBibleInterface() {
|
||||||
|
currentVerse = null;
|
||||||
|
document.getElementById('verseSelect').value = '';
|
||||||
|
document.getElementById('bibleVerseDisplay').innerHTML = 'Select a verse to begin typing practice';
|
||||||
|
document.getElementById('bibleInput').value = '';
|
||||||
|
document.getElementById('bibleFeedback').innerHTML = '';
|
||||||
|
resetStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export reset function for use by main dashboard
|
||||||
|
window.resetBibleInterface = resetBibleInterface;
|
||||||
+2
-1
@@ -83,9 +83,10 @@ window.startGame = async function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Hide student dashboard and typing practice
|
// Hide student dashboard and other sections
|
||||||
document.getElementById('studentPanel').style.display = 'none';
|
document.getElementById('studentPanel').style.display = 'none';
|
||||||
document.getElementById('typingSection').style.display = 'none';
|
document.getElementById('typingSection').style.display = 'none';
|
||||||
|
document.getElementById('bibleSection').style.display = 'none';
|
||||||
|
|
||||||
// Show game section
|
// Show game section
|
||||||
document.getElementById('gameSection').classList.remove('hidden');
|
document.getElementById('gameSection').classList.remove('hidden');
|
||||||
|
|||||||
+58
-34
@@ -1,14 +1,14 @@
|
|||||||
document.addEventListener("DOMContentLoaded", async () => {
|
document.addEventListener("DOMContentLoaded", async () => {
|
||||||
console.log('🚀 Application starting...');
|
console.log('🚀 Application starting...');
|
||||||
|
|
||||||
// Load users for admin functionality
|
// Ensure all panels are hidden initially
|
||||||
try {
|
document.getElementById('adminPanel')?.classList.add('hidden');
|
||||||
await loadUsers();
|
document.getElementById('studentPanel')?.classList.add('hidden');
|
||||||
displayUserDropdown();
|
document.getElementById('gameSection')?.classList.add('hidden');
|
||||||
populateWordUserDropdown();
|
document.getElementById('typingSection')?.classList.add('hidden');
|
||||||
} catch (e) {
|
document.getElementById('bibleSection')?.classList.add('hidden');
|
||||||
console.warn('Could not load users (normal if not admin):', e);
|
document.getElementById('nav')?.classList.add('hidden');
|
||||||
}
|
document.getElementById('roleBanner')?.classList.add('hidden');
|
||||||
|
|
||||||
// Restore saved theme
|
// Restore saved theme
|
||||||
applySavedTheme();
|
applySavedTheme();
|
||||||
@@ -19,39 +19,50 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
try {
|
try {
|
||||||
const user = JSON.parse(savedUser);
|
const user = JSON.parse(savedUser);
|
||||||
console.log('👤 Restoring session for:', user.username);
|
console.log('👤 Restoring session for:', user.username);
|
||||||
|
|
||||||
|
// Load users for admin functionality only if user is admin
|
||||||
|
if (user.role === 'admin') {
|
||||||
|
try {
|
||||||
|
await loadUsers();
|
||||||
|
displayUserDropdown();
|
||||||
|
populateWordUserDropdown();
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Could not load admin data:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setupSession(user);
|
setupSession(user);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error restoring session:', e);
|
console.error('Error restoring session:', e);
|
||||||
localStorage.removeItem("loggedInUser");
|
localStorage.removeItem("loggedInUser");
|
||||||
|
showLoginOnly();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('👤 No saved session found');
|
console.log('👤 No saved session found');
|
||||||
|
showLoginOnly();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Function to show only the login panel
|
||||||
|
function showLoginOnly() {
|
||||||
|
// Hide all panels
|
||||||
|
document.getElementById('adminPanel')?.classList.add('hidden');
|
||||||
|
document.getElementById('studentPanel')?.classList.add('hidden');
|
||||||
|
document.getElementById('gameSection')?.classList.add('hidden');
|
||||||
|
document.getElementById('typingSection')?.classList.add('hidden');
|
||||||
|
document.getElementById('bibleSection')?.classList.add('hidden');
|
||||||
|
document.getElementById('nav')?.classList.add('hidden');
|
||||||
|
document.getElementById('roleBanner')?.classList.add('hidden');
|
||||||
|
|
||||||
|
// Show login panel
|
||||||
|
document.getElementById('loginPanel')?.classList.remove('hidden');
|
||||||
|
|
||||||
// Focus on login form
|
// Focus on login form
|
||||||
const usernameField = document.getElementById('username');
|
const usernameField = document.getElementById('username');
|
||||||
if (usernameField) {
|
if (usernameField) {
|
||||||
setTimeout(() => usernameField.focus(), 100);
|
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) {
|
|
||||||
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');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Function to return to student dashboard
|
// Function to return to student dashboard
|
||||||
window.backToStudentDashboard = function() {
|
window.backToStudentDashboard = function() {
|
||||||
@@ -62,17 +73,30 @@ window.backToStudentDashboard = function() {
|
|||||||
document.getElementById('gameSection').style.display = 'none';
|
document.getElementById('gameSection').style.display = 'none';
|
||||||
document.getElementById('typingSection').classList.add('hidden');
|
document.getElementById('typingSection').classList.add('hidden');
|
||||||
document.getElementById('typingSection').style.display = 'none';
|
document.getElementById('typingSection').style.display = 'none';
|
||||||
|
document.getElementById('bibleSection').classList.add('hidden');
|
||||||
|
document.getElementById('bibleSection').style.display = 'none';
|
||||||
|
|
||||||
// Show student panel
|
// Show student panel
|
||||||
document.getElementById('studentPanel').classList.remove('hidden');
|
document.getElementById('studentPanel').classList.remove('hidden');
|
||||||
document.getElementById('studentPanel').style.display = 'block';
|
document.getElementById('studentPanel').style.display = 'block';
|
||||||
|
|
||||||
// Reset any game/typing state
|
// Reset any game/typing state
|
||||||
document.getElementById('userInput').value = '';
|
const userInput = document.getElementById('userInput');
|
||||||
document.getElementById('typingInput').value = '';
|
const typingInput = document.getElementById('typingInput');
|
||||||
document.getElementById('wordBox').textContent = '';
|
const wordBox = document.getElementById('wordBox');
|
||||||
document.getElementById('typingPrompt').textContent = 'Get ready to type...';
|
const typingPrompt = document.getElementById('typingPrompt');
|
||||||
document.getElementById('typingFeedback').textContent = '';
|
const typingFeedback = document.getElementById('typingFeedback');
|
||||||
|
|
||||||
|
if (userInput) userInput.value = '';
|
||||||
|
if (typingInput) typingInput.value = '';
|
||||||
|
if (wordBox) wordBox.textContent = '';
|
||||||
|
if (typingPrompt) typingPrompt.textContent = 'Get ready to type...';
|
||||||
|
if (typingFeedback) typingFeedback.textContent = '';
|
||||||
|
|
||||||
|
// Reset Bible typing state
|
||||||
|
if (typeof window.resetBibleInterface === 'function') {
|
||||||
|
window.resetBibleInterface();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to change theme (accessible to students)
|
// Function to change theme (accessible to students)
|
||||||
|
|||||||
+2
-1
@@ -12,10 +12,11 @@ window.startTypingPractice = async function() {
|
|||||||
const user = JSON.parse(localStorage.getItem('loggedInUser'));
|
const user = JSON.parse(localStorage.getItem('loggedInUser'));
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
|
||||||
// Hide student dashboard and game section
|
// Hide student dashboard and other sections
|
||||||
document.getElementById('studentPanel').style.display = 'none';
|
document.getElementById('studentPanel').style.display = 'none';
|
||||||
document.getElementById('gameSection').classList.add('hidden');
|
document.getElementById('gameSection').classList.add('hidden');
|
||||||
document.getElementById('gameSection').style.display = 'none';
|
document.getElementById('gameSection').style.display = 'none';
|
||||||
|
document.getElementById('bibleSection').style.display = 'none';
|
||||||
document.getElementById('typingSection').classList.remove('hidden');
|
document.getElementById('typingSection').classList.remove('hidden');
|
||||||
document.getElementById('typingSection').style.display = 'block';
|
document.getElementById('typingSection').style.display = 'block';
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -62,11 +62,13 @@ function showStudent() {
|
|||||||
// Ensure buttons are visible and functional
|
// Ensure buttons are visible and functional
|
||||||
const gameBtn = document.getElementById('startGameBtn');
|
const gameBtn = document.getElementById('startGameBtn');
|
||||||
const typingBtn = document.getElementById('startTypingBtn');
|
const typingBtn = document.getElementById('startTypingBtn');
|
||||||
|
const bibleBtn = document.getElementById('startBibleBtn');
|
||||||
|
|
||||||
if (gameBtn && typingBtn) {
|
if (gameBtn && typingBtn && bibleBtn) {
|
||||||
gameBtn.classList.remove('hidden');
|
gameBtn.classList.remove('hidden');
|
||||||
typingBtn.classList.remove('hidden');
|
typingBtn.classList.remove('hidden');
|
||||||
console.log("✅ Student buttons are now visible");
|
bibleBtn.classList.remove('hidden');
|
||||||
|
console.log("✅ All student buttons are now visible");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update student theme selector to match current theme
|
// Update student theme selector to match current theme
|
||||||
|
|||||||
+149
-77
@@ -97,7 +97,7 @@ textarea {
|
|||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Buttons */
|
/* Buttons - Global enhanced readability */
|
||||||
button {
|
button {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -105,76 +105,116 @@ button {
|
|||||||
padding: 0.75rem 1.5rem;
|
padding: 0.75rem 1.5rem;
|
||||||
margin: 0.25rem;
|
margin: 0.25rem;
|
||||||
background: var(--primary);
|
background: var(--primary);
|
||||||
color: white !important;
|
color: #ffffff !important;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: 500;
|
font-weight: 600;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: var(--transition);
|
transition: var(--transition);
|
||||||
box-shadow: var(--shadow);
|
box-shadow: var(--shadow);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||||
|
min-height: 44px;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button:hover {
|
||||||
background: var(--primary-dark);
|
background: var(--primary-dark);
|
||||||
color: white !important;
|
color: #ffffff !important;
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
box-shadow: var(--shadow-lg);
|
box-shadow: var(--shadow-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
button:active {
|
button:active {
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
color: white !important;
|
color: #ffffff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:disabled {
|
button:disabled {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
transform: none;
|
transform: none;
|
||||||
color: white !important;
|
color: #ffffff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Button variants */
|
/* Button variants with enhanced readability */
|
||||||
.btn-secondary {
|
.btn-secondary {
|
||||||
background: var(--secondary);
|
background: #f1f5f9 !important;
|
||||||
color: var(--text-primary) !important;
|
color: #1e293b !important;
|
||||||
|
border: 2px solid #e2e8f0 !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
text-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary:hover {
|
.btn-secondary:hover {
|
||||||
background: var(--border);
|
background: #e2e8f0 !important;
|
||||||
color: var(--text-primary) !important;
|
color: #1e293b !important;
|
||||||
|
border-color: #cbd5e1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: #3b82f6 !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
border: 2px solid transparent !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3) !important;
|
||||||
|
min-height: 44px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: #2563eb !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
transform: translateY(-1px) !important;
|
||||||
|
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-success {
|
.btn-success {
|
||||||
background: var(--success);
|
background: #10b981 !important;
|
||||||
color: white !important;
|
color: #ffffff !important;
|
||||||
|
border: 2px solid transparent !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3) !important;
|
||||||
|
min-height: 44px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-success:hover {
|
.btn-success:hover {
|
||||||
background: #059669;
|
background: #059669 !important;
|
||||||
color: white !important;
|
color: #ffffff !important;
|
||||||
|
transform: translateY(-1px) !important;
|
||||||
|
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-warning {
|
.btn-warning {
|
||||||
background: var(--warning);
|
background: #f59e0b !important;
|
||||||
color: white !important;
|
color: #ffffff !important;
|
||||||
|
border: 2px solid transparent !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3) !important;
|
||||||
|
min-height: 44px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-warning:hover {
|
.btn-warning:hover {
|
||||||
background: #d97706;
|
background: #d97706 !important;
|
||||||
color: white !important;
|
color: #ffffff !important;
|
||||||
|
transform: translateY(-1px) !important;
|
||||||
|
box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-error {
|
.btn-error {
|
||||||
background: var(--error);
|
background: #ef4444 !important;
|
||||||
color: white !important;
|
color: #ffffff !important;
|
||||||
|
border: 2px solid transparent !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3) !important;
|
||||||
|
min-height: 44px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-error:hover {
|
.btn-error:hover {
|
||||||
background: #dc2626;
|
background: #dc2626 !important;
|
||||||
color: white !important;
|
color: #ffffff !important;
|
||||||
|
transform: translateY(-1px) !important;
|
||||||
|
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Additional button contrast fixes */
|
/* Additional button contrast fixes */
|
||||||
@@ -204,81 +244,108 @@ button:disabled {
|
|||||||
background: #d97706 !important;
|
background: #d97706 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Back to dashboard button */
|
/* Bible typing button specific styling */
|
||||||
button[onclick="backToStudentDashboard()"] {
|
#startBibleBtn {
|
||||||
background: var(--secondary) !important;
|
background: var(--primary) !important;
|
||||||
color: var(--text-primary) !important;
|
|
||||||
border: 1px solid var(--border) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
button[onclick="backToStudentDashboard()"]:hover {
|
|
||||||
background: var(--border) !important;
|
|
||||||
color: var(--text-primary) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Theme-specific button fixes */
|
|
||||||
.theme-chalkboard button,
|
|
||||||
.theme-galaxy button {
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.3) !important;
|
|
||||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-chalkboard .btn-secondary,
|
|
||||||
.theme-galaxy .btn-secondary {
|
|
||||||
background: rgba(255, 255, 255, 0.1) !important;
|
|
||||||
color: white !important;
|
color: white !important;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.3) !important;
|
border: 2px solid rgba(255, 255, 255, 0.2) !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
min-height: 60px !important;
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
background: linear-gradient(135deg, #3b82f6, #2563eb) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-chalkboard .btn-secondary:hover,
|
#startBibleBtn:hover {
|
||||||
.theme-galaxy .btn-secondary:hover {
|
background: linear-gradient(135deg, #2563eb, #1d4ed8) !important;
|
||||||
background: rgba(255, 255, 255, 0.2) !important;
|
|
||||||
color: white !important;
|
color: white !important;
|
||||||
|
transform: translateY(-3px) !important;
|
||||||
|
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Cards and panels */
|
/* Bible typing interface styling with enhanced button readability */
|
||||||
.card {
|
#bibleSection textarea {
|
||||||
background: var(--bg-primary);
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||||
|
border: 2px solid var(--border);
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
padding: 2rem;
|
padding: 1rem;
|
||||||
margin: 1rem 0;
|
background: var(--bg-primary);
|
||||||
box-shadow: var(--shadow);
|
color: var(--text-primary);
|
||||||
border: 1px solid var(--border);
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
#bibleSection textarea:focus {
|
||||||
padding-bottom: 1rem;
|
border-color: var(--primary);
|
||||||
margin-bottom: 1rem;
|
box-shadow: 0 0 0 3px rgb(59 130 246 / 0.1);
|
||||||
border-bottom: 1px solid var(--border);
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Layout utilities */
|
#verseSelect {
|
||||||
.hidden {
|
max-width: 500px !important;
|
||||||
display: none !important;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flex {
|
/* Enhanced Bible typing button styles - Force override all button styling */
|
||||||
display: flex;
|
#bibleSection .btn-primary,
|
||||||
|
#bibleSection .btn-secondary,
|
||||||
|
#bibleFeedback .btn-primary,
|
||||||
|
#bibleFeedback .btn-secondary,
|
||||||
|
#bibleSection button,
|
||||||
|
#bibleFeedback button {
|
||||||
|
min-height: 44px !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
font-size: 1rem !important;
|
||||||
|
padding: 0.75rem 1.5rem !important;
|
||||||
|
border-radius: var(--radius) !important;
|
||||||
|
cursor: pointer !important;
|
||||||
|
transition: all 0.2s ease !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
display: inline-flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
gap: 0.5rem !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
border: none !important;
|
||||||
|
outline: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flex-col {
|
/* Bible section primary buttons (Check Typing, Show Differences) */
|
||||||
flex-direction: column;
|
#bibleSection .btn-primary,
|
||||||
|
#bibleFeedback .btn-primary {
|
||||||
|
background: #10b981 !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3) !important;
|
||||||
|
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3) !important;
|
||||||
|
border: 2px solid #10b981 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.items-center {
|
#bibleSection .btn-primary:hover,
|
||||||
align-items: center;
|
#bibleFeedback .btn-primary:hover {
|
||||||
|
background: #059669 !important;
|
||||||
|
color: #ffffff !important;
|
||||||
|
transform: translateY(-1px) !important;
|
||||||
|
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4) !important;
|
||||||
|
border: 2px solid #059669 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.justify-center {
|
/* Bible section secondary buttons (Clear, Random) */
|
||||||
justify-content: center;
|
#bibleSection .btn-secondary,
|
||||||
|
#bibleFeedback .btn-secondary {
|
||||||
|
background: #f1f5f9 !important;
|
||||||
|
color: #1e293b !important;
|
||||||
|
border: 2px solid #e2e8f0 !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
text-shadow: none !important;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gap-4 {
|
#bibleSection .btn-secondary:hover,
|
||||||
gap: 1rem;
|
#bibleFeedback .btn-secondary:hover {
|
||||||
}
|
background: #e2e8f0 !important;
|
||||||
|
color: #1e293b !important;
|
||||||
.text-center {
|
border: 2px solid #cbd5e1 !important;
|
||||||
text-align: center;
|
transform: translateY(-1px) !important;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Specific component styles */
|
/* Specific component styles */
|
||||||
@@ -372,6 +439,11 @@ button[onclick="backToStudentDashboard()"]:hover {
|
|||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Layout utilities */
|
||||||
|
.hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
.adminTab.hidden {
|
.adminTab.hidden {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user