(function () { window.__site = true; var root = document.documentElement; // Translations for the current page's language, written into the page by the build. var T = function (s) { return (window.__i18n && window.__i18n[s]) || s; }; var reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; // Theme toggle: remembers the visitor's choice; otherwise follows the system. var themeBtn = document.getElementById('theme-btn'); var current = function () { return root.getAttribute('data-theme') === 'dark' ? 'dark' : 'light'; }; var paint = function () { if (!themeBtn) return; var dark = current() === 'dark'; themeBtn.setAttribute('aria-label', T(dark ? 'Switch to light mode' : 'Switch to dark mode')); themeBtn.setAttribute('aria-pressed', String(dark)); }; var apply = function (t, save) { root.setAttribute('data-theme', t); if (save) { try { localStorage.setItem('theme', t); } catch (e) {} } paint(); }; paint(); if (themeBtn) themeBtn.addEventListener('click', function () { apply(current() === 'dark' ? 'light' : 'dark', true); }); // Nav border on scroll var nav = document.getElementById('nav'); var progress = document.getElementById('progress'); var onScroll = function () { nav && nav.classList.toggle('is-scrolled', window.scrollY > 8); if (progress) { var max = Math.max(1, document.documentElement.scrollHeight - window.innerHeight); progress.style.transform = 'scaleX(' + Math.min(1, Math.max(0, window.scrollY / max)) + ')'; } }; onScroll(); window.addEventListener('scroll', onScroll, { passive: true }); // Mobile menu var menuBtn = document.getElementById('menu-btn'); var menu = document.getElementById('mobile-menu'); if (menuBtn && menu) { menuBtn.addEventListener('click', function () { var open = menu.hidden; menu.hidden = !open; menuBtn.setAttribute('aria-expanded', String(open)); menuBtn.setAttribute('aria-label', T(open ? 'Close menu' : 'Open menu')); }); document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && !menu.hidden) { menu.hidden = true; menuBtn.setAttribute('aria-expanded', 'false'); menuBtn.focus(); } }); } // Reveal on scroll var reveals = document.querySelectorAll('.reveal'); if ('IntersectionObserver' in window && !reduce) { var io = new IntersectionObserver(function (entries) { entries.forEach(function (en) { if (en.isIntersecting) { en.target.classList.add('is-in'); io.unobserve(en.target); } }); }, { rootMargin: '0px 0px -8% 0px', threshold: 0.05 }); reveals.forEach(function (el) { io.observe(el); }); } else { reveals.forEach(function (el) { el.classList.add('is-in'); }); } // Count-up numbers: 8+ var counters = document.querySelectorAll('[data-count]'); var run = function (el) { var end = parseInt(el.getAttribute('data-count'), 10); var suffix = el.getAttribute('data-suffix') || ''; var start = null; var dur = 1200; var step = function (t) { if (!start) start = t; var p = Math.min((t - start) / dur, 1); var eased = 1 - Math.pow(1 - p, 3); el.textContent = Math.round(end * eased) + suffix; if (p < 1) requestAnimationFrame(step); }; requestAnimationFrame(step); }; if ('IntersectionObserver' in window && !reduce) { var co = new IntersectionObserver(function (entries) { entries.forEach(function (en) { if (en.isIntersecting) { run(en.target); co.unobserve(en.target); } }); }, { threshold: 0.6 }); counters.forEach(function (el) { co.observe(el); }); } // Tabs (service lines) document.querySelectorAll('[data-tabs]').forEach(function (wrap) { var btns = wrap.querySelectorAll('[role="tab"]'); var panels = wrap.querySelectorAll('[role="tabpanel"]'); var select = function (i, focus) { btns.forEach(function (b, j) { var on = i === j; b.setAttribute('aria-selected', String(on)); b.tabIndex = on ? 0 : -1; panels[j].hidden = !on; }); if (focus) btns[i].focus(); }; btns.forEach(function (b, i) { b.addEventListener('click', function () { select(i); }); b.addEventListener('keydown', function (e) { var k = e.key; if (k === 'ArrowDown' || k === 'ArrowRight') { e.preventDefault(); select((i + 1) % btns.length, true); } if (k === 'ArrowUp' || k === 'ArrowLeft') { e.preventDefault(); select((i - 1 + btns.length) % btns.length, true); } }); }); var hash = location.hash.replace('#', ''); var start = 0; panels.forEach(function (p, j) { if (p.id === hash) start = j; }); select(start); }); // Copy buttons document.querySelectorAll('[data-copy]').forEach(function (b) { b.addEventListener('click', function () { var text = b.getAttribute('data-copy'); var label = b.textContent; var done = function () { b.textContent = T('Copied'); setTimeout(function () { b.textContent = label; }, 1600); }; try { navigator.clipboard.writeText(text).then(done, function () {}); } catch (e) {} }); }); // Inquiry form: opens the visitor's email client with the message prefilled. var form = document.getElementById('inquiry'); if (form) { form.addEventListener('submit', function (e) { e.preventDefault(); if (!form.reportValidity()) return; var d = new FormData(form); var body = [ 'Name: ' + d.get('name'), 'Email: ' + d.get('email'), d.get('company') ? 'Company: ' + d.get('company') : '', d.get('topic') ? 'Topic: ' + d.get('topic') : '', '', d.get('message') ].filter(function (l, i) { return l !== '' || i === 4; }).join('\n'); var subject = 'Inquiry from ' + d.get('name') + (d.get('topic') ? ' — ' + d.get('topic') : ''); window.location.href = 'mailto:' + form.getAttribute('data-to') + '?subject=' + encodeURIComponent(subject) + '&body=' + encodeURIComponent(body); var status = document.getElementById('form-status'); if (status) status.textContent = T('Your email app should open with the message ready to send.'); }); } // GoHighLevel's form_embed.js hides each embed until the iframe reports it is // ready. If that message never arrives, show the embed anyway so the form is // never left invisible. var reveal = function (f) { f.style.opacity = '1'; f.style.visibility = 'visible'; f.style.pointerEvents = 'auto'; f.style.position = 'relative'; if (f.style.left === '-9999px') f.style.left = ''; }; var ghl = document.querySelectorAll('.ghl-frame'); if (ghl.length) { [2500, 6000].forEach(function (ms) { setTimeout(function () { ghl.forEach(function (f) { if (f.closest('[data-off]')) return; if (getComputedStyle(f).visibility === 'hidden') reveal(f); }); }, ms); }); } root.classList.add('ready'); })();