/* sizing.js — the machine estimator (/gepigeny, /en/sizing). * * THE RULE lives in estimate() and guestMib() below, and nowhere else on the site. Every number it uses * comes from /assets/sizing-data.json (built by scripts/build_sizing_web.py from the 2026-10-13 N100 * measurement); this file holds none of its own — scripts/test_build_sizing_web.py checks that. * * typical = platform + sum of the selected apps' typical figures * peak = platform + sum of their peaks (all at peak at once: pessimistic on purpose) * per box: guest memory by the installer's rule, then * well peak * headroom <= guest * tight peak <= guest < peak * headroom * no guest < peak * * The page text is not here: each page carries its own strings (#sizer-strings), so this file is the * same for both languages. */ (function (root) { 'use strict'; function guestMib(data, gb) { var r = data.guest_rule; var total = gb * 1024 - r.memtotal_shortfall_mib; var mem = total - r.reserve_mib; if (mem > total - r.max_gap_mib) { mem = total - r.max_gap_mib; } if (mem < r.min_mib) { mem = r.min_mib; } if (mem > total - r.ceiling_gap_mib) { mem = total - r.ceiling_gap_mib; } return mem; } function estimate(data, slugs) { var bySlug = {}; data.apps.forEach(function (a) { bySlug[a.slug] = a; }); var typical = data.platform_mb, peak = data.platform_mb, busy = [], unmeasured = []; slugs.forEach(function (s) { var a = bySlug[s]; if (!a || a.builtin) { return; } typical += a.typical_mb; peak += a.peak_mb; if (a.cpu_class === 'medium' || a.cpu_class === 'heavy-in-bursts') { busy.push(a); } if (!a.measured) { unmeasured.push(a); } }); var boxes = data.boxes_gb.map(function (gb) { var guest = guestMib(data, gb); var verdict = peak * data.headroom <= guest ? 'well' : (peak <= guest ? 'tight' : 'no'); return { gb: gb, guest_mb: guest, verdict: verdict }; }); var heavy = busy.filter(function (a) { return a.cpu_class === 'heavy-in-bursts'; }); return { typical_mb: typical, peak_mb: peak, boxes: boxes, busy: busy, heavy: heavy, unmeasured: unmeasured }; } root.FelhomSizing = { estimate: estimate, guestMib: guestMib }; /* ---------------------------------------------------------------- the page (drawing only) */ if (typeof document === 'undefined') { return; } function el(tag, cls, txt) { var e = document.createElement(tag); if (cls) { e.className = cls; } if (txt !== undefined) { e.textContent = txt; } return e; } function icon(name) { var ns = 'http://www.w3.org/2000/svg'; var s = document.createElementNS(ns, 'svg'); s.setAttribute('class', 'ico'); s.setAttribute('aria-hidden', 'true'); var u = document.createElementNS(ns, 'use'); u.setAttribute('href', '/assets/icons.svg?v=1#' + name); s.appendChild(u); return s; } function start() { var host = document.getElementById('sizer'); var strEl = document.getElementById('sizer-strings'); if (!host || !strEl) { return; } var T = JSON.parse(strEl.textContent); var lang = document.documentElement.lang === 'en' ? 'en' : 'hu'; var gbText = function (mb) { var v = (mb / 1024).toFixed(1); return (lang === 'hu' ? v.replace('.', ',') : v) + ' GB'; }; var appsBox = document.getElementById('sizer-apps'); var result = document.getElementById('sizer-result'); fetch(host.getAttribute('data-src')).then(function (r) { return r.json(); }).then(function (data) { data.categories.forEach(function (c) { var fs = el('fieldset', 'sizer-group'); fs.appendChild(el('legend', '', lang === 'en' ? c.name_en : c.name_hu)); data.apps.filter(function (a) { return a.category === c.id; }).forEach(function (a) { var lab = el('label', 'sizer-app'); var cb = el('input'); cb.type = 'checkbox'; cb.value = a.slug; if (a.builtin) { cb.checked = true; cb.disabled = true; } lab.appendChild(cb); var name = el('span', 'sizer-app-name', lang === 'en' ? a.name_en : a.name_hu); lab.appendChild(name); var tag = a.builtin ? T.builtin : (lang === 'en' ? a.tagline_en : a.tagline_hu); if (tag) { lab.appendChild(el('span', 'sizer-app-tag', tag)); } fs.appendChild(lab); }); appsBox.appendChild(fs); }); function selected() { return Array.prototype.map.call(appsBox.querySelectorAll('input[type=checkbox]:checked:not(:disabled)'), function (c) { return c.value; }); } function draw() { var r = estimate(data, selected()); result.textContent = ''; var totals = el('p', 'sizer-totals'); totals.appendChild(el('span', '', T.typical + ': ')); totals.appendChild(el('strong', 'sizer-num', gbText(r.typical_mb))); totals.appendChild(el('span', '', ' · ' + T.peak + ': ')); totals.appendChild(el('strong', 'sizer-num', gbText(r.peak_mb))); result.appendChild(totals); result.appendChild(el('p', 'sizer-fine', T.platform_note)); var ul = el('ul', 'sizer-boxes'); r.boxes.forEach(function (b) { var li = el('li', 'sizer-box sizer-' + b.verdict); var head = el('div', 'sizer-box-head'); head.appendChild(icon(b.verdict === 'well' ? 'i-check' : (b.verdict === 'tight' ? 'i-triangle-alert' : 'i-x'))); head.appendChild(el('strong', 'sizer-box-size', b.gb + ' GB')); head.appendChild(el('span', 'sizer-verdict', T.verdict[b.verdict])); li.appendChild(head); li.appendChild(el('p', 'sizer-fine', T.guest.replace('{x}', gbText(b.guest_mb)))); if (b.verdict === 'tight') { li.appendChild(el('p', 'sizer-why', T.tight_why)); } ul.appendChild(li); }); result.appendChild(ul); if (r.busy.length) { result.appendChild(el('h3', 'sizer-sub', T.cpu_head)); var cl = el('ul', 'sizer-cpu'); r.busy.forEach(function (a) { var li = el('li'); li.appendChild(el('strong', '', (lang === 'en' ? a.name_en : a.name_hu) + ': ')); li.appendChild(el('span', '', lang === 'en' ? a.cpu_note_en : a.cpu_note_hu)); cl.appendChild(li); }); if (r.heavy.length >= 2) { cl.appendChild(el('li', 'sizer-pair', T.heavy_pair.replace('{names}', r.heavy.map(function (a) { return lang === 'en' ? a.name_en : a.name_hu; }).join(T.and)))); } result.appendChild(cl); } if (r.unmeasured.length) { result.appendChild(el('p', 'sizer-why', T.unmeasured + ': ' + r.unmeasured.map(function (a) { return lang === 'en' ? a.name_en : a.name_hu; }).join(', '))); } } appsBox.addEventListener('change', draw); Array.prototype.forEach.call(document.querySelectorAll('[data-preset]'), function (btn) { btn.addEventListener('click', function () { var want = (btn.getAttribute('data-preset') || '').split(',').filter(Boolean); Array.prototype.forEach.call(appsBox.querySelectorAll('input[type=checkbox]:not(:disabled)'), function (c) { c.checked = want.indexOf(c.value) !== -1; }); draw(); }); }); host.hidden = false; draw(); }).catch(function () { result.textContent = T.load_error; host.hidden = false; }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', start); } else { start(); } })(this);