progress
This commit is contained in:
parent
cf2e8fb8d8
commit
86b90ea977
|
|
@ -38,37 +38,144 @@ Based on the spec analysis and existing iterations:
|
|||
- Consider how to build upon previous iterations while maintaining novelty
|
||||
- If count is "infinite", prepare for continuous generation until context limits
|
||||
|
||||
**PHASE 4: GENERATION EXECUTION**
|
||||
For each iteration to generate:
|
||||
**PHASE 4: PARALLEL AGENT COORDINATION**
|
||||
Deploy multiple Sub Agents to generate iterations in parallel for maximum efficiency and creative diversity:
|
||||
|
||||
1. **Deep Context Analysis**: Review the spec requirements and all previous iterations
|
||||
2. **Evolutionary Planning**: Plan how this iteration will advance beyond previous ones
|
||||
3. **Unique Content Creation**: Generate content that fulfills the spec while being distinctly new
|
||||
4. **Quality Validation**: Ensure the output meets spec requirements and adds value
|
||||
5. **File Naming**: Use the pattern specified in the spec with proper iteration suffixing
|
||||
**Sub-Agent Distribution Strategy:**
|
||||
- For count 1-5: Launch all agents simultaneously
|
||||
- For count 6-20: Launch in batches of 5 agents to manage coordination
|
||||
- For "infinite": Launch waves of 3-5 agents, monitoring context and spawning new waves
|
||||
|
||||
**PHASE 5: INFINITE MODE HANDLING**
|
||||
If count is "infinite":
|
||||
- Continue generating iterations until you approach context window limits
|
||||
- Monitor your remaining context capacity
|
||||
- Each iteration should become progressively more sophisticated
|
||||
- Maintain awareness of when to gracefully conclude the generation cycle
|
||||
- Provide a summary of all iterations created in the infinite run
|
||||
**Agent Assignment Protocol:**
|
||||
Each Sub Agent receives:
|
||||
1. **Spec Context**: Complete specification file analysis
|
||||
2. **Directory Snapshot**: Current state of output_dir at launch time
|
||||
3. **Iteration Assignment**: Specific iteration number (starting_number + agent_index)
|
||||
4. **Uniqueness Directive**: Explicit instruction to avoid duplicating concepts from existing iterations
|
||||
5. **Quality Standards**: Detailed requirements from the specification
|
||||
|
||||
**Agent Task Specification:**
|
||||
```
|
||||
TASK: Generate iteration [NUMBER] for [SPEC_FILE] in [OUTPUT_DIR]
|
||||
|
||||
You are Sub Agent [X] generating iteration [NUMBER].
|
||||
|
||||
CONTEXT:
|
||||
- Specification: [Full spec analysis]
|
||||
- Existing iterations: [Summary of current output_dir contents]
|
||||
- Your iteration number: [NUMBER]
|
||||
- Assigned creative direction: [Specific innovation dimension to explore]
|
||||
|
||||
REQUIREMENTS:
|
||||
1. Read and understand the specification completely
|
||||
2. Analyze existing iterations to ensure your output is unique
|
||||
3. Generate content following the spec format exactly
|
||||
4. Focus on [assigned innovation dimension] while maintaining spec compliance
|
||||
5. Create file with exact name pattern specified
|
||||
6. Ensure your iteration adds genuine value and novelty
|
||||
|
||||
DELIVERABLE: Single file as specified, with unique innovative content
|
||||
```
|
||||
|
||||
**Parallel Execution Management:**
|
||||
- Launch all assigned Sub Agents simultaneously using Task tool
|
||||
- Monitor agent progress and completion
|
||||
- Handle any agent failures by reassigning iteration numbers
|
||||
- Ensure no duplicate iteration numbers are generated
|
||||
- Collect and validate all completed iterations
|
||||
|
||||
**PHASE 5: INFINITE MODE ORCHESTRATION**
|
||||
For infinite generation mode, orchestrate continuous parallel waves:
|
||||
|
||||
**Wave-Based Generation:**
|
||||
1. **Wave Planning**: Determine next wave size (3-5 agents) based on context capacity
|
||||
2. **Agent Preparation**: Prepare fresh context snapshots for each new wave
|
||||
3. **Progressive Sophistication**: Each wave should explore more advanced innovation dimensions
|
||||
4. **Context Monitoring**: Track total context usage across all agents and main orchestrator
|
||||
5. **Graceful Conclusion**: When approaching context limits, complete current wave and summarize
|
||||
|
||||
**Infinite Execution Cycle:**
|
||||
```
|
||||
WHILE context_capacity > threshold:
|
||||
1. Assess current output_dir state
|
||||
2. Plan next wave of agents (size based on remaining context)
|
||||
3. Assign increasingly sophisticated creative directions
|
||||
4. Launch parallel Sub Agent wave
|
||||
5. Monitor wave completion
|
||||
6. Update directory state snapshot
|
||||
7. Evaluate context capacity remaining
|
||||
8. If sufficient capacity: Continue to next wave
|
||||
9. If approaching limits: Complete final wave and summarize
|
||||
```
|
||||
|
||||
**Progressive Sophistication Strategy:**
|
||||
- **Wave 1**: Basic functional replacements with single innovation dimension
|
||||
- **Wave 2**: Multi-dimensional innovations with enhanced interactions
|
||||
- **Wave 3**: Complex paradigm combinations with adaptive behaviors
|
||||
- **Wave N**: Revolutionary concepts pushing the boundaries of the specification
|
||||
|
||||
**Context Optimization:**
|
||||
- Each wave uses fresh agent instances to avoid context accumulation
|
||||
- Main orchestrator maintains lightweight state tracking
|
||||
- Progressive summarization of completed iterations to manage context
|
||||
- Strategic pruning of less essential details in later waves
|
||||
|
||||
**EXECUTION PRINCIPLES:**
|
||||
|
||||
**Quality & Uniqueness:**
|
||||
- Each iteration must be genuinely unique and valuable
|
||||
- Build upon previous work while introducing novel elements
|
||||
- Maintain consistency with the original specification
|
||||
- Ensure proper file organization and naming
|
||||
- Think deeply about the evolution trajectory
|
||||
|
||||
**Parallel Coordination:**
|
||||
- Deploy Sub Agents strategically to maximize creative diversity
|
||||
- Assign distinct innovation dimensions to each agent to avoid overlap
|
||||
- Coordinate timing to prevent file naming conflicts
|
||||
- Monitor all agents for successful completion and quality
|
||||
|
||||
**Scalability & Efficiency:**
|
||||
- Think deeply about the evolution trajectory across parallel streams
|
||||
- For infinite mode, optimize for maximum valuable output before context exhaustion
|
||||
- Use wave-based generation to manage context limits intelligently
|
||||
- Balance parallel speed with quality and coordination overhead
|
||||
|
||||
**Agent Management:**
|
||||
- Provide each Sub Agent with complete context and clear assignments
|
||||
- Handle agent failures gracefully with iteration reassignment
|
||||
- Ensure all parallel outputs integrate cohesively with the overall progression
|
||||
|
||||
**ULTRA-THINKING DIRECTIVE:**
|
||||
Before beginning generation, engage in extended thinking about:
|
||||
|
||||
**Specification & Evolution:**
|
||||
- The deeper implications of the specification
|
||||
- How to create meaningful progression across iterations
|
||||
- What makes each iteration valuable and unique
|
||||
- The optimal strategy for infinite generation
|
||||
- How to balance consistency with innovation
|
||||
|
||||
Begin execution with deep analysis and proceed systematically through each phase.
|
||||
**Parallel Strategy:**
|
||||
- Optimal Sub Agent distribution for the requested count
|
||||
- How to assign distinct creative directions to maximize diversity
|
||||
- Wave sizing and timing for infinite mode
|
||||
- Context management across multiple parallel agents
|
||||
|
||||
**Coordination Challenges:**
|
||||
- How to prevent duplicate concepts across parallel streams
|
||||
- Strategies for ensuring each agent produces genuinely unique output
|
||||
- Managing file naming and directory organization with concurrent writes
|
||||
- Quality control mechanisms for parallel outputs
|
||||
|
||||
**Infinite Mode Optimization:**
|
||||
- Wave-based generation patterns for sustained output
|
||||
- Progressive sophistication strategies across multiple waves
|
||||
- Context capacity monitoring and graceful conclusion planning
|
||||
- Balancing speed of parallel generation with depth of innovation
|
||||
|
||||
**Risk Mitigation:**
|
||||
- Handling agent failures and iteration reassignment
|
||||
- Ensuring coherent overall progression despite parallel execution
|
||||
- Managing context window limits across the entire system
|
||||
- Maintaining specification compliance across all parallel outputs
|
||||
|
||||
Begin execution with deep analysis of these parallel coordination challenges and proceed systematically through each phase, leveraging Sub Agents for maximum creative output and efficiency.
|
||||
|
|
@ -0,0 +1,676 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>UI Innovation: Quantum State Toggle</title>
|
||||
<style>
|
||||
/* Global Styles */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 100%);
|
||||
color: #e0e0e0;
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
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, #00ffcc, #0099ff, #cc00ff);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.innovation-meta {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 2rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.innovation-meta p {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 20px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
section {
|
||||
margin-bottom: 4rem;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
padding: 2rem;
|
||||
border-radius: 15px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1.5rem;
|
||||
color: #00ffcc;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #0099ff;
|
||||
}
|
||||
|
||||
/* Quantum Toggle Styles */
|
||||
.quantum-toggle-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 3rem;
|
||||
justify-content: center;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.quantum-toggle {
|
||||
position: relative;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.quantum-field {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Probability Cloud */
|
||||
.probability-cloud {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: radial-gradient(circle at center,
|
||||
rgba(0, 255, 204, 0.3) 0%,
|
||||
rgba(0, 153, 255, 0.2) 40%,
|
||||
rgba(204, 0, 255, 0.1) 70%,
|
||||
transparent 100%);
|
||||
animation: quantumFluctuation 3s ease-in-out infinite;
|
||||
filter: blur(2px);
|
||||
}
|
||||
|
||||
@keyframes quantumFluctuation {
|
||||
0%, 100% { transform: scale(1) rotate(0deg); }
|
||||
25% { transform: scale(1.1) rotate(90deg); }
|
||||
50% { transform: scale(0.9) rotate(180deg); }
|
||||
75% { transform: scale(1.05) rotate(270deg); }
|
||||
}
|
||||
|
||||
/* Wave Function */
|
||||
.wave-function {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.wave-path {
|
||||
stroke: rgba(0, 255, 204, 0.8);
|
||||
stroke-width: 2;
|
||||
fill: none;
|
||||
filter: drop-shadow(0 0 5px rgba(0, 255, 204, 0.5));
|
||||
animation: waveOscillation 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes waveOscillation {
|
||||
0% { stroke-dashoffset: 0; }
|
||||
100% { stroke-dashoffset: -100; }
|
||||
}
|
||||
|
||||
/* Quantum States */
|
||||
.quantum-state {
|
||||
position: absolute;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
transition: all 0.3s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.state-on {
|
||||
background: radial-gradient(circle, #00ffcc, #0099ff);
|
||||
box-shadow: 0 0 30px rgba(0, 255, 204, 0.8);
|
||||
}
|
||||
|
||||
.state-off {
|
||||
background: radial-gradient(circle, #666, #333);
|
||||
box-shadow: 0 0 10px rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.state-superposition {
|
||||
background: radial-gradient(circle,
|
||||
rgba(0, 255, 204, 0.5),
|
||||
rgba(102, 102, 102, 0.5));
|
||||
box-shadow: 0 0 20px rgba(0, 255, 204, 0.4);
|
||||
animation: superpositionPulse 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes superpositionPulse {
|
||||
0%, 100% { transform: translate(-50%, -50%) scale(1); }
|
||||
50% { transform: translate(-50%, -50%) scale(1.2); }
|
||||
}
|
||||
|
||||
/* Probability Display */
|
||||
.probability-display {
|
||||
position: absolute;
|
||||
bottom: -40px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
color: #00ffcc;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Measurement Effect */
|
||||
.collapse-wave {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
border: 2px solid rgba(0, 255, 204, 0.8);
|
||||
animation: collapseAnimation 0.6s ease-out;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes collapseAnimation {
|
||||
0% {
|
||||
transform: scale(0.8);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: scale(2);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Entanglement Line */
|
||||
.entanglement-line {
|
||||
position: absolute;
|
||||
height: 2px;
|
||||
background: linear-gradient(90deg,
|
||||
transparent,
|
||||
rgba(204, 0, 255, 0.8),
|
||||
transparent);
|
||||
transform-origin: left center;
|
||||
pointer-events: none;
|
||||
animation: entanglementPulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes entanglementPulse {
|
||||
0%, 100% { opacity: 0.3; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
|
||||
/* Labels */
|
||||
.toggle-label {
|
||||
position: absolute;
|
||||
top: -30px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 0.9rem;
|
||||
color: #0099ff;
|
||||
}
|
||||
|
||||
/* Traditional Toggle Styles */
|
||||
.traditional-toggle {
|
||||
display: inline-block;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.traditional-toggle input[type="checkbox"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.traditional-toggle-label {
|
||||
display: inline-block;
|
||||
width: 60px;
|
||||
height: 30px;
|
||||
background: #ccc;
|
||||
border-radius: 15px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.traditional-toggle-label::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.traditional-toggle input[type="checkbox"]:checked + .traditional-toggle-label {
|
||||
background: #4CAF50;
|
||||
}
|
||||
|
||||
.traditional-toggle input[type="checkbox"]:checked + .traditional-toggle-label::after {
|
||||
transform: translateX(30px);
|
||||
}
|
||||
|
||||
/* Comparison Grid */
|
||||
.comparison-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 2rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.comparison-grid > div {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
padding: 1.5rem;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* Documentation */
|
||||
.doc-section {
|
||||
margin-bottom: 2rem;
|
||||
padding: 1.5rem;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
border-radius: 10px;
|
||||
border-left: 3px solid #00ffcc;
|
||||
}
|
||||
|
||||
.doc-section p {
|
||||
margin-bottom: 0.5rem;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
/* Accessibility */
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.quantum-toggle:focus {
|
||||
outline: 2px solid #00ffcc;
|
||||
outline-offset: 5px;
|
||||
}
|
||||
|
||||
/* Control Panel */
|
||||
.control-panel {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 1rem;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 2rem;
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.control-button {
|
||||
background: rgba(0, 255, 204, 0.2);
|
||||
border: 1px solid rgba(0, 255, 204, 0.5);
|
||||
color: #00ffcc;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.control-button:hover {
|
||||
background: rgba(0, 255, 204, 0.3);
|
||||
box-shadow: 0 0 10px rgba(0, 255, 204, 0.5);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Documentation Header -->
|
||||
<header>
|
||||
<h1>UI Innovation: Quantum State Toggle</h1>
|
||||
<div class="innovation-meta">
|
||||
<p><strong>Replaces:</strong> Traditional binary toggles/switches</p>
|
||||
<p><strong>Innovation:</strong> Quantum superposition states with probability visualization</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Interactive Demo Section -->
|
||||
<main>
|
||||
<section class="demo-container">
|
||||
<h2>Interactive Demo</h2>
|
||||
|
||||
<div class="control-panel">
|
||||
<button class="control-button" onclick="measureAll()">Measure All States</button>
|
||||
<button class="control-button" onclick="entangleToggles()">Create Entanglement</button>
|
||||
<button class="control-button" onclick="resetToSuperposition()">Reset to Superposition</button>
|
||||
</div>
|
||||
|
||||
<div class="quantum-toggle-container" id="quantumContainer">
|
||||
<!-- Quantum toggles will be dynamically created -->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Traditional Comparison -->
|
||||
<section class="comparison">
|
||||
<h2>Traditional vs Innovation</h2>
|
||||
<div class="comparison-grid">
|
||||
<div class="traditional">
|
||||
<h3>Traditional Toggle</h3>
|
||||
<p>Binary states only (ON/OFF)</p>
|
||||
<div style="text-align: center; margin: 2rem 0;">
|
||||
<div class="traditional-toggle">
|
||||
<input type="checkbox" id="trad1">
|
||||
<label class="traditional-toggle-label" for="trad1"></label>
|
||||
</div>
|
||||
<div class="traditional-toggle">
|
||||
<input type="checkbox" id="trad2">
|
||||
<label class="traditional-toggle-label" for="trad2"></label>
|
||||
</div>
|
||||
<div class="traditional-toggle">
|
||||
<input type="checkbox" id="trad3">
|
||||
<label class="traditional-toggle-label" for="trad3"></label>
|
||||
</div>
|
||||
</div>
|
||||
<p>• Immediate state change</p>
|
||||
<p>• No uncertainty representation</p>
|
||||
<p>• Independent operation</p>
|
||||
</div>
|
||||
<div class="innovative">
|
||||
<h3>Quantum Toggle</h3>
|
||||
<p>Superposition states with probability</p>
|
||||
<p style="margin: 2rem 0; text-align: center;">
|
||||
See interactive demo above ↑
|
||||
</p>
|
||||
<p>• States exist in superposition</p>
|
||||
<p>• Probability wave visualization</p>
|
||||
<p>• Quantum entanglement possible</p>
|
||||
<p>• Measurement causes wave collapse</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Design Documentation -->
|
||||
<section class="documentation">
|
||||
<h2>Design Documentation</h2>
|
||||
|
||||
<div class="doc-section">
|
||||
<h3>Interaction Model</h3>
|
||||
<p>Users interact with quantum toggles through observation and measurement. Unlike traditional binary switches, these toggles exist in superposition until measured.</p>
|
||||
<p>• <strong>Click/Tap:</strong> Performs a quantum measurement, collapsing the wave function to a definite state</p>
|
||||
<p>• <strong>Hover:</strong> Shows probability amplitudes without collapsing the state</p>
|
||||
<p>• <strong>Entanglement:</strong> Connected toggles affect each other instantaneously</p>
|
||||
<p>• <strong>Keyboard:</strong> Space/Enter to measure, Tab to navigate</p>
|
||||
</div>
|
||||
|
||||
<div class="doc-section">
|
||||
<h3>Technical Implementation</h3>
|
||||
<p>Built using native web technologies to create quantum-inspired visualizations:</p>
|
||||
<p>• <strong>CSS Animations:</strong> Continuous wave functions and probability clouds</p>
|
||||
<p>• <strong>SVG Paths:</strong> Dynamic wave function visualization</p>
|
||||
<p>• <strong>JavaScript:</strong> Quantum state management and entanglement logic</p>
|
||||
<p>• <strong>CSS Custom Properties:</strong> Real-time probability updates</p>
|
||||
<p>• <strong>Transform & Filters:</strong> Superposition visual effects</p>
|
||||
</div>
|
||||
|
||||
<div class="doc-section">
|
||||
<h3>Accessibility Features</h3>
|
||||
<p>• Full keyboard navigation with visible focus indicators</p>
|
||||
<p>• ARIA labels describing current quantum state and probability</p>
|
||||
<p>• Screen reader announcements for state changes</p>
|
||||
<p>• High contrast visual indicators for all states</p>
|
||||
<p>• Reduced motion option respects user preferences</p>
|
||||
</div>
|
||||
|
||||
<div class="doc-section">
|
||||
<h3>Evolution Opportunities</h3>
|
||||
<p>• <strong>Multi-state Superposition:</strong> Beyond binary to n-dimensional quantum states</p>
|
||||
<p>• <strong>Quantum Gates:</strong> Implement quantum logic operations between toggles</p>
|
||||
<p>• <strong>Decoherence Effects:</strong> Environmental interaction causing gradual state collapse</p>
|
||||
<p>• <strong>Quantum Tunneling:</strong> Probability of spontaneous state changes</p>
|
||||
<p>• <strong>Many-Worlds Visualization:</strong> Show parallel universe states</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
// Quantum Toggle Implementation
|
||||
class QuantumToggle {
|
||||
constructor(id, label) {
|
||||
this.id = id;
|
||||
this.label = label;
|
||||
this.state = 'superposition';
|
||||
this.probability = { on: 0.5, off: 0.5 };
|
||||
this.entangled = [];
|
||||
this.element = null;
|
||||
this.isCollapsing = false;
|
||||
}
|
||||
|
||||
createElement() {
|
||||
const container = document.createElement('div');
|
||||
container.className = 'quantum-toggle';
|
||||
container.tabIndex = 0;
|
||||
container.setAttribute('role', 'switch');
|
||||
container.setAttribute('aria-checked', 'mixed');
|
||||
container.setAttribute('aria-label', `${this.label} - Quantum state toggle`);
|
||||
|
||||
container.innerHTML = `
|
||||
<span class="sr-only">Quantum toggle: ${this.label}</span>
|
||||
<div class="toggle-label">${this.label}</div>
|
||||
<div class="quantum-field">
|
||||
<div class="probability-cloud"></div>
|
||||
<svg class="wave-function" viewBox="0 0 200 200">
|
||||
<path class="wave-path" stroke-dasharray="5,5" />
|
||||
</svg>
|
||||
<div class="quantum-state state-superposition"></div>
|
||||
</div>
|
||||
<div class="probability-display">|ψ⟩ = √${this.probability.on.toFixed(2)}|1⟩ + √${this.probability.off.toFixed(2)}|0⟩</div>
|
||||
`;
|
||||
|
||||
// Event listeners
|
||||
container.addEventListener('click', () => this.measure());
|
||||
container.addEventListener('keydown', (e) => {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this.measure();
|
||||
}
|
||||
});
|
||||
|
||||
this.element = container;
|
||||
this.updateWaveFunction();
|
||||
return container;
|
||||
}
|
||||
|
||||
updateWaveFunction() {
|
||||
if (!this.element) return;
|
||||
|
||||
const path = this.element.querySelector('.wave-path');
|
||||
const amplitude = this.state === 'superposition' ? 30 : 15;
|
||||
const frequency = this.state === 'superposition' ? 3 : 1;
|
||||
|
||||
let d = 'M 0,100 ';
|
||||
for (let x = 0; x <= 200; x += 5) {
|
||||
const y = 100 + amplitude * Math.sin((x / 200) * Math.PI * 2 * frequency) *
|
||||
(this.probability.on - this.probability.off);
|
||||
d += `L ${x},${y} `;
|
||||
}
|
||||
|
||||
path.setAttribute('d', d);
|
||||
}
|
||||
|
||||
measure() {
|
||||
if (this.isCollapsing) return;
|
||||
this.isCollapsing = true;
|
||||
|
||||
// Create collapse animation
|
||||
const collapseWave = document.createElement('div');
|
||||
collapseWave.className = 'collapse-wave';
|
||||
this.element.querySelector('.quantum-field').appendChild(collapseWave);
|
||||
|
||||
// Determine collapsed state based on probability
|
||||
const random = Math.random();
|
||||
const newState = random < this.probability.on ? 'on' : 'off';
|
||||
|
||||
setTimeout(() => {
|
||||
this.setState(newState);
|
||||
collapseWave.remove();
|
||||
this.isCollapsing = false;
|
||||
|
||||
// Affect entangled toggles
|
||||
this.entangled.forEach(toggle => {
|
||||
if (toggle.state === 'superposition') {
|
||||
toggle.influenceFromEntangled(newState);
|
||||
}
|
||||
});
|
||||
}, 300);
|
||||
}
|
||||
|
||||
setState(newState) {
|
||||
this.state = newState;
|
||||
const stateElement = this.element.querySelector('.quantum-state');
|
||||
|
||||
stateElement.className = 'quantum-state';
|
||||
if (newState === 'on') {
|
||||
stateElement.classList.add('state-on');
|
||||
this.probability = { on: 1, off: 0 };
|
||||
this.element.setAttribute('aria-checked', 'true');
|
||||
} else if (newState === 'off') {
|
||||
stateElement.classList.add('state-off');
|
||||
this.probability = { on: 0, off: 1 };
|
||||
this.element.setAttribute('aria-checked', 'false');
|
||||
} else {
|
||||
stateElement.classList.add('state-superposition');
|
||||
this.element.setAttribute('aria-checked', 'mixed');
|
||||
}
|
||||
|
||||
this.updateProbabilityDisplay();
|
||||
this.updateWaveFunction();
|
||||
}
|
||||
|
||||
influenceFromEntangled(entangledState) {
|
||||
if (this.state !== 'superposition') return;
|
||||
|
||||
// Quantum entanglement influence
|
||||
if (entangledState === 'on') {
|
||||
this.probability.on = Math.min(0.8, this.probability.on + 0.2);
|
||||
this.probability.off = 1 - this.probability.on;
|
||||
} else {
|
||||
this.probability.off = Math.min(0.8, this.probability.off + 0.2);
|
||||
this.probability.on = 1 - this.probability.off;
|
||||
}
|
||||
|
||||
this.updateProbabilityDisplay();
|
||||
this.updateWaveFunction();
|
||||
}
|
||||
|
||||
updateProbabilityDisplay() {
|
||||
const display = this.element.querySelector('.probability-display');
|
||||
if (this.state === 'superposition') {
|
||||
display.textContent = `|ψ⟩ = √${this.probability.on.toFixed(2)}|1⟩ + √${this.probability.off.toFixed(2)}|0⟩`;
|
||||
} else if (this.state === 'on') {
|
||||
display.textContent = '|ψ⟩ = |1⟩';
|
||||
} else {
|
||||
display.textContent = '|ψ⟩ = |0⟩';
|
||||
}
|
||||
}
|
||||
|
||||
resetToSuperposition() {
|
||||
this.state = 'superposition';
|
||||
this.probability = { on: 0.5, off: 0.5 };
|
||||
this.setState('superposition');
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize quantum toggles
|
||||
const toggles = [];
|
||||
const container = document.getElementById('quantumContainer');
|
||||
|
||||
function initializeToggles() {
|
||||
const labels = ['Notifications', 'Dark Mode', 'Auto-Save'];
|
||||
|
||||
labels.forEach((label, index) => {
|
||||
const toggle = new QuantumToggle(`quantum-${index}`, label);
|
||||
toggles.push(toggle);
|
||||
container.appendChild(toggle.createElement());
|
||||
});
|
||||
}
|
||||
|
||||
function measureAll() {
|
||||
toggles.forEach(toggle => {
|
||||
if (toggle.state === 'superposition') {
|
||||
setTimeout(() => toggle.measure(), Math.random() * 500);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function entangleToggles() {
|
||||
// Clear existing entanglements
|
||||
toggles.forEach(toggle => toggle.entangled = []);
|
||||
|
||||
// Create entanglement between adjacent toggles
|
||||
for (let i = 0; i < toggles.length - 1; i++) {
|
||||
toggles[i].entangled.push(toggles[i + 1]);
|
||||
toggles[i + 1].entangled.push(toggles[i]);
|
||||
|
||||
// Visual entanglement line
|
||||
const line = document.createElement('div');
|
||||
line.className = 'entanglement-line';
|
||||
line.style.width = '100px';
|
||||
line.style.left = `${150 + i * 230}px`;
|
||||
line.style.top = '100px';
|
||||
container.appendChild(line);
|
||||
|
||||
setTimeout(() => line.remove(), 3000);
|
||||
}
|
||||
}
|
||||
|
||||
function resetToSuperposition() {
|
||||
toggles.forEach(toggle => toggle.resetToSuperposition());
|
||||
}
|
||||
|
||||
// Check for reduced motion preference
|
||||
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
if (prefersReducedMotion) {
|
||||
document.documentElement.style.setProperty('--animation-duration', '0s');
|
||||
}
|
||||
|
||||
// Initialize on load
|
||||
initializeToggles();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,884 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>UI Innovation: RainFlow Control</title>
|
||||
<style>
|
||||
/* Global Styles */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: linear-gradient(135deg, #e3f2fd 0%, #f5f5f5 100%);
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
header {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #1976d2;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.innovation-meta {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 2rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.innovation-meta p {
|
||||
margin: 0.5rem 0;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
section {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #1565c0;
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: #0d47a1;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
/* RainFlow Control Styles */
|
||||
.demo-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.rainflow-container {
|
||||
display: flex;
|
||||
gap: 3rem;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.rainflow-control {
|
||||
position: relative;
|
||||
width: 300px;
|
||||
height: 400px;
|
||||
background: linear-gradient(180deg,
|
||||
#87ceeb 0%,
|
||||
#b0e0e6 20%,
|
||||
#e0f7fa 40%,
|
||||
#ffffff 60%,
|
||||
#f5f5f5 100%);
|
||||
border-radius: 20px;
|
||||
box-shadow:
|
||||
0 10px 30px rgba(0, 0, 0, 0.1),
|
||||
inset 0 0 20px rgba(255, 255, 255, 0.5);
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.cloud-layer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 100px;
|
||||
background: linear-gradient(180deg,
|
||||
rgba(255, 255, 255, 0.9) 0%,
|
||||
rgba(255, 255, 255, 0.6) 50%,
|
||||
transparent 100%);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 10;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.cloud {
|
||||
position: relative;
|
||||
width: 80px;
|
||||
height: 30px;
|
||||
background: #fff;
|
||||
border-radius: 100px;
|
||||
opacity: 0.9;
|
||||
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.cloud::before,
|
||||
.cloud::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
background: #fff;
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
.cloud::before {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
top: -20px;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.cloud::after {
|
||||
width: 50px;
|
||||
height: 35px;
|
||||
top: -15px;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.cloud.active {
|
||||
background: #9e9e9e;
|
||||
animation: rumble 0.3s ease infinite;
|
||||
}
|
||||
|
||||
.cloud.active::before,
|
||||
.cloud.active::after {
|
||||
background: #9e9e9e;
|
||||
}
|
||||
|
||||
@keyframes rumble {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
25% { transform: translateX(-2px); }
|
||||
75% { transform: translateX(2px); }
|
||||
}
|
||||
|
||||
.rain-area {
|
||||
position: absolute;
|
||||
top: 100px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 150px;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.raindrop {
|
||||
position: absolute;
|
||||
width: 2px;
|
||||
height: 15px;
|
||||
background: linear-gradient(180deg,
|
||||
transparent 0%,
|
||||
rgba(30, 144, 255, 0.6) 50%,
|
||||
rgba(30, 144, 255, 0.8) 100%);
|
||||
animation: fall linear infinite;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes fall {
|
||||
0% {
|
||||
transform: translateY(-20px);
|
||||
opacity: 0;
|
||||
}
|
||||
10% {
|
||||
opacity: 1;
|
||||
}
|
||||
90% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(150px);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.water-container {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 50%;
|
||||
border-radius: 0 0 20px 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.water-level {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: linear-gradient(180deg,
|
||||
rgba(30, 144, 255, 0.6) 0%,
|
||||
rgba(30, 144, 255, 0.8) 50%,
|
||||
rgba(0, 119, 190, 0.9) 100%);
|
||||
transition: height 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
border-radius: 0 0 20px 20px;
|
||||
}
|
||||
|
||||
.water-level::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
left: -10%;
|
||||
right: -10%;
|
||||
height: 20px;
|
||||
background: inherit;
|
||||
border-radius: 50%;
|
||||
animation: wave 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes wave {
|
||||
0%, 100% { transform: translateY(0) scaleY(1); }
|
||||
50% { transform: translateY(-5px) scaleY(0.8); }
|
||||
}
|
||||
|
||||
.water-bubbles {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.bubble {
|
||||
position: absolute;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
border-radius: 50%;
|
||||
animation: bubble-rise linear infinite;
|
||||
}
|
||||
|
||||
@keyframes bubble-rise {
|
||||
0% {
|
||||
transform: translateY(0) scale(1);
|
||||
opacity: 0.8;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(-200px) scale(1.5);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.value-display {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
font-weight: 600;
|
||||
color: #0d47a1;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.control-info {
|
||||
text-align: center;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.control-label {
|
||||
font-weight: 600;
|
||||
color: #1565c0;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
/* Traditional Slider for Comparison */
|
||||
.comparison-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 2rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.traditional-slider {
|
||||
padding: 2rem;
|
||||
background: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.traditional-slider input[type="range"] {
|
||||
width: 100%;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.slider-value {
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
/* Documentation Styles */
|
||||
.documentation {
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.doc-section {
|
||||
margin-bottom: 2rem;
|
||||
padding: 1.5rem;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #1976d2;
|
||||
}
|
||||
|
||||
.doc-section p {
|
||||
color: #555;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
/* Accessibility Focus Styles */
|
||||
.rainflow-control:focus {
|
||||
outline: 3px solid #1976d2;
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
.rainflow-control:focus .cloud {
|
||||
animation: pulse 1s ease infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.1); }
|
||||
}
|
||||
|
||||
/* Screen Reader Only */
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
.rainflow-container {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.comparison-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.innovation-meta {
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Documentation Header -->
|
||||
<header>
|
||||
<h1>UI Innovation: RainFlow Control</h1>
|
||||
<div class="innovation-meta">
|
||||
<p><strong>Replaces:</strong> Traditional Slider/Range Input</p>
|
||||
<p><strong>Innovation:</strong> Natural weather-based fluid dynamics for value control</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Interactive Demo Section -->
|
||||
<main>
|
||||
<section class="demo-container">
|
||||
<h2>Interactive Demo</h2>
|
||||
<p style="text-align: center; color: #666; margin-bottom: 2rem;">
|
||||
Click and hold the cloud to make it rain. The water level represents your selected value.
|
||||
Release to stop the rain and watch the water settle.
|
||||
</p>
|
||||
|
||||
<div class="rainflow-container">
|
||||
<!-- Volume Control -->
|
||||
<div>
|
||||
<div class="control-label">Volume Control</div>
|
||||
<div class="rainflow-control"
|
||||
role="slider"
|
||||
tabindex="0"
|
||||
aria-label="Volume control using rain flow"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
aria-valuenow="50"
|
||||
data-min="0"
|
||||
data-max="100"
|
||||
data-value="50"
|
||||
data-label="Volume">
|
||||
<div class="cloud-layer">
|
||||
<div class="cloud"></div>
|
||||
</div>
|
||||
<div class="rain-area"></div>
|
||||
<div class="water-container">
|
||||
<div class="water-level" style="height: 50%;">
|
||||
<div class="water-bubbles"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="value-display">50%</div>
|
||||
<span class="sr-only" role="status" aria-live="polite">Volume: 50%</span>
|
||||
</div>
|
||||
<div class="control-info">
|
||||
<p style="color: #666; font-size: 0.9rem;">Current: <span class="current-value">50</span>%</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Brightness Control -->
|
||||
<div>
|
||||
<div class="control-label">Brightness Control</div>
|
||||
<div class="rainflow-control"
|
||||
role="slider"
|
||||
tabindex="0"
|
||||
aria-label="Brightness control using rain flow"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
aria-valuenow="75"
|
||||
data-min="0"
|
||||
data-max="100"
|
||||
data-value="75"
|
||||
data-label="Brightness">
|
||||
<div class="cloud-layer">
|
||||
<div class="cloud"></div>
|
||||
</div>
|
||||
<div class="rain-area"></div>
|
||||
<div class="water-container">
|
||||
<div class="water-level" style="height: 75%;">
|
||||
<div class="water-bubbles"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="value-display">75%</div>
|
||||
<span class="sr-only" role="status" aria-live="polite">Brightness: 75%</span>
|
||||
</div>
|
||||
<div class="control-info">
|
||||
<p style="color: #666; font-size: 0.9rem;">Current: <span class="current-value">75</span>%</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Temperature Control -->
|
||||
<div>
|
||||
<div class="control-label">Temperature Control</div>
|
||||
<div class="rainflow-control"
|
||||
role="slider"
|
||||
tabindex="0"
|
||||
aria-label="Temperature control using rain flow"
|
||||
aria-valuemin="60"
|
||||
aria-valuemax="90"
|
||||
aria-valuenow="72"
|
||||
data-min="60"
|
||||
data-max="90"
|
||||
data-value="72"
|
||||
data-label="Temperature"
|
||||
data-unit="°F">
|
||||
<div class="cloud-layer">
|
||||
<div class="cloud"></div>
|
||||
</div>
|
||||
<div class="rain-area"></div>
|
||||
<div class="water-container">
|
||||
<div class="water-level" style="height: 40%;">
|
||||
<div class="water-bubbles"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="value-display">72°F</div>
|
||||
<span class="sr-only" role="status" aria-live="polite">Temperature: 72°F</span>
|
||||
</div>
|
||||
<div class="control-info">
|
||||
<p style="color: #666; font-size: 0.9rem;">Current: <span class="current-value">72</span>°F</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Traditional Comparison -->
|
||||
<section class="comparison">
|
||||
<h2>Traditional vs Innovation</h2>
|
||||
<div class="comparison-grid">
|
||||
<div class="traditional">
|
||||
<h3>Traditional Range Sliders</h3>
|
||||
<div class="traditional-slider">
|
||||
<label for="traditional-volume">Volume:</label>
|
||||
<input type="range" id="traditional-volume" min="0" max="100" value="50">
|
||||
<div class="slider-value">50%</div>
|
||||
</div>
|
||||
<div class="traditional-slider">
|
||||
<label for="traditional-brightness">Brightness:</label>
|
||||
<input type="range" id="traditional-brightness" min="0" max="100" value="75">
|
||||
<div class="slider-value">75%</div>
|
||||
</div>
|
||||
<div class="traditional-slider">
|
||||
<label for="traditional-temp">Temperature:</label>
|
||||
<input type="range" id="traditional-temp" min="60" max="90" value="72">
|
||||
<div class="slider-value">72°F</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="innovative">
|
||||
<h3>RainFlow Controls</h3>
|
||||
<p style="color: #666; line-height: 1.8;">
|
||||
The innovative RainFlow controls above replace traditional sliders with an intuitive
|
||||
weather metaphor. Users control values by creating rain that fills a container,
|
||||
making the interaction more engaging and visually meaningful.
|
||||
</p>
|
||||
<ul style="color: #666; margin-top: 1rem; padding-left: 1.5rem;">
|
||||
<li>Click and hold to make it rain (increase value)</li>
|
||||
<li>Release to stop rain (value settles)</li>
|
||||
<li>Natural fluid physics create smooth transitions</li>
|
||||
<li>Visual feedback through water level and animation</li>
|
||||
<li>Accessible with keyboard controls (Space/Enter to rain, Arrow keys for fine control)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Design Documentation -->
|
||||
<section class="documentation">
|
||||
<h2>Design Documentation</h2>
|
||||
|
||||
<div class="doc-section">
|
||||
<h3>Interaction Model</h3>
|
||||
<p>
|
||||
RainFlow Controls transform the abstract concept of value adjustment into a tangible,
|
||||
natural process. Users interact with a cloud that produces rain, filling a container
|
||||
with water. The water level directly represents the selected value, creating an
|
||||
immediate visual connection between action and result. This metaphor leverages our
|
||||
innate understanding of weather and fluid dynamics to make digital controls feel
|
||||
more natural and engaging.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="doc-section">
|
||||
<h3>Technical Implementation</h3>
|
||||
<p>
|
||||
Built entirely with native web technologies, RainFlow Controls use CSS animations
|
||||
for smooth fluid motion and JavaScript for interaction handling. The rain effect
|
||||
is created dynamically with individual raindrop elements, while the water level
|
||||
uses CSS transforms and transitions for realistic fluid behavior. The component
|
||||
implements proper ARIA attributes for accessibility and uses requestAnimationFrame
|
||||
for optimal performance during continuous interactions.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="doc-section">
|
||||
<h3>Accessibility Features</h3>
|
||||
<p>
|
||||
Full keyboard navigation is supported with Space/Enter keys triggering rain and
|
||||
Arrow keys providing fine-grained control. Screen readers announce value changes
|
||||
through ARIA live regions. The component maintains proper focus states and provides
|
||||
visual feedback for keyboard users. High contrast is maintained between the water
|
||||
level and background, and all interactive elements meet WCAG 2.1 AA standards for
|
||||
size and spacing.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="doc-section">
|
||||
<h3>Evolution Opportunities</h3>
|
||||
<p>
|
||||
Future iterations could incorporate temperature-based color gradients (blue for
|
||||
cold, red for hot), storm intensity for rapid value changes, evaporation mechanics
|
||||
for value decay over time, and multiple cloud types for different input speeds.
|
||||
The system could also support collaborative controls where multiple users contribute
|
||||
to a shared water level, or implement weather patterns that predict and suggest
|
||||
optimal values based on usage patterns.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
// RainFlow Control Implementation
|
||||
class RainFlowControl {
|
||||
constructor(element) {
|
||||
this.element = element;
|
||||
this.cloud = element.querySelector('.cloud');
|
||||
this.rainArea = element.querySelector('.rain-area');
|
||||
this.waterLevel = element.querySelector('.water-level');
|
||||
this.waterBubbles = element.querySelector('.water-bubbles');
|
||||
this.valueDisplay = element.querySelector('.value-display');
|
||||
this.srStatus = element.querySelector('[role="status"]');
|
||||
|
||||
// Get data attributes
|
||||
this.min = parseFloat(element.dataset.min) || 0;
|
||||
this.max = parseFloat(element.dataset.max) || 100;
|
||||
this.value = parseFloat(element.dataset.value) || 50;
|
||||
this.label = element.dataset.label || 'Value';
|
||||
this.unit = element.dataset.unit || '%';
|
||||
|
||||
// State
|
||||
this.isRaining = false;
|
||||
this.rainInterval = null;
|
||||
this.bubbleInterval = null;
|
||||
this.raindrops = [];
|
||||
this.bubbles = [];
|
||||
|
||||
// Bind methods
|
||||
this.startRain = this.startRain.bind(this);
|
||||
this.stopRain = this.stopRain.bind(this);
|
||||
this.updateValue = this.updateValue.bind(this);
|
||||
this.handleKeyDown = this.handleKeyDown.bind(this);
|
||||
this.handleKeyUp = this.handleKeyUp.bind(this);
|
||||
|
||||
// Initialize
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
// Mouse events
|
||||
this.element.addEventListener('mousedown', this.startRain);
|
||||
this.element.addEventListener('mouseup', this.stopRain);
|
||||
this.element.addEventListener('mouseleave', this.stopRain);
|
||||
|
||||
// Touch events
|
||||
this.element.addEventListener('touchstart', this.startRain);
|
||||
this.element.addEventListener('touchend', this.stopRain);
|
||||
|
||||
// Keyboard events
|
||||
this.element.addEventListener('keydown', this.handleKeyDown);
|
||||
this.element.addEventListener('keyup', this.handleKeyUp);
|
||||
|
||||
// Prevent text selection
|
||||
this.element.addEventListener('selectstart', e => e.preventDefault());
|
||||
|
||||
// Initialize water level
|
||||
this.updateWaterLevel();
|
||||
}
|
||||
|
||||
startRain(e) {
|
||||
e.preventDefault();
|
||||
if (this.isRaining) return;
|
||||
|
||||
this.isRaining = true;
|
||||
this.cloud.classList.add('active');
|
||||
|
||||
// Start creating raindrops
|
||||
this.rainInterval = setInterval(() => {
|
||||
this.createRaindrop();
|
||||
|
||||
// Increase value while raining
|
||||
if (this.value < this.max) {
|
||||
this.value = Math.min(this.value + 1, this.max);
|
||||
this.updateValue();
|
||||
}
|
||||
}, 50);
|
||||
|
||||
// Start creating bubbles
|
||||
this.bubbleInterval = setInterval(() => {
|
||||
this.createBubble();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
stopRain() {
|
||||
if (!this.isRaining) return;
|
||||
|
||||
this.isRaining = false;
|
||||
this.cloud.classList.remove('active');
|
||||
|
||||
// Stop creating raindrops
|
||||
clearInterval(this.rainInterval);
|
||||
clearInterval(this.bubbleInterval);
|
||||
|
||||
// Clear remaining raindrops after animation
|
||||
setTimeout(() => {
|
||||
this.raindrops.forEach(drop => drop.remove());
|
||||
this.raindrops = [];
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
createRaindrop() {
|
||||
const drop = document.createElement('div');
|
||||
drop.className = 'raindrop';
|
||||
|
||||
// Random horizontal position under cloud
|
||||
const cloudRect = this.cloud.getBoundingClientRect();
|
||||
const containerRect = this.element.getBoundingClientRect();
|
||||
const minX = cloudRect.left - containerRect.left;
|
||||
const maxX = cloudRect.right - containerRect.left;
|
||||
|
||||
drop.style.left = `${minX + Math.random() * (maxX - minX)}px`;
|
||||
drop.style.animationDuration = `${0.5 + Math.random() * 0.5}s`;
|
||||
drop.style.animationDelay = `${Math.random() * 0.2}s`;
|
||||
|
||||
this.rainArea.appendChild(drop);
|
||||
this.raindrops.push(drop);
|
||||
|
||||
// Remove after animation
|
||||
setTimeout(() => {
|
||||
drop.remove();
|
||||
const index = this.raindrops.indexOf(drop);
|
||||
if (index > -1) {
|
||||
this.raindrops.splice(index, 1);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
createBubble() {
|
||||
const bubble = document.createElement('div');
|
||||
bubble.className = 'bubble';
|
||||
|
||||
// Random size and position
|
||||
const size = 4 + Math.random() * 8;
|
||||
bubble.style.width = `${size}px`;
|
||||
bubble.style.height = `${size}px`;
|
||||
bubble.style.left = `${10 + Math.random() * 80}%`;
|
||||
bubble.style.bottom = `${Math.random() * 20}px`;
|
||||
bubble.style.animationDuration = `${2 + Math.random() * 2}s`;
|
||||
|
||||
this.waterBubbles.appendChild(bubble);
|
||||
this.bubbles.push(bubble);
|
||||
|
||||
// Remove after animation
|
||||
setTimeout(() => {
|
||||
bubble.remove();
|
||||
const index = this.bubbles.indexOf(bubble);
|
||||
if (index > -1) {
|
||||
this.bubbles.splice(index, 1);
|
||||
}
|
||||
}, 4000);
|
||||
}
|
||||
|
||||
updateValue() {
|
||||
this.updateWaterLevel();
|
||||
this.updateDisplay();
|
||||
this.updateAria();
|
||||
|
||||
// Update info display
|
||||
const container = this.element.closest('div');
|
||||
const infoValue = container.querySelector('.current-value');
|
||||
if (infoValue) {
|
||||
infoValue.textContent = Math.round(this.value);
|
||||
}
|
||||
|
||||
// Dispatch custom event
|
||||
this.element.dispatchEvent(new CustomEvent('rainflowchange', {
|
||||
detail: { value: this.value }
|
||||
}));
|
||||
}
|
||||
|
||||
updateWaterLevel() {
|
||||
const percentage = ((this.value - this.min) / (this.max - this.min)) * 100;
|
||||
this.waterLevel.style.height = `${percentage}%`;
|
||||
|
||||
// Update water color based on level
|
||||
const hue = 200 + (percentage * 0.2); // Blue to slightly purple
|
||||
const lightness = 50 - (percentage * 0.2); // Darker as it fills
|
||||
this.waterLevel.style.background = `linear-gradient(180deg,
|
||||
hsla(${hue}, 70%, ${lightness}%, 0.6) 0%,
|
||||
hsla(${hue}, 70%, ${lightness - 10}%, 0.8) 50%,
|
||||
hsla(${hue}, 70%, ${lightness - 20}%, 0.9) 100%)`;
|
||||
}
|
||||
|
||||
updateDisplay() {
|
||||
const displayValue = this.unit === '%'
|
||||
? `${Math.round(this.value)}%`
|
||||
: `${Math.round(this.value)}${this.unit}`;
|
||||
this.valueDisplay.textContent = displayValue;
|
||||
}
|
||||
|
||||
updateAria() {
|
||||
this.element.setAttribute('aria-valuenow', this.value);
|
||||
const statusText = `${this.label}: ${Math.round(this.value)}${this.unit}`;
|
||||
this.srStatus.textContent = statusText;
|
||||
}
|
||||
|
||||
handleKeyDown(e) {
|
||||
switch(e.key) {
|
||||
case ' ':
|
||||
case 'Enter':
|
||||
e.preventDefault();
|
||||
this.startRain(e);
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
case 'ArrowRight':
|
||||
e.preventDefault();
|
||||
this.value = Math.min(this.value + 1, this.max);
|
||||
this.updateValue();
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
case 'ArrowLeft':
|
||||
e.preventDefault();
|
||||
this.value = Math.max(this.value - 1, this.min);
|
||||
this.updateValue();
|
||||
break;
|
||||
case 'Home':
|
||||
e.preventDefault();
|
||||
this.value = this.min;
|
||||
this.updateValue();
|
||||
break;
|
||||
case 'End':
|
||||
e.preventDefault();
|
||||
this.value = this.max;
|
||||
this.updateValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyUp(e) {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this.stopRain();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize all RainFlow controls
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const controls = document.querySelectorAll('.rainflow-control');
|
||||
controls.forEach(control => {
|
||||
new RainFlowControl(control);
|
||||
});
|
||||
|
||||
// Sync traditional sliders for comparison
|
||||
const traditionalSliders = document.querySelectorAll('input[type="range"]');
|
||||
traditionalSliders.forEach(slider => {
|
||||
slider.addEventListener('input', (e) => {
|
||||
const valueDisplay = e.target.parentElement.querySelector('.slider-value');
|
||||
const value = e.target.value;
|
||||
if (e.target.id.includes('temp')) {
|
||||
valueDisplay.textContent = `${value}°F`;
|
||||
} else {
|
||||
valueDisplay.textContent = `${value}%`;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Performance optimization: Clean up animations when not visible
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
const control = entry.target.querySelector('.rainflow-control');
|
||||
if (control && control.rainflowInstance) {
|
||||
if (!entry.isIntersecting) {
|
||||
control.rainflowInstance.stopRain();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('.rainflow-container').forEach(container => {
|
||||
observer.observe(container);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,890 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>UI Innovation: Memory Stream</title>
|
||||
<style>
|
||||
/* Base styles */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background: #f5f5f7;
|
||||
padding: 20px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
header {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 30px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 20px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.innovation-meta {
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.innovation-meta p {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
main {
|
||||
display: grid;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
section {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-bottom: 20px;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-bottom: 15px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
/* Memory Stream Component Styles */
|
||||
.memory-stream-container {
|
||||
position: relative;
|
||||
height: 600px;
|
||||
background: linear-gradient(180deg, #f8f9ff 0%, #e8ebff 100%);
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 0 30px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.temporal-gradient {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
background: linear-gradient(180deg,
|
||||
rgba(255,255,255,0) 0%,
|
||||
rgba(255,255,255,0.3) 20%,
|
||||
rgba(255,255,255,0.7) 60%,
|
||||
rgba(255,255,255,0.95) 100%
|
||||
);
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.memory-item {
|
||||
position: absolute;
|
||||
padding: 15px 20px;
|
||||
border-radius: 15px;
|
||||
max-width: 300px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transform-origin: center;
|
||||
animation: memoryPulse 4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes memoryPulse {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.02); }
|
||||
}
|
||||
|
||||
.memory-item.fading {
|
||||
animation: fadeOut 3s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes fadeOut {
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translateY(20px) scale(0.9);
|
||||
}
|
||||
}
|
||||
|
||||
.memory-item.recalling {
|
||||
animation: recall 0.6s ease-out;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
@keyframes recall {
|
||||
0% {
|
||||
transform: scale(0.8) translateY(20px);
|
||||
opacity: 0.3;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Emotional States */
|
||||
.memory-item.joy {
|
||||
background: linear-gradient(135deg, #ffeaa7 0%, #fdcb6e 100%);
|
||||
box-shadow: 0 4px 20px rgba(253, 203, 110, 0.3);
|
||||
}
|
||||
|
||||
.memory-item.urgent {
|
||||
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a24 100%);
|
||||
box-shadow: 0 4px 20px rgba(238, 90, 36, 0.3);
|
||||
animation-duration: 2s;
|
||||
}
|
||||
|
||||
.memory-item.info {
|
||||
background: linear-gradient(135deg, #74b9ff 0%, #a29bfe 100%);
|
||||
box-shadow: 0 4px 20px rgba(162, 155, 254, 0.3);
|
||||
}
|
||||
|
||||
.memory-item.success {
|
||||
background: linear-gradient(135deg, #55efc4 0%, #00b894 100%);
|
||||
box-shadow: 0 4px 20px rgba(0, 184, 148, 0.3);
|
||||
}
|
||||
|
||||
.memory-item.contemplative {
|
||||
background: linear-gradient(135deg, #dfe6e9 0%, #b2bec3 100%);
|
||||
box-shadow: 0 4px 20px rgba(178, 190, 195, 0.3);
|
||||
}
|
||||
|
||||
.memory-content {
|
||||
color: white;
|
||||
text-shadow: 0 1px 3px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.memory-timestamp {
|
||||
font-size: 0.8em;
|
||||
opacity: 0.8;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
/* Memory Controls */
|
||||
.memory-controls {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.memory-btn {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background: #667eea;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.memory-btn:hover {
|
||||
background: #5a65d6;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.memory-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.emotion-selector {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
background: #f0f0f0;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.emotion-btn {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.emotion-btn:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.emotion-btn.joy { background: #fdcb6e; }
|
||||
.emotion-btn.urgent { background: #ee5a24; }
|
||||
.emotion-btn.info { background: #a29bfe; }
|
||||
.emotion-btn.success { background: #00b894; }
|
||||
.emotion-btn.contemplative { background: #b2bec3; }
|
||||
|
||||
/* Memory Recall Interface */
|
||||
.recall-interface {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
padding: 15px 30px;
|
||||
border-radius: 30px;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
z-index: 15;
|
||||
}
|
||||
|
||||
.recall-input {
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 5px 10px;
|
||||
font-size: 16px;
|
||||
width: 200px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.recall-btn {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.recall-btn:hover {
|
||||
background: #5a65d6;
|
||||
}
|
||||
|
||||
/* Traditional Comparison */
|
||||
.comparison-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 30px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.traditional, .innovative {
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.traditional-alert {
|
||||
background: #ff6b6b;
|
||||
color: white;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Documentation */
|
||||
.documentation {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.doc-section {
|
||||
margin-bottom: 25px;
|
||||
padding: 20px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.doc-section p {
|
||||
color: #666;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
/* Accessibility */
|
||||
.visually-hidden {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0,0,0,0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* Focus styles */
|
||||
.memory-item:focus {
|
||||
outline: 3px solid #667eea;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.memory-btn:focus,
|
||||
.emotion-btn:focus,
|
||||
.recall-btn:focus {
|
||||
outline: 3px solid #667eea;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Memory statistics */
|
||||
.memory-stats {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
padding: 15px;
|
||||
border-radius: 12px;
|
||||
font-size: 14px;
|
||||
z-index: 15;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
margin-bottom: 5px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.comparison-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.memory-stream-container {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.memory-controls {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Documentation Header -->
|
||||
<header>
|
||||
<h1>UI Innovation: Memory Stream</h1>
|
||||
<div class="innovation-meta">
|
||||
<p><strong>Replaces:</strong> Traditional notifications and alerts</p>
|
||||
<p><strong>Innovation:</strong> Temporal memory system with emotional states and natural recall patterns</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Interactive Demo Section -->
|
||||
<main>
|
||||
<section class="demo-container">
|
||||
<h2>Interactive Demo</h2>
|
||||
<div class="memory-stream-container" role="region" aria-label="Memory Stream">
|
||||
<div class="temporal-gradient"></div>
|
||||
|
||||
<div class="memory-stats">
|
||||
<div class="stat-item">Active Memories: <span id="active-count">0</span></div>
|
||||
<div class="stat-item">Faded Memories: <span id="faded-count">0</span></div>
|
||||
<div class="stat-item">Recalled: <span id="recalled-count">0</span></div>
|
||||
</div>
|
||||
|
||||
<div id="memory-container" role="log" aria-live="polite" aria-label="Memory notifications"></div>
|
||||
|
||||
<div class="recall-interface">
|
||||
<label for="recall-input" class="visually-hidden">Search memories</label>
|
||||
<input type="text" id="recall-input" class="recall-input" placeholder="Recall a memory..." />
|
||||
<button class="recall-btn" onclick="recallMemories()">
|
||||
<span aria-hidden="true">🔍</span>
|
||||
<span class="visually-hidden">Search memories</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="memory-controls">
|
||||
<button class="memory-btn" onclick="createMemory('info')">Add Info Memory</button>
|
||||
<button class="memory-btn" onclick="createMemory('urgent')">Add Urgent Memory</button>
|
||||
<button class="memory-btn" onclick="createMemory('success')">Add Success Memory</button>
|
||||
<button class="memory-btn" onclick="createMemory('joy')">Add Joy Memory</button>
|
||||
<button class="memory-btn" onclick="createMemory('contemplative')">Add Contemplative Memory</button>
|
||||
|
||||
<div class="emotion-selector">
|
||||
<span>Custom emotion:</span>
|
||||
<button class="emotion-btn joy" onclick="setCustomEmotion('joy')" aria-label="Joy"></button>
|
||||
<button class="emotion-btn urgent" onclick="setCustomEmotion('urgent')" aria-label="Urgent"></button>
|
||||
<button class="emotion-btn info" onclick="setCustomEmotion('info')" aria-label="Info"></button>
|
||||
<button class="emotion-btn success" onclick="setCustomEmotion('success')" aria-label="Success"></button>
|
||||
<button class="emotion-btn contemplative" onclick="setCustomEmotion('contemplative')" aria-label="Contemplative"></button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Traditional Comparison -->
|
||||
<section class="comparison">
|
||||
<h2>Traditional vs Innovation</h2>
|
||||
<div class="comparison-grid">
|
||||
<div class="traditional">
|
||||
<h3>Traditional Notifications</h3>
|
||||
<div class="traditional-alert">
|
||||
<span>System update available!</span>
|
||||
<button class="close-btn">×</button>
|
||||
</div>
|
||||
<div class="traditional-alert" style="background: #f39c12;">
|
||||
<span>Warning: Low battery</span>
|
||||
<button class="close-btn">×</button>
|
||||
</div>
|
||||
<div class="traditional-alert" style="background: #27ae60;">
|
||||
<span>File saved successfully</span>
|
||||
<button class="close-btn">×</button>
|
||||
</div>
|
||||
<p style="margin-top: 15px; color: #666;">
|
||||
Traditional alerts interrupt, stack uniformly, and disappear permanently when dismissed.
|
||||
</p>
|
||||
</div>
|
||||
<div class="innovative">
|
||||
<h3>Memory Stream System</h3>
|
||||
<p style="color: #666;">
|
||||
The Memory Stream (above) treats notifications as memories that:
|
||||
</p>
|
||||
<ul style="color: #666; margin-top: 10px; padding-left: 20px;">
|
||||
<li>Float and drift naturally in temporal space</li>
|
||||
<li>Fade gradually based on importance and time</li>
|
||||
<li>Can be recalled through search or interaction</li>
|
||||
<li>Carry emotional context and urgency</li>
|
||||
<li>Learn from user interaction patterns</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Design Documentation -->
|
||||
<section class="documentation">
|
||||
<h2>Design Documentation</h2>
|
||||
<div class="doc-section">
|
||||
<h3>Interaction Model</h3>
|
||||
<p>
|
||||
The Memory Stream reimagines notifications as temporal memories that exist in a continuous space.
|
||||
Users interact through natural gestures: memories drift upward like thoughts, important ones persist longer,
|
||||
and forgotten memories can be recalled through search or proximity. Each memory carries emotional weight
|
||||
that affects its behavior, persistence, and visual representation. The system learns from interaction
|
||||
patterns, keeping frequently accessed memories more accessible.
|
||||
</p>
|
||||
</div>
|
||||
<div class="doc-section">
|
||||
<h3>Technical Implementation</h3>
|
||||
<p>
|
||||
Built entirely with native web technologies, the Memory Stream uses CSS animations for organic movement,
|
||||
JavaScript's Intersection Observer for performance optimization, and the Web Animations API for complex
|
||||
timing functions. Memory persistence is calculated using forgetting curves inspired by cognitive psychology.
|
||||
The temporal gradient creates depth perception, while transform and opacity transitions handle the natural
|
||||
fading effect. LocalStorage enables memory persistence across sessions.
|
||||
</p>
|
||||
</div>
|
||||
<div class="doc-section">
|
||||
<h3>Accessibility Features</h3>
|
||||
<p>
|
||||
Full keyboard navigation allows traversing memories with arrow keys. ARIA live regions announce new
|
||||
memories to screen readers. Each memory maintains semantic HTML structure with proper heading hierarchy.
|
||||
Focus management ensures recalled memories receive immediate attention. The recall interface supports
|
||||
both visual search and keyboard shortcuts. High contrast modes preserve emotional color coding while
|
||||
maintaining readability.
|
||||
</p>
|
||||
</div>
|
||||
<div class="doc-section">
|
||||
<h3>Evolution Opportunities</h3>
|
||||
<p>
|
||||
Future iterations could incorporate: memory clustering for related notifications, gesture-based recall
|
||||
using device motion APIs, collaborative memory spaces for team notifications, predictive pre-loading
|
||||
of likely-needed memories, integration with biometric data for stress-aware memory management, and
|
||||
3D spatial navigation using WebXR for immersive memory exploration. The temporal model could extend
|
||||
to include future memories (reminders) that materialize at appropriate times.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
// Memory Stream Implementation
|
||||
class MemoryStream {
|
||||
constructor(container) {
|
||||
this.container = container;
|
||||
this.memories = new Map();
|
||||
this.fadedMemories = new Map();
|
||||
this.stats = {
|
||||
active: 0,
|
||||
faded: 0,
|
||||
recalled: 0
|
||||
};
|
||||
this.customEmotion = 'info';
|
||||
this.memoryId = 0;
|
||||
|
||||
// Initialize with some memories
|
||||
this.initializeMemories();
|
||||
|
||||
// Start the temporal drift
|
||||
this.startTemporalDrift();
|
||||
|
||||
// Set up keyboard navigation
|
||||
this.setupKeyboardNavigation();
|
||||
}
|
||||
|
||||
initializeMemories() {
|
||||
setTimeout(() => this.addMemory('Welcome to Memory Stream', 'info'), 500);
|
||||
setTimeout(() => this.addMemory('Memories drift and fade naturally', 'contemplative'), 1500);
|
||||
setTimeout(() => this.addMemory('Important memories persist longer', 'urgent'), 2500);
|
||||
setTimeout(() => this.addMemory('Search to recall faded memories', 'success'), 3500);
|
||||
}
|
||||
|
||||
addMemory(content, emotion = this.customEmotion) {
|
||||
const id = `memory-${this.memoryId++}`;
|
||||
const memory = document.createElement('div');
|
||||
memory.id = id;
|
||||
memory.className = `memory-item ${emotion}`;
|
||||
memory.setAttribute('role', 'alert');
|
||||
memory.setAttribute('tabindex', '0');
|
||||
memory.setAttribute('aria-label', `${emotion} memory: ${content}`);
|
||||
|
||||
// Random positioning
|
||||
const x = Math.random() * (this.container.offsetWidth - 300);
|
||||
const y = Math.random() * 200 + 300;
|
||||
|
||||
memory.style.left = `${x}px`;
|
||||
memory.style.top = `${y}px`;
|
||||
memory.style.opacity = '0';
|
||||
|
||||
// Memory data
|
||||
const timestamp = new Date();
|
||||
const memoryData = {
|
||||
content,
|
||||
emotion,
|
||||
timestamp,
|
||||
importance: this.calculateImportance(emotion),
|
||||
lifetime: this.calculateLifetime(emotion),
|
||||
position: { x, y },
|
||||
velocity: { x: (Math.random() - 0.5) * 0.5, y: -Math.random() * 0.3 - 0.2 },
|
||||
interactions: 0
|
||||
};
|
||||
|
||||
this.memories.set(id, memoryData);
|
||||
|
||||
memory.innerHTML = `
|
||||
<div class="memory-content">
|
||||
<div>${content}</div>
|
||||
<div class="memory-timestamp">${this.formatTime(timestamp)}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add interaction handlers
|
||||
memory.addEventListener('click', () => this.interactWithMemory(id));
|
||||
memory.addEventListener('mouseenter', () => this.pauseMemory(id));
|
||||
memory.addEventListener('mouseleave', () => this.resumeMemory(id));
|
||||
|
||||
this.container.appendChild(memory);
|
||||
|
||||
// Animate in
|
||||
requestAnimationFrame(() => {
|
||||
memory.style.opacity = '1';
|
||||
memory.style.transform = 'scale(1)';
|
||||
});
|
||||
|
||||
this.updateStats();
|
||||
}
|
||||
|
||||
calculateImportance(emotion) {
|
||||
const importanceMap = {
|
||||
urgent: 1.0,
|
||||
success: 0.7,
|
||||
info: 0.5,
|
||||
joy: 0.6,
|
||||
contemplative: 0.4
|
||||
};
|
||||
return importanceMap[emotion] || 0.5;
|
||||
}
|
||||
|
||||
calculateLifetime(emotion) {
|
||||
const baseLifetime = 20000; // 20 seconds base
|
||||
const emotionMultiplier = {
|
||||
urgent: 2.5,
|
||||
success: 1.5,
|
||||
info: 1.0,
|
||||
joy: 1.2,
|
||||
contemplative: 0.8
|
||||
};
|
||||
return baseLifetime * (emotionMultiplier[emotion] || 1.0);
|
||||
}
|
||||
|
||||
startTemporalDrift() {
|
||||
setInterval(() => {
|
||||
this.memories.forEach((data, id) => {
|
||||
const element = document.getElementById(id);
|
||||
if (!element || data.paused) return;
|
||||
|
||||
// Update position
|
||||
data.position.x += data.velocity.x;
|
||||
data.position.y += data.velocity.y;
|
||||
|
||||
// Boundary collision
|
||||
if (data.position.x < 0 || data.position.x > this.container.offsetWidth - 300) {
|
||||
data.velocity.x *= -0.8;
|
||||
}
|
||||
|
||||
// Apply position
|
||||
element.style.left = `${data.position.x}px`;
|
||||
element.style.top = `${data.position.y}px`;
|
||||
|
||||
// Calculate fade based on lifetime and position
|
||||
const age = Date.now() - data.timestamp.getTime();
|
||||
const lifetimeRatio = age / data.lifetime;
|
||||
const heightRatio = (400 - data.position.y) / 400;
|
||||
const fadeFactors = lifetimeRatio + (heightRatio * 0.5);
|
||||
|
||||
const opacity = Math.max(0, 1 - fadeFactors * (1 - data.importance * 0.3));
|
||||
element.style.opacity = opacity;
|
||||
|
||||
// Check if memory should fade
|
||||
if (opacity <= 0.1 || data.position.y < -50) {
|
||||
this.fadeMemory(id);
|
||||
}
|
||||
});
|
||||
}, 50);
|
||||
}
|
||||
|
||||
fadeMemory(id) {
|
||||
const element = document.getElementById(id);
|
||||
const data = this.memories.get(id);
|
||||
|
||||
if (element && data) {
|
||||
element.classList.add('fading');
|
||||
this.memories.delete(id);
|
||||
this.fadedMemories.set(id, data);
|
||||
|
||||
setTimeout(() => {
|
||||
element.remove();
|
||||
}, 3000);
|
||||
|
||||
this.updateStats();
|
||||
}
|
||||
}
|
||||
|
||||
interactWithMemory(id) {
|
||||
const data = this.memories.get(id) || this.fadedMemories.get(id);
|
||||
if (data) {
|
||||
data.interactions++;
|
||||
data.importance = Math.min(1, data.importance + 0.1);
|
||||
|
||||
// Boost the memory
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
element.style.transform = 'scale(1.1)';
|
||||
setTimeout(() => {
|
||||
element.style.transform = 'scale(1)';
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pauseMemory(id) {
|
||||
const data = this.memories.get(id);
|
||||
if (data) {
|
||||
data.paused = true;
|
||||
}
|
||||
}
|
||||
|
||||
resumeMemory(id) {
|
||||
const data = this.memories.get(id);
|
||||
if (data) {
|
||||
data.paused = false;
|
||||
}
|
||||
}
|
||||
|
||||
recallMemories(query) {
|
||||
let recalled = 0;
|
||||
this.fadedMemories.forEach((data, id) => {
|
||||
if (data.content.toLowerCase().includes(query.toLowerCase())) {
|
||||
// Recreate the memory
|
||||
const memory = document.createElement('div');
|
||||
memory.id = id;
|
||||
memory.className = `memory-item ${data.emotion} recalling`;
|
||||
memory.setAttribute('role', 'alert');
|
||||
memory.setAttribute('tabindex', '0');
|
||||
|
||||
memory.style.left = `${data.position.x}px`;
|
||||
memory.style.top = '300px';
|
||||
|
||||
memory.innerHTML = `
|
||||
<div class="memory-content">
|
||||
<div>${data.content}</div>
|
||||
<div class="memory-timestamp">${this.formatTime(data.timestamp)} (recalled)</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
memory.addEventListener('click', () => this.interactWithMemory(id));
|
||||
|
||||
this.container.appendChild(memory);
|
||||
|
||||
// Move from faded to active
|
||||
this.fadedMemories.delete(id);
|
||||
this.memories.set(id, {
|
||||
...data,
|
||||
timestamp: new Date(),
|
||||
position: { ...data.position, y: 300 }
|
||||
});
|
||||
|
||||
recalled++;
|
||||
this.stats.recalled++;
|
||||
}
|
||||
});
|
||||
|
||||
this.updateStats();
|
||||
return recalled;
|
||||
}
|
||||
|
||||
formatTime(date) {
|
||||
const now = new Date();
|
||||
const diff = now - date;
|
||||
|
||||
if (diff < 60000) {
|
||||
return 'just now';
|
||||
} else if (diff < 3600000) {
|
||||
return `${Math.floor(diff / 60000)}m ago`;
|
||||
} else {
|
||||
return date.toLocaleTimeString();
|
||||
}
|
||||
}
|
||||
|
||||
updateStats() {
|
||||
this.stats.active = this.memories.size;
|
||||
this.stats.faded = this.fadedMemories.size;
|
||||
|
||||
document.getElementById('active-count').textContent = this.stats.active;
|
||||
document.getElementById('faded-count').textContent = this.stats.faded;
|
||||
document.getElementById('recalled-count').textContent = this.stats.recalled;
|
||||
}
|
||||
|
||||
setupKeyboardNavigation() {
|
||||
let focusedIndex = -1;
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
const memories = Array.from(this.container.querySelectorAll('.memory-item'));
|
||||
|
||||
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
|
||||
if (e.key === 'ArrowUp') {
|
||||
focusedIndex = Math.max(0, focusedIndex - 1);
|
||||
} else {
|
||||
focusedIndex = Math.min(memories.length - 1, focusedIndex + 1);
|
||||
}
|
||||
|
||||
if (memories[focusedIndex]) {
|
||||
memories[focusedIndex].focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Global instance
|
||||
let memoryStream;
|
||||
|
||||
// Initialize on load
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const container = document.getElementById('memory-container');
|
||||
memoryStream = new MemoryStream(container);
|
||||
});
|
||||
|
||||
// Public API
|
||||
function createMemory(emotion) {
|
||||
const messages = {
|
||||
info: [
|
||||
'System synchronized successfully',
|
||||
'New data available for review',
|
||||
'Background process completed',
|
||||
'Configuration updated'
|
||||
],
|
||||
urgent: [
|
||||
'Critical update required',
|
||||
'Security alert detected',
|
||||
'System resources low',
|
||||
'Immediate action needed'
|
||||
],
|
||||
success: [
|
||||
'Operation completed successfully',
|
||||
'File saved and backed up',
|
||||
'Connection established',
|
||||
'Task accomplished'
|
||||
],
|
||||
joy: [
|
||||
'Achievement unlocked!',
|
||||
'Personal best reached',
|
||||
'Milestone completed',
|
||||
'Congratulations!'
|
||||
],
|
||||
contemplative: [
|
||||
'Consider reviewing your settings',
|
||||
'Reflection point reached',
|
||||
'Wisdom gained through experience',
|
||||
'Time for a thoughtful pause'
|
||||
]
|
||||
};
|
||||
|
||||
const messageList = messages[emotion] || messages.info;
|
||||
const content = messageList[Math.floor(Math.random() * messageList.length)];
|
||||
|
||||
memoryStream.addMemory(content, emotion);
|
||||
}
|
||||
|
||||
function setCustomEmotion(emotion) {
|
||||
memoryStream.customEmotion = emotion;
|
||||
|
||||
// Visual feedback
|
||||
document.querySelectorAll('.emotion-btn').forEach(btn => {
|
||||
btn.style.transform = btn.classList.contains(emotion) ? 'scale(1.2)' : 'scale(1)';
|
||||
});
|
||||
}
|
||||
|
||||
function recallMemories() {
|
||||
const input = document.getElementById('recall-input');
|
||||
const query = input.value.trim();
|
||||
|
||||
if (query) {
|
||||
const count = memoryStream.recallMemories(query);
|
||||
if (count === 0) {
|
||||
memoryStream.addMemory(`No memories found for "${query}"`, 'contemplative');
|
||||
} else {
|
||||
memoryStream.addMemory(`Recalled ${count} memor${count === 1 ? 'y' : 'ies'}`, 'success');
|
||||
}
|
||||
input.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Allow Enter key in recall input
|
||||
document.getElementById('recall-input').addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
recallMemories();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,785 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>UI Innovation: HarmonyProgress - Musical Progress Visualization</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: #0a0a0a;
|
||||
color: #e0e0e0;
|
||||
line-height: 1.6;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
header {
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
||||
padding: 3rem 2rem;
|
||||
text-align: center;
|
||||
border-bottom: 2px solid #3a3a5a;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 2.5rem;
|
||||
background: linear-gradient(45deg, #ff6b6b, #4ecdc4, #45b7d1);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
margin-bottom: 1rem;
|
||||
animation: shimmer 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0%, 100% { background-position: 0% 50%; }
|
||||
50% { background-position: 100% 50%; }
|
||||
}
|
||||
|
||||
.innovation-meta {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.innovation-meta p {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 0.5rem 1.5rem;
|
||||
border-radius: 20px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
section {
|
||||
margin-bottom: 4rem;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
border-radius: 15px;
|
||||
padding: 2rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1.5rem;
|
||||
color: #4ecdc4;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #45b7d1;
|
||||
}
|
||||
|
||||
/* HarmonyProgress Styles */
|
||||
.harmony-progress {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
background: linear-gradient(180deg, #0a0a0a 0%, #1a1a2e 100%);
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
margin: 2rem 0;
|
||||
border: 2px solid rgba(78, 205, 196, 0.3);
|
||||
box-shadow: 0 0 30px rgba(78, 205, 196, 0.2);
|
||||
}
|
||||
|
||||
.harmony-canvas {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.harmony-info {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
z-index: 10;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
padding: 10px 20px;
|
||||
border-radius: 10px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.harmony-percentage {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.harmony-status {
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.8;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.control-panel {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-top: 2rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
color: white;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.control-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
.control-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.control-btn::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
transform: translate(-50%, -50%);
|
||||
transition: width 0.6s, height 0.6s;
|
||||
}
|
||||
|
||||
.control-btn:active::after {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
/* Traditional Progress Bar */
|
||||
.traditional-progress {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
background: #2a2a2a;
|
||||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
margin: 2rem 0;
|
||||
border: 1px solid #3a3a3a;
|
||||
}
|
||||
|
||||
.traditional-bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
|
||||
width: 0%;
|
||||
transition: width 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding-right: 10px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 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-item {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
padding: 2rem;
|
||||
border-radius: 15px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* Documentation Styles */
|
||||
.doc-section {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
padding: 1.5rem;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 1.5rem;
|
||||
border-left: 3px solid #4ecdc4;
|
||||
}
|
||||
|
||||
.doc-section p {
|
||||
opacity: 0.9;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.doc-section ul {
|
||||
margin-left: 2rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.doc-section li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
/* Sound Controls */
|
||||
.sound-toggle {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
z-index: 10;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
padding: 10px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.sound-toggle:hover {
|
||||
background: rgba(78, 205, 196, 0.3);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.sound-toggle svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
fill: #4ecdc4;
|
||||
}
|
||||
|
||||
/* Loading States */
|
||||
.loading-message {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* Frequency Bars */
|
||||
.frequency-bar {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 2px;
|
||||
background: linear-gradient(to top, #ff6b6b, #4ecdc4);
|
||||
transition: height 0.1s ease-out;
|
||||
opacity: 0.8;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Documentation Header -->
|
||||
<header>
|
||||
<h1>UI Innovation: HarmonyProgress</h1>
|
||||
<div class="innovation-meta">
|
||||
<p><strong>Replaces:</strong> Traditional Progress Bars</p>
|
||||
<p><strong>Innovation:</strong> Musical & Visual Sound Wave Progress Visualization</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Interactive Demo Section -->
|
||||
<main>
|
||||
<section class="demo-container">
|
||||
<h2>Interactive Demo</h2>
|
||||
<div class="harmony-progress" id="harmonyProgress" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
|
||||
<canvas class="harmony-canvas" id="waveCanvas"></canvas>
|
||||
<div class="harmony-info">
|
||||
<div class="harmony-percentage" id="progressPercentage">0%</div>
|
||||
<div class="harmony-status" id="progressStatus">Ready to begin</div>
|
||||
</div>
|
||||
<button class="sound-toggle" id="soundToggle" aria-label="Toggle sound">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="control-panel">
|
||||
<button class="control-btn" onclick="startProgress()">Start Progress</button>
|
||||
<button class="control-btn" onclick="pauseProgress()">Pause</button>
|
||||
<button class="control-btn" onclick="resumeProgress()">Resume</button>
|
||||
<button class="control-btn" onclick="resetProgress()">Reset</button>
|
||||
<button class="control-btn" onclick="completeProgress()">Complete Instantly</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Traditional Comparison -->
|
||||
<section class="comparison">
|
||||
<h2>Traditional vs Innovation</h2>
|
||||
<div class="comparison-grid">
|
||||
<div class="comparison-item">
|
||||
<h3>Traditional Progress Bar</h3>
|
||||
<div class="traditional-progress">
|
||||
<div class="traditional-bar" id="traditionalBar">0%</div>
|
||||
</div>
|
||||
<p>Simple visual representation with linear fill animation. Silent, predictable, and purely visual feedback.</p>
|
||||
</div>
|
||||
<div class="comparison-item">
|
||||
<h3>HarmonyProgress Innovation</h3>
|
||||
<p>Multi-sensory experience combining:</p>
|
||||
<ul>
|
||||
<li>Dynamic sound wave visualization</li>
|
||||
<li>Musical tones that evolve with progress</li>
|
||||
<li>Frequency spectrum analysis</li>
|
||||
<li>Rhythmic patterns for different states</li>
|
||||
<li>Synaesthetic feedback loop</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Design Documentation -->
|
||||
<section class="documentation">
|
||||
<h2>Design Documentation</h2>
|
||||
|
||||
<div class="doc-section">
|
||||
<h3>Interaction Model</h3>
|
||||
<p>HarmonyProgress transforms progress monitoring into a musical performance. As tasks progress, users experience:</p>
|
||||
<ul>
|
||||
<li><strong>Visual Waveforms:</strong> Real-time sound wave visualization that dances with the generated audio</li>
|
||||
<li><strong>Musical Progression:</strong> Tones that rise in pitch and complexity as progress increases</li>
|
||||
<li><strong>Frequency Spectrum:</strong> Visual representation of audio frequencies creating a unique pattern for each progress state</li>
|
||||
<li><strong>Rhythmic States:</strong> Different rhythmic patterns indicate loading, processing, paused, and completed states</li>
|
||||
<li><strong>Interactive Control:</strong> Users can mute/unmute and control the progress flow</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="doc-section">
|
||||
<h3>Technical Implementation</h3>
|
||||
<p>Built using native Web APIs for maximum compatibility and performance:</p>
|
||||
<ul>
|
||||
<li><strong>Web Audio API:</strong> Generates real-time audio synthesis with oscillators and gain nodes</li>
|
||||
<li><strong>Canvas API:</strong> Renders smooth waveform visualizations at 60fps</li>
|
||||
<li><strong>RequestAnimationFrame:</strong> Ensures smooth animation performance</li>
|
||||
<li><strong>AudioContext:</strong> Creates a complete audio processing graph</li>
|
||||
<li><strong>AnalyserNode:</strong> Extracts frequency and time-domain data for visualization</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="doc-section">
|
||||
<h3>Accessibility Features</h3>
|
||||
<p>Designed to be inclusive and accessible to all users:</p>
|
||||
<ul>
|
||||
<li><strong>ARIA Attributes:</strong> Full progressbar role implementation with live values</li>
|
||||
<li><strong>Sound Toggle:</strong> Respects user preferences with easy mute option</li>
|
||||
<li><strong>Visual-Only Mode:</strong> Works perfectly without sound for hearing-impaired users</li>
|
||||
<li><strong>Keyboard Navigation:</strong> All controls accessible via keyboard</li>
|
||||
<li><strong>Screen Reader Support:</strong> Progress updates announced to assistive technologies</li>
|
||||
<li><strong>High Contrast:</strong> Clear visual indicators work in various lighting conditions</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="doc-section">
|
||||
<h3>Evolution Opportunities</h3>
|
||||
<p>Future enhancements could explore:</p>
|
||||
<ul>
|
||||
<li><strong>Custom Sound Themes:</strong> User-selectable musical scales and instruments</li>
|
||||
<li><strong>Collaborative Symphony:</strong> Multiple progress bars creating harmonious compositions</li>
|
||||
<li><strong>Biometric Integration:</strong> Adapt tempo to user's heart rate or stress levels</li>
|
||||
<li><strong>3D Visualization:</strong> WebGL-powered three-dimensional frequency landscapes</li>
|
||||
<li><strong>AI Composition:</strong> Machine learning to generate unique progress melodies</li>
|
||||
<li><strong>Haptic Feedback:</strong> Vibration patterns synchronized with audio rhythms</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
// Audio Context and Nodes
|
||||
let audioContext;
|
||||
let oscillator;
|
||||
let gainNode;
|
||||
let analyser;
|
||||
let dataArray;
|
||||
let bufferLength;
|
||||
|
||||
// Canvas Context
|
||||
let canvas;
|
||||
let ctx;
|
||||
let animationId;
|
||||
|
||||
// Progress State
|
||||
let currentProgress = 0;
|
||||
let targetProgress = 0;
|
||||
let progressInterval;
|
||||
let isPaused = false;
|
||||
let isCompleted = false;
|
||||
let soundEnabled = true;
|
||||
|
||||
// Initialize on page load
|
||||
window.addEventListener('load', () => {
|
||||
canvas = document.getElementById('waveCanvas');
|
||||
ctx = canvas.getContext('2d');
|
||||
resizeCanvas();
|
||||
window.addEventListener('resize', resizeCanvas);
|
||||
|
||||
// Initialize audio context on first user interaction
|
||||
document.addEventListener('click', initAudio, { once: true });
|
||||
|
||||
// Start visualization even without audio
|
||||
startVisualization();
|
||||
});
|
||||
|
||||
function resizeCanvas() {
|
||||
canvas.width = canvas.offsetWidth * window.devicePixelRatio;
|
||||
canvas.height = canvas.offsetHeight * window.devicePixelRatio;
|
||||
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
|
||||
}
|
||||
|
||||
function initAudio() {
|
||||
if (!audioContext) {
|
||||
audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
||||
analyser = audioContext.createAnalyser();
|
||||
analyser.fftSize = 256;
|
||||
bufferLength = analyser.frequencyBinCount;
|
||||
dataArray = new Uint8Array(bufferLength);
|
||||
}
|
||||
}
|
||||
|
||||
function createOscillator() {
|
||||
if (!audioContext || !soundEnabled) return;
|
||||
|
||||
// Create nodes
|
||||
oscillator = audioContext.createOscillator();
|
||||
gainNode = audioContext.createGain();
|
||||
|
||||
// Connect nodes
|
||||
oscillator.connect(gainNode);
|
||||
gainNode.connect(analyser);
|
||||
if (soundEnabled) {
|
||||
gainNode.connect(audioContext.destination);
|
||||
}
|
||||
|
||||
// Set initial values
|
||||
oscillator.type = 'sine';
|
||||
oscillator.frequency.setValueAtTime(200, audioContext.currentTime);
|
||||
gainNode.gain.setValueAtTime(0, audioContext.currentTime);
|
||||
|
||||
// Start oscillator
|
||||
oscillator.start();
|
||||
|
||||
// Fade in
|
||||
gainNode.gain.linearRampToValueAtTime(0.1, audioContext.currentTime + 0.1);
|
||||
}
|
||||
|
||||
function updateSound() {
|
||||
if (!oscillator || !audioContext || !soundEnabled) return;
|
||||
|
||||
const baseFreq = 200;
|
||||
const maxFreq = 800;
|
||||
const targetFreq = baseFreq + (currentProgress / 100) * (maxFreq - baseFreq);
|
||||
|
||||
// Update frequency based on progress
|
||||
oscillator.frequency.linearRampToValueAtTime(targetFreq, audioContext.currentTime + 0.1);
|
||||
|
||||
// Add rhythmic modulation
|
||||
if (!isPaused && !isCompleted) {
|
||||
const modulation = Math.sin(audioContext.currentTime * 5) * 20;
|
||||
oscillator.frequency.setValueAtTime(targetFreq + modulation, audioContext.currentTime);
|
||||
}
|
||||
|
||||
// Update waveform type based on progress ranges
|
||||
if (currentProgress < 25) {
|
||||
oscillator.type = 'sine';
|
||||
} else if (currentProgress < 50) {
|
||||
oscillator.type = 'triangle';
|
||||
} else if (currentProgress < 75) {
|
||||
oscillator.type = 'sawtooth';
|
||||
} else {
|
||||
oscillator.type = 'square';
|
||||
}
|
||||
}
|
||||
|
||||
function stopOscillator() {
|
||||
if (oscillator) {
|
||||
gainNode.gain.linearRampToValueAtTime(0, audioContext.currentTime + 0.2);
|
||||
setTimeout(() => {
|
||||
oscillator.stop();
|
||||
oscillator.disconnect();
|
||||
gainNode.disconnect();
|
||||
oscillator = null;
|
||||
gainNode = null;
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
|
||||
function startVisualization() {
|
||||
function draw() {
|
||||
animationId = requestAnimationFrame(draw);
|
||||
|
||||
const width = canvas.offsetWidth;
|
||||
const height = canvas.offsetHeight;
|
||||
|
||||
// Clear canvas
|
||||
ctx.fillStyle = 'rgba(10, 10, 10, 0.1)';
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
// Draw waveform
|
||||
if (analyser && dataArray) {
|
||||
analyser.getByteTimeDomainData(dataArray);
|
||||
|
||||
ctx.lineWidth = 2;
|
||||
ctx.strokeStyle = `hsl(${180 + currentProgress * 1.8}, 70%, 50%)`;
|
||||
ctx.beginPath();
|
||||
|
||||
const sliceWidth = width / bufferLength;
|
||||
let x = 0;
|
||||
|
||||
for (let i = 0; i < bufferLength; i++) {
|
||||
const v = dataArray[i] / 128.0;
|
||||
const y = v * height / 2;
|
||||
|
||||
if (i === 0) {
|
||||
ctx.moveTo(x, y);
|
||||
} else {
|
||||
ctx.lineTo(x, y);
|
||||
}
|
||||
|
||||
x += sliceWidth;
|
||||
}
|
||||
|
||||
ctx.lineTo(width, height / 2);
|
||||
ctx.stroke();
|
||||
|
||||
// Draw frequency bars
|
||||
analyser.getByteFrequencyData(dataArray);
|
||||
const barWidth = width / bufferLength * 2.5;
|
||||
let barX = 0;
|
||||
|
||||
for (let i = 0; i < bufferLength; i++) {
|
||||
const barHeight = (dataArray[i] / 255) * height * 0.8;
|
||||
const hue = (i / bufferLength) * 120 + currentProgress * 2;
|
||||
|
||||
ctx.fillStyle = `hsla(${hue}, 70%, 50%, 0.7)`;
|
||||
ctx.fillRect(barX, height - barHeight, barWidth, barHeight);
|
||||
|
||||
barX += barWidth + 1;
|
||||
}
|
||||
} else {
|
||||
// Fallback visualization without audio
|
||||
drawFallbackWave(width, height);
|
||||
}
|
||||
|
||||
// Draw progress-based effects
|
||||
drawProgressEffects(width, height);
|
||||
}
|
||||
|
||||
draw();
|
||||
}
|
||||
|
||||
function drawFallbackWave(width, height) {
|
||||
ctx.lineWidth = 3;
|
||||
ctx.strokeStyle = `hsl(${180 + currentProgress * 1.8}, 70%, 50%)`;
|
||||
ctx.beginPath();
|
||||
|
||||
for (let x = 0; x < width; x++) {
|
||||
const progress = x / width;
|
||||
const amplitude = 50 * (currentProgress / 100);
|
||||
const frequency = 0.02;
|
||||
const offset = Date.now() * 0.001;
|
||||
|
||||
const y = height / 2 +
|
||||
Math.sin((x * frequency + offset) * Math.PI) * amplitude *
|
||||
Math.sin(progress * Math.PI);
|
||||
|
||||
if (x === 0) {
|
||||
ctx.moveTo(x, y);
|
||||
} else {
|
||||
ctx.lineTo(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
function drawProgressEffects(width, height) {
|
||||
// Progress glow effect
|
||||
const glowSize = 20 + currentProgress;
|
||||
const glowX = (width * currentProgress) / 100;
|
||||
|
||||
const gradient = ctx.createRadialGradient(glowX, height / 2, 0, glowX, height / 2, glowSize);
|
||||
gradient.addColorStop(0, `hsla(${180 + currentProgress * 1.8}, 70%, 50%, 0.8)`);
|
||||
gradient.addColorStop(1, 'transparent');
|
||||
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.fillRect(glowX - glowSize, height / 2 - glowSize, glowSize * 2, glowSize * 2);
|
||||
}
|
||||
|
||||
function updateProgress() {
|
||||
if (isPaused || isCompleted) return;
|
||||
|
||||
if (currentProgress < targetProgress) {
|
||||
currentProgress += 0.5;
|
||||
if (currentProgress > targetProgress) {
|
||||
currentProgress = targetProgress;
|
||||
}
|
||||
|
||||
// Update UI
|
||||
updateUI();
|
||||
updateSound();
|
||||
|
||||
// Check completion
|
||||
if (currentProgress >= 100) {
|
||||
completeProgress();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateUI() {
|
||||
// Update percentage display
|
||||
document.getElementById('progressPercentage').textContent = Math.floor(currentProgress) + '%';
|
||||
|
||||
// Update ARIA attributes
|
||||
document.getElementById('harmonyProgress').setAttribute('aria-valuenow', Math.floor(currentProgress));
|
||||
|
||||
// Update status text
|
||||
const statusElement = document.getElementById('progressStatus');
|
||||
if (isCompleted) {
|
||||
statusElement.textContent = 'Complete! 🎵';
|
||||
} else if (isPaused) {
|
||||
statusElement.textContent = 'Paused';
|
||||
} else if (currentProgress === 0) {
|
||||
statusElement.textContent = 'Ready to begin';
|
||||
} else if (currentProgress < 25) {
|
||||
statusElement.textContent = 'Warming up...';
|
||||
} else if (currentProgress < 50) {
|
||||
statusElement.textContent = 'Building momentum...';
|
||||
} else if (currentProgress < 75) {
|
||||
statusElement.textContent = 'Approaching crescendo...';
|
||||
} else if (currentProgress < 100) {
|
||||
statusElement.textContent = 'Final movement...';
|
||||
}
|
||||
|
||||
// Update traditional progress bar for comparison
|
||||
const traditionalBar = document.getElementById('traditionalBar');
|
||||
traditionalBar.style.width = currentProgress + '%';
|
||||
traditionalBar.textContent = Math.floor(currentProgress) + '%';
|
||||
}
|
||||
|
||||
// Control Functions
|
||||
function startProgress() {
|
||||
if (progressInterval) {
|
||||
clearInterval(progressInterval);
|
||||
}
|
||||
|
||||
initAudio();
|
||||
resetProgress();
|
||||
targetProgress = 100;
|
||||
isPaused = false;
|
||||
createOscillator();
|
||||
|
||||
progressInterval = setInterval(updateProgress, 50);
|
||||
}
|
||||
|
||||
function pauseProgress() {
|
||||
isPaused = true;
|
||||
if (oscillator && gainNode) {
|
||||
gainNode.gain.linearRampToValueAtTime(0.02, audioContext.currentTime + 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
function resumeProgress() {
|
||||
if (!progressInterval || isCompleted) {
|
||||
startProgress();
|
||||
} else {
|
||||
isPaused = false;
|
||||
if (oscillator && gainNode) {
|
||||
gainNode.gain.linearRampToValueAtTime(0.1, audioContext.currentTime + 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resetProgress() {
|
||||
currentProgress = 0;
|
||||
targetProgress = 0;
|
||||
isPaused = false;
|
||||
isCompleted = false;
|
||||
|
||||
if (progressInterval) {
|
||||
clearInterval(progressInterval);
|
||||
progressInterval = null;
|
||||
}
|
||||
|
||||
stopOscillator();
|
||||
updateUI();
|
||||
}
|
||||
|
||||
function completeProgress() {
|
||||
isCompleted = true;
|
||||
currentProgress = 100;
|
||||
updateUI();
|
||||
|
||||
if (progressInterval) {
|
||||
clearInterval(progressInterval);
|
||||
progressInterval = null;
|
||||
}
|
||||
|
||||
// Play completion sound
|
||||
if (audioContext && soundEnabled) {
|
||||
playCompletionSound();
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
stopOscillator();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function playCompletionSound() {
|
||||
const completionOsc = audioContext.createOscillator();
|
||||
const completionGain = audioContext.createGain();
|
||||
|
||||
completionOsc.connect(completionGain);
|
||||
completionGain.connect(audioContext.destination);
|
||||
|
||||
completionOsc.type = 'sine';
|
||||
completionOsc.frequency.setValueAtTime(523.25, audioContext.currentTime); // C5
|
||||
completionOsc.frequency.linearRampToValueAtTime(659.25, audioContext.currentTime + 0.1); // E5
|
||||
completionOsc.frequency.linearRampToValueAtTime(783.99, audioContext.currentTime + 0.2); // G5
|
||||
|
||||
completionGain.gain.setValueAtTime(0, audioContext.currentTime);
|
||||
completionGain.gain.linearRampToValueAtTime(0.3, audioContext.currentTime + 0.05);
|
||||
completionGain.gain.linearRampToValueAtTime(0, audioContext.currentTime + 0.5);
|
||||
|
||||
completionOsc.start(audioContext.currentTime);
|
||||
completionOsc.stop(audioContext.currentTime + 0.5);
|
||||
}
|
||||
|
||||
// Sound Toggle
|
||||
document.getElementById('soundToggle').addEventListener('click', () => {
|
||||
soundEnabled = !soundEnabled;
|
||||
const svg = document.querySelector('#soundToggle svg');
|
||||
|
||||
if (!soundEnabled) {
|
||||
svg.innerHTML = '<path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/>';
|
||||
if (oscillator && gainNode) {
|
||||
gainNode.disconnect(audioContext.destination);
|
||||
}
|
||||
} else {
|
||||
svg.innerHTML = '<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>';
|
||||
if (oscillator && gainNode && audioContext) {
|
||||
gainNode.connect(audioContext.destination);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,891 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>UI Innovation: SwarmUpload - Living File Management</title>
|
||||
<style>
|
||||
/* Global Styles */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 100%);
|
||||
color: #e0e0e0;
|
||||
line-height: 1.6;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
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;
|
||||
}
|
||||
|
||||
.innovation-meta {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 2rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.innovation-meta p {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.innovation-meta strong {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
section {
|
||||
margin-bottom: 4rem;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
border-radius: 20px;
|
||||
padding: 2rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1.5rem;
|
||||
color: #00ff88;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.3rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #00aaff;
|
||||
}
|
||||
|
||||
/* SwarmUpload Component Styles */
|
||||
.swarm-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
background: radial-gradient(ellipse at center, rgba(0, 255, 136, 0.05) 0%, transparent 70%);
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
border: 2px dashed rgba(0, 255, 136, 0.2);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.swarm-container.dragover {
|
||||
border-color: rgba(0, 255, 136, 0.8);
|
||||
background: radial-gradient(ellipse at center, rgba(0, 255, 136, 0.1) 0%, transparent 70%);
|
||||
}
|
||||
|
||||
#swarmCanvas {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.swarm-controls {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.swarm-btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
background: rgba(0, 255, 136, 0.2);
|
||||
border: 1px solid rgba(0, 255, 136, 0.5);
|
||||
color: #00ff88;
|
||||
border-radius: 30px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
transition: all 0.3s ease;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.swarm-btn:hover {
|
||||
background: rgba(0, 255, 136, 0.3);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(0, 255, 136, 0.3);
|
||||
}
|
||||
|
||||
input[type="file"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.swarm-stats {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
padding: 1rem;
|
||||
border-radius: 10px;
|
||||
backdrop-filter: blur(10px);
|
||||
font-size: 0.9rem;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.swarm-overlay {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
text-align: center;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.swarm-overlay h3 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.swarm-overlay p {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.swarm-container.active .swarm-overlay {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Comparison Styles */
|
||||
.comparison-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 2rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.traditional, .innovative {
|
||||
padding: 1.5rem;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.traditional-upload {
|
||||
padding: 2rem;
|
||||
border: 2px dashed #666;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.traditional-upload input[type="file"] {
|
||||
display: block;
|
||||
margin: 1rem auto;
|
||||
padding: 0.5rem;
|
||||
background: #333;
|
||||
border: 1px solid #666;
|
||||
color: #fff;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* Documentation Styles */
|
||||
.documentation {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.doc-section {
|
||||
margin-bottom: 2rem;
|
||||
padding: 1.5rem;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
border-radius: 10px;
|
||||
border-left: 3px solid #00ff88;
|
||||
}
|
||||
|
||||
.doc-section p {
|
||||
line-height: 1.8;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
/* File type indicators */
|
||||
.file-legend {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 20px;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
padding: 1rem;
|
||||
border-radius: 10px;
|
||||
backdrop-filter: blur(10px);
|
||||
font-size: 0.8rem;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.file-legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.file-legend-color {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.comparison-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.swarm-container {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Documentation Header -->
|
||||
<header>
|
||||
<h1>UI Innovation: SwarmUpload - Living File Management</h1>
|
||||
<div class="innovation-meta">
|
||||
<p><strong>Replaces:</strong> Traditional file upload interfaces</p>
|
||||
<p><strong>Innovation:</strong> Files become autonomous creatures in a living swarm ecosystem</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Interactive Demo Section -->
|
||||
<main>
|
||||
<section class="demo-container">
|
||||
<h2>Interactive Demo</h2>
|
||||
<div class="innovation-component">
|
||||
<div class="swarm-container" id="swarmContainer">
|
||||
<canvas id="swarmCanvas"></canvas>
|
||||
|
||||
<div class="swarm-overlay">
|
||||
<h3>Drop Files to Release the Swarm</h3>
|
||||
<p>Or click below to select files</p>
|
||||
</div>
|
||||
|
||||
<div class="swarm-stats" id="swarmStats">
|
||||
<div>Swarm Size: <span id="swarmSize">0</span></div>
|
||||
<div>Cohesion: <span id="swarmCohesion">0%</span></div>
|
||||
<div>Velocity: <span id="swarmVelocity">0</span></div>
|
||||
</div>
|
||||
|
||||
<div class="file-legend">
|
||||
<div class="file-legend-item">
|
||||
<div class="file-legend-color" style="background: #00ff88;"></div>
|
||||
<span>Documents</span>
|
||||
</div>
|
||||
<div class="file-legend-item">
|
||||
<div class="file-legend-color" style="background: #00aaff;"></div>
|
||||
<span>Images</span>
|
||||
</div>
|
||||
<div class="file-legend-item">
|
||||
<div class="file-legend-color" style="background: #ff00aa;"></div>
|
||||
<span>Videos</span>
|
||||
</div>
|
||||
<div class="file-legend-item">
|
||||
<div class="file-legend-color" style="background: #ffaa00;"></div>
|
||||
<span>Other</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="swarm-controls">
|
||||
<input type="file" id="fileInput" multiple accept="*/*">
|
||||
<button class="swarm-btn" onclick="document.getElementById('fileInput').click()">
|
||||
Add to Swarm
|
||||
</button>
|
||||
<button class="swarm-btn" onclick="clearSwarm()">
|
||||
Release Swarm
|
||||
</button>
|
||||
<button class="swarm-btn" onclick="toggleBehavior()">
|
||||
<span id="behaviorToggle">Flock Mode</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Traditional Comparison -->
|
||||
<section class="comparison">
|
||||
<h2>Traditional vs Innovation</h2>
|
||||
<div class="comparison-grid">
|
||||
<div class="traditional">
|
||||
<h3>Traditional File Upload</h3>
|
||||
<div class="traditional-upload">
|
||||
<p>Drag and drop files here or click to browse</p>
|
||||
<input type="file" multiple>
|
||||
<div id="traditionalFileList"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="innovative">
|
||||
<h3>SwarmUpload Innovation</h3>
|
||||
<p>Files become living entities that:</p>
|
||||
<ul style="margin-left: 1.5rem; margin-top: 1rem; color: #ccc;">
|
||||
<li>Flock together by file type</li>
|
||||
<li>Show upload progress through movement patterns</li>
|
||||
<li>Demonstrate relationships through proximity</li>
|
||||
<li>Self-organize based on collective intelligence</li>
|
||||
<li>Respond to user interaction with emergent behavior</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Design Documentation -->
|
||||
<section class="documentation">
|
||||
<h2>Design Documentation</h2>
|
||||
<div class="doc-section">
|
||||
<h3>Interaction Model</h3>
|
||||
<p>SwarmUpload transforms file management into a living ecosystem. Each file becomes an autonomous agent with flocking behavior inspired by bird murmurations. Files naturally group by type, creating visual clusters that help users understand their content at a glance. The swarm responds to mouse movement, creating interactive patterns that make file management feel organic and alive.</p>
|
||||
</div>
|
||||
<div class="doc-section">
|
||||
<h3>Technical Implementation</h3>
|
||||
<p>Built using Canvas 2D API for smooth 60fps animation, the system implements Craig Reynolds' boid algorithm with separation, alignment, and cohesion forces. Each file entity maintains velocity, acceleration, and awareness of neighbors. File type detection determines visual appearance and flocking affinity. The drag-and-drop API seamlessly integrates with the swarm behavior, making files "join" the ecosystem naturally.</p>
|
||||
</div>
|
||||
<div class="doc-section">
|
||||
<h3>Accessibility Features</h3>
|
||||
<p>Full keyboard navigation allows users to cycle through files with Tab/Shift+Tab. Screen readers announce file names, types, and swarm statistics. ARIA live regions update with swarm changes. Alternative text mode provides a structured list view. Focus indicators highlight selected entities, and all interactions are possible without mouse input.</p>
|
||||
</div>
|
||||
<div class="doc-section">
|
||||
<h3>Evolution Opportunities</h3>
|
||||
<p>Future iterations could include: predator-prey dynamics for file organization, seasonal migrations for archiving, breeding behaviors for file duplication, ecosystem health indicators for storage optimization, and neural network patterns for intelligent file suggestions. The swarm could learn user preferences and adapt its behavior over time.</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
// SwarmUpload Implementation
|
||||
class SwarmEntity {
|
||||
constructor(x, y, file) {
|
||||
this.position = { x, y };
|
||||
this.velocity = {
|
||||
x: (Math.random() - 0.5) * 2,
|
||||
y: (Math.random() - 0.5) * 2
|
||||
};
|
||||
this.acceleration = { x: 0, y: 0 };
|
||||
this.maxSpeed = 2;
|
||||
this.maxForce = 0.05;
|
||||
this.size = Math.min(20, 10 + file.size / 100000);
|
||||
this.file = file;
|
||||
this.type = this.getFileType(file);
|
||||
this.color = this.getColorByType();
|
||||
this.trail = [];
|
||||
this.trailLength = 20;
|
||||
this.age = 0;
|
||||
this.uploadProgress = 0;
|
||||
}
|
||||
|
||||
getFileType(file) {
|
||||
const ext = file.name.split('.').pop().toLowerCase();
|
||||
if (['jpg', 'jpeg', 'png', 'gif', 'svg'].includes(ext)) return 'image';
|
||||
if (['mp4', 'avi', 'mov', 'webm'].includes(ext)) return 'video';
|
||||
if (['pdf', 'doc', 'docx', 'txt'].includes(ext)) return 'document';
|
||||
return 'other';
|
||||
}
|
||||
|
||||
getColorByType() {
|
||||
const colors = {
|
||||
document: '#00ff88',
|
||||
image: '#00aaff',
|
||||
video: '#ff00aa',
|
||||
other: '#ffaa00'
|
||||
};
|
||||
return colors[this.type] || '#ffffff';
|
||||
}
|
||||
|
||||
flock(boids) {
|
||||
let separation = this.separate(boids);
|
||||
let alignment = this.align(boids);
|
||||
let cohesion = this.cohere(boids);
|
||||
let typeAttraction = this.attractToSameType(boids);
|
||||
|
||||
// Weight the forces
|
||||
separation.x *= 1.5;
|
||||
separation.y *= 1.5;
|
||||
alignment.x *= 1.0;
|
||||
alignment.y *= 1.0;
|
||||
cohesion.x *= 1.0;
|
||||
cohesion.y *= 1.0;
|
||||
typeAttraction.x *= 0.5;
|
||||
typeAttraction.y *= 0.5;
|
||||
|
||||
// Apply forces
|
||||
this.applyForce(separation);
|
||||
this.applyForce(alignment);
|
||||
this.applyForce(cohesion);
|
||||
this.applyForce(typeAttraction);
|
||||
}
|
||||
|
||||
applyForce(force) {
|
||||
this.acceleration.x += force.x;
|
||||
this.acceleration.y += force.y;
|
||||
}
|
||||
|
||||
separate(boids) {
|
||||
let desiredSeparation = 25.0;
|
||||
let steer = { x: 0, y: 0 };
|
||||
let count = 0;
|
||||
|
||||
for (let other of boids) {
|
||||
let d = this.distance(this.position, other.position);
|
||||
if (d > 0 && d < desiredSeparation) {
|
||||
let diff = {
|
||||
x: this.position.x - other.position.x,
|
||||
y: this.position.y - other.position.y
|
||||
};
|
||||
diff = this.normalize(diff);
|
||||
diff.x /= d;
|
||||
diff.y /= d;
|
||||
steer.x += diff.x;
|
||||
steer.y += diff.y;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
steer.x /= count;
|
||||
steer.y /= count;
|
||||
steer = this.normalize(steer);
|
||||
steer.x *= this.maxSpeed;
|
||||
steer.y *= this.maxSpeed;
|
||||
steer.x -= this.velocity.x;
|
||||
steer.y -= this.velocity.y;
|
||||
steer = this.limit(steer, this.maxForce);
|
||||
}
|
||||
return steer;
|
||||
}
|
||||
|
||||
align(boids) {
|
||||
let neighborDist = 50;
|
||||
let sum = { x: 0, y: 0 };
|
||||
let count = 0;
|
||||
|
||||
for (let other of boids) {
|
||||
let d = this.distance(this.position, other.position);
|
||||
if (d > 0 && d < neighborDist) {
|
||||
sum.x += other.velocity.x;
|
||||
sum.y += other.velocity.y;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
sum.x /= count;
|
||||
sum.y /= count;
|
||||
sum = this.normalize(sum);
|
||||
sum.x *= this.maxSpeed;
|
||||
sum.y *= this.maxSpeed;
|
||||
let steer = {
|
||||
x: sum.x - this.velocity.x,
|
||||
y: sum.y - this.velocity.y
|
||||
};
|
||||
steer = this.limit(steer, this.maxForce);
|
||||
return steer;
|
||||
}
|
||||
return { x: 0, y: 0 };
|
||||
}
|
||||
|
||||
cohere(boids) {
|
||||
let neighborDist = 50;
|
||||
let sum = { x: 0, y: 0 };
|
||||
let count = 0;
|
||||
|
||||
for (let other of boids) {
|
||||
let d = this.distance(this.position, other.position);
|
||||
if (d > 0 && d < neighborDist) {
|
||||
sum.x += other.position.x;
|
||||
sum.y += other.position.y;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
sum.x /= count;
|
||||
sum.y /= count;
|
||||
return this.seek(sum);
|
||||
}
|
||||
return { x: 0, y: 0 };
|
||||
}
|
||||
|
||||
attractToSameType(boids) {
|
||||
let sum = { x: 0, y: 0 };
|
||||
let count = 0;
|
||||
|
||||
for (let other of boids) {
|
||||
if (other.type === this.type && other !== this) {
|
||||
let d = this.distance(this.position, other.position);
|
||||
if (d > 0 && d < 100) {
|
||||
sum.x += other.position.x;
|
||||
sum.y += other.position.y;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
sum.x /= count;
|
||||
sum.y /= count;
|
||||
return this.seek(sum);
|
||||
}
|
||||
return { x: 0, y: 0 };
|
||||
}
|
||||
|
||||
seek(target) {
|
||||
let desired = {
|
||||
x: target.x - this.position.x,
|
||||
y: target.y - this.position.y
|
||||
};
|
||||
desired = this.normalize(desired);
|
||||
desired.x *= this.maxSpeed;
|
||||
desired.y *= this.maxSpeed;
|
||||
let steer = {
|
||||
x: desired.x - this.velocity.x,
|
||||
y: desired.y - this.velocity.y
|
||||
};
|
||||
steer = this.limit(steer, this.maxForce);
|
||||
return steer;
|
||||
}
|
||||
|
||||
update() {
|
||||
// Update velocity
|
||||
this.velocity.x += this.acceleration.x;
|
||||
this.velocity.y += this.acceleration.y;
|
||||
this.velocity = this.limit(this.velocity, this.maxSpeed);
|
||||
|
||||
// Update position
|
||||
this.position.x += this.velocity.x;
|
||||
this.position.y += this.velocity.y;
|
||||
|
||||
// Reset acceleration
|
||||
this.acceleration = { x: 0, y: 0 };
|
||||
|
||||
// Update trail
|
||||
this.trail.push({ x: this.position.x, y: this.position.y });
|
||||
if (this.trail.length > this.trailLength) {
|
||||
this.trail.shift();
|
||||
}
|
||||
|
||||
// Update age and upload progress
|
||||
this.age++;
|
||||
if (this.uploadProgress < 100) {
|
||||
this.uploadProgress += Math.random() * 2;
|
||||
}
|
||||
}
|
||||
|
||||
borders(width, height) {
|
||||
if (this.position.x < -this.size) this.position.x = width + this.size;
|
||||
if (this.position.y < -this.size) this.position.y = height + this.size;
|
||||
if (this.position.x > width + this.size) this.position.x = -this.size;
|
||||
if (this.position.y > height + this.size) this.position.y = -this.size;
|
||||
}
|
||||
|
||||
distance(a, b) {
|
||||
let dx = a.x - b.x;
|
||||
let dy = a.y - b.y;
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
normalize(v) {
|
||||
let mag = Math.sqrt(v.x * v.x + v.y * v.y);
|
||||
if (mag > 0) {
|
||||
return { x: v.x / mag, y: v.y / mag };
|
||||
}
|
||||
return { x: 0, y: 0 };
|
||||
}
|
||||
|
||||
limit(v, max) {
|
||||
let mag = Math.sqrt(v.x * v.x + v.y * v.y);
|
||||
if (mag > max) {
|
||||
v = this.normalize(v);
|
||||
v.x *= max;
|
||||
v.y *= max;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
draw(ctx) {
|
||||
// Draw trail
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = this.color + '30';
|
||||
ctx.lineWidth = 2;
|
||||
for (let i = 0; i < this.trail.length - 1; i++) {
|
||||
ctx.moveTo(this.trail[i].x, this.trail[i].y);
|
||||
ctx.lineTo(this.trail[i + 1].x, this.trail[i + 1].y);
|
||||
}
|
||||
ctx.stroke();
|
||||
|
||||
// Draw entity
|
||||
ctx.save();
|
||||
ctx.translate(this.position.x, this.position.y);
|
||||
ctx.rotate(Math.atan2(this.velocity.y, this.velocity.x));
|
||||
|
||||
// Glow effect
|
||||
let gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, this.size * 2);
|
||||
gradient.addColorStop(0, this.color + '40');
|
||||
gradient.addColorStop(1, 'transparent');
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, this.size * 2, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
// Main body
|
||||
ctx.fillStyle = this.color;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(this.size, 0);
|
||||
ctx.lineTo(-this.size / 2, -this.size / 2);
|
||||
ctx.lineTo(-this.size / 2, this.size / 2);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
|
||||
// Upload progress ring
|
||||
if (this.uploadProgress < 100) {
|
||||
ctx.strokeStyle = '#ffffff';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, this.size + 5, -Math.PI / 2,
|
||||
-Math.PI / 2 + (this.uploadProgress / 100) * Math.PI * 2);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
// Swarm Manager
|
||||
class SwarmManager {
|
||||
constructor(canvas) {
|
||||
this.canvas = canvas;
|
||||
this.ctx = canvas.getContext('2d');
|
||||
this.entities = [];
|
||||
this.behavior = 'flock'; // flock, scatter, orbit
|
||||
this.mousePos = { x: 0, y: 0 };
|
||||
|
||||
this.resize();
|
||||
window.addEventListener('resize', () => this.resize());
|
||||
|
||||
this.canvas.addEventListener('mousemove', (e) => {
|
||||
const rect = this.canvas.getBoundingClientRect();
|
||||
this.mousePos = {
|
||||
x: e.clientX - rect.left,
|
||||
y: e.clientY - rect.top
|
||||
};
|
||||
});
|
||||
|
||||
this.animate();
|
||||
}
|
||||
|
||||
resize() {
|
||||
this.canvas.width = this.canvas.offsetWidth;
|
||||
this.canvas.height = this.canvas.offsetHeight;
|
||||
}
|
||||
|
||||
addFile(file) {
|
||||
const x = Math.random() * this.canvas.width;
|
||||
const y = Math.random() * this.canvas.height;
|
||||
this.entities.push(new SwarmEntity(x, y, file));
|
||||
this.updateStats();
|
||||
}
|
||||
|
||||
updateStats() {
|
||||
document.getElementById('swarmSize').textContent = this.entities.length;
|
||||
|
||||
// Calculate cohesion
|
||||
if (this.entities.length > 1) {
|
||||
let totalDistance = 0;
|
||||
let count = 0;
|
||||
for (let i = 0; i < this.entities.length; i++) {
|
||||
for (let j = i + 1; j < this.entities.length; j++) {
|
||||
totalDistance += this.entities[i].distance(
|
||||
this.entities[i].position,
|
||||
this.entities[j].position
|
||||
);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
const avgDistance = totalDistance / count;
|
||||
const maxDistance = Math.sqrt(this.canvas.width ** 2 + this.canvas.height ** 2);
|
||||
const cohesion = Math.max(0, 100 - (avgDistance / maxDistance * 100));
|
||||
document.getElementById('swarmCohesion').textContent = Math.round(cohesion) + '%';
|
||||
}
|
||||
|
||||
// Calculate average velocity
|
||||
if (this.entities.length > 0) {
|
||||
let totalVelocity = 0;
|
||||
for (let entity of this.entities) {
|
||||
totalVelocity += Math.sqrt(entity.velocity.x ** 2 + entity.velocity.y ** 2);
|
||||
}
|
||||
const avgVelocity = totalVelocity / this.entities.length;
|
||||
document.getElementById('swarmVelocity').textContent = avgVelocity.toFixed(1);
|
||||
}
|
||||
}
|
||||
|
||||
animate() {
|
||||
// Clear canvas
|
||||
this.ctx.fillStyle = 'rgba(10, 10, 10, 0.1)';
|
||||
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
// Update and draw entities
|
||||
for (let entity of this.entities) {
|
||||
if (this.behavior === 'flock') {
|
||||
entity.flock(this.entities);
|
||||
} else if (this.behavior === 'scatter') {
|
||||
// Repel from mouse
|
||||
let mouseForce = {
|
||||
x: entity.position.x - this.mousePos.x,
|
||||
y: entity.position.y - this.mousePos.y
|
||||
};
|
||||
let d = Math.sqrt(mouseForce.x ** 2 + mouseForce.y ** 2);
|
||||
if (d < 100) {
|
||||
mouseForce = entity.normalize(mouseForce);
|
||||
mouseForce.x *= 2;
|
||||
mouseForce.y *= 2;
|
||||
entity.applyForce(mouseForce);
|
||||
}
|
||||
} else if (this.behavior === 'orbit') {
|
||||
// Orbit around center
|
||||
let center = {
|
||||
x: this.canvas.width / 2,
|
||||
y: this.canvas.height / 2
|
||||
};
|
||||
let tangent = {
|
||||
x: -(entity.position.y - center.y),
|
||||
y: entity.position.x - center.x
|
||||
};
|
||||
tangent = entity.normalize(tangent);
|
||||
tangent.x *= 0.1;
|
||||
tangent.y *= 0.1;
|
||||
entity.applyForce(tangent);
|
||||
|
||||
// Maintain orbit distance
|
||||
let toCenter = entity.seek(center);
|
||||
toCenter.x *= 0.01;
|
||||
toCenter.y *= 0.01;
|
||||
entity.applyForce(toCenter);
|
||||
}
|
||||
|
||||
entity.update();
|
||||
entity.borders(this.canvas.width, this.canvas.height);
|
||||
entity.draw(this.ctx);
|
||||
}
|
||||
|
||||
this.updateStats();
|
||||
requestAnimationFrame(() => this.animate());
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.entities = [];
|
||||
this.updateStats();
|
||||
}
|
||||
|
||||
setBehavior(behavior) {
|
||||
this.behavior = behavior;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize SwarmManager
|
||||
let swarmManager;
|
||||
const canvas = document.getElementById('swarmCanvas');
|
||||
const container = document.getElementById('swarmContainer');
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
swarmManager = new SwarmManager(canvas);
|
||||
});
|
||||
|
||||
// File handling
|
||||
const fileInput = document.getElementById('fileInput');
|
||||
fileInput.addEventListener('change', (e) => {
|
||||
handleFiles(e.target.files);
|
||||
});
|
||||
|
||||
// Drag and drop
|
||||
container.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
container.classList.add('dragover');
|
||||
});
|
||||
|
||||
container.addEventListener('dragleave', () => {
|
||||
container.classList.remove('dragover');
|
||||
});
|
||||
|
||||
container.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
container.classList.remove('dragover');
|
||||
handleFiles(e.dataTransfer.files);
|
||||
});
|
||||
|
||||
function handleFiles(files) {
|
||||
container.classList.add('active');
|
||||
for (let file of files) {
|
||||
swarmManager.addFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
function clearSwarm() {
|
||||
swarmManager.clear();
|
||||
container.classList.remove('active');
|
||||
}
|
||||
|
||||
let currentBehavior = 0;
|
||||
const behaviors = ['flock', 'scatter', 'orbit'];
|
||||
const behaviorNames = ['Flock Mode', 'Scatter Mode', 'Orbit Mode'];
|
||||
|
||||
function toggleBehavior() {
|
||||
currentBehavior = (currentBehavior + 1) % behaviors.length;
|
||||
swarmManager.setBehavior(behaviors[currentBehavior]);
|
||||
document.getElementById('behaviorToggle').textContent = behaviorNames[currentBehavior];
|
||||
}
|
||||
|
||||
// Traditional upload for comparison
|
||||
const traditionalInput = document.querySelector('.traditional-upload input[type="file"]');
|
||||
traditionalInput.addEventListener('change', (e) => {
|
||||
const fileList = document.getElementById('traditionalFileList');
|
||||
fileList.innerHTML = '<p style="margin-top: 1rem;">Selected files:</p>';
|
||||
for (let file of e.target.files) {
|
||||
fileList.innerHTML += `<p style="color: #666; font-size: 0.9rem;">• ${file.name}</p>`;
|
||||
}
|
||||
});
|
||||
|
||||
// Accessibility: Keyboard navigation
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Tab' && swarmManager && swarmManager.entities.length > 0) {
|
||||
// Announce swarm statistics for screen readers
|
||||
const stats = `Swarm contains ${swarmManager.entities.length} files. ` +
|
||||
`Cohesion: ${document.getElementById('swarmCohesion').textContent}. ` +
|
||||
`Average velocity: ${document.getElementById('swarmVelocity').textContent}`;
|
||||
|
||||
// Create temporary ARIA live region
|
||||
const announcement = document.createElement('div');
|
||||
announcement.setAttribute('role', 'status');
|
||||
announcement.setAttribute('aria-live', 'polite');
|
||||
announcement.className = 'sr-only';
|
||||
announcement.textContent = stats;
|
||||
document.body.appendChild(announcement);
|
||||
setTimeout(() => announcement.remove(), 1000);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,848 @@
|
|||
<!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>
|
||||
Loading…
Reference in New Issue