2026-02-28 13:10:28 +00:00

382 lines
17 KiB
JavaScript

document.addEventListener('DOMContentLoaded', () => {
// 1. Mock Data
const startups = [
{
id: 1,
name: "FinFlow",
tagline: "Next-gen liquidity management for SMEs",
irr: 25,
pd: 5,
fragility: 15,
revenueConcentration: 20,
sector: "Fintech",
description: "FinFlow uses advanced AI to predict cash flow gaps and automate short-term financing for medium-sized enterprises. They have already secured partnerships with 3 major banks and have a growing user base.",
growth: "15% MoM",
funding: "$4.5M Series A",
burnRate: "$150k/mo"
},
{
id: 2,
name: "EcoPay",
tagline: "Sustainable payments infrastructure",
irr: 18,
pd: 3,
fragility: 10,
revenueConcentration: 15,
sector: "Payments",
description: "EcoPay provides carbon-neutral payment processing. They offset the carbon footprint of every transaction and offer lower fees for green certified businesses.",
growth: "8% MoM",
funding: "$2.2M Seed",
burnRate: "$60k/mo"
},
{
id: 3,
name: "CyberShield",
tagline: "Zero-trust cybersecurity for remote teams",
irr: 30,
pd: 12,
fragility: 25,
revenueConcentration: 40,
sector: "Cybersecurity",
description: "CyberShield offers a decentralized zero-trust network access (ZTNA) platform. High growth but operating in a highly competitive market with significant R&D spend.",
growth: "25% MoM",
funding: "$12M Series B",
burnRate: "$450k/mo"
},
{
id: 4,
name: "DataNexus",
tagline: "Federated learning for enterprise data",
irr: 22,
pd: 7,
fragility: 18,
revenueConcentration: 25,
sector: "Big Data/AI",
description: "DataNexus enables companies to train machine learning models on sensitive data without moving it, preserving privacy and compliance.",
growth: "12% MoM",
funding: "$6M Series A",
burnRate: "$200k/mo"
},
{
id: 5,
name: "HealthBridge",
tagline: "Telemedicine platform for rural healthcare",
irr: 20,
pd: 4,
fragility: 12,
revenueConcentration: 10,
sector: "Healthtech",
description: "HealthBridge connects rural clinics with specialist doctors via a low-bandwidth, high-security video and diagnostics platform.",
growth: "10% MoM",
funding: "$3.5M Series A",
burnRate: "$110k/mo"
}
];
// 2. Navigation Control
const sections = ['hero', 'profile', 'results', 'detail'];
const sidebar = document.getElementById('wrapper');
const sidebarToggle = document.getElementById('sidebarToggle');
const showSection = (sectionId) => {
sections.forEach(s => {
const el = document.getElementById(`${s}-section`);
if (el) el.classList.add('d-none');
const navLink = document.querySelector(`.nav-link[data-section="${s}"]`);
if (navLink) navLink.classList.remove('active');
});
const target = document.getElementById(`${sectionId}-section`);
if (target) {
target.classList.remove('d-none');
// Re-trigger fade-in animation
target.style.animation = 'none';
target.offsetHeight; // trigger reflow
target.style.animation = null;
}
const activeNav = document.querySelector(`.nav-link[data-section="${sectionId}"]`);
if (activeNav) activeNav.classList.add('active');
// Scroll to top
window.scrollTo(0, 0);
};
// Sidebar Toggle
if (sidebarToggle) {
sidebarToggle.addEventListener('click', (e) => {
e.preventDefault();
sidebar.classList.toggle('toggled');
});
}
// Nav Link Clicks
document.querySelectorAll('.nav-link, .navigate-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
e.preventDefault();
const target = btn.dataset.section || btn.dataset.target;
showSection(target);
});
});
// 3. Profile Setup
const minIrrInput = document.getElementById('min-irr');
const maxPdInput = document.getElementById('max-pd');
const irrValBadge = document.getElementById('irr-val');
const pdValBadge = document.getElementById('pd-val');
minIrrInput.addEventListener('input', () => {
irrValBadge.textContent = `${minIrrInput.value}%`;
});
maxPdInput.addEventListener('input', () => {
pdValBadge.textContent = `${maxPdInput.value}%`;
});
// 4. Matching Logic
const matchBtn = document.getElementById('match-startups-btn');
const matchesContainer = document.getElementById('matches-container');
matchBtn.addEventListener('click', () => {
const minIrr = parseFloat(minIrrInput.value);
const maxPd = parseFloat(maxPdInput.value);
// Filter & Calculate RAR
let matches = startups.map(s => {
return {
...s,
rar: (s.irr * (1 - s.pd/100)).toFixed(2)
};
});
// Filter based on user constraints
matches = matches.filter(s => s.irr >= minIrr && s.pd <= maxPd);
// Sort by RAR descending
matches.sort((a, b) => b.rar - a.rar);
// Take top 3
const topMatches = matches.slice(0, 3);
renderMatches(topMatches);
showSection('results');
});
const renderMatches = (matches) => {
matchesContainer.innerHTML = '';
if (matches.length === 0) {
matchesContainer.innerHTML = `
<div class="col-12 text-center py-5">
<div class="h3 text-secondary">No startups match your current criteria.</div>
<p>Try lowering your IRR requirement or increasing your PD allowance.</p>
</div>
`;
return;
}
matches.forEach((s, index) => {
const card = document.createElement('div');
card.className = 'col-md-4';
card.innerHTML = `
<div class="card h-100 rounded-4 overflow-hidden border-secondary">
<div class="card-header bg-primary-subtle border-0 py-3 text-center">
<span class="badge bg-primary rounded-pill px-3 py-2">Match #${index + 1}</span>
</div>
<div class="card-body p-4 text-center">
<h3 class="fw-bold mb-1">${s.name}</h3>
<span class="text-secondary small d-block mb-3">${s.sector}</span>
<div class="rar-box p-3 bg-dark rounded-4 mb-4 border border-secondary">
<span class="text-secondary small d-block">RAR Score</span>
<span class="display-6 fw-bold text-primary">${s.rar}%</span>
</div>
<div class="row g-2 mb-4">
<div class="col-6">
<div class="p-2 border border-secondary rounded-3 text-center">
<span class="text-secondary small d-block">Expected IRR</span>
<span class="fw-bold">${s.irr}%</span>
</div>
</div>
<div class="col-6">
<div class="p-2 border border-secondary rounded-3 text-center">
<span class="text-secondary small d-block">12M PD</span>
<span class="fw-bold">${s.pd}%</span>
</div>
</div>
</div>
<button class="btn btn-outline-light w-100 rounded-pill py-2 view-details-btn" data-id="${s.id}">
View Details <i class="fas fa-chevron-right ms-2"></i>
</button>
</div>
</div>
`;
matchesContainer.appendChild(card);
});
// Attach listeners to "View Details" buttons
document.querySelectorAll('.view-details-btn').forEach(btn => {
btn.addEventListener('click', () => {
const startupId = parseInt(btn.dataset.id);
showStartupDetail(startupId);
});
});
};
// 5. Detail View
const detailContent = document.getElementById('startup-detail-content');
const showStartupDetail = (id) => {
const s = startups.find(item => item.id === id);
const rar = (s.irr * (1 - s.pd/100)).toFixed(2);
detailContent.innerHTML = `
<div class="row g-4">
<div class="col-lg-8">
<div class="card bg-dark-subtle border-secondary rounded-4 p-4 p-md-5 mb-4">
<div class="d-flex flex-wrap align-items-center justify-content-between mb-4 gap-3">
<div>
<h1 class="display-4 fw-bold mb-1">${s.name}</h1>
<p class="lead text-secondary mb-0">${s.tagline}</p>
</div>
<div class="text-end">
<span class="badge bg-primary-subtle text-primary border border-primary-subtle px-3 py-2 rounded-pill h5 mb-0">${s.sector}</span>
</div>
</div>
<div class="mb-5">
<h4 class="fw-bold mb-3 border-start border-primary border-4 ps-3">Executive Summary</h4>
<p class="text-secondary" style="line-height: 1.8;">${s.description}</p>
</div>
<div class="row g-3 mb-5">
<div class="col-sm-6 col-xl-3">
<div class="p-3 bg-dark rounded-4 border border-secondary text-center h-100">
<span class="text-secondary small d-block mb-1">Growth</span>
<span class="h5 fw-bold mb-0 text-success">${s.growth}</span>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="p-3 bg-dark rounded-4 border border-secondary text-center h-100">
<span class="text-secondary small d-block mb-1">Funding</span>
<span class="h5 fw-bold mb-0">${s.funding}</span>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="p-3 bg-dark rounded-4 border border-secondary text-center h-100">
<span class="text-secondary small d-block mb-1">Burn Rate</span>
<span class="h5 fw-bold mb-0 text-danger">${s.burnRate}</span>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="p-3 bg-dark rounded-4 border border-secondary text-center h-100">
<span class="text-secondary small d-block mb-1">RAR Score</span>
<span class="h5 fw-bold mb-0 text-primary">${rar}%</span>
</div>
</div>
</div>
<div>
<h4 class="fw-bold mb-4">Stress-Test Performance</h4>
<div class="chart-container" style="height: 300px;">
<canvas id="stressTestChart"></canvas>
</div>
<p class="small text-secondary mt-3"><i class="fas fa-info-circle me-1"></i> Simulated performance under varying market conditions (Fragility Score: ${s.fragility}).</p>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card bg-dark-subtle border-secondary rounded-4 p-4 mb-4">
<h4 class="fw-bold mb-4">Risk Metrics</h4>
<div class="mb-4">
<div class="d-flex justify-content-between mb-2">
<span class="text-secondary">Fragility Score</span>
<span class="fw-bold text-danger">${s.fragility}</span>
</div>
<div class="progress bg-dark" style="height: 8px;">
<div class="progress-bar bg-danger" role="progressbar" style="width: ${s.fragility}%"></div>
</div>
</div>
<div class="mb-4">
<div class="d-flex justify-content-between mb-2">
<span class="text-secondary">Revenue Concentration</span>
<span class="fw-bold text-warning">${s.revenueConcentration}%</span>
</div>
<div class="progress bg-dark" style="height: 8px;">
<div class="progress-bar bg-warning" role="progressbar" style="width: ${s.revenueConcentration}%"></div>
</div>
</div>
<div class="mb-0">
<div class="d-flex justify-content-between mb-2">
<span class="text-secondary">Expected 12M PD</span>
<span class="fw-bold text-info">${s.pd}%</span>
</div>
<div class="progress bg-dark" style="height: 8px;">
<div class="progress-bar bg-info" role="progressbar" style="width: ${s.pd}%"></div>
</div>
</div>
</div>
<div class="card bg-primary rounded-4 p-4 border-0">
<h4 class="fw-bold text-white mb-3">Interested in ${s.name}?</h4>
<p class="text-white-50 mb-4">Get direct access to their data room and contact the founding team.</p>
<button class="btn btn-light w-100 rounded-pill fw-bold py-2 bg-white text-primary">Request Access</button>
</div>
</div>
</div>
`;
showSection('detail');
renderStressChart(s);
};
const renderStressChart = (startup) => {
const ctx = document.getElementById('stressTestChart').getContext('2d');
// Generate mock stress data based on startup metrics
const baseReturn = startup.irr;
const labels = ['Stable', 'Moderate Volatility', 'High Volatility', 'Economic Downturn', 'Black Swan'];
const data = [
baseReturn,
baseReturn * (1 - startup.fragility/200),
baseReturn * (1 - startup.fragility/100),
baseReturn * (1 - startup.fragility/50),
baseReturn * (1 - startup.fragility/25)
];
new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [{
label: 'Projected IRR (%)',
data: data,
borderColor: '#0d6efd',
backgroundColor: 'rgba(13, 110, 253, 0.1)',
fill: true,
tension: 0.4,
pointRadius: 6,
pointBackgroundColor: '#0d6efd',
borderWidth: 3
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: false,
grid: { color: 'rgba(255, 255, 255, 0.05)' },
ticks: { color: '#8b949e' }
},
x: {
grid: { display: false },
ticks: { color: '#8b949e' }
}
},
plugins: {
legend: { display: false }
}
}
});
};
});