feat: Complete tablet UI overhaul with popup menu system and help bubble
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
This commit is contained in:
1 parent
0b9d46fed1
commit
8d37038177
24 files changed
+9506
-126
No files matched your search
@@ -0,0 +1,422 @@
|
||||
<!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>
|
||||
Reference in new issue
Block a user