// ───────────────────────────────────────────────────────────────────────── // 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 (
{/* Inputs */}
{[ { k: "mortgage", l: T("typeMortgage", "🏠 Hipoteczny") }, { k: "cash", l: T("typeCash", "💳 Cash Loan") }, ].map(t => ( ))}
setAmount(Math.max(1000, +e.target.value || 0))} style={{ fontFamily: "var(--font-mono)", fontWeight: 700, fontSize: 18 }} /> setAmount(+e.target.value)} style={rangeStyle()} />
{years} setYears(+e.target.value)} style={{ ...rangeStyle(), flex: 1 }} />
{/* Result */}
{T("monthlyPayment", "Monthly Payment")} · {rate.toFixed(2)}%
{c.currency}
); } function Field({ label, suffix, children }) { return (
{suffix && {suffix}}
{children}
); } function Stat({ label, value, currency }) { return (
{label}
{window.formatMoney(value, currency)}
); } function rangeStyle() { return { width: "100%", height: 4, background: "var(--surface-2)", borderRadius: 999, outline: "none", appearance: "none", cursor: "pointer", marginTop: 8, accentColor: "var(--accent)", }; } /* ────────── Full calculator (mortgage/cash) ────────── */ function FullCalculator({ country, mode = "mortgage", 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 banksForCountry = window.BANKS.filter(b => b.country === country); const allBanks = banksForCountry.length > 0 ? banksForCountry : window.BANKS.filter(b => b.country === "pl"); const [selectedBank, setSelectedBank] = useStateC(allBanks[0]); const [amount, setAmount] = useStateC(mode === "mortgage" ? 400000 : 30000); const [years, setYears] = useStateC(mode === "mortgage" ? 25 : 5); const [income, setIncome] = useStateC(8000); const [obligations, setObligations] = useStateC(800); const [rateMode, setRateMode] = useStateC("equal"); const [downpay, setDownpay] = useStateC(20); // ───── Real bank questions (detailed scoring) ───── const [step, setStep] = useStateC(1); const [showDetailed, setShowDetailed] = useStateC(false); const [profile, setProfile] = useStateC({ employment: "uop_perm", // UoP bezterm., terminowa, B2B, zlecenie/dzieło, emerytura, rolnik tenure: "3y+", // staż: 3y+, 1-3y, 6-12m, 0-6m, próbny bizYears: "na", // dla B2B: 2+, 1-2, <1 civilStatus: "married", // singiel, małżeństwo, partnerstwo, rozwiedziony household: 2, // liczba osób children: 0, // dzieci na utrzymaniu benefit800: 0, // 800+ świadczenie age: "30-40", // do30, 30-40, 40-50, 50-60, 60+ bik: "clean", // clean, minor, major, none coIncome: 0, // dochód współwnioskodawcy extraIncome: 0, // inne dochody cardLimits: 0, // limity kart alimony: 0, // alimenty wypłacane propertyType: "apt_secondary",// rodzaj nieruchomości propertyLocation: "big_city", // lokalizacja propertyState: "good", // stan propertyPurpose: "own", // przeznaczenie }); useEffectC(() => { if (!allBanks.find(b => b.id === selectedBank.id)) setSelectedBank(allBanks[0]); }, [country]); const months = years * 12; const rate = mode === "mortgage" ? selectedBank.mortgage : selectedBank.cash; const monthly = window.annuity(amount, rate, months); const total = monthly * months; const interest = total - amount; const provision = amount * (selectedBank.prov / 100); const totalCost = interest + provision; // ───── Detailed scoring (realne pytania bankowe) ───── const totalIncome = income + (profile.coIncome || 0) + (profile.extraIncome || 0) + (profile.benefit800 || 0); const totalObligations = obligations + ((profile.cardLimits || 0) * 0.05) + (profile.alimony || 0); const dti = totalIncome > 0 ? ((monthly + totalObligations) / totalIncome) * 100 : 100; const scoringFactors = useMemoC(() => { const f = []; // Forma zatrudnienia const empWeights = { uop_perm: 20, uop_temp: 10, b2b: 15, contract: 5, pension: 18, farmer: 8 }; f.push({ label: window.t(lang, "calc.scoreEmployment", "Employment Type"), value: empWeights[profile.employment] || 5, max: 20 }); // Staż const tenW = { "3y+": 15, "1-3y": 10, "6-12m": 5, "0-6m": 1, probation: 0 }; f.push({ label: window.t(lang, "calc.scoreTenure", "Employment Tenure"), value: tenW[profile.tenure] || 5, max: 15 }); // BIK const bikW = { clean: 25, minor: 12, major: 0, none: 8 }; f.push({ label: window.t(lang, "calc.scoreHistory", "Credit History"), value: bikW[profile.bik] || 8, max: 25 }); // DTI const dtiScore = dti <= 30 ? 20 : dti <= 50 ? 14 : dti <= 65 ? 6 : 0; f.push({ label: window.t(lang, "calc.scoreDti", "DTI Ratio"), value: dtiScore, max: 20 }); // Wiek const ageW = { "do30": 8, "30-40": 10, "40-50": 9, "50-60": 6, "60+": 3 }; f.push({ label: window.t(lang, "calc.scoreAge", "Applicant Age"), value: ageW[profile.age] || 8, max: 10 }); // Wkład własny (tylko hipoteka) if (mode === "mortgage") { const dpScore = downpay >= 40 ? 10 : downpay >= 20 ? 7 : downpay >= 10 ? 3 : 0; f.push({ label: window.t(lang, "calc.scoreDownPayment", "Down Payment"), value: dpScore, max: 10 }); } return f; }, [profile, dti, downpay, mode]); const score = Math.min(100, Math.round( scoringFactors.reduce((sum, x) => sum + x.value, 0) / scoringFactors.reduce((sum, x) => sum + x.max, 0) * 100 )); // Build top-5 ranking for sidebar const ranked = [...allBanks].sort((a, b) => (mode === "mortgage" ? a.mortgage - b.mortgage : a.cash - b.cash)).slice(0, 5); 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}
{c.currency}
{/* 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 (

{title}

{desc &&
{desc}
}
{children}
); } function ResultStat({ label, value }) { return (
{label}
{value}
); } /* ────────── 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, });