Files

97 lines
3.4 KiB
TypeScript

/** Contas puras da vitrine (sem DOM), separadas para serem testadas uma a uma. */
export interface ReelPosition {
/** Projeto em cena. */
index: number;
/** Quanto do trecho do projeto já passou (0 a 1). */
local: number;
}
/** Em que projeto e em que ponto dele a rolagem está, dado o avanço total `t` (0 a 1) e o peso de cada trecho. */
export function locateInReel(weights: number[], t: number): ReelPosition {
const total = weights.reduce((sum, weight) => sum + weight, 0);
if (weights.length === 0 || total <= 0) return { index: 0, local: 0 };
const position = Math.min(1, Math.max(0, t)) * total;
let start = 0;
for (let index = 0; index < weights.length; index += 1) {
const end = start + weights[index];
if (position < end || index === weights.length - 1) {
const local = (position - start) / weights[index];
return { index, local: Math.min(1, Math.max(0, local)) };
}
start = end;
}
return { index: weights.length - 1, local: 1 };
}
/** Início do trecho de cada projeto, como fração (0 a 1) do avanço total. */
export function reelStarts(weights: number[]): number[] {
const total = weights.reduce((sum, weight) => sum + weight, 0);
let acc = 0;
return weights.map((weight) => {
const start = total > 0 ? acc / total : 0;
acc += weight;
return start;
});
}
/** Fração do trecho de cada projeto em que o vídeo fica parado, no começo e no fim. */
export const REEL_HOLD = 0.1;
/**
* Pausa no fim do último projeto: depois dele não há troca a esperar, e o palco já se solta da página. Fica só o
* bastante para o último quadro chegar antes de o palco subir (era a mesma pausa de 10%, com a rolagem ainda presa).
*/
export const REEL_LAST_TAIL = 0.03;
/** As pausas do projeto `index` de `count`: no começo e no fim do trecho dele. */
export function reelHolds(index: number, count: number) {
return {
head: REEL_HOLD,
tail: index === count - 1 ? REEL_LAST_TAIL : REEL_HOLD,
};
}
/** Avanço total (0 a 1) em que o projeto `index` está em `local` (0 a 1) do trecho dele. */
export function reelOffset(weights: number[], index: number, local: number) {
const total = weights.reduce((sum, weight) => sum + weight, 0);
if (total <= 0 || index < 0 || index >= weights.length) return 0;
return reelStarts(weights)[index] + (local * weights[index]) / total;
}
/**
* O trecho de cada projeto tem uma pausa no começo e outra no fim: o vídeo só anda no meio.
* Isso dá tempo de ler o título antes de o site começar a rolar e de descansar antes da troca.
*/
export function videoProgress(
local: number,
hold = REEL_HOLD,
tail = hold,
): number {
const span = 1 - hold - tail;
return Math.min(1, Math.max(0, (local - hold) / span));
}
/** O contrário de `videoProgress`: onde, no trecho do projeto (0 a 1), o avanço `progress` do vídeo acontece. */
export function localAtProgress(
progress: number,
hold = REEL_HOLD,
tail = hold,
) {
return hold + progress * (1 - hold - tail);
}
/** Aproxima `current` de `target` de forma suave, independente da taxa de quadros (`tau` em segundos). */
export function easeToward(
current: number,
target: number,
deltaSeconds: number,
tau = 0.07,
): number {
const factor = 1 - Math.exp(-Math.max(0, deltaSeconds) / tau);
return current + (target - current) * factor;
}
/** Saída da legenda (ms) antes de a do novo projeto entrar: curta, para a troca nunca mostrar duas legendas. */
export const CAPTION_OUT_MS = 120;