Layouts and brands
Define a layout to share format and brand settings across videos:
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
Load fonts
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/.
Define a brand
A brand provides colors, fonts, motion curves, audio settings, and component defaults.
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.