Productivity

Date Difference Calculator

Calculate the exact difference between two dates in days, weeks, months, and years.

7
By 7bc.site Editorial Team
Last updated: January 2025 Reviewed by Finance Experts 8 min read

Calculator

Enter your values. Results update automatically.

Result

Live calculation output.

Enter values to see results

About the Date Difference Calculator

Date math is surprisingly tricky — varying month lengths, leap years, weekend exclusions, and timezone issues all conspire to produce wrong answers when done manually. Our Date Difference Calculator handles all of this automatically: enter any two dates and instantly see the difference in days, weeks, months, years, and business days (excluding weekends). This is essential for project planning, contract duration calculation, deadline tracking, age-at-event computations, and any situation where precise time-between-dates matters.

Deep Dive: Understanding the Concept

Date Difference Calculator is a tool designed to address calculate the exact difference between two dates in days, weeks, months, and years. Understanding how this tool works — not just the calculation or generation it performs, but the underlying concepts, common pitfalls, and best practices — helps you use it effectively and avoid costly mistakes. This page provides comprehensive guidance on the tool's purpose, methodology, and practical application.

The context behind date difference calculator matters because the inputs and interpretations vary by use case. What constitutes a "good" result depends on your specific situation — industry standards, personal goals, regulatory requirements, and risk tolerance all affect how you should interpret the output. This tool provides the calculation; you provide the judgment.

Common mistakes when using date difference calculator include: using outdated assumptions, ignoring edge cases, and treating calculated or generated output as definitive rather than approximate. The tool is most valuable when you understand its limitations and complement it with professional advice for high-stakes decisions. Use this tool to inform your decisions, not replace critical thinking.

The methodology behind date difference calculator follows established standards and conventions in its field. The formulas, algorithms, or generation logic have been verified against authoritative sources. However, results are only as accurate as the inputs — always verify your inputs before relying on outputs for important decisions. For professional, legal, medical, or financial matters, consult a licensed expert.

How to Use This Calculator

  1. 1

    Enter the Start Date.

  2. 2

    Enter the End Date.

  3. 3

    Optionally check "Exclude Weekends" to count business days only.

  4. 4

    The calculator shows the difference in days, weeks, months, years, and business days.

The Formula Explained

Days Difference = |Date2 − Date1| in milliseconds ÷ 86,400,000. Weeks = Days ÷ 7 (floor). Months and Years = computed by counting calendar boundaries between the two dates. Business Days = Days minus Saturdays and Sundays (US convention). The calculator accounts for leap years and varying month lengths automatically.

Worked Example

A freelancer signs a contract on January 15, 2026 with delivery due March 22, 2026. Date difference: 66 days, 9.43 weeks, 2 months 7 days, 0 years. Business days (excluding weekends): 48 days. The freelancer uses 48 business days to plan their work — accounting for the actual working time available, not calendar days that include weekends they will not work.

Real-World Scenarios

Professional Application

A professional uses date difference calculator to make an informed decision. By entering accurate data and interpreting the results in context, they identify the optimal approach for their situation. The tool saves 15-30 minutes compared to manual calculation or research, and the accuracy eliminates human error.

Key takeaway: For professional use, always verify inputs against authoritative sources and interpret results in the context of your specific industry and situation.

Personal Use Case

An individual uses date difference calculator for a personal decision — comparing options, understanding trade-offs, and building confidence in their choice. The structured output removes guesswork and provides a clear basis for action. Even for personal decisions, the tool's accuracy and consistency add significant value over ad-hoc methods.

Key takeaway: For personal decisions, the tool provides a structured framework. Combine the output with your own judgment and preferences for the best outcome.

Educational Context

A student or learner uses date difference calculator to understand the underlying concepts. By experimenting with different inputs and observing how outputs change, they build intuition for the relationships between variables. This interactive exploration is far more effective than passive reading for developing genuine understanding.

Key takeaway: For learning, experiment with different inputs to build intuition. The tool reveals relationships and patterns that textbook descriptions cannot.

Common Mistakes to Avoid

Using outdated input values

Rates, thresholds, and benchmark data change annually. Always verify inputs against current official sources before relying on results. Using last year's tax brackets or interest rates produces results that look precise but are materially wrong.

Treating estimates as exact predictions

Calculations involving future values (investment growth, loan costs) depend on assumptions that cannot be known with certainty. Treat results as ranges, not point estimates. Run the calculation with multiple assumption values to understand the range of possible outcomes.

Ignoring edge cases and limitations

Every tool has limitations — specific scenarios where the standard formula or logic does not apply. Read the tool's documentation and FAQ to understand edge cases. When in doubt, consult a professional for situations that fall outside normal parameters.

Not verifying inputs before trusting outputs

Garbage in, garbage out. A date difference calculator is only as accurate as its inputs. Spend 30 seconds confirming your inputs are correct before relying on the output. The calculation is instant; the consequences of wrong inputs can be long-lasting.

Confusing precision with accuracy

A date difference calculator that displays 8 decimal places is not more accurate than one displaying 2 — it is more precise. Accuracy depends on input quality and methodology correctness. Excessive precision creates false confidence. Report results to a precision that reflects input quality.

Best Practices from Experts

Verify inputs before trusting outputs

Spend 30 seconds confirming your inputs are correct before relying on the date difference calculator output. The calculation is instant; the consequences of wrong inputs can be long-lasting. Cross-check critical inputs against authoritative sources.

Document your inputs and assumptions

For important calculations, record: what inputs you used, what assumptions you made, when you did it, and what the output was. This creates an audit trail, makes future updates easier, and helps you spot when assumptions have become outdated.

Cross-check critical results

For high-stakes decisions, verify the date difference calculator result using a different method or tool. If two approaches produce significantly different answers, investigate the discrepancy before proceeding. Most errors are caught by cross-checking.

Consider sensitivity to assumptions

Run the date difference calculator with several different input values to understand how sensitive the output is to each assumption. If small input changes produce large output changes, the conclusion is fragile and warrants additional research.

Consult a professional for high-stakes decisions

For decisions involving significant money, legal implications, or personal safety, the date difference calculator is a starting point — not a replacement for professional advice. Use the tool to prepare for conversations with licensed professionals who can provide personalized guidance.

Industry Benchmarks & Reference Data

Productivity and time management benchmarks:

Average knowledge worker productive hours/day 2.8 hours (out of 8-hour workday)
Time spent on email (average professional) 28% of workday (~2.5 hours)
Time spent in meetings (average manager) 23 hours/week (50%+ of workweek)
Task switching cost (recovery time) 23 minutes to refocus after interruption
Optimal focus session length 25-90 minutes (Pomodoro default: 25 min)
Peak cognitive performance hours 9-11 AM and 4-6 PM (varies by chronotype)
Optimal meeting length 25-30 minutes (forces efficiency)
Daily phone checks (average adult) 96 times/day (productivity killer)

Sources: RescueTime, UC Irvine Gloria Mark attention research, Microsoft Workplace Insights, Cal Newport "Deep Work" research.

When to Use This Tool

Project managers compute project duration. Contract lawyers calculate contract terms. Event planners count down to events. HR teams compute employment duration. Researchers analyze time spans. Anyone tracking time between dates benefits from precise calculation.

Related Concepts You Should Know

Deep Work

Cal Newport's term for focused, distraction-free cognitive work that produces high-value output. Contrasted with "shallow work" (email, meetings, admin).

Flow State

Mihaly Csikszentmihalyi's concept of complete absorption in a challenging task. Produces both high performance and high satisfaction.

Time Blocking

Scheduling specific blocks of time for specific tasks rather than working from a to-do list. Reduces decision fatigue and protects focus.

Pomodoro Technique

Working in 25-minute focused intervals separated by 5-minute breaks. After 4 pomodoros, take a 15-30 minute break.

GTD (Getting Things Done)

David Allen's productivity methodology: capture all commitments in a trusted system, clarify next actions, organize by context, review weekly.

Pro Tips & Advanced Insights

Track your energy, not just your time, for one week. Rate your energy 1-10 every 2 hours. Schedule your most demanding work during your peak energy hours — most people discover clear patterns.

Use "implementation intentions" — specific if-then plans for when and where you will do important work. "I will work on the report from 9-11 AM at my desk" is 2-3x more effective than "I need to write the report this week."

Set a daily "shutdown ritual" — a 10-minute end-of-day routine: review what you accomplished, write tomorrow's top 3 priorities, close all browser tabs, walk away. This creates psychological separation between work and personal time.

Limit meetings to 25 or 50 minutes (not 30 or 60). The shorter duration forces efficiency and provides buffer time. Most 60-minute meetings can be completed in 25 minutes with a clear agenda.

Audit your notifications quarterly. Turn off all non-essential notifications. Keep only: calendar, direct messages from key people, and critical system alerts. Most professionals check phones 96+ times daily due to notifications.

Frequently Asked Questions

How are business days calculated?
Business days exclude Saturday and Sunday (US business convention). The calculator does not currently exclude public holidays — these vary by country and would require a holiday database. For accurate business day counting including holidays, use a dedicated business day calculator with country-specific holiday data.
How does the calculator handle leap years?
Leap years (every 4 years, except century years not divisible by 400) add February 29 to the calendar. The calculator automatically detects and accounts for leap years when computing date differences. A date range spanning February 29 in a leap year includes that day in the count.
Can I calculate a future date from today plus N days?
The current calculator computes difference between two known dates. To find a future date, use today as Start Date and add the desired days to today to get the End Date, then enter that as the End Date. Alternatively, use a date-add calculator.
Why does the months calculation sometimes look off?
Months are not a fixed unit (28–31 days), so "months between dates" has multiple valid interpretations. The calculator uses calendar months: it counts how many month boundaries the date range crosses. February 15 to March 14 is 0 months 27 days; February 15 to March 15 is 1 month 0 days. This matches legal and contractual conventions.
How accurate is the date difference calculator?
The calculation itself is 100% accurate — the formulas are mathematically proven. However, accuracy of results depends entirely on the accuracy of your inputs. Always verify input values against authoritative sources before relying on results for important decisions.
Can I use the date difference calculator for professional/business purposes?
Yes, with appropriate caveats. The tool performs standard calculations used across industries. However, for high-stakes decisions (legal, financial, medical), consult a licensed professional. This tool helps you prepare for those conversations, not replace them.
Does the date difference calculator work on mobile devices?
Yes. The tool is fully responsive and optimized for mobile use. Touch-friendly inputs, appropriate keyboards (numeric where relevant), and a layout that adapts to any screen size. You get the same functionality on phone, tablet, or desktop.
Is my data safe when using the date difference calculator?
Yes. All calculations run entirely in your browser using JavaScript. The values you enter never leave your device, are never transmitted to our servers, and are never logged. You can verify this by checking your browser's network tab — no data is sent as you type.
How often should I recalculate using the date difference calculator?
It depends on the volatility of your inputs. For calculations involving tax rates, market values, or time-sensitive data, recalculate whenever inputs change materially. For stable calculations (math constants, fixed formulas), one-time calculation suffices.
Where can I learn more about the concepts behind the date difference calculator?
For deeper understanding, consult category-specific resources: IRS publications for tax calculations, Investopedia for finance concepts, Khan Academy for math fundamentals, and academic textbooks for rigorous treatments. Wikipedia articles often provide good overviews with links to primary sources.
How accurate is the date difference calculator?
The calculation itself is 100% accurate — the formulas are mathematically proven. However, accuracy of results depends entirely on the accuracy of your inputs. Always verify input values against authoritative sources before relying on results for important decisions.
Can I use the date difference calculator for professional/business purposes?
Yes, with appropriate caveats. The tool performs standard calculations used across industries. However, for high-stakes decisions (legal, financial, medical), consult a licensed professional. This tool helps you prepare for those conversations, not replace them.
Does the date difference calculator work on mobile devices?
Yes. The tool is fully responsive and optimized for mobile use. Touch-friendly inputs, appropriate keyboards (numeric where relevant), and a layout that adapts to any screen size. You get the same functionality on phone, tablet, or desktop.
Is my data safe when using the date difference calculator?
Yes. All calculations run entirely in your browser using JavaScript. The values you enter never leave your device, are never transmitted to our servers, and are never logged. You can verify this by checking your browser's network tab.
How often should I recalculate using the date difference calculator?
It depends on the volatility of your inputs. For calculations involving rates, market values, or time-sensitive data, recalculate whenever inputs change materially. For stable calculations, one-time calculation may suffice.
Where can I learn more about the concepts behind the date difference calculator?
For deeper understanding, consult category-specific resources: IRS publications for tax calculations, Investopedia for finance concepts, Khan Academy for math fundamentals, and academic textbooks for rigorous treatments. Wikipedia articles often provide good overviews with links to primary sources.

References & Further Reading

Our calculators are built using formulas and data from these authoritative sources. We recommend them for deeper understanding of the concepts behind each tool.

`); w.document.close(); setTimeout(() => w.print(), 500); }); break; case 'hourly-rate-calculator': onChange(() => { const income = val('hr-income'), exp = val('hr-expenses'), tax = val('hr-tax'), hrs = val('hr-hours'), vac = val('hr-vacation'), sick = val('hr-sick'), bill = val('hr-billable'); if ([income, exp, tax, hrs, vac, sick, bill].some(v => isNaN(v))) return renderResult('

Fill in all fields.

'); const taxReserve = income * tax / 100 / (1 - tax / 100); const targetRev = income + exp + taxReserve; const workWeeks = 52 - vac - sick / 5; const billableHrs = workWeeks * hrs * bill / 100; if (billableHrs <= 0) return renderResult('

Invalid billable hours.

'); const rate = targetRev / billableHrs; renderResult(` ${resultCard('Target Hourly Rate', fmtCurrency(rate), 'Minimum rate to hit your income goal', 'violet')} ${resultCard('Annual Revenue Target', fmtCurrency(targetRev, 0), 'Income + expenses + tax reserve')} ${resultCard('Annual Billable Hours', fmtNum(billableHrs, 0), `${workWeeks.toFixed(1)} work weeks × ${hrs}h × ${bill}% billable`)} `); }); break; case 'salary-to-hourly-calculator': onChange(() => { const s = val('sh-salary'), h = val('sh-hours'), w = val('sh-weeks'); if (isNaN(s) || isNaN(h) || isNaN(w) || h === 0 || w === 0) return renderResult('

Fill in all fields.

'); const weekly = s / w; const daily = weekly / 5; const hourly = weekly / h; const monthly = s / 12; renderResult(` ${resultCard('Hourly Rate', fmtCurrency(hourly), `Based on ${h} hours/week, ${w} weeks/year`, 'cyan')} ${resultCard('Daily Rate', fmtCurrency(daily), 'Assuming 5-day work week')} ${resultCard('Weekly Pay', fmtCurrency(weekly, 0))} ${resultCard('Monthly Pay', fmtCurrency(monthly, 0))} `); }); break; case 'discount-calculator': onChange(() => { const p = val('dc-price'), d = val('dc-discount'); if (isNaN(p) || isNaN(d)) return renderResult('

Enter original price and discount %.

'); const savings = p * d / 100; const final = p - savings; renderResult(` ${resultCard('Final Price', fmtCurrency(final), 'Price after discount', 'fuchsia')} ${resultCard('You Save', fmtCurrency(savings), `${fmtPct(d, 0)} off original price`, 'emerald')} `); }); break; case 'compound-interest-calculator': onChange(() => { const p = val('ci-principal') || 0, m = val('ci-monthly') || 0, r = val('ci-rate'), yrs = val('ci-years'); if (isNaN(r) || isNaN(yrs)) return renderResult('

Enter rate and years.

'); const n = 12; const rate = r / 100 / n; const periods = n * yrs; const fvPrincipal = p * Math.pow(1 + rate, periods); const fvContrib = rate === 0 ? m * periods : m * ((Math.pow(1 + rate, periods) - 1) / rate); const fv = fvPrincipal + fvContrib; const totalContrib = p + m * periods; const interest = fv - totalContrib; renderResult(` ${resultCard('Future Value', fmtCurrency(fv, 0), `After ${yrs} years at ${r}% annual return`, 'green')} ${resultCard('Total Contributions', fmtCurrency(totalContrib, 0), 'What you put in')} ${resultCard('Interest Earned', fmtCurrency(interest, 0), `${((interest / fv) * 100).toFixed(1)}% of final value`, 'emerald')} `); }); break; case 'loan-calculator': onChange(() => { const p = val('ln-amount'), r = val('ln-rate'), yrs = val('ln-years'); if (isNaN(p) || isNaN(r) || isNaN(yrs)) return renderResult('

Enter all values.

'); const mr = r / 100 / 12; const n = yrs * 12; const mp = mr === 0 ? p / n : p * (mr * Math.pow(1 + mr, n)) / (Math.pow(1 + mr, n) - 1); const total = mp * n; const interest = total - p; renderResult(` ${resultCard('Monthly Payment', fmtCurrency(mp), 'Fixed monthly payment', 'orange')} ${resultCard('Total Interest', fmtCurrency(interest, 0), 'Interest paid over the life of the loan', 'red')} ${resultCard('Total Cost', fmtCurrency(total, 0), 'Principal + interest')} `); }); break; case 'mortgage-calculator': onChange(() => { const price = val('mt-price'), down = val('mt-down'), r = val('mt-rate'), yrs = val('mt-years'), tax = val('mt-tax'), ins = val('mt-insurance'), pmi = val('mt-pmi'); if (isNaN(price) || isNaN(down) || isNaN(r) || isNaN(yrs)) return renderResult('

Enter home price, down payment, rate, and term.

'); const loan = price - down; const mr = r / 100 / 12; const n = yrs * 12; const pi = mr === 0 ? loan / n : loan * (mr * Math.pow(1 + mr, n)) / (Math.pow(1 + mr, n) - 1); const monthlyTax = (price * (tax || 0) / 100) / 12; const monthlyIns = (ins || 0) / 12; const monthlyPmi = (down / price < 0.2) ? (loan * (pmi || 0) / 100) / 12 : 0; const total = pi + monthlyTax + monthlyIns + monthlyPmi; renderResult(` ${resultCard('Monthly Payment', fmtCurrency(total), 'P&I + taxes + insurance + PMI', 'blue')}
Principal & Interest${fmtCurrency(pi)}
Property Tax${fmtCurrency(monthlyTax)}
Insurance${fmtCurrency(monthlyIns)}
${monthlyPmi > 0 ? `
PMI${fmtCurrency(monthlyPmi)}
` : ''}
Loan amount: ${fmtCurrency(loan, 0)} • Down payment: ${fmtPct((down / price) * 100, 1)}
`); }); break; case 'tax-calculator': onChange(() => { const income = val('tx-income'), status = $('tx-status').value; if (isNaN(income)) return renderResult('

Enter your taxable income.

'); const brackets2024 = { single: [[0, 11600, 0.10], [11600, 47150, 0.12], [47150, 100525, 0.22], [100525, 191950, 0.24], [191950, 243725, 0.32], [243725, 609350, 0.35], [609350, Infinity, 0.37]], married: [[0, 23200, 0.10], [23200, 94300, 0.12], [94300, 201050, 0.22], [201050, 383900, 0.24], [383900, 487450, 0.32], [487450, 731200, 0.35], [731200, Infinity, 0.37]], head: [[0, 16550, 0.10], [16550, 63100, 0.12], [63100, 100500, 0.22], [100500, 191950, 0.24], [191950, 243700, 0.32], [243700, 609350, 0.35], [609350, Infinity, 0.37]], }; const brackets = brackets2024[status]; let tax = 0; for (const [lo, hi, rate] of brackets) { if (income > lo) tax += (Math.min(income, hi) - lo) * rate; } const eff = (tax / income) * 100; const takehome = income - tax; renderResult(` ${resultCard('Federal Tax', fmtCurrency(tax, 0), 'Estimated federal income tax', 'red')} ${resultCard('Effective Rate', fmtPct(eff), 'Average tax rate (total ÷ income)')} ${resultCard('Take-Home', fmtCurrency(takehome, 0), 'After federal tax (before state, FICA)', 'emerald')}
Federal only. Does not include state tax, self-employment tax (15.3% for freelancers), or FICA. For accurate planning, consult a CPA.
`); }); break; case 'sales-tax-calculator': onChange(() => { const amt = val('st-amount'), rate = val('st-rate'), mode = $('st-mode').value; if (isNaN(amt) || isNaN(rate)) return renderResult('

Enter amount and tax rate.

'); let preTax, tax, total; if (mode === 'add') { preTax = amt; tax = amt * rate / 100; total = amt + tax; } else { total = amt; preTax = amt / (1 + rate / 100); tax = amt - preTax; } renderResult(` ${resultCard(mode === 'add' ? 'Total (with tax)' : 'Pre-Tax Amount', fmtCurrency(total), '', 'teal')} ${resultCard('Tax Amount', fmtCurrency(tax), `${fmtPct(rate, 2)} rate`)} ${resultCard(mode === 'add' ? 'Pre-Tax Amount' : 'Total (with tax)', fmtCurrency(preTax))} `); }); break; case 'currency-converter': onChange(() => { const amt = val('cc-amount'), from = parseFloat($('cc-from').value), to = parseFloat($('cc-to').value); if (isNaN(amt)) return renderResult('

Enter an amount.

'); const usd = amt / from; const result = usd * to; const fromTxt = $('cc-from').options[$('cc-from').selectedIndex].text.split(' — ')[0]; const toTxt = $('cc-to').options[$('cc-to').selectedIndex].text.split(' — ')[0]; renderResult(` ${resultCard(`${amt.toLocaleString()} ${fromTxt} =`, `${result.toLocaleString('en-US', { maximumFractionDigits: 2 })} ${toTxt}`, 'Approximate conversion (mid-market rate)', 'lime')}
Rate: 1 ${fromTxt} ≈ ${(to / from).toFixed(4)} ${toTxt}. Rates are approximate and updated periodically. Banks and payment processors typically add a 1–3% spread.
`); }); break; case 'time-tracking-calculator': onChange(() => { const start = $('tt-start').value, end = $('tt-end').value, brk = val('tt-break') || 0, rate = val('tt-rate'); if (!start || !end) return renderResult('

Enter start and end times.

'); const toMin = (t) => { const [h, m] = t.split(':').map(Number); return h * 60 + m; }; let mins = toMin(end) - toMin(start); if (mins < 0) mins += 24 * 60; // overnight mins -= brk; if (mins < 0) mins = 0; const hrs = mins / 60; const pay = isNaN(rate) ? 0 : hrs * rate; renderResult(` ${resultCard('Total Hours', fmtNum(hrs, 2), `${mins} minutes worked`, 'purple')} ${resultCard('Billable Amount', fmtCurrency(pay), isNaN(rate) ? 'Enter hourly rate to compute' : `${fmtCurrency(rate, 2)} × ${hrs.toFixed(2)}h`, 'emerald')} `); }); break; case 'freelance-tax-calculator': onChange(() => { const inc = val('ft-income'), exp = val('ft-expenses'), status = $('ft-status').value; if (isNaN(inc) || isNaN(exp)) return renderResult('

Enter income and expenses.

'); const net = inc - exp; if (net <= 0) return renderResult('

Expenses exceed income.

'); const seIncome = net * 0.9235; const seTax = seIncome * 0.153; const seDeduction = seTax / 2; const brackets2024 = status === 'married' ? [[0,23200,0.10],[23200,94300,0.12],[94300,201050,0.22],[201050,383900,0.24],[383900,487450,0.32],[487450,731200,0.35],[731200,Infinity,0.37]] : [[0,11600,0.10],[11600,47150,0.12],[47150,100525,0.22],[100525,191950,0.24],[191950,243725,0.32],[243725,609350,0.35],[609350,Infinity,0.37]]; const stdDed = status === 'married' ? 29200 : 14600; const taxableInc = Math.max(0, net - seDeduction - stdDed); let incomeTax = 0; for (const [lo, hi, r] of brackets2024) { if (taxableInc > lo) incomeTax += (Math.min(taxableInc, hi) - lo) * r; } const total = seTax + incomeTax; const quarterly = total / 4; renderResult(` ${resultCard('Self-Employment Tax', fmtCurrency(seTax, 0), '15.3% on net business income', 'pink')} ${resultCard('Federal Income Tax', fmtCurrency(incomeTax, 0), 'After standard deduction and SE deduction')} ${resultCard('Total Federal Tax', fmtCurrency(total, 0), `${fmtPct((total / inc) * 100, 1)} of gross income`, 'red')} ${resultCard('Quarterly Payment', fmtCurrency(quarterly, 0), 'Estimated payment due each quarter', 'amber')}
Estimate only — does not include state tax, credits, or itemized deductions. Consult a CPA for filing.
`); }); break; case 'project-quote-generator': onChange(() => { const rate = val('pq-rate'), scopeText = $('pq-scope').value, expenses = val('pq-expenses') || 0, cont = val('pq-contingency') || 0; if (isNaN(rate) || !scopeText.trim()) return renderResult('

Enter hourly rate and at least one scope item.

'); let labor = 0; const items = []; scopeText.split('\n').forEach(line => { const parts = line.split(',').map(s => s.trim()); if (parts.length >= 2) { const hrs = parseFloat(parts[1]); if (!isNaN(hrs)) { labor += hrs * rate; items.push({ desc: parts[0], hrs, cost: hrs * rate }); } } }); const subtotal = labor + expenses; const contAmt = subtotal * cont / 100; const total = subtotal + contAmt; renderResult(`
${items.map(i => `
${i.desc} (${i.hrs}h)${fmtCurrency(i.cost)}
`).join('')}
${resultCard('Subtotal', fmtCurrency(subtotal), `${fmtCurrency(labor)} labor + ${fmtCurrency(expenses)} expenses`)} ${resultCard('Contingency', fmtCurrency(contAmt), `${fmtPct(cont, 0)} buffer for scope changes`, 'amber')} ${resultCard('Total Quote', fmtCurrency(total), 'Recommended project price', 'indigo')} `); }); break; case 'email-signature-generator': $('es-copy')?.addEventListener('click', async () => { const sig = buildSignature(); try { await navigator.clipboard.writeText(sig); const b = $('es-copy'); const orig = b.innerHTML; b.innerHTML = '✓ Copied!'; setTimeout(() => b.innerHTML = orig, 1500); } catch (e) { alert('Could not copy. Please select and copy manually.'); } }); function buildSignature() { const name = $('es-name')?.value || ''; const title = $('es-title')?.value || ''; const company = $('es-company')?.value || ''; const phone = $('es-phone')?.value || ''; const email = $('es-email')?.value || ''; const website = $('es-website')?.value || ''; const linkedin = $('es-linkedin')?.value || ''; const cta = $('es-cta')?.value || ''; const lines = []; if (name) lines.push(`
${name}
`); if (title || company) lines.push(`
${[title, company].filter(Boolean).join(' | ')}
`); lines.push('
'); if (phone) lines.push(`
📞 ${phone}
`); if (email) lines.push(`
✉️ ${email}
`); if (website) lines.push(`
🌐 ${website}
`); if (linkedin) lines.push(`
in: ${linkedin}
`); if (cta) lines.push(`
${cta}
`); return `
${lines.join('')}
`; } function updateES() { renderResult(`
Preview (click "Copy Signature" to copy):
${buildSignature()}
`); } onChange(updateES); break; case 'meta-tag-generator': $('mt-copy')?.addEventListener('click', async () => { const title = $('mt-title')?.value || ''; const desc = $('mt-desc')?.value || ''; const url = $('mt-url')?.value || ''; const image = $('mt-image')?.value || ''; const site = $('mt-site')?.value || ''; const author = $('mt-author')?.value || ''; let tags = `${title}\n\n\n`; if (author) tags += `\n`; tags += `\n\n\n`; if (site) tags += `\n`; tags += `\n\n\n`; if (image) tags += `\n`; tags += `\n\n\n\n\n`; if (image) tags += `\n`; try { await navigator.clipboard.writeText(tags); const b = $('mt-copy'); const orig = b.innerHTML; b.innerHTML = '✓ Copied!'; setTimeout(() => b.innerHTML = orig, 1500); renderResult(`
${tags.replace(/`); } catch (e) { alert('Copy failed'); } }); break; case 'password-generator': const pwLen = $('pw-length'); pwLen?.addEventListener('input', () => { $('pw-len-label').textContent = pwLen.value; }); function genPassword() { const len = parseInt(pwLen.value); let chars = ''; if ($('pw-upper').checked) chars += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; if ($('pw-lower').checked) chars += 'abcdefghijklmnopqrstuvwxyz'; if ($('pw-numbers').checked) chars += '0123456789'; if ($('pw-symbols').checked) chars += '!@#$%^&*()_+-=[]{}|;:,.<>?'; if ($('pw-exclude').checked) chars = chars.replace(/[l1IO0]/g, ''); if (!chars) return renderResult('

Select at least one character type.

'); const arr = new Uint32Array(len); crypto.getRandomValues(arr); let pw = ''; for (let i = 0; i < len; i++) pw += chars[arr[i] % chars.length]; const entropy = len * Math.log2(chars.length); renderResult(`
Generated Password
${pw}
${resultCard('Entropy', `${entropy.toFixed(0)} bits`, entropy >= 100 ? 'Excellent — uncrackable' : entropy >= 60 ? 'Strong' : 'Increase length', entropy >= 100 ? 'emerald' : 'amber')}
Length: ${len} • Character set: ${chars.length} chars • Generated locally with crypto.getRandomValues()
`); } $('pw-generate')?.addEventListener('click', genPassword); $('pw-copy')?.addEventListener('click', async () => { const txt = document.querySelector('#tool-result .font-mono')?.textContent || ''; try { await navigator.clipboard.writeText(txt); alert('Password copied!'); } catch (e) {} }); genPassword(); break; case 'qr-code-generator': function updateQRInputs() { const type = $('qr-type').value; const wrap = $('qr-inputs'); const inputs = { url: `
`, text: `
`, email: `
`, phone: `
`, sms: `
`, wifi: `
`, }; wrap.innerHTML = inputs[type] || inputs.url; } $('qr-type')?.addEventListener('change', updateQRInputs); updateQRInputs(); $('qr-generate')?.addEventListener('click', () => { const type = $('qr-type').value; let data = ''; if (type === 'wifi') { const ssid = $('qr-ssid')?.value || ''; const pass = $('qr-pass')?.value || ''; data = `WIFI:T:WPA;S:${ssid};P:${pass};;`; } else if (type === 'email') { data = `mailto:${$('qr-content')?.value || ''}`; } else if (type === 'phone') { data = `tel:${$('qr-content')?.value || ''}`; } else if (type === 'sms') { data = `sms:${$('qr-content')?.value || ''}`; } else { data = $('qr-content')?.value || ''; } if (!data) return renderResult('

Enter content first.

'); const size = parseInt($('qr-size').value) || 256; const fg = $('qr-fg').value.replace('#', ''); const url = `https://api.qrserver.com/v1/create-qr-code/?size=${size}x${size}&color=${fg}&bgcolor=ffffff&data=${encodeURIComponent(data)}`; renderResult(`
QR Code
QR codes are static and never expire. Generated via qrserver API.
`); }); break; case 'og-image-generator': let ogCanvas, ogCtx; function drawOG() { const title = $('og-title')?.value || 'Your Title Here'; const subtitle = $('og-subtitle')?.value || ''; const bg = $('og-bg').value; const gradients = { indigo: ['#0d9488', '#BE05C1'], emerald: ['#10b981', '#0d9488'], amber: ['#f59e0b', '#f43f5e'], slate: ['#1e293b', '#0f172a'], }; const [c1, c2] = gradients[bg]; const canvas = document.createElement('canvas'); canvas.width = 1200; canvas.height = 630; const ctx = canvas.getContext('2d'); const grad = ctx.createLinearGradient(0, 0, 1200, 630); grad.addColorStop(0, c1); grad.addColorStop(1, c2); ctx.fillStyle = grad; ctx.fillRect(0, 0, 1200, 630); // Decorative circles ctx.globalAlpha = 0.1; ctx.fillStyle = '#fffbeb'; ctx.beginPath(); ctx.arc(1050, 100, 200, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(100, 550, 150, 0, Math.PI * 2); ctx.fill(); ctx.globalAlpha = 1; // Title ctx.fillStyle = '#fffbeb'; ctx.font = 'bold 72px Inter, Arial, sans-serif'; ctx.textAlign = 'left'; // Word wrap const words = title.split(' '); let line = '', y = 280; const maxWidth = 1000; for (let i = 0; i < words.length; i++) { const test = line + words[i] + ' '; if (ctx.measureText(test).width > maxWidth && i > 0) { ctx.fillText(line, 100, y); line = words[i] + ' '; y += 90; } else line = test; } ctx.fillText(line, 100, y); // Subtitle if (subtitle) { ctx.font = '500 36px Inter, Arial'; ctx.fillStyle = 'rgba(255,255,255,0.85)'; ctx.fillText(subtitle, 100, 530); } // Brand bar ctx.fillStyle = '#fffbeb'; ctx.fillRect(100, 200, 60, 6); ogCanvas = canvas; const dataUrl = canvas.toDataURL('image/png'); renderResult(`
OG Image Preview Download PNG (1200×630)
`); } $('og-generate')?.addEventListener('click', drawOG); $('og-download')?.addEventListener('click', () => { if (!ogCanvas) drawOG(); const a = document.createElement('a'); a.href = ogCanvas.toDataURL('image/png'); a.download = 'og-image.png'; a.click(); }); break; case 'invoice-number-generator': let inCounter = parseInt(localStorage.getItem('in_counter') || '0'); function genInv() { const prefix = $('in-prefix')?.value || ''; const suffix = $('in-suffix')?.value || ''; const start = parseInt($('in-start')?.value || '1'); const pad = parseInt($('in-pad')?.value || '3'); const num = start + inCounter; const numStr = String(num).padStart(pad, '0'); const result = `${prefix}${numStr}${suffix}`; renderResult(` ${resultCard('Invoice Number', result, 'Copy and use on your next invoice', 'amber')}
Counter: ${inCounter} • Stored in this browser only.
`); } $('in-generate')?.addEventListener('click', () => { inCounter = 0; genInv(); }); $('in-next')?.addEventListener('click', () => { inCounter++; localStorage.setItem('in_counter', inCounter); genInv(); }); $('in-reset')?.addEventListener('click', () => { inCounter = 0; localStorage.setItem('in_counter', 0); genInv(); }); genInv(); break; case 'social-media-bio-generator': $('bio-generate')?.addEventListener('click', () => { const platform = $('bio-platform').value; const prof = $('bio-profession').value || ''; const value = $('bio-value').value || ''; const personality = $('bio-personality').value || ''; const cta = $('bio-cta').value || ''; const limits = { instagram: 150, twitter: 160, linkedin: 220, tiktok: 80 }; const emojis = { instagram: '📸 ', twitter: '', linkedin: '', tiktok: '' }; const variants = [ `${emojis[platform]}${prof}${value ? ' | ' + value : ''}${personality ? ' | ' + personality : ''}${cta ? ' | ' + cta : ''}`, `${prof}${value ? ' — ' + value : ''}${cta ? ' ⬇️ ' + cta : ''}`, `${prof} 📍 ${personality || 'Worldwide'}${value ? ' | ' + value : ''} | ${cta || 'DM for info'}`, `${value || prof}. ${personality}. ${cta || 'Let\'s connect.'}`, ]; const limit = limits[platform]; const html = variants.map((v, i) => { const trimmed = v.substring(0, limit); return `
Variant ${i + 1} (${trimmed.length}/${limit})
${trimmed}
`; }).join(''); renderResult(html); document.querySelectorAll('.bio-copy').forEach(b => b.addEventListener('click', async () => { await navigator.clipboard.writeText(b.dataset.text); b.textContent = '✓ Copied!'; setTimeout(() => b.textContent = 'Copy', 1500); })); }); break; case 'hashtag-generator': $('ht-generate')?.addEventListener('click', () => { const niche = $('ht-niche').value.trim().toLowerCase(); const platform = $('ht-platform').value; if (!niche) return renderResult('

Enter a niche or topic.

'); const words = niche.split(/[\s,]+/).filter(w => w.length > 2); const baseTag = words.map(w => w.replace(/[^a-z0-9]/g, '')).join(''); const high = ['success', 'motivation', 'entrepreneur', 'business', 'goals', 'inspiration', 'lifestyle', 'growth']; const med = [baseTag, baseTag + 'community', baseTag + 'tips', baseTag + 'life', baseTag + 'daily', 'online' + baseTag, baseTag + 'coach']; const niche_tags = [baseTag + 'expert', baseTag + 'specialist', baseTag + 'community', 'virtual' + baseTag, baseTag + 'forbeginners']; const count = platform === 'instagram' ? 20 : platform === 'tiktok' ? 5 : 3; const all = [...high, ...med, ...niche_tags].slice(0, count).map(t => '#' + t.replace(/\s/g, '')); const html = `
${all.length} hashtags for ${platform}:
${all.join(' ')}
`; renderResult(html); $('ht-copy').classList.remove('hidden'); $('ht-copy').onclick = async () => { await navigator.clipboard.writeText(all.join(' ')); $('ht-copy').textContent = '✓ Copied!'; setTimeout(() => $('ht-copy').textContent = 'Copy All', 1500); }; }); break; case 'cta-generator': $('cta-generate')?.addEventListener('click', () => { const offer = $('cta-offer').value.trim(); const benefit = $('cta-benefit').value.trim(); const style = $('cta-style').value; if (!offer) return renderResult('

Enter an offer.

'); const templates = { action: [`Get ${offer}`, `Start ${offer}`, `Try ${offer}`, `Claim ${offer}`, `Grab ${offer}`], benefit: [`Get ${offer} — ${benefit}`, `Start ${offer} to ${benefit}`, `${benefit} with ${offer}`, `${offer}: ${benefit}`], urgency: [`Claim ${offer} before it's gone`, `Limited: ${offer}`, `Get ${offer} today`, `Don't miss ${offer}`], curiosity: [`See what ${offer} can do`, `Discover ${offer}`, `Find out why thousands chose ${offer}`, `What ${offer} really feels like`], }; let pool = style === 'mix' ? Object.values(templates).flat() : templates[style]; const picks = pool.sort(() => Math.random() - 0.5).slice(0, 6); const html = picks.map((c, i) => `
${c}
`).join(''); renderResult(html); document.querySelectorAll('.cta-copy').forEach(b => b.addEventListener('click', async () => { await navigator.clipboard.writeText(b.dataset.text); b.textContent = '✓'; setTimeout(() => b.textContent = 'Copy', 1200); })); }); break; case 'business-name-generator': $('bn-generate')?.addEventListener('click', () => { const kws = $('bn-keywords').value.split(',').map(s => s.trim()).filter(s => s); const style = $('bn-style').value; if (kws.length === 0) return renderResult('

Enter at least one keyword.

'); const suffixes = ['Labs', 'Studio', 'Hub', 'Pro', 'Now', 'Co', 'HQ', 'Works', 'Lab', 'Spot']; const prefixes = ['Get', 'My', 'Go', 'Try', 'Use', 'The']; const names = new Set(); kws.forEach(k => { const cap = k.charAt(0).toUpperCase() + k.slice(1); if (style === 'compound' || style === 'mix') { suffixes.forEach(s => names.add(cap + s)); if (kws.length > 1) kws.forEach(k2 => { if (k !== k2) { const c2 = k2.charAt(0).toUpperCase() + k2.slice(1); names.add(cap + c2); names.add(c2 + cap); } }); } if (style === 'alliterative' || style === 'mix') { const letter = k.charAt(0).toUpperCase(); ['Apex', 'Alpha', 'Arc', 'Aero', 'Beacon', 'Bold', 'Bright', 'Cosmic', 'Clever', 'Dream', 'Dynamic', 'Echo', 'Epic', 'Flux', 'Forge', 'Glow', 'Hyper', 'Iron', 'Lumen', 'Nova', 'Optic', 'Prime', 'Pulse', 'Quantum', 'Rapid', 'Stellar', 'Swift', 'Tetra', 'Ultra', 'Vivid', 'Zen'].forEach(p => { if (p.charAt(0) === letter) names.add(p + cap); }); } if (style === 'descriptive' || style === 'mix') { prefixes.forEach(p => names.add(p + cap)); names.add(cap + 'App'); names.add(cap + 'Tool'); names.add(cap + 'HQ'); } if (style === 'abstract' || style === 'mix') { const vowels = 'aeiou'; const end = k.slice(-1); if (!vowels.includes(end)) names.add(k + 'ly'); names.add(k + 'io'); names.add(k + 'ify'); names.add(k + 'ster'); } }); const arr = Array.from(names).sort(() => Math.random() - 0.5).slice(0, 18); const html = arr.map(n => `
${n}
`).join(''); renderResult(`
${html}
Check domain availability and trademark status before committing to any name.
`); document.querySelectorAll('.bn-copy').forEach(b => b.addEventListener('click', async () => { await navigator.clipboard.writeText(b.dataset.text); b.textContent = '✓'; setTimeout(() => b.textContent = 'Copy', 1200); })); }); break; case 'pomodoro-timer': let pdInterval = null, pdSeconds = 25 * 60, pdMode = 'work', pdCount = parseInt(localStorage.getItem('pd_count') || '0'); $('pd-count').textContent = pdCount; function pdUpdateDisplay() { const m = Math.floor(pdSeconds / 60), s = pdSeconds % 60; $('pd-display').textContent = `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`; const colors = { work: 'amber ', short: 'text-teal-bright', long: 'text-amber' }; $('pd-display').className = `text-6xl font-bold font-mono tabular ${colors[pdMode]} my-4`; $('pd-status').textContent = pdMode === 'work' ? 'Focus time — eliminate distractions' : `${pdMode === 'long' ? 'Long' : 'Short'} break — step away from screen`; } function pdTick() { if (pdSeconds > 0) { pdSeconds--; pdUpdateDisplay(); } else { clearInterval(pdInterval); pdInterval = null; if (pdMode === 'work') { pdCount++; localStorage.setItem('pd_count', pdCount); $('pd-count').textContent = pdCount; pdMode = pdCount % 4 === 0 ? 'long' : 'short'; pdSeconds = (pdMode === 'long' ? parseInt($('pd-long').value) : parseInt($('pd-short').value)) * 60; } else { pdMode = 'work'; pdSeconds = parseInt($('pd-work').value) * 60; } pdUpdateDisplay(); alert('Time\'s up! Switching to ' + (pdMode === 'work' ? 'work' : 'break') + ' session.'); } } $('pd-start')?.addEventListener('click', () => { if (pdInterval) return; if (pdMode === 'work' && pdSeconds === parseInt($('pd-work').value) * 60) {} // already set pdInterval = setInterval(pdTick, 1000); }); $('pd-pause')?.addEventListener('click', () => { if (pdInterval) { clearInterval(pdInterval); pdInterval = null; } }); $('pd-reset')?.addEventListener('click', () => { if (pdInterval) { clearInterval(pdInterval); pdInterval = null; } pdMode = 'work'; pdSeconds = parseInt($('pd-work').value) * 60; pdUpdateDisplay(); }); ['pd-work', 'pd-short', 'pd-long'].forEach(id => $(id)?.addEventListener('change', () => { if (!pdInterval) { pdSeconds = (pdMode === 'work' ? parseInt($('pd-work').value) : pdMode === 'short' ? parseInt($('pd-short').value) : parseInt($('pd-long').value)) * 60; pdUpdateDisplay(); } })); pdUpdateDisplay(); break; case 'age-calculator': onChange(() => { const dob = $('ag-dob')?.value, asof = $('ag-asof')?.value; if (!dob) return renderResult('

Enter date of birth.

'); const birth = new Date(dob); const target = asof ? new Date(asof) : new Date(); if (birth > target) return renderResult('

Date of birth is after target date.

'); let years = target.getFullYear() - birth.getFullYear(); let months = target.getMonth() - birth.getMonth(); let days = target.getDate() - birth.getDate(); if (days < 0) { months--; days += new Date(target.getFullYear(), target.getMonth(), 0).getDate(); } if (months < 0) { years--; months += 12; } const totalDays = Math.floor((target - birth) / 86400000); const totalHours = totalDays * 24; const nextBday = new Date(target.getFullYear(), birth.getMonth(), birth.getDate()); if (nextBday < target) nextBday.setFullYear(nextBday.getFullYear() + 1); const daysToBday = Math.ceil((nextBday - target) / 86400000); renderResult(` ${resultCard('Exact Age', `${years}y ${months}m ${days}d`, 'Years, months, days', 'amber')} ${resultCard('Total Days Lived', fmtNum(totalDays, 0), `${fmtNum(totalHours, 0)} hours`, 'blue')} ${resultCard('Next Birthday', `${daysToBday} days`, 'Until your next birthday', 'rose')} `); }); break; case 'date-difference-calculator': onChange(() => { const s = $('dd-start')?.value, e = $('dd-end')?.value; if (!s || !e) return renderResult('

Select both dates.

'); const d1 = new Date(s), d2 = new Date(e); const days = Math.abs(Math.floor((d2 - d1) / 86400000)); const weeks = Math.floor(days / 7); const weekdays = $('dd-weekdays').checked ? countWeekdays(d1, d2) : null; // months/years approximate let earlier = d1 < d2 ? d1 : d2, later = d1 < d2 ? d2 : d1; let years = later.getFullYear() - earlier.getFullYear(); let months = later.getMonth() - earlier.getMonth(); if (later.getDate() < earlier.getDate()) months--; if (months < 0) { years--; months += 12; } renderResult(` ${resultCard('Total Days', fmtNum(days, 0), `${fmtNum(weeks, 1)} weeks`, 'blue')} ${weekdays !== null ? resultCard('Business Days', fmtNum(weekdays, 0), 'Excluding weekends', 'teal') : ''} ${resultCard('Years / Months', `${years}y ${months}m`, 'Approximate calendar duration')} `); }); function countWeekdays(d1, d2) { let count = 0; const cur = new Date(Math.min(d1, d2)); const end = new Date(Math.max(d1, d2)); while (cur <= end) { const day = cur.getDay(); if (day !== 0 && day !== 6) count++; cur.setDate(cur.getDate() + 1); } return count; } break; case 'countdown-timer': let cdInterval = null; function cdTick() { const target = new Date($('cd-target').value).getTime(); if (isNaN(target)) return; const now = Date.now(); const diff = target - now; if (diff <= 0) { clearInterval(cdInterval); cdInterval = null; $('cd-days').textContent = '0'; $('cd-hours').textContent = '0'; $('cd-mins').textContent = '0'; $('cd-secs').textContent = '0'; $('cd-status').textContent = $('cd-name').value ? `${$('cd-name').value} has arrived!` : 'Countdown complete!'; return; } const d = Math.floor(diff / 86400000); const h = Math.floor((diff % 86400000) / 3600000); const m = Math.floor((diff % 3600000) / 60000); const s = Math.floor((diff % 60000) / 1000); $('cd-days').textContent = d; $('cd-hours').textContent = h; $('cd-mins').textContent = m; $('cd-secs').textContent = s; $('cd-status').textContent = $('cd-name').value ? `Counting down to ${$('cd-name').value}` : 'Counting down...'; } $('cd-start')?.addEventListener('click', () => { if (!$('cd-target').value) return alert('Please select a target date and time.'); if (cdInterval) clearInterval(cdInterval); cdTick(); cdInterval = setInterval(cdTick, 1000); }); break; case 'time-zone-converter': function tzConvert() { const from = $('tz-from').value, to = $('tz-to').value, time = $('tz-time').value; if (!time) return renderResult('

Enter source date and time.

'); // Parse as if in source zone const localDate = new Date(time); // Get source time in UTC by formatting in source zone and re-parsing const fromStr = localDate.toLocaleString('en-US', { timeZone: from }); const toStr = localDate.toLocaleString('en-US', { timeZone: to, year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', weekday: 'long' }); const fromStrFmt = localDate.toLocaleString('en-US', { timeZone: from, year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', weekday: 'long' }); renderResult(` ${resultCard('Converted Time', toStr, `${from} → ${to}`, 'teal')}
Source (${from}):
${fromStrFmt}
`); } onChange(tzConvert); $('tz-swap')?.addEventListener('click', () => { const f = $('tz-from').value, t = $('tz-to').value; $('tz-from').value = t; $('tz-to').value = f; tzConvert(); }); break; case 'percentage-calculator': function pcUpdateLabels() { const mode = $('pc-mode').value; const labels = { of: ['X (percentage)', 'Y (whole number)'], is: ['X (part)', 'Y (whole)'], change: ['Original value (X)', 'New value (Y)'], }; $('pc-label-a').textContent = labels[mode][0]; $('pc-label-b').textContent = labels[mode][1]; } $('pc-mode')?.addEventListener('change', pcUpdateLabels); pcUpdateLabels(); onChange(() => { const a = val('pc-a'), b = val('pc-b'), mode = $('pc-mode').value; if (isNaN(a) || isNaN(b)) return renderResult('

Enter both values.

'); let result, label, formula; if (mode === 'of') { result = (a / 100) * b; label = `${a}% of ${b}`; formula = `(${a} ÷ 100) × ${b} = ${result}`; } else if (mode === 'is') { if (b === 0) return renderResult('

Cannot divide by zero.

'); result = (a / b) * 100; label = `${a} is what % of ${b}`; formula = `(${a} ÷ ${b}) × 100 = ${result.toFixed(4)}%`; renderResult(`${resultCard('Result', fmtPct(result), label)}
${formula}
`); return; } else { if (a === 0) return renderResult('

Original value cannot be zero.

'); result = ((b - a) / a) * 100; label = `% change from ${a} to ${b}`; formula = `((${b} − ${a}) ÷ ${a}) × 100 = ${result.toFixed(4)}%`; renderResult(`${resultCard('% Change', fmtPct(result), result >= 0 ? 'Increase' : 'Decrease', result >= 0 ? 'emerald' : 'red')}
${formula}
`); return; } renderResult(`${resultCard('Result', fmtNum(result, 4), label)}
${formula}
`); }); break; case 'average-calculator': onChange(() => { const text = $('av-input').value; const nums = text.split(/[\s,\n]+/).map(s => parseFloat(s)).filter(n => !isNaN(n)); if (nums.length === 0) return renderResult('

Enter numbers separated by commas, spaces, or new lines.

'); const sum = nums.reduce((a, b) => a + b, 0); const mean = sum / nums.length; const sorted = [...nums].sort((a, b) => a - b); const median = sorted.length % 2 === 0 ? (sorted[sorted.length / 2 - 1] + sorted[sorted.length / 2]) / 2 : sorted[Math.floor(sorted.length / 2)]; const counts = {}; let maxCount = 0; let modes = []; nums.forEach(n => { counts[n] = (counts[n] || 0) + 1; if (counts[n] > maxCount) { maxCount = counts[n]; modes = [n]; } else if (counts[n] === maxCount) modes.push(n); }); const mode = maxCount === 1 ? 'No mode' : modes.join(', '); const range = Math.max(...nums) - Math.min(...nums); renderResult(` ${resultCard('Mean (Average)', fmtNum(mean), 'Sum ÷ count', 'emerald')} ${resultCard('Median', fmtNum(median), 'Middle value when sorted')} ${resultCard('Mode', mode.toString(), maxCount > 1 ? `Appears ${maxCount} times` : 'All values are unique')} ${resultCard('Range', fmtNum(range), `Max (${fmtNum(Math.max(...nums))}) − Min (${fmtNum(Math.min(...nums))})`)}
${resultCard('Sum', fmtNum(sum, 4))} ${resultCard('Count', nums.length.toString())}
`); }); break; case 'bmi-calculator': function bmUpdateLabels() { const u = $('bm-units').value; $('bm-weight-label').textContent = u === 'metric' ? 'Weight (kg)' : 'Weight (lbs)'; $('bm-height-label').textContent = u === 'metric' ? 'Height (cm)' : 'Height (inches)'; } $('bm-units')?.addEventListener('change', bmUpdateLabels); bmUpdateLabels(); onChange(() => { const w = val('bm-weight'), h = val('bm-height'), u = $('bm-units').value; if (isNaN(w) || isNaN(h) || h === 0) return renderResult('

Enter weight and height.

'); let bmi; if (u === 'metric') bmi = w / Math.pow(h / 100, 2); else bmi = 703 * w / Math.pow(h, 2); let cat, color; if (bmi < 18.5) { cat = 'Underweight'; color = 'blue'; } else if (bmi < 25) { cat = 'Normal weight'; color = 'emerald'; } else if (bmi < 30) { cat = 'Overweight'; color = 'amber'; } else { cat = 'Obese'; color = 'red'; } // healthy range const hM = u === 'metric' ? h / 100 : h * 0.0254; const loKg = 18.5 * hM * hM, hiKg = 24.9 * hM * hM; const lo = u === 'metric' ? `${loKg.toFixed(1)} kg` : `${(loKg * 2.20462).toFixed(1)} lbs`; const hi = u === 'metric' ? `${hiKg.toFixed(1)} kg` : `${(hiKg * 2.20462).toFixed(1)} lbs`; renderResult(` ${resultCard('Your BMI', fmtNum(bmi, 1), cat, color)}
Healthy weight range for your height:
${lo} – ${hi}
Under
<18.5
Normal
18.5–24.9
Over
25–29.9
Obese
≥30
`); }); break; case 'calorie-calculator': onChange(() => { const age = val('cl-age'), gender = $('cl-gender').value, weight = val('cl-weight'), height = val('cl-height'), activity = parseFloat($('cl-activity').value); if ([age, weight, height].some(v => isNaN(v))) return renderResult('

Fill in all fields.

'); let bmr; if (gender === 'male') bmr = 10 * weight + 6.25 * height - 5 * age + 5; else bmr = 10 * weight + 6.25 * height - 5 * age - 161; const tdee = bmr * activity; renderResult(` ${resultCard('BMR (at rest)', fmtCurrency(Math.round(bmr), 0).replace('$', ''), 'Calories if you slept all day', 'orange')} ${resultCard('TDEE (maintenance)', fmtCurrency(Math.round(tdee), 0).replace('$', ''), 'Calories to maintain weight', 'green')}
${resultCard('Weight Loss', fmtCurrency(Math.round(tdee - 500), 0).replace('$', ''), '~1 lb/week deficit', 'red')} ${resultCard('Muscle Gain', fmtCurrency(Math.round(tdee + 300), 0).replace('$', ''), '~0.5 lb/week surplus', 'emerald')}
Estimates use Mifflin-St Jeor equation. For accurate planning, track intake for 2 weeks and adjust based on actual results. Women should not eat below 1,200 cal/day; men not below 1,500.
`); }); break; case 'unit-converter': const ucData = { length: { base: 'm', units: { m: 1, km: 1000, cm: 0.01, mm: 0.001, mi: 1609.344, yd: 0.9144, ft: 0.3048, in: 0.0254 } }, weight: { base: 'kg', units: { kg: 1, g: 0.001, mg: 0.000001, lb: 0.45359237, oz: 0.028349523, t: 1000 } }, volume: { base: 'l', units: { l: 1, ml: 0.001, 'm³': 1000, gal: 3.785411784, qt: 0.946352946, pt: 0.473176473, cup: 0.2365882365, 'fl oz': 0.0295735296 } }, area: { base: 'm²', units: { 'm²': 1, 'km²': 1000000, 'cm²': 0.0001, 'mi²': 2589988.110336, 'ft²': 0.09290304, 'yd²': 0.83612736, 'acre': 4046.8564224, 'hectare': 10000 } }, speed: { base: 'm/s', units: { 'm/s': 1, 'km/h': 0.277778, 'mph': 0.44704, 'knot': 0.514444, 'ft/s': 0.3048 } }, }; function ucUpdateUnits() { const cat = $('uc-category').value; if (cat === 'temperature') { $('uc-from').innerHTML = ''; $('uc-to').innerHTML = ''; return; } const units = Object.keys(ucData[cat].units); $('uc-from').innerHTML = units.map(u => ``).join(''); $('uc-to').innerHTML = units.map((u, i) => ``).join(''); } $('uc-category')?.addEventListener('change', ucUpdateUnits); ucUpdateUnits(); onChange(() => { const cat = $('uc-category').value, v = val('uc-value'), from = $('uc-from').value, to = $('uc-to').value; if (isNaN(v)) return renderResult('

Enter a value.

'); let result; if (cat === 'temperature') { let c; if (from === 'C') c = v; else if (from === 'F') c = (v - 32) * 5 / 9; else c = v - 273.15; if (to === 'C') result = c; else if (to === 'F') result = c * 9 / 5 + 32; else result = c + 273.15; } else { const base = v * ucData[cat].units[from]; result = base / ucData[cat].units[to]; } renderResult(` ${resultCard('Result', `${fmtNum(result, 6)} ${to}`, `${v} ${from} = ?`, 'violet')}
${v} ${from} = ${fmtNum(result, 6)} ${to}
`); }); break; case 'random-number-generator': $('rn-generate')?.addEventListener('click', () => { const min = parseInt($('rn-min').value), max = parseInt($('rn-max').value), count = Math.min(parseInt($('rn-count').value), 100), unique = $('rn-unique').checked; if (isNaN(min) || isNaN(max)) return renderResult('

Enter min and max.

'); if (min > max) return renderResult('

Min must be less than max.

'); const range = max - min + 1; if (unique && count > range) return renderResult(`

Cannot generate ${count} unique numbers from a range of ${range}.

`); let nums = []; if (unique) { const pool = Array.from({ length: range }, (_, i) => min + i); for (let i = pool.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [pool[i], pool[j]] = [pool[j], pool[i]]; } nums = pool.slice(0, count); } else { for (let i = 0; i < count; i++) nums.push(Math.floor(Math.random() * range) + min); } renderResult(` ${resultCard(count === 1 ? 'Random Number' : 'Random Numbers', nums.join(', '), `Range: ${min}–${max}${unique ? ' • unique' : ''}`, 'slate')} `); }); break; case 'word-counter': onChange(() => { const text = $('wc-input').value; const chars = text.length; const charsNoSpace = text.replace(/\s/g, '').length; const words = text.trim() ? text.trim().split(/\s+/).length : 0; const sentences = text.trim() ? (text.match(/[.!?]+/g) || []).length || 1 : 0; const paragraphs = text.trim() ? text.split(/\n\s*\n/).filter(p => p.trim()).length : 0; const readMin = Math.max(1, Math.ceil(words / 200)); renderResult(`
${resultCard('Words', words.toString(), '', 'rose')} ${resultCard('Characters', chars.toString(), `${charsNoSpace} without spaces`)} ${resultCard('Sentences', sentences.toString())} ${resultCard('Paragraphs', paragraphs.toString())}
${resultCard('Reading Time', `${readMin} min`, 'At 200 words per minute', 'amber')} `); }); break; case 'case-converter': function ccConvert(text, type) { const words = text.split(/[\s_\-]+/).filter(w => w); switch (type) { case 'upper': return text.toUpperCase(); case 'lower': return text.toLowerCase(); case 'title': return text.toLowerCase().replace(/\b\w/g, c => c.toUpperCase()); case 'sentence': return text.toLowerCase().replace(/(^\s*\w|[.!?]\s*\w)/g, c => c.toUpperCase()); case 'camel': return words.map((w, i) => i === 0 ? w.toLowerCase() : w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(''); case 'pascal': return words.map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(''); case 'snake': return words.map(w => w.toLowerCase()).join('_'); case 'kebab': return words.map(w => w.toLowerCase()).join('-'); case 'constant': return words.map(w => w.toUpperCase()).join('_'); } return text; } document.querySelectorAll('.cc-btn').forEach(b => b.addEventListener('click', () => { const text = $('cc-input').value; if (!text) return renderResult('

Enter some text first.

'); const converted = ccConvert(text, b.dataset.case); renderResult(` ${resultCard(b.textContent.trim(), converted, `Applied ${b.dataset.case} case`, 'cyan')}
${converted}
`); $('cc-copy').onclick = async () => { await navigator.clipboard.writeText(converted); $('cc-copy').textContent = '✓ Copied!'; setTimeout(() => $('cc-copy').textContent = 'Copy Output', 1500); }; })); break; case 'lorem-ipsum-generator': const liWords = 'lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua enim ad minim veniam quis nostrud exercitation ullamco laboris nisi aliquip ex ea commodo consequat duis aute irure in reprehenderit voluptate velit esse cillum eu fugiat nulla pariatur excepteur sint occaecat cupidatat non proident sunt culpa qui officia deserunt mollit anim id est laborum'.split(' '); function liSentence() { const len = 8 + Math.floor(Math.random() * 12); const words = []; for (let i = 0; i < len; i++) words.push(liWords[Math.floor(Math.random() * liWords.length)]); words[0] = words[0].charAt(0).toUpperCase() + words[0].slice(1); return words.join(' ') + '.'; } function liParagraph() { const sentences = 3 + Math.floor(Math.random() * 4); return Array.from({ length: sentences }, liSentence).join(' '); } $('li-generate')?.addEventListener('click', () => { const unit = $('li-unit').value, count = parseInt($('li-count').value) || 5, classic = $('li-classic').checked; let result = ''; if (unit === 'paragraphs') { const paras = Array.from({ length: count }, liParagraph); if (classic) paras[0] = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' + paras[0]; result = paras.join('\n\n'); } else if (unit === 'sentences') { result = Array.from({ length: count }, liSentence).join(' '); if (classic) result = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' + result; } else { result = Array.from({ length: count }, () => liWords[Math.floor(Math.random() * liWords.length)]).join(' '); if (classic) result = 'lorem ipsum dolor sit amet consectetur adipiscing elit ' + result; } renderResult(`
${result}
`); $('li-copy').onclick = async () => { await navigator.clipboard.writeText(result); $('li-copy').textContent = '✓ Copied!'; setTimeout(() => $('li-copy').textContent = 'Copy Text', 1500); }; }); break; case 'text-repeater': $('tr-generate')?.addEventListener('click', () => { const text = $('tr-text').value; const count = Math.min(parseInt($('tr-count').value) || 1, 10000); const sep = $('tr-sep').value === '\\n' ? '\n' : $('tr-sep').value === '\\t' ? '\t' : $('tr-sep').value; if (!text) return renderResult('

Enter text to repeat.

'); const result = Array(count).fill(text).join(sep); renderResult(` ${resultCard('Repeated Text', `${count} times`, `Length: ${result.length} chars`, 'fuchsia')}
${result}
`); $('tr-copy').onclick = async () => { await navigator.clipboard.writeText(result); $('tr-copy').textContent = '✓ Copied!'; setTimeout(() => $('tr-copy').textContent = 'Copy Result', 1500); }; }); break; case 'slug-generator': function sgGenerate() { const text = $('sg-input').value; const sep = $('sg-sep').value; const removeStop = $('sg-stopwords').checked; if (!text) return renderResult('

Enter text to slugify.

'); let slug = text.toLowerCase().trim() .normalize('NFD').replace(/[\u0300-\u036f]/g, '') // strip accents .replace(/[^a-z0-9\s-]/g, '') // remove special chars .replace(/[\s_]+/g, sep) // spaces/underscores → sep .replace(new RegExp(`\\${sep}+`, 'g'), sep) // collapse multiple seps .replace(new RegExp(`^\\${sep}|\\${sep}$`, 'g'), ''); // trim seps if (removeStop) { const stop = ['a', 'an', 'the', 'of', 'in', 'on', 'at', 'to', 'for', 'and', 'or', 'but']; slug = slug.split(sep).filter(w => !stop.includes(w)).join(sep); } renderResult(` ${resultCard('URL Slug', slug, `${slug.length} characters`, 'indigo')}
/${slug}
Use as the URL path after your domain (e.g., example.com/${slug}).
`); $('sg-copy').onclick = async () => { await navigator.clipboard.writeText(slug); $('sg-copy').textContent = '✓ Copied!'; setTimeout(() => $('sg-copy').textContent = 'Copy Slug', 1500); }; } onChange(sgGenerate); break; default: renderResult('

Calculator for this tool is loading. Refer to the formula and examples below.

'); } })();