// Constants for pricing const HOURLY_RATE = 100; // 100 sats per hour const MIN_HOURS = 1; const MAX_HOURS = 2160; // 3 months const MIN_SATS = HOURLY_RATE * MIN_HOURS; const MAX_SATS = 216000; // Maximum for 3 months // Utility functions for duration formatting function formatDuration(hours) { const exactHours = `${hours} hour${hours === 1 ? '' : 's'}`; // Break down the time into components const months = Math.floor(hours / 720); const remainingAfterMonths = hours % 720; const weeks = Math.floor(remainingAfterMonths / 168); const remainingAfterWeeks = remainingAfterMonths % 168; const days = Math.floor(remainingAfterWeeks / 24); const remainingHours = remainingAfterWeeks % 24; // Build the detailed breakdown const parts = []; if (months > 0) { parts.push(`${months} month${months === 1 ? '' : 's'}`); } if (weeks > 0) { parts.push(`${weeks} week${weeks === 1 ? '' : 's'}`); } if (days > 0) { parts.push(`${days} day${days === 1 ? '' : 's'}`); } if (remainingHours > 0 || parts.length === 0) { parts.push(`${remainingHours} hour${remainingHours === 1 ? '' : 's'}`); } // Combine all parts with proper grammar let breakdown = ''; if (parts.length > 1) { const lastPart = parts.pop(); breakdown = parts.join(', ') + ' and ' + lastPart; } else { breakdown = parts[0]; } return `${exactHours} (${breakdown})`; } // Price calculation with volume discounts function calculatePrice(hours) { try { hours = parseInt(hours); if (hours < MIN_HOURS) return MIN_SATS; let basePrice = hours * HOURLY_RATE; if (hours >= 2160) { // 3 months basePrice = basePrice * 0.75; } else if (hours >= 720) { // 30 days basePrice = basePrice * 0.85; } else if (hours >= 168) { // 7 days basePrice = basePrice * 0.90; } else if (hours >= 24) { // 1 day basePrice = basePrice * 0.95; } return Math.round(basePrice); } catch (error) { console.error('Error calculating price:', error); return MIN_SATS; } } // Calculate hours from price function calculateHoursFromPrice(sats) { try { sats = parseInt(sats); if (sats < MIN_SATS) return MIN_HOURS; if (sats > MAX_SATS) return MAX_HOURS; // Binary search for the closest hour value const binarySearchHours = (min, max, targetSats) => { while (min <= max) { const mid = Math.floor((min + max) / 2); const price = calculatePrice(mid); if (price === targetSats) return mid; if (price < targetSats) min = mid + 1; else max = mid - 1; } return max; }; let hours = 0; if (sats >= calculatePrice(2160)) { hours = Math.floor(sats / (HOURLY_RATE * 0.75)); } else if (sats >= calculatePrice(720)) { hours = binarySearchHours(720, 2159, sats); } else if (sats >= calculatePrice(168)) { hours = binarySearchHours(168, 719, sats); } else if (sats >= calculatePrice(24)) { hours = binarySearchHours(24, 167, sats); } else { hours = binarySearchHours(1, 23, sats); } return Math.max(MIN_HOURS, Math.min(MAX_HOURS, hours)); } catch (error) { console.error('Error calculating hours from price:', error); return MIN_HOURS; } } // Update all displays and inputs function updateDisplays(hours, skipSource = null) { const elements = { priceDisplay: document.getElementById('price-display'), durationDisplay: document.getElementById('duration-display'), customHours: document.getElementById('custom-hours'), customSats: document.getElementById('custom-sats') }; hours = Math.max(MIN_HOURS, Math.min(MAX_HOURS, hours)); const price = calculatePrice(hours); // Update displays if (elements.priceDisplay && elements.durationDisplay) { elements.priceDisplay.textContent = price; elements.durationDisplay.textContent = formatDuration(hours); } // Update inputs (skip the source of the update) if (skipSource !== 'hours' && elements.customHours) { elements.customHours.value = hours; } if (skipSource !== 'sats' && elements.customSats) { elements.customSats.value = price; } } // Main pricing interface export const Pricing = { init() { console.log('Initializing pricing system...'); const elements = { customHours: document.getElementById('custom-hours'), customSats: document.getElementById('custom-sats'), presetButtons: document.querySelectorAll('.duration-preset') }; // Initial display updateDisplays(24); // Start with 24 hours as default // Event listeners for custom inputs elements.customHours?.addEventListener('input', (e) => { let hours = parseInt(e.target.value) || MIN_HOURS; hours = Math.max(MIN_HOURS, Math.min(MAX_HOURS, hours)); updateDisplays(hours, 'hours'); }); elements.customSats?.addEventListener('input', (e) => { let sats = parseInt(e.target.value) || MIN_SATS; sats = Math.max(MIN_SATS, Math.min(MAX_SATS, sats)); const hours = calculateHoursFromPrice(sats); updateDisplays(hours, 'sats'); }); // Add blur events to enforce minimums elements.customHours?.addEventListener('blur', (e) => { if (!e.target.value || parseInt(e.target.value) < MIN_HOURS) { updateDisplays(MIN_HOURS, 'hours'); } }); elements.customSats?.addEventListener('blur', (e) => { if (!e.target.value || parseInt(e.target.value) < MIN_SATS) { updateDisplays(MIN_HOURS, 'sats'); } }); // Handle preset buttons elements.presetButtons.forEach(button => { button.addEventListener('click', () => { const hours = parseInt(button.getAttribute('data-hours')); updateDisplays(hours); }); }); } }; // Auto-initialize on script load document.addEventListener('DOMContentLoaded', () => { Pricing.init(); });