From 2ab24681d79879e7e5f1fe34cccc1372ca796bc1 Mon Sep 17 00:00:00 2001 From: martbost Date: Mon, 3 Aug 2026 14:28:53 -0500 Subject: [PATCH] =?UTF-8?q?My=20Progress:=20Custom=20lanes=20option=20?= =?UTF-8?q?=E2=80=94=20track=20any=20Boost=20Planner=20mix=20(e.g.=20L4+L4?= =?UTF-8?q?+L2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- index.html | 66 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/index.html b/index.html index e5a8517..02c7546 100644 --- a/index.html +++ b/index.html @@ -1440,7 +1440,10 @@ + + +
@@ -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