Skip to content
Esc
navigateopen⌘Jpreview
On this page

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.

Was this page helpful?