var tierCount = 0, launchCount = 0, sponsorCount = 0, coachCount = 0;
var CUR = { symbol: '$', code: 'USD' };
var MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December'];
// Currency config β symbol and display label
var CURRENCIES = {
USD: { symbol: '$', label: '$ USD' },
CAD: { symbol: '$', label: '$ CAD' },
GBP: { symbol: 'Β£', label: 'Β£ GBP' },
AUD: { symbol: '$', label: '$ AUD' },
EUR: { symbol: 'β¬', label: 'β¬ EUR' }
};
function setCurrency(code) {
CUR = { symbol: CURRENCIES[code].symbol, code: code };
['USD','CAD','GBP','AUD','EUR'].forEach(function(c) {
var btn = document.getElementById('cur'+c);
if (btn) btn.classList.toggle('on', c === code);
});
document.getElementById('goalPrefix').textContent = CUR.symbol;
document.querySelectorAll('.cur-prefix').forEach(function(el) { el.textContent = CUR.symbol; });
calc();
}
function fmt(n) { return CUR.symbol + Math.round(n).toLocaleString(); }
function tstr(m) {
var y = Math.floor(m/12), mo = m%12;
if (y > 0 && mo > 0) return y+'y '+mo+'mo';
if (y > 0) return y+' year'+(y>1?'s':'');
return m+' month'+(m!==1?'s':'');
}
function initDates() {
var now = new Date();
var mSel = document.getElementById('sMon');
var ySel = document.getElementById('sYear');
mSel.innerHTML = ''; ySel.innerHTML = '';
MONTHS.forEach(function(m, i) {
var o = document.createElement('option');
o.value = i; o.textContent = m;
if (i === now.getMonth()) o.selected = true;
mSel.appendChild(o);
});
for (var y = now.getFullYear(); y <= now.getFullYear() + 5; y++) {
var o = document.createElement('option');
o.value = y; o.textContent = y;
ySel.appendChild(o);
}
}
function getStart() {
return { m: parseInt(document.getElementById('sMon').value), y: parseInt(document.getElementById('sYear').value) };
}
function monthLabel(offset) {
var s = getStart();
var d = new Date(s.y, s.m + offset, 1);
return MONTHS[d.getMonth()] + ' ' + d.getFullYear();
}
function monthOpts(selM, selY) {
var now = new Date(); var mo = '', yo = '';
MONTHS.forEach(function(m, i) { mo += ''; });
for (var y = now.getFullYear(); y <= now.getFullYear()+5; y++) { yo += ''; }
return { mo:mo, yo:yo };
}
function launchOffset(id) {
var s = getStart();
var lm = parseInt(document.getElementById('l'+id+'-m').value);
var ly = parseInt(document.getElementById('l'+id+'-y').value);
return (ly - s.y) * 12 + (lm - s.m) + 1;
}
function launchDateLabel(id) {
var lm = parseInt(document.getElementById('l'+id+'-m').value);
var ly = parseInt(document.getElementById('l'+id+'-y').value);
return MONTHS[lm] + ' ' + ly;
}
function addTier(d) {
d = d || { name:'', price:'', freq:'monthly', newM:'', startM:0, churn:false, churnR:5 };
tierCount++;
var id = tierCount;
var labels = ['First Tier','Second Tier','Third Tier','Fourth Tier'];
var div = document.createElement('div');
div.className = 'tier-block'; div.id = 'tier-'+id; div.dataset.freq = d.freq;
div.innerHTML =
'=r.mo) totMRR+=r.mrrContrib; });
var mBoosts=allBoosts.filter(function(b){return b.mo===m;});
var boostTot=mBoosts.reduce(function(s,b){return s+b.amt;},0);
var combined=totMRR+boostTot;
rows.push({m:m, lbl:monthLabel(m-1), members:Math.round(totM), mrr:totMRR, boostTot:boostTot, combined:combined,
mW:mBoosts.filter(function(b){return b.btype==='workshop';}),
mS:mBoosts.filter(function(b){return b.btype==='sponsor';}),
mC:mBoosts.filter(function(b){return b.btype==='coach';})
});
if (rGoal===null && totMRR>=goal) rGoal=m;
if (cGoal===null && combined>=goal) cGoal=m;
}
var milestones=[0.25,0.5,0.75].map(function(pct){var row=rows.find(function(r){return r.mrr>=goal*pct;});return{pct:Math.round(pct*100),lbl:row?row.lbl:null,mrr:row?row.mrr:0};});
var html='';
if (rGoal) {
html+='';
if (cGoal&&cGoal ';
} else {
html+='';
}
html+='
';
} else {
var fin=rows[rows.length-1].mrr;
if (cGoal) {
html+=''+(labels[id-1]||'Tier '+id)+'
'+
''+
''+
''+
'
'+
''+CUR.symbol+'
'+
''+
''+
''+
''+
'
new members/mo
'+
''+
''+
'
';
document.getElementById('tiersList').appendChild(div);
if (tierCount >= 4) document.getElementById('addTierBtn').style.display = 'none';
calc();
}
function rmTier(id) { document.getElementById('tier-'+id).remove(); document.getElementById('addTierBtn').style.display='flex'; calc(); }
function setFreq(id, freq, btn) {
document.getElementById('tier-'+id).dataset.freq = freq;
document.getElementById('tier-'+id).querySelectorAll('.freq-btn').forEach(function(b){b.classList.remove('on');});
btn.classList.add('on'); calc();
}
function togChurn(id) { document.getElementById('t'+id+'-tog').classList.toggle('on'); document.getElementById('t'+id+'-churnwrap').classList.toggle('show'); calc(); }
function getTiers() {
var tiers = [];
document.querySelectorAll('.tier-block').forEach(function(block) {
var id = block.id.replace('tier-','');
var name = (document.getElementById('t'+id+'-name').value||'Tier').trim()||'Tier';
var rawP = parseFloat(document.getElementById('t'+id+'-price').value)||0;
var freq = block.dataset.freq||'monthly';
var mp = freq==='quarterly' ? rawP/3 : freq==='annual' ? rawP/12 : rawP;
var newM = parseFloat(document.getElementById('t'+id+'-new').value)||0;
var startM = parseFloat(document.getElementById('t'+id+'-start').value)||0;
var churnOn = document.getElementById('t'+id+'-tog').classList.contains('on');
var churnR = churnOn ? (parseFloat(document.getElementById('t'+id+'-churnR').value)||0)/100 : 0;
if (mp > 0 || newM > 0) tiers.push({name:name, mp:mp, newM:newM, startM:startM, churnR:churnR, rawP:rawP, freq:freq});
});
return tiers;
}
function addLaunch(d) {
var now = new Date();
d = d || { name:'', price:'', parts:'', amt:'', lm:now.getMonth(), ly:now.getFullYear() };
launchCount++;
var id = launchCount;
var opts = monthOpts(d.lm, d.ly);
// Determine mode: if amt is set but price/parts are not, it's direct mode
var isDirect = (d.amt && !d.price && !d.parts) ? true : false;
var isCalc = (d.price || d.parts) ? true : false;
// If both price and amt exist (priceΓparts was calculated), show calculated state
var calcedAmt = (d.price && d.parts) ? Math.round(parseFloat(d.price)*parseFloat(d.parts)) : '';
var amtDisplay = isCalc && calcedAmt ? calcedAmt : (isDirect ? d.amt : '');
var amtReadonly = isCalc && calcedAmt ? 'readonly' : '';
var amtBg = isCalc && calcedAmt ? 'style="background:var(--warm);color:var(--mid);"' : '';
var totMsg = isCalc && calcedAmt ? '= '+CUR.symbol+parseInt(calcedAmt).toLocaleString()+' total' : '';
var div = document.createElement('div');
div.className = 'launch-block'; div.id = 'launch-'+id;
if(isDirect) div.dataset.totmode = 'direct';
div.innerHTML =
'Include churn
'+
''+
'Rate:'+
'
'+
'%
'+
'Workshop / One-Time Offer
'+
''+
''+
''+
''+
''+
'
'+
''+CUR.symbol+'
'+CUR.symbol+'
'+totMsg+'
'+
''+
''+
'
';
document.getElementById('launchList').appendChild(div);
calc();
}
function rmLaunch(id) { document.getElementById('launch-'+id).remove(); calc(); }
function ltotMode(id,m){ document.getElementById('launch-'+id).dataset.totmode=m; }
function ltot(id) {
var block=document.getElementById('launch-'+id);
if(block.dataset.totmode==='direct') return;
var pEl=document.getElementById('l'+id+'-price'),nEl=document.getElementById('l'+id+'-parts'),aEl=document.getElementById('l'+id+'-amt'),disp=document.getElementById('l'+id+'-tot');
var p=parseFloat(pEl.value)||0,n=parseFloat(nEl.value)||0;
if(p>0&&n>0){var tot=Math.round(p*n);aEl.value=tot;aEl.style.background='var(--warm)';aEl.style.color='var(--mid)';aEl.readOnly=true;disp.textContent='= '+CUR.symbol+tot.toLocaleString()+' total';}
else{aEl.value='';aEl.readOnly=false;aEl.style.background='';aEl.style.color='';disp.textContent='';}
calc();
}
function ltotFocus(id) {
// User clicked into Total Revenue β switch to direct mode but DON'T clear price/parts
var block=document.getElementById('launch-'+id);
var pVal=document.getElementById('l'+id+'-price').value;
var nVal=document.getElementById('l'+id+'-parts').value;
// Only go direct if there's no priceΓparts to auto-calculate
if(!(parseFloat(pVal)>0 && parseFloat(nVal)>0)){
ltotMode(id,'direct');
var aEl=document.getElementById('l'+id+'-amt');
aEl.readOnly=false;aEl.style.background='';aEl.style.color='';
document.getElementById('l'+id+'-tot').textContent='';
}
}
function ltotDirect(id) {
var aEl=document.getElementById('l'+id+'-amt');
if(aEl.value){
ltotMode(id,'direct');
aEl.readOnly=false;aEl.style.background='';aEl.style.color='';
document.getElementById('l'+id+'-tot').textContent='';
} else {
ltotMode(id,'calc');
document.getElementById('l'+id+'-tot').textContent='';
ltot(id); return;
}
calc();
}
function ltotPriceFocus(id) {
// User clicked into price or parts β switch back to calc mode, unlock total
ltotMode(id,'calc');
var aEl=document.getElementById('l'+id+'-amt');
aEl.readOnly=false;aEl.style.background='';aEl.style.color='';
// Don't clear anything β just recalculate when they finish typing
}
function getLaunches() {
var launches = [];
document.querySelectorAll('.launch-block').forEach(function(row) {
var id = row.id.replace('launch-','');
var name = (document.getElementById('l'+id+'-name').value||'Workshop').trim()||'Workshop';
var p = parseFloat(document.getElementById('l'+id+'-price').value)||0;
var n = parseFloat(document.getElementById('l'+id+'-parts').value)||0;
var direct = parseFloat(document.getElementById('l'+id+'-amt').value)||0;
var amt = (p>0&&n>0) ? p*n : direct;
var mo = launchOffset(id);
var lbl = launchDateLabel(id);
if (amt>0 && mo>0) launches.push({name:name, amt:amt, mo:mo, lbl:lbl, parts:n||0, price:p||0, btype:'workshop'});
});
return launches;
}
// ββ Sponsorships ββββββββββββββββββββββββββββββββββββββββββ
function addSponsor(d) {
var now = new Date();
d = d || { name:'', amt:'', lm:now.getMonth(), ly:now.getFullYear() };
sponsorCount++;
var id = sponsorCount;
var opts = monthOpts(d.lm !== undefined ? parseInt(d.lm) : now.getMonth(), d.ly ? parseInt(d.ly) : now.getFullYear());
var div = document.createElement('div');
div.className = 'sponsor-block'; div.id = 'sponsor-'+id;
div.innerHTML =
'Sponsorship / Underwriter
'+
''+
''+
''+
''+
''+
'
';
document.getElementById('sponsorsList').appendChild(div);
calc();
}
function rmSponsor(id) { var el=document.getElementById('sponsor-'+id); if(el) el.remove(); calc(); }
function getSponsors() {
var sponsors = [];
document.querySelectorAll('.sponsor-block').forEach(function(row) {
var id = row.id.replace('sponsor-','');
var name = (document.getElementById('sp'+id+'-name').value||'').trim()||'Sponsor';
var amt = parseFloat(document.getElementById('sp'+id+'-amt').value)||0;
var lm = parseInt(document.getElementById('sp'+id+'-m').value);
var ly = parseInt(document.getElementById('sp'+id+'-y').value);
var s = getStart();
var mo = (ly - s.y)*12 + (lm - s.m) + 1;
var lbl = MONTHS[lm]+' '+ly;
if (amt>0) sponsors.push({name:name, amt:amt, mo:mo, lbl:lbl, btype:'sponsor'});
});
return sponsors;
}
// ββ 1:1 Coaching βββββββββββββββββββββββββββββββββββββββββ
function addCoaching(d) {
var now = new Date();
d = d || { name:'', ctype:'session', freq:'once', price:'', qty:'', lm:now.getMonth(), ly:now.getFullYear() };
coachCount++;
var id = coachCount;
var ctype = d.ctype || 'session';
var freq = ctype==='retainer' ? 'monthly' : (d.freq||'once');
var opts = monthOpts(d.lm !== undefined ? parseInt(d.lm) : now.getMonth(), d.ly ? parseInt(d.ly) : now.getFullYear());
var div = document.createElement('div');
div.className = 'coach-block'; div.id = 'coach-'+id;
div.dataset.ctype = ctype;
div.dataset.freq = freq;
d.freq = freq;
div.innerHTML = buildCoachInner(id, ctype, d, opts);
document.getElementById('coachingList').appendChild(div);
calc();
}
function buildCoachInner(id, ctype, d, opts) {
d = d||{};
var pLbl = ctype==='retainer' ? 'Monthly Rate' : ctype==='package' ? 'Package Price' : 'Price / Session';
var pPh = ctype==='retainer' ? '500' : ctype==='package' ? '450' : '150';
var qLbl = ctype==='retainer' ? 'Clients' : ctype==='package' ? 'Packages / Period' : 'Sessions / Period';
var qPh = ctype==='retainer' ? '2' : ctype==='package' ? '3' : '2';
var nPh = ctype==='retainer' ? 'e.g. Monthly Strategy Partner' : ctype==='package' ? 'e.g. 3-Session Kickstart' : 'e.g. Discovery Session';
var dateLbl = ctype==='retainer' ? 'Retainer Starts' : 'Starting';
var freq = d.freq || 'once';
var freqSection = '';
if (ctype !== 'retainer') {
freqSection =
''+CUR.symbol+'
lump sum received'+
''+
'
';
}
var retainerNote = ctype==='retainer' ? ''+
''+
''+
''+
'
'+
(freq!=='once'?'β» Repeats from start date β treated as recurring revenue
':'') +
'β» Added to monthly recurring baseline from start date
' : '';
var pill = ctype==='retainer' ? 'Retainer' : freq!=='once' ? ''+freq.charAt(0).toUpperCase()+freq.slice(1)+'' : '';
return '1:1 Coaching'+pill+'
'+
''+
''+
''+
''+
'
'+
''+
''+
''+
'
'+
freqSection+
''+CUR.symbol+'
'+retainerNote+'
'+
''+
''+
''+
'
';
}
function setCoachFreq(id, freq, btn) {
var block = document.getElementById('coach-'+id);
block.dataset.freq = freq;
// Rebuild inner preserving all current values
var now = new Date();
var d = {
name: (document.getElementById('co'+id+'-name')||{}).value||'',
price: (document.getElementById('co'+id+'-price')||{}).value||'',
qty: (document.getElementById('co'+id+'-qty')||{}).value||'',
lm: parseInt((document.getElementById('co'+id+'-m')||{}).value||now.getMonth()),
ly: parseInt((document.getElementById('co'+id+'-y')||{}).value||now.getFullYear()),
ctype: block.dataset.ctype,
freq: freq
};
var opts = monthOpts(d.lm, d.ly);
block.innerHTML = buildCoachInner(id, d.ctype, d, opts);
calc();
}
function setCoachType(id, ctype, btn) {
var block = document.getElementById('coach-'+id);
var now = new Date();
var d = {
name: (document.getElementById('co'+id+'-name')||{}).value||'',
price: (document.getElementById('co'+id+'-price')||{}).value||'',
qty: (document.getElementById('co'+id+'-qty')||{}).value||'',
lm: parseInt((document.getElementById('co'+id+'-m')||{}).value||now.getMonth()),
ly: parseInt((document.getElementById('co'+id+'-y')||{}).value||now.getFullYear()),
ctype: ctype,
freq: ctype==='retainer' ? 'monthly' : (block.dataset.freq||'once')
};
var opts = monthOpts(d.lm, d.ly);
block.dataset.ctype = ctype;
block.dataset.freq = d.freq;
block.innerHTML = buildCoachInner(id, ctype, d, opts);
calc();
}
function rmCoaching(id) { var el=document.getElementById('coach-'+id); if(el) el.remove(); calc(); }
function getCoaching() {
var coaching = [];
document.querySelectorAll('.coach-block').forEach(function(block) {
var id = block.id.replace('coach-','');
var ctype = block.dataset.ctype||'session';
var freq = block.dataset.freq || (ctype==='retainer'?'monthly':'once');
var name = (document.getElementById('co'+id+'-name').value||'').trim()||'Coaching';
var price = parseFloat(document.getElementById('co'+id+'-price').value)||0;
var qty = parseFloat(document.getElementById('co'+id+'-qty').value)||1;
var lm = parseInt(document.getElementById('co'+id+'-m').value);
var ly = parseInt(document.getElementById('co'+id+'-y').value);
var s = getStart();
var mo = (ly - s.y)*12 + (lm - s.m) + 1;
var lbl = MONTHS[lm]+' '+ly;
// For retainer or recurring sessions/packages: monthly MRR contribution
// For quarterly: price*qty per quarter = price*qty/3 per month
var amtPerOccurrence = price * qty;
var mrrContrib = 0;
if (ctype==='retainer' || freq==='monthly') {
mrrContrib = amtPerOccurrence;
} else if (freq==='quarterly') {
mrrContrib = amtPerOccurrence / 3;
}
if (price>0) coaching.push({
name:name, price:price, qty:qty, amt:amtPerOccurrence,
mo:mo, lbl:lbl, ctype:ctype, freq:freq,
mrrContrib:mrrContrib, btype:'coach'
});
});
return coaching;
}
function escAttr(v) { if(v===undefined||v===null) return ''; return String(v).replace(/"/g,'"').replace(/'/g,'''); }
function updateStickyBar(goal, rows, rGoal, cGoal) {
var set = function(id, val, highlight) {
var el = document.getElementById(id);
if (!el) return;
el.textContent = val;
el.className = 'sticky-val' + (highlight ? ' has-goal' : (val === 'β' || val === 'not set') ? ' empty' : '');
};
var goalVal = parseFloat(document.getElementById('goal').value)||0;
set('st-goal', goalVal > 0 ? fmt(goalVal)+'/mo' : 'not set', false);
if (!rows || !rows.length) {
set('st-mrr','β',false);
set('st-when','β',false);
set('st-cwhen','β',false);
set('st-boost','β',false);
return;
}
// Starting MRR β month 1 baseline
set('st-mrr', fmt(rows[0].mrr)+'/mo', false);
// Recurring alone goal date
set('st-when', rGoal ? rows[rGoal-1].lbl : 'not yet', rGoal != null);
// With everything goal date
var combinedGoal = cGoal || rGoal;
set('st-cwhen', combinedGoal ? rows[combinedGoal-1].lbl : 'not yet', combinedGoal != null);
// Months sooner β only meaningful if boosts actually move the needle
if (rGoal && cGoal && cGoal < rGoal) {
var diff = rGoal - cGoal;
set('st-boost', diff + ' month' + (diff !== 1 ? 's' : ''), true);
} else if (rGoal && (!cGoal || cGoal >= rGoal)) {
set('st-boost', 'β', false);
} else {
set('st-boost', 'β', false);
}
}
function calc() {
var goal = parseFloat(document.getElementById('goal').value)||0;
var tiers = getTiers();
var launches = getLaunches();
var sponsors = getSponsors();
var coaching = getCoaching();
var resEl = document.getElementById('resContent');
var tCard = document.getElementById('tableCard');
var tbody = document.getElementById('tBody');
if (!tiers.length || goal<=0) {
resEl.innerHTML = 'Fill in your income goal and at least one membership tier to see your projection.
';
tCard.style.display='none'; updateStickyBar(goal, null, null, null); updateURL(); return;
}
// Coaching with mrrContrib > 0 (retainers + recurring sessions/packages) add to MRR
var recurringCoach = coaching.filter(function(c){ return c.mrrContrib > 0; });
// One-time boosts: only "once" sessions/packages
var oneTimeCoach = coaching.filter(function(c){ return c.mrrContrib === 0; });
// All one-time boosts pool
var allBoosts = launches.concat(sponsors).concat(oneTimeCoach);
var MAX=360, mc=tiers.map(function(t){return t.startM;}), rows=[], rGoal=null, cGoal=null;
for (var m=1; m<=MAX; m++) {
var totM=0, totMRR=0;
for (var i=0; iRecurring Revenue Alone
'+rows[rGoal-1].lbl+'
'+tstr(rGoal)+' Β· '+fmt(goal)+'/mo
With All One-Time Boosts
'+rows[cGoal-1].lbl+'
'+tstr(rGoal-cGoal)+' sooner Β· '+tstr(cGoal)+' total
With All One-Time Boosts
No change
Boosts land after recurring goal is reached
Recurring Revenue Alone
Plateaus at '+fmt(fin)+'/mo
Won\'t reach '+fmt(goal)+'/mo alone
With All One-Time Boosts
'+rows[cGoal-1].lbl+'
'+tstr(cGoal)+' β boosts bridge the gap
Recurring revenue plateaus at '+fmt(fin)+'/mo. Try adding more members per month or more one-time revenue.
';
tCard.style.display='none'; updateStickyBar(goal, rows, null, null); updateURL(); return;
}
}
html+='';
milestones.forEach(function(ms){html+='';});
var avgP=tiers.reduce(function(s,t){return s+t.mp;},0)/tiers.length;
html+='
';
// Workshops summary strip
var activeWorkshops=launches.filter(function(l){return l.mo>0&&l.mo<=MAX;});
if (activeWorkshops.length) {
var wTot=activeWorkshops.reduce(function(s,l){return s+l.amt;},0);
html+=''+ms.pct+'% of goal
'+(ms.lbl||'β')+'
'+fmt(ms.mrr)+'/mo
Avg member value
'+fmt(avgP)+'
/mo blended
π Workshops & One-Time Offers
';
activeWorkshops.sort(function(a,b){return a.mo-b.mo;}).forEach(function(l){
var detail=l.price>0&&l.parts>0 ? l.parts+' Γ '+fmt(l.price) : '';
html+=''+l.name+' ('+l.lbl+(detail?' Β· '+detail:'')+')'+fmt(l.amt)+'
';
});
html+='Total'+fmt(wTot)+'
πΌ Sponsorships & Underwriting
';
sponsors.forEach(function(sp){ html+=''+sp.name+' ('+sp.lbl+')'+fmt(sp.amt)+'
'; });
html+='Total'+fmt(sponsorTot)+'
π― 1:1 Coaching
';
recurringCoach.forEach(function(c){
var freqLbl = c.ctype==='retainer' ? 'retainer' : c.freq;
var mrrLbl = c.freq==='quarterly' ? fmt(c.mrrContrib)+'/mo ('+fmt(c.amt)+'/quarter)' : fmt(c.mrrContrib)+'/mo';
html+=''+c.name+' ('+freqLbl+' Β· starts '+c.lbl+')'+mrrLbl+'
';
});
oneTimeCoach.forEach(function(c){ html+=''+c.name+' ('+c.lbl+')'+fmt(c.amt)+'
'; });
if (recurringCoach.length) {
var totalRecurringMRR=recurringCoach.reduce(function(s,r){return s+r.mrrContrib;},0);
html+='Coaching MRR added+'+fmt(totalRecurringMRR)+'/mo
';
}
html+='One-Time Revenue Timeline
';
allBoostItems.sort(function(a,b){return a.mo-b.mo;}).forEach(function(b){
var icon=b.btype==='sponsor'?'πΌ':b.btype==='coach'?'π―':'π';
var rowMRR=rows[b.mo-1]?rows[b.mo-1].mrr:0;
var saved=null;
if (rGoal&&b.mo<=rGoal) {
var boostTarget=goal-b.amt;
var wbg=rows.find(function(r){return r.m>=b.mo&&r.mrr>=boostTarget;});
if(wbg) saved=rGoal-wbg.m;
}
var saveStr=saved===null?'':saved===0?'Goal already reached':saved>0?saved+' month'+(saved!==1?'s':'')+' sooner':'';
html+=''+icon+' '+b.name+' ('+b.lbl+')'+fmt(b.amt)+''+(saveStr?'β '+saveStr+'':'')+'
';
});
html+='Scenario Overview
';
html+='
';
if (tiers.length) {
html+='Monthly Goal'+fmt(goal)+'/mo
';
html+='Starting'+startLbl+'
';
html+='Currency'+CUR.code+'
';
html+='Membership Tiers
';
tiers.forEach(function(t){
var fLbl=t.freq==='quarterly'?'/quarter':t.freq==='annual'?'/year':'/month';
var details=fmt(t.rawP)+fLbl+' Β· '+t.newM+' new/mo';
if(t.startM>0) details+=' Β· '+t.startM+' existing';
if(t.churnR>0) details+=' Β· '+(t.churnR*100).toFixed(1)+'% churn';
html+='
';
}
if (launches.length) {
html+=''+t.name+''+details+'
';
});
html+='Workshops & One-Time Launches
';
launches.forEach(function(l){
var ldetail;
if(l.parts && l.price) { ldetail = l.parts+' participants Γ '+fmt(l.price)+' = '+fmt(l.amt)+' Β· '+l.lbl; }
else if(l.parts) { ldetail = l.parts+' participants Β· '+fmt(l.amt)+' Β· '+l.lbl; }
else { ldetail = fmt(l.amt)+' Β· '+l.lbl; }
html+='
';
}
if (sponsors.length) {
html+=''+l.name+''+ldetail+'
';
});
html+='Sponsorships & Underwriting
';
sponsors.forEach(function(s){
html+='
';
}
if (coaching.length) {
html+=''+s.name+''+fmt(s.amt)+' Β· '+s.lbl+'
';
});
html+='1:1 Coaching
';
coaching.forEach(function(c){
var isRecurring = c.ctype==='retainer' || (c.freq && c.freq!=='once');
var mrrC = c.mrrContrib !== undefined ? c.mrrContrib :
(c.ctype==='retainer'||c.freq==='monthly') ? c.price*(c.qty||1) :
c.freq==='quarterly' ? (c.price*(c.qty||1))/3 : 0;
var freqLbl = c.ctype==='retainer' ? 'retainer' : (c.freq&&c.freq!=='once') ? c.freq : 'one-time';
var det = isRecurring ? fmt(mrrC)+'/mo ('+freqLbl+' Β· starts '+c.lbl+')' : fmt(c.price*(c.qty||1))+' Β· '+c.lbl;
html+='
';
}
var notes=document.getElementById('scenarioNotes').value.trim();
if(notes){html+=''+c.name+''+det+'
';
});
html+='Notes
'+notes.replace(/\n/g,'
')+'
';}
document.getElementById('pdfSummary').innerHTML=html;
document.getElementById('pdfScenario').textContent=scenName;
document.getElementById('pdfMeta').textContent='Generated '+new Date().toLocaleDateString('en-US',{month:'long',day:'numeric',year:'numeric'});
}
function showPDF(cb) {
buildSummary();
var toShow=[document.getElementById('pdfHdr'),document.getElementById('pdfSummary'),document.querySelector('.pdf-foot')];
var toHide=[document.querySelector('.print-bar'),document.querySelector('.about-card'),document.querySelector('.gift-card'),document.querySelector('header'),document.querySelector('.currency-bar'),document.getElementById('addTierBtn')].concat(Array.from(document.querySelectorAll('.add-btn,.rm-btn,.tog-wrap,.freq-sel,.rst-btn')));
toShow.forEach(function(el){if(el) el.style.display='block';});
toHide.forEach(function(el){if(el) el.style.display='none';});
cb(function(){
toShow.forEach(function(el){if(el) el.style.display='';});
toHide.forEach(function(el){if(el) el.style.display='';});
});
}
function doPrint(){showPDF(function(restore){setTimeout(function(){window.print();restore();},120);});}
function getState() {
var tiers=[],launches=[],sponsors=[],coaching=[];
document.querySelectorAll('.tier-block').forEach(function(b){
var id=b.id.replace('tier-','');
tiers.push({name:document.getElementById('t'+id+'-name').value,price:document.getElementById('t'+id+'-price').value,freq:b.dataset.freq,newM:document.getElementById('t'+id+'-new').value,startM:document.getElementById('t'+id+'-start').value,churn:document.getElementById('t'+id+'-tog').classList.contains('on'),churnR:document.getElementById('t'+id+'-churnR').value});
});
document.querySelectorAll('.launch-block').forEach(function(r){
var id=r.id.replace('launch-','');
var mode=r.dataset.totmode||'calc';
launches.push({name:document.getElementById('l'+id+'-name').value,price:document.getElementById('l'+id+'-price').value,parts:document.getElementById('l'+id+'-parts').value,amt:document.getElementById('l'+id+'-amt').value,lm:document.getElementById('l'+id+'-m').value,ly:document.getElementById('l'+id+'-y').value,mode:mode});
});
document.querySelectorAll('.sponsor-block').forEach(function(r){
var id=r.id.replace('sponsor-','');
sponsors.push({name:document.getElementById('sp'+id+'-name').value,amt:document.getElementById('sp'+id+'-amt').value,lm:document.getElementById('sp'+id+'-m').value,ly:document.getElementById('sp'+id+'-y').value});
});
document.querySelectorAll('.coach-block').forEach(function(b){
var id=b.id.replace('coach-','');
coaching.push({name:document.getElementById('co'+id+'-name').value,ctype:b.dataset.ctype,freq:b.dataset.freq||'once',price:document.getElementById('co'+id+'-price').value,qty:document.getElementById('co'+id+'-qty').value,lm:document.getElementById('co'+id+'-m').value,ly:document.getElementById('co'+id+'-y').value});
});
return{goal:document.getElementById('goal').value,sm:document.getElementById('sMon').value,sy:document.getElementById('sYear').value,sn:document.getElementById('scenarioName').value,notes:document.getElementById('scenarioNotes').value,cur:CUR.code,tiers:tiers,launches:launches,sponsors:sponsors,coaching:coaching};
}
function updateURL(){try{var enc=btoa(encodeURIComponent(JSON.stringify(getState())));history.replaceState(null,'',location.pathname+'?s='+enc);}catch(e){}}
function loadURL(){
try{
var p=new URLSearchParams(location.search).get('s');
if(!p) return false;
var st=JSON.parse(decodeURIComponent(atob(p)));
if(st.goal) document.getElementById('goal').value=st.goal;
if(st.sm!==undefined) document.getElementById('sMon').value=st.sm;
if(st.sy!==undefined) document.getElementById('sYear').value=st.sy;
if(st.sn) document.getElementById('scenarioName').value=st.sn;
if(st.notes) document.getElementById('scenarioNotes').value=st.notes;
if(st.cur) setCurrency(st.cur);
(st.tiers||[]).forEach(function(t){addTier(t);});
(st.launches||[]).forEach(function(l){addLaunch(l);});
(st.sponsors||[]).forEach(function(s){addSponsor(s);});
(st.coaching||[]).forEach(function(c){addCoaching(c);});
return true;
}catch(e){return false;}
}
function doShare(){
if(location.protocol === 'file:'){
alert('Share links only work once this calculator is live on your website.\n\nOnce it\'s at kellypratt.com/revenuecalculator, the π Share Link button will generate a shareable URL with all your numbers encoded in it.');
return;
}
updateURL();
var url=location.href;
if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(url).then(flashCopied).catch(function(){prompt('Copy this link:',url);});}
else{prompt('Copy this link:',url);}
}
function flashCopied(){var el=document.getElementById('copiedMsg');el.classList.add('show');setTimeout(function(){el.classList.remove('show');},3000);}
function doPostInStudio(){
updateURL();
var scenName = document.getElementById('scenarioName').value.trim();
var label = scenName ? '"'+scenName+'"' : 'my revenue scenario';
// Check if we're on a real URL or a local file
var isLocal = location.protocol === 'file:';
var shareUrl = isLocal ? '[paste your share link here]' : location.href;
var msg = 'Just ran '+label+' through the Community Revenue Calculator β would love your eyes on it! π\n\n'+(isLocal ? 'β οΈ Share link not available from local file β use the π Share Link button once this is live on the site, then paste it here.\n' : shareUrl+'\n')+'\nWhat do you see that I might be missing? π±';
if(navigator.clipboard && navigator.clipboard.writeText){
navigator.clipboard.writeText(msg).then(function(){
var el=document.getElementById('studioMsg');
el.classList.add('show');
setTimeout(function(){el.classList.remove('show');},4000);
setTimeout(function(){window.open('https://members.athenavillage.com/spaces/17638940/feed','_blank');},400);
}).catch(function(){
prompt('Copy this to post in Creator Conversations:',msg);
window.open('https://members.athenavillage.com/spaces/17638940/feed','_blank');
});
} else {
prompt('Copy this to post in Creator Conversations:',msg);
window.open('https://members.athenavillage.com/spaces/17638940/feed','_blank');
}
}
function doSendToKelly(){
window.open('https://members.athenavillage.com/plans/1936469?bundle_token=d1c9ecb81e2e542025c468ccfa961077','_blank');
}
function doReset(){
if(!confirm('Reset everything and start fresh?')) return;
document.getElementById('tiersList').innerHTML='';
document.getElementById('launchList').innerHTML='';
document.getElementById('sponsorsList').innerHTML='';
document.getElementById('coachingList').innerHTML='';
document.getElementById('resContent').innerHTML='')+'
Fill in your income goal and at least one membership tier to see your projection.
';
document.getElementById('tableCard').style.display='none';
document.getElementById('scenarioName').value='';
document.getElementById('scenarioNotes').value='';
document.getElementById('goal').value='';
document.getElementById('addTierBtn').style.display='flex';
tierCount=0; launchCount=0; sponsorCount=0; coachCount=0;
setCurrency('USD');
initDates();
addTier({name:'',price:'',freq:'monthly',newM:'',startM:0,churn:false,churnR:5});
history.replaceState(null,'',location.pathname);
}
// Init
initDates();
var loaded = loadURL();
if (!loaded) {
// Mighty Networks recommended defaults β empty names so users fill in their own
addTier({name:'', price:'29', freq:'monthly', newM:'5', startM:0, churn:true, churnR:5});
addLaunch({name:'', price:'', parts:'', amt:'', lm:new Date().getMonth(), ly:new Date().getFullYear()});
}