Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Bright Smile Digital - Lifetime Patient Value Calculator</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> | |
| <style> | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| color: #222222; | |
| } | |
| .brand-bg { | |
| background-color: #FF3C00; | |
| } | |
| .brand-text { | |
| color: #FF3C00; | |
| } | |
| .accent-bg { | |
| background-color: #F5F5F5; | |
| } | |
| input:focus { | |
| outline: 2px solid #FF3C00; | |
| outline-offset: 2px; | |
| } | |
| .result-box { | |
| border-left: 4px solid #FF3C00; | |
| } | |
| .confetti { | |
| position: absolute; | |
| width: 10px; | |
| height: 10px; | |
| background-color: #FF3C00; | |
| opacity: 0; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .loader { | |
| border-top-color: #FF3C00; | |
| animation: spin 1s linear infinite; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-white min-h-screen"> | |
| <div class="container mx-auto px-4 py-12 max-w-6xl"> | |
| <div class="text-center mb-12"> | |
| <h1 class="text-3xl md:text-4xl font-bold mb-3">Bright Smile Digital</h1> | |
| <h2 class="text-2xl md:text-3xl font-semibold brand-text">Lifetime Patient Value + Retention Strategy Tool</h2> | |
| </div> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-8 mb-12"> | |
| <!-- Calculator Form --> | |
| <div class="bg-white rounded-lg shadow-md p-6"> | |
| <h3 class="text-xl font-semibold mb-6">Enter Your Practice Metrics</h3> | |
| <div class="space-y-5"> | |
| <div> | |
| <label for="visitValue" class="block text-sm font-medium mb-1">Average Visit Value ($)</label> | |
| <input type="number" id="visitValue" class="w-full px-4 py-2 border rounded-md focus:border-brand" placeholder="e.g. 250" min="0"> | |
| </div> | |
| <div> | |
| <label for="visitsPerYear" class="block text-sm font-medium mb-1">Visits per Year</label> | |
| <input type="number" id="visitsPerYear" class="w-full px-4 py-2 border rounded-md" placeholder="e.g. 2" min="0"> | |
| </div> | |
| <div> | |
| <label for="patientLifespan" class="block text-sm font-medium mb-1">Average Patient Lifespan (Years)</label> | |
| <input type="number" id="patientLifespan" class="w-full px-4 py-2 border rounded-md" placeholder="e.g. 10" min="0"> | |
| </div> | |
| <div> | |
| <label for="referralRate" class="block text-sm font-medium mb-1">Referral Rate (%)</label> | |
| <input type="number" id="referralRate" class="w-full px-4 py-2 border rounded-md" placeholder="e.g. 15" min="0" max="100"> | |
| </div> | |
| <button onclick="calculateLTV()" id="calculateBtn" class="w-full brand-bg text-white font-semibold py-3 px-4 rounded-md hover:bg-opacity-90 transition duration-200 flex items-center justify-center"> | |
| <span id="btnText">Calculate Lifetime Value</span> | |
| <div id="loader" class="hidden ml-2 h-5 w-5 border-2 border-white border-solid rounded-full loader"></div> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Results Section --> | |
| <div class="bg-white rounded-lg shadow-md p-6 relative overflow-hidden"> | |
| <div id="confetti-container" class="absolute inset-0 overflow-hidden pointer-events-none"></div> | |
| <h3 class="text-xl font-semibold mb-6">Results & Strategy</h3> | |
| <div id="results" class="space-y-6 hidden"> | |
| <div class="result-box pl-4"> | |
| <p class="text-sm font-medium text-gray-500">Total Lifetime Value</p> | |
| <p id="totalLTV" class="text-2xl font-bold brand-text">$0</p> | |
| </div> | |
| <div class="result-box pl-4"> | |
| <p class="text-sm font-medium text-gray-500">Estimated Value from Referrals</p> | |
| <p id="referralValue" class="text-2xl font-bold brand-text">$0</p> | |
| </div> | |
| <div class="accent-bg rounded-lg p-4"> | |
| <h4 class="font-semibold mb-2">Retention Strategy Tips</h4> | |
| <ul id="strategyTips" class="text-sm space-y-2"> | |
| <!-- Dynamic tips will be inserted here --> | |
| </ul> | |
| </div> | |
| </div> | |
| <div id="placeholder" class="text-center py-12"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mx-auto text-gray-300 mb-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /> | |
| </svg> | |
| <p class="text-gray-500">Enter your practice metrics to see calculations and strategy recommendations.</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Comparison Graph --> | |
| <div id="graphContainer" class="hidden bg-white rounded-lg shadow-md p-6 mb-12"> | |
| <h3 class="text-xl font-semibold mb-6">Value Comparison</h3> | |
| <div class="h-80"> | |
| <canvas id="comparisonChart"></canvas> | |
| </div> | |
| <div class="mt-4 text-sm text-gray-500"> | |
| <p>Adjust your inputs above to see how changes affect your patient lifetime value.</p> | |
| </div> | |
| </div> | |
| <!-- Previous Calculations --> | |
| <div id="historyContainer" class="hidden bg-white rounded-lg shadow-md p-6"> | |
| <h3 class="text-xl font-semibold mb-6">Your Calculations</h3> | |
| <div id="historyList" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> | |
| <!-- Previous calculations will appear here --> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Retention strategy tips pool | |
| const strategyTips = [ | |
| "Implement a patient appreciation program with small gifts for annual visits", | |
| "Create a referral incentive program offering discounts for successful referrals", | |
| "Send personalized birthday/holiday greetings to maintain top-of-mind awareness", | |
| "Develop an educational email newsletter with oral health tips", | |
| "Offer a complimentary teeth whitening session for patients who refer 3+ new patients", | |
| "Create a VIP program for your most loyal patients with exclusive benefits", | |
| "Implement a patient feedback system to continuously improve your service", | |
| "Host free community dental health seminars to attract new patients", | |
| "Offer flexible payment plans to make treatment more accessible", | |
| "Create a 'bring a friend' promotion for preventive care visits", | |
| "Develop a mobile app for easy appointment scheduling and reminders", | |
| "Provide a small gift for children after their first visit to create positive associations", | |
| "Implement a patient anniversary recognition program", | |
| "Offer a free oral health screening for all new patient referrals", | |
| "Create a 'frequent visitor' rewards program for regular patients" | |
| ]; | |
| let calculationHistory = []; | |
| let chart = null; | |
| function getRandomTips(count = 4) { | |
| const shuffled = [...strategyTips].sort(() => 0.5 - Math.random()); | |
| return shuffled.slice(0, count); | |
| } | |
| function createConfetti() { | |
| const container = document.getElementById('confetti-container'); | |
| container.innerHTML = ''; | |
| for (let i = 0; i < 50; i++) { | |
| const confetti = document.createElement('div'); | |
| confetti.className = 'confetti'; | |
| confetti.style.left = Math.random() * 100 + '%'; | |
| confetti.style.top = -10 + 'px'; | |
| confetti.style.backgroundColor = `hsl(${Math.random() * 60 + 10}, 100%, 50%)`; | |
| confetti.style.transform = `rotate(${Math.random() * 360}deg)`; | |
| confetti.style.width = Math.random() * 8 + 4 + 'px'; | |
| confetti.style.height = Math.random() * 8 + 4 + 'px'; | |
| const animationDuration = Math.random() * 3 + 2; | |
| confetti.style.animation = `fall ${animationDuration}s linear forwards`; | |
| container.appendChild(confetti); | |
| // Animation | |
| setTimeout(() => { | |
| confetti.style.opacity = '1'; | |
| confetti.style.top = '100%'; | |
| confetti.style.left = confetti.style.left.split('%')[0] * 1 + (Math.random() * 20 - 10) + '%'; | |
| }, 10); | |
| } | |
| // Add CSS for animation | |
| const style = document.createElement('style'); | |
| style.innerHTML = ` | |
| @keyframes fall { | |
| to { | |
| top: 100%; | |
| opacity: 0; | |
| transform: rotate(360deg); | |
| } | |
| } | |
| `; | |
| document.head.appendChild(style); | |
| // Remove confetti after animation | |
| setTimeout(() => { | |
| container.innerHTML = ''; | |
| document.head.removeChild(style); | |
| }, 3000); | |
| } | |
| function showLoading() { | |
| document.getElementById('btnText').classList.add('hidden'); | |
| document.getElementById('loader').classList.remove('hidden'); | |
| document.getElementById('calculateBtn').disabled = true; | |
| } | |
| function hideLoading() { | |
| document.getElementById('btnText').classList.remove('hidden'); | |
| document.getElementById('loader').classList.add('hidden'); | |
| document.getElementById('calculateBtn').disabled = false; | |
| } | |
| function updateChart(currentData, historyData) { | |
| const ctx = document.getElementById('comparisonChart').getContext('2d'); | |
| if (chart) { | |
| chart.destroy(); | |
| } | |
| // Prepare labels and data | |
| const labels = ['Current Calculation']; | |
| const ltvData = [currentData.totalLTV]; | |
| const referralData = [currentData.referralValue]; | |
| historyData.forEach((calc, index) => { | |
| labels.push(`Calc ${index + 1}`); | |
| ltvData.push(calc.totalLTV); | |
| referralData.push(calc.referralValue); | |
| }); | |
| chart = new Chart(ctx, { | |
| type: 'bar', | |
| data: { | |
| labels: labels, | |
| datasets: [ | |
| { | |
| label: 'Lifetime Value', | |
| data: ltvData, | |
| backgroundColor: '#FF3C00', | |
| borderColor: '#FF3C00', | |
| borderWidth: 1 | |
| }, | |
| { | |
| label: 'Referral Value', | |
| data: referralData, | |
| backgroundColor: '#F5F5F5', | |
| borderColor: '#222222', | |
| borderWidth: 1 | |
| } | |
| ] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| scales: { | |
| y: { | |
| beginAtZero: true, | |
| ticks: { | |
| callback: function(value) { | |
| return '$' + value.toLocaleString(); | |
| } | |
| } | |
| } | |
| }, | |
| plugins: { | |
| tooltip: { | |
| callbacks: { | |
| label: function(context) { | |
| return context.dataset.label + ': $' + context.raw.toLocaleString(); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| function addToHistory(calculation) { | |
| calculationHistory.unshift(calculation); | |
| if (calculationHistory.length > 5) { | |
| calculationHistory.pop(); | |
| } | |
| updateHistoryDisplay(); | |
| } | |
| function updateHistoryDisplay() { | |
| const historyList = document.getElementById('historyList'); | |
| historyList.innerHTML = ''; | |
| calculationHistory.forEach((calc, index) => { | |
| const historyItem = document.createElement('div'); | |
| historyItem.className = 'accent-bg p-4 rounded-lg'; | |
| historyItem.innerHTML = ` | |
| <h4 class="font-semibold mb-2">Calculation ${index + 1}</h4> | |
| <p class="text-sm"><span class="font-medium">Visit Value:</span> $${calc.visitValue}</p> | |
| <p class="text-sm"><span class="font-medium">Visits/Year:</span> ${calc.visitsPerYear}</p> | |
| <p class="text-sm"><span class="font-medium">Lifespan:</span> ${calc.patientLifespan} yrs</p> | |
| <p class="text-sm"><span class="font-medium">Referral Rate:</span> ${calc.referralRate}%</p> | |
| <div class="mt-2 pt-2 border-t border-gray-200"> | |
| <p class="text-sm"><span class="font-medium">LTV:</span> <span class="brand-text">$${calc.totalLTV.toLocaleString()}</span></p> | |
| <p class="text-sm"><span class="font-medium">Referrals:</span> <span class="brand-text">$${calc.referralValue.toLocaleString()}</span></p> | |
| </div> | |
| `; | |
| historyList.appendChild(historyItem); | |
| }); | |
| if (calculationHistory.length > 0) { | |
| document.getElementById('historyContainer').classList.remove('hidden'); | |
| } | |
| } | |
| function calculateLTV() { | |
| showLoading(); | |
| // Get input values | |
| const visitValue = parseFloat(document.getElementById('visitValue').value) || 0; | |
| const visitsPerYear = parseFloat(document.getElementById('visitsPerYear').value) || 0; | |
| const patientLifespan = parseFloat(document.getElementById('patientLifespan').value) || 0; | |
| const referralRate = parseFloat(document.getElementById('referralRate').value) || 0; | |
| // Simulate loading delay | |
| setTimeout(() => { | |
| // Calculate values | |
| const annualValue = visitValue * visitsPerYear; | |
| const totalLTV = annualValue * patientLifespan; | |
| const referralValue = totalLTV * (referralRate / 100); | |
| // Format as currency | |
| const formatter = new Intl.NumberFormat('en-US', { | |
| style: 'currency', | |
| currency: 'USD', | |
| minimumFractionDigits: 0, | |
| maximumFractionDigits: 0 | |
| }); | |
| // Display results | |
| document.getElementById('totalLTV').textContent = formatter.format(totalLTV); | |
| document.getElementById('referralValue').textContent = formatter.format(referralValue); | |
| // Show random strategy tips | |
| const tipsContainer = document.getElementById('strategyTips'); | |
| tipsContainer.innerHTML = ''; | |
| getRandomTips().forEach(tip => { | |
| const li = document.createElement('li'); | |
| li.className = 'flex items-start'; | |
| li.innerHTML = ` | |
| <span class="brand-text mr-2">•</span> | |
| <span>${tip}</span> | |
| `; | |
| tipsContainer.appendChild(li); | |
| }); | |
| // Show results section | |
| document.getElementById('results').classList.remove('hidden'); | |
| document.getElementById('placeholder').classList.add('hidden'); | |
| // Show celebration | |
| createConfetti(); | |
| // Add to history and update chart | |
| const currentCalculation = { | |
| visitValue, | |
| visitsPerYear, | |
| patientLifespan, | |
| referralRate, | |
| totalLTV, | |
| referralValue | |
| }; | |
| addToHistory(currentCalculation); | |
| updateChart(currentCalculation, calculationHistory); | |
| // Show graph container | |
| document.getElementById('graphContainer').classList.remove('hidden'); | |
| hideLoading(); | |
| }, 1500); | |
| } | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=JayStormX8/ltv-tool" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |