382 lines
17 KiB
JavaScript
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 }
|
|
}
|
|
}
|
|
});
|
|
};
|
|
});
|