Add motion-design skills from iart-ai/motion-design-skills
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013TAaJBzbmePTECuv62BXYX
This commit is contained in:
47 files changed
+4385
No files matched your search
Whitespace-only changes.
@@ -0,0 +1,175 @@
|
||||
---
|
||||
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.
|
||||
@@ -0,0 +1,165 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,180 @@
|
||||
# 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.
|
||||
Reference in new issue
Block a user