{"id":392,"date":"2026-07-19T21:53:29","date_gmt":"2026-07-19T19:53:29","guid":{"rendered":"https:\/\/jacekhoroszko.com\/?page_id=392"},"modified":"2026-07-19T23:14:50","modified_gmt":"2026-07-19T21:14:50","slug":"kalkulatory","status":"publish","type":"page","link":"http:\/\/jacekhoroszko.com\/index.php\/kalkulatory\/","title":{"rendered":"Kalkulator Portfela Inwestycyjnego"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"392\" class=\"elementor elementor-392\">\n\t\t\t\t<div class=\"elementor-element elementor-element-f49f818 e-con-full e-flex e-con e-parent\" data-id=\"f49f818\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-18f72401 elementor-widget__width-inherit elementor-widget elementor-widget-html\" data-id=\"18f72401\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<!-- ============================================================\n     Kalkulator Portfela Inwestycyjnego \u2014 Jacek Horoszko Investment Partner\n     Wklej CA\u0141\u0104 zawarto\u015b\u0107 tego pliku do bloku \"Niestandardowy HTML\"\n     (Custom HTML) w edytorze WordPress.\n     ============================================================ -->\n<div id=\"jhip-portfel-calculator\">\n  <div class=\"jhip-pf-header\">\n    <h2>Kalkulator Portfela Inwestycyjnego<\/h2>\n    <p>Zbuduj portfel, sprawd\u017a alokacj\u0119, \u015bredni\u0105 stop\u0119 zwrotu i oczekiwany doch\u00f3d<\/p>\n  <\/div>\n  <div class=\"jhip-pf-tabs\" id=\"jhip-pf-tabs\"><\/div>\n  <div class=\"jhip-pf-content\" id=\"jhip-pf-content\"><\/div>\n<\/div>\n\n<style>\n  #jhip-portfel-calculator, #jhip-portfel-calculator *, #jhip-portfel-calculator *::before, #jhip-portfel-calculator *::after {\n    box-sizing: border-box;\n  }\n  #jhip-portfel-calculator {\n    --navy:#0F152E; --surface:#1A2442; --surface2:#222E54; --gold:#D3AF37; --red:#A11927; --white:#fff;\n    --text:#F4F5F9; --muted:#9AA6C4; --border:rgba(255,255,255,0.14); --border-soft:rgba(255,255,255,0.08);\n    --income:#E0A54C; --growth:#3FBF9F;\n    --shadow:0 8px 24px rgba(0,0,0,0.35);\n    font-family: system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;\n    font-size: 13px; line-height: 1.4; color: var(--text);\n    max-width: 960px; margin: 0 auto;\n    background: var(--navy); border: 1px solid var(--border-soft); border-radius: 18px;\n    padding: 22px 24px 26px; box-shadow: var(--shadow);\n  }\n  #jhip-portfel-calculator button { font-family: inherit; }\n\n  #jhip-portfel-calculator .jhip-pf-header { margin-bottom: 16px; }\n  #jhip-portfel-calculator .jhip-pf-header h2 { font-size: 21px; font-weight: 700; font-family: Georgia,serif; color: #fff; margin: 0 0 4px; }\n  #jhip-portfel-calculator .jhip-pf-header p { font-size: 12px; color: var(--muted); margin: 0; }\n\n  #jhip-portfel-calculator .jhip-pf-tabs { display: flex; gap: 2px; flex-wrap: wrap; border-bottom: 1px solid var(--border); padding: 0 2px; }\n  #jhip-portfel-calculator .jhip-pf-content { padding: 20px 2px 6px; }\n  #jhip-portfel-calculator .kap-tab-btn { padding: 11px 16px; border: none; background: none; cursor: pointer; font-size: 12px; font-weight: 700; color: var(--muted); border-bottom: 2.5px solid transparent; }\n  #jhip-portfel-calculator .kap-tab-btn.active { color: #fff; border-bottom-color: var(--gold); }\n\n  #jhip-portfel-calculator .su-section-label { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.09em; color: var(--muted); margin: 20px 0 10px; }\n  #jhip-portfel-calculator .su-section-label:first-of-type { margin-top: 0; }\n  #jhip-portfel-calculator .su-field { margin-bottom: 9px; min-width: 0; }\n  #jhip-portfel-calculator .su-field-label { display: block; font-size: 9px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.07em; color: var(--muted); margin-bottom: 4px; }\n  #jhip-portfel-calculator .su-text-input { width: 100%; padding: 9px 11px; border: 1.5px solid var(--border); border-radius: 8px; font-size: 13px; color: var(--text); background: var(--surface2); font-family: inherit; outline: none; }\n  #jhip-portfel-calculator .su-text-input:focus { border-color: var(--gold); }\n  #jhip-portfel-calculator .su-money-wrap { position: relative; }\n  #jhip-portfel-calculator .su-money-input { width: 100%; padding: 8px 32px 8px 10px; border: 1.5px solid var(--border); border-radius: 8px; font-size: 13px; color: var(--text); background: var(--surface2); font-family: inherit; outline: none; }\n  #jhip-portfel-calculator .su-money-input:focus { border-color: var(--gold); }\n  #jhip-portfel-calculator .su-money-input.pf-invalid { border-color: var(--red); }\n  #jhip-portfel-calculator .su-money-suffix { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); font-size: 10px; color: var(--muted); pointer-events: none; }\n  #jhip-portfel-calculator .kap-select { width: 100%; padding: 7px 9px; border: 1.5px solid var(--border); border-radius: 8px; font-size: 12px; font-weight: 600; color: var(--text); background: var(--surface2); outline: none; font-family: inherit; cursor: pointer; }\n  #jhip-portfel-calculator .kap-select:focus { border-color: var(--gold); }\n  #jhip-portfel-calculator .tool-btn { padding: 9px 18px; border-radius: 20px; border: none; background: var(--gold); color: var(--navy); font-size: 12px; font-weight: 700; cursor: pointer; }\n  #jhip-portfel-calculator .tool-btn:hover { opacity: 0.86; }\n  #jhip-portfel-calculator .kap-actionbar { display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; align-items: center; }\n  #jhip-portfel-calculator .kap-btn-genpdf { background: var(--gold); border: none; border-radius: 8px; padding: 8px 18px; font-size: 13px; font-weight: 700; color: var(--navy); cursor: pointer; }\n\n  #jhip-portfel-calculator .eme-form-card { background: var(--surface); border-radius: 12px; border: 1px solid var(--border-soft); padding: 18px 20px; margin-bottom: 14px; }\n  #jhip-portfel-calculator .eme-form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px 16px; }\n  #jhip-portfel-calculator .eme-result-cards { display: grid; grid-template-columns: repeat(auto-fit,minmax(200px,1fr)); gap: 12px; margin-bottom: 18px; }\n  #jhip-portfel-calculator .eme-result-card { background: var(--surface); border-radius: 12px; border: 1px solid var(--border-soft); border-top: 4px solid; padding: 14px 16px; }\n  #jhip-portfel-calculator .eme-result-label { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); margin-bottom: 6px; }\n  #jhip-portfel-calculator .eme-result-val { font-size: 20px; font-weight: 900; color: #fff; }\n  #jhip-portfel-calculator .eme-result-sub { font-size: 11px; color: var(--muted); margin-top: 4px; }\n  #jhip-portfel-calculator .ik-limit-note { font-size: 11px; color: var(--muted); margin-top: 4px; }\n  #jhip-portfel-calculator .ik-limit-note strong { color: #fff; }\n\n  \/* \u2014 prze\u0142\u0105cznik typu aktywa (kategoria \/ typ zwrotu, tryb w\u0142asny) \u2014 *\/\n  #jhip-portfel-calculator .pf-pill-row { display: flex; gap: 6px; flex-wrap: wrap; }\n  #jhip-portfel-calculator .pf-pill { padding: 6px 12px; border-radius: 20px; border: 1.5px solid var(--border); background: transparent; color: var(--muted); font-size: 11px; font-weight: 700; cursor: pointer; }\n  #jhip-portfel-calculator .pf-pill.active { border-color: var(--gold); background: rgba(211,175,55,0.16); color: #fff; }\n  #jhip-portfel-calculator .pf-badge { display: inline-block; padding: 4px 10px; border-radius: 20px; font-size: 10px; font-weight: 700; }\n  #jhip-portfel-calculator .pf-badge-fin { background: rgba(59,130,246,0.18); color: #93C5FD; }\n  #jhip-portfel-calculator .pf-badge-mat { background: rgba(63,191,159,0.18); color: #86EFAC; }\n  #jhip-portfel-calculator .pf-badge-growth { background: rgba(63,191,159,0.18); color: #6EE7C8; }\n  #jhip-portfel-calculator .pf-badge-income { background: rgba(224,165,76,0.2); color: #F0C27B; }\n  #jhip-portfel-calculator .pf-badge-reinvest { background: rgba(211,175,55,0.2); color: var(--gold); }\n\n  \/* \u2014 lista aktyw\u00f3w \u2014 *\/\n  #jhip-portfel-calculator .pf-asset-item { background: var(--surface); border-radius: 10px; border: 1px solid var(--border-soft); border-left: 4px solid var(--gold); padding: 12px 14px; margin-bottom: 10px; }\n  #jhip-portfel-calculator .pf-asset-row { display: flex; justify-content: space-between; align-items: flex-start; gap: 10px; }\n  #jhip-portfel-calculator .pf-asset-name { font-weight: 700; color: #fff; font-size: 13px; margin-bottom: 5px; }\n  #jhip-portfel-calculator .pf-asset-meta { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; font-size: 11px; color: var(--muted); }\n  #jhip-portfel-calculator .pf-asset-val { font-weight: 700; color: #fff; }\n  #jhip-portfel-calculator .pf-asset-actions { display: flex; gap: 6px; flex-shrink: 0; }\n  #jhip-portfel-calculator .pf-btn-icon { width: 26px; height: 26px; border-radius: 7px; border: 1px solid var(--border); background: var(--surface2); color: var(--muted); cursor: pointer; font-size: 12px; }\n  #jhip-portfel-calculator .pf-btn-icon:hover { color: #fff; }\n  #jhip-portfel-calculator .pf-btn-icon-del:hover { color: var(--red); border-color: var(--red); }\n  #jhip-portfel-calculator .pf-progress-bar { height: 5px; border-radius: 3px; background: var(--surface2); margin-top: 9px; overflow: hidden; }\n  #jhip-portfel-calculator .pf-progress-fill { height: 100%; border-radius: 3px; }\n  #jhip-portfel-calculator .pf-empty-state { text-align: center; padding: 24px 10px; color: var(--muted); font-size: 12px; }\n\n  \/* \u2014 alokacja: donut + legenda + split \u2014 *\/\n  #jhip-portfel-calculator .pf-alloc-row { display: flex; gap: 24px; flex-wrap: wrap; align-items: center; margin-bottom: 18px; }\n  #jhip-portfel-calculator .pf-donut { width: 150px; height: 150px; border-radius: 50%; flex-shrink: 0; position: relative; }\n  #jhip-portfel-calculator .pf-donut-hole { position: absolute; inset: 20px; border-radius: 50%; background: var(--navy); display: flex; flex-direction: column; align-items: center; justify-content: center; }\n  #jhip-portfel-calculator .pf-donut-hole-val { font-size: 14px; font-weight: 800; color: #fff; }\n  #jhip-portfel-calculator .pf-donut-hole-lbl { font-size: 9px; color: var(--muted); text-transform: uppercase; letter-spacing: .05em; margin-top: 2px; }\n  #jhip-portfel-calculator .pf-legend { flex: 1; min-width: 200px; display: flex; flex-direction: column; gap: 7px; }\n  #jhip-portfel-calculator .pf-legend-item { display: flex; align-items: center; gap: 8px; font-size: 12px; }\n  #jhip-portfel-calculator .pf-legend-dot { width: 10px; height: 10px; border-radius: 3px; flex-shrink: 0; }\n  #jhip-portfel-calculator .pf-legend-name { flex: 1; color: var(--text); }\n  #jhip-portfel-calculator .pf-legend-pct { color: var(--muted); font-weight: 700; }\n  #jhip-portfel-calculator .pf-split-bar { display: flex; height: 10px; border-radius: 6px; overflow: hidden; margin-bottom: 8px; }\n  #jhip-portfel-calculator .pf-split-label { display: flex; justify-content: space-between; font-size: 11px; color: var(--muted); margin-bottom: 18px; }\n\n  \/* \u2014 tabela zestawienia \u2014 *\/\n  #jhip-portfel-calculator .pf-table-wrap { overflow-x: auto; }\n  #jhip-portfel-calculator table.pf-table { width: 100%; border-collapse: collapse; font-size: 12px; }\n  #jhip-portfel-calculator table.pf-table th { text-align: left; padding: 8px 10px; font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); border-bottom: 1.5px solid var(--border); white-space: nowrap; }\n  #jhip-portfel-calculator table.pf-table td { padding: 9px 10px; border-bottom: 1px solid var(--border-soft); white-space: nowrap; color: var(--text); }\n  #jhip-portfel-calculator table.pf-table th.pf-r, #jhip-portfel-calculator table.pf-table td.pf-r { text-align: right; }\n  #jhip-portfel-calculator table.pf-table th.pf-c, #jhip-portfel-calculator table.pf-table td.pf-c { text-align: center; }\n  #jhip-portfel-calculator table.pf-table tfoot td { font-weight: 800; color: #fff; border-bottom: none; border-top: 2px solid var(--border); }\n\n  @media (max-width: 560px) {\n    #jhip-portfel-calculator { padding: 16px; }\n    #jhip-portfel-calculator .eme-form-grid { grid-template-columns: 1fr; }\n    #jhip-portfel-calculator .pf-alloc-row { flex-direction: column; align-items: stretch; }\n  }\n<\/style>\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/@emailjs\/browser@4\/dist\/email.min.js\"><\/script>\n<script>\n(function () {\n  \/\/ \u2500\u2500 Pomocnicze formatery \u2500\u2500\n  const esc = s => String(s).replace(\/&\/g, '&amp;').replace(\/<\/g, '&lt;').replace(\/>\/g, '&gt;');\n  const fmtPLN = v => new Intl.NumberFormat('pl-PL', { style: 'currency', currency: 'PLN', maximumFractionDigits: 0 }).format(Math.round(v || 0));\n  const fmtPct = v => `${Number(v || 0).toFixed(2)}%`;\n\n  \/\/ \u2500\u2500 EmailJS (to samo konto\/szablon co kalkulator IKE\/IKZE) \u2500\u2500\n  const EMAILJS_PUBLIC_KEY  = 'lKDuaTSIkMgu12jLj';\n  const EMAILJS_SERVICE_ID  = 'service_cs1bt29';\n  const EMAILJS_TEMPLATE_ID = 'template_06apygu';\n  const PF_LOGO_URL  = 'https:\/\/jacekhoroszko.com\/wp-content\/uploads\/2026\/07\/logo-email.png';\n  const PF_PHOTO_URL = 'https:\/\/jacekhoroszko.com\/wp-content\/uploads\/2026\/07\/photo-email.jpg';\n  const JHIP_LEAD_EMAIL = 'jacek.horoszko@phinance.pl';\n\n  \/\/ \u2500\u2500 Predefiniowane rodzaje aktyw\u00f3w \u2500\u2500\n  const ASSET_TYPES = [\n    { id: 'akcje',        label: 'Akcje \/ Fundusze Akcji',        category: 'financial', returnType: 'growth', rate: 29,  min: 0,     def: 10000 },\n    { id: 'obligacje',     label: 'Obligacje \/ Fundusze Obligacji', category: 'financial', returnType: 'growth', rate: 6,   min: 0,     def: 10000 },\n    { id: 'mieszane',      label: 'Fundusze Mieszane',                          category: 'financial', returnType: 'growth', rate: 14,  min: 0,     def: 10000 },\n    { id: 'niepubliczne',  label: 'Fundusze Surowcowe',                         category: 'financial', returnType: 'growth', rate: 17,  min: 0,     def: 10000 },\n    { id: 'surowcowe',     label: 'Fundusze Private Debt',                      category: 'financial', returnType: 'growth', rate: 9,   min: 10000, def: 50000, dynamicRate: v => v > 200000 ? 11 : 9 },\n    { id: 'zloto_fin',     label: 'Z\u0142oto (aktywo finansowe)',                   category: 'financial', returnType: 'growth', rate: 19,  min: 50,    def: 1000 },\n    { id: 'pozyczka',      label: 'Po\u017cyczka inwestycyjna',                      category: 'financial', returnType: 'income', rate: 9,   min: 0,     def: 100000, freq: 'quarterly' },\n    { id: 'nieruch_pl',    label: 'Nieruchomo\u015bci \u2014 Polska',                     category: 'material',  returnType: 'income', rate: 5.5, min: 400000, def: 500000, freq: 'monthly' },\n    { id: 'nieruch_es',    label: 'Nieruchomo\u015bci \u2014 Hiszpania',                  category: 'material',  returnType: 'income', rate: 7,   min: 800000, def: 1000000, freq: 'monthly' },\n    { id: 'grunt',         label: 'Grunt inwestycyjny',                         category: 'material',  returnType: 'growth', rate: 15,  min: 50000, def: 70000 },\n    { id: 'zloto_material',label: 'Z\u0142oto (aktywo fizyczne)',                    category: 'material',  returnType: 'growth', rate: 19,  min: 1000,  def: 20000 },\n    { id: 'custom',        label: 'Inne (w\u0142asne aktywo)',                       category: 'financial', returnType: 'growth', rate: 0,   min: 0,     def: '' },\n  ];\n  function assetType(id) { return ASSET_TYPES.find(t => t.id === id) || ASSET_TYPES[0]; }\n\n  const FIN_COLORS = ['#3B82F6', '#1D4ED8', '#60A5FA', '#93C5FD', '#1E3A8A', '#BFDBFE'];\n  const MAT_COLORS = ['#3FBF9F', '#15803D', '#4ADE80', '#86EFAC', '#14532D', '#BBF7D0'];\n  const FREQ_N  = { monthly: 12, quarterly: 4, semiannual: 2, annual: 1 };\n  const FREQ_SH = { monthly: 'mies.', quarterly: 'kw.', semiannual: 'p\u00f3\u0142r.', annual: 'rok' };\n  const FREQ_FL = { monthly: 'Wyp\u0142ata miesi\u0119czna', quarterly: 'Wyp\u0142ata kwartalna', semiannual: 'Wyp\u0142ata p\u00f3\u0142roczna', annual: 'Wyp\u0142ata roczna' };\n\n  const EMPTY_FORM = (() => {\n    const t = ASSET_TYPES[0];\n    return { typeId: t.id, name: t.label, value: t.def, category: t.category, returnType: t.returnType, returnRate: t.rate, incomeFrequency: t.freq || 'annual', reinvest: false, reinvestMode: 'existing', reinvestTargetId: '', reinvestNewTypeId: '' };\n  })();\n\n  const state = { tab: 'portfel', assets: [], form: { ...EMPTY_FORM }, editId: null, nextId: 1, formError: '' };\n\n  \/\/ \u2500\u2500 Silnik oblicze\u0144 (identyczny z Analizatorem Portfela) \u2500\u2500\n  function effRate(rate, freq) {\n    const n = FREQ_N[freq || 'annual'] || 1;\n    return n === 1 ? rate : (Math.pow(1 + rate \/ 100 \/ n, n) - 1) * 100;\n  }\n  function calcStats(assets) {\n    const total = assets.reduce((s, a) => s + (a.value || 0), 0);\n    if (!total) return { total: 0, wr: 0, income: 0, growth: 0 };\n    let wr = 0, income = 0, growth = 0;\n    assets.forEach(a => {\n      const v = a.value || 0, r = a.returnRate || 0, w = v \/ total;\n      if (a.returnType === 'income' && a.reinvest && a.reinvestTargetName) {\n        \/\/ Kupon przekierowany do realnego, konkretnego aktywa finansowego (patrz submitForm) \u2014\n        \/\/ jego wk\u0142ad jest ju\u017c policzony w tamtej pozycji, wi\u0119c tutaj nie liczymy nic (unikamy podw\u00f3jnego liczenia).\n      } else if (a.returnType === 'income' && a.reinvest) {\n        \/\/ Starsze wpisy bez wskazanego celu reinwestycji (dane sprzed wprowadzenia wymuszonego wyboru celu) \u2014\n        \/\/ szacunek przez efektywn\u0105 stop\u0119 sk\u0142adan\u0105, dla zachowania wstecznej zgodno\u015bci.\n        const er = effRate(r, a.incomeFrequency);\n        growth += v * er \/ 100; wr += w * er;\n      } else if (a.returnType === 'income') {\n        income += v * r \/ 100; wr += w * r;\n      } else {\n        growth += v * r \/ 100; wr += w * r;\n      }\n    });\n    return { total, wr, income, growth };\n  }\n  function getColorMap(assets) {\n    let fi = 0, mi = 0; const m = {};\n    assets.forEach(a => { m[a.id] = a.category === 'financial' ? FIN_COLORS[fi++ % FIN_COLORS.length] : MAT_COLORS[mi++ % MAT_COLORS.length]; });\n    return m;\n  }\n\n  \/\/ \u2500\u2500 Nawigacja i render \u2500\u2500\n  function renderTabs() {\n    document.getElementById('jhip-pf-tabs').innerHTML = `\n      <button class=\"kap-tab-btn ${state.tab === 'portfel' ? 'active' : ''}\" onclick=\"JhipPortfel.setTab('portfel')\">\ud83e\uddfa Portfel<\/button>\n      <button class=\"kap-tab-btn ${state.tab === 'wyniki' ? 'active' : ''}\" onclick=\"JhipPortfel.setTab('wyniki')\">\ud83d\udcc8 Wyniki<\/button>\n    `;\n  }\n  function renderContentOnly() {\n    document.getElementById('jhip-pf-content').innerHTML = state.tab === 'portfel' ? renderPortfelTab() : renderResultsTab();\n  }\n  function render() { renderTabs(); renderContentOnly(); }\n  function setTab(t) { state.tab = t; render(); }\n\n  \/\/ \u2500\u2500 Formularz dodawania aktywa \u2500\u2500\n  function updateFormType(typeId) {\n    const t = assetType(typeId);\n    if (typeId === 'custom') {\n      state.form = { typeId: 'custom', name: '', value: '', category: 'financial', returnType: 'growth', returnRate: '', incomeFrequency: 'annual', reinvest: false, reinvestMode: 'existing', reinvestTargetId: '', reinvestNewTypeId: '' };\n    } else {\n      const value = t.def;\n      const rate = typeof t.dynamicRate === 'function' ? t.dynamicRate(Number(value) || 0) : t.rate;\n      state.form = { typeId, name: t.label, value, category: t.category, returnType: t.returnType, returnRate: rate, incomeFrequency: t.freq || 'annual', reinvest: false, reinvestMode: 'existing', reinvestTargetId: '', reinvestNewTypeId: '' };\n    }\n    state.formError = '';\n    renderContentOnly();\n  }\n  function updateForm(key, value) {\n    state.form[key] = value;\n    state.formError = '';\n    renderContentOnly();\n  }\n  \/\/ Aktualizacja p\u00f3l tekstowych\/liczbowych BEZ przerenderowania formularza \u2014 re-render przy ka\u017cdym\n  \/\/ \"blur\" niszczy\u0142by i odtwarza\u0142 przycisk \"Dodaj do portfela\" w trakcie klikni\u0119cia we\u0144 (mousedown\n  \/\/ odpala blur\u2192render zanim zd\u0105\u017cy doj\u015b\u0107 mouseup), przez co klikni\u0119cie czasem w og\u00f3le nie dzia\u0142a\u0142o.\n  function updateFormField(key, value) {\n    state.form[key] = value;\n    state.formError = '';\n    \/\/ Dla aktyw\u00f3w z dynamiczn\u0105 stop\u0105 (zale\u017cn\u0105 od kwoty) przeliczamy stop\u0119 od razu po zmianie\n    \/\/ warto\u015bci i podmieniamy pole bezpo\u015brednio w DOM \u2014 bez pe\u0142nego renderContentOnly(), kt\u00f3re\n    \/\/ zniszczy\u0142oby przycisk \"Dodaj do portfela\" w trakcie klikni\u0119cia we\u0144 (patrz komentarz wy\u017cej).\n    if (key === 'value') {\n      const t = assetType(state.form.typeId);\n      if (typeof t.dynamicRate === 'function') {\n        const newRate = t.dynamicRate(Number(value) || 0);\n        state.form.returnRate = newRate;\n        const rateInput = document.getElementById('jhip-pf-rate-input');\n        if (rateInput) rateInput.value = newRate;\n      }\n    }\n  }\n  function toggleReinvest() { updateForm('reinvest', !state.form.reinvest); }\n\n  function submitForm() {\n    const { form, editId } = state;\n    const name = (form.name || '').trim();\n    const value = parseFloat(form.value);\n    const rate = parseFloat(form.returnRate);\n    if (!name || !value || value <= 0 || isNaN(rate)) {\n      state.formError = 'Uzupe\u0142nij nazw\u0119, warto\u015b\u0107 i stop\u0119 zwrotu.';\n      renderContentOnly();\n      return;\n    }\n    const t = assetType(form.typeId);\n    if (form.typeId !== 'custom' && t.min > 0 && value < t.min) {\n      state.formError = `Minimalna kwota inwestycji dla tego aktywa to ${fmtPLN(t.min)}.`;\n      renderContentOnly();\n      return;\n    }\n    const isReinvest = form.returnType === 'income' && !!form.reinvest;\n    const couponAmount = value * rate \/ 100;\n    let reinvestTargetName = null, reinvestExistingTarget = null, reinvestNewAsset = null;\n    if (isReinvest) {\n      const mode = form.reinvestMode || 'existing';\n      if (mode === 'existing') {\n        reinvestExistingTarget = state.assets.find(a => a.id === form.reinvestTargetId && a.category === 'financial' && a.id !== editId);\n        if (!reinvestExistingTarget) {\n          state.formError = 'Wybierz istniej\u0105ce aktywo finansowe, do kt\u00f3rego ma trafi\u0107 reinwestowany kupon.';\n          renderContentOnly();\n          return;\n        }\n        reinvestTargetName = reinvestExistingTarget.name;\n      } else {\n        const newType = assetType(form.reinvestNewTypeId);\n        if (!form.reinvestNewTypeId) {\n          state.formError = 'Wybierz rodzaj nowego aktywa, w kt\u00f3re ma zosta\u0107 zainwestowany kupon.';\n          renderContentOnly();\n          return;\n        }\n        if (newType.min > 0 && couponAmount < newType.min) {\n          state.formError = `Kwota kuponu (${fmtPLN(couponAmount)}) jest ni\u017csza ni\u017c minimalna kwota inwestycji dla wybranego aktywa (${fmtPLN(newType.min)}).`;\n          renderContentOnly();\n          return;\n        }\n        reinvestTargetName = newType.label;\n        reinvestNewAsset = { id: String(state.nextId++), typeId: newType.id, name: newType.label, value: couponAmount, category: newType.category, returnType: newType.returnType, returnRate: newType.rate, incomeFrequency: newType.returnType === 'income' ? (newType.freq || 'annual') : null, reinvest: false };\n      }\n    }\n    const asset = {\n      id: editId || String(state.nextId++),\n      typeId: form.typeId,\n      name,\n      value,\n      category: form.category,\n      returnType: form.returnType,\n      returnRate: rate,\n      incomeFrequency: form.returnType === 'income' ? (form.incomeFrequency || 'annual') : null,\n      reinvest: isReinvest,\n      reinvestTargetName,\n    };\n    if (editId) { state.assets = state.assets.map(a => a.id === editId ? asset : a); }\n    else { state.assets.push(asset); }\n    \/\/ Realizacja reinwestycji: dopisanie kwoty kuponu do istniej\u0105cego aktywa finansowego albo dodanie nowego.\n    if (isReinvest && reinvestExistingTarget) { reinvestExistingTarget.value += couponAmount; }\n    if (isReinvest && reinvestNewAsset) { state.assets.push(reinvestNewAsset); }\n    state.editId = null;\n    state.form = { ...EMPTY_FORM };\n    state.formError = '';\n    renderContentOnly();\n  }\n  function startEdit(id) {\n    const a = state.assets.find(x => x.id === id);\n    if (!a) return;\n    state.editId = id;\n    state.form = { typeId: 'custom', name: a.name, value: String(a.value), category: a.category, returnType: a.returnType, returnRate: String(a.returnRate), incomeFrequency: a.incomeFrequency || 'annual', reinvest: !!a.reinvest, reinvestMode: 'existing', reinvestTargetId: '', reinvestNewTypeId: '' };\n    state.formError = '';\n    renderContentOnly();\n  }\n  function removeAsset(id) {\n    state.assets = state.assets.filter(a => a.id !== id);\n    if (state.editId === id) { state.editId = null; state.form = { ...EMPTY_FORM }; }\n    renderContentOnly();\n  }\n  function cancelEdit() {\n    state.editId = null;\n    state.form = { ...EMPTY_FORM };\n    state.formError = '';\n    renderContentOnly();\n  }\n\n  \/\/ \u2500\u2500 Render \u2014 blok wyboru celu reinwestycji kuponu \u2500\u2500\n  function renderReinvestBlock(form, editId) {\n    const couponAmount = (parseFloat(form.value) || 0) * (parseFloat(form.returnRate) || 0) \/ 100;\n    const existingFinancial = state.assets.filter(a => a.category === 'financial' && a.id !== editId);\n    const eligibleNewTypes = ASSET_TYPES.filter(x => x.category === 'financial' && x.id !== 'custom' && (x.min || 0) <= couponAmount);\n    const mode = form.reinvestMode || (existingFinancial.length ? 'existing' : 'new');\n    return `<div class=\"su-section-label\">Cel reinwestycji kuponu<\/div>\n      <div class=\"ik-limit-note\" style=\"margin-top:-4px;margin-bottom:10px\">Roczna kwota kuponu do reinwestycji: <strong>${fmtPLN(couponAmount)}<\/strong><\/div>\n      <div class=\"pf-pill-row\" style=\"margin-bottom:10px\">\n        <button type=\"button\" class=\"pf-pill ${mode === 'existing' ? 'active' : ''}\" onclick=\"JhipPortfel.updateForm('reinvestMode','existing')\">Istniej\u0105ce aktywo<\/button>\n        <button type=\"button\" class=\"pf-pill ${mode === 'new' ? 'active' : ''}\" onclick=\"JhipPortfel.updateForm('reinvestMode','new')\">Nowe aktywo z listy<\/button>\n      <\/div>\n      ${mode === 'existing'\n        ? (existingFinancial.length\n          ? `<select class=\"kap-select\" onchange=\"JhipPortfel.updateForm('reinvestTargetId',this.value)\">\n              <option value=\"\">\u2014 wybierz aktywo \u2014<\/option>\n              ${existingFinancial.map(a => `<option value=\"${a.id}\" ${form.reinvestTargetId === a.id ? 'selected' : ''}>${esc(a.name)} (${fmtPLN(a.value)})<\/option>`).join('')}\n            <\/select>`\n          : `<div class=\"ik-limit-note\" style=\"color:#F3A3AC\">Brak istniej\u0105cych aktyw\u00f3w finansowych w portfelu \u2014 wybierz \u201eNowe aktywo z listy\u201d.<\/div>`)\n        : (eligibleNewTypes.length\n          ? `<select class=\"kap-select\" onchange=\"JhipPortfel.updateForm('reinvestNewTypeId',this.value)\">\n              <option value=\"\">\u2014 wybierz rodzaj aktywa \u2014<\/option>\n              ${eligibleNewTypes.map(x => `<option value=\"${x.id}\" ${form.reinvestNewTypeId === x.id ? 'selected' : ''}>${x.label} (${x.rate}%)<\/option>`).join('')}\n            <\/select>`\n          : `<div class=\"ik-limit-note\" style=\"color:#F3A3AC\">Kwota kuponu (${fmtPLN(couponAmount)}) nie mie\u015bci si\u0119 w minimalnej kwocie inwestycji \u017cadnego aktywa finansowego z listy. Uzupe\u0142nij warto\u015b\u0107 i stop\u0119 zwrotu lub wybierz \u201eIstniej\u0105ce aktywo\u201d.<\/div>`)}\n    `;\n  }\n\n  \/\/ \u2500\u2500 Render \u2014 zak\u0142adka \"Portfel\" (formularz + lista) \u2500\u2500\n  function renderPortfelTab() {\n    const { form, editId, formError } = state;\n    const t = assetType(form.typeId);\n    const isCustom = form.typeId === 'custom';\n    const cm = getColorMap(state.assets);\n    const stats = calcStats(state.assets);\n    const finOptions = ASSET_TYPES.filter(x => x.category === 'financial' && x.id !== 'custom');\n    const matOptions = ASSET_TYPES.filter(x => x.category === 'material');\n    return `<div>\n      <div class=\"eme-form-card\">\n        <div class=\"su-section-label\" style=\"margin-top:0\">${editId ? 'Edytuj aktywo' : 'Dodaj aktywo do portfela'}<\/div>\n        <div class=\"su-field\">\n          <span class=\"su-field-label\">Rodzaj aktywa<\/span>\n          <select class=\"kap-select\" onchange=\"JhipPortfel.updateFormType(this.value)\" ${editId ? 'disabled' : ''}>\n            <optgroup label=\"Aktywa finansowe\">\n              ${finOptions.map(x => `<option value=\"${x.id}\" ${form.typeId === x.id ? 'selected' : ''}>${x.label}<\/option>`).join('')}\n            <\/optgroup>\n            <optgroup label=\"Aktywa materialne\">\n              ${matOptions.map(x => `<option value=\"${x.id}\" ${form.typeId === x.id ? 'selected' : ''}>${x.label}<\/option>`).join('')}\n            <\/optgroup>\n            <optgroup label=\"Inne\">\n              <option value=\"custom\" ${form.typeId === 'custom' ? 'selected' : ''}>Inne (w\u0142asne aktywo)<\/option>\n            <\/optgroup>\n          <\/select>\n        <\/div>\n        <div class=\"eme-form-grid\">\n          <div class=\"su-field\">\n            <span class=\"su-field-label\">Nazwa aktywa<\/span>\n            <input class=\"su-text-input\" type=\"text\" value=\"${esc(form.name)}\" onchange=\"JhipPortfel.updateFormField('name',this.value)\">\n          <\/div>\n          <div class=\"su-field\">\n            <span class=\"su-field-label\">Warto\u015b\u0107 inwestycji (PLN)<\/span>\n            <div class=\"su-money-wrap\"><input class=\"su-money-input ${formError ? 'pf-invalid' : ''}\" type=\"number\" min=\"0\" value=\"${form.value}\" onchange=\"JhipPortfel.updateFormField('value',this.value)\"><span class=\"su-money-suffix\">PLN<\/span><\/div>\n            ${!isCustom && t.min > 0 ? `<div class=\"ik-limit-note\">Minimalna kwota inwestycji: <strong>${fmtPLN(t.min)}<\/strong><\/div>` : ''}\n          <\/div>\n        <\/div>\n        <div class=\"eme-form-grid\">\n          <div class=\"su-field\">\n            <span class=\"su-field-label\">Stopa zwrotu (% p.a.)<\/span>\n            <input id=\"jhip-pf-rate-input\" class=\"su-text-input\" type=\"number\" min=\"0\" step=\"0.1\" value=\"${form.returnRate}\" onchange=\"JhipPortfel.updateFormField('returnRate',this.value)\">\n          <\/div>\n          <div class=\"su-field\">\n            <span class=\"su-field-label\">Kategoria \/ typ zwrotu<\/span>\n            ${isCustom ? `\n            <div class=\"pf-pill-row\" style=\"margin-bottom:6px\">\n              <button type=\"button\" class=\"pf-pill ${form.category === 'financial' ? 'active' : ''}\" onclick=\"JhipPortfel.updateForm('category','financial')\">\ud83d\udcca Finansowe<\/button>\n              <button type=\"button\" class=\"pf-pill ${form.category === 'material' ? 'active' : ''}\" onclick=\"JhipPortfel.updateForm('category','material')\">\ud83c\udfd7 Materialne<\/button>\n            <\/div>\n            <div class=\"pf-pill-row\">\n              <button type=\"button\" class=\"pf-pill ${form.returnType === 'growth' ? 'active' : ''}\" onclick=\"JhipPortfel.updateForm('returnType','growth')\">\u2191 Wzrost<\/button>\n              <button type=\"button\" class=\"pf-pill ${form.returnType === 'income' ? 'active' : ''}\" onclick=\"JhipPortfel.updateForm('returnType','income')\">\u21a9 Kupon\/CF<\/button>\n            <\/div>` : `\n            <div class=\"pf-pill-row\">\n              <span class=\"pf-badge ${form.category === 'financial' ? 'pf-badge-fin' : 'pf-badge-mat'}\">${form.category === 'financial' ? '\ud83d\udcca Finansowe' : '\ud83c\udfd7 Materialne'}<\/span>\n              <span class=\"pf-badge ${form.returnType === 'income' ? 'pf-badge-income' : 'pf-badge-growth'}\">${form.returnType === 'income' ? '\u21a9 Kupon \/ CF' : '\u2191 Wzrost kapita\u0142u'}<\/span>\n            <\/div>`}\n          <\/div>\n        <\/div>\n        ${form.returnType === 'income' ? `\n        <div class=\"su-section-label\">Wyp\u0142ata kuponu<\/div>\n        <div class=\"eme-form-grid\">\n          <div class=\"su-field\">\n            <span class=\"su-field-label\">Cz\u0119stotliwo\u015b\u0107<\/span>\n            <select class=\"kap-select\" onchange=\"JhipPortfel.updateForm('incomeFrequency',this.value)\">\n              <option value=\"monthly\" ${form.incomeFrequency === 'monthly' ? 'selected' : ''}>Miesi\u0119cznie<\/option>\n              <option value=\"quarterly\" ${form.incomeFrequency === 'quarterly' ? 'selected' : ''}>Kwartalnie<\/option>\n              <option value=\"semiannual\" ${form.incomeFrequency === 'semiannual' ? 'selected' : ''}>P\u00f3\u0142rocznie<\/option>\n              <option value=\"annual\" ${(!form.incomeFrequency || form.incomeFrequency === 'annual') ? 'selected' : ''}>Rocznie<\/option>\n            <\/select>\n          <\/div>\n          <div class=\"su-field\" style=\"display:flex;align-items:flex-end\">\n            <label style=\"display:flex;align-items:center;gap:8px;font-size:12px;color:var(--text);font-weight:600;cursor:pointer\">\n              <input type=\"checkbox\" ${form.reinvest ? 'checked' : ''} onchange=\"JhipPortfel.updateForm('reinvest',this.checked)\"> \u267b Reinwestuj kupon w aktywo finansowe\n            <\/label>\n          <\/div>\n        <\/div>\n        ${form.reinvest ? renderReinvestBlock(form, editId) : ''}` : ''}\n        ${formError ? `<div class=\"ik-limit-note\" style=\"color:#F3A3AC;margin-top:4px\">${esc(formError)}<\/div>` : ''}\n        <div style=\"text-align:right;margin-top:14px;display:flex;gap:8px;justify-content:flex-end\">\n          ${editId ? `<button class=\"tool-btn\" style=\"background:transparent;color:var(--muted);border:1px solid var(--border)\" onclick=\"JhipPortfel.cancelEdit()\">Anuluj<\/button>` : ''}\n          <button class=\"tool-btn\" onclick=\"JhipPortfel.submitForm()\">${editId ? '\u2713 Zapisz zmiany' : '+ Dodaj do portfela'}<\/button>\n        <\/div>\n      <\/div>\n\n      <div class=\"su-section-label\">Aktywa w portfelu ${state.assets.length ? `(${state.assets.length})` : ''}<\/div>\n      ${state.assets.length === 0 ? `<div class=\"pf-empty-state\">Brak aktyw\u00f3w \u2014 dodaj pierwsze powy\u017cej.<\/div>` : state.assets.map(a => {\n        const pct = stats.total ? (a.value \/ stats.total * 100) : 0;\n        const c = cm[a.id];\n        const annual = a.value * a.returnRate \/ 100;\n        return `<div class=\"pf-asset-item\" style=\"border-left-color:${c}\">\n          <div class=\"pf-asset-row\">\n            <div style=\"min-width:0\">\n              <div class=\"pf-asset-name\">${esc(a.name)}<\/div>\n              <div class=\"pf-asset-meta\">\n                <span class=\"pf-asset-val\">${fmtPLN(a.value)}<\/span>\n                <span>${pct.toFixed(1)}%<\/span>\n                <span class=\"pf-badge ${a.category === 'financial' ? 'pf-badge-fin' : 'pf-badge-mat'}\">${a.category === 'financial' ? 'Finansowe' : 'Materialne'}<\/span>\n                <span class=\"pf-badge ${a.returnType !== 'income' ? 'pf-badge-growth' : (a.reinvest ? 'pf-badge-reinvest' : 'pf-badge-income')}\">${a.returnType === 'income' ? `${a.reinvest ? '\u267b' : '\u21a9'} ${a.returnRate}% \/ ${FREQ_SH[a.incomeFrequency || 'annual']}` : `\u2191 ${a.returnRate}%`}<\/span>\n                ${a.reinvest && a.reinvestTargetName ? `<span>\u21aa reinwestowane w: ${esc(a.reinvestTargetName)}<\/span>` : `<span>${fmtPLN(annual)}\/rok<\/span>`}\n              <\/div>\n            <\/div>\n            <div class=\"pf-asset-actions\">\n              <button class=\"pf-btn-icon\" onclick=\"JhipPortfel.startEdit('${a.id}')\" title=\"Edytuj\">\u270e<\/button>\n              <button class=\"pf-btn-icon pf-btn-icon-del\" onclick=\"JhipPortfel.removeAsset('${a.id}')\" title=\"Usu\u0144\">\u2715<\/button>\n            <\/div>\n          <\/div>\n          <div class=\"pf-progress-bar\"><div class=\"pf-progress-fill\" style=\"width:${pct}%;background:${c}\"><\/div><\/div>\n        <\/div>`;\n      }).join('')}\n      ${state.assets.length ? `<div style=\"text-align:right;margin-top:6px\"><button class=\"tool-btn\" onclick=\"JhipPortfel.setTab('wyniki')\">Przejd\u017a do wynik\u00f3w \u2192<\/button><\/div>` : ''}\n    <\/div>`;\n  }\n\n  \/\/ \u2500\u2500 Render \u2014 donut alokacji (CSS conic-gradient, bez zale\u017cno\u015bci JS) \u2500\u2500\n  function buildDonutHTML(assets, stats) {\n    if (!assets.length || !stats.total) return `<div class=\"pf-empty-state\">Dodaj aktywa, aby zobaczy\u0107 alokacj\u0119 portfela.<\/div>`;\n    const cm = getColorMap(assets);\n    let acc = 0;\n    const stops = assets.map(a => {\n      const from = acc; acc += (a.value \/ stats.total) * 100;\n      return `${cm[a.id]} ${from.toFixed(2)}% ${acc.toFixed(2)}%`;\n    }).join(', ');\n    const legend = assets.map(a => {\n      const pct = a.value \/ stats.total * 100;\n      return `<div class=\"pf-legend-item\"><div class=\"pf-legend-dot\" style=\"background:${cm[a.id]}\"><\/div><span class=\"pf-legend-name\">${esc(a.name)}<\/span><span class=\"pf-legend-pct\">${pct.toFixed(1)}%<\/span><\/div>`;\n    }).join('');\n    const finVal = assets.filter(a => a.category === 'financial').reduce((s, a) => s + a.value, 0);\n    const matVal = assets.filter(a => a.category === 'material').reduce((s, a) => s + a.value, 0);\n    const finPct = stats.total ? finVal \/ stats.total * 100 : 0;\n    const matPct = stats.total ? matVal \/ stats.total * 100 : 0;\n    return `\n      <div class=\"pf-alloc-row\">\n        <div class=\"pf-donut\" style=\"background:conic-gradient(${stops})\">\n          <div class=\"pf-donut-hole\">\n            <div class=\"pf-donut-hole-val\">${fmtPLN(stats.total)}<\/div>\n            <div class=\"pf-donut-hole-lbl\">Warto\u015b\u0107 portfela<\/div>\n          <\/div>\n        <\/div>\n        <div class=\"pf-legend\">${legend}<\/div>\n      <\/div>\n      <div class=\"pf-split-bar\">\n        <div style=\"width:${finPct}%;background:#3B82F6\"><\/div>\n        <div style=\"width:${matPct}%;background:#3FBF9F\"><\/div>\n      <\/div>\n      <div class=\"pf-split-label\">\n        <span>\ud83d\udcca Finansowe: <strong style=\"color:#93C5FD\">${finPct.toFixed(1)}%<\/strong> (${fmtPLN(finVal)})<\/span>\n        <span>\ud83c\udfd7 Materialne: <strong style=\"color:#86EFAC\">${matPct.toFixed(1)}%<\/strong> (${fmtPLN(matVal)})<\/span>\n      <\/div>`;\n  }\n\n  function buildTableHTML(assets, stats) {\n    if (!assets.length) return `<div class=\"pf-empty-state\">Brak aktyw\u00f3w w portfelu.<\/div>`;\n    const sorted = [...assets].sort((a, b) => (a.category === 'financial' ? 0 : 1) - (b.category === 'financial' ? 0 : 1));\n    const rows = sorted.map(a => {\n      const pct = stats.total ? (a.value \/ stats.total * 100) : 0;\n      const annual = a.value * a.returnRate \/ 100;\n      const isRedirected = a.returnType === 'income' && a.reinvest && a.reinvestTargetName;\n      const cfCell = isRedirected ? `<span style=\"color:var(--muted);font-style:italic\">\u21aa ${esc(a.reinvestTargetName)}<\/span>` : fmtPLN(a.returnType === 'income' && a.reinvest ? a.value * effRate(a.returnRate, a.incomeFrequency) \/ 100 : annual);\n      return `<tr>\n        <td>${esc(a.name)}<\/td>\n        <td><span class=\"pf-badge ${a.category === 'financial' ? 'pf-badge-fin' : 'pf-badge-mat'}\">${a.category === 'financial' ? 'Finansowe' : 'Materialne'}<\/span><\/td>\n        <td class=\"pf-r\">${fmtPLN(a.value)}<\/td>\n        <td class=\"pf-r\">${pct.toFixed(1)}%<\/td>\n        <td class=\"pf-r\">${a.returnRate}%<\/td>\n        <td class=\"pf-c\"><span class=\"pf-badge ${a.returnType !== 'income' ? 'pf-badge-growth' : (a.reinvest ? 'pf-badge-reinvest' : 'pf-badge-income')}\">${a.returnType === 'income' ? (a.reinvest ? '\u267b Reinwestuje' : '\u21a9 Kupon \/ CF') : '\u2191 Wzrost'}<\/span><\/td>\n        <td class=\"pf-r\">${cfCell}<\/td>\n      <\/tr>`;\n    }).join('');\n    return `<div class=\"pf-table-wrap\"><table class=\"pf-table\">\n      <thead><tr><th>Aktywa<\/th><th>Kategoria<\/th><th class=\"pf-r\">Warto\u015b\u0107<\/th><th class=\"pf-r\">Udzia\u0142<\/th><th class=\"pf-r\">Stopa<\/th><th class=\"pf-c\">Typ zwrotu<\/th><th class=\"pf-r\">Roczny CF\/Wzrost<\/th><\/tr><\/thead>\n      <tbody>${rows}<\/tbody>\n      <tfoot><tr><td colspan=\"2\">\u0141\u0104CZNIE<\/td><td class=\"pf-r\">${fmtPLN(stats.total)}<\/td><td class=\"pf-r\">100%<\/td><td class=\"pf-r\" style=\"color:var(--gold)\">${fmtPct(stats.wr)}<\/td><td><\/td><td class=\"pf-r\">${fmtPLN(stats.income + stats.growth)}<\/td><\/tr><\/tfoot>\n    <\/table><\/div>`;\n  }\n\n  \/\/ \u2500\u2500 Render \u2014 zak\u0142adka \"Wyniki\" \u2500\u2500\n  function renderResultsTab() {\n    const assets = state.assets;\n    const stats = calcStats(assets);\n    return `<div>\n      <div class=\"kap-actionbar\">\n        <button class=\"kap-btn-genpdf\" onclick=\"JhipPortfel.openEmailForm()\">\u2709 Wy\u015blij na e-mail<\/button>\n      <\/div>\n      <div id=\"jhip-pf-email-panel\" style=\"display:none;background:var(--surface);border:1px solid var(--border-soft);border-radius:10px;padding:14px 16px;margin-bottom:14px\">\n        <div style=\"display:flex;gap:8px;flex-wrap:wrap;align-items:center\">\n          <input id=\"jhip-pf-email-name\" type=\"text\" class=\"su-text-input\" placeholder=\"Imi\u0119\" style=\"max-width:160px\" onkeydown=\"if(event.key==='Enter'){JhipPortfel.sendResultEmail()}\">\n          <input id=\"jhip-pf-email-phone\" type=\"tel\" class=\"su-text-input\" placeholder=\"Telefon\" style=\"max-width:160px\" onkeydown=\"if(event.key==='Enter'){JhipPortfel.sendResultEmail()}\">\n          <input id=\"jhip-pf-email-input\" type=\"email\" class=\"su-text-input\" placeholder=\"twoj@email.pl\" style=\"max-width:220px\" onkeydown=\"if(event.key==='Enter'){JhipPortfel.sendResultEmail()}\">\n          <button id=\"jhip-pf-email-send-btn\" class=\"tool-btn\" onclick=\"JhipPortfel.sendResultEmail()\">Wy\u015blij<\/button>\n          <button class=\"tool-btn\" style=\"background:transparent;color:var(--muted);border:1px solid var(--border)\" onclick=\"JhipPortfel.closeEmailForm()\">Anuluj<\/button>\n        <\/div>\n        <div id=\"jhip-pf-email-status\" style=\"font-size:11px;margin-top:8px\"><\/div>\n      <\/div>\n\n      <div class=\"eme-result-cards\">\n        <div class=\"eme-result-card\" style=\"border-top-color:var(--gold)\">\n          <div class=\"eme-result-label\">Warto\u015b\u0107 portfela<\/div>\n          <div class=\"eme-result-val\">${fmtPLN(stats.total)}<\/div>\n          <div class=\"eme-result-sub\">\u0142\u0105cznie \u00b7 ${assets.length} ${assets.length === 1 ? 'aktywo' : 'aktyw\u00f3w'}<\/div>\n        <\/div>\n        <div class=\"eme-result-card\" style=\"border-top-color:var(--gold)\">\n          <div class=\"eme-result-label\">\u015ar. wa\u017cona stopa zwrotu<\/div>\n          <div class=\"eme-result-val\">${fmtPct(stats.wr)}<\/div>\n        <\/div>\n        <div class=\"eme-result-card\" style=\"border-top-color:var(--income)\">\n          <div class=\"eme-result-label\">Roczny kupon<\/div>\n          <div class=\"eme-result-val\">${fmtPLN(stats.income)}<\/div>\n          <div class=\"eme-result-sub\">doch\u00f3d pasywny (wyp\u0142acany)<\/div>\n        <\/div>\n        <div class=\"eme-result-card\" style=\"border-top-color:var(--growth)\">\n          <div class=\"eme-result-label\">Oczekiwany wzrost kap.<\/div>\n          <div class=\"eme-result-val\">${fmtPLN(stats.growth)}<\/div>\n          <div class=\"eme-result-sub\">rocznie<\/div>\n        <\/div>\n      <\/div>\n\n      <div class=\"su-section-label\" style=\"margin-top:0\">Alokacja portfela<\/div>\n      ${buildDonutHTML(assets, stats)}\n\n      <div class=\"su-section-label\">Zestawienie szczeg\u00f3\u0142owe<\/div>\n      ${buildTableHTML(assets, stats)}\n    <\/div>`;\n  }\n\n  \/\/ \u2500\u2500 E-mail \u2500\u2500\n  function emailjsReady() {\n    return typeof window.emailjs !== 'undefined' && EMAILJS_PUBLIC_KEY.indexOf('TWOJ_') !== 0;\n  }\n\n  function buildEmailHTML(assets, stats) {\n    const date = new Date().toLocaleDateString('pl-PL', { day: 'numeric', month: 'long', year: 'numeric' });\n    const row = (label, val, color) => `<tr><td bgcolor=\"#ffffff\" style=\"background-color:#ffffff;padding:7px 0;border-bottom:1px solid #EEEEEE;color:#6B7280;font-size:13px\">${label}<\/td><td bgcolor=\"#ffffff\" style=\"background-color:#ffffff;padding:7px 0;border-bottom:1px solid #EEEEEE;text-align:right;font-weight:700;font-size:13px;color:${color || '#0F152E'}\">${val}<\/td><\/tr>`;\n    const rows = row('Warto\u015b\u0107 portfela', fmtPLN(stats.total), '#D3AF37')\n      + row('\u015arednia wa\u017cona stopa zwrotu', fmtPct(stats.wr))\n      + row('Roczny kupon \/ CF (wyp\u0142acany)', fmtPLN(stats.income))\n      + row('Oczekiwany wzrost kapita\u0142u (rocznie)', fmtPLN(stats.growth))\n      + row('Liczba aktyw\u00f3w', String(assets.length));\n    const assetRows = assets.map(a => row(esc(a.name) + ` (${a.category === 'financial' ? 'fin.' : 'mat.'}, ${a.returnRate}%)`, fmtPLN(a.value))).join('');\n    return `<div style=\"font-family:Arial,Helvetica,sans-serif;max-width:600px;margin:0 auto;background:#ffffff\">\n      <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background:#0F152E;border-radius:10px 10px 0 0\">\n        <tr>\n          <td style=\"padding:20px 12px 20px 26px;width:56px\" valign=\"middle\"><img decoding=\"async\" src=\"${PF_LOGO_URL}\" width=\"44\" alt=\"\" style=\"display:block;width:44px;height:auto\"><\/td>\n          <td style=\"padding:22px 26px 22px 0\" valign=\"middle\">\n            <div style=\"color:#9AA6C4;font-size:10px;letter-spacing:.09em;text-transform:uppercase;margin-bottom:4px\">Jacek Horoszko Investment Partner<\/div>\n            <div style=\"color:#ffffff;font-size:20px;font-weight:700\">Analiza portfela inwestycyjnego<\/div>\n            <div style=\"color:#9AA6C4;font-size:11px;margin-top:4px\">Sporz\u0105dzono: ${date}<\/div>\n          <\/td>\n        <\/tr>\n      <\/table>\n      <div bgcolor=\"#ffffff\" style=\"background-color:#ffffff;border:1px solid #E5E7EB;border-top:none;border-radius:0 0 10px 10px;padding:22px 26px\">\n        <table bgcolor=\"#ffffff\" style=\"width:100%;border-collapse:collapse;background-color:#ffffff\">${rows}<\/table>\n        <div bgcolor=\"#ffffff\" style=\"background-color:#ffffff;margin-top:20px;font-size:11px;font-weight:700;color:#0F152E\">Sk\u0142ad portfela<\/div>\n        <table bgcolor=\"#ffffff\" style=\"width:100%;border-collapse:collapse;margin-top:6px;background-color:#ffffff\">${assetRows}<\/table>\n        <div bgcolor=\"#ffffff\" style=\"background-color:#ffffff;margin-top:20px;font-size:10px;color:#9CA3AF;line-height:1.6\">Analiza sporz\u0105dzona przy u\u017cyciu Kalkulatora Portfela Inwestycyjnego Jacek Horoszko Investment Partner. Dane maj\u0105 charakter pogl\u0105dowy. Przesz\u0142e wyniki nie gwarantuj\u0105 przysz\u0142ych rezultat\u00f3w. Dokument nie stanowi porady inwestycyjnej ani oferty w rozumieniu przepis\u00f3w prawa.<\/div>\n        <table role=\"presentation\" bgcolor=\"#ffffff\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin-top:16px;padding-top:14px;border-top:1px solid #E5E7EB;width:100%;background-color:#ffffff\">\n          <tr>\n            <td bgcolor=\"#ffffff\" style=\"background-color:#ffffff;width:56px\" valign=\"top\"><img decoding=\"async\" src=\"${PF_PHOTO_URL}\" width=\"48\" alt=\"\" style=\"display:block;width:48px;height:auto;border-radius:8px\"><\/td>\n            <td bgcolor=\"#ffffff\" style=\"background-color:#ffffff\" valign=\"top\">\n              <div style=\"color:#0F152E;font-size:13px;font-weight:700\">Jacek Horoszko<\/div>\n              <div style=\"color:#9CA3AF;font-size:11px\">Investment Partner \u00b7 www.jacekhoroszko.pl<\/div>\n              <div style=\"color:#9CA3AF;font-size:11px\">694 526 704 \u00b7 jacek.horoszko@phinance.pl<\/div>\n            <\/td>\n          <\/tr>\n        <\/table>\n      <\/div>\n    <\/div>`;\n  }\n\n  function buildLeadEmailHTML(assets, stats, lead) {\n    const row = (label, val) => `<tr><td bgcolor=\"#ffffff\" style=\"background-color:#ffffff;padding:7px 0;border-bottom:1px solid #EEEEEE;color:#6B7280;font-size:13px\">${label}<\/td><td bgcolor=\"#ffffff\" style=\"background-color:#ffffff;padding:7px 0;border-bottom:1px solid #EEEEEE;text-align:right;font-weight:700;font-size:13px;color:#0F152E\">${val}<\/td><\/tr>`;\n    const rows = row('Imi\u0119', esc(lead.imie))\n      + row('E-mail', esc(lead.email))\n      + row('Telefon', esc(lead.telefon))\n      + row('Warto\u015b\u0107 portfela', fmtPLN(stats.total))\n      + row('\u015ar. wa\u017cona stopa zwrotu', fmtPct(stats.wr))\n      + row('Liczba aktyw\u00f3w', String(assets.length));\n    return `<div style=\"font-family:Arial,Helvetica,sans-serif;max-width:600px;margin:0 auto;background:#ffffff\">\n      <div style=\"background:#0F152E;padding:18px 24px;border-radius:10px 10px 0 0\">\n        <div style=\"color:#9AA6C4;font-size:10px;letter-spacing:.09em;text-transform:uppercase\">Kalkulator Portfela \u2014 jacekhoroszko.com<\/div>\n        <div style=\"color:#ffffff;font-size:18px;font-weight:700;margin-top:4px\">Nowe zg\u0142oszenie od klienta<\/div>\n      <\/div>\n      <div bgcolor=\"#ffffff\" style=\"background-color:#ffffff;border:1px solid #E5E7EB;border-top:none;border-radius:0 0 10px 10px;padding:20px 24px\">\n        <table bgcolor=\"#ffffff\" style=\"width:100%;border-collapse:collapse;background-color:#ffffff\">${rows}<\/table>\n      <\/div>\n    <\/div>`;\n  }\n\n  function openEmailForm() {\n    const panel = document.getElementById('jhip-pf-email-panel');\n    if (panel) panel.style.display = 'block';\n  }\n  function closeEmailForm() {\n    const panel = document.getElementById('jhip-pf-email-panel');\n    if (panel) panel.style.display = 'none';\n  }\n\n  function sendResultEmail() {\n    const nameInput = document.getElementById('jhip-pf-email-name');\n    const phoneInput = document.getElementById('jhip-pf-email-phone');\n    const input = document.getElementById('jhip-pf-email-input');\n    const statusEl = document.getElementById('jhip-pf-email-status');\n    const btn = document.getElementById('jhip-pf-email-send-btn');\n    const imie = (nameInput.value || '').trim();\n    const telefon = (phoneInput.value || '').trim();\n    const email = (input.value || '').trim();\n    const emailRe = \/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$\/;\n    const phoneRe = \/^[0-9+()\\s-]{6,20}$\/;\n    if (!imie) {\n      statusEl.textContent = 'Podaj imi\u0119.';\n      statusEl.style.color = '#F3A3AC';\n      return;\n    }\n    if (!phoneRe.test(telefon)) {\n      statusEl.textContent = 'Podaj poprawny numer telefonu.';\n      statusEl.style.color = '#F3A3AC';\n      return;\n    }\n    if (!emailRe.test(email)) {\n      statusEl.textContent = 'Podaj poprawny adres e-mail.';\n      statusEl.style.color = '#F3A3AC';\n      return;\n    }\n    if (!emailjsReady()) {\n      statusEl.textContent = 'Wysy\u0142ka e-mail nie jest jeszcze skonfigurowana.';\n      statusEl.style.color = '#F3A3AC';\n      return;\n    }\n    const assets = state.assets;\n    const stats = calcStats(assets);\n    btn.disabled = true;\n    btn.textContent = 'Wysy\u0142anie\u2026';\n    statusEl.textContent = '';\n\n    const clientSend = emailjs.send(EMAILJS_SERVICE_ID, EMAILJS_TEMPLATE_ID, {\n      to_email: email,\n      subject: `${imie}, oto Twoja analiza portfela inwestycyjnego \u2014 Jacek Horoszko Investment Partner`,\n      summary_html: buildEmailHTML(assets, stats),\n    }, { publicKey: EMAILJS_PUBLIC_KEY });\n\n    const leadSend = emailjs.send(EMAILJS_SERVICE_ID, EMAILJS_TEMPLATE_ID, {\n      to_email: JHIP_LEAD_EMAIL,\n      subject: `Nowe zg\u0142oszenie z kalkulatora portfela \u2014 ${imie}`,\n      summary_html: buildLeadEmailHTML(assets, stats, { imie, telefon, email }),\n    }, { publicKey: EMAILJS_PUBLIC_KEY });\n\n    Promise.all([clientSend, leadSend]).then(function () {\n      statusEl.textContent = '\u2713 Wys\u0142ano! Sprawd\u017a skrzynk\u0119 ' + esc(email) + '.';\n      statusEl.style.color = '#8FD19E';\n      btn.disabled = false;\n      btn.textContent = 'Wy\u015blij';\n    }, function (err) {\n      statusEl.textContent = 'Nie uda\u0142o si\u0119 wys\u0142a\u0107. Spr\u00f3buj ponownie.';\n      statusEl.style.color = '#F3A3AC';\n      btn.disabled = false;\n      btn.textContent = 'Wy\u015blij';\n      console.error('EmailJS error:', err);\n    });\n  }\n\n  \/\/ \u2500\u2500 Inicjalizacja \u2500\u2500\n  window.JhipPortfel = { setTab, updateFormType, updateForm, updateFormField, toggleReinvest, submitForm, startEdit, removeAsset, cancelEdit, openEmailForm, closeEmailForm, sendResultEmail };\n\n  function init() {\n    if (!document.getElementById('jhip-pf-tabs')) return;\n    render();\n  }\n  if (document.readyState === 'loading') {\n    document.addEventListener('DOMContentLoaded', init);\n  } else {\n    init();\n  }\n})();\n<\/script>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-268572f elementor-widget elementor-widget-spacer\" data-id=\"268572f\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"spacer.default\">\n\t\t\t\t\t\t\t<div class=\"elementor-spacer\">\n\t\t\t<div class=\"elementor-spacer-inner\"><\/div>\n\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Kalkulator Portfela Inwestycyjnego Zbuduj portfel, sprawd\u017a alokacj\u0119, \u015bredni\u0105 stop\u0119 zwrotu i oczekiwany doch\u00f3d<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-392","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"http:\/\/jacekhoroszko.com\/index.php\/wp-json\/wp\/v2\/pages\/392","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/jacekhoroszko.com\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"http:\/\/jacekhoroszko.com\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"http:\/\/jacekhoroszko.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/jacekhoroszko.com\/index.php\/wp-json\/wp\/v2\/comments?post=392"}],"version-history":[{"count":13,"href":"http:\/\/jacekhoroszko.com\/index.php\/wp-json\/wp\/v2\/pages\/392\/revisions"}],"predecessor-version":[{"id":477,"href":"http:\/\/jacekhoroszko.com\/index.php\/wp-json\/wp\/v2\/pages\/392\/revisions\/477"}],"wp:attachment":[{"href":"http:\/\/jacekhoroszko.com\/index.php\/wp-json\/wp\/v2\/media?parent=392"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}