My Progress: Custom lanes option — track any Boost Planner mix (e.g. L4+L4+L2)

Phase dropdown gains 'Custom lanes'; a lanes input derives profit/cycle
from LEVELS, maps the ladder rung to the highest lane, estimates
cycles-to-next accelerate-basis (labeled est.), persists with the rest
of the ROI state. Engine untouched; test_scenarios ALL PASS.
This commit is contained in:
martbost
2026-08-03 14:28:53 -05:00
committed by root
parent 0b75bed258
commit 2ab24681d7
+52 -14
View File
@@ -1440,7 +1440,10 @@
<option value="l6l3">L6 + L3 Hybrid</option>
<option value="l7solo">L7 Solo</option>
<option value="l7x3">3× L7 Endgame</option>
<option value="custom">🛠 Custom lanes… (any mix)</option>
</select>
<input type="text" id="roiCustomLanes" placeholder="e.g. L4+L4+L2" style="display:none;margin-top:6px;">
<small id="roiCustomHint" style="display:none;color:var(--text-muted);font-size:0.72rem;margin-top:3px;">The campaigns you're actually running, up to 3 lanes — Boost Planner mixes welcome (e.g. L4+L4+L2)</small>
</div>
<div class="roi-field">
<label>Cycles Completed This Phase</label>
@@ -2746,30 +2749,64 @@ function updateROI() {
// Update label to reflect profit mode
document.getElementById('roiStatRemaining').querySelector('.roi-stat-label').textContent = remaining > 0 ? 'To Breakeven' : 'Pure Profit';
// Phase progress
const pd = PHASE_DATA[phase];
// Phase progress. "custom" = the Boost Planner reality: any mix of up to
// 3 campaign slots (e.g. L4+L4+L2). Economics derived from LEVELS; the
// cycles-to-next estimate is accelerate-basis (bank everything) — pocket
// modes stretch it, so it's labeled est.
const customEl = document.getElementById('roiCustomLanes');
const customHintEl = document.getElementById('roiCustomHint');
const isCustom = phase === 'custom';
customEl.style.display = isCustom ? '' : 'none';
customHintEl.style.display = isCustom ? '' : 'none';
let pd, phaseKeyForLadder = phase, customBadge = null, customHi = 0;
if (isCustom) {
const lanes = (customEl.value.match(/[1-7]/g) || []).slice(0, 3).map(Number);
if (lanes.length) {
const profitPer = lanes.reduce((s, l) => s + LEVELS[l - 1].netProfit, 0);
customHi = Math.max(...lanes);
const nextLvl = customHi < 7 ? LEVELS[customHi] : null; // LEVELS[hi] = the (hi+1) record
pd = {
cycles: nextLvl ? Math.max(1, Math.ceil(nextLvl.total / profitPer)) : 999,
profitPer,
nextLabel: nextLvl ? `L${customHi + 1}` : null,
needsCapital: nextLvl ? nextLvl.total : 0,
est: true,
};
const equiv = { 1: 'l1', 2: 'l2l1', 3: 'l3l1', 4: 'l4', 5: 'l5', 6: 'l6', 7: 'l7solo' };
phaseKeyForLadder = equiv[customHi];
customBadge = 'Custom: ' + lanes.map(l => 'L' + l).join('+') + ` · $${profitPer.toFixed(2)}/cycle`;
} else {
pd = { cycles: 0, profitPer: 0, nextLabel: null, needsCapital: 0 };
customBadge = 'Type your lanes above (e.g. L4+L4+L2)';
}
} else {
pd = PHASE_DATA[phase] || { cycles: 0, profitPer: 0, nextLabel: null, needsCapital: 0 };
}
let phasePct = 0;
let phaseLabel = '';
if (phase === 'l7x3') {
phasePct = 100;
phaseLabel = '🏆 Endgame Reached';
} else {
} else if (pd.cycles > 0) {
phasePct = Math.min((cyclesDone / pd.cycles) * 100, 100);
phaseLabel = `Cycle ${cyclesDone} of ${pd.cycles}`;
phaseLabel = pd.est ? `Cycle ${cyclesDone} of ~${pd.cycles} (est.)` : `Cycle ${cyclesDone} of ${pd.cycles}`;
} else {
phaseLabel = '—';
}
document.getElementById('roiStatPhase').querySelector('.roi-stat-value').textContent = phaseLabel;
document.getElementById('roiBarPhase').style.width = phasePct + '%';
// Ladder visualization
// Ladder visualization (custom lanes map to their highest level's rung)
const ladderOrder = ['l1','l2l1','l3l1','l4','l4l3','l5','l5l3','l6','l6l3','l7solo','l7x3'];
const phaseIndex = ladderOrder.indexOf(phase);
const phaseIndex = ladderOrder.indexOf(phaseKeyForLadder);
// Location badge
const phaseNames = {
l1:'L1 Only', l2l1:'L2 + L1', l3l1:'L3 + L1', l4:'L4 Only',
l4l3:'L4 + L3 Hybrid', l5:'L5 Only', l5l3:'L5 + L3 Hybrid',
l6:'L6 Only', l6l3:'L6 + L3 Hybrid', l7solo:'L7 Solo', l7x3:'3× L7 Endgame'
};
document.getElementById('roiHerePhase').textContent = phase && phaseNames[phase] ? phaseNames[phase] : '—';
document.getElementById('roiHerePhase').textContent =
customBadge || (phase && phaseNames[phase] ? phaseNames[phase] : "—");
document.querySelectorAll('.roi-ladder-step').forEach(el => {
el.classList.remove('active', 'passed');
@@ -2785,11 +2822,11 @@ function updateROI() {
if (earned >= 337.50) milestones.push({ icon: '✅', label: '75% ROI Recouped', sub: 'Three quarters there' });
if (earned >= deposits) milestones.push({ icon: '💰', label: '100% ROI — Breakeven!', sub: 'Everything from here is pure profit' });
if (phase === 'l5l3' || phaseIndex > ladderOrder.indexOf('l5l3'))
if (phase === 'l5l3' || phaseIndex > ladderOrder.indexOf('l5l3') || customHi >= 5)
milestones.push({ icon: '🏅', label: 'L5 Active', sub: 'You reached the serious scaling tier' });
if (phase === 'l6l3' || phaseIndex > ladderOrder.indexOf('l6l3'))
if (phase === 'l6l3' || phaseIndex > ladderOrder.indexOf('l6l3') || customHi >= 6)
milestones.push({ icon: '🏅', label: 'L6 Active', sub: 'Double-digit clicks/day territory' });
if (phase === 'l7solo' || phaseIndex > ladderOrder.indexOf('l7solo'))
if (phase === 'l7solo' || phaseIndex > ladderOrder.indexOf('l7solo') || customHi >= 7)
milestones.push({ icon: '🌟', label: 'L7 — The Big One', sub: '$840/cycle profit, $270/day clicks' });
if (phase === 'l7x3')
@@ -2833,7 +2870,7 @@ function updateROI() {
`).join('');
// Save to localStorage
saveROIData(deposits, earned, phase, cyclesDone);
saveROIData(deposits, earned, phase, cyclesDone, customEl.value);
}
// ─── localStorage persistence ──────────────────────────
@@ -2842,9 +2879,9 @@ function getROIStorageKey() {
return 'cbp_roi_' + ref;
}
function saveROIData(deposits, earned, phase, cyclesDone) {
function saveROIData(deposits, earned, phase, cyclesDone, customLanes) {
const key = getROIStorageKey();
const data = { deposits, earned, phase, cyclesDone };
const data = { deposits, earned, phase, cyclesDone, customLanes: customLanes || '' };
try { localStorage.setItem(key, JSON.stringify(data)); } catch(e) {}
}
@@ -2858,7 +2895,7 @@ function loadROIData() {
}
// Wire up ROI inputs
['roiDeposits','roiTotalEarned','roiPhase','roiCyclesDone'].forEach(id => {
['roiDeposits','roiTotalEarned','roiPhase','roiCyclesDone','roiCustomLanes'].forEach(id => {
const el = document.getElementById(id);
el.addEventListener('input', updateROI);
el.addEventListener('change', updateROI);
@@ -2871,6 +2908,7 @@ if (saved) {
document.getElementById('roiTotalEarned').value = saved.earned || '';
if (saved.phase) document.getElementById('roiPhase').value = saved.phase;
document.getElementById('roiCyclesDone').value = saved.cyclesDone !== undefined ? saved.cyclesDone : '';
document.getElementById('roiCustomLanes').value = saved.customLanes || '';
}
// Initial render on page load