document.addEventListener('DOMContentLoaded', () => { // --- DOM Elements --- const emojiGrid = document.getElementById('emoji-grid'); const searchInput = document.getElementById('search-input'); const darkModeToggle = document.getElementById('dark-mode-toggle'); const darkModeToggleDesktop = document.getElementById('dark-mode-toggle-desktop'); const lightIcon = document.getElementById('theme-toggle-light-icon'); const darkIcon = document.getElementById('theme-toggle-dark-icon'); const lightIconDesktop = document.getElementById('theme-toggle-light-icon-desktop'); const darkIconDesktop = document.getElementById('theme-toggle-dark-icon-desktop'); // Check if essential elements exist if (!emojiGrid || !searchInput) { console.error('Critical DOM elements missing'); return; } const modal = document.getElementById('emoji-modal'); const modalContent = document.getElementById('modal-content'); const modalCloseBtn = document.getElementById('modal-close-btn'); const modalEmoji = document.getElementById('modal-emoji'); const modalName = document.getElementById('modal-name'); const modalCategory = document.getElementById('modal-category'); const modalKeywords = document.getElementById('modal-keywords'); const modalCopyBtn = document.getElementById('modal-copy-btn'); const loadMoreBtn = document.getElementById('load-more-btn'); const categoryButtons = document.querySelectorAll('.category-btn'); const currentCategoryTitle = document.getElementById('current-category-title'); const currentCategoryCount = document.getElementById('current-category-count'); const offcanvasToggle = document.getElementById('offcanvas-toggle'); const offcanvasOverlay = document.getElementById('offcanvas-overlay'); const offcanvasClose = document.getElementById('offcanvas-close'); const offcanvasBackdrop = document.getElementById('offcanvas-backdrop'); const offcanvasSidebar = document.getElementById('offcanvas-sidebar'); const offcanvasNav = document.getElementById('offcanvas-nav'); // --- State --- let allEmojis = []; let currentEmojiList = []; let currentPage = 1; const EMOJIS_PER_PAGE = 100; let indonesianKeywords = {}; let currentCategory = 'all'; let categorizedEmojis = {}; // --- Dark Mode Logic --- const applyTheme = (isDark) => { document.documentElement.classList.toggle('dark', isDark); // Mobile icons if (lightIcon && darkIcon) { lightIcon.classList.toggle('hidden', !isDark); darkIcon.classList.toggle('hidden', isDark); } // Desktop icons if (lightIconDesktop && darkIconDesktop) { lightIconDesktop.classList.toggle('hidden', !isDark); darkIconDesktop.classList.toggle('hidden', isDark); } }; const toggleDarkMode = () => { const isDark = !document.documentElement.classList.contains('dark'); localStorage.setItem('darkMode', isDark); applyTheme(isDark); }; if (darkModeToggle) darkModeToggle.addEventListener('click', toggleDarkMode); if (darkModeToggleDesktop) darkModeToggleDesktop.addEventListener('click', toggleDarkMode); // Set initial icon state on load const initialIsDark = document.documentElement.classList.contains('dark'); applyTheme(initialIsDark); // --- Data Fetching & Processing --- const jsonFiles = [ 'array.json', 'list.json', 'categories.json', 'indonesian-keywords.json', // Add other files if they should be loaded, keeping the initial set small ].map(file => `src/${file}`); function parseEmojiData(data) { let results = []; if (Array.isArray(data)) { data.forEach(item => results.push(...parseEmojiData(item))); } else if (typeof data === 'object' && data !== null) { if (data.hasOwnProperty('emoji') && data.hasOwnProperty('name')) { results.push(data); } Object.values(data).forEach(value => results.push(...parseEmojiData(value))); } return results; } Promise.all(jsonFiles.map(file => fetch(file) .then(response => { if (!response.ok) { console.error(`Failed to load ${file}: ${response.status}`); throw new Error(`Failed to load ${file}`); } return response.json(); }) .catch(error => { console.error(`Error loading ${file}:`, error); return file.includes('indonesian-keywords') ? {keywords: {}} : []; }) )) .then(allData => { // Extract Indonesian keywords from the last file const keywordsData = allData[allData.length - 1]; if (keywordsData && keywordsData.keywords) { indonesianKeywords = keywordsData.keywords; } // Process emoji data from other files const emojiData = allData.slice(0, -1); const uniqueEmojis = new Map(); const parsedEmojis = emojiData.flat().flatMap(parseEmojiData); parsedEmojis.forEach(emoji => { if (emoji && emoji.name && !uniqueEmojis.has(emoji.name)) { // Add Indonesian keywords to emoji object if (indonesianKeywords[emoji.emoji]) { emoji.indonesianKeywords = indonesianKeywords[emoji.emoji]; } uniqueEmojis.set(emoji.name, emoji); } }); allEmojis = Array.from(uniqueEmojis.values()); // Organize emojis by category categorizedEmojis = { 'all': allEmojis, 'Smileys & Emotion': allEmojis.filter(e => e.category === 'Smileys & Emotion'), 'People & Body': allEmojis.filter(e => e.category === 'People & Body'), 'Animals & Nature': allEmojis.filter(e => e.category === 'Animals & Nature'), 'Food & Drink': allEmojis.filter(e => e.category === 'Food & Drink'), 'Travel & Places': allEmojis.filter(e => e.category === 'Travel & Places'), 'Activities': allEmojis.filter(e => e.category === 'Activities'), 'Objects': allEmojis.filter(e => e.category === 'Objects'), 'Symbols': allEmojis.filter(e => e.category === 'Symbols'), 'Flags': allEmojis.filter(e => e.category === 'Flags') }; currentEmojiList = allEmojis; initializeCategoryMenu(); updateCategoryDisplay(); displayPage(1); }) .catch(error => { console.error('Error processing emoji data:', error); emojiGrid.innerHTML = '

Failed to load emoji data.

'; }); // --- UI Rendering --- function updateLoadMoreButton() { const end = currentPage * EMOJIS_PER_PAGE; if (currentEmojiList.length > end) { loadMoreBtn.classList.remove('hidden'); } else { loadMoreBtn.classList.add('hidden'); } } function displayPage(page) { currentPage = page; const start = (page - 1) * EMOJIS_PER_PAGE; const end = start + EMOJIS_PER_PAGE; const emojisToDisplay = currentEmojiList.slice(start, end); if (page === 1) { emojiGrid.innerHTML = ''; // Clear the grid only for the first page } emojisToDisplay.forEach(emoji => { const emojiCard = document.createElement('div'); emojiCard.className = 'flex flex-col items-center justify-center p-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm cursor-pointer transition-transform duration-200 hover:scale-105 hover:shadow-md'; emojiCard.innerHTML = `
${emoji.emoji}
${emoji.name}
`; emojiCard.addEventListener('click', () => openModal(emoji)); emojiGrid.appendChild(emojiCard); }); // Show 'No emojis found' only if the grid is still empty after rendering if (emojiGrid.innerHTML === '') { emojiGrid.innerHTML = '

No emojis found.

'; } updateLoadMoreButton(); } loadMoreBtn.addEventListener('click', () => { displayPage(currentPage + 1); }); // --- Category Logic --- function initializeCategoryMenu() { // Desktop category buttons categoryButtons.forEach(btn => { btn.addEventListener('click', () => { const category = btn.dataset.category; setActiveCategory(category); }); }); // Offcanvas category menu const categories = [ { key: 'all', name: 'All Emojis', icon: '🌟' }, { key: 'Smileys & Emotion', name: 'Smileys & Emotion', icon: '😀' }, { key: 'People & Body', name: 'People & Body', icon: '👋' }, { key: 'Animals & Nature', name: 'Animals & Nature', icon: '🐶' }, { key: 'Food & Drink', name: 'Food & Drink', icon: '🍎' }, { key: 'Travel & Places', name: 'Travel & Places', icon: '🌍' }, { key: 'Activities', name: 'Activities', icon: '⚽' }, { key: 'Objects', name: 'Objects', icon: '💡' }, { key: 'Symbols', name: 'Symbols', icon: '❤️' }, { key: 'Flags', name: 'Flags', icon: '🏳️' } ]; offcanvasNav.innerHTML = categories.map(cat => ` `).join(''); // Offcanvas category button events document.querySelectorAll('.offcanvas-category-btn').forEach(btn => { btn.addEventListener('click', () => { const category = btn.dataset.category; setActiveCategory(category); closeOffcanvas(); }); }); // Offcanvas menu controls offcanvasToggle.addEventListener('click', openOffcanvas); offcanvasClose.addEventListener('click', closeOffcanvas); offcanvasBackdrop.addEventListener('click', closeOffcanvas); } function setActiveCategory(category) { currentCategory = category; // Update active state for desktop buttons categoryButtons.forEach(btn => { btn.classList.toggle('active', btn.dataset.category === category); if (btn.dataset.category === category) { btn.classList.add('bg-blue-100', 'dark:bg-blue-900', 'text-blue-700', 'dark:text-blue-300'); } else { btn.classList.remove('bg-blue-100', 'dark:bg-blue-900', 'text-blue-700', 'dark:text-blue-300'); } }); // Update active state for offcanvas buttons document.querySelectorAll('.offcanvas-category-btn').forEach(btn => { if (btn.dataset.category === category) { btn.classList.add('bg-blue-100', 'dark:bg-blue-900', 'text-blue-700', 'dark:text-blue-300'); } else { btn.classList.remove('bg-blue-100', 'dark:bg-blue-900', 'text-blue-700', 'dark:text-blue-300'); } }); // Filter emojis by category const baseEmojis = categorizedEmojis[category] || []; // Apply search filter if there's a search term const searchTerm = searchInput.value.toLowerCase().trim(); if (searchTerm) { currentEmojiList = baseEmojis.filter(emoji => { const nameMatch = emoji.name.toLowerCase().includes(searchTerm); const keywordMatch = emoji.keywords && Array.isArray(emoji.keywords) && emoji.keywords.some(k => k.toLowerCase().includes(searchTerm)); const categoryMatch = emoji.category && emoji.category.toLowerCase().includes(searchTerm); const subcategoryMatch = emoji.subcategory && emoji.subcategory.toLowerCase().includes(searchTerm); // Indonesian keywords search const indonesianMatch = emoji.indonesianKeywords && Array.isArray(emoji.indonesianKeywords) && emoji.indonesianKeywords.some(k => k.toLowerCase().includes(searchTerm)); return nameMatch || keywordMatch || categoryMatch || subcategoryMatch || indonesianMatch; }); } else { currentEmojiList = baseEmojis; } updateCategoryDisplay(); displayPage(1); } function updateCategoryDisplay() { const categoryName = currentCategory === 'all' ? 'All Emojis' : currentCategory; currentCategoryTitle.textContent = categoryName; currentCategoryCount.textContent = `${currentEmojiList.length} emojis`; } function openOffcanvas() { offcanvasOverlay.classList.remove('hidden'); setTimeout(() => { offcanvasSidebar.classList.remove('-translate-x-full'); }, 10); } function closeOffcanvas() { offcanvasSidebar.classList.add('-translate-x-full'); setTimeout(() => { offcanvasOverlay.classList.add('hidden'); }, 300); } // --- Search Logic --- searchInput.addEventListener('input', (e) => { const searchTerm = e.target.value.toLowerCase().trim(); const baseEmojis = categorizedEmojis[currentCategory] || []; if (searchTerm) { currentEmojiList = baseEmojis.filter(emoji => { const nameMatch = emoji.name.toLowerCase().includes(searchTerm); const keywordMatch = emoji.keywords && Array.isArray(emoji.keywords) && emoji.keywords.some(k => k.toLowerCase().includes(searchTerm)); const categoryMatch = emoji.category && emoji.category.toLowerCase().includes(searchTerm); const subcategoryMatch = emoji.subcategory && emoji.subcategory.toLowerCase().includes(searchTerm); // Indonesian keywords search const indonesianMatch = emoji.indonesianKeywords && Array.isArray(emoji.indonesianKeywords) && emoji.indonesianKeywords.some(k => k.toLowerCase().includes(searchTerm)); return nameMatch || keywordMatch || categoryMatch || subcategoryMatch || indonesianMatch; }); } else { currentEmojiList = baseEmojis; } updateCategoryDisplay(); displayPage(1); }); // --- Modal Logic --- function openModal(emoji) { modalEmoji.textContent = emoji.emoji; modalName.textContent = emoji.name; modalCategory.textContent = [emoji.category, emoji.subcategory].filter(Boolean).join(' / '); modalKeywords.innerHTML = ''; const tags = new Set(); if (emoji.keywords && Array.isArray(emoji.keywords)) { emoji.keywords.forEach(k => tags.add(k)); } if (emoji.indonesianKeywords && Array.isArray(emoji.indonesianKeywords)) { emoji.indonesianKeywords.forEach(k => tags.add(k)); } if (emoji.category) tags.add(emoji.category); if (emoji.subcategory) tags.add(emoji.subcategory); const tagArray = Array.from(tags); if (tagArray.length > 0) { tagArray.forEach(tagText => { const tag = document.createElement('button'); tag.className = 'px-2 py-1 bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-200 rounded-md text-sm hover:bg-gray-300 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500'; tag.textContent = tagText; tag.addEventListener('click', () => { searchInput.value = tagText; searchInput.dispatchEvent(new Event('input', { bubbles: true })); closeModal(); }); modalKeywords.appendChild(tag); }); } else { modalKeywords.innerHTML = '

No tags available.

'; } modalCopyBtn.onclick = () => copyToClipboard(emoji.emoji); modal.classList.remove('hidden'); modal.classList.add('flex'); setTimeout(() => { modalContent.classList.remove('scale-95', 'opacity-0'); }, 10); } function closeModal() { modalContent.classList.add('scale-95', 'opacity-0'); setTimeout(() => { modal.classList.add('hidden'); modal.classList.remove('flex'); }, 300); } modalCloseBtn.addEventListener('click', closeModal); modal.addEventListener('click', (e) => { if (e.target === modal) { closeModal(); } }); // --- Clipboard Logic --- function copyToClipboard(text) { navigator.clipboard.writeText(text).then(() => { const originalText = modalCopyBtn.textContent; modalCopyBtn.textContent = 'Copied!'; setTimeout(() => { modalCopyBtn.textContent = originalText; }, 2000); }).catch(err => { console.error('Failed to copy: ', err); const originalText = modalCopyBtn.textContent; modalCopyBtn.textContent = 'Failed!'; setTimeout(() => { modalCopyBtn.textContent = originalText; }, 2000); }); } // Initialize footer with dynamic year function initFooter() { const yearElement = document.getElementById('current-year'); if (yearElement) { const currentYear = new Date().getFullYear(); yearElement.textContent = currentYear; } } // Initialize footer initFooter(); });