PageSourceSearch

https://www.adawatai.dev/main-tools-script.js

js adawatai.dev collected 2026-10-04 14:06:44 UTC 66,033 bytes, 1,498 lines download raw bytes

1// main-tools-script.js - Fixed Links & Buttons (Stable Version)
2
3document.addEventListener('DOMContentLoaded', () => {
4    // --- Language and Data Initialization ---
5    const currentLang = document.documentElement.lang || 'en';
6    const langData = (typeof AllData !== 'undefined' && AllData[currentLang]) ? AllData[currentLang] : (typeof AllData !== 'undefined' ? AllData['en'] : {
7        UIStrings: {},
8        ToolsData: {
9            tools: {},
10            breakingNews: []
11        }
12    });
13    const UIStrings = langData.UIStrings;
14    const ToolsData = langData.ToolsData;
15
16    // --- UTILITY AND SECURITY OBJECTS ---
17    const PerformanceUtils = {
18        debounce(func, wait) {
19            let timeout;
20            return function executedFunction(...args) {
21                const later = () => {
22                    clearTimeout(timeout);
23                    func(...args);
24                };
25                clearTimeout(timeout);
26                timeout = setTimeout(later, wait);
27            };
28        },
29        throttle(func, limit) {
30            let inThrottle;
31            return function () {
32                const args = arguments;
33                const context = this;
34                if (!inThrottle) {
35                    func.apply(context, args);
36                    inThrottle = true;
37                    setTimeout(() => inThrottle = false, limit);
38                }
39            }
40        },
41        batchDOMUpdates(callback) {
42            requestAnimationFrame(callback);
43        },
44        createFragment() {
45            return document.createDocumentFragment();
46        }
47    };
48    const InputSecurity = {
49        cache: new Map(),
50        sanitizeHTML(input) {
51            if (this.cache.has(input)) return this.cache.get(input);
52            const div = document.createElement('div');
53            div.textContent = input;
54            const result = div.innerHTML;
55            if (this.cache.size > 100) this.cache.clear();
56            this.cache.set(input, result);
57            return result;
58        },
59        validateEmail(email) {
60            const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
61            return emailRegex.test(email) && email.length <= 254;
62        },
63        sanitizeSearchInput(input) {
64            return input.replace(/[<>\"'&]/g, '').replace(/javascript:/gi, '').replace(/on\w+=/gi, '').substring(0, 100);
65        },
66        validateFormInput(input, type) {
67            const sanitized = this.sanitizeHTML(input.trim());
68            switch (type) {
69                case 'email':
70                    return this.validateEmail(sanitized) ? sanitized : null;
71                case 'name':
72                    return sanitized.length > 0 && sanitized.length <= 50 ? sanitized : null;
73                case 'message':
74                    return sanitized.length > 0 && sanitized.length <= 1000 ? sanitized : null;
75                case 'search':
76                    return this.sanitizeSearchInput(sanitized);
77                default:
78                    return sanitized;
79            }
80        }
81    };
82    const RateLimit = {
83        storage: new Map(),
84        cleanOldEntries() {
85            const now = Date.now();
86            for (const [key, data] of this.storage.entries()) {
87                if (now > data.resetTime) {
88                    this.storage.delete(key);
89                }
90            }
91        },
92        isAllowed(action, identifier = 'global') {
93            if (this.storage.size > 50) this.cleanOldEntries();
94            const key = `${action}_${identifier}`;
95            const now = Date.now();
96            const config = (typeof SecurityConfig !== 'undefined' && SecurityConfig.rateLimits[action]) ? SecurityConfig.rateLimits[action] : {
97                attempts: 50,
98                window: 60000
99            };
100            if (!config) return true;
101            if (!this.storage.has(key)) {
102                this.storage.set(key, {
103                    count: 1,
104                    resetTime: now + config.window
105                });
106                return true;
107            }
108            const data = this.storage.get(key);
109            if (now > data.resetTime) {
110                this.storage.set(key, {
111                    count: 1,
112                    resetTime: now + config.window
113                });
114                return true;
115            }
116            if (data.count >= config.attempts) return false;
117            data.count++;
118            return true;
119        }
120    };
121    const SecureStorage = {
122        prefix: 'aitools_',
123        setItem(key, value) {
124            try {
125                localStorage.setItem(this.prefix + key, JSON.stringify(value));
126            } catch (e) {
127                console.warn('Storage limit exceeded');
128            }
129        },
130        getItem(key) {
131            try {
132                const item = localStorage.getItem(this.prefix + key);
133                return item ? JSON.parse(item) : null;
134            } catch (e) {
135                return null;
136            }
137        },
138        removeItem(key) {
139            try {
140                localStorage.removeItem(this.prefix + key);
141            } catch (e) {
142                console.warn("Could not remove item from storage.");
143            }
144        }
145    };
146
147    const loadMoreArrowSVG = `<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>`;
148
149    function getAllTools() {
150        if (getAllTools.cache) return getAllTools.cache;
151        let allTools = [];
152        for (const category in ToolsData.tools) {
153            allTools = allTools.concat(ToolsData.tools[category]);
154        }
155        getAllTools.cache = allTools;
156        return allTools;
157    }
158
159    function getToolsByCategory(category) {
160        if (category === 'all') return getAllTools();
161        if (category in ToolsData.tools) return ToolsData.tools[category];
162        return getAllTools().filter(tool => tool.categories.includes(category));
163    }
164
165    function getToolsByTab(tab) {
166        if (tab === 'favorites') return getFavoriteTools();
167        if (tab === 'all') return getAllTools();
168        return getAllTools().filter(tool => tool.tab && tool.tab.includes(tab));
169    }
170
171    function getFavoriteTools() {
172        const favorites = getFavorites();
173        return getAllTools().filter(tool => favorites.includes(tool.id));
174    }
175
176    function getCategoryDisplayName(category) {
177        // (Mapping kept as original)
178        const enMap = {
179            'text': 'Writing',
180            'ai': 'AI',
181            'image': 'Images',
182            'art': 'Art',
183            'design': 'Design',
184            'code': 'Coding',
185            'development': 'Development',
186            'video': 'Video',
187            'content': 'Content',
188            'audio': 'Audio',
189            'docs': 'Documents',
190            'marketing': 'Marketing',
191            'creation': 'Content Creation',
192            'productivity': 'Productivity',
193            'voice': 'Voice',
194            'ocr': 'OCR',
195            'data': 'Data',
196            'writing': 'Writing',
197            'translation': 'Translation',
198            'gaming': 'Gaming',
199            'ecommerce': 'eCommerce',
200            'editing': 'Editing',
201            'seo': 'SEO',
202            'ide': 'IDE',
203            'web': 'Web',
204            'business': 'Business',
205            'social': 'Social Media',
206            'streaming': 'Streaming',
207            'podcast': 'Podcast',
208            'music': 'Music',
209            'research': 'Research',
210            'education': 'Education',
211            'testing': 'Testing',
212            'ui/ux': 'UI/UX',
213            '3d': '3D'
214        };
215        const arMap = {
216            'text': 'كتابة',
217            'ai': 'AI',
218            'image': 'صور',
219            'art': 'فن',
220            'design': 'تصميم',
221            'code': 'برمجة',
222            'development': 'تطوير',
223            'video': 'فيديو',
224            'content': 'محتوى',
225            'audio': 'صوت',
226            'docs': 'مستندات',
227            'marketing': 'تسويق',
228            'creation': 'إبداع',
229            'productivity': 'إنتاجية',
230            'voice': 'صوت',
231            'ocr': 'OCR',
232            'data': 'بيانات',
233            'writing': 'كتابة',
234            'translation': 'ترجمة',
235            'gaming': 'ألعاب',
236            'ecommerce': 'تجارة إلكترونية',
237            'editing': 'تحرير',
238            'seo': 'SEO',
239            'ide': 'IDE',
240            'web': 'ويب',
241            'business': 'أعمال',
242            'social': 'سوشيال ميديا',
243            'streaming': 'بث',
244            'podcast': 'بودكاست',
245            'music': 'موسيقى',
246            'research': 'بحث',
247            'education': 'تعليم',
248            'testing': 'اختبار',
249            'ui/ux': 'UI/UX',
250            '3d': '3D'
251        };
252        const map = currentLang === 'ar' ? arMap : enMap;
253        return map[category] || category.charAt(0).toUpperCase() + category.slice(1);
254    }
255
256    function createToolCard(tool, delay = 0) {
257        const subscriptions = tool.subscription.split(',');
258        const categories = tool.categories.split(',');
259        const favorites = getFavorites();
260        const isFavorite = favorites.includes(tool.id);
261
262        const article = document.createElement('article');
263        article.className = 'tool-card fade-in';
264        Object.assign(article.dataset, {
265            toolId: tool.id,
266            delay: delay
267        });
268
269        const cardContent = document.createElement('div');
270        cardContent.className = 'tool-card-content p-5 flex flex-col h-full';
271
272        // --- [START] Header: Circular Logo + Title + Favorite ---
273        const header = document.createElement('header');
274        header.className = 'flex items-center gap-4 mb-5 relative';
275
276        // 1. حاوية اللوجو الدائري
277        const logoContainer = document.createElement('div');
278        logoContainer.className = 'w-14 h-14 flex-shrink-0 rounded-full overflow-hidden border-2 border-white dark:border-gray-700 shadow-md flex items-center justify-center bg-primary/10';
279
280        const logoImg = document.createElement('img');
281        logoImg.className = 'w-full h-full object-cover'; // لضمان ملء الدائرة بشكل مثالي
282        logoImg.alt = tool.name;
283
284        // منطق اختيار الصورة: يدوية أو حروف
285        if (tool.logo && tool.logo.trim() !== "") {
286            // إذا أضفت مسار صورة في ملف البيانات مثل "img/atoms.png"
287            logoImg.src = tool.logo;
288        } else {
289            // إذا لم تضف صورة، يتم إنشاء دائرة ملونة بداخلها أول حروف الاسم
290            // نستخدم خدمة ui-avatars لتوليد الحروف بشكل دائري واحترافي
291            logoImg.src = `https://ui-avatars.com/api/?name=${encodeURIComponent(tool.name)}&background=7e22ce&color=fff&bold=true&rounded=true&size=128`;
292        }
293
294        // في حالة حدوث خطأ في تحميل الصورة اليدوية، يتم الرجوع للحروف
295        logoImg.onerror = function () {
296            this.src = `https://ui-avatars.com/api/?name=${encodeURIComponent(tool.name)}&background=7e22ce&color=fff&bold=true&rounded=true`;
297        };
298
299        logoContainer.appendChild(logoImg);
300
301        // 2. حاوية العنوان
302        const titleAndBadges = document.createElement('div');
303        titleAndBadges.className = 'flex-grow min-w-0 pr-10'; // مساحة لزر القلب
304
305        const title = document.createElement('h3');
306        title.className = 'tool-card-title text-xl font-bold truncate text-gray-800 dark:text-white';
307        title.textContent = tool.name;
308        titleAndBadges.appendChild(title);
309
310        // 3. زر المفضلة (يبقى في مكانه بالأعلى)
311        const favButton = document.createElement('button');
312        favButton.className = `favorites-badge absolute top-0 right-0 ${isFavorite ? 'active' : ''}`;
313        favButton.dataset.toolId = tool.id;
314        const favIcon = document.createElement('i');
315        favIcon.className = `fa-${isFavorite ? 'solid' : 'regular'} fa-heart`;
316        favButton.appendChild(favIcon);
317
318        header.append(logoContainer, titleAndBadges, favButton);
319        // --- [END] Header ---
320
321        // وصف الأداة
322        const description = document.createElement('p');
323        description.className = 'tool-card-description text-gray-500 dark:text-gray-400 text-sm mb-6 line-clamp-3 leading-relaxed';
324        description.textContent = tool.description;
325
326        // التصنيفات والأسعار
327        // التصنيفات والأسعار (تنسيق محسن)
328        const tagsContainer = document.createElement('div');
329        tagsContainer.className = 'flex flex-wrap items-center justify-between gap-3 mt-auto mb-6';
330
331        // 1. حاوية السعر (FREEMIUM / PAID / FREE)
332        const pricingDiv = document.createElement('div');
333        pricingDiv.className = 'flex gap-2';
334        subscriptions.forEach(sub => {
335            const span = document.createElement('span');
336            // أضفنا shadow خفيف و جعلنا الخط أغمق قليلاً للوضوح
337            span.className = `pricing-tag px-3 py-1 rounded-full text-[10px] font-black uppercase shadow-sm tag-${sub.trim()}`;
338            span.textContent = UIStrings[sub.trim()] || sub.trim();
339            pricingDiv.appendChild(span);
340        });
341
342        // 2. حاوية التصنيف (Coding / AI / إلخ) بجانبها كـ Badge
343        const categoryDiv = document.createElement('div');
344        categoryDiv.className = 'flex gap-2';
345        categories.slice(0, 1).forEach(cat => {
346            const span = document.createElement('span');
347            // جعلنا التصنيف يبدو كـ Badge احترافي بخلفية خفيفة
348            span.className = 'text-[10px] font-bold px-3 py-1 bg-primary/20 text-primary-light border border-primary/30 rounded-full dark:text-purple-300';
349            span.textContent = getCategoryDisplayName(cat.trim());
350            categoryDiv.appendChild(span);
351        });
352
353        tagsContainer.append(pricingDiv, categoryDiv);
354
355        tagsContainer.append(pricingDiv, categoryDiv);
356
357        // --- [START] قسم الأزرار المطور (زيارة + مقال) ---
358        const footer = document.createElement('footer');
359        footer.className = 'tool-card-actions mt-auto flex gap-2'; // جعلنا الحاوية مرنة لتستوعب زرين
360
361        // 1. زر زيارة الموقع (دائماً موجود)
362        const visitLink = document.createElement('a');
363        visitLink.className = 'tool-action-btn btn-primary flex-grow flex items-center justify-center gap-2 py-3 rounded-xl font-bold transition-all text-sm active:scale-95';
364        visitLink.href = tool.details?.website || '#';
365        visitLink.target = '_blank';
366        visitLink.rel = 'noopener noreferrer';
367        visitLink.innerHTML = `<span>${UIStrings.visitWebsite}</span> <i class="fas fa-external-link-alt text-[10px]"></i>`;
368
369        // 2. زر المقال (يظهر فقط إذا أضفت سطر article في بيانات الأداة)
370        if (tool.article && tool.article.trim() !== "") {
371            const articleLink = document.createElement('a');
372            // ستايل ثانوي (Secondary) ليكون مختلفاً عن زر الزيارة
373            articleLink.className = 'tool-action-btn btn-secondary flex-grow flex items-center justify-center gap-2 py-3 rounded-xl font-bold transition-all text-sm border border-primary/20 bg-primary/5 text-primary dark:text-purple-300 hover:bg-primary/10 active:scale-95';
374            articleLink.href = tool.article;
375            // يمكنك تغيير الأيقونة لما يناسبك
376            articleLink.innerHTML = `<i class="fas fa-book-open text-[10px]"></i> <span>${UIStrings.readArticle}</span>`;
377
378            // ترتيب الأزرار: المقال على اليسار والزيارة على اليمين (أو العكس كما تحب)
379            footer.append(articleLink, visitLink);
380        } else {
381            // إذا لم يوجد مقال، الزر الوحيد يأخذ العرض الكامل
382            visitLink.classList.add('w-full');
383            footer.append(visitLink);
384        }
385
386        cardContent.append(header, description, tagsContainer, footer);
387        article.appendChild(cardContent);
388        return article;
389    } // نهاية الدالة هنا
390    function showNotification(message, type = 'success') {
391        const container = document.getElementById('notification-container');
392        if (!container) return;
393        const bgColor = type === 'success' ? 'bg-secondary' : 'bg-red-500';
394        PerformanceUtils.batchDOMUpdates(() => {
395            const notification = document.createElement('div');
396            notification.className = `notification ${bgColor} text-white px-4 py-2 rounded-lg shadow-lg mb-2`;
397            notification.textContent = InputSecurity.sanitizeHTML(message);
398            notification.setAttribute('role', 'alert');
399            container.appendChild(notification);
400            setTimeout(() => {
401                notification.remove();
402            }, 3300);
403        });
404    }
405
406    function openModal(modal) {
407        if (!modal || modal.classList.contains('active')) return;
408        document.body.classList.add('modal-open');
409        modal.classList.add('active');
410    }
411
412    function closeModal(modal) {
413        if (!modal || !modal.classList.contains('active')) return;
414        modal.classList.remove('active');
415        if (!document.querySelector('.modal.active, #favorites-modal.active, #newsletter-modal-overlay.active')) {
416            document.body.classList.remove('modal-open');
417        }
418    }
419
420    function displayFavorites() {
421        const favoritesList = document.getElementById('favorites-list');
422        if (!favoritesList) return;
423        const favoriteToolIds = getFavorites();
424        const favoriteTools = getAllTools().filter(tool => favoriteToolIds.includes(tool.id));
425        if (favoriteTools.length === 0) {
426            favoritesList.innerHTML = `<div class="text-center py-12 text-gray-400"><i class="fas fa-heart-broken text-5xl mb-4 text-gray-500"></i><p class="font-semibold text-lg">${UIStrings.noFavorites}</p><p class="text-sm mt-2">${UIStrings.noFavoritesHint}</p></div>`;
427            return;
428        }
429        const categoryIcons = {
430            'image': 'fas fa-image',
431            'text': 'fas fa-edit',
432            'code': 'fas fa-code',
433            'video': 'fas fa-video-camera',
434            'audio': 'fas fa-microphone-alt',
435            'docs': 'fas fa-file-alt',
436            'ai': 'fas fa-robot',
437            'art': 'fas fa-palette',
438            'design': 'fas fa-drafting-compass',
439            'development': 'fas fa-laptop-code',
440            'content': 'fas fa-newspaper',
441            'marketing': 'fas fa-bullhorn',
442            'creation': 'fas fa-magic',
443            'productivity': 'fas fa-tasks',
444            'voice': 'fas fa-volume-up',
445            'ocr': 'fas fa-scanner',
446            'data': 'fas fa-database',
447            'writing': 'fas fa-edit'
448        };
449        favoritesList.innerHTML = favoriteTools.map(tool => {
450            const primaryCategory = tool.categories.split(',')[0];
451            const iconClass = categoryIcons[primaryCategory] || 'fas fa-robot';
452            // === [FIX 2] Force English Links for Favorites ===
453           // التعديل: فتح رابط الأداة من المفضلة
454const articleUrl = tool.details && tool.details.website ? tool.details.website : '#';
455
456            return `<div class="favorite-item"><div class="favorite-item-icon"><i class="${iconClass}"></i></div><div class="favorite-item-details"><h4 class="favorite-item-title">${InputSecurity.sanitizeHTML(tool.name)}</h4><p class="favorite-item-description">${InputSecurity.sanitizeHTML(tool.description)}</p></div><div class="favorite-item-actions"><a href="${articleUrl}" target="_blank" class="favorite-action-button favorite-action-view" title="${UIStrings.viewDetails}"><i class="fas fa-eye"></i></a><button class="favorite-action-button favorite-action-delete" data-tool-id="${tool.id}" title="${UIStrings.remove}">
456<i class="fas fa-trash-alt"></i></button></div></div>`;
457        }).join('');
458    }
459
460    function initializeEventListeners() {
461        const favoritesModal = document.getElementById('favorites-modal');
462        const newsletterModal = document.getElementById('newsletter-modal-overlay');
463
464        document.querySelectorAll('.modal, #favorites-modal, #newsletter-modal-overlay').forEach(modal => {
465            if (modal) {
466                modal.addEventListener('click', function (e) {
467                    if (e.target === this || e.target.closest('.close-modal, #close-favorites-modal, #modal-close-btn')) {
468                        closeModal(this);
469                    }
470                });
471            }
472        });
473
474        const favBtn = document.getElementById('favorites-btn');
475        if (favBtn) {
476            favBtn.addEventListener('click', () => {
477                displayFavorites();
478                openModal(favoritesModal);
479            });
480        }
481
482        const favMobileBtn = document.getElementById('favorites-mobile');
483        if (favMobileBtn) {
484            favMobileBtn.addEventListener('click', (e) => {
485                e.preventDefault();
486                displayFavorites();
487                openModal(favoritesModal);
488                const mobileMenu = document.getElementById('mobile-menu');
489                const mobileMenuToggle = document.getElementById('mobile-menu-toggle');
490                if (mobileMenu && mobileMenuToggle) {
491                    mobileMenu.classList.add('hidden');
492                    mobileMenuToggle.setAttribute('aria-expanded', 'false');
493                }
494            });
495        }
496
497        if (favoritesModal) {
498            favoritesModal.addEventListener('click', (e) => {
499                const removeBtn = e.target.closest('.favorite-action-delete');
500                if (removeBtn) {
501                    const toolId = removeBtn.getAttribute('data-tool-id');
502                    toggleFavorite(toolId);
503                    displayFavorites();
504                    updateFavoritesCount();
505                    const cardBadge = document.querySelector(`.tool-card .favorites-badge[data-tool-id="${toolId}"]`);
506                    if (cardBadge) {
507                        cardBadge.classList.remove('active');
508                        cardBadge.querySelector('i').className = 'fa-regular fa-heart';
509                    }
510                }
511            });
512        }
513
514        document.addEventListener('keydown', (e) => {
515            if (e.key === 'Escape') {
516                document.querySelectorAll('.modal.active, #favorites-modal.active, #newsletter-modal-overlay.active').forEach(closeModal);
517            }
518        });
519
520        const searchInput = document.getElementById('search-input');
521        if (searchInput) {
522            searchInput.addEventListener('input', function () {
523                if (!RateLimit.isAllowed('search')) {
524                    showNotification(UIStrings.rateLimitExceeded);
525                    return;
526                }
527                showSearchSuggestions(this.value.trim());
528            });
529            searchInput.addEventListener('keydown', handleSuggestionKeyboard);
530            searchInput.addEventListener('focus', function () {
531                if (this.value.trim().length > 0) {
532                    showSearchSuggestions(this.value.trim());
533                }
534            });
535            document.addEventListener('click', function (e) {
536                if (!searchInput.closest('.search-container').contains(e.target)) {
537                    hideSuggestions();
538                }
539            }, {
540                passive: true
541            });
542        }
543
544        setupAdvancedSearchToggle();
545        setupFilterHandlers();
546        setupTabHandlers();
547        setupFormHandlers();
548        setupNavigationHandlers();
549        setupUtilityHandlers();
550        setupThemeHandler();
551        initializeNewsletterModal(); // Calling the updated function
552        const throttledScrollHandler = PerformanceUtils.throttle(() => {
553            updateBackToTopButton();
554            checkFadeElements();
555        }, 100);
556        window.addEventListener('scroll', throttledScrollHandler, {
557            passive: true
558        });
559        updateFavoritesCount();
560        displayTotalToolCount();
561    }
562
563    function toggleFavorite(toolId) {
564        let favorites = getFavorites();
565        const index = favorites.indexOf(toolId);
566        if (index === -1) {
567            favorites.push(toolId);
568        } else {
569            favorites.splice(index, 1);
570        }
571        SecureStorage.setItem('favorites', favorites);
572    }
573
574    function getFavorites() {
575        return SecureStorage.getItem('favorites') || [];
576    }
577
578    function updateFavoritesCount() {
579        const favoritesCount = getFavorites().length;
580        const countElement = document.getElementById('favorites-count');
581        if (countElement) {
582            if (favoritesCount > 0) {
583                countElement.textContent = favoritesCount;
584                countElement.classList.remove('hidden');
585            } else {
586                countElement.classList.add('hidden');
587            }
588        }
589    }
590
591    let currentSearchTimeout = null;
592    let searchCache = new Map();
593
594    function performSmartSearch(searchTerm) {
595        const cacheKey = searchTerm;
596        if (searchCache.has(cacheKey)) {
597            return searchCache.get(cacheKey);
598        }
599        if (!searchTerm || searchTerm.length === 0) {
600            return [];
601        }
602        searchTerm = InputSecurity.sanitizeSearchInput(searchTerm.toLowerCase().trim());
603        const allTools = getAllTools();
604        const results = [];
605        allTools.forEach(tool => {
606            let score = 0;
607            const toolName = tool.name.toLowerCase();
608            const toolDesc = tool.description.toLowerCase();
609            const toolCategories = tool.categories.toLowerCase();
610            const toolKeywords = tool.keywords ? tool.keywords.map(k => k.toLowerCase()) : [];
611            if (toolName.includes(searchTerm)) {
612                score += toolName.startsWith(searchTerm) ? 500 : 300;
613            }
614            if (toolDesc.includes(searchTerm)) {
615                score += toolDesc.startsWith(searchTerm) ? 200 : 100;
616            }
617            toolKeywords.forEach(keyword => {
618                if (keyword.includes(searchTerm)) {
619                    score += 400;
620                }
621            });
622            if (toolCategories.includes(searchTerm)) {
623                score += 150;
624            }
625            if (score > 0) {
626                results.push({
627                    tool,
628                    score
629                });
630            }
631        });
632        const sortedResults = results.sort((a, b) => b.score - a.score).slice(0, 12).map(result => result.tool);
633        if (searchCache.size > 50) {
634            searchCache.clear();
635        }
636        searchCache.set(cacheKey, sortedResults);
637        return sortedResults;
638    }
639
640    function showSearchSuggestions(searchTerm) {
641        const suggestionsContainer = document.getElementById('search-suggestions');
642        const searchIcon = document.getElementById('search-icon');
643        const searchLoading = document.getElementById('search-loading');
644        if (!searchTerm || searchTerm.trim().length === 0) {
645            suggestionsContainer.classList.add('hidden');
646            suggestionsContainer.classList.remove('visible');
647            searchLoading.classList.add('hidden');
648            searchIcon.classList.remove('hidden');
649            return;
650        }
651        searchIcon.classList.add('hidden');
652        searchLoading.classList.remove('hidden');
653        if (currentSearchTimeout) {
654            clearTimeout(currentSearchTimeout);
655        }
656        currentSearchTimeout = setTimeout(() => {
657            const matchingTools = performSmartSearch(searchTerm.trim());
658            PerformanceUtils.batchDOMUpdates(() => {
659                searchLoading.classList.add('hidden');
660                searchIcon.classList.remove('hidden');
661                suggestionsContainer.innerHTML = '';
662                if (matchingTools.length === 0) {
663                    const emptyDiv = document.createElement('div');
664                    emptyDiv.className = 'suggestion-empty';
665                    emptyDiv.innerHTML = `<i class="fas fa-search"></i><div>${UIStrings.noResults}</div><div style="font-size: 12px; margin-top: 4px;">${UIStrings.noResultsHint}</div>`;
666                    suggestionsContainer.appendChild(emptyDiv);
667                } else {
668                    const fragment = PerformanceUtils.createFragment();
669                    matchingTools.forEach((tool, index) => {
670                        fragment.appendChild(createAdvancedSuggestionItem(tool, searchTerm, index));
671                    });
672                    suggestionsContainer.appendChild(fragment);
673                }
674                suggestionsContainer.classList.remove('hidden');
675                requestAnimationFrame(() => {
676                    suggestionsContainer.classList.add('visible');
677                });
678            });
679        }, 200);
680    }
681
682    function createAdvancedSuggestionItem(tool, searchTerm, index) {
683        const div = document.createElement('div');
684        div.className = 'suggestion-item';
685        div.dataset.toolId = tool.id;
686        div.dataset.index = index;
687        div.setAttribute('role', 'option');
688        div.setAttribute('tabindex', '-1');
689        const primaryCategory = tool.categories.split(',')[0];
690        const categoryDisplayName = getCategoryDisplayName(primaryCategory);
691        const categoryIcons = {
692            'image': 'fas fa-image',
693            'text': 'fas fa-pen',
694            'code': 'fas fa-code',
695            'video': 'fas fa-video',
696            'audio': 'fas fa-microphone',
697            'docs': 'fas fa-file-alt',
698            'ai': 'fas fa-brain',
699            'art': 'fas fa-palette',
700            'design': 'fas fa-drafting-compass',
701            'development': 'fas fa-laptop-code',
702            'content': 'fas fa-newspaper',
703            'marketing': 'fas fa-bullhorn',
704            'creation': 'fas fa-magic',
705            'productivity': 'fas fa-tasks',
706            'voice': 'fas fa-volume-up',
707            'ocr': 'fas fa-scanner',
708            'data': 'fas fa-database',
709            'writing': 'fas fa-pen'
710        };
711        const iconClass = categoryIcons[primaryCategory] || 'fas fa-cog';
712        const highlightedName = highlightSearchMatch(tool.name, searchTerm);
713        const highlightedDesc = highlightSearchMatch(tool.description, searchTerm);
714        let subscriptionBadge = '';
715        const subscriptions = tool.subscription.split(',');
716        if (subscriptions.includes('free') && !subscriptions.includes('paid')) {
717            subscriptionBadge = `<span style="background: #22c55e; color: white; padding: 2px 6px; border-radius: 10px; font-size: 10px; margin-right: 6px;">${UIStrings.free}</span>`;
718        } else if (subscriptions.includes('paid') && !subscriptions.includes('free')) {
719            subscriptionBadge = `<span style="background: #ef4444; color: white; padding: 2px 6px; border-radius: 10px; font-size: 10px; margin-right: 6px;">${UIStrings.paid}</span>`;
720        } else if (subscriptions.includes('freemium')) {
721            subscriptionBadge = `<span style="background: #f59e0b; color: white; padding: 2px 6px; border-radius: 10px; font-size: 10px; margin-right: 6px;">${UIStrings.freemium}</span>`;
722        }
723        div.innerHTML = `<div class="suggestion-icon"><i class="${iconClass}"></i></div><div class="suggestion-content"><div class="suggestion-title">${highlightedName}</div><div class="suggestion-description">${highlightedDesc.substring(0, 80)}${highlightedDesc.length > 80 ? '...' : ''}</div><div style="display: flex; align-items: center; margin-top: 4px;">${subscriptionBadge}<span class="suggestion-category">${InputSecurity.sanitizeHTML(categoryDisplayName)}</span></div></div>`;
724        div.addEventListener('mouseenter', function () {
725            document.querySelectorAll('.suggestion-item.selected').forEach(item => {
726                item.classList.remove('selected');
727            });
728            this.classList.add('selected');
729        });
730        div.addEventListener('click', function (e) {
731            e.preventDefault();
732            e.stopPropagation();
733            hideSuggestions();
734            
735            // === التعديل الجديد: جلب رابط الأداة وفتحه مباشرة ===
736            const toolWebsite = tool.details && tool.details.website ? tool.details.website : '#';
737            window.open(toolWebsite, '_blank', 'noopener,noreferrer');
738            
739            if (typeof gtag !== 'undefined') {
740                gtag('event', 'search_suggestion_click', {
741                    'search_term': searchTerm,
742                    'tool_name': tool.name,
743                    'position': index + 1
744                });
745            }
746        });
747        return div;
748    }
749
750    function highlightSearchMatch(text, searchTerm) {
751        if (!searchTerm || searchTerm.length === 0) return InputSecurity.sanitizeHTML(text);
752        const sanitizedText = InputSecurity.sanitizeHTML(text);
753        const regex = new RegExp(`(${searchTerm.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')})`, 'gi');
754        return sanitizedText.replace(regex, '<span class="suggestion-highlight">$1</span>');
755    }
756
757    function hideSuggestions() {
758        const suggestionsContainer = document.getElementById('search-suggestions');
759        suggestionsContainer.classList.remove('visible');
760        setTimeout(() => {
761            suggestionsContainer.classList.add('hidden');
762        }, 300);
763    }
764
765    function handleSuggestionKeyboard(e) {
766        const suggestionsContainer = document.getElementById('search-suggestions');
767        const suggestionItems = suggestionsContainer.querySelectorAll('.suggestion-item');
768        if (suggestionItems.length === 0) return;
769        const currentSelected = suggestionsContainer.querySelector('.suggestion-item.selected');
770        let newIndex = -1;
771        switch (e.key) {
772            case 'ArrowDown':
773                e.preventDefault();
774                if (currentSelected) {
775                    const currentIndex = parseInt(currentSelected.getAttribute('data-index'));
776                    newIndex = Math.min(currentIndex + 1, suggestionItems.length - 1);
777                } else {
778                    newIndex = 0;
779                }
780                break;
781            case 'ArrowUp':
782                e.preventDefault();
783                if (currentSelected) {
784                    const currentIndex = parseInt(currentSelected.getAttribute('data-index'));
785                    newIndex = Math.max(currentIndex - 1, 0);
786                } else {
787                    newIndex = suggestionItems.length - 1;
788                }
789                break;
790            case 'Enter':
791                e.preventDefault();
792                if (currentSelected) {
793                    currentSelected.click();
794                } else {
795                    performSearch();
796                }
797                return;
798            case 'Escape':
799                hideSuggestions();
800                return;
801            case 'Tab':
802                if (currentSelected) {
803                    e.preventDefault();
804                    const searchInput = document.getElementById('search-input');
805                    const toolId = currentSelected.getAttribute('data-tool-id');
806                    const tool = getAllTools().find(t => t.id === toolId);
807                    if (tool) {
808                        searchInput.value = tool.name;
809                        hideSuggestions();
810                    }
811                }
812                return;
813        }
814        if (newIndex >= 0) {
815            suggestionItems.forEach(item => item.classList.remove('selected'));
816            suggestionItems[newIndex].classList.add('selected');
817            suggestionItems[newIndex].scrollIntoView({
818                block: 'nearest',
819                behavior: 'smooth'
820            });
821        }
822    }
823
824    function initializeTools() {
825        filterByTab('all');
826    }
827
828    function setupUtilityHandlers() {
829        document.getElementById('load-more-tools').addEventListener('click', function () {
830            loadMoreTools(this);
831        });
832        document.addEventListener('click', function (e) {
833            if (e.target.closest('.favorites-badge')) {
834                handleFavoriteToggle(e.target.closest('.favorites-badge'));
835            }
836        });
837        const mobileMenuToggle = document.getElementById('mobile-menu-toggle');
838        const mobileMenu = document.getElementById('mobile-menu');
839        mobileMenuToggle.addEventListener('click', function () {
840            const isHidden = mobileMenu.classList.contains('hidden');
841            mobileMenu.classList.toggle('hidden');
842            this.setAttribute('aria-expanded', isHidden ? 'true' : 'false');
843        });
844    }
845
846    // --- NEWSLETTER LOGIC (FIXED) ---
847    function initializeNewsletterModal() {
848        const overlay = document.getElementById('newsletter-modal-overlay');
849        const form = document.getElementById('newsletter-modal-form');
850
851        const toggleModal = (show) => {
852            if (!overlay) return;
853            if (show) {
854                document.body.classList.add('modal-open');
855                overlay.style.display = 'flex';
856                requestAnimationFrame(() => {
857                    overlay.classList.add('active');
858                    overlay.style.opacity = '1';
859                });
860            } else {
861                overlay.style.opacity = '0';
862                overlay.classList.remove('active');
863                setTimeout(() => {
864                    overlay.style.display = 'none';
865                    document.body.classList.remove('modal-open');
866                }, 300);
867
868                const currentState = SecureStorage.getItem('newsletterState');
869                if (currentState !== 'subscribed') {
870                    SecureStorage.setItem('newsletterState', 'closed');
871                    SecureStorage.setItem('newsletterClosedTimestamp', Date.now());
872                }
873            }
874        };
875
876        // Event Delegation for Buttons
877        document.addEventListener('click', (e) => {
878            if (e.target.closest('#newsletter-btn, #newsletter-btn-mobile, #open-newsletter-modal-btn')) {
879                e.preventDefault();
880                toggleModal(true);
881            }
882            if (e.target.closest('#modal-close-btn') || e.target === overlay) {
883                e.preventDefault();
884                toggleModal(false);
885            }
886        });
887
888        if (form) {
889            form.addEventListener('submit', function (e) {
890                e.preventDefault();
891                const submitBtn = document.getElementById('modal-submit-btn');
892                const originalBtnHTML = submitBtn.innerHTML;
893                submitBtn.innerHTML = `<div class="loading-spinner mx-auto"></div>`;
894                submitBtn.disabled = true;
895
896                fetch(form.action, {
897                    method: 'POST',
898                    body: new FormData(form),
899                    headers: {
900                        'Accept': 'application/json'
901                    }
902                }).then(response => {
903                    if (response.ok) {
904                        SecureStorage.setItem('newsletterState', 'subscribed');
905                        submitBtn.innerHTML = `<i class="fas fa-check"></i>`;
906                        showNotification(UIStrings.newsletterSuccess);
907                        form.reset();
908                        setTimeout(() => {
909                            toggleModal(false);
910                            submitBtn.innerHTML = originalBtnHTML;
911                            submitBtn.disabled = false;
912                        }, 2000);
913                    } else {
914                        throw new Error('Failed');
915                    }
916                }).catch(() => {
917                    showNotification(UIStrings.newsletterError, 'error');
918                    submitBtn.innerHTML = originalBtnHTML;
919                    submitBtn.disabled = false;
920                });
921            });
922        }
923        setupAutomaticModalTrigger(() => toggleModal(true));
924    }
925
926    function setupAutomaticModalTrigger(openModalCallback) {
927        const MODAL_COOLING_PERIOD = 7 * 24 * 60 * 60 * 1000;
928        let modalHasBeenTriggered = false;
929        const newsletterState = SecureStorage.getItem('newsletterState');
930        if (newsletterState === 'subscribed') return;
931        if (newsletterState === 'closed') {
932            const closedTimestamp = SecureStorage.getItem('newsletterClosedTimestamp');
933            if (closedTimestamp && (Date.now() - closedTimestamp < MODAL_COOLING_PERIOD)) return;
934        }
935        const triggerModal = () => {
936            if (modalHasBeenTriggered) return;
937            openModalCallback();
938            modalHasBeenTriggered = true;
939            document.body.removeEventListener('mouseout', handleExitIntent);
940        };
941        const handleExitIntent = (e) => {
942            if (e.clientY <= 0) triggerModal();
943        };
944        document.body.addEventListener('mouseout', handleExitIntent);
945        setTimeout(() => {
946            triggerModal();
947        }, 25000);
948    }
949
950    function setupAdvancedSearchToggle() {
951        const toggle = document.getElementById('advanced-search-toggle');
952        const options = document.getElementById('advanced-search-options');
953        const chevronDown = document.getElementById('chevron-down');
954        const chevronUp = document.getElementById('chevron-up');
955        toggle.addEventListener('click', function () {
956            const isOpen = options.classList.toggle('open');
957            chevronDown.classList.toggle('hidden', isOpen);
958            chevronUp.classList.toggle('hidden', !isOpen);
959            this.setAttribute('aria-expanded', isOpen);
960        });
961        document.getElementById('reset-filters').addEventListener('click', resetFilters);
962        document.getElementById('apply-filters').addEventListener('click', applyFilters);
963    }
964
965    function setupFilterHandlers() {
966        document.addEventListener('click', function (e) {
967            if (e.target.matches('.filter-btn')) {
968                handleFilterButtonClick(e.target, document.querySelectorAll('.filter-btn'));
969            }
970            if (e.target.matches('.quick-filter-btn')) {
971                handleFilterButtonClick(e.target, document.querySelectorAll('.quick-filter-btn'));
972            }
973        });
974        document.getElementById('search-input').addEventListener('keypress', e => {
975            if (e.key === 'Enter' && !document.querySelector('.suggestion-item.selected')) {
976                performSearch();
977            }
978        });
979        document.getElementById('quick-sort').addEventListener('change', e => sortTools(e.target.value));
980    }
981
982    function handleFilterButtonClick(clickedBtn, allButtons) {
983        allButtons.forEach(btn => {
984            btn.classList.remove('active');
985            btn.setAttribute('aria-pressed', 'false');
986        });
987        clickedBtn.classList.add('active');
988        clickedBtn.setAttribute('aria-pressed', 'true');
989        const category = clickedBtn.dataset.category;
990        if (clickedBtn.matches('.filter-btn')) {
991            document.getElementById('tabs-container').scrollIntoView({
992                behavior: 'smooth'
993            });
994            const matchingQuickFilter = document.querySelector(`.quick-filter-btn[data-category="${category}"]`);
995            if (matchingQuickFilter) {
996                document.querySelectorAll('.quick-filter-btn').forEach(b => {
997                    b.classList.remove('active');
998                    b.setAttribute('aria-pressed', 'false');
999                });
1000                matchingQuickFilter.classList.add('active');
1001                matchingQuickFilter.setAttribute('aria-pressed', 'true');
1002            }
1003        }
1004        filterByCategory(category);
1005    }
1006
1007    function setupTabHandlers() {
1008        document.addEventListener('click', e => {
1009            if (e.target.matches('.tab-button')) {
1010                document.querySelectorAll('.tab-button').forEach(btn => {
1011                    btn.classList.remove('active');
1012                    btn.setAttribute('aria-selected', 'false');
1013                });
1014                e.target.classList.add('active');
1015                e.target.setAttribute('aria-selected', 'true');
1016                filterByTab(e.target.dataset.tab);
1017            }
1018        });
1019    }
1020
1021    function setupFormHandlers() {
1022        document.getElementById('contact-form').addEventListener('submit', handleContactFormSubmit);
1023    }
1024
1025    function handleContactFormSubmit(e) {
1026        e.preventDefault();
1027        const form = e.target;
1028        if (!RateLimit.isAllowed('contact')) {
1029            showNotification(UIStrings.contactRateLimit, 'error');
1030            return;
1031        }
1032        const data = {
1033            name: form.name.value,
1034            email: form.email.value,
1035            subject: form.subject.value,
1036            message: form.message.value
1037        };
1038        if (!validateContactForm(data).isValid) {
1039            showNotification(UIStrings.contactInvalid, 'error');
1040            return;
1041        }
1042        const btn = form.querySelector('button[type="submit"]');
1043        const originalText = btn.textContent;
1044        btn.innerHTML = '<div class="loading-spinner mx-auto"></div>';
1045        btn.disabled = true;
1046        fetch(form.action, {
1047            method: form.method,
1048            body: new FormData(form),
1049            headers: {
1050                'Accept': 'application/json'
1051            }
1052        }).then(res => {
1053            btn.disabled = false;
1054            btn.textContent = originalText;
1055            if (res.ok) {
1056                form.reset();
1057                showNotification(UIStrings.contactSuccess);
1058            } else {
1059                showNotification(UIStrings.contactError, 'error');
1060            }
1061        }).catch(() => {
1062            btn.disabled = false;
1063            btn.textContent = originalText;
1064            showNotification(UIStrings.networkError, 'error');
1065        });
1066    }
1067
1068    function validateContactForm(data) {
1069        const valid = Object.values(data).every(val => InputSecurity.validateFormInput(val, 'name'));
1070        return {
1071            isValid: valid
1072        };
1073    }
1074
1075    function setupNavigationHandlers() {
1076        document.addEventListener('click', e => {
1077            const navLink = e.target.closest('.nav-link[href^="#"]');
1078            if (navLink) {
1079                e.preventDefault();
1080                handleNavigation(navLink.getAttribute('href').substring(1));
1081            }
1082            const footerLink = e.target.closest('.footer-link[data-target]');
1083            if (footerLink) {
1084                e.preventDefault();
1085                handleNavigation(footerLink.dataset.target);
1086            }
1087            const categoryFooterLink = e.target.closest('.category-footer-link[data-category]');
1088            if (categoryFooterLink) {
1089                e.preventDefault();
1090                navigateToCategory(categoryFooterLink.dataset.category);
1091            }
1092        });
1093    }
1094
1095    function setupThemeHandler() {
1096        document.getElementById('theme-toggle').addEventListener('click', toggleTheme);
1097        window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
1098            if (!SecureStorage.getItem('darkMode')) {
1099                document.documentElement.classList.toggle('dark', e.matches);
1100            }
1101        });
1102    }
1103
1104    function resetFilters() {
1105        document.querySelectorAll('input[type="checkbox"]').forEach(cb => cb.checked = false);
1106        document.getElementById('sort-select').value = "popular";
1107        showNotification(UIStrings.filtersReset);
1108    }
1109
1110    function applyFilters() {
1111        const subFilters = Array.from(document.querySelectorAll('.subscription-filter:checked')).map(cb => cb.value);
1112        const catFilters = Array.from(document.querySelectorAll('.category-filter:checked')).map(cb => cb.value);
1113        const featFilters = Array.from(document.querySelectorAll('.feature-filter:checked')).map(cb => cb.value);
1114        filterAndRender(subFilters, catFilters, featFilters);
1115        showNotification(UIStrings.filtersApplied);
1116        document.getElementById('advanced-search-options').classList.remove('open');
1117        document.getElementById('chevron-down').classList.remove('hidden');
1118        document.getElementById('chevron-up').classList.add('hidden');
1119        document.getElementById('advanced-search-toggle').setAttribute('aria-expanded', 'false');
1120    }
1121
1122    function filterAndRender(subscriptions = [], categories = [], features = []) {
1123        let filteredTools = getAllTools();
1124        if (subscriptions.length > 0) {
1125            filteredTools = filteredTools.filter(tool => subscriptions.some(s => tool.subscription.includes(s)));
1126        }
1127        if (categories.length > 0) {
1128            filteredTools = filteredTools.filter(tool => categories.some(c => tool.categories.includes(c)));
1129        }
1130        if (features.length > 0) {
1131            filteredTools = filteredTools.filter(tool => features.every(f => (tool.features || "").includes(f)));
1132        }
1133        renderTools(filteredTools);
1134    }
1135
1136    function toggleTheme() {
1137        const isDark = document.documentElement.classList.toggle('dark');
1138        SecureStorage.setItem('darkMode', isDark ? 'dark' : 'light');
1139        showNotification(isDark ? UIStrings.darkMode : UIStrings.lightMode);
1140    }
1141
1142    function handleFavoriteToggle(badge) {
1143        const toolId = badge.dataset.toolId;
1144        const isFavorite = getFavorites().includes(toolId);
1145        toggleFavorite(toolId);
1146        updateFavoritesCount();
1147        PerformanceUtils.batchDOMUpdates(() => {
1148            badge.classList.toggle('active', !isFavorite);
1149            badge.querySelector('i').className = `fa-${!isFavorite ? 'solid' : 'regular'} fa-heart`;
1150            badge.setAttribute('aria-label', !isFavorite ? UIStrings.removeFavorite : UIStrings.addFavorite);
1151        });
1152        if (document.querySelector('.tab-button.active')?.dataset.tab === 'favorites') {
1153            filterByTab('favorites');
1154        }
1155        showNotification(!isFavorite ? UIStrings.addedToFav : UIStrings.removedFromFav);
1156    }
1157
1158    function updateBackToTopButton() {
1159        document.getElementById('backToTopBtn').classList.toggle('visible', window.pageYOffset > 300);
1160    }
1161
1162    function navigateToCategory(category) {
1163        document.getElementById('tabs-container').scrollIntoView({
1164            behavior: 'smooth'
1165        });
1166        const quickFilter = document.querySelector(`.quick-filter-btn[data-category="${category}"]`);
1167        if (quickFilter) {
1168            document.querySelectorAll('.quick-filter-btn').forEach(b => {
1169                b.classList.remove('active');
1170                b.setAttribute('aria-pressed', 'false');
1171            });
1172            quickFilter.classList.add('active');
1173            quickFilter.setAttribute('aria-pressed', 'true');
1174        }
1175        filterByCategory(category);
1176    }
1177
1178    function updateResultsCount(count, total) {
1179        const resultsText = document.getElementById('results-count');
1180        if (resultsText) resultsText.innerHTML = `Showing <span class="font-medium text-white">${count}</span> of ${total} tools`;
1181    }
1182
1183    function performSearch() {
1184        const searchTerm = document.getElementById('search-input').value.trim().toLowerCase();
1185        if (searchTerm === '') {
1186            showNotification(UIStrings.enterSearchTerm);
1187            return;
1188        }
1189        if (!RateLimit.isAllowed('search')) {
1190            showNotification(UIStrings.rateLimitExceeded);
1191            return;
1192        }
1193        const matchingTools = performSmartSearch(searchTerm);
1194        renderTools(matchingTools);
1195        showNotification(UIStrings.searchResult.replace('{count}', matchingTools.length).replace('{term}', searchTerm));
1196        document.getElementById('tabs-container').scrollIntoView({
1197            behavior: 'smooth'
1198        });
1199        hideSuggestions();
1200    }
1201
1202    function filterByCategory(category) {
1203        let toolsToShow = getToolsByCategory(category);
1204        renderTools(toolsToShow);
1205    }
1206
1207    function filterByTab(tab) {
1208        document.querySelectorAll('.quick-filter-btn').forEach(b => {
1209            b.classList.remove('active');
1210            b.setAttribute('aria-pressed', 'false');
1211        });
1212        const allBtn = document.querySelector('.quick-filter-btn[data-category="all"]');
1213        if (allBtn) {
1214            allBtn.classList.add('active');
1215            allBtn.setAttribute('aria-pressed', 'true');
1216        }
1217        let toolsToShow = getToolsByTab(tab);
1218        renderTools(toolsToShow);
1219    }
1220
1221    function renderTools(tools) {
1222        const toolsContainer = document.getElementById('tools-container');
1223        const displayTools = tools.slice(0, 9);
1224
1225        toolsContainer.innerHTML = ''; // مسح القديم
1226
1227        if (displayTools.length === 0) {
1228            toolsContainer.innerHTML = `<div class="col-span-1 md:col-span-2 lg:col-span-3 text-center py-12 text-gray-400"><i class="fas fa-search text-5xl mb-4"></i><p class="font-semibold text-lg">${UIStrings.noFilterResults}</p></div>`;
1229        } else {
1230            const fragment = PerformanceUtils.createFragment();
1231            displayTools.forEach((tool, i) => fragment.appendChild(createToolCard(tool, (i % 3) * 100)));
1232            toolsContainer.appendChild(fragment);
1233        }
1234
1235        updateResultsCount(displayTools.length, tools.length);
1236
1237        const loadMoreBtn = document.getElementById('load-more-tools');
1238        if (tools.length <= 9) {
1239            loadMoreBtn.style.display = 'none';
1240        } else {
1241            loadMoreBtn.style.display = 'flex';
1242            loadMoreBtn.disabled = false;
1243            const remaining = tools.length - displayTools.length;
1244            const toLoad = Math.min(9, remaining);
1245            loadMoreBtn.innerHTML = `<span>${UIStrings.loadMore.replace('{count}', toLoad)}</span>${loadMoreArrowSVG}`;
1246        }
1247
1248        // === 👇 هذا هو السطر السحري للإصلاح 👇 ===
1249        // نجبر المتصفح على فحص العناصر فوراً بعد إضافتها
1250        setTimeout(() => {
1251            checkFadeElements();
1252        }, 50);
1253    }
1254
1255    function loadMoreTools(button) {
1256        button.innerHTML = '<div class="loading-spinner mx-auto"></div>';
1257        button.disabled = true;
1258        const toolsContainer = document.getElementById('tools-container');
1259        const currentCount = toolsContainer.children.length;
1260        const activeCategory = document.querySelector('.quick-filter-btn.active').dataset.category;
1261        const activeTab = document.querySelector('.tab-button.active').dataset.tab;
1262        let allFilteredTools;
1263        if (activeCategory === 'all') {
1264            allFilteredTools = getToolsByTab(activeTab);
1265        } else {
1266            const base = getToolsByCategory(activeCategory);
1267            allFilteredTools = (activeTab === 'favorites') ? base.filter(t => getFavorites().includes(t.id)) : base.filter(t => t.tab && t.tab.includes(activeTab));
1268        }
1269        const nextTools = allFilteredTools.slice(currentCount, currentCount + 9);
1270        setTimeout(() => {
1271            const fragment = PerformanceUtils.createFragment();
1272            nextTools.forEach((tool, i) => fragment.appendChild(createToolCard(tool, (i % 3) * 100)));
1273            toolsContainer.appendChild(fragment);
1274            checkFadeElements();
1275            const totalVisible = toolsContainer.children.length;
1276            updateResultsCount(totalVisible, allFilteredTools.length);
1277            if (totalVisible >= allFilteredTools.length) {
1278                button.innerHTML = `<span>${UIStrings.allLoaded}</span>`;
1279                button.disabled = true;
1280            } else {
1281                const remaining = allFilteredTools.length - totalVisible;
1282                const toLoad = Math.min(9, remaining);
1283                button.innerHTML = `<span>${UIStrings.loadMore.replace('{count}', toLoad)}</span>${loadMoreArrowSVG}`;
1284                button.disabled = false;
1285            }
1286        }, 800);
1287    }
1288
1289    function sortTools(sortMethod) {
1290        const toolCards = Array.from(document.querySelectorAll('#tools-container > .tool-card'));
1291        toolCards.sort((a, b) => {
1292            const toolA = getAllTools().find(t => t.id === a.dataset.toolId);
1293            const toolB = getAllTools().find(t => t.id === b.dataset.toolId);
1294            switch (sortMethod) {
1295                case 'newest':
1296                    return new Date(toolB.dateAdded || 0) - new Date(toolA.dateAdded || 0);
1297                case 'name-asc':
1298                    return toolA.name.localeCompare(toolB.name);
1299                default:
1300                    return 0;
1301            }
1302        });
1303        const toolsContainer = document.getElementById('tools-container');
1304        toolsContainer.innerHTML = '';
1305        toolCards.forEach(card => toolsContainer.appendChild(card));
1306    }
1307
1308    function handleNavigation(target) {
1309        const element = document.getElementById(target === 'home' ? '' : `${target}-section`);
1310        if (element) element.scrollIntoView({
1311            behavior: 'smooth'
1312        });
1313        else if (target === 'home') window.scrollTo({
1314            top: 0,
1315            behavior: 'smooth'
1316        });
1317        // === FIX 6: Force Blog Link to English ===
1318        else if (target === 'blog') window.location.href = 'blog/index.html';
1319    }
1320
1321    function startCounting() {
1322        document.querySelectorAll('.counter').forEach(counter => {
1323            const target = +counter.dataset.target;
1324            let count = 0;
1325            const inc = Math.ceil(target / 100);
1326            const update = () => {
1327                if (count < target) {
1328                    count += inc;
1329                    counter.innerText = Math.min(count, target).toLocaleStr
1329ing(currentLang === 'ar' ? 'ar-EG' : 'en-US');
1330                    requestAnimationFrame(update);
1331                } else {
1332                    counter.innerText = target.toLocaleString(currentLang === 'ar' ? 'ar-EG' : 'en-US');
1333                }
1334            };
1335            update();
1336        });
1337    }
1338
1339    function checkFadeElements() {
1340        // استخدمنا +100 بكسل لضمان ظهور العناصر حتى لو كانت في أسفل الشاشة قليلاً
1341        const triggerBottom = window.innerHeight + 100;
1342
1343        document.querySelectorAll('.fade-in:not(.appear)').forEach(el => {
1344            const boxTop = el.getBoundingClientRect().top;
1345
1346            // إذا كان العنصر ظاهراً في الشاشة (أو قريباً جداً)، أظهره فوراً
1347            if (boxTop < triggerBottom) {
1348                // إضافة تأخير بسيط بناءً على ترتيب العنصر ليعطي تأثير تتابع جميل
1349                setTimeout(() => {
1350                    el.classList.add('appear');
1351                }, el.dataset.delay || 0);
1352            }
1353        });
1354    }
1355
1356    function displayTotalToolCount() {
1357        const el = document.getElementById('total-tools-display');
1358        if (!el) return;
1359        const total = getAllTools().length;
1360        el.textContent = UIStrings.totalTools.replace('{count}', total);
1361    }
1362
1363    function generateStructuredData(tools) {
1364        const faqQuestions = {
1365            'en': [{
1366                "@type": "Question",
1367                "name": "What is the AI Tools Hub?",
1368                "acceptedAnswer": {
1369                    "@type": "Answer",
1370                    "text": "AI Tools Hub is a curated directory that collects, categorizes, and reviews the best and latest artificial intelligence tools available on the market."
1371                }
1372            }, {
1373                "@type": "Question",
1374                "name": "Are the tools listed on this site free?",
1375                "acceptedAnswer": {
1376                    "@type": "Answer",
1377                    "text": "We feature a variety of tools, including free, freemium, and paid options. Each tool card is clearly marked with its pricing model so you can easily find tools that fit your budget."
1378                }
1379            }],
1380            'ar': [{
1381                "@type": "Question",
1382                "name": "ما هو دليل أدوات الذكاء الاصطناعي؟",
1383                "acceptedAnswer": {
1384                    "@type": "Answer",
1385                    "text": "دليل أدوات الذكاء الاصطناعي هو دليل منسق يجمع ويصنف ويراجع أفضل وأحدث أدوات الذكاء الاصطناعي المتاحة في السوق."
1386                }
1387            }, {
1388                "@type": "Question",
1389                "name": "هل الأدوات المدرجة في هذا الموقع مجانية؟",
1390                "acceptedAnswer": {
1391                    "@type": "Answer",
1392                    "text": "نحن نعرض مجموعة متنوعة من الأدوات، بما في ذلك الخيارات المجانية، والفريميوم، والمدفوعة. كل بطاقة أداة تحمل علامة واضحة لنموذج التسعير الخاص بها حتى تتمكن بسهولة من العثور على الأدوات التي تناسب ميزانيتك."
1393                }
1394            }]
1395        };
1396        const faqPage = {
1397            "@context": "https://schema.org",
1398            "@type": "FAQPage",
1399            "mainEntity": faqQuestions[currentLang]
1400        };
1401        document.getElementById('faq-structured-data').textContent = JSON.stringify(faqPage);
1402        const itemList = {
1403            "@context": "https://schema.org",
1404            "@type": "ItemList",
1405            "name": "AI Tools Directory",
1406            "numberOfItems": tools.length,
1407            "itemListElement": tools.slice(0, 10).map((tool, index) => ({
1408                "@type": "ListItem",
1409                "position": index + 1,
1410                "item": {
1411                    "@type": "SoftwareApplication",
1412                    "name": tool.name,
1413                    "description": tool.description,
1414                    "url": tool.details.website,
1415                    "applicationCategory": "BusinessApplication",
1416                    "operatingSystem": "Web-based",
1417                    "brand": {
1418                        "@type": "Brand",
1419                        "name": tool.details.company
1420                    }
1421                }
1422            }))
1423        };
1424        document.getElementById('itemlist-structured-data').textContent = JSON.stringify(itemList);
1425    }
1426
1427   function initBreakingNewsTicker() {
1428        const tickerEl = document.getElementById('news-ticker');
1429        const breakingNewsSection = document.getElementById('breaking-news');
1430        if (!tickerEl || !breakingNewsSection) return;
1431        
1432        const news = ToolsData.breakingNews || [];
1433        if (news.length === 0) {
1434            breakingNewsSection.classList.add('hidden');
1435            return;
1436        }
1437        breakingNewsSection.classList.remove('hidden');
1438        
1439        // === التعديل الجديد: قراءة الرابط المباشر (url) بدلاً من مسار المقالات ===
1440        const newsItemsHTML = news.map(item => {
1441            // نستخدم item.url الذي وضعناه في البيانات، وإذا لم يوجد نضع '#'
1442            const linkUrl = item.url ? item.url : '#'; 
1443            return `<a href="${linkUrl}" class="news-ticker-item">${InputSecurity.sanitizeHTML(item.text)}</a>`;
1444        }).join('');
1445        
1446        // ننسخ المحتوى مرتين لضمان استمرار الحركة (Infinite loop)
1447        tickerEl.innerHTML = newsItemsHTML + newsItemsHTML;
1448        tickerEl.style.animationDuration = `${news.length * 15}s`;
1449    }
1450
1451    function setupBreakingNewsControls() {
1452        const btn = document.getElementById('ticker-pause-btn');
1453        const ticker = document.getElementById('news-ticker');
1454        const section = document.getElementById('breaking-news');
1455        if (!btn || !ticker || !section) return;
1456        const icon = btn.querySelector('i');
1457        btn.addEventListener('click', () => {
1458            const isPaused = section.classList.contains('paused');
1459            section.classList.toggle('paused', !isPaused);
1460            icon.className = `fas fa-${isPaused ? 'pause' : 'play'} text-xs`;
1461            ticker.style.animationPlayState = isPaused ? 'running' : 'paused';
1462            btn.setAttribute('aria-label', isPaused ? UIStrings.pauseTicker : UIStrings.playTicker);
1463        });
1464    }
1465
1466    // --- Main Execution ---
1467    getAllTools.cache = null;
1468    initializeTools();
1469    checkFadeElements();
1470    initializeEventListeners();
1471    const statsSection = document.querySelector('.counter');
1472    if (statsSection) {
1473        new IntersectionObserver((entries, observer) => {
1474            entries.forEach(entry => {
1475                if (entry.isIntersecting) {
1476                    startCounting();
1477                    observer.unobserve(entry.target);
1478                }
1479            });
1480        }, {
1481            threshold: 0.5
1482        }).observe(statsSection);
1483    }
1484    const backToTopBtn = document.getElementById('backToTopBtn');
1485    if (backToTopBtn) backToTopBtn.addEventListener('click', () => window.scrollTo({
1486        top: 0,
1487        behavior: 'smooth'
1488    }));
1489    initBreakingNewsTicker();
1490    setupBreakingNewsControls();
1491    generateStructuredData(getAllTools());
1492
1493
1494
1495
1496    // تشغيل الكود
1497    celebrate2026();
1498});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.