yb2026/assets/js/about.js

57 lines
2.4 KiB
JavaScript

/* about.js : interactions propres à la page « Qui suis-je »
- terminal : la commande se tape lettre par lettre, puis les lignes de la fiche apparaissent
- portrait : légère inclinaison 3D qui suit la souris
- parcours : la ligne de la frise se remplit selon le scroll
Mode capture ?ybstatic=1 : tout est affiché d'emblée. */
(function () {
const STATIC = document.documentElement.classList.contains('yb-static');
const TOUCH = matchMedia('(pointer: coarse)').matches;
/* Terminal tapé */
const term = document.querySelector('[data-type]');
if (term) {
const cmd = term.querySelector('[data-type-cmd]'), rows = [...term.querySelectorAll('[data-type-row]')];
const full = cmd ? cmd.textContent : '';
if (STATIC) rows.forEach(r => r.classList.add('is-typed'));
else {
if (cmd) cmd.textContent = '';
const io = new IntersectionObserver(es => es.forEach(en => {
if (!en.isIntersecting) return;
io.disconnect();
let i = 0;
const type = () => {
if (cmd && i <= full.length) { cmd.textContent = full.slice(0, i++); return setTimeout(type, 45 + Math.random() * 40); }
rows.forEach((r, k) => setTimeout(() => r.classList.add('is-typed'), 180 + k * 160));
};
setTimeout(type, 300);
}), { threshold: 0.45 });
io.observe(term);
}
}
/* Portrait incliné à la souris */
const frame = document.querySelector('[data-tilt]');
if (frame && !STATIC && !TOUCH) {
const fig = frame.parentElement;
fig.addEventListener('mousemove', e => {
const r = frame.getBoundingClientRect();
const x = (e.clientX - r.left) / r.width - 0.5, y = (e.clientY - r.top) / r.height - 0.5;
frame.style.setProperty('--ry', (x * 10).toFixed(2) + 'deg');
frame.style.setProperty('--rx', (-y * 10).toFixed(2) + 'deg');
});
fig.addEventListener('mouseleave', () => { frame.style.setProperty('--ry', '0deg'); frame.style.setProperty('--rx', '0deg'); });
}
/* Frise du parcours */
const tl = document.querySelector('[data-timeline]');
if (tl && !STATIC) {
const fill = tl.querySelector('.qs-tl__fill');
const tick = () => {
const r = tl.getBoundingClientRect();
const p = Math.max(0, Math.min(1, (innerHeight * 0.6 - r.top) / r.height));
fill.style.height = (p * 100).toFixed(1) + '%';
requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
}
})();