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 = '';
+ }
+ }
+ });
+}