jsx
/** App.jsx - FIRE calculator for South African context */
const { useState, useMemo } = React;

function InputRow({ label, value, onChange, step = 1, min, max, icon }) {
  return (
    <div className="grid grid-cols-2 gap-3 items-center">
      <label className="text-sm text-slate-700 flex items-center gap-2">
        {icon && <i data-feather={icon} className="w-4 h-4"></i>}
        {label}
      </label>
      <input
        className="p-2 border rounded w-full focus:ring-2 focus:ring-primary-500 focus:border-transparent"
        type="number"
        value={value}
        step={step}
        min={min}
        max={max}
        onChange={(e) => onChange(Number(e.target.value))}
      />
    </div>
  );
}

function ResultsCard({ title, children, icon }) {
  return (
    <div className="bg-white/90 p-4 rounded-lg shadow-md border border-gray-100 hover:shadow-lg transition-shadow">
      <h3 className="font-semibold text-slate-800 mb-2 flex items-center gap-2">
        {icon && <i data-feather={icon} className="w-4 h-4"></i>}
        {title}
      </h3>
      <div className="text-slate-700">{children}</div>
    </div>
  );
}

function App() {
  // Defaults tuned for South Africa
  const [age, setAge] = useState(30);
  const [targetAge, setTargetAge] = useState(50);
  const [monthlyIncome, setMonthlyIncome] = useState(25000);
  const [monthlyExpenses, setMonthlyExpenses] = useState(15000);
  const [desiredMonthly, setDesiredMonthly] = useState(15000);
  const [currentInvestments, setCurrentInvestments] = useState(50000);
  const [monthlyContribution, setMonthlyContribution] = useState(3000);
  const [expectedReturn, setExpectedReturn] = useState(8); // nominal %
  const [inflation, setInflation] = useState(5);
  const [swr, setSWR] = useState(4);

  const years = Math.max(0, targetAge - age);

  const results = useMemo(() => {
    // compute real return (decimal)
    const rReal = window.realReturn(expectedReturn, inflation);

    const fv1 = window.fvLump(currentInvestments, rReal, years);
    const fv2 = window.fvMonthly(monthlyContribution, rReal, years);
    const portfolio = fv1 + fv2;

    const desiredAnnual = desiredMonthly * 12;
    const target = window.fireNumber(desiredAnnual, swr);

    const shortfall = portfolio - target;
    const estAnnualWithdraw = portfolio * (swr / 100);
    const estMonthlyWithdraw = estAnnualWithdraw / 12;

    return {
      years,
      rReal,
      fvLump: fv1,
      fvMonthly: fv2,
      portfolio,
      target,
      shortfall,
      estMonthlyWithdraw,
      desiredAnnual
    };
  }, [age, targetAge, currentInvestments, monthlyContribution, expectedReturn, inflation, swr, desiredMonthly]);

  return (
    <div className="max-w-3xl mx-auto">
      <header className="mb-6 text-center">
        <h1 className="text-3xl font-bold text-primary-800">Rand Freedom Tracker</h1>
        <p className="text-sm text-secondary-600 mt-2">
          <i data-feather="map" className="inline mr-1 w-4 h-4"></i>
          Chart your path to financial independence in South Africa
        </p>
      </header>

      <main className="space-y-6">
        <section className="bg-white/80 p-4 rounded-lg shadow-sm grid gap-3 border border-gray-200">
          <h2 className="font-semibold text-primary-700 flex items-center gap-2">
            <i data-feather="sliders" className="w-5 h-5"></i>
            Your Financial Parameters
          </h2>
          
          <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
            <div className="space-y-3">
              <InputRow label="Current age" value={age} onChange={setAge} min={18} icon="user" />
              <InputRow label="Target FIRE age" value={targetAge} onChange={setTargetAge} min={age} icon="target" />
              <InputRow label="Monthly income (R)" value={monthlyIncome} onChange={setMonthlyIncome} icon="dollar-sign" />
              <InputRow label="Monthly expenses (R)" value={monthlyExpenses} onChange={setMonthlyExpenses} icon="shopping-cart" />
            </div>
            <div className="space-y-3">
              <InputRow label="Desired freedom budget (R)" value={desiredMonthly} onChange={setDesiredMonthly} icon="award" />
              <InputRow label="Current investments (R)" value={currentInvestments} onChange={setCurrentInvestments} icon="pie-chart" />
              <InputRow label="Monthly contribution (R)" value={monthlyContribution} onChange={setMonthlyContribution} icon="plus-circle" />
              <InputRow label="Expected return (%)" value={expectedReturn} onChange={setExpectedReturn} step={0.1} icon="trending-up" />
              <InputRow label="Inflation (%)" value={inflation} onChange={setInflation} step={0.1} icon="bar-chart-2" />
              <InputRow label="Withdrawal rate (%)" value={swr} onChange={setSWR} step={0.1} icon="percent" />
            </div>
          </div>
        </section>

        <section className="grid grid-cols-1 md:grid-cols-2 gap-4">
          <ResultsCard title={`Portfolio at age ${targetAge}`} icon="dollar-sign">
            <div className="text-2xl font-semibold text-primary-600">{window.formatR(results.portfolio)}</div>
            <div className="text-xs text-slate-500 mt-1">Includes lump sum + future monthly contributions</div>
          </ResultsCard>

          <ResultsCard title="Your FIRE Number" icon="flag">
            <div className="text-2xl font-semibold text-secondary-600">{window.formatR(results.target)}</div>
            <div className="text-xs text-slate-500 mt-1">Based on desired monthly budget × 12 and SWR</div>
          </ResultsCard>

          <ResultsCard title="Surplus / Shortfall" icon={results.shortfall >= 0 ? "thumbs-up" : "thumbs-down"}>
            <div className={`text-2xl font-semibold ${results.shortfall >= 0 ? 'text-emerald-600' : 'text-rose-600'}`}>
              {window.formatR(results.shortfall)}
            </div>
            <div className="text-xs text-slate-500 mt-1">
              {results.shortfall >= 0 ? 'Projected surplus' : 'Projected shortfall'}
            </div>
          </ResultsCard>

          <ResultsCard title="Estimated monthly income" icon="coffee">
            <div className="text-2xl font-semibold text-primary-600">{window.formatR(results.estMonthlyWithdraw)}</div>
            <div className="text-xs text-slate-500 mt-1">Using SWR = {swr}%</div>
          </ResultsCard>
        </section>

        <section className="bg-white/90 p-4 rounded-lg shadow border border-gray-100">
          <h3 className="font-semibold mb-2 text-secondary-700 flex items-center gap-2">
            <i data-feather="alert-circle" className="w-4 h-4"></i>
            Strategic Insights
          </h3>
          <ul className="text-sm text-slate-700 list-disc pl-5 space-y-1">
            <li>Every 1% higher return can increase your portfolio by {window.formatR(results.fvLump * 0.01 * years)}</li>
            <li>Increasing monthly contributions by R1,000 would grow your portfolio by {window.formatR(window.fvMonthly(1000, results.rReal, years))}</li>
            <li>Delaying retirement by 1 year adds {window.formatR(window.fvMonthly(monthlyContribution, results.rReal, 1) + window.fvLump(results.portfolio, results.rReal, 1) - results.portfolio)}</li>
          </ul>
        </section>
      </main>

      <footer className="mt-8 text-xs text-center text-slate-500 border-t pt-4">
        <div className="flex justify-center gap-4">
          <a href="#" className="hover:text-primary-600 flex items-center gap-1">
            <i data-feather="github" className="w-3 h-3"></i> Source
          </a>
          <a href="#" className="hover:text-primary-600 flex items-center gap-1">
            <i data-feather="help-circle" className="w-3 h-3"></i> Help
          </a>
          <a href="#" className="hover:text-primary-600 flex items-center gap-1">
            <i data-feather="share-2" className="w-3 h-3"></i> Share
          </a>
        </div>
        <div className="mt-2">All amounts displayed in ZAR. Calculations use real return after inflation.</div>
      </footer>
    </div>
  );
}

// Render
ReactDOM.createRoot(document.getElementById('root')).render(<App />);

</html>