// Base do kit pixel: 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/pressstart2p.woff2'; import fonte1 from './fontes/vt323.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 = { "ceu": "#29ADFF", "noite": "#1D2B53", "creme": "#FFF1E8", "amarelo": "#FFEC27", "laranja": "#FFA300", "magenta": "#FF004D", "verde": "#00E436", "roxo": "#7E2553", "azul": "#29ADFF", "cinza": "#83769C" }; // ---------- fonte ---------- export const FONTE_TITULO = '"PX Press Start 2P", monospace'; export const FONTE_TEXTO = '"PX VT323", monospace'; const FONTES: [string, string, string][] = [['PX Press Start 2P', fonte0, '400'], ['PX VT323', fonte1, '400']]; if (typeof document !== 'undefined' && !(globalThis as Record).__k_pixel) { (globalThis as Record).__k_pixel = true; const h = delayRender('pixel: 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 = 3; 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.2) */ export const mola = (t: number, s = 2.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 (124 bpm) em quadros */ export const BATIDA = 14.52; // ---------- 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 !== '');