:root{
--bg:#0f1012; --panel:#191b1f; --panel2:#22252a; --gold:#d5aa45;
--gold2:#f0ce78; --text:#f5f5f5; --muted:#aeb3bb; --line:#343840;
--ok:#2ecc71; --warn:#f1c40f; --bad:#e67e22; --danger:#e74c3c;
}
*{box-sizing:border-box}
body{margin:0;font-family:Arial,Helvetica,sans-serif;background:linear-gradient(135deg,#090a0b,#181a1f);color:var(--text)}
.wrap{max-width:1120px;margin:auto;padding:24px}
.header{display:flex;gap:18px;align-items:center;padding:22px;border:1px solid #6f5827;background:linear-gradient(135deg,#111,#23201a);border-radius:18px;margin-bottom:18px}
.logo{width:74px;height:74px;border:2px solid var(--gold);border-radius:50%;display:grid;place-items:center;font-size:30px;font-weight:900;color:var(--gold)}
h1{margin:0;font-size:clamp(24px,4vw,40px);color:var(--gold2)}
.tag{margin-top:6px;color:#ddd}
.notice{background:#2b2517;border-left:4px solid var(--gold);padding:12px 14px;border-radius:8px;margin-bottom:18px;color:#f3e5bd}
.grid{display:grid;grid-template-columns:1fr 1fr;gap:18px}
.card{background:rgba(25,27,31,.96);border:1px solid var(--line);border-radius:18px;padding:20px;box-shadow:0 10px 30px rgba(0,0,0,.25)}
.card h2{margin:0 0 16px;color:var(--gold2);font-size:20px}
.fields{display:grid;grid-template-columns:1fr 1fr;gap:14px}
.full{grid-column:1/-1}
label{display:block;font-size:13px;color:#d4d6d9;margin-bottom:7px;font-weight:700}
input,select{width:100%;padding:13px 12px;border-radius:10px;border:1px solid #454a53;background:#101216;color:white;font-size:16px;outline:none}
input:focus,select:focus{border-color:var(--gold);box-shadow:0 0 0 3px rgba(213,170,69,.15)}
.hint{font-size:12px;color:var(--muted);margin-top:5px}
.actions{display:flex;gap:10px;flex-wrap:wrap;margin-top:18px}
button{cursor:pointer;border:none;border-radius:10px;padding:13px 18px;font-size:15px;font-weight:800}
.primary{background:linear-gradient(135deg,var(--gold),var(--gold2));color:#141414}
.secondary{background:#31353d;color:white;border:1px solid #4b505a}
.dangerBtn{background:#4b2222;color:#ffd5d5}
.results{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-top:8px}
.metric{background:#111318;border:1px solid #353941;border-radius:14px;padding:18px;text-align:center}
.metric .name{color:var(--muted);font-size:12px;text-transform:uppercase;letter-spacing:.6px}
.metric .value{font-size:30px;font-weight:900;margin-top:8px;color:var(--gold2)}
.status{margin-top:14px;padding:15px;border-radius:12px;text-align:center;font-size:19px;font-weight:900;background:#252830}
.bar{height:15px;background:#0d0e11;border-radius:20px;overflow:hidden;margin-top:12px;border:1px solid #343840}
.bar>div{height:100%;width:0;background:linear-gradient(90deg,var(--danger),var(--warn),var(--ok));transition:.4s}
.analysis{margin-top:14px;line-height:1.6;color:#d6d8dc;background:#111318;padding:14px;border-radius:12px;border:1px solid #343840}
.report{margin-top:18px;background:white;color:#111;border-radius:16px;padding:25px}
.reportHeader{text-align:center;border-bottom:2px solid #111;padding-bottom:12px;margin-bottom:15px}
.reportHeader h2{color:#111;margin:0 0 4px;font-size:24px}
.reportHeader p{margin:3px 0}
.reportGrid{display:grid;grid-template-columns:1fr 1fr;gap:9px 28px}
.reportResult{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin:18px 0}
.reportBox{border:2px solid #222;border-radius:10px;padding:13px;text-align:center}
.reportBox strong{display:block;font-size:23px;margin-top:6px}
.signatures{display:grid;grid-template-columns:1fr 1fr;text-align:center;margin-top:36px;min-height:100px}
.footer{text-align:center;color:#666;font-size:12px;border-top:1px solid #bbb;padding-top:12px}
.hidden{display:none!important}
@media(max-width:800px){.grid,.fields{grid-template-columns:1fr}.results,.reportResult{grid-template-columns:1fr}.reportGrid{grid-template-columns:1fr}.wrap{padding:12px}.header{align-items:flex-start}}
@media print{
body{background:white}.no-print,.header,.notice,.grid{display:none!important}.wrap{max-width:none;padding:0}.report{display:block!important;border-radius:0;padding:12mm}.reportHeader h2{font-size:22px}
}
2. Thông số máy xả
3. Kết quả chẩn đoán
Nhập đầy đủ thông số bắt buộc rồi bấm TÍNH KẾT QUẢ.
Kết luận: —
Kết quả được tính theo mô hình tuyến tính từ thời gian xả của bình yếu nhất.
Đây là số liệu hỗ trợ chẩn đoán; cần kết hợp kiểm tra sụt áp, nội trở, bộ sạc,
dòng tiêu thụ, lốp, tải trọng và điều kiện vận hành thực tế.
const $ = id => document.getElementById(id);
$("date").valueAsDate = new Date();
$("batteryGroup").addEventListener("change", function(){
if(this.value === "custom") return;
const [ah, minutes] = this.value.split("|").map(Number);
$("ratedAh").value = ah;
$("standardRuntime").value = minutes;
});
function num(id){ return Number($(id).value); }
function text(id){ return $(id).value.trim() || "—"; }
function clamp(v,min,max){ return Math.min(max,Math.max(min,v)); }
function assess(soh){
if(soh >= 85) return {label:"TỐT — BỘ BÌNH CÒN KHỎE", color:"#2ecc71"};
if(soh >= 70) return {label:"ĐẠT — CÒN SỬ DỤNG ĐƯỢC", color:"#f1c40f"};
if(soh >= 50) return {label:"YẾU — NÊN THEO DÕI / TƯ VẤN THAY", color:"#e67e22"};
return {label:"KÉM — CẦN KIỂM TRA SÂU HOẶC THAY THẾ", color:"#e74c3c"};
}
function calculate(){
const newRange = num("newRange");
const runtime = num("runtime");
const count = num("batteryCount");
const ratedAh = num("ratedAh");
const standardRuntime = num("standardRuntime");
const factor = num("rangeFactor") / 100;
if([newRange,runtime,count,ratedAh,standardRuntime].some(v => !Number.isFinite(v) || v <= 0)){
alert("Anh vui lòng nhập đầy đủ các thông số bắt buộc.");
return;
}
// Mô hình tuyến tính:
// SOH = thời gian xả thực tế / thời gian xả chuẩn × 100
// Capacity thực = Ah định danh × SOH
// Quãng đường = km lúc mới × SOH × hệ số hiệu chỉnh
const rawSoh = runtime / standardRuntime * 100;
const soh = clamp(rawSoh, 0, 100);
const capacity = ratedAh * soh / 100;
const range = newRange * soh / 100 * factor;
const status = assess(soh);
$("rangeOut").textContent = range.toFixed(1) + " km";
$("capacityOut").textContent = capacity.toFixed(1) + " Ah";
$("sohOut").textContent = soh.toFixed(1) + "%";
$("sohBar").style.width = soh.toFixed(1) + "%";
$("statusOut").textContent = status.label;
$("statusOut").style.color = status.color;
$("analysisOut").innerHTML =
`Công thức: SOH = ${runtime} ÷ ${standardRuntime} × 100 = ${rawSoh.toFixed(1)}%.
` +
`Hệ thống giới hạn SOH hiển thị tối đa 100%. Quãng đường đã áp dụng hệ số hiệu chỉnh ${(factor*100).toFixed(0)}%.
` +
`Khuyến nghị: Luôn lấy thời gian xả của bình yếu nhất vì đây là điểm nghẽn của cả bộ.`;
$("report").classList.remove("hidden");
$("rCustomer").textContent = text("customer");
$("rPhone").textContent = text("phone");
$("rVehicle").textContent = text("vehicle");
$("rShop").textContent = text("shop");
$("rTechnician").textContent = text("technician");
$("rDate").textContent = $("date").value ? new Date($("date").value+"T00:00:00").toLocaleDateString("vi-VN") : "—";
$("rNewRange").textContent = newRange.toFixed(1) + " km";
$("rRuntime").textContent = runtime.toFixed(1) + " phút";
$("rCount").textContent = count + " bình";
$("rRated").textContent = ratedAh.toFixed(1) + " Ah";
$("rStdRuntime").textContent = standardRuntime.toFixed(1) + " phút";
$("rFactor").textContent = (factor*100).toFixed(0) + "%";
$("rrRange").textContent = range.toFixed(1) + " km";
$("rrCapacity").textContent = capacity.toFixed(1) + " Ah";
$("rrSoh").textContent = soh.toFixed(1) + "%";
$("rConclusion").textContent = status.label;
localStorage.setItem("duyTaoBatteryTool", JSON.stringify({
customer:$("customer").value, phone:$("phone").value, vehicle:$("vehicle").value,
shop:$("shop").value, technician:$("technician").value
}));
}
function resetForm(){
if(!confirm("Xóa toàn bộ dữ liệu đang nhập?")) return;
document.querySelectorAll('input').forEach(el => {
if(el.type !== "date") el.value = "";
});
$("shop").value = "DUY TẠO CYCLES";
$("technician").value = "Phạm Duy Tạo";
$("date").valueAsDate = new Date();
$("batteryCount").value = 6;
$("batteryGroup").value = "20|100";
$("ratedAh").value = 20;
$("standardRuntime").value = 100;
$("rangeFactor").value = 100;
$("rangeOut").textContent = "0.0 km";
$("capacityOut").textContent = "0.0 Ah";
$("sohOut").textContent = "0%";
$("sohBar").style.width = "0%";
$("statusOut").textContent = "CHỜ DỮ LIỆU";
$("statusOut").style.color = "white";
$("analysisOut").innerHTML = 'Nhập đầy đủ thông số bắt buộc rồi bấm TÍNH KẾT QUẢ.';
$("report").classList.add("hidden");
}
try{
const saved = JSON.parse(localStorage.getItem("duyTaoBatteryTool") || "{}");
["customer","phone","vehicle","shop","technician"].forEach(k => {
if(saved[k]) $(k).value = saved[k];
});
}catch(e){}