Generate the card:
Create src/episodes/<active-series>/cards/<card-name>/spec.ts:
import { z } from "zod";
import type { CardSpec } from "../../../../engine/cards/types";
import { <CardName>Component } from "./component";
export type <CardName>Data = {
};
export const cardSpec = {
type: "<card-name>",
title: "<Human Label>",
description: "<one-line description of what this card renders>",
schema: z.object({ }),
defaults: { } satisfies Partial<<CardName>Data>,
component: <CardName>Component,
agentHints: {
whenToUse: "<when an agent should pick this card>",
notForUseCases: "<what this card is NOT for>",
exampleData: { },
},
} satisfies CardSpec<<CardName>Data>;
Create src/episodes/<active-series>/cards/<card-name>/component.tsx:
import React from "react";
import { BaseSlide, useCardContext } from "../../../../engine/primitives/BaseSlide";
import type { CardRenderProps } from "../../../../engine/cards/types";
import type { <CardName>Data } from "./spec";
const Body: React.FC<{ data: <CardName>Data }> = ({ data }) => {
const { theme } = useCardContext();
return <div>{/* card content */}</div>;
};
export const <CardName>Component: React.FC<CardRenderProps<<CardName>Data>> = ({ data }) => (
<BaseSlide>
<Body data={data} />
</BaseSlide>
);
- Never call
useCardContext, useTheme, or Remotion hooks (useCurrentFrame, useVideoConfig) in the outer <CardName>Component that returns <BaseSlide>. They only work inside BaseSlide's subtree — put them in the Body child. See references/card-primitives.md for the Gotcha.
- If the card renders chart labels or legends with SVG
<text>, avoid fractional text coordinates. Snap label x / y to integer pixels, especially when using textAnchor="middle", and prefer textRendering="geometricPrecision" to reduce shimmer in Remotion renders.