From 4a6640c05f042f9186be52d2b4c8deaa0cebc927 Mon Sep 17 00:00:00 2001 From: SujalKamate Date: Sun, 14 Jun 2026 23:02:13 +0530 Subject: [PATCH] feat: add Startup Runway Calculator --- Projects/Startup Runway Calculator/README.md | 29 + Projects/Startup Runway Calculator/index.html | 233 ++++++ .../Startup Runway Calculator/project.json | 17 + Projects/Startup Runway Calculator/script.js | 787 ++++++++++++++++++ Projects/Startup Runway Calculator/style.css | 758 +++++++++++++++++ .../Startup Runway Calculator/thumbnail.svg | 143 ++++ 6 files changed, 1967 insertions(+) create mode 100644 Projects/Startup Runway Calculator/README.md create mode 100644 Projects/Startup Runway Calculator/index.html create mode 100644 Projects/Startup Runway Calculator/project.json create mode 100644 Projects/Startup Runway Calculator/script.js create mode 100644 Projects/Startup Runway Calculator/style.css create mode 100644 Projects/Startup Runway Calculator/thumbnail.svg diff --git a/Projects/Startup Runway Calculator/README.md b/Projects/Startup Runway Calculator/README.md new file mode 100644 index 00000000..e3545612 --- /dev/null +++ b/Projects/Startup Runway Calculator/README.md @@ -0,0 +1,29 @@ +# Startup Runway Calculator + +An advanced financial modeling dashboard for startups to compute cash runway lengths, track operating expenses (OpEx) by category, adjust monthly revenue growth rates, simulate hiring/cost events, and compare planning scenarios. + +## Core Features + +- **Granular Operating Expenses Tracker**: Itemize cash outlays across key startup operational pools: + * **Salaries & Payroll**: Core staff, contractors, benefits. + * **Marketing & Acquisition**: Ads, events, agency costs. + * **SaaS & Infrastructure**: Server hosting, software subscriptions. + * **Office & Overhead**: Rent, utilities, logistics. + * **Miscellaneous / Legal**: Fees, insurance, other administrative expenses. +- **Runway & Net Burn Forecast**: Instantly evaluates how many months of operational cash remain based on current balances, monthly revenue, and monthly growth rate. +- **Urgency Meter & Alive classification**: + * Classified as **Default Alive** (profitable or growing to profitability before depletion) or **Default Dead**. + * Dynamic visual status badges (Red for runway < 6 months, Amber for 6-12 months, Emerald for >12 months or Profitable). +- **Interactive SVG Chart**: Dynamic line chart rendering cash balance decline or upward trajectory over the next 12-24 months. +- **What-If Scenario Sandbox**: Quick adjustments for revenue growth rate inputs and immediate visualization of how scaling expenses affects cash lifecycle. +- **Scenario Vault**: Save multiple plan configurations (e.g. "Worst Case", "Moderate Base", "Aggressive Growth Plan") in `localStorage`. + +## Run it + +Open `index.html` in any modern web browser. + +## Technical Details + +- **HTML5 & CSS3**: Glassmorphic dashboard templates, neon status indicators, and responsive flexboxes. +- **Vanilla JavaScript**: Math systems calculating compound growth rates, zero cash dates, and generating dynamic coordinate matrices for responsive SVG chart lines. +- **Storage**: JSON models mapped and loaded from `localStorage`. diff --git a/Projects/Startup Runway Calculator/index.html b/Projects/Startup Runway Calculator/index.html new file mode 100644 index 00000000..1722b417 --- /dev/null +++ b/Projects/Startup Runway Calculator/index.html @@ -0,0 +1,233 @@ + + + + + + Startup Runway Calculator + + + + + + + + + +
+ + +
+
+ + +
+
+ Global Health Status + DEFAULT ALIVE +
+
+
+
+ + +
+ + + + + +
+ + +
+ +
+ Runway Remaining + 0.0 mo + Evaluating... +
+ +
+ Monthly Net Burn + $0 + Revenues vs Expenses +
+ +
+ Zero Cash Date + --/-- + Estimated depletion +
+ +
+ Core Status + --- + Profitability Index +
+ +
+ + +
+
+

24-Month Cash Balance Projection

+
+ Projected Cash + Revenue +
+
+ +
+ + + + +
+
+ + +
+
+

Monthly Forecast Ledgers

+
+
+ + + + + + + + + + + + + + +
MonthStarting CashRevenueOpExNet BurnEnding Cash
+
+
+ +
+ + + + +
+ + + + + + + diff --git a/Projects/Startup Runway Calculator/project.json b/Projects/Startup Runway Calculator/project.json new file mode 100644 index 00000000..4a235025 --- /dev/null +++ b/Projects/Startup Runway Calculator/project.json @@ -0,0 +1,17 @@ +{ + "title": "Startup Runway Calculator", + "description": "An advanced financial planning dashboard for startups featuring runway forecasting, OpEx trackers, interactive growth projections, and scenario planning.", + "author": { + "name": "Sujal", + "github": "Sujal" + }, + "tags": [ + "finance", + "startup", + "calculator", + "vanilla-js", + "localstorage" + ], + "entry": "index.html", + "thumbnail": "thumbnail.svg" +} diff --git a/Projects/Startup Runway Calculator/script.js b/Projects/Startup Runway Calculator/script.js new file mode 100644 index 00000000..bf419087 --- /dev/null +++ b/Projects/Startup Runway Calculator/script.js @@ -0,0 +1,787 @@ +// Startup Runway Calculator - Interaction Script + +// Predefined Scenario Snapshot Templates +const DEFAULT_SCENARIOS = [ + { + id: "sc-baseline", + name: "Current Baseline Plan", + cash: 500000, + revenue: 25000, + growth: 5, + payroll: 40000, + marketing: 8000, + saas: 3000, + rent: 4000, + misc: 2000 + }, + { + id: "sc-bootstrap", + name: "Bootstrapped Slow Burn", + cash: 250000, + revenue: 15000, + growth: 2, + payroll: 18000, + marketing: 2000, + saas: 1500, + rent: 1500, + misc: 1000 + }, + { + id: "sc-aggressive", + name: "Aggressive Growth Push", + cash: 800000, + revenue: 40000, + growth: 12, + payroll: 85000, + marketing: 25000, + saas: 8000, + rent: 8000, + misc: 5000 + } +]; + +// App State +let scenarios = []; +let activeScenarioId = "sc-baseline"; + +// Date configuration (Base date is June 2026) +const BASE_YEAR = 2026; +const BASE_MONTH = 5; // 0-indexed (June is 5) +const MONTH_NAMES = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + +// DOM Elements +const txtCash = document.getElementById("txt-cash"); +const txtRevenue = document.getElementById("txt-revenue"); +const sliderGrowth = document.getElementById("slider-growth"); +const lblGrowthVal = document.getElementById("lbl-growth-val"); + +const txtPayroll = document.getElementById("txt-payroll"); +const txtMarketing = document.getElementById("txt-marketing"); +const txtSaas = document.getElementById("txt-saas"); +const txtRent = document.getElementById("txt-rent"); +const txtMisc = document.getElementById("txt-misc"); + +const lblHeaderStatus = document.getElementById("lbl-header-status"); +const lblRunway = document.getElementById("lbl-runway"); +const lblRunwayUrgency = document.getElementById("lbl-runway-urgency"); +const lblNetBurn = document.getElementById("lbl-net-burn"); +const lblZeroDate = document.getElementById("lbl-zero-date"); +const lblClassification = document.getElementById("lbl-classification"); + +const forecastTableBody = document.getElementById("forecast-table-body"); +const svgElement = document.getElementById("projection-svg"); +const chartContainer = document.getElementById("chart-container"); + +const txtScenarioName = document.getElementById("txt-scenario-name"); +const btnSaveScenario = document.getElementById("btn-save-scenario"); +const scenarioList = document.getElementById("scenario-list"); +const btnResetWorkspace = document.getElementById("btn-reset-workspace"); +const recommendationsFeed = document.getElementById("recommendations-feed"); + +// CREATE TOOLTIP ELEMENT +let tooltip = document.createElement("div"); +tooltip.className = "chart-tooltip"; +document.body.appendChild(tooltip); + +// INITIALIZATION +window.addEventListener("DOMContentLoaded", () => { + loadScenarios(); + setupEventListeners(); + calculateAndRender(); +}); + +// Load saved snapshots or seeds +function loadScenarios() { + const stored = localStorage.getItem("runwaysync_scenarios"); + if (stored) { + try { + scenarios = JSON.parse(stored); + } catch (e) { + console.error("Error parsing scenarios", e); + scenarios = [...DEFAULT_SCENARIOS]; + } + } else { + scenarios = [...DEFAULT_SCENARIOS]; + saveToStorage(false); + } +} + +function saveToStorage(shouldUpdate = true) { + localStorage.setItem("runwaysync_scenarios", JSON.stringify(scenarios)); + if (shouldUpdate) { + renderScenarioList(); + } +} + +// Event Listeners setup +function setupEventListeners() { + const inputs = [txtCash, txtRevenue, txtPayroll, txtMarketing, txtSaas, txtRent, txtMisc]; + + inputs.forEach(input => { + input.addEventListener("input", () => { + activeScenarioId = null; // deselect saved scenario on modifications + updateActiveScenarioHighlight(); + calculateAndRender(); + }); + }); + + sliderGrowth.addEventListener("input", () => { + lblGrowthVal.textContent = `${sliderGrowth.value}%`; + activeScenarioId = null; + updateActiveScenarioHighlight(); + calculateAndRender(); + }); + + // Scenario buttons + btnSaveScenario.addEventListener("click", saveCurrentScenario); + btnResetWorkspace.addEventListener("click", resetWorkspace); + + // Resize window triggers chart updates + window.addEventListener("resize", renderChart); +} + +// MAIN CALCULATOR ENGINE +let monthlyData = []; + +function calculateAndRender() { + // 1. Read input values + const cash = Math.max(0, Number(txtCash.value) || 0); + const revenue = Math.max(0, Number(txtRevenue.value) || 0); + const growth = Number(sliderGrowth.value) || 0; + + const payroll = Math.max(0, Number(txtPayroll.value) || 0); + const marketing = Math.max(0, Number(txtMarketing.value) || 0); + const saas = Math.max(0, Number(txtSaas.value) || 0); + const rent = Math.max(0, Number(txtRent.value) || 0); + const misc = Math.max(0, Number(txtMisc.value) || 0); + + const totalOpEx = payroll + marketing + saas + rent + misc; + + // 2. Perform 24-Month Forecast Projection + monthlyData = []; + let currentCash = cash; + let currentRevenue = revenue; + let hasDepleted = false; + let zeroCashMonthIndex = -1; + + for (let m = 0; m <= 24; m++) { + // Rev growth compounds monthly (Month 0 is baseline starting metrics) + if (m > 0) { + currentRevenue = currentRevenue * (1 + growth / 100); + } + + const netBurn = totalOpEx - currentRevenue; + const startingCash = currentCash; + + if (currentCash > 0) { + currentCash = currentCash - netBurn; + if (currentCash <= 0) { + currentCash = 0; + if (!hasDepleted) { + hasDepleted = true; + zeroCashMonthIndex = m; + } + } + } else { + currentCash = 0; + } + + monthlyData.push({ + monthIndex: m, + monthName: getProjectedMonthName(m), + startingCash: startingCash, + revenue: currentRevenue, + opex: totalOpEx, + netBurn: netBurn, + endingCash: currentCash + }); + } + + // 3. Compute Runway length (Granular Months) + let runwayMonths = 0; + let isProfitable = false; + let isAlive = false; + + // Evaluate if startup is already profitable or default alive + if (revenue >= totalOpEx && growth >= 0) { + isProfitable = true; + isAlive = true; + runwayMonths = Infinity; + } else { + // If not immediately profitable, simulate forward up to 120 months to find if growth saves them + let simCash = cash; + let simRev = revenue; + let simDepleted = false; + let simMonths = 0; + + while (simMonths < 120) { + simMonths++; + simRev = simRev * (1 + growth / 100); + const simBurn = totalOpEx - simRev; + + if (simRev >= totalOpEx) { + // Profitable crossover reached before cash depletion + isAlive = true; + break; + } + + if (simCash > 0) { + const prevCash = simCash; + simCash -= simBurn; + if (simCash <= 0) { + simDepleted = true; + // Calculate fractional month of survival + runwayMonths = (simMonths - 1) + (prevCash / simBurn); + break; + } + } else { + simDepleted = true; + runwayMonths = simMonths - 1; + break; + } + } + + if (!simDepleted && simMonths >= 120) { + isAlive = true; + runwayMonths = Infinity; + } + } + + // 4. Update metrics dashboard text labels + // Burn (Month 1 baseline burn) + const initialBurn = totalOpEx - revenue; + lblNetBurn.textContent = formatCurrency(initialBurn); + + if (initialBurn > 0) { + lblNetBurn.className = "m-val text-rose"; + } else { + lblNetBurn.className = "m-val text-emerald"; + lblNetBurn.textContent = "+" + formatCurrency(Math.abs(initialBurn)); + } + + // Runway Remaining label + if (runwayMonths === Infinity) { + lblRunway.textContent = "∞ (Infinite)"; + lblRunway.className = "m-val text-emerald"; + lblRunwayUrgency.textContent = "Revenue exceeds expenses"; + lblZeroDate.textContent = "N/A"; + lblZeroDate.className = "m-val text-emerald"; + lblClassification.textContent = "Profitable"; + lblClassification.className = "m-val text-emerald"; + + lblHeaderStatus.textContent = "DEFAULT ALIVE"; + lblHeaderStatus.className = "status-indicator status-green"; + } else { + lblRunway.textContent = `${runwayMonths.toFixed(1)} mo`; + + // Classify Urgency alerts color + if (runwayMonths < 6) { + lblRunway.className = "m-val text-rose"; + lblRunwayUrgency.textContent = "Critical cash runway"; + lblZeroDate.className = "m-val text-rose"; + } else if (runwayMonths <= 12) { + lblRunway.className = "m-val text-amber"; + lblRunwayUrgency.textContent = "Caution cash runway"; + lblZeroDate.className = "m-val text-amber"; + } else { + lblRunway.className = "m-val text-emerald"; + lblRunwayUrgency.textContent = "Stable cash runway"; + lblZeroDate.className = "m-val text-emerald"; + } + + // Set zero-cash date text + const zeroDate = getProjectedDateFromMonths(runwayMonths); + lblZeroDate.textContent = zeroDate; + + // Set Default Alive vs Dead status + if (isAlive) { + lblClassification.textContent = "Default Alive"; + lblClassification.className = "m-val text-emerald"; + lblHeaderStatus.textContent = "DEFAULT ALIVE"; + lblHeaderStatus.className = "status-indicator status-green"; + } else { + lblClassification.textContent = "Default Dead"; + lblClassification.className = "m-val text-rose"; + lblHeaderStatus.textContent = "DEFAULT DEAD"; + lblHeaderStatus.className = "status-indicator status-red"; + } + } + + // 5. Populate Monthly ledgers table + renderForecastTable(); + + // 6. Draw dynamic forecast SVG line chart + renderChart(); + + // 7. Render Scenario Vault list + renderScenarioList(); + + // 8. Render Runway recommendations feed + renderRecommendations(runwayMonths, totalOpEx, payroll, marketing, saas, growth); +} + +// Monthly Forecast Data Table +function renderForecastTable() { + forecastTableBody.innerHTML = ""; + monthlyData.forEach(row => { + const tr = document.createElement("tr"); + tr.innerHTML = ` + ${row.monthName} + ${formatCurrency(row.startingCash)} + ${formatCurrency(row.revenue)} + ${formatCurrency(row.opex)} + ${row.netBurn > 0 ? "-" : "+"}${formatCurrency(Math.abs(row.netBurn))} + ${formatCurrency(row.endingCash)} + `; + forecastTableBody.appendChild(tr); + }); +} + +// Render dynamic forecasting SVG line chart +function renderChart() { + svgElement.innerHTML = ""; + + const w = svgElement.clientWidth || 800; + const h = 320; + const paddingLeft = 70; + const paddingRight = 40; + const paddingTop = 30; + const paddingBottom = 40; + + const chartW = w - paddingLeft - paddingRight; + const chartH = h - paddingTop - paddingBottom; + + // Calculate scales Y-max + const maxCash = Math.max(...monthlyData.map(d => d.startingCash), ...monthlyData.map(d => d.endingCash), 100000); + const maxRevenue = Math.max(...monthlyData.map(d => d.revenue), 10000); + const yMax = Math.max(maxCash, maxRevenue * 1.2); + + // Draw Grid Lines (Horizontal and Vertical) + const gridCountY = 5; + for (let i = 0; i <= gridCountY; i++) { + const yVal = yMax * (i / gridCountY); + const yPos = paddingTop + chartH - (yVal / yMax) * chartH; + + // Gridline path + const line = document.createElementNS("http://www.w3.org/2000/svg", "line"); + line.setAttribute("x1", paddingLeft); + line.setAttribute("y1", yPos); + line.setAttribute("x2", w - paddingRight); + line.setAttribute("y2", yPos); + line.setAttribute("class", "grid-line"); + svgElement.appendChild(line); + + // Label Text + const text = document.createElementNS("http://www.w3.org/2000/svg", "text"); + text.setAttribute("x", paddingLeft - 10); + text.setAttribute("y", yPos + 4); + text.setAttribute("text-anchor", "end"); + text.setAttribute("class", "axis-label"); + text.textContent = formatCurrencyShort(yVal); + svgElement.appendChild(text); + } + + // Draw X-axis labels + const labelInterval = 4; + monthlyData.forEach((d, i) => { + const xPos = paddingLeft + (i / 24) * chartW; + + // Vertical grid ticks + if (i % labelInterval === 0 || i === 24) { + const line = document.createElementNS("http://www.w3.org/2000/svg", "line"); + line.setAttribute("x1", xPos); + line.setAttribute("y1", paddingTop); + line.setAttribute("x2", xPos); + line.setAttribute("y2", paddingTop + chartH); + line.setAttribute("class", "grid-line"); + svgElement.appendChild(line); + + const text = document.createElementNS("http://www.w3.org/2000/svg", "text"); + text.setAttribute("x", xPos); + text.setAttribute("y", paddingTop + chartH + 20); + text.setAttribute("text-anchor", "middle"); + text.setAttribute("class", "axis-label"); + text.textContent = d.monthName; + svgElement.appendChild(text); + } + }); + + // Axis lines + const xAxis = document.createElementNS("http://www.w3.org/2000/svg", "line"); + xAxis.setAttribute("x1", paddingLeft); + xAxis.setAttribute("y1", paddingTop + chartH); + xAxis.setAttribute("x2", w - paddingRight); + xAxis.setAttribute("y2", paddingTop + chartH); + xAxis.setAttribute("class", "axis-line"); + svgElement.appendChild(xAxis); + + const yAxis = document.createElementNS("http://www.w3.org/2000/svg", "line"); + yAxis.setAttribute("x1", paddingLeft); + yAxis.setAttribute("y1", paddingTop); + yAxis.setAttribute("x2", paddingLeft); + yAxis.setAttribute("y2", paddingTop + chartH); + yAxis.setAttribute("class", "axis-line"); + svgElement.appendChild(yAxis); + + // Draw Revenue Line path + let revPathPoints = ""; + monthlyData.forEach((d, i) => { + const x = paddingLeft + (i / 24) * chartW; + const y = paddingTop + chartH - (d.revenue / yMax) * chartH; + revPathPoints += `${i === 0 ? "M" : "L"} ${x} ${y}`; + }); + + const revPath = document.createElementNS("http://www.w3.org/2000/svg", "path"); + revPath.setAttribute("d", revPathPoints); + revPath.setAttribute("class", "line-revenue"); + svgElement.appendChild(revPath); + + // Draw Cash Projection Line path + let cashPathPoints = ""; + let crossoverX = null; + let crossoverY = null; + let zeroCashIndex = -1; + + monthlyData.forEach((d, i) => { + const x = paddingLeft + (i / 24) * chartW; + const y = paddingTop + chartH - (d.endingCash / yMax) * chartH; + + cashPathPoints += `${i === 0 ? "M" : "L"} ${x} ${y}`; + + if (d.endingCash === 0 && zeroCashIndex === -1 && i > 0 && monthlyData[i-1].endingCash > 0) { + zeroCashIndex = i; + // Interpolate depletion intersection + const prevX = paddingLeft + ((i - 1) / 24) * chartW; + const prevY = paddingTop + chartH - (monthlyData[i-1].endingCash / yMax) * chartH; + const diffCash = monthlyData[i-1].endingCash; + const burn = monthlyData[i].netBurn; + const frac = diffCash / burn; + + crossoverX = prevX + frac * (x - prevX); + crossoverY = paddingTop + chartH; // Y coordinate is bottom axis + } + }); + + const cashPath = document.createElementNS("http://www.w3.org/2000/svg", "path"); + cashPath.setAttribute("d", cashPathPoints); + cashPath.setAttribute("class", "line-cash"); + svgElement.appendChild(cashPath); + + // Draw Crossover Dot (Zero cash date indicator) + if (crossoverX !== null) { + const dot = document.createElementNS("http://www.w3.org/2000/svg", "circle"); + dot.setAttribute("cx", crossoverX); + dot.setAttribute("cy", crossoverY); + dot.setAttribute("class", "chart-dot dot-cash-depletion"); + dot.setAttribute("title", "Cash Depletion Date"); + svgElement.appendChild(dot); + + dot.addEventListener("mouseenter", (e) => { + showTooltip(e, `Cash Depletion Intersection
Runway ends here.`); + }); + dot.addEventListener("mouseleave", hideTooltip); + } + + // Draw Hover Dots/Nodes for Tooltips details + monthlyData.forEach((d, i) => { + const x = paddingLeft + (i / 24) * chartW; + const yCash = paddingTop + chartH - (d.endingCash / yMax) * chartH; + + const dot = document.createElementNS("http://www.w3.org/2000/svg", "circle"); + dot.setAttribute("cx", x); + dot.setAttribute("cy", yCash); + dot.setAttribute("r", 4); + dot.setAttribute("fill", "#0ea5e9"); + dot.setAttribute("stroke", "#ffffff"); + dot.setAttribute("stroke-width", "1"); + dot.setAttribute("opacity", "0"); + dot.setAttribute("style", "cursor: pointer; pointer-events: all;"); + svgElement.appendChild(dot); + + // Event hooks + dot.addEventListener("mouseenter", (e) => { + dot.setAttribute("opacity", "1"); + dot.setAttribute("r", 6); + showTooltip(e, ` + ${d.monthName} Forecast
+ Starting Cash: ${formatCurrency(d.startingCash)}
+ Revenue: ${formatCurrency(d.revenue)}
+ OpEx: ${formatCurrency(d.opex)}
+ Net Burn: ${d.netBurn > 0 ? "" : "+"}${formatCurrency(-d.netBurn)}
+ Ending Cash: ${formatCurrency(d.endingCash)} + `); + }); + + dot.addEventListener("mouseleave", () => { + dot.setAttribute("opacity", "0"); + dot.setAttribute("r", 4); + hideTooltip(); + }); + }); +} + +// Tooltip helpers +function showTooltip(e, htmlContent) { + tooltip.innerHTML = htmlContent; + tooltip.style.display = "block"; + + // Position tooltip relative to screen dimensions + const tooltipW = tooltip.offsetWidth; + const tooltipH = tooltip.offsetHeight; + + let x = e.pageX + 15; + let y = e.pageY - tooltipH - 10; + + if (x + tooltipW > window.innerWidth) { + x = e.pageX - tooltipW - 15; + } + if (y < window.scrollY) { + y = e.pageY + 20; + } + + tooltip.style.left = `${x}px`; + tooltip.style.top = `${y}px`; +} + +function hideTooltip() { + tooltip.style.display = "none"; +} + +// Helper date generator +function getProjectedMonthName(monthsFromNow) { + const date = new Date(BASE_YEAR, BASE_MONTH + monthsFromNow, 1); + return `${MONTH_NAMES[date.getMonth()]} ${date.getFullYear()}`; +} + +function getProjectedDateFromMonths(months) { + if (months === Infinity) return "N/A"; + const intMonths = Math.floor(months); + const frac = months - intMonths; + + const date = new Date(BASE_YEAR, BASE_MONTH + intMonths, 1); + // Interpolate day approximation + const daysInMonth = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); + const day = Math.max(1, Math.round(frac * daysInMonth)); + + return `${MONTH_NAMES[date.getMonth()]} ${day}, ${date.getFullYear()}`; +} + +// CURRENCY FORMATTERS +function formatCurrency(amount) { + return new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + maximumFractionDigits: 0 + }).format(amount); +} + +function formatCurrencyShort(amount) { + if (amount >= 1000000) { + return `$${(amount / 1000000).toFixed(1)}M`; + } else if (amount >= 1000) { + return `$${(amount / 1000).toFixed(0)}k`; + } + return `$${amount}`; +} + +// RUNWAY RECOMMENDATIONS ALERTS GENERATOR +function renderRecommendations(runway, totalOpEx, payroll, marketing, saas, growth) { + recommendationsFeed.innerHTML = ""; + + // 1. General Urgency Alert + const alertCard = document.createElement("div"); + if (runway === Infinity) { + alertCard.className = "advisory-item adv-success"; + alertCard.innerHTML = ` +
Sustainable Status
+

Your startup is Default Alive. Revenue projections surpass operational expenses, creating self-sustainability without immediate external capital.

+ `; + } else if (runway < 6) { + alertCard.className = "advisory-item adv-danger"; + alertCard.innerHTML = ` +
Action Required!
+

Critical runway length. Cash depletes in ${runway.toFixed(1)} months. Pause non-essential hiring immediately and seek bridge funding sources.

+ `; + } else if (runway <= 12) { + alertCard.className = "advisory-item adv-warning"; + alertCard.innerHTML = ` +
Warning Runway
+

Caution cash burn. Runway is ${runway.toFixed(1)} months. Optimize vendor contracts and focus marketing budgets strictly on high-converting channels.

+ `; + } else { + alertCard.className = "advisory-item adv-success"; + alertCard.innerHTML = ` +
Stable Health
+

Healthy Runway of ${runway.toFixed(1)} months. You have adequate space to focus on product-market fit or launch next equity funding cycles.

+ `; + } + recommendationsFeed.appendChild(alertCard); + + // 2. OpEx Analysis Advice + const opexCard = document.createElement("div"); + opexCard.className = "advisory-item"; + opexCard.style.borderLeftColor = "var(--clr-primary)"; + opexCard.style.background = "rgba(14, 165, 233, 0.05)"; + + const payrollPct = totalOpEx > 0 ? Math.round((payroll / totalOpEx) * 100) : 0; + const marketingPct = totalOpEx > 0 ? Math.round((marketing / totalOpEx) * 100) : 0; + + if (payrollPct > 65) { + opexCard.innerHTML = ` +
Personnel Expense Heavy
+

Payroll represents ${payrollPct}% of your operational cost. Consider structuring new hires on equity-heavy compensation packages to save baseline burn.

+ `; + } else if (marketingPct > 25 && runway < 12) { + opexCard.innerHTML = ` +
Reduce Acquisition Costs
+

Marketing spends eat ${marketingPct}% of cash flow. Scaling down customer acquisition budgets by 30% could extend your runway by ${(runway * 0.15).toFixed(1)} months.

+ `; + } else { + opexCard.innerHTML = ` +
Balanced Expenditures
+

OpEx is evenly divided: Payroll (${payrollPct}%) and SaaS/Admin overhead. Focus operational focus on customer feedback metrics.

+ `; + } + recommendationsFeed.appendChild(opexCard); + + // 3. Growth rate advice + if (growth > 10 && runway < 12) { + const growthCard = document.createElement("div"); + growthCard.className = "advisory-item adv-warning"; + growthCard.innerHTML = ` +
Over-optimistic Expansion
+

Compounding a ${growth}% growth rate is high. If sales growth pulls back to 2%, your runway will drop instantly. Test conservative models.

+ `; + recommendationsFeed.appendChild(growthCard); + } +} + +// SCENARIO VAULT CONTROLLERS +function renderScenarioList() { + scenarioList.innerHTML = ""; + + if (scenarios.length === 0) { + scenarioList.innerHTML = `
  • No plans saved yet.
  • `; + return; + } + + scenarios.forEach(sc => { + const li = document.createElement("li"); + li.className = `scenario-item ${sc.id === activeScenarioId ? "active-scenario" : ""}`; + + li.innerHTML = ` + ${escapeHtml(sc.name)} + + `; + + // Click on name to load scenario details + li.addEventListener("click", (e) => { + if (e.target.tagName !== "I" && e.target.tagName !== "BUTTON") { + loadScenarioDetails(sc.id); + } + }); + + // Delete scenario button + li.querySelector(".btn-delete-scenario").addEventListener("click", (e) => { + e.stopPropagation(); + deleteScenario(sc.id); + }); + + scenarioList.appendChild(li); + }); +} + +// Load values from active scenario +function loadScenarioDetails(id) { + const sc = scenarios.find(s => s.id === id); + if (!sc) return; + + activeScenarioId = id; + + txtCash.value = sc.cash; + txtRevenue.value = sc.revenue; + sliderGrowth.value = sc.growth; + lblGrowthVal.textContent = `${sc.growth}%`; + + txtPayroll.value = sc.payroll; + txtMarketing.value = sc.marketing; + txtSaas.value = sc.saas; + txtRent.value = sc.rent; + txtMisc.value = sc.misc; + + updateActiveScenarioHighlight(); + calculateAndRender(); +} + +// Highlight active scenario in Vault +function updateActiveScenarioHighlight() { + document.querySelectorAll(".scenario-item").forEach(item => { + item.classList.remove("active-scenario"); + }); + if (activeScenarioId) { + renderScenarioList(); + } +} + +// Save Current scenario Snapshot +function saveCurrentScenario() { + const name = txtScenarioName.value.trim(); + if (!name) { + alert("Please enter a Scenario Name first."); + return; + } + + const id = "sc-" + Date.now(); + const snapshot = { + id: id, + name: name, + cash: Number(txtCash.value) || 0, + revenue: Number(txtRevenue.value) || 0, + growth: Number(sliderGrowth.value) || 0, + payroll: Number(txtPayroll.value) || 0, + marketing: Number(txtMarketing.value) || 0, + saas: Number(txtSaas.value) || 0, + rent: Number(txtRent.value) || 0, + misc: Number(txtMisc.value) || 0 + }; + + scenarios.push(snapshot); + txtScenarioName.value = ""; + activeScenarioId = id; + + saveToStorage(); + calculateAndRender(); +} + +// Delete scenario +function deleteScenario(id) { + if (confirm("Are you sure you want to delete this planning scenario?")) { + scenarios = scenarios.filter(s => s.id !== id); + if (activeScenarioId === id) { + activeScenarioId = null; + } + saveToStorage(); + calculateAndRender(); + } +} + +// Reset data parameters memory +function resetWorkspace() { + if (confirm("Are you absolutely sure you want to clear scenarios? This will restore baseline templates.")) { + scenarios = [...DEFAULT_SCENARIOS]; + activeScenarioId = "sc-baseline"; + saveToStorage(); + loadScenarioDetails("sc-baseline"); + } +} + +// Escape HTML utility +function escapeHtml(str) { + if (!str) return ""; + return str + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} diff --git a/Projects/Startup Runway Calculator/style.css b/Projects/Startup Runway Calculator/style.css new file mode 100644 index 00000000..80c7067b --- /dev/null +++ b/Projects/Startup Runway Calculator/style.css @@ -0,0 +1,758 @@ +/* Startup Runway Calculator - Dashboard Styles */ + +:root { + --bg-main: #090d16; + --bg-card: #111827; + --bg-card-hover: #1f2937; + --border-color: #374151; + --text-main: #f3f4f6; + --text-muted: #9ca3af; + --text-highlight: #ffffff; + + /* Accent theme palettes */ + --clr-primary: #0ea5e9; + --clr-primary-glow: rgba(14, 165, 233, 0.15); + + --clr-emerald: #10b981; + --clr-emerald-glow: rgba(16, 185, 129, 0.15); + + --clr-amber: #f59e0b; + --clr-amber-glow: rgba(245, 158, 11, 0.15); + + --clr-rose: #ef4444; + --clr-rose-glow: rgba(239, 68, 68, 0.15); + + --font-headers: 'Outfit', sans-serif; + --font-body: 'Inter', sans-serif; + + --transition-speed: 0.25s; + --radius-sm: 8px; + --radius-md: 12px; + --radius-lg: 16px; + --glow-shadow: 0 0 20px rgba(14, 165, 233, 0.25); +} + +/* GENERAL STYLES */ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + background-color: var(--bg-main); + color: var(--text-main); + font-family: var(--font-body); + min-height: 100vh; + display: flex; + flex-direction: column; + position: relative; + overflow-x: hidden; + line-height: 1.5; +} + +/* GLOW BACKGROUND */ +.glow-bg { + position: absolute; + top: -150px; + left: 50%; + transform: translateX(-50%); + width: 800px; + height: 400px; + background: radial-gradient(circle, rgba(14, 165, 233, 0.1) 0%, rgba(16, 185, 129, 0.05) 50%, rgba(9, 13, 22, 0) 100%); + filter: blur(80px); + z-index: -1; + pointer-events: none; +} + +/* SCROLLBARS */ +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0.1); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb { + background: var(--border-color); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--text-muted); +} + +/* HEADER styling */ +.app-header { + background: rgba(17, 24, 39, 0.7); + backdrop-filter: blur(12px); + border-bottom: 1px solid var(--border-color); + padding: 1rem 2rem; + position: sticky; + top: 0; + z-index: 100; +} + +.header-container { + max-width: 1600px; + margin: 0 auto; + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 1rem; +} + +.logo { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.logo h1 { + font-family: var(--font-headers); + font-size: 1.75rem; + font-weight: 800; + letter-spacing: -0.5px; + background: linear-gradient(135deg, #ffffff 30%, var(--clr-primary) 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} + +.logo h1 span { + color: var(--text-muted); + -webkit-text-fill-color: var(--text-muted); + font-weight: 400; +} + +.glow-icon { + font-size: 1.5rem; + color: var(--clr-primary); + text-shadow: 0 0 10px rgba(14, 165, 233, 0.5); + animation: pulse 3s infinite alternate; +} + +.logo .badge { + font-size: 0.75rem; + font-weight: 600; + padding: 0.25rem 0.6rem; + background: rgba(31, 41, 55, 0.6); + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 1px; +} + +.header-stats { + display: flex; + gap: 1rem; +} + +.status-indicator { + display: inline-flex; + align-items: center; + padding: 0.4rem 0.8rem; + border-radius: var(--radius-sm); + font-size: 0.8rem; + font-weight: 700; + letter-spacing: 0.5px; + transition: all var(--transition-speed); +} + +.status-green { + background: var(--clr-emerald-glow); + color: #6ee7b7; + border: 1px solid var(--clr-emerald); + box-shadow: 0 0 10px rgba(16, 185, 129, 0.15); +} + +.status-amber { + background: var(--clr-amber-glow); + color: #fde047; + border: 1px solid var(--clr-amber); + box-shadow: 0 0 10px rgba(245, 158, 11, 0.15); +} + +.status-red { + background: var(--clr-rose-glow); + color: #fca5a5; + border: 1px solid var(--clr-rose); + box-shadow: 0 0 10px rgba(239, 68, 68, 0.15); +} + +/* MAIN LAYOUT */ +.main-container { + display: grid; + grid-template-columns: 300px 1fr 300px; + gap: 1.5rem; + max-width: 1600px; + width: 100%; + margin: 0 auto; + padding: 1.5rem 2rem; + flex: 1; +} + +/* SECTION CARDS */ +.section-card { + background: var(--bg-card); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + padding: 1.25rem; + margin-bottom: 1.25rem; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + transition: border-color var(--transition-speed); +} + +.section-card:hover { + border-color: rgba(255, 255, 255, 0.08); +} + +.card-header-row { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + padding-bottom: 0.5rem; +} + +.card-header-row h3 { + font-family: var(--font-headers); + font-size: 1.1rem; + font-weight: 600; + display: flex; + align-items: center; + gap: 0.5rem; + color: var(--text-highlight); +} + +/* INPUTS & CONTROLS */ +.form-group { + margin-bottom: 1rem; +} + +.form-group label { + display: block; + font-size: 0.8rem; + font-weight: 500; + text-transform: uppercase; + color: var(--text-muted); + margin-bottom: 0.4rem; + letter-spacing: 0.5px; +} + +input[type="text"], +input[type="number"], +select { + width: 100%; + padding: 0.65rem 0.9rem; + background-color: rgba(9, 13, 22, 0.6); + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + color: var(--text-main); + font-family: var(--font-body); + font-size: 0.9rem; + transition: border-color var(--transition-speed), box-shadow var(--transition-speed); +} + +input[type="text"]:focus, +input[type="number"]:focus, +select:focus { + outline: none; + border-color: var(--clr-primary); + box-shadow: 0 0 0 2px rgba(14, 165, 233, 0.2); +} + +/* CUSTOM RANGE SLIDER */ +.slider-label-row { + display: flex; + justify-content: space-between; + align-items: center; +} + +.slider-val { + font-weight: 700; + color: var(--clr-primary); + font-size: 0.9rem; +} + +input[type="range"] { + -webkit-appearance: none; + width: 100%; + background: transparent; + margin: 0.5rem 0; +} + +input[type="range"]:focus { + outline: none; +} + +input[type="range"]::-webkit-slider-runnable-track { + width: 100%; + height: 6px; + cursor: pointer; + background: var(--border-color); + border-radius: 3px; +} + +input[type="range"]::-webkit-slider-thumb { + height: 16px; + width: 16px; + border-radius: 50%; + background: var(--clr-primary); + cursor: pointer; + -webkit-appearance: none; + margin-top: -5px; + box-shadow: 0 0 10px rgba(14, 165, 233, 0.5); + transition: transform 0.1s; +} + +input[type="range"]::-webkit-slider-thumb:hover { + transform: scale(1.2); +} + +/* BUTTONS */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + padding: 0.65rem 1.25rem; + border-radius: var(--radius-sm); + font-family: var(--font-body); + font-weight: 600; + font-size: 0.9rem; + cursor: pointer; + transition: all var(--transition-speed); + border: 1px solid transparent; + width: 100%; +} + +.btn-sm { + padding: 0.45rem 0.9rem; + font-size: 0.8rem; +} + +.btn-primary { + background: linear-gradient(135deg, var(--clr-primary) 0%, #0284c7 100%); + color: white; + box-shadow: var(--glow-shadow); +} + +.btn-primary:hover { + transform: translateY(-1px); + filter: brightness(1.1); + box-shadow: 0 0 25px rgba(14, 165, 233, 0.4); +} + +.btn-danger { + background: linear-gradient(135deg, var(--clr-rose) 0%, #dc2626 100%); + color: white; +} + +.btn-danger:hover { + filter: brightness(1.1); +} + +.footer-btn { + background: transparent; + border: 1px dashed var(--border-color); + color: var(--text-muted); + padding: 0.75rem; + border-radius: var(--radius-sm); + cursor: pointer; + transition: all var(--transition-speed); + font-family: var(--font-body); + font-size: 0.85rem; + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + width: 100%; +} + +.footer-btn:hover { + border-color: var(--clr-rose); + color: var(--clr-rose); + background: rgba(239, 68, 68, 0.05); +} + +/* SIDEBAR COLUMN (LEFT / RIGHT) */ +.sidebar-column { + display: flex; + flex-direction: column; +} + +/* CENTER COLUMN METRICS */ +.chart-column { + display: flex; + flex-direction: column; +} + +.metrics-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 0.75rem; + margin-bottom: 1.25rem; +} + +.metric-card { + background: var(--bg-card); + border: 1px solid var(--border-color); + padding: 1rem 0.75rem; + border-radius: var(--radius-md); + display: flex; + flex-direction: column; + gap: 0.25rem; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); +} + +.m-lbl { + font-size: 0.7rem; + color: var(--text-muted); + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.m-val { + font-family: var(--font-headers); + font-size: 1.5rem; + font-weight: 800; + color: var(--text-highlight); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.m-sub { + font-size: 0.7rem; + color: var(--text-muted); +} + +.text-rose { color: #fca5a5; } +.text-amber { color: #fde047; } +.text-emerald { color: #6ee7b7; } + +/* SVG CHART WORKSPACE */ +.chart-card { + padding: 1.25rem; +} + +.chart-legend { + display: flex; + gap: 1rem; + font-size: 0.75rem; +} + +.legend-item { + display: flex; + align-items: center; + gap: 0.35rem; + color: var(--text-muted); +} + +.legend-dot { + width: 8px; + height: 8px; + border-radius: 50%; +} + +.dot-cyan { background-color: var(--clr-primary); } +.dot-emerald { background-color: var(--clr-emerald); } + +.chart-container { + width: 100%; + height: 300px; + margin-top: 1rem; + position: relative; +} + +#projection-svg { + width: 100%; + height: 100%; + display: block; +} + +/* CHART SVG SPECIFICS */ +.grid-line { + stroke: rgba(255, 255, 255, 0.05); + stroke-width: 1; +} + +.axis-line { + stroke: var(--border-color); + stroke-width: 1.5; +} + +.axis-label { + fill: var(--text-muted); + font-size: 10px; + font-family: var(--font-body); +} + +.line-cash { + fill: none; + stroke: var(--clr-primary); + stroke-width: 3; + stroke-linecap: round; + stroke-linejoin: round; + filter: drop-shadow(0px 0px 6px rgba(14, 165, 233, 0.3)); +} + +.line-revenue { + fill: none; + stroke: var(--clr-emerald); + stroke-width: 2.5; + stroke-linecap: round; + stroke-linejoin: round; + stroke-dasharray: 4,4; +} + +.chart-dot { + fill: #090d16; + stroke-width: 2.5; +} + +.dot-cash-depletion { + fill: var(--clr-rose); + stroke: #ffffff; + r: 6; + cursor: pointer; +} + +.chart-interactive-overlay { + fill: none; + pointer-events: all; +} + +.chart-tooltip { + position: absolute; + background: rgba(17, 24, 39, 0.95); + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + padding: 0.5rem; + font-size: 0.75rem; + color: var(--text-main); + pointer-events: none; + box-shadow: 0 4px 10px rgba(0,0,0,0.5); + z-index: 10; + display: none; +} + +/* DATA TABLE */ +.table-card { + margin-top: 1.25rem; +} + +.table-wrapper { + max-height: 250px; + overflow-y: auto; + border-radius: var(--radius-sm); + border: 1px solid var(--border-color); +} + +.forecast-table { + width: 100%; + border-collapse: collapse; + font-size: 0.8rem; + text-align: left; +} + +.forecast-table th, +.forecast-table td { + padding: 0.6rem 0.8rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.03); +} + +.forecast-table th { + background: rgba(17, 24, 39, 0.9); + color: var(--text-muted); + font-weight: 600; + position: sticky; + top: 0; + z-index: 1; +} + +.forecast-table tbody tr:hover { + background: rgba(255, 255, 255, 0.02); +} + +/* SCENARIO VAULT */ +.scenario-save-form { + padding: 0.75rem; + background: rgba(9, 13, 22, 0.4); + border: 1px solid rgba(255, 255, 255, 0.02); + border-radius: var(--radius-sm); + margin-bottom: 1rem; +} + +.saved-scenarios-section h5 { + font-family: var(--font-headers); + font-size: 0.85rem; + font-weight: 700; + text-transform: uppercase; + color: var(--text-muted); + margin-bottom: 0.5rem; +} + +.scenario-ul { + list-style: none; + display: flex; + flex-direction: column; + gap: 0.5rem; + max-height: 180px; + overflow-y: auto; +} + +.scenario-item { + display: flex; + align-items: center; + justify-content: space-between; + background: rgba(9, 13, 22, 0.6); + border: 1px solid var(--border-color); + padding: 0.4rem 0.6rem; + border-radius: var(--radius-sm); + font-size: 0.8rem; + cursor: pointer; + transition: all var(--transition-speed); +} + +.scenario-item:hover { + border-color: var(--clr-primary); + background: rgba(17, 24, 39, 0.8); +} + +.scenario-item.active-scenario { + border-color: var(--clr-emerald); + background: rgba(16, 185, 129, 0.05); +} + +.scenario-name { + font-weight: 600; + color: var(--text-highlight); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 180px; +} + +.btn-delete-scenario { + background: transparent; + border: none; + color: var(--text-muted); + cursor: pointer; + padding: 0.2rem; + transition: color var(--transition-speed); +} + +.btn-delete-scenario:hover { + color: var(--clr-rose); +} + +/* RECOMMENDATIONS FEED */ +.advisory-card { + flex: 1; +} + +.advisory-feed { + display: flex; + flex-direction: column; + gap: 0.75rem; + max-height: 300px; + overflow-y: auto; +} + +.advisory-item { + padding: 0.75rem; + border-radius: var(--radius-sm); + font-size: 0.8rem; + border-left: 4px solid transparent; + background: rgba(9, 13, 22, 0.4); +} + +.adv-success { + border-left-color: var(--clr-emerald); + background: rgba(16, 185, 129, 0.05); + color: #a7f3d0; +} + +.adv-warning { + border-left-color: var(--clr-amber); + background: rgba(245, 158, 11, 0.05); + color: #fef08a; +} + +.adv-danger { + border-left-color: var(--clr-rose); + background: rgba(239, 68, 68, 0.05); + color: #fecaca; +} + +.advisory-title { + font-weight: 700; + display: flex; + align-items: center; + gap: 0.4rem; + margin-bottom: 0.25rem; + font-size: 0.85rem; +} + +/* FOOTER */ +.app-footer { + border-top: 1px solid var(--border-color); + padding: 1.5rem 2rem; + text-align: center; + background-color: rgba(9, 13, 22, 0.8); + font-size: 0.85rem; + color: var(--text-muted); + margin-top: auto; +} + +.footer-container a { + color: var(--clr-primary); + text-decoration: none; + transition: color var(--transition-speed); +} + +.footer-container a:hover { + color: #38bdf8; + text-decoration: underline; +} + +/* ANIMATIONS */ +@keyframes pulse { + 0% { text-shadow: 0 0 8px rgba(14, 165, 233, 0.3); } + 100% { text-shadow: 0 0 16px rgba(14, 165, 233, 0.7); } +} + +/* RESPONSIVE LAYOUTS */ +@media (max-width: 1200px) { + .main-container { + grid-template-columns: 280px 1fr; + } + .advisory-card, .scenario-card { + grid-column: span 1; + } +} + +@media (max-width: 900px) { + .main-container { + grid-template-columns: 1fr; + padding: 1rem; + } + .sidebar-column, .chart-column { + grid-column: span 1; + } + .metrics-grid { + grid-template-columns: repeat(2, 1fr); + } + .header-container { + flex-direction: column; + align-items: flex-start; + } +} diff --git a/Projects/Startup Runway Calculator/thumbnail.svg b/Projects/Startup Runway Calculator/thumbnail.svg new file mode 100644 index 00000000..e59357ca --- /dev/null +++ b/Projects/Startup Runway Calculator/thumbnail.svg @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RunwaySync + + + FORECAST + + + + + + + DEFAULT ALIVE + + + + + + + RUNWAY REMAINING + 18.4 mo + + + + MONTHLY BURN + $27,000 + + + + CASH BALANCE + $500,000 + + + + REV GROWTH + +5.0% + + + + + + 24-Month Forecast + + + + Cash + + Rev + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Jun '26 + Dec '27 + May '28 + + + + + + Scenario Vault + + + + + ★ Baseline Plan + + + + Slow Burn + + + + + Growth Push + + + + + + + 💡 Runways stable. + + +