Files
dylanmazzei-website/static/assets/site.js
T
Alejandro Veras 2d37e3fcce Use Dylan's DM logo, switch accent to brand blue, add dark mode
Recreate the DM monogram and wordmark as SVG and type so they stay sharp in
both themes, move the accent from violet to the logo's blue, and add a small
theme toggle in the nav that follows the system until the visitor picks one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:25:52 -04:00

149 lines
6.1 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 mq = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;
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); });
if (mq && mq.addEventListener) {
mq.addEventListener('change', function (e) {
var saved = null;
try { saved = localStorage.getItem('theme'); } catch (err) {}
if (!saved) apply(e.matches ? 'dark' : 'light', false);
});
}
// Nav border on scroll
var nav = document.getElementById('nav');
var onScroll = function () { nav && nav.classList.toggle('is-scrolled', window.scrollY > 8); };
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.';
});
}
root.classList.add('ready');
})();