CI / verify (push) Failing after 5m29s
Estado do disco em 14/10/2026, sem filtrar. Inclui as copias .sync-conflict-* do Syncthing de 07/10 e os 15 arquivos que o Syncthing apagou; todo esse conteudo ja existia em outros commits. So havia fora do Git: AGENTS.md, CLAUDE.md e a alteracao em data/project-health.json.
118 lines
3.3 KiB
TypeScript
118 lines
3.3 KiB
TypeScript
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_START = 0.06;
|
|
const BOOT_START = 0.18;
|
|
const CURSOR_START = 0.23;
|
|
const BROWSE_START = 0.3;
|
|
const BROWSE_END = 0.84;
|
|
const CLOSE_START = 0.9;
|
|
const CLOSE_LID_START = 0.93;
|
|
const OPEN_ANGLE = 112;
|
|
const ARRIVAL = 0.18;
|
|
|
|
function clamp(value: number, min: number, max: number): number {
|
|
return Math.min(Math.max(value, min), max);
|
|
}
|
|
|
|
function between(value: number, start: number, end: number): number {
|
|
return clamp((value - start) / (end - start), 0, 1);
|
|
}
|
|
|
|
export function getNotebookSeekProgress(
|
|
index: number,
|
|
tabCount: number,
|
|
): number {
|
|
const count = Number.isFinite(tabCount)
|
|
? Math.max(0, Math.floor(tabCount))
|
|
: 0;
|
|
if (!count) return BROWSE_START;
|
|
const selected = clamp(
|
|
Number.isFinite(index) ? Math.floor(index) : 0,
|
|
0,
|
|
count - 1,
|
|
);
|
|
return (
|
|
BROWSE_START + ((BROWSE_END - BROWSE_START) * (selected + 0.6)) / count
|
|
);
|
|
}
|
|
|
|
/** One reversible clock. Each tab arrives/clicks before its long reading hold. */
|
|
export function getNotebookFrame(
|
|
progress: number,
|
|
tabCount: number,
|
|
target?: NotebookFrame,
|
|
): NotebookFrame {
|
|
const p = clamp(Number.isFinite(progress) ? progress : 0, 0, 1);
|
|
const count = Number.isFinite(tabCount)
|
|
? Math.max(0, Math.floor(tabCount))
|
|
: 0;
|
|
const phase: NotebookPhase =
|
|
p < OPEN_START
|
|
? "approach"
|
|
: p < BOOT_START
|
|
? "open"
|
|
: p < CURSOR_START
|
|
? "boot"
|
|
: p < BROWSE_START
|
|
? "cursor-in"
|
|
: p < BROWSE_END
|
|
? "browse"
|
|
: p < CLOSE_START
|
|
? "hold"
|
|
: "close";
|
|
const slot = between(p, BROWSE_START, BROWSE_END) * count;
|
|
const destination = clamp(Math.floor(slot), 0, Math.max(0, count - 1));
|
|
const local = slot - destination;
|
|
const moving = phase === "browse" && destination > 0 && local < ARRIVAL;
|
|
const activeTabIndex = !count ? -1 : moving ? destination - 1 : destination;
|
|
const travel = between(local, 0, ARRIVAL);
|
|
const cursorTab = moving
|
|
? destination - 1 + travel * travel * (3 - 2 * travel)
|
|
: destination;
|
|
const entry = between(p, CURSOR_START, BROWSE_START);
|
|
const frame = target ?? ({} as NotebookFrame);
|
|
frame.phase = phase;
|
|
frame.lidAngle =
|
|
OPEN_ANGLE *
|
|
between(p, OPEN_START, BOOT_START) *
|
|
(1 - between(p, CLOSE_LID_START, 1));
|
|
frame.deviceOpacity = between(p, 0, OPEN_START);
|
|
frame.screenOpacity =
|
|
between(p, BOOT_START, CURSOR_START) *
|
|
(1 - between(p, CLOSE_START, CLOSE_LID_START));
|
|
frame.activeTabIndex = activeTabIndex;
|
|
frame.cursorVisible = count > 0 && p >= CURSOR_START && p < CLOSE_START;
|
|
frame.cursorX = count
|
|
? phase === "cursor-in"
|
|
? 0.5 + (0.5 / count - 0.5) * entry
|
|
: (cursorTab + 0.5) / count
|
|
: 0.5;
|
|
frame.cursorY = phase === "cursor-in" ? 0.95 - 0.65 * entry : 0.3;
|
|
frame.cursorClicking =
|
|
count > 0 &&
|
|
phase === "browse" &&
|
|
(destination === 0
|
|
? local < 0.07
|
|
: local >= ARRIVAL && local < ARRIVAL + 0.07);
|
|
return frame;
|
|
}
|