motion-color-and-light
Skill0 starsChanged 40 days agoMITView on GitHub
Choose palette, contrast, and lighting for a moving video - not a static UI - so color survives H.264/VP9 compression, banding, and YouTube/Instagram/TikTok recompression across OLED, LCD, and projectors. Use when picking video colors, building a gradient mesh, adding glow/bloom/depth, setting a dark-background default, asking "why does my gradient band", "why is my neon muddy after upload", "is this contrast video-safe", or "what palette for my video". Do NOT use when laying out scene timing or beats - use video-storyboard instead. Do NOT use for motion-easing or animation-feel decisions - use motion-design-principles instead. Do NOT use to style or animate the on-screen text itself - use kinetic-typography instead (you give it the palette, it sets the type in motion). Do NOT use to structure a product walkthrough or decide what to show - use product-demo-director instead. Do NOT use for audio, beat-matching, or scoring - use sound-and-music-sync instead. Do NOT use to re-crop or re-spec a video for a platform's aspect ratio and safe areas - use social-video-formatter instead.
In this repository6 other files it gives its agents.
The file
Show its rules and text
We split this skill into the rules it gives an agent
22 rules. About 0 are common, 11 are distinctive, and 11 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈11
Distinctive · estimate
This author's own choices. Worth a look.
11
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 29
Run these in order. Do not pick a gradient before you have set the background default, because the background decides which gradients will …
- Very specific: line 44
Use a very dark desaturated tint, never pure black - pure #000000 crushes to a flat void, kills the sense of depth, and gives bloom nothing …
- Distinctive: line 49
When you DO want a light background (clean SaaS, editorial, daylight product shot), commit to it fully and raise your shadows - a light …
- Very specific: line 62
Avoid pure white #FFFFFF on pure black - the maximum-contrast edge is exactly where the encoder rings (haloes) and where bright text …
- Distinctive: line 65
Keep critical text inside the title-safe area (≈90% of frame) - TVs and some players crop the edges.
- Very specific: line 67
Run contrast_check.js (below) on every text/background pair.
- Distinctive: line 72
Keep it disciplined: a dark background, a near-white for text, and ONE or two accents that do the emotional work.
- Distinctive: line 99
Bias the mesh toward the accent hue,: not gray - a gradient that carries a little chroma hides banding better than a pure-luminance gray …
- Very specific: line 102
Keep mesh pools soft and off-center.
- Very specific: line 122
Before committing, mentally (and with the calculator) flag the danger zones, then verify on a real encode - render a few seconds via …
- Very specific: line 140
Every text/background pair has been run through contrast_check.js and clears the video-safe threshold without sitting on the …
- Distinctive: line 145
Every gradient and glow ships with a noise/dither overlay, and the banding-prone ramps were checked on a real encode at the target bitrate, …
- Very specific: line 152
Do NOT use pure #000000 as the stage or pure #FFFFFF for large fills/text - they crush depth and ring on encode.
- Distinctive: line 154
Do NOT treat WCAG UI contrast as sufficient.
- Distinctive: line 156
Do NOT ship a smooth dark gradient or wide glow without a noise/dither overlay - it WILL band after upload.
- Distinctive: line 158
Do NOT fill the whole frame with a saturated accent.
- Very specific: line 160
Do NOT judge color only in Remotion Studio - Studio shows your authored pixels, not the recompressed ones.
- Very specific: line 163
Do NOT set scene timing or beats here - that is video-storyboard.
- Very specific: line 166
Do NOT animate or style the on-screen text - hand the palette to kinetic-typography and let it set the type in motion.
- Very specific: line 168
Do NOT decide what the demo shows or how it is structured (product-demo-director), touch audio/beats/scoring (sound-and-music-sync), or …
- Distinctive: line 242
Read it: the tinted near-white title is the clean win - high ratio, no ringing.
- Distinctive: line 306
Always preview on the actual platform, not just your local file.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Motion Color and Light
This is the taste layer for color in motion. A palette that looks immaculate as a static Figma frame can fall apart the moment it moves and gets encoded: smooth gradients break into visible stair-step bands, saturated neon turns to muddy mush, and a contrast ratio that passed for UI text disappears under motion blur. Video is not UI. The pixels you author are not the pixels the viewer sees - they pass through H.264/VP9 compression, chroma subsampling, and a second re-compression pass when YouTube, Instagram, or TikTok ingests the file, then land on a screen you do not control (a bright OLED phone, a washed-out conference projector, an old LCD).
You decide palette, contrast, and lighting so the image survives all of that.
This pairs with the remotion-video-production mechanics pack: you make the color
decisions here, then the resulting hex values and gradient definitions become
inputs to remotion-compose. Storyboard and motion decisions come from
video-storyboard and motion-design-principles; you take the locked beat list
and give it a palette.
Workflow
Run these in order. Do not pick a gradient before you have set the background default, because the background decides which gradients will even survive.
Step 1 - Default to a dark background
Unless the brief demands otherwise, start on a dark, near-black background (not
pure #000). Dark backgrounds are the video default for real reasons:
- Glow, bloom, and light effects only read against darkness. On white, a glow has nothing to bloom into.
- Dark fields hide compression noise; large flat bright areas show every macroblock and mosquito artifact the encoder leaves behind.
- OLED screens render deep darks beautifully and save power; the look feels premium.
Use a very dark desaturated tint, never pure black - pure #000000 crushes to a
flat void, kills the sense of depth, and gives bloom nothing to sit on. Reach for
something like #0B0B0F (the house default in remotion-compose) or a tinted
near-black that leans toward your accent hue (#0A0E14 for a cool/blue brand).
When you DO want a light background (clean SaaS, editorial, daylight product shot), commit to it fully and raise your shadows - a light video lives or dies on soft, layered shadows, not glow.
Step 2 - Set video-safe contrast (not UI contrast)
UI contrast targets (WCAG 4.5:1 for body text) are a floor, not the goal, and they assume a static pixel the reader can stare at. In motion, text is on screen for 1-3 seconds, often moving, often motion-blurred, and often recompressed to mush. Push contrast harder:
- On-screen titles against a dark background: near-white (
#F5F6F8), not gray. Gray-on-dark that passes WCAG still reads as weak and smears under motion blur. - Avoid pure white
#FFFFFFon pure black - the maximum-contrast edge is exactly where the encoder rings (haloes) and where bright text "buzzes" on cheap screens. Pull the white down a hair (#F2F2F5) and the black up (#0B0B0F). - Keep critical text inside the title-safe area (≈90% of frame) - TVs and some players crop the edges.
- Run
contrast_check.js(below) on every text/background pair. It reports the ratio AND flags the pure-white-on-pure-black ringing trap and weak gray text.
Step 3 - Build the palette around one accent, on a dark stage
Keep it disciplined: a dark background, a near-white for text, and ONE or two accents that do the emotional work. More than two saturated accents in motion reads as chaos and gives the encoder more saturated edges to mangle.
- Saturated accents survive better as light (glows, strokes, small fills) than as large flat fills - see Step 5 on what compression does to them.
- Pick accents with enough luminance separation from the background that they pop without needing maximum saturation. A mid-bright accent at 80% saturation reads cleaner post-compression than a 100%-saturation neon.
Step 4 - Use gradient meshes for depth, and make them survive encoding
Flat dark backgrounds can feel dead. A subtle gradient mesh - two or three soft radial pools of color bleeding into the dark - adds depth and a sense of light source without any literal object. This is the single highest-leverage move for making a video look expensive. But gradients are exactly what compression destroys via banding: a smooth dark-to-darker ramp gets quantized into visible stair-step bands.
Defeat banding at author time:
- Add noise/dither. A faint film-grain or noise overlay (1-3% opacity) breaks up the banding into the encoder by giving it high-frequency detail to spend bits on. This is the #1 fix and costs almost nothing.
- Keep gradient contrast low and the ramp long. Banding is worst on subtle, slow ramps over a large area in the darks. A longer, gentler transition or a slightly higher-contrast ramp bands less visibly.
- Bias the mesh toward the accent hue, not gray - a gradient that carries a little chroma hides banding better than a pure-luminance gray ramp, because the encoder treats luma and chroma differently.
- Keep mesh pools soft and off-center; hand the radial-gradient CSS straight to
remotion-composeas theAbsoluteFillbackground.
Step 5 - Add glow, bloom, and depth deliberately
Light is what sells motion graphics as premium. Layer it:
- Glow = a colored blur behind a bright element (a text-shadow or a blurred duplicate). Tie the glow color to the element, on the dark stage.
- Bloom = the bright core blowing out slightly into its glow - fake it with a white-hot center fading to the accent.
- Depth = near-large-bright vs far-small-dim, plus a faint atmospheric haze (the gradient mesh) so foreground elements feel lit from somewhere.
Compression caveat: glow is a soft gradient too, so it bands and gets crushed on low bitrate. Keep glows generous in radius (soft, wide blurs survive; tight hard-edged ones get eaten) and lean on the Step 4 noise overlay.
Step 6 - Predict what compression does, then verify
Before committing, mentally (and with the calculator) flag the danger zones, then
verify on a real encode - render a few seconds via remotion-render and watch it
at the target platform's bitrate, not just in Studio.
- Large flat saturated fills (especially reds and deep blues) → chroma subsampling (4:2:0) blurs the color edges and the fill goes muddy. Use the accent as light/stroke, not a full-frame fill.
- Subtle dark gradients → banding. Apply Step 4.
- Pure-white-on-pure-black hard edges → ringing/haloes. Apply Step 2.
- Fast motion of fine detail → the encoder spends its bitrate on motion and starves detail; do not put your most delicate gradient on your fastest move.
Quality bar
The color pass is A+ only when all hold:
- Background is a tinted near-black (or a fully-committed light with layered
shadows), never pure
#000000or pure#FFFFFFas the working stage. - Every text/background pair has been run through
contrast_check.jsand clears the video-safe threshold without sitting on the pure-white/pure-black ringing edge. - The palette is one dark stage + one near-white + at most two accents; accents are used as light (glow/stroke/small fill) more than as large flat fills.
- Every gradient and glow ships with a noise/dither overlay, and the banding-prone ramps were checked on a real encode at the target bitrate, not only in preview.
- The deliverable is a token set (hex + gradient/glow CSS) ready to paste into
remotion-compose, not a vague mood description.
Do NOT
- Do NOT use pure
#000000as the stage or pure#FFFFFFfor large fills/text - they crush depth and ring on encode. Tint them in a hair. - Do NOT treat WCAG UI contrast as sufficient; motion-blurred, briefly-shown text needs more separation than static UI text.
- Do NOT ship a smooth dark gradient or wide glow without a noise/dither overlay - it WILL band after upload.
- Do NOT fill the whole frame with a saturated accent; chroma subsampling turns big saturated fills muddy. Use saturation as light, not as paint.
- Do NOT judge color only in Remotion Studio - Studio shows your authored pixels,
not the recompressed ones. Verify on a real
remotion-renderencode at the platform bitrate. - Do NOT set scene timing or beats here - that is
video-storyboard. Do NOT make the easing/animation-feel calls - that ismotion-design-principles. You own color and light only. - Do NOT animate or style the on-screen text - hand the palette to
kinetic-typographyand let it set the type in motion. - Do NOT decide what the demo shows or how it is structured
(
product-demo-director), touch audio/beats/scoring (sound-and-music-sync), or re-crop and re-spec for a platform's aspect ratio and safe areas (social-video-formatter).
Calculator
Self-contained Node script. Save as contrast_check.js and run with
node contrast_check.js. No dependencies. Edit the pairs array with your
actual text/background hex values.
// Video-safe contrast checker. Run: node contrast_check.js
// Reports WCAG ratio plus video-specific warnings (ringing edge, weak gray).
const pairs = [
{ label: 'Title on stage', fg: '#F2F2F5', bg: '#0B0B0F' },
{ label: 'Naive max', fg: '#FFFFFF', bg: '#000000' },
{ label: 'Gray subtitle', fg: '#8A8A93', bg: '#0B0B0F' },
{ label: 'Accent on stage', fg: '#6C5CE7', bg: '#0B0B0F' },
]
const hexToRgb = (h) => {
const n = parseInt(h.replace('#', ''), 16)
return [(n >> 16) & 255, (n >> 8) & 255, n & 255]
}
// Relative luminance per WCAG.
const lum = (h) => {
const c = hexToRgb(h).map((v) => {
const s = v / 255
return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4)
})
return 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2]
}
const ratio = (fg, bg) => {
const a = lum(fg), b = lum(bg)
const hi = Math.max(a, b), lo = Math.min(a, b)
return (hi + 0.05) / (lo + 0.05)
}
const isNearPure = (h, target) => {
const [r, g, b] = hexToRgb(h)
const t = target === 'white' ? 255 : 0
return Math.abs(r - t) <= 6 && Math.abs(g - t) <= 6 && Math.abs(b - t) <= 6
}
// Video-safe floor: push past the WCAG 4.5 UI floor for moving, brief text.
const VIDEO_SAFE = 7.0
for (const p of pairs) {
const r = ratio(p.fg, p.bg)
const warns = []
if (r < 4.5) warns.push('FAILS even UI contrast')
else if (r < VIDEO_SAFE) warns.push('passes UI but weak for motion (<7:1)')
if (isNearPure(p.fg, 'white') && isNearPure(p.bg, 'black'))
warns.push('pure-white-on-pure-black: rings/haloes on encode - tint both in')
console.log(
p.label.padEnd(18),
p.fg, 'on', p.bg,
'-> ' + r.toFixed(2) + ':1',
warns.length ? ' [' + warns.join('; ') + ']' : ' ok (video-safe)'
)
}Worked example output
With the pairs above the script prints:
Title on stage #F2F2F5 on #0B0B0F -> 17.58:1 ok (video-safe)
Naive max #FFFFFF on #000000 -> 21.00:1 [pure-white-on-pure-black: rings/haloes on encode - tint both in]
Gray subtitle #8A8A93 on #0B0B0F -> 5.74:1 [passes UI but weak for motion (<7:1)]
Accent on stage #6C5CE7 on #0B0B0F -> 4.04:1 [FAILS even UI contrast]Read it: the tinted near-white title is the clean win - high ratio, no ringing. The naive pure-white-on-pure-black scores the highest ratio of all yet is the WORST choice, because maximum-contrast edges are exactly where the encoder rings; tint both ends in. The gray subtitle passes the old UI floor but smears under motion blur - push it brighter. The accent fails as TEXT, which is fine: an accent is for glow, strokes, and fills, never body copy on the dark stage. Use the accent as light (Step 5), not as words.
Template: color-and-light tokens
Copy this, fill it, and hand it to remotion-compose as the composition's color
props. This is the deliverable.
MOTION COLOR + LIGHT TOKENS. [FILL: project name]. Platform: [FILL: 16:9 / 9:16]
STAGE
Background (tinted near-black): #[FILL e.g. 0B0B0F]
Stage tint leans toward: [FILL: neutral / cool / warm / accent]
TEXT
Primary title (near-white): #[FILL e.g. F2F2F5] contrast: ___:1 (>=7)
Secondary / subtitle: #[FILL] contrast: ___:1
ACCENTS (max two; used as light, not full fills)
Accent 1: #[FILL] used for: [FILL: glow / stroke / fill]
Accent 2 (optional): #[FILL] used for: [FILL]
GRADIENT MESH (background depth)
radial-gradient CSS: [FILL: e.g. radial-gradient(60% 50% at 30% 20%,
#1A1340 0%, transparent 60%)]
second pool (optional): [FILL]
Noise/dither overlay: [FILL: grain PNG or CSS noise @ __% opacity] (REQUIRED)
GLOW / BLOOM
Glow on titles: [FILL: text-shadow 0 0 __px <accent @ __%>]
Bloom core: [FILL: white-hot center -> accent]
COMPRESSION CHECKLIST (verify on a real remotion-render encode)
[ ] No large flat saturated fill (chroma subsampling -> muddy)
[ ] Every gradient/glow has the noise overlay (banding)
[ ] No pure-white-on-pure-black hard edges (ringing)
[ ] Checked at target platform bitrate, not just in Studioreferences/why-video-color-is-different
The authored pixel is not the delivered pixel. Three transforms sit between them:
- Codec quantization (H.264 / VP9 / AV1). The encoder throws away detail it judges imperceptible to hit a bitrate. In smooth gradients it quantizes the subtle ramp into discrete steps - that is banding. Adding a touch of noise gives the encoder high-frequency detail to preserve, which dithers the banding away. This is why grain looks expensive: it is cheap insurance against banding.
- Chroma subsampling (4:2:0). Almost all delivery video stores full luminance but only quarter-resolution color. Bright/dark detail survives; fine color detail and saturated color edges blur. Large flat saturated fills go muddy; thin saturated strokes on a dark field survive better because the eye reads them by luminance. Use saturation as light, not as paint.
- Platform recompression. YouTube/Instagram/TikTok re-encode your upload to their own ladder. Your careful first encode is recompressed a second time, so anything marginal (a subtle gradient, a hard max-contrast edge) degrades twice. Always preview on the actual platform, not just your local file.
references/dark-vs-light-stage
- Dark stage (default). Best for glow/bloom/depth, hides compression noise in
the darks, premium on OLED. Risk: banding in the dark gradients (fix with
noise) and crushed blacks if you go to pure
#000(fix with a tinted near-black). This is the right default for product demos, launches, AI/dev tools, and anything cinematic. - Light stage (commit fully). Best for clean SaaS, editorial, daylight
product. There is no glow to lean on, so depth comes from layered, soft
shadows and generous whitespace. Risk: big flat near-white areas show every
macroblock - keep large light fields slightly off-white and add the faintest
texture. Never go pure
#FFFFFFedge to edge.
Whichever you pick, do not sit in the muddy middle. A confident dark or a confident light reads as designed; a mid-gray stage reads as a default nobody chose.
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
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.

