diff --git a/src/components/Header.tsx b/src/components/Header.tsx
new file mode 100644
index 0000000..11a8ee9
--- /dev/null
+++ b/src/components/Header.tsx
@@ -0,0 +1,225 @@
+'use client';
+
+import React, { useState, useCallback } from 'react';
+import { useSimulation } from '@/contexts/SimulationContext';
+import { encodeState, copyToClipboard, exportCSV } from '@/lib/sharing';
+import { formatCurrency } from '@/lib/formatters';
+import { useTheme } from '@/hooks/useTheme';
+
+// ─── Icons ──────────────────────────────────────────────────────────────────
+
+function ShareIcon() {
+ return (
+
+ );
+}
+
+function DownloadIcon() {
+ return (
+
+ );
+}
+
+function PrintIcon() {
+ return (
+
+ );
+}
+
+function SunIcon() {
+ return (
+
+ );
+}
+
+function MoonIcon() {
+ return (
+
+ );
+}
+
+function MonitorIcon() {
+ return (
+
+ );
+}
+
+// ─── Header ─────────────────────────────────────────────────────────────────
+
+export default function Header() {
+ const { state, results, reset } = useSimulation();
+ const { theme, cycle } = useTheme();
+ const [shareStatus, setShareStatus] = useState<'idle' | 'copied' | 'error'>('idle');
+
+ const handleShare = useCallback(async () => {
+ try {
+ const hash = await encodeState(state);
+ const url = `${window.location.origin}${window.location.pathname}#${hash}`;
+ const success = await copyToClipboard(url);
+ if (success) {
+ setShareStatus('copied');
+ setTimeout(() => setShareStatus('idle'), 2500);
+ } else {
+ setShareStatus('error');
+ setTimeout(() => setShareStatus('idle'), 2500);
+ }
+ } catch {
+ setShareStatus('error');
+ setTimeout(() => setShareStatus('idle'), 2500);
+ }
+ }, [state]);
+
+ const handleExportCSV = useCallback(() => {
+ const headers = [
+ 'Année',
+ 'Intérêts (€)',
+ 'Capital (€)',
+ 'Assurance (€)',
+ 'Restant dû (€)',
+ 'Total annuel (€)',
+ 'Mensualité projetée (€)',
+ 'Loyer actuel (€)',
+ 'Différence (€)',
+ 'Prix revente (€)',
+ 'Plus/moins-value (€)',
+ ];
+
+ const rows = results.yearlySummaries
+ .filter((y) => y.year <= results.maxDuration)
+ .map((y) => [
+ y.year,
+ y.totalInterest,
+ y.totalPrincipal,
+ y.totalInsurance,
+ y.totalRemaining,
+ y.yearlyTotal,
+ y.monthlyTotal,
+ y.currentMonthlyTotal,
+ y.difference,
+ y.resalePrice,
+ y.netResale,
+ ]);
+
+ exportCSV(headers, rows);
+ }, [results]);
+
+ const handlePrint = useCallback(() => {
+ window.print();
+ }, []);
+
+ return (
+
+
+
+ {/* Logo */}
+
+
+
+ S
+
+
+
SimO
+
+ Simulateur de prêt immobilier
+
+
+
+
+
+ {/* Monthly payment quick view */}
+
+
+
+ {formatCurrency(results.projectedMonthlyTotal)}
+
+
Mensualité
+
+
+
+
+ {formatCurrency(results.grandTotal)}
+
+
Coût total
+
+
+
+ {/* Actions */}
+
+
+
+
+
+
+
+
+
+ {/* Theme toggle */}
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/InputForms.tsx b/src/components/InputForms.tsx
new file mode 100644
index 0000000..2e1cf71
--- /dev/null
+++ b/src/components/InputForms.tsx
@@ -0,0 +1,311 @@
+'use client';
+
+import React from 'react';
+import { useSimulation } from '@/contexts/SimulationContext';
+import { CollapsibleSection, NumberInput, RateInput, Toggle } from './ui';
+import { formatCurrency } from '@/lib/formatters';
+
+// ─── Icons ──────────────────────────────────────────────────────────────────
+
+function HomeIcon() {
+ return (
+
+ );
+}
+
+function WalletIcon() {
+ return (
+
+ );
+}
+
+function ArrowTrendIcon() {
+ return (
+
+ );
+}
+
+function CogIcon() {
+ return (
+
+ );
+}
+
+// ─── Project Form ───────────────────────────────────────────────────────────
+
+export function ProjectForm() {
+ const { state, results, setField, setPropertyType } = useSimulation();
+
+ return (
+ }
+ badge={
+ {state.propertyType === 'neuf' ? 'Neuf' : 'Ancien'}
+ }
+ >
+
+ {/* Neuf / Ancien toggle */}
+
+ setPropertyType(v as 'neuf' | 'ancien')}
+ />
+
+
+ {/* Price */}
+
setField('propertyPrice', v)}
+ suffix="€"
+ step={1000}
+ min={0}
+ />
+
+ {/* Fees grid */}
+
+ setField('notaryFeesRate', v)}
+ hint={formatCurrency(results.notaryFees)}
+ />
+ setField('guaranteeRate', v)}
+ hint={formatCurrency(results.guaranteeFees)}
+ />
+ setField('bankFees', v)}
+ suffix="€"
+ step={100}
+ />
+ setField('worksCost', v)}
+ suffix="€"
+ step={500}
+ />
+
+
+ {/* Down payment */}
+ setField('downPayment', v)}
+ suffix="€"
+ step={1000}
+ min={0}
+ />
+
+ {/* Summary */}
+
+
+ Financement total
+
+ {formatCurrency(results.totalFinancing)}
+
+
+
+ Crédit nécessaire
+ {formatCurrency(results.totalCredit)}
+
+
+
+
+ );
+}
+
+// ─── Current Costs Form ─────────────────────────────────────────────────────
+
+export function CurrentCostsForm() {
+ const { state, setField } = useSimulation();
+
+ const total =
+ state.currentRent +
+ state.currentUtilities +
+ state.currentCharges +
+ state.currentHomeInsurance;
+
+ return (
+ }
+ badge={{formatCurrency(total)}/mois}
+ defaultOpen={false}
+ >
+
+
setField('currentRent', v)}
+ suffix="€/mois"
+ step={10}
+ />
+ setField('rentEvolutionRate', v)}
+ />
+
+ setField('currentUtilities', v)}
+ suffix="€"
+ step={5}
+ />
+ setField('currentCharges', v)}
+ suffix="€"
+ step={5}
+ />
+
+ setField('currentHomeInsurance', v)}
+ suffix="€/mois"
+ step={1}
+ />
+
+
+
+ Total mensuel actuel
+ {formatCurrency(total)}
+
+
+
+
+ );
+}
+
+// ─── Projected Costs Form ───────────────────────────────────────────────────
+
+export function ProjectedCostsForm() {
+ const { state, results, setField } = useSimulation();
+
+ const chargesTotal =
+ state.projectedCharges +
+ state.propertyTax +
+ state.projectedUtilities +
+ state.projectedHomeInsurance;
+
+ return (
+ }
+ badge={{formatCurrency(chargesTotal)}/mois}
+ defaultOpen={false}
+ >
+
+
+ setField('projectedCharges', v)}
+ suffix="€"
+ step={5}
+ />
+ setField('propertyTax', v)}
+ suffix="€/mois"
+ step={5}
+ hint="Montant mensuel"
+ />
+ setField('projectedUtilities', v)}
+ suffix="€"
+ step={5}
+ />
+ setField('projectedHomeInsurance', v)}
+ suffix="€"
+ step={1}
+ />
+
+
+
+
+ Charges mensuelles
+ {formatCurrency(chargesTotal)}
+
+
+ Assurance prêt
+
+ {formatCurrency(
+ results.loanResults.reduce((s, lr) => s + lr.monthlyInsurance, 0)
+ )}
+
+
+
+
+ Total mensuel projeté
+ {formatCurrency(results.projectedMonthlyTotal)}
+
+
+
+
+ );
+}
+
+// ─── Resale Form ────────────────────────────────────────────────────────────
+
+export function ResaleForm() {
+ const { state, setField } = useSimulation();
+
+ return (
+ }
+ defaultOpen={false}
+ >
+
+ setField('firstYearDepreciation', -Math.abs(v))}
+ hint="Perte de valeur la première année"
+ />
+ setField('annualAppreciation', v)}
+ hint="Croissance moyenne du prix/an"
+ />
+ setField('saleFees', v)}
+ hint="Agence + notaire vendeur"
+ />
+
+
+ );
+}
diff --git a/src/components/LoansForm.tsx b/src/components/LoansForm.tsx
new file mode 100644
index 0000000..285eb37
--- /dev/null
+++ b/src/components/LoansForm.tsx
@@ -0,0 +1,336 @@
+'use client';
+
+import React, { useState } from 'react';
+import { useSimulation } from '@/contexts/SimulationContext';
+import { CollapsibleSection, NumberInput, RateInput } from './ui';
+import { formatCurrency } from '@/lib/formatters';
+import { LOAN_PRESETS } from '@/lib/defaults';
+import type { LoanInput } from '@/lib/types';
+
+// ─── Icons ──────────────────────────────────────────────────────────────────
+
+function BankIcon() {
+ return (
+
+ );
+}
+
+function PlusIcon() {
+ return (
+
+ );
+}
+
+function TrashIcon() {
+ return (
+
+ );
+}
+
+function ChevronIcon({ open }: { open: boolean }) {
+ return (
+
+ );
+}
+
+// ─── Add Loan Menu ──────────────────────────────────────────────────────────
+
+function AddLoanMenu({ onAdd }: { onAdd: (loan: LoanInput) => void }) {
+ const { state } = useSimulation();
+ const [open, setOpen] = useState(false);
+
+ const availablePresets = LOAN_PRESETS.filter((p) => {
+ if (p.neufOnly && state.propertyType === 'ancien') return false;
+ return true;
+ });
+
+ return (
+
+
+
+ {open && (
+ <>
+
setOpen(false)} />
+
+ {availablePresets.map((preset) => (
+
+ ))}
+
+ >
+ )}
+
+ );
+}
+
+// ─── Loan Card ──────────────────────────────────────────────────────────────
+
+function LoanCard({
+ loan,
+ effectiveAmount,
+}: {
+ loan: LoanInput;
+ effectiveAmount: number;
+}) {
+ const { updateLoan, removeLoan, toggleLoan } = useSimulation();
+ const [expanded, setExpanded] = useState(true);
+
+ const amount = loan.isMainLoan ? effectiveAmount : loan.amount;
+ const monthlyRate = loan.rate / 12;
+ const months = (loan.duration - loan.deferral) * 12;
+ let monthlyPayment = 0;
+ if (months > 0 && amount > 0) {
+ if (monthlyRate === 0) {
+ monthlyPayment = amount / months;
+ } else {
+ const factor = Math.pow(1 + monthlyRate, months);
+ monthlyPayment = (amount * monthlyRate * factor) / (factor - 1);
+ }
+ }
+ const monthlyInsurance = amount * loan.insuranceRate / 12;
+ const totalMonthly = monthlyPayment + monthlyInsurance;
+
+ return (
+
+ {/* Header */}
+
+ {/* Enable/Disable checkbox */}
+
toggleLoan(loan.id)}
+ className="rounded border-slate-300 text-blue-600 focus:ring-blue-500
+ h-4 w-4 cursor-pointer"
+ />
+
+ {/* Name */}
+
+
+
+ {/* Body */}
+ {expanded && loan.enabled && (
+
+
+ {/* Name input */}
+
+
+ updateLoan(loan.id, 'name', e.target.value)}
+ />
+
+
+ {/* Amount (only for non-main loans) */}
+ {!loan.isMainLoan && (
+
updateLoan(loan.id, 'amount', v)}
+ suffix="€"
+ step={1000}
+ min={0}
+ />
+ )}
+
+ {loan.isMainLoan && (
+
+ Montant calculé automatiquement : {formatCurrency(effectiveAmount)}
+
+ = Crédit total − Somme des autres prêts
+
+ )}
+
+ {/* Rate + Duration */}
+
+ updateLoan(loan.id, 'rate', v)}
+ />
+ updateLoan(loan.id, 'duration', v)}
+ suffix="ans"
+ step={1}
+ min={1}
+ max={30}
+ />
+
+
+ {/* Deferral + Insurance */}
+
+ updateLoan(loan.id, 'deferral', v)}
+ suffix="ans"
+ step={1}
+ min={0}
+ max={loan.duration}
+ hint="Période sans remboursement"
+ />
+ updateLoan(loan.id, 'insuranceRate', v)}
+ hint={`${formatCurrency(monthlyInsurance)}/mois`}
+ />
+
+
+ {/* Monthly summary */}
+
+
+ Mensualité hors assurance
+ {formatCurrency(monthlyPayment, 2)}
+
+
+ Assurance
+ {formatCurrency(monthlyInsurance, 2)}
+
+
+
+ Total mensuel
+ {formatCurrency(totalMonthly, 2)}
+
+
+
+ {/* Delete button */}
+ {!loan.isMainLoan && (
+
+ )}
+
+
+ )}
+
+ );
+}
+
+// ─── Loans Form ─────────────────────────────────────────────────────────────
+
+export default function LoansForm() {
+ const { state, results, addLoan } = useSimulation();
+
+ const totalMonthly = results.loanResults.reduce(
+ (sum, lr) => sum + lr.monthlyPayment + lr.monthlyInsurance,
+ 0
+ );
+
+ return (
+
}
+ badge={
{formatCurrency(totalMonthly)}/mois}
+ >
+
+ {/* Loan list */}
+ {state.loans.map((loan) => {
+ const loanResult = results.loanResults.find((lr) => lr.loan.id === loan.id);
+ return (
+
+ );
+ })}
+
+ {/* Add loan */}
+
+
+ {/* Global summary */}
+
+
+ Capital emprunté
+ {formatCurrency(results.totalCredit)}
+
+
+ Total intérêts
+ {formatCurrency(results.totalInterest)}
+
+
+ Total assurance
+ {formatCurrency(results.totalInsurance)}
+
+
+
+ Coût total du crédit
+ {formatCurrency(results.grandTotal)}
+
+
+
+
+ );
+}