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

@@ -70,9 +70,12 @@ type Observer = {
};
let observers: Observer[] = [];
/** O que o globals.css dá ao hero: a altura do mergulho (telas de hero além dele) vem de `--hero-tail`. */
const HERO_TAIL = { "--hero-tail": 1.7 } as React.CSSProperties;
function Hero({ children }: { children?: React.ReactNode }) {
return (
<section className="hero">
<section className="hero" style={HERO_TAIL}>
<HeroField />
{children}
</section>
@@ -597,3 +600,122 @@ describe("HeroField: desmontagem em pontos que faltavam", () => {
expect(hero).not.toHaveAttribute("data-dive");
});
});
/** Um contexto WebGL de teste que conta ao navegador o nome do renderizador (WEBGL_debug_renderer_info). */
function contextRenderedBy(renderer: unknown, loseContext = jest.fn()) {
const RENDERER = 37446;
return {
getExtension: (name: string) => {
if (name === "WEBGL_debug_renderer_info")
return { UNMASKED_RENDERER_WEBGL: RENDERER };
return name === "WEBGL_lose_context" ? { loseContext } : null;
},
getParameter: (parameter: number) =>
parameter === RENDERER ? renderer : undefined,
};
}
describe("HeroField: WebGL2 por software (supportsWebGL2 -> isSoftwareWebGL)", () => {
it.each([
[
"SwiftShader (Chrome sem GPU)",
"ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero)), SwiftShader driver)",
],
["llvmpipe (Mesa)", "llvmpipe (LLVM 15.0.7, 256 bits)"],
[
"Microsoft Basic Render Driver",
"ANGLE (Microsoft, Microsoft Basic Render Driver Direct3D11 vs_5_0 ps_5_0)",
],
])(
"path: %s -> the plain 100svh hero from hydration: no dive, no scroll listeners, no observer, no scene, and the probe context is still released",
async (_name, renderer) => {
const loseContext = jest.fn();
setContext(() => contextRenderedBy(renderer, loseContext));
const addListener = jest.spyOn(window, "addEventListener");
render(<Hero />);
act(() => {
window.dispatchEvent(new Event("pointermove"));
});
await advance(3000);
await finishLoading();
expect(section()).not.toHaveAttribute("data-dive");
expect(screen.queryByTestId("scene")).toBeNull();
expect(mockScenes).toHaveLength(0);
expect(mockPrepare).not.toHaveBeenCalled();
expect(observers).toHaveLength(0);
expect(addListener.mock.calls.map(([type]) => type)).not.toContain(
"scroll",
);
expect(loseContext).toHaveBeenCalledTimes(1);
},
);
it("path: a hardware renderer -> the scene mounts and the dive starts", async () => {
setContext(() =>
contextRenderedBy("ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Direct3D11)"),
);
render(<Hero />);
await advance(1200);
await finishLoading();
expect(screen.getByTestId("scene")).toBeInTheDocument();
expect(section()).toHaveAttribute("data-dive");
});
it("path: no renderer info extension (Firefox, Safari) -> cannot tell, so the scene mounts", async () => {
setContext(() => ({ getExtension: () => null }));
render(<Hero />);
await advance(1200);
await finishLoading();
expect(screen.getByTestId("scene")).toBeInTheDocument();
});
it("path: the renderer name is not a string -> cannot tell, so the scene mounts", async () => {
setContext(() => contextRenderedBy(null));
render(<Hero />);
await advance(1200);
await finishLoading();
expect(screen.getByTestId("scene")).toBeInTheDocument();
});
it.each([
["missing from the stylesheet", undefined],
["not a number", "abc"],
])(
"path: --hero-tail %s -> no tail, the rail is the whole hero minus one screen (1960 px, never NaN)",
(_name, tail) => {
Object.defineProperty(window, "innerHeight", {
configurable: true,
value: 800,
});
const style = tail
? ({ "--hero-tail": tail } as React.CSSProperties)
: undefined;
render(
<section className="hero" style={style}>
<HeroField />
</section>,
);
section().getBoundingClientRect = () =>
({ top: -700, height: 2760, bottom: 2060 }) as DOMRect;
act(() => {
window.dispatchEvent(new Event("resize"));
jest.advanceTimersByTime(40);
});
// 2760 - 800 = 1960 de trilho; com a cauda de 1,7 tela (o CSS real) seria 1400 e o progresso 0,5
expect(Number(section().style.getPropertyValue("--hero-p"))).toBeCloseTo(
700 / 1960,
3,
);
},
);
});