Files
dylanmazzei-website/static/assets/site.js
T
Alejandro Veras eb9137b300 Add Spanish and Arabic versions with a language switch
- English stays at /, Spanish at /es/ and Arabic at /ar/ (right to left)
- EN · ES · AR switch in the header and mobile menu
- Pages are written in English; the build translates them through
  src/i18n/es.json and src/i18n/ar.json, including browser-script strings
- rtl.css mirrors the layout and loads Arabic fonts on /ar/ pages
- hreflang alternates on every page and a multilingual sitemap
- scripts/i18n-extract.mjs lists strings that still need translating

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 16:18:11 -04:00

172 lines
6.9 KiB
JavaScript

(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: <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 = 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');
})();