diff --git a/submissions/docs/engine-missing-keyframes-ks/README.md b/submissions/docs/engine-missing-keyframes-ks/README.md new file mode 100644 index 00000000000..b95a4e158e3 --- /dev/null +++ b/submissions/docs/engine-missing-keyframes-ks/README.md @@ -0,0 +1,40 @@ +# Engine: Missing Keyframes Bug Report + +## What does this do? + +Identifies 6 motion engine animations (`spin`, `wobble`, `flip-x`, `flip-y`, `heartbeat`, `rubber-band`) that silently fail at runtime because their `@keyframes` are defined in `easemotion/engine/compiler.js`'s `KEYFRAME_MAP` but missing from `core/animations.css`. + +The demo includes the missing keyframes inline so they work immediately, and the suggested `ease-*` utility classes that should accompany them when promoted to core. + +## How is it used? + +```html + +
⚙️
+
🔔
+
Flip
+
Flip
+
❤️
+
🏀
+``` + +After the fix, these would also work as plain CSS classes: + +```html +
⚙️
+
🔔
+
Flip
+
Flip
+
❤️
+
🏀
+``` + +## Why is it useful? + +The engine's `ANIMATION_NAMES` set in `parser.js` and `KEYFRAME_MAP` in `compiler.js` accept 25 animation names. Only 19 actually work. The remaining 6 (24% of the API) produce no visible output — the user sees no animation, no error in the browser, only a `console.warn` in development builds. + +This bug report documents the exact gap and the exact fix needed: add the 6 missing `@keyframes` and `ease-*` utility classes to `core/animations.css`. + +**Files to update in core:** +- `core/animations.css` — add `@keyframes ease-kf-spin`, `-wobble`, `-flip-x`, `-flip-y`, `-heartbeat`, `-rubber-band` and matching `.ease-spin` through `.ease-rubber-band` utility classes +- `easemotion.min.css` — rebuild diff --git a/submissions/docs/engine-missing-keyframes-ks/demo.html b/submissions/docs/engine-missing-keyframes-ks/demo.html new file mode 100644 index 00000000000..222c40868cb --- /dev/null +++ b/submissions/docs/engine-missing-keyframes-ks/demo.html @@ -0,0 +1,125 @@ + + + + + + Engine Missing Keyframes — Bug Report & Fix + + + + + +
+

+ Motion Engine: Missing Keyframes Demo +

+

+ 6 animations silently fail via em="" DSL because their + @keyframes are missing from core/animations.css. + This demo proves they work correctly after the fix. +

+ +
+ +
+

Spin

+
+ +
+

em="spin"failed

+
em="spin 2s linear repeat-infinite"
+
+ +
+

Wobble

+
+ +
+

em="wobble"failed

+
em="wobble 1s ease repeat-infinite"
+
+ +
+

Flip X

+
+ +
+

em="flip-x"failed

+
em="flip-x 800ms ease repeat-infinite"
+
+ +
+

Flip Y

+
+ +
+

em="flip-y"failed

+
em="flip-y 800ms ease repeat-infinite"
+
+ +
+

Heartbeat

+
+ +
+

em="heartbeat"failed

+
em="heartbeat 1.5s ease repeat-infinite"
+
+ +
+

Rubber Band

+
+ +
+

em="rubber-band"failed

+
em="rubber-band 1s ease repeat-infinite"
+
+
+ +
+

What's wrong?

+

+ The engine's KEYFRAME_MAP in easemotion/engine/compiler.js + references these 6 @keyframes, but they were never defined in + core/animations.css. The parser accepts the names but compilation + produces CSS referencing non-existent keyframes — resulting in zero visible motion. +

+

+ Fix: The @keyframes in style.css need to + be promoted into core/animations.css as ease-kf-spin, + ease-kf-wobble, ease-kf-flip-x, ease-kf-flip-y, + ease-kf-heartbeat, and ease-kf-rubber-band. + Utility classes .ease-spin, .ease-wobble, etc. should also + be added for plain CSS usage. +

+
+
+ + + + diff --git a/submissions/docs/engine-missing-keyframes-ks/style.css b/submissions/docs/engine-missing-keyframes-ks/style.css new file mode 100644 index 00000000000..a4c94dfa8b9 --- /dev/null +++ b/submissions/docs/engine-missing-keyframes-ks/style.css @@ -0,0 +1,191 @@ +/* ============================================================ + Engine Missing Keyframes — style.css + Bug report demonstrating 6 motion engine animations that + silently fail without these @keyframes in core/animations.css. + + These keyframes and utility classes need to be promoted into + core/animations.css as: + ease-kf-spin, ease-kf-wobble, ease-kf-flip-x, + ease-kf-flip-y, ease-kf-heartbeat, ease-kf-rubber-band + + And utility classes: + ease-spin, ease-wobble, ease-flip-x, + ease-flip-y, ease-heartbeat, ease-rubber-band + ============================================================ */ + +/* ── Waiting for promotion to core/animations.css ──────────── */ +/* These @keyframes fill the gap between the engine's KEYFRAME_MAP + and what core/animations.css actually ships. Without them, + em="spin", em="wobble", em="flip-x", em="flip-y", + em="heartbeat", and em="rubber-band" produce no visible motion. */ + +@keyframes ease-kf-spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +@keyframes ease-kf-wobble { + 0% { transform: translateX(0); } + 15% { transform: translateX(-6px) rotate(-3deg); } + 30% { transform: translateX(5px) rotate(2deg); } + 45% { transform: translateX(-4px) rotate(-1deg); } + 60% { transform: translateX(3px) rotate(1deg); } + 75% { transform: translateX(-1px); } + 100% { transform: translateX(0); } +} + +@keyframes ease-kf-flip-x { + from { transform: perspective(400px) rotateX(90deg); opacity: 0; } + to { transform: perspective(400px) rotateX(0deg); opacity: 1; } +} + +@keyframes ease-kf-flip-y { + from { transform: perspective(400px) rotateY(90deg); opacity: 0; } + to { transform: perspective(400px) rotateY(0deg); opacity: 1; } +} + +@keyframes ease-kf-heartbeat { + 0% { transform: scale(1); } + 14% { transform: scale(1.3); } + 28% { transform: scale(1); } + 42% { transform: scale(1.3); } + 70% { transform: scale(1); } +} + +@keyframes ease-kf-rubber-band { + 0% { transform: scaleX(1); } + 30% { transform: scaleX(1.25) scaleY(0.75); } + 40% { transform: scaleX(0.75) scaleY(1.25); } + 50% { transform: scaleX(1.15) scaleY(0.85); } + 65% { transform: scaleX(0.95) scaleY(1.05); } + 75% { transform: scaleX(1.05) scaleY(0.95); } + 100% { transform: scaleX(1); } +} + +/* ── Utility classes (for plain CSS usage, not just em="") ── */ + +.ease-spin { + animation: ease-kf-spin 1.2s linear var(--ease-animation-iterations, infinite); +} + +.ease-wobble { + animation: ease-kf-wobble 1s var(--ease-ease) both; +} + +.ease-flip-x { + animation: ease-kf-flip-x var(--ease-speed-medium) var(--ease-ease) both; + backface-visibility: hidden; +} + +.ease-flip-y { + animation: ease-kf-flip-y var(--ease-speed-medium) var(--ease-ease) both; + backface-visibility: hidden; +} + +.ease-heartbeat { + animation: ease-kf-heartbeat 1.5s var(--ease-ease) var(--ease-animation-iterations, infinite); +} + +.ease-rubber-band { + animation: ease-kf-rubber-band 1s var(--ease-ease) both; +} + +/* ── Animated demo elements ────────────────────────────────── */ + +.anim-spin { + animation: ease-kf-spin 2s linear infinite; + font-size: 3rem; + display: inline-block; + line-height: 1; +} + +.anim-wobble { + animation: ease-kf-wobble 1s var(--ease-ease) infinite; + font-size: 3rem; + display: inline-block; + line-height: 1; +} + +.anim-flip-x, .anim-flip-y { + animation-duration: 1.2s; + animation-timing-function: var(--ease-ease); + animation-iteration-count: infinite; + backface-visibility: hidden; +} + +.anim-flip-x { animation-name: ease-kf-flip-x; } +.anim-flip-y { animation-name: ease-kf-flip-y; } + +.anim-heartbeat { + animation: ease-kf-heartbeat 1.5s var(--ease-ease) infinite; + font-size: 3rem; + display: inline-block; + line-height: 1; +} + +.anim-rubber-band { + animation: ease-kf-rubber-band 1s var(--ease-ease) infinite; + font-size: 3rem; + display: inline-block; + line-height: 1; +} + +/* ── Demo layout helpers ───────────────────────────────────── */ + +.demo-card { + min-height: 200px; +} + +.demo-visual { + display: flex; + align-items: center; + justify-content: center; + padding: var(--ease-space-6) 0 var(--ease-space-4); + min-height: 90px; +} + +.demo-emoji { + font-size: 3rem; + display: inline-block; + line-height: 1; +} + +.demo-square { + width: 64px; + height: 64px; + background: var(--ease-color-primary); + border-radius: var(--ease-radius-md); + display: flex; + align-items: center; + justify-content: center; + color: #fff; + font-size: 1.5rem; + font-weight: 700; +} + +.flip-demo { + perspective: 600px; +} + +.status-ok { + color: var(--ease-color-success); + font-weight: 600; +} + +.status-fail { + color: var(--ease-color-danger); + font-weight: 600; +} + +pre { + white-space: pre-wrap; + word-break: break-all; +} + +/* Reduced motion */ +@media (prefers-reduced-motion: reduce) { + .anim-spin, .anim-wobble, .anim-flip-x, .anim-flip-y, + .anim-heartbeat, .anim-rubber-band { + animation: none; + } +}