(() => { // wrap content into .inner for independent inner animation document.querySelectorAll('.timeline .content').forEach((c) => { if (!c.querySelector('.inner')) { const wrap = document.createElement('div'); wrap.className = 'inner'; wrap.innerHTML = c.innerHTML; c.innerHTML = ''; c.appendChild(wrap); } }); // delegate clicks for toggles document.addEventListener('click', (e) => { const btn = e.target.closest('.toggle'); if (!btn) return; const content = btn.nextElementSibling; if (!content) return; toggle(content, btn); }); function toggle(content, button) { const isOpen = content.classList.contains('open'); // ensure we have an inner element const inner = content.querySelector('.inner') || content; // measure current height const computed = getComputedStyle(content); const startHeight = parseFloat(computed.height) || 0; // freeze current height so transition starts from visual state content.style.height = startHeight + 'px'; if (isOpen) { // close: remove open class to start inner exit animation, then collapse height content.classList.remove('open'); button.setAttribute('aria-expanded', 'false'); content.setAttribute('aria-hidden', 'true'); // after paint, animate height to 0 requestAnimationFrame(() => { // start from the frozen pixel height content.style.height = startHeight + 'px'; requestAnimationFrame(() => { content.style.height = '0px'; }); }); const onEnd = (ev) => { if (ev.propertyName !== 'height') return; content.style.height = ''; content.removeEventListener('transitionend', onEnd); }; content.addEventListener('transitionend', onEnd); } else { // open: add class first so computed scrollHeight includes padding/margins button.setAttribute('aria-expanded', 'true'); content.setAttribute('aria-hidden', 'false'); // ensure starting height (likely 0) content.style.height = startHeight + 'px'; // paint, add class, then measure target height requestAnimationFrame(() => { content.classList.add('open'); requestAnimationFrame(() => { const target = content.scrollHeight; content.style.height = target + 'px'; const onEnd = (ev) => { if (ev.propertyName !== 'height') return; content.style.height = ''; content.removeEventListener('transitionend', onEnd); }; content.addEventListener('transitionend', onEnd); }); }); } } })();