// Base do kit produto-3d (v2, filme de produto escuro): cores, fontes, curvas e medidas. // Todo número visual sai daqui. Os elementos importam só este arquivo, 'react' e 'remotion'. import {continueRender, delayRender} from 'remotion'; import syncopateUrl from './fontes/syncopate-latin.woff2'; import plexUrl from './fontes/plex-mono.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: 88}; export const LARGURA_UTIL = LARGURA - SEGURA.lado * 2; // 904 px // ---------- cor (ver regras/cor.md) ---------- export const COR = { /** o breu do estúdio e o chão polido */ breu: '#030304', chao: '#07080A', /** o poço de luz que cai do teto (volume) */ feixe: '#9AA3B5', /** corpo do produto: alumínio anodizado quase preto */ anodizado: '#1B1C21', borracha: '#101114', metal: '#B9BEC6', vidro: '#05070C', /** luz de borda: esquerda quente, direita fria */ bordaQuente: '#FFE6CC', bordaFria: '#D2E4FF', texto: '#F2F2F0', apagado: '#7B7F88', linha: '#4A4E57', /** o ÚNICO acento: o anel do produto e um traço na tipografia */ acento: '#FF5A1F', }; // ---------- fonte ---------- // Famílias com o prefixo do kit (P3), pra não colidir com outro kit no mesmo estúdio. export const FONTE_D = '"P3 Syncopate", "Syncopate", system-ui, sans-serif'; export const FONTE_M = '"P3 Plex Mono", "IBM Plex Mono", ui-monospace, monospace'; if (typeof document !== 'undefined' && !(globalThis as Record).__kitP3v2) { (globalThis as Record).__kitP3v2 = true; for (const [fam, url, peso] of [['P3 Syncopate', syncopateUrl, '700'], ['P3 Plex Mono', plexUrl, '400 600']]) { const h = delayRender('produto-3d: ' + fam); new FontFace(fam, `url(${url}) format('woff2')`, {weight: peso, style: 'normal'}).load() .then((ff) => {document.fonts.add(ff); continueRender(h);}) .catch(() => continueRender(h)); } } /** tamanhos (tela 1080×1920) */ export const TIPO = { titulo: {tamanho: 68, tracking: 0.04}, marca: {tamanho: 92, tracking: 0.08}, numero: {tamanho: 150, tracking: 0}, rotulo: {tamanho: 27, tracking: 0.16}, }; // ---------- tempo e curva ---------- export const c01 = (t: number) => Math.max(0, Math.min(1, t)); 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; 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 peças: arranca devagar, pousa longo (a calma cara do filme) */ export const suave = bezier(0.5, 0, 0.15, 1); /** entrada de texto: sai rápido, pousa longo */ export const saida = bezier(0.16, 1, 0.3, 1); /** saída: acelera pra fora */ export const entrada = bezier(0.7, 0, 0.84, 0); /** encaixe: acelera e assenta seco no fim (o "clac" da vista explodida) */ export const encaixe = bezier(0.65, 0, 0.95, 0.4); export const CURVAS = {suave, saida, entrada, encaixe, linear: (t: number) => c01(t)} as const; export type NomeCurva = keyof typeof CURVAS; /** trilha de chaves [[quadro, valor], ...] → valor no quadro f */ 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]; }; /** uma batida da trilha em quadros */ export const BATIDA = 18; // ---------- medida de texto ---------- let canvas: HTMLCanvasElement | null = null; export const larguraTexto = (texto: string, tamanho: number, familia: string, peso: number, trackingEm: number): number => { if (typeof document === 'undefined') return texto.length * tamanho * 0.8; canvas = canvas || document.createElement('canvas'); const ctx = canvas.getContext('2d'); if (!ctx) return texto.length * tamanho * 0.8; ctx.font = `${peso} ${tamanho}px ${familia}`; return ctx.measureText(texto).width + texto.length * trackingEm * tamanho; }; /** 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, familia = FONTE_D, peso = 700, trackingEm = 0.04) => { const w = larguraTexto(texto, tamanho, familia, peso, trackingEm); return w > largura ? Math.floor(tamanho * (largura / w)) : tamanho; };