// Base do kit quadro-giz: 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/schoolbell.woff2'; import fonte1 from './fontes/patrick-hand.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 = { "quadro": "#2E4A3E", "quadroClaro": "#3A5C4D", "madeira": "#8B5E3C", "madeiraEscura": "#5B3B22", "branco": "#F4F1E8", "amarelo": "#F7D774", "rosa": "#F4A6B8", "azul": "#9FD3F0", "verde": "#A8DDA0" }; // ---------- fonte ---------- export const FONTE_TITULO = '"QG Schoolbell", cursive'; export const FONTE_TEXTO = '"QG Patrick Hand", cursive'; const FONTES: [string, string, string][] = [['QG Schoolbell', fonte0, '400'], ['QG Patrick Hand', fonte1, '400']]; if (typeof document !== 'undefined' && !(globalThis as Record).__k_quadro_giz) { (globalThis as Record).__k_quadro_giz = true; const h = delayRender('quadro-giz: 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 ~1.7) */ export const mola = (t: number, s = 1.7) => { 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 (90 bpm) em quadros */ export const BATIDA = 20; // ---------- 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 !== '');