PageSourceSearch

https://lamountains.com/wp-content/themes/telemac-editorial/assets/js/main.js?ver=1780325053

js lamountains.com collected 2026-10-02 07:53:03 UTC 4,791 bytes, 137 lines download raw bytes

1/**
2 * Telemac Editorial - main.js
3 * Mobile menu + reading progress + TOC scrollspy
4 * Target: < 3KB minified
5 */
6(function () {
7  'use strict';
8
9  // ===== Mobile menu toggle =====
10  var toggle = document.querySelector('.te-menu-toggle');
11  var header = document.querySelector('.te-header');
12
13  if (toggle && header) {
14    toggle.addEventListener('click', function (e) {
15      e.stopPropagation();
16      var expanded = header.classList.toggle('te-nav-open');
17      toggle.setAttribute('aria-expanded', expanded);
18    });
19
20    // Close on nav link click
21    var navLinks = header.querySelectorAll('.te-nav a');
22    for (var i = 0; i < navLinks.length; i++) {
23      navLinks[i].addEventListener('click', function () {
24        header.classList.remove('te-nav-open');
25        toggle.setAttribute('aria-expanded', 'false');
26      });
27    }
28
29    // Close on outside click
30    document.addEventListener('click', function (e) {
31      if (!header.contains(e.target)) {
32        header.classList.remove('te-nav-open');
33        toggle.setAttribute('aria-expanded', 'false');
34      }
35    });
36
37    // Close on ESC
38    document.addEventListener('keydown', function (e) {
39      if (e.key === 'Escape' && header.classList.contains('te-nav-open')) {
40        header.classList.remove('te-nav-open');
41        toggle.setAttribute('aria-expanded', 'false');
42        toggle.focus();
43      }
44    });
45  }
46
47  // ===== Search overlay =====
48  var searchToggle = document.querySelector('.te-search-toggle');
49  var searchOverlay = document.getElementById('te-search-overlay');
50  if (searchToggle && searchOverlay && header) {
51    var searchInput = searchOverlay.querySelector('input[type="search"]');
52    function openSearch() {
53      header.classList.add('te-search-open');
54      searchToggle.setAttribute('aria-expanded', 'true');
55      if (searchInput) setTimeout(function () { searchInput.focus(); }, 50);
56    }
57    function closeSearch() {
58      header.classList.remove('te-search-open');
59      searchToggle.setAttribute('aria-expanded', 'false');
60    }
61    searchToggle.addEventListener('click', function (e) {
62      e.stopPropagation();
63      if (header.classList.contains('te-search-open')) closeSearch();
64      else openSearch();
65    });
66    document.addEventListener('click', function (e) {
67      if (!header.classList.contains('te-search-open')) return;
68      if (!searchOverlay.contains(e.target) && !searchToggle.contains(e.target)) closeSearch();
69    });
70    document.addEventListener('keydown', function (e) {
71      if (e.key === 'Escape' && header.classList.contains('te-search-open')) {
72        closeSearch();
73        searchToggle.focus();
74      }
75    });
76  }
77
78  // ===== Reading progress bar (single post) =====
79  var progress = document.getElementById('te-progress');
80  if (progress) {
81    var rafId = null;
82    function updateProgress() {
83      rafId = null;
84      var doc = document.documentElement;
85      var scrolled = window.scrollY || doc.scrollTop || 0;
86      var total = doc.scrollHeight - doc.clientHeight;
87      var pct = total > 0 ? (scrolled / total) * 100 : 0;
88      progress.style.width = Math.min(100, Math.max(0, pct)) + '%';
89    }
90    function onScroll() {
91      if (rafId) return;
92      rafId = requestAnimationFrame(updateProgress);
93    }
94    window.addEventListener('scroll', onScroll, { passive: true });
95    updateProgress();
96  }
97
98  // ===== TOC scrollspy (IntersectionObserver) =====
99  var tocLinks = document.querySelectorAll('#te-toc-list a');
100  if (tocLinks.length > 0 && 'IntersectionObserver' in window) {
101    var tocMap = {};
102    tocLinks.forEach(function (link) {
103      var id = link.getAttribute('href').slice(1);
104      tocMap[id] = link;
105    });
106
107    var observed = {};
108    var observer = new IntersectionObserver(
109      function (entries) {
110        entries.forEach(function (entry) {
111          observed[entry.target.id] = entry.isIntersecting;
112        });
113        // Find the first visible heading
114        var active = null;
115        for (var id in observed) {
116          if (observed[id]) { active = id; break; }
117        }
118        if (!active) {
119          // Fallback: find last heading above viewport
120          var headings = document.querySelectorAll('.te-article-body h2[id]');
121          var scrollPos = window.scrollY + 150;
122          for (var i = 0; i < headings.length; i++) {
123            if (headings[i].offsetTop <= scrollPos) active = headings[i].id;
124          }
125        }
126        tocLinks.forEach(function (link) {
127          var id = link.getAttribute('href').slice(1);
128          link.classList.toggle('active', id === active);
129        });
130      },
131      { rootMargin: '-100px 0px -60% 0px', threshold: 0 }
132    );
133
134    var headings = document.querySelectorAll('.te-article-body h2[id]');
135    headings.forEach(function (h) { observer.observe(h); });
136  }
137})();

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.