// ─────────────────────────────────────────────────────────────────────────
// calculator.jsx — Main credit calculator + tile system for home page
// ─────────────────────────────────────────────────────────────────────────
const { useState: useStateC, useEffect: useEffectC, useMemo: useMemoC } = React;
/* ────────── Mini calculator (compact, for home bento) ────────── */
function MiniCalculator({ country, navigate, lang = "pl" }) {
const c = window.COUNTRIES.find(x => x.code === country) || window.COUNTRIES[0];
const T = (k, fb) => window.t(lang, `calc.${k}`, fb);
const [amount, setAmount] = useStateC(400000);
const [years, setYears] = useStateC(25);
const [type, setType] = useStateC("mortgage");
const months = years * 12;
const rate = type === "mortgage" ? c.avg : (c.avg + 6.5);
const monthly = window.annuity(amount, rate, months);
const total = monthly * months;
const interest = total - amount;
return (
{/* Form */}
{mode === "mortgage" ? T("typeMortgage", "Mortgage") : T("typeCash", "Cash Loan")}
{T("params", "Loan Parameters")}
{c.flag} {c.name} · {c.rateName} {c.rate}%
setAmount(Math.max(1000, +e.target.value || 0))}
style={{ fontFamily: "var(--font-mono)", fontWeight: 700, fontSize: 18 }} />
setAmount(+e.target.value)} style={rangeStyle()} />
setYears(mode === "mortgage" ? Math.max(1, +e.target.value || 1) : Math.max(1, +e.target.value || 1) / 12)}
style={{ fontFamily: "var(--font-mono)", fontWeight: 700, fontSize: 18 }} />
setYears(+e.target.value)} style={rangeStyle()} />
setIncome(Math.max(0, +e.target.value || 0))}
style={{ fontFamily: "var(--font-mono)", fontWeight: 700, fontSize: 18 }} />
setObligations(Math.max(0, +e.target.value || 0))}
style={{ fontFamily: "var(--font-mono)", fontWeight: 700, fontSize: 18 }} />
{mode === "mortgage" && (
<>
{downpay}%
setDownpay(+e.target.value)} style={{ ...rangeStyle(), flex: 1 }} />
{[["equal", window.t(lang, "common.equalInstallments", "Equal")], ["decreasing", window.t(lang, "common.decreasingInstallments", "Decreasing")]].map(([k, l]) => (
))}
>
)}
{/* Result hero */}
{T("monthlyPayment", "Monthly Payment")} · {selectedBank.name}
{/* Detailed bank questionnaire — real questions banks ask */}
{showDetailed && (
)}
{/* DTI + score */}
{T("dti", "DTI (payment/income)")}
50 ? "var(--bad)" : dti > 35 ? "var(--warn)" : "var(--good)" }}>{dti.toFixed(1)}%
{T("dtiHint", "Banks prefer DTI below 50%")}
70 ? "color-mix(in oklch, var(--good) 8%, var(--bg-2))" : "var(--bg-2)", borderRadius: 12, border: "1px solid " + (score > 70 ? "var(--good)" : "var(--border-soft)") }}>
{T("chances", "Approval Probability")}
70 ? "var(--good)" : score > 40 ? "var(--warn)" : "var(--bad)" }}>{score}%
{showDetailed ? `${window.t(lang, "common.basedOn", "Based on")} ${scoringFactors.length} ${window.t(lang, "common.factorsWord", "factors")}` : window.t(lang, "common.completeDetailed", "Complete detailed analysis to increase accuracy")}
{/* Factor breakdown (visible when detailed mode on) */}
{showDetailed && (
{T("factors", "Scoring Factors")}
{T("sumPoints", "Total Points")}
{scoringFactors.map((f, i) => {
const pct = (f.value / f.max) * 100;
return (
{f.label}
75 ? "var(--good)" : pct > 40 ? "var(--warn)" : "var(--bad)", fontWeight: 700 }}>
{f.value}/{f.max}
75 ? "var(--good)" : pct > 40 ? "var(--warn)" : "var(--bad)", borderRadius: 999, transition: "width 0.6s var(--ease)" }} />
);
})}
)}
{/* Sidebar: bank ranking */}
{T("bankRanking", "Bank Ranking")}
{ranked.map((b, i) => (
))}
{T("aiTip", "AI Tip")}
{T("aiTipText1", "Your payment is")} {((monthly / income) * 100).toFixed(0)}% {T("aiTipText2", "of your income.")}{" "}
{dti > 50 ? window.t(lang, "common.reduceAmount", "Reduce amount or extend period.") : dti > 35 ? window.t(lang, "common.safeLevel", "Safe level with some buffer.") : window.t(lang, "common.comfortLevel", "Comfortable level for approval.")}
);
}
/* ────────── Detailed profile (real bank questions) ────────── */
function DetailedProfile({ profile, setProfile, mode, currency, lang = "pl" }) {
const set = (k, v) => setProfile({ ...profile, [k]: v });
return (
{/* Group: Employment */}
{profile.employment === "b2b" && (
)}
set("coIncome", +e.target.value || 0)} />
set("extraIncome", +e.target.value || 0)} />
{/* Group: Family situation */}
set("benefit800", +e.target.value || 0)} />
{/* Group: Liabilities & BIK */}
set("cardLimits", +e.target.value || 0)} />
set("alimony", +e.target.value || 0)} />
{/* Group: Property (only mortgage) */}
{mode === "mortgage" && (
)}
);
}
function ProfileGroup({ title, desc, children }) {
return (
);
}
function ResultStat({ label, value }) {
return (
);
}
/* ────────── Bento Tile renderer ────────── */
function Tile({ tile, country, navigate, density = "regular", lang = "pl" }) {
const tt = (k, fb) => window.t(lang, `tiles.${tile.kind}.${k}`, fb);
// Calc kind is special — uses full mini calculator
if (tile.kind === "calc") {
return (
{tt("kicker", "💰 Kalkulator")}
{tt("title", tile.title)}
{tt("sub", tile.sub) &&
{tt("sub", tile.sub)}
}
);
}
if (tile.kind === "rate") {
const r = window.REFERENCE_RATES.find(x => x.code === tile.code) || window.REFERENCE_RATES[0];
return (
navigate("markets")}>
{window.t(lang, "common.live", "Live")}
{r.name}
{r.value.toFixed(2)}%
);
}
if (tile.kind === "fx") {
return (
navigate("markets")}>
{window.FX.slice(0, 4).map(fx => (
{fx.pair}
{fx.value.toFixed(4)}
))}
);
}
if (tile.kind === "banks") {
const top = [...window.BANKS].filter(b => b.country === country || country === "pl").sort((a, b) => a.mortgage - b.mortgage).slice(0, 5);
return (
navigate("banks")}>
{top.map((b, i) => (
{i + 1}
{b.name}
{b.mortgage.toFixed(2)}%
))}
);
}
if (tile.kind === "programs") {
const localizedPrograms = (window.getLocalizedPrograms && window.getLocalizedPrograms(lang)) || window.PROGRAMS;
return (
navigate("programs")}>
{localizedPrograms.slice(0, 4).map(p => {
const c = window.COUNTRIES.find(c => c.code === p.country);
return (
{c?.flag}
{p.name.split("—")[0].split(" ").slice(0, 3).join(" ")}
);
})}
{tt("footer", "7+ subsidy programs in Europe")}
);
}
if (tile.kind === "ai") {
return (
{tt("body", "Enter amount and income — AI estimates your loan approval chances using 12 factors.")}
);
}
if (tile.kind === "compare") {
return (
navigate("mortgage")}>
🇵🇱 PLN
6.80%
{tt("pln", "avg rate")}
🇪🇺 EUR
3.85%
{tt("eur", "avg rate")}
{tt("diffNote", "≈ 2,100 PLN monthly difference on a 400k loan")}
);
}
if (tile.kind === "changes") {
const changes = [...window.BANKS].sort((a, b) => Math.abs(b.trend) - Math.abs(a.trend)).slice(0, 4);
return (
navigate("banks")}>
{changes.map(b => {
const c = window.COUNTRIES.find(c => c.code === b.country);
const up = b.trend > 0;
const flat = Math.abs(b.trend) < 0.005;
return (
{c?.flag}
{b.name}
{b.mortgage.toFixed(2)}%
{flat ? "→" : (up ? "▲" : "▼")}{Math.abs(b.trend).toFixed(2)}
);
})}
{window.t(lang, "common.weeklyUpdate", "Weekly update")}
);
}
if (tile.kind === "ad") {
return (
);
}
if (tile.kind === "articles") {
const localizedArticles = (window.getLocalizedArticles && window.getLocalizedArticles(lang)) || window.ARTICLES;
return (
{tt("kicker", "📚 Wiedza")}
{tt("title", tile.title)}
{localizedArticles.slice(0, 3).map(a => (
))}
);
}
return null;
}
function TileHeader({ tile, icon, tt }) {
const title = tt ? tt("title", tile.title) : tile.title;
const sub = tt ? tt("sub", tile.sub) : tile.sub;
return (
{icon}
{title}
{sub &&
{sub}
}
);
}
function BentoCard({ tile, span = "md", children, accent = false, plain = false, onClick }) {
const gridSpans = {
xl: { col: "span 8", row: "span 2", minH: 380 },
full: { col: "span 8", row: "span 1", minH: 280 },
lg: { col: "span 6", row: "span 1", minH: 260 },
md: { col: "span 4", row: "span 1", minH: 220 },
sm: { col: "span 2", row: "span 1", minH: 220 },
};
const s = gridSpans[span] || gridSpans.md;
const baseStyle = {
gridColumn: s.col,
gridRow: s.row,
minHeight: s.minH,
padding: plain ? 0 : 24,
background: accent ? "linear-gradient(155deg, var(--accent-soft), transparent 70%), var(--surface)" : "var(--surface)",
border: "1px solid " + (accent ? "var(--accent)" : "var(--border-soft)"),
borderRadius: "var(--radius-lg)",
cursor: onClick ? "pointer" : "default",
transition: "all 0.22s var(--ease)",
display: "flex", flexDirection: "column",
overflow: "hidden",
position: "relative",
};
return (
{
if (onClick) {
e.currentTarget.style.transform = "translateY(-3px)";
e.currentTarget.style.boxShadow = "var(--shadow)";
}
}}
onMouseLeave={e => {
e.currentTarget.style.transform = "translateY(0)";
e.currentTarget.style.boxShadow = "none";
}}>
{children}
);
}
Object.assign(window, {
MiniCalculator, FullCalculator, Tile, BentoCard, Field, Stat,
});