// Base do kit adesivos: cores, fontes, curvas e medidas. Todo número visual sai daqui. // Os elementos importam só este arquivo, 'react' e 'remotion'. Nada de fora da pasta do kit. import {continueRender, delayRender} from 'remotion'; import fonteA from './fontes/bowlby-latin.woff2'; import fonteB from './fontes/nunito-latin.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 ---------- export const COR = { gema: '#FFD23F', gemaClara: '#FFE88A', tinta: '#141414', branco: '#FFFFFF', rosa: '#FF4F9A', azul: '#2D5BFF', verde: '#19C37D', laranja: '#FF7A1A', }; /** estado de um adesivo que cola com um tapa: entra a 2,6x, bate, achata 10% e assenta */ export const cola = (f: number, em: number) => { const p = seg(f, em, em + 8), q = seg(f, em + 8, em + 16), k = 1 - e5(p); const sq = Math.sin(Math.PI * q) * 0.1; return {sx: (1 + k * 1.6) * (1 + sq), sy: (1 + k * 1.6) * (1 - sq), visivel: f >= em - 1, tilt: k * 14, sombra: lerp(10, 40, k)}; }; /** balanço de adesivo já colado (graus) e flutuação (px), por semente */ export const flutua = (f: number, seed: number) => ({gira: Math.sin(f / 21 + seed) * 1.5, sobe: Math.sin(f / 17 + seed * 2) * 8}); export const SOMBRA = (px: number) => `drop-shadow(0 ${px * 0.4}px ${px * 0.45}px rgba(0,0,0,0.28))`; // ---------- fonte ---------- // Famílias com o prefixo do kit, pra não colidir com outro kit no mesmo estúdio. export const FAM_A = 'AD Bowlby One'; export const FONTE_A = '"AD Bowlby One", Impact, sans-serif'; export const FAM_B = 'AD Nunito'; export const FONTE_B = '"AD Nunito", system-ui, sans-serif'; // As medidas de texto (caber) só valem depois das fontes: a Montagem espera `esperarFontes()` antes de desenhar. const TOTAL_FONTES = 2; let nProntas = 0; const aguardando: (() => void)[] = []; const marcarFonte = () => {nProntas++; if (nProntas >= TOTAL_FONTES) aguardando.splice(0).forEach((r) => r());}; export const fontesProntas = () => nProntas >= TOTAL_FONTES || typeof document === 'undefined'; export const esperarFontes = () => new Promise((res) => {if (fontesProntas()) res(); else aguardando.push(res);}); if (typeof document !== 'undefined' && !(globalThis as Record).__adA) { (globalThis as Record).__adA = true; const hA = delayRender('adesivos: AD Bowlby One'); new FontFace('AD Bowlby One', `url(${fonteA}) format('woff2')`, {weight: '400', style: 'normal'}).load() .then((ff) => {document.fonts.add(ff); marcarFonte(); continueRender(hA);}) .catch(() => {marcarFonte(); continueRender(hA);}); } if (typeof document !== 'undefined' && !(globalThis as Record).__adB) { (globalThis as Record).__adB = true; const hB = delayRender('adesivos: AD Nunito'); new FontFace('AD Nunito', `url(${fonteB}) format('woff2')`, {weight: '700 900', style: 'normal'}).load() .then((ff) => {document.fonts.add(ff); marcarFonte(); continueRender(hB);}) .catch(() => {marcarFonte(); continueRender(hB);}); } // ---------- 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) => c01((f - a) / (b - a)); /** saída rápida e pouso longo, ≈ cubic-bezier(.2,.8,.2,1) */ export const e5 = (t: number) => 1 - Math.pow(1 - c01(t), 5); /** saída com overshoot (passa de 1 e volta): toda entrada do kit usa esta ou a e5 */ export const ob = (t: number, s = 2.2) => {const x = c01(t) - 1; return 1 + (s + 1) * x * x * x + s * x * x;}; export const lerp = (a: number, b: number, t: number) => a + (b - a) * t; /** pseudo-aleatório estável (0..1) a partir de um inteiro */ export const rnd = (n: number) => {const x = Math.sin(n * 127.1 + 311.7) * 43758.5453; return x - Math.floor(x);}; /** uma batida da trilha do kit em quadros */ export const BATIDA = 16.0714; // ---------- medida de texto ---------- let canvas: HTMLCanvasElement | null = null; const cache = new Map(); export const larguraTexto = (texto: string, tamanho: number, fam: string, peso = 800, trackingEm = 0): number => { const chave = `${texto}|${tamanho}|${fam}|${peso}|${trackingEm}`; const c = cache.get(chave); if (c !== undefined) return c; if (typeof document === 'undefined') return texto.length * tamanho * 0.58; canvas = canvas || document.createElement('canvas'); const ctx = canvas.getContext('2d'); if (!ctx) return texto.length * tamanho * 0.58; const fonte = `${peso} ${tamanho}px "${fam}"`; ctx.font = fonte; const w = ctx.measureText(texto).width + texto.length * trackingEm * tamanho; if (fontesProntas() && document.fonts.check(fonte)) cache.set(chave, w); return w; }; /** o maior tamanho até `tamanho` em que o texto cabe em `largura` (nunca palavra cortada) */ export const caber = (texto: string, tamanho: number, fam: string, largura = LARGURA_UTIL, peso = 800, trackingEm = 0) => { const w = larguraTexto(texto, tamanho, fam, 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, ''); /** corta "a *b* c" em [{t:'a ', d:false}, {t:'b', d:true}, {t:' c', d:false}] */ export const partes = (t: string) => t.split('*').map((p, i) => ({t: p, d: i % 2 === 1})).filter((p) => p.t.length > 0);