<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Project Charter — Aramco × UT Austin</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;0,9..40,800;1,9..40,400&family=DM+Serif+Display:ital@0;1&display=swap');
:root {
--ag:#00843D; --ag2:#005C2A; --agl:#E4F2EA;
--ab:#004B8D; --ab2:#003166; --abl:#E4EEF8; --abl2:#C8DCF5;
--uo:#BF5700; --uol:#FAF0E8;
--bg:#F1F1F6;
--s0:#FFFFFF; --s1:#F7F7FA; --s2:#EFEFF4; --s3:#E5E5EA;
--l0:#09090F; --l1:#2C2C30; --l2:#48484D; --l3:#6B6B70; --l4:#AEAEB4;
--sep:rgba(60,60,67,.13);
--gn:#34C759; --gnl:#E8F9EE;
--or:#FF9500; --orl:#FFF3E0;
--rd:#FF3B30; --rdl:#FFECEB;
--r:18px; --rsm:12px;
--sh:0 2px 14px rgba(0,0,0,.065),0 1px 3px rgba(0,0,0,.04);
--shmd:0 6px 24px rgba(0,0,0,.09),0 2px 6px rgba(0,0,0,.05);
--shlg:0 20px 60px rgba(0,0,0,.15),0 4px 14px rgba(0,0,0,.08);
--ease:cubic-bezier(.4,0,.2,1);
--spring:cubic-bezier(.34,1.56,.64,1);
--font:'DM Sans',-apple-system,'Helvetica Neue',sans-serif;
--serif:'DM Serif Display',Georgia,serif;
}
*{box-sizing:border-box;margin:0;padding:0;}
html{scroll-behavior:smooth;}
body{font-family:var(--font);background:var(--bg);color:var(--l0);min-height:100vh;-webkit-font-smoothing:antialiased;}
/* ── TOP NAV (brand + actions only) ── */
.nav{
position:sticky;top:0;z-index:300;height:54px;
background:rgba(255,255,255,.9);
backdrop-filter:saturate(180%) blur(24px);
-webkit-backdrop-filter:saturate(180%) blur(24px);
border-bottom:.5px solid var(--sep);
display:flex;align-items:center;justify-content:space-between;
padding:0 28px;gap:12px;
}
.nav-brand{display:flex;align-items:center;gap:10px;}
.nav-logo{
width:34px;height:34px;border-radius:9px;flex-shrink:0;
background:linear-gradient(145deg,var(--ab2),var(--ag));
display:flex;align-items:center;justify-content:center;
font-size:12px;font-weight:900;color:#fff;letter-spacing:-.5px;
}
.nav-name{font-size:14px;font-weight:800;letter-spacing:-.2px;}
.nav-sub{font-size:10px;color:var(--l3);margin-top:1px;}
.nav-right{display:flex;align-items:center;gap:7px;}
.mode-toggle{display:flex;align-items:center;background:var(--s2);border-radius:9px;padding:3px;gap:2px;}
.mtab{font-size:12px;font-weight:700;padding:5px 14px;border-radius:7px;border:none;background:transparent;color:var(--l3);cursor:pointer;transition:all .15s;}
.mtab.active{background:#fff;color:var(--l0);box-shadow:0 1px 3px rgba(0,0,0,.08);}
/* ── BUTTONS ── */
.btn{display:inline-flex;align-items:center;gap:5px;font-family:var(--font);font-size:12px;font-weight:800;padding:7px 15px;border-radius:9px;border:none;cursor:pointer;transition:all .15s;white-space:nowrap;}
.btn-green{background:var(--ag);color:#fff;} .btn-green:hover{background:var(--ag2);}
.btn-blue{background:var(--ab);color:#fff;} .btn-blue:hover{background:var(--ab2);}
.btn-ghost{background:var(--s2);color:var(--l2);} .btn-ghost:hover{background:var(--s3);}
/* ── HERO BANNER ── */
.hero{
background:linear-gradient(130deg,var(--ab2) 0%,var(--ab) 45%,#006830 100%);
padding:32px 32px 0;
position:relative;overflow:hidden;
}
.hero::before{
content:'';position:absolute;inset:0;
background:radial-gradient(ellipse 80% 60% at 70% 50%,rgba(0,196,80,.11),transparent),
radial-gradient(ellipse 50% 40% at 15% 80%,rgba(0,50,120,.22),transparent);
}
.hero::after{
content:'';position:absolute;inset:0;pointer-events:none;
background:url("data:image/svg+xml,%3Csvg width='40' height='40' viewBox='0 0 40 40' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%23fff' fill-opacity='0.025'%3E%3Ccircle cx='20' cy='20' r='14'/%3E%3C/g%3E%3C/svg%3E");
}
.hero-content{position:relative;z-index:1;max-width:1280px;margin:0 auto;display:flex;align-items:flex-start;justify-content:space-between;gap:24px;padding-bottom:20px;}
.hero-left{flex:1;min-width:0;}
.hero-badge{display:inline-flex;align-items:center;gap:5px;background:rgba(255,255,255,.14);border:1px solid rgba(255,255,255,.22);color:rgba(255,255,255,.88);padding:3px 11px;border-radius:20px;font-size:10px;font-weight:800;letter-spacing:.6px;text-transform:uppercase;margin-bottom:10px;}
.hero-id{font-size:11px;font-weight:800;color:rgba(255,255,255,.55);letter-spacing:.5px;margin-bottom:3px;}
.hero-title{font-family:var(--serif);font-size:clamp(18px,2.6vw,30px);color:#fff;line-height:1.2;margin-bottom:3px;}
.hero-short{font-size:13px;font-weight:600;color:rgba(255,255,255,.65);margin-bottom:8px;font-style:italic;}
.hero-desc{font-size:13px;color:rgba(255,255,255,.7);line-height:1.58;max-width:620px;}
.hero-pills{display:flex;flex-wrap:wrap;gap:7px;margin-top:12px;}
.hero-pill{display:flex;align-items:center;gap:4px;background:rgba(255,255,255,.12);border:1px solid rgba(255,255,255,.18);color:rgba(255,255,255,.88);padding:4px 11px;border-radius:20px;font-size:11px;font-weight:700;}
.hero-right{display:flex;flex-direction:column;align-items:flex-end;gap:8px;flex-shrink:0;}
.hero-kpis{display:flex;gap:8px;}
.hero-kpi{background:rgba(255,255,255,.12);border:1px solid rgba(255,255,255,.18);border-radius:var(--rsm);padding:11px 16px;text-align:center;min-width:90px;backdrop-filter:blur(6px);}
.hero-kpi-val{font-size:19px;font-weight:900;color:#fff;letter-spacing:-.5px;}
.hero-kpi-lbl{font-size:9px;color:rgba(255,255,255,.6);font-weight:800;text-transform:uppercase;letter-spacing:.5px;margin-top:2px;}
/* ── TABS — live inside / under the banner ── */
.hero-tabs-wrap{
position:relative;z-index:2;max-width:1280px;margin:0 auto;
display:flex;align-items:flex-end;gap:0;padding-top:6px;
}
.htab{
font-size:13px;font-weight:700;padding:10px 26px 9px;
background:rgba(255,255,255,.12);border:none;cursor:pointer;
color:rgba(255,255,255,.7);letter-spacing:-.1px;
border-radius:12px 12px 0 0;transition:all .15s;
border-right:.5px solid rgba(255,255,255,.1);
}
.htab:last-child{border-right:none;}
.htab:hover{background:rgba(255,255,255,.2);color:#fff;}
.htab.active{background:var(--bg);color:var(--ab);font-weight:800;}
/* ── MAIN ── */
.main{max-width:1280px;margin:0 auto;padding:20px 20px 40px;}
.tab-panel{display:none;} .tab-panel.active{display:block;}
.two-col{display:grid;grid-template-columns:1fr 1fr;gap:14px;align-items:start;}
.two-col.lw{grid-template-columns:46% 1fr;}
.col{display:flex;flex-direction:column;gap:14px;}
/* ── PANEL ── */
.panel{background:var(--s0);border-radius:var(--r);box-shadow:var(--sh);overflow:hidden;}
.ph{padding:14px 18px 12px;border-bottom:.5px solid var(--sep);display:flex;align-items:center;justify-content:space-between;}
.ph-l{display:flex;align-items:center;gap:9px;}
.ph-ic{width:30px;height:30px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:14px;flex-shrink:0;}
.ph-title{font-size:13px;font-weight:800;letter-spacing:-.2px;}
.ph-sub{font-size:10px;color:var(--l3);margin-top:1px;}
.ubadge{font-size:9px;font-weight:900;padding:2px 7px;border-radius:5px;letter-spacing:.4px;text-transform:uppercase;}
.ubadge-live{background:var(--gnl);color:var(--gn);}
.ubadge-static{background:var(--s2);color:var(--l4);}
.pb{padding:16px 18px;}
/* ── FORM ── */
.fg{margin-bottom:13px;} .fg:last-child{margin-bottom:0;}
.frow{display:grid;grid-template-columns:1fr 1fr;gap:10px;}
.frow3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;}
.flabel{font-size:10px;font-weight:900;color:var(--l3);text-transform:uppercase;letter-spacing:.55px;margin-bottom:5px;display:flex;align-items:center;gap:4px;}
.freq{color:var(--rd);font-size:12px;}
input[type=text],input[type=date],input[type=number],input[type=url],select,textarea{
width:100%;font-family:var(--font);font-size:13px;font-weight:500;color:var(--l0);
background:var(--s1);border:1.5px solid var(--sep);border-radius:var(--rsm);
padding:9px 12px;transition:border-color .15s,background .15s,box-shadow .15s;
-webkit-appearance:none;resize:vertical;
}
input:focus,select:focus,textarea:focus{outline:none;border-color:var(--ab);background:#fff;box-shadow:0 0 0 3px var(--abl);}
select{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath fill='%236B6B70' d='M0 0l5 6 5-6z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 11px center;padding-right:28px;}
textarea{min-height:80px;line-height:1.6;}
/* view fields */
.vf{display:none;background:var(--s1);border-radius:var(--rsm);padding:9px 12px;font-size:13px;font-weight:500;color:var(--l1);line-height:1.55;min-height:38px;word-break:break-word;}
.vf.empty{color:var(--l4);font-style:italic;}
body.view-mode input,body.view-mode select,body.view-mode textarea,body.view-mode .counter-wrap{display:none!important;}
body.view-mode .vf{display:block!important;}
body.view-mode .add-btn,body.view-mode .del-btn,body.view-mode .del-photo{display:none!important;}
body.view-mode .ms-ec{display:none!important;}
body.view-mode .ms-vc{display:table-cell!important;}
/* ── STATUS BADGE IN HEADER AREA ── */
.status-inline{display:flex;align-items:center;gap:8px;background:var(--s1);border-radius:var(--rsm);padding:10px 14px;margin-bottom:10px;}
.si-dot{width:10px;height:10px;border-radius:50%;flex-shrink:0;}
.si-label{font-size:13px;font-weight:700;flex:1;}
.si-pct{font-size:12px;font-weight:800;color:var(--l3);}
.prog-bg{height:5px;border-radius:3px;background:var(--s2);flex:1;overflow:hidden;}
.prog-fill{height:100%;border-radius:3px;background:linear-gradient(90deg,var(--ag),#00C851);transition:width .5s var(--ease);}
.div{height:.5px;background:var(--sep);margin:13px 0;}
/* ── MILESTONE TABLE ── */
.ms-wrap{overflow-x:auto;margin-bottom:6px;}
.ms-tbl{width:100%;border-collapse:separate;border-spacing:0 5px;}
.ms-tbl th{font-size:9px;font-weight:900;color:var(--l4);text-transform:uppercase;letter-spacing:.5px;padding:4px 8px;text-align:left;white-space:nowrap;}
.ms-tbl td{padding:7px 8px;font-size:12px;background:var(--s1);border-top:.5px solid var(--sep);border-bottom:.5px solid var(--sep);vertical-align:middle;}
.ms-tbl td:first-child{border-left:.5px solid var(--sep);border-radius:var(--rsm) 0 0 var(--rsm);padding-left:10px;}
.ms-tbl td:last-child{border-right:.5px solid var(--sep);border-radius:0 var(--rsm) var(--rsm) 0;}
.ms-tbl td input,.ms-tbl td select{padding:5px 7px;font-size:11px;border-radius:7px;min-height:auto;background:var(--s0);}
.ms-vc{display:none;font-size:11px;font-weight:700;white-space:nowrap;}
/* status pills */
.spill{display:inline-flex;align-items:center;padding:3px 8px;border-radius:20px;font-size:10px;font-weight:900;letter-spacing:.1px;}
.sp-paid{background:var(--gnl);color:var(--gn);}
.sp-pending{background:var(--orl);color:var(--or);}
.sp-ses{background:var(--abl);color:var(--ab);}
.sp-delayed{background:var(--rdl);color:var(--rd);}
.sp-progress{background:#EDE9FE;color:#7C3AED;}
.sp-invoice{background:#E0F2FE;color:#0369A1;}
/* ── ADD / DEL ── */
.add-btn{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:8px;border-radius:var(--rsm);border:none;background:var(--abl);color:var(--ab);font-family:var(--font);font-size:12px;font-weight:800;cursor:pointer;transition:opacity .15s;margin-top:6px;}
.add-btn:hover{opacity:.75;}
.del-btn{background:var(--rdl);color:var(--rd);border:none;border-radius:6px;padding:3px 7px;font-size:11px;cursor:pointer;transition:opacity .15s;}
.del-btn:hover{opacity:.72;}
/* ── COUNTER ── */
.counter-wrap{display:flex;align-items:center;gap:6px;}
.cbtn{width:28px;height:28px;border-radius:7px;border:1.5px solid var(--sep);background:var(--s0);cursor:pointer;font-size:16px;font-weight:300;display:flex;align-items:center;justify-content:center;color:var(--l2);transition:background .12s;flex-shrink:0;}
.cbtn:hover{background:var(--s2);}
.cinput{width:52px;text-align:center;font-weight:900;font-size:15px;border-radius:8px;padding:5px 6px;}
/* ── BUDGET BARS ── */
.bbar-row{display:flex;align-items:center;gap:10px;margin-bottom:8px;}
.bbar-yr{font-size:10px;font-weight:900;color:var(--l3);width:30px;flex-shrink:0;}
.bbar-bg{flex:1;height:7px;background:var(--s2);border-radius:4px;overflow:hidden;}
.bbar-fill{height:100%;border-radius:4px;transition:width .5s var(--ease);}
.bbar-val{font-size:11px;font-weight:800;color:var(--l2);width:68px;text-align:right;flex-shrink:0;}
/* ── RISK ITEM ── */
.risk-item{display:grid;grid-template-columns:84px 1fr 1fr auto;gap:8px;align-items:start;background:var(--s1);border-radius:var(--rsm);padding:10px 12px;margin-bottom:8px;}
/* ── GATE ITEM ── */
.gate-item{display:grid;grid-template-columns:1fr 110px 120px auto;gap:8px;align-items:end;background:var(--s1);border-radius:var(--rsm);padding:10px 12px;margin-bottom:8px;}
/* ── UPDATES (bigger, prominent) ── */
.update-item{
background:var(--s1);border-radius:var(--r);
border-left:4px solid var(--ab);
padding:16px 18px;margin-bottom:12px;
transition:box-shadow .2s;
}
.update-item:hover{box-shadow:var(--shmd);}
.update-top{display:grid;grid-template-columns:140px 1fr auto;gap:10px;align-items:end;margin-bottom:12px;}
.update-date-wrap{position:relative;}
.update-date-icon{position:absolute;left:10px;top:50%;transform:translateY(-50%);font-size:14px;pointer-events:none;}
.update-date-input{padding-left:32px!important;}
.update-cat-label{font-size:10px;font-weight:900;color:var(--l3);text-transform:uppercase;letter-spacing:.5px;margin-bottom:5px;}
.update-details{width:100%;font-family:var(--font);font-size:14px;font-weight:500;color:var(--l0);background:var(--s0);border:1.5px solid var(--sep);border-radius:var(--rsm);padding:11px 13px;resize:vertical;min-height:90px;line-height:1.65;transition:border-color .15s,box-shadow .15s;}
.update-details:focus{outline:none;border-color:var(--ab);box-shadow:0 0 0 3px var(--abl);}
.update-view{background:var(--s0);border-radius:var(--rsm);padding:10px 13px;font-size:14px;line-height:1.65;color:var(--l1);}
.update-meta-view{font-size:11px;font-weight:800;color:var(--ab);margin-bottom:6px;}
/* ── PHOTOS ── */
.photo-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(130px,1fr));gap:8px;margin-bottom:10px;}
.photo-item{aspect-ratio:4/3;border-radius:var(--rsm);overflow:hidden;position:relative;cursor:pointer;background:var(--s2);}
.photo-item img{width:100%;height:100%;object-fit:cover;display:block;transition:transform .2s;}
.photo-item:hover img{transform:scale(1.04);}
.photo-cap{position:absolute;bottom:0;left:0;right:0;background:linear-gradient(transparent,rgba(0,0,0,.58));color:#fff;font-size:10px;font-weight:700;padding:14px 7px 5px;line-height:1.3;}
.photo-del{position:absolute;top:5px;right:5px;background:rgba(0,0,0,.5);color:#fff;border:none;border-radius:50%;width:20px;height:20px;font-size:11px;cursor:pointer;display:none;align-items:center;justify-content:center;}
.photo-item:hover .photo-del{display:flex;}
.photo-strip{display:flex;align-items:center;gap:8px;padding:9px 12px;background:var(--s1);border:1.5px dashed var(--sep);border-radius:var(--rsm);cursor:pointer;transition:all .15s;}
.photo-strip:hover,.photo-strip.drag-over{border-color:var(--ag);background:var(--agl);}
.photo-strip-icon{font-size:18px;}
.photo-strip-txt{font-size:12px;color:var(--l3);font-weight:600;}
/* ── KPI MINI ── */
.kpi-row{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:12px;}
.kpi-mini{background:var(--s1);border-radius:var(--rsm);padding:12px;text-align:center;}
.kpi-mini-val{font-size:20px;font-weight:900;letter-spacing:-.5px;}
.kpi-mini-lbl{font-size:10px;color:var(--l3);font-weight:700;margin-top:2px;text-transform:uppercase;letter-spacing:.4px;}
/* ── LIGHTBOX ── */
.lb{position:fixed;inset:0;z-index:500;background:rgba(0,0,0,.9);backdrop-filter:blur(14px);display:flex;align-items:center;justify-content:center;padding:20px;opacity:0;pointer-events:none;transition:opacity .2s;}
.lb.open{opacity:1;pointer-events:all;}
.lb img{max-width:100%;max-height:85vh;border-radius:10px;box-shadow:var(--shlg);}
.lb-close{position:absolute;top:18px;right:22px;background:rgba(255,255,255,.15);border:none;color:#fff;width:34px;height:34px;border-radius:50%;cursor:pointer;font-size:15px;display:flex;align-items:center;justify-content:center;}
.lb-cap{position:absolute;bottom:24px;left:50%;transform:translateX(-50%);color:rgba(255,255,255,.8);font-size:12px;font-weight:700;background:rgba(0,0,0,.4);padding:5px 14px;border-radius:20px;white-space:nowrap;}
/* ── TOAST ── */
.toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%) translateY(16px);background:var(--l0);color:#fff;padding:9px 20px;border-radius:11px;font-size:12px;font-weight:800;box-shadow:var(--shlg);opacity:0;transition:all .25s var(--spring);z-index:999;pointer-events:none;display:flex;align-items:center;gap:7px;}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0);}
@keyframes fadeUp{from{opacity:0;transform:translateY(10px);}to{opacity:1;transform:translateY(0);}}
.panel{animation:fadeUp .28s var(--ease) both;}
.panel:nth-child(2){animation-delay:.05s;} .panel:nth-child(3){animation-delay:.1s;}
@media(max-width:920px){
.two-col,.two-col.lw{grid-template-columns:1fr;}
.hero-right{display:none;}
.htab{padding:9px 16px 8px;font-size:12px;}
}
</style>
</head>
<body>
<!-- LIGHTBOX -->
<div class="lb" id="lb" onclick="closeLB()">
<button class="lb-close" onclick="closeLB()">✕</button>
<img id="lb-img" src="" alt="">
<div class="lb-cap" id="lb-cap"></div>
</div>
<!-- TOAST -->
<div class="toast" id="toast"></div>
<!-- ══ TOP NAV (brand + actions only) ══ -->
<nav class="nav">
<div class="nav-brand">
<div class="nav-logo">A×U</div>
<div>
<div class="nav-name">Project Charter</div>
<div class="nav-sub">Aramco × UT Austin Research</div>
</div>
</div>
<div class="nav-right">
<div class="mode-toggle">
<button class="mtab active" id="mt-view" onclick="setMode('view')">👁 View</button>
<button class="mtab" id="mt-edit" onclick="setMode('edit')">✏️ Edit</button>
</div>
<button class="btn btn-green" onclick="saveData()">💾 Save</button>
<button class="btn btn-blue" onclick="exportExcel()">⬇ Excel</button>
</div>
</nav>
<!-- ══ HERO BANNER ══ -->
<div class="hero">
<div class="hero-content">
<div class="hero-left">
<div class="hero-badge">📋 Project Charter</div>
<div class="hero-id" id="hero-id">Project ID: —</div>
<div class="hero-title" id="hero-title">Enter project name to get started</div>
<div class="hero-short" id="hero-short"></div>
<div class="hero-desc" id="hero-desc">Complete the Summary tab to populate this header.</div>
<div class="hero-pills" id="hero-pills"></div>
</div>
<div class="hero-right">
<div class="hero-kpis">
<div class="hero-kpi"><div class="hero-kpi-val" id="hkpi-comp">0%</div><div class="hero-kpi-lbl">Complete</div></div>
<div class="hero-kpi"><div class="hero-kpi-val" id="hkpi-paid">$0</div><div class="hero-kpi-lbl">Paid</div></div>
<div class="hero-kpi"><div class="hero-kpi-val" id="hkpi-total">$0</div><div class="hero-kpi-lbl">Total</div></div>
</div>
</div>
</div>
<!-- TABS live at bottom of hero -->
<div class="hero-tabs-wrap">
<button class="htab active" onclick="showTab('summary')">📋 Summary</button>
<button class="htab" onclick="showTab('technology')">🔬 Technology</button>
<button class="htab" onclick="showTab('progress')">📊 Progress</button>
</div>
</div>
<!-- ══ MAIN CONTENT ══ -->
<div class="main">
<!-- ════════════════════════════════════════
TAB 1: SUMMARY
Left : Project Identity + Registry
Right: Team + Status
════════════════════════════════════════ -->
<div class="tab-panel active" id="tab-summary">
<div class="two-col lw">
<!-- LEFT -->
<div class="col">
<div class="panel">
<div class="ph">
<div class="ph-l"><div class="ph-ic" style="background:var(--abl)">🏷</div>
<div><div class="ph-title">Project Identity</div><div class="ph-sub">Core identifiers — set once</div></div></div>
<span class="ubadge ubadge-static">Static</span>
</div>
<div class="pb">
<div class="frow">
<div class="fg"><div class="flabel">Project ID <span class="freq">*</span></div>
<input type="text" id="f_project_id" placeholder="ARUT-2024-001" oninput="updateHero()">
<div class="vf" id="v_project_id"></div></div>
<div class="fg"><div class="flabel">Focus Area</div>
<select id="f_focus_area" onchange="updateHero()">
<option value="">Select…</option>
<option>Reservoir Characterization</option><option>Enhanced Oil Recovery</option>
<option>Carbon Capture & Storage</option><option>Hydrogen Production</option>
<option>Drilling & Completions</option><option>Digital & AI</option>
<option>Pipeline Integrity</option><option>Renewable Energy</option>
<option>Process Engineering</option><option>Materials Science</option><option>Other</option>
</select>
<div class="vf" id="v_focus_area"></div></div>
</div>
<div class="fg"><div class="flabel">Project Full Name <span class="freq">*</span></div>
<input type="text" id="f_project_name" placeholder="Full descriptive project title" oninput="updateHero()">
<div class="vf" id="v_project_name"></div></div>
<div class="fg"><div class="flabel">Short Name</div>
<input type="text" id="f_short_name" placeholder="Brief working title or acronym" oninput="updateHero()">
<div class="vf" id="v_short_name"></div></div>
<div class="fg"><div class="flabel">Description <span class="freq">*</span></div>
<textarea id="f_description" rows="3" placeholder="Describe the project scope, approach, and context…" oninput="updateHero()"></textarea>
<div class="vf" id="v_description"></div></div>
<div class="frow">
<div class="fg"><div class="flabel">Start Date <span class="freq">*</span></div>
<input type="date" id="f_start_date" oninput="updateHero()">
<div class="vf" id="v_start_date"></div></div>
<div class="fg"><div class="flabel">End Date <span class="freq">*</span></div>
<input type="date" id="f_end_date" oninput="updateHero()">
<div class="vf" id="v_end_date"></div></div>
</div>
<div class="frow3">
<div class="fg"><div class="flabel">Total Value ($)</div>
<input type="number" id="f_total_value" placeholder="0" oninput="updateHero()">
<div class="vf" id="v_total_value"></div></div>
<div class="fg"><div class="flabel">Expected TRV ($)</div>
<input type="number" id="f_trv" placeholder="0">
<div class="vf" id="v_trv"></div></div>
<div class="fg"><div class="flabel">Expected TPV ($)</div>
<input type="number" id="f_tpv" placeholder="0">
<div class="vf" id="v_tpv"></div></div>
</div>
<div class="div"></div>
<div class="frow">
<div class="fg"><div class="flabel">Overall Status</div>
<select id="f_overall_status" onchange="updateHero()">
<option value="">Select…</option>
<option>On Track</option><option>At Risk</option>
<option>Delayed</option><option>Completed</option><option>On Hold</option>
</select>
<div class="vf" id="v_overall_status"></div></div>
<div class="fg"><div class="flabel">Last Updated By</div>
<input type="text" id="f_updated_by" placeholder="Name / role">
<div class="vf" id="v_updated_by"></div></div>
</div>
</div>
</div>
<div class="panel">
<div class="ph">
<div class="ph-l"><div class="ph-ic" style="background:var(--s2)">🗂</div>
<div><div class="ph-title">Project Registry</div><div class="ph-sub">Classification & stage gate</div></div></div>
<span class="ubadge ubadge-static">Static</span>
</div>
<div class="pb">
<div class="frow">
<div class="fg"><div class="flabel">Technology Program</div>
<input type="text" id="f_tech_program" placeholder="e.g. EXPEC ARC Program">
<div class="vf" id="v_tech_program"></div></div>
<div class="fg"><div class="flabel">SAP / WO Number</div>
<input type="text" id="f_sap" placeholder="Internal reference">
<div class="vf" id="v_sap"></div></div>
</div>
<div class="frow">
<div class="fg"><div class="flabel">Current TRL</div>
<select id="f_trl">
<option value="">Select TRL…</option>
<option>TRL 1 — Basic principles</option><option>TRL 2 — Concept formulated</option>
<option>TRL 3 — Proof of concept</option><option>TRL 4 — Lab validated</option>
<option>TRL 5 — Relevant environment</option><option>TRL 6 — Demonstrated</option>
<option>TRL 7 — Prototype</option><option>TRL 8 — System qualified</option>
<option>TRL 9 — Operational</option>
</select>
<div class="vf" id="v_trl"></div></div>
<div class="fg"><div class="flabel">Target TRL</div>
<select id="f_trl_target">
<option value="">Select…</option>
<option>TRL 3</option><option>TRL 4</option><option>TRL 5</option>
<option>TRL 6</option><option>TRL 7</option><option>TRL 8</option><option>TRL 9</option>
</select>
<div class="vf" id="v_trl_target"></div></div>
</div>
<div class="fg"><div class="flabel">Current Stage</div>
<select id="f_stage" onchange="updateHero()">
<option value="">Select stage…</option>
<option value="Stage 1 — Ideation & Concept">Stage 1 — Ideation & Concept</option>
<option value="Stage 2 — Feasibility & Development">Stage 2 — Feasibility & Development</option>
<option value="Stage 3 — Prototype & Bench Scale">Stage 3 — Prototype & Bench Scale</option>
<option value="Stage 4 — Pilot & Demonstration">Stage 4 — Pilot & Demonstration</option>
<option value="Stage 5 — Field Deployment">Stage 5 — Field Deployment</option>
<option value="Stage C — Commercialization">Stage C — Commercialization</option>
</select>
<div class="vf" id="v_stage"></div></div>
<div class="frow">
<div class="fg"><div class="flabel">Next Gate Date</div>
<input type="date" id="f_next_gate">
<div class="vf" id="v_next_gate"></div></div>
<div class="fg"><div class="flabel">IP Classification</div>
<select id="f_ip_class">
<option value="">Select…</option>
<option>Aramco Owned</option><option>Joint Ownership</option>
<option>UT Owned (Licensed)</option><option>Open / Published</option>
</select>
<div class="vf" id="v_ip_class"></div></div>
</div>
<div class="frow">
<div class="fg"><div class="flabel">Deployment Opportunity</div>
<input type="text" id="f_deployment" placeholder="Target field / asset">
<div class="vf" id="v_deployment"></div></div>
<div class="fg"><div class="flabel">Demo Run Planning</div>
<input type="text" id="f_demo_plan" placeholder="Planned demo date or description">
<div class="vf" id="v_demo_plan"></div></div>
</div>
<div class="fg"><div class="flabel">Related Projects / Dependencies</div>
<textarea id="f_related" rows="2" placeholder="Related project IDs or dependent programs…"></textarea>
<div class="vf" id="v_related"></div></div>
<div class="fg"><div class="flabel">SharePoint Folder URL</div>
<input type="url" id="f_sp_url" placeholder="https://aramco.sharepoint.com/…">
<div class="vf" id="v_sp_url"></div></div>
</div>
</div>
</div><!-- end left col -->
<!-- RIGHT -->
<div class="col">
<div class="panel">
<div class="ph">
<div class="ph-l"><div class="ph-ic" style="background:var(--abl)">🏢</div>
<div><div class="ph-title">Aramco Team</div><div class="ph-sub">Saudi Aramco project personnel</div></div></div>
<span class="ubadge ubadge-static">Static</span>
</div>
<div class="pb">
<div class="fg"><div class="flabel">Project Leader <span class="freq">*</span></div>
<input type="text" id="f_aramco_pl" placeholder="Full name" oninput="updateHero()">
<div class="vf" id="v_aramco_pl"></div></div>
<div class="fg"><div class="flabel">Other Project Members</div>
<textarea id="f_aramco_members" rows="3" placeholder="Names and roles, one per line…"></textarea>
<div class="vf" id="v_aramco_members"></div></div>
<div class="fg"><div class="flabel">Division / Department</div>
<input type="text" id="f_aramco_div" placeholder="e.g. EXPEC ARC — Reservoir Simulation">
<div class="vf" id="v_aramco_div"></div></div>
<div class="fg"><div class="flabel">Business Sponsor</div>
<input type="text" id="f_aramco_sponsor" placeholder="Sponsor / proponent name & department">
<div class="vf" id="v_aramco_sponsor"></div></div>
<div class="fg"><div class="flabel">Project FTE</div>
<input type="number" id="f_aramco_fte" placeholder="e.g. 2.5" step="0.5">
<div class="vf" id="v_aramco_fte"></div></div>
</div>
</div>
<div class="panel">
<div class="ph">
<div class="ph-l"><div class="ph-ic" style="background:var(--uol)">🎓</div>
<div><div class="ph-title">UT Austin Team</div><div class="ph-sub">University project personnel</div></div></div>
<span class="ubadge ubadge-static">Static</span>
</div>
<div class="pb">
<div class="fg"><div class="flabel">Principal Investigator <span class="freq">*</span></div>
<input type="text" id="f_ut_pi" placeholder="Prof. Full Name" oninput="updateHero()">
<div class="vf" id="v_ut_pi"></div></div>
<div class="fg"><div class="flabel">Project Leader (UT)</div>
<input type="text" id="f_ut_pl" placeholder="Full name">
<div class="vf" id="v_ut_pl"></div></div>
<div class="fg"><div class="flabel">Technical Lead (UT)</div>
<input type="text" id="f_ut_tl" placeholder="Full name">
<div class="vf" id="v_ut_tl"></div></div>
<div class="fg"><div class="flabel">Other Team Members / Students</div>
<textarea id="f_ut_members" rows="3" placeholder="PhD students, postdocs, researchers, one per line…"></textarea>
<div class="vf" id="v_ut_members"></div></div>
<div class="fg"><div class="flabel">Department / Center</div>
<input type="text" id="f_ut_dept" placeholder="e.g. Hildebrand Dept. of Petroleum Engineering">
<div class="vf" id="v_ut_dept"></div></div>
<div class="frow">
<div class="fg"><div class="flabel">Project FTE</div>
<input type="number" id="f_ut_fte" placeholder="e.g. 3.0" step="0.5">
<div class="vf" id="v_ut_fte"></div></div>
<div class="fg"><div class="flabel">Business / Contracts Manager</div>
<input type="text" id="f_ut_bm" placeholder="Full name">
<div class="vf" id="v_ut_bm"></div></div>
</div>
</div>
</div>
</div><!-- end right col -->
</div>
</div><!-- end tab-summary -->
<!-- ════════════════════════════════════════
TAB 2: TECHNOLOGY
Left : Technology Outline + Key Objectives + Risks
Right: Photo Gallery
════════════════════════════════════════ -->
<div class="tab-panel" id="tab-technology">
<div class="two-col">
<!-- LEFT -->
<div class="col">
<div class="panel">
<div class="ph">
<div class="ph-l"><div class="ph-ic" style="background:var(--agl)">🔬</div>
<div><div class="ph-title">Technology Outline</div><div class="ph-sub">Technical framing and value proposition</div></div></div>
<span class="ubadge ubadge-static">Static</span>
</div>
<div class="pb">
<div class="fg"><div class="flabel">Key Objectives</div>
<textarea id="f_objectives" rows="3" placeholder="Primary objectives (one per line)…"></textarea>
<div class="vf" id="v_objectives"></div></div>
<div class="div"></div>
<div class="fg"><div class="flabel">Specific Business Challenge / Opportunity</div>
<textarea id="f_challenge" rows="3" placeholder="The specific challenge or opportunity being addressed…"></textarea>
<div class="vf" id="v_challenge"></div></div>
<div class="fg"><div class="flabel">Proposed Technology Solution</div>
<textarea id="f_solution" rows="4" placeholder="Detail the proposed technology, approach, and innovation…"></textarea>
<div class="vf" id="v_solution"></div></div>
<div class="fg"><div class="flabel">Technology Acceptance Criteria</div>
<textarea id="f_acceptance" rows="3" placeholder="Measurable criteria for technology success and acceptance…"></textarea>
<div class="vf" id="v_acceptance"></div></div>
<div class="fg"><div class="flabel">Benchmark Solution <span style="font-size:10px;color:var(--l4);font-weight:400;text-transform:none;letter-spacing:0">(next best alternative)</span></div>
<textarea id="f_benchmark" rows="2" placeholder="Current state-of-the-art or competing approach…"></textarea>
<div class="vf" id="v_benchmark"></div></div>
<div class="fg"><div class="flabel">Sources of Value</div>
<textarea id="f_value_sources" rows="3" placeholder="Expected value streams: cost reduction, production increase, safety, IP…"></textarea>
<div class="vf" id="v_value_sources"></div></div>
<div class="fg"><div class="flabel">Personnel Development Opportunities</div>
<textarea id="f_personnel_dev" rows="2" placeholder="Training, skill development, talent pipeline opportunities…"></textarea>
<div class="vf" id="v_personnel_dev"></div></div>
<div class="fg"><div class="flabel">Key Assumptions & Constraints</div>
<textarea id="f_assumptions" rows="2" placeholder="Key assumptions and constraints the project operates under…"></textarea>
<div class="vf" id="v_assumptions"></div></div>
</div>
</div>
<div class="panel">
<div class="ph">
<div class="ph-l"><div class="ph-ic" style="background:var(--orl)">⚠️</div>
<div><div class="ph-title">Project Risks</div><div class="ph-sub">Identified risks and mitigations</div></div></div>
<span class="ubadge ubadge-static">Static</span>
</div>
<div class="pb">
<div id="risks-container"></div>
<button class="add-btn" onclick="addRisk()">+ Add Risk</button>
</div>
</div>
</div><!-- end left col -->
<!-- RIGHT: Photos (sticky) -->
<div class="col">
<div class="panel" style="position:sticky;top:70px;">
<div class="ph">
<div class="ph-l"><div class="ph-ic" style="background:var(--abl)">📸</div>
<div><div class="ph-title">Technology Photos</div><div class="ph-sub">Lab, equipment, field & results</div></div></div>
<span class="ubadge ubadge-live">🟢 Live</span>
</div>
<div class="pb">
<div class="photo-grid" id="photo-grid"></div>
<div class="photo-strip" id="photo-drop" onclick="document.getElementById('photo-input').click()">
<span class="photo-strip-icon">📷</span>
<span class="photo-strip-txt"><strong>Upload photos</strong> — click or drag & drop</span>
</div>
<input type="file" id="photo-input" accept="image/*" multiple style="display:none" onchange="handlePhotos(this.files)">
</div>
</div>
</div>
</div>
</div><!-- end tab-technology -->
<!-- ════════════════════════════════════════
TAB 3: PROGRESS
Left : Updates & Highlights + Milestones + Gates
Right: Budget (auto-calc) + KPIs + Links
════════════════════════════════════════ -->
<div class="tab-panel" id="tab-progress">
<div class="two-col">
<!-- LEFT -->
<div class="col">
<!-- Updates & Highlights — big, prominent -->
<div class="panel">
<div class="ph">
<div class="ph-l"><div class="ph-ic" style="background:var(--abl)">✨</div>
<div><div class="ph-title">Updates & Highlights</div><div class="ph-sub">Period-by-period project updates — regularly added</div></div></div>
<span class="ubadge ubadge-live">🟢 Live</span>
</div>
<div class="pb">
<div id="updates-container"></div>
<button class="add-btn" onclick="addUpdate()">+ Add Update / Highlight</button>
</div>
</div>
<!-- Milestones -->
<div class="panel">
<div class="ph">
<div class="ph-l"><div class="ph-ic" style="background:var(--abl)">🎯</div>
<div><div class="ph-title">Milestones</div><div class="ph-sub">Deliverables, payments & status</div></div></div>
<span class="ubadge ubadge-live">🟢 Live</span>
</div>
<div class="pb">
<div class="ms-wrap">
<table class="ms-tbl" id="ms-tbl">
<thead><tr>
<th>ID</th><th>Name / Deliverable</th><th>Due</th>
<th>Value ($)</th><th>Paid ($)</th>
<th>Status</th><th class="ms-ec">Invoice</th><th class="ms-ec"></th>
</tr></thead>
<tbody id="ms-tbody"></tbody>
</table>
</div>
<button class="add-btn" onclick="addMilestone()">+ Add Milestone</button>
</div>
</div>
<!-- Gate Meetings -->
<div class="panel">
<div class="ph">
<div class="ph-l"><div class="ph-ic" style="background:var(--uol)">🚪</div>
<div><div class="ph-title">Gate Meetings & Key Events</div><div class="ph-sub">Stage-gate reviews & deployments</div></div></div>
<span class="ubadge ubadge-live">🟢 Live</span>
</div>
<div class="pb">
<div id="gates-container"></div>
<button class="add-btn" onclick="addGate()">+ Add Gate / Event</button>
</div>
</div>
</div><!-- end left col -->
<!-- RIGHT -->
<div class="col">
<!-- Budget auto-calc -->
<div class="panel">
<div class="ph">
<div class="ph-l"><div class="ph-ic" style="background:var(--agl)">💰</div>
<div><div class="ph-title">Budget Summary</div><div class="ph-sub">Auto-calculated from milestones by year</div></div></div>
<span class="ubadge ubadge-live">🟢 Live</span>
</div>
<div class="pb">
<div class="kpi-row" id="budget-kpis"></div>
<div id="budget-chart"></div>
<div class="div"></div>
<div style="display:flex;align-items:center;gap:10px;">
<div class="fg" style="margin:0;flex:0 0 110px"><div class="flabel">Currency</div>
<select id="f_currency"><option>USD</option><option>SAR</option><option>EUR</option><option>GBP</option></select>
<div class="vf" id="v_currency"></div></div>
<div style="font-size:11px;color:var(--l4);margin-top:14px">Budget values derived from milestone due dates & amounts — no manual entry needed.</div>
</div>
</div>
</div>
<!-- Research KPIs -->
<div class="panel">
<div class="ph">
<div class="ph-l"><div class="ph-ic" style="background:var(--gnl)">📈</div>
<div><div class="ph-title">Research Output KPIs</div><div class="ph-sub">Publications, patents, datasets</div></div></div>
<span class="ubadge ubadge-live">🟢 Live</span>
</div>
<div class="pb">
<div class="kpi-row">
<div class="kpi-mini">
<div class="kpi-mini-val" style="color:var(--ab)"><span id="kv-pubs">0</span></div>
<div class="kpi-mini-lbl">Publications</div>
<div class="counter-wrap" style="justify-content:center;margin-top:6px">
<button class="cbtn" onclick="counter('f_pubs','kv-pubs',-1)">−</button>
<input class="cinput" type="number" id="f_pubs" value="0" min="0" oninput="document.getElementById('kv-pubs').textContent=Math.max(0,+this.value||0)">
<button class="cbtn" onclick="counter('f_pubs','kv-pubs',1)">+</button>
</div>
<div class="vf" id="v_pubs" style="text-align:center;display:none"></div>
</div>
<div class="kpi-mini">
<div class="kpi-mini-val" style="color:var(--uo)"><span id="kv-patents">0</span></div>
<div class="kpi-mini-lbl">Patents Filed</div>
<div class="counter-wrap" style="justify-content:center;margin-top:6px">
<button class="cbtn" onclick="counter('f_patents','kv-patents',-1)">−</button>
<input class="cinput" type="number" id="f_patents" value="0" min="0" oninput="document.getElementById('kv-patents').textContent=Math.max(0,+this.value||0)">
<button class="cbtn" onclick="counter('f_patents','kv-patents',1)">+</button>
</div>
<div class="vf" id="v_patents" style="text-align:center;display:none"></div>
</div>
<div class="kpi-mini">
<div class="kpi-mini-val" style="color:var(--gn)"><span id="kv-datasets">0</span></div>
<div class="kpi-mini-lbl">Datasets / SW</div>
<div class="counter-wrap" style="justify-content:center;margin-top:6px">
<button class="cbtn" onclick="counter('f_datasets','kv-datasets',-1)">−</button>
<input class="cinput" type="number" id="f_datasets" value="0" min="0" oninput="document.getElementById('kv-datasets').textContent=Math.max(0,+this.value||0)">
<button class="cbtn" onclick="counter('f_datasets','kv-datasets',1)">+</button>
</div>
<div class="vf" id="v_datasets" style="text-align:center;display:none"></div>
</div>
</div>
<div class="fg"><div class="flabel">Publication List</div>
<textarea id="f_pub_list" rows="3" placeholder="Authors, Year — Journal, DOI (one per line)…"></textarea>
<div class="vf" id="v_pub_list"></div></div>
<div class="fg"><div class="flabel">Patent List</div>
<textarea id="f_patent_list" rows="2" placeholder="Patent No. — Title (one per line)…"></textarea>
<div class="vf" id="v_patent_list"></div></div>
</div>
</div>
<!-- Links -->
<div class="panel">
<div class="ph">
<div class="ph-l"><div class="ph-ic" style="background:var(--s2)">🔗</div>
<div><div class="ph-title">Links & References</div></div></div>
<span class="ubadge ubadge-static">Static</span>
</div>
<div class="pb">
<div class="frow">
<div class="fg"><div class="flabel">Invoice Folder URL</div>
<input type="url" id="f_inv_url" placeholder="https://…/invoices">
<div class="vf" id="v_inv_url"></div></div>
<div class="fg"><div class="flabel">Reports Folder URL</div>
<input type="url" id="f_rpt_url" placeholder="https://…/reports">
<div class="vf" id="v_rpt_url"></div></div>
</div>
<div class="fg"><div class="flabel">Additional Notes</div>
<textarea id="f_notes" rows="2" placeholder="Any additional comments or context…"></textarea>
<div class="vf" id="v_notes"></div></div>
</div>
</div>
</div><!-- end right col -->
</div>
</div><!-- end tab-progress -->
</div><!-- end .main -->
<script>
/* ═══════════════════════════════════════
DATA & STORAGE
═══════════════════════════════════════ */
const SK = 'aramco_charter_v3';
let DATA = { fields:{}, milestones:[], gates:[], risks:[], photos:[], updates:[] };
window.addEventListener('DOMContentLoaded', () => {
loadData();
restoreAll();
// Photo drop zone
const pd = document.getElementById('photo-drop');
pd.addEventListener('dragover', e => { e.preventDefault(); pd.classList.add('drag-over'); });
pd.addEventListener('dragleave', () => pd.classList.remove('drag-over'));
pd.addEventListener('drop', e => { e.preventDefault(); pd.classList.remove('drag-over'); handlePhotos(e.dataTransfer.files); });
updateHero();
updateBudget();
});
function loadData() {
try { const r = localStorage.getItem(SK); if (r) DATA = JSON.parse(r); } catch(e) {}
}
function saveData() {
document.querySelectorAll('[id^="f_"]').forEach(el => { DATA.fields[el.id] = el.value; });
syncMilestones(); syncGates(); syncRisks(); syncUpdates();
try {
localStorage.setItem(SK, JSON.stringify(DATA));
showToast('✅ Saved');
updateHero(); updateBudget();
} catch(e) { showToast('⚠️ Save failed — data may be too large'); }
}
function restoreAll() {
// Simple fields
Object.entries(DATA.fields).forEach(([k, v]) => {
const el = document.getElementById(k);
if (el) el.value = v;
});
// Counter live display sync
[['f_pubs','kv-pubs'],['f_patents','kv-patents'],['f_datasets','kv-datasets']].forEach(([fid, kid]) => {
const val = DATA.fields[fid] || '0';
const el = document.getElementById(fid); if (el) el.value = val;
const kv = document.getElementById(kid); if (kv) kv.textContent = val;
});
DATA.milestones.forEach(ms => addMilestone(ms));
DATA.gates.forEach(g => addGate(g));
DATA.risks.forEach(r => addRisk(r));
DATA.photos.forEach(p => renderPhoto(p.dataUrl, p.caption));
DATA.updates.forEach(u => addUpdate(u));
}
/* ═══════════════════════════════════════
VIEW / EDIT MODE
═══════════════════════════════════════ */
let mode = 'view';
function setMode(m) {
mode = m;
document.getElementById('mt-view').classList.toggle('active', m === 'view');
document.getElementById('mt-edit').classList.toggle('active', m === 'edit');
if (m === 'view') { document.body.classList.add('view-mode'); syncViewFields(); }
else { document.body.classList.remove('view-mode'); }
}
function syncViewFields() {
document.querySelectorAll('[id^="f_"]').forEach(el => {
const vid = 'v_' + el.id.slice(2);
const vEl = document.getElementById(vid);
if (!vEl) return;
const val = el.value || '';
const isMonetary = ['total_value','trv','tpv'].some(k => el.id.includes(k));
if (!val) { vEl.textContent = '—'; vEl.className = 'vf empty'; }
else { vEl.textContent = isMonetary ? '$' + Number(val).toLocaleString() : val; vEl.className = 'vf'; }
});
// counter view fields
[['f_pubs','v_pubs'],['f_patents','v_patents'],['f_datasets','v_datasets']].forEach(([fid,vid]) => {
const vEl = document.getElementById(vid);
if (vEl) { vEl.textContent = document.getElementById(fid)?.value || '0'; vEl.className = 'vf'; }
});
}
/* ═══════════════════════════════════════
HERO
═══════════════════════════════════════ */
function updateHero() {
const id = fv('project_id') || '—';
const name = fv('project_name') || 'Enter project name to get started';
const short = fv('short_name');
const desc = fv('description') || 'Complete the Summary tab to populate this header.';
const start = fv('start_date');
const end = fv('end_date');
const pl = fv('aramco_pl');
const pi = fv('ut_pi');
const fa = fv('focus_area');
const stage = fv('stage');
const status = fv('overall_status');
document.getElementById('hero-id').textContent = 'Project ID: ' + id;
document.getElementById('hero-title').textContent = name;
document.getElementById('hero-short').textContent = short ? '(' + short + ')' : '';
document.getElementById('hero-short').style.display = short ? '' : 'none';
document.getElementById('hero-desc').textContent = desc.substring(0, 200) + (desc.length > 200 ? '…' : '');
document.title = name !== 'Enter project name to get started' ? name + ' — Charter' : 'Project Charter';
const pills = [];
if (fa) pills.push(`<div class="hero-pill">🎯 ${esc(fa)}</div>`);
if (stage) pills.push(`<div class="hero-pill">📍 ${esc(stage)}</div>`);
if (status) {
const sc = status === 'On Track' ? '#00843D' : status === 'Completed' ? '#004B8D' : status === 'At Risk' ? '#FF9500' : '#FF3B30';
pills.push(`<div class="hero-pill" style="background:rgba(255,255,255,.18);border-color:rgba(255,255,255,.28)"><span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:${sc};margin-right:2px"></span>${esc(status)}</div>`);
}
if (pl) pills.push(`<div class="hero-pill">🏢 ${esc(pl)}</div>`);
if (pi) pills.push(`<div class="hero-pill">🎓 ${esc(pi)}</div>`);
if (start) pills.push(`<div class="hero-pill">📅 ${start}${end ? ' → ' + end : ''}</div>`);
document.getElementById('hero-pills').innerHTML = pills.join('');
updateBudget();
}
/* ═══════════════════════════════════════
BUDGET (auto from milestones)
═══════════════════════════════════════ */
function updateBudget() {
const rows = DATA.milestones.length ? DATA.milestones : collectMsFromDOM();
const yrs = {};
let totalVal = 0, totalPaid = 0;
rows.forEach(ms => {
const yr = ms.due ? (new Date(ms.due + 'T00:00:00')).getFullYear() : null;
if (yr && yr > 2000 && yr < 2060) {
if (!yrs[yr]) yrs[yr] = { val: 0, paid: 0 };
yrs[yr].val += (+ms.value || 0);
yrs[yr].paid += (+ms.paid || 0);
}
totalVal += (+ms.value || 0);
totalPaid += (+ms.paid || 0);
});
const pct = totalVal > 0 ? Math.round(totalPaid / totalVal * 100) : 0;
document.getElementById('hkpi-comp').textContent = pct + '%';
document.getElementById('hkpi-paid').textContent = fmtS(totalPaid);
document.getElementById('hkpi-total').textContent = fmtS(totalVal);
const remaining = totalVal - totalPaid;
document.getElementById('budget-kpis').innerHTML = `
<div class="kpi-mini"><div class="kpi-mini-val" style="color:var(--ag)">${fmtS(totalPaid)}</div><div class="kpi-mini-lbl">Paid</div></div>
<div class="kpi-mini"><div class="kpi-mini-val" style="color:var(--or)">${fmtS(remaining)}</div><div class="kpi-mini-lbl">Remaining</div></div>
<div class="kpi-mini"><div class="kpi-mini-val" style="color:var(--ab)">${pct}%</div><div class="kpi-mini-lbl">Complete</div></div>`;
const sortedYrs = Object.keys(yrs).sort();
const maxV = Math.max(...Object.values(yrs).map(y => y.val), 1);
let html = '';
if (!sortedYrs.length) {
html = '<div style="text-align:center;color:var(--l4);font-size:12px;padding:14px 0">Add milestones with due dates to see budget by year</div>';
} else {
sortedYrs.forEach(yr => {
const { val, paid } = yrs[yr];
html += `
<div style="margin-top:10px;font-size:10px;font-weight:900;color:var(--l3);text-transform:uppercase;letter-spacing:.4px">${yr}</div>
<div class="bbar-row" style="margin-top:5px"><div class="bbar-yr">Plan</div>
<div class="bbar-bg"><div class="bbar-fill" style="width:${val/maxV*100}%;background:var(--ab);opacity:.35"></div></div>
<div class="bbar-val">${fmtS(val)}</div></div>
<div class="bbar-row"><div class="bbar-yr">Paid</div>
<div class="bbar-bg"><div class="bbar-fill" style="width:${paid/maxV*100}%;background:var(--ag)"></div></div>
<div class="bbar-val">${fmtS(paid)}</div></div>`;
});
}
document.getElementById('budget-chart').innerHTML = html;
}
function collectMsFromDOM() {
const rows = [];
document.querySelectorAll('#ms-tbody tr').forEach(tr => {
const inps = tr.querySelectorAll('input');
const sel = tr.querySelector('select');
if (inps.length >= 4) rows.push({ due: inps[2]?.value, value: inps[3]?.value, paid: inps[4]?.value, status: sel?.value });
});
return rows;
}
/* ═══════════════════════════════════════
MILESTONES
═══════════════════════════════════════ */
let msN = 0;
function addMilestone(d = {}) {
msN++;
const statuses = ['Pending','In Progress','SES Created','Invoice Submitted','Paid','Delayed'];
const opts = statuses.map(s => `<option ${s === (d.status || 'Pending') ? 'selected' : ''}>${s}</option>`).join('');
const tr = document.createElement('tr');
tr.innerHTML = `
<td><input type="text" value="${esc(d.id || 'T' + msN)}" style="width:38px;font-size:11px"></td>
<td><input type="text" value="${esc(d.name || '')}" placeholder="Name / deliverable" style="min-width:130px;font-size:11px"></td>
<td><input type="date" value="${d.due || ''}" style="font-size:11px" oninput="syncMilestones();updateBudget()"></td>
<td><input type="number" value="${d.value || ''}" placeholder="0" style="width:72px;font-size:11px" oninput="syncMilestones();updateBudget()"></td>
<td><input type="number" value="${d.paid || ''}" placeholder="0" style="width:72px;font-size:11px" oninput="syncMilestones();updateBudget()"></td>
<td class="ms-ec"><select style="min-width:98px;font-size:11px" onchange="syncMilestones();updateBudget()">${opts}</select></td>
<td class="ms-vc">${sPill(d.status || 'Pending')}</td>
<td class="ms-ec"><input type="text" value="${esc(d.invoice || '')}" placeholder="URL / filename" style="min-width:88px;font-size:11px"></td>
<td class="ms-ec"><button class="del-btn" onclick="this.closest('tr').remove();syncMilestones();updateBudget()">✕</button></td>`;
document.getElementById('ms-tbody').appendChild(tr);
tr.querySelectorAll('input,select').forEach(el => el.addEventListener('change', () => { syncMilestones(); updateBudget(); }));
}
function sPill(s) {
const m = { Paid:'sp-paid','In Progress':'sp-progress','SES Created':'sp-ses','Invoice Submitted':'sp-invoice',Delayed:'sp-delayed' };
return `<span class="spill ${m[s]||'sp-pending'}">${s}</span>`;
}
function syncMilestones() {
DATA.milestones = [];
document.querySelectorAll('#ms-tbody tr').forEach(tr => {
const inps = tr.querySelectorAll('input');
const sel = tr.querySelector('select');
DATA.milestones.push({
id: inps[0]?.value, name: inps[1]?.value, due: inps[2]?.value,
value: inps[3]?.value, paid: inps[4]?.value,
status: sel?.value || 'Pending', invoice: inps[5]?.value || ''
});
const vc = tr.querySelector('.ms-vc');
if (vc) vc.innerHTML = sPill(sel?.value || 'Pending');
});
}
/* ═══════════════════════════════════════
GATES
═══════════════════════════════════════ */
function addGate(d = {}) {
const statuses = ['Not Started','Planned','Completed','Delayed','Skipped'];
const div = document.createElement('div');
div.className = 'gate-item';
div.innerHTML = `
<div class="fg" style="margin:0"><input type="text" value="${esc(d.name || '')}" placeholder="Gate / event name" style="font-size:12px"></div>
<div class="fg" style="margin:0"><input type="date" value="${d.date || ''}" style="font-size:12px"></div>
<div class="fg" style="margin:0"><select style="font-size:12px">${statuses.map(s => `<option ${s === (d.status || 'Not Started') ? 'selected' : ''}>${s}</option>`).join('')}</select></div>
<button class="del-btn" onclick="this.closest('.gate-item').remove();syncGates()">✕</button>`;
document.getElementById('gates-container').appendChild(div);
div.querySelectorAll('input,select').forEach(el => el.addEventListener('change', syncGates));
}
function syncGates() {
DATA.gates = [];
document.querySelectorAll('#gates-container .gate-item').forEach(div => {
const inps = div.querySelectorAll('input');
const sel = div.querySelector('select');
DATA.gates.push({ name: inps[0]?.value, date: inps[1]?.value, status: sel?.value });
});
}
/* ═══════════════════════════════════════
RISKS
═══════════════════════════════════════ */
function addRisk(d = {}) {
const levels = ['Low','Medium','High','Critical'];
const div = document.createElement('div');
div.className = 'risk-item';
div.innerHTML = `
<div class="fg" style="margin:0">
<div class="flabel" style="margin-bottom:4px">Level</div>
<select style="font-size:11px">${levels.map(l => `<option ${l === (d.level || 'Medium') ? 'selected' : ''}>${l}</option>`).join('')}</select>
</div>
<div class="fg" style="margin:0">
<div class="flabel" style="margin-bottom:4px">Risk Description</div>
<input type="text" value="${esc(d.desc || '')}" placeholder="Describe the risk…" style="font-size:12px">
</div>
<div class="fg" style="margin:0">
<div class="flabel" style="margin-bottom:4px">Mitigation Plan</div>
<input type="text" value="${esc(d.mitigation || '')}" placeholder="Mitigation approach…" style="font-size:12px">
</div>
<div style="display:flex;align-items:flex-end;padding-bottom:2px">
<button class="del-btn" onclick="this.closest('.risk-item').remove();syncRisks()">✕</button>
</div>`;
document.getElementById('risks-container').appendChild(div);
div.querySelectorAll('input,select').forEach(el => el.addEventListener('change', syncRisks));
}
function syncRisks() {
DATA.risks = [];
document.querySelectorAll('#risks-container .risk-item').forEach(div => {
const sel = div.querySelector('select');
const inps = div.querySelectorAll('input');
DATA.risks.push({ level: sel?.value, desc: inps[0]?.value, mitigation: inps[1]?.value });
});
}
/* ═══════════════════════════════════════
UPDATES & HIGHLIGHTS
═══════════════════════════════════════ */
const UPDATE_CATS = ['General Update','Milestone Achieved','Publication','Patent Filed','Gate Meeting','Field Activity','Personnel Change','Budget Update','Technical Breakthrough'];
function addUpdate(d = {}) {
const div = document.createElement('div');
div.className = 'update-item';
const cats = UPDATE_CATS.map(c => `<option ${c === (d.cat || 'General Update') ? 'selected' : ''}>${c}</option>`).join('');
div.innerHTML = `
<div class="update-top">
<div>
<div class="update-cat-label">📅 Date</div>
<div class="update-date-wrap">
<input type="date" class="update-date-input" value="${d.date || new Date().toISOString().slice(0,10)}" style="font-size:13px;font-weight:700">
</div>
</div>
<div>
<div class="update-cat-label">Category</div>
<select style="font-size:13px">${cats}</select>
</div>
<button class="del-btn" onclick="this.closest('.update-item').remove();syncUpdates()" style="margin-bottom:2px">✕</button>
</div>
<textarea class="update-details" rows="4" placeholder="Describe what was achieved, discovered, or updated this period…">${esc(d.details || '')}</textarea>`;
document.getElementById('updates-container').appendChild(div);
div.querySelectorAll('input,select,textarea').forEach(el => el.addEventListener('change', syncUpdates));
}
function syncUpdates() {
DATA.updates = [];
document.querySelectorAll('#updates-container .update-item').forEach(div => {
const dt = div.querySelector('input[type=date]')?.value || '';
const cat = div.querySelector('select')?.value || '';
const det = div.querySelector('textarea')?.value || '';
DATA.updates.push({ date: dt, cat, details: det });
});
}
/* ═══════════════════════════════════════
PHOTOS
═══════════════════════════════════════ */
function handlePhotos(files) {
Array.from(files).forEach(f => {
if (!f.type.startsWith('image/')) return;
const reader = new FileReader();
reader.onload = e => {
const dataUrl = e.target.result;
const caption = f.name.replace(/\.[^.]+$/, '').replace(/[-_]/g, ' ');
DATA.photos.push({ dataUrl, caption });
renderPhoto(dataUrl, caption);
try { localStorage.setItem(SK, JSON.stringify(DATA)); } catch(e) {}
};
reader.readAsDataURL(f);
});
}
function renderPhoto(dataUrl, caption) {
const div = document.createElement('div');
div.className = 'photo-item';
div.innerHTML = `
<img src="${dataUrl}" alt="${esc(caption)}" onclick="openLB('${dataUrl.replace(/\\/g,'\\\\').replace(/'/g,"\\'")}','${esc(caption)}')">
<div class="photo-cap">${esc(caption)}</div>
<button class="photo-del del-photo" onclick="event.stopPropagation();delPhoto(this)">✕</button>`;
document.getElementById('photo-grid').appendChild(div);
}
function delPhoto(btn) {
const item = btn.closest('.photo-item');
const src = item.querySelector('img').src;
DATA.photos = DATA.photos.filter(p => p.dataUrl !== src);
item.remove();
try { localStorage.setItem(SK, JSON.stringify(DATA)); } catch(e) {}
}
function openLB(src, cap) {
document.getElementById('lb-img').src = src;
document.getElementById('lb-cap').textContent = cap;
document.getElementById('lb').classList.add('open');
}
function closeLB() { document.getElementById('lb').classList.remove('open'); }
/* ═══════════════════════════════════════
COUNTER
═══════════════════════════════════════ */
function counter(fid, kid, delta) {
const el = document.getElementById(fid);
const nv = Math.max(0, (+el.value || 0) + delta);
el.value = nv;
const kv = document.getElementById(kid);
if (kv) kv.textContent = nv;
}
/* ═══════════════════════════════════════
TABS
═══════════════════════════════════════ */
function showTab(name) {
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
document.querySelectorAll('.htab').forEach(b => b.classList.remove('active'));
document.getElementById('tab-' + name)?.classList.add('active');
const map = { summary: 0, technology: 1, progress: 2 };
document.querySelectorAll('.htab')[map[name]]?.classList.add('active');
}
/* ═══════════════════════════════════════
EXCEL EXPORT
═══════════════════════════════════════ */
function exportExcel() {
saveData();
const wb = XLSX.utils.book_new();
const g = k => DATA.fields['f_' + k] || DATA.fields[k] || '';
// ── Sheet 1: Summary
const s1 = [
['PROJECT CHARTER — ' + g('project_id')],
['Generated:', new Date().toLocaleDateString()],
[],
['SECTION','FIELD','VALUE'],
['Project Identity','Project ID', g('project_id')],
['', 'Project Name', g('project_name')],
['', 'Short Name', g('short_name')],
['', 'Focus Area', g('focus_area')],
['', 'Description', g('description')],
['', 'Start Date', g('start_date')],
['', 'End Date', g('end_date')],
['', 'Total Value', g('total_value')],
['', 'TRV', g('trv')],
['', 'TPV', g('tpv')],
['', 'Overall Status', g('overall_status')],
['', 'Last Updated By', g('updated_by')],
[],
['Registry', 'Technology Program', g('tech_program')],
['', 'SAP Number', g('sap')],
['', 'Current TRL', g('trl')],
['', 'Target TRL', g('trl_target')],
['', 'Current Stage', g('stage')],
['', 'Next Gate Date', g('next_gate')],
['', 'IP Classification', g('ip_class')],
['', 'Deployment', g('deployment')],
['', 'Demo Plan', g('demo_plan')],
[],
['Aramco Team', 'Project Leader', g('aramco_pl')],
['', 'Other Members', g('aramco_members')],
['', 'Division', g('aramco_div')],
['', 'Business Sponsor', g('aramco_sponsor')],
['', 'FTE', g('aramco_fte')],
[],
['UT Austin Team', 'Principal Investigator',g('ut_pi')],
['', 'Project Leader', g('ut_pl')],
['', 'Technical Lead', g('ut_tl')],
['', 'Other Members', g('ut_members')],
['', 'Department', g('ut_dept')],
['', 'FTE', g('ut_fte')],
['', 'Business Manager', g('ut_bm')],
[],
['KPIs', 'Publications', g('pubs')],
['', 'Patents Filed', g('patents')],
['', 'Datasets / SW', g('datasets')],
];
const ws1 = XLSX.utils.aoa_to_sheet(s1);
ws1['!cols'] = [{wch:18},{wch:26},{wch:60}];
XLSX.utils.book_append_sheet(wb, ws1, 'Summary');
// ── Sheet 2: Technology
const s2 = [
['FIELD','VALUE'],
['Key Objectives', g('objectives')],
['Business Challenge', g('challenge')],
['Technology Solution', g('solution')],
['Acceptance Criteria', g('acceptance')],
['Benchmark Solution', g('benchmark')],
['Value Sources', g('value_sources')],
['Personnel Development', g('personnel_dev')],
['Assumptions', g('assumptions')],
[],['RISKS','',''],
['Level','Description','Mitigation'],
];
DATA.risks.forEach(r => s2.push([r.level, r.desc, r.mitigation]));
const ws2 = XLSX.utils.aoa_to_sheet(s2);
ws2['!cols'] = [{wch:24},{wch:50},{wch:40}];
XLSX.utils.book_append_sheet(wb, ws2, 'Technology');
// ── Sheet 3: Milestones
const ms3 = [['ID','Name / Deliverable','Due Date','Value ($)','Paid ($)','Status','Invoice']];
DATA.milestones.forEach(m => ms3.push([m.id, m.name, m.due, +m.value||0, +m.paid||0, m.status, m.invoice]));
const ws3 = XLSX.utils.aoa_to_sheet(ms3);
ws3['!cols'] = [{wch:8},{wch:32},{wch:12},{wch:12},{wch:12},{wch:18},{wch:28}];
XLSX.utils.book_append_sheet(wb, ws3, 'Milestones');
// ── Sheet 4: Budget (auto)
const byYr = {};
DATA.milestones.forEach(ms => {
const yr = ms.due ? (new Date(ms.due + 'T00:00:00')).getFullYear() : null;
if (yr && yr > 2000) {
if (!byYr[yr]) byYr[yr] = { planned: 0, paid: 0 };
byYr[yr].planned += (+ms.value || 0);
byYr[yr].paid += (+ms.paid || 0);
}
});
const s4 = [['Year','Planned ($)','Paid ($)','Remaining ($)']];
Object.keys(byYr).sort().forEach(yr => {
const {planned, paid} = byYr[yr];
s4.push([+yr, planned, paid, planned - paid]);
});
const ws4 = XLSX.utils.aoa_to_sheet(s4);
ws4['!cols'] = [{wch:8},{wch:14},{wch:14},{wch:14}];
XLSX.utils.book_append_sheet(wb, ws4, 'Budget');
// ── Sheet 5: Gates
const s5 = [['Gate / Event','Date','Status']];
DATA.gates.forEach(g2 => s5.push([g2.name, g2.date, g2.status]));
const ws5 = XLSX.utils.aoa_to_sheet(s5);
ws5['!cols'] = [{wch:28},{wch:12},{wch:14}];
XLSX.utils.book_append_sheet(wb, ws5, 'Gates');
// ── Sheet 6: Updates
const s6 = [['Date','Category','Details']];
DATA.updates.forEach(u => s6.push([u.date, u.cat, u.details]));
const ws6 = XLSX.utils.aoa_to_sheet(s6);
ws6['!cols'] = [{wch:12},{wch:22},{wch:70}];
XLSX.utils.book_append_sheet(wb, ws6, 'Updates');
// ── Sheet 7: IP
const s7 = [['Type','Detail']];
(g('pub_list') || '').split('\n').filter(Boolean).forEach(l => s7.push(['Publication', l.trim()]));
(g('patent_list')|| '').split('\n').filter(Boolean).forEach(l => s7.push(['Patent', l.trim()]));
const ws7 = XLSX.utils.aoa_to_sheet(s7);
ws7['!cols'] = [{wch:14},{wch:70}];
XLSX.utils.book_append_sheet(wb, ws7, 'IP & Publications');
const pid = g('project_id') || 'charter';
XLSX.writeFile(wb, `${pid}_Charter_${new Date().toISOString().slice(0,10)}.xlsx`);
showToast('📊 Excel exported!');
}
/* ═══════════════════════════════════════
UTILITIES
═══════════════════════════════════════ */
function fv(k) { return document.getElementById('f_' + k)?.value || ''; }
function fmtS(n){ return n >= 1e6 ? '$'+(n/1e6).toFixed(1)+'M' : n >= 1e3 ? '$'+(n/1e3).toFixed(0)+'K' : n ? '$'+n : '$0'; }
function esc(s) { return String(s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,'''); }
let toastTimer;
function showToast(msg) {
const el = document.getElementById('toast');
el.textContent = msg; el.classList.add('show');
clearTimeout(toastTimer);
toastTimer = setTimeout(() => el.classList.remove('show'), 2600);
}
document.addEventListener('keydown', e => { if (e.key === 'Escape') closeLB(); });
</script>
</body>
</html>