From 9690868e33ba904bd33d2a91eb7175da39495289 Mon Sep 17 00:00:00 2001 From: KoalaDev <6156589+Shik3i@users.noreply.github.com> Date: Fri, 3 Jul 2026 11:35:00 +0200 Subject: [PATCH] website: Add smooth FAQ accordion spring animation with rotation icon transition --- website/app.js | 60 +++++++++++++++++++++++++++++++++++++++++++++++ website/style.css | 7 +++--- 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/website/app.js b/website/app.js index acb5c2d..37324a2 100644 --- a/website/app.js +++ b/website/app.js @@ -1248,4 +1248,64 @@ document.addEventListener('DOMContentLoaded', () => { updateDynamicVersion(); localizeHomeLinks(); initLanguageSelectorValue(); + // FAQ Accordion Transition (Web Animations API for smooth vertical spring height collapse/expand) + try { + document.querySelectorAll('.faq-item').forEach(details => { + const summary = details.querySelector('summary'); + let isAnimating = false; + + summary.addEventListener('click', (e) => { + e.preventDefault(); + if (isAnimating) return; + + const startHeight = details.offsetHeight; + + if (details.hasAttribute('open')) { + isAnimating = true; + const computed = window.getComputedStyle(details); + const padY = parseInt(computed.paddingTop) + parseInt(computed.paddingBottom); + const borderY = parseInt(computed.borderTopWidth) + parseInt(computed.borderBottomWidth); + const closedHeight = summary.offsetHeight + padY + (isNaN(borderY) ? 0 : borderY); + + const animation = details.animate([ + { height: startHeight + 'px' }, + { height: closedHeight + 'px' } + ], { + duration: 250, + easing: 'cubic-bezier(0.16, 1, 0.3, 1)' + }); + + animation.onfinish = () => { + details.removeAttribute('open'); + details.style.height = ''; + isAnimating = false; + }; + } else { + isAnimating = true; + details.setAttribute('open', ''); + const openHeight = details.offsetHeight; + + const computed = window.getComputedStyle(details); + const padY = parseInt(computed.paddingTop) + parseInt(computed.paddingBottom); + const borderY = parseInt(computed.borderTopWidth) + parseInt(computed.borderBottomWidth); + const closedHeight = summary.offsetHeight + padY + (isNaN(borderY) ? 0 : borderY); + + const animation = details.animate([ + { height: closedHeight + 'px' }, + { height: openHeight + 'px' } + ], { + duration: 250, + easing: 'cubic-bezier(0.16, 1, 0.3, 1)' + }); + + animation.onfinish = () => { + details.style.height = ''; + isAnimating = false; + }; + } + }); + }); + } catch (err) { + console.warn(err); + } }); diff --git a/website/style.css b/website/style.css index 2a16ba6..1a2e901 100644 --- a/website/style.css +++ b/website/style.css @@ -2890,6 +2890,7 @@ html:not(.lang-de) [lang="de"] { padding: 1.5rem 2rem; cursor: pointer; transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease; + overflow: hidden; } .faq-item:hover { @@ -2922,13 +2923,13 @@ html:not(.lang-de) [lang="de"] { font-size: 1.4rem; color: var(--accent); font-weight: 300; - transition: transform 0.3s ease; + transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1); flex-shrink: 0; + transform-origin: center; } .faq-item[open] summary::after { - content: '−'; - transform: rotate(180deg); + transform: rotate(45deg); } .faq-item p {