스킬 불러오는 중
스킬 불러오는 중
iart-ai/motion-design-skills · GitHub
This skill should be used when the user asks to "build a color palette for a motion piece", "make a gradient background", "animate a color transition", "interpolate colors smoothly", "fix muddy/gray gradients", "convert colors to OKLCH", "color-grade a video for mood", "fix washed-out After Effects renders", "gamma shift", "Rec.709 vs sRGB", "colors shifted after rendering", or "match preview to final video". Covers palette construction, gradients, perceptual interpolation (OKLCH/Lab), grading order, and video/render color management.
프로젝트 폴더에서 아래 명령어를 실행하고, 설치할 에이전트를 선택하세요.
npx skills add iart-ai/motion-design-skills --skill color-motion설치 명령을 직접 실행해야 적용됩니다. 지원 에이전트와 필요한 권한·라이선스는 제작자의 안내를 확인하세요.
README.mdreferences/color-management.md# Video / render color management (After Effects)
How to diagnose and prevent the most common After Effects color complaint: a render that looks correct inside the AE composition viewer but appears washed-out, too bright, milky, or gamma-shifted when played in QuickTime Player, uploaded to a browser, or moved between macOS and Windows. The root cause is almost always a mismatch in how gamma / transfer functions are interpreted across AE, the codec metadata, the OS player, and the display pipeline — not a problem with the artwork.
## Mental model: where shifts come from
A pixel value travels through four stages, each of which can apply or assume a gamma. A shift is a mismatch between two of them:
1. **AE project working space** — the math AE uses internally (linear vs a tagged display space). Set in File > Project Settings > Color tab.
2. **The render's embedded tag** — what transfer/primaries metadata the codec writes (or omits) into the file.
3. **The player's interpretation** — QuickTime, Chrome, VLC, and Windows Media each guess differently when the tag is missing or when they apply ColorSync.
4. **The display + OS gamma** — macOS historically assumed ~1.8, now ~2.2/2.4-ish via ColorSync; the long-standing "QuickTime gamma bug" comes from QuickTime/AVFoundation applying an extra gamma curve (~1.96) that Chrome and most editors do not.
Key fact: most web video is **Rec.709**, whose camera/encode side is roughly gamma 2.2-ish but whose *standard display* gamma is **2.4** (BT.1886). macOS QuickTime effectively renders nearer **1.96**, which is why a file can look fine in QuickTime but slightly dark/contrasty in a browser, or vice-versa. The fix is never "eyeball it in QuickTime" — QuickTime is the least trustworthy reference.
## Decision tree: which problem is this?
```
Render looks DIFFERENT from comp preview?
├─ Looks washed-out / milky / brighter, low contrast
│ └─ Almost always: untagged or mistagged H.264/ProRes +
│ player applying its own gamma (QuickTime gamma bug).
│ → Use ProRes for masters; tag Rec.709; verify in
│ Chrome, NOT QuickTime.
├─ Looks fine in QuickTime, WRONG (dark/contrasty) in Chrome
│ └─ QuickTime is applying ~1.96 gamma; Chrome honors the
│ 2.4/sRGB pipeline. The browser is the correct reference
│ for web delivery.
├─ Looks fine on macOS, wrong on Windows (or reverse)
│ └─ Missing color tag → each OS assumes a default. Embed an
│ explicit Rec.709 (or sRGB) tag on export.
└─ Banding / posterization in gradients
└─ 8-bit project on heavy grades. Set Project > Depth to
16 bpc and/or enable Linearize Working Space.
```
## Project Color Settings (exact menu paths)
Open **File > Project Settings… > Color** tab.
- **Depth**: set to **16 bits per channel** for grades/gradients (8 bpc is fine for simple cuts; 32 bpc float only for HDR/heavy linear comps).
- **Working Space**: For standard web/SDR delivery choose **Rec.709 Gamma 2.4** (or **sRGB IEC61966-2.1** if the deliverable is screenshots/UI). Setting any working space (not "None") turns AE color management ON, so the viewer now shows display-referred color consistently.
- **Linearize Working Space** / **Blend Colors Using 1.0 Gamma**: enable when doing glows, blurs, and additive composites so blending math is physically correct. This changes the look of feathered edges and screen blends — enable it early, not at the end.
- **Compensate for Scene-referred Profiles**: leave default unless mixing log/cineon footage.
To preview what a viewer/OS will actually see, use the composition viewer's **bottom toolbar > "Use Display Color Management"** toggle, and the eyedropper/Info panel to read numeric RGB values rather than trusting the on-screen brightness.
## Export settings per platform
Prefer the **Render Queue** (Composition > Add to Render Queue) for ProRes masters, or **File > Export > Add to Adobe Media Encoder Queue** for H.264 web files.
| Target | Codec / format | Color tag to embed | Notes |
|---|---|---|---|
| Editing master (FCP/Premiere/Resolve) | **ProRes 422 HQ / 4444** (QuickTime) | Rec.709 | Highest fidelity; avoids H.264 gamma guessing |
| Web / `<video>` / social | **H.264** (MP4) via Media Encoder | **Rec.709**, tagged | Set Match Source then override profile; verify in Chrome |
| YouTube / Vimeo upload | H.264 high bitrate or ProRes | Rec.709 | Platforms re-encode; correct tag prevents their guess |
| Transparency for web | **PNG / ProRes 4444 + alpha** then convert | sRGB | Browsers treat PNG as sRGB |
| HDR | ProRes 4444 / HEVC 10-bit | Rec.2020 PQ/HLG | Requires 32 bpc float project |
In **Adobe Media Encoder**, the QuickTime/ProRes color tag is governed by the AE **Output Module > Color Management** sub-tab. In the Render Queue, click the **Output Module** name > **Color Management** tab and set the output profile to **Rec.709 Gamma 2.4** (do not leave "Preserve RGB" if a shift is occurring).
## The QuickTime gamma bug — workarounds
QuickTime Player and any AVFoundation-based player can apply an extra ~1.96 gamma to ProRes/H.264, making files look darker/contrastier than they do in Chrome, Premiere, or Resolve. It is a *player* problem, not a render problem.
1. **Never grade or approve to QuickTime.** Use Chrome, Premiere, or Resolve as the reference, since those match how the web and other editors display the file.
2. For ProRes masters that must look right in QuickTime too, **embed the Rec.709 (Gamma 2.4) tag** via Output Module > Color Management. Correctly tagged ProRes is interpreted consistently by modern macOS.
3. If a legacy pipeline still shifts, render a **second 1:1 reference still** (Composition > Save Frame As > File) as PNG and compare numeric values — pixels, not perception.
4. Avoid the old "nudge gamma by 1.08 to cancel QuickTime" hacks for new work; they bake a wrong curve that breaks on every correct player. Only use a compensating curve if delivering *exclusively* to a known-broken legacy QuickTime target.
## Per-player / per-OS gamma reference
| Environment | Effective transfer assumption | Notes |
|---|---|---|
| Rec.709 standard display (BT.1886) | **2.4** | The correct broadcast/web display gamma |
| sRGB display | **~2.2** (piecewise) | UI, screenshots, PNG |
| macOS QuickTime / AVFoundation | **~1.96** | Source of the "QuickTime gamma bug"; brighter/contrast-shifted vs browser |
| Chrome / Firefox `<video>` | honors 2.4 / sRGB pipeline | Correct reference for web delivery |
| macOS classic (pre-10.6) | 1.8 | Legacy only; ignore for new work |
| Windows default | 2.2 | Assumed when file is untagged |
Takeaway: the *only* reliable cross-environment guarantee is an **explicit embedded tag**. Without it, every player above falls back to its own default and they disagree.
## Numeric test-patch method (perception-free)
Eyes adapt; numbers do not. To prove whether a shift exists:
1. In the comp, add a Solid (Layer > New > Solid) filled with **50% grey: RGB 128,128,128** (or 188,188,188 for a known mid-light patch).
2. Note the value AE reports in the **Info panel** when hovering with managed display on.
3. Render the file with the intended export settings.
4. Re-import the rendered file into a fresh comp (or open in an app with a pixel readout) and sample the same patch.
5. If the sampled value differs from 128 by more than ~2-3 levels, a gamma transform is being applied somewhere in the chain. A washed-out look corresponds to the value rising (e.g. 128 → 150+).
This isolates whether the shift is in the *render* (values changed in the file) vs the *playback* (file values correct, player re-curving).
## Verify the embedded tag with ffprobe
```bash
ffprobe -v error -select_streams v:0 \
-show_entries stream=color_transfer,color_primaries,color_space,color_range \
-of default=nk=0 export.mp4
```
Desired for SDR web (Rec.709):
```
color_space=bt709
color_primaries=bt709
color_transfer=bt709
color_range=tv # (limited) for video; pc/full for some web pipelines
```
If `color_transfer=unknown` or fields are absent, the file is untagged → re-export with the tag, or stamp it without re-encoding:
```bash
ffmpeg -i export.mp4 -c copy \
-color_primaries bt709 -color_trc bt709 -colorspace bt709 \
export_tagged.mp4
```
Note: `-c copy` only writes container/stream metadata; it does not fix pixels that were already baked wrong — use it to *tag*, not to *correct*.
## Limited vs full range pitfall
H.264 for video is typically **limited/TV range** (luma 16-235). If AE/AME outputs full-range pixels but the file is tagged limited (or vice versa), blacks crush or wash. Symptoms: milky blacks = full data read as limited; crushed blacks = limited data read as full. Keep range consistent end-to-end and tag it (`color_range`).
## Quick reference
| Symptom | Likely cause | Fix |
|---|---|---|
| Washed-out / milky blacks | Untagged H.264, player adds gamma | Tag Rec.709 in Output Module > Color Management |
| Fine in QuickTime, dark in Chrome | QuickTime ~1.96 gamma | Trust Chrome; QuickTime is wrong for web |
| Differs macOS vs Windows | No embedded color tag | Embed explicit Rec.709/sRGB tag |
| Banding in gradients | 8 bpc project | Project Settings > Depth = 16 bpc |
| Glows/blurs look wrong | Non-linear blending | Enable Linearize Working Space |
| Screenshots/UI look off | Wrong working space | Use sRGB working space, not Rec.709 |
## Gotchas
- "Working Space = None" disables AE color management entirely — the viewer then lies about the final look. Always set an explicit space for managed delivery.
- Toggling Linearize Working Space late changes the look of every feather, glow, and screen blend; decide at project start.
- AME "Match Source" can silently drop the color tag; verify the exported file's tag (e.g. with `ffprobe -show_streams`, look for `color_transfer`/`color_primaries`).
- ProRes is not magic — an untagged ProRes still shifts. The *tag* is what fixes consistency, not the codec.
- sRGB and Rec.709 share primaries but differ in transfer (sRGB ~2.2 piecewise vs Rec.709 display 2.4); using the wrong one shifts midtone brightness.
## Pre-export checklist
- [ ] Project Settings > Color: Working Space set to the deliverable's space (Rec.709 Gamma 2.4 for SDR video; sRGB for UI/stills).
- [ ] Project Settings > Color: Depth = 16 bpc (or 32 for HDR/heavy linear).
- [ ] Linearize Working Space decision made at project start (on for glow/blur-heavy comps).
- [ ] Display Color Management toggle ON while reviewing, so the viewer reflects final look.
- [ ] 50% grey test patch placed and noted before render.
- [ ] Output Module > Color Management: explicit output profile = Rec.709 Gamma 2.4 (not "Preserve RGB" when a shift exists).
- [ ] Codec chosen per target (ProRes 422 HQ/4444 master, H.264 web).
- [ ] Reviewed final in **Chrome / Premiere / Resolve**, not QuickTime.
- [ ] `ffprobe` confirms color_primaries/transfer/space = bt709 (or sRGB target) and color_range consistent.
- [ ] Re-imported render and re-sampled the grey patch; value matches within ~2-3 levels.
## When the client only has QuickTime
If the approval environment is genuinely locked to macOS QuickTime and the file must look right *there*:
1. Deliver correctly tagged ProRes (modern macOS QuickTime respects Rec.709 tags fairly well).
2. If a residual shift remains and cannot be avoided, produce a *separate* QuickTime-targeted file with a measured compensating gamma adjustment — and clearly label it "QuickTime-only; do not use for web." Never let this compensated file become the web master.
3. Educate: provide the same file opened in Chrome side-by-side so the difference is attributed to the player, not the work.
references/palettes-and-interpolation.md# Palettes, gradients, interpolation, and grading
Exact recipes and runnable code. OKLCH notation: `oklch(L C H)`, L in 0-1 perceptual lightness, C chroma (0 to ~0.37), H hue degrees.
## Palette construction rules
1. **Count**: 1 primary, 1 accent, 2-3 neutrals. Resist adding hues.
2. **Contrast from lightness**: build a 5-9 step ramp by varying L while holding C and H roughly constant, nudging chroma down at the extremes (very light/dark colors can't hold high chroma).
3. **Accent placement**: accent hue should sit 90-180deg from the primary on the OKLCH wheel for clean separation; complementary (~180deg) is boldest, analogous (~30deg) is calmest.
4. **Neutral tinting**: give neutrals a tiny chroma (0.005-0.02) in the primary's hue family so the frame feels unified, not assembled from separate kits.
5. **Dark-scene accents**: high L (0.7-0.85), moderate C (0.12-0.18). Avoid max chroma — it vibrates on dark backgrounds.
### Generating a tonal ramp (hold hue, vary lightness)
```
oklch(0.97 0.03 264) // 50
oklch(0.92 0.06 264) // 100
oklch(0.84 0.10 264) // 200
oklch(0.74 0.14 264) // 300
oklch(0.66 0.18 264) // 400
oklch(0.58 0.19 264) // 500 (base)
oklch(0.50 0.17 264) // 600
oklch(0.40 0.14 264) // 700
oklch(0.28 0.10 264) // 800
oklch(0.18 0.06 264) // 900
```
Note chroma rises toward the mid-lightness band and falls at both ends — this matches what's physically representable and looks natural.
### Named starter palettes (OKLCH)
```
// "Midnight product" — confident, techy
primary oklch(0.62 0.19 264) accent oklch(0.78 0.17 50)
bg oklch(0.16 0.02 264) fg oklch(0.97 0.005 264)
// "Warm editorial" — premium, soft
primary oklch(0.55 0.12 25) accent oklch(0.80 0.10 90)
bg oklch(0.96 0.01 60) fg oklch(0.25 0.03 25)
// "Neon dark" — energetic, bold
primary oklch(0.72 0.20 150) accent oklch(0.68 0.24 330)
bg oklch(0.14 0.03 280) fg oklch(0.95 0.02 150)
```
## Gradient cookbook
### Two-stop, vivid (OKLCH)
```css
background: linear-gradient(135deg in oklch, #2b6cff, #ffb02e);
/* control the hue arc explicitly */
background: linear-gradient(in oklch shorter hue, oklch(0.6 0.2 264), oklch(0.8 0.17 50));
```
`shorter hue` / `longer hue` / `increasing hue` / `decreasing hue` pick the direction around the wheel — `longer hue` produces rainbow sweeps, `shorter` the direct path.
### Mesh background (stacked radials)
```css
.mesh {
background-color: #0b0f1a;
background-image:
radial-gradient(at 18% 22%, oklch(0.62 0.19 264 / 0.7), transparent 50%),
radial-gradient(at 82% 28%, oklch(0.70 0.16 320 / 0.6), transparent 55%),
radial-gradient(at 55% 88%, oklch(0.78 0.17 50 / 0.5), transparent 50%),
radial-gradient(at 40% 50%, oklch(0.55 0.14 200 / 0.4), transparent 60%);
}
```
### Animated drift
```css
.mesh { background-size: 140% 140%; animation: drift 16s ease-in-out infinite alternate; }
@keyframes drift {
0% { background-position: 0% 0%; }
100% { background-position: 12% -8%; }
}
```
### Conic
```css
background: conic-gradient(in oklch from 90deg, #2b6cff, #b02eff, #ffb02e, #2b6cff);
```
### Grain overlay (kills banding)
```html
<svg width="0" height="0">
<filter id="grain"><feTurbulence type="fractalNoise" baseFrequency="0.8" numOctaves="2"/></filter>
</svg>
```
```css
.grain::after {
content: ""; position: absolute; inset: 0; pointer-events: none;
filter: url(#grain); opacity: 0.04; mix-blend-mode: overlay;
}
```
## Perceptual interpolation in code
### culori (JS) — interpolate and sample
```js
import { interpolate, formatHex, formatCss } from "culori";
// Interpolate in OKLCH (vivid midpoints, even lightness)
const mix = interpolate(["#2b6cff", "#ffb02e"], "oklch");
formatHex(mix(0.5)); // vivid mid, not gray
// Build N stops for an animated transition
const stops = Array.from({ length: 60 }, (_, i) => formatCss(mix(i / 59)));
// Control hue direction
import { fixupHueLonger } from "culori";
const rainbow = interpolate(["#2b6cff", "#ffb02e"], "oklch", {
h: { use: fixupHueLonger },
});
```
### d3-interpolate / d3-scale-chromatic
```js
import { interpolateLab, interpolateHcl, quantize } from "d3-interpolate";
const lab = interpolateLab("#2b6cff", "#ffb02e"); // perceptual, smooth lightness
lab(0.5); // mid color
const ramp = quantize(interpolateHcl("#2b6cff", "#ffb02e"), 8); // 8 even stops
```
`interpolateLab` and `interpolateHcl` avoid the sRGB gray-midpoint problem; `interpolateRgb` does not — only use RGB interpolation for near-identical colors.
### Animating a color with @property (CSS, browser interpolates)
```css
@property --from { syntax: "<color>"; initial-value: oklch(0.62 0.19 264); inherits: false; }
.swatch { background: var(--from); transition: --from 400ms cubic-bezier(0.16,1,0.3,1); }
.swatch.active { --from: oklch(0.78 0.17 50); }
```
Without `@property` registration the color snaps instead of interpolating. The browser interpolates registered `<color>` properties in OKLab by default — perceptually clean for free.
## Grading order checklist
Apply in this exact order; reordering changes the result.
1. **Exposure / white balance** — set overall brightness, neutralize or intentionally tint the cast.
2. **Contrast** — set black/white points, expand or compress tonal range.
3. **Midtones / saturation** — overall saturation and midtone color shift.
4. **Split-tone** — tint shadows and highlights separately. Cinematic default: teal shadows (`hue ~190`), orange highlights (`hue ~40`), kept subtle.
5. **Unify** — final overall tint / LUT and a gentle vignette.
### CSS-filter grade (quick, for web video/canvas)
```css
.graded {
filter:
brightness(1.02) /* 1: exposure */
contrast(1.12) /* 2: contrast */
saturate(1.08); /* 3: saturation */
}
/* 4 split-tone: overlay a teal->orange gradient at low opacity with mix-blend-mode */
```
### Fragment-shader split-tone (GLSL sketch)
```glsl
vec3 graded = color;
float lum = dot(graded, vec3(0.2126, 0.7152, 0.0722));
vec3 shadowTint = vec3(0.0, 0.45, 0.5); // teal
vec3 highlightTint = vec3(1.0, 0.6, 0.2); // orange
graded = mix(graded, graded * shadowTint, (1.0 - lum) * 0.12);
graded = mix(graded, graded + highlightTint * 0.06, lum);
```
Keep split-tone strength under ~0.15; over-tinting reads as a cheap Instagram filter. Grade the whole piece with the same chain so cuts match.
---
Interpolate color in perceptual space and gradients shift without muddy mids. Built by **[iart.ai](https://iart.ai/?utm_source=github&utm_medium=readme&utm_campaign=motion-design-skills&utm_content=skill_footer&utm_term=color-motion)** — the AI motion agent for editable, on-brand motion graphics.
SKILL.md---
name: color-motion
description: This skill should be used when the user asks to "build a color palette for a motion piece", "make a gradient background", "animate a color transition", "interpolate colors smoothly", "fix muddy/gray gradients", "convert colors to OKLCH", "color-grade a video for mood", "fix washed-out After Effects renders", "gamma shift", "Rec.709 vs sRGB", "colors shifted after rendering", or "match preview to final video". Covers palette construction, gradients, perceptual interpolation (OKLCH/Lab), grading order, and video/render color management.
version: 0.1.0
---
# Color in Motion
Choose, pair, and animate color for motion design: building restrained palettes, constructing gradients that read as premium, interpolating colors in perceptually-uniform spaces, and grading a piece for mood and consistency. Produces palettes with exact OKLCH/hex values, gradient CSS/code, and transition parameters.
## When to use
- Picking or pairing a palette for a motion or video piece.
- Building gradient backgrounds (mesh, noise, animated drift).
- Animating color transitions (button states, theme switch, loaders).
- Grading a video for mood, or fixing dull/gray gradient interpolation.
- Fixing washed-out, gamma-shifted, or platform-inconsistent After Effects renders, and choosing correct per-platform export/color tags.
## Core techniques
### 1. Build a restrained palette
Limit hues. A strong motion palette is **1 primary + 1 accent + 2-3 neutrals**. Derive contrast and depth from lightness and chroma, not from adding more hues. Extra hues fight for attention and read as amateur.
In dark scenes, make the accent pop with **low-to-moderate chroma and high lightness** rather than maximum saturation — a blown-out saturated accent vibrates and looks cheap on dark backgrounds.
Use OKLCH to reason about color: `oklch(L C H)` where L is perceptual lightness (0-1), C is chroma (0 ≈ 0.37 max), H is hue angle (0-360). Holding L and C constant while rotating H gives hues of genuinely equal visual weight — impossible to do reliably in HSL.
```
Primary: oklch(0.62 0.19 264) /* confident blue */
Accent: oklch(0.78 0.17 50) /* warm amber, lighter so it pops */
Neutral-0: oklch(0.97 0.005 264) /* near-white, faint hue tint */
Neutral-1: oklch(0.55 0.01 264)
Neutral-2: oklch(0.18 0.015 264) /* near-black, same hue family */
```
Tinting neutrals slightly toward the primary hue (tiny chroma) unifies the frame; pure gray neutrals next to colored elements look disconnected.
### 2. Interpolate in OKLCH or Lab — never raw sRGB
Linearly interpolating two colors in sRGB passes through a desaturated, often gray or muddy midpoint (blue->yellow goes through gray; blue->red through muddy purple). Perceptual spaces (OKLCH, OKLab, CIE Lab) keep midpoints vivid and lightness even.
CSS gradients support this natively:
```css
/* Vivid, even midpoint */
background: linear-gradient(in oklch, #2b6cff, #ffb02e);
/* Control hue path around the wheel for two-stop gradients */
background: linear-gradient(in oklch longer hue, #2b6cff, #ffb02e);
background: conic-gradient(in oklch, red, blue, red);
```
For animated transitions interpolate with a library (see references) and emit per-frame colors, or for two-color CSS transitions register a typed custom property so the browser interpolates in the chosen space.
### 3. Gradients that read as premium
- **Mesh / multi-radial**: stack several `radial-gradient`s at different positions over a base color. Soft, organic, expensive-looking.
- **Add grain**: a faint noise overlay (SVG `feTurbulence` or a tiled PNG at ~3-5% opacity) kills banding and adds texture.
- **Animate slowly**: drift gradient positions over 8-20s with `ease-in-out alternate`. Slow is premium; fast gradient motion looks like a screensaver.
```css
.mesh {
background-color: #0b0f1a;
background-image:
radial-gradient(at 20% 25%, oklch(0.62 0.19 264 / 0.7), transparent 50%),
radial-gradient(at 80% 30%, oklch(0.70 0.16 320 / 0.6), transparent 55%),
radial-gradient(at 50% 85%, oklch(0.78 0.17 50 / 0.5), transparent 50%);
animation: drift 16s ease-in-out infinite alternate;
}
@keyframes drift {
to { background-position: 8% -6%, -8% 6%, 4% 8%; }
}
```
### 4. Color transition animation
Inline timing essentials so this skill stands alone: enter/highlight with ease-out (`cubic-bezier(0.16, 1, 0.3, 1)`), state changes 150-300ms, theme/full-screen washes 400-800ms. Use `linear` only for continuous loops.
```css
@property --c { syntax: "<color>"; initial-value: #2b6cff; inherits: false; }
.btn { background: var(--c); transition: --c 200ms cubic-bezier(0.16,1,0.3,1); }
.btn:hover { --c: #ffb02e; }
```
`@property` registration is what makes the color actually interpolate (unregistered custom properties jump instantly).
### 5. Grade for mood — correct order
Apply grading operations in this order; reordering changes the result:
1. **Exposure / white balance** — fix overall brightness and neutralize/intentionally set the cast first.
2. **Contrast** — set black and white points; expand or compress the tonal range.
3. **Midtones / saturation** — adjust overall saturation and midtone color.
4. **Split-tone** — tint shadows and highlights separately. The classic cinematic look is **teal shadows + orange highlights** (complementary, flatters skin). Keep it subtle.
5. **Unify** — a final overall tint/LUT and a subtle vignette to bind the frame.
In code, grade by chaining CSS filters or a fragment shader; for video, an actual LUT or grading panel. Always grade the whole piece consistently so shots cut together.
### 6. Video / render color management
When a render looks correct in the After Effects comp viewer but washed-out, too bright, milky, or gamma-shifted in QuickTime, a browser, or across macOS/Windows, the cause is a gamma/transfer mismatch across four stages — AE working space, the render's embedded tag, the player's interpretation, and the display/OS gamma — not the artwork.
Core rules:
- **Set an explicit working space.** File > Project Settings > Color tab: Working Space = **Rec.709 Gamma 2.4** for SDR video (or **sRGB** for UI/stills); "None" disables color management and the viewer lies about the final look. Set Depth to **16 bpc** for grades/gradients to avoid banding.
- **Rec.709 vs sRGB.** They share primaries but differ in transfer (sRGB ~2.2 piecewise vs Rec.709 display 2.4 / BT.1886). Using the wrong one shifts midtone brightness — Rec.709 for video, sRGB for screenshots/PNG/UI.
- **Embed a color tag on export.** The shift across macOS/Windows/players comes from untagged files; each player guesses its own default. Tag Rec.709 (or sRGB) explicitly via Render Queue > **Output Module > Color Management** (do not leave "Preserve RGB" when a shift occurs).
- **The QuickTime gamma bug.** QuickTime/AVFoundation apply an extra ~1.96 gamma, so a file can look fine in QuickTime but dark/contrasty in Chrome. **Never grade or approve to QuickTime** — Chrome, Premiere, or Resolve is the correct reference for web delivery.
- **Match preview to final.** Toggle "Use Display Color Management" in the comp viewer while reviewing, and verify the export numerically: place a 50% grey (RGB 128) patch, render, re-sample — a value that rises (128 → 150+) confirms a washed-out gamma shift. Confirm the embedded tag with `ffprobe` (`color_primaries`/`color_transfer`/`color_space` = bt709).
Per-platform export at a glance: **ProRes 422 HQ/4444** tagged Rec.709 for editing masters; **H.264 (MP4)** tagged Rec.709 for web/`<video>`/social (verify in Chrome); ProRes 4444 + alpha → PNG (sRGB) for transparency; ProRes 4444 / HEVC 10-bit Rec.2020 PQ/HLG for HDR (needs 32 bpc float).
See `references/color-management.md` for the full diagnosis decision tree, exact menu paths, per-platform export table, the QuickTime workarounds, per-OS/player gamma values, the numeric test-patch method, `ffprobe`/`ffmpeg` tag commands, limited-vs-full-range pitfalls, and the pre-export checklist.
## Deliver & verify (standalone HTML)
> **Packaged helper** (`scripts/`): `scripts/seek-shot.sh anim.html 0 1.5 3` freezes the `?t=N` harness and screenshots each moment; `scripts/contact-sheet.sh sheet.png frame-*.png` tiles them for one-glance review. See `scripts/README.md`.
For a web palette/gradient/color-transition deliverable, ship **one `.html` file that opens directly in a browser** — no build step. CSS handles the color work; the file is self-contained.
**Output contract:**
- One `.html` file: your markup + the gradient/transition in inline `<style>` (CDN only if a JS interpolation lib is needed).
- One animation driver — a CSS `@keyframes` drift or a single `@property` transition; nothing competing.
- Include the freeze harness below so any moment can be screenshotted deterministically.
**Freeze harness — pin a CSS-animation frame for screenshots.** `?t=N` sets a negative `animation-delay` and pauses, so the gradient/transition lands on the exact frame at `N` seconds.
```html
<script>
const t = new URLSearchParams(location.search).get("t");
if (t !== null) {
document.querySelectorAll(".bg, .btn").forEach(el => { // animated elements
el.style.animationDelay = (-parseFloat(t)) + "s";
el.style.animationPlayState = "paused";
});
}
window.__ready = true; // ready signal for headless wait
</script>
```
**Verify loop — render → freeze → screenshot → check:**
1. Open the file frozen at start / mid / end: `…/color.html?t=0`, `?t=<period/2>`, `?t=<period>`.
2. Screenshot each frozen frame.
3. Check **fidelity** (matches the palette/brief) and **color artifacts** — at each frozen frame confirm the gradient/transition has **no banding and no muddy or gray midpoint** (the OKLCH/Lab path stays vivid). Add grain if banding appears.
4. **Iterate:** if banding or a muddy/gray midpoint shows up, adjust the OKLCH/Lab chroma & lightness (or add grain), re-render the **same** `?t=N`, and re-screenshot to confirm the fix — loop until every frozen frame is clean.
```bash
npx playwright screenshot --wait-for-timeout=500 "file://$PWD/color.html?t=8" frame-mid.png
```
**Before you finish:**
1. Opens standalone in a browser — no console errors, no missing CDN.
2. One color driver; `?t=N` freezes the exact frame correctly.
3. Screenshotted at start / mid / end — no banding, no muddy/gray midpoints, lightness even.
4. `prefers-reduced-motion` honored (drift paused on a static, readable frame).
5. Interpolation is in OKLCH/Lab (gradients/`@property`), never raw sRGB.
## Quick reference
| Need | Do |
|---|---|
| Palette size | 1 primary + 1 accent + 2-3 neutrals |
| Equal-weight hues | hold L,C in OKLCH, vary H |
| Vivid gradient | `linear-gradient(in oklch, ...)` |
| Smooth color anim | interpolate in OKLCH/Lab, register via `@property` |
| Premium bg | stacked radials + grain + 8-20s slow drift |
| Cinematic grade | teal shadows / orange highlights, subtle |
| Fix washed-out AE render | tag Rec.709 in Output Module > Color Management; verify in Chrome |
| Avoid | raw-sRGB interpolation (muddy midpoints); grading to QuickTime |
## Reference files
- `references/palettes-and-interpolation.md` — palette construction rules with OKLCH values and named recipes, gradient cookbook (mesh, conic, grain), culori and d3-interpolate code for perceptual interpolation and scales, and the full grading-order checklist with filter/shader examples.
- `references/color-management.md` — full video/render color management: diagnosis decision tree, AE project/output color settings with exact menu paths, per-platform export table, QuickTime gamma-bug workarounds, per-OS/player gamma values, the numeric test-patch method, `ffprobe`/`ffmpeg` tag verification, limited-vs-full-range pitfalls, and the pre-export checklist.