d0357f1c52
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>
148 lines
6.0 KiB
JavaScript
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');
|
|
})();
|