Loading...
Loading...
This skill should be used when the user asks to "add an animated background", "build a mesh/gradient background", "make an aurora/shader background", "add constellation/particle background", "animated hero background", or "a subtle looping background behind content". Covers CSS mesh gradients, GLSL shader gradients (Three.js), canvas particle constellations, seamless loops, and reduced-motion/performance handling — fully self-contained.
npx skill4agent add iart-ai/motion-design-skills motion-background| Look | Technique | Cost |
|---|---|---|
| Soft animated gradient/mesh | CSS gradients + keyframes | Cheapest, no JS |
| Flowing aurora / organic noise | GLSL fragment shader (Three.js full-screen quad) | GPU, moderate |
| Constellation / drifting dots | Canvas 2D particles | CPU, scales with count |
| Depth / parallax | Same shader/particles with mouse-driven offset | Moderate |
linear-gradientbackdropprefers-reduced-motionbackground-position.bg {
position: fixed; inset: 0; z-index: -1;
background:
radial-gradient(40% 50% at 20% 30%, #5b8cff55, transparent 60%),
radial-gradient(45% 55% at 80% 20%, #b05bff55, transparent 60%),
radial-gradient(50% 60% at 50% 80%, #2de1c255, transparent 60%),
#0b0e1a;
background-size: 200% 200%;
animation: meshmove 24s ease-in-out infinite;
}
@keyframes meshmove {
0%, 100% { background-position: 0% 0%, 100% 0%, 50% 100%; }
50% { background-position: 30% 20%, 70% 30%, 60% 70%; }
}
@media (prefers-reduced-motion: reduce) {
.bg { animation: none; }
}0%100%uTimeimport * as THREE from 'three';
const canvas = document.querySelector('#bg');
const renderer = new THREE.WebGLRenderer({canvas, antialias: true});
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); // cap for perf
const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
const uniforms = {
uTime: {value: 0},
uRes: {value: new THREE.Vector2()},
uColorA: {value: new THREE.Color('#5b8cff')},
uColorB: {value: new THREE.Color('#b05bff')},
};
const material = new THREE.ShaderMaterial({
uniforms,
vertexShader: `void main(){ gl_Position = vec4(position, 1.0); }`,
fragmentShader: `
precision highp float;
uniform float uTime; uniform vec2 uRes;
uniform vec3 uColorA, uColorB;
// hash + value noise
float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1,311.7)))*43758.5453); }
float noise(vec2 p){
vec2 i = floor(p), f = fract(p);
float a = hash(i), b = hash(i+vec2(1,0));
float c = hash(i+vec2(0,1)), d = hash(i+vec2(1,1));
vec2 u = f*f*(3.0-2.0*f);
return mix(mix(a,b,u.x), mix(c,d,u.x), u.y);
}
float fbm(vec2 p){
float v=0.0, a=0.5;
for(int i=0;i<5;i++){ v += a*noise(p); p*=2.0; a*=0.5; }
return v;
}
void main(){
vec2 uv = gl_FragCoord.xy / uRes.xy;
float n = fbm(uv*3.0 + vec2(uTime*0.05, uTime*0.03));
vec3 col = mix(uColorA, uColorB, smoothstep(0.2, 0.8, n + uv.y*0.3));
gl_FragColor = vec4(col, 1.0);
}`,
});
const quad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), material);
scene.add(quad);
function resize(){
renderer.setSize(window.innerWidth, window.innerHeight);
uniforms.uRes.value.set(window.innerWidth, window.innerHeight);
}
window.addEventListener('resize', resize); resize();
const clock = new THREE.Clock();
let running = true;
function loop(){
if (running){
uniforms.uTime.value = clock.getElapsedTime();
renderer.render(scene, camera);
}
requestAnimationFrame(loop);
}
loop();const canvas = document.querySelector('#stars');
const ctx = canvas.getContext('2d');
let W, H, pts;
const COUNT = 80, LINK = 120;
function init(){
W = canvas.width = innerWidth; H = canvas.height = innerHeight;
pts = Array.from({length: COUNT}, () => ({
x: Math.random()*W, y: Math.random()*H,
vx: (Math.random()-0.5)*0.3, vy: (Math.random()-0.5)*0.3,
}));
}
addEventListener('resize', init); init();
function frame(){
ctx.clearRect(0, 0, W, H);
for (const p of pts){
p.x += p.vx; p.y += p.vy;
if (p.x<0||p.x>W) p.vx*=-1;
if (p.y<0||p.y>H) p.vy*=-1;
ctx.fillStyle = '#9db4ff'; ctx.fillRect(p.x, p.y, 2, 2);
}
for (let i=0;i<COUNT;i++) for (let j=i+1;j<COUNT;j++){
const dx=pts[i].x-pts[j].x, dy=pts[i].y-pts[j].y;
const d=Math.hypot(dx, dy);
if (d<LINK){
ctx.strokeStyle = `rgba(157,180,255,${1-d/LINK})`;
ctx.beginPath(); ctx.moveTo(pts[i].x,pts[i].y); ctx.lineTo(pts[j].x,pts[j].y); ctx.stroke();
}
}
requestAnimationFrame(frame);
}
frame();const PERIOD = 12; // seconds
const phase = (t % PERIOD) / PERIOD * Math.PI * 2;
const offset = Math.sin(phase) * amp; // returns to start at t = PERIODsincosphaseuTime = phasesincosconst reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
if (reduce) {
running = false; // render one static frame, then stop
renderer.render(scene, camera);
}
// Pause when the tab is hidden
document.addEventListener('visibilitychange', () => {
running = !document.hidden && !reduce;
});
// Pause when the canvas scrolls offscreen
new IntersectionObserver(([e]) => {
running = e.isIntersecting && !document.hidden && !reduce;
}).observe(canvas);runningPackaged helper ():scripts/freezes thescripts/seek-shot.sh anim.html 0 1.5 3harness and screenshots each moment;?t=Ntiles them for one-glance review. Seescripts/contact-sheet.sh sheet.png frame-*.png.scripts/README.md
.html.html@keyframes<script>
const t = new URLSearchParams(location.search).get("t");
if (t !== null) {
const T = parseFloat(t);
// CSS mesh gradient:
document.querySelectorAll(".bg").forEach(el => {
el.style.animationDelay = (-T) + "s";
el.style.animationPlayState = "paused";
});
// GLSL shader instead? → render exactly one frame at fixed time:
// uniforms.uTime.value = T; renderer.render(scene, camera); running = false;
// Canvas particles? → seed deterministically, step the sim to T, draw once, stop the loop.
}
window.__ready = true; // ready signal for headless wait
</script>…/bg.html?t=0?t=<period/2>?t=<period>t=0t=periodnpx playwright screenshot --wait-for-timeout=600 "file://$PWD/bg.html?t=12" frame-mid.png?t=Nprefers-reduced-motiondevicePixelRatio| Need | Do |
|---|---|
| Cheapest gradient | CSS layered |
| Organic flow | GLSL |
| Network/dots | canvas particles + distance-linked lines |
| No seam | drive motion by |
| Perf cap | |
| Accessibility | |
| Save battery | pause on |
devicePixelRatiobackground-positionreferences/background-recipes.md