Major Changes: - 🪟 Popup Menu System: Converted slide-down navigation to draggable, minimizable popup window - ❓ Help Bubble: Added contextual help system for admin dashboard (accessible via ? bubble) - 🎨 Universal Theme Engine: All elements now properly match selected theme across all panels - 📱 Enhanced Tablet Support: Comprehensive touch gestures, handwriting recognition, and Apple Pencil optimization - 🖊️ Unified Write Mode: Combined handwriting and keyboard input with large canvas interface Technical Implementation: - Draggable popup with minimize/restore functionality and boundary constraints - Dynamic theme switching using CSS variables throughout all components - Help system with modal overlay and keyboard shortcuts (ESC to close) - Responsive design optimizations for mobile, tablet, and desktop - Comprehensive CSS overrides to eliminate hardcoded colors - JavaScript fixes for canvas stroke colors and input styling UI/UX Improvements: - Clean admin dashboard without instruction clutter - Professional popup interface with smooth animations - Better contrast and readability across all themes (default, playground, chalkboard, galaxy, nature) - Improved accessibility with proper focus management - Enhanced mobile responsiveness with touch-optimized controls Files Modified: - Menu system: tablet-menu.js, tablet-menu.css - Theme engine: styles.css, themes integration - Admin interface: index.html, ui.js - Input systems: game.js, unified-write.js, handwriting.js - Authentication: auth.js with tablet menu cleanup
422 lines
15 KiB
HTML
422 lines
15 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
<title>Tablet & Handwriting Test</title>
|
|
<style>
|
|
:root {
|
|
--primary: #3b82f6;
|
|
--primary-dark: #2563eb;
|
|
--secondary: #f1f5f9;
|
|
--success: #10b981;
|
|
--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);
|
|
}
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
|
background: var(--bg-secondary);
|
|
margin: 0;
|
|
padding: 2rem;
|
|
touch-action: manipulation;
|
|
}
|
|
|
|
.container {
|
|
max-width: 800px;
|
|
margin: 0 auto;
|
|
background: var(--bg-primary);
|
|
border-radius: 20px;
|
|
padding: 2rem;
|
|
box-shadow: var(--shadow-lg);
|
|
}
|
|
|
|
h1 {
|
|
text-align: center;
|
|
color: var(--primary);
|
|
font-size: 2.5rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.demo-section {
|
|
margin: 2rem 0;
|
|
padding: 1.5rem;
|
|
border: 2px solid var(--border);
|
|
border-radius: 12px;
|
|
}
|
|
|
|
.demo-section h2 {
|
|
color: var(--text-primary);
|
|
font-size: 1.5rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
/* Button styles */
|
|
button {
|
|
background: var(--primary);
|
|
color: white;
|
|
border: none;
|
|
padding: 1rem 2rem;
|
|
border-radius: 12px;
|
|
font-size: 1.125rem;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
min-height: 50px;
|
|
min-width: 120px;
|
|
margin: 0.5rem;
|
|
transition: all 0.2s ease;
|
|
touch-action: manipulation;
|
|
}
|
|
|
|
button:hover, button:active {
|
|
background: var(--primary-dark);
|
|
transform: translateY(-2px);
|
|
box-shadow: var(--shadow-lg);
|
|
}
|
|
|
|
/* Input mode toggle */
|
|
.input-mode-toggle {
|
|
display: flex;
|
|
background: var(--secondary);
|
|
border-radius: 12px;
|
|
padding: 0.5rem;
|
|
margin: 1.5rem auto;
|
|
max-width: 350px;
|
|
box-shadow: inset 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.input-mode-toggle button {
|
|
flex: 1;
|
|
padding: 1rem 1.5rem;
|
|
background: transparent;
|
|
color: var(--text-secondary);
|
|
font-size: 1.125rem;
|
|
min-height: 50px;
|
|
margin: 0;
|
|
box-shadow: none;
|
|
}
|
|
|
|
.input-mode-toggle button.active {
|
|
background: var(--bg-primary);
|
|
color: var(--primary);
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
|
}
|
|
|
|
/* Text input */
|
|
input[type="text"] {
|
|
width: 100%;
|
|
max-width: 500px;
|
|
font-size: 1.5rem;
|
|
text-align: center;
|
|
background: white;
|
|
color: var(--text-primary);
|
|
font-weight: 500;
|
|
letter-spacing: 0.5px;
|
|
min-height: 60px;
|
|
padding: 1.25rem;
|
|
border: 3px solid var(--primary);
|
|
border-radius: 12px;
|
|
margin: 1.5rem auto;
|
|
display: block;
|
|
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.2);
|
|
}
|
|
|
|
/* Canvas styles */
|
|
#handwritingCanvas {
|
|
border: 3px solid var(--primary);
|
|
border-radius: 12px;
|
|
background: white;
|
|
cursor: crosshair;
|
|
display: block;
|
|
margin: 1rem auto;
|
|
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
touch-action: none;
|
|
max-width: 100%;
|
|
}
|
|
|
|
.canvas-controls {
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: 1rem;
|
|
margin: 1rem 0;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.canvas-controls button {
|
|
padding: 0.75rem 1.5rem;
|
|
font-size: 1rem;
|
|
min-width: 100px;
|
|
}
|
|
|
|
.clear-btn {
|
|
background: var(--error) !important;
|
|
}
|
|
|
|
.recognize-btn {
|
|
background: var(--success) !important;
|
|
}
|
|
|
|
.recognition-status {
|
|
margin: 1rem auto;
|
|
padding: 1rem;
|
|
border-radius: 8px;
|
|
text-align: center;
|
|
max-width: 400px;
|
|
}
|
|
|
|
.recognition-status.success {
|
|
background: #f0fdf4;
|
|
color: var(--success);
|
|
border: 2px solid var(--success);
|
|
}
|
|
|
|
.recognition-status.error {
|
|
background: #fef2f2;
|
|
color: var(--error);
|
|
border: 2px solid var(--error);
|
|
}
|
|
|
|
.recognized-text {
|
|
background: var(--secondary);
|
|
border-radius: 8px;
|
|
padding: 1rem;
|
|
margin: 1rem auto;
|
|
font-size: 1.25rem;
|
|
font-weight: 600;
|
|
text-align: center;
|
|
max-width: 400px;
|
|
min-height: 50px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border: 2px solid var(--border);
|
|
}
|
|
|
|
.recognized-text.has-content {
|
|
background: var(--bg-primary);
|
|
border-color: var(--primary);
|
|
color: var(--primary);
|
|
}
|
|
|
|
.handwriting-container {
|
|
background: var(--bg-primary);
|
|
border-radius: 16px;
|
|
padding: 2rem;
|
|
margin: 2rem auto;
|
|
box-shadow: 0 8px 25px rgba(0,0,0,0.1);
|
|
border: 2px solid var(--border);
|
|
max-width: 700px;
|
|
text-align: center;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>📱 Tablet & Handwriting Test</h1>
|
|
|
|
<div class="demo-section">
|
|
<h2>🎮 Spelling Game Interface</h2>
|
|
|
|
<!-- Word Display -->
|
|
<div style="font-size: 3rem; font-weight: 700; text-align: center; padding: 2rem; margin: 2rem auto; max-width: 600px; background: linear-gradient(135deg, #ffffff 0%, #f1f5f9 100%); border: 3px solid var(--primary); border-radius: 20px; color: var(--primary); box-shadow: var(--shadow-lg); min-height: 120px; display: flex; align-items: center; justify-content: center; letter-spacing: 2px;">
|
|
EXAMPLE
|
|
</div>
|
|
|
|
<!-- Input Mode Toggle -->
|
|
<div class="input-mode-toggle">
|
|
<button class="active" data-mode="typing" onclick="toggleMode('typing')">
|
|
⌨️ Type
|
|
</button>
|
|
<button data-mode="handwriting" onclick="toggleMode('handwriting')">
|
|
✏️ Write
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Typing Input -->
|
|
<input type="text" id="typingInput" placeholder="Type the word you saw..." style="display: block;">
|
|
|
|
<!-- Handwriting Container -->
|
|
<div id="handwritingContainer" class="handwriting-container" style="display: none;">
|
|
<h3>✏️ Write the word you saw:</h3>
|
|
|
|
<!-- Canvas -->
|
|
<canvas id="handwritingCanvas" width="600" height="250" style="width: 100%; max-width: 600px; height: 250px;"></canvas>
|
|
|
|
<!-- Controls -->
|
|
<div class="canvas-controls">
|
|
<button onclick="clearCanvas()" class="clear-btn">🗑️ Clear</button>
|
|
<button onclick="recognizeText()" class="recognize-btn">🔍 Recognize</button>
|
|
</div>
|
|
|
|
<!-- Status -->
|
|
<div id="recognitionStatus" class="recognition-status" style="display: none;"></div>
|
|
|
|
<!-- Recognized Text -->
|
|
<div id="recognizedText" class="recognized-text">Draw to see recognized text here</div>
|
|
</div>
|
|
|
|
<button onclick="submitAnswer()" style="background: var(--success); font-size: 1.25rem; padding: 1.25rem 2.5rem; margin-top: 1rem;">Submit Answer</button>
|
|
</div>
|
|
|
|
<div class="demo-section">
|
|
<h2>📊 Test Results</h2>
|
|
<div id="testResults" style="padding: 1rem; background: var(--secondary); border-radius: 8px; font-family: monospace;">
|
|
Ready for testing...
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Simple handwriting canvas implementation
|
|
let canvas, ctx, isDrawing = false, strokes = [];
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
canvas = document.getElementById('handwritingCanvas');
|
|
ctx = canvas.getContext('2d');
|
|
|
|
// Set canvas properties
|
|
ctx.lineCap = 'round';
|
|
ctx.lineJoin = 'round';
|
|
ctx.strokeStyle = '#1e293b';
|
|
ctx.lineWidth = 3;
|
|
|
|
// Mouse events
|
|
canvas.addEventListener('mousedown', startDrawing);
|
|
canvas.addEventListener('mousemove', draw);
|
|
canvas.addEventListener('mouseup', stopDrawing);
|
|
|
|
// Touch events
|
|
canvas.addEventListener('touchstart', handleTouch);
|
|
canvas.addEventListener('touchmove', handleTouch);
|
|
canvas.addEventListener('touchend', stopDrawing);
|
|
});
|
|
|
|
function handleTouch(e) {
|
|
e.preventDefault();
|
|
const rect = canvas.getBoundingClientRect();
|
|
const scaleX = canvas.width / rect.width;
|
|
const scaleY = canvas.height / rect.height;
|
|
|
|
const touch = e.touches[0] || e.changedTouches[0];
|
|
const mouseEvent = new MouseEvent(
|
|
e.type === 'touchstart' ? 'mousedown' :
|
|
e.type === 'touchmove' ? 'mousemove' : 'mouseup',
|
|
{
|
|
clientX: touch.clientX,
|
|
clientY: touch.clientY
|
|
}
|
|
);
|
|
|
|
if (e.type === 'touchstart') startDrawing(mouseEvent);
|
|
else if (e.type === 'touchmove') draw(mouseEvent);
|
|
else stopDrawing();
|
|
}
|
|
|
|
function startDrawing(e) {
|
|
isDrawing = true;
|
|
const rect = canvas.getBoundingClientRect();
|
|
const scaleX = canvas.width / rect.width;
|
|
const scaleY = canvas.height / rect.height;
|
|
|
|
const x = (e.clientX - rect.left) * scaleX;
|
|
const y = (e.clientY - rect.top) * scaleY;
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(x, y);
|
|
}
|
|
|
|
function draw(e) {
|
|
if (!isDrawing) return;
|
|
|
|
const rect = canvas.getBoundingClientRect();
|
|
const scaleX = canvas.width / rect.width;
|
|
const scaleY = canvas.height / rect.height;
|
|
|
|
const x = (e.clientX - rect.left) * scaleX;
|
|
const y = (e.clientY - rect.top) * scaleY;
|
|
|
|
ctx.lineTo(x, y);
|
|
ctx.stroke();
|
|
}
|
|
|
|
function stopDrawing() {
|
|
isDrawing = false;
|
|
}
|
|
|
|
function clearCanvas() {
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
document.getElementById('recognizedText').textContent = 'Draw to see recognized text here';
|
|
document.getElementById('recognizedText').className = 'recognized-text';
|
|
updateTestResults('Canvas cleared');
|
|
}
|
|
|
|
function recognizeText() {
|
|
// Simple mock recognition
|
|
const mockWords = ['EXAMPLE', 'TEST', 'HELLO', 'WORLD', 'TABLET'];
|
|
const recognized = mockWords[Math.floor(Math.random() * mockWords.length)];
|
|
|
|
document.getElementById('recognizedText').textContent = recognized;
|
|
document.getElementById('recognizedText').className = 'recognized-text has-content';
|
|
|
|
updateTestResults(`Recognized: "${recognized}"`);
|
|
}
|
|
|
|
function toggleMode(mode) {
|
|
const buttons = document.querySelectorAll('.input-mode-toggle button');
|
|
const typingInput = document.getElementById('typingInput');
|
|
const handwritingContainer = document.getElementById('handwritingContainer');
|
|
|
|
buttons.forEach(btn => {
|
|
btn.classList.remove('active');
|
|
if (btn.dataset.mode === mode) {
|
|
btn.classList.add('active');
|
|
}
|
|
});
|
|
|
|
if (mode === 'typing') {
|
|
typingInput.style.display = 'block';
|
|
handwritingContainer.style.display = 'none';
|
|
updateTestResults('Switched to typing mode');
|
|
} else {
|
|
typingInput.style.display = 'none';
|
|
handwritingContainer.style.display = 'block';
|
|
updateTestResults('Switched to handwriting mode');
|
|
}
|
|
}
|
|
|
|
function submitAnswer() {
|
|
const typingInput = document.getElementById('typingInput');
|
|
const handwritingContainer = document.getElementById('handwritingContainer');
|
|
const recognizedText = document.getElementById('recognizedText');
|
|
|
|
let answer = '';
|
|
if (handwritingContainer.style.display !== 'none') {
|
|
answer = recognizedText.textContent;
|
|
if (answer === 'Draw to see recognized text here') answer = '';
|
|
} else {
|
|
answer = typingInput.value;
|
|
}
|
|
|
|
updateTestResults(`Submitted answer: "${answer}"`);
|
|
}
|
|
|
|
function updateTestResults(message) {
|
|
const results = document.getElementById('testResults');
|
|
const timestamp = new Date().toLocaleTimeString();
|
|
results.innerHTML += `[${timestamp}] ${message}<br>`;
|
|
results.scrollTop = results.scrollHeight;
|
|
}
|
|
|
|
// Initial test
|
|
updateTestResults('Tablet and handwriting test interface loaded');
|
|
updateTestResults('Try switching between typing and handwriting modes');
|
|
updateTestResults('Test drawing on the canvas when in handwriting mode');
|
|
</script>
|
|
</body>
|
|
</html> |