Cannabis Drying Interactive Dashboard — test.1
HTML
<h1>Cannabis Drying — Scientifically Tuned Reference Dashboard (test.1)</h1>
<p class="lead">Revised thresholds based on known cannabis drying data — only truly optimal conditions marked as optimal.</p>
<div>
<label>Manual Entry — check any condition:</label><br>
Temp (°F): <input id="tIn" type="number" value="60" min="30" max="70"> RH (%): <input id="rhIn" type="number" value="55" min="30" max="70">
<button onclick="calcOne()">Calculate</button>
<div id="calcResult">Enter temperature and humidity, then click Calculate to view drying conditions.</div>
</div>
<div class="legend">
<span class="badge g">Optimal</span>
<span class="badge y">Sub-optimal / Fast Drying</span>
<span class="badge r">High Risk / Mold</span>
</div>
<div class="grid" id="heatWrap"></div>
<div class="data" id="dataWrap"></div>
CSS
body{font-family:Inter,system-ui,Arial,sans-serif;background:#fbfbfb;color:#111;margin:0;padding:24px}
h1{font-size:22px;margin:0}
p.lead{margin:4px 0 20px;color:#6b7280}
.grid, .data{overflow:auto;background:#fff;border-radius:10px;box-shadow:0 2px 8px rgba(2,6,23,0.06);padding:14px;margin-top:20px}
table{border-collapse:collapse;width:100%;font-size:12px}
th,td{border:1px solid #eee;padding:6px;text-align:center}
th{background:#f3f4f6;position:sticky;top:0}
.legend{margin:12px 0;display:flex;gap:8px;align-items:center}
.badge{padding:4px 8px;border-radius:6px;font-size:12px;font-weight:600}
.g{background:#22c55e;color:white}.y{background:#fde047;color:black}.r{background:#ef4444;color:white}
.cond-opt{background:#22c55e;color:white;border-radius:4px;padding:2px 4px;margin-right:4px;}
.cond-fast{background:#fde047;color:black;border-radius:4px;padding:2px 4px;margin-right:4px;}
.cond-sub{background:#fde047;color:black;border-radius:4px;padding:2px 4px;}
.mold-low{background:#22c55e;color:white;border-radius:4px;padding:2px 4px;}
.mold-mod{background:#fde047;color:black;border-radius:4px;padding:2px 4px;}
.mold-high{background:#ef4444;color:white;border-radius:4px;padding:2px 4px;}
input[type=number]{width:90px;padding:6px;margin:4px;border:1px solid #ddd;border-radius:6px}
button{background:#2f855a;color:white;border:0;border-radius:6px;padding:6px 10px;cursor:pointer}
#calcResult{margin-top:12px;padding:10px;background:#fff;border-radius:8px;box-shadow:0 1px 4px rgba(0,0,0,0.1);font-size:13px}
JavaScript
function es_kpa(T_c){return 0.6108*Math.exp((17.27*T_c)/(T_c+237.3));}
function dewpoint_from_ea(ea_kpa){if(ea_kpa<=0)return NaN;let ln=Math.log(ea_kpa/0.6108);return(237.3*ln)/(17.27-ln);}
function dryingDays(vpd){
if(vpd < 0.35) return 16;
if(vpd < 0.5) return 13;
if(vpd < 0.9) return 10;
if(vpd < 1.3) return 8;
if(vpd < 1.6) return 6;
return 4;
}
function moldDays(RH,vpd){
if(RH >= 90 || vpd < 0.20) return 1;
if(RH >= 85 || vpd < 0.25) return 2;
if(RH >= 80 || vpd < 0.30) return 3;
if(RH >= 75 || vpd < 0.35) return 5;
if(RH >= 70 || vpd < 0.40) return 7;
return '—';
}
function terpPreservation(vpd){
if(vpd < 0.4) return 88;
if(vpd < 0.9) return 96;
if(vpd < 1.4) return 93;
return 75;
}
function cannabinoidPreservation(vpd){
if(vpd < 0.4) return 90;
if(vpd < 1.2) return 95;
return 88;
}
function conditionReason(vpd,RH){
if(vpd < 0.4) return 'Too humid / low VPD — drying too slow, risk of mold.';
if(vpd >= 0.4 && vpd <= 1.2 && RH >= 45 && RH <= 60) return 'Balanced VPD and RH — steady, even drying.';
if(vpd > 1.2 && vpd <= 1.6) return 'Upper optimal range — faster drying, watch internal moisture balance.';
if(vpd > 1.6) return 'Too dry / high VPD — drying too fast, uneven drying and terpene loss risk.';
if(RH > 65) return 'High RH — moisture equalization slowed, potential mold.';
if(RH < 40) return 'Low RH — outer layers dry too quickly, uneven drying.';
return 'Moderate drying — acceptable but not ideal.';
}
function computeMetrics(T_f,RH){
const T_c=(T_f-32)*5/9;
const es=es_kpa(T_c);
const ea=es*(RH/100);
const vpd=Math.max(es-ea,0);
const Td_c=dewpoint_from_ea(ea);
const Td_f=(Td_c*9/5)+32;
const emc=Math.max(2,(0.12*RH)+(0.02*(T_f-40))+4).toFixed(1);
...