add Podscan-inspired teaser calculator to landing page
Interactive ROI estimate with 5 sliders (courts, peak rate, utilization, rent/sqm, exit multiple) and 4 output metrics (investment, monthly cash flow, annual revenue, equity IRR). Simplified model with Newton-Raphson IRR solver. Soft-gates full planner behind signup CTA. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,26 +9,187 @@
|
|||||||
<meta property="og:type" content="website">
|
<meta property="og:type" content="website">
|
||||||
<meta property="og:url" content="{{ config.BASE_URL }}">
|
<meta property="og:url" content="{{ config.BASE_URL }}">
|
||||||
<link rel="canonical" href="{{ config.BASE_URL }}">
|
<link rel="canonical" href="{{ config.BASE_URL }}">
|
||||||
|
<style>
|
||||||
|
.teaser-calc {
|
||||||
|
background: #1E293B;
|
||||||
|
border: 1px solid rgba(255,255,255,0.08);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 2rem;
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.teaser-calc .slider-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
}
|
||||||
|
.teaser-calc .slider-row label {
|
||||||
|
width: 140px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #94A3B8;
|
||||||
|
}
|
||||||
|
.teaser-calc .slider-row input[type=range] {
|
||||||
|
flex: 1;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
height: 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: rgba(255,255,255,0.08);
|
||||||
|
outline: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.teaser-calc .slider-row input[type=range]::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
width: 16px; height: 16px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #3B82F6;
|
||||||
|
border: 2px solid #0F172A;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.teaser-calc .slider-row input[type=range]::-moz-range-thumb {
|
||||||
|
width: 16px; height: 16px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #3B82F6;
|
||||||
|
border: 2px solid #0F172A;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.teaser-calc .slider-row .val {
|
||||||
|
width: 70px;
|
||||||
|
text-align: right;
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #F8FAFC;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.teaser-results {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
padding-top: 1.5rem;
|
||||||
|
border-top: 1px solid rgba(255,255,255,0.08);
|
||||||
|
}
|
||||||
|
.teaser-metric {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.teaser-metric .tm-label {
|
||||||
|
font-size: 10px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
color: #64748B;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.teaser-metric .tm-value {
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
.teaser-metric .tm-sub {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #64748B;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
.tm-green { color: #10B981; }
|
||||||
|
.tm-blue { color: #3B82F6; }
|
||||||
|
.tm-white { color: #F8FAFC; }
|
||||||
|
.tm-red { color: #EF4444; }
|
||||||
|
.teaser-cta {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
}
|
||||||
|
.teaser-cta p {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #64748B;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.teaser-results { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
.teaser-calc .slider-row { flex-wrap: wrap; }
|
||||||
|
.teaser-calc .slider-row label { width: 100%; margin-bottom: -0.5rem; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main class="container">
|
<main class="container">
|
||||||
<!-- Hero -->
|
<!-- Hero -->
|
||||||
<header style="text-align: center; padding: 4rem 0 3rem;">
|
<header style="text-align: center; padding: 4rem 0 2rem;">
|
||||||
<h1 style="font-size: 2.5rem; line-height: 1.15;">Plan Your Padel Business<br>in Minutes, Not Months</h1>
|
<h1 style="font-size: 2.5rem; line-height: 1.15;">Plan Your Padel Business<br>in Minutes, Not Months</h1>
|
||||||
<p style="font-size: 1.2rem; max-width: 640px; margin: 1rem auto 0;">
|
<p style="font-size: 1.2rem; max-width: 640px; margin: 1rem auto 0; color: #94A3B8;">
|
||||||
The most sophisticated padel court financial planner available. Model your investment with 60+ variables, sensitivity analysis, and professional-grade projections. 100% free.
|
Model your padel court investment with 60+ variables, sensitivity analysis, and professional-grade projections. 100% free.
|
||||||
</p>
|
</p>
|
||||||
<div style="margin-top: 2rem;">
|
|
||||||
{% if user %}
|
|
||||||
<a href="{{ url_for('planner.index') }}" role="button" style="margin-right: 1rem;">Open Planner</a>
|
|
||||||
{% else %}
|
|
||||||
<a href="{{ url_for('auth.signup') }}" role="button" style="margin-right: 1rem;">Create Free Account</a>
|
|
||||||
{% endif %}
|
|
||||||
<a href="{{ url_for('billing.pricing') }}" role="button" class="secondary outline">Learn More</a>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<!-- Teaser Calculator -->
|
||||||
|
<section style="padding: 2rem 0 3rem;">
|
||||||
|
<div class="teaser-calc">
|
||||||
|
<h2 style="text-align: center; margin-bottom: 1.5rem; font-size: 1.25rem;">Quick ROI Estimate</h2>
|
||||||
|
|
||||||
|
<div class="slider-row">
|
||||||
|
<label>Courts</label>
|
||||||
|
<input type="range" id="tc-courts" min="2" max="12" step="1" value="6" oninput="tCalc()">
|
||||||
|
<span class="val" id="tv-courts">6</span>
|
||||||
|
</div>
|
||||||
|
<div class="slider-row">
|
||||||
|
<label>Peak Rate</label>
|
||||||
|
<input type="range" id="tc-rate" min="20" max="100" step="5" value="50" oninput="tCalc()">
|
||||||
|
<span class="val" id="tv-rate">€50/hr</span>
|
||||||
|
</div>
|
||||||
|
<div class="slider-row">
|
||||||
|
<label>Utilization</label>
|
||||||
|
<input type="range" id="tc-util" min="15" max="75" step="5" value="40" oninput="tCalc()">
|
||||||
|
<span class="val" id="tv-util">40%</span>
|
||||||
|
</div>
|
||||||
|
<div class="slider-row">
|
||||||
|
<label>Rent / m²</label>
|
||||||
|
<input type="range" id="tc-rent" min="2" max="15" step="1" value="4" oninput="tCalc()">
|
||||||
|
<span class="val" id="tv-rent">€4/m²</span>
|
||||||
|
</div>
|
||||||
|
<div class="slider-row">
|
||||||
|
<label>Exit Multiple</label>
|
||||||
|
<input type="range" id="tc-exit" min="3" max="10" step="0.5" value="6" oninput="tCalc()">
|
||||||
|
<span class="val" id="tv-exit">6.0x</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="teaser-results">
|
||||||
|
<div class="teaser-metric">
|
||||||
|
<div class="tm-label">Total Investment</div>
|
||||||
|
<div class="tm-value tm-white" id="tr-invest">—</div>
|
||||||
|
<div class="tm-sub">CAPEX (rent model)</div>
|
||||||
|
</div>
|
||||||
|
<div class="teaser-metric">
|
||||||
|
<div class="tm-label">Monthly Cash Flow</div>
|
||||||
|
<div class="tm-value" id="tr-cf">—</div>
|
||||||
|
<div class="tm-sub">After debt service</div>
|
||||||
|
</div>
|
||||||
|
<div class="teaser-metric">
|
||||||
|
<div class="tm-label">Annual Revenue</div>
|
||||||
|
<div class="tm-value tm-blue" id="tr-rev">—</div>
|
||||||
|
<div class="tm-sub">Net of booking fees</div>
|
||||||
|
</div>
|
||||||
|
<div class="teaser-metric">
|
||||||
|
<div class="tm-label">Equity IRR</div>
|
||||||
|
<div class="tm-value" id="tr-irr">—</div>
|
||||||
|
<div class="tm-sub" id="tr-irr-sub">5-year hold</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="teaser-cta">
|
||||||
|
<p>This is a simplified estimate. The full planner models 60+ variables with monthly cash flows, sensitivity analysis, and more.</p>
|
||||||
|
{% if user %}
|
||||||
|
<a href="{{ url_for('planner.index') }}" role="button">Open Full Planner</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ url_for('auth.signup') }}" role="button">Unlock the Full Planner — Free</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<!-- The Journey -->
|
<!-- The Journey -->
|
||||||
<section style="padding: 3rem 0;">
|
<section style="padding: 3rem 0;">
|
||||||
<h2 style="text-align: center;">From Idea to Operating Hall</h2>
|
<h2 style="text-align: center;">From Idea to Operating Hall</h2>
|
||||||
@@ -108,3 +269,121 @@
|
|||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
function tCalc() {
|
||||||
|
var courts = +document.getElementById('tc-courts').value;
|
||||||
|
var rate = +document.getElementById('tc-rate').value;
|
||||||
|
var util = +document.getElementById('tc-util').value;
|
||||||
|
var rentSqm = +document.getElementById('tc-rent').value;
|
||||||
|
var exitMult = +document.getElementById('tc-exit').value;
|
||||||
|
|
||||||
|
// Display slider values
|
||||||
|
document.getElementById('tv-courts').textContent = courts;
|
||||||
|
document.getElementById('tv-rate').innerHTML = '€' + rate + '/hr';
|
||||||
|
document.getElementById('tv-util').textContent = util + '%';
|
||||||
|
document.getElementById('tv-rent').innerHTML = '€' + rentSqm + '/m\u00B2';
|
||||||
|
document.getElementById('tv-exit').textContent = exitMult.toFixed(1) + 'x';
|
||||||
|
|
||||||
|
// Simplified model (rent model, indoor, all double courts)
|
||||||
|
var sqmPerCourt = 300; // court + shared space
|
||||||
|
var totalSqm = courts * sqmPerCourt;
|
||||||
|
var courtCost = courts * 25000;
|
||||||
|
var capex = courtCost + 60000 + 80000 + 40000 + 100000 + 50000; // elec + sanitary + fitout + planning + parking
|
||||||
|
|
||||||
|
// Revenue
|
||||||
|
var hoursDay = 16, daysMonth = 29;
|
||||||
|
var availHours = courts * hoursDay * daysMonth;
|
||||||
|
var bookedHours = availHours * (util / 100);
|
||||||
|
var offPeakRate = rate * 0.65;
|
||||||
|
var wRate = rate * 0.4 + offPeakRate * 0.6;
|
||||||
|
var courtRev = bookedHours * wRate;
|
||||||
|
var netRev = courtRev * 0.9; // 10% booking fee
|
||||||
|
|
||||||
|
// Operating costs
|
||||||
|
var rent = totalSqm * rentSqm;
|
||||||
|
var opex = rent + courts * 400 + 350; // utilities/maint per court + marketing
|
||||||
|
|
||||||
|
// EBITDA
|
||||||
|
var ebitda = netRev - opex;
|
||||||
|
|
||||||
|
// Financing (85% LTV, 5%, 10yr)
|
||||||
|
var loanPct = 0.85, iRate = 0.05, term = 10;
|
||||||
|
var debt = capex * loanPct;
|
||||||
|
var equity = capex - debt;
|
||||||
|
var mRate = iRate / 12;
|
||||||
|
var nPay = term * 12;
|
||||||
|
var pmt = debt * mRate / (1 - Math.pow(1 + mRate, -nPay));
|
||||||
|
var netCF = ebitda - pmt;
|
||||||
|
|
||||||
|
// Annual figures
|
||||||
|
var annualRev = netRev * 12;
|
||||||
|
var annualCF = netCF * 12;
|
||||||
|
|
||||||
|
// IRR approximation (Newton's method, 5yr hold)
|
||||||
|
var hold = 5;
|
||||||
|
var y3ebitda = ebitda * 12;
|
||||||
|
var exitVal = y3ebitda * exitMult;
|
||||||
|
var cfs = [-equity];
|
||||||
|
for (var y = 1; y <= hold; y++) {
|
||||||
|
var cf = annualCF;
|
||||||
|
if (y === hold) cf += exitVal - debt * Math.pow((1 + mRate), 12 * hold - 12 * hold); // simplified: remaining debt ~ original for short holds
|
||||||
|
// Simplified: remaining debt after Y years
|
||||||
|
var remDebt = 0;
|
||||||
|
if (y === hold) {
|
||||||
|
// Outstanding balance after hold*12 payments
|
||||||
|
var paid = hold * 12;
|
||||||
|
remDebt = debt * Math.pow(1 + mRate, paid) - pmt * (Math.pow(1 + mRate, paid) - 1) / mRate;
|
||||||
|
if (remDebt < 0) remDebt = 0;
|
||||||
|
cf = annualCF + exitVal - remDebt;
|
||||||
|
}
|
||||||
|
cfs.push(cf);
|
||||||
|
}
|
||||||
|
|
||||||
|
var irr = calcIRR(cfs);
|
||||||
|
|
||||||
|
// Format outputs
|
||||||
|
var fmt = function(n) { return (n >= 0 ? '' : '-') + '\u20AC' + Math.abs(Math.round(n)).toLocaleString('de-DE'); };
|
||||||
|
|
||||||
|
document.getElementById('tr-invest').innerHTML = fmt(capex);
|
||||||
|
|
||||||
|
var cfEl = document.getElementById('tr-cf');
|
||||||
|
cfEl.innerHTML = fmt(netCF);
|
||||||
|
cfEl.className = 'tm-value ' + (netCF >= 0 ? 'tm-green' : 'tm-red');
|
||||||
|
|
||||||
|
document.getElementById('tr-rev').innerHTML = fmt(annualRev);
|
||||||
|
|
||||||
|
var irrEl = document.getElementById('tr-irr');
|
||||||
|
if (irr !== null && isFinite(irr)) {
|
||||||
|
irrEl.textContent = (irr * 100).toFixed(1) + '%';
|
||||||
|
irrEl.className = 'tm-value ' + (irr >= 0 ? 'tm-green' : 'tm-red');
|
||||||
|
} else {
|
||||||
|
irrEl.innerHTML = '—';
|
||||||
|
irrEl.className = 'tm-value tm-white';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcIRR(cfs) {
|
||||||
|
// Newton-Raphson IRR solver
|
||||||
|
var guess = 0.15;
|
||||||
|
for (var i = 0; i < 100; i++) {
|
||||||
|
var npv = 0, dnpv = 0;
|
||||||
|
for (var t = 0; t < cfs.length; t++) {
|
||||||
|
var f = Math.pow(1 + guess, t);
|
||||||
|
npv += cfs[t] / f;
|
||||||
|
if (t > 0) dnpv -= t * cfs[t] / (f * (1 + guess));
|
||||||
|
}
|
||||||
|
if (Math.abs(dnpv) < 1e-10) break;
|
||||||
|
var next = guess - npv / dnpv;
|
||||||
|
if (Math.abs(next - guess) < 1e-7) return next;
|
||||||
|
guess = next;
|
||||||
|
if (guess < -0.99 || guess > 10) return null;
|
||||||
|
}
|
||||||
|
return guess;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run on load
|
||||||
|
tCalc();
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user