feat(remotion): shared FinishPass cinematic grade (quality floor) + @remotion/lottie
The single highest-ROI quality lift — one finish applied at the FlexStory level lifts all 12 blocks at once, no per-block change: - GRADE_FILTER: a headless-safe colour grade (contrast/saturation/lift) applied as a CSS `filter` on the content root — backdrop-filter does NOT render in headless Chrome, so the grade lives on the content, not an overlay. - FinishPass: split-tone (cool-shadows multiply + warm-highlights screen) + a soft brand duotone + top light-bloom, layered over each scene. - Installed @remotion/lottie@4.0.290 (artist-made animations — next lever). Verified: visible richer/graded look on CharacterScene + Slideshow, subtle enough to suit the muted palette, consistent across blocks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import { FONT } from "../lib/fonts";
|
|||||||
import { useLayout } from "../lib/aspect";
|
import { useLayout } from "../lib/aspect";
|
||||||
import { getBlock } from "../scenes/registry";
|
import { getBlock } from "../scenes/registry";
|
||||||
import { withDefaults, clampDuration } from "../scenes/types";
|
import { withDefaults, clampDuration } from "../scenes/types";
|
||||||
|
import { FinishPass, GRADE_FILTER } from "../scenes/chrome";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FlexStory — the scene sequencer. A template is `scenes: SceneInstance[]`; this
|
* FlexStory — the scene sequencer. A template is `scenes: SceneInstance[]`; this
|
||||||
@@ -78,7 +79,7 @@ export const FlexStory: React.FC<Props> = (props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AbsoluteFill style={{ backgroundColor: colors.backgroundColor, fontFamily: FONT }}>
|
<AbsoluteFill style={{ backgroundColor: colors.backgroundColor, fontFamily: FONT, filter: GRADE_FILTER }}>
|
||||||
{music ? <Audio src={resolveAudio(music)} loop volume={musicVolume} /> : null}
|
{music ? <Audio src={resolveAudio(music)} loop volume={musicVolume} /> : null}
|
||||||
|
|
||||||
{scenes.map((sc, i) => {
|
{scenes.map((sc, i) => {
|
||||||
@@ -101,6 +102,9 @@ export const FlexStory: React.FC<Props> = (props) => {
|
|||||||
</Sequence>
|
</Sequence>
|
||||||
))
|
))
|
||||||
: null}
|
: null}
|
||||||
|
|
||||||
|
{/* Cinematic finish over every scene — the shared quality floor. */}
|
||||||
|
<FinishPass colors={colors} />
|
||||||
</AbsoluteFill>
|
</AbsoluteFill>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -79,6 +79,28 @@ export const Vignette: React.FC = () => (
|
|||||||
<AbsoluteFill style={{ pointerEvents: "none", background: "radial-gradient(125% 108% at 50% 42%, transparent 56%, rgba(30,38,58,0.16) 100%)" }} />
|
<AbsoluteFill style={{ pointerEvents: "none", background: "radial-gradient(125% 108% at 50% 42%, transparent 56%, rgba(30,38,58,0.16) 100%)" }} />
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FinishPass — a shared cinematic colour grade applied over the whole composited
|
||||||
|
* frame: a contrast/saturation/lift grade (backdrop-filter), a subtle duotone tint
|
||||||
|
* pulled from the brand palette, and a soft top light-bloom. Layered ON TOP of the
|
||||||
|
* blocks' own grain/vignette so it lifts every block at once. One component = the
|
||||||
|
* quality floor for the whole catalogue.
|
||||||
|
*/
|
||||||
|
export const FinishPass: React.FC<{ colors: SceneColors; intensity?: number }> = ({ colors, intensity = 1 }) => (
|
||||||
|
<>
|
||||||
|
{/* split-tone: cool shadows (multiply) + warm highlights (screen) — headless-safe */}
|
||||||
|
<AbsoluteFill style={{ pointerEvents: "none", mixBlendMode: "multiply", opacity: 0.18 * intensity, background: `linear-gradient(160deg, ${mixHex(colors.secondaryColor, "#1a2030", 0.45)}, ${mixHex(colors.backgroundColor, "#2a2238", 0.3)})` }} />
|
||||||
|
<AbsoluteFill style={{ pointerEvents: "none", mixBlendMode: "screen", opacity: 0.16 * intensity, background: `radial-gradient(120% 90% at 50% 8%, ${mixHex(colors.accentColor, "#ffffff", 0.4)}, transparent 60%)` }} />
|
||||||
|
{/* soft brand duotone + top light bloom */}
|
||||||
|
<AbsoluteFill style={{ pointerEvents: "none", mixBlendMode: "soft-light", opacity: 0.32 * intensity, background: `linear-gradient(135deg, ${colors.accentColor} 0%, transparent 50%, ${colors.secondaryColor} 100%)` }} />
|
||||||
|
<AbsoluteFill style={{ pointerEvents: "none", background: `radial-gradient(130% 92% at 50% -15%, ${hexToRgba("#ffffff", 0.16 * intensity)}, transparent 55%)` }} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
/** The headless-safe colour grade — applied as a CSS `filter` on the content root
|
||||||
|
* (backdrop-filter does NOT render in headless Chrome). Pair with <FinishPass>. */
|
||||||
|
export const GRADE_FILTER = "contrast(1.08) saturate(1.14) brightness(1.01)";
|
||||||
|
|
||||||
export const ProgressDots: React.FC<{ index: number; total: number; colors: SceneColors; L: Layout }> = ({ index, total, colors, L }) => (
|
export const ProgressDots: React.FC<{ index: number; total: number; colors: SceneColors; L: Layout }> = ({ index, total, colors, L }) => (
|
||||||
<div style={{ position: "absolute", bottom: L.vmin(44), left: 0, right: 0, display: "flex", justifyContent: "center", gap: L.vmin(8) }}>
|
<div style={{ position: "absolute", bottom: L.vmin(44), left: 0, right: 0, display: "flex", justifyContent: "center", gap: L.vmin(8) }}>
|
||||||
{Array.from({ length: total }).map((_, k) => (
|
{Array.from({ length: total }).map((_, k) => (
|
||||||
|
|||||||
Reference in New Issue
Block a user