/** * platform-specs.ts — deterministic per-platform delivery spec table. * * The asset-format analogue of cinematography.ts `--detail` levels: a pure, * no-LLM lookup mapping a social/ad platform id to its aspect ratio and copy * word-caps, so campaign output is correct-by-construction (right dimensions, * platform-appropriate copy length + tone). * * Concept (not code) adapted from the MIT-licensed Open-Pomelli `platforms.ts`. * A `bodyMaxWords`/`ctaMaxWords` of 0 means that surface suppresses that copy * slot (e.g. a thumbnail has a headline but no body). */ export type PlatformAspect = '16:9' | '9:16' | '1:1' | '4:5'; export interface PlatformSpec { id: string; label: string; aspect: PlatformAspect; headlineMaxWords: number; bodyMaxWords: number; ctaMaxWords: number; tone: string; } const SPECS: readonly PlatformSpec[] = [ { id: 'tiktok', label: 'TikTok', aspect: '9:16', headlineMaxWords: 8, bodyMaxWords: 22, ctaMaxWords: 4, tone: 'native, fast, conversational' }, { id: 'instagram-reel', label: 'Instagram Reel', aspect: '9:16', headlineMaxWords: 8, bodyMaxWords: 22, ctaMaxWords: 4, tone: 'aspirational, punchy' }, { id: 'instagram-post', label: 'Instagram Post', aspect: '1:1', headlineMaxWords: 10, bodyMaxWords: 30, ctaMaxWords: 4, tone: 'aspirational, polished' }, { id: 'youtube-short', label: 'YouTube Short', aspect: '9:16', headlineMaxWords: 8, bodyMaxWords: 20, ctaMaxWords: 4, tone: 'hooky, high-energy' }, { id: 'youtube-thumb', label: 'YouTube Thumbnail', aspect: '16:9', headlineMaxWords: 6, bodyMaxWords: 0, ctaMaxWords: 0, tone: 'bold, curiosity-driving' }, { id: 'linkedin', label: 'LinkedIn', aspect: '1:1', headlineMaxWords: 12, bodyMaxWords: 40, ctaMaxWords: 5, tone: 'credible, professional' }, { id: 'facebook', label: 'Facebook', aspect: '1:1', headlineMaxWords: 10, bodyMaxWords: 30, ctaMaxWords: 5, tone: 'clear, friendly' }, { id: 'x', label: 'X / Twitter', aspect: '16:9', headlineMaxWords: 10, bodyMaxWords: 28, ctaMaxWords: 4, tone: 'sharp, concise' }, { id: 'banner', label: 'Web Banner', aspect: '16:9', headlineMaxWords: 6, bodyMaxWords: 12, ctaMaxWords: 3, tone: 'direct, benefit-led' }, ]; const BY_ID = new Map(SPECS.map((spec) => [spec.id, spec])); export function getPlatformSpec(id: string): PlatformSpec | undefined { return BY_ID.get(id.toLowerCase()); } export function listPlatformSpecs(): PlatformSpec[] { return SPECS.map((spec) => ({ ...spec })); } export function platformIds(): string[] { return SPECS.map((spec) => spec.id); }