infinite-agents-public/legacy/src/ui_innovation_4.html

884 lines
31 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>UI Innovation: 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>