feat: scroll-driven maintenance window set-piece in the infrastructure chapter
This commit is contained in:
88
src/journey/maintenance-progress.ts
Normal file
88
src/journey/maintenance-progress.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import type { JourneySnapshot } from './types';
|
||||
|
||||
/**
|
||||
* The 03:12 window: the Infrastructure chapter's set-piece is driven by how
|
||||
* far the visitor has read through the chapter, never by time, so scrolling
|
||||
* back up replays it in reverse (a rollback). Shared by the DOM (HUD sub-line,
|
||||
* diagram steps) and the lazy scene chunk, so it stays tiny and pure.
|
||||
*
|
||||
* The beats follow the featured operation in the content: back up, convert
|
||||
* and transfer, verify, then cut over.
|
||||
*/
|
||||
export type MaintenanceStep = 'open' | 'backup' | 'transfer' | 'verify' | 'done';
|
||||
|
||||
/** Upper bounds of each step, in section-local progress. */
|
||||
export const MAINTENANCE_BOUNDS = { open: 0.1, backup: 0.35, transfer: 0.65, verify: 0.9 } as const;
|
||||
|
||||
/**
|
||||
* The clock reads live Bucharest time again from here. The window keeps its
|
||||
* own clock right up to the next chapter: "CUTOVER OK" always reads 03:51.
|
||||
*/
|
||||
export const MAINTENANCE_LIVE_FROM = 1;
|
||||
|
||||
type ProgressInput = Pick<JourneySnapshot, 'activeSectionId' | 'sectionActivation'>;
|
||||
|
||||
/**
|
||||
* Section-local progress: 0 with the Infrastructure heading on the activation
|
||||
* line, 1 when the next chapter's heading reaches it. Null outside the chapter.
|
||||
*/
|
||||
export function maintenanceProgress(snapshot: ProgressInput): number | null {
|
||||
if (snapshot.activeSectionId !== 'infrastructure') return null;
|
||||
const { activeHeadingOffsetVh: active, nextHeadingOffsetVh: next } = snapshot.sectionActivation;
|
||||
if (next === null || !Number.isFinite(active) || !Number.isFinite(next)) return null;
|
||||
const span = next - active;
|
||||
if (!(span > 0)) return null;
|
||||
return Math.min(1, Math.max(0, -active / span));
|
||||
}
|
||||
|
||||
export function maintenanceStep(p: number): MaintenanceStep {
|
||||
if (p < MAINTENANCE_BOUNDS.open) return 'open';
|
||||
if (p < MAINTENANCE_BOUNDS.backup) return 'backup';
|
||||
if (p < MAINTENANCE_BOUNDS.transfer) return 'transfer';
|
||||
if (p < MAINTENANCE_BOUNDS.verify) return 'verify';
|
||||
return 'done';
|
||||
}
|
||||
|
||||
/** The window opens at 03:12 and cuts over at 03:51; the clock runs with the scroll. */
|
||||
export function maintenanceClock(p: number, live: () => string): string {
|
||||
if (p >= MAINTENANCE_LIVE_FROM) return live();
|
||||
if (p >= MAINTENANCE_BOUNDS.verify) return '03:51';
|
||||
const run = Math.min(1, Math.max(0, (p - MAINTENANCE_BOUNDS.open) / (MAINTENANCE_BOUNDS.verify - MAINTENANCE_BOUNDS.open)));
|
||||
return `03:${String(12 + Math.round(run * 39)).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
const LABELS: Readonly<Record<MaintenanceStep, string>> = {
|
||||
open: 'MAINTENANCE WINDOW',
|
||||
backup: 'BACKUP',
|
||||
transfer: 'CONVERT · TRANSFER',
|
||||
verify: 'VERIFY',
|
||||
done: 'CUTOVER OK',
|
||||
};
|
||||
|
||||
/** The gantry plate's sub-line, e.g. "MAINTENANCE WINDOW · 03:12". */
|
||||
export function maintenanceSubline(p: number, reversing: boolean, live: () => string): string {
|
||||
const label = reversing ? 'ROLLBACK CHECKPOINT' : LABELS[maintenanceStep(p)];
|
||||
return `${label} · ${maintenanceClock(p, live)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Latches the scroll direction inside the window: progress falling means a
|
||||
* rollback until it rises again. Small jitter is ignored.
|
||||
*/
|
||||
export function createMaintenanceDirection(): (p: number | null) => boolean {
|
||||
let last: number | null = null;
|
||||
let reversing = false;
|
||||
return (p) => {
|
||||
if (p === null) {
|
||||
last = null;
|
||||
reversing = false;
|
||||
return false;
|
||||
}
|
||||
if (last !== null) {
|
||||
if (p < last - 0.001) reversing = true;
|
||||
else if (p > last + 0.001) reversing = false;
|
||||
}
|
||||
last = p;
|
||||
return reversing && p > 0 && p < 1;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user