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:
1 parent
8d07b8c6bb
commit
c9a0b80656
48 files changed
+7210
-1241
No files matched your search
@@ -0,0 +1,199 @@
|
||||
import {
|
||||
chapterAnchors,
|
||||
chapterFraction,
|
||||
chapterIndexAt,
|
||||
desktopFractionAt,
|
||||
getProjectVideo,
|
||||
getShowcaseAssets,
|
||||
mobileFractionAt,
|
||||
SHOWCASE,
|
||||
type ShowcaseConfig,
|
||||
} from "./project-showcase";
|
||||
|
||||
/**
|
||||
* Testes de caminhos-base de lib/project-showcase.ts (complemento de project-showcase.test.ts):
|
||||
* getProjectVideo (id da vitrine x id fora dela x chaves herdadas de Object, nos dois aparelhos) e os trechos
|
||||
* de chapterAnchors/interpolate/chapterFraction que o arquivo principal não exercita.
|
||||
*/
|
||||
|
||||
function config(
|
||||
chapters: ShowcaseConfig["chapters"],
|
||||
durations = { desktop: 10, mobile: 20 },
|
||||
): ShowcaseConfig {
|
||||
return { id: "fixture", accent: "#fff", durations, weight: 1, chapters };
|
||||
}
|
||||
|
||||
describe("getProjectVideo", () => {
|
||||
it.each(SHOWCASE.map(({ id }) => id))(
|
||||
"path: showcase project %s -> assets for the requested device",
|
||||
(id) => {
|
||||
expect(getProjectVideo(id, "desktop")).toEqual(
|
||||
getShowcaseAssets(id, "desktop"),
|
||||
);
|
||||
expect(getProjectVideo(id, "mobile")).toEqual(
|
||||
getShowcaseAssets(id, "mobile"),
|
||||
);
|
||||
expect(getProjectVideo(id)).toEqual(getShowcaseAssets(id, "desktop"));
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
"",
|
||||
"nao-existe",
|
||||
"constructor",
|
||||
"__proto__",
|
||||
"toString",
|
||||
"SEV-MODAINTIMA",
|
||||
])(
|
||||
"path: id %j is not a showcase project -> undefined for both devices",
|
||||
(id) => {
|
||||
expect(getProjectVideo(id)).toBeUndefined();
|
||||
expect(getProjectVideo(id, "mobile")).toBeUndefined();
|
||||
},
|
||||
);
|
||||
|
||||
it("path: the two devices differ in size and in poster, not in the project folder", () => {
|
||||
const desktop = getProjectVideo("nutriscan", "desktop");
|
||||
const mobile = getProjectVideo("nutriscan", "mobile");
|
||||
|
||||
expect(desktop).toMatchObject({ width: 1440, height: 900 });
|
||||
expect(mobile).toMatchObject({ width: 390, height: 844 });
|
||||
expect(desktop?.poster).toBe("/videos/nutriscan/desktop.webp");
|
||||
expect(mobile?.poster).toBe("/videos/nutriscan/mobile.webp");
|
||||
});
|
||||
});
|
||||
|
||||
describe("chapterAnchors", () => {
|
||||
it("path: chapters that advance on both videos -> one anchor each between [0,0] and [1,1]", () => {
|
||||
const anchors = chapterAnchors(
|
||||
config([
|
||||
{ label: "A", desktop: 2, mobile: 4 },
|
||||
{ label: "B", desktop: 5, mobile: 10 },
|
||||
]),
|
||||
);
|
||||
|
||||
expect(anchors).toEqual([
|
||||
[0, 0],
|
||||
[0.2, 0.2],
|
||||
[0.5, 0.5],
|
||||
[1, 1],
|
||||
]);
|
||||
});
|
||||
|
||||
it("path: a chapter at second 0 on both videos -> no duplicate of the origin", () => {
|
||||
const anchors = chapterAnchors(
|
||||
config([
|
||||
{ label: "Abertura", desktop: 0, mobile: 0 },
|
||||
{ label: "B", desktop: 5, mobile: 10 },
|
||||
]),
|
||||
);
|
||||
|
||||
expect(anchors).toEqual([
|
||||
[0, 0],
|
||||
[0.5, 0.5],
|
||||
[1, 1],
|
||||
]);
|
||||
});
|
||||
|
||||
it("path: a chapter that does not advance on the desktop video -> skipped", () => {
|
||||
const anchors = chapterAnchors(
|
||||
config([
|
||||
{ label: "A", desktop: 4, mobile: 4 },
|
||||
{ label: "B", desktop: 4, mobile: 12 },
|
||||
]),
|
||||
);
|
||||
|
||||
expect(anchors).toEqual([
|
||||
[0, 0],
|
||||
[0.4, 0.2],
|
||||
[1, 1],
|
||||
]);
|
||||
});
|
||||
|
||||
it("path: a chapter that does not advance on the mobile video -> skipped", () => {
|
||||
const anchors = chapterAnchors(
|
||||
config([
|
||||
{ label: "A", desktop: 4, mobile: 10 },
|
||||
{ label: "B", desktop: 8, mobile: 10 },
|
||||
]),
|
||||
);
|
||||
|
||||
expect(anchors).toEqual([
|
||||
[0, 0],
|
||||
[0.4, 0.5],
|
||||
[1, 1],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mobileFractionAt / desktopFractionAt (interpolation)", () => {
|
||||
const sample = config([
|
||||
{ label: "A", desktop: 2, mobile: 10 },
|
||||
{ label: "B", desktop: 8, mobile: 15 },
|
||||
]);
|
||||
|
||||
it.each([
|
||||
[-1, 0],
|
||||
[0, 0],
|
||||
[0.2, 0.5],
|
||||
[0.5, 0.5 + (0.3 / 0.6) * 0.25],
|
||||
[0.8, 0.75],
|
||||
[1, 1],
|
||||
[2, 1],
|
||||
])(
|
||||
"path: desktop fraction %f -> mobile fraction %f",
|
||||
(fraction, expected) => {
|
||||
expect(mobileFractionAt(sample, fraction)).toBeCloseTo(expected, 9);
|
||||
},
|
||||
);
|
||||
|
||||
it("path: desktopFractionAt is the inverse on the anchors", () => {
|
||||
expect(desktopFractionAt(sample, 0.5)).toBeCloseTo(0.2, 9);
|
||||
expect(desktopFractionAt(sample, 0.75)).toBeCloseTo(0.8, 9);
|
||||
expect(desktopFractionAt(sample, 0)).toBe(0);
|
||||
expect(desktopFractionAt(sample, 1)).toBe(1);
|
||||
});
|
||||
|
||||
it("path: a chapter right at the end makes a zero-length span -> no division by zero", () => {
|
||||
const flat = config([{ label: "Fim", desktop: 10, mobile: 20 }]);
|
||||
|
||||
expect(mobileFractionAt(flat, 1)).toBe(1);
|
||||
expect(Number.isFinite(mobileFractionAt(flat, 0.999))).toBe(true);
|
||||
expect(Number.isFinite(desktopFractionAt(flat, 1))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("chapterIndexAt / chapterFraction", () => {
|
||||
const sample = config([
|
||||
{ label: "A", desktop: 0, mobile: 0 },
|
||||
{ label: "B", desktop: 4, mobile: 8 },
|
||||
{ label: "C", desktop: 8, mobile: 16 },
|
||||
]);
|
||||
|
||||
it.each([
|
||||
[-5, 0],
|
||||
[0, 0],
|
||||
[0.399, 0],
|
||||
[0.4, 1],
|
||||
[0.7999, 1],
|
||||
[0.8, 2],
|
||||
[1, 2],
|
||||
[9, 2],
|
||||
])("path: fraction %f -> chapter %i", (fraction, expected) => {
|
||||
expect(chapterIndexAt(sample, fraction)).toBe(expected);
|
||||
});
|
||||
|
||||
it("path: chapter index in range -> start fraction on each video", () => {
|
||||
expect(chapterFraction(sample, 1, "desktop")).toBeCloseTo(0.4, 9);
|
||||
expect(chapterFraction(sample, 1, "mobile")).toBeCloseTo(0.4, 9);
|
||||
expect(chapterFraction(sample, 2, "desktop")).toBeCloseTo(0.8, 9);
|
||||
});
|
||||
|
||||
it.each([-1, 3, 99])(
|
||||
"path: chapter index %i does not exist -> 0",
|
||||
(index) => {
|
||||
expect(chapterFraction(sample, index, "desktop")).toBe(0);
|
||||
expect(chapterFraction(sample, index, "mobile")).toBe(0);
|
||||
},
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user