Merge branch 'pr-575' into visual-pr-playground

This commit is contained in:
pewdiepie-archdaemon
2026-06-02 06:26:31 +09:00
2 changed files with 60 additions and 38 deletions
+34 -12
View File
@@ -33,31 +33,53 @@ export function initSectionCollapse(Storage) {
Storage.setJSON('section-collapsed', state); Storage.setJSON('section-collapsed', state);
// Always clear any in-flight animation classes from a previous toggle // Always clear any in-flight animation classes from a previous toggle
// so back-to-back clicks restart cleanly. // so back-to-back clicks restart cleanly. Bump a generation token so
// any callback still pending from a superseded toggle becomes a no-op.
section.classList.remove('section-just-expanded', 'section-just-collapsing'); section.classList.remove('section-just-expanded', 'section-just-collapsing');
const gen = (section._collapseGen = (section._collapseGen || 0) + 1);
if (willCollapse) { if (willCollapse) {
// Domino-out: play the fade/slide-down on .list-item children // Domino-out: play the fade/slide-down on the row children BEFORE
// BEFORE actually adding .collapsed (which hides them via // actually adding .collapsed (which hides them via display:none),
// display:none). After the cascade finishes, lock in collapse. // then lock in collapse once the cascade finishes.
// Force reflow so the keyframes restart. //
// We wait on the REAL animations (getAnimations) rather than a fixed
// timeout. Different sections animate different rows — .list-item in
// most, .models-row in #models-section — so any hard-coded duration
// either stalls with a dead pause (when the selector matches nothing,
// as it did for #models-section) or guesses the wrong length. Force a
// reflow first so the keyframes restart from the top.
// eslint-disable-next-line no-unused-expressions // eslint-disable-next-line no-unused-expressions
section.offsetHeight; section.offsetHeight;
section.classList.add('section-just-collapsing'); section.classList.add('section-just-collapsing');
const itemCount = Math.min(12, section.querySelectorAll('.list-item').length);
const total = itemCount * 25 + 230; // matches CSS keyframes + stagger const lockCollapsed = () => {
setTimeout(() => { if (section._collapseGen !== gen) return; // superseded by a newer toggle
section.classList.remove('section-just-collapsing'); section.classList.remove('section-just-collapsing');
section.classList.add('collapsed'); section.classList.add('collapsed');
}, total); };
// Only the domino-out keyframes gate the collapse — ignore unrelated
// (and possibly infinite, e.g. spinners) animations in the subtree.
const dominoOut = section.getAnimations({ subtree: true })
.filter(a => a.animationName === 'section-domino-out');
if (dominoOut.length === 0) {
lockCollapsed(); // nothing to animate — collapse now, no dead pause
} else { } else {
// Expand path — already had this: remove .collapsed and replay Promise.allSettled(dominoOut.map(a => a.finished)).then(lockCollapsed);
// the inbound domino. // Safety net: if an animation never settles (e.g. element removed),
// still lock in the collapse so the section can't get stuck open.
setTimeout(lockCollapsed, 600);
}
} else {
// Expand path — remove .collapsed and replay the inbound domino.
section.classList.remove('collapsed'); section.classList.remove('collapsed');
// eslint-disable-next-line no-unused-expressions // eslint-disable-next-line no-unused-expressions
section.offsetHeight; section.offsetHeight;
section.classList.add('section-just-expanded'); section.classList.add('section-just-expanded');
setTimeout(() => section.classList.remove('section-just-expanded'), 700); setTimeout(() => {
if (section._collapseGen !== gen) return; // superseded by a newer toggle
section.classList.remove('section-just-expanded');
}, 700);
} }
} }
+26 -26
View File
@@ -1251,21 +1251,21 @@ body.bg-pattern-sparkles {
for ~700ms), the .list-item children cascade in one after another, for ~700ms), the .list-item children cascade in one after another,
same feel as the chat input's tools menu. Each row springs in same feel as the chat input's tools menu. Each row springs in
from a tiny offset below + scaled-down, staggered by nth-child. */ from a tiny offset below + scaled-down, staggered by nth-child. */
.section.section-just-expanded .list-item { .section.section-just-expanded :is(.list-item, .models-row) {
animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards;
} }
.section.section-just-expanded .list-item:nth-child(1) { animation-delay: 0.04s; } .section.section-just-expanded :is(.list-item, .models-row):nth-child(1) { animation-delay: 0.04s; }
.section.section-just-expanded .list-item:nth-child(2) { animation-delay: 0.08s; } .section.section-just-expanded :is(.list-item, .models-row):nth-child(2) { animation-delay: 0.08s; }
.section.section-just-expanded .list-item:nth-child(3) { animation-delay: 0.12s; } .section.section-just-expanded :is(.list-item, .models-row):nth-child(3) { animation-delay: 0.12s; }
.section.section-just-expanded .list-item:nth-child(4) { animation-delay: 0.16s; } .section.section-just-expanded :is(.list-item, .models-row):nth-child(4) { animation-delay: 0.16s; }
.section.section-just-expanded .list-item:nth-child(5) { animation-delay: 0.20s; } .section.section-just-expanded :is(.list-item, .models-row):nth-child(5) { animation-delay: 0.20s; }
.section.section-just-expanded .list-item:nth-child(6) { animation-delay: 0.24s; } .section.section-just-expanded :is(.list-item, .models-row):nth-child(6) { animation-delay: 0.24s; }
.section.section-just-expanded .list-item:nth-child(7) { animation-delay: 0.28s; } .section.section-just-expanded :is(.list-item, .models-row):nth-child(7) { animation-delay: 0.28s; }
.section.section-just-expanded .list-item:nth-child(8) { animation-delay: 0.32s; } .section.section-just-expanded :is(.list-item, .models-row):nth-child(8) { animation-delay: 0.32s; }
.section.section-just-expanded .list-item:nth-child(9) { animation-delay: 0.36s; } .section.section-just-expanded :is(.list-item, .models-row):nth-child(9) { animation-delay: 0.36s; }
.section.section-just-expanded .list-item:nth-child(10) { animation-delay: 0.40s; } .section.section-just-expanded :is(.list-item, .models-row):nth-child(10) { animation-delay: 0.40s; }
.section.section-just-expanded .list-item:nth-child(11) { animation-delay: 0.44s; } .section.section-just-expanded :is(.list-item, .models-row):nth-child(11) { animation-delay: 0.44s; }
.section.section-just-expanded .list-item:nth-child(12) { animation-delay: 0.48s; } .section.section-just-expanded :is(.list-item, .models-row):nth-child(12) { animation-delay: 0.48s; }
@keyframes section-domino-in { @keyframes section-domino-in {
0% { opacity: 0; transform: translateY(8px) translateX(-4px) scale(0.92); } 0% { opacity: 0; transform: translateY(8px) translateX(-4px) scale(0.92); }
60% { opacity: 1; } 60% { opacity: 1; }
@@ -1279,21 +1279,21 @@ body.bg-pattern-sparkles {
nth-last-child so the BOTTOM item leaves first and the cascade nth-last-child so the BOTTOM item leaves first and the cascade
rolls upward mirrors the "stacked deck" feeling of the open rolls upward mirrors the "stacked deck" feeling of the open
animation reversed. */ animation reversed. */
.section.section-just-collapsing .list-item { .section.section-just-collapsing :is(.list-item, .models-row) {
animation: section-domino-out 0.22s ease-in forwards; animation: section-domino-out 0.22s ease-in forwards;
} }
.section.section-just-collapsing .list-item:nth-last-child(1) { animation-delay: 0.00s; } .section.section-just-collapsing :is(.list-item, .models-row):nth-last-child(1) { animation-delay: 0.00s; }
.section.section-just-collapsing .list-item:nth-last-child(2) { animation-delay: 0.025s; } .section.section-just-collapsing :is(.list-item, .models-row):nth-last-child(2) { animation-delay: 0.025s; }
.section.section-just-collapsing .list-item:nth-last-child(3) { animation-delay: 0.05s; } .section.section-just-collapsing :is(.list-item, .models-row):nth-last-child(3) { animation-delay: 0.05s; }
.section.section-just-collapsing .list-item:nth-last-child(4) { animation-delay: 0.075s; } .section.section-just-collapsing :is(.list-item, .models-row):nth-last-child(4) { animation-delay: 0.075s; }
.section.section-just-collapsing .list-item:nth-last-child(5) { animation-delay: 0.10s; } .section.section-just-collapsing :is(.list-item, .models-row):nth-last-child(5) { animation-delay: 0.10s; }
.section.section-just-collapsing .list-item:nth-last-child(6) { animation-delay: 0.125s; } .section.section-just-collapsing :is(.list-item, .models-row):nth-last-child(6) { animation-delay: 0.125s; }
.section.section-just-collapsing .list-item:nth-last-child(7) { animation-delay: 0.15s; } .section.section-just-collapsing :is(.list-item, .models-row):nth-last-child(7) { animation-delay: 0.15s; }
.section.section-just-collapsing .list-item:nth-last-child(8) { animation-delay: 0.175s; } .section.section-just-collapsing :is(.list-item, .models-row):nth-last-child(8) { animation-delay: 0.175s; }
.section.section-just-collapsing .list-item:nth-last-child(9) { animation-delay: 0.20s; } .section.section-just-collapsing :is(.list-item, .models-row):nth-last-child(9) { animation-delay: 0.20s; }
.section.section-just-collapsing .list-item:nth-last-child(10) { animation-delay: 0.225s; } .section.section-just-collapsing :is(.list-item, .models-row):nth-last-child(10) { animation-delay: 0.225s; }
.section.section-just-collapsing .list-item:nth-last-child(11) { animation-delay: 0.25s; } .section.section-just-collapsing :is(.list-item, .models-row):nth-last-child(11) { animation-delay: 0.25s; }
.section.section-just-collapsing .list-item:nth-last-child(12) { animation-delay: 0.275s; } .section.section-just-collapsing :is(.list-item, .models-row):nth-last-child(12) { animation-delay: 0.275s; }
@keyframes section-domino-out { @keyframes section-domino-out {
0% { opacity: 1; transform: translateY(0) translateX(0) scale(1); } 0% { opacity: 1; transform: translateY(0) translateX(0) scale(1); }
100% { opacity: 0; transform: translateY(6px) translateX(-3px) scale(0.94); } 100% { opacity: 0; transform: translateY(6px) translateX(-3px) scale(0.94); }