PageSourceSearch

https://idealmediapro.com/script.js

js idealmediapro.com collected 2026-09-24 09:03:52 UTC 10,758 bytes, 335 lines download raw bytes

1document.addEventListener('DOMContentLoaded', () => {
2
3  // ===== MOBILE HAMBURGER =====
4  const hamburger = document.getElementById('hamburger');
5  const mobileNav = document.getElementById('mobileNav');
6
7  if (hamburger && mobileNav) {
8    hamburger.addEventListener('click', () => {
9      hamburger.classList.toggle('active');
10      mobileNav.classList.toggle('open');
11    });
12
13    mobileNav.querySelectorAll('a').forEach(link => {
14      link.addEventListener('click', () => {
15        hamburger.classList.remove('active');
16        mobileNav.classList.remove('open');
17      });
18    });
19  }
20
21  // ===== DROPDOWN TOGGLE =====
22  const dropdownToggles = document.querySelectorAll('.dropdown-toggle');
23
24  function closeAllDropdowns() {
25    document.querySelectorAll('.nav-dropdown.open').forEach(d => {
26      d.classList.remove('open');
27      d.querySelector('.dropdown-toggle').setAttribute('aria-expanded', 'false');
28    });
29  }
30
31  dropdownToggles.forEach(toggle => {
32    toggle.addEventListener('click', e => {
33      e.stopPropagation();
34      const dropdown = toggle.closest('.nav-dropdown');
35      const isOpen = dropdown.classList.contains('open');
36      closeAllDropdowns();
37      if (!isOpen) {
38        dropdown.classList.add('open');
39        toggle.setAttribute('aria-expanded', 'true');
40      }
41    });
42  });
43
44  document.querySelectorAll('.dropdown-menu a').forEach(link => {
45    link.addEventListener('click', closeAllDropdowns);
46  });
47
48  document.addEventListener('click', e => {
49    if (!e.target.closest('.nav-dropdown')) {
50      closeAllDropdowns();
51    }
52  });
53
54  document.addEventListener('keydown', e => {
55    if (e.key === 'Escape') closeAllDropdowns();
56  });
57
58  // ===== HEADER SCROLL SHADOW =====
59  const header = document.querySelector('.site-header');
60
61  window.addEventListener('scroll', () => {
62    if (window.scrollY > 50) {
63      header.classList.add('scrolled');
64    } else {
65      header.classList.remove('scrolled');
66    }
67  });
68
69  // ===== HERO DEVICE TYPING EFFECT =====
70  const typingText = document.querySelector('.hdf-typing-text');
71  if (typingText) {
72    const isMobile = window.innerWidth < 768;
73    const typeSpeed = isMobile ? 100 : 55;
74    const deleteSpeed = isMobile ? 60 : 28;
75    const phrases = [
76      { segs: [['lead generation', true], [' machine.', false]] },
77      { segs: [['call', true], [' magnet.', false]] },
78      { segs: [['booking', true], [' engine.', false]] },
79      { segs: [['client conversion', true], [' system.', false]] },
80      { segs: [['Get found. Get called. ', false], ['Get paid.', true]] },
81      { segs: [['Turn clicks into ', false], ['customers.', true]] },
82      { segs: [['Dominate your ', false], ['local market.', true]] },
83      { segs: [['Turn your site into ', false], ['revenue machine.', true]] },
84    ];
85
86    function getFullText(segs) { return segs.map(s => s[0]).join(''); }
87    function buildHTML(segs, count) {
88      let html = '', rem = count;
89      for (const [text, hl] of segs) {
90        if (rem <= 0) break;
91        const take = Math.min(rem, text.length);
92        const part = text.substring(0, take);
93        html += hl ? '<span style="color:var(--orange)">' + part + '</span>' : part;
94        rem -= take;
95      }
96      return html;
97    }
98
99    let phraseIndex = 0;
100    let charIndex = 0;
101    let isDeleting = false;
102    let isPaused = false;
103
104    function typeLoop() {
105      const currentPhrase = phrases[phraseIndex];
106      const fullText = getFullText(currentPhrase.segs);
107
108      if (isPaused) {
109        setTimeout(typeLoop, 2200);
110        isPaused = false;
111        return;
112      }
113
114      if (!isDeleting) {
115        typingText.innerHTML = buildHTML(currentPhrase.segs, charIndex + 1);
116        charIndex++;
117        if (charIndex === fullText.length) {
118          isPaused = true;
119          isDeleting = true;
120          setTimeout(typeLoop, 2800);
121          return;
122        }
123        setTimeout(typeLoop, typeSpeed);
124      } else {
125        typingText.innerHTML = buildHTML(currentPhrase.segs, charIndex - 1);
126        charIndex--;
127        if (charIndex === 0) {
128          isDeleting = false;
129          phraseIndex = (phraseIndex + 1) % phrases.length;
130        }
131        setTimeout(typeLoop, deleteSpeed);
132      }
133    }
134
135    typeLoop();
136  }
137
138  // ===== HERO DEVICE LEAD COUNTER =====
139  const leadCounter = document.querySelector('.hdf-lc-num');
140  if (leadCounter) {
141    let started = false;
142
143    function startLeadCounter() {
144      if (started) return;
145      started = true;
146
147      const target = parseInt(leadCounter.getAttribute('data-count'));
148      const duration = 1500;
149      const startTime = performance.now();
150
151      function update(currentTime) {
152        const elapsed = currentTime - startTime;
153        const progress = Math.min(elapsed / duration, 1);
154        const eased = 1 - Math.pow(1 - progress, 3);
155        leadCounter.textContent = Math.floor(eased * target);
156        if (progress < 1) requestAnimationFrame(update);
157        else leadCounter.textContent = target;
158      }
159
160      requestAnimationFrame(update);
161    }
162
163    const device = document.querySelector('.hero-device-frame');
164    if (device) {
165      const observer = new IntersectionObserver((entries) => {
166        entries.forEach(entry => {
167          if (entry.isIntersecting) { startLeadCounter(); observer.unobserve(entry.target); }
168        });
169      }, { threshold: 0.3 });
170      observer.observe(device);
171    }
172  }
173
174  // ===== INTERSECTION OBSERVER (Scroll Animations) =====
175  const observerOptions = {
176    threshold: 0.08,
177    rootMargin: '0px 0px -40px 0px'
178  };
179
180  const observer = new IntersectionObserver((entries) => {
181    entries.forEach(entry => {
182      if (entry.isIntersecting) {
183        entry.target.classList.add('is-visible');
184      }
185    });
186  }, observerOptions);
187
188  document.querySelectorAll('.reveal').forEach(el => observer.observe(el));
189
190  // ===== SERVICE ACCORDION =====
191  const svcItems = document.querySelectorAll('.svc-item');
192
193  svcItems.forEach(item => {
194    const header = item.querySelector('.svc-header');
195    header.addEventListener('click', () => {
196      const isOpen = item.classList.contains('open');
197
198      svcItems.forEach(i => {
199        i.classList.remove('open');
200        i.querySelector('.svc-header').setAttribute('aria-expanded', 'false');
201      });
202
203      if (!isOpen) {
204        item.classList.add('open');
205        item.querySelector('.svc-header').setAttribute('aria-expanded', 'true');
206      }
207    });
208  });
209
210  // ===== FAQ ACCORDION =====
211  const faqItems = document.querySelectorAll('.faq-item');
212
213  faqItems.forEach(item => {
214    const question = item.querySelector('.faq-question');
215    question.addEventListener('click', () => {
216      const isOpen = item.classList.contains('open');
217
218      faqItems.forEach(i => {
219        i.classList.remove('open');
220        i.querySelector('.faq-question').setAttribute('aria-expanded', 'false');
221      });
222
223      if (!isOpen) {
224        item.classList.add('open');
225        item.querySelector('.faq-question').setAttribute('aria-expanded', 'true');
226      }
227    });
228  });
229
230  // ===== SMOOTH SCROLL FOR ANCHOR LINKS =====
231  document.querySelectorAll('a[href^="#"]').forEach(anchor => {
232    anchor.addEventListener('click', (e) => {
233      const href = anchor.getAttribute('href');
234      if (href === '#') return;
235      const target = document.querySelector(href);
236      if (target) {
237        e.preventDefault();
238        const headerHeight = header ? header.offsetHeight : 68;
239        const targetPosition = target.getBoundingClientRect().top + window.scrollY - headerHeight;
240        window.scrollTo({
241          top: targetPosition,
242          behavior: 'smooth'
243        });
244      }
245    });
246  });
247
248  // ===== ACTIVE NAV LINK ON SCROLL (cached offsets, rAF-throttled) =====
249  const sections = document.querySelectorAll('section[id]');
250  const navAnchors = document.querySelectorAll('.desktop-nav a');
251  let sectionOffsets = [];
252
253  function cacheOffsets() {
254    sectionOffsets = Array.from(sections, s => ({ id: s.getAttribute('id'), top: s.offsetTop - 120 }));
255  }
256
257  if (sections.length && navAnchors.length) {
258    cacheOffsets();
259    window.addEventListener('resize', cacheOffsets);
260    let ticking = false;
261    window.addEventListener('scroll', () => {
262      if (!ticking) {
263        requestAnimationFrame(() => {
264          let current = '';
265          const sy = window.scrollY;
266          for (const s of sectionOffsets) {
267            if (sy >= s.top) current = s.id;
268          }
269          navAnchors.forEach(link => {
270            const linkHref = link.getAttribute('href').replace('#', '');
271            if (linkHref === current) {
272              link.style.color = '#11110f';
273              link.style.fontWeight = '700';
274            } else {
275              link.style.color = '';
276              link.style.fontWeight = '';
277            }
278          });
279          ticking = false;
280        });
281        ticking = true;
282      }
283    });
284  }
285
286  // ===== SERVICE CARD GLOW ON TOUCH (only one at a time) =====
287  if (matchMedia('(hover: none)').matches) {
288    const cards = document.querySelectorAll('.service-card');
289    const ratios = new Map();
290    const obs = new IntersectionObserver((entries) => {
291      entries.forEach(e => ratios.set(e.target, e.intersectionRatio));
292      let best = null, bestR = 0;
293      ratios.forEach((r, c) => { if (r > bestR) { bestR = r; best = c; } });
294      cards.forEach(c => c.classList.toggle('sc-visible', c === best && bestR > 0));
295    }, { threshold: [0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1] });
296    cards.forEach(c => obs.observe(c));
297  }
298
299  // ===== RESULTS COUNTER =====
300  const resultsNumbers = document.querySelectorAll('.results-number');
301  if (resultsNumbers.length) {
302    const countUp = (el) => {
303      const raw = el.dataset.target;
304      const target = parseFloat(raw);
305      const prefix = el.dataset.prefix || '';
306      const suffix = el.dataset.suffix || '';
307      const duration = 1800;
308      const start = performance.now();
309      const isMillions = target >= 1000000;
310      const step = (now) => {
311        const p = Math.min((now - start) / duration, 1);
312        const eased = 1 - Math.pow(1 - p, 3);
313        const val = Math.round(eased * target);
314        let display;
315        if (isMillions) {
316          display = (val / 1000000).toFixed(1).replace(/\.0$/, '') + 'M';
317        } else {
318          display = val;
319        }
320        el.textContent = prefix + display + suffix;
321        if (p < 1) requestAnimationFrame(step);
322      };
323      requestAnimationFrame(step);
324    };
325    const resObs = new IntersectionObserver((entries) => {
326      entries.forEach(e => {
327        if (!e.isIntersecting) return;
328        countUp(e.target);
329        resObs.unobserve(e.target);
330      });
331    }, { threshold: 0.4 });
332    resultsNumbers.forEach(n => resObs.observe(n));
333  }
334
335});

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.