" +
"" +
'";
// language switcher dropdown toggle (bind document listener once)
var langBtn = document.getElementById("langBtn");
var langMenuEl = document.getElementById("langMenu");
langBtn.addEventListener("click", function (e) {
e.stopPropagation();
langMenuEl.classList.toggle("open");
});
if (!window._langMenuBound) {
window._langMenuBound = true;
document.addEventListener("click", function (e) {
var langEl = e.target && e.target.closest ? e.target.closest("[data-lang]") : null;
if (langEl) {
e.preventDefault();
window.switchLanguage(langEl.getAttribute("data-lang"));
return;
}
var btnEl = document.getElementById("langBtn");
var menuEl = document.getElementById("langMenu");
if (menuEl && !menuEl.contains(e.target) && btnEl && !btnEl.contains(e.target)) {
menuEl.classList.remove("open");
}
});
}
}
// In-place language switch used by the Blogger single-file version
window.switchLanguage = function (lang) {
if (!window.I18N[lang]) return;
window.LANG = lang;
document.documentElement.setAttribute("lang", lang);
var i18n = window.I18N[lang];
document.documentElement.setAttribute("dir", i18n.dir);
if (i18n.dir === "rtl") document.documentElement.classList.add("rtl");
else document.documentElement.classList.remove("rtl");
if (window.BLOGGER_MODE) {
buildStaticPage();
appEl = document.getElementById("app");
renderApp();
renderStep();
}
};
// Replace elements with data-i18n attribute using translations
function applyTranslatedStatics() {
var els = document.querySelectorAll("[data-i18n]");
for (var i = 0; i < els.length; i++) {
var el = els[i];
var key = el.getAttribute("data-i18n");
el.textContent = T(key);
}
}
// ---------- Layout ----------
function renderApp() {
appEl.innerHTML =
'
' +
'' +
'' +
'
' +
'' +
'' +
'
' +
'
';
document.getElementById("btnBack").addEventListener("click", function () {
if (currentStep > 1) { currentStep--; renderStep(); }
});
document.getElementById("btnNext").addEventListener("click", function () {
if (currentStep < 5) {
currentStep++;
} else {
document.getElementById("btnPrint") && scrollToRecipe();
}
renderStep();
});
}
function renderStepsNav() {
var el = document.getElementById("wizardSteps");
if (!el) return;
var html = "";
for (var i = 1; i <= 5; i++) {
var done = i < currentStep ? " done" : "";
var active = i === currentStep ? " active" : "";
html += '
";
return html;
}
function bindLye() {
var segs = document.querySelectorAll("[data-lye]");
for (var i = 0; i < segs.length; i++) {
segs[i].addEventListener("click", function () {
state.lyeType = this.getAttribute("data-lye");
renderStep();
});
}
var us = document.querySelectorAll("[data-unit]");
for (var j = 0; j < us.length; j++) {
us[j].addEventListener("click", function () {
state.weightUnit = this.getAttribute("data-unit");
renderStep();
});
}
var kp = document.getElementById("kohPct");
if (kp) kp.addEventListener("input", function () {
state.kohPercent = parseInt(kp.value, 10);
document.getElementById("kohPctVal").textContent = state.kohPercent;
});
var np = document.getElementById("naohPurity");
if (np) np.addEventListener("input", function () { state.naohPurity = parseFloat(np.value) || 100; });
var kpu = document.getElementById("kohPurity");
if (kpu) kpu.addEventListener("input", function () { state.kohPurity = parseFloat(kpu.value) || 90; });
var sf = document.getElementById("superfat");
if (sf) sf.addEventListener("input", function () { state.superfat = parseFloat(sf.value) || 0; });
}
// ---------- Step 4: Water ----------
function stepWater() {
var html = '
' + esc(T("waterTitle")) + "
";
html += '
';
html += '
" +
'
' +
'" +
'" +
'" +
"
";
if (state.waterMethod === 1) {
html += '
" +
'%
';
} else if (state.waterMethod === 2) {
html += '
" +
' : 1
';
} else {
html += '
" +
'%
';
}
html += '
" +
'%' +
'
' + esc(T("waterDiscountHint")) + "
";
html += "
";
return html;
}
function bindWater() {
var segs = document.querySelectorAll("[data-wm]");
for (var i = 0; i < segs.length; i++) {
segs[i].addEventListener("click", function () {
state.waterMethod = parseInt(this.getAttribute("data-wm"), 10);
renderStep();
});
}
var wp = document.getElementById("waterOilPct");
if (wp) wp.addEventListener("input", function () { state.waterOilPercent = parseFloat(wp.value) || 38; });
var wr = document.getElementById("waterRatio");
if (wr) wr.addEventListener("input", function () { state.waterRatio = parseFloat(wr.value) || 2.2; });
var lc = document.getElementById("lyeConc");
if (lc) lc.addEventListener("input", function () { state.lyeConcentration = parseFloat(lc.value) || 33; });
var wd = document.getElementById("waterDisc");
if (wd) wd.addEventListener("input", function () { state.waterDiscount = parseFloat(wd.value) || 0; });
}
// ---------- Step 5: Recipe ----------
function stepRecipe() {
var r = C.calculate(state);
var html = '
";
}
function fattyAcidBars(r) {
var fa = r.fa;
var order = ["lauric", "myristic", "palmitic", "stearic", "oleic", "linoleic", "linolenic", "ricinoleic"];
var html = "";
order.forEach(function (k) {
html += '
";
}
function additiveRows(r) {
var a = r.additives;
var f = function (g) { return C.fmt(C.fromGrams(g, state.weightUnit)); };
var u = unitLabel();
return '
";
}
function scrollToRecipe() {
var el = document.querySelector(".recipe-head");
if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
}
// Global click delegation for soap type buttons rendered inside step
document.addEventListener("click", function (e) {
var t = e.target.closest ? e.target.closest("[data-soaptype]") : null;
if (t) {
state.soapType = t.getAttribute("data-soaptype");
applySoapDefaults();
renderStep();
}
});
// Rerender binds after each step render
var origRenderStep = renderStep;
renderStep = function () {
origRenderStep();
if (currentStep === 1) bindSoapType();
if (currentStep === 3) bindLye();
if (currentStep === 4) bindWater();
};
document.addEventListener("DOMContentLoaded", init);
if (document.readyState !== "loading") init();
})();