192 lines
6.0 KiB
HTML
192 lines
6.0 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
<link rel="stylesheet" href="./static/mini-nord.css">
|
|
<style>
|
|
input,
|
|
select {
|
|
width: 95%;
|
|
}
|
|
</style>
|
|
<title>Cannabis Dosing Calculator</title>
|
|
</head>
|
|
|
|
<body>
|
|
<div id="app">
|
|
<h1 style="text-align:center;">Dosing Calculator</h1>
|
|
|
|
<fieldset>
|
|
<legend>Ingredients</legend>
|
|
<div class="container">
|
|
<div class="row">
|
|
<div class="col-sm-6 col-md-3">
|
|
<label for="thc-per">THC Amt (%)</label>
|
|
<input type="text" id="thc-per" value="10" />
|
|
</div>
|
|
<div class="col-sm-6 col-md-3">
|
|
<label for="cbd-per">CBD Amt (%)</label>
|
|
<input type="text" id="cbd-per" value="0" />
|
|
</div>
|
|
<div class="col-sm-6 col-md-3">
|
|
<label for="weight">Material (g)</label>
|
|
<input type="text" id="weight" value="3.5" />
|
|
</div>
|
|
<div class="col-sm-6 col-md-3">
|
|
<label for="oil-amount">Oil (cups)</label>
|
|
<input type="text" id="oil-amount" value=".25" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</fieldset>
|
|
|
|
<fieldset>
|
|
<legend>Results</legend>
|
|
<div class="container">
|
|
|
|
<div class="row">
|
|
<div class="col-sm-6 col-md-3">
|
|
<label for="dose-amount">Dose</label>
|
|
<input type="text" id="dose-amount" value="1" />
|
|
</div>
|
|
<div class="col-sm-6 col-md-3">
|
|
<label for="dose-type">Volume</label>
|
|
<select id="dose-type" value="ml">
|
|
<option value="ml">mL</option>
|
|
<option value="tsp">tsp</option>
|
|
<option value="tbsp">Tbsp</option>
|
|
<option value="cup">Cup</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-sm-6 col-md-3">
|
|
<label for="thc-dose">THC Dose (mg)</label>
|
|
<input type="text" id="thc-dose" value="" />
|
|
</div>
|
|
<div class="col-sm-6 col-md-3">
|
|
<label for="cbd-dose">CBD Dose (mg)</label>
|
|
<input type="text" id="cbd-dose" value="" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-sm-6 col-md-3">
|
|
<label for="thc-total">THC Content (mg)</label>
|
|
<input readonly type="text" id="thc-total" value="" />
|
|
</div>
|
|
<div class="col-sm-6 col-md-3">
|
|
<label for="cbd-total">CBD Content (mg)</label>
|
|
<input readonly type="text" id="cbd-total" value="" />
|
|
</div>
|
|
<div class="col-sm-6 col-md-3">
|
|
<label for="absorb-per">Absorb (%)</label>
|
|
<select id="absorb-per">
|
|
<option value="80">80%</option>
|
|
<option value="85" selected>85%</option>
|
|
<option value="90">90%</option>
|
|
<option value="95">95%</option>
|
|
<option value="100">100%</option>
|
|
</select>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</fieldset>
|
|
</div>
|
|
|
|
<script>
|
|
(function app() {
|
|
// create map of values and input ids
|
|
const inputMap = {
|
|
thcPercent: '#thc-per',
|
|
cbdPercent: '#cbd-per',
|
|
weight: '#weight',
|
|
oil: '#oil-amount',
|
|
doseAmount: '#dose-amount',
|
|
doseType: '#dose-type',
|
|
thcDose: '#thc-dose',
|
|
cbdDose: '#cbd-dose',
|
|
thcTotal: '#thc-total',
|
|
cbdTotal: '#cbd-total',
|
|
absorbPercent: '#absorb-per',
|
|
};
|
|
|
|
// cache input selectors
|
|
const inputs = Object.keys(inputMap).reduce((acc, name) => {
|
|
const val = document.querySelector(inputMap[name]);
|
|
acc[name] = val;
|
|
return acc;
|
|
}, {});
|
|
|
|
// helper functions
|
|
function round(val, dec = 2) {
|
|
const mult = Math.pow(10, dec);
|
|
return Math.round(val * mult) / mult;
|
|
}
|
|
|
|
function saveForm() {
|
|
const data = Object.entries(inputs).reduce((acc, [name, input]) => {
|
|
acc[name] = input.value;
|
|
return acc;
|
|
}, {});
|
|
|
|
localStorage.setItem('data', JSON.stringify(data));
|
|
}
|
|
|
|
function restoreForm() {
|
|
const data = JSON.parse(localStorage.getItem('data') || '{}');
|
|
Object.entries(data).forEach(([name, value]) => {
|
|
inputs[name].value = value;
|
|
});
|
|
}
|
|
|
|
// recalculate form values on change
|
|
function handleChange() {
|
|
// persist inputs to localstorage
|
|
saveForm();
|
|
|
|
// collect data
|
|
const totalTHC = inputs.weight.value * inputs.thcPercent.value / 100;
|
|
const totalCBD = inputs.weight.value * inputs.cbdPercent.value / 100;
|
|
const absorbFactor = inputs.absorbPercent.value / 100;
|
|
const dosefactor = (() => {
|
|
const type = inputs.doseType.value;
|
|
if (type === 'tsp') return 0.20289;
|
|
if (type === 'tbsp') return 0.06762;
|
|
if (type === 'cup') return 0.00422;
|
|
return 1; // mL
|
|
})();
|
|
|
|
// update total amounts
|
|
inputs.thcTotal.value = round(totalTHC * absorbFactor * 1000);
|
|
inputs.cbdTotal.value = round(totalCBD * absorbFactor * 1000);
|
|
|
|
// update dose amounts
|
|
const volumeML = inputs.oil.value * 236.58813;
|
|
inputs.thcDose.value = round(totalTHC / volumeML * absorbFactor * inputs.doseAmount.value * 1000 / dosefactor);
|
|
inputs.cbdDose.value = round(totalCBD / volumeML * absorbFactor * inputs.doseAmount.value * 1000 / dosefactor);
|
|
}
|
|
|
|
// listen for changes on all inputs
|
|
document.querySelectorAll('#app input').forEach(el => {
|
|
el.addEventListener('keyup', (ev) => handleChange(el, ev));
|
|
});
|
|
|
|
// listen for changes on all selects
|
|
document.querySelectorAll('#app select').forEach(el => {
|
|
el.addEventListener('change', (ev) => handleChange(el, ev));
|
|
});
|
|
|
|
// restore input values
|
|
restoreForm();
|
|
|
|
// update form on load
|
|
handleChange();
|
|
})();
|
|
|
|
</script>
|
|
</body>
|
|
|
|
</html> |