From c66ff7fde6c8a9e1793e31ef0b73c8f151639fe5 Mon Sep 17 00:00:00 2001 From: Evandro Fonseca Junior Date: Sun, 30 Aug 2026 23:42:26 -0300 Subject: [PATCH] style: format notebook contracts --- app/components/story/notebookProjects.test.ts | 21 +++--- app/components/story/notebookProjects.ts | 9 ++- app/components/story/notebookTimeline.ts | 72 +++++++++++++++---- 3 files changed, 79 insertions(+), 23 deletions(-) diff --git a/app/components/story/notebookProjects.test.ts b/app/components/story/notebookProjects.test.ts index c7e460e..05f0237 100644 --- a/app/components/story/notebookProjects.test.ts +++ b/app/components/story/notebookProjects.test.ts @@ -1,4 +1,4 @@ -import { portfolio, type PortfolioProject } from "@/lib/portfolio-data"; +import { type PortfolioProject, portfolio } from "@/lib/portfolio-data"; import { selectNotebookProjects } from "./notebookProjects"; const projectsFixture = portfolio.projects as PortfolioProject[]; @@ -13,7 +13,11 @@ describe("selectNotebookProjects", () => { "sev-modaintima", "as-servicos", ]); - expect(result.every((project) => project.screenshotSrc.startsWith("/assets/prints/"))).toBe(true); + expect( + result.every((project) => + project.screenshotSrc.startsWith("/assets/prints/"), + ), + ).toBe(true); }); it("omits a project when its screenshot mapping is missing", () => { @@ -22,15 +26,16 @@ describe("selectNotebookProjects", () => { { ...projectsFixture[0], id: "unknown-live-project" }, ]); - expect(result.some((project) => project.id === "unknown-live-project")).toBe(false); + expect( + result.some((project) => project.id === "unknown-live-project"), + ).toBe(false); }); it("omits a project without an approved demo URL", () => { - const result = selectNotebookProjects( - projectsFixture.map((project) => - project.id === "nutriscan" ? { ...project, demoUrl: null } : project, - ), - ); + const projectsWithoutDemoUrl = projectsFixture.map((project) => + project.id === "nutriscan" ? { ...project, demoUrl: null } : project, + ) as unknown as PortfolioProject[]; + const result = selectNotebookProjects(projectsWithoutDemoUrl); expect(result.some((project) => project.id === "nutriscan")).toBe(false); }); diff --git a/app/components/story/notebookProjects.ts b/app/components/story/notebookProjects.ts index 00c157e..b897eac 100644 --- a/app/components/story/notebookProjects.ts +++ b/app/components/story/notebookProjects.ts @@ -16,14 +16,19 @@ const NOTEBOOK_ORDER = [ "as-servicos", ] as const; -const SCREENSHOT_BY_PROJECT_ID: Record<(typeof NOTEBOOK_ORDER)[number], string> = { +const SCREENSHOT_BY_PROJECT_ID: Record< + (typeof NOTEBOOK_ORDER)[number], + string +> = { nutriscan: "/assets/prints/print-nutriscan.png", "sc-plus": "/assets/prints/print-scplus.png", "sev-modaintima": "/assets/prints/print-sev.png", "as-servicos": "/assets/prints/print-asservicos.png", }; -function isApprovedDemoUrl(value: PortfolioProject["demoUrl"]): value is string { +function isApprovedDemoUrl( + value: PortfolioProject["demoUrl"], +): value is string { return typeof value === "string" && /^https?:\/\//.test(value); } diff --git a/app/components/story/notebookTimeline.ts b/app/components/story/notebookTimeline.ts index cb63efb..36c80f9 100644 --- a/app/components/story/notebookTimeline.ts +++ b/app/components/story/notebookTimeline.ts @@ -52,8 +52,19 @@ function getPhase(progress: number): NotebookPhase { return "close"; } -function getActiveTabIndex(progress: number, tabCount: number, phase: NotebookPhase): number { - if (tabCount < 1 || phase === "approach" || phase === "open" || phase === "boot" || phase === "cursor-in" || phase === "close") { +function getActiveTabIndex( + progress: number, + tabCount: number, + phase: NotebookPhase, +): number { + if ( + tabCount < 1 || + phase === "approach" || + phase === "open" || + phase === "boot" || + phase === "cursor-in" || + phase === "close" + ) { return -1; } @@ -61,11 +72,16 @@ function getActiveTabIndex(progress: number, tabCount: number, phase: NotebookPh return tabCount - 1; } - const browseProgress = (progress - BROWSE_START) / (BROWSE_END - BROWSE_START); + const browseProgress = + (progress - BROWSE_START) / (BROWSE_END - BROWSE_START); return clamp(Math.floor(browseProgress * tabCount), 0, tabCount - 1); } -function getCursorPoint(progress: number, phase: NotebookPhase, activeTabIndex: number): { x: number; y: number } { +function getCursorPoint( + progress: number, + phase: NotebookPhase, + activeTabIndex: number, +): { x: number; y: number } { if (phase === "cursor-in") { const cursorProgress = (progress - 0.34) / 0.1; return { @@ -82,8 +98,18 @@ function getCursorPoint(progress: number, phase: NotebookPhase, activeTabIndex: return point; } -function isCursorClicking(progress: number, phase: NotebookPhase, tabCount: number): boolean { - if (tabCount < 1 || phase === "approach" || phase === "open" || phase === "boot" || phase === "close") { +function isCursorClicking( + progress: number, + phase: NotebookPhase, + tabCount: number, +): boolean { + if ( + tabCount < 1 || + phase === "approach" || + phase === "open" || + phase === "boot" || + phase === "close" + ) { return false; } @@ -95,16 +121,24 @@ function isCursorClicking(progress: number, phase: NotebookPhase, tabCount: numb return false; } - const browseProgress = (progress - BROWSE_START) / (BROWSE_END - BROWSE_START); + const browseProgress = + (progress - BROWSE_START) / (BROWSE_END - BROWSE_START); const slotProgress = (browseProgress * tabCount) % 1; return slotProgress < 0.12; } -export function getNotebookFrame(progress: number, tabCount: number): NotebookFrame { +export function getNotebookFrame( + progress: number, + tabCount: number, +): NotebookFrame { const normalizedProgress = normalizeProgress(progress); const normalizedTabCount = Math.max(0, Math.floor(tabCount)); const phase = getPhase(normalizedProgress); - const activeTabIndex = getActiveTabIndex(normalizedProgress, normalizedTabCount, phase); + const activeTabIndex = getActiveTabIndex( + normalizedProgress, + normalizedTabCount, + phase, + ); const isApproach = normalizedProgress < 0.1; const isOpening = phase === "open"; const isBooting = phase === "boot"; @@ -112,7 +146,11 @@ export function getNotebookFrame(progress: number, tabCount: number): NotebookFr const lidAngle = isOpening ? interpolate(0, OPEN_ANGLE, (normalizedProgress - 0.1) / 0.15) : isClosing - ? interpolate(OPEN_ANGLE, 0, (normalizedProgress - HOLD_END) / (1 - HOLD_END)) + ? interpolate( + OPEN_ANGLE, + 0, + (normalizedProgress - HOLD_END) / (1 - HOLD_END), + ) : normalizedProgress >= 0.25 ? OPEN_ANGLE : 0; @@ -128,8 +166,14 @@ export function getNotebookFrame(progress: number, tabCount: number): NotebookFr : isClosing ? interpolate(1, 0.92, (normalizedProgress - HOLD_END) / (1 - HOLD_END)) : 1; - const cursorVisible = (phase === "cursor-in" || phase === "browse" || phase === "hold") && normalizedTabCount > 0; - const { x: cursorX, y: cursorY } = getCursorPoint(normalizedProgress, phase, activeTabIndex); + const cursorVisible = + (phase === "cursor-in" || phase === "browse" || phase === "hold") && + normalizedTabCount > 0; + const { x: cursorX, y: cursorY } = getCursorPoint( + normalizedProgress, + phase, + activeTabIndex, + ); return { phase, @@ -140,6 +184,8 @@ export function getNotebookFrame(progress: number, tabCount: number): NotebookFr cursorVisible, cursorX, cursorY, - cursorClicking: cursorVisible && isCursorClicking(normalizedProgress, phase, normalizedTabCount), + cursorClicking: + cursorVisible && + isCursorClicking(normalizedProgress, phase, normalizedTabCount), }; }