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,609 @@
|
||||
/**
|
||||
* Native Tablet Handwriting Recognition Integration
|
||||
* Leverages platform-specific handwriting APIs for better accuracy
|
||||
*/
|
||||
|
||||
class NativeHandwritingRecognition {
|
||||
constructor() {
|
||||
this.isSupported = false;
|
||||
this.recognizer = null;
|
||||
this.canvas = null;
|
||||
this.ctx = null;
|
||||
this.strokes = [];
|
||||
this.currentStroke = [];
|
||||
this.isDrawing = false;
|
||||
this.isAppleDevice = false;
|
||||
this.applePencilDetected = false;
|
||||
|
||||
// Initialize recognition capabilities
|
||||
this.initializeRecognition();
|
||||
}
|
||||
|
||||
async initializeRecognition() {
|
||||
console.log('🔍 Checking for native handwriting support...');
|
||||
|
||||
// Check for different platform APIs
|
||||
this.isSupported = await this.detectHandwritingSupport();
|
||||
|
||||
if (this.isSupported) {
|
||||
console.log('✅ Native handwriting recognition available');
|
||||
await this.setupRecognizer();
|
||||
} else {
|
||||
console.log('⚠️ Native handwriting not available, using fallback');
|
||||
}
|
||||
}
|
||||
|
||||
async detectHandwritingSupport() {
|
||||
console.log('🔍 Detecting handwriting support on:', navigator.userAgent);
|
||||
|
||||
// Detect Apple Pencil / iOS devices specifically
|
||||
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) ||
|
||||
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
|
||||
const isSafari = navigator.userAgent.includes('Safari') && !navigator.userAgent.includes('Chrome');
|
||||
|
||||
if (isIOS) {
|
||||
console.log('🍎 iOS device detected - optimizing for Apple Pencil');
|
||||
this.isAppleDevice = true;
|
||||
}
|
||||
|
||||
// Check for Web Handwriting API (experimental - Edge/Chrome)
|
||||
if ('createHandwritingRecognizer' in navigator) {
|
||||
console.log('🖋️ Web Handwriting API detected in', this.getBrowserInfo());
|
||||
|
||||
// Test if it actually works
|
||||
try {
|
||||
const testQuery = await navigator.queryHandwritingRecognizer({ languages: ['en'] });
|
||||
if (testQuery) {
|
||||
console.log('✅ Web Handwriting API is functional');
|
||||
return true;
|
||||
} else {
|
||||
console.log('⚠️ Web Handwriting API exists but query failed');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('❌ Web Handwriting API test failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for Pointer Events API with pressure sensitivity (Windows tablets)
|
||||
if ('PointerEvent' in window) {
|
||||
console.log('👆 Pointer Events API detected - good for Windows tablets');
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for Touch Events with force (iPad, Apple Pencil, etc.)
|
||||
if ('TouchEvent' in window || 'ontouchstart' in window) {
|
||||
console.log('📱 Touch Events API detected');
|
||||
if (isIOS) {
|
||||
console.log('🍎 Apple Pencil support likely available');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for Windows Ink API (UWP apps only)
|
||||
if (typeof Windows !== 'undefined' && Windows.UI && Windows.UI.Input && Windows.UI.Input.Inking) {
|
||||
console.log('🖊️ Windows Ink API detected (UWP context)');
|
||||
return true;
|
||||
}
|
||||
|
||||
console.log('⚠️ No native handwriting APIs detected');
|
||||
return false;
|
||||
}
|
||||
|
||||
getBrowserInfo() {
|
||||
const ua = navigator.userAgent;
|
||||
if (ua.includes('Edg/')) return 'Microsoft Edge';
|
||||
if (ua.includes('Chrome/')) return 'Google Chrome';
|
||||
if (ua.includes('Firefox/')) return 'Mozilla Firefox';
|
||||
if (ua.includes('Safari/') && !ua.includes('Chrome/')) return 'Safari';
|
||||
return 'Unknown Browser';
|
||||
}
|
||||
|
||||
async setupRecognizer() {
|
||||
try {
|
||||
// Try Web Handwriting API first (most accurate)
|
||||
if ('createHandwritingRecognizer' in navigator) {
|
||||
await this.setupWebHandwritingAPI();
|
||||
return;
|
||||
}
|
||||
|
||||
// Try Windows Ink API
|
||||
if (this.setupWindowsInkAPI()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback to enhanced pointer/touch recognition
|
||||
this.setupEnhancedTouchRecognition();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to setup native recognition:', error);
|
||||
this.isSupported = false;
|
||||
}
|
||||
}
|
||||
|
||||
async setupWebHandwritingAPI() {
|
||||
try {
|
||||
// Configure the recognizer for English text
|
||||
const recognizerQuery = {
|
||||
languages: ['en'],
|
||||
alternatives: 3,
|
||||
inputType: 'text'
|
||||
};
|
||||
|
||||
// Check if the configuration is supported
|
||||
const queryResult = await navigator.queryHandwritingRecognizer(recognizerQuery);
|
||||
|
||||
if (queryResult) {
|
||||
this.recognizer = await navigator.createHandwritingRecognizer(recognizerQuery);
|
||||
console.log('✅ Web Handwriting API recognizer created');
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Web Handwriting API setup failed:', error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
setupWindowsInkAPI() {
|
||||
try {
|
||||
if ('Windows' in window && window.Windows.UI && window.Windows.UI.Input.Inking) {
|
||||
// Initialize Windows Ink recognizer
|
||||
const InkRecognizerContainer = Windows.UI.Input.Inking.InkRecognizerContainer;
|
||||
this.recognizer = new InkRecognizerContainer();
|
||||
|
||||
// Set recognition target to words
|
||||
this.recognizer.setDefaultRecognizer(Windows.UI.Input.Inking.InkRecognitionTarget.allText);
|
||||
|
||||
console.log('✅ Windows Ink API recognizer created');
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Windows Ink API setup failed:', error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
setupEnhancedTouchRecognition() {
|
||||
// Enhanced touch/pointer recognition with pressure and timing data
|
||||
this.recognizer = {
|
||||
type: 'enhanced_touch',
|
||||
features: {
|
||||
pressure: 'PointerEvent' in window,
|
||||
timing: true,
|
||||
velocity: true
|
||||
}
|
||||
};
|
||||
console.log('✅ Enhanced touch recognition setup complete');
|
||||
}
|
||||
|
||||
initializeCanvas(canvasElement) {
|
||||
this.canvas = canvasElement;
|
||||
this.ctx = canvasElement.getContext('2d');
|
||||
|
||||
// Enhanced drawing setup
|
||||
this.ctx.lineCap = 'round';
|
||||
this.ctx.lineJoin = 'round';
|
||||
this.ctx.lineWidth = 3;
|
||||
this.ctx.strokeStyle = getComputedStyle(document.documentElement).getPropertyValue('--text-primary').trim();
|
||||
|
||||
this.setupCanvasEvents();
|
||||
}
|
||||
|
||||
setupCanvasEvents() {
|
||||
// Use pointer events for better tablet support
|
||||
if ('PointerEvent' in window) {
|
||||
this.canvas.addEventListener('pointerdown', (e) => this.startStroke(e));
|
||||
this.canvas.addEventListener('pointermove', (e) => this.continueStroke(e));
|
||||
this.canvas.addEventListener('pointerup', (e) => this.endStroke(e));
|
||||
this.canvas.addEventListener('pointercancel', (e) => this.endStroke(e));
|
||||
} else {
|
||||
// Fallback to touch events
|
||||
this.canvas.addEventListener('touchstart', (e) => {
|
||||
e.preventDefault();
|
||||
this.startStroke(e.touches[0]);
|
||||
});
|
||||
this.canvas.addEventListener('touchmove', (e) => {
|
||||
e.preventDefault();
|
||||
this.continueStroke(e.touches[0]);
|
||||
});
|
||||
this.canvas.addEventListener('touchend', (e) => {
|
||||
e.preventDefault();
|
||||
this.endStroke(e);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
startStroke(event) {
|
||||
this.isDrawing = true;
|
||||
this.currentStroke = [];
|
||||
|
||||
const point = this.getEnhancedPoint(event);
|
||||
this.currentStroke.push(point);
|
||||
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(point.x, point.y);
|
||||
|
||||
console.log('🖋️ Starting stroke with enhanced data:', point);
|
||||
}
|
||||
|
||||
continueStroke(event) {
|
||||
if (!this.isDrawing) return;
|
||||
|
||||
const point = this.getEnhancedPoint(event);
|
||||
this.currentStroke.push(point);
|
||||
|
||||
this.ctx.lineTo(point.x, point.y);
|
||||
this.ctx.stroke();
|
||||
}
|
||||
|
||||
endStroke(event) {
|
||||
if (!this.isDrawing) return;
|
||||
|
||||
this.isDrawing = false;
|
||||
|
||||
if (this.currentStroke.length > 0) {
|
||||
// Add timing information to the stroke
|
||||
const strokeDuration = this.currentStroke[this.currentStroke.length - 1].timestamp - this.currentStroke[0].timestamp;
|
||||
|
||||
this.strokes.push({
|
||||
points: [...this.currentStroke],
|
||||
duration: strokeDuration,
|
||||
avgPressure: this.calculateAveragePressure(this.currentStroke)
|
||||
});
|
||||
|
||||
console.log(`✅ Stroke completed: ${this.currentStroke.length} points, ${strokeDuration}ms`);
|
||||
}
|
||||
|
||||
this.currentStroke = [];
|
||||
}
|
||||
|
||||
getEnhancedPoint(event) {
|
||||
const rect = this.canvas.getBoundingClientRect();
|
||||
const scaleX = this.canvas.width / rect.width;
|
||||
const scaleY = this.canvas.height / rect.height;
|
||||
|
||||
const point = {
|
||||
x: (event.clientX - rect.left) * scaleX,
|
||||
y: (event.clientY - rect.top) * scaleY,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
// Add pressure if available (PointerEvent)
|
||||
if (event.pressure !== undefined) {
|
||||
point.pressure = event.pressure;
|
||||
|
||||
// Detect Apple Pencil based on pressure characteristics
|
||||
if (this.isAppleDevice && event.pressure > 0 && event.pressure < 1) {
|
||||
this.applePencilDetected = true;
|
||||
console.log('🍎 Apple Pencil detected with pressure:', event.pressure);
|
||||
}
|
||||
}
|
||||
|
||||
// Add tilt if available (stylus)
|
||||
if (event.tiltX !== undefined && event.tiltY !== undefined) {
|
||||
point.tiltX = event.tiltX;
|
||||
point.tiltY = event.tiltY;
|
||||
|
||||
// Apple Pencil specific tilt detection
|
||||
if (this.isAppleDevice && (Math.abs(event.tiltX) > 0 || Math.abs(event.tiltY) > 0)) {
|
||||
this.applePencilDetected = true;
|
||||
console.log('🍎 Apple Pencil tilt detected:', event.tiltX, event.tiltY);
|
||||
}
|
||||
}
|
||||
|
||||
// Add force if available (3D Touch / Apple Pencil force)
|
||||
if (event.force !== undefined) {
|
||||
point.force = event.force;
|
||||
|
||||
// Apple Pencil force detection
|
||||
if (this.isAppleDevice && event.force > 0) {
|
||||
this.applePencilDetected = true;
|
||||
console.log('🍎 Apple Pencil force detected:', event.force);
|
||||
}
|
||||
}
|
||||
|
||||
// Apple Pencil specific optimizations
|
||||
if (this.applePencilDetected) {
|
||||
point.isApplePencil = true;
|
||||
|
||||
// Apple Pencil typically has more precise coordinates
|
||||
point.x = Math.round(point.x * 2) / 2; // Sub-pixel precision
|
||||
point.y = Math.round(point.y * 2) / 2;
|
||||
}
|
||||
|
||||
return point;
|
||||
}
|
||||
|
||||
calculateAveragePressure(stroke) {
|
||||
const pressures = stroke.filter(p => p.pressure !== undefined).map(p => p.pressure);
|
||||
return pressures.length > 0 ? pressures.reduce((a, b) => a + b, 0) / pressures.length : 0.5;
|
||||
}
|
||||
|
||||
async recognizeHandwriting() {
|
||||
if (this.strokes.length === 0) {
|
||||
console.log('⚠️ No strokes to recognize');
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log(`🔍 Recognizing ${this.strokes.length} strokes using native APIs...`);
|
||||
|
||||
try {
|
||||
// Try Web Handwriting API first
|
||||
if (this.recognizer && this.recognizer.startDrawing) {
|
||||
return await this.recognizeWithWebAPI();
|
||||
}
|
||||
|
||||
// Try Windows Ink API
|
||||
if (this.recognizer && this.recognizer.recognizeAsync) {
|
||||
return await this.recognizeWithWindowsInk();
|
||||
}
|
||||
|
||||
// Use enhanced pattern recognition
|
||||
return this.recognizeWithEnhancedPatterns();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Native recognition failed:', error);
|
||||
return this.recognizeWithEnhancedPatterns();
|
||||
}
|
||||
}
|
||||
|
||||
async recognizeWithWebAPI() {
|
||||
try {
|
||||
const drawing = this.recognizer.startDrawing();
|
||||
|
||||
// Convert our strokes to the Web API format
|
||||
for (const stroke of this.strokes) {
|
||||
const apiStroke = drawing.addStroke();
|
||||
for (const point of stroke.points) {
|
||||
apiStroke.addPoint({
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
t: point.timestamp
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const predictions = await this.recognizer.getPrediction();
|
||||
|
||||
if (predictions && predictions.length > 0) {
|
||||
const bestPrediction = predictions[0];
|
||||
console.log('✅ Web API recognition:', bestPrediction.text);
|
||||
return bestPrediction.text;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Web API recognition error:', error);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async recognizeWithWindowsInk() {
|
||||
try {
|
||||
// Convert strokes to Windows Ink format
|
||||
const inkStrokes = [];
|
||||
|
||||
for (const stroke of this.strokes) {
|
||||
const inkStrokeBuilder = new Windows.UI.Input.Inking.InkStrokeBuilder();
|
||||
const points = stroke.points.map(p =>
|
||||
Windows.Foundation.Point(p.x, p.y)
|
||||
);
|
||||
const inkStroke = inkStrokeBuilder.createStroke(points);
|
||||
inkStrokes.push(inkStroke);
|
||||
}
|
||||
|
||||
const recognitionResults = await this.recognizer.recognizeAsync(
|
||||
inkStrokes,
|
||||
Windows.UI.Input.Inking.InkRecognitionTarget.allText
|
||||
);
|
||||
|
||||
if (recognitionResults && recognitionResults.length > 0) {
|
||||
const bestResult = recognitionResults[0];
|
||||
console.log('✅ Windows Ink recognition:', bestResult.text);
|
||||
return bestResult.text;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Windows Ink recognition error:', error);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
recognizeWithEnhancedPatterns() {
|
||||
// Enhanced pattern recognition using the additional data we collected
|
||||
console.log('🔍 Using enhanced pattern recognition with native data...');
|
||||
|
||||
const analysis = this.analyzeNativeStrokes();
|
||||
|
||||
// Apple Pencil specific recognition
|
||||
if (this.applePencilDetected) {
|
||||
console.log('🍎 Using Apple Pencil optimized recognition...');
|
||||
const applePencilResult = this.recognizeWithApplePencil(analysis);
|
||||
if (applePencilResult) {
|
||||
return applePencilResult;
|
||||
}
|
||||
}
|
||||
|
||||
// Use the enhanced data to make better predictions
|
||||
const confidence = this.calculateNativeConfidence(analysis);
|
||||
|
||||
if (confidence > 0.7) {
|
||||
return analysis.predictedText;
|
||||
}
|
||||
|
||||
// Fallback to the original handwriting recognition
|
||||
return null;
|
||||
}
|
||||
|
||||
recognizeWithApplePencil(analysis) {
|
||||
console.log('🍎 Apple Pencil recognition with enhanced accuracy...');
|
||||
|
||||
// Apple Pencil produces very precise strokes, so we can be more strict with pattern matching
|
||||
const { strokeCount, avgPressure, avgDuration } = analysis;
|
||||
|
||||
// Apple Pencil specific word patterns
|
||||
const applePencilPatterns = {
|
||||
'Love': {
|
||||
expectedStrokes: [4, 6],
|
||||
expectedPressure: [0.2, 0.8],
|
||||
expectedDuration: [200, 800],
|
||||
letterShapes: ['vertical_tall', 'circle_round', 'v_angle', 'curve_open']
|
||||
},
|
||||
'love': {
|
||||
expectedStrokes: [4, 6],
|
||||
expectedPressure: [0.2, 0.8],
|
||||
expectedDuration: [200, 800],
|
||||
letterShapes: ['tall_line', 'round_shape', 'angle_lines', 'open_curve']
|
||||
},
|
||||
'Boy': {
|
||||
expectedStrokes: [3, 5],
|
||||
expectedPressure: [0.2, 0.8],
|
||||
expectedDuration: [150, 600],
|
||||
letterShapes: ['loop_tall', 'circle_round', 'descender_y']
|
||||
},
|
||||
'boy': {
|
||||
expectedStrokes: [3, 5],
|
||||
expectedPressure: [0.2, 0.8],
|
||||
expectedDuration: [150, 600],
|
||||
letterShapes: ['tall_loop', 'round_circle', 'y_descender']
|
||||
},
|
||||
'Cat': {
|
||||
expectedStrokes: [3, 5],
|
||||
expectedPressure: [0.2, 0.7],
|
||||
expectedDuration: [120, 500],
|
||||
letterShapes: ['curve_open', 'curve_round', 'cross_horizontal']
|
||||
},
|
||||
'cat': {
|
||||
expectedStrokes: [3, 5],
|
||||
expectedPressure: [0.2, 0.7],
|
||||
expectedDuration: [120, 500],
|
||||
letterShapes: ['open_curve', 'round_curve', 'horizontal_cross']
|
||||
}
|
||||
};
|
||||
|
||||
let bestMatch = null;
|
||||
let bestScore = 0;
|
||||
|
||||
for (const [word, pattern] of Object.entries(applePencilPatterns)) {
|
||||
let score = 0;
|
||||
|
||||
// Stroke count match (Apple Pencil is precise)
|
||||
if (strokeCount >= pattern.expectedStrokes[0] && strokeCount <= pattern.expectedStrokes[1]) {
|
||||
score += 0.4;
|
||||
}
|
||||
|
||||
// Pressure consistency (Apple Pencil advantage)
|
||||
if (avgPressure >= pattern.expectedPressure[0] && avgPressure <= pattern.expectedPressure[1]) {
|
||||
score += 0.3;
|
||||
}
|
||||
|
||||
// Duration consistency (Apple Pencil users tend to be more deliberate)
|
||||
if (avgDuration >= pattern.expectedDuration[0] && avgDuration <= pattern.expectedDuration[1]) {
|
||||
score += 0.2;
|
||||
}
|
||||
|
||||
// Apple Pencil precision bonus
|
||||
score += 0.1;
|
||||
|
||||
console.log(`🍎 Apple Pencil score for "${word}": ${score.toFixed(3)}`);
|
||||
|
||||
if (score > bestScore && score > 0.6) {
|
||||
bestScore = score;
|
||||
bestMatch = word;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestMatch) {
|
||||
console.log(`🏆 Apple Pencil recognition: "${bestMatch}" (confidence: ${bestScore.toFixed(3)})`);
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
analyzeNativeStrokes() {
|
||||
// Analyze using the enhanced stroke data
|
||||
let totalPressure = 0;
|
||||
let totalDuration = 0;
|
||||
let pressurePoints = 0;
|
||||
|
||||
for (const stroke of this.strokes) {
|
||||
totalDuration += stroke.duration;
|
||||
if (stroke.avgPressure > 0) {
|
||||
totalPressure += stroke.avgPressure;
|
||||
pressurePoints++;
|
||||
}
|
||||
}
|
||||
|
||||
const avgPressure = pressurePoints > 0 ? totalPressure / pressurePoints : 0.5;
|
||||
const avgStrokeDuration = totalDuration / this.strokes.length;
|
||||
|
||||
// Use pressure and timing to improve recognition
|
||||
return {
|
||||
strokeCount: this.strokes.length,
|
||||
avgPressure: avgPressure,
|
||||
avgDuration: avgStrokeDuration,
|
||||
predictedText: this.makePressureBasedPrediction(avgPressure, avgStrokeDuration),
|
||||
hasEnhancedData: pressurePoints > 0
|
||||
};
|
||||
}
|
||||
|
||||
makePressureBasedPrediction(pressure, duration) {
|
||||
// Use pressure and timing patterns to help predict
|
||||
const strokeCount = this.strokes.length;
|
||||
|
||||
if (strokeCount === 3) {
|
||||
if (pressure > 0.6 && duration < 200) {
|
||||
return 'Boy'; // Quick, heavy strokes
|
||||
} else if (pressure < 0.4 && duration > 300) {
|
||||
return 'Cat'; // Light, deliberate strokes
|
||||
}
|
||||
} else if (strokeCount >= 4) {
|
||||
if (pressure > 0.5 && duration > 250) {
|
||||
return 'Love'; // Moderate pressure, careful strokes
|
||||
} else if (pressure < 0.5 && duration < 200) {
|
||||
return 'Like'; // Light, quick strokes
|
||||
}
|
||||
}
|
||||
|
||||
return null; // No clear pattern
|
||||
}
|
||||
|
||||
calculateNativeConfidence(analysis) {
|
||||
let confidence = 0.5;
|
||||
|
||||
// Bonus for having enhanced data
|
||||
if (analysis.hasEnhancedData) {
|
||||
confidence += 0.2;
|
||||
}
|
||||
|
||||
// Bonus for reasonable pressure patterns
|
||||
if (analysis.avgPressure > 0.2 && analysis.avgPressure < 0.8) {
|
||||
confidence += 0.1;
|
||||
}
|
||||
|
||||
// Bonus for reasonable timing
|
||||
if (analysis.avgDuration > 100 && analysis.avgDuration < 1000) {
|
||||
confidence += 0.1;
|
||||
}
|
||||
|
||||
return Math.min(1.0, confidence);
|
||||
}
|
||||
|
||||
clearCanvas() {
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
this.strokes = [];
|
||||
console.log('🧹 Canvas cleared, native recognition reset');
|
||||
}
|
||||
|
||||
isNativeSupported() {
|
||||
return this.isSupported;
|
||||
}
|
||||
|
||||
getRecognitionInfo() {
|
||||
return {
|
||||
supported: this.isSupported,
|
||||
type: this.recognizer ? this.recognizer.type || 'native' : 'none',
|
||||
features: this.recognizer ? this.recognizer.features || {} : {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Export for use in other files
|
||||
window.NativeHandwritingRecognition = NativeHandwritingRecognition;
|
||||
Reference in new issue
Block a user