Files
dylanmazzei-website/static/assets/site.js
T
Alejandro Veras d0357f1c52 Merge option 2 homepage and visual language into the site
Rebuild the homepage on option 2's sections and effects (3D DNA, spinning
cube, giant words, fireworks journey, neon type, chrome ribbon) with copy
kept verbatim from the content document. Each venture now has its own 3D
object, the portfolio dashboard sits beside the DNA, the journey uses the
original stats row and the playbook keeps our book. Inner pages adopt the
same navy, glass and gradient skin, with dark as the default theme.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 12:47:20 -04:00

148 lines
6.0 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.';
});
}
root.classList.add('ready');
})();