// Base do kit colagem: cores, fontes, curvas, passo e medidas. Todo número visual sai daqui. // Os elementos importam só este arquivo, 'react' e 'remotion'. import {continueRender, delayRender, useCurrentFrame} from 'remotion'; import fonte0 from './fontes/abril-fatface.woff2'; import fonte1 from './fontes/special-elite.woff2'; // ---------- tela ---------- export const LARGURA = 1080, ALTURA = 1920, FPS = 30; export const SEGURA = {topo: 168, base: 1330, lado: 80}; export const LARGURA_UTIL = LARGURA - SEGURA.lado * 2; // 920 px // ---------- cor ---------- export const COR = { "kraft": "#E6D8BC", "jornal": "#D9CCAE", "branco": "#FAF7F0", "tinta": "#1B1B1B", "vermelho": "#E63B2E", "amarelo": "#F6D743", "azul": "#2B59C3", "fita": "rgba(246,215,67,0.85)" }; // ---------- fonte ---------- export const FONTE_TITULO = '"CL Abril Fatface", serif'; export const FONTE_TEXTO = '"CL Special Elite", monospace'; const FONTES: [string, string, string][] = [['CL Abril Fatface', fonte0, '400'], ['CL Special Elite', fonte1, '400']]; if (typeof document !== 'undefined' && !(globalThis as Record).__k_colagem) { (globalThis as Record).__k_colagem = true; const h = delayRender('colagem: fontes'); Promise.all(FONTES.map(([nome, url, peso]) => new FontFace(nome, `url(${url}) format('woff2')`, {weight: peso, style: 'normal'}).load().then((ff) => {document.fonts.add(ff);}).catch(() => undefined))) .then(() => continueRender(h)); } // ---------- tempo e curva ---------- /** passo da animação: 1 = liso, 2 ou 3 = movimento em passos (stop-motion / 8 bits) */ export const PASSO = 2; export const q = (f: number) => Math.floor(f / PASSO) * PASSO; /** quadro atual da cena, já em passos */ export const useF = () => q(useCurrentFrame()); 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) => c01((f - a) / (b - a)); /** saída rápida e pouso longo */ export const e5 = (t: number) => 1 - Math.pow(1 - c01(t), 5); /** passa do alvo e volta (overshoot ~2) */ export const mola = (t: number, s = 2) => { const x = c01(t) - 1; return x * x * ((s + 1) * x + s) + 1; }; export const lerp = (a: number, b: number, t: number) => a + (b - a) * t; /** ruído determinístico 0..1 */ export const ruido = (n: number) => { const x = Math.sin(n * 127.1 + 311.7) * 43758.5453; return x - Math.floor(x); }; /** uma batida da trilha do kit (108 bpm) em quadros */ export const BATIDA = 16.67; // ---------- medida de texto ---------- let canvas: HTMLCanvasElement | null = null; const cache = new Map(); export const larguraTexto = (texto: string, tamanho: number, familia = FONTE_TITULO, peso = 400, trackingEm = 0): number => { const chave = `${texto}|${tamanho}|${familia}|${peso}|${trackingEm}`; const c = cache.get(chave); if (c !== undefined) return c; if (typeof document === 'undefined') return texto.length * tamanho * 0.6; canvas = canvas || document.createElement('canvas'); const ctx = canvas.getContext('2d'); if (!ctx) return texto.length * tamanho * 0.6; const fonte = `${peso} ${tamanho}px ${familia}`; 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` */ export const caber = (texto: string, tamanho: number, largura = LARGURA_UTIL, familia = FONTE_TITULO, peso = 400, trackingEm = 0) => { const w = larguraTexto(texto, tamanho, familia, peso, trackingEm); return w > largura ? Math.floor(tamanho * (largura / w)) : tamanho; }; /** tira os asteriscos de destaque: "Usar *IA*" -> "Usar IA" */ export const textoLimpo = (t: string) => t.replace(/\*/g, ''); /** divide "Usar *IA* já" em [{t:'Usar ',d:false},{t:'IA',d:true},{t:' já',d:false}] */ export const comDestaque = (t: string) => t.split('*').map((p, i) => ({t: p, d: i % 2 === 1})).filter((p) => p.t !== '');