Layouts and brands

Define a layout to share format and brand settings across videos:

videos/layout.tsx
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:

videos/brands/paper.ts
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.

On this page