infinite-agents-public/src_infinite/ui_hybrid_20.html

815 lines
29 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 Hybrid 20 - Digital Minimalism Action Controller</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--pure-white: #ffffff;
--soft-black: #0a0a0a;
--neutral-100: #f5f5f5;
--neutral-200: #e5e5e5;
--neutral-300: #d4d4d4;
--neutral-400: #a3a3a3;
--neutral-500: #737373;
--neutral-600: #525252;
--neutral-700: #404040;
--neutral-800: #262626;
--neutral-900: #171717;
--accent-primary: #0066ff;
--accent-success: #00d26a;
--accent-error: #ff3838;
--accent-warning: #ffab00;
--radius-small: 2px;
--radius-medium: 4px;
--radius-large: 8px;
--transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
--transition-medium: 300ms cubic-bezier(0.4, 0, 0.2, 1);
--transition-slow: 600ms cubic-bezier(0.4, 0, 0.2, 1);
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: var(--pure-white);
color: var(--soft-black);
line-height: 1.5;
font-size: 14px;
overflow-x: hidden;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 60px 20px;
}
.header {
margin-bottom: 80px;
text-align: center;
}
.header h1 {
font-size: 32px;
font-weight: 300;
letter-spacing: -0.02em;
margin-bottom: 8px;
}
.header p {
font-size: 16px;
color: var(--neutral-500);
font-weight: 400;
}
/* Action Controller Base */
.action-controller {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 120px;
height: 40px;
padding: 0 24px;
background: var(--pure-white);
border: 1px solid var(--neutral-300);
border-radius: var(--radius-medium);
font-size: 14px;
font-weight: 500;
color: var(--soft-black);
cursor: pointer;
position: relative;
overflow: hidden;
transition: all var(--transition-fast);
user-select: none;
-webkit-tap-highlight-color: transparent;
}
.action-controller:hover {
border-color: var(--neutral-400);
background: var(--neutral-100);
}
.action-controller:active {
transform: scale(0.98);
}
/* Primary Action */
.action-controller.primary {
background: var(--soft-black);
border-color: var(--soft-black);
color: var(--pure-white);
}
.action-controller.primary:hover {
background: var(--neutral-800);
border-color: var(--neutral-800);
}
/* Action States */
.action-controller .state-default,
.action-controller .state-loading,
.action-controller .state-confirm,
.action-controller .state-success,
.action-controller .state-error {
display: flex;
align-items: center;
gap: 8px;
transition: opacity var(--transition-fast), transform var(--transition-fast);
}
.action-controller[data-state="default"] .state-default {
opacity: 1;
transform: translateY(0);
}
.action-controller[data-state="loading"] .state-loading,
.action-controller[data-state="confirm"] .state-confirm,
.action-controller[data-state="success"] .state-success,
.action-controller[data-state="error"] .state-error {
opacity: 1;
transform: translateY(0);
}
.action-controller:not([data-state="default"]) .state-default,
.action-controller:not([data-state="loading"]) .state-loading,
.action-controller:not([data-state="confirm"]) .state-confirm,
.action-controller:not([data-state="success"]) .state-success,
.action-controller:not([data-state="error"]) .state-error {
opacity: 0;
transform: translateY(20px);
position: absolute;
}
/* Loading State */
.loading-spinner {
width: 16px;
height: 16px;
border: 2px solid var(--neutral-300);
border-top-color: var(--soft-black);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
.action-controller.primary .loading-spinner {
border-color: rgba(255, 255, 255, 0.3);
border-top-color: var(--pure-white);
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Progress Bar */
.action-controller .progress-bar {
position: absolute;
bottom: 0;
left: 0;
height: 2px;
background: var(--accent-primary);
transform-origin: left;
transition: transform var(--transition-slow);
}
/* Success/Error States */
.action-controller[data-state="success"] {
border-color: var(--accent-success);
color: var(--accent-success);
}
.action-controller[data-state="error"] {
border-color: var(--accent-error);
color: var(--accent-error);
}
/* Icons */
.icon {
width: 16px;
height: 16px;
stroke-width: 2;
stroke: currentColor;
fill: none;
flex-shrink: 0;
}
/* Action Groups */
.action-group {
display: flex;
gap: 12px;
margin-bottom: 40px;
}
.action-group.vertical {
flex-direction: column;
max-width: 240px;
}
/* Demo Sections */
.demo-section {
margin-bottom: 80px;
}
.demo-section h2 {
font-size: 20px;
font-weight: 400;
margin-bottom: 24px;
color: var(--neutral-700);
}
.demo-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 40px;
}
/* Card Action */
.action-card {
background: var(--pure-white);
border: 1px solid var(--neutral-200);
border-radius: var(--radius-large);
padding: 24px;
transition: all var(--transition-fast);
}
.action-card:hover {
border-color: var(--neutral-300);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04);
}
.action-card h3 {
font-size: 16px;
font-weight: 500;
margin-bottom: 8px;
}
.action-card p {
color: var(--neutral-600);
margin-bottom: 20px;
font-size: 14px;
}
/* Confirmation Dialog */
.confirm-dialog {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0.9);
background: var(--pure-white);
border-radius: var(--radius-large);
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1);
padding: 32px;
max-width: 400px;
width: 90%;
opacity: 0;
pointer-events: none;
transition: all var(--transition-medium);
z-index: 1000;
}
.confirm-dialog.active {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
pointer-events: auto;
}
.confirm-dialog h3 {
font-size: 18px;
font-weight: 500;
margin-bottom: 12px;
}
.confirm-dialog p {
color: var(--neutral-600);
margin-bottom: 24px;
}
.confirm-dialog .dialog-actions {
display: flex;
gap: 12px;
justify-content: flex-end;
}
/* Overlay */
.overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.2);
opacity: 0;
pointer-events: none;
transition: opacity var(--transition-medium);
z-index: 999;
}
.overlay.active {
opacity: 1;
pointer-events: auto;
}
/* Toast Notifications */
.toast-container {
position: fixed;
bottom: 24px;
right: 24px;
display: flex;
flex-direction: column;
gap: 12px;
z-index: 1001;
}
.toast {
background: var(--pure-white);
border: 1px solid var(--neutral-300);
border-radius: var(--radius-medium);
padding: 16px 20px;
display: flex;
align-items: center;
gap: 12px;
min-width: 280px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
transform: translateX(400px);
transition: transform var(--transition-medium);
}
.toast.show {
transform: translateX(0);
}
.toast.success {
border-color: var(--accent-success);
}
.toast.error {
border-color: var(--accent-error);
}
/* Minimal Animations */
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.pulse {
animation: pulse 2s infinite;
}
/* Responsive */
@media (max-width: 768px) {
.container {
padding: 40px 16px;
}
.header {
margin-bottom: 60px;
}
.demo-grid {
grid-template-columns: 1fr;
gap: 24px;
}
.action-group {
flex-wrap: wrap;
}
.toast-container {
left: 16px;
right: 16px;
bottom: 16px;
}
.toast {
min-width: auto;
width: 100%;
}
}
</style>
</head>
<body>
<div class="container">
<header class="header">
<h1>Minimal Action Controller</h1>
<p>Precise control states with pure geometric design</p>
</header>
<!-- Basic Actions -->
<section class="demo-section">
<h2>Primary Actions</h2>
<div class="action-group">
<button class="action-controller primary" data-state="default" onclick="handleAction(this, 'save')">
<span class="state-default">Save Changes</span>
<span class="state-loading">
<div class="loading-spinner"></div>
<span>Saving...</span>
</span>
<span class="state-success">
<svg class="icon" viewBox="0 0 24 24">
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
<span>Saved</span>
</span>
<div class="progress-bar"></div>
</button>
<button class="action-controller" data-state="default" onclick="handleAction(this, 'export')">
<span class="state-default">Export Data</span>
<span class="state-loading">
<div class="loading-spinner"></div>
<span>Exporting...</span>
</span>
<span class="state-success">
<svg class="icon" viewBox="0 0 24 24">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</svg>
<span>Exported</span>
</span>
<div class="progress-bar"></div>
</button>
<button class="action-controller" data-state="default" onclick="handleAction(this, 'sync')">
<span class="state-default">
<svg class="icon" viewBox="0 0 24 24">
<polyline points="23 4 23 10 17 10"></polyline>
<polyline points="1 20 1 14 7 14"></polyline>
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
</svg>
<span>Sync</span>
</span>
<span class="state-loading">
<div class="loading-spinner"></div>
<span>Syncing...</span>
</span>
<span class="state-success">
<svg class="icon" viewBox="0 0 24 24">
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
<span>Synced</span>
</span>
<div class="progress-bar"></div>
</button>
</div>
</section>
<!-- Confirmation Actions -->
<section class="demo-section">
<h2>Confirmation Actions</h2>
<div class="demo-grid">
<div class="action-card">
<h3>Delete Item</h3>
<p>This action requires confirmation before proceeding.</p>
<button class="action-controller" data-state="default" onclick="handleConfirmAction(this, 'delete')">
<span class="state-default">
<svg class="icon" viewBox="0 0 24 24">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
</svg>
<span>Delete</span>
</span>
<span class="state-confirm">
<svg class="icon" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="8" x2="12" y2="12"></line>
<line x1="12" y1="16" x2="12.01" y2="16"></line>
</svg>
<span>Confirm Delete?</span>
</span>
<span class="state-loading">
<div class="loading-spinner"></div>
<span>Deleting...</span>
</span>
<span class="state-success">
<svg class="icon" viewBox="0 0 24 24">
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
<span>Deleted</span>
</span>
</button>
</div>
<div class="action-card">
<h3>Archive Project</h3>
<p>Move this project to archived items.</p>
<button class="action-controller" data-state="default" onclick="handleConfirmAction(this, 'archive')">
<span class="state-default">
<svg class="icon" viewBox="0 0 24 24">
<polyline points="21 8 21 21 3 21 3 8"></polyline>
<rect x="1" y="3" width="22" height="5"></rect>
<line x1="10" y1="12" x2="14" y2="12"></line>
</svg>
<span>Archive</span>
</span>
<span class="state-confirm">
<span>Archive Project?</span>
</span>
<span class="state-loading">
<div class="loading-spinner"></div>
<span>Archiving...</span>
</span>
<span class="state-success">
<svg class="icon" viewBox="0 0 24 24">
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
<span>Archived</span>
</span>
</button>
</div>
<div class="action-card">
<h3>Reset Settings</h3>
<p>Restore all settings to default values.</p>
<button class="action-controller" data-state="default" onclick="handleConfirmAction(this, 'reset')">
<span class="state-default">Reset to Default</span>
<span class="state-confirm">Reset All Settings?</span>
<span class="state-loading">
<div class="loading-spinner"></div>
<span>Resetting...</span>
</span>
<span class="state-success">
<svg class="icon" viewBox="0 0 24 24">
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
<span>Reset Complete</span>
</span>
</button>
</div>
</div>
</section>
<!-- Error States -->
<section class="demo-section">
<h2>Error Handling</h2>
<div class="action-group">
<button class="action-controller" data-state="default" onclick="handleErrorAction(this)">
<span class="state-default">Test Error State</span>
<span class="state-loading">
<div class="loading-spinner"></div>
<span>Processing...</span>
</span>
<span class="state-error">
<svg class="icon" viewBox="0 0 24 24">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
<span>Failed</span>
</span>
</button>
<button class="action-controller" data-state="default" onclick="handleRetryAction(this)">
<span class="state-default">Network Request</span>
<span class="state-loading">
<div class="loading-spinner"></div>
<span>Connecting...</span>
</span>
<span class="state-error">
<svg class="icon" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="8" x2="12" y2="12"></line>
<line x1="12" y1="16" x2="12.01" y2="16"></line>
</svg>
<span>Retry</span>
</span>
<span class="state-success">
<svg class="icon" viewBox="0 0 24 24">
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
<span>Connected</span>
</span>
</button>
</div>
</section>
<!-- Progress Actions -->
<section class="demo-section">
<h2>Progress Indicators</h2>
<div class="action-group vertical">
<button class="action-controller primary" data-state="default" onclick="handleProgressAction(this, 'upload')">
<span class="state-default">
<svg class="icon" viewBox="0 0 24 24">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="17 8 12 3 7 8"></polyline>
<line x1="12" y1="3" x2="12" y2="15"></line>
</svg>
<span>Upload File</span>
</span>
<span class="state-loading">
<span class="pulse">Uploading...</span>
</span>
<span class="state-success">
<svg class="icon" viewBox="0 0 24 24">
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
<span>Upload Complete</span>
</span>
<div class="progress-bar"></div>
</button>
<button class="action-controller" data-state="default" onclick="handleProgressAction(this, 'process')">
<span class="state-default">Process Data</span>
<span class="state-loading">Processing...</span>
<span class="state-success">
<svg class="icon" viewBox="0 0 24 24">
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
<span>Processed</span>
</span>
<div class="progress-bar"></div>
</button>
<button class="action-controller" data-state="default" onclick="handleProgressAction(this, 'generate')">
<span class="state-default">Generate Report</span>
<span class="state-loading">Generating...</span>
<span class="state-success">View Report</span>
<div class="progress-bar"></div>
</button>
</div>
</section>
</div>
<!-- Confirmation Dialog -->
<div class="overlay" onclick="closeDialog()"></div>
<div class="confirm-dialog" id="confirmDialog">
<h3>Confirm Action</h3>
<p>Are you sure you want to proceed with this action?</p>
<div class="dialog-actions">
<button class="action-controller" onclick="closeDialog()">Cancel</button>
<button class="action-controller primary" id="confirmButton">Confirm</button>
</div>
</div>
<!-- Toast Container -->
<div class="toast-container" id="toastContainer"></div>
<script>
// Action Controller Functions
function handleAction(button, type) {
if (button.dataset.state !== 'default') return;
button.dataset.state = 'loading';
// Simulate async operation
setTimeout(() => {
button.dataset.state = 'success';
showToast(`${type.charAt(0).toUpperCase() + type.slice(1)} completed successfully`, 'success');
// Reset after delay
setTimeout(() => {
button.dataset.state = 'default';
}, 2000);
}, 2000);
}
function handleConfirmAction(button, type) {
const currentState = button.dataset.state;
if (currentState === 'default') {
button.dataset.state = 'confirm';
// Auto-reset if no action taken
setTimeout(() => {
if (button.dataset.state === 'confirm') {
button.dataset.state = 'default';
}
}, 3000);
} else if (currentState === 'confirm') {
button.dataset.state = 'loading';
setTimeout(() => {
button.dataset.state = 'success';
showToast(`${type.charAt(0).toUpperCase() + type.slice(1)} completed`, 'success');
setTimeout(() => {
button.dataset.state = 'default';
}, 2000);
}, 1500);
}
}
function handleErrorAction(button) {
if (button.dataset.state !== 'default') return;
button.dataset.state = 'loading';
setTimeout(() => {
button.dataset.state = 'error';
showToast('Operation failed. Please try again.', 'error');
setTimeout(() => {
button.dataset.state = 'default';
}, 3000);
}, 1500);
}
function handleRetryAction(button) {
const currentState = button.dataset.state;
if (currentState === 'default' || currentState === 'error') {
button.dataset.state = 'loading';
// Simulate network request with 50% failure rate
setTimeout(() => {
if (Math.random() > 0.5) {
button.dataset.state = 'success';
showToast('Connection established', 'success');
setTimeout(() => {
button.dataset.state = 'default';
}, 2000);
} else {
button.dataset.state = 'error';
showToast('Connection failed. Click to retry.', 'error');
}
}, 1500);
}
}
function handleProgressAction(button, type) {
if (button.dataset.state !== 'default') return;
button.dataset.state = 'loading';
const progressBar = button.querySelector('.progress-bar');
// Animate progress
let progress = 0;
const interval = setInterval(() => {
progress += Math.random() * 20;
if (progress > 100) progress = 100;
progressBar.style.transform = `scaleX(${progress / 100})`;
if (progress >= 100) {
clearInterval(interval);
button.dataset.state = 'success';
showToast(`${type.charAt(0).toUpperCase() + type.slice(1)} completed`, 'success');
setTimeout(() => {
button.dataset.state = 'default';
progressBar.style.transform = 'scaleX(0)';
}, 2000);
}
}, 200);
}
// Dialog Functions
function showDialog() {
document.getElementById('confirmDialog').classList.add('active');
document.querySelector('.overlay').classList.add('active');
}
function closeDialog() {
document.getElementById('confirmDialog').classList.remove('active');
document.querySelector('.overlay').classList.remove('active');
}
// Toast Functions
function showToast(message, type = 'default') {
const toast = document.createElement('div');
toast.className = `toast ${type}`;
let icon = '';
if (type === 'success') {
icon = '<svg class="icon" viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"></polyline></svg>';
} else if (type === 'error') {
icon = '<svg class="icon" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>';
}
toast.innerHTML = `${icon}<span>${message}</span>`;
const container = document.getElementById('toastContainer');
container.appendChild(toast);
// Trigger show animation
setTimeout(() => toast.classList.add('show'), 10);
// Remove after delay
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => toast.remove(), 300);
}, 3000);
}
// Initialize
document.addEventListener('DOMContentLoaded', () => {
// Add click outside to close confirmation states
document.addEventListener('click', (e) => {
if (!e.target.closest('.action-controller')) {
document.querySelectorAll('.action-controller[data-state="confirm"]').forEach(btn => {
btn.dataset.state = 'default';
});
}
});
});
</script>
</body>
</html>