4ba5f05f75
form_embed.js hides each embed off-screen until the iframe reports ready. With loading="lazy" the hidden iframe often never loaded, so it never reported ready and the form stayed invisible. Load the form eagerly, reserve its height, and reveal any embed still hidden after a few seconds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
170 lines
6.7 KiB
JavaScript
170 lines
6.7 KiB
JavaScript
(function () {
|
|
window.__site = true;
|
|
var root = document.documentElement;
|
|
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', 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', 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: <b data-count="8" data-suffix="+">8+</b>
|
|
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 = '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 = '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');
|
|
})();
|