Freelance

Invoice Generator

Create professional, downloadable invoices with line items, tax, and totals.

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 Invoice Generator

Every freelancer and small business owner needs to send invoices, but subscription invoicing software can cost $20–$50 per month and lock your data behind logins. Our free Invoice Generator lets you create professional, print-ready invoices in under two minutes — directly in your browser, with no signup, no watermark, and no monthly fee. Add your business details, itemize your services, apply tax and discounts, and the calculator updates totals instantly. When you are done, use your browser print function to save the invoice as a PDF. Your data never leaves your device, which makes this tool ideal for sensitive client information.

Deep Dive: Understanding the Concept

For freelancers and independent contractors, invoice generator represents one of the most consequential financial skills to master. Unlike traditional employees who have employer-provided tools, benefits, and tax withholding, freelancers must self-manage every aspect of their financial lives. This includes not just the calculation itself, but the strategic decisions around pricing, time allocation, and business structure that flow from it.

The freelancer economy has grown dramatically — by 2027, an estimated 86.5 million Americans will freelance (50.9% of the workforce, per Upwork's Freelance Forward study). Yet most freelancers earn 20–40% less than they could because they lack basic financial literacy around pricing, taxes, and time management. Mastering calculations like this one is the difference between treating freelancing as a hobby and building a sustainable business.

What makes freelance financial calculations unique is the blending of personal and business finances. A freelancer's "salary" is whatever they choose to pay themselves from business profits. Their "benefits" are self-funded (health insurance, retirement, paid time off). Their "employer taxes" come out of their own pocket (self-employment tax of 15.3%). This creates complexity that traditional employee financial tools cannot handle — you need freelancer-specific calculations.

The emotional dimension of freelance finances is often overlooked. Traditional financial advice assumes steady income and clear boundaries between work and personal life. Freelancers experience income volatility (feast-or-famine cycles), decision fatigue (every choice is yours to make), and identity blur (you are both the business and the worker). Sound financial calculations provide an anchor — numbers that ground decisions in reality rather than anxiety.

How to Use This Calculator

  1. 1

    Fill in your business name, address, and contact details at the top of the invoice.

  2. 2

    Enter your client's name and billing address.

  3. 3

    Add an invoice number, invoice date, and due date.

  4. 4

    For each line item, enter a description, quantity, and unit price — click "Add Line Item" to add more rows.

  5. 5

    Optionally set a tax rate and discount percentage. The total updates automatically.

  6. 6

    Click "Print / Save as PDF" to download or print your invoice.

The Formula Explained

The invoice computes: Subtotal = sum of (Quantity × Unit Price) for all line items. Tax Amount = Subtotal × (Tax Rate ÷ 100). Discount Amount = Subtotal × (Discount ÷ 100). Total = Subtotal − Discount + Tax. The generator uses your browser's native print-to-PDF capability so you retain full control over the final document.

Worked Example

A freelance writer bills a client for blog content. Line 1: "4 blog posts (1,200 words each)" — Quantity 4, Unit Price $250 → $1,000. Line 2: "SEO keyword research" — Quantity 1, Unit Price $200 → $200. Subtotal = $1,200. Discount = 0%. Tax (VAT 19%) = $228. Total = $1,428. The invoice is ready in under a minute, looks professional, and can be emailed as a PDF attachment or sent via a shared link.

Real-World Scenarios

Professional Use Case

A marketing manager uses the invoice generator to optimize a quarterly campaign. By inputting real business data, they identify the optimal configuration that improves results by 15–25% compared to previous approaches. The 30-minute investment in proper calculation pays dividends throughout the quarter.

Key takeaway: For professional applications, the time invested in proper calculation typically returns 5–10x in improved outcomes.

Educational Context

A student learning about freelance concepts uses this tool to verify homework, explore what-if scenarios, and build intuition for how inputs affect outputs. Interactive calculation builds deeper understanding than static textbook examples.

Key takeaway: For learning, calculators that show intermediate steps and allow rapid iteration build intuition faster than manual calculation methods.

Personal Decision

An individual uses the invoice generator for a significant personal decision — comparing options, understanding trade-offs, and building confidence in their choice. The structured calculation removes emotion and reveals the mathematically optimal path.

Key takeaway: For personal decisions, structured calculation surfaces options that emotional reasoning might miss. Even when you "trust your gut," running the numbers first improves decision quality.

Common Mistakes to Avoid

Forgetting self-employment tax (15.3%)

Freelancers pay both halves of FICA — 12.4% Social Security + 2.9% Medicare = 15.3% on net business income. This is on top of income tax. Many new freelancers are shocked when they owe $7,500+ in SE tax on $50,000 income. Always budget for this.

Not setting aside money for taxes throughout the year

Unlike W-2 employees, freelancers have no tax withholding. You must self-set-aside 25–35% of every payment for federal tax, plus state tax if applicable. Failure to do so leads to a devastating tax bill in April plus underpayment penalties.

Pricing based on what you "think sounds right"

Most freelancers price emotionally — picking a rate that feels comfortable without doing the math. This systematically underprices by 30–50%. Calculate your minimum viable rate using the Hourly Rate Calculator, then add 20–30% buffer for negotiation and unexpected costs.

Tracking time imprecisely

Memory-based time tracking underestimates actual work hours by 15–25%. Use a time tracking tool (Toggl, Harvest, or even our Time Tracking Calculator) to log hours in real-time. Accurate time data is the foundation of profitable pricing.

Mixing personal and business finances

Commingled finances create bookkeeping nightmares, complicate tax filing, and weaken legal liability protection for LLCs/S-corps. Open a separate business checking account on day one. Run all business income and expenses through it.

Best Practices from Experts

Set aside 30% of every payment for taxes immediately

Open a separate "tax savings" account. The moment a client payment arrives, transfer 30% to this account. This prevents the most common freelancer financial disaster: a tax bill you cannot pay.

Maintain a 3–6 month emergency fund separate from business reserves

Freelance income is volatile. A 3–6 month personal emergency fund covers your living expenses during slow periods. Additionally, keep 1–2 months of business expenses in a business savings account for slow client months.

Track every business expense, even small ones

Software subscriptions, home office allocation, professional development, travel, equipment — all are potentially tax-deductible. Use accounting software (QuickBooks Self-Employed, Wave, FreshBooks) to capture expenses automatically. Most freelancers leave $2,000–$5,000 in deductions unclaimed each year.

Increase rates annually, even with existing clients

Cost of living rises 2–4% annually. Your rates should rise at least as fast. Communicate annual increases 60 days in advance with a value justification (new skills, additional services, market rate adjustment). Most clients accept increases of 5–10% without complaint.

Pay yourself a consistent salary, not "whatever is left"

Determine a monthly salary that covers your living expenses plus savings goals. Pay yourself this amount on a fixed schedule (1st and 15th of each month, for example). Surplus business income stays in the business as reserves or reinvestment. This smooths income volatility and forces discipline.

Industry Benchmarks & Reference Data

Freelance business benchmarks vary widely by industry and experience level. The following represent typical ranges for established freelancers in the United States:

Average freelance hourly rate (all industries) $20–$150/hour (median ~$50/hour per Upwork 2024)
Freelance developer rates $60–$200/hour (junior to senior)
Freelance designer rates $50–$150/hour
Freelance writer rates $40–$200/hour (specialized content commands premium)
Freelance consultant rates $100–$500/hour (strategy and specialized expertise)
Billable utilization rate (target) 60–75% of working hours (rest for admin/sales)
Effective tax rate for $75k freelancer income 25–35% (federal + SE tax + state, varies by state)
Annual business expenses (typical) $5,000–$15,000 (software, equipment, professional services)
Client acquisition cost (freelancers) $200–$2,000 per new client (marketing + sales time)
Average client retention 12–24 months (longer for retainer relationships)

Sources: Upwork Freelance Forward 2024, Freelancers Union Annual Report, Contently Rates Survey, BLS Occupational Outlook Handbook. Rates vary significantly by location, niche, and experience.

When to Use This Tool

Freelancers use this tool to bill clients without paying for accounting software. Small business owners use it to send one-off invoices. Consultants use it for project milestones. Contractors use it for hourly billing. International freelancers use it to apply country-specific VAT or GST. Anyone who needs a clean, professional invoice fast — without setup overhead — benefits from this generator.

Related Concepts You Should Know

Self-Employment Tax

The 15.3% tax freelancers pay on net business income, covering both employer and employee portions of Social Security (12.4%) and Medicare (2.9%). Deductible: half of SE tax reduces adjusted gross income.

Quarterly Estimated Tax

Federal tax payments freelancers must make four times per year (April 15, June 15, September 15, January 15) to cover income tax and SE tax. Failure triggers underpayment penalties.

Independent Contractor vs. Employee

Legal classification affecting tax treatment, benefits, and labor law protections. IRS uses 20-factor test. Misclassification (treating employees as contractors) triggers significant penalties.

Pass-Through Taxation

Tax structure where business income "passes through" to owner's personal tax return (sole proprietorship, partnership, S-corp, LLC default). Qualified Business Income deduction can reduce taxable business income by up to 20%.

Retirement Plans for Self-Employed

Solo 401(k), SEP-IRA, and SIMPLE IRA allow freelancers to contribute significantly more than traditional IRAs. Solo 401(k) allows $23,000 employee + $46,000 employer = $69,000 total (2024 limits).

Pro Tips & Advanced Insights

Always quote project rates, not hourly rates, for fixed-scope work. Project rates capture the value you provide (which often exceeds time × rate). Hourly rates penalize you for being efficient — the faster you complete work, the less you earn.

Build a "rate floor" below which you will not accept work. Calculate this as your minimum viable hourly rate (use our Hourly Rate Calculator). Decline projects below this floor, even during slow periods. Bad clients at low rates consume capacity that prevents finding good clients at proper rates.

Use "value-based pricing" for high-impact work. If a project will generate $100,000 in value for the client, charging $10,000 (10% of value) is reasonable even if it only takes you 20 hours ($500/hour effective rate). This requires understanding client economics and confidence to price based on value delivered, not time spent.

Maintain a "client concentration" metric — no single client should represent more than 30% of your revenue. Client concentration creates cash flow risk if that client leaves. Diversify by acquiring new clients even when busy.

Invest in professional development annually. Allocate 5–10% of revenue to courses, conferences, and certifications. The ROI on skill development (new specialties, higher rates, better clients) typically exceeds 200% within 12 months.

Frequently Asked Questions

Is the invoice generator really free?
Yes. There is no signup, no subscription, no usage limit, and no watermark on the invoice. You can generate as many invoices as you want. We do not store your data — everything happens in your browser.
How do I save the invoice as a PDF?
Click the "Print / Save as PDF" button. Your browser's print dialog will open. Select "Save as PDF" as the destination and click Save. The PDF will be formatted exactly as it appears in the preview.
Can I add my logo to the invoice?
The current version focuses on a clean text-based design that works for any business. Logo upload is a planned feature. For now, you can paste a logo URL or describe your logo in the business name field, then use your browser's print preview to verify appearance.
What tax rate should I use?
It depends on your country and tax status. Common examples: US sales tax varies by state (0–10%); EU VAT ranges from 17–27% depending on the country; UK VAT is 20%; Australia GST is 10%; India GST is 18% for most services. Consult a local tax advisor if you are unsure.
Is my data secure?
Yes. All calculations and form inputs happen entirely in your browser. Nothing is uploaded to a server. When you close or refresh the page, the data is gone. This is by design — your client information stays private.
How accurate is the invoice generator?
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 invoice generator 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 invoice generator 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 invoice generator?
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 invoice generator?
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 invoice generator?
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.

'); } })();