/** * Check if a number is prime */ function isPrime(n: number): boolean { if (n < 2) return false; if (n === 2) return true; if (n % 2 === 0) return false; const sqrt = Math.sqrt(n); for (let i = 3; i <= sqrt; i += 2) { if (n % i === 0) return false; } return true; } /** * Generate the first n prime numbers */ function generatePrimes(count: number): number[] { const primes: number[] = []; let candidate = 2; while (primes.length < count) { if (isPrime(candidate)) { primes.push(candidate); } candidate++; } return primes; } /** * Precomputed list primes */ export const PRIMES = generatePrimes(2000); /** * Get the index of a prime in the precomputed list, or -1 if not found */ export function getPrimeIndex(prime: number): number { return PRIMES.indexOf(prime); } /** * Get the prime at a specific index in the precomputed list */ export function getPrimeAtIndex(index: number): number | undefined { return PRIMES[index]; } /** * Get the next prime in the precomputed list */ export function getNextPrime(currentPrime: number): number { const index = getPrimeIndex(currentPrime); if (index === -1 || index === PRIMES.length - 1) { return currentPrime; // Stay at current if not found or at end } return PRIMES[index + 1]; } /** * Get the previous prime in the precomputed list */ export function getPreviousPrime(currentPrime: number): number { const index = getPrimeIndex(currentPrime); if (index === -1 || index === 0) { return currentPrime; // Stay at current if not found or at start } return PRIMES[index - 1]; } /** * Calculate the best initial prime based on viewport dimensions * The grid size will be (prime - 1) × (prime - 1) pixels * * @param maxPrime - Maximum allowed prime (default 2000) * @returns The largest prime that will fit in the viewport without scrolling */ export function getInitialPrimeForViewport(maxPrime: number = 2000): number { // MUI's 'md' breakpoint is 900px const isMobile = window.innerWidth < 900; let availableWidth: number; let availableHeight: number; if (isMobile) { // Mobile layout: controls at top, full width available // Account for left/right padding (~20px total on mobile) availableWidth = window.innerWidth - 20; // Account for controls section at top (approximately 400-450px) and padding // Being conservative with 500px to ensure it fits availableHeight = window.innerHeight - 500; } else { // Desktop layout: sidebar on left // Account for sidebar (300px) and padding/margins (~50px total) availableWidth = window.innerWidth - 350; // Account for top/bottom padding (~50px total) availableHeight = window.innerHeight - 50; } // Use the smaller dimension to ensure it fits both ways const availableSize = Math.min(availableWidth, availableHeight); // Find the largest prime where (prime - 1) fits in the available space // The target grid size is availableSize, so target prime is availableSize + 1 const targetPrime = availableSize + 1; // Find the largest prime that doesn't exceed targetPrime or maxPrime const maxAllowedPrime = Math.min(targetPrime, maxPrime); // Find the largest prime in our list that doesn't exceed the max for (let i = PRIMES.length - 1; i >= 0; i--) { if (PRIMES[i] <= maxAllowedPrime) { return PRIMES[i]; } } // Fallback to smallest prime if viewport is too small return PRIMES[0]; }