/** * useCanvasElapsedTime Hook * * Tracks elapsed time since page load for element visibility timing. * Used in constructor.tsx to control element appear/disappear based on timing. */ import { useState, useEffect, useRef } from 'react'; interface UseCanvasElapsedTimeOptions { /** Identifier for the current page (resets timer on change) */ pageId: string; /** Whether the timer is active */ enabled?: boolean; /** Update interval in milliseconds (default: 100ms) */ intervalMs?: number; } interface UseCanvasElapsedTimeResult { /** Current elapsed time in seconds since page load */ elapsedSec: number; /** Reset the elapsed time to zero */ reset: () => void; /** Timestamp when the current page started (for external calculations) */ startedAt: number; } /** * Hook to track elapsed time since page load. * Resets when pageId changes. Used for element visibility timing. * * @example * const { elapsedSec } = useCanvasElapsedTime({ * pageId: activePageId, * enabled: !isLoading, * }); * * // Check if element should be visible * const isVisible = elapsedSec >= element.appearDelaySec; */ export function useCanvasElapsedTime({ pageId, enabled = true, intervalMs = 100, }: UseCanvasElapsedTimeOptions): UseCanvasElapsedTimeResult { const [elapsedSec, setElapsedSec] = useState(0); const startedAtRef = useRef(Date.now()); // Reset timer when pageId changes useEffect(() => { if (typeof window === 'undefined') return; if (!enabled || !pageId) { setElapsedSec(0); return; } startedAtRef.current = Date.now(); setElapsedSec(0); const intervalId = window.setInterval(() => { const elapsed = (Date.now() - startedAtRef.current) / 1000; setElapsedSec(elapsed > 0 ? elapsed : 0); }, intervalMs); return () => window.clearInterval(intervalId); }, [pageId, enabled, intervalMs]); const reset = () => { startedAtRef.current = Date.now(); setElapsedSec(0); }; return { elapsedSec, reset, startedAt: startedAtRef.current, }; } /** * Check if an element should be visible based on timing settings. * * @param elapsedSec - Current elapsed time in seconds * @param appearDelaySec - Delay before element appears (default: 0) * @param appearDurationSec - Duration element is visible (null = infinite) * @returns Whether the element should be visible */ export function isElementVisibleAtTime( elapsedSec: number, appearDelaySec?: number, appearDurationSec?: number | null, ): boolean { const delay = Number(appearDelaySec || 0); // Not yet visible if (elapsedSec < delay) return false; // No duration limit - always visible after delay if (appearDurationSec === null || appearDurationSec === undefined) { return true; } const duration = Number(appearDurationSec); if (!Number.isFinite(duration) || duration <= 0) return true; // Check if still within visibility window return elapsedSec <= delay + duration; } export default useCanvasElapsedTime;