Files
gamboola-app/app.js
2026-08-20 22:43:58 +03:00

630 lines
26 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* ========================================================
غمبولة بيوتي — App Logic
⚠️ منطق الأعمال (الكتالوج / الأسعار / صيغة نص الأوردر / الأرقام الثابتة /
طريقة الحفظ في localStorage) منقول زي ما هو من النسخة السابقة بدون أي تغيير.
ممنوع تتغير القيم دي إلا لو طُلب صراحة.
======================================================== */
// =========================================================
// 1) الكتالوج (زي ما هو بالظبط — الأسماء والأحجام والأسعار)
// =========================================================
// ⚠️ سعر البخاخ هنا 365 ج (زي الكتالوج الرسمي). لو الرقم الصح فعليًا 356،
// غيّري الرقم في السطر اللي فيه "بخاخ علاج التساقط" تحت.
const CATALOG = [
{ cat: "روتين العناية بالشعر", items: [
{ name: "بخاخ علاج التساقط", sizes: [["125 مل", 365]] },
{ name: "تركيبة روبانزل", sizes: [["125 مل", 225]] },
{ name: "شامبو", sizes: [["125 مل", 110], ["200 مل", 190]] },
{ name: "بلسم", sizes: [["125 مل", 110], ["200 مل", 190]] },
{ name: "حمام كريم", sizes: [["200 مل", 175], ["350 مل", 275]] },
{ name: "سيرم", sizes: [["125 مل", 275]] },
{ name: "كريم تسريح", sizes: [["125 مل", 165], ["250 مل", 265]] },
{ name: "الروتين الكامل (باقة)", sizes: [["كل المنتجات", 1425]] },
]},
{ cat: "زيوت ومكملات فردية", items: [
{ name: "خلطة مساج", sizes: [["-", 265]] },
{ name: "جوز هند", sizes: [["125 مل", 205]] },
{ name: "نبتة شيا", sizes: [["50 مل", 140]] },
{ name: "أرجان بجوجوبا", sizes: [["-", 265]] },
{ name: "جوجوبا", sizes: [["125 مل", 185]] },
{ name: "هير سول (ماء ورد)", sizes: [["200 مل", 100]] },
]},
{ cat: "العناية بالبشرة والجسم", items: [
{ name: "كريم تفتيح الجسم", sizes: [["300 مل", 350]] },
{ name: "الشمعة المنورة", sizes: [["-", 285]] },
]},
{ cat: "الرموش والحواجب", items: [
{ name: "ماسكرا الرموش والحواجب", sizes: [["10 مل", 265]] },
]},
{ cat: "شامبو القشرة", items: [
{ name: "شامبو القشرة", sizes: [["-", 275]] },
]},
];
let customRows = [];
let customIdCounter = 0;
let orderLog = [];
// =========================================================
// 2) Navigation (Bottom Nav + Screens)
// =========================================================
const SCREEN_TITLES = {
home: 'إدارة الأوردرات',
products: 'اختيار المنتجات',
order: 'مراجعة الأوردر',
customer: 'بيانات العميل',
preview: 'إرسال الأوردر',
earnings: 'الأرباح',
};
function showScreen(name){
document.querySelectorAll('.screen').forEach(s => s.classList.toggle('active', s.dataset.screen === name));
document.querySelectorAll('.nav-btn[data-screen-btn]').forEach(b => {
b.classList.toggle('active', b.dataset.screenBtn === name);
});
document.getElementById('headerSubtitle').textContent = SCREEN_TITLES[name] || '';
document.getElementById('app').scrollTop = 0;
window.scrollTo(0,0);
updateStickyBar();
if(name === 'earnings') renderCounter();
// ملحوظة: render() بيتنادى من كل الـ inputs عند أي تغيير، وبيحدّث
// الداشبورد ونص المعاينة أوتوماتيك، فمش محتاجين ننادي حاجة تانية هنا
// (تجنّبًا لأي استدعاء متبادل بين الدوال).
render();
}
function initNav(){
document.querySelectorAll('[data-screen-btn]').forEach(btn => {
btn.addEventListener('click', () => showScreen(btn.dataset.screenBtn));
});
document.querySelectorAll('[data-goto]').forEach(btn => {
btn.addEventListener('click', () => showScreen(btn.dataset.goto));
});
}
// =========================================================
// 3) بناء قايمة المنتجات (Accordion + Cards)
// =========================================================
function buildCategories(){
const container = document.getElementById('categoriesContainer');
container.innerHTML = '';
CATALOG.forEach((catObj, ci) => {
const catDiv = document.createElement('div');
catDiv.className = 'cat' + (ci === 0 ? ' open' : '');
catDiv.innerHTML = `
<div class="cat-header" data-toggle-cat="${ci}">
<span class="cat-title">${catObj.cat} <span class="cat-count">${catObj.items.length}</span></span>
<span class="cat-chevron">▾</span>
</div>
<div class="cat-body"><div class="cat-body-inner" id="catBody${ci}"></div></div>
`;
container.appendChild(catDiv);
const body = catDiv.querySelector(`#catBody${ci}`);
catObj.items.forEach((item, ii) => {
const row = document.createElement('div');
row.className = 'prod-card';
row.id = `prodCard_${ci}_${ii}`;
const sizeOptions = item.sizes.map((s, si) => `<option value="${si}">${s[0]} — ${s[1]} ج</option>`).join('');
row.innerHTML = `
<div class="prod-top">
<div class="prod-name">${item.name}<span class="selected-tick">✔</span></div>
<div class="prod-line-total" id="lt_${ci}_${ii}">0 ج</div>
</div>
<div class="prod-controls">
<select class="size-select" id="size_${ci}_${ii}" data-render-on-change>
${sizeOptions}
</select>
<div class="qty-stepper">
<button type="button" data-qty-step="-1" data-qty-target="qty_${ci}_${ii}">−</button>
<span class="qty-value" id="qty_${ci}_${ii}">0</span>
<button type="button" data-qty-step="1" data-qty-target="qty_${ci}_${ii}">+</button>
</div>
</div>
`;
body.appendChild(row);
});
});
container.querySelectorAll('[data-toggle-cat]').forEach(header => {
header.addEventListener('click', () => {
header.closest('.cat').classList.toggle('open');
});
});
container.querySelectorAll('[data-render-on-change]').forEach(sel => {
sel.addEventListener('change', render);
});
container.querySelectorAll('[data-qty-step]').forEach(btn => {
btn.addEventListener('click', () => {
const targetId = btn.dataset.qtyTarget;
const el = document.getElementById(targetId);
const step = Number(btn.dataset.qtyStep);
let current = Number(el.textContent) || 0;
current = Math.max(0, current + step);
el.textContent = current;
render();
});
});
}
function getQty(ci, ii){
const el = document.getElementById(`qty_${ci}_${ii}`);
return el ? (Number(el.textContent) || 0) : 0;
}
// =========================================================
// 4) منتج مخصص
// =========================================================
function addCustomRow(){
const id = customIdCounter++;
customRows.push({ id, name: '', size: '', price: 0, qty: 0 });
renderCustomRows();
}
function removeCustomRow(id){
customRows = customRows.filter(r => r.id !== id);
renderCustomRows();
render();
}
function renderCustomRows(){
const el = document.getElementById('customList');
if(customRows.length === 0){
el.innerHTML = '<div class="empty-state" style="padding:14px 0;"><span class="empty-text">مفيش منتجات مخصصة مضافة</span></div>';
return;
}
el.innerHTML = customRows.map(r => `
<div class="custom-item">
<button type="button" class="rm-x" data-rm-custom="${r.id}">✕</button>
<input type="text" placeholder="اسم المنتج" value="${r.name}" data-custom-field="name" data-custom-id="${r.id}">
<div class="custom-grid">
<input type="text" placeholder="الحجم" value="${r.size}" data-custom-field="size" data-custom-id="${r.id}">
<input type="number" placeholder="سعر" value="${r.price || ''}" min="0" data-custom-field="price" data-custom-id="${r.id}">
</div>
<div class="qty-stepper" style="align-self:flex-start;">
<button type="button" data-custom-qty-step="-1" data-custom-qty-id="${r.id}">−</button>
<span class="qty-value" data-custom-qty-value="${r.id}">${r.qty}</span>
<button type="button" data-custom-qty-step="1" data-custom-qty-id="${r.id}">+</button>
</div>
</div>
`).join('');
el.querySelectorAll('[data-rm-custom]').forEach(b => {
b.addEventListener('click', () => removeCustomRow(Number(b.dataset.rmCustom)));
});
el.querySelectorAll('[data-custom-field]').forEach(inp => {
inp.addEventListener('input', () => {
const id = Number(inp.dataset.customId);
const field = inp.dataset.customField;
updateCustom(id, field, inp.value);
});
});
el.querySelectorAll('[data-custom-qty-step]').forEach(b => {
b.addEventListener('click', () => {
const id = Number(b.dataset.customQtyId);
const row = customRows.find(r => r.id === id);
if(!row) return;
row.qty = Math.max(0, (row.qty || 0) + Number(b.dataset.customQtyStep));
const span = el.querySelector(`[data-custom-qty-value="${id}"]`);
if(span) span.textContent = row.qty;
render();
});
});
}
function updateCustom(id, field, value){
const row = customRows.find(r => r.id === id);
if(!row) return;
row[field] = (field === 'price') ? Number(value) : value;
render();
}
// =========================================================
// 5) localStorage — نفس المفاتيح القديمة بالظبط
// =========================================================
function saveManagerPhone(){ try { localStorage.setItem('gamboola_manager_phone', document.getElementById('managerPhone').value); } catch(e){} }
function loadManagerPhone(){ try { const v = localStorage.getItem('gamboola_manager_phone'); if(v) document.getElementById('managerPhone').value = v; } catch(e){} }
function saveAgentName(){ try { localStorage.setItem('gamboola_agent_name', document.getElementById('agentName').value); } catch(e){} }
function loadAgentName(){ try { const v = localStorage.getItem('gamboola_agent_name'); if(v) document.getElementById('agentName').value = v; } catch(e){} }
function saveRate(){ try { localStorage.setItem('gamboola_rate', document.getElementById('rate').value); } catch(e){} }
function loadRate(){ try { const v = localStorage.getItem('gamboola_rate'); if(v) document.getElementById('rate').value = v; } catch(e){} }
function saveNotifyPhone(){ try { localStorage.setItem('gamboola_notify_phone', document.getElementById('notifyPhone').value); } catch(e){} }
function loadNotifyPhone(){ try { const v = localStorage.getItem('gamboola_notify_phone'); if(v) document.getElementById('notifyPhone').value = v; } catch(e){} }
function loadOrderLog(){
try { const v = localStorage.getItem('gamboola_order_log'); orderLog = v ? JSON.parse(v) : []; }
catch(e){ orderLog = []; }
}
function saveOrderLog(){ try { localStorage.setItem('gamboola_order_log', JSON.stringify(orderLog)); } catch(e){} }
function todayStr(){
const d = new Date();
return `${d.getDate()}/${d.getMonth()+1}/${d.getFullYear()}`;
}
// =========================================================
// 6) الحساب الرئيسي + بناء نص الأوردر (منطق الأعمال الأصلي)
// =========================================================
function collectItems(){
let items = [];
let productsTotal = 0;
CATALOG.forEach((catObj, ci) => {
catObj.items.forEach((item, ii) => {
const sizeSel = document.getElementById(`size_${ci}_${ii}`);
const qty = getQty(ci, ii);
const sizeIdx = Number(sizeSel.value);
const [sizeLabel, price] = item.sizes[sizeIdx];
const lineTotal = qty * price;
const ltEl = document.getElementById(`lt_${ci}_${ii}`);
if(ltEl) ltEl.textContent = lineTotal + ' ج';
const card = document.getElementById(`prodCard_${ci}_${ii}`);
if(card) card.classList.toggle('selected', qty > 0);
if(qty > 0){
items.push({ name: item.name, size: sizeLabel, qty, price, lineTotal });
productsTotal += lineTotal;
}
});
});
customRows.forEach(r => {
const lineTotal = (r.qty || 0) * (r.price || 0);
if(r.qty > 0 && r.name){
items.push({ name: r.name, size: r.size || '', qty: r.qty, price: r.price, lineTotal });
productsTotal += lineTotal;
}
});
return { items, productsTotal };
}
function render(){
const { items, productsTotal } = collectItems();
const shipping = Number(document.getElementById('shipping').value) || 0;
const grandTotal = productsTotal + shipping;
document.getElementById('productsTotal').textContent = productsTotal + ' ج';
document.getElementById('grandTotal').textContent = grandTotal + ' ج';
// ملخص شاشة "الأوردر"
const summaryEl = document.getElementById('orderSummaryList');
if(items.length === 0){
summaryEl.innerHTML = '<div class="empty-state"><span class="empty-icon">🛒</span><span class="empty-text">مفيش منتجات متضافة للطلب لسه</span></div>';
} else {
summaryEl.innerHTML = items.map(it => `
<div class="summary-row">
<div>
<div class="sr-name">${it.name}</div>
<div class="sr-meta">${it.size ? it.size + ' • ' : ''}الكمية ${it.qty}</div>
</div>
<div class="sr-price">${it.lineTotal} ج</div>
</div>
`).join('');
}
const totalQty = items.reduce((s,it)=>s+it.qty,0);
updateStickyBar(totalQty, grandTotal);
updateNavBadge(totalQty);
// بنحدّث الداشبورد ونص الأوردر النهائي دايمًا (حسابات رخيصة)،
// من غير أي استدعاء متبادل بين الدوال عشان نتجنب أي تكرار لا نهائي.
renderHome(items, productsTotal);
buildOutputText(items, shipping, grandTotal);
return { items, productsTotal, shipping, grandTotal };
}
// ⚠️ صيغة نص الأوردر — الأرقام الثابتة (1500 / 000000) والفواصل والترتيب
// منقولين حرفيًا زي ما اتأكد عليهم. ممنوع تتغير إلا بطلب صريح.
function buildOutputText(items, shipping, grandTotal){
let productsLine = '';
if(items.length === 0){
productsLine = '(لسه مفيش منتجات مختارة)';
} else {
const parts = items.map(it => {
const qtyPart = it.qty > 1 ? `×${it.qty}` : '';
const sizePart = it.size ? ` ${it.size}` : '';
return `${it.name}${sizePart}${qtyPart} ${it.lineTotal}`;
});
parts.push(`شحن ${shipping}`);
productsLine = parts.join('+');
}
const name = document.getElementById('custName').value || '';
const phone1 = document.getElementById('phone1').value || '';
const phone2 = document.getElementById('phone2').value || '';
const address = document.getElementById('address').value || '';
const agentName = document.getElementById('agentName').value || '';
const date = document.getElementById('orderDate').value || todayStr();
const text =
`الاوردر /${productsLine}
1500
000000
${grandTotal}
000000
${name}
${phone1}
${phone2}
العنوان بالتفصيل / ${address}
${agentName} ${date}`;
document.getElementById('outputText').value = text;
}
// =========================================================
// 7) نسخ + واتساب (نفس المنطق الأصلي، مع fallback للموبايل)
// =========================================================
function copyText(){
const ta = document.getElementById('outputText');
const btn = document.getElementById('copyBtn');
const orig = btn.innerHTML;
const done = () => {
btn.innerHTML = 'تم النسخ ✓';
btn.style.background = 'linear-gradient(135deg, #4CAF7D, #3d9469)';
showToast('تم نسخ النص ✓');
setTimeout(() => { btn.innerHTML = orig; btn.style.background = ''; }, 1500);
};
if(navigator.clipboard && navigator.clipboard.writeText){
navigator.clipboard.writeText(ta.value).then(done).catch(() => fallbackCopy(ta, done));
} else {
fallbackCopy(ta, done);
}
}
function fallbackCopy(ta, done){
ta.removeAttribute('readonly');
ta.focus();
ta.select();
try { document.execCommand('copy'); done(); } catch(e){ showToast('انسخي النص يدويًا بالضغط المطول عليه'); }
ta.setAttribute('readonly', true);
}
function sendWhatsapp(){
const text = encodeURIComponent(document.getElementById('outputText').value);
const phone = document.getElementById('managerPhone').value.replace(/[^0-9]/g, '');
const url = phone ? `https://wa.me/${phone}?text=${text}` : `https://wa.me/?text=${text}`;
window.open(url, '_blank');
}
// =========================================================
// 8) عداد الأوردرات والأرباح (نفس المنطق الأصلي)
// =========================================================
function registerOrder(){
const name = document.getElementById('custName').value || '(بدون اسم)';
const grand = document.getElementById('grandTotal').textContent;
const date = document.getElementById('orderDate').value || todayStr();
orderLog.push({ name, grand, date, ts: Date.now() });
saveOrderLog();
renderCounter();
renderHome();
showToast('تم تسجيل الأوردر ✅');
}
function undoLastOrder(){
if(orderLog.length === 0) return;
if(!confirm('متأكدة تمسحي آخر أوردر مسجل؟')) return;
orderLog.pop();
saveOrderLog();
renderCounter();
renderHome();
}
function clearLog(){
if(!confirm('متأكدة تمسحي كل السجل والعدّاد يرجع صفر؟')) return;
orderLog = [];
saveOrderLog();
renderCounter();
renderHome();
}
function renderCounter(){
const rate = Number(document.getElementById('rate').value) || 0;
const count = orderLog.length;
const earned = count * rate;
document.getElementById('earnCount').textContent = count;
document.getElementById('earnRateDisplay').textContent = rate + ' ج';
document.getElementById('earnTotal').textContent = earned + ' ج';
const logEl = document.getElementById('orderLog');
if(count === 0){
logEl.innerHTML = '<div class="empty-state"><span class="empty-icon">🗒️</span><span class="empty-text">مفيش أوردرات مسجلة لسه</span></div>';
} else {
const last15 = orderLog.slice(-15).reverse();
logEl.innerHTML = last15.map((o, i) => `
<div class="tl-item">
<div class="tl-price">${o.grand}</div>
<div class="tl-title">#${count - i} — ${o.name}</div>
<div class="tl-meta">${o.date}</div>
</div>
`).join('');
}
}
function notifyWhatsapp(){
const rate = Number(document.getElementById('rate').value) || 0;
const count = orderLog.length;
const earned = count * rate;
const msg = `تحديث أوردرات غمبولة 📊\nعدد الأوردرات: ${count}\nالمستحق: ${earned} جنيه`;
const text = encodeURIComponent(msg);
const phone = document.getElementById('notifyPhone').value.replace(/[^0-9]/g, '');
const url = phone ? `https://wa.me/${phone}?text=${text}` : `https://wa.me/?text=${text}`;
window.open(url, '_blank');
}
// =========================================================
// 9) واجهة إضافية: Home dashboard / Sticky bar / Badge / Toast
// =========================================================
// items/productsTotal اختياريين — لو معدّياش، بتتحسب من تاني (بدون ما تنادي render()).
function renderHome(items, productsTotal){
if(items === undefined){
const collected = collectItems();
items = collected.items;
productsTotal = collected.productsTotal;
}
const rate = Number(document.getElementById('rate').value) || 0;
document.getElementById('homeOrderCount').textContent = orderLog.length;
document.getElementById('homeEarned').textContent = (orderLog.length * rate) + ' ج';
const shipping = Number(document.getElementById('shipping').value) || 0;
document.getElementById('homeCartItems').textContent = items.reduce((s,it)=>s+it.qty,0);
document.getElementById('homeCartTotal').textContent = (items.length ? productsTotal + shipping : 0) + ' ج';
}
// totalQty/grandTotal اختياريين — لو معدّياش، بيتحسبوا من تاني.
function updateStickyBar(totalQty, grandTotal){
const bar = document.getElementById('stickyOrderBar');
const productsScreenActive = document.querySelector('.screen[data-screen="products"]').classList.contains('active');
if(!productsScreenActive){ bar.classList.remove('show'); return; }
if(totalQty === undefined){
const collected = collectItems();
totalQty = collected.items.reduce((s,it)=>s+it.qty,0);
const shipping = Number(document.getElementById('shipping').value) || 0;
grandTotal = collected.productsTotal + shipping;
}
if(!totalQty){
bar.classList.remove('show');
return;
}
document.getElementById('stickyText').textContent = `${totalQty} منتجات — ${grandTotal} ج`;
bar.classList.add('show');
}
function updateNavBadge(count){
const badge = document.getElementById('productsBadge');
if(count > 0){
badge.textContent = count;
badge.classList.remove('hidden');
} else {
badge.classList.add('hidden');
}
}
let toastTimer = null;
function showToast(msg){
const toast = document.getElementById('toast');
toast.textContent = msg;
toast.classList.add('show');
clearTimeout(toastTimer);
toastTimer = setTimeout(() => toast.classList.remove('show'), 1800);
}
function resetCurrentOrder(){
CATALOG.forEach((catObj, ci) => {
catObj.items.forEach((item, ii) => {
const qtyEl = document.getElementById(`qty_${ci}_${ii}`);
if(qtyEl) qtyEl.textContent = '0';
const sizeEl = document.getElementById(`size_${ci}_${ii}`);
if(sizeEl) sizeEl.value = '0';
});
});
customRows = [];
renderCustomRows();
document.getElementById('custName').value = '';
document.getElementById('phone1').value = '';
document.getElementById('phone2').value = '';
document.getElementById('address').value = '';
document.getElementById('orderDate').value = todayStr();
render();
renderHome();
}
// =========================================================
// 10) PWA: تسجيل Service Worker + زرار التثبيت
// =========================================================
function registerServiceWorker(){
if('serviceWorker' in navigator){
window.addEventListener('load', () => {
navigator.serviceWorker.register('sw.js').catch(() => {});
});
}
}
let deferredInstallPrompt = null;
function initInstallPrompt(){
const installBtn = document.getElementById('installBtn');
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
deferredInstallPrompt = e;
installBtn.classList.add('show');
});
installBtn.addEventListener('click', async () => {
if(!deferredInstallPrompt) return;
deferredInstallPrompt.prompt();
await deferredInstallPrompt.userChoice;
deferredInstallPrompt = null;
installBtn.classList.remove('show');
});
window.addEventListener('appinstalled', () => {
installBtn.classList.remove('show');
showToast('تم تثبيت التطبيق 🎉');
});
// iOS Safari: مفيش beforeinstallprompt، فبنعرض تلميح بسيط بدل ما نضايق المستخدم
const isIos = /iphone|ipad|ipod/i.test(navigator.userAgent);
const isStandalone = window.navigator.standalone === true || window.matchMedia('(display-mode: standalone)').matches;
const dismissed = localStorage.getItem('gamboola_ios_hint_dismissed') === '1';
if(isIos && !isStandalone && !dismissed){
document.getElementById('iosHint').classList.remove('hidden');
}
document.getElementById('closeIosHint').addEventListener('click', () => {
document.getElementById('iosHint').classList.add('hidden');
try { localStorage.setItem('gamboola_ios_hint_dismissed', '1'); } catch(e){}
});
}
// =========================================================
// 11) ربط الأحداث العامة + التشغيل الأول
// =========================================================
function initFieldListeners(){
['shipping'].forEach(id => document.getElementById(id).addEventListener('input', render));
['custName','phone1','phone2','address'].forEach(id => document.getElementById(id).addEventListener('input', render));
document.getElementById('agentName').addEventListener('input', () => { saveAgentName(); render(); });
document.getElementById('orderDate').addEventListener('input', render);
document.getElementById('managerPhone').addEventListener('input', saveManagerPhone);
document.getElementById('rate').addEventListener('input', () => { saveRate(); renderCounter(); renderHome(); });
document.getElementById('notifyPhone').addEventListener('input', saveNotifyPhone);
document.getElementById('addCustomBtn').addEventListener('click', addCustomRow);
document.getElementById('copyBtn').addEventListener('click', copyText);
document.getElementById('sendWaBtn').addEventListener('click', sendWhatsapp);
document.getElementById('registerOrderBtn').addEventListener('click', registerOrder);
document.getElementById('undoOrderBtn').addEventListener('click', undoLastOrder);
document.getElementById('clearLogBtn').addEventListener('click', clearLog);
document.getElementById('notifyWaBtn').addEventListener('click', notifyWhatsapp);
document.getElementById('startNewOrderBtn').addEventListener('click', () => {
resetCurrentOrder();
showScreen('products');
});
document.getElementById('goToCustomerBtn').addEventListener('click', () => showScreen('customer'));
document.getElementById('goToPreviewBtn').addEventListener('click', () => showScreen('preview'));
}
function init(){
document.getElementById('orderDate').value = todayStr();
buildCategories();
renderCustomRows();
loadManagerPhone();
loadAgentName();
loadRate();
loadNotifyPhone();
loadOrderLog();
initFieldListeners();
initNav();
registerServiceWorker();
initInstallPrompt();
render();
renderCounter();
renderHome();
showScreen('home');
}
document.addEventListener('DOMContentLoaded', init);