feat: definir contratos do notebook story
This commit is contained in:
1 parent
d89dde21ee
commit
8b858528f8
4 files changed
+337
No files matched your search
@@ -0,0 +1,145 @@
|
||||
export type NotebookPhase =
|
||||
| "approach"
|
||||
| "open"
|
||||
| "boot"
|
||||
| "cursor-in"
|
||||
| "browse"
|
||||
| "hold"
|
||||
| "close";
|
||||
|
||||
export type NotebookFrame = {
|
||||
phase: NotebookPhase;
|
||||
lidAngle: number;
|
||||
deviceOpacity: number;
|
||||
screenOpacity: number;
|
||||
activeTabIndex: number;
|
||||
cursorVisible: boolean;
|
||||
cursorX: number;
|
||||
cursorY: number;
|
||||
cursorClicking: boolean;
|
||||
};
|
||||
|
||||
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 },
|
||||
];
|
||||
|
||||
function clamp(value: number, min: number, max: number): number {
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
|
||||
function normalizeProgress(progress: number): number {
|
||||
return clamp(Number.isFinite(progress) ? progress : 0, 0, 1);
|
||||
}
|
||||
|
||||
function interpolate(from: number, to: number, progress: number): number {
|
||||
return from + (to - from) * clamp(progress, 0, 1);
|
||||
}
|
||||
|
||||
function getPhase(progress: number): NotebookPhase {
|
||||
if (progress < 0.1) return "approach";
|
||||
if (progress < 0.25) return "open";
|
||||
if (progress < 0.34) return "boot";
|
||||
if (progress < BROWSE_START) return "cursor-in";
|
||||
if (progress < BROWSE_END) return "browse";
|
||||
if (progress < HOLD_END) return "hold";
|
||||
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") {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (phase === "hold") {
|
||||
return tabCount - 1;
|
||||
}
|
||||
|
||||
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 } {
|
||||
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),
|
||||
};
|
||||
}
|
||||
|
||||
if (activeTabIndex < 0) {
|
||||
return { x: 0.06, y: 0.88 };
|
||||
}
|
||||
|
||||
const point = CURSOR_POINTS[activeTabIndex % CURSOR_POINTS.length];
|
||||
return point;
|
||||
}
|
||||
|
||||
function isCursorClicking(progress: number, phase: NotebookPhase, tabCount: number): boolean {
|
||||
if (tabCount < 1 || phase === "approach" || phase === "open" || phase === "boot" || phase === "close") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (phase === "cursor-in") {
|
||||
return progress >= 0.43;
|
||||
}
|
||||
|
||||
if (phase === "hold") {
|
||||
return false;
|
||||
}
|
||||
|
||||
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 {
|
||||
const normalizedProgress = normalizeProgress(progress);
|
||||
const normalizedTabCount = Math.max(0, Math.floor(tabCount));
|
||||
const phase = getPhase(normalizedProgress);
|
||||
const activeTabIndex = getActiveTabIndex(normalizedProgress, normalizedTabCount, phase);
|
||||
const isApproach = normalizedProgress < 0.1;
|
||||
const isOpening = phase === "open";
|
||||
const isBooting = phase === "boot";
|
||||
const isClosing = phase === "close";
|
||||
const lidAngle = isOpening
|
||||
? interpolate(0, OPEN_ANGLE, (normalizedProgress - 0.1) / 0.15)
|
||||
: isClosing
|
||||
? interpolate(OPEN_ANGLE, 0, (normalizedProgress - HOLD_END) / (1 - HOLD_END))
|
||||
: normalizedProgress >= 0.25
|
||||
? OPEN_ANGLE
|
||||
: 0;
|
||||
const screenOpacity = isBooting
|
||||
? interpolate(0, 1, (normalizedProgress - 0.25) / 0.09)
|
||||
: normalizedProgress >= 0.34 && !isClosing
|
||||
? 1
|
||||
: isClosing
|
||||
? interpolate(1, 0, (normalizedProgress - HOLD_END) / (1 - HOLD_END))
|
||||
: 0;
|
||||
const deviceOpacity = isApproach
|
||||
? interpolate(0, 1, normalizedProgress / 0.1)
|
||||
: 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);
|
||||
|
||||
return {
|
||||
phase,
|
||||
lidAngle,
|
||||
deviceOpacity,
|
||||
screenOpacity,
|
||||
activeTabIndex,
|
||||
cursorVisible,
|
||||
cursorX,
|
||||
cursorY,
|
||||
cursorClicking: cursorVisible && isCursorClicking(normalizedProgress, phase, normalizedTabCount),
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user