infinite-agents-public/src_group/ui_hybrid_1/script.js

563 lines
18 KiB
JavaScript

// 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);