feat(showcase): moldura de navegador, um video por vez e video ampliado nos cards
- cards com moldura de navegador (endereco real) e brilho na cor do site no card que toca - um video por vez: o do card sob o mouse/foco, ou o mais perto do centro da tela; nada baixa antes de tocar, e um botao pausa os videos que se mexem sozinhos - janela ampliada em <dialog> nativo: troca computador/celular mantendo o ponto do site, capitulos clicaveis, controles proprios, setas entre projetos e foco devolvido ao fechar - dados e capitulos de cada projeto em lib/project-showcase.ts (substitui project-videos.ts) Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
1 parent
60dc2f1878
commit
a1a1e91135
18 files changed
+3564
-155
No files matched your search
@@ -0,0 +1,94 @@
|
||||
import { existsSync, statSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import {
|
||||
chapterAnchors,
|
||||
chapterFraction,
|
||||
chapterIndexAt,
|
||||
desktopFractionAt,
|
||||
getOgImagePath,
|
||||
getProjectVideo,
|
||||
getScrubSource,
|
||||
getShowcaseAssets,
|
||||
mobileFractionAt,
|
||||
PROJECT_VIDEO_IDS,
|
||||
SHOWCASE,
|
||||
} from "./project-showcase";
|
||||
|
||||
const MAX_BYTES = 3 * 1024 * 1024;
|
||||
const publicFile = (path: string) => join(process.cwd(), "public", path);
|
||||
|
||||
describe("showcase chapters", () => {
|
||||
it.each(SHOWCASE)("$id has ordered chapters inside both videos", (config) => {
|
||||
const [first] = config.chapters;
|
||||
expect(first.desktop).toBe(0);
|
||||
expect(first.mobile).toBe(0);
|
||||
config.chapters.forEach((chapter, index) => {
|
||||
expect(chapter.label.trim()).not.toBe("");
|
||||
expect(chapter.desktop).toBeLessThan(config.durations.desktop);
|
||||
expect(chapter.mobile).toBeLessThan(config.durations.mobile);
|
||||
if (index > 0) {
|
||||
expect(chapter.desktop).toBeGreaterThan(
|
||||
config.chapters[index - 1].desktop,
|
||||
);
|
||||
expect(chapter.mobile).toBeGreaterThan(
|
||||
config.chapters[index - 1].mobile,
|
||||
);
|
||||
}
|
||||
});
|
||||
expect(config.weight).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it.each(SHOWCASE)("$id keeps the two timelines in sync", (config) => {
|
||||
const anchors = chapterAnchors(config);
|
||||
expect(anchors[0]).toEqual([0, 0]);
|
||||
expect(anchors[anchors.length - 1]).toEqual([1, 1]);
|
||||
for (const fraction of [0, 0.13, 0.5, 0.77, 1]) {
|
||||
const mobile = mobileFractionAt(config, fraction);
|
||||
expect(mobile).toBeGreaterThanOrEqual(0);
|
||||
expect(mobile).toBeLessThanOrEqual(1);
|
||||
expect(desktopFractionAt(config, mobile)).toBeCloseTo(fraction, 9);
|
||||
}
|
||||
config.chapters.forEach((_, index) => {
|
||||
expect(
|
||||
mobileFractionAt(config, chapterFraction(config, index, "desktop")),
|
||||
).toBeCloseTo(chapterFraction(config, index, "mobile"), 9);
|
||||
});
|
||||
});
|
||||
|
||||
it("finds the chapter on screen", () => {
|
||||
const lavacao = SHOWCASE.find((config) => config.id === "lavacao-047");
|
||||
expect(lavacao).toBeDefined();
|
||||
if (!lavacao) return;
|
||||
expect(chapterIndexAt(lavacao, 0)).toBe(0);
|
||||
expect(chapterIndexAt(lavacao, 3 / lavacao.durations.desktop)).toBe(1);
|
||||
expect(chapterIndexAt(lavacao, 10 / lavacao.durations.desktop)).toBe(2);
|
||||
expect(chapterIndexAt(lavacao, 1)).toBe(lavacao.chapters.length - 1);
|
||||
expect(chapterIndexAt(lavacao, 2)).toBe(lavacao.chapters.length - 1);
|
||||
expect(chapterIndexAt(lavacao, -1)).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("showcase assets", () => {
|
||||
it("returns undefined for projects without a recorded video", () => {
|
||||
expect(getProjectVideo("reddit-explorer")).toBeUndefined();
|
||||
});
|
||||
|
||||
it.each(PROJECT_VIDEO_IDS)(
|
||||
"has every file on disk for %s, each video within 3 MB",
|
||||
(id) => {
|
||||
const files = [
|
||||
...(["desktop", "mobile"] as const).flatMap((device) => {
|
||||
const assets = getShowcaseAssets(id, device);
|
||||
return [assets.poster, ...assets.sources.map((s) => s.src)];
|
||||
}),
|
||||
getScrubSource(id, "desktop"),
|
||||
getScrubSource(id, "mobile"),
|
||||
getOgImagePath(id),
|
||||
];
|
||||
for (const file of files) {
|
||||
expect(existsSync(publicFile(file))).toBe(true);
|
||||
expect(statSync(publicFile(file)).size).toBeLessThanOrEqual(MAX_BYTES);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user