This commit is contained in:
IndyDevDan 2025-06-10 11:34:24 -05:00
parent ef1a41d365
commit 189d92a771
16 changed files with 6491 additions and 0 deletions

360
specs/invent_new_ui_v4.md Normal file
View File

@ -0,0 +1,360 @@
# Themed Hybrid UI Component Specification v4
## Evolution from v3: Modular Architecture
This specification builds upon v3's successful themed hybrid component approach with a critical architectural improvement: **separation of concerns through modular file structure**. While v3 delivered powerful themed components in single HTML files, v4 embraces modern development practices by splitting each component into three distinct files within organized directories.
### Key Improvements in v4:
- **Maintainability**: Styles and scripts can be modified without touching HTML structure
- **Reusability**: CSS themes and JavaScript behaviors can be extended or shared
- **Performance**: Better browser caching, conditional loading, and optimization opportunities
- **Collaboration**: Teams can work on styling, structure, and behavior independently
- **Scalability**: Components are ready for integration into larger systems
- **Developer Experience**: Clean separation follows industry best practices
## Core Challenge (Enhanced)
Create a **uniquely themed UI component** that combines multiple existing UI elements into one elegant solution, now with **professional-grade file organization** that demonstrates mastery of modern web development practices.
Apply a distinctive design language while solving multiple interface problems in a single, cohesive component - achieving "two birds with one stone" efficiency through both functional integration and architectural excellence.
## Output Requirements
**Directory Structure**: `ui_hybrid_[iteration_number]/`
Each iteration creates its own directory containing exactly three files:
```
ui_hybrid_[iteration_number]/
├── index.html # Semantic HTML structure
├── styles.css # Complete styling and theme implementation
└── script.js # All JavaScript functionality and interactions
```
**File Naming**:
- Directory: `ui_hybrid_[iteration_number]` (e.g., `ui_hybrid_1`, `ui_hybrid_2`)
- Files: Always `index.html`, `styles.css`, `script.js` (consistent naming)
## Content Structure
### **index.html** - Semantic Structure
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>[Theme Name] [Hybrid Component Name]</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<main>
<h1>[Hybrid Component Name] - [Theme Name] Theme</h1>
<!-- Clean semantic HTML structure -->
<!-- No inline styles or scripts -->
<div class="hybrid-component">
<!-- Component structure with meaningful class names -->
<!-- Data attributes for JavaScript hooks -->
<!-- Accessibility attributes (ARIA labels, roles) -->
</div>
<!-- Additional component instances or examples -->
</main>
<script src="script.js"></script>
</body>
</html>
```
### **styles.css** - Complete Theme Implementation
```css
/* Theme Variables and Custom Properties */
:root {
/* Color palette for the theme */
/* Typography scale */
/* Animation timings */
/* Spacing system */
}
/* Reset and Base Styles */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
/* Theme Foundation */
body {
/* Theme-specific base styles */
/* Background treatments */
/* Default typography */
}
/* Component Architecture */
.hybrid-component {
/* Main component container */
/* Theme-specific treatments */
}
/* Component Sub-elements */
.hybrid-component__[element] {
/* BEM or consistent naming convention */
/* Element-specific theme styling */
}
/* State Classes */
.is-active, .is-loading, .is-error {
/* State-based styling */
/* Theme-consistent feedback */
}
/* Animations and Transitions */
@keyframes [themeAnimation] {
/* Theme-specific animations */
}
/* Responsive Design */
@media (max-width: 768px) {
/* Mobile adaptations maintaining theme */
}
/* Print Styles */
@media print {
/* Print-optimized theme variant */
}
```
### **script.js** - Functionality and Interactions
```javascript
// Strict mode for better error catching
'use strict';
// Theme Configuration
const THEME_CONFIG = {
// Animation durations
// API endpoints if needed
// Theme-specific settings
};
// Component State Management
class HybridComponent {
constructor(element) {
this.element = element;
this.state = {
// Component state properties
};
this.init();
}
init() {
// Setup event listeners
// Initialize sub-components
// Load any necessary data
this.bindEvents();
this.setupThemeFeatures();
}
bindEvents() {
// Event delegation for efficiency
// Touch and mouse events
// Keyboard navigation
}
setupThemeFeatures() {
// Theme-specific interactions
// Special effects or behaviors
// Animation triggers
}
// Component Methods
updateState(updates) {
// State management logic
// UI updates based on state
}
// API Methods if needed
async fetchData() {
// Data loading with error handling
}
// Utility Methods
debounce(func, wait) {
// Performance optimizations
}
}
// Initialize on DOM Ready
document.addEventListener('DOMContentLoaded', () => {
// Find all component instances
const components = document.querySelectorAll('.hybrid-component');
// Initialize each instance
components.forEach(element => {
new HybridComponent(element);
});
// Setup any global theme features
initializeThemeEffects();
});
// Global Theme Functions
function initializeThemeEffects() {
// Ambient animations
// Parallax effects
// Theme-wide interactions
}
// Export for potential module usage
if (typeof module !== 'undefined' && module.exports) {
module.exports = HybridComponent;
}
```
## Design Dimensions (Preserved from v3)
### **Unique Theme Development**
Each component must embody a distinctive design language that creates personality and memorable experience. The multi-file structure enhances theme implementation:
#### **Enhanced Theme Implementation**
- **CSS Variables**: Define theme tokens in `:root` for consistent application
- **Modular Styles**: Theme variations can be swapped by changing stylesheets
- **JavaScript Theming**: Dynamic theme features separated from core functionality
- **Asset Organization**: Theme-specific assets referenced properly from each file
[All theme categories from v3 remain the same: Organic Nature, Digital Minimalism, Retro Computing, etc.]
### **Hybrid Component Strategy**
The same powerful combinations from v3, now with better architectural separation:
#### **Architectural Benefits per Component Type**
- **Search Hub**: Search logic isolated in JS, theme animations in CSS
- **Input Intelligence**: Validation rules in JS, visual feedback in CSS
- **Data Explorer**: Sorting algorithms in JS, table styling in CSS
- **Media Player**: Playback logic in JS, visualizer styles in CSS
[All component combinations from v3 remain valid]
## Enhancement Principles (Evolved)
### **Architectural Excellence** (New in v4)
- **Separation of Concerns**: Each file has a single, clear responsibility
- **No Inline Styles/Scripts**: All styling in CSS, all behavior in JavaScript
- **Progressive Enhancement**: HTML works without CSS/JS, enhanced by both
- **Module Boundaries**: Clear interfaces between files, no tight coupling
- **Future-Ready**: Structure supports build tools, frameworks, component libraries
### **Development Best Practices** (New in v4)
- **CSS Organization**: Logical section ordering, consistent naming conventions
- **JavaScript Patterns**: Modern ES6+, class-based or functional approaches
- **HTML Semantics**: Proper element selection, accessibility-first markup
- **Performance Focus**: Optimized selectors, efficient event handling
- **Documentation**: Clear comments explaining theme decisions and component logic
[All original enhancement principles from v3 remain: Thematic Consistency, Functional Integration, Practical Excellence]
## File Integration Guidelines
### **Linking Strategy**
- **Consistent Paths**: Always use relative paths (`href="styles.css"`)
- **Load Order**: CSS in `<head>`, JavaScript before `</body>`
- **No CDNs**: All functionality self-contained within the three files
- **Fallbacks**: Graceful degradation if CSS or JS fails to load
### **Communication Between Files**
- **HTML → CSS**: Semantic class names, data attributes for styling hooks
- **HTML → JS**: IDs for unique elements, data attributes for configuration
- **CSS → JS**: CSS custom properties readable by JavaScript
- **JS → CSS**: Dynamic class additions, CSS variable updates
### **Naming Conventions**
- **CSS Classes**: BEM, semantic, or consistent methodology
- **JavaScript**: camelCase for variables/functions, PascalCase for classes
- **Data Attributes**: `data-component-*` for component-specific data
- **CSS Variables**: `--theme-*` prefix for theme variables
## Quality Standards (Enhanced)
### **Code Quality** (New in v4)
- **Valid HTML**: Passes W3C validation, proper semantic structure
- **CSS Organization**: Logical property grouping, no redundancy
- **JavaScript Quality**: No global pollution, proper error handling
- **Cross-Browser**: Works in all modern browsers (Chrome, Firefox, Safari, Edge)
- **Performance**: Lighthouse score of 90+ in all categories
### **File-Specific Standards** (New in v4)
- **HTML**: Semantic, accessible, minimal, no presentation logic
- **CSS**: Organized, maintainable, efficient selectors, mobile-first
- **JavaScript**: Modular, testable, documented, memory-efficient
[All original quality standards from v3 remain in effect]
## Migration Example: v3 to v4
**v3 Structure (Single File):**
```
ui_hybrid_1.html (contains everything)
```
**v4 Structure (Modular):**
```
ui_hybrid_1/
├── index.html (structure only)
├── styles.css (all styling)
└── script.js (all behavior)
```
The same themed hybrid component now benefits from:
- 3x better caching (each file cached independently)
- Easier debugging (concerns separated)
- Simpler version control (changes isolated to relevant files)
- Team collaboration (parallel development possible)
- Build tool ready (can be processed, minified, bundled)
## Iteration Evolution (Enhanced)
### **Architectural Sophistication** (New in v4)
- **Foundation (1-3)**: Clean separation, basic modular structure
- **Refinement (4-6)**: Advanced CSS architecture, sophisticated JS patterns
- **Innovation (7+)**: Creative file communication, advanced state management
### **Development Complexity**
- **Phase 1**: Standard separation with clear file boundaries
- **Phase 2**: Advanced patterns like CSS custom properties + JS integration
- **Phase 3**: Sophisticated architectures with event systems, style injection
- **Phase 4**: Revolutionary approaches to component modularity
## Ultra-Thinking Directive (Enhanced)
Before each themed hybrid creation, deeply consider:
**Architectural Decisions:**
- How can the three-file structure enhance this specific theme?
- What belongs in CSS vs JavaScript for this component type?
- How can files communicate elegantly for this use case?
- What patterns best support this component's evolution?
- How does separation improve maintainability here?
**File Responsibility Planning:**
- What is the minimal, semantic HTML needed?
- Which styles are structural vs thematic?
- What JavaScript is essential vs enhancement?
- How can each file remain focused and clean?
- Where are the natural boundaries between concerns?
**Integration Excellence:**
- How do the files work together seamlessly?
- What naming conventions ensure clarity?
- How can we avoid tight coupling?
- What patterns enable future extensions?
- How does the architecture support the theme?
[All original ultra-thinking directives from v3 remain relevant]
**Generate components that are:**
- **Architecturally Sound**: Professional-grade file organization and separation
- **Thematically Distinctive**: Strong design personality across all three files
- **Functionally Integrated**: Multiple UI capabilities with clean code boundaries
- **Professionally Crafted**: Industry-standard patterns and practices
- **Immediately Impressive**: Excellence visible in both UI and code structure
The evolution from v3 to v4 represents growth from powerful prototypes to production-ready components, maintaining all creative excellence while adding architectural sophistication.

View File

@ -0,0 +1,147 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Organic Search Hub - Nature-Inspired Discovery</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<main class="search-hub" role="main">
<header class="search-hub__header">
<h1 class="search-hub__title">
<span class="title-prefix">Discover</span>
<span class="title-main">Nature's Answers</span>
</h1>
<p class="search-hub__subtitle">Let your search grow organically</p>
</header>
<section class="search-container" role="search">
<div class="search-input-wrapper">
<div class="search-input-group">
<input
type="search"
id="main-search"
class="search-input"
placeholder="Plant your search seed..."
aria-label="Search"
autocomplete="off"
>
<button class="search-button" aria-label="Search">
<svg class="search-icon" viewBox="0 0 24 24" aria-hidden="true">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.35-4.35"></path>
</svg>
</button>
</div>
<div class="filter-controls">
<button class="filter-toggle" aria-expanded="false" aria-controls="advanced-filters">
<span class="filter-icon"></span>
<span class="filter-text">Cultivate filters</span>
</button>
</div>
</div>
<div id="advanced-filters" class="advanced-filters" aria-hidden="true">
<fieldset class="filter-group">
<legend>Growth Season</legend>
<label class="filter-option">
<input type="radio" name="timeframe" value="today">
<span class="filter-label">Today's Sprouts</span>
</label>
<label class="filter-option">
<input type="radio" name="timeframe" value="week">
<span class="filter-label">This Week's Bloom</span>
</label>
<label class="filter-option">
<input type="radio" name="timeframe" value="month">
<span class="filter-label">Monthly Harvest</span>
</label>
<label class="filter-option">
<input type="radio" name="timeframe" value="all" checked>
<span class="filter-label">Perennial Collection</span>
</label>
</fieldset>
<fieldset class="filter-group">
<legend>Garden Types</legend>
<label class="filter-option">
<input type="checkbox" name="type" value="articles">
<span class="filter-label">Knowledge Seeds</span>
</label>
<label class="filter-option">
<input type="checkbox" name="type" value="images">
<span class="filter-label">Visual Blooms</span>
</label>
<label class="filter-option">
<input type="checkbox" name="type" value="videos">
<span class="filter-label">Motion Vines</span>
</label>
<label class="filter-option">
<input type="checkbox" name="type" value="audio">
<span class="filter-label">Sound Leaves</span>
</label>
</fieldset>
</div>
<div class="search-suggestions" role="listbox" aria-label="Search suggestions">
<!-- Dynamically populated suggestions -->
</div>
</section>
<section class="quick-actions" aria-label="Quick actions">
<h2 class="section-title">Quick Roots</h2>
<div class="action-grid">
<button class="action-button" data-action="trending">
<span class="action-icon action-icon--trending"></span>
<span class="action-label">Trending Growth</span>
</button>
<button class="action-button" data-action="seasonal">
<span class="action-icon action-icon--seasonal"></span>
<span class="action-label">Seasonal Picks</span>
</button>
<button class="action-button" data-action="evergreen">
<span class="action-icon action-icon--evergreen"></span>
<span class="action-label">Evergreen Wisdom</span>
</button>
<button class="action-button" data-action="wild">
<span class="action-icon action-icon--wild"></span>
<span class="action-label">Wild Discovery</span>
</button>
</div>
</section>
<section class="search-ecosystem">
<div class="recent-searches">
<h3 class="ecosystem-title">
<span class="ecosystem-icon ecosystem-icon--recent"></span>
Recent Plantings
</h3>
<ul class="search-list" role="list">
<!-- Dynamically populated recent searches -->
</ul>
</div>
<div class="popular-searches">
<h3 class="ecosystem-title">
<span class="ecosystem-icon ecosystem-icon--popular"></span>
Community Garden
</h3>
<ul class="search-list" role="list">
<!-- Dynamically populated popular searches -->
</ul>
</div>
</section>
<footer class="search-hub__footer">
<p class="growth-status">
<span class="status-indicator"></span>
<span class="status-text">Your search garden is thriving</span>
</p>
</footer>
</main>
<script src="script.js"></script>
</body>
</html>

View File

@ -0,0 +1,563 @@
// Organic Search Hub - Interactive Functionality
class OrganicSearchHub {
constructor() {
this.searchInput = document.getElementById('main-search');
this.searchButton = document.querySelector('.search-button');
this.filterToggle = document.querySelector('.filter-toggle');
this.advancedFilters = document.getElementById('advanced-filters');
this.suggestionsContainer = document.querySelector('.search-suggestions');
this.recentSearchesList = document.querySelector('.recent-searches .search-list');
this.popularSearchesList = document.querySelector('.popular-searches .search-list');
this.actionButtons = document.querySelectorAll('.action-button');
this.statusText = document.querySelector('.status-text');
this.searchHistory = this.loadSearchHistory();
this.currentSuggestions = [];
this.debounceTimer = null;
this.init();
}
init() {
this.bindEvents();
this.populateRecentSearches();
this.populatePopularSearches();
this.initializeFilters();
this.startGrowthAnimation();
}
bindEvents() {
// Search input events
this.searchInput.addEventListener('input', (e) => this.handleSearchInput(e));
this.searchInput.addEventListener('focus', () => this.showSuggestions());
this.searchInput.addEventListener('keydown', (e) => this.handleKeyNavigation(e));
// Search button
this.searchButton.addEventListener('click', () => this.performSearch());
// Filter toggle
this.filterToggle.addEventListener('click', () => this.toggleFilters());
// Action buttons
this.actionButtons.forEach(button => {
button.addEventListener('click', (e) => this.handleQuickAction(e));
});
// Click outside to close suggestions
document.addEventListener('click', (e) => {
if (!e.target.closest('.search-container')) {
this.hideSuggestions();
}
});
// Filter changes
this.advancedFilters.addEventListener('change', () => this.updateSearchContext());
}
handleSearchInput(e) {
const query = e.target.value.trim();
// Debounce search suggestions
clearTimeout(this.debounceTimer);
this.debounceTimer = setTimeout(() => {
if (query.length > 0) {
this.generateSuggestions(query);
this.showSuggestions();
} else {
this.hideSuggestions();
}
}, 300);
// Update growth status
this.updateGrowthStatus(query);
}
generateSuggestions(query) {
// Simulated organic suggestions based on nature themes
const natureTerms = [
'sustainable gardens', 'permaculture design', 'native plants',
'pollinator habitats', 'composting methods', 'seed saving',
'organic farming', 'forest bathing', 'wildlife photography',
'botanical illustration', 'mushroom foraging', 'tree identification',
'bird watching', 'natural remedies', 'eco-friendly practices'
];
// Filter and enhance suggestions
const suggestions = natureTerms
.filter(term => term.toLowerCase().includes(query.toLowerCase()))
.slice(0, 5)
.map(term => ({
text: term,
type: this.categorizeSuggestion(term),
relevance: this.calculateRelevance(term, query)
}))
.sort((a, b) => b.relevance - a.relevance);
this.currentSuggestions = suggestions;
this.renderSuggestions(suggestions);
}
categorizeSuggestion(term) {
const categories = {
garden: ['garden', 'plant', 'seed', 'compost'],
wildlife: ['bird', 'wildlife', 'pollinator', 'mushroom'],
practice: ['sustainable', 'organic', 'eco-friendly', 'permaculture'],
experience: ['bathing', 'watching', 'photography', 'illustration']
};
for (const [category, keywords] of Object.entries(categories)) {
if (keywords.some(keyword => term.toLowerCase().includes(keyword))) {
return category;
}
}
return 'general';
}
calculateRelevance(term, query) {
const lowerTerm = term.toLowerCase();
const lowerQuery = query.toLowerCase();
// Exact match gets highest score
if (lowerTerm === lowerQuery) return 100;
// Starting with query gets high score
if (lowerTerm.startsWith(lowerQuery)) return 80;
// Contains query gets medium score
if (lowerTerm.includes(lowerQuery)) return 60;
// Fuzzy match gets lower score
return this.fuzzyMatch(lowerTerm, lowerQuery) ? 40 : 0;
}
fuzzyMatch(str, pattern) {
let patternIdx = 0;
for (let i = 0; i < str.length; i++) {
if (str[i] === pattern[patternIdx]) {
patternIdx++;
}
if (patternIdx === pattern.length) return true;
}
return false;
}
renderSuggestions(suggestions) {
if (suggestions.length === 0) {
this.hideSuggestions();
return;
}
const html = suggestions.map((suggestion, index) => `
<div class="suggestion-item" data-index="${index}" role="option">
<span class="suggestion-icon suggestion-icon--${suggestion.type}"></span>
<span class="suggestion-text">${this.highlightMatch(suggestion.text)}</span>
</div>
`).join('');
this.suggestionsContainer.innerHTML = html;
// Add click handlers to suggestions
this.suggestionsContainer.querySelectorAll('.suggestion-item').forEach(item => {
item.addEventListener('click', () => {
const index = parseInt(item.dataset.index);
this.selectSuggestion(this.currentSuggestions[index]);
});
});
}
highlightMatch(text) {
const query = this.searchInput.value.trim();
if (!query) return text;
const regex = new RegExp(`(${query})`, 'gi');
return text.replace(regex, '<strong>$1</strong>');
}
selectSuggestion(suggestion) {
this.searchInput.value = suggestion.text;
this.hideSuggestions();
this.performSearch();
}
showSuggestions() {
if (this.currentSuggestions.length > 0) {
this.suggestionsContainer.classList.add('active');
}
}
hideSuggestions() {
this.suggestionsContainer.classList.remove('active');
}
handleKeyNavigation(e) {
const items = this.suggestionsContainer.querySelectorAll('.suggestion-item');
const activeItem = this.suggestionsContainer.querySelector('.suggestion-item.active');
let currentIndex = activeItem ? parseInt(activeItem.dataset.index) : -1;
switch(e.key) {
case 'ArrowDown':
e.preventDefault();
currentIndex = (currentIndex + 1) % items.length;
this.highlightSuggestion(currentIndex);
break;
case 'ArrowUp':
e.preventDefault();
currentIndex = currentIndex <= 0 ? items.length - 1 : currentIndex - 1;
this.highlightSuggestion(currentIndex);
break;
case 'Enter':
if (currentIndex >= 0) {
e.preventDefault();
this.selectSuggestion(this.currentSuggestions[currentIndex]);
} else {
this.performSearch();
}
break;
case 'Escape':
this.hideSuggestions();
break;
}
}
highlightSuggestion(index) {
const items = this.suggestionsContainer.querySelectorAll('.suggestion-item');
items.forEach(item => item.classList.remove('active'));
if (index >= 0 && index < items.length) {
items[index].classList.add('active');
items[index].scrollIntoView({ block: 'nearest' });
}
}
toggleFilters() {
const isExpanded = this.filterToggle.getAttribute('aria-expanded') === 'true';
this.filterToggle.setAttribute('aria-expanded', !isExpanded);
this.advancedFilters.setAttribute('aria-hidden', isExpanded);
if (!isExpanded) {
this.advancedFilters.style.display = 'grid';
// Animate in
requestAnimationFrame(() => {
this.advancedFilters.style.transform = 'scaleY(1)';
this.advancedFilters.style.opacity = '1';
});
} else {
this.advancedFilters.style.transform = 'scaleY(0)';
this.advancedFilters.style.opacity = '0';
setTimeout(() => {
this.advancedFilters.style.display = 'none';
}, 400);
}
}
performSearch() {
const query = this.searchInput.value.trim();
if (!query) return;
// Add to search history
this.addToSearchHistory(query);
// Get active filters
const filters = this.getActiveFilters();
// Simulate search action
console.log('Searching for:', query, 'with filters:', filters);
// Update UI feedback
this.showSearchFeedback(query);
// Clear suggestions
this.hideSuggestions();
}
getActiveFilters() {
const filters = {
timeframe: null,
types: []
};
// Get timeframe
const timeframeInput = this.advancedFilters.querySelector('input[name="timeframe"]:checked');
if (timeframeInput) {
filters.timeframe = timeframeInput.value;
}
// Get types
const typeInputs = this.advancedFilters.querySelectorAll('input[name="type"]:checked');
typeInputs.forEach(input => {
filters.types.push(input.value);
});
return filters;
}
handleQuickAction(e) {
const button = e.currentTarget;
const action = button.dataset.action;
// Animate button
button.style.transform = 'scale(0.95)';
setTimeout(() => {
button.style.transform = '';
}, 200);
// Perform action
switch(action) {
case 'trending':
this.searchInput.value = 'trending in sustainable living';
break;
case 'seasonal':
this.searchInput.value = this.getSeasonalQuery();
break;
case 'evergreen':
this.searchInput.value = 'timeless gardening wisdom';
break;
case 'wild':
this.searchInput.value = this.getRandomNatureQuery();
break;
}
this.performSearch();
}
getSeasonalQuery() {
const month = new Date().getMonth();
const seasons = {
spring: 'spring planting guide',
summer: 'summer garden care',
fall: 'autumn harvest tips',
winter: 'winter garden preparation'
};
if (month >= 2 && month <= 4) return seasons.spring;
if (month >= 5 && month <= 7) return seasons.summer;
if (month >= 8 && month <= 10) return seasons.fall;
return seasons.winter;
}
getRandomNatureQuery() {
const queries = [
'rare botanical species',
'unexplored nature trails',
'unique ecosystem discoveries',
'hidden garden gems',
'secret foraging spots'
];
return queries[Math.floor(Math.random() * queries.length)];
}
addToSearchHistory(query) {
// Remove if already exists
this.searchHistory = this.searchHistory.filter(item => item !== query);
// Add to beginning
this.searchHistory.unshift(query);
// Keep only last 10
this.searchHistory = this.searchHistory.slice(0, 10);
// Save to localStorage
this.saveSearchHistory();
// Update UI
this.populateRecentSearches();
}
loadSearchHistory() {
try {
return JSON.parse(localStorage.getItem('organicSearchHistory')) || [];
} catch {
return [];
}
}
saveSearchHistory() {
try {
localStorage.setItem('organicSearchHistory', JSON.stringify(this.searchHistory));
} catch (e) {
console.error('Failed to save search history:', e);
}
}
populateRecentSearches() {
if (this.searchHistory.length === 0) {
this.recentSearchesList.innerHTML = '<li class="empty-state">No recent searches yet</li>';
return;
}
const html = this.searchHistory.slice(0, 5).map(query =>
`<li data-query="${query}">${query}</li>`
).join('');
this.recentSearchesList.innerHTML = html;
// Add click handlers
this.recentSearchesList.querySelectorAll('li').forEach(item => {
if (!item.classList.contains('empty-state')) {
item.addEventListener('click', () => {
this.searchInput.value = item.dataset.query;
this.performSearch();
});
}
});
}
populatePopularSearches() {
const popularSearches = [
'composting basics',
'native plant species',
'butterfly gardens',
'organic pest control',
'rainwater harvesting'
];
const html = popularSearches.map(query =>
`<li data-query="${query}">${query}</li>`
).join('');
this.popularSearchesList.innerHTML = html;
// Add click handlers
this.popularSearchesList.querySelectorAll('li').forEach(item => {
item.addEventListener('click', () => {
this.searchInput.value = item.dataset.query;
this.performSearch();
});
});
}
updateSearchContext() {
const filters = this.getActiveFilters();
let context = 'Searching ';
if (filters.timeframe && filters.timeframe !== 'all') {
context += `${filters.timeframe}'s `;
}
if (filters.types.length > 0) {
context += filters.types.join(', ');
} else {
context += 'all content';
}
this.showStatusMessage(context);
}
updateGrowthStatus(query) {
const messages = [
'Your search is taking root...',
'Ideas are sprouting...',
'Knowledge is blooming...',
'Wisdom is growing...',
'Discoveries are flourishing...'
];
if (query.length > 0) {
const index = Math.min(Math.floor(query.length / 3), messages.length - 1);
this.statusText.textContent = messages[index];
} else {
this.statusText.textContent = 'Your search garden is thriving';
}
}
showSearchFeedback(query) {
const messages = [
`Cultivating results for "${query}"...`,
`Harvesting knowledge about "${query}"...`,
`Growing insights on "${query}"...`,
`Nurturing information about "${query}"...`
];
const message = messages[Math.floor(Math.random() * messages.length)];
this.showStatusMessage(message);
// Reset after delay
setTimeout(() => {
this.statusText.textContent = 'Your search garden is thriving';
}, 3000);
}
showStatusMessage(message) {
this.statusText.style.opacity = '0';
setTimeout(() => {
this.statusText.textContent = message;
this.statusText.style.opacity = '1';
}, 200);
}
initializeFilters() {
// Set initial state
this.advancedFilters.style.transform = 'scaleY(0)';
this.advancedFilters.style.opacity = '0';
this.advancedFilters.style.transformOrigin = 'top';
this.advancedFilters.style.transition = 'all 400ms cubic-bezier(0.34, 1.56, 0.64, 1)';
}
startGrowthAnimation() {
// Animate elements on load
const elements = [
{ el: '.search-hub__header', delay: 0 },
{ el: '.search-container', delay: 200 },
{ el: '.quick-actions', delay: 400 },
{ el: '.search-ecosystem', delay: 600 }
];
elements.forEach(({ el, delay }) => {
const element = document.querySelector(el);
if (element) {
element.style.opacity = '0';
element.style.transform = 'translateY(20px)';
setTimeout(() => {
element.style.transition = 'all 800ms ease-out';
element.style.opacity = '1';
element.style.transform = 'translateY(0)';
}, delay);
}
});
}
}
// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
new OrganicSearchHub();
});
// Add custom styles for suggestion type icons
const style = document.createElement('style');
style.textContent = `
.suggestion-icon--garden::after { content: '🌱'; }
.suggestion-icon--wildlife::after { content: '🦋'; }
.suggestion-icon--practice::after { content: '♻️'; }
.suggestion-icon--experience::after { content: '🌄'; }
.suggestion-icon--general::after { content: '🔍'; }
.suggestion-icon::after {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 12px;
}
.suggestion-item.active {
background: var(--morning-mist);
padding-left: var(--space-branch);
}
.empty-state {
opacity: 0.6;
font-style: italic;
cursor: default !important;
}
.empty-state:hover {
transform: none !important;
color: inherit !important;
}
`;
document.head.appendChild(style);

View File

@ -0,0 +1,628 @@
/* Organic Nature Theme - Search Hub Styles */
:root {
/* Nature-inspired color palette */
--moss-dark: #2d4a2b;
--moss-medium: #4a6741;
--moss-light: #6b8e4e;
--sage-green: #87a96b;
--spring-green: #a8c09a;
--leaf-accent: #7fb069;
--bark-brown: #6f4e37;
--soil-dark: #3e2723;
--sand-light: #f5e6d3;
--morning-mist: #e8f5e9;
--sky-blue: #87ceeb;
--pollen-yellow: #f7dc6f;
--berry-red: #d32f2f;
/* Organic spacing rhythm */
--space-seed: 0.25rem;
--space-sprout: 0.5rem;
--space-stem: 1rem;
--space-branch: 1.5rem;
--space-canopy: 2.5rem;
--space-forest: 4rem;
/* Natural transitions */
--transition-breeze: 200ms ease-out;
--transition-growth: 400ms cubic-bezier(0.34, 1.56, 0.64, 1);
--transition-season: 600ms ease-in-out;
/* Organic shapes */
--radius-pebble: 0.25rem;
--radius-stone: 0.5rem;
--radius-boulder: 1rem;
--radius-hill: 2rem;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: linear-gradient(135deg, var(--morning-mist) 0%, var(--sand-light) 100%);
color: var(--soil-dark);
line-height: 1.6;
min-height: 100vh;
position: relative;
overflow-x: hidden;
}
/* Organic background pattern */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image:
radial-gradient(circle at 20% 30%, var(--spring-green) 0%, transparent 40%),
radial-gradient(circle at 80% 70%, var(--sage-green) 0%, transparent 30%),
radial-gradient(circle at 40% 90%, var(--leaf-accent) 0%, transparent 25%);
opacity: 0.1;
pointer-events: none;
animation: organicFloat 30s ease-in-out infinite;
}
@keyframes organicFloat {
0%, 100% { transform: translate(0, 0) scale(1); }
25% { transform: translate(-20px, -30px) scale(1.05); }
50% { transform: translate(30px, -20px) scale(0.98); }
75% { transform: translate(-10px, 20px) scale(1.02); }
}
/* Main container */
.search-hub {
max-width: 1200px;
margin: 0 auto;
padding: var(--space-branch) var(--space-stem);
position: relative;
z-index: 1;
}
/* Header styling */
.search-hub__header {
text-align: center;
margin-bottom: var(--space-canopy);
animation: bloomIn 800ms ease-out;
}
.search-hub__title {
font-size: clamp(2rem, 5vw, 3.5rem);
font-weight: 300;
color: var(--moss-dark);
margin-bottom: var(--space-sprout);
display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-seed);
}
.title-prefix {
font-size: 0.6em;
color: var(--moss-medium);
letter-spacing: 0.2em;
text-transform: uppercase;
}
.title-main {
background: linear-gradient(135deg, var(--moss-dark), var(--leaf-accent));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.search-hub__subtitle {
color: var(--moss-medium);
font-style: italic;
opacity: 0.8;
}
/* Search container */
.search-container {
background: rgba(255, 255, 255, 0.9);
border-radius: var(--radius-hill);
padding: var(--space-canopy);
box-shadow:
0 10px 40px rgba(46, 74, 43, 0.1),
0 2px 10px rgba(46, 74, 43, 0.05);
backdrop-filter: blur(10px);
position: relative;
margin-bottom: var(--space-canopy);
}
/* Search input group */
.search-input-group {
position: relative;
display: flex;
align-items: center;
background: var(--morning-mist);
border-radius: var(--radius-boulder);
border: 2px solid transparent;
transition: all var(--transition-growth);
overflow: hidden;
}
.search-input-group:focus-within {
border-color: var(--leaf-accent);
transform: translateY(-2px);
box-shadow:
0 5px 20px rgba(127, 176, 105, 0.3),
0 2px 8px rgba(127, 176, 105, 0.2);
}
.search-input {
flex: 1;
padding: var(--space-stem) var(--space-branch);
font-size: 1.125rem;
background: transparent;
border: none;
outline: none;
color: var(--soil-dark);
}
.search-input::placeholder {
color: var(--moss-medium);
opacity: 0.7;
}
.search-button {
padding: var(--space-stem) var(--space-branch);
background: var(--leaf-accent);
border: none;
cursor: pointer;
color: white;
transition: all var(--transition-breeze);
display: flex;
align-items: center;
justify-content: center;
}
.search-button:hover {
background: var(--moss-light);
transform: scale(1.05);
}
.search-icon {
width: 24px;
height: 24px;
stroke: currentColor;
stroke-width: 2;
fill: none;
}
/* Filter controls */
.filter-controls {
margin-top: var(--space-stem);
}
.filter-toggle {
display: flex;
align-items: center;
gap: var(--space-sprout);
padding: var(--space-sprout) var(--space-stem);
background: transparent;
border: 2px solid var(--spring-green);
border-radius: var(--radius-stone);
color: var(--moss-dark);
cursor: pointer;
transition: all var(--transition-breeze);
font-size: 0.875rem;
}
.filter-toggle:hover {
background: var(--spring-green);
transform: translateY(-1px);
}
.filter-icon {
width: 16px;
height: 16px;
background: var(--moss-medium);
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M3 4h18v2.172a2 2 0 0 1-.586 1.414l-6.828 6.828A2 2 0 0 0 13 15.828V20l-4 2v-6.172a2 2 0 0 0-.586-1.414L1.586 7.586A2 2 0 0 1 1 6.172V4z'/%3E%3C/svg%3E") center/contain no-repeat;
transition: transform var(--transition-breeze);
}
.filter-toggle[aria-expanded="true"] .filter-icon {
transform: rotate(180deg);
}
/* Advanced filters */
.advanced-filters {
margin-top: var(--space-branch);
padding: var(--space-branch);
background: rgba(232, 245, 233, 0.5);
border-radius: var(--radius-boulder);
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: var(--space-branch);
transform-origin: top;
transition: all var(--transition-growth);
}
.advanced-filters[aria-hidden="true"] {
display: none;
}
.filter-group {
border: none;
padding: 0;
}
.filter-group legend {
font-weight: 600;
color: var(--moss-dark);
margin-bottom: var(--space-sprout);
display: flex;
align-items: center;
gap: var(--space-sprout);
}
.filter-option {
display: flex;
align-items: center;
padding: var(--space-sprout);
margin-bottom: var(--space-seed);
cursor: pointer;
border-radius: var(--radius-stone);
transition: all var(--transition-breeze);
}
.filter-option:hover {
background: rgba(127, 176, 105, 0.1);
}
.filter-option input {
margin-right: var(--space-sprout);
accent-color: var(--leaf-accent);
}
/* Search suggestions */
.search-suggestions {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
border-radius: var(--radius-boulder);
box-shadow: 0 10px 30px rgba(46, 74, 43, 0.15);
margin-top: var(--space-sprout);
max-height: 300px;
overflow-y: auto;
display: none;
z-index: 10;
}
.search-suggestions.active {
display: block;
animation: growDown 300ms ease-out;
}
@keyframes growDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.suggestion-item {
padding: var(--space-stem);
cursor: pointer;
border-bottom: 1px solid var(--morning-mist);
transition: all var(--transition-breeze);
display: flex;
align-items: center;
gap: var(--space-sprout);
}
.suggestion-item:hover {
background: var(--morning-mist);
padding-left: var(--space-branch);
}
.suggestion-icon {
width: 20px;
height: 20px;
background: var(--spring-green);
border-radius: 50%;
flex-shrink: 0;
}
/* Quick actions */
.quick-actions {
margin-bottom: var(--space-canopy);
}
.section-title {
font-size: 1.5rem;
color: var(--moss-dark);
margin-bottom: var(--space-branch);
display: flex;
align-items: center;
gap: var(--space-stem);
}
.section-title::before {
content: '';
width: 40px;
height: 2px;
background: linear-gradient(90deg, transparent, var(--leaf-accent), transparent);
}
.action-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: var(--space-stem);
}
.action-button {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-sprout);
padding: var(--space-branch);
background: white;
border: 2px solid var(--spring-green);
border-radius: var(--radius-boulder);
cursor: pointer;
transition: all var(--transition-growth);
position: relative;
overflow: hidden;
}
.action-button::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: radial-gradient(circle at center, var(--leaf-accent), transparent);
opacity: 0;
transition: opacity var(--transition-breeze);
}
.action-button:hover {
transform: translateY(-3px);
box-shadow: 0 10px 25px rgba(127, 176, 105, 0.3);
border-color: var(--leaf-accent);
}
.action-button:hover::before {
opacity: 0.1;
}
.action-icon {
width: 48px;
height: 48px;
background: var(--moss-light);
border-radius: 50%;
position: relative;
}
.action-icon--trending::after {
content: '📈';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24px;
}
.action-icon--seasonal::after {
content: '🌿';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24px;
}
.action-icon--evergreen::after {
content: '🌲';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24px;
}
.action-icon--wild::after {
content: '🦋';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24px;
}
.action-label {
font-weight: 500;
color: var(--moss-dark);
}
/* Search ecosystem */
.search-ecosystem {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: var(--space-branch);
margin-bottom: var(--space-canopy);
}
.recent-searches,
.popular-searches {
background: rgba(255, 255, 255, 0.8);
border-radius: var(--radius-boulder);
padding: var(--space-branch);
backdrop-filter: blur(5px);
}
.ecosystem-title {
font-size: 1.125rem;
color: var(--moss-dark);
margin-bottom: var(--space-stem);
display: flex;
align-items: center;
gap: var(--space-sprout);
}
.ecosystem-icon {
width: 24px;
height: 24px;
background: var(--spring-green);
border-radius: 50%;
position: relative;
}
.ecosystem-icon--recent::after {
content: '🕐';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 14px;
}
.ecosystem-icon--popular::after {
content: '⭐';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 14px;
}
.search-list {
list-style: none;
}
.search-list li {
padding: var(--space-sprout) 0;
border-bottom: 1px solid var(--morning-mist);
cursor: pointer;
transition: all var(--transition-breeze);
position: relative;
padding-left: var(--space-branch);
}
.search-list li::before {
content: '🌱';
position: absolute;
left: 0;
opacity: 0.5;
transition: all var(--transition-breeze);
}
.search-list li:hover {
color: var(--leaf-accent);
transform: translateX(5px);
}
.search-list li:hover::before {
opacity: 1;
}
/* Footer */
.search-hub__footer {
text-align: center;
padding: var(--space-branch) 0;
}
.growth-status {
display: inline-flex;
align-items: center;
gap: var(--space-sprout);
padding: var(--space-sprout) var(--space-branch);
background: rgba(255, 255, 255, 0.5);
border-radius: var(--radius-hill);
color: var(--moss-medium);
font-size: 0.875rem;
}
.status-indicator {
width: 8px;
height: 8px;
background: var(--leaf-accent);
border-radius: 50%;
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.2); opacity: 0.7; }
}
/* Animations */
@keyframes bloomIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Responsive design */
@media (max-width: 768px) {
.search-hub {
padding: var(--space-stem);
}
.search-container {
padding: var(--space-branch);
}
.action-grid {
grid-template-columns: repeat(2, 1fr);
}
.search-ecosystem {
grid-template-columns: 1fr;
}
}
/* Accessibility */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* Focus styles */
:focus-visible {
outline: 3px solid var(--leaf-accent);
outline-offset: 2px;
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-track {
background: var(--morning-mist);
}
::-webkit-scrollbar-thumb {
background: var(--moss-light);
border-radius: var(--radius-stone);
}
::-webkit-scrollbar-thumb:hover {
background: var(--moss-medium);
}

View File

@ -0,0 +1,134 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Digital Minimalism - Input Intelligence</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<main class="container">
<header class="header">
<h1 class="title">Input Intelligence</h1>
<p class="subtitle">Smart form interactions with minimal design</p>
</header>
<section class="input-section">
<form class="intelligent-form" id="smartForm">
<div class="input-group" data-input-type="email">
<label for="emailInput" class="input-label">Email Address</label>
<div class="input-wrapper">
<input
type="text"
id="emailInput"
class="intelligent-input"
placeholder="Type your email"
autocomplete="off"
aria-describedby="emailHint emailError"
>
<div class="input-progress" aria-hidden="true"></div>
<div class="input-indicator" aria-hidden="true"></div>
</div>
<p id="emailHint" class="input-hint">We'll validate as you type</p>
<p id="emailError" class="input-error" role="alert" aria-live="polite"></p>
<ul class="suggestions-list" id="emailSuggestions" role="listbox" aria-label="Email suggestions"></ul>
</div>
<div class="input-group" data-input-type="phone">
<label for="phoneInput" class="input-label">Phone Number</label>
<div class="input-wrapper">
<input
type="tel"
id="phoneInput"
class="intelligent-input"
placeholder="(123) 456-7890"
autocomplete="off"
aria-describedby="phoneHint phoneError"
>
<div class="input-progress" aria-hidden="true"></div>
<div class="input-indicator" aria-hidden="true"></div>
</div>
<p id="phoneHint" class="input-hint">Auto-formatting enabled</p>
<p id="phoneError" class="input-error" role="alert" aria-live="polite"></p>
</div>
<div class="input-group" data-input-type="card">
<label for="cardInput" class="input-label">Credit Card</label>
<div class="input-wrapper">
<input
type="text"
id="cardInput"
class="intelligent-input"
placeholder="1234 5678 9012 3456"
autocomplete="off"
aria-describedby="cardHint cardError"
>
<div class="input-progress" aria-hidden="true"></div>
<div class="input-indicator" aria-hidden="true"></div>
<div class="card-type" aria-label="Card type"></div>
</div>
<p id="cardHint" class="input-hint">Secure input with automatic spacing</p>
<p id="cardError" class="input-error" role="alert" aria-live="polite"></p>
</div>
<div class="input-group" data-input-type="password">
<label for="passwordInput" class="input-label">Secure Password</label>
<div class="input-wrapper">
<input
type="password"
id="passwordInput"
class="intelligent-input"
placeholder="Create a strong password"
autocomplete="new-password"
aria-describedby="passwordHint passwordError passwordStrength"
>
<div class="input-progress" aria-hidden="true"></div>
<button type="button" class="password-toggle" aria-label="Toggle password visibility">
<span class="password-toggle-icon"></span>
</button>
</div>
<p id="passwordHint" class="input-hint">Minimum 8 characters</p>
<div id="passwordStrength" class="password-strength" role="status" aria-live="polite">
<div class="strength-meter">
<div class="strength-fill"></div>
</div>
<span class="strength-text"></span>
</div>
<p id="passwordError" class="input-error" role="alert" aria-live="polite"></p>
</div>
<div class="form-progress">
<div class="progress-bar">
<div class="progress-fill" style="width: 0%"></div>
</div>
<p class="progress-text">0% Complete</p>
</div>
<button type="submit" class="submit-button" disabled>
<span class="button-text">Submit Form</span>
<span class="button-loader"></span>
</button>
</form>
</section>
<footer class="footer">
<div class="stats">
<div class="stat-item">
<span class="stat-value" id="validFields">0</span>
<span class="stat-label">Valid Fields</span>
</div>
<div class="stat-item">
<span class="stat-value" id="totalFields">4</span>
<span class="stat-label">Total Fields</span>
</div>
<div class="stat-item">
<span class="stat-value" id="inputQuality">0%</span>
<span class="stat-label">Input Quality</span>
</div>
</div>
</footer>
</main>
<script src="script.js"></script>
</body>
</html>

View File

@ -0,0 +1,431 @@
// Digital Minimalism - Input Intelligence Script
class InputIntelligence {
constructor() {
this.form = document.getElementById('smartForm');
this.inputs = {};
this.validators = {
email: this.validateEmail.bind(this),
phone: this.validatePhone.bind(this),
card: this.validateCard.bind(this),
password: this.validatePassword.bind(this)
};
this.formatters = {
phone: this.formatPhone.bind(this),
card: this.formatCard.bind(this)
};
this.validFields = new Set();
this.init();
}
init() {
// Initialize all input fields
this.form.querySelectorAll('.intelligent-input').forEach(input => {
const type = input.closest('.input-group').dataset.inputType;
this.inputs[type] = {
element: input,
group: input.closest('.input-group'),
error: input.closest('.input-group').querySelector('.input-error'),
hint: input.closest('.input-group').querySelector('.input-hint'),
isValid: false
};
// Add event listeners
input.addEventListener('input', (e) => this.handleInput(e, type));
input.addEventListener('focus', (e) => this.handleFocus(e, type));
input.addEventListener('blur', (e) => this.handleBlur(e, type));
input.addEventListener('keydown', (e) => this.handleKeydown(e, type));
});
// Initialize email suggestions
this.initEmailSuggestions();
// Initialize password toggle
this.initPasswordToggle();
// Form submission
this.form.addEventListener('submit', (e) => this.handleSubmit(e));
// Update initial stats
this.updateStats();
}
handleInput(event, type) {
const input = event.target;
const value = input.value;
// Apply formatter if available
if (this.formatters[type]) {
const formatted = this.formatters[type](value);
if (formatted !== value) {
input.value = formatted;
// Restore cursor position
const cursorPos = this.getCursorPosition(input, value, formatted);
input.setSelectionRange(cursorPos, cursorPos);
}
}
// Validate input
this.validateField(type, input.value);
// Update progress
this.updateProgress();
// Type-specific handlers
if (type === 'email') {
this.updateEmailSuggestions(input.value);
} else if (type === 'card') {
this.updateCardType(input.value);
} else if (type === 'password') {
this.updatePasswordStrength(input.value);
}
}
handleFocus(event, type) {
const group = this.inputs[type].group;
group.classList.add('active');
// Show suggestions for email
if (type === 'email') {
const suggestions = document.getElementById('emailSuggestions');
if (suggestions.children.length > 0) {
suggestions.classList.add('visible');
}
}
}
handleBlur(event, type) {
const group = this.inputs[type].group;
group.classList.remove('active');
// Hide suggestions
if (type === 'email') {
setTimeout(() => {
document.getElementById('emailSuggestions').classList.remove('visible');
}, 200);
}
}
handleKeydown(event, type) {
if (type === 'email' && (event.key === 'ArrowDown' || event.key === 'ArrowUp' || event.key === 'Enter')) {
this.handleSuggestionNavigation(event);
}
}
validateField(type, value) {
const validator = this.validators[type];
const input = this.inputs[type];
if (!validator) return;
const validation = validator(value);
input.isValid = validation.isValid;
input.group.classList.toggle('valid', validation.isValid);
input.group.classList.toggle('invalid', !validation.isValid && value.length > 0);
// Update error message
if (!validation.isValid && value.length > 0) {
input.error.textContent = validation.error;
input.error.classList.add('visible');
input.hint.style.opacity = '0';
} else {
input.error.classList.remove('visible');
input.hint.style.opacity = '1';
}
// Update valid fields set
if (validation.isValid) {
this.validFields.add(type);
} else {
this.validFields.delete(type);
}
// Update submit button state
this.updateSubmitButton();
this.updateStats();
}
validateEmail(email) {
if (!email) return { isValid: false, error: '' };
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const isValid = emailRegex.test(email);
return {
isValid,
error: isValid ? '' : 'Please enter a valid email address'
};
}
validatePhone(phone) {
if (!phone) return { isValid: false, error: '' };
const cleaned = phone.replace(/\D/g, '');
const isValid = cleaned.length === 10;
return {
isValid,
error: isValid ? '' : 'Please enter a 10-digit phone number'
};
}
validateCard(card) {
if (!card) return { isValid: false, error: '' };
const cleaned = card.replace(/\s/g, '');
const isValid = cleaned.length >= 13 && cleaned.length <= 19 && this.luhnCheck(cleaned);
return {
isValid,
error: isValid ? '' : 'Please enter a valid card number'
};
}
validatePassword(password) {
if (!password) return { isValid: false, error: '' };
const checks = {
length: password.length >= 8,
uppercase: /[A-Z]/.test(password),
lowercase: /[a-z]/.test(password),
number: /\d/.test(password),
special: /[!@#$%^&*(),.?":{}|<>]/.test(password)
};
const passedChecks = Object.values(checks).filter(Boolean).length;
const isValid = passedChecks >= 3 && checks.length;
let error = '';
if (!checks.length) error = 'Password must be at least 8 characters';
else if (passedChecks < 3) error = 'Password needs more complexity';
return { isValid, error, strength: passedChecks };
}
formatPhone(value) {
const cleaned = value.replace(/\D/g, '');
if (cleaned.length <= 3) return cleaned;
if (cleaned.length <= 6) return `(${cleaned.slice(0, 3)}) ${cleaned.slice(3)}`;
return `(${cleaned.slice(0, 3)}) ${cleaned.slice(3, 6)}-${cleaned.slice(6, 10)}`;
}
formatCard(value) {
const cleaned = value.replace(/\s/g, '');
const parts = [];
for (let i = 0; i < cleaned.length; i += 4) {
parts.push(cleaned.slice(i, i + 4));
}
return parts.join(' ').trim();
}
luhnCheck(value) {
let sum = 0;
let isEven = false;
for (let i = value.length - 1; i >= 0; i--) {
let digit = parseInt(value[i]);
if (isEven) {
digit *= 2;
if (digit > 9) digit -= 9;
}
sum += digit;
isEven = !isEven;
}
return sum % 10 === 0;
}
getCursorPosition(input, oldValue, newValue) {
const oldPos = input.selectionStart;
const oldLength = oldValue.length;
const newLength = newValue.length;
if (oldLength < newLength) {
// Characters were added
return oldPos + (newLength - oldLength);
}
return oldPos;
}
initEmailSuggestions() {
const emailInput = this.inputs.email.element;
const suggestionsList = document.getElementById('emailSuggestions');
this.emailDomains = ['gmail.com', 'yahoo.com', 'outlook.com', 'hotmail.com', 'icloud.com'];
this.selectedSuggestion = -1;
}
updateEmailSuggestions(value) {
const suggestionsList = document.getElementById('emailSuggestions');
suggestionsList.innerHTML = '';
this.selectedSuggestion = -1;
if (!value.includes('@') || value.endsWith('@')) {
const localPart = value.split('@')[0];
if (localPart.length > 0) {
this.emailDomains.forEach(domain => {
const suggestion = `${localPart}@${domain}`;
const li = document.createElement('li');
li.className = 'suggestion-item';
li.textContent = suggestion;
li.setAttribute('role', 'option');
li.addEventListener('click', () => {
this.inputs.email.element.value = suggestion;
this.validateField('email', suggestion);
suggestionsList.classList.remove('visible');
});
suggestionsList.appendChild(li);
});
suggestionsList.classList.add('visible');
}
} else {
suggestionsList.classList.remove('visible');
}
}
handleSuggestionNavigation(event) {
const suggestionsList = document.getElementById('emailSuggestions');
const suggestions = suggestionsList.querySelectorAll('.suggestion-item');
if (suggestions.length === 0) return;
if (event.key === 'ArrowDown') {
event.preventDefault();
this.selectedSuggestion = Math.min(this.selectedSuggestion + 1, suggestions.length - 1);
} else if (event.key === 'ArrowUp') {
event.preventDefault();
this.selectedSuggestion = Math.max(this.selectedSuggestion - 1, -1);
} else if (event.key === 'Enter' && this.selectedSuggestion >= 0) {
event.preventDefault();
suggestions[this.selectedSuggestion].click();
return;
}
suggestions.forEach((item, index) => {
item.classList.toggle('selected', index === this.selectedSuggestion);
});
}
updateCardType(value) {
const cardType = this.inputs.card.group.querySelector('.card-type');
const cleaned = value.replace(/\s/g, '');
let type = '';
if (cleaned.startsWith('4')) type = 'VISA';
else if (cleaned.startsWith('5')) type = 'MC';
else if (cleaned.startsWith('3')) type = 'AMEX';
else if (cleaned.startsWith('6')) type = 'DISC';
if (type) {
cardType.textContent = type;
cardType.classList.add('visible');
} else {
cardType.classList.remove('visible');
}
}
initPasswordToggle() {
const toggle = this.form.querySelector('.password-toggle');
const passwordInput = this.inputs.password.element;
toggle.addEventListener('click', () => {
const isPassword = passwordInput.type === 'password';
passwordInput.type = isPassword ? 'text' : 'password';
toggle.classList.toggle('visible', !isPassword);
});
}
updatePasswordStrength(value) {
const strengthContainer = document.getElementById('passwordStrength');
const strengthFill = strengthContainer.querySelector('.strength-fill');
const strengthText = strengthContainer.querySelector('.strength-text');
if (!value) {
strengthContainer.classList.remove('visible');
return;
}
const validation = this.validatePassword(value);
const strength = validation.strength || 0;
strengthContainer.classList.add('visible');
strengthFill.style.width = `${(strength / 5) * 100}%`;
const strengthLabels = ['Very Weak', 'Weak', 'Fair', 'Good', 'Strong'];
strengthText.textContent = strengthLabels[strength - 1] || 'Very Weak';
}
updateProgress() {
const totalFields = Object.keys(this.inputs).length;
const filledFields = Object.values(this.inputs).filter(input => input.element.value.length > 0).length;
const validFields = this.validFields.size;
const progress = (validFields / totalFields) * 100;
const progressFill = this.form.querySelector('.progress-fill');
const progressText = this.form.querySelector('.progress-text');
progressFill.style.width = `${progress}%`;
progressText.textContent = `${Math.round(progress)}% Complete`;
}
updateSubmitButton() {
const submitButton = this.form.querySelector('.submit-button');
const allValid = this.validFields.size === Object.keys(this.inputs).length;
submitButton.disabled = !allValid;
}
updateStats() {
document.getElementById('validFields').textContent = this.validFields.size;
document.getElementById('totalFields').textContent = Object.keys(this.inputs).length;
const quality = (this.validFields.size / Object.keys(this.inputs).length) * 100;
document.getElementById('inputQuality').textContent = `${Math.round(quality)}%`;
}
async handleSubmit(event) {
event.preventDefault();
const submitButton = this.form.querySelector('.submit-button');
submitButton.classList.add('loading');
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 2000));
// Collect form data
const formData = {};
Object.entries(this.inputs).forEach(([type, input]) => {
formData[type] = input.element.value;
});
console.log('Form submitted:', formData);
// Reset form
submitButton.classList.remove('loading');
this.form.reset();
this.validFields.clear();
// Reset all states
Object.values(this.inputs).forEach(input => {
input.group.classList.remove('valid', 'invalid');
input.error.classList.remove('visible');
input.isValid = false;
});
this.updateProgress();
this.updateStats();
// Show success message
alert('Form submitted successfully!');
}
}
// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
new InputIntelligence();
});

View File

@ -0,0 +1,446 @@
/* Digital Minimalism Theme - Input Intelligence Styles */
:root {
/* Monochromatic palette */
--color-primary: #000000;
--color-secondary: #666666;
--color-tertiary: #999999;
--color-background: #ffffff;
--color-surface: #f8f8f8;
--color-border: #e5e5e5;
--color-error: #000000;
--color-success: #000000;
--color-warning: #666666;
/* Spacing system */
--space-xs: 0.5rem;
--space-sm: 1rem;
--space-md: 1.5rem;
--space-lg: 2rem;
--space-xl: 3rem;
--space-xxl: 4rem;
/* Typography */
--font-primary: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
--font-mono: 'SF Mono', Monaco, Consolas, monospace;
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.25rem;
--font-size-xl: 2rem;
/* Animation */
--transition-base: 0.2s ease;
--transition-slow: 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-primary);
font-size: var(--font-size-base);
line-height: 1.6;
color: var(--color-primary);
background-color: var(--color-background);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.container {
width: 100%;
max-width: 480px;
padding: var(--space-lg);
}
/* Header */
.header {
text-align: center;
margin-bottom: var(--space-xxl);
}
.title {
font-size: var(--font-size-xl);
font-weight: 300;
letter-spacing: -0.02em;
margin-bottom: var(--space-xs);
}
.subtitle {
font-size: var(--font-size-sm);
color: var(--color-secondary);
font-weight: 400;
}
/* Form Layout */
.intelligent-form {
display: flex;
flex-direction: column;
gap: var(--space-xl);
}
/* Input Groups */
.input-group {
position: relative;
}
.input-label {
display: block;
font-size: var(--font-size-sm);
font-weight: 500;
margin-bottom: var(--space-xs);
color: var(--color-primary);
transition: color var(--transition-base);
}
.input-wrapper {
position: relative;
display: flex;
align-items: center;
}
/* Intelligent Input */
.intelligent-input {
width: 100%;
padding: var(--space-sm) 0;
font-size: var(--font-size-base);
font-family: inherit;
border: none;
border-bottom: 1px solid var(--color-border);
background: transparent;
color: var(--color-primary);
transition: border-color var(--transition-base);
outline: none;
}
.intelligent-input::placeholder {
color: var(--color-tertiary);
}
.intelligent-input:focus {
border-bottom-color: var(--color-primary);
}
.intelligent-input.valid {
border-bottom-color: var(--color-primary);
}
.intelligent-input.invalid {
border-bottom-color: var(--color-primary);
}
/* Input Progress */
.input-progress {
position: absolute;
bottom: 0;
left: 0;
height: 1px;
background-color: var(--color-primary);
transition: width var(--transition-slow);
width: 0;
}
.input-group.active .input-progress {
width: 100%;
}
/* Input Indicator */
.input-indicator {
position: absolute;
right: 0;
width: 8px;
height: 8px;
border-radius: 50%;
background-color: var(--color-border);
transition: all var(--transition-base);
opacity: 0;
}
.input-group.valid .input-indicator {
background-color: var(--color-primary);
opacity: 1;
}
.input-group.invalid .input-indicator {
background-color: var(--color-primary);
opacity: 1;
animation: pulse 1s infinite;
}
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.2); opacity: 0.5; }
}
/* Hints and Errors */
.input-hint {
font-size: var(--font-size-sm);
color: var(--color-tertiary);
margin-top: var(--space-xs);
transition: opacity var(--transition-base);
}
.input-error {
font-size: var(--font-size-sm);
color: var(--color-error);
margin-top: var(--space-xs);
opacity: 0;
transform: translateY(-4px);
transition: all var(--transition-base);
position: absolute;
}
.input-error.visible {
opacity: 1;
transform: translateY(0);
position: static;
}
/* Suggestions */
.suggestions-list {
position: absolute;
top: calc(100% + var(--space-xs));
left: 0;
right: 0;
background: var(--color-background);
border: 1px solid var(--color-border);
border-radius: 4px;
list-style: none;
opacity: 0;
visibility: hidden;
transform: translateY(-8px);
transition: all var(--transition-base);
z-index: 10;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
.suggestions-list.visible {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.suggestion-item {
padding: var(--space-sm);
cursor: pointer;
transition: background-color var(--transition-base);
font-size: var(--font-size-sm);
}
.suggestion-item:hover,
.suggestion-item.selected {
background-color: var(--color-surface);
}
/* Card Type Indicator */
.card-type {
position: absolute;
right: 0;
font-size: var(--font-size-sm);
font-family: var(--font-mono);
color: var(--color-secondary);
opacity: 0;
transition: opacity var(--transition-base);
}
.card-type.visible {
opacity: 1;
}
/* Password Toggle */
.password-toggle {
position: absolute;
right: 0;
background: none;
border: none;
cursor: pointer;
padding: var(--space-xs);
color: var(--color-secondary);
transition: color var(--transition-base);
}
.password-toggle:hover {
color: var(--color-primary);
}
.password-toggle-icon::before {
content: '👁';
font-size: var(--font-size-lg);
filter: grayscale(1);
}
.password-toggle.visible .password-toggle-icon::before {
content: '👁‍🗨';
}
/* Password Strength */
.password-strength {
margin-top: var(--space-sm);
display: flex;
align-items: center;
gap: var(--space-sm);
opacity: 0;
transition: opacity var(--transition-base);
}
.password-strength.visible {
opacity: 1;
}
.strength-meter {
flex: 1;
height: 2px;
background-color: var(--color-border);
border-radius: 1px;
overflow: hidden;
}
.strength-fill {
height: 100%;
background-color: var(--color-primary);
transition: width var(--transition-slow);
width: 0;
}
.strength-text {
font-size: var(--font-size-sm);
color: var(--color-secondary);
min-width: 80px;
text-align: right;
}
/* Form Progress */
.form-progress {
margin-top: var(--space-lg);
padding-top: var(--space-lg);
border-top: 1px solid var(--color-border);
}
.progress-bar {
height: 2px;
background-color: var(--color-surface);
border-radius: 1px;
overflow: hidden;
margin-bottom: var(--space-sm);
}
.progress-fill {
height: 100%;
background-color: var(--color-primary);
transition: width var(--transition-slow);
}
.progress-text {
font-size: var(--font-size-sm);
color: var(--color-secondary);
text-align: center;
}
/* Submit Button */
.submit-button {
margin-top: var(--space-lg);
padding: var(--space-md) var(--space-lg);
font-size: var(--font-size-base);
font-weight: 500;
font-family: inherit;
background-color: var(--color-primary);
color: var(--color-background);
border: none;
border-radius: 2px;
cursor: pointer;
transition: all var(--transition-base);
position: relative;
overflow: hidden;
}
.submit-button:disabled {
background-color: var(--color-surface);
color: var(--color-tertiary);
cursor: not-allowed;
}
.submit-button:not(:disabled):hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.submit-button:not(:disabled):active {
transform: translateY(0);
box-shadow: none;
}
.button-loader {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 20px;
height: 20px;
border: 2px solid var(--color-background);
border-top-color: transparent;
border-radius: 50%;
animation: spin 0.8s linear infinite;
opacity: 0;
}
.submit-button.loading .button-text {
opacity: 0;
}
.submit-button.loading .button-loader {
opacity: 1;
}
@keyframes spin {
to { transform: translate(-50%, -50%) rotate(360deg); }
}
/* Footer Stats */
.footer {
margin-top: var(--space-xxl);
padding-top: var(--space-lg);
border-top: 1px solid var(--color-border);
}
.stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--space-lg);
text-align: center;
}
.stat-item {
display: flex;
flex-direction: column;
gap: var(--space-xs);
}
.stat-value {
font-size: var(--font-size-lg);
font-weight: 300;
font-variant-numeric: tabular-nums;
}
.stat-label {
font-size: var(--font-size-sm);
color: var(--color-secondary);
}
/* Responsive */
@media (max-width: 640px) {
.container {
padding: var(--space-md);
}
.title {
font-size: 1.5rem;
}
.intelligent-form {
gap: var(--space-lg);
}
}

View File

@ -0,0 +1,143 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DATACOM-3000 // Retro Data Explorer</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="terminal-container">
<header class="terminal-header">
<div class="terminal-title">
<span class="terminal-icon">◼◼◼</span>
<h1>DATACOM-3000 SYSTEM v2.31</h1>
</div>
<div class="terminal-controls">
<button class="terminal-btn minimize" aria-label="Minimize">_</button>
<button class="terminal-btn maximize" aria-label="Maximize"></button>
<button class="terminal-btn close" aria-label="Close">X</button>
</div>
</header>
<nav class="command-bar">
<div class="command-prompt">
<span class="prompt-symbol">C:\DATA&gt;</span>
<input type="text" class="command-input" placeholder="Enter command..." aria-label="Command input">
</div>
<div class="status-indicators">
<span class="indicator" data-status="active">SYS</span>
<span class="indicator" data-status="active">NET</span>
<span class="indicator" data-status="idle">DSK</span>
</div>
</nav>
<main class="data-explorer">
<section class="controls-panel">
<div class="filter-group">
<label for="search-filter" class="control-label">&gt; SEARCH:</label>
<input type="text" id="search-filter" class="filter-input" placeholder="*.*">
</div>
<div class="sort-group">
<label class="control-label">&gt; SORT BY:</label>
<select id="sort-selector" class="sort-select">
<option value="id">ID</option>
<option value="name">NAME</option>
<option value="size">SIZE</option>
<option value="date">DATE</option>
</select>
<button class="sort-direction" aria-label="Toggle sort direction"></button>
</div>
<div class="action-group">
<button class="action-btn" data-action="import">
<span class="btn-icon">[↓]</span> IMPORT
</button>
<button class="action-btn" data-action="export">
<span class="btn-icon">[↑]</span> EXPORT
</button>
<button class="action-btn" data-action="refresh">
<span class="btn-icon">[↻]</span> REFRESH
</button>
</div>
</section>
<section class="data-viewport">
<div class="data-table-wrapper">
<table class="data-table" role="grid">
<thead>
<tr role="row">
<th role="columnheader" class="column-header" data-column="id">
<span class="header-text">ID</span>
<span class="resize-handle" aria-hidden="true"></span>
</th>
<th role="columnheader" class="column-header" data-column="name">
<span class="header-text">NAME</span>
<span class="resize-handle" aria-hidden="true"></span>
</th>
<th role="columnheader" class="column-header" data-column="type">
<span class="header-text">TYPE</span>
<span class="resize-handle" aria-hidden="true"></span>
</th>
<th role="columnheader" class="column-header" data-column="size">
<span class="header-text">SIZE</span>
<span class="resize-handle" aria-hidden="true"></span>
</th>
<th role="columnheader" class="column-header" data-column="modified">
<span class="header-text">MODIFIED</span>
<span class="resize-handle" aria-hidden="true"></span>
</th>
<th role="columnheader" class="column-header" data-column="preview">
<span class="header-text">PREVIEW</span>
</th>
</tr>
</thead>
<tbody id="data-tbody" role="rowgroup">
<!-- Data rows will be dynamically inserted here -->
</tbody>
</table>
</div>
<aside class="data-preview">
<div class="preview-header">
<h3>&gt; DATA PREVIEW</h3>
<button class="preview-close" aria-label="Close preview">X</button>
</div>
<div class="preview-content" id="preview-content">
<pre class="ascii-art">
╔═══════════════════╗
║ NO DATA SELECTED ║
║ ║
║ SELECT A ROW TO ║
║ VIEW PREVIEW ║
╚═══════════════════╝
</pre>
</div>
</aside>
</section>
<footer class="status-bar">
<div class="memory-usage">
<span class="label">MEM:</span>
<div class="memory-bar">
<div class="memory-used"></div>
</div>
<span class="memory-text">640K/1024K</span>
</div>
<div class="record-count">
<span class="label">RECORDS:</span>
<span id="record-count">0</span>
</div>
<div class="system-time">
<span id="system-time">00:00:00</span>
</div>
</footer>
</main>
<div class="scanlines" aria-hidden="true"></div>
</div>
<script src="script.js"></script>
</body>
</html>

View File

@ -0,0 +1,481 @@
// DATACOM-3000 Data Explorer JavaScript Module
class DataExplorer {
constructor() {
this.data = [];
this.filteredData = [];
this.sortColumn = 'id';
this.sortDirection = 'asc';
this.selectedRow = null;
this.columns = ['id', 'name', 'type', 'size', 'modified'];
this.init();
}
init() {
this.bindEvents();
this.generateMockData();
this.updateDisplay();
this.startSystemClock();
this.simulateMemoryUsage();
}
bindEvents() {
// Search filter
const searchInput = document.getElementById('search-filter');
searchInput.addEventListener('input', (e) => this.handleSearch(e.target.value));
// Sort controls
const sortSelector = document.getElementById('sort-selector');
sortSelector.addEventListener('change', (e) => this.handleSort(e.target.value));
const sortDirection = document.querySelector('.sort-direction');
sortDirection.addEventListener('click', () => this.toggleSortDirection());
// Action buttons
document.querySelectorAll('.action-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const action = e.currentTarget.dataset.action;
this.handleAction(action);
});
});
// Column headers for sorting
document.querySelectorAll('.column-header').forEach(header => {
header.addEventListener('click', (e) => {
if (!e.target.classList.contains('resize-handle')) {
const column = header.dataset.column;
this.handleSort(column);
}
});
});
// Column resizing
this.initColumnResizing();
// Preview close button
const previewClose = document.querySelector('.preview-close');
previewClose.addEventListener('click', () => this.closePreview());
// Command input
const commandInput = document.querySelector('.command-input');
commandInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
this.executeCommand(e.target.value);
e.target.value = '';
}
});
}
generateMockData() {
const fileTypes = ['DAT', 'TXT', 'BIN', 'LOG', 'CFG', 'SYS', 'EXE', 'BAT'];
const names = [
'SYSTEM', 'CONFIG', 'DATA', 'BACKUP', 'ARCHIVE', 'TEMP',
'CACHE', 'INDEX', 'DATABASE', 'REPORT', 'LOG', 'USER'
];
for (let i = 1; i <= 50; i++) {
const type = fileTypes[Math.floor(Math.random() * fileTypes.length)];
const name = names[Math.floor(Math.random() * names.length)];
const size = Math.floor(Math.random() * 999999) + 1;
const date = new Date(Date.now() - Math.floor(Math.random() * 31536000000));
this.data.push({
id: String(i).padStart(4, '0'),
name: `${name}_${String(i).padStart(2, '0')}.${type}`,
type: type,
size: this.formatSize(size),
sizeBytes: size,
modified: this.formatDate(date),
modifiedTimestamp: date.getTime(),
rawData: this.generatePreviewData(type)
});
}
this.filteredData = [...this.data];
}
generatePreviewData(type) {
const previews = {
'DAT': '00000000 48 45 58 20 44 55 4D 50 20 44 41 54 41 20 46 49 |HEX DUMP DATA FI|\n00000010 4C 45 20 46 4F 52 20 50 52 45 56 49 45 57 20 4F |LE FOR PREVIEW O|\n00000020 4E 4C 59 20 2D 20 4E 4F 54 20 41 43 54 55 41 4C |NLY - NOT ACTUAL|',
'TXT': 'PLAINTEXT DOCUMENT\n==================\nLorem ipsum dolor sit amet, consectetur adipiscing elit.\nSed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n\n[END OF FILE]',
'BIN': '01001000 01000101 01001100 01001100 01001111\n01010111 01001111 01010010 01001100 01000100\n00100001 00100000 00110010 00110000 00110010\n00110100 00100000 01000010 01001001 01001110',
'LOG': '[1990-01-01 00:00:00] SYSTEM INITIALIZED\n[1990-01-01 00:00:01] MEMORY CHECK... OK\n[1990-01-01 00:00:02] DISK CHECK... OK\n[1990-01-01 00:00:03] NETWORK CHECK... OK\n[1990-01-01 00:00:04] READY FOR OPERATION',
'CFG': 'SYSTEM.MEMORY=640K\nSYSTEM.DISK=40MB\nSYSTEM.CPU=80486\nSYSTEM.SPEED=33MHZ\nSYSTEM.CACHE=ENABLED\nSYSTEM.SOUND=BEEPER',
'SYS': 'SYSTEM FILE - PROTECTED\n\nACCESS DENIED\n\nAUTHORIZATION REQUIRED',
'EXE': 'EXECUTABLE FILE HEADER\n=====================\nMAGIC NUMBER: 4D5A\nBYTES ON LAST PAGE: 0090\nPAGES IN FILE: 0003\nRELOCATIONS: 0000\nHEADER SIZE: 0004',
'BAT': '@ECHO OFF\nCLS\nECHO BATCH FILE PREVIEW\nECHO ==================\nPAUSE\nEXIT'
};
return previews[type] || 'UNKNOWN FILE TYPE';
}
formatSize(bytes) {
if (bytes < 1024) return `${bytes}B`;
if (bytes < 1048576) return `${Math.floor(bytes / 1024)}K`;
return `${(bytes / 1048576).toFixed(1)}M`;
}
formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
}
handleSearch(query) {
if (!query) {
this.filteredData = [...this.data];
} else {
const searchTerm = query.toLowerCase();
this.filteredData = this.data.filter(item =>
item.name.toLowerCase().includes(searchTerm) ||
item.type.toLowerCase().includes(searchTerm) ||
item.id.includes(searchTerm)
);
}
this.updateDisplay();
}
handleSort(column) {
if (this.sortColumn === column) {
this.toggleSortDirection();
} else {
this.sortColumn = column;
this.sortDirection = 'asc';
document.querySelector('.sort-direction').textContent = '▲';
}
this.sortData();
this.updateDisplay();
}
toggleSortDirection() {
this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
const directionBtn = document.querySelector('.sort-direction');
directionBtn.textContent = this.sortDirection === 'asc' ? '▲' : '▼';
this.sortData();
this.updateDisplay();
}
sortData() {
this.filteredData.sort((a, b) => {
let valueA, valueB;
switch (this.sortColumn) {
case 'size':
valueA = a.sizeBytes;
valueB = b.sizeBytes;
break;
case 'date':
case 'modified':
valueA = a.modifiedTimestamp;
valueB = b.modifiedTimestamp;
break;
default:
valueA = a[this.sortColumn];
valueB = b[this.sortColumn];
}
if (valueA < valueB) return this.sortDirection === 'asc' ? -1 : 1;
if (valueA > valueB) return this.sortDirection === 'asc' ? 1 : -1;
return 0;
});
}
handleAction(action) {
switch (action) {
case 'import':
this.showMessage('IMPORT FUNCTION NOT IMPLEMENTED');
this.simulateImport();
break;
case 'export':
this.exportData();
break;
case 'refresh':
this.refreshData();
break;
}
}
simulateImport() {
// Simulate file import
setTimeout(() => {
const newItem = {
id: String(this.data.length + 1).padStart(4, '0'),
name: `IMPORT_${Date.now()}.DAT`,
type: 'DAT',
size: this.formatSize(Math.floor(Math.random() * 99999) + 1000),
sizeBytes: Math.floor(Math.random() * 99999) + 1000,
modified: this.formatDate(new Date()),
modifiedTimestamp: Date.now(),
rawData: 'IMPORTED DATA FILE\n================\n[DATA IMPORTED SUCCESSFULLY]'
};
this.data.push(newItem);
this.filteredData = [...this.data];
this.updateDisplay();
this.showMessage('IMPORT COMPLETE: 1 FILE ADDED');
}, 1000);
}
exportData() {
const exportText = this.filteredData.map(item =>
`${item.id},${item.name},${item.type},${item.size},${item.modified}`
).join('\n');
const header = 'ID,NAME,TYPE,SIZE,MODIFIED\n';
const fullExport = header + exportText;
// Create download
const blob = new Blob([fullExport], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `DATACOM_EXPORT_${Date.now()}.CSV`;
a.click();
URL.revokeObjectURL(url);
this.showMessage(`EXPORT COMPLETE: ${this.filteredData.length} RECORDS`);
}
refreshData() {
// Simulate refresh animation
const tbody = document.getElementById('data-tbody');
tbody.style.opacity = '0.5';
setTimeout(() => {
tbody.style.opacity = '1';
this.updateDisplay();
this.showMessage('DATA REFRESHED');
}, 500);
}
updateDisplay() {
const tbody = document.getElementById('data-tbody');
tbody.innerHTML = '';
this.filteredData.forEach((item, index) => {
const row = document.createElement('tr');
row.setAttribute('role', 'row');
row.dataset.index = index;
row.innerHTML = `
<td role="gridcell">${item.id}</td>
<td role="gridcell">${item.name}</td>
<td role="gridcell">${item.type}</td>
<td role="gridcell">${item.size}</td>
<td role="gridcell">${item.modified}</td>
<td role="gridcell">
<button class="preview-btn" data-index="${index}"></button>
</td>
`;
row.addEventListener('click', (e) => {
if (!e.target.classList.contains('preview-btn')) {
this.selectRow(row, item);
}
});
tbody.appendChild(row);
});
// Bind preview buttons
document.querySelectorAll('.preview-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const index = parseInt(btn.dataset.index);
this.showPreview(this.filteredData[index]);
});
});
// Update record count
document.getElementById('record-count').textContent = this.filteredData.length;
}
selectRow(row, item) {
// Remove previous selection
document.querySelectorAll('.data-table tbody tr').forEach(tr => {
tr.classList.remove('selected');
});
// Add selection to current row
row.classList.add('selected');
this.selectedRow = item;
}
showPreview(item) {
const previewContent = document.getElementById('preview-content');
previewContent.innerHTML = `
<div class="preview-info">
<p><span class="label">FILE:</span> ${item.name}</p>
<p><span class="label">TYPE:</span> ${item.type}</p>
<p><span class="label">SIZE:</span> ${item.size}</p>
<p><span class="label">DATE:</span> ${item.modified}</p>
</div>
<div class="preview-separator"></div>
<pre class="preview-data">${item.rawData}</pre>
`;
// Show preview panel on mobile
const preview = document.querySelector('.data-preview');
preview.classList.add('active');
}
closePreview() {
const preview = document.querySelector('.data-preview');
preview.classList.remove('active');
}
executeCommand(command) {
const cmd = command.toUpperCase().trim();
switch (cmd) {
case 'CLS':
case 'CLEAR':
this.filteredData = [];
this.updateDisplay();
this.showMessage('SCREEN CLEARED');
break;
case 'DIR':
case 'LS':
this.showMessage(`${this.data.length} FILES IN DIRECTORY`);
break;
case 'HELP':
this.showMessage('COMMANDS: CLS, DIR, SORT, FILTER, EXIT');
break;
case 'EXIT':
this.showMessage('CANNOT EXIT - SYSTEM LOCKED');
break;
default:
if (cmd.startsWith('FILTER ')) {
const query = cmd.substring(7);
document.getElementById('search-filter').value = query;
this.handleSearch(query);
} else if (cmd.startsWith('SORT ')) {
const column = cmd.substring(5).toLowerCase();
if (this.columns.includes(column)) {
this.handleSort(column);
}
} else {
this.showMessage('UNKNOWN COMMAND - TYPE HELP');
}
}
}
showMessage(message) {
const commandInput = document.querySelector('.command-input');
const originalPlaceholder = commandInput.placeholder;
commandInput.placeholder = message;
setTimeout(() => {
commandInput.placeholder = originalPlaceholder;
}, 3000);
}
initColumnResizing() {
let isResizing = false;
let currentColumn = null;
let startX = 0;
let startWidth = 0;
document.querySelectorAll('.resize-handle').forEach(handle => {
handle.addEventListener('mousedown', (e) => {
isResizing = true;
currentColumn = handle.parentElement;
startX = e.pageX;
startWidth = currentColumn.offsetWidth;
document.body.style.cursor = 'col-resize';
e.preventDefault();
});
});
document.addEventListener('mousemove', (e) => {
if (!isResizing) return;
const width = startWidth + (e.pageX - startX);
if (width > 50) {
currentColumn.style.width = `${width}px`;
}
});
document.addEventListener('mouseup', () => {
isResizing = false;
currentColumn = null;
document.body.style.cursor = 'default';
});
}
startSystemClock() {
const updateClock = () => {
const now = new Date();
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
document.getElementById('system-time').textContent = `${hours}:${minutes}:${seconds}`;
};
updateClock();
setInterval(updateClock, 1000);
}
simulateMemoryUsage() {
const memoryBar = document.querySelector('.memory-used');
const memoryText = document.querySelector('.memory-text');
setInterval(() => {
const used = 640 + Math.floor(Math.random() * 100);
const percentage = (used / 1024) * 100;
memoryBar.style.width = `${percentage}%`;
memoryText.textContent = `${used}K/1024K`;
if (percentage > 90) {
memoryBar.style.backgroundColor = 'var(--error-red)';
} else if (percentage > 75) {
memoryBar.style.backgroundColor = 'var(--warning-yellow)';
} else {
memoryBar.style.backgroundColor = 'var(--terminal-text)';
}
}, 2000);
}
}
// Initialize on DOM ready
document.addEventListener('DOMContentLoaded', () => {
window.dataExplorer = new DataExplorer();
// Add keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.ctrlKey || e.metaKey) {
switch (e.key) {
case 'f':
e.preventDefault();
document.getElementById('search-filter').focus();
break;
case 'e':
e.preventDefault();
window.dataExplorer.handleAction('export');
break;
case 'r':
e.preventDefault();
window.dataExplorer.handleAction('refresh');
break;
}
}
});
});
// Add some retro console messages
console.log('%c╔════════════════════════════╗', 'color: #00ff00');
console.log('%c║ DATACOM-3000 SYSTEM v2.31 ║', 'color: #00ff00');
console.log('%c║ (C) 1990 RETROTECH CORP ║', 'color: #00ff00');
console.log('%c╚════════════════════════════╝', 'color: #00ff00');
console.log('%cSYSTEM INITIALIZED', 'color: #ffb000');
console.log('%cMEMORY: 640K OK', 'color: #00ff00');
console.log('%cREADY.', 'color: #00ff00');

View File

@ -0,0 +1,518 @@
/* DATACOM-3000 Retro Computing Theme */
:root {
/* Terminal Colors */
--terminal-green: #00ff00;
--terminal-amber: #ffb000;
--terminal-bg: #0a0a0a;
--terminal-bg-light: #1a1a1a;
--terminal-border: #333333;
--terminal-glow: rgba(0, 255, 0, 0.5);
--terminal-text: #00ff00;
--terminal-dim: #00aa00;
/* System Colors */
--error-red: #ff0040;
--warning-yellow: #ffff00;
--info-cyan: #00ffff;
/* Typography */
--font-mono: 'Courier New', Courier, monospace;
--font-size-base: 14px;
--line-height: 1.4;
/* Spacing */
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
/* Effects */
--scanline-opacity: 0.05;
--crt-curve: 0.02;
}
/* Global Reset and Base Styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-mono);
font-size: var(--font-size-base);
line-height: var(--line-height);
background-color: #000;
color: var(--terminal-text);
overflow: hidden;
text-transform: uppercase;
}
/* CRT Effect Container */
.terminal-container {
position: relative;
width: 100vw;
height: 100vh;
background-color: var(--terminal-bg);
border: 2px solid var(--terminal-border);
overflow: hidden;
box-shadow:
inset 0 0 20px rgba(0, 255, 0, 0.1),
0 0 40px var(--terminal-glow);
}
/* Terminal Header */
.terminal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--spacing-sm) var(--spacing-md);
background-color: var(--terminal-bg-light);
border-bottom: 2px solid var(--terminal-border);
}
.terminal-title {
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.terminal-icon {
color: var(--terminal-amber);
animation: blink 2s infinite;
}
.terminal-title h1 {
font-size: 16px;
font-weight: normal;
letter-spacing: 2px;
}
.terminal-controls {
display: flex;
gap: var(--spacing-xs);
}
.terminal-btn {
width: 20px;
height: 20px;
background-color: transparent;
border: 1px solid var(--terminal-text);
color: var(--terminal-text);
cursor: pointer;
font-family: inherit;
font-size: 12px;
transition: all 0.2s;
}
.terminal-btn:hover {
background-color: var(--terminal-text);
color: var(--terminal-bg);
box-shadow: 0 0 10px var(--terminal-glow);
}
/* Command Bar */
.command-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--spacing-sm) var(--spacing-md);
background-color: var(--terminal-bg-light);
border-bottom: 1px solid var(--terminal-border);
}
.command-prompt {
display: flex;
align-items: center;
flex: 1;
}
.prompt-symbol {
color: var(--terminal-amber);
margin-right: var(--spacing-sm);
}
.command-input {
flex: 1;
background-color: transparent;
border: none;
color: var(--terminal-text);
font-family: inherit;
font-size: inherit;
outline: none;
}
.command-input::placeholder {
color: var(--terminal-dim);
}
.status-indicators {
display: flex;
gap: var(--spacing-md);
}
.indicator {
padding: 2px 8px;
border: 1px solid var(--terminal-text);
font-size: 12px;
}
.indicator[data-status="active"] {
background-color: var(--terminal-text);
color: var(--terminal-bg);
animation: pulse 2s infinite;
}
.indicator[data-status="idle"] {
color: var(--terminal-dim);
border-color: var(--terminal-dim);
}
/* Data Explorer Main Area */
.data-explorer {
display: flex;
flex-direction: column;
height: calc(100vh - 120px);
}
/* Controls Panel */
.controls-panel {
display: flex;
gap: var(--spacing-lg);
padding: var(--spacing-md);
background-color: var(--terminal-bg-light);
border-bottom: 1px solid var(--terminal-border);
}
.filter-group,
.sort-group,
.action-group {
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.control-label {
color: var(--terminal-amber);
font-size: 12px;
}
.filter-input,
.sort-select {
background-color: var(--terminal-bg);
border: 1px solid var(--terminal-border);
color: var(--terminal-text);
padding: var(--spacing-xs) var(--spacing-sm);
font-family: inherit;
font-size: inherit;
outline: none;
}
.filter-input:focus,
.sort-select:focus {
border-color: var(--terminal-text);
box-shadow: 0 0 5px var(--terminal-glow);
}
.sort-direction {
background-color: transparent;
border: 1px solid var(--terminal-border);
color: var(--terminal-text);
padding: var(--spacing-xs);
cursor: pointer;
font-family: inherit;
}
.action-btn {
background-color: var(--terminal-bg);
border: 1px solid var(--terminal-border);
color: var(--terminal-text);
padding: var(--spacing-xs) var(--spacing-md);
cursor: pointer;
font-family: inherit;
font-size: 12px;
display: flex;
align-items: center;
gap: var(--spacing-xs);
transition: all 0.2s;
}
.action-btn:hover {
border-color: var(--terminal-text);
box-shadow: 0 0 10px var(--terminal-glow);
transform: translateY(-1px);
}
.btn-icon {
color: var(--terminal-amber);
}
/* Data Viewport */
.data-viewport {
flex: 1;
display: flex;
overflow: hidden;
}
/* Data Table */
.data-table-wrapper {
flex: 1;
overflow: auto;
background-color: var(--terminal-bg);
}
.data-table {
width: 100%;
border-collapse: collapse;
}
.column-header {
position: relative;
padding: var(--spacing-sm) var(--spacing-md);
background-color: var(--terminal-bg-light);
border: 1px solid var(--terminal-border);
color: var(--terminal-amber);
text-align: left;
cursor: pointer;
user-select: none;
}
.column-header:hover {
background-color: #2a2a2a;
}
.header-text {
display: inline-block;
}
.resize-handle {
position: absolute;
right: 0;
top: 0;
width: 4px;
height: 100%;
cursor: col-resize;
background-color: transparent;
}
.resize-handle:hover {
background-color: var(--terminal-text);
}
.data-table tbody tr {
border-bottom: 1px solid var(--terminal-border);
transition: background-color 0.2s;
}
.data-table tbody tr:hover {
background-color: var(--terminal-bg-light);
}
.data-table tbody tr.selected {
background-color: rgba(0, 255, 0, 0.1);
box-shadow: inset 0 0 20px var(--terminal-glow);
}
.data-table td {
padding: var(--spacing-sm) var(--spacing-md);
color: var(--terminal-text);
font-size: 13px;
}
/* Data Preview */
.data-preview {
width: 300px;
background-color: var(--terminal-bg-light);
border-left: 2px solid var(--terminal-border);
display: flex;
flex-direction: column;
}
.preview-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--spacing-sm) var(--spacing-md);
border-bottom: 1px solid var(--terminal-border);
}
.preview-header h3 {
font-size: 14px;
font-weight: normal;
color: var(--terminal-amber);
}
.preview-close {
background-color: transparent;
border: 1px solid var(--terminal-text);
color: var(--terminal-text);
padding: 2px 6px;
cursor: pointer;
font-family: inherit;
font-size: 12px;
}
.preview-content {
flex: 1;
padding: var(--spacing-md);
overflow: auto;
}
.ascii-art {
color: var(--terminal-dim);
font-size: 12px;
line-height: 1.2;
}
/* Status Bar */
.status-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--spacing-sm) var(--spacing-md);
background-color: var(--terminal-bg-light);
border-top: 2px solid var(--terminal-border);
}
.memory-usage {
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.memory-bar {
width: 100px;
height: 10px;
background-color: var(--terminal-bg);
border: 1px solid var(--terminal-border);
position: relative;
overflow: hidden;
}
.memory-used {
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 62.5%;
background-color: var(--terminal-text);
animation: memory-pulse 3s infinite;
}
.label {
color: var(--terminal-amber);
font-size: 12px;
}
.memory-text,
#record-count,
#system-time {
font-size: 12px;
}
/* CRT Scanlines Effect */
.scanlines {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
background: linear-gradient(
transparent 50%,
rgba(0, 255, 0, var(--scanline-opacity)) 50%
);
background-size: 100% 4px;
animation: scanlines 8s linear infinite;
}
/* Animations */
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; }
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.6; }
}
@keyframes memory-pulse {
0%, 100% { opacity: 0.8; }
50% { opacity: 1; }
}
@keyframes scanlines {
0% { transform: translateY(0); }
100% { transform: translateY(4px); }
}
/* Scrollbar Styling */
::-webkit-scrollbar {
width: 12px;
height: 12px;
}
::-webkit-scrollbar-track {
background-color: var(--terminal-bg);
border: 1px solid var(--terminal-border);
}
::-webkit-scrollbar-thumb {
background-color: var(--terminal-dim);
border: 1px solid var(--terminal-border);
}
::-webkit-scrollbar-thumb:hover {
background-color: var(--terminal-text);
}
/* Responsive Design */
@media (max-width: 1024px) {
.data-preview {
position: absolute;
right: 0;
top: 0;
height: 100%;
transform: translateX(100%);
transition: transform 0.3s;
}
.data-preview.active {
transform: translateX(0);
}
}
@media (max-width: 768px) {
.controls-panel {
flex-wrap: wrap;
}
.action-group {
width: 100%;
justify-content: space-between;
}
}
/* Accessibility Focus Styles */
*:focus {
outline: 2px solid var(--terminal-amber);
outline-offset: 2px;
}
/* Print Styles */
@media print {
.terminal-container {
box-shadow: none;
border: 1px solid #000;
}
.scanlines {
display: none;
}
* {
color: #000 !important;
background-color: #fff !important;
}
}

View File

@ -0,0 +1,145 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Neon Wave Player - Cyberpunk Media Interface</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<main class="media-player" role="application" aria-label="Neon Wave Media Player">
<div class="player-container">
<!-- Visualizer Section -->
<section class="visualizer-section" aria-label="Audio Visualizer">
<canvas id="waveform-visualizer" class="waveform-canvas" width="800" height="200"></canvas>
<div class="glitch-overlay" aria-hidden="true"></div>
</section>
<!-- Media Display -->
<section class="media-display" aria-label="Now Playing">
<div class="album-art-container">
<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Crect width='300' height='300' fill='%23111'/%3E%3Ctext x='150' y='150' text-anchor='middle' fill='%23ff00ff' font-size='24'%3ENO MEDIA%3C/text%3E%3C/svg%3E"
alt="Album artwork"
class="album-art"
id="album-art">
<div class="hologram-effect" aria-hidden="true"></div>
</div>
<div class="track-info">
<h1 id="track-title" class="track-title">Select a Track</h1>
<p id="track-artist" class="track-artist">No Artist</p>
<div class="track-metadata">
<span id="track-genre" class="metadata-tag">Genre</span>
<span id="track-year" class="metadata-tag">Year</span>
<span id="track-bpm" class="metadata-tag">BPM</span>
</div>
</div>
</section>
<!-- Progress Bar -->
<section class="progress-section" aria-label="Playback Progress">
<div class="time-display">
<time id="current-time" class="time-current">00:00</time>
<div class="progress-container">
<div class="progress-bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
<div class="progress-fill" id="progress-fill"></div>
<div class="progress-glow"></div>
</div>
<input type="range"
id="seek-slider"
class="seek-slider"
min="0"
max="100"
value="0"
aria-label="Seek position">
</div>
<time id="duration-time" class="time-duration">00:00</time>
</div>
</section>
<!-- Control Panel -->
<section class="control-panel" aria-label="Playback Controls">
<div class="main-controls">
<button class="control-btn" id="shuffle-btn" aria-label="Shuffle" data-active="false">
<svg viewBox="0 0 24 24" class="control-icon">
<path d="M3 17h2.5L12 7h-2.5l-6.5 10zm6.5-10L3 17h2.5l6.5-10H9.5zm5 0L21 17h-2.5L12 7h2.5zm-2.5 10h2.5l6.5-10H19l-6.5 10z"/>
</svg>
</button>
<button class="control-btn" id="prev-btn" aria-label="Previous track">
<svg viewBox="0 0 24 24" class="control-icon">
<path d="M6 6h2v12H6zm3.5 6l8.5 6V6z"/>
</svg>
</button>
<button class="control-btn control-btn-primary" id="play-pause-btn" aria-label="Play/Pause">
<svg viewBox="0 0 24 24" class="control-icon" id="play-icon">
<path d="M8 5v14l11-7z"/>
</svg>
<svg viewBox="0 0 24 24" class="control-icon hidden" id="pause-icon">
<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>
</svg>
</button>
<button class="control-btn" id="next-btn" aria-label="Next track">
<svg viewBox="0 0 24 24" class="control-icon">
<path d="M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z"/>
</svg>
</button>
<button class="control-btn" id="repeat-btn" aria-label="Repeat" data-mode="off">
<svg viewBox="0 0 24 24" class="control-icon">
<path d="M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4z"/>
</svg>
</button>
</div>
</section>
<!-- Volume and EQ Section -->
<section class="audio-controls" aria-label="Audio Controls">
<div class="volume-control">
<button class="control-btn" id="volume-btn" aria-label="Volume">
<svg viewBox="0 0 24 24" class="control-icon">
<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.02z"/>
</svg>
</button>
<div class="volume-slider-container">
<input type="range"
id="volume-slider"
class="volume-slider"
min="0"
max="100"
value="70"
aria-label="Volume level">
<div class="volume-level" id="volume-level">70%</div>
</div>
</div>
<div class="equalizer">
<button class="eq-preset" data-preset="flat">FLAT</button>
<button class="eq-preset" data-preset="bass">BASS</button>
<button class="eq-preset" data-preset="vocal">VOCAL</button>
<button class="eq-preset active" data-preset="cyber">CYBER</button>
</div>
</section>
<!-- Playlist Section -->
<section class="playlist-section" aria-label="Playlist">
<header class="playlist-header">
<h2 class="playlist-title">Neural Tracks</h2>
<button class="playlist-action" id="add-track-btn" aria-label="Add track">
<svg viewBox="0 0 24 24" class="action-icon">
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</svg>
</button>
</header>
<div class="playlist-container" id="playlist-container">
<ul class="playlist" id="playlist" role="list" aria-label="Track list">
<!-- Playlist items will be dynamically added -->
</ul>
</div>
</section>
</div>
<!-- Audio Element -->
<audio id="audio-player" class="hidden"></audio>
</main>
<script src="script.js"></script>
</body>
</html>

View File

@ -0,0 +1,448 @@
// Neon Wave Player - JavaScript Module
class NeonWavePlayer {
constructor() {
// Audio elements
this.audio = document.getElementById('audio-player');
this.canvas = document.getElementById('waveform-visualizer');
this.ctx = this.canvas.getContext('2d');
// Audio context for visualizer
this.audioContext = null;
this.analyser = null;
this.source = null;
this.dataArray = null;
// Player state
this.isPlaying = false;
this.currentTrackIndex = 0;
this.volume = 0.7;
this.shuffleMode = false;
this.repeatMode = 'off'; // off, one, all
// Playlist data
this.playlist = [
{
title: 'Digital Dreams',
artist: 'Neon Collective',
duration: '3:45',
genre: 'Synthwave',
year: '2089',
bpm: '128',
url: 'https://example.com/track1.mp3'
},
{
title: 'Chrome Hearts',
artist: 'Cyber Punk',
duration: '4:12',
genre: 'Dark Synth',
year: '2090',
bpm: '140',
url: 'https://example.com/track2.mp3'
},
{
title: 'Neural Network',
artist: 'AI Composer',
duration: '5:23',
genre: 'Ambient',
year: '2091',
bpm: '90',
url: 'https://example.com/track3.mp3'
},
{
title: 'Hologram Highway',
artist: 'Future Bass',
duration: '3:58',
genre: 'Electronic',
year: '2088',
bpm: '150',
url: 'https://example.com/track4.mp3'
},
{
title: 'Quantum Flux',
artist: 'Void Walker',
duration: '6:30',
genre: 'Experimental',
year: '2092',
bpm: '175',
url: 'https://example.com/track5.mp3'
}
];
this.init();
}
init() {
this.setupEventListeners();
this.renderPlaylist();
this.loadTrack(0);
this.setupAudioContext();
this.resizeCanvas();
// Start animation loop
this.animate();
}
setupAudioContext() {
try {
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
this.analyser = this.audioContext.createAnalyser();
this.analyser.fftSize = 256;
this.analyser.smoothingTimeConstant = 0.8;
const bufferLength = this.analyser.frequencyBinCount;
this.dataArray = new Uint8Array(bufferLength);
// Connect audio element to analyser
this.source = this.audioContext.createMediaElementSource(this.audio);
this.source.connect(this.analyser);
this.analyser.connect(this.audioContext.destination);
} catch (error) {
console.error('Failed to setup audio context:', error);
}
}
setupEventListeners() {
// Play/Pause
document.getElementById('play-pause-btn').addEventListener('click', () => this.togglePlayPause());
// Navigation
document.getElementById('prev-btn').addEventListener('click', () => this.previousTrack());
document.getElementById('next-btn').addEventListener('click', () => this.nextTrack());
// Modes
document.getElementById('shuffle-btn').addEventListener('click', () => this.toggleShuffle());
document.getElementById('repeat-btn').addEventListener('click', () => this.toggleRepeat());
// Volume
const volumeSlider = document.getElementById('volume-slider');
volumeSlider.addEventListener('input', (e) => this.setVolume(e.target.value / 100));
// Seek
const seekSlider = document.getElementById('seek-slider');
seekSlider.addEventListener('input', (e) => this.seek(e.target.value));
// Audio events
this.audio.addEventListener('timeupdate', () => this.updateProgress());
this.audio.addEventListener('ended', () => this.handleTrackEnd());
this.audio.addEventListener('loadedmetadata', () => this.updateDuration());
// EQ presets
document.querySelectorAll('.eq-preset').forEach(btn => {
btn.addEventListener('click', (e) => this.setEQPreset(e.target.dataset.preset));
});
// Add track button
document.getElementById('add-track-btn').addEventListener('click', () => this.addTrack());
// Window resize
window.addEventListener('resize', () => this.resizeCanvas());
}
resizeCanvas() {
const container = this.canvas.parentElement;
this.canvas.width = container.offsetWidth;
this.canvas.height = 200;
}
loadTrack(index) {
const track = this.playlist[index];
if (!track) return;
this.currentTrackIndex = index;
// Update UI
document.getElementById('track-title').textContent = track.title;
document.getElementById('track-artist').textContent = track.artist;
document.getElementById('track-genre').textContent = track.genre;
document.getElementById('track-year').textContent = track.year;
document.getElementById('track-bpm').textContent = track.bpm + ' BPM';
// Update playlist highlighting
this.updatePlaylistHighlight();
// Load audio (using placeholder URL)
// In real implementation, this would load actual audio files
// this.audio.src = track.url;
// Reset progress
document.getElementById('progress-fill').style.width = '0%';
document.getElementById('current-time').textContent = '00:00';
document.getElementById('duration-time').textContent = track.duration;
}
togglePlayPause() {
if (this.audioContext && this.audioContext.state === 'suspended') {
this.audioContext.resume();
}
if (this.isPlaying) {
this.pause();
} else {
this.play();
}
}
play() {
// In real implementation, this would play the audio
// this.audio.play();
this.isPlaying = true;
document.getElementById('play-icon').classList.add('hidden');
document.getElementById('pause-icon').classList.remove('hidden');
// Simulate playback
this.simulatePlayback();
}
pause() {
// In real implementation, this would pause the audio
// this.audio.pause();
this.isPlaying = false;
document.getElementById('play-icon').classList.remove('hidden');
document.getElementById('pause-icon').classList.add('hidden');
// Stop simulation
if (this.playbackInterval) {
clearInterval(this.playbackInterval);
}
}
simulatePlayback() {
let progress = 0;
this.playbackInterval = setInterval(() => {
if (progress >= 100) {
this.handleTrackEnd();
return;
}
progress += 0.5;
document.getElementById('progress-fill').style.width = progress + '%';
document.getElementById('seek-slider').value = progress;
// Update time display
const duration = this.parseDuration(this.playlist[this.currentTrackIndex].duration);
const current = (progress / 100) * duration;
document.getElementById('current-time').textContent = this.formatTime(current);
}, 100);
}
parseDuration(durationStr) {
const [minutes, seconds] = durationStr.split(':').map(Number);
return minutes * 60 + seconds;
}
formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
previousTrack() {
if (this.shuffleMode) {
this.playRandomTrack();
} else {
this.currentTrackIndex = (this.currentTrackIndex - 1 + this.playlist.length) % this.playlist.length;
this.loadTrack(this.currentTrackIndex);
if (this.isPlaying) this.play();
}
}
nextTrack() {
if (this.shuffleMode) {
this.playRandomTrack();
} else {
this.currentTrackIndex = (this.currentTrackIndex + 1) % this.playlist.length;
this.loadTrack(this.currentTrackIndex);
if (this.isPlaying) this.play();
}
}
playRandomTrack() {
let newIndex;
do {
newIndex = Math.floor(Math.random() * this.playlist.length);
} while (newIndex === this.currentTrackIndex && this.playlist.length > 1);
this.loadTrack(newIndex);
if (this.isPlaying) this.play();
}
handleTrackEnd() {
if (this.repeatMode === 'one') {
this.seek(0);
this.play();
} else if (this.repeatMode === 'all' || this.currentTrackIndex < this.playlist.length - 1) {
this.nextTrack();
} else {
this.pause();
}
}
toggleShuffle() {
this.shuffleMode = !this.shuffleMode;
const shuffleBtn = document.getElementById('shuffle-btn');
shuffleBtn.dataset.active = this.shuffleMode;
}
toggleRepeat() {
const modes = ['off', 'all', 'one'];
const currentIndex = modes.indexOf(this.repeatMode);
this.repeatMode = modes[(currentIndex + 1) % modes.length];
const repeatBtn = document.getElementById('repeat-btn');
repeatBtn.dataset.mode = this.repeatMode;
// Update visual indicator
if (this.repeatMode === 'off') {
repeatBtn.style.color = '';
} else if (this.repeatMode === 'all') {
repeatBtn.style.color = 'var(--neon-cyan)';
} else {
repeatBtn.style.color = 'var(--neon-pink)';
}
}
setVolume(value) {
this.volume = value;
this.audio.volume = value;
document.getElementById('volume-level').textContent = Math.round(value * 100) + '%';
// Update volume icon
const volumeBtn = document.getElementById('volume-btn');
if (value === 0) {
volumeBtn.innerHTML = '<svg viewBox="0 0 24 24" class="control-icon"><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"/></svg>';
} else if (value < 0.5) {
volumeBtn.innerHTML = '<svg viewBox="0 0 24 24" class="control-icon"><path d="M7 9v6h4l5 5V4l-5 5H7z"/></svg>';
} else {
volumeBtn.innerHTML = '<svg viewBox="0 0 24 24" class="control-icon"><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.02z"/></svg>';
}
}
seek(value) {
const duration = this.parseDuration(this.playlist[this.currentTrackIndex].duration);
const seekTime = (value / 100) * duration;
// In real implementation, this would seek the audio
// this.audio.currentTime = seekTime;
document.getElementById('progress-fill').style.width = value + '%';
document.getElementById('current-time').textContent = this.formatTime(seekTime);
}
updateProgress() {
const progress = (this.audio.currentTime / this.audio.duration) * 100;
document.getElementById('progress-fill').style.width = progress + '%';
document.getElementById('seek-slider').value = progress;
document.getElementById('current-time').textContent = this.formatTime(this.audio.currentTime);
}
updateDuration() {
document.getElementById('duration-time').textContent = this.formatTime(this.audio.duration);
}
setEQPreset(preset) {
// Update active state
document.querySelectorAll('.eq-preset').forEach(btn => {
btn.classList.remove('active');
});
document.querySelector(`[data-preset="${preset}"]`).classList.add('active');
// In real implementation, this would apply audio filters
console.log(`EQ preset set to: ${preset}`);
}
renderPlaylist() {
const playlistElement = document.getElementById('playlist');
playlistElement.innerHTML = '';
this.playlist.forEach((track, index) => {
const li = document.createElement('li');
li.className = 'playlist-item';
li.innerHTML = `
<span class="track-number">${(index + 1).toString().padStart(2, '0')}</span>
<div class="track-details">
<span class="track-name">${track.title}</span>
<span class="track-duration">${track.duration}</span>
</div>
`;
li.addEventListener('click', () => {
this.loadTrack(index);
this.play();
});
playlistElement.appendChild(li);
});
}
updatePlaylistHighlight() {
document.querySelectorAll('.playlist-item').forEach((item, index) => {
if (index === this.currentTrackIndex) {
item.classList.add('active');
} else {
item.classList.remove('active');
}
});
}
addTrack() {
// In real implementation, this would open a file picker or dialog
alert('Add track functionality would open a file picker in a real application');
}
animate() {
requestAnimationFrame(() => this.animate());
if (!this.isPlaying || !this.analyser) return;
// Get frequency data
this.analyser.getByteFrequencyData(this.dataArray);
// Clear canvas
this.ctx.fillStyle = 'rgba(22, 22, 22, 0.2)';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
// Draw waveform
const barWidth = (this.canvas.width / this.dataArray.length) * 2.5;
let x = 0;
for (let i = 0; i < this.dataArray.length; i++) {
// Generate synthetic data for demo
const barHeight = this.isPlaying ?
Math.sin(Date.now() * 0.001 + i * 0.5) * 50 + Math.random() * 100 :
0;
// Create gradient
const gradient = this.ctx.createLinearGradient(0, this.canvas.height, 0, this.canvas.height - barHeight);
gradient.addColorStop(0, '#ff006e');
gradient.addColorStop(0.5, '#00f5ff');
gradient.addColorStop(1, '#8338ec');
this.ctx.fillStyle = gradient;
this.ctx.fillRect(x, this.canvas.height - barHeight, barWidth, barHeight);
// Add glow effect
this.ctx.shadowBlur = 20;
this.ctx.shadowColor = '#00f5ff';
x += barWidth + 1;
}
}
}
// Initialize player when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
const player = new NeonWavePlayer();
// Add glitch effect on hover
const playerContainer = document.querySelector('.player-container');
playerContainer.addEventListener('mouseenter', () => {
const glitchOverlay = document.querySelector('.glitch-overlay');
glitchOverlay.style.animation = 'glitch 2s infinite';
});
playerContainer.addEventListener('mouseleave', () => {
const glitchOverlay = document.querySelector('.glitch-overlay');
glitchOverlay.style.animation = 'glitch 10s infinite';
});
});

View File

@ -0,0 +1,646 @@
/* Cyberpunk Neon Theme - CSS Custom Properties */
:root {
/* Neon Color Palette */
--neon-pink: #ff006e;
--neon-cyan: #00f5ff;
--neon-purple: #8338ec;
--neon-yellow: #ffbe0b;
--neon-green: #3bff00;
/* Dark Base Colors */
--bg-primary: #0a0a0a;
--bg-secondary: #161616;
--bg-tertiary: #1e1e1e;
--bg-glass: rgba(10, 10, 10, 0.85);
/* Glow Effects */
--glow-pink: 0 0 20px rgba(255, 0, 110, 0.5), 0 0 40px rgba(255, 0, 110, 0.3);
--glow-cyan: 0 0 20px rgba(0, 245, 255, 0.5), 0 0 40px rgba(0, 245, 255, 0.3);
--glow-purple: 0 0 20px rgba(131, 56, 236, 0.5), 0 0 40px rgba(131, 56, 236, 0.3);
/* Typography */
--font-primary: 'Orbitron', monospace;
--font-secondary: 'Roboto Mono', monospace;
/* Spacing */
--spacing-xs: 0.5rem;
--spacing-sm: 1rem;
--spacing-md: 1.5rem;
--spacing-lg: 2rem;
--spacing-xl: 3rem;
/* Transitions */
--transition-fast: 0.2s ease;
--transition-medium: 0.4s ease;
--transition-slow: 0.6s ease;
}
/* Font Imports */
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Roboto+Mono:wght@300;400;700&display=swap');
/* Global Styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-secondary);
background: var(--bg-primary);
color: var(--neon-cyan);
min-height: 100vh;
overflow-x: hidden;
position: relative;
}
/* Animated Background */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
radial-gradient(circle at 20% 80%, rgba(255, 0, 110, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(0, 245, 255, 0.1) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, rgba(131, 56, 236, 0.1) 0%, transparent 50%);
pointer-events: none;
z-index: -1;
}
/* Main Player Container */
.media-player {
max-width: 1200px;
margin: 0 auto;
padding: var(--spacing-lg);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.player-container {
background: var(--bg-glass);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 0, 110, 0.3);
border-radius: 20px;
padding: var(--spacing-xl);
box-shadow:
0 0 50px rgba(255, 0, 110, 0.2),
inset 0 0 50px rgba(0, 245, 255, 0.1);
width: 100%;
position: relative;
overflow: hidden;
}
/* Glitch Effect Overlay */
.glitch-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
opacity: 0;
animation: glitch 10s infinite;
}
@keyframes glitch {
0%, 90%, 100% { opacity: 0; }
92% {
opacity: 1;
background: repeating-linear-gradient(
0deg,
rgba(255, 0, 110, 0.1),
rgba(255, 0, 110, 0.1) 2px,
transparent 2px,
transparent 4px
);
}
94% {
opacity: 1;
transform: translateX(2px);
}
96% {
opacity: 1;
transform: translateX(-2px);
}
}
/* Visualizer Section */
.visualizer-section {
position: relative;
margin-bottom: var(--spacing-lg);
border-radius: 10px;
overflow: hidden;
background: var(--bg-secondary);
border: 1px solid rgba(0, 245, 255, 0.3);
}
.waveform-canvas {
width: 100%;
height: 200px;
display: block;
filter: drop-shadow(0 0 10px var(--neon-cyan));
}
/* Media Display */
.media-display {
display: grid;
grid-template-columns: 300px 1fr;
gap: var(--spacing-lg);
margin-bottom: var(--spacing-lg);
align-items: center;
}
.album-art-container {
position: relative;
width: 300px;
height: 300px;
border-radius: 10px;
overflow: hidden;
border: 2px solid var(--neon-pink);
box-shadow: var(--glow-pink);
}
.album-art {
width: 100%;
height: 100%;
object-fit: cover;
}
.hologram-effect {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
45deg,
transparent 30%,
rgba(0, 245, 255, 0.1) 50%,
transparent 70%
);
animation: hologram 3s infinite;
}
@keyframes hologram {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
.track-info {
padding: var(--spacing-md);
}
.track-title {
font-family: var(--font-primary);
font-size: 2.5rem;
font-weight: 900;
color: var(--neon-pink);
text-shadow: var(--glow-pink);
margin-bottom: var(--spacing-xs);
text-transform: uppercase;
letter-spacing: 2px;
}
.track-artist {
font-size: 1.25rem;
color: var(--neon-cyan);
margin-bottom: var(--spacing-md);
opacity: 0.8;
}
.track-metadata {
display: flex;
gap: var(--spacing-sm);
flex-wrap: wrap;
}
.metadata-tag {
background: var(--bg-tertiary);
border: 1px solid var(--neon-purple);
color: var(--neon-purple);
padding: var(--spacing-xs) var(--spacing-sm);
border-radius: 20px;
font-size: 0.875rem;
text-transform: uppercase;
box-shadow: var(--glow-purple);
transition: var(--transition-fast);
}
.metadata-tag:hover {
background: var(--neon-purple);
color: var(--bg-primary);
}
/* Progress Section */
.progress-section {
margin-bottom: var(--spacing-lg);
}
.time-display {
display: flex;
align-items: center;
gap: var(--spacing-md);
}
.time-current,
.time-duration {
font-family: var(--font-primary);
font-size: 1rem;
color: var(--neon-green);
text-shadow: 0 0 10px rgba(59, 255, 0, 0.5);
min-width: 60px;
}
.progress-container {
flex: 1;
position: relative;
height: 10px;
}
.progress-bar {
width: 100%;
height: 100%;
background: var(--bg-tertiary);
border-radius: 5px;
overflow: hidden;
position: relative;
border: 1px solid rgba(0, 245, 255, 0.3);
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--neon-pink), var(--neon-cyan));
width: 0%;
transition: width 0.1s linear;
position: relative;
}
.progress-glow {
position: absolute;
top: -5px;
right: -10px;
width: 20px;
height: 20px;
background: var(--neon-cyan);
border-radius: 50%;
filter: blur(10px);
opacity: 0.8;
}
.seek-slider {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
}
/* Control Panel */
.control-panel {
margin-bottom: var(--spacing-lg);
}
.main-controls {
display: flex;
justify-content: center;
align-items: center;
gap: var(--spacing-md);
}
.control-btn {
background: var(--bg-tertiary);
border: 2px solid var(--neon-cyan);
border-radius: 50%;
width: 60px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: var(--transition-fast);
position: relative;
overflow: hidden;
}
.control-btn:hover {
transform: scale(1.1);
box-shadow: var(--glow-cyan);
}
.control-btn:active {
transform: scale(0.95);
}
.control-btn-primary {
width: 80px;
height: 80px;
border-color: var(--neon-pink);
background: linear-gradient(135deg, var(--bg-tertiary), var(--bg-secondary));
}
.control-btn-primary:hover {
box-shadow: var(--glow-pink);
}
.control-icon {
width: 24px;
height: 24px;
fill: currentColor;
}
.control-btn-primary .control-icon {
width: 32px;
height: 32px;
}
.control-btn[data-active="true"] {
border-color: var(--neon-green);
color: var(--neon-green);
box-shadow: var(--glow-cyan);
}
/* Audio Controls */
.audio-controls {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-lg);
padding: var(--spacing-md);
background: var(--bg-secondary);
border-radius: 10px;
border: 1px solid rgba(131, 56, 236, 0.3);
}
.volume-control {
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.volume-slider-container {
position: relative;
width: 150px;
}
.volume-slider {
width: 100%;
height: 6px;
-webkit-appearance: none;
appearance: none;
background: var(--bg-tertiary);
border-radius: 3px;
outline: none;
border: 1px solid rgba(255, 0, 110, 0.3);
}
.volume-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 18px;
height: 18px;
background: var(--neon-pink);
border-radius: 50%;
cursor: pointer;
box-shadow: var(--glow-pink);
transition: var(--transition-fast);
}
.volume-slider::-webkit-slider-thumb:hover {
transform: scale(1.2);
}
.volume-level {
position: absolute;
top: -30px;
right: 0;
font-size: 0.875rem;
color: var(--neon-yellow);
font-family: var(--font-primary);
}
.equalizer {
display: flex;
gap: var(--spacing-xs);
}
.eq-preset {
background: var(--bg-tertiary);
border: 1px solid var(--neon-purple);
color: var(--neon-purple);
padding: var(--spacing-xs) var(--spacing-sm);
border-radius: 5px;
font-family: var(--font-primary);
font-size: 0.75rem;
cursor: pointer;
transition: var(--transition-fast);
text-transform: uppercase;
letter-spacing: 1px;
}
.eq-preset:hover,
.eq-preset.active {
background: var(--neon-purple);
color: var(--bg-primary);
box-shadow: var(--glow-purple);
}
/* Playlist Section */
.playlist-section {
background: var(--bg-secondary);
border-radius: 10px;
padding: var(--spacing-md);
border: 1px solid rgba(0, 245, 255, 0.3);
}
.playlist-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-md);
}
.playlist-title {
font-family: var(--font-primary);
font-size: 1.5rem;
color: var(--neon-cyan);
text-shadow: var(--glow-cyan);
text-transform: uppercase;
}
.playlist-action {
background: transparent;
border: 1px solid var(--neon-green);
color: var(--neon-green);
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: var(--transition-fast);
}
.playlist-action:hover {
background: var(--neon-green);
color: var(--bg-primary);
box-shadow: 0 0 20px rgba(59, 255, 0, 0.5);
}
.action-icon {
width: 20px;
height: 20px;
fill: currentColor;
}
.playlist-container {
max-height: 300px;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: var(--neon-purple) var(--bg-tertiary);
}
.playlist-container::-webkit-scrollbar {
width: 8px;
}
.playlist-container::-webkit-scrollbar-track {
background: var(--bg-tertiary);
border-radius: 4px;
}
.playlist-container::-webkit-scrollbar-thumb {
background: var(--neon-purple);
border-radius: 4px;
}
.playlist {
list-style: none;
}
.playlist-item {
display: flex;
align-items: center;
padding: var(--spacing-sm);
border-bottom: 1px solid rgba(255, 0, 110, 0.1);
cursor: pointer;
transition: var(--transition-fast);
position: relative;
}
.playlist-item:hover {
background: rgba(0, 245, 255, 0.1);
padding-left: var(--spacing-md);
}
.playlist-item.active {
background: rgba(255, 0, 110, 0.1);
border-left: 3px solid var(--neon-pink);
}
.playlist-item.active::before {
content: '▶';
position: absolute;
left: var(--spacing-sm);
color: var(--neon-pink);
animation: pulse 1s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.track-number {
min-width: 30px;
color: var(--neon-purple);
font-family: var(--font-primary);
margin-right: var(--spacing-sm);
}
.track-details {
flex: 1;
}
.track-name {
color: var(--neon-cyan);
font-weight: 500;
display: block;
}
.track-duration {
color: var(--neon-green);
font-size: 0.875rem;
opacity: 0.8;
}
/* Utility Classes */
.hidden {
display: none;
}
/* Responsive Design */
@media (max-width: 768px) {
.media-player {
padding: var(--spacing-sm);
}
.player-container {
padding: var(--spacing-md);
}
.media-display {
grid-template-columns: 1fr;
text-align: center;
}
.album-art-container {
margin: 0 auto;
width: 200px;
height: 200px;
}
.track-title {
font-size: 1.75rem;
}
.main-controls {
gap: var(--spacing-sm);
}
.control-btn {
width: 50px;
height: 50px;
}
.control-btn-primary {
width: 60px;
height: 60px;
}
.audio-controls {
flex-direction: column;
gap: var(--spacing-md);
}
}
/* Animations */
@keyframes neon-flicker {
0%, 100% { opacity: 1; }
41.99% { opacity: 1; }
42% { opacity: 0.8; }
43% { opacity: 1; }
45% { opacity: 0.3; }
46% { opacity: 1; }
}
.track-title {
animation: neon-flicker 5s infinite;
}

View File

@ -0,0 +1,150 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Art Deco Time Manager - UI Hybrid 5</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="time-manager-container">
<header class="tm-header">
<div class="art-deco-frame">
<h1 class="tm-title">Chronos Suite</h1>
<p class="tm-subtitle">Your Elegant Time Companion</p>
</div>
<div class="tm-current-time">
<div class="time-display" id="currentTime">00:00:00</div>
<div class="date-display" id="currentDate">Monday, January 1, 1920</div>
</div>
</header>
<nav class="tm-navigation">
<button class="nav-tab active" data-view="calendar">Calendar</button>
<button class="nav-tab" data-view="timer">Timer</button>
<button class="nav-tab" data-view="schedule">Schedule</button>
<button class="nav-tab" data-view="timezone">World Time</button>
<button class="nav-tab" data-view="deadlines">Deadlines</button>
</nav>
<main class="tm-content">
<section class="view-panel active" id="calendarView">
<div class="calendar-container">
<div class="calendar-header">
<button class="calendar-nav" id="prevMonth"></button>
<h2 class="calendar-month-year" id="monthYear">January 1920</h2>
<button class="calendar-nav" id="nextMonth"></button>
</div>
<div class="calendar-grid" id="calendarGrid">
<div class="day-header">Sun</div>
<div class="day-header">Mon</div>
<div class="day-header">Tue</div>
<div class="day-header">Wed</div>
<div class="day-header">Thu</div>
<div class="day-header">Fri</div>
<div class="day-header">Sat</div>
</div>
<div class="events-panel">
<h3 class="events-title">Today's Affairs</h3>
<ul class="events-list" id="eventsList"></ul>
</div>
</div>
</section>
<section class="view-panel" id="timerView">
<div class="timer-container">
<div class="timer-display">
<div class="timer-digits" id="timerDisplay">00:00:00</div>
</div>
<div class="timer-controls">
<button class="timer-btn" id="startTimer">Start</button>
<button class="timer-btn" id="pauseTimer">Pause</button>
<button class="timer-btn" id="resetTimer">Reset</button>
</div>
<div class="timer-presets">
<h3>Quick Sets</h3>
<div class="preset-buttons">
<button class="preset-btn" data-minutes="5">5 min</button>
<button class="preset-btn" data-minutes="15">15 min</button>
<button class="preset-btn" data-minutes="30">30 min</button>
<button class="preset-btn" data-minutes="60">1 hour</button>
</div>
</div>
</div>
</section>
<section class="view-panel" id="scheduleView">
<div class="schedule-container">
<h2 class="schedule-title">Daily Programme</h2>
<form class="schedule-form" id="scheduleForm">
<input type="time" class="schedule-input" id="scheduleTime" required>
<input type="text" class="schedule-input" id="scheduleTask" placeholder="Enter appointment details" required>
<button type="submit" class="schedule-btn">Add to Schedule</button>
</form>
<div class="schedule-timeline" id="scheduleTimeline"></div>
</div>
</section>
<section class="view-panel" id="timezoneView">
<div class="timezone-container">
<h2 class="timezone-title">World Clock</h2>
<div class="timezone-grid" id="timezoneGrid">
<div class="timezone-card" data-tz="America/New_York">
<h3>New York</h3>
<div class="tz-time"></div>
</div>
<div class="timezone-card" data-tz="Europe/London">
<h3>London</h3>
<div class="tz-time"></div>
</div>
<div class="timezone-card" data-tz="Europe/Paris">
<h3>Paris</h3>
<div class="tz-time"></div>
</div>
<div class="timezone-card" data-tz="Asia/Tokyo">
<h3>Tokyo</h3>
<div class="tz-time"></div>
</div>
</div>
<div class="timezone-converter">
<h3>Time Converter</h3>
<input type="time" class="converter-input" id="converterTime">
<select class="converter-select" id="fromTimezone">
<option value="local">Local Time</option>
<option value="America/New_York">New York</option>
<option value="Europe/London">London</option>
<option value="Asia/Tokyo">Tokyo</option>
</select>
<button class="converter-btn" id="convertTime">Convert</button>
<div class="converter-results" id="converterResults"></div>
</div>
</div>
</section>
<section class="view-panel" id="deadlinesView">
<div class="deadlines-container">
<h2 class="deadlines-title">Important Deadlines</h2>
<form class="deadline-form" id="deadlineForm">
<input type="text" class="deadline-input" id="deadlineTitle" placeholder="Deadline title" required>
<input type="datetime-local" class="deadline-input" id="deadlineDate" required>
<select class="deadline-input" id="deadlinePriority">
<option value="low">Low Priority</option>
<option value="medium">Medium Priority</option>
<option value="high">High Priority</option>
</select>
<button type="submit" class="deadline-btn">Add Deadline</button>
</form>
<div class="deadlines-list" id="deadlinesList"></div>
</div>
</section>
</main>
<footer class="tm-footer">
<div class="art-deco-ornament"></div>
<p class="footer-text">Tempus Fugit · Carpe Diem</p>
</footer>
</div>
<script src="script.js"></script>
</body>
</html>

View File

@ -0,0 +1,541 @@
// Art Deco Time Manager - Interactive JavaScript
class TimeManager {
constructor() {
this.currentView = 'calendar';
this.timerInterval = null;
this.timerSeconds = 0;
this.timerRunning = false;
this.selectedDate = new Date();
this.events = this.loadFromStorage('events') || {};
this.schedule = this.loadFromStorage('schedule') || [];
this.deadlines = this.loadFromStorage('deadlines') || [];
this.init();
}
init() {
this.setupNavigationListeners();
this.setupTimeDisplay();
this.setupCalendar();
this.setupTimer();
this.setupSchedule();
this.setupTimezone();
this.setupDeadlines();
this.updateWorldClocks();
setInterval(() => this.updateWorldClocks(), 1000);
}
// Navigation Management
setupNavigationListeners() {
const navTabs = document.querySelectorAll('.nav-tab');
navTabs.forEach(tab => {
tab.addEventListener('click', (e) => {
this.switchView(e.target.dataset.view);
});
});
}
switchView(viewName) {
// Update navigation tabs
document.querySelectorAll('.nav-tab').forEach(tab => {
tab.classList.toggle('active', tab.dataset.view === viewName);
});
// Update view panels
document.querySelectorAll('.view-panel').forEach(panel => {
panel.classList.toggle('active', panel.id === `${viewName}View`);
});
this.currentView = viewName;
}
// Time Display
setupTimeDisplay() {
const updateTime = () => {
const now = new Date();
const timeStr = now.toLocaleTimeString('en-US', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
const dateStr = now.toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
});
document.getElementById('currentTime').textContent = timeStr;
document.getElementById('currentDate').textContent = dateStr;
};
updateTime();
setInterval(updateTime, 1000);
}
// Calendar Functionality
setupCalendar() {
this.renderCalendar();
document.getElementById('prevMonth').addEventListener('click', () => {
this.selectedDate.setMonth(this.selectedDate.getMonth() - 1);
this.renderCalendar();
});
document.getElementById('nextMonth').addEventListener('click', () => {
this.selectedDate.setMonth(this.selectedDate.getMonth() + 1);
this.renderCalendar();
});
}
renderCalendar() {
const year = this.selectedDate.getFullYear();
const month = this.selectedDate.getMonth();
const firstDay = new Date(year, month, 1).getDay();
const daysInMonth = new Date(year, month + 1, 0).getDate();
const daysInPrevMonth = new Date(year, month, 0).getDate();
// Update month/year display
document.getElementById('monthYear').textContent =
new Date(year, month).toLocaleDateString('en-US', {
month: 'long',
year: 'numeric'
});
// Clear existing calendar days
const grid = document.getElementById('calendarGrid');
const existingDays = grid.querySelectorAll('.calendar-day');
existingDays.forEach(day => day.remove());
// Add previous month's trailing days
for (let i = firstDay - 1; i >= 0; i--) {
const dayEl = this.createCalendarDay(
daysInPrevMonth - i,
month - 1,
year,
true
);
grid.appendChild(dayEl);
}
// Add current month's days
for (let day = 1; day <= daysInMonth; day++) {
const dayEl = this.createCalendarDay(day, month, year, false);
grid.appendChild(dayEl);
}
// Add next month's leading days
const totalCells = grid.children.length - 7; // Subtract header row
const remainingCells = 42 - totalCells; // 6 weeks * 7 days
for (let day = 1; day <= remainingCells; day++) {
const dayEl = this.createCalendarDay(day, month + 1, year, true);
grid.appendChild(dayEl);
}
this.renderEvents();
}
createCalendarDay(day, month, year, isOtherMonth) {
const dayEl = document.createElement('div');
dayEl.className = 'calendar-day';
if (isOtherMonth) dayEl.classList.add('other-month');
const date = new Date(year, month, day);
const today = new Date();
if (this.isSameDay(date, today)) {
dayEl.classList.add('today');
}
dayEl.textContent = day;
dayEl.addEventListener('click', () => this.selectDate(date));
// Check for events
const dateKey = this.getDateKey(date);
if (this.events[dateKey]) {
dayEl.style.borderBottom = '3px solid var(--deco-gold)';
}
return dayEl;
}
selectDate(date) {
this.selectedDate = date;
this.renderEvents();
}
renderEvents() {
const eventsList = document.getElementById('eventsList');
eventsList.innerHTML = '';
const dateKey = this.getDateKey(this.selectedDate);
const dayEvents = this.events[dateKey] || [];
if (dayEvents.length === 0) {
eventsList.innerHTML = '<li class="event-item">No appointments scheduled</li>';
return;
}
dayEvents.forEach(event => {
const li = document.createElement('li');
li.className = 'event-item';
li.textContent = event;
eventsList.appendChild(li);
});
}
// Timer Functionality
setupTimer() {
document.getElementById('startTimer').addEventListener('click', () => this.startTimer());
document.getElementById('pauseTimer').addEventListener('click', () => this.pauseTimer());
document.getElementById('resetTimer').addEventListener('click', () => this.resetTimer());
// Preset buttons
document.querySelectorAll('.preset-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const minutes = parseInt(e.target.dataset.minutes);
this.timerSeconds = minutes * 60;
this.updateTimerDisplay();
});
});
}
startTimer() {
if (!this.timerRunning) {
this.timerRunning = true;
this.timerInterval = setInterval(() => {
if (this.timerSeconds > 0) {
this.timerSeconds--;
this.updateTimerDisplay();
} else {
this.pauseTimer();
this.showNotification('Timer Complete!');
}
}, 1000);
}
}
pauseTimer() {
this.timerRunning = false;
if (this.timerInterval) {
clearInterval(this.timerInterval);
this.timerInterval = null;
}
}
resetTimer() {
this.pauseTimer();
this.timerSeconds = 0;
this.updateTimerDisplay();
}
updateTimerDisplay() {
const hours = Math.floor(this.timerSeconds / 3600);
const minutes = Math.floor((this.timerSeconds % 3600) / 60);
const seconds = this.timerSeconds % 60;
const display = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
document.getElementById('timerDisplay').textContent = display;
}
// Schedule Functionality
setupSchedule() {
const form = document.getElementById('scheduleForm');
form.addEventListener('submit', (e) => {
e.preventDefault();
this.addScheduleItem();
});
this.renderSchedule();
}
addScheduleItem() {
const time = document.getElementById('scheduleTime').value;
const task = document.getElementById('scheduleTask').value;
if (time && task) {
this.schedule.push({ time, task });
this.schedule.sort((a, b) => a.time.localeCompare(b.time));
this.saveToStorage('schedule', this.schedule);
this.renderSchedule();
// Reset form
document.getElementById('scheduleForm').reset();
}
}
renderSchedule() {
const timeline = document.getElementById('scheduleTimeline');
timeline.innerHTML = '';
if (this.schedule.length === 0) {
timeline.innerHTML = '<div class="timeline-item">No appointments scheduled</div>';
return;
}
this.schedule.forEach((item, index) => {
const itemEl = document.createElement('div');
itemEl.className = 'timeline-item';
itemEl.innerHTML = `
<span class="timeline-time">${item.time}</span>
<span class="timeline-task">${item.task}</span>
`;
timeline.appendChild(itemEl);
});
}
// Timezone Functionality
setupTimezone() {
document.getElementById('convertTime').addEventListener('click', () => {
this.convertTime();
});
}
updateWorldClocks() {
const cards = document.querySelectorAll('.timezone-card');
cards.forEach(card => {
const timezone = card.dataset.tz;
const timeEl = card.querySelector('.tz-time');
const time = new Date().toLocaleTimeString('en-US', {
timeZone: timezone,
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
timeEl.textContent = time;
});
}
convertTime() {
const time = document.getElementById('converterTime').value;
const fromTz = document.getElementById('fromTimezone').value;
const results = document.getElementById('converterResults');
if (!time) {
results.innerHTML = '<p>Please select a time to convert</p>';
return;
}
const [hours, minutes] = time.split(':').map(Number);
const date = new Date();
date.setHours(hours, minutes, 0, 0);
const zones = [
{ name: 'New York', tz: 'America/New_York' },
{ name: 'London', tz: 'Europe/London' },
{ name: 'Paris', tz: 'Europe/Paris' },
{ name: 'Tokyo', tz: 'Asia/Tokyo' }
];
results.innerHTML = '<h4>Converted Times:</h4>';
zones.forEach(zone => {
const convertedTime = date.toLocaleTimeString('en-US', {
timeZone: zone.tz,
hour12: false,
hour: '2-digit',
minute: '2-digit'
});
results.innerHTML += `<p>${zone.name}: ${convertedTime}</p>`;
});
}
// Deadlines Functionality
setupDeadlines() {
const form = document.getElementById('deadlineForm');
form.addEventListener('submit', (e) => {
e.preventDefault();
this.addDeadline();
});
this.renderDeadlines();
setInterval(() => this.updateDeadlineCountdowns(), 60000); // Update every minute
}
addDeadline() {
const title = document.getElementById('deadlineTitle').value;
const date = document.getElementById('deadlineDate').value;
const priority = document.getElementById('deadlinePriority').value;
if (title && date) {
const deadline = {
id: Date.now(),
title,
date: new Date(date),
priority
};
this.deadlines.push(deadline);
this.deadlines.sort((a, b) => a.date - b.date);
this.saveToStorage('deadlines', this.deadlines);
this.renderDeadlines();
// Reset form
document.getElementById('deadlineForm').reset();
}
}
renderDeadlines() {
const list = document.getElementById('deadlinesList');
list.innerHTML = '';
if (this.deadlines.length === 0) {
list.innerHTML = '<div class="deadline-item">No deadlines set</div>';
return;
}
this.deadlines.forEach(deadline => {
const itemEl = document.createElement('div');
itemEl.className = `deadline-item ${deadline.priority}-priority`;
const countdown = this.getCountdown(deadline.date);
itemEl.innerHTML = `
<div class="deadline-header">
<span class="deadline-title-text">${deadline.title}</span>
<span class="deadline-priority">${deadline.priority.toUpperCase()}</span>
</div>
<div class="deadline-date">${deadline.date.toLocaleString()}</div>
<div class="deadline-countdown">${countdown}</div>
<button class="deadline-remove" data-id="${deadline.id}">×</button>
`;
const removeBtn = itemEl.querySelector('.deadline-remove');
removeBtn.addEventListener('click', () => this.removeDeadline(deadline.id));
list.appendChild(itemEl);
});
}
removeDeadline(id) {
this.deadlines = this.deadlines.filter(d => d.id !== id);
this.saveToStorage('deadlines', this.deadlines);
this.renderDeadlines();
}
updateDeadlineCountdowns() {
if (this.currentView === 'deadlines') {
this.renderDeadlines();
}
}
getCountdown(targetDate) {
const now = new Date();
const diff = targetDate - now;
if (diff < 0) {
return 'Overdue';
}
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
if (days > 0) {
return `${days} days, ${hours} hours remaining`;
} else if (hours > 0) {
return `${hours} hours, ${minutes} minutes remaining`;
} else {
return `${minutes} minutes remaining`;
}
}
// Utility Functions
isSameDay(date1, date2) {
return date1.getFullYear() === date2.getFullYear() &&
date1.getMonth() === date2.getMonth() &&
date1.getDate() === date2.getDate();
}
getDateKey(date) {
return `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
}
showNotification(message) {
// Create a temporary notification
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
padding: 20px;
background: var(--deco-gold);
color: var(--deco-black);
border: 2px solid var(--deco-black);
font-family: var(--font-accent);
text-transform: uppercase;
letter-spacing: 0.1rem;
z-index: 1000;
animation: slideIn 0.5s ease;
`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.style.animation = 'slideOut 0.5s ease';
setTimeout(() => notification.remove(), 500);
}, 3000);
}
// Storage Functions
saveToStorage(key, data) {
try {
localStorage.setItem(`timeManager_${key}`, JSON.stringify(data));
} catch (e) {
console.error('Failed to save to storage:', e);
}
}
loadFromStorage(key) {
try {
const data = localStorage.getItem(`timeManager_${key}`);
if (data) {
const parsed = JSON.parse(data);
// Convert date strings back to Date objects for deadlines
if (key === 'deadlines') {
return parsed.map(d => ({
...d,
date: new Date(d.date)
}));
}
return parsed;
}
} catch (e) {
console.error('Failed to load from storage:', e);
}
return null;
}
}
// Add animations to stylesheet dynamically
const style = document.createElement('style');
style.textContent = `
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOut {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(100%);
opacity: 0;
}
}
`;
document.head.appendChild(style);
// Initialize Time Manager when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
new TimeManager();
});

View File

@ -0,0 +1,710 @@
/* Art Deco Elegance Theme - Time Manager Styles */
:root {
/* Art Deco Color Palette */
--deco-gold: #D4AF37;
--deco-light-gold: #F4E4BC;
--deco-dark-gold: #B8941F;
--deco-black: #1A1A1A;
--deco-cream: #F5F2E8;
--deco-gray: #4A4A4A;
--deco-accent: #8B4513;
/* Typography */
--font-display: 'Playfair Display', 'Georgia', serif;
--font-body: 'Crimson Text', 'Times New Roman', serif;
--font-accent: 'Oswald', 'Arial', sans-serif;
/* Spacing */
--spacing-xs: 0.25rem;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 2rem;
--spacing-xl: 3rem;
/* Borders and Ornaments */
--border-ornate: 2px solid var(--deco-gold);
--shadow-elegant: 0 4px 20px rgba(212, 175, 55, 0.3);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: var(--font-body);
background-color: var(--deco-black);
color: var(--deco-cream);
line-height: 1.6;
min-height: 100vh;
background-image:
repeating-linear-gradient(
45deg,
transparent,
transparent 10px,
rgba(212, 175, 55, 0.05) 10px,
rgba(212, 175, 55, 0.05) 20px
);
}
.time-manager-container {
max-width: 1200px;
margin: 0 auto;
padding: var(--spacing-lg);
}
/* Header Styles */
.tm-header {
text-align: center;
margin-bottom: var(--spacing-xl);
}
.art-deco-frame {
display: inline-block;
padding: var(--spacing-lg);
position: relative;
background: var(--deco-black);
border: 3px solid var(--deco-gold);
clip-path: polygon(
0 10px, 10px 0,
calc(100% - 10px) 0, 100% 10px,
100% calc(100% - 10px), calc(100% - 10px) 100%,
10px 100%, 0 calc(100% - 10px)
);
}
.art-deco-frame::before,
.art-deco-frame::after {
content: '';
position: absolute;
width: 100%;
height: 100%;
border: 1px solid var(--deco-gold);
pointer-events: none;
}
.art-deco-frame::before {
top: -6px;
left: -6px;
right: -6px;
bottom: -6px;
}
.tm-title {
font-family: var(--font-display);
font-size: 3rem;
color: var(--deco-gold);
text-transform: uppercase;
letter-spacing: 0.3rem;
margin-bottom: var(--spacing-sm);
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
}
.tm-subtitle {
font-family: var(--font-accent);
font-size: 1.2rem;
color: var(--deco-light-gold);
text-transform: uppercase;
letter-spacing: 0.2rem;
}
.tm-current-time {
margin-top: var(--spacing-lg);
padding: var(--spacing-md);
background: linear-gradient(135deg, var(--deco-black) 0%, var(--deco-gray) 100%);
border: var(--border-ornate);
display: inline-block;
}
.time-display {
font-family: var(--font-accent);
font-size: 2.5rem;
color: var(--deco-gold);
letter-spacing: 0.1rem;
}
.date-display {
font-family: var(--font-body);
font-size: 1.2rem;
color: var(--deco-cream);
margin-top: var(--spacing-xs);
}
/* Navigation Styles */
.tm-navigation {
display: flex;
justify-content: center;
gap: var(--spacing-sm);
margin-bottom: var(--spacing-xl);
padding: var(--spacing-md);
background: var(--deco-black);
border-top: var(--border-ornate);
border-bottom: var(--border-ornate);
}
.nav-tab {
font-family: var(--font-accent);
font-size: 1rem;
text-transform: uppercase;
letter-spacing: 0.1rem;
padding: var(--spacing-sm) var(--spacing-lg);
background: transparent;
color: var(--deco-cream);
border: 1px solid var(--deco-gold);
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.nav-tab::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: var(--deco-gold);
transition: left 0.3s ease;
z-index: -1;
}
.nav-tab:hover::before,
.nav-tab.active::before {
left: 0;
}
.nav-tab:hover,
.nav-tab.active {
color: var(--deco-black);
}
/* Content Panel Styles */
.tm-content {
background: var(--deco-black);
border: var(--border-ornate);
padding: var(--spacing-lg);
box-shadow: var(--shadow-elegant);
min-height: 500px;
}
.view-panel {
display: none;
}
.view-panel.active {
display: block;
animation: fadeIn 0.5s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* Calendar Styles */
.calendar-container {
max-width: 800px;
margin: 0 auto;
}
.calendar-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-lg);
padding: var(--spacing-md);
background: var(--deco-gray);
border: var(--border-ornate);
}
.calendar-nav {
font-family: var(--font-accent);
font-size: 1.5rem;
background: transparent;
color: var(--deco-gold);
border: none;
cursor: pointer;
padding: var(--spacing-sm);
transition: transform 0.2s ease;
}
.calendar-nav:hover {
transform: scale(1.2);
}
.calendar-month-year {
font-family: var(--font-display);
font-size: 1.8rem;
color: var(--deco-gold);
text-transform: uppercase;
letter-spacing: 0.2rem;
}
.calendar-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 1px;
background: var(--deco-gold);
border: var(--border-ornate);
padding: 1px;
}
.day-header {
font-family: var(--font-accent);
text-transform: uppercase;
text-align: center;
padding: var(--spacing-md);
background: var(--deco-gray);
color: var(--deco-gold);
letter-spacing: 0.1rem;
}
.calendar-day {
aspect-ratio: 1;
background: var(--deco-black);
border: 1px solid transparent;
padding: var(--spacing-sm);
cursor: pointer;
transition: all 0.3s ease;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-family: var(--font-body);
}
.calendar-day:hover {
background: var(--deco-gray);
border-color: var(--deco-gold);
}
.calendar-day.selected {
background: var(--deco-gold);
color: var(--deco-black);
}
.calendar-day.today {
border: 2px solid var(--deco-gold);
}
.calendar-day.other-month {
opacity: 0.3;
}
.events-panel {
margin-top: var(--spacing-xl);
padding: var(--spacing-lg);
background: var(--deco-gray);
border: var(--border-ornate);
}
.events-title {
font-family: var(--font-display);
font-size: 1.5rem;
color: var(--deco-gold);
margin-bottom: var(--spacing-md);
text-transform: uppercase;
letter-spacing: 0.1rem;
}
.events-list {
list-style: none;
}
.event-item {
padding: var(--spacing-md);
margin-bottom: var(--spacing-sm);
background: var(--deco-black);
border-left: 4px solid var(--deco-gold);
font-family: var(--font-body);
}
/* Timer Styles */
.timer-container {
text-align: center;
max-width: 600px;
margin: 0 auto;
}
.timer-display {
padding: var(--spacing-xl);
background: var(--deco-gray);
border: var(--border-ornate);
margin-bottom: var(--spacing-lg);
position: relative;
}
.timer-display::before,
.timer-display::after {
content: '❦';
position: absolute;
font-size: 2rem;
color: var(--deco-gold);
}
.timer-display::before {
top: var(--spacing-md);
left: var(--spacing-md);
}
.timer-display::after {
bottom: var(--spacing-md);
right: var(--spacing-md);
}
.timer-digits {
font-family: var(--font-accent);
font-size: 4rem;
color: var(--deco-gold);
letter-spacing: 0.2rem;
}
.timer-controls {
display: flex;
justify-content: center;
gap: var(--spacing-md);
margin-bottom: var(--spacing-lg);
}
.timer-btn,
.preset-btn,
.schedule-btn,
.deadline-btn,
.converter-btn {
font-family: var(--font-accent);
text-transform: uppercase;
letter-spacing: 0.1rem;
padding: var(--spacing-md) var(--spacing-lg);
background: var(--deco-black);
color: var(--deco-gold);
border: var(--border-ornate);
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.timer-btn:hover,
.preset-btn:hover,
.schedule-btn:hover,
.deadline-btn:hover,
.converter-btn:hover {
background: var(--deco-gold);
color: var(--deco-black);
}
.timer-presets {
padding: var(--spacing-lg);
background: var(--deco-black);
border: var(--border-ornate);
}
.timer-presets h3 {
font-family: var(--font-display);
color: var(--deco-gold);
margin-bottom: var(--spacing-md);
text-transform: uppercase;
letter-spacing: 0.1rem;
}
.preset-buttons {
display: flex;
justify-content: center;
gap: var(--spacing-sm);
flex-wrap: wrap;
}
/* Schedule Styles */
.schedule-container {
max-width: 700px;
margin: 0 auto;
}
.schedule-title,
.timezone-title,
.deadlines-title {
font-family: var(--font-display);
font-size: 2rem;
color: var(--deco-gold);
text-align: center;
margin-bottom: var(--spacing-lg);
text-transform: uppercase;
letter-spacing: 0.2rem;
}
.schedule-form,
.deadline-form {
display: grid;
gap: var(--spacing-md);
margin-bottom: var(--spacing-lg);
padding: var(--spacing-lg);
background: var(--deco-gray);
border: var(--border-ornate);
}
.schedule-input,
.deadline-input,
.converter-input,
.converter-select {
font-family: var(--font-body);
font-size: 1rem;
padding: var(--spacing-md);
background: var(--deco-black);
color: var(--deco-cream);
border: 1px solid var(--deco-gold);
transition: border-color 0.3s ease;
}
.schedule-input:focus,
.deadline-input:focus,
.converter-input:focus,
.converter-select:focus {
outline: none;
border-color: var(--deco-light-gold);
}
.schedule-timeline {
padding: var(--spacing-lg);
background: var(--deco-black);
border: var(--border-ornate);
}
.timeline-item {
display: flex;
align-items: center;
padding: var(--spacing-md);
margin-bottom: var(--spacing-sm);
border-bottom: 1px solid var(--deco-gray);
}
.timeline-time {
font-family: var(--font-accent);
color: var(--deco-gold);
margin-right: var(--spacing-lg);
min-width: 80px;
}
.timeline-task {
font-family: var(--font-body);
color: var(--deco-cream);
}
/* Timezone Styles */
.timezone-container {
max-width: 900px;
margin: 0 auto;
}
.timezone-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: var(--spacing-md);
margin-bottom: var(--spacing-xl);
}
.timezone-card {
padding: var(--spacing-lg);
background: var(--deco-gray);
border: var(--border-ornate);
text-align: center;
transition: transform 0.3s ease;
}
.timezone-card:hover {
transform: translateY(-5px);
box-shadow: var(--shadow-elegant);
}
.timezone-card h3 {
font-family: var(--font-display);
color: var(--deco-gold);
margin-bottom: var(--spacing-sm);
text-transform: uppercase;
letter-spacing: 0.1rem;
}
.tz-time {
font-family: var(--font-accent);
font-size: 1.5rem;
color: var(--deco-cream);
}
.timezone-converter {
padding: var(--spacing-lg);
background: var(--deco-gray);
border: var(--border-ornate);
}
.timezone-converter h3 {
font-family: var(--font-display);
color: var(--deco-gold);
margin-bottom: var(--spacing-md);
text-transform: uppercase;
letter-spacing: 0.1rem;
text-align: center;
}
.converter-results {
margin-top: var(--spacing-lg);
padding: var(--spacing-md);
background: var(--deco-black);
border: 1px solid var(--deco-gold);
min-height: 100px;
}
/* Deadlines Styles */
.deadlines-container {
max-width: 800px;
margin: 0 auto;
}
.deadlines-list {
display: grid;
gap: var(--spacing-md);
}
.deadline-item {
padding: var(--spacing-lg);
background: var(--deco-gray);
border: var(--border-ornate);
position: relative;
transition: transform 0.3s ease;
}
.deadline-item:hover {
transform: translateX(5px);
}
.deadline-item.high-priority {
border-color: #FF6B6B;
box-shadow: 0 0 20px rgba(255, 107, 107, 0.3);
}
.deadline-item.medium-priority {
border-color: #FFD93D;
box-shadow: 0 0 20px rgba(255, 217, 61, 0.3);
}
.deadline-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-sm);
}
.deadline-title-text {
font-family: var(--font-display);
font-size: 1.3rem;
color: var(--deco-gold);
}
.deadline-priority {
font-family: var(--font-accent);
font-size: 0.9rem;
text-transform: uppercase;
padding: var(--spacing-xs) var(--spacing-sm);
background: var(--deco-black);
color: var(--deco-gold);
border: 1px solid var(--deco-gold);
}
.deadline-date {
font-family: var(--font-body);
color: var(--deco-cream);
margin-bottom: var(--spacing-sm);
}
.deadline-countdown {
font-family: var(--font-accent);
color: var(--deco-light-gold);
font-size: 0.9rem;
}
.deadline-remove {
position: absolute;
top: var(--spacing-sm);
right: var(--spacing-sm);
background: transparent;
color: var(--deco-gold);
border: none;
font-size: 1.2rem;
cursor: pointer;
opacity: 0.7;
transition: opacity 0.3s ease;
}
.deadline-remove:hover {
opacity: 1;
}
/* Footer Styles */
.tm-footer {
text-align: center;
margin-top: var(--spacing-xl);
padding: var(--spacing-lg);
position: relative;
}
.art-deco-ornament {
width: 200px;
height: 2px;
background: var(--deco-gold);
margin: 0 auto var(--spacing-md);
position: relative;
}
.art-deco-ornament::before,
.art-deco-ornament::after {
content: '◆';
position: absolute;
top: 50%;
transform: translateY(-50%);
color: var(--deco-gold);
font-size: 1rem;
}
.art-deco-ornament::before {
left: -20px;
}
.art-deco-ornament::after {
right: -20px;
}
.footer-text {
font-family: var(--font-body);
font-style: italic;
color: var(--deco-cream);
letter-spacing: 0.1rem;
}
/* Responsive Design */
@media (max-width: 768px) {
.tm-title {
font-size: 2rem;
}
.time-display {
font-size: 2rem;
}
.tm-navigation {
flex-wrap: wrap;
}
.nav-tab {
font-size: 0.9rem;
padding: var(--spacing-xs) var(--spacing-md);
}
.timezone-grid {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.timer-digits {
font-size: 3rem;
}
}