Layouts and brands
Inherit format, typography, motion, safe areas, audio policy, and design tokens through the videos tree.
Layouts are modules that establish presentation policy without adding timeline frames.
import {defineBrand, defineVideoLayout} from "odori";
export const odoriBrand = defineBrand({
name: "odori",
colors: {
background: "#000000",
surface: "#0a0a0a",
foreground: "#ededed",
muted: "#a1a1a1",
accent: "#ffffff",
border: "#1f1f1f",
},
typography: {sans: '"Geist Sans", sans-serif', mono: '"Geist Mono", monospace'},
fonts: [
{family: "Geist Sans", url: "/fonts/Geist-Variable.woff2", weight: "100 900"},
{family: "Geist Mono", url: "/fonts/GeistMono-Variable.woff2", weight: "100 900"},
],
motion: {standard: [0.16, 1, 0.3, 1], staggerFrames: 3},
audio: {cues: {"bed.main": "score"}, targetLufs: -14},
});
export const productLayout = defineVideoLayout({
format: {width: 1920, height: 1080, fps: 30},
brand: odoriBrand,
safeArea: {x: 96, y: 72},
});
videos/social/layout.tsx can override the format while inheriting the brand:
import {defineVideoLayout} from "odori";
import {productLayout} from "../layout";
export const socialLayout = defineVideoLayout({
extends: productLayout,
format: {width: 1080, height: 1920},
safeArea: {x: 64, y: 120},
});
Merge rules
- scalar format values replace inherited values
- safe areas replace as a unit
- motion and audio policies merge onto the inherited policy
- a brand replaces the inherited brand, because a brand is itself resolved policy
Fonts are policy, not imports
A brand declares font families and URLs. The runtime injects the matching
@font-face rules and the render worker waits for document.fonts.ready
before capturing a frame, so a still, a preview, and an export use identical
faces. Serve font files from public/.
Brands are policy, not a template
A brand provides tokens, fonts, motion curves, audio options, and component defaults. It should guide installed components without forcing every video into the same scene sequence.
Any module under a brands/ directory is discovered, so Studio can preview a
video with a different token set without editing component source:
import {defineBrand} from "odori";
export const paperBrand = defineBrand({
name: "paper",
colors: {background: "#fafafa", surface: "#ffffff", foreground: "#0a0a0a", border: "#e5e5e5"},
});
Components read tokens through useBrand(), and the runtime also exposes them
as CSS variables such as --odori-foreground for styles that are easier to
express in CSS.