// Base do kit apple-tipografico (v2, filme de produto): cores, fonte, curvas e medidas. // Todo número visual sai daqui. Os elementos importam só este arquivo, 'react' e 'remotion'. import {continueRender, delayRender} from 'remotion'; import interUrl from './fontes/inter-latin-500-800.woff2'; // ---------- tela ---------- export const LARGURA = 1080, ALTURA = 1920, FPS = 30; // área segura (Reels/TikTok): o que se lê fica entre estas linhas export const SEGURA = {topo: 168, base: 1330, lado: 80}; export const LARGURA_UTIL = LARGURA - SEGURA.lado * 2; // 920 px // ---------- cor (medida no filme de referência, ver regras/cor.md) ---------- export const COR = { /** o chão do estúdio: cinza-creme quente, sem emenda */ estudio: '#EAE4DA', estudioLuz: '#F3EFE8', estudioBorda: '#DAD2C5', /** o corpo do produto: branco cetim */ produto: '#EEEBE5', ponteira: '#E2DED6', grafite: '#2A2A2D', metal: '#C8C5BF', /** a sombra de contato (multiplica o chão) */ sombra: '#3A3026', tinta: '#1D1D1F', apagado: '#6E6E73', preto: '#000000', logo: '#F5F5F7', rodape: '#86868B', /** o único acento: o brilho do detalhe tocável */ acento: '#7CE23A', /** o cartão radial do título (centro → borda) */ radial: ['#F7E7C4', '#F2DDAE', '#9FE3B4', '#46BFD9', '#2366D6', '#173E9C'] as string[], }; // ---------- fonte ---------- // Nome de família com o prefixo do kit, pra não colidir com outro kit no mesmo estúdio. export const FONTE = '"AT Inter", "Inter", system-ui, sans-serif'; if (typeof document !== 'undefined' && !(globalThis as {__atInter?: boolean}).__atInter) { (globalThis as {__atInter?: boolean}).__atInter = true; const h = delayRender('apple-tipografico: Inter'); new FontFace('AT Inter', `url(${interUrl}) format('woff2')`, {weight: '500 800', style: 'normal'}).load() .then((ff) => {document.fonts.add(ff); continueRender(h);}) .catch(() => continueRender(h)); } /** tamanhos e pesos (tela 1080×1920) */ export const TIPO = { legenda: {tamanho: 50, peso: 600, tracking: -0.012}, titulo: {tamanho: 92, peso: 600, tracking: -0.022}, sobretitulo: {tamanho: 34, peso: 500, tracking: -0.005}, rodape: {tamanho: 19, peso: 500, tracking: 0}, }; // ---------- tempo e curva ---------- export const c01 = (t: number) => Math.max(0, Math.min(1, t)); /** progresso 0..1 de f entre a e b */ export const seg = (f: number, a: number, b: number) => (b <= a ? (f >= b ? 1 : 0) : c01((f - a) / (b - a))); export const lerp = (a: number, b: number, t: number) => a + (b - a) * t; /** cubic-bezier(x1,y1,x2,y2) como função 0..1 */ export const bezier = (x1: number, y1: number, x2: number, y2: number) => { const cx = 3 * x1, bx = 3 * (x2 - x1) - cx, ax = 1 - cx - bx; const cy = 3 * y1, by = 3 * (y2 - y1) - cy, ay = 1 - cy - by; const sx = (t: number) => ((ax * t + bx) * t + cx) * t; return (x: number) => { x = c01(x); let t = x; for (let i = 0; i < 8; i++) {const d = (3 * ax * t + 2 * bx) * t + cx; const e = sx(t) - x; if (Math.abs(e) < 1e-5 || d === 0) break; t -= e / d;} t = c01(t); return ((ay * t + by) * t + cy) * t; }; }; /** câmera e objetos: começa e termina devagar, sem tranco (o "premium" do filme) */ export const suave = bezier(0.45, 0, 0.2, 1); /** entrada de texto e de peça: sai rápido, pousa longo */ export const saida = bezier(0.16, 1, 0.3, 1); /** saída de texto: acelera pra fora */ export const entrada = bezier(0.7, 0, 0.84, 0); export const CURVAS = {suave, saida, entrada, linear: (t: number) => c01(t)} as const; export type NomeCurva = keyof typeof CURVAS; /** trilha de chaves [[quadro, valor], ...] → valor no quadro f (curva `suave` entre chaves) */ export const trilha = (f: number, chaves: [number, number][] | number | undefined, padrao = 0, curva: NomeCurva = 'suave'): number => { if (chaves === undefined) return padrao; if (typeof chaves === 'number') return chaves; if (!chaves.length) return padrao; if (f <= chaves[0][0]) return chaves[0][1]; for (let i = 1; i < chaves.length; i++) { const [qa, va] = chaves[i - 1], [qb, vb] = chaves[i]; if (f <= qb) return lerp(va, vb, CURVAS[curva](seg(f, qa, qb))); } return chaves[chaves.length - 1][1]; }; // ---------- medida de texto ---------- let canvas: HTMLCanvasElement | null = null; const cache = new Map(); export const larguraTexto = (texto: string, tamanho: number, peso = 600, trackingEm = -0.012): number => { const chave = `${texto}|${tamanho}|${peso}|${trackingEm}`; const c = cache.get(chave); if (c !== undefined) return c; if (typeof document === 'undefined') return texto.length * tamanho * 0.55; canvas = canvas || document.createElement('canvas'); const ctx = canvas.getContext('2d'); if (!ctx) return texto.length * tamanho * 0.55; const fonte = `${peso} ${tamanho}px "AT Inter"`; ctx.font = fonte; const w = ctx.measureText(texto).width + texto.length * trackingEm * tamanho; if (document.fonts.check(fonte)) cache.set(chave, w); return w; }; /** o maior tamanho até `tamanho` em que o texto cabe em `largura`. Nunca corta palavra. */ export const caber = (texto: string, tamanho: number, largura = LARGURA_UTIL, peso = 600, trackingEm = -0.012) => { const w = larguraTexto(texto, tamanho, peso, trackingEm); return w > largura ? Math.floor(tamanho * (largura / w)) : tamanho; };