web-animation-css-animations-8a23a6 — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited web-animation-css-animations-8a23a6 (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Quick Guide: Transitions carry state changes (hover, focus, a toggled attribute);@keyframescarries motion that loops, auto-plays, or has more than two steps;animation-timelinecarries scroll- and viewport-linked progress. Confining animation totransformandopacitykeeps the frames on the compositor thread, and every animation gets aprefers-reduced-motionbranch.
Detailed Resources:
@property gradients, typewriter, stagger, clip-path morphs:hover, :focus-visible, a data attribute, a toggled class —then a transition on the base rule is the whole mechanism; follow examples/transitions.md.
@keyframes and an animation shorthand; follow examples/keyframes.md.
animation-timeline: scroll() or view() rather than time, and the keyframes describe progress from 0 to 1; follow examples/keyframes.md.
<critical_requirements>
Animate `transform` and `opacity`. Both are composited, so the frames run off the main thread and survive a busy tab; width, top and margin re-run layout on every frame instead.
Give every animation a `prefers-reduced-motion` branch. The preference is a vestibular safety setting rather than an off switch — an opacity fade at a shorter duration usually satisfies it while keeping the state change legible.
Use `ease-out` on enter and `ease-in` on exit. An element arriving decelerates into place and one leaving accelerates away; linear reads as mechanical for anything but continuous rotation.
Scope `will-change` to the interaction that needs it. Each declaration holds a compositing layer for as long as the rule applies, so a blanket selector holds one per element on the page at once.
</critical_requirements>
Auto-detection: @keyframes, transition-property, transition-duration, animation-timeline, scroll-timeline, view-timeline, animation-range, animation-fill-mode, prefers-reduced-motion, will-change, cubic-bezier, linear(), @property, steps(), transform-origin
Applies to:
Handled elsewhere:
handle; the Web Animations API is where one comes from, either element.animate() or element.getAnimations() over what CSS already declared
<decision_framework>
Element entering -> ease-out (fast start, slow settle)
Element exiting -> ease-in (slow start, fast departure)
Symmetric motion -> ease-in-out
Continuous rotation -> linear
Playful, overshooting -> cubic-bezier with a control point past 1
Anything else -> ease-outease, the browser default, is generic enough that two adjacent animations using it read as unrelated; name the curve instead.
animation-timeline: scroll() or view(), withanimation-range deciding where progress starts and ends
animation-delay computed from an --index custom property, withbackwards fill so the pre-animation state holds during the delay
declarative and reads the property each frame
cubic-bezier past the 0–1 range, or linear() with apoint list for a curve no cubic can express
</decision_framework>
<patterns>
Durations, easings and travel distances defined once as custom properties, so motion stays consistent across components and is retunable in one place.
:root {
--duration-fast: 150ms;
--duration-normal: 250ms;
--ease-out: cubic-bezier(0, 0, 0.2, 1); /* enter */
--ease-in: cubic-bezier(0.4, 0, 1, 1); /* exit */
--ease-spring: cubic-bezier(0.175, 0.885, 0.32, 1.275); /* overshoot */
--lift-md: -4px;
}Full code: examples/core.md
Name each property being transitioned, and express movement and size as transform so no frame triggers layout.
.card {
transition:
transform var(--duration-fast) var(--ease-out),
opacity var(--duration-fast) var(--ease-out);
}
.card:hover {
transform: translateY(var(--lift-md)) scale(1.02);
}translate() replaces top/left, scale() replaces width/height, and a pseudo-element whose opacity animates replaces an animated box-shadow.
Full code: examples/core.md
Two shapes. Progressive enhancement makes the still state the base and opts motion in, so an animation added later cannot escape the check:
.element {
opacity: 1;
transform: translateY(0);
}
@media (prefers-reduced-motion: no-preference) {
.element {
animation: fade-slide-in var(--duration-normal) var(--ease-out);
}
}Graceful degradation animates by default and overrides under reduce — the right shape when the reduced form is a shorter fade rather than nothing:
@media (prefers-reduced-motion: reduce) {
.notification {
animation: fade-in calc(var(--notification-duration) * 0.5) var(--ease-out);
}
}Reduced motion does not mean no animation. Opacity is generally safe; what it replaces is spatial travel, scale and rotation.
Full code: examples/core.md
For motion that loops, auto-plays on mount, or passes through more than two states.
@keyframes fade-slide-in {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.modal {
animation: fade-slide-in var(--modal-enter-duration) var(--ease-out) forwards;
}forwards holds the final state after the run; backwards shows the initial state during animation-delay.
Full code: examples/core.md and examples/keyframes.md
will-change promotes the element to its own compositing layer, which costs GPU memory proportional to the element's painted area. Declare it on the rule that is about to animate.
.card:hover {
will-change: transform;
}Full code: examples/core.md
animation-timeline drives keyframes from scroll progress instead of elapsed time, with no scroll listener and no per-frame JavaScript.
.progress-bar {
animation: grow-width linear;
animation-timeline: scroll();
}
@keyframes grow-width {
from {
transform: scaleX(0);
}
to {
transform: scaleX(1);
}
}scroll() tracks a scroll container's position; view() tracks the element's own passage through the viewport, with animation-range bounding it.
Browser support: Chrome/Edge 115+, Safari 26+, Firefox behind a flag.
Full code: examples/keyframes.md
Registering a custom property gives it a type, which is what makes it interpolable — gradient angles and colour stops animate only once registered.
@property --gradient-angle {
syntax: "<angle>";
initial-value: 0deg;
inherits: false;
}
@keyframes rotate-gradient {
to {
--gradient-angle: 360deg;
}
}Browser support: Chrome/Edge 85+, Safari 16.4+, Firefox 128+.
Full code: examples/keyframes.md
</patterns>
<red_flags>
Breaks at runtime:
transition: all — picks up every property a later edit adds, including layout-triggering ones —name each transitioned property explicitly
width, height, top, left, margin or padding — re-runs layout every frame anddrops frames as soon as the main thread is busy — animate transform and leave layout still
box-shadow — repaints the element and its shadow each frame — animate the opacity ofa pseudo-element that carries the shadow
will-change on a permanent or broad selector — holds one compositing layer per matched element,and on mobile enough layers exhaust GPU memory and kill the tab — declare it on the interaction rule only
prefers-reduced-motion branch — full-travel motion reaches users who haveasked their OS for none — add the branch when the animation is written, not afterwards
forwards — the element snaps back to its pre-animation state on thefinal frame — add the fill mode
Surprising behaviour:
transform on an ancestor creates a containing block, so a position: fixed descendant anchors tothat ancestor rather than to the viewport
will-change creates a stacking context, changing how z-index resolves against siblingsanimation-fill-mode: backwards, a delayed animation renders its final state during thedelay instead of its first frame
display: none cannot be animated — use opacity with visibility, or grid-template-rowsanimating 0fr to 1fr
stroke-dasharray and stroke-dashoffset; transform moves the pathrather than drawing it
scroll() timeline needs a scrollable ancestor — an overflow: hidden parent yields no progress</red_flags>
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.