agentleFS
soilmasssoilmass/motion-design-agent/skills/compositing/SKILL.md

compositing

Skill7 starsChanged 10 months agoMITView on GitHub

Multi-layer compositing animations including blend modes, layer effects, glass morphism, split screens, and picture-in-picture. Use when creating layered visual effects, overlay animations, or complex multi-element compositions.

2 other projects use a version of this.

Adopted by

Most recently taken up by

The file

Files
SKILL.md

Compositing

Layer-based animation and visual effects compositing.

Standards Reference

StandardDocumentation
W3C DTCGdesigntokens.org
CSS Blend Modesdeveloper.mozilla.org/CSS/mix-blend-mode
CSS Backdrop Filterdeveloper.mozilla.org/CSS/backdrop-filter

Quick Reference

Load from: tokens/compositing.tokens.json

Layer Timing

TokenValueUse
stagger.tight50msRapid layer sequence
stagger.normal100msStandard stagger
crossfade.normal400msLayer crossfade

Backdrop Blur

TokenValueUse
subtle4pxLight glass effect
medium12pxStandard glass
heavy24pxFrosted glass

Selection Guide

What compositing effect?
│
├── BLEND MODES → Color mixing
│   ├── Multiply → darken
│   ├── Screen → lighten
│   ├── Overlay → contrast
│   └── Transition between modes
│
├── GLASS/BLUR → Frosted effects
│   ├── Backdrop blur → glass morphism
│   ├── Scrim → darkened overlay
│   └── Animated blur changes
│
├── LAYERS → Stacked elements
│   ├── Z-index animation
│   ├── Staggered reveals
│   └── Crossfade transitions
│
├── SPLIT SCREEN → Multiple views
│   ├── Horizontal split
│   ├── Vertical split
│   └── Animated splits
│
└── PICTURE-IN-PICTURE → Inset content
    ├── Corner PiP
    ├── Draggable PiP
    └── Expand/collapse

Patterns

Glass Morphism

Frosted glass effect with animation.

CSS:

.glass {
  background: rgba(255, 255, 255, 0.1);
  backdrop-filter: blur(12px); /* {motion.compositing.backdrop-blur.medium} */
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid rgba(255, 255, 255, 0.2);
  transition: backdrop-filter 300ms ease-out;
}

.glass.focused {
  backdrop-filter: blur(24px); /* {motion.compositing.backdrop-blur.heavy} */
}

Animated blur:

function animateBlur(element, fromBlur, toBlur, duration = 300) {
  const startTime = performance.now();

  function update(currentTime) {
    const progress = Math.min((currentTime - startTime) / duration, 1);
    const eased = 1 - Math.pow(1 - progress, 3);

    const currentBlur = fromBlur + (toBlur - fromBlur) * eased;
    element.style.backdropFilter = `blur(${currentBlur}px)`;

    if (progress < 1) {
      requestAnimationFrame(update);
    }
  }

  requestAnimationFrame(update);
}

Scrim Overlay

Animated darkening overlay.

CSS:

.scrim {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0);
  pointer-events: none;
  transition: background 300ms ease-out;
}

.scrim.active {
  background: rgba(0, 0, 0, 0.5);
  /* {motion.compositing.opacity-layers.scrim-medium} */
  pointer-events: auto;
}

/* With blur */
.scrim.blur.active {
  backdrop-filter: blur(4px);
}

Blend Mode Transition

Animated blend mode changes.

JavaScript:

class BlendTransition {
  constructor(element) {
    this.element = element;
    this.overlay = this.createOverlay();
  }

  createOverlay() {
    const overlay = document.createElement('div');
    overlay.style.cssText = `
      position: absolute;
      inset: 0;
      opacity: 0;
      transition: opacity 300ms;
    `;
    this.element.style.position = 'relative';
    this.element.appendChild(overlay);
    return overlay;
  }

  transitionTo(blendMode, color, duration = 300) {
    // Fade in new blend
    this.overlay.style.background = color;
    this.overlay.style.mixBlendMode = blendMode;

    this.overlay.animate([
      { opacity: 0 },
      { opacity: 1 }
    ], {
      duration,
      fill: 'forwards',
      easing: 'ease-out'
    });
  }

  remove(duration = 300) {
    this.overlay.animate([
      { opacity: 1 },
      { opacity: 0 }
    ], {
      duration,
      fill: 'forwards',
      easing: 'ease-in'
    }).onfinish = () => {
      this.overlay.style.opacity = 0;
    };
  }
}

Layer Stagger

Sequential layer animations.

CSS:

.layer-stack .layer {
  opacity: 0;
  transform: translateY(20px);
}

.layer-stack.animate .layer {
  animation: layer-in 400ms ease-out forwards;
}

.layer-stack.animate .layer:nth-child(1) { animation-delay: 0ms; }
.layer-stack.animate .layer:nth-child(2) { animation-delay: 100ms; }
/* {motion.compositing.layer-timing.stagger.normal} */
.layer-stack.animate .layer:nth-child(3) { animation-delay: 200ms; }
.layer-stack.animate .layer:nth-child(4) { animation-delay: 300ms; }

@keyframes layer-in {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

Framer Motion:

const containerVariants = {
  hidden: {},
  visible: {
    transition: {
      staggerChildren: 0.1 // {motion.compositing.layer-timing.stagger.normal}
    }
  }
};

const layerVariants = {
  hidden: { opacity: 0, y: 20 },
  visible: { opacity: 1, y: 0 }
};

<motion.div variants={containerVariants} initial="hidden" animate="visible">
  {layers.map((layer, i) => (
    <motion.div key={i} variants={layerVariants}>
      {layer}
    </motion.div>
  ))}
</motion.div>

Crossfade Layers

Smooth layer crossfade transition.

CSS:

.crossfade-container {
  position: relative;
}

.crossfade-layer {
  position: absolute;
  inset: 0;
  transition: opacity 400ms ease-out;
  /* {motion.compositing.layer-timing.crossfade.normal} */
}

.crossfade-layer.active {
  opacity: 1;
  z-index: 1;
}

.crossfade-layer:not(.active) {
  opacity: 0;
  z-index: 0;
}

JavaScript:

function crossfade(fromLayer, toLayer, duration = 400) {
  // Prepare toLayer
  toLayer.style.opacity = '0';
  toLayer.style.zIndex = '1';

  // Animate both simultaneously
  fromLayer.animate([
    { opacity: 1 },
    { opacity: 0 }
  ], { duration, fill: 'forwards' });

  toLayer.animate([
    { opacity: 0 },
    { opacity: 1 }
  ], { duration, fill: 'forwards' });
}

Split Screen

Animated split screen effect.

CSS:

.split-container {
  display: flex;
  height: 100vh;
  overflow: hidden;
}

.split-left,
.split-right {
  flex: 1;
  transition: flex 800ms cubic-bezier(0.4, 0, 0.2, 1);
  /* {motion.compositing.split-screen.duration.normal} */
}

.split-container.focus-left .split-left {
  flex: 2;
}

.split-container.focus-left .split-right {
  flex: 1;
}

/* With gap */
.split-container.with-gap {
  gap: 8px; /* {motion.compositing.split-screen.gap.normal} */
}

Vertical split:

.split-vertical {
  flex-direction: column;
}

.split-top,
.split-bottom {
  flex: 1;
  transition: flex 800ms cubic-bezier(0.4, 0, 0.2, 1);
}

Picture-in-Picture

Floating PiP element.

CSS:

.pip-container {
  position: fixed;
  bottom: 24px;
  right: 24px;
  width: 320px;
  aspect-ratio: 16/9;
  border-radius: 12px; /* {motion.compositing.picture-in-picture.corner-radius} */
  overflow: hidden;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
  transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1);
  /* {motion.compositing.picture-in-picture.transition} */
  z-index: 1000;
}

.pip-container.minimized {
  width: 160px;
  opacity: 0.8;
}

.pip-container.expanded {
  width: 100%;
  height: 100%;
  bottom: 0;
  right: 0;
  border-radius: 0;
}

Draggable PiP:

class DraggablePiP {
  constructor(element) {
    this.element = element;
    this.isDragging = false;
    this.currentPos = { x: 0, y: 0 };
    this.bind();
  }

  bind() {
    this.element.addEventListener('mousedown', (e) => this.startDrag(e));
    document.addEventListener('mousemove', (e) => this.drag(e));
    document.addEventListener('mouseup', () => this.endDrag());
  }

  startDrag(e) {
    this.isDragging = true;
    this.element.style.transition = 'none';
    this.startPos = {
      x: e.clientX - this.currentPos.x,
      y: e.clientY - this.currentPos.y
    };
  }

  drag(e) {
    if (!this.isDragging) return;

    this.currentPos = {
      x: e.clientX - this.startPos.x,
      y: e.clientY - this.startPos.y
    };

    this.element.style.transform = `translate(${this.currentPos.x}px, ${this.currentPos.y}px)`;
  }

  endDrag() {
    this.isDragging = false;
    this.element.style.transition = 'transform 300ms ease-out';
    this.snapToCorner();
  }

  snapToCorner() {
    const rect = this.element.getBoundingClientRect();
    const centerX = rect.left + rect.width / 2;
    const centerY = rect.top + rect.height / 2;

    // Determine closest corner
    const snapX = centerX < window.innerWidth / 2 ? 24 : window.innerWidth - rect.width - 24;
    const snapY = centerY < window.innerHeight / 2 ? 24 : window.innerHeight - rect.height - 24;

    this.element.style.left = `${snapX}px`;
    this.element.style.top = `${snapY}px`;
    this.element.style.right = 'auto';
    this.element.style.bottom = 'auto';
    this.element.style.transform = 'none';
    this.currentPos = { x: 0, y: 0 };
  }
}

Color Overlay Animation

Animated color grading overlay.

CSS:

.color-overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
  mix-blend-mode: overlay;
  opacity: 0;
  transition: opacity 500ms ease-out, background 500ms ease-out;
  /* {motion.compositing.color-overlay.fade-speed.normal} */
}

.color-overlay.warm {
  background: rgba(255, 200, 150, 0.3);
  opacity: 1;
}

.color-overlay.cool {
  background: rgba(150, 200, 255, 0.3);
  opacity: 1;
}

.color-overlay.dramatic {
  background: linear-gradient(
    to bottom,
    rgba(0, 0, 50, 0.3),
    rgba(50, 0, 50, 0.3)
  );
  opacity: 1;
}

Layer Effects Animation

Filter transitions.

CSS:

.layer {
  transition: filter 500ms ease-out;
}

.layer.desaturated {
  filter: saturate(0); /* {motion.compositing.layer-effects.saturation.desaturate} */
}

.layer.muted {
  filter: saturate(0.5); /* {motion.compositing.layer-effects.saturation.muted} */
}

.layer.vibrant {
  filter: saturate(1.3) contrast(1.1);
  /* {motion.compositing.layer-effects.saturation.vibrant} */
}

.layer.darkened {
  filter: brightness(0.7);
  /* {motion.compositing.layer-effects.brightness.dark} */
}

Reduced Motion

For prefers-reduced-motion: reduce:

Full MotionReduced Alternative
Layer staggerInstant reveal
CrossfadeInstant swap
Split animationInstant split
PiP transitionInstant position
Blur animationStatic blur

Implementation:

@media (prefers-reduced-motion: reduce) {
  .layer-stack .layer,
  .crossfade-layer,
  .split-left,
  .split-right,
  .pip-container {
    transition: none;
  }

  .layer-stack.animate .layer {
    animation: none;
    opacity: 1;
    transform: none;
  }
}

Cross-References

  • See opacity/ for fade patterns
  • See blur/ for blur effects
  • See masks/ for layer masking
  • See transitions/ for page transitions

References

  • references/blend-modes.md — Blend mode reference guide

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.