GPW
PRIVATE WEALTH

Bonjour Joseph

Accès sécurisé à votre espace privé.

Touchez Face ID pour ouvrir le compte démo Joseph.
STUDENT ACCESS
Student sign inAccess your own training profile
Open a training accountCreate your individual student profile

Training environment. Use fictitious/demo identity material only.

Operator access

Dashboard

MASTER RC13 · MULTI-STUDENT
GPW PRIVATE CLIENT

Good evening

Your banking, wealth and training workspace.

🇪🇺
ACTIVE ACCOUNT
Available balance
Account reference
ACTIVITY

Latest movements

TRAINING

Learning workspace

Bonjour Joseph

Available balance
● • • •

Latest movements

    Total cash
    Accounts0
    Movements0
    Messages0

    Add currency account

    Create a training account in another currency.

    GPW COMPLIANCE ACADEMY

    Open a training account

    Simulate the same onboarding sequence used by modern digital banks.

    TRAINING ONLY

    1 · Account application

    2 · Identity verification

    Didactic capture only.Use demo/training documents. Never upload a real identity document to this internal simulator.
    Simulated liveness

    Center your face, remove glasses if possible, look left, look right, then capture a frontal image.

    My compliance cases

    REAL IDV INTEGRATION

    Provider-ready architecture

    For live identity verification, GPW should hand the user off to a specialised KYC provider rather than storing real IDs itself.

    VeriffPersonaExternal KYC SDK
    GPWPRIVATE CLIENT

    Joseph Samba

    GIC-PRIVATE-000002

    Active
    AccessPrivate
    SecurityFace ID
    TrainingAcademy
    Mobile
    AddressPrivate client profile
    StatusActive
    GPW Banking Training Engine · RC11.3
    Search

    Transaction history

    Tap for details

      Add money

      Choisissez le compte à créditer.

      Bank transfer

      Coordonnées bancaires de ce compte

      From another GPW account

      Transfert interne avec conversion automatique

      Other methods

      Approvisionnement instantané simulé

      Move money

      Live exchange preview

      You receive
      GPWFOREIGN EXCHANGE

      Exchange

      Live market preview with training fallback.

      Market rateLoading…

      Financing request

      GPWPRIVATE TRANSFER

      Send money

      Simple transfer instruction.

      1 Beneficiary2 Amount3 Review

      Who are you paying?

      Country will be detected from IBAN

      How much?

      EUR
      Use a clear reason so the beneficiary and operator understand the payment.

      Review

      Instruction received

      Internal version — operation to be approved by the banking operator.

      Search
      Savings
      Your savings spaces

      Savings spaces

        Create savings space

        GPWPRIVATE CARDS

        Choose your card

        Compare the three service levels and select the profile that fits the training case.

        Monthly fee€ 19
        Credit line€ 15.000
        ServicePrivate

        Everyday private banking training: transfers, card controls, statements and standard priority assistance.

        Monthly fee€ 39
        Credit line€ 35.000
        ServicePriority

        Enhanced private-client scenario with higher limits, priority operations and more demanding travel and purchase cases.

        Monthly fee€ 89
        Credit line€ 75.000
        ServiceDedicated

        High-net-worth training profile with dedicated operations, premium service expectations and complex transaction scenarios.

        Selected monthly fee€ 19
        Selected credit line€ 15,000
        Selected servicePrivate

        Request this card

        Requests & cards

          NP
          Nathalie PredettiPrivate Assistance · Online
          Hello Joseph, I’m Nathalie Predetti. How can I help you today?
          Search
          Wealth tools

          Wealth

          Savings, bourse, digital assets, patrimoine and financing.

          Crypto snapshot

          Market focus

          Global equitiesBalanced
          Fixed incomeShort duration
          CommoditiesGold & energy
          Risk profileModerate

          Patrimony

          Liquid assets€ 3.297.848
          Investments€ 846.500
          Digital assets€ 211.400
          Inventory items12 assets
          LEARN BY DOING

          Financial literacy

          Micro-lessons

          Digital assets watchlist

          Training market
          Search
          DIGITAL ASSETS

          Digital assets

          A broader crypto universe for training, suitability and client conversations.

          Top coins

          Live / fallback

          Suitability notes

          Academy
          • VolatilityPrices can move sharply in both directions.
          • StablecoinsUSDT and USDC are mainly used for transfers and liquidity, not for growth expectations.
          • DiversificationClients usually compare BTC / ETH core exposure with smaller satellite positions.
          • ComplianceSource of funds, wallet origin and suitability remain essential.
          Search
          MARKETS & BOURSE

          Markets

          Dedicated overview for indices, equities, ETFs and commodities.

          Watchlist

          Indicative

          Portfolio ideas

          Training only
          Core equityMSCI World ETF
          IncomeEuro bonds
          DefensiveGold ETC
          TacticalNasdaq 100

          Investor themes

          Vivid-inspired richness
          • TechnologyApple, Microsoft, ASML and semiconductors
          • Luxury EuropeLVMH and premium consumer names
          • EnergyTotalEnergies and diversified commodities
          • MacroRates, inflation and risk sentiment
          Search
          PATRIMONY

          Patrimony

          Net worth overview, allocation and inventory management.

          Allocation

          Updated today
          Cash€ 3.297.848
          Portfolio€ 846.500
          Digital assets€ 211.400
          Real assets€ 1.935.000

          Inventory & holdings

          Management
          Residential propertyValencia · core patrimonial asset€ 1.250.000
          Classic vehiclesCollection · 5 units€ 420.000
          Luxury watches & jewelryInsured inventory€ 165.000
          Business equipmentOperating inventory€ 100.000

          Management tasks

          Training workflow
          • ValuationKeep market values, dates and supporting documents updated.
          • CustodySeparate liquid assets, market assets and inventory items.
          • ReportingProvide a consolidated patrimony view to the client.
          • SuitabilityLink liquidity needs with risk tolerance and investment horizon.

          Statements

          Generate training documents from account activity.

          Generate document

          Proof of funds

          Training-only document generator.

          VERSION DIDACTIQUELe document généré sera explicitement marqué comme simulation de formation, sans valeur bancaire, juridique ou financière.
          Available balanceEvidence amount
          Search

          More

          Digital assets

          Live
          BTC Bitcoin
          ETH Ethereum
          SOL Solana
          XRP Ripple
          ADA Cardano
          USDT Tether

          Market board

          Training market
          GPW WEALTHCORE
          Operator Control Center · Wealth Operations
          Clients0
          Accounts0
          Transactions0
          Pending0
          GPW COMPLIANCE DESK
          Onboarding · KYC training · account-opening decisions
          Open cases0
          Awaiting documents0
          Under review0
          Approved0

          Compliance cases

          Clients

          NameEmailTypeStatusAccountsAction

          Client

          • Email
          • Phone
          • Type

          Create account

          Client accounts

            Credit

            Debit

            No movement posted in this session.

            Approvals

            IDTypeClientDetailsStatusAction

            Ledger

            IDReferenceTypeAmountStatus

            Audit

              Client messages

                Conversation

                Outgoing transfers

                ClientBeneficiaryCountryReferenceAmountStatus

                Card requests

                ClientProductLimitCardStatus

                Select account

                `); w.document.close(); } function openStatementGenerator150(t){statementTitle150.textContent=t;statementDate150.value=new Date().toISOString().slice(0,10);renderSelectors150();show('c-statement-generator','Statement')} async function generateStatement150(){let a=accounts150.find(x=>x.number===statementAccount150.value)||activeAccount150,d=await api('/api/client-summary'),rows=(d.movements||[]).map(m=>`${m.created_at}${m.reference}${m.amount_display}`).join('');let w=window.open('','_blank');w.document.write(` ${statementTitle150.textContent}

                GPW Private Wealth

                TRAINING ACCOUNT DOCUMENT

                ${statementTitle150.textContent}

                Joseph Samba · ${a.number} · ${a.currency}

                Balance: ${fmt150(a.balance,a.currency)} · Date: ${statementDate150.value}

                ${rows}
                DateDescriptionAmount

                Internal training document${statementSigned150.checked?' · training signature/seal':''}.

                `);w.document.close();setTimeout(()=>w.print(),300)} function lang150(t){t=t.toLowerCase();if(/[¿¡]|hola|quiero|tarjeta|gracias/.test(t))return'es';if(/bonjour|virement|carte|merci|prêt|épargne/.test(t))return'fr';if(/hallo|überweisung|karte|danke|kredit/.test(t))return'de';if(/olá|transferência|cartão|obrigado/.test(t))return'pt';if(/ciao|bonifico|carta|grazie/.test(t))return'it';return'en'} function answer150(q,l){let t=/transfer|virement|transferencia|überweisung|bonifico/.test(q.toLowerCase())?'transfer':/card|carte|tarjeta|karte|carta/.test(q.toLowerCase())?'card':'general';let r={fr:{transfer:"Je peux vous guider pour un virement. Ouvrez Transfer, puis saisissez bénéficiaire, IBAN et montant.",card:"Vous pouvez comparer Gold, Platinum et Black dans Pay et poursuivre la demande.",general:"Je peux vous aider pour les comptes, virements, cartes, financement, épargne et change."},es:{transfer:"Puedo guiarte con una transferencia: beneficiario, IBAN e importe.",card:"Puedes comparar Gold, Platinum y Black en Pay.",general:"Puedo ayudarte con cuentas, transferencias, tarjetas, financiación, ahorro y cambio."},de:{transfer:"Ich kann Sie bei einer Überweisung führen: Empfänger, IBAN und Betrag.",card:"Unter Pay können Sie Gold, Platinum und Black vergleichen.",general:"Ich helfe bei Konten, Überweisungen, Karten, Finanzierung, Sparen und Devisen."},pt:{transfer:"Posso orientar uma transferência: beneficiário, IBAN e valor.",card:"Em Pay pode comparar Gold, Platinum e Black.",general:"Posso ajudar com contas, transferências, cartões, financiamento, poupança e câmbio."},it:{transfer:"Posso guidarti in un bonifico: beneficiario, IBAN e importo.",card:"In Pay puoi confrontare Gold, Platinum e Black.",general:"Posso aiutarti con conti, bonifici, carte, finanziamenti, risparmio e cambio."},en:{transfer:"I can guide you through a transfer: beneficiary, IBAN and amount.",card:"You can compare Gold, Platinum and Black in Pay.",general:"I can help with accounts, transfers, cards, financing, savings and FX."}};return (r[l]||r.en)[t]} function advisorSend150(){let q=chatInput.value.trim();if(!q)return;let m=document.createElement('div');m.className='bubble me';m.textContent=q;chatWindow.appendChild(m);chatInput.value='';let l=lang150(q);setTimeout(()=>{let b=document.createElement('div');b.className='bubble advisor-bubble140';b.textContent=answer150(q,l);chatWindow.appendChild(b);chatWindow.scrollTop=chatWindow.scrollHeight},180)} (async()=>{try{if('caches'in window){for(const k of await caches.keys()){if(k!=='gpw-v0151')await caches.delete(k)}}if('serviceWorker'in navigator){const regs=await navigator.serviceWorker.getRegistrations();for(const r of regs){if(r.active&&r.active.scriptURL.includes('sw.js')) await r.update()}}}catch(e){}})(); const LANGWORDS152={ fr:["bonjour","salut","je","j’","j'","veux","voudrais","aimerais","crédit","credit","prêt","pret","virement","carte","compte","épargne","epargne","comment","ça","merci","financement"], es:["hola","quiero","quisiera","tarjeta","transferencia","cuenta","ahorro","crédito","credito","préstamo","prestamo","gracias","cómo","como"], de:["hallo","ich","möchte","uberweisung","überweisung","karte","konto","sparen","kredit","darlehen","danke","wie"], it:["ciao","vorrei","voglio","bonifico","carta","conto","risparmio","prestito","credito","grazie","come"], pt:["olá","ola","quero","gostaria","transferência","transferencia","cartão","cartao","conta","poupança","poupanca","crédito","credito","empréstimo","emprestimo","obrigado","como"], ar:["مرحبا","أريد","حساب","تحويل","بطاقة","قرض","تمويل","شكرا"], zh:["你好","您好","账户","转账","银行卡","贷款","融资","储蓄","谢谢"] }; function detectLang152(text){ const t=text.toLowerCase(); let best='en',score=0; Object.entries(LANGWORDS152).forEach(([lang,words])=>{let s=words.reduce((n,w)=>n+(t.includes(w)?1:0),0);if(s>score){score=s;best=lang}}); return best; } function intent152(text){ const t=text.toLowerCase(); if(/crédit|credit|prêt|pret|loan|financ|kredit|darlehen|prestito|empréstimo|emprestimo|قرض|تمويل|贷款|融资/.test(t))return'finance'; if(/virement|transfer|transferencia|überweisung|uberweisung|bonifico|transferência|تحويل|转账/.test(t))return'transfer'; if(/carte|card|tarjeta|karte|cartão|cartao|بطاقة|银行卡/.test(t))return'card'; if(/épargne|epargne|saving|ahorro|sparen|risparmio|poupança|poupanca|储蓄/.test(t))return'saving'; if(/change|fx|exchange|devise|divisa|währung|cambio|câmbio|صرف|汇率/.test(t))return'fx'; if(/compte|account|cuenta|konto|conto|conta|حساب|账户/.test(t))return'account'; return'general'; } const RESP152={ fr:{finance:"Bien sûr. Pour préparer une demande de financement, indiquez-moi d’abord le montant souhaité, la devise et l’objet du financement. Je pourrai ensuite vous guider vers la demande à transmettre à Wealth Operations.",transfer:"Je peux vous accompagner. Il me faut le nom du bénéficiaire et son IBAN. L’application détectera le pays, puis vous indiquerez le montant et la référence avant validation opérateur.",card:"Vous pouvez comparer Gold, Platinum et Black dans Pay. Chaque carte affiche son réseau, son coût mensuel d’entraînement, sa ligne de crédit et son niveau de service. Je peux aussi vous aider à choisir.",saving:"Vous pouvez créer un espace d’épargne, choisir son nom et sa devise, puis l’alimenter depuis votre compte principal.",fx:"Vous pouvez convertir entre vos comptes en devises depuis Move. Le taux et le montant reçu sont affichés avant validation.",account:"Vous pouvez ouvrir plusieurs comptes en devises et les alimenter depuis le compte principal. Touchez Accounts puis + pour en créer un.",general:"Je peux vous aider pour vos comptes, transferts, cartes, financement, épargne, change et relevés. Dites-moi ce que vous souhaitez faire."}, en:{finance:"Certainly. Tell me the desired amount, currency and purpose of the financing. I can then guide you through the training request for Wealth Operations.",transfer:"I can guide you through it. Enter the beneficiary and IBAN, then the amount and reference. The destination country is detected automatically before operator approval.",card:"In Pay you can compare Gold, Platinum and Black. Each card shows its network, monthly training fee, credit line and service level.",saving:"Create a savings space, choose its name and currency, then fund it from your principal account.",fx:"Use Move to exchange between currency accounts. The rate and amount received are shown before confirmation.",account:"You can create additional currency accounts and fund them from the principal balance. Open Accounts and tap +.",general:"I can help with accounts, transfers, cards, financing, savings, FX and statements. Tell me what you want to do."}, es:{finance:"Claro. Indícame el importe, la divisa y la finalidad de la financiación. Después puedo guiarte para preparar la solicitud de formación para Wealth Operations.",transfer:"Puedo ayudarte. Introduce beneficiario e IBAN; la aplicación detectará el país. Después añade importe y referencia antes de la aprobación del operador.",card:"En Pay puedes comparar Gold, Platinum y Black. Cada tarjeta muestra red, cuota mensual de formación, línea de crédito y nivel de servicio.",saving:"Puedes crear un espacio de ahorro, elegir nombre y divisa y financiarlo desde la cuenta principal.",fx:"Usa Move para cambiar entre cuentas en distintas divisas. Verás el tipo y el importe recibido antes de confirmar.",account:"Puedes crear cuentas adicionales en otras divisas y financiarlas desde el saldo principal. Abre Accounts y pulsa +.",general:"Puedo ayudarte con cuentas, transferencias, tarjetas, financiación, ahorro, cambio y extractos."}, de:{finance:"Gern. Nennen Sie Betrag, Währung und Zweck der Finanzierung. Danach kann ich Sie durch die Trainingsanfrage an Wealth Operations führen.",transfer:"Geben Sie Empfänger und IBAN ein. Das Zielland wird automatisch erkannt; danach folgen Betrag und Verwendungszweck vor der Operator-Freigabe.",card:"Unter Pay können Sie Gold, Platinum und Black vergleichen. Netzwerk, Trainingsgebühr, Kreditlinie und Service-Level werden angezeigt.",saving:"Sie können einen Sparbereich anlegen, Namen und Währung wählen und ihn vom Hauptkonto finanzieren.",fx:"Mit Move können Sie zwischen Währungskonten wechseln. Kurs und Empfangsbetrag werden vor Bestätigung angezeigt.",account:"Sie können zusätzliche Währungskonten erstellen und vom Hauptsaldo aus finanzieren. Öffnen Sie Accounts und tippen Sie auf +.",general:"Ich helfe bei Konten, Überweisungen, Karten, Finanzierung, Sparen, Devisen und Kontoauszügen."}, it:{finance:"Certamente. Indicami importo, valuta e finalità del finanziamento. Poi posso guidarti nella richiesta didattica a Wealth Operations.",transfer:"Inserisci beneficiario e IBAN; il paese viene riconosciuto automaticamente. Poi aggiungi importo e causale prima dell’approvazione operatore.",card:"In Pay puoi confrontare Gold, Platinum e Black con circuito, costo mensile, linea di credito e livello di servizio.",saving:"Puoi creare uno spazio di risparmio, scegliere nome e valuta e finanziarlo dal conto principale.",fx:"Usa Move per convertire tra conti in valuta. Tasso e importo ricevuto vengono mostrati prima della conferma.",account:"Puoi creare conti aggiuntivi in valuta e alimentarli dal saldo principale. Apri Accounts e premi +.",general:"Posso aiutarti con conti, bonifici, carte, finanziamenti, risparmio, cambio ed estratti."}, pt:{finance:"Claro. Informe o valor, a moeda e a finalidade do financiamento. Depois posso orientar o pedido de treino para Wealth Operations.",transfer:"Introduza beneficiário e IBAN; o país será reconhecido automaticamente. Depois indique valor e referência antes da aprovação do operador.",card:"Em Pay pode comparar Gold, Platinum e Black, com rede, mensalidade de treino, linha de crédito e nível de serviço.",saving:"Pode criar um espaço de poupança, escolher nome e moeda e financiá-lo a partir da conta principal.",fx:"Use Move para converter entre contas em moeda. A taxa e o valor recebido aparecem antes da confirmação.",account:"Pode criar contas adicionais em moeda e financiá-las a partir do saldo principal. Abra Accounts e toque em +.",general:"Posso ajudar com contas, transferências, cartões, financiamento, poupança, câmbio e extratos."}, ar:{finance:"بالتأكيد. أخبرني بالمبلغ والعملة والغرض من التمويل، ثم يمكنني إرشادك لإعداد طلب التدريب إلى Wealth Operations.",general:"يمكنني مساعدتك في الحسابات والتحويلات والبطاقات والتمويل والادخار والصرف وكشوف الحساب."}, zh:{finance:"当然可以。请告诉我融资金额、币种和用途,我可以继续引导您准备提交给 Wealth Operations 的培训申请。",general:"我可以帮助您处理账户、转账、银行卡、融资、储蓄、外汇和对账单。"} }; function advisorSend150(){ let q=chatInput.value.trim();if(!q)return; let me=document.createElement('div');me.className='bubble me';me.textContent=q;chatWindow.appendChild(me);chatInput.value=''; const lang=detectLang152(q),intent=intent152(q),bank=RESP152[lang]||RESP152.en; setTimeout(()=>{let b=document.createElement('div');b.className='bubble advisor-bubble140';b.textContent=bank[intent]||bank.general;chatWindow.appendChild(b);chatWindow.scrollTop=chatWindow.scrollHeight},180); } function quick150(q){chatInput.value=q;advisorSend150()} function principal152(){return accounts150.find(a=>a.currency==='EUR' && (a.type==='Private Current'||a.name==='Private Current'))||accounts150[0]} function balanceAdjustments152(){return JSON.parse(localStorage.getItem('gpw_balance_adjustments152')||'{}')} function saveAdjustments152(x){localStorage.setItem('gpw_balance_adjustments152',JSON.stringify(x))} function applyAdjustments152(){ const adj=balanceAdjustments152(); accounts150.forEach(a=>{ // Never overwrite database-backed balances with stale browser values. if(a.source==='extra' && adj[a.number]!=null && Number.isFinite(Number(adj[a.number]))){ a.balance=Number(adj[a.number]); } }); } function purgeBaseBalanceOverrides161(){ const adj=balanceAdjustments152(); let changed=false; accounts150.forEach(a=>{ if(a.source!=='extra' && Object.prototype.hasOwnProperty.call(adj,a.number)){ delete adj[a.number]; changed=true; } }); if(changed)saveAdjustments152(adj); } const _loadAccounts152=loadAccounts150; loadAccounts150=async function(){ await _loadAccounts152(); purgeBaseBalanceOverrides161(); applyAdjustments152(); if(typeof renderActiveAccount150==='function')renderActiveAccount150(); if(typeof renderAccountSelectors150==='function')renderAccountSelectors150(); renderHomeAccount152(); } function renderHomeAccount152(){if(!activeAccount150)return;let f=document.getElementById('homeFlag152'),a=document.getElementById('homeAcct152');if(f)f.textContent=FLAGS150[activeAccount150.currency]||'◉';if(a)a.textContent=`${activeAccount150.name} · ${activeAccount150.currency}`} async function populateFund152(){ await loadAccounts150(); const p=principal152();if(fundPrincipalBal152)fundPrincipalBal152.textContent=fmt150(p.balance,p.currency); fundTo152.innerHTML=accounts150.filter(a=>a.number!==p.number).map(a=>``).join(''); if(activeAccount150 && activeAccount150.number!==p.number)fundTo152.value=activeAccount150.number; updateFund152(); } async function updateFund152(){ let p=principal152(),to=accounts150.find(a=>a.number===fundTo152.value);if(!p||!to)return; let amt=parseFloat(String(fundAmount152.value).replace(',','.'))||0,rate=1; try{let r=await fetch(`https://api.frankfurter.app/latest?amount=1&from=${p.currency}&to=${to.currency}`);let d=await r.json();rate=d.rates[to.currency]||1}catch(e){const f={EUR:1,USD:1.17,GBP:.87,CHF:.93,JPY:172};rate=(f[to.currency]||1)/(f[p.currency]||1)} fundReceive152.dataset.value=String(amt*rate);fundReceive152.textContent=fmt150(amt*rate,to.currency);fundRate152.textContent=`1 ${p.currency} = ${rate.toFixed(to.currency==='JPY'?2:4)} ${to.currency}`; } function submitFund152(){ let p=principal152(),to=accounts150.find(a=>a.number===fundTo152.value),amt=parseFloat(String(fundAmount152.value).replace(',','.'))||0,recv=parseFloat(fundReceive152.dataset.value||0); if(!p||!to||amt<=0)return toastMsg('Enter an amount.'); if(p.balance=0?'+':'')+fmt150(amount,a.currency)}); localStorage.setItem('gpw_local_movements154',JSON.stringify(x.slice(0,250))); } function movementsFor154(a){ const local=localMovements154().filter(x=>x.account===a.number); const principal=principal152(); const base=(window.gpwSummary154&&principal&&a.number===principal.number)?(window.gpwSummary154.movements||[]):[]; return [...local,...base]; } function renderAccountMovements154(){ if(!activeAccount150)return; const items=movementsFor154(activeAccount150); const acc=document.getElementById('accountMovements'); if(acc)renderMovements140(acc,items); const home=document.getElementById('cMovements'); if(home)renderMovements140(home,items.slice(0,6)); } function renderHomeAccount154(){ if(!activeAccount150)return; let f=document.getElementById('homeFlag152'),a=document.getElementById('homeAcct152'),b=document.getElementById('mobileCash'); if(f)f.textContent=FLAGS150[activeAccount150.currency]||'◉'; if(a)a.textContent=`${activeAccount150.name} · ${activeAccount150.currency}`; if(b)b.textContent=fmt150(activeAccount150.balance,activeAccount150.currency); let dots=document.querySelector('.dots152,.dots154'); if(dots){ dots.className='dots154';dots.innerHTML=''; accounts150.forEach((acc,i)=>{ let d=document.createElement('button');d.className=acc.number===activeAccount150.number?'on':''; d.onclick=()=>selectAccountIndex154(i);dots.appendChild(d); }) } } function selectAccountIndex154(i){ let a=accounts150[i];if(!a)return; activeAccount150=a;localStorage.setItem('gpw_active_account150',a.number); renderAccount150();renderHomeAccount154();renderAccountMovements154(); setTimeout(()=>syncClientFromServer160(true),80); } let swipeBound154=false; function bindHomeSwipe154(){ if(swipeBound154)return; let hero=document.querySelector('.hero152');if(!hero)return; swipeBound154=true;let sx=null; hero.addEventListener('touchstart',e=>sx=e.touches[0].clientX,{passive:true}); hero.addEventListener('touchend',e=>{ if(sx===null)return; let dx=e.changedTouches[0].clientX-sx; if(Math.abs(dx)>45&&accounts150.length>1){ let i=accounts150.findIndex(a=>a.number===activeAccount150.number); selectAccountIndex154(dx<0?(i+1)%accounts150.length:(i-1+accounts150.length)%accounts150.length); } sx=null; },{passive:true}); } function deleteCurrencyAccount154(no){ let a=accounts150.find(x=>x.number===no); if(!a||a.source!=='extra')return; if(!confirm(`Delete ${a.name} · ${a.currency}?`))return; let ex=JSON.parse(localStorage.getItem('gpw_extra_accounts150')||'[]').filter(x=>(x.number||'').replace(/-/g,'')!==no); localStorage.setItem('gpw_extra_accounts150',JSON.stringify(ex)); let adj=balanceAdjustments152();delete adj[no];saveAdjustments152(adj); if(activeAccount150.number===no){let p=principal152();localStorage.setItem('gpw_active_account150',p.number)} loadAccounts150();closeAccountSheet150();toastMsg('Account deleted.'); } function selectedTopupAccount155(){ return accounts150.find(a=>a.number===fundTo152.value)||activeAccount150||accounts150[0]; } function renderTopupDetails155(){ const a=selectedTopupAccount155();if(!a)return; const no=document.getElementById('topupAccountNo155'),ccy=document.getElementById('topupCurrency155'),pv=document.getElementById('gpwSourcePreview155'); if(no)no.textContent=a.number;if(ccy)ccy.textContent=a.currency; if(pv){ const sources=accounts150.filter(x=>x.number!==a.number); pv.textContent=sources.length?sources.map(x=>`${x.name} · ${x.currency} · ${fmt150(x.balance,x.currency)}`).join(' • '):'No other GPW account is available.'; } } async function copyRaw155(txt){ try{await navigator.clipboard.writeText(txt);toastMsg('Copied.')}catch(e){ const t=document.createElement('textarea');t.value=txt;document.body.appendChild(t);t.select();document.execCommand('copy');t.remove();toastMsg('Copied.'); } } function copyText155(id){const el=document.getElementById(id);if(el)copyRaw155(el.textContent.trim())} function openMoveToActive155(){ const target=selectedTopupAccount155(); show('c-exchange','Between accounts',false); setTimeout(()=>{ renderSelectors150(); if(target && document.getElementById('moveTo150')) moveTo150.value=target.number; const source=accounts150.find(a=>a.number!==target?.number); if(source && document.getElementById('moveFrom150')) moveFrom150.value=source.number; updateMove150(); },40); } function openMoveFromHome155(){ show('c-exchange','Between accounts'); setTimeout(()=>{ renderSelectors150(); if(activeAccount150 && document.getElementById('moveFrom150'))moveFrom150.value=activeAccount150.number; const other=accounts150.find(a=>a.number!==activeAccount150?.number); if(other && document.getElementById('moveTo150'))moveTo150.value=other.number; updateMove150(); },40); } function instantTopup155(method){ const a=selectedTopupAccount155(),amount=parseFloat(String(document.getElementById('instantTopupAmount155').value).replace(',','.'))||0; if(!a)return toastMsg('Select an account.'); if(amount<=0)return toastMsg('Enter an amount.'); const ok=confirm(`${method}\n\nTraining simulation\nCredit ${fmt150(amount,a.currency)} to ${a.name} · ${a.currency}?`); if(!ok)return; a.balance+=amount; let adj=balanceAdjustments152();adj[a.number]=a.balance;saveAdjustments152(adj); saveLocalMovement154(a,amount,`${method} funding · training simulation`); activeAccount150=a;localStorage.setItem('gpw_active_account150',a.number); renderAccount150();renderHomeAccount154();renderAccountMovements154();renderTopupDetails155(); toastMsg(`${method}: training funding approved.`); } const _populateFund155=populateFund152; populateFund152=async function(){ await loadAccounts150(); const current=activeAccount150||accounts150[0]; fundTo152.innerHTML=accounts150.map(a=>``).join(''); if(current)fundTo152.value=current.number; renderTopupDetails155(); } // GPW 0.15.6 premium audit helpers function fmt150(v,c){ const n=Number(v); const val=Number.isFinite(n)?n:0; try{return new Intl.NumberFormat('de-DE',{style:'currency',currency:c||'EUR',minimumFractionDigits:2,maximumFractionDigits:2}).format(val)} catch(e){return val.toLocaleString('de-DE',{minimumFractionDigits:2,maximumFractionDigits:2})+' '+(c||'EUR')} } let faceBusy159=false; async function faceId156(){ if(faceBusy159)return; faceBusy159=true; const gate=document.getElementById('faceGate159'); const card=document.getElementById('faceId156'); gate&&gate.classList.add('scan159'); card&&card.classList.add('scan159'); loginStatus.textContent='Reconnaissance Face ID de formation…'; try{ await new Promise(r=>setTimeout(r,1050)); const d=await api('/api/training-direct-client',{method:'POST',body:'{}'}); token=d.token;role='client'; loginStatus.textContent='Identité reconnue.'; await new Promise(r=>setTimeout(r,260)); login.classList.add('hide');app.classList.remove('hide'); document.body.classList.add('client-mobile'); document.body.classList.remove('operator-mobile'); buildNav(); show('c-dashboard','Home',false); setTimeout(()=>syncClientFromServer160(true),120); }catch(e){ loginStatus.textContent=e.message||'Accès momentanément indisponible.'; }finally{ gate&&gate.classList.remove('scan159'); card&&card.classList.remove('scan159'); faceBusy159=false; } } function renderTopupAccounts156(){ const box=document.getElementById('topupAccounts156'),sel=document.getElementById('fundTo152');if(!box||!sel)return; box.innerHTML=''; accounts150.forEach(a=>{ const b=document.createElement('button');b.className='topup-account156'+(sel.value===a.number?' on':''); b.innerHTML=`${FLAGS150[a.currency]||'◉'}${a.name}${a.currency}${fmt150(a.balance,a.currency)}`; b.onclick=()=>{sel.value=a.number;activeAccount150=a;localStorage.setItem('gpw_active_account150',a.number);renderTopupAccounts156();renderTopupDetails155()};box.appendChild(b) }) } const _populateFund156=populateFund152; populateFund152=async function(){ await loadAccounts150(); const sel=document.getElementById('fundTo152');if(!sel)return; sel.innerHTML=accounts150.map(a=>``).join(''); const current=activeAccount150||accounts150[0];if(current)sel.value=current.number; renderTopupAccounts156();renderTopupDetails155(); } function detectLang156(text){ const t=(text||'').toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,''); const tests=[ ['de',/(guten\s*(tag|morgen|abend)|hallo|danke|bitte|mochte|ich|konto|uberweisung|karte|kredit|wie|was)/], ['fr',/(bonjour|bonsoir|salut|merci|je\s|j'|j’|voudrais|aimerais|comment|compte|virement|carte|credit|pret|epargne)/], ['es',/(hola|buenos|buenas|gracias|quiero|quisiera|cuenta|transferencia|tarjeta|prestamo|ahorro|como)/], ['it',/(ciao|buongiorno|grazie|vorrei|conto|bonifico|carta|prestito|risparmio|come)/], ['pt',/(ola|bom\s*dia|boa\s*tarde|obrigad|quero|conta|transferencia|cartao|emprestimo|poupanca|como)/], ['ar',/[\u0600-\u06ff]/],['zh',/[\u4e00-\u9fff]/] ]; for(const [l,r] of tests)if(r.test(t))return l; return localStorage.getItem('gpw_nathalie_lang156')||(navigator.language||'en').slice(0,2); } const R156={ fr:{general:"Bien sûr. Je peux vous aider avec vos comptes, virements, cartes, épargne, change, relevés et demandes de financement. Dites-moi simplement ce que vous souhaitez faire.",finance:"Bien sûr. Indiquez-moi le montant souhaité, la devise et l'objet du financement. Je vous guiderai ensuite étape par étape vers la demande à transmettre à Wealth Operations.",transfer:"Pour un virement, saisissez le bénéficiaire ou l'entreprise puis l'IBAN. Le pays est reconnu automatiquement. Ensuite, indiquez le montant et la référence avant l'envoi pour validation opérateur.",account:"Vous pouvez gérer votre compte principal et vos comptes en devises. Pour alimenter un compte USD, GBP ou autre, ouvrez Add money puis sélectionnez le compte cible en haut de l'écran.",saving:"Dans Savings, choisissez librement le nom, la devise et le montant à allouer. Le solde de l'espace est mis à jour après création.",card:"Dans Pay, faites défiler Gold, Platinum et Black. Les caractéristiques, le coût mensuel d'entraînement et la ligne de crédit s'affichent avec la carte sélectionnée.",fx:"Between accounts permet de transférer entre vos propres comptes. Le taux et le montant reçu s'affichent avant confirmation."}, de:{general:"Gern. Ich kann Ihnen bei Konten, Überweisungen, Karten, Sparen, Devisen, Kontoauszügen und Finanzierung helfen. Sagen Sie mir einfach, was Sie tun möchten.",finance:"Gern. Nennen Sie mir Betrag, Währung und Zweck der Finanzierung. Danach führe ich Sie Schritt für Schritt durch die Trainingsanfrage an Wealth Operations.",transfer:"Für eine Überweisung geben Sie Empfänger oder Firma und anschließend die IBAN ein. Das Zielland wird automatisch erkannt. Danach folgen Betrag und Verwendungszweck vor der Operator-Freigabe.",account:"Sie können Ihr Hauptkonto und zusätzliche Währungskonten verwalten. Um ein USD-, GBP- oder anderes Konto aufzuladen, öffnen Sie Add money und wählen oben das Zielkonto.",saving:"Unter Savings wählen Sie Namen, Währung und Betrag frei. Der Sparsaldo wird nach dem Anlegen aktualisiert.",card:"Unter Pay können Sie Gold, Platinum und Black vergleichen. Eigenschaften, monatliche Trainingsgebühr und Kreditlinie erscheinen passend zur gewählten Karte.",fx:"Between accounts überträgt Geld zwischen Ihren eigenen Konten. Kurs und Empfangsbetrag werden vor der Bestätigung angezeigt."}, es:{general:"Claro. Puedo ayudarte con cuentas, transferencias, tarjetas, ahorro, cambio, extractos y financiación. Dime qué deseas hacer.",finance:"Indícame el importe, la divisa y la finalidad. Después te guiaré paso a paso para preparar la solicitud de formación a Wealth Operations.",transfer:"Para una transferencia introduce beneficiario o empresa y el IBAN. El país se detecta automáticamente. Después añade importe y referencia antes de la aprobación del operador.",account:"Puedes gestionar la cuenta principal y cuentas en otras divisas. Para añadir fondos a USD, GBP u otra, abre Add money y selecciona arriba la cuenta de destino.",saving:"En Savings elige libremente nombre, divisa e importe. El saldo se actualiza tras crear el espacio.",card:"En Pay puedes comparar Gold, Platinum y Black. Se muestran características, cuota mensual de formación y línea de crédito de la tarjeta elegida.",fx:"Between accounts permite mover fondos entre tus propias cuentas mostrando tipo de cambio e importe recibido antes de confirmar."}, it:{general:"Certo. Posso aiutarti con conti, bonifici, carte, risparmio, cambio, estratti e finanziamenti. Dimmi cosa vuoi fare.",account:"Puoi gestire il conto principale e i conti in valuta. Per aggiungere fondi a USD, GBP o altro, apri Add money e seleziona il conto di destinazione in alto."}, pt:{general:"Claro. Posso ajudar com contas, transferências, cartões, poupança, câmbio, extratos e financiamento. Diga-me o que pretende fazer.",account:"Pode gerir a conta principal e contas em outras moedas. Para adicionar fundos a USD, GBP ou outra, abra Add money e selecione a conta de destino no topo."}, en:{general:"Of course. I can help with accounts, transfers, cards, savings, FX, statements and financing. Tell me what you want to do.",finance:"Tell me the amount, currency and purpose of the financing. I will then guide you step by step through the training request for Wealth Operations.",transfer:"For a transfer, enter the beneficiary or company and the IBAN. The destination country is detected automatically. Then enter the amount and reference before operator approval.",account:"You can manage the principal account and additional currency accounts. To fund USD, GBP or another account, open Add money and select the target account at the top.",saving:"In Savings, choose the name, currency and amount freely. The savings balance updates after creation.",card:"In Pay, swipe through Gold, Platinum and Black. Features, monthly training fee and credit line update with the selected card.",fx:"Between accounts moves funds between your own accounts. The rate and amount received are shown before confirmation."} }; function advisorSend150(){ let q=chatInput.value.trim();if(!q)return; let me=document.createElement('div');me.className='bubble me';me.textContent=q;chatWindow.appendChild(me);chatInput.value=''; const lang=detectLang156(q);localStorage.setItem('gpw_nathalie_lang156',lang);const intent=intent152(q);const bank=R156[lang]||R156.en; setTimeout(()=>{let b=document.createElement('div');b.className='bubble advisor-bubble140';b.textContent=bank[intent]||bank.general||R156.en.general;chatWindow.appendChild(b);chatWindow.scrollTop=chatWindow.scrollHeight},160) } function setWelcome156(){ const l=localStorage.getItem('gpw_nathalie_lang156')||(navigator.language||'en').slice(0,2); const msg={fr:"Bonjour Joseph, je suis Nathalie Predetti. Comment puis-je vous aider aujourd’hui ?",de:"Guten Tag Joseph, ich bin Nathalie Predetti. Wie kann ich Ihnen heute helfen?",es:"Hola Joseph, soy Nathalie Predetti. ¿Cómo puedo ayudarte hoy?",it:"Buongiorno Joseph, sono Nathalie Predetti. Come posso aiutarti oggi?",pt:"Olá Joseph, sou Nathalie Predetti. Como posso ajudar hoje?",en:"Hello Joseph, I’m Nathalie Predetti. How can I help you today?"}; const el=document.getElementById('nathalieWelcome152');if(el)el.textContent=msg[l]||msg.en; } const NATHALIE_EXTRA158={ fr:{statement:"Je peux vous aider à générer un relevé. Ouvrez Relevés, choisissez le compte, la date et le document. Le PDF reprend l'identité GPW, le solde et les mouvements.",topup:"Pour approvisionner un compte, ouvrez Add money, choisissez d'abord le compte cible puis virement bancaire, autre compte GPW, Apple Pay ou carte dans le parcours de formation.",crypto:"Dans Wealth, la rubrique Digital assets présente les principales monnaies numériques avec leurs symboles. Search permet aussi de retrouver un actif.",fees:"Les coûts affichés sont des paramètres didactiques. Les cartes affichent leur coût mensuel et leur ligne de crédit sous le produit sélectionné."}, en:{statement:"Open Statements, choose the account, date and document type. The generated PDF includes GPW identity, balance and training movements.",topup:"Open Add money, choose the target account first, then bank transfer, another GPW account, Apple Pay or card in the training flow.",crypto:"Wealth includes a Digital assets list with the main currencies and visual symbols. Search can also locate an asset.",fees:"Displayed fees are training parameters. Card monthly fees and credit lines appear under the selected product."}, de:{statement:"Unter Statements wählen Sie Konto, Datum und Dokumenttyp. Das PDF enthält GPW-Identität, Saldo und Trainingsbewegungen.",topup:"Unter Add money wählen Sie zuerst das Zielkonto und danach Banküberweisung, anderes GPW-Konto, Apple Pay oder Karte.",crypto:"Unter Wealth finden Sie Digital assets mit den wichtigsten digitalen Währungen und Symbolen."}, es:{statement:"En Statements selecciona cuenta, fecha y documento. El PDF incluye identidad GPW, saldo y movimientos de formación.",topup:"En Add money elige primero la cuenta de destino y después transferencia bancaria, otra cuenta GPW, Apple Pay o tarjeta.",crypto:"En Wealth, Digital assets muestra las principales monedas digitales con símbolos visuales."} }; Object.assign(NATHALIE_EXTRA158.fr,{pof:'Je peux générer une preuve de fonds strictement didactique, clairement marquée sans valeur bancaire. Ouvrez Statements puis Proof of funds, choisissez le compte et un montant inférieur ou égal au solde disponible.'}); Object.assign(NATHALIE_EXTRA158.en,{pof:'I can generate a strictly didactic proof-of-funds document, clearly marked as not valid for banking use. Open Statements > Proof of funds and select an amount no higher than the available training balance.'}); Object.assign(NATHALIE_EXTRA158.de,{pof:'Ich kann einen eindeutig als didaktisch gekennzeichneten Kapitalnachweis erzeugen. Öffnen Sie Statements > Proof of funds und wählen Sie einen Betrag bis zum verfügbaren Trainingssaldo.'}); Object.assign(NATHALIE_EXTRA158.es,{pof:'Puedo generar una prueba de fondos estrictamente didáctica y marcada sin validez bancaria. Abre Statements > Proof of funds y selecciona un importe no superior al saldo de formación.'}); const _intent152_158=intent152; intent152=function(q){const t=(q||'').toLowerCase();if(/proof of funds|preuve de fonds|pof|kapitalnachweis|prueba de fondos/.test(t))return'pof';if(/relev|statement|extract|auszug|estratto/.test(t))return'statement';if(/approvision|add money|top.?up|apple pay|alimenter|fund/.test(t))return'topup';if(/crypto|bitcoin|btc|ethereum|eth|solana|xrp/.test(t))return'crypto';if(/co[uû]t|fee|tarif|price|preis|precio/.test(t))return'fees';return _intent152_158(q)} const _R156_158=R156; Object.keys(NATHALIE_EXTRA158).forEach(l=>{_R156_158[l]=Object.assign(_R156_158[l]||{},NATHALIE_EXTRA158[l])}); setTimeout(setWelcome156,350); setTimeout(()=>{ const params=new URLSearchParams(location.search); const op=params.get('operator')==='1'; const og=document.getElementById('operatorGate159'); if(op && og){og.classList.remove('hide');document.getElementById('faceId156').classList.add('hide');document.getElementById('faceGate159').classList.add('operator159');loginStatus.textContent='Operator console · protected access';} },100); /* ============================================================ GPW MASTER RC3 — VISUAL REBUILD Financial state lives in SQLite. localStorage is used only for harmless UI preferences (active account / language / role hints). ============================================================ */ let gpwUserMaster={name:'Training Client',email:'',phone:'',client_reference:''}; function firstNameMaster(){return (gpwUserMaster.name||'Client').trim().split(/\s+/)[0]||'Client'} function flag150(c){return FLAGS150[c]||'◉'} function historyBack(){goBack142()} function safeHtmlMaster(v){return String(v??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]))} function applyUserIdentityMaster(){ const name=gpwUserMaster.name||'Training Client', first=firstNameMaster(); const hello=document.getElementById('hello152'); if(hello)hello.textContent=`Bonjour ${first}`; document.querySelectorAll('.profile-hero140 h2').forEach(x=>x.textContent=name); document.querySelectorAll('.profile-hero140 p').forEach(x=>x.textContent=gpwUserMaster.client_reference||'GPW TRAINING CLIENT'); const info=document.querySelector('.profile140 .info140'); if(info){const rows=info.querySelectorAll('div');if(rows[0])rows[0].querySelector('b').textContent=gpwUserMaster.email||'—';if(rows[1])rows[1].querySelector('b').textContent=gpwUserMaster.phone||'—'} const holder=document.querySelector('.account-details150>div:nth-child(2) b');if(holder)holder.textContent=name; const cardNameEl=document.getElementById('cardName');if(cardNameEl && (!cardNameEl.dataset.touched)){cardNameEl.value=name} const welcome=document.getElementById('nathalieWelcome152'); if(welcome){const lang=localStorage.getItem('gpw_nathalie_lang156')||(navigator.language||'fr').slice(0,2);const m={fr:`Bonjour ${first}, je suis Nathalie Predetti. Comment puis-je vous aider aujourd’hui ?`,en:`Hello ${first}, I’m Nathalie Predetti. How can I help you today?`,es:`Hola ${first}, soy Nathalie Predetti. ¿Cómo puedo ayudarte hoy?`,de:`Guten Tag ${first}, ich bin Nathalie Predetti. Wie kann ich Ihnen heute helfen?`};welcome.textContent=m[lang]||m.fr} document.querySelectorAll('.bank-lines156 button').forEach(btn=>{const sm=btn.querySelector('small');if(sm&&sm.textContent.trim()==='Beneficiary'){const b=btn.querySelector('b');if(b)b.textContent=name;btn.onclick=()=>copyRaw155(name)}}); } function purgeLegacyFinancialLocalMaster(){ ['gpw_extra_accounts150','gpw_balance_adjustments152','gpw_local_movements154'].forEach(k=>localStorage.removeItem(k)); } async function loadAccounts150(){ const d=await api('/api/client-summary');window.gpwSummary154=d;gpwUserMaster=d.user||gpwUserMaster;applyUserIdentityMaster();purgeLegacyFinancialLocalMaster(); accounts150=(d.accounts||[]).map(a=>norm150({...a,source:'base'})); const wanted=localStorage.getItem('gpw_active_account150');activeAccount150=accounts150.find(a=>a.number===wanted)||accounts150[0]||null; renderAccount150();renderHomeAccount154();renderAccountMovements154();bindHomeSwipe154();renderTransferSourceMaster(); return d; } async function loadClient(){ const d=await api('/api/client-summary');window.gpwSummary154=d;gpwUserMaster=d.user||gpwUserMaster;applyUserIdentityMaster(); const cashEl=document.getElementById('cash');if(cashEl)cashEl.textContent=d.total_cash_display; const ac=document.getElementById('accCount'),mc=document.getElementById('movCount'),msg=document.getElementById('msgCount');if(ac)ac.textContent=d.accounts.length;if(mc)mc.textContent=d.movements.length;if(msg)msg.textContent=d.unread_messages; await loadAccounts150(); const home=document.getElementById('cMovements');if(home&&activeAccount150)renderMovements140(home,movementsFor154(activeAccount150).slice(0,6)); } function movementsFor154(a){ if(!a||!window.gpwSummary154)return[]; return (window.gpwSummary154.movements||[]).filter(m=>Number(m.account_id)===Number(a.id)); } function localMovements154(){return[]} function saveLocalMovement154(){/* deprecated in MASTER: financial history is SQLite-backed */} function balanceAdjustments152(){return{}} function saveAdjustments152(){/* deprecated */} function applyAdjustments152(){} function purgeBaseBalanceOverrides161(){purgeLegacyFinancialLocalMaster()} async function createCurrencyAccount150(){ try{const d=await api('/api/client/account',{method:'POST',body:JSON.stringify({currency:newAccountCurrency150.value,name:newAccountName150.value||'Main'})});toastMsg(d.message);await loadAccounts150();show('c-accounts','Accounts',false)}catch(e){toastMsg(e.message)} } function deleteCurrencyAccount154(){toastMsg('MASTER RC2 keeps SQLite accounts for audit continuity. Deactivate them from WealthCore instead of deleting history.')} async function submitMove150(){ const f=accounts150.find(a=>a.number===moveFrom150.value),t=accounts150.find(a=>a.number===moveTo150.value);const amt=parseFloat(String(moveAmount150.value).replace(',','.'))||0,recv=parseFloat(moveReceive150.dataset.v||0); if(!f||!t||f.id===t.id)return toastMsg('Select two different accounts.');if(amt<=0||recv<=0)return toastMsg('Enter an amount.'); try{const d=await api('/api/client/internal-transfer',{method:'POST',body:JSON.stringify({source_account_id:f.id,target_account_id:t.id,source_amount:amt,target_amount:recv,reference:`Internal transfer ${f.currency}→${t.currency}`})});activeAccount150=t;localStorage.setItem('gpw_active_account150',t.number);toastMsg(d.message);await syncClientFromServer160(true);await loadAccounts150();show('c-accounts','Accounts',false)}catch(e){toastMsg(e.message)} } async function instantTopup155(method){ const a=selectedTopupAccount155(),amount=parseFloat(String(document.getElementById('instantTopupAmount155').value).replace(',','.'))||0;if(!a)return toastMsg('Select an account.');if(amount<=0)return toastMsg('Enter an amount.'); if(!confirm(`${method}\n\nTraining simulation\nCredit ${fmt150(amount,a.currency)} to ${a.name} · ${a.currency}?`))return; try{const d=await api('/api/client/training-topup',{method:'POST',body:JSON.stringify({account_id:a.id,amount,method})});toastMsg(d.message);activeAccount150=a;localStorage.setItem('gpw_active_account150',a.number);await syncClientFromServer160(true);await loadAccounts150();renderTopupAccounts156();renderTopupDetails155()}catch(e){toastMsg(e.message)} } async function createSavings(){ const src=accounts150.find(a=>a.currency===svCurrency.value)||activeAccount150;if(!src)return toastMsg('Create or select an account in this currency first.'); try{const d=await api('/api/client/savings',{method:'POST',body:JSON.stringify({name:svName.value,currency:src.currency,target:svTarget.value,source_account_id:src.id})});toastMsg(d.message);await syncClientFromServer160(true);await loadSavings();await loadAccounts150()}catch(e){toastMsg(e.message)} } function ensureTransferSourceMaster(){ if(document.getElementById('trSourceMaster'))return;const ts=document.getElementById('ts1');if(!ts)return;const h=ts.querySelector('h3');const label=document.createElement('label');label.textContent='From account';label.htmlFor='trSourceMaster';const sel=document.createElement('select');sel.id='trSourceMaster';if(h){h.insertAdjacentElement('afterend',sel);sel.insertAdjacentElement('beforebegin',label)} } function renderTransferSourceMaster(){ensureTransferSourceMaster();const s=document.getElementById('trSourceMaster');if(!s)return;s.innerHTML=accounts150.map(a=>``).join('');if(activeAccount150)s.value=String(activeAccount150.id)} async function submitTransfer(){ const sourceId=Number((document.getElementById('trSourceMaster')||{}).value||activeAccount150?.id||0);if(!sourceId)return toastMsg('Select a source account.'); try{const d=await api('/api/client/transfer',{method:'POST',body:JSON.stringify({source_account_id:sourceId,beneficiary_name:trName.value,beneficiary_type:'Person',country:trCountry.value,beneficiary_reference:trRef.value,amount:trAmount.value,narrative:trNarrative.value})});trNotice.classList.remove('hide');toastMsg(d.message);ts3.classList.add('hide');setTimeout(()=>trNotice.scrollIntoView({behavior:'smooth'}),50)}catch(e){toastMsg(e.message)} } function openSearch157(){const x=document.getElementById('searchOverlay157');if(x){x.classList.remove('hide');const i=document.getElementById('globalSearch157');if(i){i.value='';i.focus();runSearch157('')}}} function closeSearch157(){const x=document.getElementById('searchOverlay157');if(x)x.classList.add('hide')} function runSearch157(q){ const box=document.getElementById('searchResults157');if(!box)return;const term=(q||'').trim().toLowerCase();const items=[ ['Accounts','c-accounts'],['Transfer','c-transfers'],['Add money','c-fund-account'],['Between accounts','c-exchange'],['Statements','c-statements'],['Savings','c-savings'],['Foreign exchange','c-fx'],['Cards / Pay','c-cards'],['Wealth','c-wealth'],['Digital assets','c-digital-assets'],['Bourse & markets','c-bourse'],['Patrimony & inventory','c-patrimony'],['Proof of funds','c-pof-generator'],['Financing','c-loans'],['Private assistance','c-messages'],['Profile','c-profile'] ]; accounts150.forEach(a=>items.push([`${a.name} ${a.currency} ${a.number}`,'c-accounts'])); const crypto=['Bitcoin BTC','Ethereum ETH','Solana SOL','XRP','Cardano ADA','BNB','USDT','USDC','Chainlink LINK','Avalanche AVAX','Polkadot DOT','MATIC Polygon'];crypto.forEach(c=>items.push([c,'c-digital-assets'])); const found=items.filter(([n])=>!term||n.toLowerCase().includes(term)).slice(0,16);box.innerHTML=found.length?'':'
                No result
                ';found.forEach(([n,id])=>{const b=document.createElement('button');b.innerHTML=`${safeHtmlMaster(n)}`;b.onclick=()=>{closeSearch157();if(id==='c-pof-generator')openPOF162();else show(id,n)};box.appendChild(b)}) } async function generateStatement150(){ await syncClientFromServer160(true);const d=window.gpwSummary154||await api('/api/client-summary');const a=accounts150.find(x=>x.number===statementAccount150.value)||activeAccount150;if(!a)return toastMsg('Select an account.'); const rows=(d.movements||[]).filter(m=>Number(m.account_id)===Number(a.id)).map(m=>`${safeHtmlMaster(m.created_at)}${safeHtmlMaster(m.reference)}${safeHtmlMaster(m.amount_display)}`).join(''); const w=window.open('','_blank');if(!w)return toastMsg('Allow pop-ups to generate the document.');const name=safeHtmlMaster(gpwUserMaster.name),typ=safeHtmlMaster(statementTitle150.textContent),date=safeHtmlMaster(statementDate150.value||new Date().toISOString().slice(0,10)); w.document.write(`${typ}

                GPW Private Wealth

                BANKING TRAINING ENGINE
                TRAINING DOCUMENT — NOT A REAL BANK STATEMENT

                ${typ}

                Account holder${name}
                Date${date}
                Account${safeHtmlMaster(a.number)}
                Balance${safeHtmlMaster(fmt150(a.balance,a.currency))}
                ${rows||''}
                DateDescriptionAmount
                No training activity for this account.
                Internal didactic document generated by GPW Banking Training Engine.${statementSigned150.checked?' Training signature/seal option selected.':''}
                `);w.document.close(); } async function generatePOF162(){ await syncClientFromServer160(true);await loadAccounts150();const sel=document.getElementById('pofAccount162'),a=accounts150.find(x=>x.number===sel.value)||activeAccount150;if(!a)return toastMsg('Select an account.');const raw=(document.getElementById('pofAmount162').value||'').replace(/\s/g,'').replace(',','.');const amt=Number(raw);if(!Number.isFinite(amt)||amt<=0)return toastMsg('Enter a valid amount.');if(amt>a.balance+0.00001)return toastMsg('The evidence amount cannot exceed the available training balance.'); const lang=document.getElementById('pofLang162').value||'fr',t=pofText162(lang),purpose=esc162(document.getElementById('pofPurpose162').value||'—'),issued=new Date().toLocaleDateString(lang==='en'?'en-GB':lang==='de'?'de-DE':lang==='es'?'es-ES':'fr-FR'),docId='GPW-TRAIN-POF-'+new Date().toISOString().slice(0,10).replaceAll('-','')+'-'+Math.random().toString(36).slice(2,7).toUpperCase(),name=esc162(gpwUserMaster.name||'Training Client'); const w=window.open('','_blank');if(!w)return toastMsg('Allow pop-ups to generate the document.');w.document.write(`${t.title}
                ${t.notice}
                GPWPRIVATE WEALTH · TRAINING ENGINE
                TRAINING ONLY
                ${t.notice}
                GPW Banking Training Engine

                ${t.title}

                ${t.lead}

                ${t.holder}${name}
                ${t.date}${issued}
                ${t.account}${esc162(a.number)}
                ${t.currency}${esc162(a.currency)}
                ${t.available}${fmt150(a.balance,a.currency)}
                ${t.purpose}${purpose}
                ${t.evidence}${fmt150(amt,a.currency)}

                ${t.body}

                ${t.notice}
                Generated solely for internal learning and interface familiarisation. No third party should rely on this document as evidence of real funds.
                Document ID
                ${docId}
                `);w.document.close(); } async function syncClientFromServer160(force=false){ if(role!=='client'||!token||gpwSyncBusy160)return;gpwSyncBusy160=true; try{const d=await api('/api/client-summary');if(force||gpwLastRevision160===null||d.revision!==gpwLastRevision160){gpwLastRevision160=d.revision;window.gpwSummary154=d;gpwUserMaster=d.user||gpwUserMaster;accounts150=(d.accounts||[]).map(a=>norm150({...a,source:'base'}));const wanted=localStorage.getItem('gpw_active_account150');activeAccount150=accounts150.find(a=>a.number===wanted)||accounts150[0]||null;applyUserIdentityMaster();renderAccount150();renderHomeAccount154();renderAccountMovements154();renderTransferSourceMaster();const cash=document.getElementById('cash');if(cash)cash.textContent=d.total_cash_display}} catch(e){}finally{gpwSyncBusy160=false} } function renderSelectors150(){ const l=document.getElementById('accountList150');if(l){l.innerHTML='';accounts150.forEach(a=>{const b=document.createElement('button');b.className='acct-row154';b.innerHTML=`${flag150(a.currency)}${safeHtmlMaster(a.name)}${a.currency} · ${safeHtmlMaster(a.number)}${fmt150(a.balance,a.currency)}`;b.onclick=()=>{activeAccount150=a;localStorage.setItem('gpw_active_account150',a.number);renderAccount150();renderHomeAccount154();renderAccountMovements154();closeAccountSheet150()};l.appendChild(b)})} ['moveFrom150','moveTo150','statementAccount150'].forEach(id=>{const el=document.getElementById(id);if(el)el.innerHTML=accounts150.map(a=>``).join('')}); const ca=document.getElementById('cardAccount');if(ca)ca.innerHTML=accounts150.map(a=>``).join(''); renderTransferSourceMaster(); } async function requestCard140(){ cardOrderStatus.classList.add('hide');const a=accounts150.find(x=>Number(x.id)===Number(cardAccount.value))||activeAccount150;if(!a)return toastMsg('Select an account.'); try{const d=await api('/api/client/card',{method:'POST',body:JSON.stringify({account_id:a.id,product:cardProduct.value,limit:cardLimit.value,currency:a.currency,delivery_name:cardName.value||gpwUserMaster.name})});cardOrderStatus.innerHTML=`Request received
                ${safeHtmlMaster(cardProduct.value)}
                Status: pending operator review`;cardOrderStatus.classList.remove('hide');toastMsg(d.message);loadCards()}catch(e){toastMsg(e.message)} } // Refresh account data after any server-backed change and when transfer page opens. const _showMaster=show;show=function(id,t,push=true){_showMaster(id,t,push);if(id==='c-transfers')setTimeout(renderTransferSourceMaster,20);if(id==='c-savings'&&activeAccount150){setTimeout(()=>{if(document.getElementById('svCurrency'))svCurrency.value=activeAccount150.currency},20)}}; setTimeout(()=>{ensureTransferSourceMaster();if(document.getElementById('cardName'))cardName.addEventListener('input',()=>cardName.dataset.touched='1')},250); function readFileData176(input){return new Promise((resolve,reject)=>{const f=input.files&&input.files[0];if(!f)return resolve('');const r=new FileReader();r.onload=()=>resolve(r.result);r.onerror=reject;r.readAsDataURL(f)})} ['obFront176','obBack176','obSelfie176'].forEach((id,i)=>setTimeout(()=>{const el=document.getElementById(id);if(el)el.addEventListener('change',()=>{const st=document.getElementById(['frontState176','backState176','selfieState176'][i]);if(st)st.textContent=el.files?.[0]?.name?'Ready':'Capture'})},200)); async function createOnboarding176(){try{const d=await api('/api/client/onboarding',{method:'POST',body:JSON.stringify({product:obProduct176.value,currency:obCurrency176.value,purpose:obPurpose176.value,expected_activity:obActivity176.value})});obApplicationStatus176.innerHTML=`Case ${d.case_id} created.
                Compliance instructions sent. Email: ${d.email_status}`;toastMsg(d.message);await loadOnboarding176()}catch(e){toastMsg(e.message)}} async function loadOnboarding176(){try{const d=await api('/api/client/onboarding');const items=d.items||[];obCase176.innerHTML=items.filter(x=>!['approved','declined'].includes(x.status)).map(x=>``).join('')||'';obCases176.innerHTML=items.length?items.map(x=>`
                Case ${x.id} · ${safeHtmlMaster(x.product)}${x.currency} · ${x.created_at}
                ${x.status.replaceAll('_',' ')}${x.kyc_status.replaceAll('_',' ')}
                `).join(''):'
                No onboarding case yet.
                ';const dsk=document.getElementById('deskOnboarding176');if(dsk)dsk.textContent=items[0]?`Case ${items[0].id} · ${items[0].status.replaceAll('_',' ')}`:'No active case'}catch(e){}} async function submitIdentity176(){const cid=Number(obCase176.value||0);if(!cid)return toastMsg('Create or select an onboarding case first.');try{const [front,back,selfie]=await Promise.all([readFileData176(obFront176),readFileData176(obBack176),readFileData176(obSelfie176)]);if(!(front&&back&&selfie))return toastMsg('Capture ID front, ID back and face image.');const d=await api('/api/client/identity-submit',{method:'POST',body:JSON.stringify({case_id:cid,document_type:obDocType176.value,front_data:front,back_data:back,selfie_data:selfie})});obIdentityStatus176.innerHTML='Package submitted.
                Compliance review is now pending.';toastMsg(d.message);await loadOnboarding176()}catch(e){toastMsg(e.message)}} async function loadCompliance176(){try{const d=await api('/api/operator/onboarding');const a=d.items||[];coOpen176.textContent=a.filter(x=>['awaiting_documents','under_review'].includes(x.status)).length;coDocs176.textContent=a.filter(x=>x.status==='awaiting_documents').length;coReview176.textContent=a.filter(x=>x.status==='under_review').length;coApproved176.textContent=a.filter(x=>x.status==='approved').length;complianceRows176.innerHTML=a.length?a.map(x=>`
                CASE ${x.id}${safeHtmlMaster(x.client_name)}${safeHtmlMaster(x.client_email)}
                PRODUCT${safeHtmlMaster(x.product)} · ${x.currency}${safeHtmlMaster(x.purpose||'No purpose supplied')}
                KYC${x.kyc_status.replaceAll('_',' ')}${x.identity_status.replaceAll('_',' ')}
                STATUS${x.status.replaceAll('_',' ')}
                `).join(''):'
                No compliance cases.
                '}catch(e){toastMsg(e.message)}} async function reviewOnboarding176(id,action){try{const d=await api('/api/operator/onboarding-review',{method:'POST',body:JSON.stringify({id,action})});toastMsg(d.message);loadCompliance176();loadOpSummary()}catch(e){toastMsg(e.message)}} function renderDesktopClient176(){const box=document.querySelector('.desktop-client176');if(!box||!activeAccount150)return;const first=firstNameMaster();const hh=new Date().getHours();deskHello176.textContent=`${hh<12?'Good morning':hh<18?'Good afternoon':'Good evening'}, ${first}`;deskFlag176.textContent=flag150(activeAccount150.currency);deskAccount176.textContent=`${activeAccount150.name} · ${activeAccount150.currency} ▾`;deskBalance176.textContent=fmt150(activeAccount150.balance,activeAccount150.currency);deskAccountRef176.textContent=activeAccount150.number||'—';deskAccounts176.innerHTML=accounts150.map(a=>``).join('');const mv=movementsFor154(activeAccount150).slice(0,8);deskMovements176.innerHTML=mv.map(m=>``).join('')||'
                No movements.
                ';loadOnboarding176();} const _renderHome176=renderHomeAccount154;renderHomeAccount154=function(){_renderHome176();setTimeout(renderDesktopClient176,10)};
                GPW ACADEMY

                Financial literacy

                GPW

                Install GPW

                Banking Training Engine

                Checking this device…