perf(movimento): uma transição, 3D leve e vídeo certo por aparelho

Passagens com fade e 32 px, cena anterior termina em opacidade 0, sem visibility nem clip-path. 3D do hero como fundo ambiente, sob demanda e desligado em toque, aparelho fraco, saveData e movimento reduzido. Lenis fora do toque e salto direto em âncoras distantes. Vitrine só de clientes, até 3 capítulos, fixa só no desktop e carrossel no celular; vídeo só do projeto em cena com preload metadata. Rótulos mono a partir de 12 px e limão só no contato.
This commit is contained in:
Evandro Fonseca Junior committed 2026-10-14 03:32:23 -03:00
1 parent 8d07b8c6bb
commit c9a0b80656
48 files changed
+7210 -1241

No files matched your search

@@ -1,4 +1,6 @@
import { act, fireEvent, render, screen } from "@testing-library/react";
import { renderToString } from "react-dom/server";
import { MOBILE_QUERY } from "@/lib/device";
import { getFeaturedProjects } from "@/lib/portfolio-data";
import { ProjectMedia } from "./ProjectMedia";
import { ShowcaseProvider } from "./ShowcaseProvider";
@@ -18,6 +20,16 @@ if (!nutriscan) throw new Error("projeto ausente");
type Callback = IntersectionObserverCallback;
const callbacks: Callback[] = [];
const Original = global.IntersectionObserver;
const originalMatchMedia = window.matchMedia;
function mockPhone() {
window.matchMedia = jest.fn().mockImplementation((query: string) => ({
matches: query === MOBILE_QUERY,
media: query,
addEventListener() {},
removeEventListener() {},
}));
}
beforeEach(() => {
jest.useFakeTimers();
@@ -43,6 +55,11 @@ afterEach(() => {
jest.useRealTimers();
jest.restoreAllMocks();
global.IntersectionObserver = Original;
window.matchMedia = originalMatchMedia;
Object.defineProperty(navigator, "connection", {
configurable: true,
value: undefined,
});
});
function renderCard() {
@@ -156,6 +173,69 @@ describe("ProjectMedia", () => {
).toBeNull();
});
it("sends no media at all in the server HTML: the device is only known in the browser", () => {
const html = renderToString(
<ShowcaseProvider projects={projects}>
<ProjectMedia project={nutriscan as NonNullable<typeof nutriscan>} />
</ShowcaseProvider>,
);
expect(html).toContain("ds-frame-screen");
expect(html).not.toContain("<video");
expect(html).not.toContain("/videos/");
});
it("asks for the poster only once the cover is within about a screen of the window", () => {
const { frame, container } = renderCard();
const screenElement = container.querySelector(
".ds-frame-screen",
) as HTMLElement;
const video = container.querySelector("video") as HTMLVideoElement;
expect(screenElement.style.backgroundImage).toBe("");
expect(video).not.toHaveAttribute("poster");
expect(video).toHaveAttribute("preload", "metadata");
show(frame);
expect(screenElement.style.backgroundImage).toContain(
"/videos/nutriscan/desktop.webp",
);
expect(video).toHaveAttribute("poster", "/videos/nutriscan/desktop.webp");
});
it("uses the phone version of the poster and the video below 768 px", () => {
mockPhone();
const { frame, container } = renderCard();
show(frame);
const screenElement = container.querySelector(
".ds-frame-screen",
) as HTMLElement;
expect(screenElement.style.backgroundImage).toContain(
"/videos/nutriscan/mobile.webp",
);
const sources = [...container.querySelectorAll("video source")].map(
(source) => source.getAttribute("src"),
);
expect(sources).toEqual([
"/videos/nutriscan/mobile.webm",
"/videos/nutriscan/mobile.mp4",
]);
});
it("keeps the poster and never attaches a video when the data saver is on", () => {
Object.defineProperty(navigator, "connection", {
configurable: true,
value: { saveData: true },
});
const { frame, container } = renderCard();
show(frame);
expect(container.querySelector("video")).toBeNull();
expect(container.querySelector("source")).toBeNull();
expect(
screen.getByRole("img", { name: "Captura de tela de NutriScan" }),
).toBeInTheDocument();
});
it("renders nothing for a project without a recorded video", () => {
const { container } = render(
<ShowcaseProvider projects={projects}>