diff --git a/frontend/js/db.js b/frontend/js/db.js index b27b2a7..4cd1c2c 100644 --- a/frontend/js/db.js +++ b/frontend/js/db.js @@ -906,22 +906,8 @@ async function runMigrations() { console.log("[db.js] Seeded 5 serving units"); } - // Seed product_units for eggs (one-time) - try { - const puCnt = await select("SELECT COUNT(*) as cnt FROM product_units"); - if (puCnt[0].cnt === 0) { - const eggProducts = await select( - `SELECT id FROM products WHERE name_lower LIKE '%яйц%' AND name_lower LIKE '%кур%'` - ); - for (const ep of eggProducts) { - await execute( - `INSERT OR IGNORE INTO product_units (product_id, unit_id, grams_per_unit) - VALUES (?1, 1, 60)`, [ep.id] - ); - } - if (eggProducts.length > 0) console.log(`[db.js] Linked ${eggProducts.length} egg products`); - } - } catch (e) { console.error("[db.js] egg unit seeding error:", e); } + // product_units: user adds per-product unit mappings manually via UI. + // No auto-seeding — product names too varied for reliable matching. // --- Bikes table --- await execute(`CREATE TABLE IF NOT EXISTS bikes ( diff --git a/frontend/js/gps.js b/frontend/js/gps.js index 16d891b..58b2138 100644 --- a/frontend/js/gps.js +++ b/frontend/js/gps.js @@ -67,12 +67,12 @@ GPS.computeStats = function(points, weightKg, opts) { const rho = 1.225; // air density at sea level (kg/m^3) const efficiency = 0.25; // human mechanical efficiency (~25%) const Crr = opts?.surfaceCrr ?? 0.006; // rolling resistance (mixed surface default) - const mTotal = (weightKg ?? 80) + (opts?.bikeWeightKg ?? 0); + const wKg = parseFloat(weightKg) || 80; // ensure numeric (profile stores strings) + const mTotal = wKg + (parseFloat(opts?.bikeWeightKg) || 0); const isBike = (opts?.activityType ?? "bike") === "bike"; let totalKcal = 0; - // Basal metabolic rate per minute (MET=1 sitting, formula: MET * 3.5 * kg / 200) - const basalPerMin = (1.0 * 3.5 * (weightKg ?? 80)) / 200; + const basalPerMin = (1.0 * 3.5 * wKg) / 200; // Cadence tracking let totalPedalRevs = 0; @@ -111,8 +111,8 @@ GPS.computeStats = function(points, weightKg, opts) { const rawAlt = sorted[i].altitude ?? sorted[i].altitude_gps_m ?? 0; const rawAltPrev = sorted[i-1].altitude ?? sorted[i-1].altitude_gps_m ?? 0; const altDiff = rawAlt - rawAltPrev; - const gradient = d > 2 ? altDiff / d : 0; // rise/run - const sinG = gradient; // for small angles sin ~= gradient + const gradient = d > 2 ? Math.max(-0.5, Math.min(0.5, altDiff / d)) : 0; // clamp to +-50% + const sinG = gradient; const cosG = Math.sqrt(1 - sinG * sinG); // Power required (watts): gravity + rolling resistance + aerodynamic drag @@ -149,7 +149,7 @@ GPS.computeStats = function(points, weightKg, opts) { else if (speedKmh < 16) met = 12.5; // fast running else met = 15.0; // sprint - totalKcal += (dt / 60) * (met * 3.5 * (weightKg ?? 80)) / 200; + totalKcal += (dt / 60) * (met * 3.5 * wKg) / 200; } } diff --git a/frontend/js/main.js b/frontend/js/main.js index 177e1de..531719b 100644 --- a/frontend/js/main.js +++ b/frontend/js/main.js @@ -607,11 +607,14 @@ async function renderStatsCharts(container, days, foodTotals, bmr) { const closeBtn = document.createElement("div"); closeBtn.className = "chart-close-btn"; closeBtn.innerHTML = "✕"; - closeBtn.onclick = (e) => { - e.stopPropagation(); + function collapseChart() { wrap.classList.remove("chart-full"); closeBtn.remove(); - }; + window.removeEventListener("popstate", chartPopHandler); + } + closeBtn.onclick = (e) => { e.stopPropagation(); collapseChart(); history.back(); }; + const chartPopHandler = () => { collapseChart(); }; + window.addEventListener("popstate", chartPopHandler, { once: true }); wrap.appendChild(closeBtn); }; }); @@ -3218,6 +3221,14 @@ function esc(str) { window.addEventListener("popstate", (e) => { // If a modal is open, don't handle here (modal has its own handler) if (document.querySelector(".modal-overlay")) return; + // Chart fullscreen on screen (not in modal) — collapse it + const fsChart = document.querySelector(".chart-full"); + if (fsChart) { + fsChart.classList.remove("chart-full"); + const btn = fsChart.querySelector(".chart-close-btn"); + if (btn) btn.remove(); + return; + } // Stale modal/chart entries left after app restart — skip them if (e.state && (e.state.modal || e.state.chartFull)) { history.back();