feat(story): build procedural notebook animation

This commit is contained in:
Evandro Fonseca Junior committed 2026-09-01 04:35:11 -03:00
1 parent f3ed9da777
commit b079afdd73
9 files changed
+360 -213

No files matched your search

+29 -18
View File
@@ -23,12 +23,9 @@ const OPEN_ANGLE = 112;
const BROWSE_START = 0.44;
const BROWSE_END = 0.78;
const HOLD_END = 0.87;
const CURSOR_POINTS = [
{ x: 0.26, y: 0.68 },
{ x: 0.58, y: 0.32 },
{ x: 0.74, y: 0.72 },
{ x: 0.38, y: 0.28 },
];
const CLOSE_LID_START = 0.9;
const TAB_CLICK_Y = 0.3;
const PARK = { x: 0.5, y: 0.95 };
function clamp(value: number, min: number, max: number): number {
return Math.min(Math.max(value, min), max);
@@ -77,6 +74,11 @@ function getActiveTabIndex(
return clamp(Math.floor(browseProgress * tabCount), 0, tabCount - 1);
}
function tabCenterX(index: number, tabCount: number): number {
if (tabCount < 1) return PARK.x;
return (index + 0.5) / tabCount;
}
function getCursorPoint(
progress: number,
phase: NotebookPhase,
@@ -86,27 +88,30 @@ function getCursorPoint(
if (phase === "cursor-in") {
const cursorProgress = (progress - 0.34) / 0.1;
return {
x: interpolate(0.06, 0.26, cursorProgress),
y: interpolate(0.88, 0.68, cursorProgress),
x: interpolate(PARK.x, tabCenterX(0, tabCount), cursorProgress),
y: interpolate(PARK.y, TAB_CLICK_Y, cursorProgress),
};
}
if (activeTabIndex < 0) {
return { x: 0.06, y: 0.88 };
return { ...PARK };
}
const target = CURSOR_POINTS[activeTabIndex % CURSOR_POINTS.length];
const target = { x: tabCenterX(activeTabIndex, tabCount), y: TAB_CLICK_Y };
if (phase !== "browse" || activeTabIndex === 0) return target;
const browseProgress =
(progress - BROWSE_START) / (BROWSE_END - BROWSE_START);
const slotProgress = browseProgress * tabCount - activeTabIndex;
const previous = CURSOR_POINTS[(activeTabIndex - 1) % CURSOR_POINTS.length];
const previous = {
x: tabCenterX(activeTabIndex - 1, tabCount),
y: TAB_CLICK_Y,
};
const moveProgress = clamp(slotProgress / 0.36, 0, 1);
return {
x: interpolate(previous.x, target.x, moveProgress),
y: interpolate(previous.y, target.y, moveProgress),
y: target.y,
};
}
@@ -158,11 +163,13 @@ export function getNotebookFrame(
const lidAngle = isOpening
? interpolate(0, OPEN_ANGLE, (normalizedProgress - 0.1) / 0.15)
: isClosing
? interpolate(
OPEN_ANGLE,
0,
(normalizedProgress - HOLD_END) / (1 - HOLD_END),
)
? normalizedProgress < CLOSE_LID_START
? OPEN_ANGLE
: interpolate(
OPEN_ANGLE,
0,
(normalizedProgress - CLOSE_LID_START) / (1 - CLOSE_LID_START),
)
: normalizedProgress >= 0.25
? OPEN_ANGLE
: 0;
@@ -171,7 +178,11 @@ export function getNotebookFrame(
: normalizedProgress >= 0.34 && !isClosing
? 1
: isClosing
? interpolate(1, 0, (normalizedProgress - HOLD_END) / (1 - HOLD_END))
? interpolate(
1,
0,
(normalizedProgress - HOLD_END) / (CLOSE_LID_START - HOLD_END),
)
: 0;
const deviceOpacity = isApproach
? interpolate(0, 1, normalizedProgress / 0.1)