// Base do kit saas-linear (v2, "SaaS · produto em ação"): cores, fontes, curvas e medidas. // Os elementos importam só este arquivo, 'react' e 'remotion'. Nada de fora da pasta do kit. import {continueRender, delayRender} from 'remotion'; import serifaUrl from './fontes/newsreader-400.woff2'; import ui400Url from './fontes/inter-400.woff2'; import ui600Url from './fontes/inter-600.woff2'; // ---------- tela do vídeo ---------- export const LARGURA = 1080, ALTURA = 1920, FPS = 30; export const SEGURA = {topo: 168, base: 1330, lado: 80}; // ---------- a tela do produto (o "monitor") ---------- // Todo elemento de cena se posiciona em coordenadas da TELA (0..1500 x 0..940), não do vídeo. // No plano fechado (zoom 1) a tela aparece 1:1, centrada em x 540 / y 860 do vídeo: // o que fica entre x 290 e 1210 da tela cabe na área útil do vídeo (80..1000). export const TELA = {w: 1500, h: 940, cx: 750, cy: 470, util: {x0: 290, x1: 1210}}; export const CENTRO_VIDEO = {x: 540, y: 860}; // ---------- cor ---------- export const COR = { parede: '#3B1E19', // fundo atrás do monitor (sala quente, pouca luz) parede2: '#6A3426', // a luz quente que bate na parede mesa: '#1E1512', moldura: '#141312', // borda do monitor tela: '#F4F1EA', // creme da interface cartao: '#FFFFFF', linha: '#E7E2D8', tinta: '#29261F', // texto principal mute: '#8C877C', // texto de apoio, placeholder acento: '#D46F4D', // a marca (flor coral): só na marca e no botão de enviar azul: '#3D7BF0', // toggle ligado, "Conectado" agenda: ['#E7655A', '#F0A63A', '#3FA86C', '#4B83E6', '#E08A6C'], }; export const SOMBRA = { cartao: '0 2px 4px rgba(41,38,31,0.05), 0 18px 40px rgba(41,38,31,0.10)', menu: '0 4px 10px rgba(41,38,31,0.08), 0 30px 70px rgba(41,38,31,0.16)', }; // ---------- fonte ---------- export const FONTE_S = '"SL Newsreader", Georgia, serif'; export const FONTE_U = '"SL Inter", system-ui, sans-serif'; const LISTA = [ {fam: 'SL Newsreader', url: serifaUrl, peso: '400', estilo: 'normal'}, {fam: 'SL Inter', url: ui400Url, peso: '400', estilo: 'normal'}, {fam: 'SL Inter', url: ui600Url, peso: '600', estilo: 'normal'}, ]; if (typeof document !== 'undefined' && !(globalThis as Record).__kit_SL2) { (globalThis as Record).__kit_SL2 = true; for (const f of LISTA) { const h = delayRender('saas-linear: ' + f.fam + ' ' + f.peso); new FontFace(f.fam, `url(${f.url}) format('woff2')`, {weight: f.peso, style: f.estilo}).load() .then((ff) => {document.fonts.add(ff); continueRender(h);}) .catch(() => continueRender(h)); } } // ---------- 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)); /** curva padrão do kit: cubic-bezier(.22,1,.36,1) aproximada (saída rápida, pouso longo e preciso) */ export const e4 = (t: number) => 1 - Math.pow(1 - c01(t), 4); /** entra e sai suave (câmera, cursor) */ export const e3 = (t: number) => {const x = c01(t); return x * x * (3 - 2 * x);}; /** overshoot pequeno, ~4% (cartões, blocos da agenda) */ export const eb = (t: number) => {const x = c01(t) - 1, c = 1.4; return 1 + (c + 1) * x * x * x + c * x * x;}; export const lerp = (a: number, b: number, t: number) => a + (b - a) * t; /** uma batida da trilha (100 bpm) em quadros */ export const BATIDA = 18; // ---------- texto ---------- let canvas: HTMLCanvasElement | null = null; export const larguraTexto = (texto: string, tamanho: number, familia = FONTE_S, peso = 400): number => { if (typeof document === 'undefined') return texto.length * tamanho * 0.5; canvas = canvas || document.createElement('canvas'); const ctx = canvas.getContext('2d'); if (!ctx) return texto.length * tamanho * 0.5; ctx.font = `${peso} ${tamanho}px ${familia}`; return ctx.measureText(texto).width; }; /** o maior tamanho até `tamanho` em que o texto cabe em `largura`. Nunca palavra cortada. */ export const caber = (texto: string, tamanho: number, largura: number, familia = FONTE_S, peso = 400) => { const w = larguraTexto(texto, tamanho, familia, peso); return w > largura ? Math.floor(tamanho * (largura / w)) : tamanho; };