fix(navegacao): foco em âncora de outra página, vitrine acessível e 3D mais leve

Foco cai no destino ao chegar de outra página; âncoras ignoram ?tipo=; movimento reduzido salta em todos os cliques; cronômetro da janela ampliada; degradê no topo de Projetos e legendas do mergulho com intervalos testados; vitrine acima da barra de cookies e com 2 paradas por projeto; reel-list.css no global; 3D montado em tarefas curtas, limitado a ~30 qps e estático com WebGL por software; pôsteres da vitrine de 330 para 158 KB; SC Plus regravado; scripts sem funções acima de complexidade 10.
This commit is contained in:
Evandro Fonseca Junior committed 2026-10-14 19:14:48 -03:00
1 parent 9e2d65a926
commit 204ebd5fa5
49 files changed
+2197 -606

No files matched your search

@@ -502,14 +502,14 @@ describe("createField: quando pedir o próximo quadro", () => {
const scene = setup({ visible: true });
scene.settle(200);
expect(scene.frame()).toBe(0);
expect(scene.frame(50)).toBe(0);
});
it("path: everything settled and the hero is not visible -> no frame", () => {
const scene = setup({ visible: false });
scene.settle(200);
expect(scene.frame()).toBe(0);
expect(scene.frame(50)).toBe(0);
});
it("path: settled, visible, and the mouse is moving -> frames are requested", () => {
@@ -518,7 +518,7 @@ describe("createField: quando pedir o próximo quadro", () => {
(performance.now as jest.Mock).mockReturnValue(scene.now());
pointerMove("mouse");
expect(scene.frame()).toBeGreaterThan(0);
expect(scene.frame(50)).toBeGreaterThan(0);
});
it("path: settled, mouse active, but the hero is not visible -> no frame (cursor only counts on screen)", () => {
@@ -527,7 +527,7 @@ describe("createField: quando pedir o próximo quadro", () => {
(performance.now as jest.Mock).mockReturnValue(scene.now());
pointerMove("mouse");
expect(scene.frame()).toBe(0);
expect(scene.frame(50)).toBe(0);
});
it("path: the scroll target moved after the hero left the screen -> still finishes settling", () => {
@@ -543,11 +543,11 @@ describe("createField: quando pedir o próximo quadro", () => {
scene.settle(200);
(performance.now as jest.Mock).mockReturnValue(scene.now());
pointerMove("mouse");
expect(scene.frame()).toBe(0);
expect(scene.frame(50)).toBe(0);
scene.engine.setVisible(true);
expect(scene.frame()).toBeGreaterThan(0);
expect(scene.frame(50)).toBeGreaterThan(0);
});
it("path: several requests before a frame become ONE animation frame", () => {
@@ -843,6 +843,86 @@ describe("createField: densidade, contexto WebGL, erros e limpeza", () => {
});
});
describe("createField: o cursor sozinho não passa de ~30 quadros por segundo", () => {
/** Quantos quadros saem (renderer.render) em `seconds` de uma tela que entrega um quadro a cada `refreshMs`. */
function drawnOn(
scene: ReturnType<typeof setup>,
refreshMs: number,
seconds = 1,
) {
const before = scene.renderer.render.mock.calls.length;
const frames = Math.round((seconds * 1000) / refreshMs);
for (let i = 0; i < frames; i++) scene.frame(refreshMs);
return scene.renderer.render.mock.calls.length - before;
}
function withMovingCursor() {
const scene = setup({ visible: true });
scene.settle(200);
(performance.now as jest.Mock).mockReturnValue(scene.now());
pointerMove("mouse");
return scene;
}
it.each([
[144, 1000 / 144],
[120, 1000 / 120],
[60, 1000 / 60],
])(
"path: a %i Hz screen with the cursor moving -> about 30 frames a second, not one per screen frame",
(_hz, refreshMs) => {
const scene = withMovingCursor();
const drawn = drawnOn(scene, refreshMs, 2);
expect(drawn).toBeGreaterThanOrEqual(2 * 25);
expect(drawn).toBeLessThanOrEqual(2 * 31);
},
);
it("path: a skipped frame draws nothing, keeps the clock of the last drawn one and asks for the next", () => {
const scene = withMovingCursor();
scene.frame(50); // o último desenhado
const drawn = scene.renderer.render.mock.calls.length;
expect(scene.frame(10)).toBe(1); // 10 ms depois: pula e pede outro
expect(scene.renderer.render.mock.calls.length).toBe(drawn);
expect(scene.frame(10)).toBe(1);
expect(scene.frame(10)).toBe(1);
expect(scene.renderer.render.mock.calls.length).toBe(drawn);
// 40 ms depois do último desenhado: sai (o relógio do quadro pulado não conta)
scene.frame(10);
expect(scene.renderer.render.mock.calls.length).toBe(drawn + 1);
});
it("path: the intro and the scroll are not throttled (one frame per screen frame while they settle)", () => {
const scene = setup({ visible: true });
scene.frame(); // a entrada ainda corre
const intro = drawnOn(scene, 1000 / 144, 0.5);
expect(intro).toBe(Math.round(500 / (1000 / 144)));
scene.settle(200); // tudo assentado
scene.progressRef.current = 1; // a rolagem se move: o mergulho segue a cada quadro
const scrolling = drawnOn(scene, 1000 / 60, 0.25);
expect(scrolling).toBe(15);
});
it("path: the frame the pulse asks for 33 ms after the last one is drawn, not skipped", () => {
const scene = setup({ visible: true });
scene.settle(200);
scene.frame(33);
// o quadro de uma batida que sai com folga de temporizador (33 ms) entra
expect(scene.renderer.render.mock.calls.length).toBeGreaterThan(0);
const drawn = scene.renderer.render.mock.calls.length;
scene.frame(33);
expect(scene.renderer.render.mock.calls.length).toBe(drawn + 1);
});
});
describe("createField: a batida de 30 quadros por segundo", () => {
it("path: setVisible(true) twice in a row -> a single pulse; leaving and coming back starts a new one", () => {
const interval = jest