Skip to main content

HR Performance Analytics


<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HR Performance Dashboard v4</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jspdf@2.5.1/dist/jspdf.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/pptxgenjs@3.12.0/dist/pptxgen.bundle.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=DM+Sans:wght@300;400;500;600;700&display=swap');
:root{
  --bg:#0a0a0f;--bg2:#111118;--card:#14141e;--card2:#1a1a28;
  --border:#252535;--text:#d4d4e8;--dim:#6b6b8a;
  --cyan:#00d4ff;--pink:#ff2d78;--purple:#7c3aff;
  --green:#00ff9d;--orange:#ff6b35;--yellow:#ffd700;
  --amber:#ffab40;--violet:#e040fb;
}
*{box-sizing:border-box;margin:0;padding:0;}
body{background:var(--bg);color:var(--text);font-family:'DM Sans',sans-serif;min-height:100vh;overflow-x:hidden;}
body::before{content:'';position:fixed;top:-50%;left:-50%;width:200%;height:200%;
  background:radial-gradient(ellipse at 15% 15%,rgba(0,212,255,.05) 0%,transparent 50%),
             radial-gradient(ellipse at 85% 85%,rgba(124,58,255,.05) 0%,transparent 50%);
  pointer-events:none;z-index:0;}
.wrap{position:relative;z-index:1;}
::-webkit-scrollbar{width:5px;height:5px;}
::-webkit-scrollbar-track{background:var(--bg2);}
::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px;}

/* ── HEADER ── */
header{padding:16px 28px 0;display:flex;align-items:center;justify-content:space-between;
  border-bottom:1px solid var(--border);background:rgba(10,10,15,.97);
  backdrop-filter:blur(12px);position:sticky;top:0;z-index:200;}
.logo{display:flex;align-items:center;gap:11px;}
.logo-icon{width:36px;height:36px;background:linear-gradient(135deg,var(--cyan),var(--purple));
  border-radius:8px;display:flex;align-items:center;justify-content:center;
  font-family:'Space Mono',monospace;font-size:13px;font-weight:700;color:#fff;}
.logo-text{font-family:'Space Mono',monospace;font-size:12px;letter-spacing:2px;text-transform:uppercase;}
.logo-text span{color:var(--cyan);}
.header-right{display:flex;align-items:center;gap:10px;padding-bottom:4px;}
.hdr-mode{font-family:'Space Mono',monospace;font-size:9px;color:var(--dim);}
.hdr-mode strong{color:var(--cyan);display:block;font-size:10px;}

/* EXPORT BUTTONS */
.export-btns{display:flex;gap:8px;}
.exp-btn{display:flex;align-items:center;gap:6px;padding:7px 14px;border-radius:7px;
  font-family:'Space Mono',monospace;font-size:9px;letter-spacing:1px;text-transform:uppercase;
  cursor:pointer;border:1px solid;transition:.2s;font-weight:700;white-space:nowrap;}
.exp-btn:hover{transform:translateY(-1px);}
.exp-btn:active{transform:translateY(0);}
.exp-btn.pdf{background:rgba(255,45,120,.1);border-color:rgba(255,45,120,.4);color:var(--pink);}
.exp-btn.pdf:hover{background:rgba(255,45,120,.18);border-color:var(--pink);box-shadow:0 0 12px rgba(255,45,120,.25);}
.exp-btn.pptx{background:rgba(255,107,53,.1);border-color:rgba(255,107,53,.4);color:var(--orange);}
.exp-btn.pptx:hover{background:rgba(255,107,53,.18);border-color:var(--orange);box-shadow:0 0 12px rgba(255,107,53,.25);}
.exp-btn.all{background:rgba(0,212,255,.08);border-color:rgba(0,212,255,.35);color:var(--cyan);}
.exp-btn.all:hover{background:rgba(0,212,255,.14);border-color:var(--cyan);box-shadow:0 0 12px rgba(0,212,255,.2);}
.exp-icon{font-size:13px;}

/* EXPORT LOADING OVERLAY */
.export-overlay{display:none;position:fixed;inset:0;z-index:9999;
  background:rgba(10,10,15,.85);backdrop-filter:blur(6px);
  align-items:center;justify-content:center;flex-direction:column;gap:16px;}
.export-overlay.show{display:flex;}
.export-spinner{width:44px;height:44px;border:3px solid var(--border);
  border-top-color:var(--cyan);border-radius:50%;animation:spin .8s linear infinite;}
@keyframes spin{to{transform:rotate(360deg);}}
.export-msg{font-family:'Space Mono',monospace;font-size:11px;letter-spacing:2px;
  text-transform:uppercase;color:var(--cyan);}
.export-sub{font-size:11px;color:var(--dim);}

/* ── UPLOAD ── */
.upload-wrap{margin:16px 28px;background:var(--card);border:1px solid var(--border);border-radius:11px;overflow:hidden;}
.upload-head{display:flex;align-items:center;justify-content:space-between;padding:13px 18px;
  cursor:pointer;user-select:none;border-bottom:1px solid transparent;transition:.2s;}
.upload-head.open{border-bottom-color:var(--border);}
.upload-head-left{display:flex;align-items:center;gap:8px;font-family:'Space Mono',monospace;
  font-size:10px;letter-spacing:2px;text-transform:uppercase;color:var(--cyan);}
.upload-head-left::before{content:'';width:5px;height:5px;background:var(--cyan);border-radius:50%;box-shadow:0 0 7px var(--cyan);}
.upload-arrow{font-size:10px;color:var(--dim);transition:.2s;}
.upload-arrow.open{transform:rotate(180deg);}
.upload-body{display:none;padding:16px;}
.upload-body.open{display:block;}
.upload-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:9px;}
.u-item{background:var(--bg2);border:1px solid var(--border);border-radius:7px;padding:10px 12px;transition:.2s;}
.u-item:hover{border-color:var(--cyan);}
.u-item.master{border-color:rgba(0,212,255,.35);background:rgba(0,212,255,.03);}
.u-label{font-size:9px;font-family:'Space Mono',monospace;color:var(--dim);text-transform:uppercase;letter-spacing:1px;margin-bottom:6px;display:block;}
.u-item.master .u-label{color:var(--cyan);}
.u-item input[type=file]{width:100%;font-size:10px;color:var(--text);background:transparent;border:none;outline:none;cursor:pointer;}
.u-item input[type=file]::file-selector-button{background:var(--card2);border:1px solid var(--border);color:var(--text);
  padding:3px 8px;border-radius:3px;font-size:8px;cursor:pointer;margin-right:6px;
  font-family:'Space Mono',monospace;transition:.2s;}
.u-item input[type=file]::file-selector-button:hover{background:var(--cyan);color:#000;}

/* ── STATUS ── */
.status-bar{display:flex;gap:8px;padding:0 28px;margin-bottom:-4px;flex-wrap:wrap;}
.s-pill{background:var(--card);border:1px solid var(--border);border-radius:20px;
  padding:4px 12px;font-size:9px;font-family:'Space Mono',monospace;color:var(--dim);
  display:flex;align-items:center;gap:5px;}
.s-dot{width:5px;height:5px;border-radius:50%;background:var(--green);box-shadow:0 0 5px var(--green);}
.s-dot.off{background:var(--dim);box-shadow:none;}

/* ── TABS ── */
.tab-bar{display:flex;padding:0 28px;border-bottom:1px solid var(--border);margin-top:16px;overflow-x:auto;}
.tab-btn{padding:12px 20px;font-family:'Space Mono',monospace;font-size:9px;letter-spacing:1.5px;
  text-transform:uppercase;color:var(--dim);background:transparent;border:none;cursor:pointer;
  position:relative;transition:.2s;white-space:nowrap;flex-shrink:0;}
.tab-btn::after{content:'';position:absolute;bottom:-1px;left:0;right:0;height:2px;
  background:var(--cyan);transform:scaleX(0);transition:.25s ease;box-shadow:0 0 8px var(--cyan);}
.tab-btn:hover{color:var(--text);}
.tab-btn.active{color:var(--cyan);}
.tab-btn.active::after{transform:scaleX(1);}
.tab-btn.t-ins.active{color:var(--green);}
.tab-btn.t-ins.active::after{background:var(--green);box-shadow:0 0 8px var(--green);}
.tab-btn.t-instr.active{color:var(--amber);}
.tab-btn.t-instr.active::after{background:var(--amber);box-shadow:0 0 8px var(--amber);}

.tab-pane{display:none;padding:24px 28px;}
.tab-pane.active{display:block;}

/* ── SECTION TITLE ── */
.sec-t{font-family:'Space Mono',monospace;font-size:9px;letter-spacing:3px;text-transform:uppercase;
  color:var(--dim);margin-bottom:14px;display:flex;align-items:center;gap:10px;}
.sec-t::after{content:'';flex:1;height:1px;background:var(--border);}

/* ── KPI STRIP ── */
.kpi-strip{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px;margin-bottom:20px;}
.kpi-card{background:var(--card);border:1px solid var(--border);border-radius:9px;padding:13px 15px;
  position:relative;overflow:hidden;transition:.2s;}
.kpi-card::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;}
.kpi-card.c-cyan::before{background:linear-gradient(90deg,var(--cyan),transparent);}
.kpi-card.c-green::before{background:linear-gradient(90deg,var(--green),transparent);}
.kpi-card.c-purple::before{background:linear-gradient(90deg,var(--purple),transparent);}
.kpi-card.c-amber::before{background:linear-gradient(90deg,var(--amber),transparent);}
.kpi-card.c-pink::before{background:linear-gradient(90deg,var(--pink),transparent);}
.kpi-card:hover{border-color:rgba(0,212,255,.2);}
.kpi-lbl{font-family:'Space Mono',monospace;font-size:7px;letter-spacing:1.5px;text-transform:uppercase;color:var(--dim);}
.kpi-val{font-family:'Space Mono',monospace;font-size:20px;font-weight:700;color:var(--text);line-height:1.1;margin:3px 0 2px;}
.kpi-sub{font-size:9px;color:var(--dim);}
.kpi-ico{position:absolute;right:12px;top:12px;font-size:16px;opacity:.2;}

/* ── EMPLOYEE CARDS ── */
.emp-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:16px;}
.emp-card{background:var(--card);border:1px solid var(--border);border-radius:11px;padding:16px;
  transition:.2s;position:relative;overflow:hidden;}
.emp-card::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;
  background:linear-gradient(90deg,var(--cyan),var(--purple));opacity:0;transition:.2s;}
.emp-card:hover{border-color:rgba(0,212,255,.3);transform:translateY(-2px);}
.emp-card:hover::before{opacity:1;}
.emp-head{display:flex;align-items:center;gap:10px;margin-bottom:10px;}
.emp-av{width:40px;height:40px;border-radius:9px;display:flex;align-items:center;justify-content:center;
  font-family:'Space Mono',monospace;font-size:13px;font-weight:700;flex-shrink:0;}
.emp-nm{font-size:13px;font-weight:600;}
.emp-id{font-family:'Space Mono',monospace;font-size:9px;color:var(--dim);margin-top:1px;}
.emp-badges{display:flex;gap:6px;margin-bottom:10px;flex-wrap:wrap;}
.e-badge{background:var(--card2);border:1px solid var(--border);border-radius:5px;padding:4px 9px;
  font-family:'Space Mono',monospace;font-size:8px;display:flex;flex-direction:column;gap:1px;}
.e-badge .bl{color:var(--dim);}
.e-badge .bv{font-size:12px;font-weight:700;}
.e-badge.tot .bv{color:var(--cyan);}
.e-badge.avg .bv{color:var(--green);}
.e-badge.best .bv{color:var(--yellow);}
.e-badge.weak .bv{color:var(--pink);}
.emp-ch{position:relative;height:190px;}

/* ── SERVICE TAB ── */
.svc-tot-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(165px,1fr));gap:9px;margin-bottom:20px;}
.stc{background:var(--card);border:1px solid var(--border);border-radius:9px;padding:12px 14px;}
.stc-lbl{font-family:'Space Mono',monospace;font-size:7px;letter-spacing:1px;text-transform:uppercase;color:var(--dim);margin-bottom:3px;}
.stc-val{font-family:'Space Mono',monospace;font-size:17px;font-weight:700;color:var(--cyan);}
.stc-avg{font-size:9px;color:var(--dim);margin-top:1px;}
.svc-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(380px,1fr));gap:16px;}
.svc-card{background:var(--card);border:1px solid var(--border);border-radius:11px;padding:16px;transition:.2s;}
.svc-card:hover{border-color:rgba(0,212,255,.2);}
.svc-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:11px;padding-bottom:9px;border-bottom:1px solid var(--border);}
.svc-name{font-family:'Space Mono',monospace;font-size:9px;letter-spacing:2px;text-transform:uppercase;color:var(--cyan);}
.svc-stats{display:flex;gap:10px;}
.svc-stat{font-family:'Space Mono',monospace;font-size:8px;color:var(--dim);display:flex;flex-direction:column;align-items:flex-end;}
.svc-stat strong{font-size:11px;color:var(--text);}
.svc-ch{position:relative;height:170px;}

/* ── DISTRIBUTION ── */
.dist-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(270px,1fr));gap:16px;}
.dist-card{background:var(--card);border:1px solid var(--border);border-radius:11px;padding:16px;transition:.2s;}
.dist-card:hover{border-color:rgba(124,58,255,.3);}
.dist-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:11px;padding-bottom:9px;border-bottom:1px solid var(--border);}
.dist-name{font-family:'Space Mono',monospace;font-size:9px;letter-spacing:1.5px;text-transform:uppercase;color:var(--purple);}
.dist-tot{font-family:'Space Mono',monospace;font-size:8px;color:var(--dim);background:var(--card2);padding:2px 8px;border-radius:3px;}
.dist-ch{position:relative;height:190px;}

/* ── INSIGHTS ── */
.ins-kpi{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:10px;margin-bottom:22px;}
.ik{background:var(--card);border:1px solid var(--border);border-radius:10px;padding:14px 16px;
  display:flex;gap:12px;align-items:center;transition:.2s;position:relative;overflow:hidden;}
.ik::before{content:'';position:absolute;top:0;left:0;bottom:0;width:3px;}
.ik.green::before{background:var(--green);}
.ik.cyan::before{background:var(--cyan);}
.ik.pink::before{background:var(--pink);}
.ik.amber::before{background:var(--amber);}
.ik.purple::before{background:var(--purple);}
.ik:hover{border-color:rgba(0,212,255,.2);}
.ik-ico{font-size:20px;flex-shrink:0;}
.ik-lbl{font-family:'Space Mono',monospace;font-size:7px;letter-spacing:1.5px;text-transform:uppercase;color:var(--dim);margin-bottom:2px;}
.ik-val{font-family:'Space Mono',monospace;font-size:17px;font-weight:700;}
.ik-sub{font-size:9px;color:var(--dim);margin-top:1px;}

.ins-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(400px,1fr));gap:16px;margin-bottom:20px;}
.ins-card{background:var(--card);border:1px solid var(--border);border-radius:11px;padding:16px;transition:.2s;}
.ins-card:hover{border-color:rgba(0,255,157,.15);}
.ins-title{font-family:'Space Mono',monospace;font-size:8px;letter-spacing:2px;text-transform:uppercase;
  color:var(--green);margin-bottom:12px;padding-bottom:9px;border-bottom:1px solid var(--border);
  display:flex;align-items:center;gap:7px;}
.ins-title span{font-size:13px;}
.ins-ch{position:relative;height:210px;}

.lb-wrap{background:var(--card);border:1px solid var(--border);border-radius:11px;padding:16px;margin-bottom:16px;}
.lb-title{font-family:'Space Mono',monospace;font-size:8px;letter-spacing:2px;text-transform:uppercase;color:var(--amber);margin-bottom:12px;padding-bottom:9px;border-bottom:1px solid var(--border);}
.lb-row{display:flex;align-items:center;gap:10px;padding:8px 0;border-bottom:1px solid rgba(37,37,53,.5);}
.lb-row:last-child{border-bottom:none;}
.lb-rank{font-family:'Space Mono',monospace;font-size:10px;font-weight:700;width:22px;text-align:center;flex-shrink:0;}
.lb-rank.r1{color:var(--yellow);}
.lb-rank.r2{color:#c0c0c0;}
.lb-rank.r3{color:#cd7f32;}
.lb-rank.rn{color:var(--dim);}
.lb-av{width:28px;height:28px;border-radius:6px;display:flex;align-items:center;justify-content:center;
  font-family:'Space Mono',monospace;font-size:10px;font-weight:700;flex-shrink:0;}
.lb-info{flex:1;min-width:0;}
.lb-name{font-size:12px;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.lb-sub{font-size:9px;color:var(--dim);}
.lb-bar-wrap{flex:1;background:var(--bg2);border-radius:3px;height:5px;max-width:100px;}
.lb-bar{height:100%;border-radius:3px;}
.lb-score{font-family:'Space Mono',monospace;font-size:11px;font-weight:700;min-width:34px;text-align:right;}

.radar-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:16px;}
.radar-card{background:var(--card);border:1px solid var(--border);border-radius:11px;padding:14px;transition:.2s;}
.radar-card:hover{border-color:rgba(124,58,255,.25);}
.radar-head{display:flex;align-items:center;gap:9px;margin-bottom:10px;}
.radar-av{width:30px;height:30px;border-radius:7px;display:flex;align-items:center;justify-content:center;
  font-family:'Space Mono',monospace;font-size:10px;font-weight:700;flex-shrink:0;}
.radar-nm{font-size:11px;font-weight:600;}
.radar-sc{font-family:'Space Mono',monospace;font-size:9px;color:var(--dim);}
.radar-ch{position:relative;height:190px;}

/* ── INSTRUCTIONS ── */
.intr-intro{background:linear-gradient(135deg,rgba(255,171,64,.06),rgba(255,171,64,.02));
  border:1px solid rgba(255,171,64,.2);border-radius:11px;padding:16px 20px;margin-bottom:18px;
  display:flex;gap:12px;align-items:flex-start;}
.ii-title{font-family:'Space Mono',monospace;font-size:9px;letter-spacing:2px;text-transform:uppercase;color:var(--amber);margin-bottom:5px;}
.ii-text{font-size:11px;color:var(--dim);line-height:1.7;}
.ii-text strong{color:var(--text);}
.sub-tabs{display:flex;border-bottom:1px solid var(--border);margin-bottom:18px;overflow-x:auto;}
.st-btn{padding:8px 16px;font-family:'Space Mono',monospace;font-size:8px;letter-spacing:1.5px;
  text-transform:uppercase;color:var(--dim);background:transparent;border:none;cursor:pointer;
  position:relative;transition:.2s;white-space:nowrap;}
.st-btn::after{content:'';position:absolute;bottom:-1px;left:0;right:0;height:1px;background:var(--amber);transform:scaleX(0);transition:.2s;}
.st-btn.active{color:var(--amber);}
.st-btn.active::after{transform:scaleX(1);}
.sub-panel{display:none;}
.sub-panel.active{display:block;}
.master-layout{display:grid;grid-template-columns:1.1fr .9fr;gap:16px;}
@media(max-width:760px){.master-layout{grid-template-columns:1fr;}}
.i-card{background:var(--card);border:1px solid var(--border);border-radius:11px;overflow:hidden;}
.i-card-head{display:flex;align-items:center;justify-content:space-between;padding:11px 15px;background:var(--card2);border-bottom:1px solid var(--border);}
.i-card-title{font-family:'Space Mono',monospace;font-size:9px;letter-spacing:1px;color:var(--text);}
.badge{font-family:'Space Mono',monospace;font-size:7px;padding:2px 7px;border-radius:3px;text-transform:uppercase;letter-spacing:.5px;}
.bm{background:rgba(0,212,255,.12);color:var(--cyan);border:1px solid rgba(0,212,255,.25);}
.bs{background:rgba(255,171,64,.12);color:var(--amber);border:1px solid rgba(255,171,64,.25);}
.bt{background:rgba(107,107,138,.12);color:var(--dim);border:1px solid rgba(107,107,138,.2);}
.i-card-body{padding:14px;}
.col-tbl{width:100%;border-collapse:collapse;}
.col-tbl th{font-family:'Space Mono',monospace;font-size:7px;letter-spacing:1.5px;text-transform:uppercase;color:var(--dim);padding:6px 10px;text-align:left;border-bottom:1px solid var(--border);background:rgba(255,255,255,.02);}
.col-tbl td{padding:7px 10px;font-size:10px;color:var(--text);border-bottom:1px solid rgba(37,37,53,.5);vertical-align:top;}
.col-tbl tr:last-child td{border-bottom:none;}
.col-tbl tr:hover td{background:rgba(255,255,255,.015);}
.cn{font-family:'Space Mono',monospace;font-size:9px;color:var(--cyan);}
.cn.a{color:var(--amber);}
.ct{font-family:'Space Mono',monospace;font-size:8px;color:var(--purple);}
.cd{color:var(--dim);font-size:10px;line-height:1.5;}
.req-y{background:rgba(255,45,120,.1);color:#ff7aaa;font-family:'Space Mono',monospace;font-size:7px;padding:2px 5px;border-radius:2px;}
.req-n{background:rgba(107,107,138,.12);color:var(--dim);font-family:'Space Mono',monospace;font-size:7px;padding:2px 5px;border-radius:2px;}
.csv-pre{background:#0d0d16;border:1px solid var(--border);border-radius:7px;padding:11px 13px;margin-top:10px;overflow-x:auto;}
.csv-pre-lbl{font-family:'Space Mono',monospace;font-size:7px;letter-spacing:1.5px;text-transform:uppercase;color:var(--dim);margin-bottom:5px;}
.csv-pre pre{font-family:'Space Mono',monospace;font-size:9.5px;line-height:1.8;white-space:pre;}
.ch{color:var(--amber);}
.cr{color:#6b9bff;}
.svc-instr-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:12px;}
.si-card{background:var(--card);border:1px solid var(--border);border-radius:10px;overflow:hidden;transition:.2s;}
.si-card:hover{border-color:rgba(255,171,64,.2);}
.si-head{display:flex;align-items:center;gap:7px;padding:10px 13px;background:var(--card2);border-bottom:1px solid var(--border);}
.si-num{font-family:'Space Mono',monospace;font-size:8px;color:var(--dim);background:rgba(255,255,255,.05);padding:1px 5px;border-radius:2px;}
.si-name{font-family:'Space Mono',monospace;font-size:9px;color:var(--text);flex:1;}
.si-file{font-family:'Space Mono',monospace;font-size:8px;color:var(--amber);background:rgba(255,171,64,.08);padding:2px 7px;border-radius:3px;border:1px solid rgba(255,171,64,.2);white-space:nowrap;}
.si-body{padding:12px 13px;}
.si-desc{font-size:10px;color:var(--dim);line-height:1.6;margin-bottom:9px;}
.si-cols{display:flex;flex-direction:column;gap:5px;margin-bottom:9px;}
.si-col{display:flex;align-items:flex-start;gap:7px;padding:5px 8px;background:rgba(255,255,255,.02);border-radius:5px;}
.si-cn{font-family:'Space Mono',monospace;font-size:8px;color:var(--amber);min-width:110px;flex-shrink:0;}
.si-ci{flex:1;}
.si-ct{font-family:'Space Mono',monospace;font-size:7px;color:var(--purple);margin-bottom:1px;}
.si-cd{font-size:9px;color:var(--dim);line-height:1.4;}
.si-req{font-family:'Space Mono',monospace;font-size:7px;padding:1px 5px;border-radius:2px;flex-shrink:0;align-self:center;}
.si-sample{background:#0d0d16;border:1px solid var(--border);border-radius:5px;padding:8px 10px;overflow-x:auto;}
.si-sample pre{font-family:'Space Mono',monospace;font-size:9px;line-height:1.8;white-space:pre;}
.rules-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:12px;margin-bottom:18px;}
.rule{background:var(--card);border:1px solid var(--border);border-radius:9px;padding:14px;display:flex;gap:11px;transition:.2s;}
.rule:hover{border-color:rgba(0,212,255,.2);}
.rule-ico{width:32px;height:32px;border-radius:7px;display:flex;align-items:center;justify-content:center;font-size:14px;flex-shrink:0;}
.ri-c{background:rgba(0,212,255,.1);}
.ri-a{background:rgba(255,171,64,.1);}
.ri-p{background:rgba(124,58,255,.1);}
.ri-g{background:rgba(0,255,157,.08);}
.rule-title{font-family:'Space Mono',monospace;font-size:8px;letter-spacing:1px;text-transform:uppercase;color:var(--text);margin-bottom:4px;}
.rule-desc{font-size:10px;color:var(--dim);line-height:1.6;}
.tip-box{background:rgba(0,255,157,.04);border:1px solid rgba(0,255,157,.15);border-radius:9px;padding:12px 16px;display:flex;gap:10px;}
.tip-txt{font-size:11px;color:var(--dim);line-height:1.7;}
.tip-txt strong{color:var(--green);}
</style>
</head>
<body>
<div class="wrap">

<!-- EXPORT OVERLAY -->
<div class="export-overlay" id="expOverlay">
  <div class="export-spinner"></div>
  <div class="export-msg" id="expMsg">Generating...</div>
  <div class="export-sub" id="expSub">Please wait</div>
</div>

<!-- HEADER -->
<header>
  <div class="logo">
    <div class="logo-icon">HR</div>
    <div class="logo-text">Performance <span>Analytics</span></div>
  </div>
  <div class="header-right">
    <div class="export-btns">
      <button class="exp-btn pdf" onclick="exportPDF()"><span class="exp-icon">📄</span>Export PDF</button>
      <button class="exp-btn pptx" onclick="exportPPTX()"><span class="exp-icon">📊</span>Export PPTX</button>
      <button class="exp-btn all" onclick="exportBoth()"><span class="exp-icon">⬇</span>Export All</button>
    </div>
    <div class="hdr-mode"><strong id="hdrMode">MOCK DATA</strong>v4.0</div>
  </div>
</header>

<!-- UPLOAD -->
<div class="upload-wrap">
  <div class="upload-head" id="upHead">
    <div class="upload-head-left">CSV Data Import</div>
    <div class="upload-arrow" id="upArrow">▼</div>
  </div>
  <div class="upload-body" id="upBody">
    <div class="upload-grid">
      <div class="u-item master"><label class="u-label">★ Master CSV</label><input type="file" accept=".csv" id="up_master"></div>
      <div class="u-item"><label class="u-label">Hiring</label><input type="file" accept=".csv" id="up_hiring"></div>
      <div class="u-item"><label class="u-label">Status Update</label><input type="file" accept=".csv" id="up_employee_status_update"></div>
      <div class="u-item"><label class="u-label">Adv. Children Edu</label><input type="file" accept=".csv" id="up_advance_children_education"></div>
      <div class="u-item"><label class="u-label">Education Settlement</label><input type="file" accept=".csv" id="up_education_settlement"></div>
      <div class="u-item"><label class="u-label">Wages</label><input type="file" accept=".csv" id="up_wages"></div>
      <div class="u-item"><label class="u-label">Work Schedule / OT</label><input type="file" accept=".csv" id="up_work_schedule_ot"></div>
      <div class="u-item"><label class="u-label">Internal Transfer</label><input type="file" accept=".csv" id="up_internal_transfer"></div>
      <div class="u-item"><label class="u-label">HOP</label><input type="file" accept=".csv" id="up_hop"></div>
      <div class="u-item"><label class="u-label">Separation</label><input type="file" accept=".csv" id="up_separation"></div>
      <div class="u-item"><label class="u-label">Grievance</label><input type="file" accept=".csv" id="up_grievance"></div>
      <div class="u-item"><label class="u-label">Investigation Cases</label><input type="file" accept=".csv" id="up_investigation_cases"></div>
    </div>
  </div>
</div>

<!-- STATUS -->
<div class="status-bar">
  <div class="s-pill"><span class="s-dot"></span>7 Employees</div>
  <div class="s-pill"><span class="s-dot"></span>11 Services</div>
  <div class="s-pill" id="modePill"><span class="s-dot off"></span>Mock Data</div>
  <div class="s-pill"><span class="s-dot" style="background:var(--amber);box-shadow:0 0 5px var(--amber)"></span>Grand Total: <strong id="gtVal" style="color:var(--amber);font-family:'Space Mono',monospace;font-size:9px;margin-left:3px;">—</strong></div>
</div>

<!-- TAB BAR -->
<div class="tab-bar">
  <button class="tab-btn active" data-tab="tp-emp">Per Employee</button>
  <button class="tab-btn" data-tab="tp-svc">Per Service</button>
  <button class="tab-btn" data-tab="tp-dist">Distribution</button>
  <button class="tab-btn t-ins" data-tab="tp-insights">Insights</button>
  <button class="tab-btn t-instr" data-tab="tp-instr">CSV Instructions</button>
</div>

<!-- TAB 1 -->
<div class="tab-pane active" id="tp-emp">
  <div class="sec-t">KPI Overview</div>
  <div class="kpi-strip" id="empKpi"></div>
  <div class="sec-t">Employee Performance Cards</div>
  <div class="emp-grid" id="empGrid"></div>
</div>

<!-- TAB 2 -->
<div class="tab-pane" id="tp-svc">
  <div class="sec-t">Service Totals</div>
  <div class="svc-tot-grid" id="svcTotals"></div>
  <div class="sec-t">Service Comparison Charts</div>
  <div class="svc-grid" id="svcGrid"></div>
</div>

<!-- TAB 3 -->
<div class="tab-pane" id="tp-dist">
  <div class="sec-t">Distribution by Service</div>
  <div class="dist-grid" id="distGrid"></div>
</div>

<!-- TAB 4: INSIGHTS -->
<div class="tab-pane" id="tp-insights">
  <div class="sec-t">Performance Intelligence</div>
  <div class="ins-kpi" id="insKpi"></div>
  <div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:20px;">
    <div class="lb-wrap"><div class="lb-title">🏆 Overall Ranking</div><div id="lbList"></div></div>
    <div class="ins-card"><div class="ins-title"><span>📡</span>Team Average by Service</div><div class="ins-ch"><canvas id="teamAvgChart"></canvas></div></div>
  </div>
  <div class="sec-t">Individual Radar Profiles</div>
  <div class="radar-grid" id="radarGrid"></div>
  <div class="sec-t">Comparative Insights</div>
  <div class="ins-grid" id="insGrid"></div>
</div>

<!-- TAB 5: INSTRUCTIONS -->
<div class="tab-pane" id="tp-instr">
  <div class="intr-intro">
    <div style="font-size:20px;flex-shrink:0;">📋</div>
    <div>
      <div class="ii-title">CSV Format Guide</div>
      <div class="ii-text">Upload a <strong>Master CSV</strong> (all 11 services) or <strong>individual service CSVs</strong>. Service files override master for that service. Headers are <strong>case-insensitive</strong>; spaces normalize to underscores.</div>
    </div>
  </div>
  <div class="sub-tabs">
    <button class="st-btn active" data-panel="sp-master">Master CSV</button>
    <button class="st-btn" data-panel="sp-services">Service CSVs</button>
    <button class="st-btn" data-panel="sp-rules">Merge Rules</button>
  </div>
  <div class="sub-panel active" id="sp-master">
    <div class="master-layout">
      <div class="i-card">
        <div class="i-card-head"><span class="i-card-title">master.csv — Column Reference</span><span class="badge bm">Master</span></div>
        <div class="i-card-body">
          <table class="col-tbl">
            <thead><tr><th>Column</th><th>Type</th><th>Req</th><th>Description</th></tr></thead>
            <tbody>
              <tr><td><span class="cn">employee_id</span></td><td><span class="ct">string</span></td><td><span class="req-y">Yes</span></td><td class="cd">Primary key — unique per employee, used for cross-file merging.</td></tr>
              <tr><td><span class="cn">employee_name</span></td><td><span class="ct">string</span></td><td><span class="req-y">Yes</span></td><td class="cd">Full display name shown in all cards and charts.</td></tr>
              <tr><td><span class="cn">hiring</span></td><td><span class="ct">number</span></td><td><span class="req-y">Yes</span></td><td class="cd">Score for Hiring service (0–100).</td></tr>
              <tr><td><span class="cn">employee_status_update</span></td><td><span class="ct">number</span></td><td><span class="req-y">Yes</span></td><td class="cd">Score for status change transactions.</td></tr>
              <tr><td><span class="cn">advance_children_education</span></td><td><span class="ct">number</span></td><td><span class="req-y">Yes</span></td><td class="cd">Advance on children's education benefit score.</td></tr>
              <tr><td><span class="cn">education_settlement</span></td><td><span class="ct">number</span></td><td><span class="req-y">Yes</span></td><td class="cd">Education reimbursement settlement score.</td></tr>
              <tr><td><span class="cn">wages</span></td><td><span class="ct">number</span></td><td><span class="req-y">Yes</span></td><td class="cd">Wage processing / payroll score.</td></tr>
              <tr><td><span class="cn">work_schedule_ot</span></td><td><span class="ct">number</span></td><td><span class="req-y">Yes</span></td><td class="cd">Work schedule and overtime approval score.</td></tr>
              <tr><td><span class="cn">internal_transfer</span></td><td><span class="ct">number</span></td><td><span class="req-y">Yes</span></td><td class="cd">Internal transfer requests score.</td></tr>
              <tr><td><span class="cn">hop</span></td><td><span class="ct">number</span></td><td><span class="req-y">Yes</span></td><td class="cd">HOP (Head of Personnel) transaction score.</td></tr>
              <tr><td><span class="cn">separation</span></td><td><span class="ct">number</span></td><td><span class="req-y">Yes</span></td><td class="cd">Separation / offboarding case score.</td></tr>
              <tr><td><span class="cn">grievance</span></td><td><span class="ct">number</span></td><td><span class="req-y">Yes</span></td><td class="cd">Grievance cases handled score.</td></tr>
              <tr><td><span class="cn">investigation_cases</span></td><td><span class="ct">number</span></td><td><span class="req-y">Yes</span></td><td class="cd">Investigation cases closed score.</td></tr>
            </tbody>
          </table>
        </div>
      </div>
      <div style="display:flex;flex-direction:column;gap:12px;">
        <div class="i-card">
          <div class="i-card-head"><span class="i-card-title">Sample — master.csv</span><span class="badge bt">Example</span></div>
          <div class="i-card-body">
            <div class="csv-pre"><div class="csv-pre-lbl">CSV Preview</div>
              <pre><span class="ch">employee_id,employee_name,hiring,employee_status_update,advance_children_education,education_settlement,wages,work_schedule_ot,internal_transfer,hop,separation,grievance,investigation_cases</span>
<span class="cr">EMP001,Ahmed Al-Rashidi,85,72,90,68,77,55,80,62,71,45,58</span>
<span class="cr">EMP002,Sara Binsaleh,91,88,74,82,95,70,65,79,83,60,72</span>
<span class="cr">EMP003,Omar Khalidi,67,55,80,73,60,85,90,50,66,77,88</span></pre>
            </div>
          </div>
        </div>
        <div class="i-card">
          <div class="i-card-head"><span class="i-card-title">Formatting Notes</span><span class="badge bt">Tips</span></div>
          <div class="i-card-body">
            <div class="si-cols" style="gap:5px;">
              <div class="si-col"><div class="si-cn" style="color:var(--cyan)">Header row</div><div class="si-ci"><div class="si-cd">Must be line 1. Auto-lowercased; spaces → underscores.</div></div></div>
              <div class="si-col"><div class="si-cn" style="color:var(--cyan)">Delimiter</div><div class="si-ci"><div class="si-cd">Comma only. No tabs or semicolons.</div></div></div>
              <div class="si-col"><div class="si-cn" style="color:var(--cyan)">Values</div><div class="si-ci"><div class="si-cd">Integers or decimals. Empty → 0.</div></div></div>
              <div class="si-col"><div class="si-cn" style="color:var(--cyan)">Encoding</div><div class="si-ci"><div class="si-cd">UTF-8 without BOM recommended.</div></div></div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
  <div class="sub-panel" id="sp-services">
    <div class="i-card" style="margin-bottom:16px;">
      <div class="i-card-head"><span class="i-card-title">Universal 3-Column Structure — all service CSVs</span><span class="badge bs">All Services</span></div>
      <div class="i-card-body">
        <table class="col-tbl">
          <thead><tr><th>Column</th><th>Type</th><th>Req</th><th>Description</th></tr></thead>
          <tbody>
            <tr><td><span class="cn a">employee_id</span></td><td><span class="ct">string</span></td><td><span class="req-y">Yes</span></td><td class="cd">Unique employee ID. Must match existing data for correct merging.</td></tr>
            <tr><td><span class="cn a">employee_name</span></td><td><span class="ct">string</span></td><td><span class="req-n">Opt</span></td><td class="cd">Full name. Updates display if provided; preserved if omitted.</td></tr>
            <tr><td><span class="cn a">value</span></td><td><span class="ct">number</span></td><td><span class="req-y">Yes</span></td><td class="cd">Score for this specific service. Always named <code style="color:var(--amber)">value</code>.</td></tr>
          </tbody>
        </table>
      </div>
    </div>
    <div class="svc-instr-grid" id="svcInstrGrid"></div>
  </div>
  <div class="sub-panel" id="sp-rules">
    <div class="rules-grid">
      <div class="rule"><div class="rule-ico ri-c">📥</div><div><div class="rule-title">Master CSV Priority</div><div class="rule-desc">Populates all 11 services for all employees at once. Baseline dataset.</div></div></div>
      <div class="rule"><div class="rule-ico ri-a">🔄</div><div><div class="rule-title">Service Override</div><div class="rule-desc">Individual CSV overrides only that service. Others remain unchanged.</div></div></div>
      <div class="rule"><div class="rule-ico ri-p">🆕</div><div><div class="rule-title">Auto-Create Employee</div><div class="rule-desc">Unknown employee_id in service CSV creates employee with 0s elsewhere.</div></div></div>
      <div class="rule"><div class="rule-ico ri-g">🎭</div><div><div class="rule-title">Mock Fallback</div><div class="rule-desc">No CSV? Randomized mock data for 7 employees across 11 services.</div></div></div>
      <div class="rule"><div class="rule-ico ri-c">🔁</div><div><div class="rule-title">Auto Re-render</div><div class="rule-desc">All tabs, charts, totals, PDF/PPTX use fresh data on every upload.</div></div></div>
      <div class="rule"><div class="rule-ico ri-a">⚠️</div><div><div class="rule-title">Empty → 0</div><div class="rule-desc">Non-numeric or empty cells silently default to 0.</div></div></div>
    </div>
    <div class="tip-box">
      <div style="font-size:16px;flex-shrink:0;">💡</div>
      <div class="tip-txt"><strong>Recommended:</strong> Use master.csv for a full data load, then individual service CSVs for incremental updates. The Export PDF and Export PPTX buttons always reflect the current data state.</div>
    </div>
  </div>
</div>

</div><!-- /wrap -->

<script>
/* ═══════════════════ CONFIG ═══════════════════ */
const SVCS=[
  {key:'hiring',              label:'Hiring',            file:'hiring.csv',              desc:'Hiring transaction scores per employee.'},
  {key:'employee_status_update',label:'Status Update',   file:'employee_status_update.csv',desc:'Employee status change and update scores.'},
  {key:'advance_children_education',label:'Adv.Child Edu',file:'advance_children_education.csv',desc:'Advance on children\'s education benefit.'},
  {key:'education_settlement',label:'Edu Settlement',    file:'education_settlement.csv', desc:'Education reimbursement settlement scores.'},
  {key:'wages',               label:'Wages',             file:'wages.csv',               desc:'Wage processing and payroll scores.'},
  {key:'work_schedule_ot',    label:'Work Sched/OT',     file:'work_schedule_ot.csv',    desc:'Work schedule and overtime approval scores.'},
  {key:'internal_transfer',   label:'Int. Transfer',     file:'internal_transfer.csv',   desc:'Internal transfer request scores.'},
  {key:'hop',                 label:'HOP',               file:'hop.csv',                 desc:'Head of Personnel transaction scores.'},
  {key:'separation',          label:'Separation',        file:'separation.csv',          desc:'Separation and offboarding case scores.'},
  {key:'grievance',           label:'Grievance',         file:'grievance.csv',           desc:'Grievance cases handled scores.'},
  {key:'investigation_cases', label:'Investigation',     file:'investigation_cases.csv', desc:'Investigation cases closed scores.'}
];

const ECOLS=['00d4ff','ff2d78','7c3aff','00ff9d','ff6b35','ffd700','e040fb'];
const ECOLS_CSS=ECOLS.map(c=>`#${c}`);
const AVT=[
  'linear-gradient(135deg,#00d4ff,#7c3aff)',
  'linear-gradient(135deg,#ff2d78,#ff6b35)',
  'linear-gradient(135deg,#7c3aff,#e040fb)',
  'linear-gradient(135deg,#00ff9d,#00d4ff)',
  'linear-gradient(135deg,#ff6b35,#ffd700)',
  'linear-gradient(135deg,#ffd700,#ff2d78)',
  'linear-gradient(135deg,#e040fb,#7c3aff)'
];
const MOCK=[
  {id:'EMP001',name:'Ahmed Al-Rashidi'},{id:'EMP002',name:'Sara Binsaleh'},
  {id:'EMP003',name:'Omar Khalidi'},{id:'EMP004',name:'Nora Al-Farsi'},
  {id:'EMP005',name:'Yousef Mansour'},{id:'EMP006',name:'Lina Qureshi'},
  {id:'EMP007',name:'Tariq Bashir'}
];

function rnd(a,b){return Math.floor(Math.random()*(b-a+1))+a;}
function mockData(){const d={};MOCK.forEach(e=>{d[e.id]={id:e.id,name:e.name};SVCS.forEach(s=>{d[e.id][s.key]=rnd(30,100);});});return d;}
let DATA=mockData();
const CH={emp:{},svc:{},dist:{},ins:{}};

/* helpers */
function EA(){return Object.values(DATA);}
function eTotal(e){return SVCS.reduce((s,sv)=>s+(e[sv.key]||0),0);}
function eAvg(e){return eTotal(e)/SVCS.length;}
function eBest(e){return SVCS.reduce((b,s)=>(e[s.key]||0)>(e[b.key]||0)?s:b,SVCS[0]);}
function eWorst(e){return SVCS.reduce((w,s)=>(e[s.key]||0)<(e[w.key]||0)?s:w,SVCS[0]);}
function sTotal(k){return EA().reduce((s,e)=>s+(e[k]||0),0);}
function sAvg(k){return sTotal(k)/EA().length;}
function GT(){return EA().reduce((s,e)=>s+eTotal(e),0);}
function INI(n){return n.split(' ').map(w=>w[0]).join('').slice(0,2).toUpperCase();}
function idx(e){return EA().indexOf(e);}

Chart.defaults.color='#6b6b8a';
Chart.defaults.borderColor='#252535';
Chart.defaults.font.family='DM Sans';
const CB=()=>({responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false},tooltip:{backgroundColor:'#1a1a28',borderColor:'#252535',borderWidth:1,titleColor:'#d4d4e8',bodyColor:'#6b6b8a',padding:9,cornerRadius:5}}});
function destroy(o){Object.values(o).forEach(c=>{try{c&&c.destroy&&c.destroy();}catch(e){}});}

/* ═══ KPI STRIP ═══ */
function renderKpi(){
  const arr=EA(),gt=GT(),avg=(gt/(arr.length*SVCS.length)).toFixed(1);
  const sorted=[...arr].sort((a,b)=>eTotal(b)-eTotal(a));
  const top=sorted[0],bot=sorted[sorted.length-1];
  const bSvc=SVCS.reduce((b,s)=>sTotal(s.key)>sTotal(b.key)?s:b,SVCS[0]);
  const wSvc=SVCS.reduce((b,s)=>sTotal(s.key)<sTotal(b.key)?s:b,SVCS[0]);
  document.getElementById('empKpi').innerHTML=`
    <div class="kpi-card c-cyan"><div class="kpi-lbl">Grand Total</div><div class="kpi-val">${gt}</div><div class="kpi-sub">All employees × all services</div><div class="kpi-ico">∑</div></div>
    <div class="kpi-card c-green"><div class="kpi-lbl">Team Average</div><div class="kpi-val">${avg}</div><div class="kpi-sub">Per employee per service</div><div class="kpi-ico">~</div></div>
    <div class="kpi-card c-amber"><div class="kpi-lbl">Top Performer</div><div class="kpi-val">${top.name.split(' ')[0]}</div><div class="kpi-sub">Total: ${eTotal(top)}</div><div class="kpi-ico">🏆</div></div>
    <div class="kpi-card c-pink"><div class="kpi-lbl">Needs Support</div><div class="kpi-val">${bot.name.split(' ')[0]}</div><div class="kpi-sub">Total: ${eTotal(bot)}</div><div class="kpi-ico">📌</div></div>
    <div class="kpi-card c-cyan"><div class="kpi-lbl">Strongest Svc</div><div class="kpi-val">${bSvc.label}</div><div class="kpi-sub">Total: ${sTotal(bSvc.key)}</div><div class="kpi-ico">⬆</div></div>
    <div class="kpi-card c-purple"><div class="kpi-lbl">Weakest Svc</div><div class="kpi-val">${wSvc.label}</div><div class="kpi-sub">Total: ${sTotal(wSvc.key)}</div><div class="kpi-ico">⬇</div></div>
  `;
  document.getElementById('gtVal').textContent=gt;
}

/* ═══ TAB 1 ═══ */
function renderEmp(){
  renderKpi(); destroy(CH.emp); CH.emp={};
  const g=document.getElementById('empGrid'); g.innerHTML='';
  EA().forEach((emp,i)=>{
    const t=eTotal(emp),a=eAvg(emp).toFixed(1),b=eBest(emp),w=eWorst(emp);
    const c=document.createElement('div'); c.className='emp-card';
    c.innerHTML=`<div class="emp-head"><div class="emp-av" style="background:${AVT[i%AVT.length]};color:#fff">${INI(emp.name)}</div><div><div class="emp-nm">${emp.name}</div><div class="emp-id">${emp.id}</div></div></div>
      <div class="emp-badges">
        <div class="e-badge tot"><span class="bl">TOTAL</span><span class="bv">${t}</span></div>
        <div class="e-badge avg"><span class="bl">AVG</span><span class="bv">${a}</span></div>
        <div class="e-badge best"><span class="bl">BEST</span><span class="bv">${b.label}</span></div>
        <div class="e-badge weak"><span class="bl">WEAK</span><span class="bv">${w.label}</span></div>
      </div>
      <div class="emp-ch"><canvas id="ec-${emp.id}"></canvas></div>`;
    g.appendChild(c);
    const ctx=c.querySelector(`#ec-${emp.id}`).getContext('2d');
    const col=ECOLS_CSS[i%ECOLS_CSS.length];
    CH.emp[emp.id]=new Chart(ctx,{type:'bar',data:{labels:SVCS.map(s=>s.label),datasets:[{data:SVCS.map(s=>emp[s.key]||0),backgroundColor:SVCS.map((_,j)=>`${col}${(70+j*8).toString(16).padStart(2,'0')}`),borderColor:col,borderWidth:1,borderRadius:4,borderSkipped:false}]},options:{...CB(),indexAxis:'y',scales:{x:{beginAtZero:true,max:100,grid:{color:'#1e1e30'},ticks:{font:{size:8}}},y:{grid:{display:false},ticks:{font:{size:8}}}}}});
  });
}

/* ═══ TAB 2 ═══ */
function renderSvc(){
  destroy(CH.svc); CH.svc={};
  const ts=document.getElementById('svcTotals'); ts.innerHTML='';
  SVCS.forEach(s=>{const t=sTotal(s.key),a=sAvg(s.key).toFixed(1);const d=document.createElement('div');d.className='stc';d.innerHTML=`<div class="stc-lbl">${s.label}</div><div class="stc-val">${t}</div><div class="stc-avg">avg ${a}/emp</div>`;ts.appendChild(d);});
  const g=document.getElementById('svcGrid'); g.innerHTML='';
  SVCS.forEach(s=>{
    const emps=EA(),vals=emps.map(e=>e[s.key]||0),t=sTotal(s.key),a=sAvg(s.key).toFixed(1);
    const c=document.createElement('div');c.className='svc-card';
    c.innerHTML=`<div class="svc-head"><div class="svc-name">${s.label}</div><div class="svc-stats"><div class="svc-stat"><span>TOTAL</span><strong>${t}</strong></div><div class="svc-stat"><span>AVG</span><strong>${a}</strong></div></div></div><div class="svc-ch"><canvas id="sc-${s.key}"></canvas></div>`;
    g.appendChild(c);
    const ctx=c.querySelector(`#sc-${s.key}`).getContext('2d');
    CH.svc[s.key]=new Chart(ctx,{type:'bar',data:{labels:emps.map(e=>e.name.split(' ')[0]),datasets:[{data:vals,backgroundColor:ECOLS_CSS.map(c=>c+'99'),borderColor:ECOLS_CSS,borderWidth:1.5,borderRadius:5,borderSkipped:false}]},options:{...CB(),scales:{y:{beginAtZero:true,max:100,grid:{color:'#1e1e30'},ticks:{font:{size:8}}},x:{grid:{display:false},ticks:{font:{size:9}}}}}});
  });
}

/* ═══ TAB 3 ═══ */
function renderDist(){
  destroy(CH.dist); CH.dist={};
  const g=document.getElementById('distGrid'); g.innerHTML='';
  SVCS.forEach(s=>{
    const emps=EA(),vals=emps.map(e=>e[s.key]||0),t=sTotal(s.key);
    const c=document.createElement('div');c.className='dist-card';
    c.innerHTML=`<div class="dist-head"><div class="dist-name">${s.label}</div><div class="dist-tot">Total: ${t}</div></div><div class="dist-ch"><canvas id="dc-${s.key}"></canvas></div>`;
    g.appendChild(c);
    const ctx=c.querySelector(`#dc-${s.key}`).getContext('2d');
    CH.dist[s.key]=new Chart(ctx,{type:'doughnut',data:{labels:emps.map(e=>e.name.split(' ')[0]),datasets:[{data:vals,backgroundColor:ECOLS_CSS.map(c=>c+'cc'),borderColor:'#14141e',borderWidth:2}]},options:{...CB(),cutout:'60%',plugins:{...CB().plugins,legend:{display:true,position:'bottom',labels:{color:'#9090b0',font:{size:8},boxWidth:8,padding:6}}}}});
  });
}

/* ═══ TAB 4: INSIGHTS ═══ */
function renderInsights(){
  const arr=EA(),sorted=[...arr].sort((a,b)=>eTotal(b)-eTotal(a));
  const gt=GT(),pct=((gt/(arr.length*SVCS.length*100))*100).toFixed(1);
  const top=sorted[0],bot=sorted[sorted.length-1],bSvc=SVCS.reduce((b,s)=>sTotal(s.key)>sTotal(b.key)?s:b,SVCS[0]);
  document.getElementById('insKpi').innerHTML=`
    <div class="ik green"><div class="ik-ico">🎯</div><div><div class="ik-lbl">Efficiency</div><div class="ik-val" style="color:var(--green)">${pct}%</div><div class="ik-sub">of max possible</div></div></div>
    <div class="ik cyan"><div class="ik-ico">📊</div><div><div class="ik-lbl">Grand Total</div><div class="ik-val" style="color:var(--cyan)">${gt}</div><div class="ik-sub">${arr.length}emp × ${SVCS.length}svc</div></div></div>
    <div class="ik amber"><div class="ik-ico">🏆</div><div><div class="ik-lbl">Top Performer</div><div class="ik-val" style="color:var(--amber)">${top.name.split(' ')[0]}</div><div class="ik-sub">Score ${eTotal(top)}</div></div></div>
    <div class="ik pink"><div class="ik-ico">📌</div><div><div class="ik-lbl">Needs Support</div><div class="ik-val" style="color:var(--pink)">${bot.name.split(' ')[0]}</div><div class="ik-sub">Gap: ${eTotal(top)-eTotal(bot)}</div></div></div>
    <div class="ik purple"><div class="ik-ico">⬆</div><div><div class="ik-lbl">Best Service</div><div class="ik-val" style="color:var(--purple)">${bSvc.label}</div><div class="ik-sub">Total ${sTotal(bSvc.key)}</div></div></div>
  `;
  /* leaderboard */
  const lb=document.getElementById('lbList'); lb.innerHTML='';
  const maxT=eTotal(sorted[0]);
  sorted.forEach((emp,i)=>{
    const t=eTotal(emp),ei=arr.indexOf(emp),col=ECOLS_CSS[ei%ECOLS_CSS.length];
    const rc=i===0?'r1':i===1?'r2':i===2?'r3':'rn';
    const ri=i===0?'🥇':i===1?'🥈':i===2?'🥉':`#${i+1}`;
    lb.innerHTML+=`<div class="lb-row"><div class="lb-rank ${rc}">${ri}</div><div class="lb-av" style="background:${AVT[ei%AVT.length]};color:#fff">${INI(emp.name)}</div><div class="lb-info"><div class="lb-name">${emp.name}</div><div class="lb-sub">Avg ${eAvg(emp).toFixed(1)} · Best: ${eBest(emp).label}</div></div><div class="lb-bar-wrap"><div class="lb-bar" style="width:${(t/maxT*100).toFixed(0)}%;background:${col}"></div></div><div class="lb-score" style="color:${col}">${t}</div></div>`;
  });
  /* team avg chart */
  if(CH.ins.ta) CH.ins.ta.destroy();
  CH.ins.ta=new Chart(document.getElementById('teamAvgChart').getContext('2d'),{type:'bar',data:{labels:SVCS.map(s=>s.label),datasets:[{data:SVCS.map(s=>sAvg(s.key)),backgroundColor:SVCS.map((_,i)=>`${ECOLS_CSS[i%ECOLS_CSS.length]}99`),borderColor:SVCS.map((_,i)=>ECOLS_CSS[i%ECOLS_CSS.length]),borderWidth:1.5,borderRadius:4,borderSkipped:false}]},options:{...CB(),indexAxis:'y',scales:{x:{beginAtZero:true,max:100,grid:{color:'#1e1e30'},ticks:{font:{size:8}}},y:{grid:{display:false},ticks:{font:{size:8}}}}}});
  /* radar grid */
  destroy(CH.ins.radars||{}); CH.ins.radars={};
  const rg=document.getElementById('radarGrid'); rg.innerHTML='';
  arr.forEach((emp,i)=>{
    const col=ECOLS_CSS[i%ECOLS_CSS.length];
    const c=document.createElement('div');c.className='radar-card';
    c.innerHTML=`<div class="radar-head"><div class="radar-av" style="background:${AVT[i%AVT.length]};color:#fff">${INI(emp.name)}</div><div><div class="radar-nm">${emp.name}</div><div class="radar-sc">Total: ${eTotal(emp)} · Avg: ${eAvg(emp).toFixed(1)}</div></div></div><div class="radar-ch"><canvas id="rc-${emp.id}"></canvas></div>`;
    rg.appendChild(c);
    CH.ins.radars[emp.id]=new Chart(c.querySelector(`#rc-${emp.id}`).getContext('2d'),{type:'radar',data:{labels:SVCS.map(s=>s.label),datasets:[{data:SVCS.map(s=>emp[s.key]||0),borderColor:col,backgroundColor:col+'22',pointBackgroundColor:col,pointRadius:3,borderWidth:2}]},options:{...CB(),scales:{r:{beginAtZero:true,max:100,grid:{color:'#252535'},angleLines:{color:'#252535'},pointLabels:{color:'#6b6b8a',font:{size:7}},ticks:{backdropColor:'transparent',color:'#6b6b8a',font:{size:7},stepSize:25}}}}});
  });
  /* insight charts */
  destroy(CH.ins.extra||{}); CH.ins.extra={};
  const ig=document.getElementById('insGrid'); ig.innerHTML='';
  // totals
  const cA=document.createElement('div');cA.className='ins-card';cA.innerHTML=`<div class="ins-title"><span>📈</span>Total Score per Employee</div><div class="ins-ch"><canvas id="ins-tot"></canvas></div>`;ig.appendChild(cA);
  const srtd=[...arr].sort((a,b)=>eTotal(b)-eTotal(a));
  CH.ins.extra.tot=new Chart(cA.querySelector('#ins-tot').getContext('2d'),{type:'bar',data:{labels:srtd.map(e=>e.name.split(' ')[0]),datasets:[{data:srtd.map(e=>eTotal(e)),backgroundColor:srtd.map(e=>ECOLS_CSS[arr.indexOf(e)%ECOLS_CSS.length]+'bb'),borderColor:srtd.map(e=>ECOLS_CSS[arr.indexOf(e)%ECOLS_CSS.length]),borderWidth:1.5,borderRadius:5,borderSkipped:false}]},options:{...CB(),scales:{y:{beginAtZero:true,grid:{color:'#1e1e30'},ticks:{font:{size:8}}},x:{grid:{display:false},ticks:{font:{size:9}}}}}});
  // svc volume
  const cB=document.createElement('div');cB.className='ins-card';cB.innerHTML=`<div class="ins-title"><span>🌐</span>Service Volume (Polar)</div><div class="ins-ch"><canvas id="ins-pol"></canvas></div>`;ig.appendChild(cB);
  CH.ins.extra.pol=new Chart(cB.querySelector('#ins-pol').getContext('2d'),{type:'polarArea',data:{labels:SVCS.map(s=>s.label),datasets:[{data:SVCS.map(s=>sTotal(s.key)),backgroundColor:ECOLS_CSS.map(c=>c+'55'),borderColor:ECOLS_CSS,borderWidth:1.5}]},options:{...CB(),scales:{r:{beginAtZero:true,grid:{color:'#252535'},ticks:{backdropColor:'transparent',color:'#6b6b8a',font:{size:7}}}},plugins:{...CB().plugins,legend:{display:true,position:'right',labels:{color:'#9090b0',font:{size:8},boxWidth:8,padding:6}}}}});
  // gap
  const cC=document.createElement('div');cC.className='ins-card';cC.innerHTML=`<div class="ins-title"><span>⚡</span>Performance Gap per Service</div><div class="ins-ch"><canvas id="ins-gap"></canvas></div>`;ig.appendChild(cC);
  const gaps=SVCS.map(s=>{const v=arr.map(e=>e[s.key]||0);return Math.max(...v)-Math.min(...v);});
  CH.ins.extra.gap=new Chart(cC.querySelector('#ins-gap').getContext('2d'),{type:'bar',data:{labels:SVCS.map(s=>s.label),datasets:[{data:gaps,backgroundColor:gaps.map(g=>g>40?'#ff2d7899':g>20?'#ffab4099':'#00ff9d88'),borderColor:gaps.map(g=>g>40?'#ff2d78':g>20?'#ffab40':'#00ff9d'),borderWidth:1.5,borderRadius:4,borderSkipped:false}]},options:{...CB(),indexAxis:'y',scales:{x:{beginAtZero:true,grid:{color:'#1e1e30'},ticks:{font:{size:8}}},y:{grid:{display:false},ticks:{font:{size:8}}}}}});
  // avg line
  const cD=document.createElement('div');cD.className='ins-card';cD.innerHTML=`<div class="ins-title"><span>📉</span>Average Score per Employee</div><div class="ins-ch"><canvas id="ins-avg"></canvas></div>`;ig.appendChild(cD);
  CH.ins.extra.avg=new Chart(cD.querySelector('#ins-avg').getContext('2d'),{type:'line',data:{labels:arr.map(e=>e.name.split(' ')[0]),datasets:[{data:arr.map(e=>parseFloat(eAvg(e).toFixed(1))),borderColor:'#00d4ff',backgroundColor:'rgba(0,212,255,.08)',pointBackgroundColor:ECOLS_CSS,pointRadius:6,pointHoverRadius:8,borderWidth:2,tension:.35,fill:true}]},options:{...CB(),scales:{y:{beginAtZero:true,max:100,grid:{color:'#1e1e30'},ticks:{font:{size:8}}},x:{grid:{display:false},ticks:{font:{size:9}}}}}});
}

/* ═══ INSTRUCTIONS SERVICE CARDS ═══ */
function renderInstrCards(){
  const g=document.getElementById('svcInstrGrid'); g.innerHTML='';
  SVCS.forEach((s,i)=>{
    const c=document.createElement('div');c.className='si-card';
    c.innerHTML=`<div class="si-head"><span class="si-num">#${String(i+1).padStart(2,'0')}</span><span class="si-name">${s.label}</span><span class="si-file">${s.file}</span></div>
    <div class="si-body"><div class="si-desc">${s.desc}</div>
    <div class="si-cols">
      <div class="si-col"><div class="si-cn">employee_id</div><div class="si-ci"><div class="si-ct">string·required</div><div class="si-cd">Unique ID matching master data.</div></div><span class="si-req" style="background:rgba(255,45,120,.12);color:#ff7aaa">REQ</span></div>
      <div class="si-col"><div class="si-cn">employee_name</div><div class="si-ci"><div class="si-ct">string·optional</div><div class="si-cd">Updates display name if provided.</div></div><span class="si-req" style="background:rgba(107,107,138,.12);color:var(--dim)">OPT</span></div>
      <div class="si-col"><div class="si-cn">value</div><div class="si-ci"><div class="si-ct">number·required</div><div class="si-cd">Score for <strong style="color:var(--text)">${s.label}</strong>.</div></div><span class="si-req" style="background:rgba(255,45,120,.12);color:#ff7aaa">REQ</span></div>
    </div>
    <div class="si-sample"><pre><span class="ch">employee_id,employee_name,value</span>\n<span class="cr">EMP001,Ahmed Al-Rashidi,85</span>\n<span class="cr">EMP002,Sara Binsaleh,91</span></pre></div></div>`;
    g.appendChild(c);
  });
}

function renderAll(){renderEmp();renderSvc();renderDist();renderInsights();}

/* ═══════════════════════════
   CSV
═══════════════════════════ */
function parseCSV(txt){
  const lines=txt.trim().split('\n').map(l=>l.trim()).filter(Boolean);
  if(!lines.length)return[];
  const hdrs=lines[0].split(',').map(h=>h.trim().toLowerCase().replace(/\s+/g,'_'));
  return lines.slice(1).map(line=>{const v=line.split(',').map(c=>c.trim());const o={};hdrs.forEach((h,i)=>{o[h]=v[i]||'';});return o;});
}
function applyMaster(rows){const nd={};rows.forEach(r=>{const id=r.employee_id||r.id,name=r.employee_name||r.name||id;if(!id)return;nd[id]={id,name};SVCS.forEach(s=>{nd[id][s.key]=parseFloat(r[s.key])||0;});});if(Object.keys(nd).length)DATA=nd;}
function applySvc(key,rows){rows.forEach(r=>{const id=r.employee_id||r.id,name=r.employee_name||r.name||id,val=parseFloat(r.value)||0;if(!id)return;if(!DATA[id]){DATA[id]={id,name};SVCS.forEach(s=>{DATA[id][s.key]=0;});}DATA[id][key]=val;if(name&&name!==id)DATA[id].name=name;});}
function readFile(f,cb){const r=new FileReader();r.onload=e=>cb(e.target.result);r.readAsText(f);}
function updateMode(){const any=[...document.querySelectorAll('input[type=file]')].some(i=>i.files&&i.files.length);const pill=document.getElementById('modePill'),dot=pill.querySelector('.s-dot'),hdr=document.getElementById('hdrMode');if(any){dot.classList.remove('off');pill.lastChild.textContent='CSV Data';hdr.textContent='CSV DATA';}else{dot.classList.add('off');pill.lastChild.textContent='Mock Data';hdr.textContent='MOCK DATA';}}

document.getElementById('up_master').addEventListener('change',function(){if(!this.files[0])return;readFile(this.files[0],t=>{applyMaster(parseCSV(t));updateMode();renderAll();});});
SVCS.forEach(s=>{const el=document.getElementById('up_'+s.key);if(!el)return;el.addEventListener('change',function(){if(!this.files[0])return;readFile(this.files[0],t=>{applySvc(s.key,parseCSV(t));updateMode();renderAll();});});});

/* ═══════════════════════════
   TABS
═══════════════════════════ */
document.querySelectorAll('.tab-btn').forEach(btn=>{btn.addEventListener('click',()=>{document.querySelectorAll('.tab-btn').forEach(b=>b.classList.remove('active'));document.querySelectorAll('.tab-pane').forEach(p=>p.classList.remove('active'));btn.classList.add('active');document.getElementById(btn.dataset.tab).classList.add('active');});});
document.querySelectorAll('.st-btn').forEach(btn=>{btn.addEventListener('click',()=>{document.querySelectorAll('.st-btn').forEach(b=>b.classList.remove('active'));document.querySelectorAll('.sub-panel').forEach(p=>p.classList.remove('active'));btn.classList.add('active');document.getElementById(btn.dataset.panel).classList.add('active');});});
document.getElementById('upHead').addEventListener('click',()=>{const b=document.getElementById('upBody'),a=document.getElementById('upArrow'),h=document.getElementById('upHead');const o=b.classList.toggle('open');a.classList.toggle('open',o);h.classList.toggle('open',o);});

/* ═══════════════════════════════════════════════
   PDF EXPORT  —  WHITE THEME
═══════════════════════════════════════════════ */
async function exportPDF(){
  showOverlay('Generating PDF Report','Building white-theme report...','expOverlay','expMsg','expSub');
  await new Promise(r=>setTimeout(r,200));
  try{
    const {jsPDF}=window.jspdf;
    const pdf=new jsPDF('l','mm','a4');
    const pw=297,ph=210;
    // ── WHITE PALETTE ──
    const W=[255,255,255], LIGHT=[248,249,252], LIGHT2=[237,240,247];
    const INK=[30,30,45],       // near-black for body text
          MUTED=[110,115,140],  // secondary text / labels
          RULE=[210,213,225];   // divider lines / borders
    // accent colours — vivid on white
    const AC_BLUE=[0,102,204],   AC_BLUE_H='0066CC';
    const AC_TEAL=[0,168,180],   AC_TEAL_H='00A8B4';
    const AC_GREEN=[16,160,90],  AC_GREEN_H='10A05A';
    const AC_ORANGE=[220,90,20], AC_ORANGE_H='DC5A14';
    const AC_PURPLE=[100,55,200],AC_PURPLE_H='6437C8';
    const AC_RED=[210,40,80],    AC_RED_H='D22850';
    // per-employee accent cycling (readable on white)
    const EAC=[AC_BLUE_H,'D22850','6437C8','10A05A','DC5A14','C49000','008B8B'];
    const EAC_RGB=[[0,102,204],[210,40,80],[100,55,200],[16,160,90],[220,90,20],[196,144,0],[0,139,139]];

    const arr=EA(),gt=GT(),sorted=[...arr].sort((a,b)=>eTotal(b)-eTotal(a));

    // helper: draw page chrome
    const pageChrome=(acRGB,title,sub)=>{
      pdf.setFillColor(...W); pdf.rect(0,0,pw,ph,'F');
      // top accent bar (6 mm)
      pdf.setFillColor(...acRGB); pdf.rect(0,0,pw,6,'F');
      // bottom rule
      pdf.setDrawColor(...RULE); pdf.setLineWidth(.3); pdf.line(0,ph-8,pw,ph-8);
      pdf.setTextColor(...MUTED); pdf.setFontSize(7); pdf.setFont('helvetica','normal');
      pdf.text('HR Performance Analytics  ·  Confidential',14,ph-3.5);
      pdf.text(`Page ${pdf.getCurrentPageInfo().pageNumber}`,pw-14,ph-3.5,{align:'right'});
      if(title){
        pdf.setTextColor(...INK); pdf.setFontSize(15); pdf.setFont('helvetica','bold');
        pdf.text(title,14,16);
      }
      if(sub){
        pdf.setTextColor(...MUTED); pdf.setFontSize(8.5); pdf.setFont('helvetica','normal');
        pdf.text(sub,14,23);
      }
    };

    // ── COVER PAGE ──
    pdf.setFillColor(...W); pdf.rect(0,0,pw,ph,'F');
    // left accent strip
    pdf.setFillColor(...AC_BLUE); pdf.rect(0,0,8,ph,'F');
    // header band
    pdf.setFillColor(...LIGHT); pdf.rect(8,0,pw-8,50,'F');
    pdf.setTextColor(...AC_BLUE); pdf.setFontSize(9); pdf.setFont('helvetica','bold');
    pdf.text('HR PERFORMANCE ANALYTICS',pw/2+4,18,{align:'center'});
    pdf.setDrawColor(...RULE); pdf.setLineWidth(.4); pdf.line(40,22,pw-20,22);
    pdf.setTextColor(...INK); pdf.setFontSize(30); pdf.setFont('helvetica','bold');
    pdf.text('Performance Dashboard',pw/2+4,40,{align:'center'});
    // main area
    pdf.setTextColor(...MUTED); pdf.setFontSize(11); pdf.setFont('helvetica','normal');
    pdf.text(`Generated: ${new Date().toLocaleDateString('en-GB',{weekday:'long',year:'numeric',month:'long',day:'numeric'})}`,pw/2+4,60,{align:'center'});
    pdf.setDrawColor(...RULE); pdf.setLineWidth(.4); pdf.line(50,65,pw-40,65);
    pdf.setTextColor(...MUTED); pdf.setFontSize(9);
    pdf.text(`${arr.length} Employees   ·   ${SVCS.length} Services   ·   Grand Total: ${gt}`,pw/2+4,73,{align:'center'});

    // KPI summary boxes
    const boxes=[
      {l:'Grand Total',v:String(gt),rgb:AC_BLUE},
      {l:'Team Average',v:(gt/(arr.length*SVCS.length)).toFixed(1),rgb:AC_TEAL},
      {l:'Top Performer',v:sorted[0].name.split(' ')[0],rgb:AC_GREEN},
      {l:'Efficiency',v:((gt/(arr.length*SVCS.length*100))*100).toFixed(1)+'%',rgb:AC_ORANGE},
    ];
    boxes.forEach((b,i)=>{
      const x=30+i*62, y=84;
      pdf.setFillColor(...LIGHT); pdf.roundedRect(x,y,55,32,2,2,'F');
      pdf.setDrawColor(...b.rgb); pdf.setLineWidth(.8); pdf.roundedRect(x,y,55,32,2,2,'S');
      // top accent
      pdf.setFillColor(...b.rgb); pdf.roundedRect(x,y,55,4,2,2,'F');
      pdf.rect(x,y+2,55,2,'F');
      pdf.setTextColor(...MUTED); pdf.setFontSize(6.5); pdf.setFont('helvetica','normal');
      pdf.text(b.l,x+27.5,y+11,{align:'center'});
      pdf.setTextColor(...b.rgb); pdf.setFontSize(17); pdf.setFont('helvetica','bold');
      pdf.text(String(b.v),x+27.5,y+24,{align:'center'});
    });

    // leaderboard preview strip
    pdf.setFillColor(...LIGHT2); pdf.roundedRect(14,125,pw-28,62,2,2,'F');
    pdf.setTextColor(...INK); pdf.setFontSize(8); pdf.setFont('helvetica','bold');
    pdf.text('Quick Rankings',20,133);
    sorted.slice(0,5).forEach((emp,i)=>{
      const rx=20, ry=138+i*9;
      const rgb=EAC_RGB[EA().indexOf(emp)%EAC_RGB.length];
      pdf.setFillColor(...rgb); pdf.roundedRect(rx,ry-4,1.8,6,0,0,'F');
      const medal=i===0?'1st':i===1?'2nd':i===2?'3rd':`#${i+1}`;
      pdf.setTextColor(...MUTED); pdf.setFontSize(6.5); pdf.setFont('helvetica','normal'); pdf.text(medal,rx+4,ry);
      pdf.setTextColor(...INK); pdf.text(emp.name,rx+16,ry);
      const bx=rx+90, bw=150, pct=eTotal(emp)/(eTotal(sorted[0])||1);
      pdf.setFillColor(...RULE); pdf.roundedRect(bx,ry-3,bw,3.5,.5,.5,'F');
      pdf.setFillColor(...rgb); pdf.roundedRect(bx,ry-3,bw*pct,3.5,.5,.5,'F');
      pdf.setTextColor(...rgb); pdf.setFont('helvetica','bold'); pdf.text(String(eTotal(emp)),bx+bw+3,ry);
    });

    // bottom
    pdf.setDrawColor(...RULE); pdf.setLineWidth(.3); pdf.line(14,ph-10,pw-14,ph-10);
    pdf.setTextColor(...MUTED); pdf.setFontSize(7); pdf.setFont('helvetica','normal');
    pdf.text('HR Performance Analytics  ·  Confidential',14,ph-4.5);

    // ── PAGE 2: EMPLOYEE SUMMARY TABLE ──
    pdf.addPage();
    pageChrome(AC_BLUE,'Employee Summary','Performance totals, averages and rankings across all services');

    const cols=['Rank','Employee','ID',...SVCS.map(s=>s.label.slice(0,9)),'Total','Avg'];
    const colW=[10,36,17,...SVCS.map(()=>16),16,13];
    const startX=14, headerY=30;
    // header
    pdf.setFillColor(...AC_BLUE); pdf.rect(startX,headerY,pw-28,7,'F');
    pdf.setTextColor(...W); pdf.setFontSize(6); pdf.setFont('helvetica','bold');
    let cx=startX;
    cols.forEach((c,i)=>{pdf.text(c,cx+colW[i]/2,headerY+4.8,{align:'center'});cx+=colW[i];});
    // rows
    sorted.forEach((emp,ri)=>{
      const rowY=headerY+7+ri*9;
      pdf.setFillColor(...(ri%2===0?LIGHT:W)); pdf.rect(startX,rowY,pw-28,9,'F');
      pdf.setDrawColor(...RULE); pdf.setLineWidth(.15); pdf.rect(startX,rowY,pw-28,9,'S');
      pdf.setFont('helvetica','normal'); pdf.setFontSize(6.5);
      const cells=[`#${ri+1}`,emp.name,emp.id,...SVCS.map(s=>String(emp[s.key]||0)),String(eTotal(emp)),eAvg(emp).toFixed(1)];
      cx=startX;
      cells.forEach((cell,ci)=>{
        if(ci===0){
          const mc=ri===0?[180,140,0]:ri===1?[100,100,100]:ri===2?[160,80,20]:MUTED;
          pdf.setTextColor(...mc); pdf.setFont('helvetica','bold');
        } else if(ci===cols.length-2){pdf.setTextColor(...AC_BLUE); pdf.setFont('helvetica','bold');}
        else if(ci===cols.length-1){pdf.setTextColor(...AC_GREEN); pdf.setFont('helvetica','bold');}
        else if(ci>2){
          const v=parseFloat(cell)||0;
          pdf.setTextColor(...(v>=80?AC_GREEN:v>=60?AC_ORANGE:AC_RED));
          pdf.setFont('helvetica','normal');
        } else {pdf.setTextColor(...INK); pdf.setFont('helvetica','normal');}
        pdf.text(cell,cx+colW[ci]/2,rowY+5.8,{align:'center'});
        cx+=colW[ci];
      });
    });

    // ── PAGES 3+: SERVICE BAR CHARTS ──
    updateOverlayMsg('Rendering service charts...','expMsg');
    for(let si=0;si<SVCS.length;si+=2){
      pdf.addPage();
      pageChrome(AC_TEAL,'Service Performance Comparison','Score distribution across all employees per service');
      for(let ci=0;ci<2&&si+ci<SVCS.length;ci++){
        const svc=SVCS[si+ci];
        const emps=EA(), vals=emps.map(e=>e[svc.key]||0);
        const t=sTotal(svc.key), a=sAvg(svc.key).toFixed(1);
        const ox=14+ci*141, oy=30, barW=130, barH=145;

        // card bg
        pdf.setFillColor(...LIGHT); pdf.roundedRect(ox,oy,135,155,2,2,'F');
        pdf.setDrawColor(...RULE); pdf.setLineWidth(.3); pdf.roundedRect(ox,oy,135,155,2,2,'S');
        // card header strip
        pdf.setFillColor(...AC_TEAL); pdf.roundedRect(ox,oy,135,8,2,2,'F');
        pdf.rect(ox,oy+4,135,4,'F');
        pdf.setTextColor(...W); pdf.setFontSize(8); pdf.setFont('helvetica','bold');
        pdf.text(svc.label,ox+67.5,oy+5.5,{align:'center'});
        // stats
        pdf.setTextColor(...MUTED); pdf.setFontSize(6.5); pdf.setFont('helvetica','normal');
        pdf.text(`Total: ${t}`,ox+4,oy+17); pdf.text(`Avg: ${a}`,ox+4,oy+24);
        // bars
        const barAreaX=ox+36, barAreaY=oy+30, maxV=Math.max(...vals,1);
        const slot=barH/(emps.length+1);
        emps.forEach((emp,ei)=>{
          const v=vals[ei], pct=v/maxV;
          const rgb=EAC_RGB[ei%EAC_RGB.length];
          const by=barAreaY+slot*(ei+0.5), bh=Math.max(slot*.5,2.5), bw=pct*(barW-32);
          // track
          pdf.setFillColor(...RULE); pdf.roundedRect(barAreaX,by-bh/2,barW-32,bh,.5,.5,'F');
          // filled bar
          pdf.setFillColor(...rgb); pdf.roundedRect(barAreaX,by-bh/2,bw||0.5,bh,.5,.5,'F');
          // label
          pdf.setTextColor(...MUTED); pdf.setFontSize(5.8); pdf.setFont('helvetica','normal');
          pdf.text(emp.name.split(' ')[0],barAreaX-2,by+1.5,{align:'right'});
          // value
          pdf.setTextColor(...rgb); pdf.setFontSize(6); pdf.setFont('helvetica','bold');
          pdf.text(String(v),barAreaX+bw+2,by+1.5);
        });
      }
      await new Promise(r=>setTimeout(r,0));
    }

    // ── LEADERBOARD PAGE ──
    pdf.addPage();
    pageChrome(AC_ORANGE,'Performance Leaderboard','Employees ranked by cumulative score across all services');
    sorted.forEach((emp,i)=>{
      const rowY=30+i*22, t=eTotal(emp), pct=t/(eTotal(sorted[0])||1);
      const rgb=EAC_RGB[EA().indexOf(emp)%EAC_RGB.length];
      pdf.setFillColor(...(i%2===0?LIGHT:W)); pdf.roundedRect(14,rowY,pw-28,18,1.5,1.5,'F');
      pdf.setDrawColor(...RULE); pdf.setLineWidth(.25); pdf.roundedRect(14,rowY,pw-28,18,1.5,1.5,'S');
      // left accent bar
      pdf.setFillColor(...rgb); pdf.roundedRect(14,rowY,2.5,18,1,1,'F');
      // rank
      const medal=i===0?'1st':i===1?'2nd':i===2?'3rd':`#${i+1}`;
      const mc=i===0?[180,140,0]:i===1?[100,100,100]:i===2?[160,80,20]:MUTED;
      pdf.setTextColor(...mc); pdf.setFontSize(9.5); pdf.setFont('helvetica','bold');
      pdf.text(medal,22,rowY+11);
      // name
      pdf.setTextColor(...INK); pdf.setFontSize(10); pdf.setFont('helvetica','bold');
      pdf.text(emp.name,40,rowY+7);
      // sub
      pdf.setTextColor(...MUTED); pdf.setFontSize(7); pdf.setFont('helvetica','normal');
      pdf.text(`Avg: ${eAvg(emp).toFixed(1)}  ·  Best: ${eBest(emp).label}  ·  Weakest: ${eWorst(emp).label}`,40,rowY+14);
      // bar
      const bx=135, by=rowY+6, bw=130, bh=5;
      pdf.setFillColor(...RULE); pdf.roundedRect(bx,by,bw,bh,1,1,'F');
      pdf.setFillColor(...rgb); pdf.roundedRect(bx,by,bw*pct,bh,1,1,'F');
      pdf.setTextColor(...rgb); pdf.setFontSize(9); pdf.setFont('helvetica','bold');
      pdf.text(String(t),bx+bw+4,rowY+11);
    });

    pdf.save('HR_Performance_Report.pdf');
    hideOverlay('expOverlay');
  }catch(err){console.error(err);hideOverlay('expOverlay');alert('PDF export failed: '+err.message);}
}

/* ═══════════════════════════════════════════════
   PPTX EXPORT  —  WHITE THEME
═══════════════════════════════════════════════ */
async function exportPPTX(){
  showOverlay('Generating PowerPoint','Building white-theme slides...','expOverlay','expMsg','expSub');
  await new Promise(r=>setTimeout(r,200));
  try{
    const pres=new PptxGenJS();
    pres.layout='LAYOUT_WIDE';
    pres.title='HR Performance Dashboard';
    pres.author='HR Analytics';

    // ── WHITE PALETTE ──
    const BG='FFFFFF', LIGHT='F5F7FA', LIGHT2='EDF0F7';
    const INK='1E1E2D',   // near-black body text
          MUTED='6E7389', // secondary / labels
          RULE='D2D5E1';  // borders / dividers
    // vivid accents on white
    const BLUE='0066CC', TEAL='0094A8', GREEN='0E8A52',
          ORANGE='C85A10', PURPLE='5B2DBF', RED='C42050',
          AMBER='B07800';
    // per-employee colour cycling (readable on white)
    const EAC=[BLUE,RED,PURPLE,GREEN,ORANGE,AMBER,'007C7C'];

    const arr=EA(),gt=GT(),sorted=[...arr].sort((a,b)=>eTotal(b)-eTotal(a));
    const empNames=arr.map(e=>e.name.split(' ')[0]);

    // helper: standard page header
    const slideHeader=(sl,acColor,title,sub)=>{
      sl.background={color:BG};
      // thin top accent bar
      sl.addShape(pres.shapes.RECTANGLE,{x:0,y:0,w:13.3,h:.12,fill:{color:acColor}});
      // footer rule
      sl.addShape(pres.shapes.LINE,{x:0,y:7.33,w:13.3,h:0,line:{color:RULE,width:.5}});
      sl.addText('HR Performance Analytics  ·  Confidential',{x:.3,y:7.35,w:8,h:.15,fontSize:6.5,color:MUTED,fontFace:'Calibri'});
      if(title) sl.addText(title,{x:.4,y:.2,w:10,h:.55,fontSize:20,bold:true,color:INK,fontFace:'Calibri'});
      if(sub)   sl.addText(sub,{x:.4,y:.74,w:12.5,h:.28,fontSize:9.5,color:MUTED,fontFace:'Calibri'});
    };

    // ── SLIDE 1: COVER ──
    let sl=pres.addSlide();
    sl.background={color:BG};
    // left accent column
    sl.addShape(pres.shapes.RECTANGLE,{x:0,y:0,w:.22,h:7.5,fill:{color:BLUE}});
    // light header band
    sl.addShape(pres.shapes.RECTANGLE,{x:.22,y:0,w:13.08,h:2.2,fill:{color:LIGHT}});
    sl.addText('HR PERFORMANCE ANALYTICS',{x:.5,y:.35,w:12.5,h:.45,fontSize:11,bold:true,color:BLUE,charSpacing:4,fontFace:'Calibri'});
    sl.addShape(pres.shapes.LINE,{x:.5,y:.88,w:12.4,h:0,line:{color:RULE,width:.6}});
    sl.addText('Performance Dashboard',{x:.5,y:1.0,w:12.4,h:.9,fontSize:40,bold:true,color:INK,fontFace:'Calibri'});
    sl.addText(`Generated ${new Date().toLocaleDateString('en-GB',{year:'numeric',month:'long',day:'numeric'})}`,{x:.5,y:2.35,w:12.4,h:.38,fontSize:12,color:MUTED,fontFace:'Calibri'});
    sl.addShape(pres.shapes.LINE,{x:.5,y:2.78,w:12.4,h:0,line:{color:RULE,width:.5}});
    // KPI boxes
    const kpis=[
      {l:'Grand Total',v:String(gt),c:BLUE},
      {l:'Team Average',v:(gt/(arr.length*SVCS.length)).toFixed(1),c:TEAL},
      {l:'Employees',v:String(arr.length),c:GREEN},
      {l:'Services',v:String(SVCS.length),c:PURPLE},
    ];
    kpis.forEach((k,i)=>{
      const x=.5+i*3.2, y=3.05;
      sl.addShape(pres.shapes.RECTANGLE,{x,y,w:3.0,h:1.6,fill:{color:LIGHT2},line:{color:RULE,width:.4}});
      // top colour strip on box
      sl.addShape(pres.shapes.RECTANGLE,{x,y,w:3.0,h:.12,fill:{color:k.c}});
      sl.addText(k.l,{x,y:y+.18,w:3.0,h:.32,fontSize:8.5,color:MUTED,align:'center',fontFace:'Calibri'});
      sl.addText(k.v,{x,y:y+.52,w:3.0,h:.82,fontSize:32,bold:true,color:k.c,align:'center',fontFace:'Calibri'});
    });
    // mini ranking strip
    sl.addShape(pres.shapes.RECTANGLE,{x:.5,y:4.88,w:12.5,h:2.3,fill:{color:LIGHT}});
    sl.addText('Quick Rankings',{x:.65,y:4.98,w:4,h:.32,fontSize:9,bold:true,color:INK,fontFace:'Calibri'});
    sorted.slice(0,5).forEach((emp,i)=>{
      const ei=arr.indexOf(emp), col=EAC[ei%EAC.length];
      const rx=.65, ry=5.38+i*.31;
      sl.addShape(pres.shapes.RECTANGLE,{x:rx,y:ry-.02,w:.07,h:.24,fill:{color:col}});
      sl.addText(`${i===0?'1st':i===1?'2nd':i===2?'3rd':'#'+(i+1)}  ${emp.name}`,{x:rx+.14,y:ry,w:4,h:.25,fontSize:8.5,color:INK,fontFace:'Calibri'});
      // bar
      const bx=5.0, bw=7.6, pct=eTotal(emp)/(eTotal(sorted[0])||1);
      sl.addShape(pres.shapes.RECTANGLE,{x:bx,y:ry+.02,w:bw,h:.14,fill:{color:RULE}});
      sl.addShape(pres.shapes.RECTANGLE,{x:bx,y:ry+.02,w:bw*pct,h:.14,fill:{color:col}});
      sl.addText(String(eTotal(emp)),{x:bx+bw+.08,y:ry,w:.7,h:.25,fontSize:8.5,bold:true,color:col,fontFace:'Calibri'});
    });

    // ── SLIDE 2: EMPLOYEE SUMMARY TABLE ──
    updateOverlayMsg('Building summary table...','expMsg');
    sl=pres.addSlide();
    slideHeader(sl,BLUE,'Employee Performance Summary',`Grand Total: ${gt}  ·  ${arr.length} Employees  ·  ${SVCS.length} Services`);
    const tCols=['#','Name','ID',...SVCS.map(s=>s.label.slice(0,9)),'Total','Avg'];
    const tW=[.32,.92,.62,...SVCS.map(()=>0.70),.63,.54];
    const tHRows=[[tCols.map(c=>({text:c,options:{bold:true,color:'FFFFFF',fontSize:6.5,align:'center',fill:{color:BLUE},border:{pt:.5,color:RULE}}})) ]];
    sorted.forEach((emp,ri)=>{
      const row=[];
      const rowFill=ri%2===0?BG:LIGHT;
      row.push({text:`#${ri+1}`,options:{bold:true,color:ri===0?AMBER:ri===1?MUTED:ri===2?ORANGE:MUTED,fontSize:7,align:'center',fill:{color:rowFill},border:{pt:.3,color:RULE}}});
      row.push({text:emp.name,options:{color:INK,fontSize:7,fill:{color:rowFill},border:{pt:.3,color:RULE}}});
      row.push({text:emp.id,options:{color:MUTED,fontSize:6.5,align:'center',fill:{color:rowFill},border:{pt:.3,color:RULE}}});
      SVCS.forEach(s=>{
        const v=emp[s.key]||0;
        row.push({text:String(v),options:{color:v>=80?GREEN:v>=60?ORANGE:RED,fontSize:7,align:'center',fill:{color:rowFill},border:{pt:.3,color:RULE}}});
      });
      row.push({text:String(eTotal(emp)),options:{bold:true,color:BLUE,fontSize:8,align:'center',fill:{color:rowFill},border:{pt:.3,color:RULE}}});
      row.push({text:eAvg(emp).toFixed(1),options:{bold:true,color:GREEN,fontSize:7,align:'center',fill:{color:rowFill},border:{pt:.3,color:RULE}}});
      tHRows.push(row);
    });
    sl.addTable(tHRows,{x:.3,y:1.1,w:12.7,colW:tW,rowH:.41,border:{pt:.3,color:RULE}});

    // ── SLIDES 3+: SERVICE BAR CHARTS ──
    updateOverlayMsg('Adding service charts...','expMsg');
    for(let si=0;si<SVCS.length;si+=2){
      sl=pres.addSlide();
      slideHeader(sl,TEAL,'Service Performance Comparison','Employee scores per service — 0 to 100 scale');
      for(let ci=0;ci<2&&si+ci<SVCS.length;ci++){
        const svc=SVCS[si+ci];
        const ox=.3+ci*6.65, oy=1.05, cw=6.2;
        const vals=arr.map(e=>e[svc.key]||0);
        // card bg
        sl.addShape(pres.shapes.RECTANGLE,{x:ox,y:oy,w:cw,h:.52,fill:{color:LIGHT2},line:{color:RULE,width:.4}});
        sl.addShape(pres.shapes.RECTANGLE,{x:ox,y:oy,w:.1,h:.52,fill:{color:TEAL}});
        sl.addText(svc.label,{x:ox+.16,y:oy+.05,w:cw*.58,h:.42,fontSize:11,bold:true,color:INK,fontFace:'Calibri'});
        sl.addText(`Total: ${sTotal(svc.key)}  ·  Avg: ${sAvg(svc.key).toFixed(1)}`,{x:ox+cw*.5,y:oy+.12,w:cw*.48,h:.28,fontSize:8,color:MUTED,align:'right',fontFace:'Calibri'});
        sl.addChart(pres.charts.BAR,[{name:svc.label,labels:empNames,values:vals}],{
          x:ox, y:oy+.54, w:cw, h:5.6, barDir:'col',
          chartColors:EAC,
          chartArea:{fill:{color:BG},roundedCorners:false},
          plotArea:{fill:{color:BG}},
          catAxisLabelColor:MUTED, valAxisLabelColor:MUTED,
          catAxisLineShow:false, valAxisLineShow:false,
          valGridLine:{color:RULE,size:.4}, catGridLine:{style:'none'},
          valAxisMinVal:0, valAxisMaxVal:100,
          showValue:true, dataLabelColor:INK, dataLabelFontSize:7,
          showLegend:false,
        });
      }
      await new Promise(r=>setTimeout(r,0));
    }

    // ── SLIDES: DISTRIBUTION DOUGHNUT ──
    updateOverlayMsg('Adding distribution charts...','expMsg');
    for(let si=0;si<SVCS.length;si+=3){
      sl=pres.addSlide();
      slideHeader(sl,PURPLE,'Service Distribution','Share of total score per employee per service');
      for(let ci=0;ci<3&&si+ci<SVCS.length;ci++){
        const svc=SVCS[si+ci];
        const ox=.25+ci*4.36, oy=1.05, cw=4.1;
        const vals=arr.map(e=>e[svc.key]||0);
        sl.addShape(pres.shapes.RECTANGLE,{x:ox,y:oy,w:cw,h:.46,fill:{color:LIGHT2},line:{color:RULE,width:.4}});
        sl.addShape(pres.shapes.RECTANGLE,{x:ox,y:oy,w:.1,h:.46,fill:{color:PURPLE}});
        sl.addText(svc.label,{x:ox+.14,y:oy+.07,w:cw-.22,h:.32,fontSize:9.5,bold:true,color:INK,fontFace:'Calibri'});
        sl.addChart(pres.charts.DOUGHNUT,[{name:svc.label,labels:empNames,values:vals}],{
          x:ox, y:oy+.48, w:cw, h:5.8,
          holeSize:55,
          chartColors:EAC,
          chartArea:{fill:{color:BG}},
          dataLabelFontSize:7, dataLabelColor:INK, showPercent:true,
          legendPos:'b', showLegend:true, legendFontSize:7.5, legendColor:MUTED,
        });
      }
      await new Promise(r=>setTimeout(r,0));
    }

    // ── SLIDE: LEADERBOARD ──
    updateOverlayMsg('Adding leaderboard...','expMsg');
    sl=pres.addSlide();
    slideHeader(sl,ORANGE,'Performance Leaderboard','Employees ranked by cumulative score across all 11 services');
    sl.addText(`Grand Total: ${gt}`,{x:9.5,y:.2,w:3.5,h:.55,fontSize:14,bold:true,color:BLUE,align:'right',fontFace:'Calibri'});
    sorted.forEach((emp,i)=>{
      const ry=1.12+i*.86, t=eTotal(emp), pct=t/(eTotal(sorted[0])||1);
      const ei=arr.indexOf(emp), col=EAC[ei%EAC.length];
      // row bg
      sl.addShape(pres.shapes.RECTANGLE,{x:.3,y:ry,w:12.7,h:.72,fill:{color:i%2===0?LIGHT:BG},line:{color:RULE,width:.3}});
      // left colour accent
      sl.addShape(pres.shapes.RECTANGLE,{x:.3,y:ry,w:.12,h:.72,fill:{color:col}});
      const medal=i===0?'🥇':i===1?'🥈':i===2?'🥉':`#${i+1}`;
      sl.addText(medal,{x:.5,y:ry+.08,w:.62,h:.54,fontSize:15,align:'center',fontFace:'Segoe UI Emoji'});
      sl.addText(emp.name,{x:1.22,y:ry+.05,w:4,h:.3,fontSize:11,bold:true,color:INK,fontFace:'Calibri'});
      sl.addText(`Avg: ${eAvg(emp).toFixed(1)}  ·  Best: ${eBest(emp).label}  ·  Weakest: ${eWorst(emp).label}`,{x:1.22,y:ry+.38,w:5.2,h:.26,fontSize:7.5,color:MUTED,fontFace:'Calibri'});
      // progress bar track
      const bx=6.8, bw=5.5;
      sl.addShape(pres.shapes.RECTANGLE,{x:bx,y:ry+.22,w:bw,h:.28,fill:{color:RULE}});
      if(pct>0) sl.addShape(pres.shapes.RECTANGLE,{x:bx,y:ry+.22,w:bw*pct,h:.28,fill:{color:col}});
      sl.addText(String(t),{x:bx+bw+.1,y:ry+.14,w:.82,h:.44,fontSize:14,bold:true,color:col,fontFace:'Calibri'});
    });

    // ── SLIDE: INSIGHTS — TOTAL + GAP ──
    updateOverlayMsg('Building insight charts...','expMsg');
    sl=pres.addSlide();
    slideHeader(sl,GREEN,'Performance Insights','Total scores and performance gap analysis across employees and services');
    sl.addShape(pres.shapes.RECTANGLE,{x:.3,y:1.0,w:6.1,h:.42,fill:{color:LIGHT2},line:{color:RULE,width:.3}});
    sl.addShape(pres.shapes.RECTANGLE,{x:.3,y:1.0,w:.1,h:.42,fill:{color:BLUE}});
    sl.addText('Total Score per Employee',{x:.46,y:1.07,w:5.9,h:.3,fontSize:9.5,bold:true,color:INK,fontFace:'Calibri'});
    sl.addChart(pres.charts.BAR,[{name:'Total',labels:sorted.map(e=>e.name.split(' ')[0]),values:sorted.map(e=>eTotal(e))}],{
      x:.3,y:1.44,w:6.1,h:5.8,barDir:'col',
      chartColors:sorted.map(e=>EAC[arr.indexOf(e)%EAC.length]),
      chartArea:{fill:{color:BG}}, catAxisLabelColor:MUTED, valAxisLabelColor:MUTED,
      valGridLine:{color:RULE,size:.4}, catGridLine:{style:'none'},
      showValue:true, dataLabelColor:INK, dataLabelFontSize:8, showLegend:false,
    });
    const gaps=SVCS.map(s=>{const v=arr.map(e=>e[s.key]||0);return Math.max(...v)-Math.min(...v);});
    sl.addShape(pres.shapes.RECTANGLE,{x:6.9,y:1.0,w:6.1,h:.42,fill:{color:LIGHT2},line:{color:RULE,width:.3}});
    sl.addShape(pres.shapes.RECTANGLE,{x:6.9,y:1.0,w:.1,h:.42,fill:{color:ORANGE}});
    sl.addText('Performance Gap per Service (Max − Min)',{x:7.06,y:1.07,w:5.9,h:.3,fontSize:9.5,bold:true,color:INK,fontFace:'Calibri'});
    sl.addChart(pres.charts.BAR,[{name:'Gap',labels:SVCS.map(s=>s.label),values:gaps}],{
      x:6.9,y:1.44,w:6.1,h:5.8,barDir:'bar',
      chartColors:gaps.map(g=>g>40?RED:g>20?ORANGE:GREEN),
      chartArea:{fill:{color:BG}}, catAxisLabelColor:MUTED, valAxisLabelColor:MUTED,
      valGridLine:{color:RULE,size:.4}, catGridLine:{style:'none'},
      showValue:true, dataLabelColor:INK, dataLabelFontSize:7.5, showLegend:false,
    });

    // ── SLIDE: TEAM ANALYTICS — AVG LINE + PIE ──
    sl=pres.addSlide();
    slideHeader(sl,TEAL,'Team Analytics','Average score per employee and service volume distribution');
    sl.addShape(pres.shapes.RECTANGLE,{x:.3,y:1.0,w:6.1,h:.42,fill:{color:LIGHT2},line:{color:RULE,width:.3}});
    sl.addShape(pres.shapes.RECTANGLE,{x:.3,y:1.0,w:.1,h:.42,fill:{color:TEAL}});
    sl.addText('Average Score per Employee',{x:.46,y:1.07,w:5.9,h:.3,fontSize:9.5,bold:true,color:INK,fontFace:'Calibri'});
    sl.addChart(pres.charts.LINE,[{name:'Avg Score',labels:arr.map(e=>e.name.split(' ')[0]),values:arr.map(e=>parseFloat(eAvg(e).toFixed(1)))}],{
      x:.3,y:1.44,w:6.1,h:5.8,lineSize:2.5,lineSmooth:true,
      chartColors:[BLUE],
      chartArea:{fill:{color:BG}}, catAxisLabelColor:MUTED, valAxisLabelColor:MUTED,
      valGridLine:{color:RULE,size:.4}, catGridLine:{style:'none'},
      valAxisMinVal:0, valAxisMaxVal:100,
      showValue:true, dataLabelColor:INK, dataLabelFontSize:8, showLegend:false,
    });
    sl.addShape(pres.shapes.RECTANGLE,{x:6.9,y:1.0,w:6.1,h:.42,fill:{color:LIGHT2},line:{color:RULE,width:.3}});
    sl.addShape(pres.shapes.RECTANGLE,{x:6.9,y:1.0,w:.1,h:.42,fill:{color:PURPLE}});
    sl.addText('Service Volume Distribution',{x:7.06,y:1.07,w:5.9,h:.3,fontSize:9.5,bold:true,color:INK,fontFace:'Calibri'});
    sl.addChart(pres.charts.PIE,[{name:'Volume',labels:SVCS.map(s=>s.label),values:SVCS.map(s=>sTotal(s.key))}],{
      x:6.9,y:1.44,w:6.1,h:5.8,
      chartColors:EAC,
      chartArea:{fill:{color:BG}},
      showPercent:true, dataLabelColor:INK, dataLabelFontSize:7.5,
      legendPos:'r', showLegend:true, legendFontSize:8, legendColor:MUTED,
    });

    await pres.writeFile({fileName:'HR_Performance_Dashboard.pptx'});
    hideOverlay('expOverlay');
  }catch(err){console.error(err);hideOverlay('expOverlay');alert('PPTX export failed: '+err.message);}
}

async function exportBoth(){await exportPDF();await new Promise(r=>setTimeout(r,300));await exportPPTX();}

/* helpers */
function showOverlay(msg,sub,oId,mId,sId){
  document.getElementById(oId).classList.add('show');
  document.getElementById(mId).textContent=msg;
  document.getElementById(sId).textContent=sub;
}
function updateOverlayMsg(msg,id){document.getElementById(id).textContent=msg;}
function hideOverlay(id){document.getElementById(id).classList.remove('show');}

/* ═══ INIT ═══ */
renderInstrCards();
renderAll();
</script>
</body>
</html>

HR Performance Dashboard v4
Generating...
Please wait
MOCK DATAv4.0
CSV Data Import
7 Employees
11 Services
Mock Data
Grand Total:
KPI Overview
Employee Performance Cards
Service Totals
Service Comparison Charts
Distribution by Service
Performance Intelligence
🏆 Overall Ranking
📡Team Average by Service
Individual Radar Profiles
Comparative Insights
📋
CSV Format Guide
Upload a Master CSV (all 11 services) or individual service CSVs. Service files override master for that service. Headers are case-insensitive; spaces normalize to underscores.
master.csv — Column ReferenceMaster
ColumnTypeReqDescription
employee_idstringYesPrimary key — unique per employee, used for cross-file merging.
employee_namestringYesFull display name shown in all cards and charts.
hiringnumberYesScore for Hiring service (0–100).
employee_status_updatenumberYesScore for status change transactions.
advance_children_educationnumberYesAdvance on children's education benefit score.
education_settlementnumberYesEducation reimbursement settlement score.
wagesnumberYesWage processing / payroll score.
work_schedule_otnumberYesWork schedule and overtime approval score.
internal_transfernumberYesInternal transfer requests score.
hopnumberYesHOP (Head of Personnel) transaction score.
separationnumberYesSeparation / offboarding case score.
grievancenumberYesGrievance cases handled score.
investigation_casesnumberYesInvestigation cases closed score.
Sample — master.csvExample
CSV Preview
employee_id,employee_name,hiring,employee_status_update,advance_children_education,education_settlement,wages,work_schedule_ot,internal_transfer,hop,separation,grievance,investigation_cases
EMP001,Ahmed Al-Rashidi,85,72,90,68,77,55,80,62,71,45,58
EMP002,Sara Binsaleh,91,88,74,82,95,70,65,79,83,60,72
EMP003,Omar Khalidi,67,55,80,73,60,85,90,50,66,77,88
Formatting NotesTips
Header row
Must be line 1. Auto-lowercased; spaces → underscores.
Delimiter
Comma only. No tabs or semicolons.
Values
Integers or decimals. Empty → 0.
Encoding
UTF-8 without BOM recommended.
Universal 3-Column Structure — all service CSVsAll Services
ColumnTypeReqDescription
employee_idstringYesUnique employee ID. Must match existing data for correct merging.
employee_namestringOptFull name. Updates display if provided; preserved if omitted.
valuenumberYesScore for this specific service. Always named value.
📥
Master CSV Priority
Populates all 11 services for all employees at once. Baseline dataset.
🔄
Service Override
Individual CSV overrides only that service. Others remain unchanged.
🆕
Auto-Create Employee
Unknown employee_id in service CSV creates employee with 0s elsewhere.
🎭
Mock Fallback
No CSV? Randomized mock data for 7 employees across 11 services.
🔁
Auto Re-render
All tabs, charts, totals, PDF/PPTX use fresh data on every upload.
⚠️
Empty → 0
Non-numeric or empty cells silently default to 0.
💡
Recommended: Use master.csv for a full data load, then individual service CSVs for incremental updates. The Export PDF and Export PPTX buttons always reflect the current data state.

Comments

Popular posts from this blog

Grounded by Loss, Lifted by Grit: A True Story of Reinvention Abroad (Saudi Arabia)

🌅 My Last Day, A Life-Changing Goodbye It was a hectic Thursday—June 24, 2021—my final day at work. As I handed over my responsibilities to my Filipino replacement and prepared to close a chapter of my life, I received a message that shattered my world. My sister reached out via Facebook: our father had passed away. 😢 I was paralyzed with grief. I turned to a close Filipino friend and shared the news; word spread quickly, and the room softened in sympathy. The Chief Executive and General Manager both offered their heartfelt condolences before departing. 🤝✨ When I arrived back at my rented room in Al-Khobar, the tears came rushing. I wept uncontrollably—my father’s memory flooding my heart. ❤️ ✅ My Final Settlement and Exit Visa Despite the emotional turmoil, the exit process went smoothly. I received my final settlement 💰 and exit visa ✈️, followed by a confirmed flight scheduled for July 25, 2021. The closure I expected was now within reach—until new obstacles emerged. 🛂 Urgent V...

Step-by-Step Guide to Renewing Your Company’s Civil Defence License in KSA—Fast and Hassle-Free

Civil Defence is a regulatory body tasked by the government to protect lives and properties regionally and internationally. As a company, it is compulsory to get a Civil Defense License before it can operate a business and has to undergo compliance to the Fire Fighting System, Safety and Security - You may refer to step by step guide on how to get around this procedure.   Step 1 Secured the below certificates and other documents to be submitted to Salamah Online, among these are as follows: 1. Facility Contract 2. CR 3. Certificate of Insurance            a.  Comprehensive          b.  Workmen            c.  Employers          d.  Property All Risks 4. Municipal & Professional License 5. CCTV Certification from Police 6. Enjaz / Police Clearance 7. Annual Maintenance Contract—Firefighting Equipment 6. Salama Code Step 2   Share a...

Fixing Slow Internet at Work—Here’s the Step-by-Step That Finally Worked

The Day the Internet Slowed Down—and How I Fixed It Fast It was a regular Thursday morning—until my phone buzzed. The general manager was on the line, clearly frustrated. She couldn’t log into SAP. “The internet’s crawling,” she said. “Can you do something about it?” Challenge accepted. Without wasting a minute, I grabbed my laptop and sprang into action. I knew that identifying the real problem quickly was key. My first move? Run a speed test—fast, simple, and revealing. Here’s how I did it, step by step: Step 1 Visit Speedtest.net 🌐 to quickly check your internet speed! 🚀 Step 2 Open the Command Prompt (CMD) 💻 and perform a quick ping test to your ISP 🌐 and other IP addresses to check for any network delays ⏳ or issues (e.g., 4.2.2.2). Step 3 Check the router's LED indicators 💡 for any unusual blinking patterns 🔄 that may indicate connectivity issues 🌐. Step 4 Check the back of the router 🔌 to confirm that all essential lights 💡 are on and functioning correctly. Step 5 T...