infinite-agents-public/src/ui_innovation_8.html

891 lines
32 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: 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>