PageSourceSearch

https://theiqra.edu.pk/assets/js/main.js?v=af0e4f4d60

js theiqra.edu.pk collected 2026-10-04 08:10:38 UTC 16,299 bytes, 499 lines download raw bytes

1/* TheIQRA Ghost Theme - Main JavaScript */
2
3// ============================================
4// DARK MODE TOGGLE
5// ============================================
6
7(function() {
8    const STORAGE_KEY = 'theiqra-theme';
9    const DARK_CLASS = 'dark';
10
11    // Detect system preference
12    function getSystemPreference() {
13        return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
14    }
15
16    // Get theme from localStorage or system preference
17    function getTheme() {
18        const stored = localStorage.getItem(STORAGE_KEY);
19        if (stored) {
20            return stored;
21        }
22        return getSystemPreference();
23    }
24
25    // Apply theme to document
26    function applyTheme(theme) {
27        const htmlElement = document.documentElement;
28        if (theme === 'dark') {
29            htmlElement.classList.add(DARK_CLASS);
30            htmlElement.setAttribute('data-theme', 'dark');
31        } else {
32            htmlElement.classList.remove(DARK_CLASS);
33            htmlElement.removeAttribute('data-theme');
34        }
35    }
36
37    // Initialize dark mode on page load
38    function initDarkMode() {
39        const theme = getTheme();
40        applyTheme(theme);
41
42        const toggle = document.getElementById('dark-mode-toggle');
43        if (toggle) {
44            toggle.addEventListener('click', function() {
45                const currentTheme = getTheme();
46                const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
47                localStorage.setItem(STORAGE_KEY, newTheme);
48                applyTheme(newTheme);
49            });
50        }
51    }
52
53    // Listen for system preference changes
54    window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) {
55        const stored = localStorage.getItem(STORAGE_KEY);
56        if (!stored) {
57            applyTheme(e.matches ? 'dark' : 'light');
58        }
59    });
60
61    // Initialize when DOM is ready
62    if (document.readyState === 'loading') {
63        document.addEventListener('DOMContentLoaded', initDarkMode);
64    } else {
65        initDarkMode();
66    }
67})();
68
69// ============================================
70// MOBILE MENU TOGGLE
71// ============================================
72
73(function() {
74    function initMobileMenu() {
75        const mobileMenuBtn = document.getElementById('mobile-menu-btn');
76        const navMenu = document.querySelector('.nav-menu');
77
78        if (mobileMenuBtn && navMenu) {
79            mobileMenuBtn.addEventListener('click', function() {
80                mobileMenuBtn.classList.toggle('active');
81                navMenu.classList.toggle('visible');
82            });
83
84            // Close menu when clicking on a link
85            const navLinks = navMenu.querySelectorAll('a');
86            navLinks.forEach(link => {
87                link.addEventListener('click', function() {
88                    mobileMenuBtn.classList.remove('active');
89                    navMenu.classList.remove('visible');
90                });
91            });
92
93            // Close menu when clicking outside
94            document.addEventListener('click', function(e) {
95                if (!e.target.closest('.main-nav')) {
96                    mobileMenuBtn.classList.remove('active');
97                    navMenu.classList.remove('visible');
98                }
99            });
100        }
101    }
102
103    if (document.readyState === 'loading') {
104        document.addEventListener('DOMContentLoaded', initMobileMenu);
105    } else {
106        initMobileMenu();
107    }
108})();
109
110// ============================================
111// COPY CODE BUTTON
112// ============================================
113
114(function() {
115    function initCopyButtons() {
116        const codeBlocks = document.querySelectorAll('pre');
117
118        codeBlocks.forEach(block => {
119            const code = block.querySelector('code');
120            if (!code) return;
121
122            // Create copy button
123            const button = document.createElement('button');
124            button.className = 'copy-button';
125            button.textContent = 'Copy';
126            button.type = 'button';
127            button.setAttribute('aria-label', 'Copy code');
128
129            // Make pre position relative for button positioning
130            block.style.position = 'relative';
131            block.appendChild(button);
132
133            // Copy functionality
134            button.addEventListener('click', function() {
135                const text = code.textContent;
136                navigator.clipboard.writeText(text).then(function() {
137                    button.textContent = 'Copied!';
138                    button.classList.add('copied');
139
140                    // Reset button after 2 seconds
141                    setTimeout(function() {
142                        button.textContent = 'Copy';
143                        button.classList.remove('copied');
144                    }, 2000);
145                }).catch(function(err) {
146                    console.error('Failed to copy: ', err);
147                    button.textContent = 'Failed';
148                    setTimeout(function() {
149                        button.textContent = 'Copy';
150                    }, 2000);
151                });
152            });
153        });
154    }
155
156    if (document.readyState === 'loading') {
157        document.addEventListener('DOMContentLoaded', initCopyButtons);
158    } else {
159        initCopyButtons();
160    }
161})();
162
163// ============================================
164// READING PROGRESS BAR
165// ============================================
166
167(function() {
168    function initReadingProgress() {
169        const progressBar = document.getElementById('reading-progress-bar');
170        if (!progressBar) return;
171
172        window.addEventListener('scroll', function() {
173            const docHeight = document.documentElement.scrollHeight - window.innerHeight;
174            const scrolled = window.scrollY;
175            const progress = (scrolled / docHeight) * 100;
176            progressBar.style.width = progress + '%';
177        });
178    }
179
180    if (document.readyState === 'loading') {
181        document.addEventListener('DOMContentLoaded', initReadingProgress);
182    } else {
183        initReadingProgress();
184    }
185})();
186
187// ============================================
188// BACK TO TOP BUTTON
189// ============================================
190
191(function() {
192    function initBackToTop() {
193        const backToTopBtn = document.getElementById('back-to-top');
194        if (!backToTopBtn) return;
195
196        const SCROLL_THRESHOLD = 300;
197
198        window.addEventListener('scroll', function() {
199            if (window.scrollY > SCROLL_THRESHOLD) {
200                backToTopBtn.classList.add('visible');
201            } else {
202                backToTopBtn.classList.remove('visible');
203            }
204        });
205
206        backToTopBtn.addEventListener('click', function() {
207            window.scrollTo({
208                top: 0,
209                behavior: 'smooth'
210            });
211        });
212    }
213
214    if (document.readyState === 'loading') {
215        document.addEventListener('DOMContentLoaded', initBackToTop);
216    } else {
217        initBackToTop();
218    }
219})();
220
221// ============================================
222// SMOOTH SCROLL FOR ANCHOR LINKS
223// ============================================
224
225(function() {
226    function initSmoothScroll() {
227        document.addEventListener('click', function(e) {
228            const target = e.target;
229
230            // Check if it's an anchor link
231            if (target.tagName === 'A' && target.getAttribute('href').startsWith('#')) {
232                const id = target.getAttribute('href');
233                const element = document.querySelector(id);
234
235                if (element) {
236                    e.preventDefault();
237                    element.scrollIntoView({
238                        behavior: 'smooth',
239                        block: 'start'
240                    });
241
242                    // Update URL without jumping
243                    window.history.pushState(null, null, id);
244                }
245            }
246        });
247    }
248
249    if (document.readyState === 'loading') {
250        document.addEventListener('DOMContentLoaded', initSmoothScroll);
251    } else {
252        initSmoothScroll();
253    }
254})();
255
256// ============================================
257// ACTIVE TOC HIGHLIGHTING
258// ============================================
259
260(function() {
261    function initTOCHighlight() {
262        const tocLinks = document.querySelectorAll('.toc a');
263        if (tocLinks.length === 0) return;
264
265        window.addEventListener('scroll', function() {
266            let current = '';
267
268            // Find the heading currently in view
269            const headings = document.querySelectorAll('article h2, article h3, article h4');
270            headings.forEach(heading => {
271                const sectionTop = heading.offsetTop;
272                if (pageYOffset >= sectionTop - 200) {
273                    current = heading.getAttribute('id');
274                }
275            });
276
277            // Remove active class from all links
278            tocLinks.forEach(link => {
279                link.classList.remove('active');
280                if (link.getAttribute('href') === '#' + current) {
281                    link.classList.add('active');
282                }
283            });
284        });
285    }
286
287    if (document.readyState === 'loading') {
288        document.addEventListener('DOMContentLoaded', initTOCHighlight);
289    } else {
290        initTOCHighlight();
291    }
292})();
293
294// ============================================
295// COPY LINK BUTTON
296// ============================================
297
298(function() {
299    function initCopyLinkButton() {
300        const copyLinkBtn = document.getElementById('copy-link-btn');
301        if (!copyLinkBtn) return;
302
303        copyLinkBtn.addEventListener('click', function() {
304            const url = window.location.href;
305            navigator.clipboard.writeText(url).then(function() {
306                const originalText = copyLinkBtn.textContent;
307                copyLinkBtn.textContent = 'Link Copied!';
308                copyLinkBtn.classList.add('copied');
309
310                setTimeout(function() {
311                    copyLinkBtn.textContent = originalText;
312                    copyLinkBtn.classList.remove('copied');
313                }, 2000);
314            }).catch(function(err) {
315                console.error('Failed to copy link: ', err);
316            });
317        });
318    }
319
320    if (document.readyState === 'loading') {
321        document.addEventListener('DOMContentLoaded', initCopyLinkButton);
322    } else {
323        initCopyLinkButton();
324    }
325})();
326
327// ============================================
328// SEARCH FUNCTIONALITY (BASIC)
329// ============================================
330
331(function() {
332    function initSearch() {
333        const searchInputs = document.querySelectorAll('#site-search, #search-input');
334        const searchModal = document.getElementById('search-modal');
335        const searchResults = document.getElementById('search-results');
336
337        if (!searchInputs.length) return;
338
339        searchInputs.forEach(input => {
340            input.addEventListener('input', function(e) {
341                const query = e.target.value.toLowerCase();
342
343                if (query.length < 2) {
344                    if (searchResults) searchResults.innerHTML = '';
345                    return;
346                }
347
348                // Basic client-side search (searches headings and paragraphs)
349                const results = [];
350                const headings = document.querySelectorAll('h1, h2, h3');
351                const paragraphs = document.querySelectorAll('p');
352
353                headings.forEach(h => {
354                    if (h.textContent.toLowerCase().includes(query)) {
355                        results.push({
356                            title: h.textContent,
357                            link: '#' + (h.id || ''),
358                            type: 'Heading'
359                        });
360                    }
361                });
362
363                if (results.length > 0 && searchResults) {
364                    searchResults.innerHTML = results.map(r =>
365                        `<div class="search-result">
366                            <a href="${r.link}">${r.title}</a>
367                            <span class="result-type">${r.type}</span>
368                        </div>`
369                    ).join('');
370                } else if (searchResults) {
371                    searchResults.innerHTML = '<p class="no-results">No results found</p>';
372                }
373            });
374        });
375    }
376
377    if (document.readyState === 'loading') {
378        document.addEventListener('DOMContentLoaded', initSearch);
379    } else {
380        initSearch();
381    }
382})();
383
384// ============================================
385// TABLE OF CONTENTS AUTO-ID ASSIGNMENT
386// ============================================
387
388(function() {
389    function initHeadingIDs() {
390        const article = document.querySelector('article');
391        if (!article) return;
392
393        const headings = article.querySelectorAll('h2, h3, h4');
394        let headingCount = {};
395
396        headings.forEach(heading => {
397            if (!heading.id) {
398                let id = heading.textContent
399                    .toLowerCase()
400                    .replace(/[^\w\s-]/g, '')
401                    .replace(/\s+/g, '-')
402                    .replace(/-+/g, '-')
403                    .replace(/^-+|-+$/g, '');
404
405                // Handle duplicate IDs
406                if (headingCount[id]) {
407                    headingCount[id]++;
408                    id += '-' + headingCount[id];
409                } else {
410                    headingCount[id] = 0;
411                }
412
413                heading.id = id;
414            }
415        });
416    }
417
418    if (document.readyState === 'loading') {
419        document.addEventListener('DOMContentLoaded', initHeadingIDs);
420    } else {
421        initHeadingIDs();
422    }
423})();
424
425// ============================================
426// KEYBOARD SHORTCUTS
427// ============================================
428
429(function() {
430    function initKeyboardShortcuts() {
431        document.addEventListener('keydown', function(e) {
432            // Ctrl+K or Cmd+K to open search
433            if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
434                e.preventDefault();
435                const searchModal = document.getElementById('search-modal');
436                const searchInput = document.getElementById('search-input');
437                if (searchModal) {
438                    searchModal.hidden = false;
439                    if (searchInput) searchInput.focus();
440                }
441            }
442
443            // Escape to close search modal
444            if (e.key === 'Escape') {
445                const searchModal = document.getElementById('search-modal');
446                if (searchModal && !searchModal.hidden) {
447                    searchModal.hidden = true;
448                }
449            }
450        });
451    }
452
453    if (document.readyState === 'loading') {
454        document.addEventListener('DOMContentLoaded', initKeyboardShortcuts);
455    } else {
456        initKeyboardShortcuts();
457    }
458})();
459
460// ============================================
461// PERFORMANCE: LAZY LOADING FOR IMAGES
462// ============================================
463
464(function() {
465    function initLazyLoading() {
466        // Modern browsers support native lazy loading
467        const images = document.querySelectorAll('img[loading="lazy"]');
468
469        // Fallback for older browsers using Intersection Observer
470        if ('IntersectionObserver' in window) {
471            const observer = new IntersectionObserver((entries, observer) => {
472                entries.forEach(entry => {
473                    if (entry.isIntersecting) {
474                        const img = entry.target;
475                        if (img.dataset.src) {
476                            img.src = img.dataset.src;
477                            img.removeAttribute('data-src');
478                            observer.unobserve(img);
479                        }
480                    }
481                });
482            });
483
484            images.forEach(img => {
485                if (img.dataset.src) {
486                    observer.observe(img);
487                }
488            });
489        }
490    }
491
492    if (document.readyState === 'loading') {
493        document.addEventListener('DOMContentLoaded', initLazyLoading);
494    } else {
495        initLazyLoading();
496    }
497})();
498
499console.log('TheIQRA Ghost Theme initialized');

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.