From 1ff26be61b6d9f27013023cd19c6867c48d7cc9f Mon Sep 17 00:00:00 2001 From: Antonio Ledebuhr Date: Tue, 25 Aug 2026 19:32:22 +0200 Subject: [PATCH] integration: keep reveal transitions on the revealed state Move the transition onto the base reveal-target selector so removing reveal-pending no longer drops the animation, while reduced-motion still suppresses it. Co-authored-by: Cursor --- src/app/shared/motion/_reveal.scss | 8 +++-- src/app/shared/motion/reveal.styles.spec.ts | 36 +++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 src/app/shared/motion/reveal.styles.spec.ts diff --git a/src/app/shared/motion/_reveal.scss b/src/app/shared/motion/_reveal.scss index 902e1a9..47b0863 100644 --- a/src/app/shared/motion/_reveal.scss +++ b/src/app/shared/motion/_reveal.scss @@ -1,10 +1,11 @@ @mixin reveal-target { + transition: + opacity var(--duration-base) var(--ease-standard), + transform var(--duration-base) var(--ease-standard); + &.reveal-pending { opacity: 0; transform: translateY(0.5rem); - transition: - opacity var(--duration-base) var(--ease-standard), - transform var(--duration-base) var(--ease-standard); } &.is-revealed { @@ -13,6 +14,7 @@ } @media (prefers-reduced-motion: reduce) { + &, &.reveal-pending, &.is-revealed { opacity: 1; diff --git a/src/app/shared/motion/reveal.styles.spec.ts b/src/app/shared/motion/reveal.styles.spec.ts new file mode 100644 index 0000000..3b68fc2 --- /dev/null +++ b/src/app/shared/motion/reveal.styles.spec.ts @@ -0,0 +1,36 @@ +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; + +const REVEAL_SCSS = readFileSync(join(process.cwd(), 'src/app/shared/motion/_reveal.scss'), 'utf8'); + +function mixinBody(source: string): string { + const start = source.indexOf('@mixin reveal-target'); + expect(start).toBeGreaterThan(-1); + return source.slice(start); +} + +describe('reveal-target mixin', () => { + it('keeps transition on the base selector so the revealed state can animate', () => { + const mixin = mixinBody(REVEAL_SCSS); + const transitionIndex = mixin.search(/transition\s*:/); + const pendingIndex = mixin.indexOf('&.reveal-pending'); + const revealedIndex = mixin.indexOf('&.is-revealed'); + + expect(transitionIndex).toBeGreaterThan(-1); + expect(pendingIndex).toBeGreaterThan(-1); + expect(revealedIndex).toBeGreaterThan(pendingIndex); + expect(transitionIndex).toBeLessThan(pendingIndex); + + const pendingBlock = mixin.match(/&\.reveal-pending\s*\{([^}]*)\}/)?.[1] ?? ''; + expect(pendingBlock).toMatch(/opacity\s*:/); + expect(pendingBlock).not.toMatch(/transition\s*:/); + + const revealedBlock = mixin.match(/&\.is-revealed\s*\{([^}]*)\}/)?.[1] ?? ''; + expect(revealedBlock).toMatch(/opacity\s*:\s*1/); + expect(revealedBlock).not.toMatch(/transition\s*:/); + + expect(mixin).toMatch(/prefers-reduced-motion:\s*reduce/); + expect(mixin).toMatch(/&\s*,\s*&\.reveal-pending/); + expect(mixin).toMatch(/&\.is-revealed\s*\{[\s\S]*transition:\s*none/); + }); +});