SPB Git

spb/ultra-sharp-agent-skills Public

Ultra-Sharp Agent Skills — a research-first skill-authoring system + 72 production-ready skills for AI agents.

Python 100%

# UI Animation Patterns — Copy-Paste Reference

# Contents

  • Motion tokens
  • Micro-interaction (button press/hover)
  • Enter/exit transition (dialog, toast)
  • Scroll reveal with IntersectionObserver
  • Page-load sequence (staggered)
  • Web Animations API sequencing
  • Reduced-motion gate
  • Gotchas

# Motion tokens

css
:root {
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);   /* enter: fast start, soft landing */
  --ease-in:  cubic-bezier(0.7, 0, 0.84, 0);   /* exit: accelerate away */
  --dur-fast: 150ms;   /* hover, press feedback */
  --dur-base: 250ms;   /* enters, exits, toggles */
  --dur-slow: 450ms;   /* scene-level changes */
}

# Micro-interaction (button press/hover)

css
.btn {
  transition: transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.btn:hover  { transform: translateY(-1px); }
.btn:active { transform: translateY(0) scale(0.98); }

# Enter/exit transition (dialog, toast)

Enter with ease-out, exit faster with ease-in.

css
.toast {
  transition: transform var(--dur-base) var(--ease-out),
              opacity   var(--dur-base) var(--ease-out);
}
.toast[data-state="closed"] {
  transform: translateY(8px);
  opacity: 0;
  transition-duration: var(--dur-fast);
  transition-timing-function: var(--ease-in);
}

Animating from display: none (modern CSS):

css
dialog[open] {
  opacity: 1;
  transform: none;
  transition: opacity var(--dur-base) var(--ease-out),
              transform var(--dur-base) var(--ease-out),
              display var(--dur-base) allow-discrete;
  @starting-style { opacity: 0; transform: translateY(12px); }
}

# Scroll reveal with IntersectionObserver

Content is fully visible without JS; the class only enables the hidden start state.

html
<section data-reveal>…</section>
css
.js [data-reveal]          { opacity: 0; transform: translateY(16px); }
.js [data-reveal].is-shown {
  opacity: 1; transform: none;
  transition: opacity var(--dur-slow) var(--ease-out),
              transform var(--dur-slow) var(--ease-out);
}
js
document.documentElement.classList.add('js');
const io = new IntersectionObserver((entries) => {
  for (const e of entries) if (e.isIntersecting) {
    e.target.classList.add('is-shown');
    io.unobserve(e.target);            // reveal once
  }
}, { threshold: 0.15 });               // fire when 15% visible
document.querySelectorAll('[data-reveal]').forEach(el => io.observe(el));

# Page-load sequence (staggered)

One orchestrated moment: hero elements enter in order, 60ms apart.

css
.hero > * {
  opacity: 0;
  transform: translateY(12px);
  animation: enter var(--dur-slow) var(--ease-out) forwards;
  animation-delay: calc(var(--i) * 60ms);
}
@keyframes enter { to { opacity: 1; transform: none; } }
html
<div class="hero">
  <h1 style="--i:0">…</h1>
  <p  style="--i:1">…</p>
  <a  style="--i:2" class="btn">…</a>
</div>

# Web Animations API sequencing

For interruption-safe, runtime-computed motion.

js
const panel = document.querySelector('.panel');
const anim = panel.animate(
  [{ transform: 'translateX(100%)' }, { transform: 'none' }],
  { duration: 250, easing: 'cubic-bezier(0.16,1,0.3,1)', fill: 'forwards' }
);
// interruptible: reverse mid-flight instead of restarting
closeBtn.onclick = () => anim.reverse();
await anim.finished;                   // sequencing point

# Reduced-motion gate

css
@media (prefers-reduced-motion: reduce) {
  [data-reveal], .hero > * { opacity: 1 !important; transform: none !important;
                             animation: none !important; transition: none !important; }
}
js
const reduced = matchMedia('(prefers-reduced-motion: reduce)').matches;
if (!reduced) el.animate(/* … */);

# Gotchas

  • transition: all invites accidental layout animations and future regressions — always list properties.
  • animation-fill-mode: forwards keeps the element on its final keyframe, which can trap it in a stale state after class changes; prefer transitioning real end states.
  • will-change left permanently on many elements consumes GPU memory — add before heavy animation, remove after.
  • Staggers multiply duration: 10 items × 100ms delay = a 1s+ sequence. Cap total sequence time near 700ms.
  • height: auto can't transition; animate grid-template-rows: 0fr → 1fr on a wrapper, or use WAAPI with measured pixel values.
  • IntersectionObserver with threshold: 1.0 never fires on elements taller than the viewport.