From 5ded3a60e57621a3a32710cacef0d6032cbffdfb Mon Sep 17 00:00:00 2001 From: Jacob Schmidt Date: Thu, 2 Apr 2026 16:41:10 -0500 Subject: [PATCH] Add credit line repayment to bank UI - Wire bank client and server for credit line repayment requests - Show credit line balance and repay action in the banking view - Extend org/bank payloads and models with credit line fields --- .../bank/functions/fnc_handleUIEvents.sqf | 5 + .../bank/functions/fnc_initUIBridge.sqf | 7 + arma/client/addons/bank/ui/_site/bank-ui.js | 2 +- arma/client/addons/bank/ui/src/bridge.js | 3 + arma/client/addons/bank/ui/src/data.js | 7 + .../addons/bank/ui/src/pages/BankView.js | 67 +++++++ .../addons/bank/ui/src/registry/events.js | 20 +++ arma/client/addons/org/ui/_site/org-ui.js | 2 +- arma/client/addons/org/ui/src/bridge.js | 12 +- .../ui/src/components/portal/treasuryCard.js | 86 ++++++++- arma/server/addons/bank/XEH_preInit.sqf | 6 + .../bank/functions/fnc_initPayloadBuilder.sqf | 42 ++++- .../addons/bank/functions/fnc_initStore.sqf | 77 ++++++++ .../addons/org/functions/fnc_initOrgStore.sqf | 65 +++++++ .../org/functions/fnc_initPayloadBuilder.sqf | 11 +- .../store/functions/fnc_initStoreStore.sqf | 12 +- arma/server/extension/src/org.rs | 18 +- arma/server/extension/src/transport.rs | 4 + lib/models/src/lib.rs | 9 +- lib/models/src/org.rs | 90 +++++++++- lib/services/src/org.rs | 165 +++++++++++++++--- lib/services/src/store.rs | 18 +- 22 files changed, 688 insertions(+), 40 deletions(-) diff --git a/arma/client/addons/bank/functions/fnc_handleUIEvents.sqf b/arma/client/addons/bank/functions/fnc_handleUIEvents.sqf index 68237d0..4d5982b 100644 --- a/arma/client/addons/bank/functions/fnc_handleUIEvents.sqf +++ b/arma/client/addons/bank/functions/fnc_handleUIEvents.sqf @@ -68,6 +68,11 @@ switch (_event) do { GVAR(BankUIBridge) call ["handleDepositEarningsRequest", [_data]]; }; }; + case "bank::repayCreditLine::request": { + if !(isNil QGVAR(BankUIBridge)) then { + GVAR(BankUIBridge) call ["handleRepayCreditLineRequest", [_data]]; + }; + }; case "bank::pin::request": { if !(isNil QGVAR(BankUIBridge)) then { GVAR(BankUIBridge) call ["handleSubmitPinRequest", [_data]]; diff --git a/arma/client/addons/bank/functions/fnc_initUIBridge.sqf b/arma/client/addons/bank/functions/fnc_initUIBridge.sqf index 40df814..59d57da 100644 --- a/arma/client/addons/bank/functions/fnc_initUIBridge.sqf +++ b/arma/client/addons/bank/functions/fnc_initUIBridge.sqf @@ -63,6 +63,13 @@ GVAR(BankUIBridgeBaseClass) = compileFinal createHashMapFromArray [ [SRPC(bank,requestDeposit), [getPlayerUID player, _amount]] call CFUNC(serverEvent); true }], + ["handleRepayCreditLineRequest", compileFinal { + params [["_data", createHashMap, [createHashMap]]]; + + private _amount = floor (_data getOrDefault ["amount", 0]); + [SRPC(bank,requestRepayCreditLine), [getPlayerUID player, _amount]] call CFUNC(serverEvent); + true + }], ["handleHydrateResponse", compileFinal { params [["_data", createHashMap, [createHashMap]], ["_event", "bank::hydrate", [""]]]; diff --git a/arma/client/addons/bank/ui/_site/bank-ui.js b/arma/client/addons/bank/ui/_site/bank-ui.js index 2922620..b733bec 100644 --- a/arma/client/addons/bank/ui/_site/bank-ui.js +++ b/arma/client/addons/bank/ui/_site/bank-ui.js @@ -1 +1 @@ -!function(){const n=window.ForgeWebUI;(window.BankApp=window.BankApp||{}).runtime=n,window.AppRuntime=n}(),function(){const n=window.BankApp=window.BankApp||{},e={atmAuthorized:!1,mode:"bank",orgFunds:0,orgName:"",playerName:"",transferTargets:[],uid:""},t={bank:0,cash:0,earnings:0,transactions:[]};function a(n,e){var t;Object.keys(n).forEach(e=>delete n[e]),Object.assign(n,(t=e,JSON.parse(JSON.stringify(t))))}n.data={account:Object.assign({},t),session:Object.assign({},e),applyAccountPatch(n){const e=Object.assign({},this.account,n||{});a(this.account,Object.assign({},t,e))},applyHydratePayload(n){a(this.session,Object.assign({},e,n?.session||{})),a(this.account,Object.assign({},t,n?.account||{}))}}}(),function(){const n=window.BankApp=window.BankApp||{},{createSignal:e}=n.runtime;n.store=new class{constructor(){[this.getMode,this.setMode]=e("bank"),[this.getNotice,this.setNotice]=e({text:"",type:""}),[this.getPendingAction,this.setPendingAction]=e(""),[this.getAtmView,this.setAtmView]=e("pin"),[this.getEnteredPin,this.setEnteredPin]=e(""),[this.getCustomAmount,this.setCustomAmount]=e(""),[this.getAccountVersion,this.setAccountVersion]=e(0),[this.getSessionVersion,this.setSessionVersion]=e(0)}finishAction(){this.setPendingAction("")}hydrateFromPayload(n){const e=String(n?.session?.mode||"bank").trim().toLowerCase(),t=Boolean(n?.session?.atmAuthorized),a=this.getMode(),s=this.getAtmView(),i=this.getPendingAction();if(this.setMode("atm"===e?"atm":"bank"),this.setPendingAction(""),this.setEnteredPin(""),this.setCustomAmount(""),this.setAccountVersion(this.getAccountVersion()+1),this.setSessionVersion(this.getSessionVersion()+1),"atm"===e)return t?"deposit"===i||"withdraw"===i||"pin"===s||"atm"!==a?void this.setAtmView("menu"):void this.setAtmView(s):void this.setAtmView("pin");this.setAtmView("dashboard")}syncAccountPatch(){this.setPendingAction(""),this.setAccountVersion(this.getAccountVersion()+1)}resetAtm(){this.setEnteredPin(""),this.setCustomAmount(""),this.setAtmView("pin")}startAction(n){this.setPendingAction(String(n||"").trim().toLowerCase())}}}(),function(){const n=window.BankApp=window.BankApp||{},e=n.store,t=window.ForgeWebUI.createBridge({closeEvent:"bank::close",globalName:"ForgeBridge",readyEvent:"bank::ready"});t.on("bank::hydrate",function(t){n.data.applyHydratePayload(t),e.hydrateFromPayload(t)}),t.on("bank::sync",function(t){n.data.applyAccountPatch(t),e.syncAccountPatch()}),t.on("bank::notice",t=>{e.finishAction(),n.actions&&n.actions.showNotice(t.type||"error",t.message||"Bank notice received.")}),n.bridge={notifyReady:()=>t.ready({loaded:!0}),receive:t.receive,requestClose:()=>t.close({}),requestDeposit:n=>t.send("bank::deposit::request",n),requestDepositEarnings:n=>t.send("bank::depositEarnings::request",n),requestRefresh:()=>t.send("bank::refresh",{}),requestSubmitPin:n=>t.send("bank::pin::request",n),requestTransfer:n=>t.send("bank::transfer::request",n),requestWithdraw:n=>t.send("bank::withdraw::request",n),sendEvent:t.send}}(),function(){const n=window.BankApp=window.BankApp||{},e=n.store;let t=null;function a(n){const e=Math.floor(Number(n||0));return Number.isFinite(e)?e:0}function s(n,a){e.setNotice({type:n,text:a}),t&&clearTimeout(t),t=setTimeout(()=>{e.setNotice({text:"",type:""}),t=null},3200)}function i(t){const i=a(t),o=n.bridge;if(!o||"function"!=typeof o.requestDeposit)return s("error","Deposit bridge is unavailable."),!1;e.startAction("deposit");return!!o.requestDeposit({amount:i})||(e.finishAction(),s("error","Deposit bridge is unavailable."),!1)}function o(t){const i=a(t),o=n.bridge;if(!o||"function"!=typeof o.requestWithdraw)return s("error","Withdraw bridge is unavailable."),!1;e.startAction("withdraw");return!!o.requestWithdraw({amount:i})||(e.finishAction(),s("error","Withdraw bridge is unavailable."),!1)}function r(){e.setEnteredPin("")}n.actions={appendCustomAmountDigit:function(n){const t=String(n||"").trim();if(!t)return;const a=String(e.getCustomAmount()||"");a.length>=7||e.setCustomAmount(a+t)},appendPinDigit:function(n){const t=String(n||"").trim();if(!t)return;const a=String(e.getEnteredPin()||"");a.length>=4||e.setEnteredPin(a+t)},backspaceCustomAmount:function(){const n=String(e.getCustomAmount()||"");e.setCustomAmount(n.slice(0,-1))},backspacePin:function(){const n=String(e.getEnteredPin()||"");e.setEnteredPin(n.slice(0,-1))},clearCustomAmount:function(){e.setCustomAmount("")},clearPin:r,closeBank:function(){const e=n.bridge;if(e&&"function"==typeof e.requestClose){if(e.requestClose())return!0}return s("error","Bank bridge is unavailable."),!1},refreshBank:function(){const e=n.bridge;if(e&&"function"==typeof e.requestRefresh){if(e.requestRefresh())return!0}return s("error","Bank refresh bridge is unavailable."),!1},requestAtmAmount:function(n,e){return"deposit"===String(n||"").trim().toLowerCase()?i(e):o(e)},requestDeposit:i,requestDepositEarnings:function(t){const i=a(t),o=n.bridge;return o&&"function"==typeof o.requestDepositEarnings?(e.startAction("depositearnings"),!!o.requestDepositEarnings({amount:i})||(e.finishAction(),s("error","Earnings bridge is unavailable."),!1)):(s("error","Earnings bridge is unavailable."),!1)},requestTransfer:function(t,i){const o=a(i),r=String(t||"").trim(),c=n.bridge;return c&&"function"==typeof c.requestTransfer?(e.startAction("transfer"),!!c.requestTransfer({amount:o,from:"bank",target:r})||(e.finishAction(),s("error","Transfer bridge is unavailable."),!1)):(s("error","Transfer bridge is unavailable."),!1)},requestWithdraw:o,selectAtmView:function(n){const t=String(n||"").trim();return!!t&&("pin"===t?(e.resetAtm(),!0):(e.setCustomAmount(""),e.setAtmView(t),!0))},showNotice:s,submitCustomAmount:function(n){const t=a(e.getCustomAmount()),r=String(n||"").trim().toLowerCase();if(t<=0)return s("error","Enter a valid transaction amount."),!1;const c="deposit"===r?i(t):o(t);return c&&e.setCustomAmount(""),c},submitPin:function(){const t=String(e.getEnteredPin()||""),a=n.bridge;return a&&"function"==typeof a.requestSubmitPin?(e.startAction("pin"),a.requestSubmitPin({pin:t})?(r(),!0):(e.finishAction(),s("error","PIN bridge is unavailable."),!1)):(s("error","PIN bridge is unavailable."),!1)}}}(),function(){const n=window.BankApp=window.BankApp||{},{h:e}=n.runtime,t=n.store,{account:a}=n.data;function s(n){return`$${Math.round(Number(n||0)).toLocaleString()}`}n.componentFns=n.componentFns||{},Object.assign(n.componentFns,{clearInputValue:function(n){const e=document.getElementById(n);e&&(e.value="")},formatCurrency:s,keypad:function(n,t,a,s){return e("div",{className:"bank-keypad"},["1","2","3","4","5","6","7","8","9"].map(t=>e("button",{type:"button",className:"bank-key",onClick:()=>n(t)},t)),e("button",{type:"button",className:"bank-key is-muted",onClick:a},"C"),e("button",{type:"button",className:"bank-key",onClick:()=>n("0")},"0"),e("button",{type:"button",className:"bank-key is-accent",onClick:s},"Enter"),e("button",{type:"button",className:"bank-key is-wide",onClick:t},"Backspace"))},metricCard:function(n,t,a,s=""){return e("div",{className:s?`bank-metric-card is-${s}`:"bank-metric-card"},e("span",{className:"bank-eyebrow"},n),e("span",{className:"bank-metric-value"},t),e("span",{className:"bank-metric-copy"},a))},pending:function(n){return t.getPendingAction()===n},pinIndicators:function(n){const t=String(n||"");return e("div",{className:"bank-pin-indicators"},[0,1,2,3].map(n=>e("span",{className:ne("div",{className:"bank-history-row"},e("div",{className:"bank-history-copy"},e("span",{className:"bank-history-title"},n.type||"Transaction"),e("span",{className:"bank-history-meta"},n.date||"Pending timestamp")),e("span",{className:"bank-history-value"},s(n.amount||0)))))}})}(),function(){const n=window.BankApp=window.BankApp||{},{h:e}=n.runtime,t=n.store,a=n.actions,{account:s,session:i}=n.data,{formatCurrency:o,statCard:r}=n.componentFns;n.componentFns=n.componentFns||{},n.componentFns.BankSidebar=function(){return t.getAccountVersion(),t.getSessionVersion(),e("aside",{className:"bank-sidebar"},e("section",{className:"bank-module"},e("div",{className:"bank-module-header"},e("div",null,e("span",{className:"bank-eyebrow"},"Account"),e("h2",{className:"bank-section-title"},"Balances")),e("span",{className:"bank-pill"},"Live")),e("div",{className:"bank-summary-grid"},r("Bank",o(s.bank),"accent"),r("Cash",o(s.cash)),r("Earnings",o(s.earnings),s.earnings>0?"warning":""),r("Org Funds",o(i.orgFunds),i.orgFunds>0?"success":""))),e("section",{className:"bank-module"},e("div",{className:"bank-module-header"},e("div",null,e("span",{className:"bank-eyebrow"},"Profile"),e("h2",{className:"bank-section-title"},"Account Holder")),e("button",{type:"button",className:"bank-btn bank-btn-secondary",onClick:()=>a.refreshBank()},"Refresh")),e("div",{className:"bank-profile-stack"},r("Name",i.playerName||"Unknown"),r("UID",i.uid||"-"),r("Organization",i.orgName||"No active organization"))))}}(),function(){const n=window.BankApp=window.BankApp||{},{h:e}=n.runtime,t=n.store,{account:a,session:s}=n.data,{formatCurrency:i}=n.componentFns;n.componentFns=n.componentFns||{},n.componentFns.BankFooter=function(){t.getAccountVersion(),t.getSessionVersion();const n=[{title:"Banking Resources",items:["Account Access Policy","Transfer & Wire Guidelines","Cash Handling Schedule","Terminal Security Notice"]},{title:"Bank Support",items:s.orgName?[`Organization: ${s.orgName}`,`Treasury Reference: ${i(s.orgFunds)}`,`${s.transferTargets.length} transfer recipient(s) currently visible.`,`Primary Ledger: ${i(a.bank)}`]:["Organization: No active treasury link",`${s.transferTargets.length} transfer recipient(s) currently visible.`,`Primary Ledger: ${i(a.bank)}`,`Cash On Hand: ${i(a.cash)}`]}];return e("footer",{className:"bank-footer-bar"},e("div",{className:"bank-footer"},...n.map(n=>e("div",{className:"bank-footer-block"},e("h3",{className:"bank-footer-title"},n.title),e("ul",{className:"bank-footer-list"},...(n.items||[]).map(n=>e("li",{className:"bank-footer-copy"},n)))))))}}(),function(){const n=window.BankApp=window.BankApp||{},{h:e}=n.runtime,t=n.store,a=n.actions,{account:s,session:i}=n.data,{clearInputValue:o,formatCurrency:r,metricCard:c,pending:u,readInputValue:l,transactionRows:m}=n.componentFns;function d(){t.getAccountVersion()}function b(){t.getSessionVersion()}n.componentFns=n.componentFns||{},n.componentFns.BankPageHeader=function(){return b(),e("div",{className:"bank-page-header"},e("div",null,e("span",{className:"bank-eyebrow"},"Treasury Desk"),e("h1",{className:"bank-title"},"Personal Banking")),e("span",{className:"bank-pill"},i.playerName||"Account Holder"))},n.componentFns.BankSummarySection=function(){return d(),b(),e("section",{className:"bank-page-section bank-summary-section"},e("div",{className:"bank-section-header"},e("div",null,e("span",{className:"bank-eyebrow"},"Overview"),e("h2",{className:"bank-section-title"},"Financial Position")),e("span",{className:"bank-pill"},"Banking Desk")),e("div",{className:"bank-summary-band"},c("Primary Balance",r(s.bank),"Available for transfers and withdrawals.","accent"),c("Cash On Hand",r(s.cash),"Funds currently carried by the player."),c("Pending Earnings",r(s.earnings),"Ready to sweep into the main account ledger.",s.earnings>0?"warning":""),c("Org Snapshot",r(i.orgFunds),"Reference value pulled from the organization treasury.",i.orgFunds>0?"success":"")))},n.componentFns.BankActionSections=function(){return b(),e("div",{className:"bank-action-sections"},e("section",{className:"bank-page-section"},e("div",{className:"bank-section-header"},e("div",null,e("span",{className:"bank-eyebrow"},"Movement"),e("h2",{className:"bank-section-title"},"Deposit / Withdraw"))),e("div",{className:"bank-form-stack"},e("input",{id:"bank-amount-input",className:"bank-input",type:"number",min:"1",placeholder:"Enter amount"}),e("div",{className:"bank-action-row"},e("button",{type:"button",className:"bank-btn bank-btn-primary",disabled:u("deposit"),onClick:()=>{a.requestDeposit(l("bank-amount-input"))&&o("bank-amount-input")}},u("deposit")?"Depositing...":"Deposit"),e("button",{type:"button",className:"bank-btn bank-btn-secondary",disabled:u("withdraw"),onClick:()=>{a.requestWithdraw(l("bank-amount-input"))&&o("bank-amount-input")}},u("withdraw")?"Withdrawing...":"Withdraw")))),e("section",{className:"bank-page-section"},e("div",{className:"bank-section-header"},e("div",null,e("span",{className:"bank-eyebrow"},"Transfer"),e("h2",{className:"bank-section-title"},"Wire Funds"))),e("div",{className:"bank-form-stack"},e("select",{id:"bank-transfer-target",className:"bank-select"},e("option",{value:""},i.transferTargets.length>0?"Select recipient":"No available recipients"),i.transferTargets.map(n=>e("option",{value:n.uid},n.name||n.uid))),e("input",{id:"bank-transfer-amount",className:"bank-input",type:"number",min:"1",placeholder:"Enter transfer amount"}),e("button",{type:"button",className:"bank-btn bank-btn-primary",disabled:u("transfer")||0===i.transferTargets.length,onClick:()=>{a.requestTransfer(l("bank-transfer-target"),l("bank-transfer-amount"))&&o("bank-transfer-amount")}},u("transfer")?"Transferring...":"Transfer Funds"))))},n.componentFns.BankSupportSection=function(){return d(),e("div",{className:"bank-support-sections"},e("section",{className:"bank-page-section"},e("div",{className:"bank-section-header"},e("div",null,e("span",{className:"bank-eyebrow"},"Sweep"),e("h2",{className:"bank-section-title"},"Deposit Earnings"))),e("p",{className:"bank-card-copy"},"Sweep pending earnings into the primary account when you want them reflected in the main balance."),e("button",{type:"button",className:"bank-btn bank-btn-primary",disabled:u("depositearnings")||Number(s.earnings||0)<=0,onClick:()=>a.requestDepositEarnings(s.earnings)},u("depositearnings")?"Depositing...":"Deposit Earnings")))},n.componentFns.BankHistorySection=function(){return d(),e("section",{className:"bank-page-section bank-history-section"},e("div",{className:"bank-section-header"},e("div",null,e("span",{className:"bank-eyebrow"},"History"),e("h2",{className:"bank-section-title"},"Recent Transactions"))),m())}}(),function(){const n=window.BankApp=window.BankApp||{},{h:e}=n.runtime,t=n.store,a=n.actions,{account:s}=n.data,{formatCurrency:i,keypad:o,pinIndicators:r}=n.componentFns;function c(n){const t="deposit"===n?"Deposit":"Withdraw";return e("div",{className:"bank-atm-action-grid"},[20,50,100,500].map(s=>e("button",{type:"button",className:"bank-btn bank-btn-primary",onClick:()=>a.requestAtmAmount(n,s)},`${t} ${i(s)}`)),e("button",{type:"button",className:"bank-btn bank-btn-secondary",onClick:()=>a.selectAtmView("deposit"===n?"customDeposit":"customWithdraw")},"Custom Amount"),e("button",{type:"button",className:"bank-btn bank-btn-secondary",onClick:()=>a.selectAtmView("menu")},"Back"))}function u(n){const s="deposit"===n?"Deposit":"Withdraw";return e("div",{className:"bank-atm-stack"},e("div",{className:"bank-pin-display"},t.getCustomAmount()?i(t.getCustomAmount()):"$0"),o(a.appendCustomAmountDigit,a.backspaceCustomAmount,a.clearCustomAmount,()=>a.submitCustomAmount(n)),e("button",{type:"button",className:"bank-btn bank-btn-secondary",onClick:()=>a.selectAtmView("menu")},`Cancel ${s}`))}n.componentFns=n.componentFns||{},n.componentFns.ATMView=function(){t.getAccountVersion();const n=t.getAtmView(),l=String(t.getEnteredPin()||"");let m="Terminal Access",d="Authenticate with the four-digit account PIN before using the terminal.",b=null;switch(n){case"menu":m="ATM Menu",d="Select a banking action. The ATM can deposit, withdraw, and show the live account balance.",b=e("div",{className:"bank-atm-action-grid"},e("button",{type:"button",className:"bank-btn bank-btn-primary",onClick:()=>a.selectAtmView("withdraw")},"Withdraw Cash"),e("button",{type:"button",className:"bank-btn bank-btn-primary",onClick:()=>a.selectAtmView("deposit")},"Deposit Cash"),e("button",{type:"button",className:"bank-btn bank-btn-secondary",onClick:()=>a.selectAtmView("balance")},"Check Balance"),e("button",{type:"button",className:"bank-btn bank-btn-secondary",onClick:()=>a.closeBank()},"Exit Terminal"));break;case"withdraw":m="Withdraw Cash",d="Choose a preset amount or enter a custom amount for withdrawal.",b=c("withdraw");break;case"deposit":m="Deposit Cash",d="Move cash on hand back into the main bank balance from the terminal.",b=c("deposit");break;case"customWithdraw":m="Custom Withdraw",d="Enter the exact withdrawal amount.",b=u("withdraw");break;case"customDeposit":m="Custom Deposit",d="Enter the exact deposit amount.",b=u("deposit");break;case"balance":m="Available Balance",d="Current bank balance available at this terminal.",b=e("div",{className:"bank-atm-stack"},e("div",{className:"bank-balance-display"},i(s.bank)),e("button",{type:"button",className:"bank-btn bank-btn-primary",onClick:()=>a.selectAtmView("menu")},"Return to Menu"));break;default:b=e("div",{className:"bank-atm-stack"},e("div",{className:"bank-pin-display"},r(l)),o(a.appendPinDigit,a.backspacePin,a.clearPin,a.submitPin),e("button",{type:"button",className:"bank-btn bank-btn-secondary",onClick:()=>a.closeBank()},"Exit Terminal"))}return e("div",{className:"bank-atm-shell"},e("section",{className:"bank-atm-panel"},e("div",{className:"bank-panel-header"},e("div",null,e("span",{className:"bank-eyebrow"},"ATM"),e("h1",{className:"bank-title"},m)),e("span",{className:"bank-pill"},"Secure Terminal")),e("p",{className:"bank-panel-copy"},d),b))}}(),function(){const n=window.BankApp=window.BankApp||{},{h:e}=n.runtime,t=window.SharedUI.componentFns.WindowTitleBar,a=n.store,s=n.actions;n.componentFns=n.componentFns||{},n.componentFns.NoticeLayer=function(){const n=a.getNotice();return n.text?e("div",{className:"bank-notice-stack"},e("div",{className:"error"===n.type?"bank-notice is-error":"bank-notice is-success"},n.text)):null},n.components=n.components||{},n.components.App=function(){const n=a.getMode();return e("div",{className:"atm"===n?"bank-shell is-atm":"bank-shell"},"atm"===n?null:t({kicker:"FORGE Finance",title:"Global Banking Network",onClose:()=>s.closeBank(),closeLabel:"Close banking interface"}),e("div",{id:"bank-notice-root"}),"atm"===n?e("div",{id:"bank-atm-root"}):[e("div",{className:"bank-scroll-shell","data-preserve-scroll-id":"bank-page-scroll"},[e("div",{className:"bank-layout"},e("div",{id:"bank-sidebar-root"}),e("main",{className:"bank-main"},e("div",{className:"bank-page"},e("div",{id:"bank-page-header-root"}),e("p",{className:"bank-page-copy"},"Manage deposits, withdrawals, transfers, and earnings sweeps from the same shared financial console."),e("div",{className:"bank-page-divider"}),e("div",{className:"bank-page-body"},e("div",{id:"bank-summary-section-root"}),e("div",{id:"bank-action-sections-root"}),e("div",{id:"bank-support-section-root"}),e("div",{id:"bank-history-section-root"}))))),e("div",{id:"bank-footer-root"})])])}}(),function(){const n=window.ForgeWebUI,e=window.BankApp,t=[{id:"bank-notice-root",preserveScroll:!1,render:()=>e.componentFns.NoticeLayer()},{id:"bank-sidebar-root",preserveScroll:!1,render:()=>e.componentFns.BankSidebar()},{id:"bank-page-header-root",preserveScroll:!1,render:()=>e.componentFns.BankPageHeader()},{id:"bank-summary-section-root",preserveScroll:!1,render:()=>e.componentFns.BankSummarySection()},{id:"bank-action-sections-root",preserveScroll:!1,render:()=>e.componentFns.BankActionSections()},{id:"bank-support-section-root",preserveScroll:!1,render:()=>e.componentFns.BankSupportSection()},{id:"bank-history-section-root",preserveScroll:!1,render:()=>e.componentFns.BankHistorySection()},{id:"bank-atm-root",preserveScroll:!1,render:()=>e.componentFns.ATMView()},{id:"bank-footer-root",preserveScroll:!1,render:()=>e.componentFns.BankFooter()}];n.createApp({name:"bank",root:"#app",setup({root:a}){const s=function(){const e=new Map;return{sync:function(){t.forEach(t=>{const a=document.getElementById(t.id),s=e.get(t.id);if(!a)return void(s&&(s.handle.dispose(),e.delete(t.id)));if(s&&s.container===a)return;s&&s.handle.dispose();const i=n.mount(a,t.render,{preserveScroll:t.preserveScroll});e.set(t.id,{container:a,handle:i})})}}}();n.mount(a,()=>e.components.App(),{preserveScroll:!1}),e.bridge&&e.bridge.notifyReady(),n.effect(()=>{e.store.getMode(),requestAnimationFrame(()=>{s.sync()})})}}).start()}(); \ No newline at end of file +!function(){const n=window.ForgeWebUI;(window.BankApp=window.BankApp||{}).runtime=n,window.AppRuntime=n}(),function(){const n=window.BankApp=window.BankApp||{},e={atmAuthorized:!1,creditLine:{amountDue:0,approvedAmount:0,availableAmount:0,interestRate:.1,outstandingPrincipal:0},mode:"bank",orgFunds:0,orgName:"",playerName:"",transferTargets:[],uid:""},t={bank:0,cash:0,earnings:0,transactions:[]};function a(n,e){var t;Object.keys(n).forEach(e=>delete n[e]),Object.assign(n,(t=e,JSON.parse(JSON.stringify(t))))}n.data={account:Object.assign({},t),session:Object.assign({},e),applyAccountPatch(n){const e=Object.assign({},this.account,n||{});a(this.account,Object.assign({},t,e))},applyHydratePayload(n){a(this.session,Object.assign({},e,n?.session||{})),a(this.account,Object.assign({},t,n?.account||{}))}}}(),function(){const n=window.BankApp=window.BankApp||{},{createSignal:e}=n.runtime;n.store=new class{constructor(){[this.getMode,this.setMode]=e("bank"),[this.getNotice,this.setNotice]=e({text:"",type:""}),[this.getPendingAction,this.setPendingAction]=e(""),[this.getAtmView,this.setAtmView]=e("pin"),[this.getEnteredPin,this.setEnteredPin]=e(""),[this.getCustomAmount,this.setCustomAmount]=e(""),[this.getAccountVersion,this.setAccountVersion]=e(0),[this.getSessionVersion,this.setSessionVersion]=e(0)}finishAction(){this.setPendingAction("")}hydrateFromPayload(n){const e=String(n?.session?.mode||"bank").trim().toLowerCase(),t=Boolean(n?.session?.atmAuthorized),a=this.getMode(),s=this.getAtmView(),i=this.getPendingAction();if(this.setMode("atm"===e?"atm":"bank"),this.setPendingAction(""),this.setEnteredPin(""),this.setCustomAmount(""),this.setAccountVersion(this.getAccountVersion()+1),this.setSessionVersion(this.getSessionVersion()+1),"atm"===e)return t?"deposit"===i||"withdraw"===i||"pin"===s||"atm"!==a?void this.setAtmView("menu"):void this.setAtmView(s):void this.setAtmView("pin");this.setAtmView("dashboard")}syncAccountPatch(){this.setPendingAction(""),this.setAccountVersion(this.getAccountVersion()+1)}resetAtm(){this.setEnteredPin(""),this.setCustomAmount(""),this.setAtmView("pin")}startAction(n){this.setPendingAction(String(n||"").trim().toLowerCase())}}}(),function(){const n=window.BankApp=window.BankApp||{},e=n.store,t=window.ForgeWebUI.createBridge({closeEvent:"bank::close",globalName:"ForgeBridge",readyEvent:"bank::ready"});t.on("bank::hydrate",function(t){n.data.applyHydratePayload(t),e.hydrateFromPayload(t)}),t.on("bank::sync",function(t){n.data.applyAccountPatch(t),e.syncAccountPatch()}),t.on("bank::notice",t=>{e.finishAction(),n.actions&&n.actions.showNotice(t.type||"error",t.message||"Bank notice received.")}),n.bridge={notifyReady:()=>t.ready({loaded:!0}),receive:t.receive,requestClose:()=>t.close({}),requestDeposit:n=>t.send("bank::deposit::request",n),requestDepositEarnings:n=>t.send("bank::depositEarnings::request",n),requestRepayCreditLine:n=>t.send("bank::repayCreditLine::request",n),requestRefresh:()=>t.send("bank::refresh",{}),requestSubmitPin:n=>t.send("bank::pin::request",n),requestTransfer:n=>t.send("bank::transfer::request",n),requestWithdraw:n=>t.send("bank::withdraw::request",n),sendEvent:t.send}}(),function(){const n=window.BankApp=window.BankApp||{},e=n.store;let t=null;function a(n){const e=Math.floor(Number(n||0));return Number.isFinite(e)?e:0}function s(n,a){e.setNotice({type:n,text:a}),t&&clearTimeout(t),t=setTimeout(()=>{e.setNotice({text:"",type:""}),t=null},3200)}function i(t){const i=a(t),o=n.bridge;if(!o||"function"!=typeof o.requestDeposit)return s("error","Deposit bridge is unavailable."),!1;e.startAction("deposit");return!!o.requestDeposit({amount:i})||(e.finishAction(),s("error","Deposit bridge is unavailable."),!1)}function o(t){const i=a(t),o=n.bridge;if(!o||"function"!=typeof o.requestWithdraw)return s("error","Withdraw bridge is unavailable."),!1;e.startAction("withdraw");return!!o.requestWithdraw({amount:i})||(e.finishAction(),s("error","Withdraw bridge is unavailable."),!1)}function r(){e.setEnteredPin("")}n.actions={appendCustomAmountDigit:function(n){const t=String(n||"").trim();if(!t)return;const a=String(e.getCustomAmount()||"");a.length>=7||e.setCustomAmount(a+t)},appendPinDigit:function(n){const t=String(n||"").trim();if(!t)return;const a=String(e.getEnteredPin()||"");a.length>=4||e.setEnteredPin(a+t)},backspaceCustomAmount:function(){const n=String(e.getCustomAmount()||"");e.setCustomAmount(n.slice(0,-1))},backspacePin:function(){const n=String(e.getEnteredPin()||"");e.setEnteredPin(n.slice(0,-1))},clearCustomAmount:function(){e.setCustomAmount("")},clearPin:r,closeBank:function(){const e=n.bridge;if(e&&"function"==typeof e.requestClose){if(e.requestClose())return!0}return s("error","Bank bridge is unavailable."),!1},refreshBank:function(){const e=n.bridge;if(e&&"function"==typeof e.requestRefresh){if(e.requestRefresh())return!0}return s("error","Bank refresh bridge is unavailable."),!1},requestAtmAmount:function(n,e){return"deposit"===String(n||"").trim().toLowerCase()?i(e):o(e)},requestDeposit:i,requestDepositEarnings:function(t){const i=a(t),o=n.bridge;return o&&"function"==typeof o.requestDepositEarnings?(e.startAction("depositearnings"),!!o.requestDepositEarnings({amount:i})||(e.finishAction(),s("error","Earnings bridge is unavailable."),!1)):(s("error","Earnings bridge is unavailable."),!1)},requestRepayCreditLine:function(t){const i=a(t),o=n.bridge;return o&&"function"==typeof o.requestRepayCreditLine?(e.startAction("repaycreditline"),!!o.requestRepayCreditLine({amount:i})||(e.finishAction(),s("error","Credit repayment bridge is unavailable."),!1)):(s("error","Credit repayment bridge is unavailable."),!1)},requestTransfer:function(t,i){const o=a(i),r=String(t||"").trim(),c=n.bridge;return c&&"function"==typeof c.requestTransfer?(e.startAction("transfer"),!!c.requestTransfer({amount:o,from:"bank",target:r})||(e.finishAction(),s("error","Transfer bridge is unavailable."),!1)):(s("error","Transfer bridge is unavailable."),!1)},requestWithdraw:o,selectAtmView:function(n){const t=String(n||"").trim();return!!t&&("pin"===t?(e.resetAtm(),!0):(e.setCustomAmount(""),e.setAtmView(t),!0))},showNotice:s,submitCustomAmount:function(n){const t=a(e.getCustomAmount()),r=String(n||"").trim().toLowerCase();if(t<=0)return s("error","Enter a valid transaction amount."),!1;const c="deposit"===r?i(t):o(t);return c&&e.setCustomAmount(""),c},submitPin:function(){const t=String(e.getEnteredPin()||""),a=n.bridge;return a&&"function"==typeof a.requestSubmitPin?(e.startAction("pin"),a.requestSubmitPin({pin:t})?(r(),!0):(e.finishAction(),s("error","PIN bridge is unavailable."),!1)):(s("error","PIN bridge is unavailable."),!1)}}}(),function(){const n=window.BankApp=window.BankApp||{},{h:e}=n.runtime,t=n.store,{account:a}=n.data;function s(n){return`$${Math.round(Number(n||0)).toLocaleString()}`}n.componentFns=n.componentFns||{},Object.assign(n.componentFns,{clearInputValue:function(n){const e=document.getElementById(n);e&&(e.value="")},formatCurrency:s,keypad:function(n,t,a,s){return e("div",{className:"bank-keypad"},["1","2","3","4","5","6","7","8","9"].map(t=>e("button",{type:"button",className:"bank-key",onClick:()=>n(t)},t)),e("button",{type:"button",className:"bank-key is-muted",onClick:a},"C"),e("button",{type:"button",className:"bank-key",onClick:()=>n("0")},"0"),e("button",{type:"button",className:"bank-key is-accent",onClick:s},"Enter"),e("button",{type:"button",className:"bank-key is-wide",onClick:t},"Backspace"))},metricCard:function(n,t,a,s=""){return e("div",{className:s?`bank-metric-card is-${s}`:"bank-metric-card"},e("span",{className:"bank-eyebrow"},n),e("span",{className:"bank-metric-value"},t),e("span",{className:"bank-metric-copy"},a))},pending:function(n){return t.getPendingAction()===n},pinIndicators:function(n){const t=String(n||"");return e("div",{className:"bank-pin-indicators"},[0,1,2,3].map(n=>e("span",{className:ne("div",{className:"bank-history-row"},e("div",{className:"bank-history-copy"},e("span",{className:"bank-history-title"},n.type||"Transaction"),e("span",{className:"bank-history-meta"},n.date||"Pending timestamp")),e("span",{className:"bank-history-value"},s(n.amount||0)))))}})}(),function(){const n=window.BankApp=window.BankApp||{},{h:e}=n.runtime,t=n.store,a=n.actions,{account:s,session:i}=n.data,{formatCurrency:o,statCard:r}=n.componentFns;n.componentFns=n.componentFns||{},n.componentFns.BankSidebar=function(){return t.getAccountVersion(),t.getSessionVersion(),e("aside",{className:"bank-sidebar"},e("section",{className:"bank-module"},e("div",{className:"bank-module-header"},e("div",null,e("span",{className:"bank-eyebrow"},"Account"),e("h2",{className:"bank-section-title"},"Balances")),e("span",{className:"bank-pill"},"Live")),e("div",{className:"bank-summary-grid"},r("Bank",o(s.bank),"accent"),r("Cash",o(s.cash)),r("Earnings",o(s.earnings),s.earnings>0?"warning":""),r("Org Funds",o(i.orgFunds),i.orgFunds>0?"success":""))),e("section",{className:"bank-module"},e("div",{className:"bank-module-header"},e("div",null,e("span",{className:"bank-eyebrow"},"Profile"),e("h2",{className:"bank-section-title"},"Account Holder")),e("button",{type:"button",className:"bank-btn bank-btn-secondary",onClick:()=>a.refreshBank()},"Refresh")),e("div",{className:"bank-profile-stack"},r("Name",i.playerName||"Unknown"),r("UID",i.uid||"-"),r("Organization",i.orgName||"No active organization"))))}}(),function(){const n=window.BankApp=window.BankApp||{},{h:e}=n.runtime,t=n.store,{account:a,session:s}=n.data,{formatCurrency:i}=n.componentFns;n.componentFns=n.componentFns||{},n.componentFns.BankFooter=function(){t.getAccountVersion(),t.getSessionVersion();const n=[{title:"Banking Resources",items:["Account Access Policy","Transfer & Wire Guidelines","Cash Handling Schedule","Terminal Security Notice"]},{title:"Bank Support",items:s.orgName?[`Organization: ${s.orgName}`,`Treasury Reference: ${i(s.orgFunds)}`,`${s.transferTargets.length} transfer recipient(s) currently visible.`,`Primary Ledger: ${i(a.bank)}`]:["Organization: No active treasury link",`${s.transferTargets.length} transfer recipient(s) currently visible.`,`Primary Ledger: ${i(a.bank)}`,`Cash On Hand: ${i(a.cash)}`]}];return e("footer",{className:"bank-footer-bar"},e("div",{className:"bank-footer"},...n.map(n=>e("div",{className:"bank-footer-block"},e("h3",{className:"bank-footer-title"},n.title),e("ul",{className:"bank-footer-list"},...(n.items||[]).map(n=>e("li",{className:"bank-footer-copy"},n)))))))}}(),function(){const n=window.BankApp=window.BankApp||{},{h:e}=n.runtime,t=n.store,a=n.actions,{account:s,session:i}=n.data,{clearInputValue:o,formatCurrency:r,metricCard:c,pending:u,readInputValue:l,transactionRows:d}=n.componentFns;function m(){t.getAccountVersion()}function b(){t.getSessionVersion()}n.componentFns=n.componentFns||{},n.componentFns.BankPageHeader=function(){return b(),e("div",{className:"bank-page-header"},e("div",null,e("span",{className:"bank-eyebrow"},"Treasury Desk"),e("h1",{className:"bank-title"},"Personal Banking")),e("span",{className:"bank-pill"},i.playerName||"Account Holder"))},n.componentFns.BankSummarySection=function(){return m(),b(),e("section",{className:"bank-page-section bank-summary-section"},e("div",{className:"bank-section-header"},e("div",null,e("span",{className:"bank-eyebrow"},"Overview"),e("h2",{className:"bank-section-title"},"Financial Position")),e("span",{className:"bank-pill"},"Banking Desk")),e("div",{className:"bank-summary-band"},c("Primary Balance",r(s.bank),"Available for transfers and withdrawals.","accent"),c("Cash On Hand",r(s.cash),"Funds currently carried by the player."),c("Pending Earnings",r(s.earnings),"Ready to sweep into the main account ledger.",s.earnings>0?"warning":""),c("Org Snapshot",r(i.orgFunds),"Reference value pulled from the organization treasury.",i.orgFunds>0?"success":""),c("Credit Due",r(i.creditLine?.amountDue||0),Number(i.creditLine?.amountDue||0)>0?`Outstanding principal ${r(i.creditLine?.outstandingPrincipal||0)} at ${Math.round(100*Number(i.creditLine?.interestRate||0))}% interest.`:"No active credit repayment is currently due.",Number(i.creditLine?.amountDue||0)>0?"warning":"")))},n.componentFns.BankActionSections=function(){return b(),e("div",{className:"bank-action-sections"},e("section",{className:"bank-page-section"},e("div",{className:"bank-section-header"},e("div",null,e("span",{className:"bank-eyebrow"},"Movement"),e("h2",{className:"bank-section-title"},"Deposit / Withdraw"))),e("div",{className:"bank-form-stack"},e("input",{id:"bank-amount-input",className:"bank-input",type:"number",min:"1",placeholder:"Enter amount"}),e("div",{className:"bank-action-row"},e("button",{type:"button",className:"bank-btn bank-btn-primary",disabled:u("deposit"),onClick:()=>{a.requestDeposit(l("bank-amount-input"))&&o("bank-amount-input")}},u("deposit")?"Depositing...":"Deposit"),e("button",{type:"button",className:"bank-btn bank-btn-secondary",disabled:u("withdraw"),onClick:()=>{a.requestWithdraw(l("bank-amount-input"))&&o("bank-amount-input")}},u("withdraw")?"Withdrawing...":"Withdraw")))),e("section",{className:"bank-page-section"},e("div",{className:"bank-section-header"},e("div",null,e("span",{className:"bank-eyebrow"},"Transfer"),e("h2",{className:"bank-section-title"},"Wire Funds"))),e("div",{className:"bank-form-stack"},e("select",{id:"bank-transfer-target",className:"bank-select"},e("option",{value:""},i.transferTargets.length>0?"Select recipient":"No available recipients"),i.transferTargets.map(n=>e("option",{value:n.uid},n.name||n.uid))),e("input",{id:"bank-transfer-amount",className:"bank-input",type:"number",min:"1",placeholder:"Enter transfer amount"}),e("button",{type:"button",className:"bank-btn bank-btn-primary",disabled:u("transfer")||0===i.transferTargets.length,onClick:()=>{a.requestTransfer(l("bank-transfer-target"),l("bank-transfer-amount"))&&o("bank-transfer-amount")}},u("transfer")?"Transferring...":"Transfer Funds"))),e("section",{className:"bank-page-section"},e("div",{className:"bank-section-header"},e("div",null,e("span",{className:"bank-eyebrow"},"Credit"),e("h2",{className:"bank-section-title"},"Repay Org Credit"))),e("div",{className:"bank-form-stack"},e("p",{className:"bank-card-copy"},Number(i.creditLine?.amountDue||0)>0?`Outstanding due ${r(i.creditLine.amountDue||0)}. Available reserved credit ${r(i.creditLine.availableAmount||0)}.`:"No repayment is currently due on the assigned organization credit line."),e("input",{id:"bank-credit-line-amount",className:"bank-input",type:"number",min:"1",placeholder:"Enter repayment amount"}),e("button",{type:"button",className:"bank-btn bank-btn-primary",disabled:u("repaycreditline")||Number(i.creditLine?.amountDue||0)<=0,onClick:()=>{a.requestRepayCreditLine(l("bank-credit-line-amount"))&&o("bank-credit-line-amount")}},u("repaycreditline")?"Posting Repayment...":"Repay Credit Line"))))},n.componentFns.BankSupportSection=function(){return m(),e("div",{className:"bank-support-sections"},e("section",{className:"bank-page-section"},e("div",{className:"bank-section-header"},e("div",null,e("span",{className:"bank-eyebrow"},"Sweep"),e("h2",{className:"bank-section-title"},"Deposit Earnings"))),e("p",{className:"bank-card-copy"},"Sweep pending earnings into the primary account when you want them reflected in the main balance."),e("button",{type:"button",className:"bank-btn bank-btn-primary",disabled:u("depositearnings")||Number(s.earnings||0)<=0,onClick:()=>a.requestDepositEarnings(s.earnings)},u("depositearnings")?"Depositing...":"Deposit Earnings")))},n.componentFns.BankHistorySection=function(){return m(),e("section",{className:"bank-page-section bank-history-section"},e("div",{className:"bank-section-header"},e("div",null,e("span",{className:"bank-eyebrow"},"History"),e("h2",{className:"bank-section-title"},"Recent Transactions"))),d())}}(),function(){const n=window.BankApp=window.BankApp||{},{h:e}=n.runtime,t=n.store,a=n.actions,{account:s}=n.data,{formatCurrency:i,keypad:o,pinIndicators:r}=n.componentFns;function c(n){const t="deposit"===n?"Deposit":"Withdraw";return e("div",{className:"bank-atm-action-grid"},[20,50,100,500].map(s=>e("button",{type:"button",className:"bank-btn bank-btn-primary",onClick:()=>a.requestAtmAmount(n,s)},`${t} ${i(s)}`)),e("button",{type:"button",className:"bank-btn bank-btn-secondary",onClick:()=>a.selectAtmView("deposit"===n?"customDeposit":"customWithdraw")},"Custom Amount"),e("button",{type:"button",className:"bank-btn bank-btn-secondary",onClick:()=>a.selectAtmView("menu")},"Back"))}function u(n){const s="deposit"===n?"Deposit":"Withdraw";return e("div",{className:"bank-atm-stack"},e("div",{className:"bank-pin-display"},t.getCustomAmount()?i(t.getCustomAmount()):"$0"),o(a.appendCustomAmountDigit,a.backspaceCustomAmount,a.clearCustomAmount,()=>a.submitCustomAmount(n)),e("button",{type:"button",className:"bank-btn bank-btn-secondary",onClick:()=>a.selectAtmView("menu")},`Cancel ${s}`))}n.componentFns=n.componentFns||{},n.componentFns.ATMView=function(){t.getAccountVersion();const n=t.getAtmView(),l=String(t.getEnteredPin()||"");let d="Terminal Access",m="Authenticate with the four-digit account PIN before using the terminal.",b=null;switch(n){case"menu":d="ATM Menu",m="Select a banking action. The ATM can deposit, withdraw, and show the live account balance.",b=e("div",{className:"bank-atm-action-grid"},e("button",{type:"button",className:"bank-btn bank-btn-primary",onClick:()=>a.selectAtmView("withdraw")},"Withdraw Cash"),e("button",{type:"button",className:"bank-btn bank-btn-primary",onClick:()=>a.selectAtmView("deposit")},"Deposit Cash"),e("button",{type:"button",className:"bank-btn bank-btn-secondary",onClick:()=>a.selectAtmView("balance")},"Check Balance"),e("button",{type:"button",className:"bank-btn bank-btn-secondary",onClick:()=>a.closeBank()},"Exit Terminal"));break;case"withdraw":d="Withdraw Cash",m="Choose a preset amount or enter a custom amount for withdrawal.",b=c("withdraw");break;case"deposit":d="Deposit Cash",m="Move cash on hand back into the main bank balance from the terminal.",b=c("deposit");break;case"customWithdraw":d="Custom Withdraw",m="Enter the exact withdrawal amount.",b=u("withdraw");break;case"customDeposit":d="Custom Deposit",m="Enter the exact deposit amount.",b=u("deposit");break;case"balance":d="Available Balance",m="Current bank balance available at this terminal.",b=e("div",{className:"bank-atm-stack"},e("div",{className:"bank-balance-display"},i(s.bank)),e("button",{type:"button",className:"bank-btn bank-btn-primary",onClick:()=>a.selectAtmView("menu")},"Return to Menu"));break;default:b=e("div",{className:"bank-atm-stack"},e("div",{className:"bank-pin-display"},r(l)),o(a.appendPinDigit,a.backspacePin,a.clearPin,a.submitPin),e("button",{type:"button",className:"bank-btn bank-btn-secondary",onClick:()=>a.closeBank()},"Exit Terminal"))}return e("div",{className:"bank-atm-shell"},e("section",{className:"bank-atm-panel"},e("div",{className:"bank-panel-header"},e("div",null,e("span",{className:"bank-eyebrow"},"ATM"),e("h1",{className:"bank-title"},d)),e("span",{className:"bank-pill"},"Secure Terminal")),e("p",{className:"bank-panel-copy"},m),b))}}(),function(){const n=window.BankApp=window.BankApp||{},{h:e}=n.runtime,t=window.SharedUI.componentFns.WindowTitleBar,a=n.store,s=n.actions;n.componentFns=n.componentFns||{},n.componentFns.NoticeLayer=function(){const n=a.getNotice();return n.text?e("div",{className:"bank-notice-stack"},e("div",{className:"error"===n.type?"bank-notice is-error":"bank-notice is-success"},n.text)):null},n.components=n.components||{},n.components.App=function(){const n=a.getMode();return e("div",{className:"atm"===n?"bank-shell is-atm":"bank-shell"},"atm"===n?null:t({kicker:"FORGE Finance",title:"Global Banking Network",onClose:()=>s.closeBank(),closeLabel:"Close banking interface"}),e("div",{id:"bank-notice-root"}),"atm"===n?e("div",{id:"bank-atm-root"}):[e("div",{className:"bank-scroll-shell","data-preserve-scroll-id":"bank-page-scroll"},[e("div",{className:"bank-layout"},e("div",{id:"bank-sidebar-root"}),e("main",{className:"bank-main"},e("div",{className:"bank-page"},e("div",{id:"bank-page-header-root"}),e("p",{className:"bank-page-copy"},"Manage deposits, withdrawals, transfers, and earnings sweeps from the same shared financial console."),e("div",{className:"bank-page-divider"}),e("div",{className:"bank-page-body"},e("div",{id:"bank-summary-section-root"}),e("div",{id:"bank-action-sections-root"}),e("div",{id:"bank-support-section-root"}),e("div",{id:"bank-history-section-root"}))))),e("div",{id:"bank-footer-root"})])])}}(),function(){const n=window.ForgeWebUI,e=window.BankApp,t=[{id:"bank-notice-root",preserveScroll:!1,render:()=>e.componentFns.NoticeLayer()},{id:"bank-sidebar-root",preserveScroll:!1,render:()=>e.componentFns.BankSidebar()},{id:"bank-page-header-root",preserveScroll:!1,render:()=>e.componentFns.BankPageHeader()},{id:"bank-summary-section-root",preserveScroll:!1,render:()=>e.componentFns.BankSummarySection()},{id:"bank-action-sections-root",preserveScroll:!1,render:()=>e.componentFns.BankActionSections()},{id:"bank-support-section-root",preserveScroll:!1,render:()=>e.componentFns.BankSupportSection()},{id:"bank-history-section-root",preserveScroll:!1,render:()=>e.componentFns.BankHistorySection()},{id:"bank-atm-root",preserveScroll:!1,render:()=>e.componentFns.ATMView()},{id:"bank-footer-root",preserveScroll:!1,render:()=>e.componentFns.BankFooter()}];n.createApp({name:"bank",root:"#app",setup({root:a}){const s=function(){const e=new Map;return{sync:function(){t.forEach(t=>{const a=document.getElementById(t.id),s=e.get(t.id);if(!a)return void(s&&(s.handle.dispose(),e.delete(t.id)));if(s&&s.container===a)return;s&&s.handle.dispose();const i=n.mount(a,t.render,{preserveScroll:t.preserveScroll});e.set(t.id,{container:a,handle:i})})}}}();n.mount(a,()=>e.components.App(),{preserveScroll:!1}),e.bridge&&e.bridge.notifyReady(),n.effect(()=>{e.store.getMode(),requestAnimationFrame(()=>{s.sync()})})}}).start()}(); \ No newline at end of file diff --git a/arma/client/addons/bank/ui/src/bridge.js b/arma/client/addons/bank/ui/src/bridge.js index 425cfd6..fab579f 100644 --- a/arma/client/addons/bank/ui/src/bridge.js +++ b/arma/client/addons/bank/ui/src/bridge.js @@ -43,6 +43,9 @@ requestDepositEarnings(payload) { return bridge.send("bank::depositEarnings::request", payload); }, + requestRepayCreditLine(payload) { + return bridge.send("bank::repayCreditLine::request", payload); + }, requestRefresh() { return bridge.send("bank::refresh", {}); }, diff --git a/arma/client/addons/bank/ui/src/data.js b/arma/client/addons/bank/ui/src/data.js index 50e0df3..5398487 100644 --- a/arma/client/addons/bank/ui/src/data.js +++ b/arma/client/addons/bank/ui/src/data.js @@ -3,6 +3,13 @@ const defaultSession = { atmAuthorized: false, + creditLine: { + amountDue: 0, + approvedAmount: 0, + availableAmount: 0, + interestRate: 0.1, + outstandingPrincipal: 0, + }, mode: "bank", orgFunds: 0, orgName: "", diff --git a/arma/client/addons/bank/ui/src/pages/BankView.js b/arma/client/addons/bank/ui/src/pages/BankView.js index e3f8f0a..bdbdc87 100644 --- a/arma/client/addons/bank/ui/src/pages/BankView.js +++ b/arma/client/addons/bank/ui/src/pages/BankView.js @@ -89,6 +89,16 @@ "Reference value pulled from the organization treasury.", session.orgFunds > 0 ? "success" : "", ), + metricCard( + "Credit Due", + formatCurrency(session.creditLine?.amountDue || 0), + Number(session.creditLine?.amountDue || 0) > 0 + ? `Outstanding principal ${formatCurrency(session.creditLine?.outstandingPrincipal || 0)} at ${Math.round(Number(session.creditLine?.interestRate || 0) * 100)}% interest.` + : "No active credit repayment is currently due.", + Number(session.creditLine?.amountDue || 0) > 0 + ? "warning" + : "", + ), ), ); } @@ -238,6 +248,63 @@ ), ), ), + h( + "section", + { className: "bank-page-section" }, + h( + "div", + { className: "bank-section-header" }, + h( + "div", + null, + h("span", { className: "bank-eyebrow" }, "Credit"), + h( + "h2", + { className: "bank-section-title" }, + "Repay Org Credit", + ), + ), + ), + h( + "div", + { className: "bank-form-stack" }, + h( + "p", + { className: "bank-card-copy" }, + Number(session.creditLine?.amountDue || 0) > 0 + ? `Outstanding due ${formatCurrency(session.creditLine.amountDue || 0)}. Available reserved credit ${formatCurrency(session.creditLine.availableAmount || 0)}.` + : "No repayment is currently due on the assigned organization credit line.", + ), + h("input", { + id: "bank-credit-line-amount", + className: "bank-input", + type: "number", + min: "1", + placeholder: "Enter repayment amount", + }), + h( + "button", + { + type: "button", + className: "bank-btn bank-btn-primary", + disabled: + pending("repaycreditline") || + Number(session.creditLine?.amountDue || 0) <= 0, + onClick: () => { + const sent = actions.requestRepayCreditLine( + readInputValue("bank-credit-line-amount"), + ); + if (sent) { + clearInputValue("bank-credit-line-amount"); + } + }, + }, + pending("repaycreditline") + ? "Posting Repayment..." + : "Repay Credit Line", + ), + ), + ), ); } diff --git a/arma/client/addons/bank/ui/src/registry/events.js b/arma/client/addons/bank/ui/src/registry/events.js index 70c5413..7ccde92 100644 --- a/arma/client/addons/bank/ui/src/registry/events.js +++ b/arma/client/addons/bank/ui/src/registry/events.js @@ -130,6 +130,25 @@ return true; } + function requestRepayCreditLine(amountValue) { + const amount = normalizeAmount(amountValue); + const bridge = BankApp.bridge; + if (!bridge || typeof bridge.requestRepayCreditLine !== "function") { + showNotice("error", "Credit repayment bridge is unavailable."); + return false; + } + + store.startAction("repaycreditline"); + const sent = bridge.requestRepayCreditLine({ amount }); + if (!sent) { + store.finishAction(); + showNotice("error", "Credit repayment bridge is unavailable."); + return false; + } + + return true; + } + function appendPinDigit(digit) { const nextDigit = String(digit || "").trim(); if (!nextDigit) { @@ -259,6 +278,7 @@ requestAtmAmount, requestDeposit, requestDepositEarnings, + requestRepayCreditLine, requestTransfer, requestWithdraw, selectAtmView, diff --git a/arma/client/addons/org/ui/_site/org-ui.js b/arma/client/addons/org/ui/_site/org-ui.js index 58c7569..38b3707 100644 --- a/arma/client/addons/org/ui/_site/org-ui.js +++ b/arma/client/addons/org/ui/_site/org-ui.js @@ -1 +1 @@ -!function(){const e=window.ForgeWebUI,n=window.RegistryApp=window.RegistryApp||{},r=window.OrgPortal=window.OrgPortal||{};n.runtime=e,r.runtime=e,window.AppRuntime=e}(),function(){const e=window.RegistryApp=window.RegistryApp||{},{createSignal:n}=e.runtime;e.store=new class{constructor(){[this.getView,this.setView]=n("home"),[this.getIsAuthenticating,this.setIsAuthenticating]=n(!1),[this.getLoginError,this.setLoginError]=n(""),[this.getIsCreating,this.setIsCreating]=n(!1),[this.getCreateError,this.setCreateError]=n("")}startLogin(){this.setLoginError(""),this.setIsAuthenticating(!0)}startCreate(){this.setCreateError(""),this.setIsCreating(!0)}failLogin(e){this.setIsAuthenticating(!1),this.setLoginError(e||"Authentication failed.")}failCreate(e){this.setIsCreating(!1),this.setCreateError(e||"Organization registration failed.")}hydratePortal(e){const n=window.OrgPortal&&window.OrgPortal.data?window.OrgPortal.data:null,r=window.OrgPortal&&window.OrgPortal.store?window.OrgPortal.store:null,t=e&&e.portalData?e.portalData:null,a=e&&e.session?e.session:null;return!!(n&&"function"==typeof n.applyLoginPayload&&r&&"function"==typeof r.hydrateFromPayload&&t&&a)&&(n.applyLoginPayload(e),r.hydrateFromPayload(e),!0)}completeLogin(e){this.hydratePortal(e)?(this.setLoginError(""),this.setIsAuthenticating(!1),this.setView("portal")):this.failLogin("Login response was missing portal data.")}completeCreate(e){this.hydratePortal(e)?(this.setCreateError(""),this.setIsCreating(!1),this.setView("portal")):this.failCreate("Organization registration response was missing portal data.")}}}(),function(){const e=window.RegistryApp=window.RegistryApp||{},n=e.store,r=window.ForgeWebUI.createBridge({closeEvent:"org::close",globalName:"ForgeBridge",readyEvent:"org::ready"});function t(e,n){return r.send(e,n)}r.on("org::login::success",e=>{n.completeLogin(e)}),r.on("org::login::failure",e=>{n.failLogin(e.message||"Authentication failed.")}),r.on("org::create::success",e=>{n.completeCreate(e)}),r.on("org::create::failure",e=>{n.failCreate(e.message||"Organization registration failed.")}),r.on("org::sync",e=>{n&&"function"==typeof n.hydratePortal&&n.hydratePortal(e)}),r.on("org::credit::success",e=>{const n=window.OrgPortal;n&&n.store&&n.store.setModal(null),n&&n.actions&&n.actions.showTreasuryNotice("success",e.message||"Credit line assigned.")}),r.on("org::credit::failure",e=>{const n=window.OrgPortal;n&&n.actions&&n.actions.showTreasuryNotice("error",e.message||"Unable to assign credit line.")}),r.on("org::member::creditUpdated",e=>{const n=window.OrgPortal;n&&n.store&&n.store.setCreditLines(n=>{const r={amount:e.amount||0,member:e.memberName||"",uid:e.memberUid||""},t=n.findIndex(e=>e.uid===r.uid);return-1===t?[...n,r]:n.map((e,n)=>n===t?r:e)})}),r.on("org::disband::success",()=>{const e=window.OrgPortal;e&&e.store&&(e.store.setModal(null),e.store.setOrgDisbanded(!0))}),r.on("org::disband::failure",e=>{const n=window.OrgPortal;n&&n.store&&n.store.setModal(null),n&&n.actions&&n.actions.showTreasuryNotice("error",e.message||"Organization disbanding failed.")}),r.on("org::leave::success",e=>{const r=window.OrgPortal;r&&r.store&&r.store.setModal(null),n.failLogin(e.message||"You have left the organization."),n.setView("home")}),r.on("org::leave::failure",e=>{const n=window.OrgPortal;n&&n.store&&n.store.setModal(null),n&&n.actions&&n.actions.showTreasuryNotice("error",e.message||"Unable to leave the organization.")}),r.on("org::portal::revoked",e=>{const r=window.OrgPortal;r&&r.store&&r.store.setModal(null),n.failLogin(e.message||"Organization access is no longer available."),n.setView("home")}),e.bridge={close:r.close,ready:r.ready,receive:r.receive,requestLogin:function(e){n.startLogin(),t("org::login::request",e)||n.failLogin("Arma login bridge is unavailable.")},requestCreateOrg:function(e){n.startCreate(),t("org::create::request",e)||n.failCreate("Arma registration bridge is unavailable.")},requestDisbandOrg:function(){if(t("org::disband::request",{}))return;const e=window.OrgPortal;e&&e.actions&&e.actions.showTreasuryNotice("error","Arma disband bridge is unavailable.")},requestLeaveOrg:function(){if(t("org::leave::request",{}))return;const e=window.OrgPortal;e&&e.actions&&e.actions.showTreasuryNotice("error","Arma leave bridge is unavailable.")},requestCreditLine:function(e){if(t("org::credit::request",e))return!0;const n=window.OrgPortal;return n&&n.actions&&n.actions.showTreasuryNotice("error","Arma credit line bridge is unavailable."),!1},sendEvent:t}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},n={type:"Organization",status:"Operational",headquarters:"ArmA Verse"};function r(e){return JSON.parse(JSON.stringify(e))}function t(e,n){Object.keys(e).forEach(n=>delete e[n]),Object.assign(e,r(n))}function a(e,n){e.splice(0,e.length,...r(n))}function o(e){if(e&&"object"==typeof e&&!Array.isArray(e))return e;if(Array.isArray(e)){if(e.every(e=>Array.isArray(e)&&e.length>=2&&"string"==typeof e[0]))return Object.fromEntries(e)}if("string"==typeof e&&""!==e.trim())try{return o(JSON.parse(e))}catch(n){return e}return e}function i(e){return(Array.isArray(e)?e:e&&"object"==typeof e?Object.values(e):[]).map(o).filter(Boolean)}e.data={portalData:{org:Object.assign({name:"",tag:"",owner:"",ownerUid:"",isDefault:!1},n),funds:0,reputation:0,creditLines:[],members:[],fleet:[],assets:[],activity:[],roadmap:[{name:"Contracts Board",status:"Planned",detail:"Track payouts, assignments, and claim approvals."},{name:"Diplomacy",status:"Future Review",detail:"Possible future module pending a full design and scope review."},{name:"Logistics Queue",status:"Future Review",detail:"Possible future module pending a full design and scope review."},{name:"Permissions",status:"Future Review",detail:"Possible future module pending a full design and scope review."}]},session:{actorName:"",actorUid:"",role:"",ceo:!1},applyLoginPayload(e){t(this.portalData.org,Object.assign({},e.portalData.org||{},n)),this.portalData.funds=e.portalData.funds||0,this.portalData.reputation=e.portalData.reputation||0,a(this.portalData.creditLines,i(e.portalData.creditLines)),a(this.portalData.members,i(e.portalData.members)),a(this.portalData.fleet,i(e.portalData.fleet)),a(this.portalData.assets,i(e.portalData.assets)),a(this.portalData.activity,i(e.portalData.activity)),a(this.portalData.roadmap,i(e.portalData.roadmap)),t(this.session,e.session||{})}}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{createSignal:n}=window.RegistryApp.runtime,{portalData:r}=e.data;function t(e){if(e&&"object"==typeof e&&!Array.isArray(e))return e;if(Array.isArray(e)){if(e.every(e=>Array.isArray(e)&&e.length>=2&&"string"==typeof e[0]))return Object.fromEntries(e)}if("string"==typeof e&&""!==e.trim())try{return t(JSON.parse(e))}catch(n){return e}return e}function a(e){return(Array.isArray(e)?e:e&&"object"==typeof e?Object.values(e):[]).map(t).filter(Boolean)}e.store=new class{constructor(){[this.getFunds,this.setFunds]=n(r.funds),[this.getReputation,this.setReputation]=n(r.reputation),[this.getMembers,this.setMembers]=n([...r.members]),[this.getCreditLines,this.setCreditLines]=n([...r.creditLines]),[this.getFleet,this.setFleet]=n([...r.fleet]),[this.getAssets,this.setAssets]=n([...r.assets]),[this.getActivity,this.setActivity]=n([...r.activity]),[this.getTreasuryNotice,this.setTreasuryNotice]=n({type:"",text:""}),[this.getModal,this.setModal]=n(null),[this.getOrgDisbanded,this.setOrgDisbanded]=n(!1)}hydrateFromPayload(e){const n=e.portalData||{};this.setFunds(n.funds||0),this.setReputation(n.reputation||0),this.setMembers([...a(n.members)]),this.setCreditLines([...a(n.creditLines)]),this.setFleet([...a(n.fleet)]),this.setAssets([...a(n.assets)]),this.setActivity([...a(n.activity)])}}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{portalData:n,session:r}=e.data;e.getters=new class{formatCurrency(e){return"$"+Number(e||0).toLocaleString()}formatVehicleType(e){return e?e.charAt(0).toUpperCase()+e.slice(1):""}formatAssetType(e){return e?e.charAt(0).toUpperCase()+e.slice(1):""}formatDisplayName(e){return e?String(e).trim().split(/\s+/).map(e=>e?e.charAt(0).toUpperCase()+e.slice(1).toLowerCase():"").join(" "):""}getAssetReadiness(){const r=e.store?e.store.getFleet():n.fleet;if(0===r.length)return null;const t=r.reduce((e,n)=>e+(100-parseInt(n.damage,10)),0);return Math.round(t/r.length)}getNormalizedRole(){return String(r.role||"").trim().toUpperCase()}isDefaultOrg(){return!0===n.org.isDefault||"DEFAULT"===String(n.org.tag||"").trim().toUpperCase()}isOrgOwner(){const e=String(n.org.ownerUid||n.org.owner||"").trim().toLowerCase(),t=String(r.actorUid||"").trim().toLowerCase();return e&&t?t===e:String(r.actorName||"").trim().toLowerCase()===String(n.org.owner||"").trim().toLowerCase()}isSessionCeo(){return!0===r.ceo}isOrgLeaderOrCeo(){return this.isOrgOwner()||"LEADER"===this.getNormalizedRole()||this.isDefaultOrg()&&this.isSessionCeo()}canManageMembers(){return this.isOrgLeaderOrCeo()}canManageTreasury(){return this.isOrgLeaderOrCeo()}canDisbandOrg(){return this.isOrgOwner()&&!this.isDefaultOrg()}canLeaveOrg(){return!this.isDefaultOrg()&&!this.isOrgOwner()}getMemberName(e){return String(e&&"object"==typeof e?e.name||"":e||"")}getMemberUid(e){return e&&"object"==typeof e?String(e.uid||""):""}isOwnerMember(e){return this.getMemberName(e).trim().toLowerCase()===String(n.org.owner||"").trim().toLowerCase()}isCurrentMember(e){const n=this.getMemberUid(e).trim().toLowerCase(),t=String(r.actorUid||"").trim().toLowerCase();return n&&t?n===t:this.getMemberName(e).trim().toLowerCase()===String(r.actorName||"").trim().toLowerCase()}isProtectedMember(e){return this.isOwnerMember(e)||this.isCurrentMember(e)}}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{portalData:n}=e.data,r=e.store,t=e.getters,a=window.RegistryApp.store;e.actions=new class{constructor(){this.treasuryNoticeTimer=null}showTreasuryNotice(e,n){r.setTreasuryNotice({type:e,text:n}),this.treasuryNoticeTimer&&clearTimeout(this.treasuryNoticeTimer),this.treasuryNoticeTimer=setTimeout(()=>{r.setTreasuryNotice({type:"",text:""}),this.treasuryNoticeTimer=null},3500)}parseAmount(e){const n=Number(e);return Number.isFinite(n)?Math.round(n):0}getInputValue(e){const n=document.getElementById(e);return n?n.value:""}closePortal(){const e=window.RegistryApp?window.RegistryApp.bridge:null;e&&"function"==typeof e.close?e.close({}):a&&a.setView("home")}openModal(e){"payroll"!==e&&"transfer"!==e&&"credit"!==e||t.canManageTreasury()?("disband"!==e||t.canDisbandOrg())&&("leave"!==e||t.canLeaveOrg())&&r.setModal({type:e}):this.showTreasuryNotice("error","Only the organization leader or CEO can manage treasury actions.")}closeModal(){r.setModal(null)}removeMember(e){if(!t.canManageMembers())return!1;if(t.isProtectedMember(e))return!1;const n=t.getMemberUid(e),a=t.getMemberName(e);return r.setMembers(e=>e.filter(e=>n?e.uid!==n:e.name!==a)),r.setCreditLines(e=>e.filter(e=>n?e.uid!==n:e.member!==a)),!0}disbandOrganization(){if(!t.canDisbandOrg())return!1;const e=window.RegistryApp?window.RegistryApp.bridge:null;return e&&"function"==typeof e.requestDisbandOrg?(this.closeModal(),e.requestDisbandOrg(),!0):(this.showTreasuryNotice("error","Disband bridge is unavailable."),!1)}leaveOrganization(){if(!t.canLeaveOrg())return!1;const e=window.RegistryApp?window.RegistryApp.bridge:null;return e&&"function"==typeof e.requestLeaveOrg?(this.closeModal(),e.requestLeaveOrg(),!0):(this.showTreasuryNotice("error","Leave bridge is unavailable."),!1)}runPayroll(e){if(!t.canManageTreasury())return this.showTreasuryNotice("error","Only the organization leader or CEO can manage treasury actions."),!1;const n=r.getMembers(),a=r.getFunds();if(0===n.length)return this.showTreasuryNotice("error","No members available for payroll."),!1;if(e<=0)return this.showTreasuryNotice("error","Enter a valid payroll amount."),!1;const o=e*n.length;return o>a?(this.showTreasuryNotice("error","Insufficient org funds for payroll."),!1):(r.setFunds(a-o),this.showTreasuryNotice("success",`Payroll sent to ${n.length} members for ${t.formatCurrency(o)}.`),!0)}sendFundsToMember(e,n){if(!t.canManageTreasury())return this.showTreasuryNotice("error","Only the organization leader or CEO can manage treasury actions."),!1;const a=r.getFunds();return e?n<=0?(this.showTreasuryNotice("error","Enter a valid transfer amount."),!1):n>a?(this.showTreasuryNotice("error","Insufficient org funds for this transfer."),!1):(r.setFunds(a-n),this.showTreasuryNotice("success",`${t.formatCurrency(n)} sent to ${e}.`),!0):(this.showTreasuryNotice("error","Select a member to receive funds."),!1)}grantCreditLine(e,n){if(!t.canManageTreasury())return this.showTreasuryNotice("error","Only the organization leader or CEO can manage treasury actions."),!1;if(!e)return this.showTreasuryNotice("error","Select a member for the credit line."),!1;if(n<=0)return this.showTreasuryNotice("error","Enter a valid credit line amount."),!1;const a=r.getMembers().find(n=>t.getMemberUid(n)===e),o=a?t.getMemberName(a):"";if(!o)return this.showTreasuryNotice("error","Selected member was not found in the organization roster."),!1;const i=window.RegistryApp?window.RegistryApp.bridge:null;return i&&"function"==typeof i.requestCreditLine?i.requestCreditLine({memberUid:e,memberName:o,amount:n}):(this.showTreasuryNotice("error","Credit line bridge is unavailable."),!1)}}}(),function(){const e=window.SharedUI=window.SharedUI||{},n=window.RegistryApp=window.RegistryApp||{},{h:r,ensureScopedStyle:t}=n.runtime,a="data-ui-navbar",o=`[${a}]`,i=`\n${o} {\n background: var(--bg-surface);\n border-bottom: 1px solid var(--border);\n box-shadow: var(--shadow);\n}\n\n${o} .app-navbar-inner {\n display: flex;\n justify-content: space-between;\n align-items: center;\n max-width: 1200px;\n width: 100%;\n margin: 0 auto;\n padding: 1rem 2rem;\n box-sizing: border-box;\n}\n\n${o} .app-navbar-brand {\n display: flex;\n flex-direction: column;\n gap: 0.125rem;\n}\n\n${o} .app-navbar-kicker {\n font-size: 0.7rem;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--text-muted);\n font-weight: 600;\n}\n\n${o} .app-navbar-title {\n font-size: 1.25rem;\n font-weight: 700;\n color: var(--primary-hover);\n letter-spacing: -0.025em;\n}\n\n${o} .app-navbar-actions {\n display: flex;\n align-items: center;\n gap: 1.5rem;\n}\n\n${o} .app-navbar-view {\n font-size: 0.8rem;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: var(--text-muted);\n font-weight: 600;\n}\n\n${o} .app-close-btn {\n background: transparent;\n color: var(--text-muted);\n border: 1px solid var(--border);\n padding: 0.5rem 1rem;\n font-size: 0.85rem;\n}\n\n${o} .app-close-btn:hover {\n background: var(--bg-surface-hover);\n color: var(--primary-hover);\n border-color: var(--primary);\n transform: none;\n box-shadow: none;\n}\n\n@media (max-width: 960px) {\n ${o} .app-navbar-inner {\n flex-direction: column;\n align-items: flex-start;\n padding: 1rem 1.5rem;\n }\n\n ${o} .app-navbar-actions {\n align-items: flex-start;\n }\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.Navbar=function({kicker:e="ORBIS",title:n="",viewLabel:o="",actionLabel:s="",onAction:l=null}){return t("shared-navbar",i),r("nav",{className:"app-navbar",[a]:""},r("div",{className:"app-navbar-inner"},r("div",{className:"app-navbar-brand"},r("span",{className:"app-navbar-kicker"},e),r("span",{className:"app-navbar-title"},n)),r("div",{className:"app-navbar-actions"},r("span",{className:"app-navbar-view"},o),s&&"function"==typeof l?r("button",{type:"button",className:"app-close-btn",onClick:l},s):null)))}}(),function(){const e=window.SharedUI=window.SharedUI||{},n=window.RegistryApp=window.RegistryApp||{},{h:r}=n.runtime;e.componentFns=e.componentFns||{},e.componentFns.Header=function({title:e,subtitle:n="Organization Registration & Management Portal",onTitleClick:t=null}){return r("div",{className:"header"},r("h1",{style:{cursor:t?"pointer":"default"},onClick:t},e),r("p",null,n))}}(),function(){const e=window.SharedUI=window.SharedUI||{},n=window.OrgPortal=window.OrgPortal||{},{h:r}=n.runtime;e.componentFns=e.componentFns||{},e.componentFns.Hero=function({className:e="",kicker:n="",title:t="",subtitle:a="",meta:o=""}){const i=["card org-panel org-span-12 org-page-header",e].filter(Boolean).join(" ");return r("section",{className:i},r("div",{className:"org-page-heading"},r("span",{className:"org-page-kicker"},n),r("h1",{className:"org-page-title"},t),r("p",{className:"org-page-subtitle"},a),r("span",{className:"org-page-meta"},o)))}}(),function(){const e=window.SharedUI=window.SharedUI||{},n=window.RegistryApp=window.RegistryApp||{},{h:r}=n.runtime;e.componentFns=e.componentFns||{},e.componentFns.Footer=function({sections:e=[]}){return r("div",{className:"footer"},r("div",{className:"wrapper"},...e.map(e=>r("div",null,r("h3",null,e.title),r("ul",{style:{listStyleType:"none",padding:0}},...(e.items||[]).map(e=>r("li",null,e)))))))}}(),function(){const e=window.SharedUI=window.SharedUI||{},n=window.RegistryApp=window.RegistryApp||{},{h:r,ensureScopedStyle:t}=n.runtime,a="data-ui-modal",o=`[${a}]`,i=`\n${o} {\n position: fixed;\n inset: 0;\n background: rgb(15 23 42 / 0.38);\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 1.5rem;\n z-index: 20;\n}\n\n${o} .app-modal-card {\n width: min(100%, 30rem);\n margin-bottom: 0;\n text-align: left;\n}\n\n${o} .app-modal-head {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 1rem;\n margin-bottom: 1rem;\n}\n\n${o} .app-modal-title {\n margin: 0;\n color: var(--primary-hover);\n font-size: 1.45rem;\n}\n\n${o} .app-modal-close {\n width: 2.25rem;\n height: 2.25rem;\n padding: 0;\n background: var(--bg-surface);\n color: var(--text-main);\n border: 1px solid var(--border);\n box-shadow: none;\n transform: none;\n}\n\n${o} .app-modal-close:hover {\n background: var(--bg-surface-hover);\n color: var(--text-main);\n box-shadow: none;\n transform: none;\n}\n\n${o} .app-modal-form {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n}\n\n${o} .app-modal-form label {\n display: block;\n margin-bottom: 0.5rem;\n color: var(--text-muted);\n font-weight: 500;\n font-size: 0.9rem;\n}\n\n${o} .app-modal-form input,\n${o} .app-modal-form select {\n width: 100%;\n padding: 0.75rem;\n border-radius: var(--radius);\n border: 1px solid var(--border);\n background: var(--bg-app);\n color: var(--text-main);\n font-family: inherit;\n font-size: 1rem;\n box-sizing: border-box;\n transition: border-color 0.2s, box-shadow 0.2s;\n}\n\n${o} .app-modal-form input:focus,\n${o} .app-modal-form select:focus {\n outline: none;\n border-color: var(--primary);\n box-shadow: 0 0 0 2px rgb(71 85 105 / 0.12);\n}\n\n${o} .app-modal-form input:disabled,\n${o} .app-modal-form select:disabled {\n background: #f1f5f9;\n color: var(--text-muted);\n cursor: not-allowed;\n}\n\n${o} .app-modal-actions {\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-end;\n gap: 0.75rem;\n margin-top: 0.5rem;\n}\n\n${o} .app-modal-actions button + button,\n${o} .app-modal-danger-actions button + button {\n margin-left: 0;\n}\n\n${o} .app-modal-danger {\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n gap: 1rem;\n padding: 1rem;\n border: 1px solid #fecaca;\n border-radius: var(--radius);\n background: #fff1f2;\n align-items: flex-start;\n}\n\n${o} .app-modal-danger p {\n margin: 0;\n color: var(--text-main);\n}\n\n${o} .app-modal-danger-actions {\n display: flex;\n flex-wrap: wrap;\n gap: 0.75rem;\n}\n\n@media (max-width: 960px) {\n ${o} .app-modal-head,\n ${o} .app-modal-danger {\n flex-direction: column;\n align-items: flex-start;\n }\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.Modal=function({title:e="",body:n=null,onClose:o=null}){return t("shared-modal",i),r("div",{className:"app-modal-backdrop",[a]:"",onClick:e=>{e.target===e.currentTarget&&o&&o()}},r("div",{className:"card app-modal-card"},r("div",{className:"app-modal-head"},r("div",null,r("h2",{className:"app-modal-title"},e)),r("button",{type:"button",className:"app-modal-close",onClick:o,"aria-label":"Close dialog"},"x")),n))}}(),function(){const e=window.SharedUI=window.SharedUI||{},n=window.RegistryApp=window.RegistryApp||{},{h:r,ensureScopedStyle:t}=n.runtime,a="data-ui-panel-card",o=`[${a}]`,i=`\n${o} {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n}\n\n${o} .org-panel-head {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 1rem;\n margin-bottom: 1.5rem;\n}\n\n${o} .org-panel-body {\n display: flex;\n flex: 1 1 auto;\n flex-direction: column;\n min-height: 0;\n}\n\n${o} .org-eyebrow {\n font-size: 0.8rem;\n font-weight: 700;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-muted);\n margin-bottom: 0.4rem;\n}\n\n${o} .org-panel-title {\n margin: 0;\n color: var(--primary-hover);\n font-size: 1.45rem;\n}\n\n${o} .org-panel-subtitle {\n margin: 0.35rem 0 0;\n color: var(--text-muted);\n font-size: 0.95rem;\n}\n\n@media (max-width: 960px) {\n ${o} .org-panel-head {\n flex-direction: column;\n align-items: flex-start;\n }\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.PanelCard=function({className:e="",eyebrow:n="",title:o="",subtitle:s="",headerExtras:l=null,body:d=null,rootProps:c={}}){const m=["card org-panel",e].filter(Boolean).join(" ");return t("shared-panel-card",i),r("section",{className:m,[a]:"",...c},r("div",{className:"org-panel-head"},r("div",null,n?r("div",{className:"org-eyebrow"},n):null,r("h2",{className:"org-panel-title"},o),s?r("p",{className:"org-panel-subtitle"},s):null),l),r("div",{className:"org-panel-body"},d))}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{h:n,ensureScopedStyle:r}=e.runtime,t="data-ui-metric-card",a=`[${t}]`,o=`\n${a} {\n display: flex;\n flex-direction: column;\n gap: 0.45rem;\n padding: 1rem;\n border-radius: var(--radius);\n border: 1px solid var(--border);\n background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);\n}\n\n${a}:nth-child(4n + 2),\n${a}:nth-child(4n + 3) {\n background: linear-gradient(180deg, rgb(248 250 252) 0%, rgb(226 232 240) 100%);\n border-color: rgb(100 116 139 / 0.35);\n box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.6);\n}\n\n${a} .org-metric-label {\n font-size: 0.76rem;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--text-muted);\n}\n\n${a} .org-metric-value {\n font-size: 1.8rem;\n color: var(--primary-hover);\n line-height: 1.1;\n}\n\n${a}:nth-child(4n + 2) .org-metric-value,\n${a}:nth-child(4n + 3) .org-metric-value {\n color: #334155;\n}\n\n${a} .org-metric-note {\n color: var(--text-muted);\n font-size: 0.9rem;\n}\n\n@media (max-width: 960px) {\n ${a}:nth-child(4n + 3) {\n background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);\n border-color: var(--border);\n box-shadow: none;\n }\n\n ${a}:nth-child(4n + 3) .org-metric-value {\n color: var(--primary-hover);\n }\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.MetricCard=function(e,a,i){return r("portal-metric-card",o),n("div",{className:"org-metric-card",[t]:""},n("span",{className:"org-metric-label"},e),n("strong",{className:"org-metric-value"},a),n("span",{className:"org-metric-note"},i))}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{h:n,ensureScopedStyle:r}=e.runtime,t="data-ui-simple-stat",a=`[${t}]`,o=`\n${a} {\n display: flex;\n flex-direction: column;\n gap: 0.2rem;\n min-width: 90px;\n}\n\n${a} .org-simple-label {\n font-size: 0.72rem;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--text-muted);\n}\n\n${a} .org-simple-value {\n font-size: 0.95rem;\n color: var(--text-main);\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.SimpleStat=function(e,a){return r("portal-simple-stat",o),n("div",{className:"org-simple-stat",[t]:""},n("span",{className:"org-simple-label"},e),n("strong",{className:"org-simple-value"},a))}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{h:n,ensureScopedStyle:r}=e.runtime,{portalData:t}=e.data,a=e.store,o=e.getters,i="data-ui-overview-card",s=`[${i}]`,l=`\n${s} .org-hero-grid {\n display: grid;\n grid-template-columns: 1.3fr 1fr;\n gap: 1.5rem;\n align-items: start;\n}\n\n${s} .org-summary {\n margin: 0;\n font-size: 1.05rem;\n color: var(--text-main);\n}\n\n${s} .org-meta-row {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 1rem;\n margin-top: 1.5rem;\n}\n\n${s} .org-meta-item {\n display: flex;\n flex-direction: column;\n gap: 0.4rem;\n padding: 1rem;\n border: 1px solid var(--border);\n border-radius: var(--radius);\n background: #f8fafc;\n}\n\n${s} .org-meta-item:nth-child(even) {\n background: linear-gradient(180deg, rgb(241 245 249) 0%, rgb(226 232 240) 100%);\n border-color: rgb(148 163 184 / 0.45);\n}\n\n${s} .org-meta-label {\n font-size: 0.76rem;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--text-muted);\n}\n\n${s} .org-meta-value {\n font-size: 1rem;\n font-weight: 600;\n color: var(--primary-hover);\n}\n\n${s} .org-metric-grid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 1rem;\n}\n\n@media (max-width: 960px) {\n ${s} .org-hero-grid,\n ${s} .org-meta-row,\n ${s} .org-metric-grid {\n grid-template-columns: 1fr;\n }\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.OverviewCard=function(){const s=e.componentFns.MetricCard,d=window.SharedUI.componentFns.PanelCard,c=o.getAssetReadiness(),m=t.org.headquarters||"ArmA Verse",g=a.getAssets().length,p=a.getFleet().length,u=a.getFunds(),f=a.getMembers().length,b=a.getReputation();return r("portal-overview-card",l),d({className:"org-span-12",eyebrow:t.org.tag,title:"Organization Overview",rootProps:{[i]:""},body:n("div",{className:"org-hero-grid"},n("div",{className:"org-hero-copy"},n("p",{className:"org-summary"},t.org.type," operating from ",m,". Treasury, fleet status, inventory, and roster management are surfaced here first."),n("div",{className:"org-meta-row"},n("div",{className:"org-meta-item"},n("span",{className:"org-meta-label"},"Director"),n("span",{className:"org-meta-value"},o.formatDisplayName(t.org.owner))),n("div",{className:"org-meta-item"},n("span",{className:"org-meta-label"},"Active Members"),n("span",{className:"org-meta-value"},`${f} total`)),n("div",{className:"org-meta-item"},n("span",{className:"org-meta-label"},"Fleet Readiness"),n("span",{className:"org-meta-value"},null===c?"N/A":`${c}%`)))),n("div",{className:"org-metric-grid"},s("Org Funds",o.formatCurrency(u),"Organization treasury balance"),s("Reputation",b,"Organization standing"),s("Asset Lines",g,"Tracked supply and equipment entries"),s("Fleet Vehicles",p,"Tracked air, ground, and naval vehicles")))})}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{h:n,ensureScopedStyle:r}=e.runtime,{portalData:t}=e.data,a=e.getters,o="data-ui-fleet-card",i=`[${o}]`,s=`\n${i} .org-simple-list {\n display: flex;\n flex-direction: column;\n flex: 1;\n gap: 0.85rem;\n min-height: 0;\n overflow: auto;\n padding-right: 0.35rem;\n scrollbar-width: thin;\n scrollbar-color: #94a3b8 #e2e8f0;\n}\n\n${i} .org-simple-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 1rem;\n padding: 1rem;\n border: 1px solid var(--border);\n border-radius: var(--radius);\n background: #f8fafc;\n}\n\n${i} .org-simple-row:nth-child(even) {\n background: linear-gradient(180deg, rgb(248 250 252) 0%, rgb(241 245 249) 100%);\n border-color: rgb(148 163 184 / 0.45);\n}\n\n${i} .org-simple-name {\n color: var(--primary-hover);\n}\n\n${i} .org-simple-meta {\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-end;\n gap: 1rem;\n}\n\n@media (max-width: 960px) {\n ${i} .org-simple-row {\n flex-direction: column;\n align-items: flex-start;\n }\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.FleetCard=function(){const t=window.SharedUI.componentFns.PanelCard,i=e.componentFns.SimpleStat,l=e.store.getFleet();return r("portal-fleet-card",s),t({className:"org-scroll-panel org-span-7",title:"Fleet",subtitle:"Individual vehicles with type, status, and overall damage.",rootProps:{[o]:""},body:n("div",{className:"org-simple-list"},...l.map(e=>n("article",{className:"org-simple-row"},n("strong",{className:"org-simple-name"},e.name),n("div",{className:"org-simple-meta"},i("Type",a.formatVehicleType(e.type)),i("Status",e.status),i("Damage",e.damage)))))})}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{h:n,ensureScopedStyle:r,createSignal:t}=e.runtime,{portalData:a}=e.data,o=e.store,i=e.getters,s=e.actions,l="data-ui-treasury-card",d=`[${l}]`,[c,m]=t("overview"),[g,p]=t(!1),u=`\n${d} .org-treasury-menu {\n position: relative;\n}\n\n${d} .org-menu-btn {\n width: 2.75rem;\n height: 2.75rem;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n border: 1px solid var(--border);\n background: #f8fafc;\n color: var(--text-muted);\n}\n\n${d} .org-menu-btn:hover {\n color: var(--primary-hover);\n border-color: rgb(148 163 184 / 0.65);\n}\n\n${d} .org-menu-btn svg {\n width: 1.1rem;\n height: 1.1rem;\n}\n\n${d} .org-menu-dropdown {\n position: absolute;\n top: calc(100% + 0.6rem);\n right: 0;\n min-width: 10.5rem;\n padding: 0.45rem;\n border: 1px solid var(--border);\n border-radius: var(--radius);\n background: #fff;\n box-shadow: 0 12px 28px rgb(15 23 42 / 0.12);\n display: flex;\n flex-direction: column;\n gap: 0.35rem;\n z-index: 5;\n}\n\n${d} .org-menu-option + .org-menu-option {\n margin-left: 0;\n}\n\n${d} .org-menu-option {\n width: 100%;\n justify-content: flex-start;\n background: transparent;\n color: var(--text-main);\n border: 1px solid transparent;\n}\n\n${d} .org-menu-option:hover {\n background: #f8fafc;\n border-color: rgb(148 163 184 / 0.35);\n}\n\n${d} .org-menu-option.is-active {\n background: rgb(226 232 240 / 0.7);\n color: var(--primary-hover);\n border-color: rgb(148 163 184 / 0.35);\n}\n\n${d} .org-finance-meta {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 1rem;\n margin-bottom: 1.5rem;\n}\n\n${d} .org-finance-meta > div {\n padding: 1rem;\n border: 1px solid var(--border);\n border-radius: var(--radius);\n background: #f8fafc;\n display: flex;\n flex-direction: column;\n gap: 0.4rem;\n}\n\n${d} .org-meta-label {\n font-size: 0.76rem;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--text-muted);\n}\n\n${d} .org-action-grid {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n margin-bottom: 1rem;\n}\n\n${d} .org-action-grid button + button {\n margin-left: 0;\n}\n\n${d} .org-action-grid button {\n width: 100%;\n}\n\n${d} .org-access-note {\n margin: 0 0 1rem;\n color: var(--text-muted);\n font-size: 0.95rem;\n}\n\n${d} .org-credit-summary {\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n padding: 0.85rem 1rem;\n border: 1px solid var(--border);\n border-radius: var(--radius);\n background: #f8fafc;\n}\n\n${d} .org-credit-summary strong {\n font-size: 1rem;\n}\n\n${d} .org-credit-summary span:last-child {\n font-size: 0.92rem;\n line-height: 1.45;\n}\n\n${d} .org-credit-lines-list {\n display: flex;\n flex-direction: column;\n gap: 0.85rem;\n}\n\n${d} .org-treasury-body {\n display: flex;\n flex: 1;\n flex-direction: column;\n gap: 1rem;\n min-height: 0;\n overflow: auto;\n padding-right: 0.35rem;\n scrollbar-width: thin;\n scrollbar-color: #94a3b8 #e2e8f0;\n}\n\n${d} .org-credit-line-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 1rem;\n padding: 1rem;\n border: 1px solid var(--border);\n border-radius: var(--radius);\n background: #f8fafc;\n}\n\n${d} .org-credit-line-row:nth-child(even) {\n background: linear-gradient(180deg, rgb(248 250 252) 0%, rgb(241 245 249) 100%);\n border-color: rgb(148 163 184 / 0.45);\n}\n\n${d} .org-credit-line-member {\n display: flex;\n flex-direction: column;\n gap: 0.3rem;\n}\n\n${d} .org-credit-line-label {\n font-size: 0.76rem;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--text-muted);\n}\n\n${d} .org-credit-line-empty {\n padding: 1rem;\n border: 1px solid var(--border);\n border-radius: var(--radius);\n background: #f8fafc;\n color: var(--text-muted);\n}\n\n@media (max-width: 960px) {\n ${d} .org-finance-meta {\n grid-template-columns: 1fr;\n }\n\n ${d} .org-credit-line-row {\n flex-direction: column;\n align-items: flex-start;\n }\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.TreasuryCard=function(){const e=window.SharedUI.componentFns.PanelCard,t=o.getCreditLines(),a=o.getReputation(),d=i.canManageTreasury(),f=c(),b=g(),w=1===t.length?"1 active credit line":`${t.length} active credit lines`;return r("portal-treasury-card",u),e({className:"org-scroll-panel org-span-5",title:"Treasury",subtitle:"Organization funds, reputation and payouts.",headerExtras:n("div",{className:"org-treasury-menu"},n("button",{type:"button",className:"org-menu-btn",title:"Treasury views","aria-label":"Treasury views",onClick:()=>p(e=>!e)},n("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round","aria-hidden":"true"},n("line",{x1:"4",y1:"7",x2:"20",y2:"7"}),n("line",{x1:"4",y1:"12",x2:"20",y2:"12"}),n("line",{x1:"4",y1:"17",x2:"20",y2:"17"}))),b?n("div",{className:"org-menu-dropdown"},n("button",{type:"button",className:"overview"===f?"org-menu-option is-active":"org-menu-option",onClick:()=>{m("overview"),p(!1)}},"Overview"),n("button",{type:"button",className:"credit"===f?"org-menu-option is-active":"org-menu-option",onClick:()=>{m("credit"),p(!1)}},"Credit Lines")):null),rootProps:{[l]:""},body:n("div",{className:"org-treasury-body"},"credit"===f?t.length>0?n("div",{className:"org-credit-lines-list"},...t.map(e=>n("article",{className:"org-credit-line-row"},n("div",{className:"org-credit-line-member"},n("span",{className:"org-credit-line-label"},"Member"),n("strong",null,e.member)),n("div",{className:"org-credit-line-member"},n("span",{className:"org-credit-line-label"},"Amount"),n("strong",null,i.formatCurrency(e.amount)))))):n("div",{className:"org-credit-line-empty"},"No active credit lines."):n("div",null,n("div",{className:"org-finance-meta"},n("div",null,n("span",{className:"org-meta-label"},"Funds"),n("strong",null,i.formatCurrency(o.getFunds()))),n("div",null,n("span",{className:"org-meta-label"},"Reputation"),n("strong",null,`${a}`))),d?n("div",{className:"org-action-grid"},n("button",{type:"button",onClick:()=>s.openModal("payroll")},"Run Payroll"),n("button",{type:"button",className:"org-secondary-btn",onClick:()=>s.openModal("transfer")},"Send Funds"),n("button",{type:"button",className:"org-secondary-btn",onClick:()=>s.openModal("credit")},"Credit Line")):n("p",{className:"org-access-note"},"Only the organization leader or CEO can manage treasury actions."),n("div",{className:"org-credit-summary"},n("span",{className:"org-meta-label"},"Credit Line Status"),n("strong",null,w),n("span",null,t.length>0?"Open the Credit Lines tab to review assigned members and amounts.":"Assign a credit line to create the first approved member limit."))))})}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{h:n,ensureScopedStyle:r}=e.runtime,{portalData:t}=e.data,a=e.getters,o="data-ui-assets-card",i=`[${o}]`,s=`\n${i} .org-simple-list {\n display: flex;\n flex-direction: column;\n flex: 1;\n gap: 0.85rem;\n min-height: 0;\n overflow: auto;\n padding-right: 0.35rem;\n scrollbar-width: thin;\n scrollbar-color: #94a3b8 #e2e8f0;\n}\n\n${i} .org-simple-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 1rem;\n padding: 1rem;\n border: 1px solid var(--border);\n border-radius: var(--radius);\n background: #f8fafc;\n}\n\n${i} .org-simple-row:nth-child(even) {\n background: linear-gradient(180deg, rgb(248 250 252) 0%, rgb(241 245 249) 100%);\n border-color: rgb(148 163 184 / 0.45);\n}\n\n${i} .org-simple-name {\n color: var(--primary-hover);\n}\n\n${i} .org-simple-meta {\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-end;\n gap: 1rem;\n}\n\n@media (max-width: 960px) {\n ${i} .org-simple-row {\n flex-direction: column;\n align-items: flex-start;\n }\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.AssetsCard=function(){const t=window.SharedUI.componentFns.PanelCard,i=e.componentFns.SimpleStat,l=e.store.getAssets();return r("portal-assets-card",s),t({className:"org-scroll-panel org-span-7",title:"Assets",subtitle:"Inventory supplies and equipment with quantity totals.",rootProps:{[o]:""},body:n("div",{className:"org-simple-list"},...l.map(e=>n("article",{className:"org-simple-row"},n("strong",{className:"org-simple-name"},e.name),n("div",{className:"org-simple-meta"},i("Type",a.formatAssetType(e.type)),i("Quantity",e.quantity)))))})}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{h:n,ensureScopedStyle:r}=e.runtime,t=e.store,a=e.getters,o=e.actions,i="data-ui-members-card",s=`[${i}]`,l=`\n${s} .org-name-list {\n display: flex;\n flex-direction: column;\n flex: 1;\n gap: 0.85rem;\n min-height: 0;\n overflow: auto;\n padding-right: 0.35rem;\n scrollbar-width: thin;\n scrollbar-color: #94a3b8 #e2e8f0;\n}\n\n${s} .org-name-row {\n display: flex;\n align-items: center;\n justify-content: flex-start;\n gap: 1rem;\n padding: 1rem;\n border: 1px solid var(--border);\n border-radius: var(--radius);\n background: #f8fafc;\n}\n\n${s} .org-name-row:nth-child(even) {\n background: linear-gradient(180deg, rgb(248 250 252) 0%, rgb(241 245 249) 100%);\n border-color: rgb(148 163 184 / 0.45);\n}\n\n${s} .org-name-row button {\n margin-left: auto;\n}\n\n@media (max-width: 960px) {\n ${s} .org-name-row {\n flex-direction: column;\n align-items: flex-start;\n }\n\n ${s} .org-name-row button {\n margin-left: 0;\n }\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.MembersCard=function(){const e=window.SharedUI.componentFns.PanelCard,s=t.getMembers(),d=a.canManageMembers();return r("portal-members-card",l),e({className:"org-scroll-panel org-span-5",title:"Members",subtitle:"Current roster listing. The organization owner and your own member entry cannot be removed.",rootProps:{[i]:""},body:n("div",{className:"org-name-list"},...s.map(e=>{const r=d&&!a.isProtectedMember(e);return n("article",{className:"org-name-row"},n("strong",null,e.name),r?n("button",{type:"button",className:"org-danger-btn org-icon-btn",title:`Remove ${e.name}`,"aria-label":`Remove ${e.name}`,onClick:()=>o.removeMember(e)},n("svg",{className:"org-icon",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round","aria-hidden":"true"},n("path",{d:"M9 3h6"}),n("path",{d:"M4 7h16"}),n("path",{d:"M6 7l1 13h10l1-13"}),n("path",{d:"M10 11v6"}),n("path",{d:"M14 11v6"}))):null)}))})}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{h:n,ensureScopedStyle:r}=e.runtime,{portalData:t}=e.data,a="data-ui-activity-card",o=`[${a}]`,i=`\n${o} .org-activity-list {\n display: flex;\n flex-direction: column;\n flex: 1;\n gap: 0.85rem;\n min-height: 0;\n overflow: auto;\n padding-right: 0.35rem;\n scrollbar-width: thin;\n scrollbar-color: #94a3b8 #e2e8f0;\n}\n\n${o} .org-activity-row {\n padding: 1rem;\n border: 1px solid var(--border);\n border-left: 3px solid #94a3b8;\n border-radius: var(--radius);\n background: #f8fafc;\n}\n\n${o} .org-activity-row:nth-child(even) {\n background: linear-gradient(180deg, rgb(248 250 252) 0%, rgb(241 245 249) 100%);\n border-color: rgb(148 163 184 / 0.45);\n border-left-color: #64748b;\n}\n\n${o} .org-activity-row p {\n margin: 0;\n color: var(--text-main);\n}\n\n${o} .org-activity-time {\n display: inline-block;\n margin-bottom: 0.35rem;\n color: var(--text-muted);\n font-size: 0.8rem;\n font-weight: 700;\n letter-spacing: 0.05em;\n text-transform: uppercase;\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.ActivityCard=function(){const t=window.SharedUI.componentFns.PanelCard,o=e.store.getActivity();return r("portal-activity-card",i),t({className:"org-scroll-panel org-span-6",title:"Command Feed",subtitle:"Recent organization-level actions and updates.",rootProps:{[a]:""},body:n("div",{className:"org-activity-list"},...o.map(e=>n("article",{className:"org-activity-row"},n("span",{className:"org-activity-time"},e.time),n("p",null,e.text))))})}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{h:n,ensureScopedStyle:r}=e.runtime,t="data-ui-future-card",a=[{name:"Contracts Board",status:"Planned",detail:"Track payouts, assignments, and claim approvals."},{name:"Diplomacy",status:"Future Review",detail:"Possible future module pending a full design and scope review."},{name:"Logistics Queue",status:"Future Review",detail:"Possible future module pending a full design and scope review."},{name:"Permissions",status:"Future Review",detail:"Possible future module pending a full design and scope review."}],o=`[${t}]`,i=`\n${o} .org-roadmap-grid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 1rem;\n flex: 1;\n min-height: 0;\n overflow: auto;\n padding-right: 0.35rem;\n scrollbar-width: thin;\n scrollbar-color: #94a3b8 #e2e8f0;\n}\n\n${o} .org-roadmap-card {\n padding: 1rem;\n display: flex;\n flex-direction: column;\n gap: 0.7rem;\n border: 1px solid var(--border);\n border-radius: var(--radius);\n background: #f8fafc;\n}\n\n${o} .org-roadmap-card:nth-child(4n + 2),\n${o} .org-roadmap-card:nth-child(4n + 3) {\n background: linear-gradient(180deg, rgb(248 250 252) 0%, rgb(241 245 249) 100%);\n border-color: rgb(100 116 139 / 0.4);\n}\n\n${o} .org-roadmap-card p {\n margin: 0;\n color: var(--text-main);\n}\n\n${o} .org-list-tag {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 0.2rem 0.55rem;\n border-radius: 999px;\n font-size: 0.72rem;\n font-weight: 700;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n background: #e2e8f0;\n color: var(--primary-hover);\n}\n\n${o} .org-roadmap-card:nth-child(4n + 2) .org-list-tag,\n${o} .org-roadmap-card:nth-child(4n + 3) .org-list-tag {\n background: #cbd5e1;\n color: #1e293b;\n}\n\n@media (max-width: 960px) {\n ${o} .org-roadmap-grid {\n grid-template-columns: 1fr;\n }\n\n ${o} .org-roadmap-card:nth-child(4n + 3) {\n background: #f8fafc;\n border-color: var(--border);\n }\n\n ${o} .org-roadmap-card:nth-child(4n + 3) .org-list-tag {\n background: #e2e8f0;\n color: var(--primary-hover);\n }\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.FutureCard=function(){const e=window.SharedUI.componentFns.PanelCard;return r("portal-future-card",i),e({className:"org-scroll-panel org-span-6",title:"Expansion Slots",subtitle:"Potential modules are tagged by status such as Planned, In Design, In Review, and Future Review.",rootProps:{[t]:""},body:n("div",{className:"org-roadmap-grid"},...a.map(e=>n("article",{className:"org-roadmap-card"},n("span",{className:"org-list-tag"},e.status),n("strong",null,e.name),n("p",null,e.detail))))})}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{h:n,ensureScopedStyle:r}=e.runtime,t=e.getters,a=e.actions,o="data-ui-danger-card",i=`[${o}]`,s=`\n${i} {\n border-color: #fecaca;\n background: linear-gradient(180deg, #ffffff 0%, #fff7f7 100%);\n}\n\n${i} .org-danger-copy {\n margin-bottom: 1rem;\n}\n\n${i} .org-danger-copy strong,\n${i} .org-danger-copy p {\n display: block;\n}\n\n${i} .org-danger-copy p {\n margin: 0.4rem 0 0;\n color: var(--text-muted);\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.DangerCard=function(){const e=window.SharedUI.componentFns.PanelCard;return r("portal-danger-card",s),t.canDisbandOrg()?e({className:"org-span-12 org-danger-panel",title:"Organization Controls",subtitle:"Leader-only actions for membership and permanent organization removal.",rootProps:{[o]:""},body:n("div",null,n("div",{className:"org-danger-copy"},n("strong",null,"Disband organization"),n("p",null,"This removes the organization and revokes access to the portal for all members.")),n("button",{type:"button",className:"org-danger-btn",onClick:()=>a.openModal("disband")},"Disband Organization"))}):null}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{h:n}=e.runtime,{portalData:r}=e.data,t=e.store,a=e.actions;e.componentFns=e.componentFns||{},e.componentFns.ModalLayer=function(){const e=window.SharedUI.componentFns.Modal,o=t.getModal();if(!o)return null;const i=t.getMembers(),s=0===i.length?{disabled:!0}:{};let l="",d=null;return"payroll"===o.type?(l="Run Payroll",d=n("div",{className:"app-modal-form"},n("div",null,n("label",null,"Amount Per Member"),n("input",{id:"treasury-payroll-amount",type:"number",min:"1",placeholder:"500",autofocus:"true"})),n("div",{className:"app-modal-actions"},n("button",{type:"button",className:"org-secondary-btn",onClick:()=>a.closeModal()},"Cancel"),n("button",{type:"button",onClick:()=>{a.runPayroll(a.parseAmount(a.getInputValue("treasury-payroll-amount")))&&a.closeModal()}},"Run Payroll")))):"transfer"===o.type?(l="Send Funds",d=n("div",{className:"app-modal-form"},n("div",null,n("label",null,"Member"),n("select",{id:"treasury-transfer-member",...s},...i.map(e=>n("option",{value:e.name},e.name)))),n("div",null,n("label",null,"Amount"),n("input",{id:"treasury-transfer-amount",type:"number",min:"1",placeholder:"1500"})),n("div",{className:"app-modal-actions"},n("button",{type:"button",className:"org-secondary-btn",onClick:()=>a.closeModal()},"Cancel"),n("button",{type:"button",...s,onClick:()=>{a.sendFundsToMember(String(a.getInputValue("treasury-transfer-member")||""),a.parseAmount(a.getInputValue("treasury-transfer-amount")))&&a.closeModal()}},"Send Funds")))):"credit"===o.type?(l="Assign Credit Line",d=n("div",{className:"app-modal-form"},n("div",null,n("label",null,"Member"),n("select",{id:"treasury-credit-member",...s},...i.map(e=>n("option",{value:e.uid},e.name)))),n("div",null,n("label",null,"Credit Amount"),n("input",{id:"treasury-credit-amount",type:"number",min:"1",placeholder:"5000"})),n("div",{className:"app-modal-actions"},n("button",{type:"button",className:"org-secondary-btn",onClick:()=>a.closeModal()},"Cancel"),n("button",{type:"button",...s,onClick:()=>{a.grantCreditLine(String(a.getInputValue("treasury-credit-member")||""),a.parseAmount(a.getInputValue("treasury-credit-amount")))&&a.closeModal()}},"Assign Credit Line")))):"disband"===o.type?(l="Disband Organization",d=n("div",{className:"app-modal-danger"},n("p",null,"This action is permanent. Disband ",r.org.name,"?"),n("div",{className:"app-modal-danger-actions"},n("button",{type:"button",className:"org-secondary-btn",onClick:()=>a.closeModal()},"Cancel"),n("button",{type:"button",className:"org-danger-btn",onClick:()=>a.disbandOrganization()},"Confirm Disband")))):"leave"===o.type&&(l="Leave Organization",d=n("div",{className:"app-modal-danger"},n("p",null,"Leave ",r.org.name," and return to the default organization?"),n("div",{className:"app-modal-danger-actions"},n("button",{type:"button",className:"org-secondary-btn",onClick:()=>a.closeModal()},"Cancel"),n("button",{type:"button",className:"org-danger-btn",onClick:()=>a.leaveOrganization()},"Confirm Leave")))),e({title:l,body:d,onClose:()=>a.closeModal()})}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{h:n}=e.runtime,{portalData:r}=e.data,t=window.RegistryApp.store;e.componentFns=e.componentFns||{},e.componentFns.DisbandedView=function(){return(0,window.SharedUI.componentFns.PanelCard)({className:"org-span-12 org-empty-state",eyebrow:"Organization Removed",title:r.org.name,body:n("div",null,n("p",{className:"org-summary"},"This organization has been disbanded. Member access, assets, and fleet management are no longer available from this portal preview."),n("button",{type:"button",className:"org-secondary-btn",onClick:()=>t.setView("home")},"Return to Registry"))})}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{h:n,ensureScopedStyle:r}=e.runtime,{portalData:t,session:a}=e.data,o=e.store,i="[data-ui-portal-view]";r("portal-view",`\n ${i} {\n --org-row-card-max-height: 36rem;\n }\n\n ${i} .org-toast-stack {\n position: fixed;\n top: 1.5rem;\n right: 2rem;\n z-index: 20;\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n pointer-events: none;\n }\n\n ${i} .org-toast {\n max-width: 24rem;\n padding: 0.9rem 1rem;\n border-radius: var(--radius);\n border: 1px solid var(--border);\n background: #fff;\n box-shadow: 0 12px 28px rgb(15 23 42 / 0.14);\n font-size: 0.92rem;\n pointer-events: auto;\n }\n\n ${i} .org-toast.is-success {\n background: #ecfdf5;\n border-color: #bbf7d0;\n color: #166534;\n }\n\n ${i} .org-toast.is-error {\n background: #fef2f2;\n border-color: #fecaca;\n color: #991b1b;\n }\n\n ${i} .org-dashboard-grid {\n display: grid;\n grid-template-columns: repeat(12, minmax(0, 1fr));\n gap: 1.5rem;\n align-items: stretch;\n }\n\n ${i} .org-panel {\n margin-bottom: 0;\n text-align: left;\n }\n\n ${i} .org-scroll-panel {\n display: flex;\n flex-direction: column;\n min-height: 0;\n max-height: var(--org-row-card-max-height);\n overflow: hidden;\n }\n\n ${i} .org-island-root {\n display: flex;\n align-self: stretch;\n min-height: 0;\n min-width: 0;\n }\n\n ${i} .org-island-root > .org-panel {\n height: 100%;\n width: 100%;\n }\n\n ${i} .org-span-12 {\n grid-column: span 12;\n }\n\n ${i} .org-span-7 {\n grid-column: span 7;\n }\n\n ${i} .org-span-6 {\n grid-column: span 6;\n }\n\n ${i} .org-span-5 {\n grid-column: span 5;\n }\n\n @media (max-width: 960px) {\n ${i} .org-toast-stack {\n top: 1rem;\n right: 1rem;\n left: 1rem;\n }\n\n ${i} .org-toast {\n max-width: none;\n }\n\n ${i} .org-span-12,\n ${i} .org-span-7,\n ${i} .org-span-6,\n ${i} .org-span-5 {\n grid-column: span 12;\n }\n\n ${i} .org-scroll-panel {\n max-height: none;\n }\n\n }\n `),e.components=e.components||{},e.componentFns=e.componentFns||{},e.componentFns.TreasuryNoticeLayer=function(){const e=o.getTreasuryNotice();return e.text?n("div",{className:"org-toast-stack"},n("div",{className:"error"===e.type?"org-toast is-error":"org-toast is-success"},e.text)):null},e.components.App=function(){const r=window.SharedUI.componentFns.Hero,i=window.SharedUI.componentFns.Footer,s=e.componentFns.FutureCard,l=e.componentFns.DangerCard,d=e.componentFns.DisbandedView,c=[{title:"Organization Controls",items:["Roster Management","Fleet Assignment","Treasury Permissions","Asset Registry"]},{title:"Planned Extensions",items:["Contracts Board","Diplomacy Layer","Procurement Queue","Reputation History"]}];return o.getOrgDisbanded()?n("main",{"data-ui-portal-view":""},n("div",{className:"container"},n("div",{className:"org-dashboard-grid"},r({kicker:t.org.tag,title:t.org.name,subtitle:"Player organization command portal",meta:`${a.actorName} - ${a.role}`}),d())),n("div",{id:"org-portal-modal-root"}),i({sections:c})):n("main",{"data-ui-portal-view":""},n("div",{id:"org-portal-toast-root"}),n("div",{className:"container"},n("div",{className:"org-dashboard-grid"},r({kicker:t.org.tag,title:t.org.name,subtitle:"Player organization command portal",meta:`${a.actorName} - ${a.role}`}),n("div",{className:"org-island-root org-span-12",id:"org-overview-card-root"}),n("div",{className:"org-island-root org-span-7",id:"org-fleet-card-root"}),n("div",{className:"org-island-root org-span-5",id:"org-treasury-card-root"}),n("div",{className:"org-island-root org-span-5",id:"org-members-card-root"}),n("div",{className:"org-island-root org-span-7",id:"org-assets-card-root"}),n("div",{className:"org-island-root org-span-6",id:"org-activity-card-root"}),s(),l())),n("div",{id:"org-portal-modal-root"}),i({sections:c}))}}(),function(){const e=window.RegistryApp=window.RegistryApp||{},{h:n,ensureScopedStyle:r}=e.runtime,t=e.store,a=e.bridge,o="data-ui-registration-view",i=`[${o}]`,s=`\n${i} {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 2rem;\n align-items: center;\n width: 100%;\n}\n\n${i} .info-panel {\n text-align: left;\n padding: 1rem;\n}\n\n${i} .create-feature-list {\n text-align: left;\n margin-top: 1.5rem;\n list-style-type: none;\n padding: 0;\n}\n\n${i} .create-feature-item {\n margin-bottom: 0.5rem;\n display: flex;\n align-items: center;\n gap: 0.5rem;\n}\n\n${i} .create-feature-icon {\n width: 1.2rem;\n height: 1.2rem;\n flex-shrink: 0;\n}\n\n${i} .price-tag {\n margin-top: 2rem;\n padding: 1rem;\n background: var(--bg-app);\n border-radius: var(--radius);\n border: 1px solid var(--border);\n}\n\n${i} .price-label {\n display: block;\n font-size: 0.9rem;\n color: var(--text-muted);\n}\n\n${i} .price-value {\n display: block;\n font-size: 2rem;\n font-weight: 700;\n color: var(--primary);\n}\n\n${i} .form-panel {\n margin: 0;\n}\n\n${i} .app-form {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n text-align: left;\n}\n\n${i} .app-form label {\n display: block;\n margin-bottom: 0.5rem;\n color: var(--text-muted);\n font-weight: 500;\n font-size: 0.9rem;\n}\n\n${i} .app-form input,\n${i} .app-form select {\n width: 100%;\n padding: 0.75rem;\n border-radius: var(--radius);\n border: 1px solid var(--border);\n background: var(--bg-app);\n color: var(--text-main);\n font-family: inherit;\n font-size: 1rem;\n box-sizing: border-box;\n transition: border-color 0.2s;\n}\n\n${i} .app-form input:focus,\n${i} .app-form select:focus {\n outline: none;\n border-color: var(--primary);\n box-shadow: 0 0 0 2px rgb(59 130 246 / 0.1);\n}\n\n${i} .form-actions {\n margin-top: 1rem;\n display: flex;\n flex-direction: column;\n gap: 1rem;\n align-items: center;\n}\n\n${i} .submit-btn {\n width: 100%;\n}\n\n${i} .cancel-link {\n font-size: 0.9rem;\n color: var(--text-muted);\n cursor: pointer;\n text-decoration: underline;\n}\n\n${i} .cancel-link:hover {\n color: var(--primary);\n}\n\n${i} .form-feedback {\n padding: 0.85rem 1rem;\n border-radius: var(--radius);\n font-size: 0.92rem;\n}\n\n${i} .form-feedback.is-error {\n background: #fef2f2;\n border: 1px solid #fecaca;\n color: #991b1b;\n}\n\n@media (max-width: 960px) {\n ${i} {\n grid-template-columns: 1fr;\n }\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.RegistrationView=function(){const e=t.getIsCreating(),i=t.getCreateError();r("main-registration-view",s);return n("div",{className:"split-container",[o]:""},n("div",{className:"info-panel"},n("h2",null,"Registration Details"),n("p",null,"Complete the form to add your organization to the Global Organization Registry."),n("ul",{className:"create-feature-list"},n("li",{className:"create-feature-item"},n("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"#10b981","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round",className:"create-feature-icon"},n("path",{d:"M20 6L9 17l-5-5"})),"Official Organization Designator"),n("li",{className:"create-feature-item"},n("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"#10b981","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round",className:"create-feature-icon"},n("path",{d:"M20 6L9 17l-5-5"})),"Secure Comms Channel"),n("li",{className:"create-feature-item"},n("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"#10b981","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round",className:"create-feature-icon"},n("path",{d:"M20 6L9 17l-5-5"})),"Deployment Roster Access"),n("li",{className:"create-feature-item"},n("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"#10b981","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round",className:"create-feature-icon"},n("path",{d:"M20 6L9 17l-5-5"})),"After-Action Report Tools")),n("div",{className:"price-tag"},n("span",{className:"price-label"},"Registration Fee"),n("span",{className:"price-value"},"$50,000"))),n("div",{className:"form-panel card"},n("h2",null,"Organization Registration"),n("div",{className:"app-form"},n("div",null,n("label",null,"Organization Name"),n("input",{id:"org-create-name",type:"text",placeholder:"e.g. Task Force 141"})),n("div",null,n("label",null,"Organization Type"),n("select",{id:"org-create-type"},n("option",{value:"infantry"},"Infantry / Milsim"),n("option",{value:"aviation"},"Aviation Wing"),n("option",{value:"pmc"},"Private Military Company"),n("option",{value:"support"},"Logistics & Support"))),n("div",{className:"form-actions"},i?n("div",{className:"form-feedback is-error"},i):null,n("button",{type:"button",className:"submit-btn",disabled:e,onClick:()=>{const e={orgName:String(document.getElementById("org-create-name")?.value||"").trim(),type:String(document.getElementById("org-create-type")?.value||"")};a&&"function"==typeof a.requestCreateOrg?a.requestCreateOrg(e):t.failCreate("Registration bridge is not available.")}},e?"Submitting Registration...":"Submit Registration"),n("span",{className:"cancel-link",onClick:()=>t.setView("home")},"Cancel / Return to Main")))))}}(),function(){const e=window.RegistryApp=window.RegistryApp||{},{h:n,ensureScopedStyle:r}=e.runtime,t=e.store,a=e.bridge,o="data-ui-home-view",i=`[${o}]`,s=`\n${i} {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 2rem;\n margin-bottom: 2rem;\n}\n\n${i} .home-feedback {\n padding: 0.85rem 1rem;\n border-radius: var(--radius);\n font-size: 0.92rem;\n background: #fef2f2;\n border: 1px solid #fecaca;\n color: #991b1b;\n}\n\n@media (max-width: 960px) {\n ${i} {\n grid-template-columns: 1fr;\n }\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.HomeView=function(){const e=t.getIsAuthenticating(),i=t.getLoginError();return r("main-home-view",s),n("div",{className:"content",[o]:""},n("div",{className:"card"},n("h2",null,"Create Organization"),n("p",null,"Establish your Task Force, PMC, or Milsim unit with the Global Organization Network. Receive your official unit designator and TO&E authorization instantly."),n("button",{onClick:()=>t.setView("create")},"Register")),n("div",{className:"card"},n("h2",null,"Organization Portal"),n("p",null,"Access your unit dashboard to modify rosters, adjust active deployments, and submit after-action reports through the secure field uplink."),i?n("div",{className:"home-feedback"},i):null,n("button",{disabled:e,onClick:()=>{a?a.requestLogin({}):t.failLogin("Login bridge is not available.")}},e?"Opening Portal...":"Login")))}}(),function(){const e=window.RegistryApp=window.RegistryApp||{},{h:n}=e.runtime,r=e.store;e.components=e.components||{},e.components.App=function(){const t=window.SharedUI.componentFns.Navbar,a=window.SharedUI.componentFns.Header,o=window.SharedUI.componentFns.Footer,i=window.SharedUI.componentFns.WindowTitleBar,s=e.componentFns.HomeView,l=e.componentFns.RegistrationView,d=window.OrgPortal&&window.OrgPortal.components?window.OrgPortal.components.App:null,c=r.getView(),m=window.OrgPortal&&window.OrgPortal.getters?window.OrgPortal.getters:null,g=window.OrgPortal&&window.OrgPortal.actions?window.OrgPortal.actions:null,p="create"===c?"Organization Registration":"portal"===c?"Organization Portal":"Entry Hub";function u(){e.bridge&&"function"==typeof e.bridge.close?e.bridge.close({}):r.setView("home")}if("portal"===c&&d){const e=m&&"function"==typeof m.canLeaveOrg&&m.canLeaveOrg();return n("div",{className:"app-shell"},i({kicker:"FORGE ORBIS",title:"Global Organization Network",onClose:u,closeLabel:"Close organization interface"}),t({title:"Global Organization Network",viewLabel:p,actionLabel:e?"Leave Organization":"",onAction:e&&g&&"function"==typeof g.openModal?()=>g.openModal("leave"):null}),n("div",{id:"org-portal-frame-root"}))}let f;return"home"===c?f=s():"create"===c&&(f=l()),n("div",{className:"app-shell"},i({kicker:"FORGE ORBIS",title:"Global Organization Network",onClose:u,closeLabel:"Close organization interface"}),n("main",null,t({title:"Global Organization Network",viewLabel:p}),n("div",{className:"container"},a({title:"Global Organization Network",onTitleClick:()=>r.setView("home")}),f),o({sections:[{title:"Registry Resources",items:["Registration Guidelines","Tax & Fee Schedule","Legal Compliance","Trademark Database"]},{title:"Bureau Support",items:["Office: Sector 7 Admin Block","Hours: 0800 - 1600 (GST)","Helpdesk: 555-01-REGISTRY","support@org-bureau.gov"]}]})))}}(),function(){const e=window.ForgeWebUI,n=window.RegistryApp,r=window.OrgPortal,t=[{id:"org-portal-frame-root",preserveScroll:!0,render:()=>r.components.App()},{id:"org-portal-toast-root",preserveScroll:!1,render:()=>r.componentFns.TreasuryNoticeLayer()},{id:"org-overview-card-root",preserveScroll:!1,render:()=>r.componentFns.OverviewCard()},{id:"org-fleet-card-root",preserveScroll:!0,render:()=>r.componentFns.FleetCard()},{id:"org-treasury-card-root",preserveScroll:!1,render:()=>r.componentFns.TreasuryCard()},{id:"org-members-card-root",preserveScroll:!0,render:()=>r.componentFns.MembersCard()},{id:"org-assets-card-root",preserveScroll:!0,render:()=>r.componentFns.AssetsCard()},{id:"org-activity-card-root",preserveScroll:!0,render:()=>r.componentFns.ActivityCard()},{id:"org-portal-modal-root",preserveScroll:!1,render:()=>r.componentFns.ModalLayer()}];e.createApp({name:"org",root:"#app",setup({root:r}){const a=function(){const n=new Map;return{sync:function(){t.forEach(r=>{const t=document.getElementById(r.id),a=n.get(r.id);if(!t)return void(a&&(a.handle.dispose(),n.delete(r.id)));if(a&&a.container===t)return;a&&a.handle.dispose();const o=e.mount(t,r.render,{preserveScroll:r.preserveScroll});n.set(r.id,{container:t,handle:o})})}}}();e.mount(r,()=>n.components.App(),{preserveScroll:!1}),n.bridge.ready({loaded:!0}),e.effect(()=>{n.store.getView(),requestAnimationFrame(()=>{a.sync()})})}}).start()}(); \ No newline at end of file +!function(){const e=window.ForgeWebUI,n=window.RegistryApp=window.RegistryApp||{},r=window.OrgPortal=window.OrgPortal||{};n.runtime=e,r.runtime=e,window.AppRuntime=e}(),function(){const e=window.RegistryApp=window.RegistryApp||{},{createSignal:n}=e.runtime;e.store=new class{constructor(){[this.getView,this.setView]=n("home"),[this.getIsAuthenticating,this.setIsAuthenticating]=n(!1),[this.getLoginError,this.setLoginError]=n(""),[this.getIsCreating,this.setIsCreating]=n(!1),[this.getCreateError,this.setCreateError]=n("")}startLogin(){this.setLoginError(""),this.setIsAuthenticating(!0)}startCreate(){this.setCreateError(""),this.setIsCreating(!0)}failLogin(e){this.setIsAuthenticating(!1),this.setLoginError(e||"Authentication failed.")}failCreate(e){this.setIsCreating(!1),this.setCreateError(e||"Organization registration failed.")}hydratePortal(e){const n=window.OrgPortal&&window.OrgPortal.data?window.OrgPortal.data:null,r=window.OrgPortal&&window.OrgPortal.store?window.OrgPortal.store:null,t=e&&e.portalData?e.portalData:null,a=e&&e.session?e.session:null;return!!(n&&"function"==typeof n.applyLoginPayload&&r&&"function"==typeof r.hydrateFromPayload&&t&&a)&&(n.applyLoginPayload(e),r.hydrateFromPayload(e),!0)}completeLogin(e){this.hydratePortal(e)?(this.setLoginError(""),this.setIsAuthenticating(!1),this.setView("portal")):this.failLogin("Login response was missing portal data.")}completeCreate(e){this.hydratePortal(e)?(this.setCreateError(""),this.setIsCreating(!1),this.setView("portal")):this.failCreate("Organization registration response was missing portal data.")}}}(),function(){const e=window.RegistryApp=window.RegistryApp||{},n=e.store,r=window.ForgeWebUI.createBridge({closeEvent:"org::close",globalName:"ForgeBridge",readyEvent:"org::ready"});function t(e,n){return r.send(e,n)}r.on("org::login::success",e=>{n.completeLogin(e)}),r.on("org::login::failure",e=>{n.failLogin(e.message||"Authentication failed.")}),r.on("org::create::success",e=>{n.completeCreate(e)}),r.on("org::create::failure",e=>{n.failCreate(e.message||"Organization registration failed.")}),r.on("org::sync",e=>{n&&"function"==typeof n.hydratePortal&&n.hydratePortal(e)}),r.on("org::credit::success",e=>{const n=window.OrgPortal;n&&n.store&&n.store.setModal(null),n&&n.actions&&n.actions.showTreasuryNotice("success",e.message||"Credit line assigned.")}),r.on("org::credit::failure",e=>{const n=window.OrgPortal;n&&n.actions&&n.actions.showTreasuryNotice("error",e.message||"Unable to assign credit line.")}),r.on("org::member::creditUpdated",e=>{const n=window.OrgPortal;n&&n.store&&n.store.setCreditLines(n=>{const r={amount:e.availableAmount||e.amount||0,amountDue:e.amountDue||0,approvedAmount:e.approvedAmount||e.availableAmount||e.amount||0,availableAmount:e.availableAmount||e.amount||0,interestRate:e.interestRate||.1,member:e.memberName||"",outstandingPrincipal:e.outstandingPrincipal||0,uid:e.memberUid||""},t=n.findIndex(e=>e.uid===r.uid);return-1===t?[...n,r]:n.map((e,n)=>n===t?r:e)})}),r.on("org::disband::success",()=>{const e=window.OrgPortal;e&&e.store&&(e.store.setModal(null),e.store.setOrgDisbanded(!0))}),r.on("org::disband::failure",e=>{const n=window.OrgPortal;n&&n.store&&n.store.setModal(null),n&&n.actions&&n.actions.showTreasuryNotice("error",e.message||"Organization disbanding failed.")}),r.on("org::leave::success",e=>{const r=window.OrgPortal;r&&r.store&&r.store.setModal(null),n.failLogin(e.message||"You have left the organization."),n.setView("home")}),r.on("org::leave::failure",e=>{const n=window.OrgPortal;n&&n.store&&n.store.setModal(null),n&&n.actions&&n.actions.showTreasuryNotice("error",e.message||"Unable to leave the organization.")}),r.on("org::portal::revoked",e=>{const r=window.OrgPortal;r&&r.store&&r.store.setModal(null),n.failLogin(e.message||"Organization access is no longer available."),n.setView("home")}),e.bridge={close:r.close,ready:r.ready,receive:r.receive,requestLogin:function(e){n.startLogin(),t("org::login::request",e)||n.failLogin("Arma login bridge is unavailable.")},requestCreateOrg:function(e){n.startCreate(),t("org::create::request",e)||n.failCreate("Arma registration bridge is unavailable.")},requestDisbandOrg:function(){if(t("org::disband::request",{}))return;const e=window.OrgPortal;e&&e.actions&&e.actions.showTreasuryNotice("error","Arma disband bridge is unavailable.")},requestLeaveOrg:function(){if(t("org::leave::request",{}))return;const e=window.OrgPortal;e&&e.actions&&e.actions.showTreasuryNotice("error","Arma leave bridge is unavailable.")},requestCreditLine:function(e){if(t("org::credit::request",e))return!0;const n=window.OrgPortal;return n&&n.actions&&n.actions.showTreasuryNotice("error","Arma credit line bridge is unavailable."),!1},sendEvent:t}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},n={type:"Organization",status:"Operational",headquarters:"ArmA Verse"};function r(e){return JSON.parse(JSON.stringify(e))}function t(e,n){Object.keys(e).forEach(n=>delete e[n]),Object.assign(e,r(n))}function a(e,n){e.splice(0,e.length,...r(n))}function o(e){if(e&&"object"==typeof e&&!Array.isArray(e))return e;if(Array.isArray(e)){if(e.every(e=>Array.isArray(e)&&e.length>=2&&"string"==typeof e[0]))return Object.fromEntries(e)}if("string"==typeof e&&""!==e.trim())try{return o(JSON.parse(e))}catch(n){return e}return e}function i(e){return(Array.isArray(e)?e:e&&"object"==typeof e?Object.values(e):[]).map(o).filter(Boolean)}e.data={portalData:{org:Object.assign({name:"",tag:"",owner:"",ownerUid:"",isDefault:!1},n),funds:0,reputation:0,creditLines:[],members:[],fleet:[],assets:[],activity:[],roadmap:[{name:"Contracts Board",status:"Planned",detail:"Track payouts, assignments, and claim approvals."},{name:"Diplomacy",status:"Future Review",detail:"Possible future module pending a full design and scope review."},{name:"Logistics Queue",status:"Future Review",detail:"Possible future module pending a full design and scope review."},{name:"Permissions",status:"Future Review",detail:"Possible future module pending a full design and scope review."}]},session:{actorName:"",actorUid:"",role:"",ceo:!1},applyLoginPayload(e){t(this.portalData.org,Object.assign({},e.portalData.org||{},n)),this.portalData.funds=e.portalData.funds||0,this.portalData.reputation=e.portalData.reputation||0,a(this.portalData.creditLines,i(e.portalData.creditLines)),a(this.portalData.members,i(e.portalData.members)),a(this.portalData.fleet,i(e.portalData.fleet)),a(this.portalData.assets,i(e.portalData.assets)),a(this.portalData.activity,i(e.portalData.activity)),a(this.portalData.roadmap,i(e.portalData.roadmap)),t(this.session,e.session||{})}}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{createSignal:n}=window.RegistryApp.runtime,{portalData:r}=e.data;function t(e){if(e&&"object"==typeof e&&!Array.isArray(e))return e;if(Array.isArray(e)){if(e.every(e=>Array.isArray(e)&&e.length>=2&&"string"==typeof e[0]))return Object.fromEntries(e)}if("string"==typeof e&&""!==e.trim())try{return t(JSON.parse(e))}catch(n){return e}return e}function a(e){return(Array.isArray(e)?e:e&&"object"==typeof e?Object.values(e):[]).map(t).filter(Boolean)}e.store=new class{constructor(){[this.getFunds,this.setFunds]=n(r.funds),[this.getReputation,this.setReputation]=n(r.reputation),[this.getMembers,this.setMembers]=n([...r.members]),[this.getCreditLines,this.setCreditLines]=n([...r.creditLines]),[this.getFleet,this.setFleet]=n([...r.fleet]),[this.getAssets,this.setAssets]=n([...r.assets]),[this.getActivity,this.setActivity]=n([...r.activity]),[this.getTreasuryNotice,this.setTreasuryNotice]=n({type:"",text:""}),[this.getModal,this.setModal]=n(null),[this.getOrgDisbanded,this.setOrgDisbanded]=n(!1)}hydrateFromPayload(e){const n=e.portalData||{};this.setFunds(n.funds||0),this.setReputation(n.reputation||0),this.setMembers([...a(n.members)]),this.setCreditLines([...a(n.creditLines)]),this.setFleet([...a(n.fleet)]),this.setAssets([...a(n.assets)]),this.setActivity([...a(n.activity)])}}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{portalData:n,session:r}=e.data;e.getters=new class{formatCurrency(e){return"$"+Number(e||0).toLocaleString()}formatVehicleType(e){return e?e.charAt(0).toUpperCase()+e.slice(1):""}formatAssetType(e){return e?e.charAt(0).toUpperCase()+e.slice(1):""}formatDisplayName(e){return e?String(e).trim().split(/\s+/).map(e=>e?e.charAt(0).toUpperCase()+e.slice(1).toLowerCase():"").join(" "):""}getAssetReadiness(){const r=e.store?e.store.getFleet():n.fleet;if(0===r.length)return null;const t=r.reduce((e,n)=>e+(100-parseInt(n.damage,10)),0);return Math.round(t/r.length)}getNormalizedRole(){return String(r.role||"").trim().toUpperCase()}isDefaultOrg(){return!0===n.org.isDefault||"DEFAULT"===String(n.org.tag||"").trim().toUpperCase()}isOrgOwner(){const e=String(n.org.ownerUid||n.org.owner||"").trim().toLowerCase(),t=String(r.actorUid||"").trim().toLowerCase();return e&&t?t===e:String(r.actorName||"").trim().toLowerCase()===String(n.org.owner||"").trim().toLowerCase()}isSessionCeo(){return!0===r.ceo}isOrgLeaderOrCeo(){return this.isOrgOwner()||"LEADER"===this.getNormalizedRole()||this.isDefaultOrg()&&this.isSessionCeo()}canManageMembers(){return this.isOrgLeaderOrCeo()}canManageTreasury(){return this.isOrgLeaderOrCeo()}canDisbandOrg(){return this.isOrgOwner()&&!this.isDefaultOrg()}canLeaveOrg(){return!this.isDefaultOrg()&&!this.isOrgOwner()}getMemberName(e){return String(e&&"object"==typeof e?e.name||"":e||"")}getMemberUid(e){return e&&"object"==typeof e?String(e.uid||""):""}isOwnerMember(e){return this.getMemberName(e).trim().toLowerCase()===String(n.org.owner||"").trim().toLowerCase()}isCurrentMember(e){const n=this.getMemberUid(e).trim().toLowerCase(),t=String(r.actorUid||"").trim().toLowerCase();return n&&t?n===t:this.getMemberName(e).trim().toLowerCase()===String(r.actorName||"").trim().toLowerCase()}isProtectedMember(e){return this.isOwnerMember(e)||this.isCurrentMember(e)}}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{portalData:n}=e.data,r=e.store,t=e.getters,a=window.RegistryApp.store;e.actions=new class{constructor(){this.treasuryNoticeTimer=null}showTreasuryNotice(e,n){r.setTreasuryNotice({type:e,text:n}),this.treasuryNoticeTimer&&clearTimeout(this.treasuryNoticeTimer),this.treasuryNoticeTimer=setTimeout(()=>{r.setTreasuryNotice({type:"",text:""}),this.treasuryNoticeTimer=null},3500)}parseAmount(e){const n=Number(e);return Number.isFinite(n)?Math.round(n):0}getInputValue(e){const n=document.getElementById(e);return n?n.value:""}closePortal(){const e=window.RegistryApp?window.RegistryApp.bridge:null;e&&"function"==typeof e.close?e.close({}):a&&a.setView("home")}openModal(e){"payroll"!==e&&"transfer"!==e&&"credit"!==e||t.canManageTreasury()?("disband"!==e||t.canDisbandOrg())&&("leave"!==e||t.canLeaveOrg())&&r.setModal({type:e}):this.showTreasuryNotice("error","Only the organization leader or CEO can manage treasury actions.")}closeModal(){r.setModal(null)}removeMember(e){if(!t.canManageMembers())return!1;if(t.isProtectedMember(e))return!1;const n=t.getMemberUid(e),a=t.getMemberName(e);return r.setMembers(e=>e.filter(e=>n?e.uid!==n:e.name!==a)),r.setCreditLines(e=>e.filter(e=>n?e.uid!==n:e.member!==a)),!0}disbandOrganization(){if(!t.canDisbandOrg())return!1;const e=window.RegistryApp?window.RegistryApp.bridge:null;return e&&"function"==typeof e.requestDisbandOrg?(this.closeModal(),e.requestDisbandOrg(),!0):(this.showTreasuryNotice("error","Disband bridge is unavailable."),!1)}leaveOrganization(){if(!t.canLeaveOrg())return!1;const e=window.RegistryApp?window.RegistryApp.bridge:null;return e&&"function"==typeof e.requestLeaveOrg?(this.closeModal(),e.requestLeaveOrg(),!0):(this.showTreasuryNotice("error","Leave bridge is unavailable."),!1)}runPayroll(e){if(!t.canManageTreasury())return this.showTreasuryNotice("error","Only the organization leader or CEO can manage treasury actions."),!1;const n=r.getMembers(),a=r.getFunds();if(0===n.length)return this.showTreasuryNotice("error","No members available for payroll."),!1;if(e<=0)return this.showTreasuryNotice("error","Enter a valid payroll amount."),!1;const o=e*n.length;return o>a?(this.showTreasuryNotice("error","Insufficient org funds for payroll."),!1):(r.setFunds(a-o),this.showTreasuryNotice("success",`Payroll sent to ${n.length} members for ${t.formatCurrency(o)}.`),!0)}sendFundsToMember(e,n){if(!t.canManageTreasury())return this.showTreasuryNotice("error","Only the organization leader or CEO can manage treasury actions."),!1;const a=r.getFunds();return e?n<=0?(this.showTreasuryNotice("error","Enter a valid transfer amount."),!1):n>a?(this.showTreasuryNotice("error","Insufficient org funds for this transfer."),!1):(r.setFunds(a-n),this.showTreasuryNotice("success",`${t.formatCurrency(n)} sent to ${e}.`),!0):(this.showTreasuryNotice("error","Select a member to receive funds."),!1)}grantCreditLine(e,n){if(!t.canManageTreasury())return this.showTreasuryNotice("error","Only the organization leader or CEO can manage treasury actions."),!1;if(!e)return this.showTreasuryNotice("error","Select a member for the credit line."),!1;if(n<=0)return this.showTreasuryNotice("error","Enter a valid credit line amount."),!1;const a=r.getMembers().find(n=>t.getMemberUid(n)===e),o=a?t.getMemberName(a):"";if(!o)return this.showTreasuryNotice("error","Selected member was not found in the organization roster."),!1;const i=window.RegistryApp?window.RegistryApp.bridge:null;return i&&"function"==typeof i.requestCreditLine?i.requestCreditLine({memberUid:e,memberName:o,amount:n}):(this.showTreasuryNotice("error","Credit line bridge is unavailable."),!1)}}}(),function(){const e=window.SharedUI=window.SharedUI||{},n=window.RegistryApp=window.RegistryApp||{},{h:r,ensureScopedStyle:t}=n.runtime,a="data-ui-navbar",o=`[${a}]`,i=`\n${o} {\n background: var(--bg-surface);\n border-bottom: 1px solid var(--border);\n box-shadow: var(--shadow);\n}\n\n${o} .app-navbar-inner {\n display: flex;\n justify-content: space-between;\n align-items: center;\n max-width: 1200px;\n width: 100%;\n margin: 0 auto;\n padding: 1rem 2rem;\n box-sizing: border-box;\n}\n\n${o} .app-navbar-brand {\n display: flex;\n flex-direction: column;\n gap: 0.125rem;\n}\n\n${o} .app-navbar-kicker {\n font-size: 0.7rem;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--text-muted);\n font-weight: 600;\n}\n\n${o} .app-navbar-title {\n font-size: 1.25rem;\n font-weight: 700;\n color: var(--primary-hover);\n letter-spacing: -0.025em;\n}\n\n${o} .app-navbar-actions {\n display: flex;\n align-items: center;\n gap: 1.5rem;\n}\n\n${o} .app-navbar-view {\n font-size: 0.8rem;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: var(--text-muted);\n font-weight: 600;\n}\n\n${o} .app-close-btn {\n background: transparent;\n color: var(--text-muted);\n border: 1px solid var(--border);\n padding: 0.5rem 1rem;\n font-size: 0.85rem;\n}\n\n${o} .app-close-btn:hover {\n background: var(--bg-surface-hover);\n color: var(--primary-hover);\n border-color: var(--primary);\n transform: none;\n box-shadow: none;\n}\n\n@media (max-width: 960px) {\n ${o} .app-navbar-inner {\n flex-direction: column;\n align-items: flex-start;\n padding: 1rem 1.5rem;\n }\n\n ${o} .app-navbar-actions {\n align-items: flex-start;\n }\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.Navbar=function({kicker:e="ORBIS",title:n="",viewLabel:o="",actionLabel:s="",onAction:l=null}){return t("shared-navbar",i),r("nav",{className:"app-navbar",[a]:""},r("div",{className:"app-navbar-inner"},r("div",{className:"app-navbar-brand"},r("span",{className:"app-navbar-kicker"},e),r("span",{className:"app-navbar-title"},n)),r("div",{className:"app-navbar-actions"},r("span",{className:"app-navbar-view"},o),s&&"function"==typeof l?r("button",{type:"button",className:"app-close-btn",onClick:l},s):null)))}}(),function(){const e=window.SharedUI=window.SharedUI||{},n=window.RegistryApp=window.RegistryApp||{},{h:r}=n.runtime;e.componentFns=e.componentFns||{},e.componentFns.Header=function({title:e,subtitle:n="Organization Registration & Management Portal",onTitleClick:t=null}){return r("div",{className:"header"},r("h1",{style:{cursor:t?"pointer":"default"},onClick:t},e),r("p",null,n))}}(),function(){const e=window.SharedUI=window.SharedUI||{},n=window.OrgPortal=window.OrgPortal||{},{h:r}=n.runtime;e.componentFns=e.componentFns||{},e.componentFns.Hero=function({className:e="",kicker:n="",title:t="",subtitle:a="",meta:o=""}){const i=["card org-panel org-span-12 org-page-header",e].filter(Boolean).join(" ");return r("section",{className:i},r("div",{className:"org-page-heading"},r("span",{className:"org-page-kicker"},n),r("h1",{className:"org-page-title"},t),r("p",{className:"org-page-subtitle"},a),r("span",{className:"org-page-meta"},o)))}}(),function(){const e=window.SharedUI=window.SharedUI||{},n=window.RegistryApp=window.RegistryApp||{},{h:r}=n.runtime;e.componentFns=e.componentFns||{},e.componentFns.Footer=function({sections:e=[]}){return r("div",{className:"footer"},r("div",{className:"wrapper"},...e.map(e=>r("div",null,r("h3",null,e.title),r("ul",{style:{listStyleType:"none",padding:0}},...(e.items||[]).map(e=>r("li",null,e)))))))}}(),function(){const e=window.SharedUI=window.SharedUI||{},n=window.RegistryApp=window.RegistryApp||{},{h:r,ensureScopedStyle:t}=n.runtime,a="data-ui-modal",o=`[${a}]`,i=`\n${o} {\n position: fixed;\n inset: 0;\n background: rgb(15 23 42 / 0.38);\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 1.5rem;\n z-index: 20;\n}\n\n${o} .app-modal-card {\n width: min(100%, 30rem);\n margin-bottom: 0;\n text-align: left;\n}\n\n${o} .app-modal-head {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 1rem;\n margin-bottom: 1rem;\n}\n\n${o} .app-modal-title {\n margin: 0;\n color: var(--primary-hover);\n font-size: 1.45rem;\n}\n\n${o} .app-modal-close {\n width: 2.25rem;\n height: 2.25rem;\n padding: 0;\n background: var(--bg-surface);\n color: var(--text-main);\n border: 1px solid var(--border);\n box-shadow: none;\n transform: none;\n}\n\n${o} .app-modal-close:hover {\n background: var(--bg-surface-hover);\n color: var(--text-main);\n box-shadow: none;\n transform: none;\n}\n\n${o} .app-modal-form {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n}\n\n${o} .app-modal-form label {\n display: block;\n margin-bottom: 0.5rem;\n color: var(--text-muted);\n font-weight: 500;\n font-size: 0.9rem;\n}\n\n${o} .app-modal-form input,\n${o} .app-modal-form select {\n width: 100%;\n padding: 0.75rem;\n border-radius: var(--radius);\n border: 1px solid var(--border);\n background: var(--bg-app);\n color: var(--text-main);\n font-family: inherit;\n font-size: 1rem;\n box-sizing: border-box;\n transition: border-color 0.2s, box-shadow 0.2s;\n}\n\n${o} .app-modal-form input:focus,\n${o} .app-modal-form select:focus {\n outline: none;\n border-color: var(--primary);\n box-shadow: 0 0 0 2px rgb(71 85 105 / 0.12);\n}\n\n${o} .app-modal-form input:disabled,\n${o} .app-modal-form select:disabled {\n background: #f1f5f9;\n color: var(--text-muted);\n cursor: not-allowed;\n}\n\n${o} .app-modal-actions {\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-end;\n gap: 0.75rem;\n margin-top: 0.5rem;\n}\n\n${o} .app-modal-actions button + button,\n${o} .app-modal-danger-actions button + button {\n margin-left: 0;\n}\n\n${o} .app-modal-danger {\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n gap: 1rem;\n padding: 1rem;\n border: 1px solid #fecaca;\n border-radius: var(--radius);\n background: #fff1f2;\n align-items: flex-start;\n}\n\n${o} .app-modal-danger p {\n margin: 0;\n color: var(--text-main);\n}\n\n${o} .app-modal-danger-actions {\n display: flex;\n flex-wrap: wrap;\n gap: 0.75rem;\n}\n\n@media (max-width: 960px) {\n ${o} .app-modal-head,\n ${o} .app-modal-danger {\n flex-direction: column;\n align-items: flex-start;\n }\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.Modal=function({title:e="",body:n=null,onClose:o=null}){return t("shared-modal",i),r("div",{className:"app-modal-backdrop",[a]:"",onClick:e=>{e.target===e.currentTarget&&o&&o()}},r("div",{className:"card app-modal-card"},r("div",{className:"app-modal-head"},r("div",null,r("h2",{className:"app-modal-title"},e)),r("button",{type:"button",className:"app-modal-close",onClick:o,"aria-label":"Close dialog"},"x")),n))}}(),function(){const e=window.SharedUI=window.SharedUI||{},n=window.RegistryApp=window.RegistryApp||{},{h:r,ensureScopedStyle:t}=n.runtime,a="data-ui-panel-card",o=`[${a}]`,i=`\n${o} {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n}\n\n${o} .org-panel-head {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 1rem;\n margin-bottom: 1.5rem;\n}\n\n${o} .org-panel-body {\n display: flex;\n flex: 1 1 auto;\n flex-direction: column;\n min-height: 0;\n}\n\n${o} .org-eyebrow {\n font-size: 0.8rem;\n font-weight: 700;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: var(--text-muted);\n margin-bottom: 0.4rem;\n}\n\n${o} .org-panel-title {\n margin: 0;\n color: var(--primary-hover);\n font-size: 1.45rem;\n}\n\n${o} .org-panel-subtitle {\n margin: 0.35rem 0 0;\n color: var(--text-muted);\n font-size: 0.95rem;\n}\n\n@media (max-width: 960px) {\n ${o} .org-panel-head {\n flex-direction: column;\n align-items: flex-start;\n }\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.PanelCard=function({className:e="",eyebrow:n="",title:o="",subtitle:s="",headerExtras:l=null,body:d=null,rootProps:c={}}){const m=["card org-panel",e].filter(Boolean).join(" ");return t("shared-panel-card",i),r("section",{className:m,[a]:"",...c},r("div",{className:"org-panel-head"},r("div",null,n?r("div",{className:"org-eyebrow"},n):null,r("h2",{className:"org-panel-title"},o),s?r("p",{className:"org-panel-subtitle"},s):null),l),r("div",{className:"org-panel-body"},d))}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{h:n,ensureScopedStyle:r}=e.runtime,t="data-ui-metric-card",a=`[${t}]`,o=`\n${a} {\n display: flex;\n flex-direction: column;\n gap: 0.45rem;\n padding: 1rem;\n border-radius: var(--radius);\n border: 1px solid var(--border);\n background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);\n}\n\n${a}:nth-child(4n + 2),\n${a}:nth-child(4n + 3) {\n background: linear-gradient(180deg, rgb(248 250 252) 0%, rgb(226 232 240) 100%);\n border-color: rgb(100 116 139 / 0.35);\n box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.6);\n}\n\n${a} .org-metric-label {\n font-size: 0.76rem;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--text-muted);\n}\n\n${a} .org-metric-value {\n font-size: 1.8rem;\n color: var(--primary-hover);\n line-height: 1.1;\n}\n\n${a}:nth-child(4n + 2) .org-metric-value,\n${a}:nth-child(4n + 3) .org-metric-value {\n color: #334155;\n}\n\n${a} .org-metric-note {\n color: var(--text-muted);\n font-size: 0.9rem;\n}\n\n@media (max-width: 960px) {\n ${a}:nth-child(4n + 3) {\n background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);\n border-color: var(--border);\n box-shadow: none;\n }\n\n ${a}:nth-child(4n + 3) .org-metric-value {\n color: var(--primary-hover);\n }\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.MetricCard=function(e,a,i){return r("portal-metric-card",o),n("div",{className:"org-metric-card",[t]:""},n("span",{className:"org-metric-label"},e),n("strong",{className:"org-metric-value"},a),n("span",{className:"org-metric-note"},i))}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{h:n,ensureScopedStyle:r}=e.runtime,t="data-ui-simple-stat",a=`[${t}]`,o=`\n${a} {\n display: flex;\n flex-direction: column;\n gap: 0.2rem;\n min-width: 90px;\n}\n\n${a} .org-simple-label {\n font-size: 0.72rem;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--text-muted);\n}\n\n${a} .org-simple-value {\n font-size: 0.95rem;\n color: var(--text-main);\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.SimpleStat=function(e,a){return r("portal-simple-stat",o),n("div",{className:"org-simple-stat",[t]:""},n("span",{className:"org-simple-label"},e),n("strong",{className:"org-simple-value"},a))}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{h:n,ensureScopedStyle:r}=e.runtime,{portalData:t}=e.data,a=e.store,o=e.getters,i="data-ui-overview-card",s=`[${i}]`,l=`\n${s} .org-hero-grid {\n display: grid;\n grid-template-columns: 1.3fr 1fr;\n gap: 1.5rem;\n align-items: start;\n}\n\n${s} .org-summary {\n margin: 0;\n font-size: 1.05rem;\n color: var(--text-main);\n}\n\n${s} .org-meta-row {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 1rem;\n margin-top: 1.5rem;\n}\n\n${s} .org-meta-item {\n display: flex;\n flex-direction: column;\n gap: 0.4rem;\n padding: 1rem;\n border: 1px solid var(--border);\n border-radius: var(--radius);\n background: #f8fafc;\n}\n\n${s} .org-meta-item:nth-child(even) {\n background: linear-gradient(180deg, rgb(241 245 249) 0%, rgb(226 232 240) 100%);\n border-color: rgb(148 163 184 / 0.45);\n}\n\n${s} .org-meta-label {\n font-size: 0.76rem;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--text-muted);\n}\n\n${s} .org-meta-value {\n font-size: 1rem;\n font-weight: 600;\n color: var(--primary-hover);\n}\n\n${s} .org-metric-grid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 1rem;\n}\n\n@media (max-width: 960px) {\n ${s} .org-hero-grid,\n ${s} .org-meta-row,\n ${s} .org-metric-grid {\n grid-template-columns: 1fr;\n }\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.OverviewCard=function(){const s=e.componentFns.MetricCard,d=window.SharedUI.componentFns.PanelCard,c=o.getAssetReadiness(),m=t.org.headquarters||"ArmA Verse",g=a.getAssets().length,p=a.getFleet().length,u=a.getFunds(),f=a.getMembers().length,b=a.getReputation();return r("portal-overview-card",l),d({className:"org-span-12",eyebrow:t.org.tag,title:"Organization Overview",rootProps:{[i]:""},body:n("div",{className:"org-hero-grid"},n("div",{className:"org-hero-copy"},n("p",{className:"org-summary"},t.org.type," operating from ",m,". Treasury, fleet status, inventory, and roster management are surfaced here first."),n("div",{className:"org-meta-row"},n("div",{className:"org-meta-item"},n("span",{className:"org-meta-label"},"Director"),n("span",{className:"org-meta-value"},o.formatDisplayName(t.org.owner))),n("div",{className:"org-meta-item"},n("span",{className:"org-meta-label"},"Active Members"),n("span",{className:"org-meta-value"},`${f} total`)),n("div",{className:"org-meta-item"},n("span",{className:"org-meta-label"},"Fleet Readiness"),n("span",{className:"org-meta-value"},null===c?"N/A":`${c}%`)))),n("div",{className:"org-metric-grid"},s("Org Funds",o.formatCurrency(u),"Organization treasury balance"),s("Reputation",b,"Organization standing"),s("Asset Lines",g,"Tracked supply and equipment entries"),s("Fleet Vehicles",p,"Tracked air, ground, and naval vehicles")))})}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{h:n,ensureScopedStyle:r}=e.runtime,{portalData:t}=e.data,a=e.getters,o="data-ui-fleet-card",i=`[${o}]`,s=`\n${i} .org-simple-list {\n display: flex;\n flex-direction: column;\n flex: 1;\n gap: 0.85rem;\n min-height: 0;\n overflow: auto;\n padding-right: 0.35rem;\n scrollbar-width: thin;\n scrollbar-color: #94a3b8 #e2e8f0;\n}\n\n${i} .org-simple-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 1rem;\n padding: 1rem;\n border: 1px solid var(--border);\n border-radius: var(--radius);\n background: #f8fafc;\n}\n\n${i} .org-simple-row:nth-child(even) {\n background: linear-gradient(180deg, rgb(248 250 252) 0%, rgb(241 245 249) 100%);\n border-color: rgb(148 163 184 / 0.45);\n}\n\n${i} .org-simple-name {\n color: var(--primary-hover);\n}\n\n${i} .org-simple-meta {\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-end;\n gap: 1rem;\n}\n\n@media (max-width: 960px) {\n ${i} .org-simple-row {\n flex-direction: column;\n align-items: flex-start;\n }\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.FleetCard=function(){const t=window.SharedUI.componentFns.PanelCard,i=e.componentFns.SimpleStat,l=e.store.getFleet();return r("portal-fleet-card",s),t({className:"org-scroll-panel org-span-7",title:"Fleet",subtitle:"Individual vehicles with type, status, and overall damage.",rootProps:{[o]:""},body:n("div",{className:"org-simple-list"},...l.map(e=>n("article",{className:"org-simple-row"},n("strong",{className:"org-simple-name"},e.name),n("div",{className:"org-simple-meta"},i("Type",a.formatVehicleType(e.type)),i("Status",e.status),i("Damage",e.damage)))))})}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{h:n,ensureScopedStyle:r,createSignal:t}=e.runtime,{portalData:a}=e.data,o=e.store,i=e.getters,s=e.actions,l="data-ui-treasury-card",d=`[${l}]`,[c,m]=t("overview"),[g,p]=t(!1),u=`\n${d} .org-treasury-menu {\n position: relative;\n}\n\n${d} .org-menu-btn {\n width: 2.75rem;\n height: 2.75rem;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n border: 1px solid var(--border);\n background: #f8fafc;\n color: var(--text-muted);\n}\n\n${d} .org-menu-btn:hover {\n color: var(--primary-hover);\n border-color: rgb(148 163 184 / 0.65);\n}\n\n${d} .org-menu-btn svg {\n width: 1.1rem;\n height: 1.1rem;\n}\n\n${d} .org-menu-dropdown {\n position: absolute;\n top: calc(100% + 0.6rem);\n right: 0;\n min-width: 10.5rem;\n padding: 0.45rem;\n border: 1px solid var(--border);\n border-radius: var(--radius);\n background: #fff;\n box-shadow: 0 12px 28px rgb(15 23 42 / 0.12);\n display: flex;\n flex-direction: column;\n gap: 0.35rem;\n z-index: 5;\n}\n\n${d} .org-menu-option + .org-menu-option {\n margin-left: 0;\n}\n\n${d} .org-menu-option {\n width: 100%;\n justify-content: flex-start;\n background: transparent;\n color: var(--text-main);\n border: 1px solid transparent;\n}\n\n${d} .org-menu-option:hover {\n background: #f8fafc;\n border-color: rgb(148 163 184 / 0.35);\n}\n\n${d} .org-menu-option.is-active {\n background: rgb(226 232 240 / 0.7);\n color: var(--primary-hover);\n border-color: rgb(148 163 184 / 0.35);\n}\n\n${d} .org-finance-meta {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 1rem;\n margin-bottom: 1.5rem;\n}\n\n${d} .org-finance-meta > div {\n padding: 1rem;\n border: 1px solid var(--border);\n border-radius: var(--radius);\n background: #f8fafc;\n display: flex;\n flex-direction: column;\n gap: 0.4rem;\n}\n\n${d} .org-meta-label {\n font-size: 0.76rem;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--text-muted);\n}\n\n${d} .org-action-grid {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n margin-bottom: 1rem;\n}\n\n${d} .org-action-grid button + button {\n margin-left: 0;\n}\n\n${d} .org-action-grid button {\n width: 100%;\n}\n\n${d} .org-access-note {\n margin: 0 0 1rem;\n color: var(--text-muted);\n font-size: 0.95rem;\n}\n\n${d} .org-credit-summary {\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n padding: 0.85rem 1rem;\n border: 1px solid var(--border);\n border-radius: var(--radius);\n background: #f8fafc;\n}\n\n${d} .org-credit-summary strong {\n font-size: 1rem;\n}\n\n${d} .org-credit-summary span:last-child {\n font-size: 0.92rem;\n line-height: 1.45;\n}\n\n${d} .org-credit-lines-list {\n display: flex;\n flex-direction: column;\n gap: 0.85rem;\n}\n\n${d} .org-treasury-body {\n display: flex;\n flex: 1;\n flex-direction: column;\n gap: 1rem;\n min-height: 0;\n overflow: auto;\n padding-right: 0.35rem;\n scrollbar-width: thin;\n scrollbar-color: #94a3b8 #e2e8f0;\n}\n\n${d} .org-credit-line-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 1rem;\n padding: 1rem;\n border: 1px solid var(--border);\n border-radius: var(--radius);\n background: #f8fafc;\n}\n\n${d} .org-credit-line-row:nth-child(even) {\n background: linear-gradient(180deg, rgb(248 250 252) 0%, rgb(241 245 249) 100%);\n border-color: rgb(148 163 184 / 0.45);\n}\n\n${d} .org-credit-line-member {\n display: flex;\n flex-direction: column;\n gap: 0.3rem;\n}\n\n${d} .org-credit-line-label {\n font-size: 0.76rem;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--text-muted);\n}\n\n${d} .org-credit-line-empty {\n padding: 1rem;\n border: 1px solid var(--border);\n border-radius: var(--radius);\n background: #f8fafc;\n color: var(--text-muted);\n}\n\n@media (max-width: 960px) {\n ${d} .org-finance-meta {\n grid-template-columns: 1fr;\n }\n\n ${d} .org-credit-line-row {\n flex-direction: column;\n align-items: flex-start;\n }\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.TreasuryCard=function(){const e=window.SharedUI.componentFns.PanelCard,t=o.getCreditLines(),a=o.getReputation(),d=i.canManageTreasury(),f=c(),b=g(),w=t.reduce((e,n)=>e+Number(n.availableAmount||n.amount||0),0),h=t.reduce((e,n)=>e+Number(n.amountDue||0),0),y=1===t.length?"1 active credit line":`${t.length} active credit lines`;return r("portal-treasury-card",u),e({className:"org-scroll-panel org-span-5",title:"Treasury",subtitle:"Organization funds, reputation and payouts.",headerExtras:n("div",{className:"org-treasury-menu"},n("button",{type:"button",className:"org-menu-btn",title:"Treasury views","aria-label":"Treasury views",onClick:()=>p(e=>!e)},n("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round","aria-hidden":"true"},n("line",{x1:"4",y1:"7",x2:"20",y2:"7"}),n("line",{x1:"4",y1:"12",x2:"20",y2:"12"}),n("line",{x1:"4",y1:"17",x2:"20",y2:"17"}))),b?n("div",{className:"org-menu-dropdown"},n("button",{type:"button",className:"overview"===f?"org-menu-option is-active":"org-menu-option",onClick:()=>{m("overview"),p(!1)}},"Overview"),n("button",{type:"button",className:"credit"===f?"org-menu-option is-active":"org-menu-option",onClick:()=>{m("credit"),p(!1)}},"Credit Lines")):null),rootProps:{[l]:""},body:n("div",{className:"org-treasury-body"},"credit"===f?t.length>0?n("div",{className:"org-credit-lines-list"},...t.map(e=>n("article",{className:"org-credit-line-row"},n("div",{className:"org-credit-line-member"},n("span",{className:"org-credit-line-label"},"Member"),n("strong",null,e.member)),n("div",{className:"org-credit-line-member"},n("span",{className:"org-credit-line-label"},"Available"),n("strong",null,i.formatCurrency(e.availableAmount||e.amount))),n("div",{className:"org-credit-line-member"},n("span",{className:"org-credit-line-label"},"Amount Due"),n("strong",null,i.formatCurrency(e.amountDue))),n("div",{className:"org-credit-line-member"},n("span",{className:"org-credit-line-label"},"Interest"),n("strong",null,`${Math.round(100*Number(e.interestRate||0))}%`))))):n("div",{className:"org-credit-line-empty"},"No active credit lines."):n("div",null,n("div",{className:"org-finance-meta"},n("div",null,n("span",{className:"org-meta-label"},"Funds"),n("strong",null,i.formatCurrency(o.getFunds()))),n("div",null,n("span",{className:"org-meta-label"},"Reputation"),n("strong",null,`${a}`)),n("div",null,n("span",{className:"org-meta-label"},"Reserved Credit"),n("strong",null,i.formatCurrency(w))),n("div",null,n("span",{className:"org-meta-label"},"Outstanding Due"),n("strong",null,i.formatCurrency(h)))),d?n("div",{className:"org-action-grid"},n("button",{type:"button",onClick:()=>s.openModal("payroll")},"Run Payroll"),n("button",{type:"button",className:"org-secondary-btn",onClick:()=>s.openModal("transfer")},"Send Funds"),n("button",{type:"button",className:"org-secondary-btn",onClick:()=>s.openModal("credit")},"Credit Line")):n("p",{className:"org-access-note"},"Only the organization leader or CEO can manage treasury actions."),n("div",{className:"org-credit-summary"},n("span",{className:"org-meta-label"},"Credit Line Status"),n("strong",null,y),n("span",null,t.length>0?"Open the Credit Lines tab to review reserved balances, due amounts, and member exposure.":"Assign a credit line to create the first approved member limit."))))})}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{h:n,ensureScopedStyle:r}=e.runtime,{portalData:t}=e.data,a=e.getters,o="data-ui-assets-card",i=`[${o}]`,s=`\n${i} .org-simple-list {\n display: flex;\n flex-direction: column;\n flex: 1;\n gap: 0.85rem;\n min-height: 0;\n overflow: auto;\n padding-right: 0.35rem;\n scrollbar-width: thin;\n scrollbar-color: #94a3b8 #e2e8f0;\n}\n\n${i} .org-simple-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 1rem;\n padding: 1rem;\n border: 1px solid var(--border);\n border-radius: var(--radius);\n background: #f8fafc;\n}\n\n${i} .org-simple-row:nth-child(even) {\n background: linear-gradient(180deg, rgb(248 250 252) 0%, rgb(241 245 249) 100%);\n border-color: rgb(148 163 184 / 0.45);\n}\n\n${i} .org-simple-name {\n color: var(--primary-hover);\n}\n\n${i} .org-simple-meta {\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-end;\n gap: 1rem;\n}\n\n@media (max-width: 960px) {\n ${i} .org-simple-row {\n flex-direction: column;\n align-items: flex-start;\n }\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.AssetsCard=function(){const t=window.SharedUI.componentFns.PanelCard,i=e.componentFns.SimpleStat,l=e.store.getAssets();return r("portal-assets-card",s),t({className:"org-scroll-panel org-span-7",title:"Assets",subtitle:"Inventory supplies and equipment with quantity totals.",rootProps:{[o]:""},body:n("div",{className:"org-simple-list"},...l.map(e=>n("article",{className:"org-simple-row"},n("strong",{className:"org-simple-name"},e.name),n("div",{className:"org-simple-meta"},i("Type",a.formatAssetType(e.type)),i("Quantity",e.quantity)))))})}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{h:n,ensureScopedStyle:r}=e.runtime,t=e.store,a=e.getters,o=e.actions,i="data-ui-members-card",s=`[${i}]`,l=`\n${s} .org-name-list {\n display: flex;\n flex-direction: column;\n flex: 1;\n gap: 0.85rem;\n min-height: 0;\n overflow: auto;\n padding-right: 0.35rem;\n scrollbar-width: thin;\n scrollbar-color: #94a3b8 #e2e8f0;\n}\n\n${s} .org-name-row {\n display: flex;\n align-items: center;\n justify-content: flex-start;\n gap: 1rem;\n padding: 1rem;\n border: 1px solid var(--border);\n border-radius: var(--radius);\n background: #f8fafc;\n}\n\n${s} .org-name-row:nth-child(even) {\n background: linear-gradient(180deg, rgb(248 250 252) 0%, rgb(241 245 249) 100%);\n border-color: rgb(148 163 184 / 0.45);\n}\n\n${s} .org-name-row button {\n margin-left: auto;\n}\n\n@media (max-width: 960px) {\n ${s} .org-name-row {\n flex-direction: column;\n align-items: flex-start;\n }\n\n ${s} .org-name-row button {\n margin-left: 0;\n }\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.MembersCard=function(){const e=window.SharedUI.componentFns.PanelCard,s=t.getMembers(),d=a.canManageMembers();return r("portal-members-card",l),e({className:"org-scroll-panel org-span-5",title:"Members",subtitle:"Current roster listing. The organization owner and your own member entry cannot be removed.",rootProps:{[i]:""},body:n("div",{className:"org-name-list"},...s.map(e=>{const r=d&&!a.isProtectedMember(e);return n("article",{className:"org-name-row"},n("strong",null,e.name),r?n("button",{type:"button",className:"org-danger-btn org-icon-btn",title:`Remove ${e.name}`,"aria-label":`Remove ${e.name}`,onClick:()=>o.removeMember(e)},n("svg",{className:"org-icon",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round","aria-hidden":"true"},n("path",{d:"M9 3h6"}),n("path",{d:"M4 7h16"}),n("path",{d:"M6 7l1 13h10l1-13"}),n("path",{d:"M10 11v6"}),n("path",{d:"M14 11v6"}))):null)}))})}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{h:n,ensureScopedStyle:r}=e.runtime,{portalData:t}=e.data,a="data-ui-activity-card",o=`[${a}]`,i=`\n${o} .org-activity-list {\n display: flex;\n flex-direction: column;\n flex: 1;\n gap: 0.85rem;\n min-height: 0;\n overflow: auto;\n padding-right: 0.35rem;\n scrollbar-width: thin;\n scrollbar-color: #94a3b8 #e2e8f0;\n}\n\n${o} .org-activity-row {\n padding: 1rem;\n border: 1px solid var(--border);\n border-left: 3px solid #94a3b8;\n border-radius: var(--radius);\n background: #f8fafc;\n}\n\n${o} .org-activity-row:nth-child(even) {\n background: linear-gradient(180deg, rgb(248 250 252) 0%, rgb(241 245 249) 100%);\n border-color: rgb(148 163 184 / 0.45);\n border-left-color: #64748b;\n}\n\n${o} .org-activity-row p {\n margin: 0;\n color: var(--text-main);\n}\n\n${o} .org-activity-time {\n display: inline-block;\n margin-bottom: 0.35rem;\n color: var(--text-muted);\n font-size: 0.8rem;\n font-weight: 700;\n letter-spacing: 0.05em;\n text-transform: uppercase;\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.ActivityCard=function(){const t=window.SharedUI.componentFns.PanelCard,o=e.store.getActivity();return r("portal-activity-card",i),t({className:"org-scroll-panel org-span-6",title:"Command Feed",subtitle:"Recent organization-level actions and updates.",rootProps:{[a]:""},body:n("div",{className:"org-activity-list"},...o.map(e=>n("article",{className:"org-activity-row"},n("span",{className:"org-activity-time"},e.time),n("p",null,e.text))))})}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{h:n,ensureScopedStyle:r}=e.runtime,t="data-ui-future-card",a=[{name:"Contracts Board",status:"Planned",detail:"Track payouts, assignments, and claim approvals."},{name:"Diplomacy",status:"Future Review",detail:"Possible future module pending a full design and scope review."},{name:"Logistics Queue",status:"Future Review",detail:"Possible future module pending a full design and scope review."},{name:"Permissions",status:"Future Review",detail:"Possible future module pending a full design and scope review."}],o=`[${t}]`,i=`\n${o} .org-roadmap-grid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 1rem;\n flex: 1;\n min-height: 0;\n overflow: auto;\n padding-right: 0.35rem;\n scrollbar-width: thin;\n scrollbar-color: #94a3b8 #e2e8f0;\n}\n\n${o} .org-roadmap-card {\n padding: 1rem;\n display: flex;\n flex-direction: column;\n gap: 0.7rem;\n border: 1px solid var(--border);\n border-radius: var(--radius);\n background: #f8fafc;\n}\n\n${o} .org-roadmap-card:nth-child(4n + 2),\n${o} .org-roadmap-card:nth-child(4n + 3) {\n background: linear-gradient(180deg, rgb(248 250 252) 0%, rgb(241 245 249) 100%);\n border-color: rgb(100 116 139 / 0.4);\n}\n\n${o} .org-roadmap-card p {\n margin: 0;\n color: var(--text-main);\n}\n\n${o} .org-list-tag {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 0.2rem 0.55rem;\n border-radius: 999px;\n font-size: 0.72rem;\n font-weight: 700;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n background: #e2e8f0;\n color: var(--primary-hover);\n}\n\n${o} .org-roadmap-card:nth-child(4n + 2) .org-list-tag,\n${o} .org-roadmap-card:nth-child(4n + 3) .org-list-tag {\n background: #cbd5e1;\n color: #1e293b;\n}\n\n@media (max-width: 960px) {\n ${o} .org-roadmap-grid {\n grid-template-columns: 1fr;\n }\n\n ${o} .org-roadmap-card:nth-child(4n + 3) {\n background: #f8fafc;\n border-color: var(--border);\n }\n\n ${o} .org-roadmap-card:nth-child(4n + 3) .org-list-tag {\n background: #e2e8f0;\n color: var(--primary-hover);\n }\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.FutureCard=function(){const e=window.SharedUI.componentFns.PanelCard;return r("portal-future-card",i),e({className:"org-scroll-panel org-span-6",title:"Expansion Slots",subtitle:"Potential modules are tagged by status such as Planned, In Design, In Review, and Future Review.",rootProps:{[t]:""},body:n("div",{className:"org-roadmap-grid"},...a.map(e=>n("article",{className:"org-roadmap-card"},n("span",{className:"org-list-tag"},e.status),n("strong",null,e.name),n("p",null,e.detail))))})}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{h:n,ensureScopedStyle:r}=e.runtime,t=e.getters,a=e.actions,o="data-ui-danger-card",i=`[${o}]`,s=`\n${i} {\n border-color: #fecaca;\n background: linear-gradient(180deg, #ffffff 0%, #fff7f7 100%);\n}\n\n${i} .org-danger-copy {\n margin-bottom: 1rem;\n}\n\n${i} .org-danger-copy strong,\n${i} .org-danger-copy p {\n display: block;\n}\n\n${i} .org-danger-copy p {\n margin: 0.4rem 0 0;\n color: var(--text-muted);\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.DangerCard=function(){const e=window.SharedUI.componentFns.PanelCard;return r("portal-danger-card",s),t.canDisbandOrg()?e({className:"org-span-12 org-danger-panel",title:"Organization Controls",subtitle:"Leader-only actions for membership and permanent organization removal.",rootProps:{[o]:""},body:n("div",null,n("div",{className:"org-danger-copy"},n("strong",null,"Disband organization"),n("p",null,"This removes the organization and revokes access to the portal for all members.")),n("button",{type:"button",className:"org-danger-btn",onClick:()=>a.openModal("disband")},"Disband Organization"))}):null}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{h:n}=e.runtime,{portalData:r}=e.data,t=e.store,a=e.actions;e.componentFns=e.componentFns||{},e.componentFns.ModalLayer=function(){const e=window.SharedUI.componentFns.Modal,o=t.getModal();if(!o)return null;const i=t.getMembers(),s=0===i.length?{disabled:!0}:{};let l="",d=null;return"payroll"===o.type?(l="Run Payroll",d=n("div",{className:"app-modal-form"},n("div",null,n("label",null,"Amount Per Member"),n("input",{id:"treasury-payroll-amount",type:"number",min:"1",placeholder:"500",autofocus:"true"})),n("div",{className:"app-modal-actions"},n("button",{type:"button",className:"org-secondary-btn",onClick:()=>a.closeModal()},"Cancel"),n("button",{type:"button",onClick:()=>{a.runPayroll(a.parseAmount(a.getInputValue("treasury-payroll-amount")))&&a.closeModal()}},"Run Payroll")))):"transfer"===o.type?(l="Send Funds",d=n("div",{className:"app-modal-form"},n("div",null,n("label",null,"Member"),n("select",{id:"treasury-transfer-member",...s},...i.map(e=>n("option",{value:e.name},e.name)))),n("div",null,n("label",null,"Amount"),n("input",{id:"treasury-transfer-amount",type:"number",min:"1",placeholder:"1500"})),n("div",{className:"app-modal-actions"},n("button",{type:"button",className:"org-secondary-btn",onClick:()=>a.closeModal()},"Cancel"),n("button",{type:"button",...s,onClick:()=>{a.sendFundsToMember(String(a.getInputValue("treasury-transfer-member")||""),a.parseAmount(a.getInputValue("treasury-transfer-amount")))&&a.closeModal()}},"Send Funds")))):"credit"===o.type?(l="Assign Credit Line",d=n("div",{className:"app-modal-form"},n("div",null,n("label",null,"Member"),n("select",{id:"treasury-credit-member",...s},...i.map(e=>n("option",{value:e.uid},e.name)))),n("div",null,n("label",null,"Credit Amount"),n("input",{id:"treasury-credit-amount",type:"number",min:"1",placeholder:"5000"})),n("div",{className:"app-modal-actions"},n("button",{type:"button",className:"org-secondary-btn",onClick:()=>a.closeModal()},"Cancel"),n("button",{type:"button",...s,onClick:()=>{a.grantCreditLine(String(a.getInputValue("treasury-credit-member")||""),a.parseAmount(a.getInputValue("treasury-credit-amount")))&&a.closeModal()}},"Assign Credit Line")))):"disband"===o.type?(l="Disband Organization",d=n("div",{className:"app-modal-danger"},n("p",null,"This action is permanent. Disband ",r.org.name,"?"),n("div",{className:"app-modal-danger-actions"},n("button",{type:"button",className:"org-secondary-btn",onClick:()=>a.closeModal()},"Cancel"),n("button",{type:"button",className:"org-danger-btn",onClick:()=>a.disbandOrganization()},"Confirm Disband")))):"leave"===o.type&&(l="Leave Organization",d=n("div",{className:"app-modal-danger"},n("p",null,"Leave ",r.org.name," and return to the default organization?"),n("div",{className:"app-modal-danger-actions"},n("button",{type:"button",className:"org-secondary-btn",onClick:()=>a.closeModal()},"Cancel"),n("button",{type:"button",className:"org-danger-btn",onClick:()=>a.leaveOrganization()},"Confirm Leave")))),e({title:l,body:d,onClose:()=>a.closeModal()})}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{h:n}=e.runtime,{portalData:r}=e.data,t=window.RegistryApp.store;e.componentFns=e.componentFns||{},e.componentFns.DisbandedView=function(){return(0,window.SharedUI.componentFns.PanelCard)({className:"org-span-12 org-empty-state",eyebrow:"Organization Removed",title:r.org.name,body:n("div",null,n("p",{className:"org-summary"},"This organization has been disbanded. Member access, assets, and fleet management are no longer available from this portal preview."),n("button",{type:"button",className:"org-secondary-btn",onClick:()=>t.setView("home")},"Return to Registry"))})}}(),function(){const e=window.OrgPortal=window.OrgPortal||{},{h:n,ensureScopedStyle:r}=e.runtime,{portalData:t,session:a}=e.data,o=e.store,i="[data-ui-portal-view]";r("portal-view",`\n ${i} {\n --org-row-card-max-height: 36rem;\n }\n\n ${i} .org-toast-stack {\n position: fixed;\n top: 1.5rem;\n right: 2rem;\n z-index: 20;\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n pointer-events: none;\n }\n\n ${i} .org-toast {\n max-width: 24rem;\n padding: 0.9rem 1rem;\n border-radius: var(--radius);\n border: 1px solid var(--border);\n background: #fff;\n box-shadow: 0 12px 28px rgb(15 23 42 / 0.14);\n font-size: 0.92rem;\n pointer-events: auto;\n }\n\n ${i} .org-toast.is-success {\n background: #ecfdf5;\n border-color: #bbf7d0;\n color: #166534;\n }\n\n ${i} .org-toast.is-error {\n background: #fef2f2;\n border-color: #fecaca;\n color: #991b1b;\n }\n\n ${i} .org-dashboard-grid {\n display: grid;\n grid-template-columns: repeat(12, minmax(0, 1fr));\n gap: 1.5rem;\n align-items: stretch;\n }\n\n ${i} .org-panel {\n margin-bottom: 0;\n text-align: left;\n }\n\n ${i} .org-scroll-panel {\n display: flex;\n flex-direction: column;\n min-height: 0;\n max-height: var(--org-row-card-max-height);\n overflow: hidden;\n }\n\n ${i} .org-island-root {\n display: flex;\n align-self: stretch;\n min-height: 0;\n min-width: 0;\n }\n\n ${i} .org-island-root > .org-panel {\n height: 100%;\n width: 100%;\n }\n\n ${i} .org-span-12 {\n grid-column: span 12;\n }\n\n ${i} .org-span-7 {\n grid-column: span 7;\n }\n\n ${i} .org-span-6 {\n grid-column: span 6;\n }\n\n ${i} .org-span-5 {\n grid-column: span 5;\n }\n\n @media (max-width: 960px) {\n ${i} .org-toast-stack {\n top: 1rem;\n right: 1rem;\n left: 1rem;\n }\n\n ${i} .org-toast {\n max-width: none;\n }\n\n ${i} .org-span-12,\n ${i} .org-span-7,\n ${i} .org-span-6,\n ${i} .org-span-5 {\n grid-column: span 12;\n }\n\n ${i} .org-scroll-panel {\n max-height: none;\n }\n\n }\n `),e.components=e.components||{},e.componentFns=e.componentFns||{},e.componentFns.TreasuryNoticeLayer=function(){const e=o.getTreasuryNotice();return e.text?n("div",{className:"org-toast-stack"},n("div",{className:"error"===e.type?"org-toast is-error":"org-toast is-success"},e.text)):null},e.components.App=function(){const r=window.SharedUI.componentFns.Hero,i=window.SharedUI.componentFns.Footer,s=e.componentFns.FutureCard,l=e.componentFns.DangerCard,d=e.componentFns.DisbandedView,c=[{title:"Organization Controls",items:["Roster Management","Fleet Assignment","Treasury Permissions","Asset Registry"]},{title:"Planned Extensions",items:["Contracts Board","Diplomacy Layer","Procurement Queue","Reputation History"]}];return o.getOrgDisbanded()?n("main",{"data-ui-portal-view":""},n("div",{className:"container"},n("div",{className:"org-dashboard-grid"},r({kicker:t.org.tag,title:t.org.name,subtitle:"Player organization command portal",meta:`${a.actorName} - ${a.role}`}),d())),n("div",{id:"org-portal-modal-root"}),i({sections:c})):n("main",{"data-ui-portal-view":""},n("div",{id:"org-portal-toast-root"}),n("div",{className:"container"},n("div",{className:"org-dashboard-grid"},r({kicker:t.org.tag,title:t.org.name,subtitle:"Player organization command portal",meta:`${a.actorName} - ${a.role}`}),n("div",{className:"org-island-root org-span-12",id:"org-overview-card-root"}),n("div",{className:"org-island-root org-span-7",id:"org-fleet-card-root"}),n("div",{className:"org-island-root org-span-5",id:"org-treasury-card-root"}),n("div",{className:"org-island-root org-span-5",id:"org-members-card-root"}),n("div",{className:"org-island-root org-span-7",id:"org-assets-card-root"}),n("div",{className:"org-island-root org-span-6",id:"org-activity-card-root"}),s(),l())),n("div",{id:"org-portal-modal-root"}),i({sections:c}))}}(),function(){const e=window.RegistryApp=window.RegistryApp||{},{h:n,ensureScopedStyle:r}=e.runtime,t=e.store,a=e.bridge,o="data-ui-registration-view",i=`[${o}]`,s=`\n${i} {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 2rem;\n align-items: center;\n width: 100%;\n}\n\n${i} .info-panel {\n text-align: left;\n padding: 1rem;\n}\n\n${i} .create-feature-list {\n text-align: left;\n margin-top: 1.5rem;\n list-style-type: none;\n padding: 0;\n}\n\n${i} .create-feature-item {\n margin-bottom: 0.5rem;\n display: flex;\n align-items: center;\n gap: 0.5rem;\n}\n\n${i} .create-feature-icon {\n width: 1.2rem;\n height: 1.2rem;\n flex-shrink: 0;\n}\n\n${i} .price-tag {\n margin-top: 2rem;\n padding: 1rem;\n background: var(--bg-app);\n border-radius: var(--radius);\n border: 1px solid var(--border);\n}\n\n${i} .price-label {\n display: block;\n font-size: 0.9rem;\n color: var(--text-muted);\n}\n\n${i} .price-value {\n display: block;\n font-size: 2rem;\n font-weight: 700;\n color: var(--primary);\n}\n\n${i} .form-panel {\n margin: 0;\n}\n\n${i} .app-form {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n text-align: left;\n}\n\n${i} .app-form label {\n display: block;\n margin-bottom: 0.5rem;\n color: var(--text-muted);\n font-weight: 500;\n font-size: 0.9rem;\n}\n\n${i} .app-form input,\n${i} .app-form select {\n width: 100%;\n padding: 0.75rem;\n border-radius: var(--radius);\n border: 1px solid var(--border);\n background: var(--bg-app);\n color: var(--text-main);\n font-family: inherit;\n font-size: 1rem;\n box-sizing: border-box;\n transition: border-color 0.2s;\n}\n\n${i} .app-form input:focus,\n${i} .app-form select:focus {\n outline: none;\n border-color: var(--primary);\n box-shadow: 0 0 0 2px rgb(59 130 246 / 0.1);\n}\n\n${i} .form-actions {\n margin-top: 1rem;\n display: flex;\n flex-direction: column;\n gap: 1rem;\n align-items: center;\n}\n\n${i} .submit-btn {\n width: 100%;\n}\n\n${i} .cancel-link {\n font-size: 0.9rem;\n color: var(--text-muted);\n cursor: pointer;\n text-decoration: underline;\n}\n\n${i} .cancel-link:hover {\n color: var(--primary);\n}\n\n${i} .form-feedback {\n padding: 0.85rem 1rem;\n border-radius: var(--radius);\n font-size: 0.92rem;\n}\n\n${i} .form-feedback.is-error {\n background: #fef2f2;\n border: 1px solid #fecaca;\n color: #991b1b;\n}\n\n@media (max-width: 960px) {\n ${i} {\n grid-template-columns: 1fr;\n }\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.RegistrationView=function(){const e=t.getIsCreating(),i=t.getCreateError();r("main-registration-view",s);return n("div",{className:"split-container",[o]:""},n("div",{className:"info-panel"},n("h2",null,"Registration Details"),n("p",null,"Complete the form to add your organization to the Global Organization Registry."),n("ul",{className:"create-feature-list"},n("li",{className:"create-feature-item"},n("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"#10b981","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round",className:"create-feature-icon"},n("path",{d:"M20 6L9 17l-5-5"})),"Official Organization Designator"),n("li",{className:"create-feature-item"},n("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"#10b981","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round",className:"create-feature-icon"},n("path",{d:"M20 6L9 17l-5-5"})),"Secure Comms Channel"),n("li",{className:"create-feature-item"},n("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"#10b981","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round",className:"create-feature-icon"},n("path",{d:"M20 6L9 17l-5-5"})),"Deployment Roster Access"),n("li",{className:"create-feature-item"},n("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"#10b981","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round",className:"create-feature-icon"},n("path",{d:"M20 6L9 17l-5-5"})),"After-Action Report Tools")),n("div",{className:"price-tag"},n("span",{className:"price-label"},"Registration Fee"),n("span",{className:"price-value"},"$50,000"))),n("div",{className:"form-panel card"},n("h2",null,"Organization Registration"),n("div",{className:"app-form"},n("div",null,n("label",null,"Organization Name"),n("input",{id:"org-create-name",type:"text",placeholder:"e.g. Task Force 141"})),n("div",null,n("label",null,"Organization Type"),n("select",{id:"org-create-type"},n("option",{value:"infantry"},"Infantry / Milsim"),n("option",{value:"aviation"},"Aviation Wing"),n("option",{value:"pmc"},"Private Military Company"),n("option",{value:"support"},"Logistics & Support"))),n("div",{className:"form-actions"},i?n("div",{className:"form-feedback is-error"},i):null,n("button",{type:"button",className:"submit-btn",disabled:e,onClick:()=>{const e={orgName:String(document.getElementById("org-create-name")?.value||"").trim(),type:String(document.getElementById("org-create-type")?.value||"")};a&&"function"==typeof a.requestCreateOrg?a.requestCreateOrg(e):t.failCreate("Registration bridge is not available.")}},e?"Submitting Registration...":"Submit Registration"),n("span",{className:"cancel-link",onClick:()=>t.setView("home")},"Cancel / Return to Main")))))}}(),function(){const e=window.RegistryApp=window.RegistryApp||{},{h:n,ensureScopedStyle:r}=e.runtime,t=e.store,a=e.bridge,o="data-ui-home-view",i=`[${o}]`,s=`\n${i} {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 2rem;\n margin-bottom: 2rem;\n}\n\n${i} .home-feedback {\n padding: 0.85rem 1rem;\n border-radius: var(--radius);\n font-size: 0.92rem;\n background: #fef2f2;\n border: 1px solid #fecaca;\n color: #991b1b;\n}\n\n@media (max-width: 960px) {\n ${i} {\n grid-template-columns: 1fr;\n }\n}\n`;e.componentFns=e.componentFns||{},e.componentFns.HomeView=function(){const e=t.getIsAuthenticating(),i=t.getLoginError();return r("main-home-view",s),n("div",{className:"content",[o]:""},n("div",{className:"card"},n("h2",null,"Create Organization"),n("p",null,"Establish your Task Force, PMC, or Milsim unit with the Global Organization Network. Receive your official unit designator and TO&E authorization instantly."),n("button",{onClick:()=>t.setView("create")},"Register")),n("div",{className:"card"},n("h2",null,"Organization Portal"),n("p",null,"Access your unit dashboard to modify rosters, adjust active deployments, and submit after-action reports through the secure field uplink."),i?n("div",{className:"home-feedback"},i):null,n("button",{disabled:e,onClick:()=>{a?a.requestLogin({}):t.failLogin("Login bridge is not available.")}},e?"Opening Portal...":"Login")))}}(),function(){const e=window.RegistryApp=window.RegistryApp||{},{h:n}=e.runtime,r=e.store;e.components=e.components||{},e.components.App=function(){const t=window.SharedUI.componentFns.Navbar,a=window.SharedUI.componentFns.Header,o=window.SharedUI.componentFns.Footer,i=window.SharedUI.componentFns.WindowTitleBar,s=e.componentFns.HomeView,l=e.componentFns.RegistrationView,d=window.OrgPortal&&window.OrgPortal.components?window.OrgPortal.components.App:null,c=r.getView(),m=window.OrgPortal&&window.OrgPortal.getters?window.OrgPortal.getters:null,g=window.OrgPortal&&window.OrgPortal.actions?window.OrgPortal.actions:null,p="create"===c?"Organization Registration":"portal"===c?"Organization Portal":"Entry Hub";function u(){e.bridge&&"function"==typeof e.bridge.close?e.bridge.close({}):r.setView("home")}if("portal"===c&&d){const e=m&&"function"==typeof m.canLeaveOrg&&m.canLeaveOrg();return n("div",{className:"app-shell"},i({kicker:"FORGE ORBIS",title:"Global Organization Network",onClose:u,closeLabel:"Close organization interface"}),t({title:"Global Organization Network",viewLabel:p,actionLabel:e?"Leave Organization":"",onAction:e&&g&&"function"==typeof g.openModal?()=>g.openModal("leave"):null}),n("div",{id:"org-portal-frame-root"}))}let f;return"home"===c?f=s():"create"===c&&(f=l()),n("div",{className:"app-shell"},i({kicker:"FORGE ORBIS",title:"Global Organization Network",onClose:u,closeLabel:"Close organization interface"}),n("main",null,t({title:"Global Organization Network",viewLabel:p}),n("div",{className:"container"},a({title:"Global Organization Network",onTitleClick:()=>r.setView("home")}),f),o({sections:[{title:"Registry Resources",items:["Registration Guidelines","Tax & Fee Schedule","Legal Compliance","Trademark Database"]},{title:"Bureau Support",items:["Office: Sector 7 Admin Block","Hours: 0800 - 1600 (GST)","Helpdesk: 555-01-REGISTRY","support@org-bureau.gov"]}]})))}}(),function(){const e=window.ForgeWebUI,n=window.RegistryApp,r=window.OrgPortal,t=[{id:"org-portal-frame-root",preserveScroll:!0,render:()=>r.components.App()},{id:"org-portal-toast-root",preserveScroll:!1,render:()=>r.componentFns.TreasuryNoticeLayer()},{id:"org-overview-card-root",preserveScroll:!1,render:()=>r.componentFns.OverviewCard()},{id:"org-fleet-card-root",preserveScroll:!0,render:()=>r.componentFns.FleetCard()},{id:"org-treasury-card-root",preserveScroll:!1,render:()=>r.componentFns.TreasuryCard()},{id:"org-members-card-root",preserveScroll:!0,render:()=>r.componentFns.MembersCard()},{id:"org-assets-card-root",preserveScroll:!0,render:()=>r.componentFns.AssetsCard()},{id:"org-activity-card-root",preserveScroll:!0,render:()=>r.componentFns.ActivityCard()},{id:"org-portal-modal-root",preserveScroll:!1,render:()=>r.componentFns.ModalLayer()}];e.createApp({name:"org",root:"#app",setup({root:r}){const a=function(){const n=new Map;return{sync:function(){t.forEach(r=>{const t=document.getElementById(r.id),a=n.get(r.id);if(!t)return void(a&&(a.handle.dispose(),n.delete(r.id)));if(a&&a.container===t)return;a&&a.handle.dispose();const o=e.mount(t,r.render,{preserveScroll:r.preserveScroll});n.set(r.id,{container:t,handle:o})})}}}();e.mount(r,()=>n.components.App(),{preserveScroll:!1}),n.bridge.ready({loaded:!0}),e.effect(()=>{n.store.getView(),requestAnimationFrame(()=>{a.sync()})})}}).start()}(); \ No newline at end of file diff --git a/arma/client/addons/org/ui/src/bridge.js b/arma/client/addons/org/ui/src/bridge.js index cfa10ae..d5fab25 100644 --- a/arma/client/addons/org/ui/src/bridge.js +++ b/arma/client/addons/org/ui/src/bridge.js @@ -136,8 +136,18 @@ OrgPortal.store.setCreditLines((currentLines) => { const nextLine = { - amount: payloadData.amount || 0, + amount: payloadData.availableAmount || payloadData.amount || 0, + amountDue: payloadData.amountDue || 0, + approvedAmount: + payloadData.approvedAmount || + payloadData.availableAmount || + payloadData.amount || + 0, + availableAmount: + payloadData.availableAmount || payloadData.amount || 0, + interestRate: payloadData.interestRate || 0.1, member: payloadData.memberName || "", + outstandingPrincipal: payloadData.outstandingPrincipal || 0, uid: payloadData.memberUid || "", }; const matchIndex = currentLines.findIndex( diff --git a/arma/client/addons/org/ui/src/components/portal/treasuryCard.js b/arma/client/addons/org/ui/src/components/portal/treasuryCard.js index 0de5144..6b95c49 100644 --- a/arma/client/addons/org/ui/src/components/portal/treasuryCard.js +++ b/arma/client/addons/org/ui/src/components/portal/treasuryCard.js @@ -215,6 +215,15 @@ ${scopeSelector} .org-credit-line-empty { const allowTreasuryActions = getters.canManageTreasury(); const activeTab = getTreasuryTab(); const isMenuOpen = getTreasuryMenuOpen(); + const totalReserved = creditLines.reduce( + (sum, line) => + sum + Number(line.availableAmount || line.amount || 0), + 0, + ); + const totalDue = creditLines.reduce( + (sum, line) => sum + Number(line.amountDue || 0), + 0, + ); const activeCreditLabel = creditLines.length === 1 ? "1 active credit line" @@ -331,16 +340,59 @@ ${scopeSelector} .org-credit-line-empty { className: "org-credit-line-label", }, - "Amount", + "Available", ), h( "strong", null, getters.formatCurrency( - line.amount, + line.availableAmount || + line.amount, ), ), ), + h( + "div", + { + className: + "org-credit-line-member", + }, + h( + "span", + { + className: + "org-credit-line-label", + }, + "Amount Due", + ), + h( + "strong", + null, + getters.formatCurrency( + line.amountDue, + ), + ), + ), + h( + "div", + { + className: + "org-credit-line-member", + }, + h( + "span", + { + className: + "org-credit-line-label", + }, + "Interest", + ), + h( + "strong", + null, + `${Math.round(Number(line.interestRate || 0) * 100)}%`, + ), + ), ), ), ) @@ -379,6 +431,34 @@ ${scopeSelector} .org-credit-line-empty { ), h("strong", null, `${reputation}`), ), + h( + "div", + null, + h( + "span", + { className: "org-meta-label" }, + "Reserved Credit", + ), + h( + "strong", + null, + getters.formatCurrency(totalReserved), + ), + ), + h( + "div", + null, + h( + "span", + { className: "org-meta-label" }, + "Outstanding Due", + ), + h( + "strong", + null, + getters.formatCurrency(totalDue), + ), + ), ), allowTreasuryActions ? h( @@ -432,7 +512,7 @@ ${scopeSelector} .org-credit-line-empty { "span", null, creditLines.length > 0 - ? "Open the Credit Lines tab to review assigned members and amounts." + ? "Open the Credit Lines tab to review reserved balances, due amounts, and member exposure." : "Assign a credit line to create the first approved member limit.", ), ), diff --git a/arma/server/addons/bank/XEH_preInit.sqf b/arma/server/addons/bank/XEH_preInit.sqf index c29e294..33960da 100644 --- a/arma/server/addons/bank/XEH_preInit.sqf +++ b/arma/server/addons/bank/XEH_preInit.sqf @@ -49,3 +49,9 @@ PREP_RECOMPILE_END; GVAR(BankStore) call ["depositEarnings", [_uid, _amount]]; }] call CFUNC(addEventHandler); + +[QGVAR(requestRepayCreditLine), { + params [["_uid", "", [""]], ["_amount", 0, [0]]]; + + GVAR(BankStore) call ["repayCreditLine", [_uid, _amount]]; +}] call CFUNC(addEventHandler); diff --git a/arma/server/addons/bank/functions/fnc_initPayloadBuilder.sqf b/arma/server/addons/bank/functions/fnc_initPayloadBuilder.sqf index 517bff3..6cd9bcd 100644 --- a/arma/server/addons/bank/functions/fnc_initPayloadBuilder.sqf +++ b/arma/server/addons/bank/functions/fnc_initPayloadBuilder.sqf @@ -48,7 +48,18 @@ GVAR(BankPayloadBuilder) = createHashMapObject [[ ["resolveOrgState", compileFinal { params [["_uid", "", [""]]]; - private _defaultState = createHashMapFromArray [["funds", 0], ["name", ""]]; + private _defaultCreditLine = createHashMapFromArray [ + ["approvedAmount", 0], + ["availableAmount", 0], + ["outstandingPrincipal", 0], + ["interestRate", 0.1], + ["amountDue", 0] + ]; + private _defaultState = createHashMapFromArray [ + ["funds", 0], + ["name", ""], + ["creditLine", _defaultCreditLine] + ]; if (_uid isEqualTo "") exitWith { _defaultState }; private _actor = EGVAR(actor,Registry) getOrDefault [_uid, createHashMap]; @@ -61,7 +72,33 @@ GVAR(BankPayloadBuilder) = createHashMapObject [[ }; if (_org isEqualTo createHashMap) exitWith { _defaultState }; - createHashMapFromArray [["funds", _org getOrDefault ["funds", 0]], ["name", _org getOrDefault ["name", ""]]] + private _creditLines = _org getOrDefault ["credit_lines", createHashMap]; + if !(_creditLines isEqualType createHashMap) then { + _creditLines = createHashMap; + }; + + private _creditLine = _creditLines getOrDefault [_uid, createHashMap]; + if !(_creditLine isEqualType createHashMap) then { + _creditLine = createHashMap; + }; + + createHashMapFromArray [ + ["funds", _org getOrDefault ["funds", 0]], + ["name", _org getOrDefault ["name", ""]], + ["creditLine", createHashMapFromArray [ + ["approvedAmount", _creditLine getOrDefault [ + "approved_amount", + _creditLine getOrDefault ["amount", 0] + ]], + ["availableAmount", _creditLine getOrDefault [ + "available_amount", + _creditLine getOrDefault ["amount", 0] + ]], + ["outstandingPrincipal", _creditLine getOrDefault ["outstanding_principal", 0]], + ["interestRate", _creditLine getOrDefault ["interest_rate", 0.1]], + ["amountDue", _creditLine getOrDefault ["amount_due", 0]] + ]] + ] }], ["buildTransferTargets", compileFinal { params [["_sourceUid", "", [""]]]; @@ -101,6 +138,7 @@ GVAR(BankPayloadBuilder) = createHashMapObject [[ ["mode", _session getOrDefault ["mode", "bank"]], ["orgFunds", _orgState getOrDefault ["funds", 0]], ["orgName", _orgState getOrDefault ["name", ""]], + ["creditLine", _orgState getOrDefault ["creditLine", createHashMap]], ["playerName", _playerName], ["transferTargets", _self call ["buildTransferTargets", [_uid]]], ["uid", _uid] diff --git a/arma/server/addons/bank/functions/fnc_initStore.sqf b/arma/server/addons/bank/functions/fnc_initStore.sqf index 4ad374c..507f236 100644 --- a/arma/server/addons/bank/functions/fnc_initStore.sqf +++ b/arma/server/addons/bank/functions/fnc_initStore.sqf @@ -167,6 +167,83 @@ GVAR(BankBaseStore) = compileFinal createHashMapFromArray [ _result set ["patch", _patch]; _result }], + ["repayCreditLine", compileFinal { + params [["_uid", "", [""]], ["_amount", 0, [0]]]; + + if (_uid isEqualTo "" || { _amount <= 0 }) exitWith { + GVAR(BankMessenger) call ["sendAlert", [_uid, "error", "Enter a valid repayment amount."]]; + false + }; + + private _originalAccount = _self call ["loadHotBank", [_uid, false, ""]]; + if (_originalAccount isEqualTo createHashMap) then { + _originalAccount = _self call ["loadHotBank", [_uid, true, ""]]; + }; + if (_originalAccount isEqualTo createHashMap) exitWith { + GVAR(BankMessenger) call ["sendAlert", [_uid, "error", "Bank account could not be loaded."]]; + false + }; + + private _checkoutContext = GVAR(BankPayloadBuilder) call ["buildCheckoutContext", ["bank", false]]; + private _previewEnvelope = _self call [ + "callHotBankEnvelope", + [ + "bank:hot:charge_checkout", + [_uid, str _amount, toJSON _checkoutContext] + ] + ]; + private _previewResult = _previewEnvelope getOrDefault ["data", createHashMap]; + private _bankPatch = _self call ["finalizeMutation", [_uid, _previewResult, false]]; + if (_bankPatch isEqualTo createHashMap) exitWith { + GVAR(BankMessenger) call ["sendAlert", [_uid, "error", _previewEnvelope getOrDefault ["error", "Credit repayment could not be funded from the bank account."]]]; + false + }; + + private _nextAccount = _previewResult getOrDefault ["account", createHashMap]; + if (_nextAccount isEqualTo createHashMap) exitWith { + GVAR(BankMessenger) call ["sendAlert", [_uid, "error", "Bank repayment preview returned an invalid account state."]]; + false + }; + + private _overrideEnvelope = _self call [ + "callHotBankEnvelope", + ["bank:hot:override", [_uid, _self call ["toJSON", [_nextAccount]]]] + ]; + if ((_overrideEnvelope getOrDefault ["data", createHashMap]) isEqualTo createHashMap) exitWith { + GVAR(BankMessenger) call ["sendAlert", [_uid, "error", _overrideEnvelope getOrDefault ["error", "Credit repayment could not reserve bank funds."]]]; + false + }; + + private _orgResult = EGVAR(org,OrgStore) call ["repayCreditLine", [_uid, _amount]]; + if !(_orgResult getOrDefault ["success", false]) exitWith { + private _rollbackEnvelope = _self call [ + "callHotBankEnvelope", + ["bank:hot:override", [_uid, _self call ["toJSON", [_originalAccount]]]] + ]; + if ((_rollbackEnvelope getOrDefault ["data", createHashMap]) isEqualTo createHashMap) then { + ["ERROR", format ["Failed to roll back bank state for %1 after org credit repayment failure.", _uid]] call EFUNC(common,log); + }; + + GVAR(BankMessenger) call ["sendAlert", [_uid, "error", _orgResult getOrDefault ["message", "Credit repayment failed."]]]; + false + }; + + GVAR(BankMessenger) call ["sendAccountSync", [_uid, _bankPatch]]; + GVAR(BankMessenger) call ["sendNotification", [_uid, "info", "Bank", _orgResult getOrDefault ["message", format ["Repaid $%1 toward the organization credit line.", [_amount] call EFUNC(common,formatNumber)]]]]; + + private _orgPatch = _orgResult getOrDefault ["patch", createHashMap]; + if (_orgPatch isNotEqualTo createHashMap) then { + { + private _memberPlayer = [_x] call EFUNC(common,getPlayer); + if (_memberPlayer isNotEqualTo objNull) then { + [CRPC(org,responseSyncOrg), [_orgPatch], _memberPlayer] call CFUNC(targetEvent); + }; + } forEach (_orgResult getOrDefault ["memberUids", []]); + }; + + _self call ["hydrateSession", [_uid, "", false]]; + true + }], ["deposit", compileFinal { params [["_uid", "", [""]], ["_amount", 0, [0]]]; diff --git a/arma/server/addons/org/functions/fnc_initOrgStore.sqf b/arma/server/addons/org/functions/fnc_initOrgStore.sqf index cf7ad81..230564e 100644 --- a/arma/server/addons/org/functions/fnc_initOrgStore.sqf +++ b/arma/server/addons/org/functions/fnc_initOrgStore.sqf @@ -83,6 +83,34 @@ GVAR(OrgModel) = compileFinal createHashMapObject [[ _org set ["assets", _migratedAssets]; + private _creditLines = _org getOrDefault ["credit_lines", createHashMap]; + if !(_creditLines isEqualType createHashMap) then { + _creditLines = createHashMap; + }; + + { + if !(_y isEqualType createHashMap) then { continue; }; + + private _line = +_y; + private _legacyAmount = _line getOrDefault ["amount", 0]; + private _approvedAmount = _line getOrDefault ["approved_amount", _legacyAmount]; + private _availableAmount = _line getOrDefault ["available_amount", _approvedAmount]; + private _outstandingPrincipal = _line getOrDefault ["outstanding_principal", 0]; + private _interestRate = _line getOrDefault ["interest_rate", 0.1]; + private _amountDue = _line getOrDefault ["amount_due", 0]; + + _line set ["uid", _line getOrDefault ["uid", _x]]; + _line set ["approved_amount", _approvedAmount]; + _line set ["available_amount", _availableAmount]; + _line set ["outstanding_principal", _outstandingPrincipal]; + _line set ["interest_rate", _interestRate]; + _line set ["amount_due", _amountDue]; + _line set ["amount", _availableAmount]; + _creditLines set [_x, _line]; + } forEach _creditLines; + + _org set ["credit_lines", _creditLines]; + _org }], ["validate", compileFinal { @@ -483,6 +511,43 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [ _result set ["memberUids", _envelope getOrDefault ["memberUids", []]]; _result }], + ["repayCreditLine", compileFinal { + params [["_requesterUid", "", [""]], ["_amount", 0, [0]]]; + + private _result = createHashMapFromArray [ + ["success", false], + ["message", ""], + ["patch", createHashMap], + ["memberUids", []] + ]; + + if (_requesterUid isEqualTo "" || { _amount <= 0 }) exitWith { + _result set ["message", "A valid repayment amount is required."]; + _result + }; + + private _requesterActor = EGVAR(actor,Registry) getOrDefault [_requesterUid, createHashMap]; + private _orgID = _requesterActor getOrDefault ["organization", "default"]; + if (_orgID isEqualTo "") then { _orgID = "default"; }; + + private _context = createHashMapFromArray [ + ["requesterUid", _requesterUid], + ["orgId", _orgID], + ["amount", _amount] + ]; + + private _envelope = _self call ["callHotOrgEnvelope", ["org:hot:repay_credit_line", [toJSON _context]]]; + if (_envelope isEqualTo createHashMap) exitWith { + _result set ["message", "Unable to apply credit repayment."]; + _result + }; + + _result set ["success", true]; + _result set ["message", _envelope getOrDefault ["message", "Credit repayment posted."]]; + _result set ["patch", _envelope getOrDefault ["patch", createHashMap]]; + _result set ["memberUids", _envelope getOrDefault ["memberUids", []]]; + _result + }], ["buildPortalPayload", compileFinal { params [["_uid", "", [""]]]; diff --git a/arma/server/addons/org/functions/fnc_initPayloadBuilder.sqf b/arma/server/addons/org/functions/fnc_initPayloadBuilder.sqf index 2a049f1..19fe960 100644 --- a/arma/server/addons/org/functions/fnc_initPayloadBuilder.sqf +++ b/arma/server/addons/org/functions/fnc_initPayloadBuilder.sqf @@ -134,10 +134,19 @@ GVAR(OrgPayloadBuilder) = createHashMapObject [[ private _creditLinesList = []; { private _creditLineData = _y; + private _availableAmount = _creditLineData getOrDefault [ + "available_amount", + _creditLineData getOrDefault ["amount", 0] + ]; _creditLinesList pushBack [ ["uid", _creditLineData getOrDefault ["uid", _x]], ["member", _creditLineData getOrDefault ["name", "Unknown Member"]], - ["amount", _creditLineData getOrDefault ["amount", 0]] + ["approvedAmount", _creditLineData getOrDefault ["approved_amount", _availableAmount]], + ["availableAmount", _availableAmount], + ["outstandingPrincipal", _creditLineData getOrDefault ["outstanding_principal", 0]], + ["interestRate", _creditLineData getOrDefault ["interest_rate", 0.1]], + ["amountDue", _creditLineData getOrDefault ["amount_due", 0]], + ["amount", _availableAmount] ]; } forEach _creditLinesRaw; diff --git a/arma/server/addons/store/functions/fnc_initStoreStore.sqf b/arma/server/addons/store/functions/fnc_initStoreStore.sqf index 8a06965..61fb751 100644 --- a/arma/server/addons/store/functions/fnc_initStoreStore.sqf +++ b/arma/server/addons/store/functions/fnc_initStoreStore.sqf @@ -29,6 +29,7 @@ GVAR(StoreBaseStore) = compileFinal createHashMapFromArray [ private _budget = 50000; private _creditLine = 0; + private _creditLineDue = 0; private _cashBalance = 0; private _bankBalance = 0; private _orgFunds = 0; @@ -72,7 +73,11 @@ GVAR(StoreBaseStore) = compileFinal createHashMapFromArray [ if (_orgCreditLines isEqualType createHashMap) then { private _playerCreditLine = _orgCreditLines getOrDefault [_uid, createHashMap]; if (_playerCreditLine isEqualType createHashMap) then { - _creditLine = _playerCreditLine getOrDefault ["amount", 0]; + _creditLine = _playerCreditLine getOrDefault [ + "available_amount", + _playerCreditLine getOrDefault ["amount", 0] + ]; + _creditLineDue = _playerCreditLine getOrDefault ["amount_due", 0]; }; }; @@ -113,7 +118,10 @@ GVAR(StoreBaseStore) = compileFinal createHashMapFromArray [ ["enabled", _creditLine > 0], ["detail", [ "No approved credit line is assigned to this member.", - "Use the approved procurement credit line." + format [ + "Use the approved procurement credit line. Outstanding due: $%1.", + [_creditLineDue] call EFUNC(common,formatNumber) + ] ] select (_creditLine > 0)] ] ]; diff --git a/arma/server/extension/src/org.rs b/arma/server/extension/src/org.rs index 77f0c10..e0456ad 100644 --- a/arma/server/extension/src/org.rs +++ b/arma/server/extension/src/org.rs @@ -5,7 +5,8 @@ use arma_rs::Group; use forge_models::{ - HotOrgRecord, OrgAssetGrantSeed, OrgCheckoutContext, OrgCreditLineContext, OrgDisbandResult, + HotOrgRecord, OrgAssetGrantSeed, OrgCheckoutContext, OrgCreditLineContext, + OrgCreditLineRepaymentContext, OrgCreditLineRepaymentResult, OrgDisbandResult, OrgEnsureMemberContext, OrgFleetGrantSeed, OrgGrantContext, OrgLeaveContext, OrgLeaveResult, OrgRegisterContext, }; @@ -59,6 +60,7 @@ pub fn group() -> Group { .command("ensure_member", ensure_hot_org_member) .command("register", register_hot_org) .command("assign_credit_line", assign_credit_line_hot_org) + .command("repay_credit_line", repay_credit_line_hot_org) .command("charge_checkout", charge_checkout_hot_org) .command("add_assets", add_assets_hot_org) .command("add_fleet", add_fleet_hot_org) @@ -176,6 +178,20 @@ pub(crate) fn charge_checkout_hot_org(json_data: String) -> String { } } +pub(crate) fn repay_credit_line_hot_org(json_data: String) -> String { + let context: OrgCreditLineRepaymentContext = match serde_json::from_str(&json_data) { + Ok(data) => data, + Err(error) => return format!("Error: Invalid org credit repayment JSON: {}", error), + }; + + match HOT_ORG_SERVICE.repay_credit_line(context) { + Ok(result) => { + serialize_result::(&result, "org credit repayment result") + } + Err(error) => format!("Error: {}", error), + } +} + pub(crate) fn add_assets_hot_org(context_json: String, assets_json: String) -> String { let context: OrgGrantContext = match serde_json::from_str(&context_json) { Ok(data) => data, diff --git a/arma/server/extension/src/transport.rs b/arma/server/extension/src/transport.rs index b1af67d..0232ba4 100644 --- a/arma/server/extension/src/transport.rs +++ b/arma/server/extension/src/transport.rs @@ -363,6 +363,10 @@ fn route_command( expect_arg_count(function_name, &arguments, 1)?; Ok(org::assign_credit_line_hot_org(arguments[0].clone())) } + "org:hot:repay_credit_line" => { + expect_arg_count(function_name, &arguments, 1)?; + Ok(org::repay_credit_line_hot_org(arguments[0].clone())) + } "org:hot:charge_checkout" => { expect_arg_count(function_name, &arguments, 1)?; Ok(org::charge_checkout_hot_org(arguments[0].clone())) diff --git a/lib/models/src/lib.rs b/lib/models/src/lib.rs index 53d7179..243a5a5 100644 --- a/lib/models/src/lib.rs +++ b/lib/models/src/lib.rs @@ -23,10 +23,11 @@ pub use cad::{ pub use garage::{Garage, HitPoints, Vehicle}; pub use locker::{Item, Locker}; pub use org::{ - CreditLineSummary, HotOrgRecord, MemberSummary, Org, OrgAssetEntry, OrgAssetGrantSeed, - OrgCheckoutContext, OrgCreditLineContext, OrgDisbandMemberResult, OrgDisbandResult, - OrgEnsureMemberContext, OrgFleetEntry, OrgFleetGrantSeed, OrgGrantContext, OrgLeaveContext, - OrgLeaveResult, OrgMutationResult, OrgRegisterContext, OrgRegisterResult, + CreditLineSummary, DEFAULT_CREDIT_LINE_INTEREST_RATE, HotOrgRecord, MemberSummary, Org, + OrgAssetEntry, OrgAssetGrantSeed, OrgCheckoutContext, OrgCreditLineContext, + OrgCreditLineRepaymentContext, OrgCreditLineRepaymentResult, OrgDisbandMemberResult, + OrgDisbandResult, OrgEnsureMemberContext, OrgFleetEntry, OrgFleetGrantSeed, OrgGrantContext, + OrgLeaveContext, OrgLeaveResult, OrgMutationResult, OrgRegisterContext, OrgRegisterResult, }; pub use store::{ StoreCheckoutContext, StoreCheckoutItemSeed, StoreCheckoutResult, StoreCheckoutVehicleSeed, diff --git a/lib/models/src/org.rs b/lib/models/src/org.rs index c2c026e..bda9235 100644 --- a/lib/models/src/org.rs +++ b/lib/models/src/org.rs @@ -3,13 +3,34 @@ use forge_shared::OrgValidationError; use serde::{Deserialize, Serialize}; use std::collections::HashMap; +pub const DEFAULT_CREDIT_LINE_INTEREST_RATE: f64 = 0.10; + +fn round_currency(value: f64) -> f64 { + (value.max(0.0) * 100.0).round() / 100.0 +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CreditLineSummary { pub uid: String, pub name: String, + #[serde(default)] + pub approved_amount: f64, + #[serde(default)] + pub available_amount: f64, + #[serde(default)] + pub outstanding_principal: f64, + #[serde(default = "default_credit_line_interest_rate")] + pub interest_rate: f64, + #[serde(default)] + pub amount_due: f64, + #[serde(default)] pub amount: f64, } +fn default_credit_line_interest_rate() -> f64 { + DEFAULT_CREDIT_LINE_INTEREST_RATE +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct OrgAssetEntry { pub classname: String, @@ -113,6 +134,14 @@ pub struct OrgCheckoutContext { pub commit: bool, } +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct OrgCreditLineRepaymentContext { + pub requester_uid: String, + pub org_id: String, + pub amount: f64, +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct OrgAssetGrantSeed { @@ -145,6 +174,19 @@ pub struct OrgMutationResult { pub message: String, } +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct OrgCreditLineRepaymentResult { + pub org: HotOrgRecord, + pub patch: HashMap, + pub member_uids: Vec, + pub paid_amount: f64, + pub principal_paid: f64, + pub interest_paid: f64, + pub remaining_amount_due: f64, + pub message: String, +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct OrgLeaveContext { @@ -241,7 +283,12 @@ impl Org { )); } - if credit_line.amount < 0.0 { + if credit_line.approved_amount < 0.0 + || credit_line.available_amount < 0.0 + || credit_line.outstanding_principal < 0.0 + || credit_line.amount_due < 0.0 + || credit_line.amount < 0.0 + { return Err(OrgValidationError::NegativeCreditLine( resolved_uid.to_string(), )); @@ -254,6 +301,12 @@ impl Org { pub fn id(&self) -> &str { &self.id } + + pub fn normalize_credit_lines(&mut self) { + for credit_line in self.credit_lines.values_mut() { + credit_line.normalize(); + } + } } impl HotOrgRecord { @@ -280,14 +333,47 @@ impl HotOrgRecord { } pub fn into_org(self) -> Org { - Org { + let mut org = Org { id: self.id, owner: self.owner, name: self.name, funds: self.funds, reputation: self.reputation, credit_lines: self.credit_lines, + }; + org.normalize_credit_lines(); + org + } +} + +impl CreditLineSummary { + pub fn normalize(&mut self) { + let legacy_amount = round_currency(self.amount); + + self.approved_amount = round_currency(self.approved_amount); + self.available_amount = round_currency(self.available_amount); + self.outstanding_principal = round_currency(self.outstanding_principal); + self.amount_due = round_currency(self.amount_due); + + if self.approved_amount <= 0.0 && self.available_amount <= 0.0 && legacy_amount > 0.0 { + self.approved_amount = legacy_amount; + self.available_amount = legacy_amount; + } else if self.approved_amount <= 0.0 && self.available_amount > 0.0 { + self.approved_amount = self.available_amount; + } else if self.available_amount <= 0.0 && self.approved_amount > 0.0 { + self.available_amount = self.approved_amount; } + + if self.interest_rate <= 0.0 { + self.interest_rate = DEFAULT_CREDIT_LINE_INTEREST_RATE; + } + + if self.amount_due <= 0.0 && self.outstanding_principal > 0.0 { + self.amount_due = + round_currency(self.outstanding_principal * (1.0 + self.interest_rate)); + } + + self.amount = self.available_amount; } } diff --git a/lib/services/src/org.rs b/lib/services/src/org.rs index 590427a..96e00e1 100644 --- a/lib/services/src/org.rs +++ b/lib/services/src/org.rs @@ -6,10 +6,11 @@ //! For full documentation, architecture, and examples, see the [crate README](../README.md). use forge_models::{ - CreditLineSummary, HotOrgRecord, MemberSummary, Org, OrgAssetEntry, OrgAssetGrantSeed, - OrgCheckoutContext, OrgCreditLineContext, OrgDisbandMemberResult, OrgDisbandResult, - OrgEnsureMemberContext, OrgFleetEntry, OrgFleetGrantSeed, OrgGrantContext, OrgLeaveContext, - OrgLeaveResult, OrgMutationResult, OrgRegisterContext, OrgRegisterResult, + CreditLineSummary, DEFAULT_CREDIT_LINE_INTEREST_RATE, HotOrgRecord, MemberSummary, Org, + OrgAssetEntry, OrgAssetGrantSeed, OrgCheckoutContext, OrgCreditLineContext, + OrgCreditLineRepaymentContext, OrgCreditLineRepaymentResult, OrgDisbandMemberResult, + OrgDisbandResult, OrgEnsureMemberContext, OrgFleetEntry, OrgFleetGrantSeed, OrgGrantContext, + OrgLeaveContext, OrgLeaveResult, OrgMutationResult, OrgRegisterContext, OrgRegisterResult, }; use forge_repositories::{OrgHotRepository, OrgRepository}; use serde_json::{Value, json}; @@ -58,7 +59,10 @@ impl OrgService { ); } - serde_json::from_value::(org_value).map_err(|e| format!("Invalid Org JSON: {}", e)) + let mut org = serde_json::from_value::(org_value) + .map_err(|e| format!("Invalid Org JSON: {}", e))?; + org.normalize_credit_lines(); + Ok(org) } /// Creates a new organization service with the provided repository. @@ -94,9 +98,12 @@ impl OrgService { } pub fn get_org(&self, key: String) -> Result { - self.repository + let mut org = self + .repository .get_by_id(&key)? - .ok_or_else(|| format!("Organization with ID '{}' not found", key)) + .ok_or_else(|| format!("Organization with ID '{}' not found", key))?; + org.normalize_credit_lines(); + Ok(org) } /// Updates an existing organization with new data from JSON. @@ -191,6 +198,7 @@ impl OrgService { } // Validate the updated organization before committing changes + updated_org.normalize_credit_lines(); updated_org .validate() .map_err(|e| format!("Validation failed: {}", e))?; @@ -532,23 +540,50 @@ impl OrgHotStateService { context.member_name }; - org.credit_lines.insert( - context.member_uid.clone(), - CreditLineSummary { + let mut credit_line = org + .credit_lines + .get(&context.member_uid) + .cloned() + .unwrap_or_else(|| CreditLineSummary { uid: context.member_uid.clone(), name: member_name.clone(), - amount: context.amount, - }, - ); + approved_amount: 0.0, + available_amount: 0.0, + outstanding_principal: 0.0, + interest_rate: DEFAULT_CREDIT_LINE_INTEREST_RATE, + amount_due: 0.0, + amount: 0.0, + }); + credit_line.normalize(); + + let next_reserved_amount = round_currency(context.amount); + let previous_reserved_amount = round_currency(credit_line.available_amount); + let treasury_delta = round_currency(next_reserved_amount - previous_reserved_amount); + if treasury_delta > 0.0 && org.funds < treasury_delta { + return Err("Organization funds cannot cover that credit assignment.".to_string()); + } + + org.funds = round_currency(org.funds - treasury_delta); + credit_line.uid = context.member_uid.clone(); + credit_line.name = member_name.clone(); + credit_line.approved_amount = next_reserved_amount; + credit_line.available_amount = next_reserved_amount; + credit_line.amount = next_reserved_amount; + if credit_line.interest_rate <= 0.0 { + credit_line.interest_rate = DEFAULT_CREDIT_LINE_INTEREST_RATE; + } + + org.credit_lines + .insert(context.member_uid.clone(), credit_line); self.repository.save(&org)?; Ok(OrgMutationResult { - patch: build_org_patch(&org, &["credit_lines"])?, + patch: build_org_patch(&org, &["funds", "credit_lines"])?, member_uids: resolve_member_uids(&org, Some(&context.requester_uid)), message: format!( - "Credit line of ${} assigned to {}.", - format_currency(context.amount), - member_name + "Credit line for {} set to ${}.", + member_name, + format_currency(next_reserved_amount) ), org, }) @@ -602,11 +637,22 @@ impl OrgHotStateService { "Assigned credit line cannot cover this checkout.".to_string() })?; - if credit_line.amount < context.amount { + credit_line.normalize(); + + if credit_line.available_amount < context.amount { return Err("Assigned credit line cannot cover this checkout.".to_string()); } - credit_line.amount -= context.amount; + let charged_amount = round_currency(context.amount); + credit_line.available_amount = + round_currency(credit_line.available_amount - charged_amount); + credit_line.approved_amount = credit_line.available_amount; + credit_line.outstanding_principal = + round_currency(credit_line.outstanding_principal + charged_amount); + credit_line.amount_due = round_currency( + credit_line.amount_due + (charged_amount * (1.0 + credit_line.interest_rate)), + ); + credit_line.amount = credit_line.available_amount; org.credit_lines .insert(context.requester_uid.clone(), credit_line); self.repository.save(&org)?; @@ -622,6 +668,81 @@ impl OrgHotStateService { } } + pub fn repay_credit_line( + &self, + context: OrgCreditLineRepaymentContext, + ) -> Result { + if context.requester_uid.trim().is_empty() || context.org_id.trim().is_empty() { + return Err("A valid requester and organization are required.".to_string()); + } + if context.amount <= 0.0 { + return Err("Repayment amount must be greater than zero.".to_string()); + } + + let mut org = self.get_org(context.org_id)?; + let member_uids = resolve_member_uids(&org, Some(&context.requester_uid)); + let mut credit_line = org + .credit_lines + .get(&context.requester_uid) + .cloned() + .ok_or_else(|| "No active credit line is assigned to this member.".to_string())?; + credit_line.normalize(); + + if credit_line.amount_due <= 0.0 { + return Err("This credit line has no outstanding balance.".to_string()); + } + + let paid_amount = round_currency(context.amount.min(credit_line.amount_due)); + let principal_paid = if paid_amount >= credit_line.amount_due { + credit_line.outstanding_principal + } else { + round_currency( + paid_amount * (credit_line.outstanding_principal / credit_line.amount_due), + ) + .min(credit_line.outstanding_principal) + .min(paid_amount) + }; + let interest_paid = round_currency(paid_amount - principal_paid); + + credit_line.outstanding_principal = + round_currency(credit_line.outstanding_principal - principal_paid); + credit_line.amount_due = round_currency(credit_line.amount_due - paid_amount); + if credit_line.outstanding_principal <= 0.0 { + credit_line.outstanding_principal = 0.0; + } + if credit_line.amount_due <= 0.0 { + credit_line.amount_due = 0.0; + } + credit_line.amount = credit_line.available_amount; + + org.funds = round_currency(org.funds + paid_amount); + org.credit_lines + .insert(context.requester_uid.clone(), credit_line.clone()); + self.repository.save(&org)?; + + Ok(OrgCreditLineRepaymentResult { + patch: build_org_patch(&org, &["funds", "credit_lines"])?, + member_uids, + paid_amount, + principal_paid, + interest_paid, + remaining_amount_due: credit_line.amount_due, + message: if credit_line.amount_due > 0.0 { + format!( + "Credit repayment posted. ${} paid with ${} still due.", + format_currency(paid_amount), + format_currency(credit_line.amount_due) + ) + } else { + format!( + "Credit repayment posted. ${} cleared the outstanding balance.", + format_currency(paid_amount) + ) + }, + org, + }) + } + pub fn add_assets( &self, context: OrgGrantContext, @@ -891,7 +1012,7 @@ fn current_org_field_value(org: &HotOrgRecord, field: &str) -> Result String { - let rounded = amount.max(0.0).round() as i64; + let rounded = round_currency(amount).round() as i64; let digits = rounded.to_string(); let mut formatted = String::new(); @@ -904,3 +1025,7 @@ fn format_currency(amount: f64) -> String { formatted.chars().rev().collect() } + +fn round_currency(amount: f64) -> f64 { + (amount.max(0.0) * 100.0).round() / 100.0 +} diff --git a/lib/services/src/store.rs b/lib/services/src/store.rs index 643b3c2..b0e06e1 100644 --- a/lib/services/src/store.rs +++ b/lib/services/src/store.rs @@ -451,13 +451,23 @@ where org.credit_lines.get_mut(requester_uid).ok_or_else(|| { "Assigned credit line cannot cover this checkout.".to_string() })?; - if credit_line.amount < charged_total { + credit_line.normalize(); + if credit_line.available_amount < charged_total { return Err( "Assigned credit line cannot cover this checkout.".to_string() ); } - credit_line.amount -= charged_total; + credit_line.available_amount = + round_currency(credit_line.available_amount - charged_total); + credit_line.approved_amount = credit_line.available_amount; + credit_line.outstanding_principal = + round_currency(credit_line.outstanding_principal + charged_total); + credit_line.amount_due = round_currency( + credit_line.amount_due + + (charged_total * (1.0 + credit_line.interest_rate)), + ); + credit_line.amount = credit_line.available_amount; org_patch.insert("credit_lines".to_string(), json!(org.credit_lines)); } _ => unreachable!(), @@ -683,3 +693,7 @@ fn format_currency(amount: f64) -> String { format!("${}", formatted.chars().rev().collect::()) } + +fn round_currency(amount: f64) -> f64 { + (amount.max(0.0) * 100.0).round() / 100.0 +}