diff --git a/public/index.html b/public/index.html index a540662..416264e 100644 --- a/public/index.html +++ b/public/index.html @@ -508,7 +508,7 @@ - + diff --git a/public/js/calculators.js b/public/js/calculators.js index 07f474f..7af2296 100644 --- a/public/js/calculators.js +++ b/public/js/calculators.js @@ -1,7 +1,12 @@ +import { parseAgeMonths, formatParsedAge } from './calculators/age.js'; +import { initImageLightbox } from './calculators/lightbox.js'; + // ============================================================ // CALCULATORS.JS — Pediatric clinical calculators // ============================================================ +initImageLightbox(); + (function() { var _inited = false; document.addEventListener('tabChanged', function(e) { @@ -10,49 +15,6 @@ initCalculators(); }); - // ── Flexible age parser ───────────────────────────────────────────── - // Accepts: "3y", "3 years", "29m", "29 months", "2y5m", "2 years 5 months", - // "15d", "15 days", "3y 2m 10d", "36" (months), "3.5 years", etc. - // Returns age in months (may be fractional), or null if unparseable. - function parseAgeMonths(input) { - if (input == null) return null; - var s = String(input).toLowerCase().trim(); - if (!s) return null; - // Plain number with no unit: if >= 24 assume years, else months. Matches - // common shorthand ("36" for 3y, "6" for 6 months). - if (/^[\d.]+$/.test(s)) { - var n = parseFloat(s); - if (isNaN(n)) return null; - return n >= 24 ? n : n; // keep as months; user can add unit if they meant years - } - var total = 0; - var matched = false; - // Use (?![a-z]) instead of \b so "2y5m" terminates "y" correctly. - // Order matters: longest-first alternations (years before yrs before y, etc.). - var my = s.match(/([\d.]+)\s*(?:years|year|yrs|yr|y)(?![a-z])/); - if (my) { total += parseFloat(my[1]) * 12; matched = true; } - var mm = s.match(/([\d.]+)\s*(?:months|month|mos|mo|m)(?![a-z])/); - if (mm) { total += parseFloat(mm[1]); matched = true; } - var mw = s.match(/([\d.]+)\s*(?:weeks|week|wks|wk|w)(?![a-z])/); - if (mw) { total += parseFloat(mw[1]) * 7 / 30.4375; matched = true; } - var md = s.match(/([\d.]+)\s*(?:days|day|d)(?![a-z])/); - if (md) { total += parseFloat(md[1]) / 30.4375; matched = true; } - return matched ? total : null; - } - - function formatParsedAge(months) { - if (months < 1) { - var d = Math.round(months * 30.4375); - return '= ' + d + ' day' + (d === 1 ? '' : 's') + ' (' + months.toFixed(2) + ' mo)'; - } - if (months < 24) { - return '= ' + (Math.round(months * 10) / 10) + ' months'; - } - var yr = Math.floor(months / 12); - var mo = Math.round(months - yr * 12); - if (mo === 12) { yr += 1; mo = 0; } - return '= ' + yr + ' yr' + (mo ? ' ' + mo + ' mo' : '') + ' (' + Math.round(months) + ' mo total)'; - } // Expose for debugging / other modules window._parseAgeMonths = parseAgeMonths; @@ -2899,43 +2861,3 @@ resultDiv.innerHTML = html; } })(); - -// ============================================================ -// IMAGE LIGHTBOX — any element with data-img-src opens fullscreen -// ============================================================ -(function() { - document.addEventListener('click', function(e) { - var opener = e.target.closest && e.target.closest('[data-img-src]'); - if (opener) { - e.preventDefault(); - var lb = document.getElementById('img-lightbox'); - if (!lb) return; - var src = opener.dataset.imgSrc; - var title = opener.dataset.imgTitle || ''; - document.getElementById('img-lightbox-img').src = src; - document.getElementById('img-lightbox-img').alt = title; - document.getElementById('img-lightbox-title').textContent = title; - document.getElementById('img-lightbox-open').href = src; - lb.style.display = 'flex'; - return; - } - // Close on close button, on backdrop click (not on image), and on Escape (below) - var closeBtn = e.target.closest && e.target.closest('#img-lightbox-close'); - var lb2 = document.getElementById('img-lightbox'); - if (!lb2 || lb2.style.display !== 'flex') return; - if (closeBtn || e.target === lb2) { - lb2.style.display = 'none'; - document.getElementById('img-lightbox-img').src = ''; - } - }); - document.addEventListener('keydown', function(e) { - if (e.key === 'Escape') { - var lb = document.getElementById('img-lightbox'); - if (lb && lb.style.display === 'flex') { - lb.style.display = 'none'; - document.getElementById('img-lightbox-img').src = ''; - } - } - }); -})(); - diff --git a/public/js/calculators/age.js b/public/js/calculators/age.js new file mode 100644 index 0000000..fea0498 --- /dev/null +++ b/public/js/calculators/age.js @@ -0,0 +1,36 @@ +export function parseAgeMonths(input) { + if (input == null) return null; + var s = String(input).toLowerCase().trim(); + if (!s) return null; + // Plain number with no unit: keep as months; user can add a unit if they meant years. + if (/^[\d.]+$/.test(s)) { + var n = parseFloat(s); + if (isNaN(n)) return null; + return n; + } + var total = 0; + var matched = false; + var my = s.match(/([\d.]+)\s*(?:years|year|yrs|yr|y)(?![a-z])/); + if (my) { total += parseFloat(my[1]) * 12; matched = true; } + var mm = s.match(/([\d.]+)\s*(?:months|month|mos|mo|m)(?![a-z])/); + if (mm) { total += parseFloat(mm[1]); matched = true; } + var mw = s.match(/([\d.]+)\s*(?:weeks|week|wks|wk|w)(?![a-z])/); + if (mw) { total += parseFloat(mw[1]) * 7 / 30.4375; matched = true; } + var md = s.match(/([\d.]+)\s*(?:days|day|d)(?![a-z])/); + if (md) { total += parseFloat(md[1]) / 30.4375; matched = true; } + return matched ? total : null; +} + +export function formatParsedAge(months) { + if (months < 1) { + var d = Math.round(months * 30.4375); + return '= ' + d + ' day' + (d === 1 ? '' : 's') + ' (' + months.toFixed(2) + ' mo)'; + } + if (months < 24) { + return '= ' + (Math.round(months * 10) / 10) + ' months'; + } + var yr = Math.floor(months / 12); + var mo = Math.round(months - yr * 12); + if (mo === 12) { yr += 1; mo = 0; } + return '= ' + yr + ' yr' + (mo ? ' ' + mo + ' mo' : '') + ' (' + Math.round(months) + ' mo total)'; +} diff --git a/public/js/calculators/lightbox.js b/public/js/calculators/lightbox.js new file mode 100644 index 0000000..581056a --- /dev/null +++ b/public/js/calculators/lightbox.js @@ -0,0 +1,35 @@ +export function initImageLightbox() { + document.addEventListener('click', function(e) { + var opener = e.target.closest && e.target.closest('[data-img-src]'); + if (opener) { + e.preventDefault(); + var lb = document.getElementById('img-lightbox'); + if (!lb) return; + var src = opener.dataset.imgSrc; + var title = opener.dataset.imgTitle || ''; + document.getElementById('img-lightbox-img').src = src; + document.getElementById('img-lightbox-img').alt = title; + document.getElementById('img-lightbox-title').textContent = title; + document.getElementById('img-lightbox-open').href = src; + lb.style.display = 'flex'; + return; + } + var closeBtn = e.target.closest && e.target.closest('#img-lightbox-close'); + var lb2 = document.getElementById('img-lightbox'); + if (!lb2 || lb2.style.display !== 'flex') return; + if (closeBtn || e.target === lb2) { + lb2.style.display = 'none'; + document.getElementById('img-lightbox-img').src = ''; + } + }); + + document.addEventListener('keydown', function(e) { + if (e.key === 'Escape') { + var lb = document.getElementById('img-lightbox'); + if (lb && lb.style.display === 'flex') { + lb.style.display = 'none'; + document.getElementById('img-lightbox-img').src = ''; + } + } + }); +}