infinite-agents-public/legacy/src_enhanced/ui_enhanced_1.html

1041 lines
35 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Text Input Enhanced</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary-color: #6366f1;
--success-color: #10b981;
--warning-color: #f59e0b;
--error-color: #ef4444;
--gray-100: #f3f4f6;
--gray-200: #e5e7eb;
--gray-300: #d1d5db;
--gray-400: #9ca3af;
--gray-500: #6b7280;
--gray-600: #4b5563;
--gray-700: #374151;
--gray-800: #1f2937;
--gray-900: #111827;
--transition-fast: 150ms;
--transition-normal: 250ms;
--transition-slow: 350ms;
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
}
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.5;
color: var(--gray-900);
background-color: var(--gray-50);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
background: linear-gradient(135deg, #f5f7fa 0%, #e9ecef 100%);
}
main {
width: 100%;
max-width: 800px;
background: white;
border-radius: 16px;
box-shadow: var(--shadow-lg);
padding: 48px;
}
h1 {
font-size: 2rem;
font-weight: 700;
color: var(--gray-900);
margin-bottom: 8px;
text-align: center;
}
.subtitle {
text-align: center;
color: var(--gray-600);
margin-bottom: 48px;
font-size: 1.125rem;
}
.component-showcase {
display: grid;
gap: 32px;
}
/* Enhanced Input Component */
.input-group {
position: relative;
margin-bottom: 8px;
}
.enhanced-input {
width: 100%;
padding: 20px 48px 8px 16px;
font-size: 16px;
border: 2px solid var(--gray-300);
border-radius: 12px;
background: white;
transition: all var(--transition-normal) cubic-bezier(0.4, 0, 0.2, 1);
outline: none;
font-family: inherit;
-webkit-appearance: none;
}
.enhanced-input:hover:not(:disabled) {
border-color: var(--gray-400);
}
.enhanced-input:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
}
.enhanced-input:disabled {
background-color: var(--gray-100);
cursor: not-allowed;
opacity: 0.7;
}
/* Floating Label */
.floating-label {
position: absolute;
left: 16px;
top: 50%;
transform: translateY(-50%);
font-size: 16px;
color: var(--gray-500);
pointer-events: none;
transition: all var(--transition-normal) cubic-bezier(0.4, 0, 0.2, 1);
background: white;
padding: 0 4px;
transform-origin: left center;
}
.enhanced-input:focus ~ .floating-label,
.enhanced-input:not(:placeholder-shown) ~ .floating-label,
.input-group.has-value .floating-label {
top: 12px;
transform: translateY(-50%) scale(0.75);
color: var(--primary-color);
}
.enhanced-input.error ~ .floating-label {
color: var(--error-color);
}
.enhanced-input.success ~ .floating-label {
color: var(--success-color);
}
/* Clear Button */
.clear-button {
position: absolute;
right: 16px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
color: var(--gray-400);
cursor: pointer;
padding: 4px;
border-radius: 6px;
transition: all var(--transition-fast);
opacity: 0;
visibility: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.input-group.has-value .clear-button {
opacity: 1;
visibility: visible;
}
.clear-button:hover {
color: var(--gray-600);
background: var(--gray-100);
}
.clear-button:active {
transform: translateY(-50%) scale(0.95);
}
/* Character Counter */
.char-counter {
position: absolute;
right: 16px;
bottom: -24px;
font-size: 12px;
color: var(--gray-500);
transition: color var(--transition-fast);
}
.char-counter.warning {
color: var(--warning-color);
}
.char-counter.error {
color: var(--error-color);
}
/* Validation Messages */
.validation-message {
position: absolute;
left: 16px;
bottom: -24px;
font-size: 12px;
opacity: 0;
transform: translateY(-4px);
transition: all var(--transition-normal);
display: flex;
align-items: center;
gap: 4px;
}
.validation-message.show {
opacity: 1;
transform: translateY(0);
}
.validation-message.error {
color: var(--error-color);
}
.validation-message.success {
color: var(--success-color);
}
.validation-icon {
width: 14px;
height: 14px;
flex-shrink: 0;
}
/* Auto-complete Dropdown */
.autocomplete-dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
margin-top: 4px;
background: white;
border: 1px solid var(--gray-200);
border-radius: 12px;
box-shadow: var(--shadow-md);
opacity: 0;
visibility: hidden;
transform: translateY(-8px);
transition: all var(--transition-normal);
max-height: 200px;
overflow-y: auto;
z-index: 1000;
}
.autocomplete-dropdown.show {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.autocomplete-item {
padding: 12px 16px;
cursor: pointer;
transition: background-color var(--transition-fast);
border-bottom: 1px solid var(--gray-100);
font-size: 14px;
}
.autocomplete-item:last-child {
border-bottom: none;
}
.autocomplete-item:hover,
.autocomplete-item.selected {
background-color: var(--gray-50);
}
.autocomplete-item.selected {
background-color: var(--primary-color);
color: white;
}
.autocomplete-item mark {
background-color: rgba(99, 102, 241, 0.2);
color: inherit;
font-weight: 600;
border-radius: 2px;
padding: 0 2px;
}
/* Password Strength Indicator */
.password-strength {
margin-top: 8px;
opacity: 0;
transform: translateY(-4px);
transition: all var(--transition-normal);
}
.password-strength.show {
opacity: 1;
transform: translateY(0);
}
.strength-bar {
height: 4px;
background: var(--gray-200);
border-radius: 2px;
overflow: hidden;
margin-bottom: 8px;
}
.strength-fill {
height: 100%;
width: 0;
transition: width var(--transition-slow), background-color var(--transition-normal);
border-radius: 2px;
}
.strength-fill.weak {
width: 33%;
background-color: var(--error-color);
}
.strength-fill.medium {
width: 66%;
background-color: var(--warning-color);
}
.strength-fill.strong {
width: 100%;
background-color: var(--success-color);
}
.strength-text {
font-size: 12px;
color: var(--gray-600);
display: flex;
align-items: center;
gap: 8px;
}
.strength-requirements {
margin-top: 8px;
font-size: 12px;
color: var(--gray-500);
}
.requirement {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 4px;
transition: color var(--transition-fast);
}
.requirement.met {
color: var(--success-color);
}
.requirement-icon {
width: 12px;
height: 12px;
}
/* Loading State */
.input-group.loading .enhanced-input {
padding-right: 48px;
}
.loading-spinner {
position: absolute;
right: 16px;
top: 50%;
transform: translateY(-50%);
width: 20px;
height: 20px;
border: 2px solid var(--gray-300);
border-top-color: var(--primary-color);
border-radius: 50%;
animation: spin 1s linear infinite;
opacity: 0;
visibility: hidden;
}
.input-group.loading .loading-spinner {
opacity: 1;
visibility: visible;
}
.input-group.loading .clear-button {
opacity: 0;
visibility: hidden;
}
@keyframes spin {
to { transform: translateY(-50%) rotate(360deg); }
}
/* Focus Ring Animation */
@keyframes focusRing {
0% {
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0);
}
100% {
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
}
}
/* Responsive Design */
@media (max-width: 640px) {
main {
padding: 24px;
}
h1 {
font-size: 1.5rem;
}
.subtitle {
font-size: 1rem;
}
.enhanced-input {
font-size: 16px; /* Prevent zoom on iOS */
}
}
/* Helper Text */
.helper-text {
margin-top: 4px;
font-size: 12px;
color: var(--gray-500);
line-height: 1.4;
}
/* Demo Section Spacing */
.demo-section {
margin-bottom: 48px;
}
.demo-section:last-child {
margin-bottom: 0;
}
.section-title {
font-size: 1.25rem;
font-weight: 600;
color: var(--gray-800);
margin-bottom: 16px;
}
/* Icon Styles */
.icon {
width: 16px;
height: 16px;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
fill: none;
}
</style>
</head>
<body>
<main>
<h1>Text Input - Enhanced</h1>
<p class="subtitle">Experience the evolution of form inputs with floating labels, smart validation, and delightful interactions</p>
<div class="component-showcase">
<!-- Basic Enhanced Input -->
<div class="demo-section">
<h2 class="section-title">Enhanced Text Input</h2>
<div class="input-group" id="name-group">
<input
type="text"
class="enhanced-input"
id="name-input"
placeholder=" "
aria-label="Full Name"
aria-describedby="name-helper"
>
<label class="floating-label" for="name-input">Full Name</label>
<button class="clear-button" aria-label="Clear input">
<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>
</button>
<div class="validation-message" role="alert"></div>
</div>
<p class="helper-text" id="name-helper">Enter your full name as it appears on official documents</p>
</div>
<!-- Email with Auto-complete -->
<div class="demo-section">
<h2 class="section-title">Email with Smart Suggestions</h2>
<div class="input-group" id="email-group">
<input
type="email"
class="enhanced-input"
id="email-input"
placeholder=" "
aria-label="Email Address"
aria-describedby="email-helper"
autocomplete="off"
>
<label class="floating-label" for="email-input">Email Address</label>
<button class="clear-button" aria-label="Clear input">
<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>
</button>
<div class="loading-spinner"></div>
<div class="validation-message" role="alert"></div>
<div class="autocomplete-dropdown" role="listbox"></div>
</div>
<p class="helper-text" id="email-helper">We'll suggest common email domains as you type</p>
</div>
<!-- Password with Strength Indicator -->
<div class="demo-section">
<h2 class="section-title">Password with Strength Meter</h2>
<div class="input-group" id="password-group">
<input
type="password"
class="enhanced-input"
id="password-input"
placeholder=" "
aria-label="Password"
aria-describedby="password-helper password-strength"
>
<label class="floating-label" for="password-input">Password</label>
<button class="clear-button" aria-label="Clear input">
<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>
</button>
<div class="validation-message" role="alert"></div>
</div>
<div class="password-strength" id="password-strength">
<div class="strength-bar">
<div class="strength-fill"></div>
</div>
<div class="strength-text">
<span class="strength-label">Password strength:</span>
<span class="strength-value"></span>
</div>
<div class="strength-requirements">
<div class="requirement" data-req="length">
<svg class="requirement-icon icon" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10"></circle>
<path d="M12 6v6l4 2"></path>
</svg>
At least 8 characters
</div>
<div class="requirement" data-req="uppercase">
<svg class="requirement-icon icon" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10"></circle>
<path d="M12 6v6l4 2"></path>
</svg>
One uppercase letter
</div>
<div class="requirement" data-req="number">
<svg class="requirement-icon icon" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10"></circle>
<path d="M12 6v6l4 2"></path>
</svg>
One number
</div>
<div class="requirement" data-req="special">
<svg class="requirement-icon icon" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10"></circle>
<path d="M12 6v6l4 2"></path>
</svg>
One special character
</div>
</div>
</div>
<p class="helper-text" id="password-helper">Create a strong password with mixed characters</p>
</div>
<!-- Message with Character Counter -->
<div class="demo-section">
<h2 class="section-title">Message with Character Counter</h2>
<div class="input-group" id="message-group">
<input
type="text"
class="enhanced-input"
id="message-input"
placeholder=" "
maxlength="280"
aria-label="Message"
aria-describedby="message-helper"
>
<label class="floating-label" for="message-input">Your Message</label>
<button class="clear-button" aria-label="Clear input">
<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>
</button>
<span class="char-counter" aria-live="polite">0 / 280</span>
<div class="validation-message" role="alert"></div>
</div>
<p class="helper-text" id="message-helper">Share your thoughts in 280 characters or less</p>
</div>
</div>
</main>
<script>
// Enhanced Input Component Class
class EnhancedInput {
constructor(groupId, options = {}) {
this.group = document.getElementById(groupId);
this.input = this.group.querySelector('.enhanced-input');
this.label = this.group.querySelector('.floating-label');
this.clearButton = this.group.querySelector('.clear-button');
this.validationMessage = this.group.querySelector('.validation-message');
this.options = options;
this.init();
}
init() {
// Input events
this.input.addEventListener('input', this.handleInput.bind(this));
this.input.addEventListener('focus', this.handleFocus.bind(this));
this.input.addEventListener('blur', this.handleBlur.bind(this));
// Clear button
if (this.clearButton) {
this.clearButton.addEventListener('click', this.handleClear.bind(this));
}
// Initialize state
this.updateState();
}
handleInput(e) {
this.updateState();
// Custom input handler
if (this.options.onInput) {
this.options.onInput(e.target.value, this);
}
// Real-time validation
if (this.options.validate) {
this.validate();
}
}
handleFocus(e) {
this.group.classList.add('focused');
if (this.options.onFocus) {
this.options.onFocus(e, this);
}
}
handleBlur(e) {
this.group.classList.remove('focused');
// Validate on blur
if (this.options.validate && this.input.value) {
this.validate();
}
if (this.options.onBlur) {
this.options.onBlur(e, this);
}
}
handleClear(e) {
e.preventDefault();
this.input.value = '';
this.input.focus();
this.updateState();
this.clearValidation();
if (this.options.onClear) {
this.options.onClear(this);
}
}
updateState() {
if (this.input.value) {
this.group.classList.add('has-value');
} else {
this.group.classList.remove('has-value');
}
}
validate() {
const result = this.options.validate(this.input.value);
if (result.valid) {
this.showSuccess(result.message);
} else {
this.showError(result.message);
}
return result.valid;
}
showError(message) {
this.input.classList.add('error');
this.input.classList.remove('success');
this.showValidationMessage(message, 'error');
}
showSuccess(message) {
this.input.classList.add('success');
this.input.classList.remove('error');
if (message) {
this.showValidationMessage(message, 'success');
}
}
showValidationMessage(message, type) {
this.validationMessage.textContent = message;
this.validationMessage.className = `validation-message ${type} show`;
}
clearValidation() {
this.input.classList.remove('error', 'success');
this.validationMessage.className = 'validation-message';
}
setLoading(loading) {
if (loading) {
this.group.classList.add('loading');
} else {
this.group.classList.remove('loading');
}
}
}
// Email Autocomplete
class EmailAutocomplete extends EnhancedInput {
constructor(groupId) {
super(groupId, {
onInput: (value, instance) => instance.handleEmailInput(value),
onFocus: (e, instance) => instance.showSuggestions(),
onBlur: (e, instance) => setTimeout(() => instance.hideSuggestions(), 200),
validate: (value) => instance.validateEmail(value)
});
this.dropdown = this.group.querySelector('.autocomplete-dropdown');
this.selectedIndex = -1;
this.suggestions = [];
this.commonDomains = [
'gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com',
'icloud.com', 'aol.com', 'protonmail.com', 'mail.com'
];
// Keyboard navigation
this.input.addEventListener('keydown', this.handleKeyDown.bind(this));
}
handleEmailInput(value) {
if (value.includes('@')) {
const [localPart, domain] = value.split('@');
if (domain && domain.length > 0) {
this.generateSuggestions(localPart, domain);
} else {
this.generateSuggestions(localPart, '');
}
} else {
this.hideSuggestions();
}
}
generateSuggestions(localPart, partialDomain) {
this.suggestions = this.commonDomains
.filter(domain => domain.startsWith(partialDomain.toLowerCase()))
.slice(0, 5)
.map(domain => `${localPart}@${domain}`);
if (this.suggestions.length > 0) {
this.renderSuggestions();
this.showSuggestions();
} else {
this.hideSuggestions();
}
}
renderSuggestions() {
const currentValue = this.input.value.toLowerCase();
this.dropdown.innerHTML = this.suggestions.map((suggestion, index) => {
const html = suggestion.replace(new RegExp(`(${currentValue})`, 'gi'), '<mark>$1</mark>');
return `
<div class="autocomplete-item" data-index="${index}" data-value="${suggestion}">
${html}
</div>
`;
}).join('');
// Add click handlers
this.dropdown.querySelectorAll('.autocomplete-item').forEach(item => {
item.addEventListener('click', () => {
this.selectSuggestion(item.dataset.value);
});
});
}
showSuggestions() {
if (this.suggestions.length > 0 && this.group.classList.contains('focused')) {
this.dropdown.classList.add('show');
}
}
hideSuggestions() {
this.dropdown.classList.remove('show');
this.selectedIndex = -1;
}
handleKeyDown(e) {
if (!this.dropdown.classList.contains('show')) return;
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
this.navigateSuggestions(1);
break;
case 'ArrowUp':
e.preventDefault();
this.navigateSuggestions(-1);
break;
case 'Enter':
if (this.selectedIndex >= 0) {
e.preventDefault();
this.selectSuggestion(this.suggestions[this.selectedIndex]);
}
break;
case 'Escape':
this.hideSuggestions();
break;
}
}
navigateSuggestions(direction) {
const items = this.dropdown.querySelectorAll('.autocomplete-item');
// Remove previous selection
if (this.selectedIndex >= 0) {
items[this.selectedIndex].classList.remove('selected');
}
// Update index
this.selectedIndex += direction;
// Wrap around
if (this.selectedIndex >= items.length) {
this.selectedIndex = 0;
} else if (this.selectedIndex < 0) {
this.selectedIndex = items.length - 1;
}
// Add new selection
items[this.selectedIndex].classList.add('selected');
items[this.selectedIndex].scrollIntoView({ block: 'nearest' });
}
selectSuggestion(value) {
this.input.value = value;
this.updateState();
this.hideSuggestions();
this.validate();
}
validateEmail(value) {
if (!value) {
return { valid: false, message: 'Email is required' };
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
return { valid: false, message: 'Please enter a valid email address' };
}
// Simulate async validation
this.setLoading(true);
setTimeout(() => {
this.setLoading(false);
this.showSuccess('Email looks good!');
}, 1000);
return { valid: true };
}
}
// Password Strength Component
class PasswordInput extends EnhancedInput {
constructor(groupId) {
super(groupId, {
onInput: (value, instance) => instance.checkStrength(value),
validate: (value) => instance.validatePassword(value)
});
this.strengthIndicator = document.getElementById('password-strength');
this.strengthFill = this.strengthIndicator.querySelector('.strength-fill');
this.strengthValue = this.strengthIndicator.querySelector('.strength-value');
this.requirements = this.strengthIndicator.querySelectorAll('.requirement');
}
checkStrength(value) {
if (!value) {
this.strengthIndicator.classList.remove('show');
return;
}
this.strengthIndicator.classList.add('show');
const checks = {
length: value.length >= 8,
uppercase: /[A-Z]/.test(value),
number: /[0-9]/.test(value),
special: /[^A-Za-z0-9]/.test(value)
};
// Update requirements
Object.entries(checks).forEach(([req, met]) => {
const element = this.strengthIndicator.querySelector(`[data-req="${req}"]`);
if (met) {
element.classList.add('met');
element.querySelector('.icon').innerHTML = '<polyline points="20 6 9 17 4 12"></polyline>';
} else {
element.classList.remove('met');
element.querySelector('.icon').innerHTML = '<circle cx="12" cy="12" r="10"></circle><path d="M12 6v6l4 2"></path>';
}
});
// Calculate strength
const score = Object.values(checks).filter(Boolean).length;
if (score <= 1) {
this.setStrength('weak', 'Weak');
} else if (score <= 3) {
this.setStrength('medium', 'Medium');
} else {
this.setStrength('strong', 'Strong');
}
}
setStrength(level, text) {
this.strengthFill.className = `strength-fill ${level}`;
this.strengthValue.textContent = text;
}
validatePassword(value) {
if (!value) {
return { valid: false, message: 'Password is required' };
}
if (value.length < 8) {
return { valid: false, message: 'Password must be at least 8 characters' };
}
return { valid: true };
}
}
// Character Counter Component
class CharacterCounterInput extends EnhancedInput {
constructor(groupId) {
super(groupId, {
onInput: (value, instance) => instance.updateCounter(value),
validate: (value) => instance.validateMessage(value)
});
this.counter = this.group.querySelector('.char-counter');
this.maxLength = parseInt(this.input.getAttribute('maxlength'));
}
updateCounter(value) {
const length = value.length;
this.counter.textContent = `${length} / ${this.maxLength}`;
if (length > this.maxLength * 0.9) {
this.counter.classList.add('error');
this.counter.classList.remove('warning');
} else if (length > this.maxLength * 0.75) {
this.counter.classList.add('warning');
this.counter.classList.remove('error');
} else {
this.counter.classList.remove('warning', 'error');
}
}
validateMessage(value) {
if (!value) {
return { valid: false, message: 'Message cannot be empty' };
}
if (value.length < 3) {
return { valid: false, message: 'Message is too short' };
}
return { valid: true, message: 'Perfect length!' };
}
}
// Initialize all components
document.addEventListener('DOMContentLoaded', () => {
// Basic input with validation
new EnhancedInput('name-group', {
validate: (value) => {
if (!value) {
return { valid: false, message: 'Name is required' };
}
if (value.length < 2) {
return { valid: false, message: 'Name must be at least 2 characters' };
}
if (!/^[a-zA-Z\s'-]+$/.test(value)) {
return { valid: false, message: 'Name can only contain letters, spaces, hyphens, and apostrophes' };
}
return { valid: true, message: 'Great name!' };
}
});
// Email with autocomplete
new EmailAutocomplete('email-group');
// Password with strength meter
new PasswordInput('password-group');
// Message with character counter
new CharacterCounterInput('message-group');
});
// Prevent iOS zoom on input focus
document.addEventListener('gesturestart', e => e.preventDefault());
</script>
</body>
</html>