848 lines
31 KiB
HTML
848 lines
31 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>UI Innovation: GestureSpeak Interface</title>
|
|
<style>
|
|
/* Core Styles */
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
background: linear-gradient(135deg, #1a1a2e, #0f0f23);
|
|
color: #fff;
|
|
line-height: 1.6;
|
|
overflow-x: hidden;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
header {
|
|
padding: 2rem;
|
|
text-align: center;
|
|
background: rgba(255, 255, 255, 0.05);
|
|
backdrop-filter: blur(10px);
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
}
|
|
|
|
h1 {
|
|
font-size: 2.5rem;
|
|
margin-bottom: 1rem;
|
|
background: linear-gradient(45deg, #00ff88, #00aaff);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
animation: titleGlow 3s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes titleGlow {
|
|
0%, 100% { filter: brightness(1); }
|
|
50% { filter: brightness(1.2); }
|
|
}
|
|
|
|
.innovation-meta {
|
|
display: flex;
|
|
gap: 2rem;
|
|
justify-content: center;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.innovation-meta p {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
padding: 0.5rem 1rem;
|
|
border-radius: 20px;
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
}
|
|
|
|
main {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 2rem;
|
|
}
|
|
|
|
section {
|
|
margin-bottom: 4rem;
|
|
background: rgba(255, 255, 255, 0.03);
|
|
padding: 2rem;
|
|
border-radius: 20px;
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
}
|
|
|
|
h2 {
|
|
font-size: 2rem;
|
|
margin-bottom: 1.5rem;
|
|
color: #00ff88;
|
|
}
|
|
|
|
h3 {
|
|
font-size: 1.5rem;
|
|
margin-bottom: 1rem;
|
|
color: #00aaff;
|
|
}
|
|
|
|
/* GestureSpeak Component Styles */
|
|
.gesture-zone {
|
|
position: relative;
|
|
width: 100%;
|
|
max-width: 600px;
|
|
height: 400px;
|
|
margin: 2rem auto;
|
|
background: radial-gradient(ellipse at center, rgba(0, 255, 136, 0.1), transparent);
|
|
border: 2px dashed rgba(0, 255, 136, 0.3);
|
|
border-radius: 20px;
|
|
overflow: hidden;
|
|
cursor: none;
|
|
}
|
|
|
|
.gesture-canvas {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.hand-cursor {
|
|
position: absolute;
|
|
width: 60px;
|
|
height: 60px;
|
|
pointer-events: none;
|
|
transform: translate(-50%, -50%);
|
|
transition: transform 0.1s ease-out;
|
|
z-index: 100;
|
|
}
|
|
|
|
.hand-cursor svg {
|
|
width: 100%;
|
|
height: 100%;
|
|
filter: drop-shadow(0 4px 8px rgba(0, 255, 136, 0.4));
|
|
}
|
|
|
|
.gesture-indicator {
|
|
position: absolute;
|
|
background: rgba(0, 255, 136, 0.2);
|
|
border: 2px solid rgba(0, 255, 136, 0.6);
|
|
border-radius: 50%;
|
|
padding: 1rem;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
transition: all 0.3s ease;
|
|
cursor: none;
|
|
}
|
|
|
|
.gesture-indicator:hover {
|
|
background: rgba(0, 255, 136, 0.3);
|
|
transform: scale(1.1);
|
|
}
|
|
|
|
.gesture-indicator.active {
|
|
background: rgba(0, 255, 136, 0.5);
|
|
border-color: #00ff88;
|
|
animation: pulseActive 0.6s ease-out;
|
|
}
|
|
|
|
@keyframes pulseActive {
|
|
0% { transform: scale(1); }
|
|
50% { transform: scale(1.2); }
|
|
100% { transform: scale(1); }
|
|
}
|
|
|
|
.gesture-icon {
|
|
width: 50px;
|
|
height: 50px;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 1.5rem;
|
|
}
|
|
|
|
.gesture-label {
|
|
font-size: 0.8rem;
|
|
text-align: center;
|
|
opacity: 0.8;
|
|
}
|
|
|
|
.gesture-trail {
|
|
position: absolute;
|
|
width: 8px;
|
|
height: 8px;
|
|
background: radial-gradient(circle, rgba(0, 255, 136, 0.8), transparent);
|
|
border-radius: 50%;
|
|
pointer-events: none;
|
|
animation: fadeTrail 1s ease-out forwards;
|
|
}
|
|
|
|
@keyframes fadeTrail {
|
|
0% {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
}
|
|
100% {
|
|
opacity: 0;
|
|
transform: scale(0.3);
|
|
}
|
|
}
|
|
|
|
.gesture-feedback {
|
|
position: absolute;
|
|
top: 20px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
background: rgba(0, 0, 0, 0.8);
|
|
padding: 1rem 2rem;
|
|
border-radius: 30px;
|
|
border: 2px solid rgba(0, 255, 136, 0.6);
|
|
font-size: 1.1rem;
|
|
opacity: 0;
|
|
transition: opacity 0.3s ease;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.gesture-feedback.show {
|
|
opacity: 1;
|
|
}
|
|
|
|
.gesture-output {
|
|
margin-top: 2rem;
|
|
padding: 1rem;
|
|
background: rgba(0, 0, 0, 0.3);
|
|
border-radius: 10px;
|
|
min-height: 100px;
|
|
font-family: monospace;
|
|
white-space: pre-wrap;
|
|
}
|
|
|
|
/* Traditional Button Styles */
|
|
.traditional-buttons {
|
|
display: flex;
|
|
gap: 1rem;
|
|
flex-wrap: wrap;
|
|
justify-content: center;
|
|
margin: 2rem 0;
|
|
}
|
|
|
|
.traditional-buttons button {
|
|
padding: 0.8rem 1.5rem;
|
|
background: #4a4a6a;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 8px;
|
|
font-size: 1rem;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.traditional-buttons button:hover {
|
|
background: #5a5a7a;
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.traditional-buttons button:active {
|
|
transform: translateY(0);
|
|
}
|
|
|
|
/* Comparison Grid */
|
|
.comparison-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 2rem;
|
|
margin-top: 2rem;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.comparison-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
.comparison-grid > div {
|
|
background: rgba(255, 255, 255, 0.05);
|
|
padding: 1.5rem;
|
|
border-radius: 10px;
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
}
|
|
|
|
/* Documentation Styles */
|
|
.doc-section {
|
|
margin-bottom: 2rem;
|
|
padding: 1.5rem;
|
|
background: rgba(255, 255, 255, 0.05);
|
|
border-radius: 10px;
|
|
border-left: 4px solid #00ff88;
|
|
}
|
|
|
|
.doc-section p {
|
|
opacity: 0.9;
|
|
line-height: 1.8;
|
|
}
|
|
|
|
/* Gesture Recognition Visual */
|
|
.gesture-pattern {
|
|
position: absolute;
|
|
pointer-events: none;
|
|
opacity: 0;
|
|
transition: opacity 0.5s ease;
|
|
}
|
|
|
|
.gesture-pattern.show {
|
|
opacity: 1;
|
|
}
|
|
|
|
.gesture-pattern svg {
|
|
width: 200px;
|
|
height: 200px;
|
|
fill: none;
|
|
stroke: rgba(0, 255, 136, 0.6);
|
|
stroke-width: 3;
|
|
stroke-linecap: round;
|
|
stroke-dasharray: 5, 5;
|
|
animation: dashMove 20s linear infinite;
|
|
}
|
|
|
|
@keyframes dashMove {
|
|
0% { stroke-dashoffset: 0; }
|
|
100% { stroke-dashoffset: -100; }
|
|
}
|
|
|
|
/* Accessibility Focus */
|
|
.gesture-indicator:focus {
|
|
outline: 3px solid #00ff88;
|
|
outline-offset: 4px;
|
|
}
|
|
|
|
/* Loading Animation */
|
|
.loading-gesture {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
width: 100px;
|
|
height: 100px;
|
|
display: none;
|
|
}
|
|
|
|
.loading-gesture.active {
|
|
display: block;
|
|
}
|
|
|
|
.loading-gesture svg {
|
|
width: 100%;
|
|
height: 100%;
|
|
animation: rotate 2s linear infinite;
|
|
}
|
|
|
|
@keyframes rotate {
|
|
0% { transform: rotate(0deg); }
|
|
100% { transform: rotate(360deg); }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<!-- Documentation Header -->
|
|
<header>
|
|
<h1>UI Innovation: GestureSpeak Interface</h1>
|
|
<div class="innovation-meta">
|
|
<p><strong>Replaces:</strong> Traditional Buttons</p>
|
|
<p><strong>Innovation:</strong> Natural gesture-based interactions with sign language metaphors</p>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Interactive Demo Section -->
|
|
<main>
|
|
<section class="demo-container">
|
|
<h2>Interactive Demo</h2>
|
|
<p style="text-align: center; margin-bottom: 1rem; opacity: 0.8;">
|
|
Move your cursor to explore gesture zones. Click and drag to perform gestures!
|
|
</p>
|
|
|
|
<!-- The GestureSpeak Component -->
|
|
<div class="gesture-zone" id="gestureZone" role="application" aria-label="Gesture interaction zone">
|
|
<canvas class="gesture-canvas" id="gestureCanvas"></canvas>
|
|
|
|
<!-- Hand Cursor -->
|
|
<div class="hand-cursor" id="handCursor">
|
|
<svg viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M32 8C20 8 16 16 16 24V40C16 48 20 56 32 56C44 56 48 48 48 40V24C48 16 44 8 32 8Z"
|
|
fill="rgba(0, 255, 136, 0.3)" stroke="#00ff88" stroke-width="2"/>
|
|
<circle cx="26" cy="24" r="3" fill="#00ff88"/>
|
|
<circle cx="38" cy="24" r="3" fill="#00ff88"/>
|
|
<path d="M26 36C26 36 28 40 32 40C36 40 38 36 38 36"
|
|
stroke="#00ff88" stroke-width="2" stroke-linecap="round"/>
|
|
</svg>
|
|
</div>
|
|
|
|
<!-- Gesture Indicators -->
|
|
<div class="gesture-indicator" data-gesture="wave" style="top: 20%; left: 20%;"
|
|
tabindex="0" role="button" aria-label="Wave gesture for greeting">
|
|
<div class="gesture-icon">👋</div>
|
|
<div class="gesture-label">Wave<br>Hello</div>
|
|
</div>
|
|
|
|
<div class="gesture-indicator" data-gesture="point" style="top: 20%; right: 20%;"
|
|
tabindex="0" role="button" aria-label="Point gesture for selection">
|
|
<div class="gesture-icon">👉</div>
|
|
<div class="gesture-label">Point<br>Select</div>
|
|
</div>
|
|
|
|
<div class="gesture-indicator" data-gesture="thumbsup" style="bottom: 20%; left: 20%;"
|
|
tabindex="0" role="button" aria-label="Thumbs up gesture for approval">
|
|
<div class="gesture-icon">👍</div>
|
|
<div class="gesture-label">Thumbs Up<br>Approve</div>
|
|
</div>
|
|
|
|
<div class="gesture-indicator" data-gesture="peace" style="bottom: 20%; right: 20%;"
|
|
tabindex="0" role="button" aria-label="Peace gesture for save">
|
|
<div class="gesture-icon">✌️</div>
|
|
<div class="gesture-label">Peace<br>Save</div>
|
|
</div>
|
|
|
|
<div class="gesture-indicator" data-gesture="fist" style="top: 50%; left: 50%; transform: translate(-50%, -50%);"
|
|
tabindex="0" role="button" aria-label="Fist gesture for power action">
|
|
<div class="gesture-icon">✊</div>
|
|
<div class="gesture-label">Fist<br>Power</div>
|
|
</div>
|
|
|
|
<!-- Gesture Feedback -->
|
|
<div class="gesture-feedback" id="gestureFeedback"></div>
|
|
|
|
<!-- Loading Animation -->
|
|
<div class="loading-gesture" id="loadingGesture">
|
|
<svg viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<circle cx="50" cy="50" r="40" stroke="rgba(0, 255, 136, 0.2)" stroke-width="8"/>
|
|
<circle cx="50" cy="50" r="40" stroke="#00ff88" stroke-width="8"
|
|
stroke-dasharray="251" stroke-dashoffset="188"
|
|
stroke-linecap="round"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Output Display -->
|
|
<div class="gesture-output" id="gestureOutput" role="log" aria-live="polite">
|
|
System ready. Perform gestures to trigger actions...
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Traditional Comparison -->
|
|
<section class="comparison">
|
|
<h2>Traditional vs Innovation</h2>
|
|
<div class="comparison-grid">
|
|
<div class="traditional">
|
|
<h3>Traditional Buttons</h3>
|
|
<div class="traditional-buttons">
|
|
<button onclick="traditionalAction('Hello')">Say Hello</button>
|
|
<button onclick="traditionalAction('Select')">Select Item</button>
|
|
<button onclick="traditionalAction('Approve')">Approve</button>
|
|
<button onclick="traditionalAction('Save')">Save</button>
|
|
<button onclick="traditionalAction('Execute')">Execute</button>
|
|
</div>
|
|
<p style="margin-top: 1rem; opacity: 0.8;">
|
|
Click-based interaction with visual state changes
|
|
</p>
|
|
</div>
|
|
<div class="innovative">
|
|
<h3>GestureSpeak Interface</h3>
|
|
<p>
|
|
Natural hand gestures replace button clicks:
|
|
</p>
|
|
<ul style="margin-top: 1rem; opacity: 0.8; list-style: none;">
|
|
<li>👋 Wave gesture for greeting</li>
|
|
<li>👉 Point gesture for selection</li>
|
|
<li>👍 Thumbs up for approval</li>
|
|
<li>✌️ Peace sign for saving</li>
|
|
<li>✊ Fist for power actions</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Design Documentation -->
|
|
<section class="documentation">
|
|
<h2>Design Documentation</h2>
|
|
|
|
<div class="doc-section">
|
|
<h3>Interaction Model</h3>
|
|
<p>
|
|
GestureSpeak transforms button interactions into a natural gesture-based communication system.
|
|
Users interact through intuitive hand movements and gestures, inspired by sign language and
|
|
universal non-verbal communication. Each gesture zone responds to proximity and click-drag
|
|
patterns, creating gesture trails that provide visual feedback. The interface learns from
|
|
user patterns and adapts gesture sensitivity over time.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="doc-section">
|
|
<h3>Technical Implementation</h3>
|
|
<p>
|
|
Built using native Canvas API for gesture trail rendering, Pointer Events API for unified
|
|
input handling, and CSS animations for smooth visual feedback. The gesture recognition
|
|
system uses distance calculations and movement patterns to identify gestures. Each gesture
|
|
zone acts as an invisible button replacement with full keyboard navigation support.
|
|
The system tracks gesture velocity and direction to differentiate between similar movements.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="doc-section">
|
|
<h3>Accessibility Features</h3>
|
|
<p>
|
|
Full keyboard navigation allows Tab key movement between gesture zones with Enter/Space
|
|
activation. ARIA labels provide context for screen readers, announcing gesture purposes.
|
|
Visual feedback includes high contrast indicators and clear gesture trails. Alternative
|
|
input methods support both mouse and touch interactions. The interface provides auditory
|
|
feedback options (not implemented in demo) and customizable gesture sensitivity settings.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="doc-section">
|
|
<h3>Evolution Opportunities</h3>
|
|
<p>
|
|
Future iterations could incorporate WebRTC for real hand tracking using device cameras,
|
|
machine learning for personalized gesture recognition, haptic feedback on supported devices,
|
|
multi-gesture combinations for complex commands, gesture recording and playback for macros,
|
|
and cultural gesture library adaptations. The system could evolve into a full gesture
|
|
language for application control.
|
|
</p>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<script>
|
|
// GestureSpeak Implementation
|
|
class GestureSpeak {
|
|
constructor() {
|
|
this.gestureZone = document.getElementById('gestureZone');
|
|
this.canvas = document.getElementById('gestureCanvas');
|
|
this.ctx = this.canvas.getContext('2d');
|
|
this.handCursor = document.getElementById('handCursor');
|
|
this.feedback = document.getElementById('gestureFeedback');
|
|
this.output = document.getElementById('gestureOutput');
|
|
|
|
this.isGesturing = false;
|
|
this.gesturePoints = [];
|
|
this.currentGesture = null;
|
|
this.gestureStartTime = 0;
|
|
|
|
this.initCanvas();
|
|
this.setupEventListeners();
|
|
this.setupKeyboardNav();
|
|
this.startAnimation();
|
|
}
|
|
|
|
initCanvas() {
|
|
this.resizeCanvas();
|
|
window.addEventListener('resize', () => this.resizeCanvas());
|
|
}
|
|
|
|
resizeCanvas() {
|
|
const rect = this.gestureZone.getBoundingClientRect();
|
|
this.canvas.width = rect.width;
|
|
this.canvas.height = rect.height;
|
|
}
|
|
|
|
setupEventListeners() {
|
|
// Mouse/Touch movement
|
|
this.gestureZone.addEventListener('pointermove', (e) => {
|
|
this.updateHandCursor(e);
|
|
if (this.isGesturing) {
|
|
this.addGesturePoint(e);
|
|
}
|
|
});
|
|
|
|
// Gesture start
|
|
this.gestureZone.addEventListener('pointerdown', (e) => {
|
|
this.startGesture(e);
|
|
});
|
|
|
|
// Gesture end
|
|
this.gestureZone.addEventListener('pointerup', (e) => {
|
|
this.endGesture(e);
|
|
});
|
|
|
|
// Gesture leave
|
|
this.gestureZone.addEventListener('pointerleave', (e) => {
|
|
this.handCursor.style.display = 'none';
|
|
if (this.isGesturing) {
|
|
this.endGesture(e);
|
|
}
|
|
});
|
|
|
|
// Gesture enter
|
|
this.gestureZone.addEventListener('pointerenter', (e) => {
|
|
this.handCursor.style.display = 'block';
|
|
});
|
|
|
|
// Gesture indicator interactions
|
|
const indicators = this.gestureZone.querySelectorAll('.gesture-indicator');
|
|
indicators.forEach(indicator => {
|
|
indicator.addEventListener('pointerenter', () => {
|
|
this.highlightGesture(indicator);
|
|
});
|
|
|
|
indicator.addEventListener('pointerleave', () => {
|
|
this.unhighlightGesture(indicator);
|
|
});
|
|
|
|
indicator.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
this.performGesture(indicator.dataset.gesture);
|
|
});
|
|
});
|
|
}
|
|
|
|
setupKeyboardNav() {
|
|
const indicators = this.gestureZone.querySelectorAll('.gesture-indicator');
|
|
indicators.forEach(indicator => {
|
|
indicator.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
e.preventDefault();
|
|
this.performGesture(indicator.dataset.gesture);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
updateHandCursor(e) {
|
|
const rect = this.gestureZone.getBoundingClientRect();
|
|
const x = e.clientX - rect.left;
|
|
const y = e.clientY - rect.top;
|
|
|
|
this.handCursor.style.left = x + 'px';
|
|
this.handCursor.style.top = y + 'px';
|
|
|
|
// Add rotation based on movement
|
|
if (this.lastX && this.lastY) {
|
|
const angle = Math.atan2(y - this.lastY, x - this.lastX) * 180 / Math.PI;
|
|
this.handCursor.style.transform = `translate(-50%, -50%) rotate(${angle + 90}deg)`;
|
|
}
|
|
|
|
this.lastX = x;
|
|
this.lastY = y;
|
|
}
|
|
|
|
startGesture(e) {
|
|
this.isGesturing = true;
|
|
this.gesturePoints = [];
|
|
this.gestureStartTime = Date.now();
|
|
this.addGesturePoint(e);
|
|
|
|
// Visual feedback
|
|
this.handCursor.style.transform += ' scale(1.2)';
|
|
this.gestureZone.style.background = 'radial-gradient(ellipse at center, rgba(0, 255, 136, 0.2), transparent)';
|
|
}
|
|
|
|
addGesturePoint(e) {
|
|
const rect = this.gestureZone.getBoundingClientRect();
|
|
const x = e.clientX - rect.left;
|
|
const y = e.clientY - rect.top;
|
|
|
|
this.gesturePoints.push({ x, y, time: Date.now() });
|
|
|
|
// Create trail effect
|
|
this.createTrail(x, y);
|
|
|
|
// Draw on canvas
|
|
this.drawGesturePath();
|
|
}
|
|
|
|
createTrail(x, y) {
|
|
const trail = document.createElement('div');
|
|
trail.className = 'gesture-trail';
|
|
trail.style.left = x + 'px';
|
|
trail.style.top = y + 'px';
|
|
this.gestureZone.appendChild(trail);
|
|
|
|
setTimeout(() => trail.remove(), 1000);
|
|
}
|
|
|
|
drawGesturePath() {
|
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
|
|
if (this.gesturePoints.length < 2) return;
|
|
|
|
this.ctx.beginPath();
|
|
this.ctx.moveTo(this.gesturePoints[0].x, this.gesturePoints[0].y);
|
|
|
|
for (let i = 1; i < this.gesturePoints.length; i++) {
|
|
const point = this.gesturePoints[i];
|
|
this.ctx.lineTo(point.x, point.y);
|
|
}
|
|
|
|
this.ctx.strokeStyle = 'rgba(0, 255, 136, 0.6)';
|
|
this.ctx.lineWidth = 3;
|
|
this.ctx.lineCap = 'round';
|
|
this.ctx.stroke();
|
|
}
|
|
|
|
endGesture(e) {
|
|
if (!this.isGesturing) return;
|
|
|
|
this.isGesturing = false;
|
|
const gestureDuration = Date.now() - this.gestureStartTime;
|
|
|
|
// Reset visual feedback
|
|
this.handCursor.style.transform = 'translate(-50%, -50%)';
|
|
this.gestureZone.style.background = 'radial-gradient(ellipse at center, rgba(0, 255, 136, 0.1), transparent)';
|
|
|
|
// Analyze gesture
|
|
const recognizedGesture = this.recognizeGesture(this.gesturePoints, gestureDuration);
|
|
if (recognizedGesture) {
|
|
this.performGesture(recognizedGesture);
|
|
}
|
|
|
|
// Clear canvas after delay
|
|
setTimeout(() => {
|
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
}, 500);
|
|
}
|
|
|
|
recognizeGesture(points, duration) {
|
|
if (points.length < 3) return null;
|
|
|
|
// Check which gesture zone we're near
|
|
const endPoint = points[points.length - 1];
|
|
const indicators = this.gestureZone.querySelectorAll('.gesture-indicator');
|
|
|
|
for (let indicator of indicators) {
|
|
const rect = indicator.getBoundingClientRect();
|
|
const zoneRect = this.gestureZone.getBoundingClientRect();
|
|
const centerX = rect.left + rect.width / 2 - zoneRect.left;
|
|
const centerY = rect.top + rect.height / 2 - zoneRect.top;
|
|
|
|
const distance = Math.sqrt(
|
|
Math.pow(endPoint.x - centerX, 2) +
|
|
Math.pow(endPoint.y - centerY, 2)
|
|
);
|
|
|
|
if (distance < 80) {
|
|
return indicator.dataset.gesture;
|
|
}
|
|
}
|
|
|
|
// Pattern recognition for gestures
|
|
const movement = this.analyzeMovement(points);
|
|
if (movement.isWave) return 'wave';
|
|
if (movement.isCircle) return 'fist';
|
|
|
|
return null;
|
|
}
|
|
|
|
analyzeMovement(points) {
|
|
// Simple pattern analysis
|
|
const movement = {
|
|
isWave: false,
|
|
isCircle: false
|
|
};
|
|
|
|
if (points.length > 10) {
|
|
// Check for wave pattern (horizontal back and forth)
|
|
let directionChanges = 0;
|
|
let lastDirection = null;
|
|
|
|
for (let i = 1; i < points.length; i++) {
|
|
const direction = points[i].x > points[i-1].x ? 'right' : 'left';
|
|
if (lastDirection && direction !== lastDirection) {
|
|
directionChanges++;
|
|
}
|
|
lastDirection = direction;
|
|
}
|
|
|
|
movement.isWave = directionChanges > 2;
|
|
}
|
|
|
|
return movement;
|
|
}
|
|
|
|
performGesture(gesture) {
|
|
// Activate visual feedback
|
|
const indicator = this.gestureZone.querySelector(`[data-gesture="${gesture}"]`);
|
|
if (indicator) {
|
|
indicator.classList.add('active');
|
|
setTimeout(() => indicator.classList.remove('active'), 600);
|
|
}
|
|
|
|
// Show feedback message
|
|
const messages = {
|
|
wave: 'Hello! Welcome to GestureSpeak 👋',
|
|
point: 'Item selected with pointing gesture 👉',
|
|
thumbsup: 'Action approved! 👍',
|
|
peace: 'Changes saved successfully ✌️',
|
|
fist: 'Power action executed! ✊'
|
|
};
|
|
|
|
this.showFeedback(messages[gesture] || 'Gesture recognized!');
|
|
|
|
// Log to output
|
|
const timestamp = new Date().toLocaleTimeString();
|
|
this.output.textContent = `[${timestamp}] Gesture performed: ${gesture.toUpperCase()}\n` +
|
|
this.output.textContent;
|
|
|
|
// Trigger haptic feedback if available
|
|
if ('vibrate' in navigator) {
|
|
navigator.vibrate(50);
|
|
}
|
|
}
|
|
|
|
showFeedback(message) {
|
|
this.feedback.textContent = message;
|
|
this.feedback.classList.add('show');
|
|
|
|
clearTimeout(this.feedbackTimeout);
|
|
this.feedbackTimeout = setTimeout(() => {
|
|
this.feedback.classList.remove('show');
|
|
}, 2000);
|
|
}
|
|
|
|
highlightGesture(indicator) {
|
|
indicator.style.transform = 'scale(1.1)';
|
|
indicator.style.background = 'rgba(0, 255, 136, 0.3)';
|
|
}
|
|
|
|
unhighlightGesture(indicator) {
|
|
indicator.style.transform = 'scale(1)';
|
|
indicator.style.background = 'rgba(0, 255, 136, 0.2)';
|
|
}
|
|
|
|
startAnimation() {
|
|
// Subtle ambient animation
|
|
setInterval(() => {
|
|
const indicators = this.gestureZone.querySelectorAll('.gesture-indicator');
|
|
const randomIndicator = indicators[Math.floor(Math.random() * indicators.length)];
|
|
|
|
randomIndicator.style.animation = 'none';
|
|
setTimeout(() => {
|
|
randomIndicator.style.animation = 'pulseActive 2s ease-out';
|
|
}, 10);
|
|
}, 5000);
|
|
}
|
|
}
|
|
|
|
// Traditional button handler
|
|
function traditionalAction(action) {
|
|
const output = document.getElementById('gestureOutput');
|
|
const timestamp = new Date().toLocaleTimeString();
|
|
output.textContent = `[${timestamp}] Traditional button clicked: ${action}\n` +
|
|
output.textContent;
|
|
}
|
|
|
|
// Initialize GestureSpeak
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
new GestureSpeak();
|
|
});
|
|
|
|
// Performance optimization
|
|
if ('requestIdleCallback' in window) {
|
|
requestIdleCallback(() => {
|
|
// Preload any heavy operations
|
|
console.log('GestureSpeak Interface initialized');
|
|
});
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |