ltv-tool / index.html
JayStormX8's picture
Add 2 files
da1d6a2 verified
<!DOCTYPE html>
<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>