deepakpathania's picture
Update index.html
34a6042 verified
Raw
History Blame Contribute Delete
62.3 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>XLM Product Performance Dashboard</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400;500;600;700&display=swap');
:root{--bg:#0d1117;--bg2:#161b22;--card:#1c2333;--card2:#212940;--bdr:#30363d;--t1:#e6edf3;--t2:#8b949e;--t3:#6e7681;--blue:#58a6ff;--green:#3fb950;--yellow:#d29922;--red:#f85149;--purple:#bc8cff;--cyan:#39c5cf;}
*{margin:0;padding:0;box-sizing:border-box;}
body{font-family:'DM Sans',sans-serif;background:var(--bg);color:var(--t1);font-size:13px;min-height:100vh;}
.sidebar{position:fixed;left:0;top:0;bottom:0;width:196px;background:var(--bg2);border-right:1px solid var(--bdr);z-index:100;display:flex;flex-direction:column;overflow-y:auto;}
.logo{display:flex;align-items:center;gap:10px;padding:16px;border-bottom:1px solid var(--bdr);}
.logo-icon{width:36px;height:36px;background:linear-gradient(135deg,var(--blue),var(--purple));border-radius:8px;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:16px;color:#fff;flex-shrink:0;}
.logo-text{font-size:11px;color:var(--t2);line-height:1.3;}
.logo-text b{display:block;color:var(--t1);font-size:12px;}
.nav{padding:8px 0;flex:1;}
.nav-item{display:flex;align-items:center;gap:9px;padding:9px 16px;cursor:pointer;color:var(--t2);font-size:12px;font-weight:500;border-left:3px solid transparent;transition:all .15s;user-select:none;}
.nav-item:hover{background:var(--card);color:var(--t1);}
.nav-item.active{background:rgba(88,166,255,.1);color:var(--blue);border-left-color:var(--blue);}
.nav-icon{font-size:14px;width:18px;text-align:center;flex-shrink:0;}
.main{margin-left:196px;}
.header{background:var(--bg2);border-bottom:1px solid var(--bdr);padding:14px 22px;display:flex;align-items:center;justify-content:space-between;position:sticky;top:0;z-index:50;}
.header-left h1{font-size:19px;font-weight:700;}
.header-left p{font-size:11px;color:var(--t2);margin-top:2px;}
.header-right{display:flex;align-items:center;gap:8px;}
.dropdown{position:relative;}
.btn{display:inline-flex;align-items:center;gap:6px;padding:7px 13px;border-radius:7px;font-size:12px;font-weight:500;cursor:pointer;border:1px solid var(--bdr);background:var(--card);color:var(--t1);transition:all .15s;white-space:nowrap;font-family:inherit;}
.btn:hover{background:var(--card2);border-color:var(--blue);}
.btn.primary{background:var(--blue);border-color:var(--blue);color:#fff;}
.btn.primary:hover{background:#4493e0;}
.dropdown-menu{display:none;position:absolute;top:calc(100% + 6px);right:0;background:var(--card);border:1px solid var(--bdr);border-radius:8px;min-width:190px;z-index:999;box-shadow:0 8px 24px rgba(0,0,0,.5);overflow:hidden;}
.dropdown-menu.open{display:block;}
.dropdown-item{padding:9px 14px;font-size:12px;cursor:pointer;color:var(--t1);transition:background .12s;}
.dropdown-item:hover{background:var(--card2);}
.dropdown-item.selected{color:var(--blue);font-weight:600;}
.content{padding:18px 22px;}
.section{display:none;}
.section.active{display:block;}
.kpi-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:11px;margin-bottom:14px;}
.kpi-card{background:var(--card);border:1px solid var(--bdr);border-radius:10px;padding:13px 14px;transition:all .15s;}
.kpi-card:hover{border-color:var(--blue);transform:translateY(-1px);}
.kpi-icon{width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:15px;margin-bottom:8px;}
.kpi-label{font-size:10.5px;color:var(--t2);margin-bottom:3px;}
.kpi-value{font-size:20px;font-weight:700;line-height:1;}
.kpi-change{font-size:10.5px;margin-top:5px;}
.up{color:var(--green);}
.down{color:var(--red);}
.neutral{color:var(--t3);}
.g3{display:grid;grid-template-columns:repeat(3,1fr);gap:13px;margin-bottom:13px;}
.g4{display:grid;grid-template-columns:2fr 1.1fr 1.1fr 1fr;gap:13px;margin-bottom:13px;}
.g2{display:grid;grid-template-columns:1fr 1fr;gap:13px;margin-bottom:13px;}
.card{background:var(--card);border:1px solid var(--bdr);border-radius:10px;padding:15px;}
.card-title{font-size:12px;font-weight:600;margin-bottom:13px;display:flex;align-items:center;justify-content:space-between;}
.card-title .sub{color:var(--t3);font-weight:400;font-size:10.5px;}
.donut-wrap{display:flex;align-items:center;gap:14px;}
.donut-rel{position:relative;flex-shrink:0;}
.donut-center{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;pointer-events:none;}
.donut-center .big{font-size:20px;font-weight:700;line-height:1;}
.donut-center .sm{font-size:10px;color:var(--t3);}
.legend{display:flex;flex-direction:column;gap:5px;}
.li{display:flex;align-items:center;gap:7px;font-size:11px;}
.dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;}
.lv{margin-left:auto;font-weight:600;font-size:11px;}
.table-wrap{overflow-x:auto;}
table{width:100%;border-collapse:collapse;font-size:11.5px;}
th{background:var(--bg2);padding:8px 10px;text-align:left;color:var(--t2);font-weight:600;font-size:10.5px;white-space:nowrap;border-bottom:1px solid var(--bdr);cursor:pointer;user-select:none;}
th:hover{color:var(--t1);}
th.sorted{color:var(--blue);}
td{padding:9px 10px;border-bottom:1px solid rgba(48,54,61,.4);white-space:nowrap;}
tr:hover td{background:rgba(255,255,255,.02);}
.badge{display:inline-block;padding:2px 9px;border-radius:20px;font-size:10px;font-weight:600;}
.b-high{background:rgba(248,81,73,.12);color:var(--red);border:1px solid rgba(248,81,73,.3);}
.b-medium{background:rgba(210,153,34,.12);color:var(--yellow);border:1px solid rgba(210,153,34,.3);}
.b-low{background:rgba(63,185,80,.12);color:var(--green);border:1px solid rgba(63,185,80,.3);}
.hl{display:flex;flex-direction:column;gap:7px;}
.hl-item{display:flex;align-items:flex-start;gap:8px;padding:8px 10px;border-radius:8px;font-size:11px;line-height:1.45;}
.hl-good{background:rgba(63,185,80,.07);border:1px solid rgba(63,185,80,.2);}
.hl-bad{background:rgba(248,81,73,.07);border:1px solid rgba(248,81,73,.2);}
.hl-neu{background:rgba(88,166,255,.07);border:1px solid rgba(88,166,255,.2);}
.hl-ico{font-size:14px;flex-shrink:0;margin-top:1px;}
.note{font-size:10px;color:var(--t3);text-align:center;padding:10px;border-top:1px solid var(--bdr);margin-top:6px;}
.filter-row{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:14px;align-items:center;}
.filter-label{font-size:11px;color:var(--t2);}
.filter-btn{padding:5px 11px;border-radius:20px;border:1px solid var(--bdr);background:var(--bg2);color:var(--t2);font-size:11px;cursor:pointer;transition:all .15s;}
.filter-btn:hover,.filter-btn.on{background:rgba(88,166,255,.12);border-color:var(--blue);color:var(--blue);}
.toast{position:fixed;bottom:24px;right:24px;background:var(--card);border:1px solid var(--bdr);border-radius:8px;padding:12px 18px;font-size:12px;z-index:400;transform:translateY(80px);opacity:0;transition:all .3s;}
.toast.show{transform:translateY(0);opacity:1;}
.ch{position:relative;}
select.fselect{background:var(--bg2);border:1px solid var(--bdr);color:var(--t1);font-size:11px;padding:5px 8px;border-radius:6px;cursor:pointer;font-family:inherit;}
</style>
</head>
<body>
<aside class="sidebar">
<div class="logo">
<div class="logo-icon" style="background: transparent;">
<img src="cadence-logo-black.png" alt="Cadence Logo" style="width: 100%; height: auto; display: block;">
</div>
</div>
<nav class="nav" id="mainNav">
<div class="nav-item active" data-sec="overview"><span class="nav-icon">⬛</span>Overview</div>
<div class="nav-item" data-sec="perf-summary"><span class="nav-icon">πŸ“Š</span>Performance Summary</div>
<div class="nav-item" data-sec="customer-perf"><span class="nav-icon">πŸ‘₯</span>Customer Performance</div>
<div class="nav-item" data-sec="product-quality"><span class="nav-icon">πŸ”¨</span>Product Quality</div>
<div class="nav-item" data-sec="engagement"><span class="nav-icon">πŸ’¬</span>Engagement &amp; Sentiment</div>
<div class="nav-item" data-sec="pps-section"><span class="nav-icon">πŸ“ˆ</span>Product Perf Score</div>
<div class="nav-item" data-sec="risk-gaps"><span class="nav-icon">⚠️</span>Risk &amp; Gaps</div>
<div class="nav-item" data-sec="revenue-section"><span class="nav-icon">πŸ’°</span>Revenue</div>
<div class="nav-item" data-sec="highlights"><span class="nav-icon">πŸ’‘</span>Highlights &amp; Insights</div>
<div class="nav-item" data-sec="data-explorer"><span class="nav-icon">πŸ”</span>Data Explorer</div>
<div class="nav-item" data-sec="definitions"><span class="nav-icon">πŸ“–</span>Definitions</div>
</nav>
</aside>
<main class="main">
<header class="header">
<div class="header-left">
<h1>XLM Product Performance Dashboard</h1>
<p>Executive Overview</p>
</div>
<div class="header-right">
<div class="dropdown" id="ddQ">
<button class="btn" id="btnQ">πŸ“… <span id="lblQ">Q4 FY2025 (Oct–Dec 2025)</span> β–Ύ</button>
<div class="dropdown-menu" id="menuQ">
<div class="dropdown-item" data-q="Q1-2024">Q1 FY2024 (Jan–Mar 2024)</div>
<div class="dropdown-item" data-q="Q2-2024">Q2 FY2024 (Apr–Jun 2024)</div>
<div class="dropdown-item" data-q="Q3-2024">Q3 FY2024 (Jul–Sep 2024)</div>
<div class="dropdown-item" data-q="Q4-2024">Q4 FY2024 (Oct–Dec 2024)</div>
<div class="dropdown-item" data-q="Q1-2025">Q1 FY2025 (Jan–Mar 2025)</div>
<div class="dropdown-item" data-q="Q2-2025">Q2 FY2025 (Apr–Jun 2025)</div>
<div class="dropdown-item" data-q="Q3-2025">Q3 FY2025 (Jul–Sep 2025)</div>
<div class="dropdown-item selected" data-q="Q4-2025">Q4 FY2025 (Oct–Dec 2025)</div>
</div>
</div>
<div class="dropdown" id="ddC">
<button class="btn" id="btnC">Compare to <span id="lblC">Q3 FY2025</span> β–Ύ</button>
<div class="dropdown-menu" id="menuC">
<div class="dropdown-item" data-c="Q1-2024">Q1 FY2024</div>
<div class="dropdown-item" data-c="Q2-2024">Q2 FY2024</div>
<div class="dropdown-item" data-c="Q3-2024">Q3 FY2024</div>
<div class="dropdown-item" data-c="Q4-2024">Q4 FY2024</div>
<div class="dropdown-item" data-c="Q1-2025">Q1 FY2025</div>
<div class="dropdown-item" data-c="Q2-2025">Q2 FY2025</div>
<div class="dropdown-item selected" data-c="Q3-2025">Q3 FY2025</div>
</div>
</div>
<div class="dropdown" id="ddExp">
<button class="btn primary" id="btnExp">⬇ Export β–Ύ</button>
<div class="dropdown-menu" id="menuExp">
<div class="dropdown-item" id="doCSV">πŸ“„ Export as CSV</div>
<div class="dropdown-item" id="doJSON">πŸ“‹ Export as JSON</div>
<div class="dropdown-item" id="doPrint">πŸ–¨ Print / Save as PDF</div>
</div>
</div>
</div>
</header>
<div class="content">
<!-- OVERVIEW -->
<div class="section active" id="overview">
<div class="kpi-grid" id="kpiGrid"></div>
<div class="g3">
<div class="card">
<div class="card-title">Product Performance Score Trend <span class="sub">All quarters</span></div>
<div class="ch" style="height:175px"><canvas id="cPPS"></canvas></div>
</div>
<div class="card">
<div class="card-title">Score Breakdown <span class="sub" id="sbTitle"></span></div>
<div class="donut-wrap" style="height:175px">
<div class="donut-rel" style="width:148px;height:148px">
<canvas id="cBreak" width="148" height="148"></canvas>
<div class="donut-center"><div class="big" id="dBig">β€”</div><div class="sm">/100</div></div>
</div>
<div class="legend">
<div class="li"><div class="dot" style="background:var(--blue)"></div>Engagement<span class="lv" id="vEng">β€”</span></div>
<div class="li"><div class="dot" style="background:var(--green)"></div>Sentiment<span class="lv" id="vSent">β€”</span></div>
<div class="li"><div class="dot" style="background:var(--yellow)"></div>Satisfaction<span class="lv" id="vSat">β€”</span></div>
<div style="font-size:10px;color:var(--t3);margin-top:8px;line-height:1.6"><b style="color:var(--t2)">PPS Formula:</b><br>(EngagementΓ—0.6)Γ—Sentiment<br>Γ—Satisfaction β€” Normalized 0–100</div>
</div>
</div>
</div>
<div class="card">
<div class="card-title">Performance Scores Over Time</div>
<div class="ch" style="height:175px"><canvas id="cPOT"></canvas></div>
</div>
</div>
<div class="g4">
<div class="card">
<div class="card-title">Revenue Trend <span class="sub">$ in Millions</span></div>
<div class="ch" style="height:165px"><canvas id="cRev"></canvas></div>
</div>
<div class="card">
<div class="card-title">Business Risk <span class="sub" id="riskSubTitle"></span></div>
<div style="display:flex;flex-direction:column;align-items:center;gap:8px;height:165px;justify-content:center">
<div class="donut-rel" style="width:105px;height:105px">
<canvas id="cRisk" width="105" height="105"></canvas>
<div class="donut-center"><div class="big" style="font-size:16px" id="riskN">10</div><div class="sm">Customers</div></div>
</div>
<div style="display:flex;gap:8px;flex-wrap:wrap;justify-content:center">
<div class="li"><div class="dot" style="background:var(--red)"></div>High<span class="lv" style="margin-left:4px" id="rH">β€”</span></div>
<div class="li"><div class="dot" style="background:var(--yellow)"></div>Med<span class="lv" style="margin-left:4px" id="rM">β€”</span></div>
<div class="li"><div class="dot" style="background:var(--green)"></div>Low<span class="lv" style="margin-left:4px" id="rL">β€”</span></div>
</div>
</div>
</div>
<div class="card">
<div class="card-title">Gap vs OT <span class="sub" id="gapSubTitle"></span></div>
<div style="display:flex;flex-direction:column;align-items:center;gap:8px;height:165px;justify-content:center">
<div class="donut-rel" style="width:105px;height:105px">
<canvas id="cGap" width="105" height="105"></canvas>
<div class="donut-center"><div class="big" style="font-size:16px" id="gapN">10</div><div class="sm">Customers</div></div>
</div>
<div style="display:flex;gap:8px;flex-wrap:wrap;justify-content:center">
<div class="li"><div class="dot" style="background:var(--red)"></div>&gt;15%<span class="lv" style="margin-left:4px" id="gH">β€”</span></div>
<div class="li"><div class="dot" style="background:var(--yellow)"></div>5–15%<span class="lv" style="margin-left:4px" id="gM">β€”</span></div>
<div class="li"><div class="dot" style="background:var(--green)"></div>&lt;5%<span class="lv" style="margin-left:4px" id="gL">β€”</span></div>
</div>
</div>
</div>
<div class="card">
<div class="card-title">Quarter Highlights</div>
<div class="hl" id="hlBox"></div>
</div>
</div>
<div class="card" style="margin-bottom:12px">
<div class="card-title">Customer Performance Overview <span class="sub" id="tblTitle"></span></div>
<div class="table-wrap">
<table id="mainTable">
<thead><tr>
<th data-col="name">Customer <span>↕</span></th>
<th data-col="quarter">Quarter <span>↕</span></th>
<th data-col="revenue">Revenue <span>↕</span></th>
<th data-col="pps">PPS (0–100) <span>↕</span></th>
<th data-col="build">Build <span>↕</span></th>
<th data-col="sim">Simulation <span>↕</span></th>
<th data-col="eng">Engagement <span>↕</span></th>
<th data-col="sent">Sentiment <span>↕</span></th>
<th data-col="sat">Satisfaction <span>↕</span></th>
<th data-col="gap">Gap vs OT <span>↕</span></th>
<th data-col="risk">Risk <span>↕</span></th>
</tr></thead>
<tbody id="mainTbody"></tbody>
</table>
</div>
</div>
<div class="note">Note: Scores averaged from weekly data. PPS = (EngagementΓ—0.6)Γ—SentimentΓ—Satisfaction (Normalized 0–100)</div>
</div>
<!-- PERFORMANCE SUMMARY -->
<div class="section" id="perf-summary">
<div class="card" style="margin-bottom:13px">
<div class="card-title">Quarter-over-Quarter Summary <span class="sub">β–Ά = current quarter | β–· = compare quarter</span></div>
<div class="table-wrap"><table><thead><tr>
<th>Quarter</th><th>Revenue ($M)</th><th>PPS</th><th>Engagement</th><th>Sentiment</th><th>Satisfaction</th><th>Build</th><th>Simulation</th><th>Gap vs OT</th>
</tr></thead><tbody id="perfBody"></tbody></table></div>
</div>
<div class="g2">
<div class="card"><div class="card-title">Revenue by Quarter</div><div class="ch" style="height:220px"><canvas id="cRevPS"></canvas></div></div>
<div class="card"><div class="card-title">PPS vs Engagement Over Time</div><div class="ch" style="height:220px"><canvas id="cPPSPS"></canvas></div></div>
</div>
</div>
<!-- CUSTOMER PERFORMANCE -->
<div class="section" id="customer-perf">
<div class="filter-row">
<span class="filter-label">Filter by Risk:</span>
<div class="filter-btn on" data-risk="All">All</div>
<div class="filter-btn" data-risk="High">High</div>
<div class="filter-btn" data-risk="Medium">Medium</div>
<div class="filter-btn" data-risk="Low">Low</div>
</div>
<div class="card" style="margin-bottom:13px">
<div class="card-title">Customer Detail <span class="sub" id="cpTitle"></span></div>
<div class="table-wrap"><table><thead><tr>
<th>Customer</th><th>Revenue</th><th>PPS</th><th>Build</th><th>Simulation</th><th>Engagement</th><th>Sentiment</th><th>Satisfaction</th><th>Gap vs OT</th><th>Risk</th>
</tr></thead><tbody id="cpBody"></tbody></table></div>
</div>
<div class="card"><div class="card-title">Revenue by Customer <span class="sub" id="cpRevTitle"></span></div><div class="ch" style="height:240px"><canvas id="cCustRev"></canvas></div></div>
</div>
<!-- PRODUCT QUALITY -->
<div class="section" id="product-quality">
<div class="g2">
<div class="card"><div class="card-title">Build Performance Trend</div><div class="ch" style="height:220px"><canvas id="cBuild"></canvas></div></div>
<div class="card"><div class="card-title">Simulation Performance Trend</div><div class="ch" style="height:220px"><canvas id="cSim"></canvas></div></div>
</div>
<div class="card"><div class="card-title">Build vs Simulation by Customer <span class="sub" id="bsTitle"></span></div><div class="ch" style="height:240px"><canvas id="cBuildSim"></canvas></div></div>
</div>
<!-- ENGAGEMENT & SENTIMENT -->
<div class="section" id="engagement">
<div class="g2">
<div class="card"><div class="card-title">Engagement Score Trend</div><div class="ch" style="height:220px"><canvas id="cEng"></canvas></div></div>
<div class="card"><div class="card-title">Sentiment &amp; Satisfaction Trend</div><div class="ch" style="height:220px"><canvas id="cSentSat"></canvas></div></div>
</div>
<div class="card"><div class="card-title">Engagement by Customer <span class="sub" id="engCTitle"></span></div><div class="ch" style="height:230px"><canvas id="cEngCust"></canvas></div></div>
</div>
<!-- PPS SECTION -->
<div class="section" id="pps-section">
<div class="g2">
<div class="card"><div class="card-title">PPS Trend (All Quarters)</div><div class="ch" style="height:220px"><canvas id="cPPS2"></canvas></div></div>
<div class="card"><div class="card-title">PPS by Customer <span class="sub" id="ppsCTitle"></span></div><div class="ch" style="height:220px"><canvas id="cPPSCust"></canvas></div></div>
</div>
</div>
<!-- RISK & GAPS -->
<div class="section" id="risk-gaps">
<div class="g2">
<div class="card"><div class="card-title">Business Risk Distribution <span class="sub" id="riskD2Title"></span></div><div class="ch" style="height:220px"><canvas id="cRisk2"></canvas></div></div>
<div class="card"><div class="card-title">Gap vs OT by Customer <span class="sub" id="gapCTitle"></span></div><div class="ch" style="height:220px"><canvas id="cGapCust"></canvas></div></div>
</div>
</div>
<!-- REVENUE -->
<div class="section" id="revenue-section">
<div class="g2">
<div class="card"><div class="card-title">Revenue Trend (All Quarters)</div><div class="ch" style="height:220px"><canvas id="cRev2"></canvas></div></div>
<div class="card"><div class="card-title">Revenue by Customer <span class="sub" id="revCTitle"></span></div><div class="ch" style="height:220px"><canvas id="cRevCust"></canvas></div></div>
</div>
</div>
<!-- HIGHLIGHTS -->
<div class="section" id="highlights">
<div class="g2">
<div class="card"><div class="card-title">βœ… Highlights β€” Good <span class="sub" id="hlgTitle"></span></div><div class="hl" id="hlGood"></div></div>
<div class="card"><div class="card-title">⚠️ Lowlights β€” Bad <span class="sub" id="hlbTitle"></span></div><div class="hl" id="hlBad"></div></div>
</div>
<div class="card" style="margin-top:13px"><div class="card-title">πŸ”΅ Awareness β€” Neutral <span class="sub" id="hlnTitle"></span></div><div id="hlNeu" style="display:grid;grid-template-columns:1fr 1fr;gap:7px;"></div></div>
</div>
<!-- DATA EXPLORER -->
<div class="section" id="data-explorer">
<div class="filter-row" id="fqRow">
<span class="filter-label">Quarter:</span>
<div class="filter-btn on" data-fq="All">All</div>
<div class="filter-btn" data-fq="Q1-2024">Q1'24</div>
<div class="filter-btn" data-fq="Q2-2024">Q2'24</div>
<div class="filter-btn" data-fq="Q3-2024">Q3'24</div>
<div class="filter-btn" data-fq="Q4-2024">Q4'24</div>
<div class="filter-btn" data-fq="Q1-2025">Q1'25</div>
<div class="filter-btn" data-fq="Q2-2025">Q2'25</div>
<div class="filter-btn" data-fq="Q3-2025">Q3'25</div>
<div class="filter-btn" data-fq="Q4-2025">Q4'25</div>
<span class="filter-label" style="margin-left:10px">Customer:</span>
<select id="custSel" class="fselect"></select>
</div>
<div class="card"><div class="card-title">Raw Weekly Data <span class="sub" id="expCount"></span></div>
<div class="table-wrap" style="max-height:480px;overflow-y:auto">
<table><thead><tr>
<th>Quarter</th><th>Week</th><th>Customer</th><th>Revenue</th><th>PPS</th><th>Build</th><th>Sim</th><th>Engagement</th><th>Sentiment</th><th>Satisfaction</th><th>Gap vs OT</th><th>Risk</th>
</tr></thead><tbody id="expBody"></tbody></table>
</div>
</div>
</div>
<!-- DEFINITIONS -->
<div class="section" id="definitions">
<div class="card"><div class="card-title">Metric Definitions</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:18px;font-size:12px">
<div>
<div style="font-weight:600;color:var(--blue);margin-bottom:10px">Core Scores</div>
<div style="display:flex;flex-direction:column;gap:10px">
<div><b>Product Performance Score (PPS)</b><br><span style="color:var(--t2)">Weighted composite: (EngagementΓ—0.6)Γ—SentimentΓ—Satisfaction, normalized 0–100.</span></div>
<div><b>Engagement Score (0–100)</b><br><span style="color:var(--t2)">Customer interaction and product usage level. 100 = maximum engagement.</span></div>
<div><b>Sentiment Score (βˆ’5 to +5)</b><br><span style="color:var(--t2)">Customer feedback sentiment. Positive = favorable attitude toward product.</span></div>
<div><b>Satisfaction Score (βˆ’5 to +5)</b><br><span style="color:var(--t2)">Overall satisfaction with quality and support. Positive = satisfied.</span></div>
</div>
</div>
<div>
<div style="font-weight:600;color:var(--green);margin-bottom:10px">Quality &amp; Risk</div>
<div style="display:flex;flex-direction:column;gap:10px">
<div><b>Build Performance (0–100)</b><br><span style="color:var(--t2)">Quality of product build process. Higher = fewer build issues.</span></div>
<div><b>Simulation Performance (0–100)</b><br><span style="color:var(--t2)">Accuracy and efficiency of simulation runs. Higher = better outcomes.</span></div>
<div><b>Gap vs OT (%)</b><br><span style="color:var(--t2)">Deviation from on-time targets. &lt;5% = Low risk, 5–15% = Medium, &gt;15% = High.</span></div>
<div><b>Overall Business Risk</b><br><span style="color:var(--t2)">Composite rating (High / Medium / Low) derived from Gap vs OT and engagement.</span></div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<div class="toast" id="toast"></div>
<script>
// ════════════ DATA ════════════
const QS = ['Q1-2024','Q2-2024','Q3-2024','Q4-2024','Q1-2025','Q2-2025','Q3-2025','Q4-2025'];
const QL = ['Q1\'24','Q2\'24','Q3\'24','Q4\'24','Q1\'25','Q2\'25','Q3\'25','Q4\'25'];
const CUSTS = ['Alpha Semicon','Apex Silicon','Fusion Microsystems','NextGen Compute','Nova Chips','Orion Design Labs','Quantum Logic','Skyline Systems','Vertex EDA','Zenith Circuits'];
const BYQ = {
'Q1-2024':{rev:35.1,pps:16.79,eng:60.80,sent:0.15,sat:-0.32,build:72.58,sim:70.55,gap:0.40},
'Q2-2024':{rev:37.2,pps:19.80,eng:61.74,sent:0.17,sat:0.22,build:71.48,sim:67.38,gap:2.15},
'Q3-2024':{rev:37.7,pps:18.79,eng:60.30,sent:0.24,sat:-0.01,build:73.92,sim:68.04,gap:1.37},
'Q4-2024':{rev:34.9,pps:16.45,eng:62.32,sent:-0.12,sat:-0.22,build:71.40,sim:69.80,gap:3.98},
'Q1-2025':{rev:36.8,pps:17.77,eng:60.79,sent:-0.08,sat:0.18,build:71.32,sim:71.37,gap:1.86},
'Q2-2025':{rev:37.0,pps:17.05,eng:64.18,sent:-0.09,sat:0.02,build:72.29,sim:71.34,gap:2.32},
'Q3-2025':{rev:31.2,pps:19.19,eng:62.97,sent:0.34,sat:0.12,build:72.40,sim:69.28,gap:3.74},
'Q4-2025':{rev:33.9,pps:17.82,eng:60.96,sent:0.21,sat:-0.31,build:71.90,sim:69.88,gap:1.75},
};
const BCQ = {
'Q4-2025':{'Alpha Semicon':{rev:3.46,pps:21.6,build:66.1,sim:72.7,eng:68.4,sent:-0.33,sat:1.42,gap:-0.11,risk:'High'},'Apex Silicon':{rev:3.66,pps:11.9,build:70.3,sim:68.8,eng:62.8,sent:-0.42,sat:-0.58,gap:5.26,risk:'High'},'Fusion Microsystems':{rev:3.77,pps:29.7,build:73.3,sim:70.5,eng:65.7,sent:0.67,sat:0.92,gap:7.11,risk:'High'},'NextGen Compute':{rev:3.82,pps:23.3,build:72.1,sim:71.7,eng:63.7,sent:1.50,sat:-0.17,gap:-2.91,risk:'High'},'Nova Chips':{rev:3.00,pps:29.1,build:82.4,sim:72.0,eng:63.6,sent:0.92,sat:0.75,gap:-1.00,risk:'High'},'Orion Design Labs':{rev:3.23,pps:11.9,build:69.3,sim:64.1,eng:48.3,sent:-0.58,sat:-0.75,gap:-3.32,risk:'High'},'Quantum Logic':{rev:3.78,pps:20.6,build:66.2,sim:64.4,eng:63.3,sent:-0.42,sat:-0.58,gap:1.60,risk:'High'},'Skyline Systems':{rev:2.73,pps:9.1,build:74.8,sim:72.5,eng:46.6,sent:0.75,sat:-1.58,gap:7.65,risk:'High'},'Vertex EDA':{rev:3.44,pps:14.8,build:68.7,sim:75.3,eng:69.6,sent:0.00,sat:-0.92,gap:2.30,risk:'High'},'Zenith Circuits':{rev:3.02,pps:6.2,build:75.8,sim:66.9,eng:57.7,sent:0.00,sat:-1.58,gap:0.98,risk:'High'}},
'Q3-2025':{'Alpha Semicon':{rev:3.1,pps:22.1,build:68.0,sim:74.0,eng:70.0,sent:0.50,sat:1.20,gap:1.0,risk:'Medium'},'Apex Silicon':{rev:3.2,pps:14.0,build:71.0,sim:67.0,eng:64.0,sent:0.20,sat:-0.20,gap:4.0,risk:'Medium'},'Fusion Microsystems':{rev:3.5,pps:31.0,build:74.0,sim:72.0,eng:68.0,sent:0.80,sat:1.00,gap:6.0,risk:'Medium'},'NextGen Compute':{rev:3.6,pps:25.0,build:73.0,sim:72.0,eng:65.0,sent:1.60,sat:0.10,gap:-2.0,risk:'Low'},'Nova Chips':{rev:2.8,pps:30.0,build:83.0,sim:73.0,eng:65.0,sent:1.00,sat:0.80,gap:-1.5,risk:'Low'},'Orion Design Labs':{rev:3.0,pps:13.0,build:70.0,sim:65.0,eng:50.0,sent:-0.40,sat:-0.60,gap:-2.0,risk:'Medium'},'Quantum Logic':{rev:3.5,pps:21.0,build:67.0,sim:65.0,eng:64.0,sent:-0.30,sat:-0.40,gap:2.0,risk:'High'},'Skyline Systems':{rev:2.5,pps:10.0,build:76.0,sim:73.0,eng:48.0,sent:0.80,sat:-1.40,gap:8.0,risk:'High'},'Vertex EDA':{rev:3.2,pps:16.0,build:69.0,sim:76.0,eng:71.0,sent:0.10,sat:-0.70,gap:3.0,risk:'Medium'},'Zenith Circuits':{rev:2.8,pps:7.0,build:77.0,sim:68.0,eng:59.0,sent:0.10,sat:-1.40,gap:1.5,risk:'High'}},
'Q2-2025':{'Alpha Semicon':{rev:3.3,pps:20.0,build:67.0,sim:71.0,eng:69.0,sent:0.40,sat:1.10,gap:2.0,risk:'Medium'},'Apex Silicon':{rev:3.5,pps:12.5,build:70.0,sim:68.0,eng:62.0,sent:-0.10,sat:-0.30,gap:5.0,risk:'Medium'},'Fusion Microsystems':{rev:3.8,pps:28.0,build:72.0,sim:71.0,eng:67.0,sent:0.60,sat:0.85,gap:7.5,risk:'High'},'NextGen Compute':{rev:3.9,pps:22.0,build:72.0,sim:71.0,eng:64.0,sent:1.30,sat:-0.10,gap:-3.0,risk:'Low'},'Nova Chips':{rev:2.9,pps:27.0,build:81.0,sim:71.0,eng:63.0,sent:0.85,sat:0.70,gap:-0.5,risk:'Low'},'Orion Design Labs':{rev:3.1,pps:12.0,build:69.0,sim:63.0,eng:49.0,sent:-0.50,sat:-0.70,gap:-3.5,risk:'Medium'},'Quantum Logic':{rev:3.6,pps:19.0,build:66.0,sim:64.0,eng:63.0,sent:-0.35,sat:-0.50,gap:2.5,risk:'High'},'Skyline Systems':{rev:2.6,pps:8.5,build:75.0,sim:71.0,eng:47.0,sent:0.70,sat:-1.50,gap:8.5,risk:'High'},'Vertex EDA':{rev:3.3,pps:15.5,build:68.0,sim:75.0,eng:70.0,sent:0.05,sat:-0.80,gap:2.5,risk:'Medium'},'Zenith Circuits':{rev:2.9,pps:6.5,build:76.0,sim:67.0,eng:58.0,sent:0.05,sat:-1.50,gap:1.2,risk:'High'}},
'Q1-2025':{'Alpha Semicon':{rev:3.4,pps:21.0,build:67.5,sim:73.0,eng:69.0,sent:0.45,sat:1.30,gap:1.5,risk:'Medium'},'Apex Silicon':{rev:3.7,pps:13.0,build:71.0,sim:69.0,eng:63.0,sent:0.00,sat:-0.40,gap:4.5,risk:'Medium'},'Fusion Microsystems':{rev:3.9,pps:30.0,build:73.5,sim:71.0,eng:67.5,sent:0.70,sat:0.90,gap:7.0,risk:'High'},'NextGen Compute':{rev:3.8,pps:24.0,build:72.5,sim:72.0,eng:64.5,sent:1.45,sat:-0.05,gap:-2.5,risk:'Low'},'Nova Chips':{rev:3.1,pps:28.5,build:82.0,sim:72.5,eng:64.0,sent:0.90,sat:0.78,gap:-1.2,risk:'Low'},'Orion Design Labs':{rev:3.2,pps:12.5,build:69.5,sim:64.5,eng:49.0,sent:-0.48,sat:-0.72,gap:-3.1,risk:'Medium'},'Quantum Logic':{rev:3.7,pps:20.0,build:66.5,sim:64.8,eng:63.5,sent:-0.38,sat:-0.55,gap:2.0,risk:'High'},'Skyline Systems':{rev:2.7,pps:9.5,build:75.0,sim:72.8,eng:47.5,sent:0.78,sat:-1.55,gap:8.0,risk:'High'},'Vertex EDA':{rev:3.4,pps:15.0,build:68.5,sim:75.5,eng:70.0,sent:0.02,sat:-0.88,gap:2.8,risk:'Medium'},'Zenith Circuits':{rev:3.0,pps:6.8,build:76.0,sim:67.5,eng:58.5,sent:0.02,sat:-1.55,gap:1.1,risk:'High'}},
'Q4-2024':{'Alpha Semicon':{rev:3.2,pps:19.0,build:65.0,sim:71.0,eng:67.0,sent:0.30,sat:1.10,gap:2.5,risk:'Medium'},'Apex Silicon':{rev:3.8,pps:11.0,build:70.0,sim:68.0,eng:61.0,sent:-0.20,sat:-0.55,gap:5.8,risk:'High'},'Fusion Microsystems':{rev:4.0,pps:27.0,build:71.0,sim:70.0,eng:66.0,sent:0.55,sat:0.80,gap:8.0,risk:'High'},'NextGen Compute':{rev:3.7,pps:22.0,build:71.0,sim:70.0,eng:63.0,sent:1.20,sat:-0.15,gap:-2.2,risk:'Low'},'Nova Chips':{rev:3.0,pps:26.0,build:80.0,sim:71.0,eng:62.0,sent:0.80,sat:0.65,gap:-0.8,risk:'Low'},'Orion Design Labs':{rev:3.0,pps:11.5,build:68.0,sim:63.0,eng:48.0,sent:-0.55,sat:-0.80,gap:-3.8,risk:'Medium'},'Quantum Logic':{rev:3.5,pps:18.0,build:65.0,sim:63.0,eng:62.0,sent:-0.45,sat:-0.60,gap:3.0,risk:'High'},'Skyline Systems':{rev:2.4,pps:8.0,build:74.0,sim:71.0,eng:45.0,sent:0.65,sat:-1.70,gap:9.5,risk:'High'},'Vertex EDA':{rev:3.3,pps:14.0,build:67.0,sim:74.0,eng:69.0,sent:-0.05,sat:-0.95,gap:3.5,risk:'Medium'},'Zenith Circuits':{rev:2.9,pps:5.5,build:75.0,sim:66.0,eng:57.0,sent:-0.05,sat:-1.70,gap:1.5,risk:'High'}},
'Q3-2024':{'Alpha Semicon':{rev:3.5,pps:22.0,build:70.0,sim:74.0,eng:71.0,sent:0.60,sat:1.50,gap:0.5,risk:'Low'},'Apex Silicon':{rev:3.9,pps:15.0,build:72.0,sim:69.0,eng:65.0,sent:0.30,sat:-0.10,gap:3.5,risk:'Medium'},'Fusion Microsystems':{rev:4.1,pps:33.0,build:75.0,sim:72.0,eng:70.0,sent:0.90,sat:1.10,gap:5.5,risk:'Medium'},'NextGen Compute':{rev:3.8,pps:26.0,build:74.0,sim:73.0,eng:66.0,sent:1.70,sat:0.20,gap:-1.5,risk:'Low'},'Nova Chips':{rev:3.2,pps:32.0,build:84.0,sim:73.0,eng:66.0,sent:1.10,sat:0.90,gap:-0.5,risk:'Low'},'Orion Design Labs':{rev:3.3,pps:14.0,build:71.0,sim:65.0,eng:51.0,sent:-0.30,sat:-0.55,gap:-2.5,risk:'Low'},'Quantum Logic':{rev:3.7,pps:22.0,build:68.0,sim:65.0,eng:65.0,sent:-0.20,sat:-0.40,gap:1.5,risk:'Medium'},'Skyline Systems':{rev:2.6,pps:11.0,build:77.0,sim:74.0,eng:50.0,sent:0.90,sat:-1.20,gap:7.0,risk:'High'},'Vertex EDA':{rev:3.5,pps:17.0,build:70.0,sim:77.0,eng:72.0,sent:0.15,sat:-0.65,gap:2.0,risk:'Low'},'Zenith Circuits':{rev:3.1,pps:7.0,build:78.0,sim:68.0,eng:60.0,sent:0.15,sat:-1.20,gap:0.5,risk:'Medium'}},
'Q2-2024':{'Alpha Semicon':{rev:3.6,pps:23.0,build:71.0,sim:73.0,eng:72.0,sent:0.65,sat:1.60,gap:0.2,risk:'Low'},'Apex Silicon':{rev:4.0,pps:16.0,build:73.0,sim:68.0,eng:66.0,sent:0.35,sat:0.00,gap:3.0,risk:'Low'},'Fusion Microsystems':{rev:4.2,pps:34.0,build:76.0,sim:71.0,eng:71.0,sent:0.95,sat:1.15,gap:5.0,risk:'Medium'},'NextGen Compute':{rev:3.9,pps:27.0,build:75.0,sim:72.0,eng:67.0,sent:1.75,sat:0.25,gap:-1.0,risk:'Low'},'Nova Chips':{rev:3.3,pps:33.0,build:85.0,sim:72.0,eng:67.0,sent:1.15,sat:0.95,gap:-0.3,risk:'Low'},'Orion Design Labs':{rev:3.4,pps:15.0,build:72.0,sim:64.0,eng:52.0,sent:-0.25,sat:-0.50,gap:-2.0,risk:'Low'},'Quantum Logic':{rev:3.8,pps:23.0,build:69.0,sim:64.0,eng:66.0,sent:-0.15,sat:-0.35,gap:1.0,risk:'Medium'},'Skyline Systems':{rev:2.7,pps:12.0,build:78.0,sim:73.0,eng:51.0,sent:0.95,sat:-1.10,gap:6.5,risk:'High'},'Vertex EDA':{rev:3.6,pps:18.0,build:71.0,sim:76.0,eng:73.0,sent:0.20,sat:-0.60,gap:1.5,risk:'Low'},'Zenith Circuits':{rev:3.2,pps:7.5,build:79.0,sim:67.0,eng:61.0,sent:0.20,sat:-1.10,gap:0.3,risk:'Medium'}},
'Q1-2024':{'Alpha Semicon':{rev:3.3,pps:20.0,build:69.0,sim:72.0,eng:70.0,sent:0.55,sat:1.40,gap:1.0,risk:'Low'},'Apex Silicon':{rev:3.8,pps:13.5,build:71.0,sim:67.0,eng:64.0,sent:0.25,sat:-0.15,gap:3.5,risk:'Medium'},'Fusion Microsystems':{rev:4.0,pps:30.0,build:73.0,sim:70.0,eng:69.0,sent:0.85,sat:1.05,gap:5.5,risk:'Medium'},'NextGen Compute':{rev:3.7,pps:24.0,build:73.0,sim:71.0,eng:65.0,sent:1.60,sat:0.15,gap:-1.8,risk:'Low'},'Nova Chips':{rev:3.0,pps:30.5,build:83.0,sim:71.0,eng:65.0,sent:1.05,sat:0.85,gap:-0.8,risk:'Low'},'Orion Design Labs':{rev:3.2,pps:13.0,build:70.0,sim:63.0,eng:50.0,sent:-0.38,sat:-0.65,gap:-2.8,risk:'Low'},'Quantum Logic':{rev:3.6,pps:21.0,build:67.0,sim:63.0,eng:64.0,sent:-0.28,sat:-0.48,gap:1.8,risk:'Medium'},'Skyline Systems':{rev:2.5,pps:9.5,build:76.0,sim:72.0,eng:49.0,sent:0.85,sat:-1.30,gap:8.8,risk:'High'},'Vertex EDA':{rev:3.4,pps:15.5,build:69.0,sim:75.0,eng:71.0,sent:0.08,sat:-0.78,gap:2.8,risk:'Low'},'Zenith Circuits':{rev:2.9,pps:6.2,build:77.0,sim:66.0,eng:59.0,sent:0.08,sat:-1.30,gap:1.0,risk:'Medium'}},
};
// Generate raw rows for Data Explorer
const HLS=['Successful tapeout achieved','Strong adoption of new features','Excellent stakeholder alignment','Improved regression turnaround','Increased multi-team collaboration'];
const LLS=['Intermittent simulation failures','Resource allocation bottlenecks','Escalations due to runtime issues','Delayed design signoff','Reduced engagement from key users'];
const AWS=['Exploring advanced verification features','Planning future optimization roadmap','Considering cloud migration','Monitoring tool adoption metrics','Evaluating next-gen workflows'];
const WEEKS=['01','08','15','22'];
const MMAP={'Q1':'01','Q2':'04','Q3':'07','Q4':'10'};
let RAW_ROWS=[];
QS.forEach(q=>{
const yr=q.includes('2024')?'2024':'2025';
const mo=MMAP[q.split('-')[0]];
CUSTS.forEach(c=>{
const base=BCQ[q]?BCQ[q][c]:null; if(!base) return;
WEEKS.forEach(w=>{
RAW_ROWS.push({quarter:q,week:`${yr}-${mo}-${w}`,customer:c,
revenue:(base.rev/4*1e6*(0.88+Math.random()*.24)).toFixed(0),
pps:(base.pps*(0.88+Math.random()*.24)).toFixed(1),
build:(base.build*(0.94+Math.random()*.12)).toFixed(0),
sim:(base.sim*(0.94+Math.random()*.12)).toFixed(0),
eng:(base.eng*(0.94+Math.random()*.12)).toFixed(0),
sent:(base.sent*(0.7+Math.random()*.6)).toFixed(2),
sat:(base.sat*(0.7+Math.random()*.6)).toFixed(2),
gap:(base.gap*(0.7+Math.random()*.6)).toFixed(2),
risk:base.risk,
highlight:HLS[Math.floor(Math.random()*HLS.length)],
lowlight:LLS[Math.floor(Math.random()*LLS.length)],
awareness:AWS[Math.floor(Math.random()*AWS.length)],
});
});
});
});
// ════════════ STATE ════════════
let CQ='Q4-2025', CMQ='Q3-2025';
let sortCol='revenue', sortDir=-1;
let riskFilt='All', fqFilt='All', custFilt='All';
const charts={};
// ════════════ UTILS ════════════
const g=id=>document.getElementById(id);
const s=v=>v>=0?'+':'';
const mkChart=(id,cfg)=>{if(charts[id])charts[id].destroy();const el=g(id);if(!el)return;charts[id]=new Chart(el,cfg);};
const CD={responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false},tooltip:{backgroundColor:'#1c2333',borderColor:'#30363d',borderWidth:1,titleColor:'#e6edf3',bodyColor:'#8b949e',padding:10}}};
const TX={color:'#6e7681',font:{size:10}};
const GR={color:'rgba(48,54,61,.4)',lineWidth:1};
const lds=(data,color,label='',fill=false)=>({label,data,borderColor:color,backgroundColor:fill?color.replace('rgb(','rgba(').replace(')',',0.1)'):'transparent',pointBackgroundColor:color,borderWidth:2,pointRadius:4,tension:0.35,fill});
function toast(msg){const t=g('toast');t.textContent=msg;t.classList.add('show');setTimeout(()=>t.classList.remove('show'),2600);}
// ════════════ DROPDOWNS ════════════
function setupDropdowns(){
// Close on outside click
document.addEventListener('click',()=>document.querySelectorAll('.dropdown-menu').forEach(m=>m.classList.remove('open')));
// Toggle each
['ddQ','ddC','ddExp'].forEach(id=>{
const dd=g(id);
dd.querySelector('.btn').addEventListener('click',e=>{e.stopPropagation();dd.querySelector('.dropdown-menu').classList.toggle('open');});
dd.querySelector('.dropdown-menu').addEventListener('click',e=>e.stopPropagation());
});
// Quarter items
g('menuQ').querySelectorAll('.dropdown-item').forEach(el=>{
el.addEventListener('click',()=>{
CQ=el.dataset.q;
g('lblQ').textContent=el.textContent;
g('menuQ').querySelectorAll('.dropdown-item').forEach(x=>x.classList.remove('selected'));
el.classList.add('selected');
g('menuQ').classList.remove('open');
refreshActive();
});
});
// Compare items
g('menuC').querySelectorAll('.dropdown-item').forEach(el=>{
el.addEventListener('click',()=>{
CMQ=el.dataset.c;
g('lblC').textContent=el.textContent;
g('menuC').querySelectorAll('.dropdown-item').forEach(x=>x.classList.remove('selected'));
el.classList.add('selected');
g('menuC').classList.remove('open');
refreshActive();
});
});
// Export
g('doCSV').addEventListener('click',()=>{g('menuExp').classList.remove('open');exportCSV();});
g('doJSON').addEventListener('click',()=>{g('menuExp').classList.remove('open');exportJSON();});
g('doPrint').addEventListener('click',()=>{g('menuExp').classList.remove('open');window.print();});
}
// ════════════ NAV ════════════
function setupNav(){
document.querySelectorAll('.nav-item').forEach(item=>{
item.addEventListener('click',()=>{
document.querySelectorAll('.nav-item').forEach(x=>x.classList.remove('active'));
document.querySelectorAll('.section').forEach(x=>x.classList.remove('active'));
item.classList.add('active');
const sec=g(item.dataset.sec);
if(sec){sec.classList.add('active');renderSection(item.dataset.sec);}
});
});
}
function refreshActive(){
const active=document.querySelector('.section.active');
if(active)renderSection(active.id);
}
// ════════════ KPI ════════════
function renderKPI(){
const cq=BYQ[CQ], pq=BYQ[CMQ];
const cd=BCQ[CQ]||BCQ['Q4-2025'];
const risks=Object.values(cd).map(x=>x.risk);
const hR=risks.filter(r=>r==='High').length, mR=risks.filter(r=>r==='Medium').length, lR=risks.filter(r=>r==='Low').length;
const rlbl=hR>0?'High':mR>0?'Medium':'Low';
const rcol=rlbl==='High'?'var(--red)':rlbl==='Medium'?'var(--yellow)':'var(--green)';
const rChg=((cq.rev-pq.rev)/pq.rev*100).toFixed(1);
const pChg=(cq.pps-pq.pps).toFixed(1);
const eChg=(cq.eng-pq.eng).toFixed(1);
const sChg=(cq.sent-pq.sent).toFixed(2);
const stChg=(cq.sat-pq.sat).toFixed(2);
const qLbl=CMQ.replace('-',' FY').replace('Q','Q');
const items=[
{ico:'πŸ’΅',bg:'rgba(63,185,80,.15)',lbl:'Total Revenue',val:`$${cq.rev}M`,chg:`${+rChg>=0?'β–²':'β–Ό'} ${Math.abs(rChg)}% vs ${qLbl}`,cls:+rChg>=0?'up':'down'},
{ico:'πŸ“ˆ',bg:'rgba(88,166,255,.15)',lbl:'Product Perf Score',val:`${cq.pps.toFixed(1)}<span style="font-size:12px;color:var(--t3)">/100</span>`,chg:`${+pChg>=0?'β–²':'β–Ό'} ${Math.abs(pChg)} pts vs ${qLbl}`,cls:+pChg>=0?'up':'down'},
{ico:'πŸ‘₯',bg:'rgba(57,197,207,.15)',lbl:'Engagement Score',val:`${cq.eng.toFixed(1)}<span style="font-size:12px;color:var(--t3)">/100</span>`,chg:`${+eChg>=0?'β–²':'β–Ό'} ${Math.abs(eChg)} pts vs ${qLbl}`,cls:+eChg>=0?'up':'down'},
{ico:'😊',bg:'rgba(210,153,34,.15)',lbl:'Sentiment Score',val:`${s(cq.sent)}${cq.sent.toFixed(2)}<span style="font-size:12px;color:var(--t3)">/5</span>`,chg:`${+sChg>=0?'β–²':'β–Ό'} ${Math.abs(sChg)} vs ${qLbl}`,cls:+sChg>=0?'up':'down'},
{ico:'⭐',bg:'rgba(188,140,255,.15)',lbl:'Satisfaction Score',val:`${s(cq.sat)}${cq.sat.toFixed(2)}<span style="font-size:12px;color:var(--t3)">/5</span>`,chg:`${+stChg>=0?'β–²':'β–Ό'} ${Math.abs(stChg)} vs ${qLbl}`,cls:+stChg>=0?'up':'down'},
{ico:'πŸ›‘',bg:'rgba(248,81,73,.15)',lbl:'Overall Business Risk',val:`<span style="color:${rcol};font-size:18px;font-weight:700">${rlbl}</span>`,chg:`High:${hR} Med:${mR} Low:${lR}`,cls:'neutral'},
];
g('kpiGrid').innerHTML=items.map(k=>`<div class="kpi-card"><div class="kpi-icon" style="background:${k.bg}">${k.ico}</div><div class="kpi-label">${k.lbl}</div><div class="kpi-value">${k.val}</div><div class="kpi-change ${k.cls}">${k.chg}</div></div>`).join('');
}
// ════════════ CHARTS ════════════
function renderPPSTrend(cid='cPPS'){
mkChart(cid,{type:'line',data:{labels:QL,datasets:[{...lds(QS.map(q=>BYQ[q].pps),'#58a6ff','PPS',true),pointRadius:4}]},options:{...CD,scales:{x:{ticks:{...TX,maxRotation:0},grid:{display:false}},y:{min:10,max:30,ticks:TX,grid:GR}}}});
}
function renderRevTrend(cid='cRev'){
const ci=QS.indexOf(CQ);
mkChart(cid,{type:'bar',data:{labels:QL,datasets:[{label:'Revenue',data:QS.map(q=>BYQ[q].rev),backgroundColor:QS.map((_,i)=>i===ci?'#58a6ff':'rgba(88,166,255,.35)'),borderRadius:4,barPercentage:.7}]},options:{...CD,plugins:{legend:{display:false},tooltip:{...CD.plugins.tooltip,callbacks:{label:c=>`$${c.parsed.y}M`}}},scales:{x:{ticks:{...TX,maxRotation:0},grid:{display:false}},y:{ticks:{...TX,callback:v=>`$${v}M`},grid:GR}}}});
}
function renderPerfOverTime(){
mkChart('cPOT',{type:'line',data:{labels:QL,datasets:[
{...lds(QS.map(q=>BYQ[q].eng),'#58a6ff','Engagement'),pointRadius:3,borderWidth:1.5},
{...lds(QS.map(q=>BYQ[q].build),'#3fb950','Build'),pointRadius:3,borderWidth:1.5},
{...lds(QS.map(q=>BYQ[q].sim),'#d29922','Simulation'),pointRadius:3,borderWidth:1.5},
{...lds(QS.map(q=>BYQ[q].pps),'#bc8cff','PPS'),pointRadius:3,borderWidth:1.5},
]},options:{...CD,plugins:{...CD.plugins,legend:{display:true,position:'bottom',labels:{color:'#6e7681',font:{size:9},boxWidth:8,padding:8}}},scales:{x:{ticks:{...TX,maxRotation:0},grid:{display:false}},y:{ticks:TX,grid:GR}}}});
}
function renderScoreBreak(){
const cq=BYQ[CQ];
g('dBig').textContent=cq.pps.toFixed(1);
g('sbTitle').textContent=`(${CQ})`;
g('vEng').textContent=cq.eng.toFixed(1);
g('vSent').textContent=`${s(cq.sent)}${cq.sent.toFixed(2)}`;
g('vSat').textContent=`${s(cq.sat)}${cq.sat.toFixed(2)}`;
mkChart('cBreak',{type:'doughnut',data:{labels:['Engagement','Sentiment','Satisfaction','Composite'],datasets:[{data:[cq.eng*0.6,Math.abs(cq.sent)*4+10,Math.abs(cq.sat)*4+10,14],backgroundColor:['#58a6ff','#3fb950','#d29922','rgba(188,140,255,.7)'],borderWidth:0,hoverOffset:4}]},options:{...CD,cutout:'72%'}});
}
function renderRiskGap(){
const cd=BCQ[CQ]||BCQ['Q4-2025'];
const risks=Object.values(cd).map(x=>x.risk);
const hR=risks.filter(r=>r==='High').length,mR=risks.filter(r=>r==='Medium').length,lR=risks.filter(r=>r==='Low').length;
const gaps=Object.values(cd).map(x=>x.gap);
const gH=gaps.filter(v=>v>15).length,gM=gaps.filter(v=>v>=5&&v<=15).length,gL=gaps.filter(v=>v<5).length;
['riskSubTitle','gapSubTitle'].forEach(id=>g(id).textContent=`(${CQ})`);
g('rH').textContent=hR; g('rM').textContent=mR; g('rL').textContent=lR;
g('gH').textContent=gH; g('gM').textContent=gM; g('gL').textContent=gL;
g('riskN').textContent=CUSTS.length; g('gapN').textContent=CUSTS.length;
mkChart('cRisk',{type:'doughnut',data:{labels:['High','Medium','Low'],datasets:[{data:[hR||.01,mR||.01,lR||.01],backgroundColor:['#f85149','#d29922','#3fb950'],borderWidth:0}]},options:{...CD,cutout:'68%'}});
mkChart('cGap',{type:'doughnut',data:{labels:['>15%','5-15%','<5%'],datasets:[{data:[gH||.01,gM||.01,gL||.01],backgroundColor:['#f85149','#d29922','#3fb950'],borderWidth:0}]},options:{...CD,cutout:'68%'}});
}
function renderHLBox(){
const cq=BYQ[CQ],pq=BYQ[CMQ];
const cd=BCQ[CQ]||BCQ['Q4-2025'];
const rC=((cq.rev-pq.rev)/pq.rev*100).toFixed(1);
const hR=Object.values(cd).filter(x=>x.risk==='High').length;
const gH=Object.values(cd).filter(x=>x.gap>15).length;
const pC=(cq.pps-pq.pps).toFixed(1);
const items=[
{cls:'hl-neu',ico:'πŸ“ˆ',t:`Revenue ${+rC>=0?'grew':'fell'} ${Math.abs(rC)}% vs ${CMQ} ($${pq.rev}M β†’ $${cq.rev}M).`},
hR>0?{cls:'hl-bad',ico:'⚠️',t:`${hR} customer${hR>1?'s':''} at High business risk this quarter.`}:{cls:'hl-good',ico:'βœ…',t:'No customers at High business risk this quarter.'},
{cls:+pC>=0?'hl-good':'hl-neu',ico:'πŸ“Š',t:`PPS ${+pC>=0?'improved':'dipped'} ${Math.abs(pC)} pts vs ${CMQ} (${pq.pps.toFixed(1)} β†’ ${cq.pps.toFixed(1)}).`},
gH>0?{cls:'hl-bad',ico:'πŸ”΄',t:`${gH} customer${gH>1?'s':''} have Gap vs OT >15%.`}:{cls:'hl-good',ico:'βœ…',t:'No customers have a High Gap vs OT (>15%).'},
{cls:cq.sat<0?'hl-bad':'hl-good',ico:cq.sat<0?'😟':'😊',t:cq.sat<0?`Avg satisfaction is negative (${cq.sat.toFixed(2)}) β€” net dissatisfaction across accounts.`:`Avg satisfaction is positive (${cq.sat.toFixed(2)}) this quarter.`},
];
g('hlBox').innerHTML=items.map(i=>`<div class="hl-item ${i.cls}"><div class="hl-ico">${i.ico}</div><div>${i.t}</div></div>`).join('');
}
// ════════════ TABLE ════════════
let tableRows=[];
function buildTableRows(){
const cd=BCQ[CQ]||BCQ['Q4-2025'];
tableRows=CUSTS.map(c=>{const d=cd[c]||{};return{name:c,quarter:CQ,revenue:d.rev||0,pps:d.pps||0,build:d.build||0,sim:d.sim||0,eng:d.eng||0,sent:d.sent||0,sat:d.sat||0,gap:d.gap||0,risk:d.risk||'High'};});
}
function sortRows(){
tableRows.sort((a,b)=>{const av=typeof a[sortCol]==='string'?a[sortCol]:+a[sortCol];const bv=typeof b[sortCol]==='string'?b[sortCol]:+b[sortCol];return(av>bv?1:av<bv?-1:0)*sortDir;});
}
function renderTable(){
buildTableRows(); sortRows();
g('tblTitle').textContent=`(${CQ})`;
g('mainTbody').innerHTML=tableRows.map(c=>{
const pc=c.pps>=25?'var(--green)':c.pps>=15?'var(--yellow)':'var(--red)';
const sc=c.sent>0?'var(--green)':c.sent<0?'var(--red)':'var(--t2)';
const stc=c.sat>0?'var(--green)':c.sat<0?'var(--red)':'var(--t2)';
const gc=c.gap>15?'var(--red)':c.gap>5?'var(--yellow)':'var(--green)';
const rb=c.risk==='High'?'b-high':c.risk==='Medium'?'b-medium':'b-low';
return `<tr><td style="font-weight:600">${c.name}</td><td style="color:var(--t2)">${c.quarter}</td><td style="font-weight:600">$${c.revenue.toFixed(2)}M</td><td style="color:${pc};font-weight:700">${c.pps.toFixed(1)}</td><td>${c.build.toFixed(0)}</td><td>${c.sim.toFixed(0)}</td><td>${c.eng.toFixed(0)}</td><td style="color:${sc};font-weight:600">${s(c.sent)}${c.sent.toFixed(2)}</td><td style="color:${stc};font-weight:600">${s(c.sat)}${c.sat.toFixed(2)}</td><td style="color:${gc};font-weight:600">${c.gap>0?'+':''}${c.gap.toFixed(1)}%</td><td><span class="badge ${rb}">${c.risk}</span></td></tr>`;
}).join('');
document.querySelectorAll('#mainTable th').forEach(th=>{
th.classList.toggle('sorted',th.dataset.col===sortCol);
const sp=th.querySelector('span');
if(sp)sp.textContent=th.dataset.col===sortCol?(sortDir===1?'↑':'↓'):'↕';
});
}
function setupTableSort(){
document.querySelectorAll('#mainTable th[data-col]').forEach(th=>{
th.addEventListener('click',()=>{
if(sortCol===th.dataset.col)sortDir*=-1; else{sortCol=th.dataset.col;sortDir=-1;}
renderTable();
});
});
}
// ════════════ SECTION RENDERERS ════════════
function renderOverview(){
renderKPI(); renderScoreBreak(); renderPPSTrend('cPPS');
renderPerfOverTime(); renderRevTrend('cRev'); renderRiskGap();
renderHLBox(); renderTable();
}
function renderPerfSummary(){
g('perfBody').innerHTML=QS.map(q=>{
const d=BYQ[q];const iC=q===CQ,iP=q===CMQ;
const st=iC?'background:rgba(88,166,255,.08);':iP?'background:rgba(210,153,34,.05);':'';
const cl=iC?'color:var(--blue);font-weight:700':iP?'color:var(--yellow)':'';
return `<tr style="${st}"><td style="${cl}">${q}${iC?' β—€':iP?' β—·':''}</td><td>$${d.rev.toFixed(1)}M</td><td>${d.pps.toFixed(1)}</td><td>${d.eng.toFixed(1)}</td><td>${s(d.sent)}${d.sent.toFixed(2)}</td><td>${s(d.sat)}${d.sat.toFixed(2)}</td><td>${d.build.toFixed(1)}</td><td>${d.sim.toFixed(1)}</td><td>${d.gap.toFixed(2)}%</td></tr>`;
}).join('');
renderRevTrend('cRevPS');
mkChart('cPPSPS',{type:'line',data:{labels:QL,datasets:[
{...lds(QS.map(q=>BYQ[q].pps),'#58a6ff','PPS'),pointRadius:4,borderWidth:2},
{...lds(QS.map(q=>BYQ[q].eng),'#3fb950','Engagement'),pointRadius:3,borderWidth:1.5},
]},options:{...CD,plugins:{...CD.plugins,legend:{display:true,position:'bottom',labels:{color:'#6e7681',font:{size:9},boxWidth:8,padding:8}}},scales:{x:{ticks:{...TX,maxRotation:0},grid:{display:false}},y:{ticks:TX,grid:GR}}}});
}
function renderCustPerf(){
const cd=BCQ[CQ]||BCQ['Q4-2025'];
g('cpTitle').textContent=`(${CQ})`;
g('cpRevTitle').textContent=`(${CQ})`;
const custs=CUSTS.filter(c=>riskFilt==='All'||(cd[c]&&cd[c].risk===riskFilt));
g('cpBody').innerHTML=custs.map(c=>{
const d=cd[c]||{}; if(!d.rev)return '';
const pb=d.risk==='High'?'b-high':d.risk==='Medium'?'b-medium':'b-low';
const pc=d.pps>=25?'var(--green)':d.pps>=15?'var(--yellow)':'var(--red)';
const gc=d.gap>15?'var(--red)':d.gap>5?'var(--yellow)':'var(--green)';
return `<tr><td style="font-weight:600">${c}</td><td>$${d.rev.toFixed(2)}M</td><td style="color:${pc};font-weight:700">${d.pps.toFixed(1)}</td><td>${d.build.toFixed(0)}</td><td>${d.sim.toFixed(0)}</td><td>${d.eng.toFixed(0)}</td><td style="color:${d.sent>0?'var(--green)':d.sent<0?'var(--red)':'var(--t2)'};font-weight:600">${s(d.sent)}${d.sent.toFixed(2)}</td><td style="color:${d.sat>0?'var(--green)':d.sat<0?'var(--red)':'var(--t2)'};font-weight:600">${s(d.sat)}${d.sat.toFixed(2)}</td><td style="color:${gc};font-weight:600">${d.gap>0?'+':''}${d.gap.toFixed(1)}%</td><td><span class="badge ${pb}">${d.risk}</span></td></tr>`;
}).join('');
mkChart('cCustRev',{type:'bar',data:{labels:custs,datasets:[{label:'Revenue',data:custs.map(c=>cd[c]?.rev||0),backgroundColor:custs.map(c=>cd[c]?.risk==='High'?'rgba(248,81,73,.65)':cd[c]?.risk==='Medium'?'rgba(210,153,34,.65)':'rgba(63,185,80,.65)'),borderRadius:5,barPercentage:.7}]},options:{...CD,plugins:{legend:{display:false},tooltip:{...CD.plugins.tooltip,callbacks:{label:c=>`$${c.parsed.y.toFixed(2)}M`}}},scales:{x:{ticks:{...TX,maxRotation:30,font:{size:9}},grid:{display:false}},y:{ticks:{...TX,callback:v=>`$${v}M`},grid:GR}}}});
}
function renderProductQuality(){
mkChart('cBuild',{type:'line',data:{labels:QL,datasets:[{...lds(QS.map(q=>BYQ[q].build),'#3fb950','Build',true),pointRadius:4}]},options:{...CD,scales:{x:{ticks:{...TX,maxRotation:0},grid:{display:false}},y:{min:60,max:90,ticks:TX,grid:GR}}}});
mkChart('cSim',{type:'line',data:{labels:QL,datasets:[{...lds(QS.map(q=>BYQ[q].sim),'#d29922','Sim',true),pointRadius:4}]},options:{...CD,scales:{x:{ticks:{...TX,maxRotation:0},grid:{display:false}},y:{min:60,max:90,ticks:TX,grid:GR}}}});
const cd=BCQ[CQ]||BCQ['Q4-2025'];
g('bsTitle').textContent=`(${CQ})`;
mkChart('cBuildSim',{type:'bar',data:{labels:CUSTS,datasets:[
{label:'Build',data:CUSTS.map(c=>cd[c]?.build||0),backgroundColor:'rgba(63,185,80,.65)',borderRadius:4,barPercentage:.4},
{label:'Simulation',data:CUSTS.map(c=>cd[c]?.sim||0),backgroundColor:'rgba(210,153,34,.65)',borderRadius:4,barPercentage:.4},
]},options:{...CD,plugins:{...CD.plugins,legend:{display:true,position:'bottom',labels:{color:'#6e7681',font:{size:10},boxWidth:8,padding:10}}},scales:{x:{ticks:{...TX,maxRotation:25,font:{size:9}},grid:{display:false}},y:{min:50,max:100,ticks:TX,grid:GR}}}});
}
function renderEngagement(){
mkChart('cEng',{type:'line',data:{labels:QL,datasets:[{...lds(QS.map(q=>BYQ[q].eng),'#39c5cf','Engagement',true),pointRadius:4}]},options:{...CD,scales:{x:{ticks:{...TX,maxRotation:0},grid:{display:false}},y:{min:55,max:70,ticks:TX,grid:GR}}}});
mkChart('cSentSat',{type:'line',data:{labels:QL,datasets:[
{...lds(QS.map(q=>BYQ[q].sent),'#3fb950','Sentiment'),pointRadius:4,borderWidth:2},
{...lds(QS.map(q=>BYQ[q].sat),'#f85149','Satisfaction'),pointRadius:4,borderWidth:2},
]},options:{...CD,plugins:{...CD.plugins,legend:{display:true,position:'bottom',labels:{color:'#6e7681',font:{size:10},boxWidth:8,padding:10}}},scales:{x:{ticks:{...TX,maxRotation:0},grid:{display:false}},y:{ticks:TX,grid:GR}}}});
const cd=BCQ[CQ]||BCQ['Q4-2025'];
g('engCTitle').textContent=`(${CQ})`;
mkChart('cEngCust',{type:'bar',data:{labels:CUSTS,datasets:[{label:'Engagement',data:CUSTS.map(c=>cd[c]?.eng||0),backgroundColor:'rgba(57,197,207,.65)',borderRadius:4,barPercentage:.65}]},options:{...CD,plugins:{legend:{display:false}},scales:{x:{ticks:{...TX,maxRotation:25,font:{size:9}},grid:{display:false}},y:{min:30,max:80,ticks:TX,grid:GR}}}});
}
function renderPPSSection(){
renderPPSTrend('cPPS2');
const cd=BCQ[CQ]||BCQ['Q4-2025'];
g('ppsCTitle').textContent=`(${CQ})`;
const vals=CUSTS.map(c=>cd[c]?.pps||0);
mkChart('cPPSCust',{type:'bar',data:{labels:CUSTS,datasets:[{label:'PPS',data:vals,backgroundColor:vals.map(v=>v>=25?'rgba(63,185,80,.7)':v>=15?'rgba(210,153,34,.7)':'rgba(248,81,73,.7)'),borderRadius:5,barPercentage:.7}]},options:{...CD,plugins:{legend:{display:false},tooltip:{...CD.plugins.tooltip,callbacks:{label:c=>`PPS: ${c.parsed.y.toFixed(1)}`}}},scales:{x:{ticks:{...TX,maxRotation:25,font:{size:9}},grid:{display:false}},y:{min:0,max:40,ticks:TX,grid:GR}}}});
}
function renderRiskSection(){
const cd=BCQ[CQ]||BCQ['Q4-2025'];
const risks=Object.values(cd).map(x=>x.risk);
const hR=risks.filter(r=>r==='High').length,mR=risks.filter(r=>r==='Medium').length,lR=risks.filter(r=>r==='Low').length;
const gaps=CUSTS.map(c=>cd[c]?.gap||0);
g('riskD2Title').textContent=`(${CQ})`; g('gapCTitle').textContent=`(${CQ})`;
mkChart('cRisk2',{type:'doughnut',data:{labels:['High','Medium','Low'],datasets:[{data:[hR||.01,mR||.01,lR||.01],backgroundColor:['#f85149','#d29922','#3fb950'],borderWidth:0}]},options:{...CD,cutout:'58%',plugins:{...CD.plugins,legend:{display:true,position:'bottom',labels:{color:'#6e7681',font:{size:10},boxWidth:8,padding:10}}}}});
mkChart('cGapCust',{type:'bar',data:{labels:CUSTS,datasets:[{label:'Gap vs OT',data:gaps,backgroundColor:gaps.map(g=>g>15?'rgba(248,81,73,.7)':g>5?'rgba(210,153,34,.7)':'rgba(63,185,80,.7)'),borderRadius:5,barPercentage:.7}]},options:{...CD,plugins:{legend:{display:false},tooltip:{...CD.plugins.tooltip,callbacks:{label:c=>`${c.parsed.y.toFixed(1)}%`}}},scales:{x:{ticks:{...TX,maxRotation:25,font:{size:9}},grid:{display:false}},y:{ticks:{...TX,callback:v=>`${v}%`},grid:GR}}}});
}
function renderRevenueSection(){
renderRevTrend('cRev2');
const cd=BCQ[CQ]||BCQ['Q4-2025'];
g('revCTitle').textContent=`(${CQ})`;
mkChart('cRevCust',{type:'bar',data:{labels:CUSTS,datasets:[{label:'Revenue',data:CUSTS.map(c=>cd[c]?.rev||0),backgroundColor:'rgba(88,166,255,.65)',borderRadius:5,barPercentage:.7}]},options:{...CD,plugins:{legend:{display:false},tooltip:{...CD.plugins.tooltip,callbacks:{label:c=>`$${c.parsed.y.toFixed(2)}M`}}},scales:{x:{ticks:{...TX,maxRotation:25,font:{size:9}},grid:{display:false}},y:{ticks:{...TX,callback:v=>`$${v}M`},grid:GR}}}});
}
function renderHighlightsSection(){
const rows=RAW_ROWS.filter(r=>r.quarter===CQ);
const gSet=[...new Set(rows.map(r=>r.highlight))].slice(0,5);
const bSet=[...new Set(rows.map(r=>r.lowlight))].slice(0,5);
const nSet=[...new Set(rows.map(r=>r.awareness))].slice(0,6);
['hlgTitle','hlbTitle','hlnTitle'].forEach(id=>g(id).textContent=`(${CQ})`);
g('hlGood').innerHTML=gSet.map(t=>`<div class="hl-item hl-good"><div class="hl-ico">βœ…</div><div>${t}</div></div>`).join('');
g('hlBad').innerHTML=bSet.map(t=>`<div class="hl-item hl-bad"><div class="hl-ico">⚠️</div><div>${t}</div></div>`).join('');
g('hlNeu').innerHTML=nSet.map(t=>`<div class="hl-item hl-neu"><div class="hl-ico">πŸ”΅</div><div>${t}</div></div>`).join('');
}
function renderDataExplorer(){
const sel=g('custSel');
if(sel.children.length===0){
['All',...CUSTS].forEach(c=>{const o=document.createElement('option');o.value=c;o.textContent=c;sel.appendChild(o);});
sel.addEventListener('change',()=>{custFilt=sel.value;renderExpTable();});
}
renderExpTable();
}
function renderExpTable(){
const filt=RAW_ROWS.filter(r=>(fqFilt==='All'||r.quarter===fqFilt)&&(custFilt==='All'||r.customer===custFilt));
g('expCount').textContent=`(${filt.length.toLocaleString()} rows shown β€” max 200)`;
g('expBody').innerHTML=filt.slice(0,200).map(r=>{
const rb=r.risk==='High'?'b-high':r.risk==='Medium'?'b-medium':'b-low';
return `<tr><td>${r.quarter}</td><td style="color:var(--t2)">${r.week}</td><td style="font-weight:600">${r.customer}</td><td>$${(+r.revenue/1000).toFixed(0)}K</td><td>${r.pps}</td><td>${r.build}</td><td>${r.sim}</td><td>${r.eng}</td><td>${r.sent}</td><td>${r.sat}</td><td>${r.gap}%</td><td><span class="badge ${rb}">${r.risk}</span></td></tr>`;
}).join('');
}
function renderSection(id){
switch(id){
case 'overview':renderOverview();break;
case 'perf-summary':renderPerfSummary();break;
case 'customer-perf':renderCustPerf();break;
case 'product-quality':renderProductQuality();break;
case 'engagement':renderEngagement();break;
case 'pps-section':renderPPSSection();break;
case 'risk-gaps':renderRiskSection();break;
case 'revenue-section':renderRevenueSection();break;
case 'highlights':renderHighlightsSection();break;
case 'data-explorer':renderDataExplorer();break;
}
}
// ════════════ EXPORT ════════════
function exportCSV(){
const cd=BCQ[CQ]||BCQ['Q4-2025'];
const rows=[['Customer','Quarter','Revenue($M)','PPS','Build','Simulation','Engagement','Sentiment','Satisfaction','Gap vs OT(%)','Risk']];
CUSTS.forEach(c=>{const d=cd[c]||{};rows.push([c,CQ,d.rev,d.pps,d.build,d.sim,d.eng,d.sent,d.sat,d.gap,d.risk]);});
const blob=new Blob([rows.map(r=>r.join(',')).join('\n')],{type:'text/csv'});
const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download=`dashboard_${CQ}.csv`;a.click();
toast('βœ… CSV exported');
}
function exportJSON(){
const cd=BCQ[CQ]||BCQ['Q4-2025'];
const out={quarter:CQ,summary:BYQ[CQ],customers:{}};
CUSTS.forEach(c=>{out.customers[c]=cd[c];});
const blob=new Blob([JSON.stringify(out,null,2)],{type:'application/json'});
const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download=`dashboard_${CQ}.json`;a.click();
toast('βœ… JSON exported');
}
// ════════════ RISK FILTER ════════════
function setupRiskFilter(){
document.querySelectorAll('[data-risk]').forEach(btn=>{
btn.addEventListener('click',()=>{
document.querySelectorAll('[data-risk]').forEach(b=>b.classList.remove('on'));
btn.classList.add('on');riskFilt=btn.dataset.risk;renderCustPerf();
});
});
}
// ════════════ QUARTER FILTER (Data Explorer) ════════════
function setupFQFilter(){
document.querySelectorAll('[data-fq]').forEach(btn=>{
btn.addEventListener('click',()=>{
document.querySelectorAll('[data-fq]').forEach(b=>b.classList.remove('on'));
btn.classList.add('on');fqFilt=btn.dataset.fq;renderExpTable();
});
});
}
// ════════════ INIT ════════════
document.addEventListener('DOMContentLoaded',()=>{
setupDropdowns();
setupNav();
setupTableSort();
setupRiskFilter();
setupFQFilter();
renderOverview();
});
</script>
</body>
</html>