' + '
0
' + '
' + it.unit + '
'; }).join(''); grid.querySelectorAll('.num').forEach(function (el, i) { animateValue(el, items[i].num, 750 + i * 40); }); } function renderBirthDetails(birth, target) { const html = [ { k: 'Day of birth', v: DAYS[birth.date.getDay()] }, { k: 'Zodiac sign', v: getZodiac(birth.m, birth.d) }, { k: 'Chinese zodiac', v: getChineseZodiac(birth.y) }, { k: 'Birthstone', v: BIRTHSTONES[birth.m - 1] }, { k: 'Born in leap year', v: isLeapYear(birth.y) ? 'Yes' : 'No' }, { k: 'Age calculated on', v: MONTHS[target.m - 1] + ' ' + target.d + ', ' + target.y } ]; document.getElementById('birth-details').innerHTML = html.map(function (x) { return '
'; }).join(''); } function renderFunFacts(age) { const heartbeats = Math.floor(age.totalMinutes * 72); const breaths = Math.floor(age.totalMinutes * 16); const sleepHours = Math.floor(age.totalHours * (8 / 24)); const weekends = Math.floor(age.totalWeeks * 2); const html = [ { k: 'Heartbeats lived (est.)', v: heartbeats.toLocaleString() }, { k: 'Breaths taken (est.)', v: breaths.toLocaleString() }, { k: 'Sleep hours (est.)', v: sleepHours.toLocaleString() }, { k: 'Weekends lived', v: weekends.toLocaleString() } ]; document.getElementById('fun-facts').innerHTML = html.map(function (x) { return '
'; }).join(''); } function renderMilestones(age) { const list = [ { label: '1,000 days old', threshold: 1000, value: age.totalDays }, { label: '10,000 days old', threshold: 10000, value: age.totalDays }, { label: '100,000 hours old', threshold: 100000, value: age.totalHours } ]; document.getElementById('milestones').innerHTML = list.map(function (m) { const reached = m.value >= m.threshold; return '
' + m.label + (reached ? ' ✓' : ' (' + (m.threshold - m.value).toLocaleString() + ' to go)') + ''; }).join(''); } function renderBirthdayCountdown(birth, target) { lastNextBirthday = nextBirthday(birth, target); const cd = countdownTo(target.date, lastNextBirthday); const labels = ['Days', 'Hours', 'Minutes', 'Seconds']; const vals = [cd.days, cd.hours, cd.minutes, cd.seconds]; document.getElementById('birthday-countdown').innerHTML = labels.map(function (u, i) { return '
'; }).join(''); } function updateCountdown() { if (!lastTarget || !lastNextBirthday) return; const cd = countdownTo(lastTarget.date, lastNextBirthday); ['cd-0', 'cd-1', 'cd-2', 'cd-3'].forEach(function (id, i) { const el = document.getElementById(id); if (el) el.textContent = [cd.days, cd.hours, cd.minutes, cd.seconds][i]; }); } function updateLiveAge() { if (!lastBirth) return; const t = todayParts(); const today = localDate(t.y, t.m, t.d); const end = lastTarget && lastTarget.date.getTime() < today.getTime() ? lastTarget.date : new Date(); const birthMs = lastBirth.date.getTime(); const endMs = end.getTime(); if (endMs < birthMs) return; const ms = endMs - birthMs; const sec = Math.floor(ms / 1000); const min = Math.floor(sec / 60); const hr = Math.floor(min / 60); const day = Math.floor(hr / 24); document.getElementById('live-age').textContent = day.toLocaleString() + ' days, ' + (hr % 24) + ' hours, ' + (min % 60) + ' minutes, ' + (sec % 60) + ' seconds'; } function clearTimers() { if (liveTimer) { clearInterval(liveTimer); liveTimer = null; } if (cdTimer) { clearInterval(cdTimer); cdTimer = null; } } function startTimers() { clearTimers(); updateLiveAge(); updateCountdown(); liveTimer = setInterval(updateLiveAge, 1000); cdTimer = setInterval(updateCountdown, 1000); } function buildResultText(birth, target, age) { return [ 'Exact Age: ' + age.years + ' years, ' + age.months + ' months, ' + age.days + ' days', 'Total: ' + age.totalMonths.toLocaleString() + ' months | ' + age.totalWeeks.toLocaleString() + ' weeks | ' + age.totalDays.toLocaleString() + ' days', age.totalHours.toLocaleString() + ' hours | ' + age.totalMinutes.toLocaleString() + ' minutes | ' + age.totalSeconds.toLocaleString() + ' seconds', 'Day of birth: ' + DAYS[birth.date.getDay()], 'Zodiac: ' + getZodiac(birth.m, birth.d) + ' | Chinese: ' + getChineseZodiac(birth.y), 'Birthstone: ' + BIRTHSTONES[birth.m - 1] + ' | Leap year birth: ' + (isLeapYear(birth.y) ? 'Yes' : 'No'), 'Born: ' + MONTHS[birth.m - 1] + ' ' + birth.d + ', ' + birth.y, 'Calculated on: ' + MONTHS[target.m - 1] + ' ' + target.d + ', ' + target.y ].join('\n'); } function validateAgeForm() { clearErrors(); const dobM = document.getElementById('dob-month'); const dobD = document.getElementById('dob-day'); const dobY = document.getElementById('dob-year'); const tarM = document.getElementById('target-month'); const tarD = document.getElementById('target-day'); const tarY = document.getElementById('target-year'); const birth = parseForm(dobM, dobD, dobY); if (!birth) { showError('dob-error', 'Please enter a valid date of birth (month, day, and year).', [dobM, dobD, dobY]); return null; } let target = parseForm(tarM, tarD, tarY); const t = todayParts(); if (!target) { target = { y: t.y, m: t.m, d: t.d, date: localDate(t.y, t.m, t.d) }; tarM.value = t.m; fillDaySelect(tarD, t.m, t.y, t.d); tarD.value = t.d; tarY.value = t.y; } const today = localDate(t.y, t.m, t.d); if (birth.date.getTime() > today.getTime()) { showError('dob-error', 'Date of birth cannot be in the future.', [dobM, dobD, dobY]); return null; } if (target.date.getTime() > today.getTime()) { showError('target-error', 'Target date cannot be in the future.', [tarM, tarD, tarY]); return null; } if (birth.date.getTime() > target.date.getTime()) { showError('target-error', 'Target date must be on or after your date of birth.', [tarM, tarD, tarY]); return null; } return { birth, target }; } function calculateAge(e) { if (e) e.preventDefault(); const data = validateAgeForm(); if (!data) return; const birth = data.birth; const target = data.target; const age = ageBetween(birth, target); lastBirth = birth; lastTarget = target; document.getElementById('exact-age-text').textContent = age.years + ' years, ' + age.months + ' months, ' + age.days + ' days'; const results = document.getElementById('results'); results.classList.add('visible'); results.scrollIntoView({ behavior: 'smooth', block: 'start' }); renderStats(age); renderBirthDetails(birth, target); renderFunFacts(age); renderMilestones(age); renderBirthdayCountdown(birth, target); startTimers(); lastResultText = buildResultText(birth, target, age); } function setupRipple(btn) { btn.addEventListener('click', function (e) { const rect = btn.getBoundingClientRect(); btn.style.setProperty('--rx', ((e.clientX - rect.left) / rect.width * 100) + '%'); btn.style.setProperty('--ry', ((e.clientY - rect.top) / rect.height * 100) + '%'); btn.classList.add('ripple'); setTimeout(function () { btn.classList.remove('ripple'); }, 400); }); } if (APP) APP.querySelectorAll('.btn-calculate').forEach(setupRipple); document.getElementById('age-form').addEventListener('submit', calculateAge); document.getElementById('dob-cal').addEventListener('click', function () { const p = document.getElementById('dob-picker'); formToPicker(document.getElementById('dob-month'), document.getElementById('dob-day'), document.getElementById('dob-year'), p); if (p.showPicker) p.showPicker(); else p.click(); }); document.getElementById('target-cal').addEventListener('click', function () { const p = document.getElementById('target-picker'); formToPicker(document.getElementById('target-month'), document.getElementById('target-day'), document.getElementById('target-year'), p); if (p.showPicker) p.showPicker(); else p.click(); }); document.getElementById('dob-picker').addEventListener('change', function () { pickerToForm(this, document.getElementById('dob-month'), document.getElementById('dob-day'), document.getElementById('dob-year')); }); document.getElementById('target-picker').addEventListener('change', function () { pickerToForm(this, document.getElementById('target-month'), document.getElementById('target-day'), document.getElementById('target-year')); }); document.getElementById('btn-copy').addEventListener('click', async function () { if (!lastResultText) { alert('Please calculate your age first.'); return; } try { await navigator.clipboard.writeText(lastResultText); this.textContent = 'Copied!'; this.classList.add('copied'); var btn = this; setTimeout(function () { btn.textContent = 'Copy Results'; btn.classList.remove('copied'); }, 2000); } catch (err) { alert('Copy failed. Please select and copy manually.'); } }); document.getElementById('btn-pdf').addEventListener('click', function () { if (!lastResultText) { alert('Please calculate your age first.'); return; } const w = window.open('', '_blank'); const safe = lastResultText.replace(//g, '>'); w.document.write('
Age Calculator ResultsAge Calculator Results
' + safe + '
Generated ' + new Date().toLocaleString() + '
'); w.document.close(); w.focus(); setTimeout(function () { w.print(); }, 350); }); document.getElementById('btn-share').addEventListener('click', async function () { if (!lastResultText) { alert('Please calculate your age first.'); return; } const shareData = { title: 'My Age Calculator Results', text: lastResultText }; if (navigator.share) { try { await navigator.share(shareData); } catch (err) { if (err.name !== 'AbortError') copyFallback(); } } else { copyFallback(); } function copyFallback() { navigator.clipboard.writeText(lastResultText).then(function () { alert('Results copied to clipboard for sharing.'); }); } }); document.getElementById('diff-form').addEventListener('submit', function (e) { e.preventDefault(); const errEl = document.getElementById('diff-error'); errEl.classList.remove('visible'); errEl.textContent = ''; const start = parseForm( document.getElementById('diff-from-m'), document.getElementById('diff-from-d'), document.getElementById('diff-from-y') ); const end = parseForm( document.getElementById('diff-to-m'), document.getElementById('diff-to-d'), document.getElementById('diff-to-y') ); if (!start || !end) { errEl.textContent = 'Please enter valid start and end dates.'; errEl.classList.add('visible'); return; } const t = todayParts(); const today = localDate(t.y, t.m, t.d); if (start.date.getTime() > today.getTime() || end.date.getTime() > today.getTime()) { errEl.textContent = 'Dates cannot be in the future.'; errEl.classList.add('visible'); return; } if (start.date.getTime() > end.date.getTime()) { errEl.textContent = 'Start date must be before or equal to end date.'; errEl.classList.add('visible'); return; } const d = ageBetween(start, end); document.getElementById('diff-output').innerHTML = [ { k: 'Years', v: d.years }, { k: 'Months (component)', v: d.months }, { k: 'Days (component)', v: d.days }, { k: 'Total weeks', v: d.totalWeeks.toLocaleString() }, { k: 'Total days', v: d.totalDays.toLocaleString() }, { k: 'Total hours', v: d.totalHours.toLocaleString() }, { k: 'Total minutes', v: d.totalMinutes.toLocaleString() } ].map(function (x) { return '
'; }).join(''); document.getElementById('diff-results').style.display = 'block'; }); initSelects(); })();