@arcblock/did-connect-service 4.1.0 → 4.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_generated/asset-bytes.js +1 -1
- package/dist/_generated/asset-manifest.js +1 -1
- package/dist/assets/admin-extra.afcb3651.js +2528 -0
- package/dist/pages/admin/tab-access.d.ts.map +1 -1
- package/dist/pages/admin/tab-access.js +16 -17
- package/dist/pages/admin/tab-access.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// AUTO-GENERATED by build-assets.mjs — do not edit
|
|
2
|
-
export default {"login.d3f05790.js":"\"use strict\";var __LoginBundle=(()=>{var ie=Object.defineProperty;var Te=Object.getOwnPropertyDescriptor;var De=Object.getOwnPropertyNames;var Ae=Object.prototype.hasOwnProperty;var Ie=(o,e)=>{for(var r in e)ie(o,r,{get:e[r],enumerable:!0})},Re=(o,e,r,t)=>{if(e&&typeof e==\"object\"||typeof e==\"function\")for(let n of De(e))!Ae.call(o,n)&&n!==r&&ie(o,n,{get:()=>e[n],enumerable:!(t=Te(e,n))||t.enumerable});return o};var Se=o=>Re(ie({},\"__esModule\",{value:!0}),o);var it={};Ie(it,{FetchHttpAdapter:()=>J,LoginPage:()=>ke,runPasskeyAuth:()=>oe});var F={CREATED:\"created\",SCANNED:\"scanned\",SUCCEED:\"succeed\",ERROR:\"error\",TIMEOUT:\"timeout\",BUSY:\"busy\",FORBIDDEN:\"forbidden\"};var O={API_PREFIX:\"/api/did\",SERVICE_PREFIX:\"/.well-known/service\",ACTION:\"login\",TOKEN_TIMEOUT:3e5,CHECK_INTERVAL:2e3,TOKEN_KEY:\"_t_\",TOKEN_DELIVERY:\"cookie\",CREATE_THROTTLE:500,AUTO_CHECK_INTERVAL:6e4};var re=\"did:abt:\";function pe(o){if(!o||typeof o!=\"string\")return!1;let e=o.replace(re,\"\");return/^(0x)?[0-9a-f]{40}$/i.test(e)}function fe(o,e=8,r=6){return!o||typeof o!=\"string\"?\"\":o.length<=e+r+3?o:`${o.slice(0,e)}...${o.slice(-r)}`}function ae(o,e={}){if(!o||typeof o!=\"string\")return{prefix:\"DID: ABT:\",chainLabel:\"ABT\",address:\"\",compact:\"\",copyText:\"\"};let{startChars:r=8,endChars:t=6}=e,n=pe(o),a=o.replace(re,\"\"),s=n?\"ETH\":\"ABT\",i=e.includePrefix??!n;return{prefix:`DID: ${s}:`,chainLabel:s,address:a,compact:fe(a,r,t),copyText:i?`${re}${a}`:a}}var L={TOKEN_EXPIRED:\"TOKEN_EXPIRED\",TOKEN_NOT_FOUND:\"TOKEN_NOT_FOUND\",TOKEN_TIMEOUT:\"TOKEN_TIMEOUT\",ALREADY_EXIST:\"ALREADY_EXIST\",ALREADY_BIND:\"ALREADY_BIND\",NETWORK_ERROR:\"NETWORK_ERROR\",INVALID_RESPONSE:\"INVALID_RESPONSE\",UNAUTHORIZED:\"UNAUTHORIZED\",FORBIDDEN:\"FORBIDDEN\"},B=class extends Error{code;constructor(e,r){super(r),this.name=\"ConnectError\",this.code=e}};var J=class{_defaultHeaders;_onRequest;constructor(e){this._defaultHeaders=e?.headers??{},this._onRequest=e?.onRequest}async get(e,r){this._validateUrl(e);let t=r?.params?this._serializeParams(r.params):\"\",n=t?`${e}${e.includes(\"?\")?\"&\":\"?\"}${t}`:e,s={method:\"GET\",headers:{...this._defaultHeaders,...r?.headers},signal:r?.signal};return this._onRequest&&(s=this._onRequest(n,s)),this._execute(n,s)}async post(e,r,t){this._validateUrl(e);let n=t?.params?this._serializeParams(t.params):\"\",a=n?`${e}${e.includes(\"?\")?\"&\":\"?\"}${n}`:e,i={method:\"POST\",headers:{\"Content-Type\":\"application/json\",...this._defaultHeaders,...t?.headers},body:r!==void 0?JSON.stringify(r):void 0,signal:t?.signal};return this._onRequest&&(i=this._onRequest(a,i)),this._execute(a,i)}_validateUrl(e){if(!e?.trim())throw new B(L.NETWORK_ERROR,\"Request URL must not be empty\");if(e.trim().toLowerCase().startsWith(\"javascript:\"))throw new B(L.NETWORK_ERROR,\"Invalid URL protocol\")}async _execute(e,r){let t;try{t=await globalThis.fetch(e,r)}catch(s){throw s?.name===\"AbortError\"?s:new B(L.NETWORK_ERROR,`Network request failed: ${s?.message??\"Unknown error\"}`)}if(!t.ok){let s=\"\",i=\"\";try{let m=await t.text();try{let y=JSON.parse(m);y.error&&(s=y.error),y.code&&(i=y.code)}catch{s=m}}catch{}let d=i===\"FORBIDDEN\"?L.FORBIDDEN:L.NETWORK_ERROR;throw new B(d,s||`HTTP ${t.status} ${t.statusText||\"Error\"}`)}if(t.status===204)return null;let n=await t.text();if(!n.trim())return null;let a=t.headers.get(\"content-type\")??\"\";if(!a.includes(\"application/json\")&&!a.includes(\"+json\"))throw new B(L.INVALID_RESPONSE,`Expected JSON response but received: ${a||\"unknown content type\"}`);try{return JSON.parse(n)}catch{throw new B(L.INVALID_RESPONSE,\"Failed to parse JSON response\")}}_serializeParams(e){let r=[];for(let t of Object.keys(e)){let n=e[t];if(n!=null)if(Array.isArray(n))for(let a of n)a!=null&&r.push(`${encodeURIComponent(t)}=${encodeURIComponent(String(a))}`);else typeof n==\"boolean\"?r.push(`${encodeURIComponent(t)}=${n?\"true\":\"false\"}`):r.push(`${encodeURIComponent(t)}=${encodeURIComponent(String(n))}`)}return r.join(\"&\")}};function X(o){let e=o.apiPrefix??O.API_PREFIX,r=o.action??O.ACTION,t=e.replace(/\\/+$/,\"\"),n=r.replace(/^\\/+/,\"\");return`${t}/${n}`}function me(){return{token:null,url:null,status:null,error:null,appInfo:null,memberAppInfo:null,loading:!1}}var Z=class{_state;_config;_http;_pollTimer=null;_checkCount=0;_maxCheckCount;_realtimeUnsub=null;_realtime;_storage;_lastCreateTime=0;_listeners=new Map;_destroyed=!1;constructor(e,r,t){this._config=this._fillDefaults(e),this._http=r,this._realtime=t?.realtime,this._storage=t?.storage,this._state=me(),this._config.checkInterval<=0?this._maxCheckCount=1/0:this._maxCheckCount=Math.ceil(this._config.tokenTimeout/this._config.checkInterval)}get state(){return Object.freeze({...this._state})}async create(e){if(Date.now()-this._lastCreateTime<O.CREATE_THROTTLE)return this.state;(this._pollTimer!==null||this._realtimeUnsub!==null)&&this.stopPolling(),this._state.loading=!0,this._emit(\"statusChange\",this.state);let r=`${X(this._config)}/token`,t={};e?(e.locale!=null&&(t.locale=e.locale),e.provider!=null?t.provider=e.provider:t.provider=\"wallet\",e.encKey!=null&&(t.__encKey=e.encKey),e.autoConnect!=null&&(t.autoConnect=e.autoConnect),e.visitorId!=null&&(t.visitorId=e.visitorId),e.sourceToken!=null&&(t.sourceToken=e.sourceToken),e.forceConnected!=null&&(t.forceConnected=e.forceConnected),e.connectUrl!=null&&(t.connectUrl=e.connectUrl),e.extraParams&&Object.assign(t,e.extraParams)):t.provider=\"wallet\";try{let n=await this._http.get(r,{params:t});return this._state.token=n.token??null,this._state.url=n.url??null,this._state.status=F.CREATED,this._state.appInfo=n.appInfo??null,this._state.memberAppInfo=n.memberAppInfo??null,this._state.loading=!1,this._state.error=null,this._state.successResult=void 0,this._state.connectedDid=void 0,this._state.mfaCode=void 0,this._state.saveConnect=void 0,this._lastCreateTime=Date.now(),this._emit(\"statusChange\",this.state),this.state}catch(n){throw this._state.loading=!1,this._state.error=n?.message??\"Failed to create token\",this._state.status=F.ERROR,this._emit(\"statusChange\",this.state),this._emit(\"error\",new B(L.NETWORK_ERROR,this._state.error)),n}}async checkStatus(){if(!this._state.token)throw new B(L.TOKEN_NOT_FOUND,\"No token available. Call create() first.\");let e=`${X(this._config)}/status`,r={[O.TOKEN_KEY]:this._state.token};this._config.appPid;try{let t=await this._http.get(e,{params:r}),n=t.status;return n&&(this._state.status=n),t.error!==void 0&&(this._state.error=t.error),t.connectedDid!==void 0&&(this._state.connectedDid=t.connectedDid),t.mfaCode!==void 0&&(this._state.mfaCode=t.mfaCode),t.saveConnect!==void 0&&(this._state.saveConnect=t.saveConnect),n===F.FORBIDDEN?(this._state.status=F.ERROR,this._state.error=t.error||\"Access forbidden\",this._emit(\"error\",new B(L.UNAUTHORIZED,this._state.error)),this.stopPolling()):n===F.SUCCEED?(this._config.tokenDelivery===\"response\"&&(this._state.successResult=t),this._emit(\"succeed\",this.state),this.stopPolling()):n===F.ERROR?(this._emit(\"error\",new B(L.INVALID_RESPONSE,this._state.error||\"Unknown error\")),this.stopPolling()):n===F.TIMEOUT&&(this._emit(\"timeout\",this.state),this.stopPolling()),this._emit(\"statusChange\",this.state),this.state}catch(t){throw t}}async cancel(){if(!this._state.token)return;let e=`${X(this._config)}/timeout`,r={[O.TOKEN_KEY]:this._state.token};try{await this._http.get(e,{params:r})}catch{}this.stopPolling()}startPolling(){if(this._state.token){if(this._checkCount=0,this._realtime)try{let e=`relay:${this._config.appPid}:${this._state.token}`;this._realtimeUnsub=this._realtime.subscribe(e,r=>{this._handleRealtimeMessage(r)});return}catch{this._realtimeUnsub=null}this._config.checkInterval>0&&(this._pollTimer=setInterval(()=>{this._pollOnce()},this._config.checkInterval))}}stopPolling(){this._pollTimer!==null&&(clearInterval(this._pollTimer),this._pollTimer=null),this._realtimeUnsub&&(this._realtimeUnsub(),this._realtimeUnsub=null)}reset(){this.stopPolling(),this._state=me(),this._checkCount=0}destroy(){this._destroyed||(this.stopPolling(),this._state.token=null,this._state.successResult=void 0,this._listeners.clear(),this._destroyed=!0)}on(e,r){let t=this._listeners.get(e);return t||(t=new Set,this._listeners.set(e,t)),t.add(r),this}off(e,r){let t=this._listeners.get(e);return t&&(t.delete(r),t.size===0&&this._listeners.delete(e)),this}_emit(e,...r){let t=this._listeners.get(e);if(!(!t||t.size===0))for(let n of[...t])try{n(...r)}catch{}}_pollOnce(){if(this._checkCount++,this._checkCount>=this._maxCheckCount){this._handleTimeout();return}this.checkStatus().catch(()=>{})}_handleTimeout(){this.stopPolling(),this._state.status=F.TIMEOUT,this._emit(\"timeout\",this.state),this._emit(\"statusChange\",this.state)}_handleRealtimeMessage(e){if(!e||typeof e!=\"object\")return;let r=e.status;r&&(this._state.status=r,e.error!==void 0&&(this._state.error=e.error),e.connectedDid!==void 0&&(this._state.connectedDid=e.connectedDid),e.mfaCode!==void 0&&(this._state.mfaCode=e.mfaCode),e.saveConnect!==void 0&&(this._state.saveConnect=e.saveConnect),r===F.FORBIDDEN?(this._state.status=F.ERROR,this._state.error=e.error||\"Access forbidden\",this._emit(\"error\",new B(L.UNAUTHORIZED,this._state.error)),this.stopPolling()):r===F.SUCCEED?(this._config.tokenDelivery===\"response\"&&(this._state.successResult=e),this._emit(\"succeed\",this.state),this.stopPolling()):r===F.ERROR?(this._emit(\"error\",new B(L.INVALID_RESPONSE,this._state.error||\"Unknown error\")),this.stopPolling()):r===F.TIMEOUT&&(this._emit(\"timeout\",this.state),this.stopPolling()),this._emit(\"statusChange\",this.state))}_fillDefaults(e){return{appPid:e.appPid,appName:e.appName,appDescription:e.appDescription??\"\",appIcon:e.appIcon??\"\",appUrl:e.appUrl??\"\",apiPrefix:e.apiPrefix??O.API_PREFIX,action:e.action??O.ACTION,servicePrefix:e.servicePrefix??O.SERVICE_PREFIX,tokenTimeout:e.tokenTimeout??O.TOKEN_TIMEOUT,checkInterval:e.checkInterval??O.CHECK_INTERVAL,tokenDelivery:e.tokenDelivery??O.TOKEN_DELIVERY}}};var C={bg:{root:\"#0a0a0b\",surface:\"#141416\",card:\"#1c1c20\",elevated:\"#232328\",hover:\"#2b2b34\",active:\"#33333e\",input:\"rgba(255,255,255,0.06)\"},border:{default:\"rgba(255,255,255,0.10)\",subtle:\"rgba(255,255,255,0.06)\",strong:\"rgba(255,255,255,0.15)\",focus:\"rgba(255,255,255,0.25)\"},text:{primary:\"#f5f5f7\",secondary:\"#9394a1\",tertiary:\"#767684\",placeholder:\"#5e5f6e\",white:\"#ffffff\"},color:{blue:\"#6c47ff\",blueHover:\"#5f15fe\",blueLight:\"rgba(108,71,255,0.15)\",blueMuted:\"#9280ff\",red:\"#e02e2e\",redLight:\"rgba(224,46,46,0.15)\",redText:\"#f98a8a\",green:\"#15892b\",greenLight:\"rgba(21,137,43,0.15)\",greenText:\"#49dc6e\",yellow:\"#fd7224\",yellowLight:\"rgba(253,114,36,0.15)\",yellowText:\"#fd9357\",info:\"#236dd7\",infoLight:\"rgba(35,109,215,0.15)\",infoText:\"#73acfa\"},radius:{xs:\"4px\",sm:\"6px\",md:\"8px\",lg:\"12px\",full:\"9999px\"},shadow:{sm:\"0 1px 3px rgba(0,0,0,0.3), 0 1px 2px -1px rgba(0,0,0,0.2)\",md:\"0 4px 6px -1px rgba(0,0,0,0.4), 0 2px 4px -2px rgba(0,0,0,0.3)\",lg:\"0 10px 15px -3px rgba(0,0,0,0.5), 0 4px 6px -4px rgba(0,0,0,0.4)\",focus:\"0 0 0 1px rgba(255,255,255,0.12), 0 0 0 3px rgba(108,71,255,0.3)\"},font:{family:\"'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif\",mono:\"'JetBrains Mono', 'SF Mono', monospace\",sizeBase:\"14px\",lineHeight:\"1.43\"}},P={bg:{root:\"#f8f9fa\",surface:\"#ffffff\",card:\"#ffffff\",elevated:\"#f0f1f3\",hover:\"#e9eaec\",active:\"#dddee1\",input:\"rgba(0,0,0,0.04)\"},border:{default:\"rgba(0,0,0,0.10)\",subtle:\"rgba(0,0,0,0.06)\",strong:\"rgba(0,0,0,0.15)\",focus:\"rgba(0,0,0,0.25)\"},text:{primary:\"#1a1a1a\",secondary:\"#6b7280\",tertiary:\"#9ca3af\",placeholder:\"#c0c5ce\",white:\"#1a1a1a\"},shadow:{sm:\"0 1px 3px rgba(0,0,0,0.08), 0 1px 2px -1px rgba(0,0,0,0.06)\",md:\"0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.06)\",lg:\"0 10px 15px -3px rgba(0,0,0,0.12), 0 4px 6px -4px rgba(0,0,0,0.08)\",focus:\"0 0 0 1px rgba(0,0,0,0.08), 0 0 0 3px rgba(108,71,255,0.2)\"}},Ne=`\n :root {\n /* Backgrounds \\u2014 layered depth */\n --bg-root: ${C.bg.root};\n --bg-surface: ${C.bg.surface};\n --bg-card: ${C.bg.card};\n --bg-elevated: ${C.bg.elevated};\n --bg-hover: ${C.bg.hover};\n --bg-active: ${C.bg.active};\n --bg-input: ${C.bg.input};\n\n /* Borders \\u2014 semi-transparent for adaptability */\n --border: ${C.border.default};\n --border-subtle:${C.border.subtle};\n --border-strong:${C.border.strong};\n --border-focus: ${C.border.focus};\n\n /* Text hierarchy */\n --text: ${C.text.primary};\n --text-secondary:${C.text.secondary};\n --text-tertiary:${C.text.tertiary};\n --text-placeholder:${C.text.placeholder};\n --text-white: ${C.text.white};\n\n /* Accents */\n --blue: ${C.color.blue};\n --blue-hover: ${C.color.blueHover};\n --blue-light: ${C.color.blueLight};\n --blue-muted: ${C.color.blueMuted};\n --red: ${C.color.red};\n --red-light: ${C.color.redLight};\n --red-text: ${C.color.redText};\n --green: ${C.color.green};\n --green-light: ${C.color.greenLight};\n --green-text: ${C.color.greenText};\n --yellow: ${C.color.yellow};\n --yellow-light: ${C.color.yellowLight};\n --yellow-text: ${C.color.yellowText};\n --info: ${C.color.info};\n --info-light: ${C.color.infoLight};\n --info-text: ${C.color.infoText};\n\n /* Radii */\n --radius-xs: ${C.radius.xs};\n --radius-sm: ${C.radius.sm};\n --radius: ${C.radius.md};\n --radius-lg: ${C.radius.lg};\n --radius-full: ${C.radius.full};\n\n /* Shadows */\n --shadow-sm: ${C.shadow.sm};\n --shadow-md: ${C.shadow.md};\n --shadow-lg: ${C.shadow.lg};\n --shadow-focus: ${C.shadow.focus};\n }\n`,Be=`\n [data-theme=\"light\"] {\n --bg-root: ${P.bg.root};\n --bg-surface: ${P.bg.surface};\n --bg-card: ${P.bg.card};\n --bg-elevated: ${P.bg.elevated};\n --bg-hover: ${P.bg.hover};\n --bg-active: ${P.bg.active};\n --bg-input: ${P.bg.input};\n\n --border: ${P.border.default};\n --border-subtle:${P.border.subtle};\n --border-strong:${P.border.strong};\n --border-focus: ${P.border.focus};\n\n --text: ${P.text.primary};\n --text-secondary:${P.text.secondary};\n --text-tertiary:${P.text.tertiary};\n --text-placeholder:${P.text.placeholder};\n --text-white: ${P.text.white};\n\n --shadow-sm: ${P.shadow.sm};\n --shadow-md: ${P.shadow.md};\n --shadow-lg: ${P.shadow.lg};\n --shadow-focus: ${P.shadow.focus};\n }\n`,Le=`\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n html { color-scheme: dark; }\n html[data-theme=\"light\"] { color-scheme: light; }\n body {\n font-family: ${C.font.family};\n background: var(--bg-root);\n color: var(--text);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n font-size: ${C.font.sizeBase};\n line-height: ${C.font.lineHeight};\n }\n a { color: var(--blue-muted); text-decoration: none; }\n a:hover { text-decoration: underline; }\n .hidden { display: none; }\n`,Pe=typeof HTMLElement<\"u\"?HTMLElement:class{},ye='<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\"/><path d=\"M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1\"/></svg>',Oe='<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"/></svg>',$e=`\n:host {\n display: inline-flex;\n align-items: center;\n max-width: 100%;\n overflow: hidden;\n font-family: 'SF Mono', 'JetBrains Mono', 'Menlo', 'Consolas', monospace;\n line-height: 1.5;\n}\n:host([block]) { display: flex; }\n.prefix {\n flex-shrink: 0;\n opacity: 0.6;\n margin-right: 4px;\n white-space: nowrap;\n}\n.address {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: var(--did-text-color, currentColor);\n}\n.address-compact {\n white-space: nowrap;\n color: var(--did-text-color, currentColor);\n cursor: default;\n}\n.clickable { cursor: pointer; }\n.clickable:hover { opacity: 0.8; }\n.copy-btn {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1em;\n height: 1em;\n margin-left: 6px;\n padding: 0;\n border: none;\n background: none;\n color: inherit;\n opacity: 0.5;\n cursor: pointer;\n transition: opacity 0.15s;\n}\n.copy-btn:hover { opacity: 1; }\n.copy-btn.copied { opacity: 1; color: var(--did-copy-success-color, #22c55e); }\n`,Me=class extends Pe{static observedAttributes=[\"did\",\"compact\",\"copyable\",\"start-chars\",\"end-chars\",\"show-prefix\",\"no-tooltip\",\"no-copy\",\"block\",\"size\"];shadow;copyTimer=null;constructor(){super(),this.shadow=this.attachShadow({mode:\"open\"})}connectedCallback(){this.render()}attributeChangedCallback(){this.render()}disconnectedCallback(){this.copyTimer&&clearTimeout(this.copyTimer)}get parts(){let o=Number(this.getAttribute(\"start-chars\")),e=Number(this.getAttribute(\"end-chars\"));return ae(this.getAttribute(\"did\")||\"\",{startChars:o>0?o:void 0,endChars:e>0?e:void 0})}render(){if(!(this.getAttribute(\"did\")||\"\")){this.shadow.innerHTML=\"\";return}let e=this.parts,r=this.hasAttribute(\"compact\"),t=!this.hasAttribute(\"no-copy\")&&this.getAttribute(\"copyable\")!==\"false\",n=this.hasAttribute(\"show-prefix\"),a=this.hasAttribute(\"no-tooltip\"),s=Number(this.getAttribute(\"size\"))||0,i=s>=12?`${s}px`:\"inherit\",d=r?e.compact:e.address,m=!a&&r?` title=\"${e.address}\"`:\"\",y=n?`<span class=\"prefix\">${e.prefix} </span>`:\"\",p=r?\"address-compact\":\"address\",l=t?\" clickable\":\"\",b=t?`<button class=\"copy-btn\" aria-label=\"Copy DID\">${ye}</button>`:\"\";if(this.shadow.innerHTML=`\n <style>${$e}:host { font-size: ${i}; }</style>\n ${y}<span class=\"${p}${l}\"${m}>${d}</span>${b}\n `,t){let T=this.shadow.querySelector(`.${p}`);T&&T.addEventListener(\"click\",x=>{x.stopPropagation(),this.copyToClipboard(e.copyText)});let _=this.shadow.querySelector(\".copy-btn\");_&&_.addEventListener(\"click\",x=>{x.stopPropagation(),this.copyToClipboard(e.copyText)})}}copyToClipboard(o){navigator.clipboard?.writeText?navigator.clipboard.writeText(o).catch(()=>this.fallbackCopy(o)):this.fallbackCopy(o),this.showCopied()}fallbackCopy(o){try{let e=document.createElement(\"input\");e.value=o,document.body.appendChild(e),e.select(),document.execCommand?.(\"copy\"),e.remove()}catch{}}showCopied(){let o=this.shadow.querySelector(\".copy-btn\");o&&(o.classList.add(\"copied\"),o.innerHTML=Oe,this.copyTimer&&clearTimeout(this.copyTimer),this.copyTimer=setTimeout(()=>{o.classList.remove(\"copied\"),o.innerHTML=ye},1500))}};typeof customElements<\"u\"&&!customElements.get(\"did-address\")&&customElements.define(\"did-address\",Me);var be={en:{signIn:\"Sign in\",signInTo:\"Sign in to {appName}\",subtitle:\"Sign in to continue\",passkeyButton:\"Continue with Passkey\",passkeySignIn:\"Sign in with Passkey\",passkeyCreate:\"Create Passkey\",passkeyFirstTime:\"First time? Create a passkey\",passkeyBackToSignIn:\"Back to sign in\",passkeyWaiting:\"Waiting for passkey\\u2026\",passkeyCreating:\"Creating your passkey\\u2026\",passkeyNoLocal:\"No passkey found on this device.\",passkeyUseAnotherDevice:\"Use another device\",passkeyCreateInstead:\"Create a new passkey instead\",didWalletButton:\"Continue with DID Wallet\",didWalletButtonShort:\"DID Wallet\",scanWithWallet:\"Scan with your DID Wallet\",waitingWallet:\"Requesting connection\",walletContinue:\"Please continue in DID Wallet\",noPasskey:\"No existing passkey found. Enter a name and create one.\",success:\"Success!\",timeout:\"Session timed out. Try again.\",refreshQR:\"Refresh\",namePlaceholder:\"Your name (optional)\",or:\"or\",emailPlaceholder:\"Email address\",emailButton:\"Email\",passkeyUnsupported:\"Your browser does not support Passkey authentication.\",connecting:\"Connecting...\",verifying:\"Verifying...\",openInWallet:\"Open in DID Wallet\",authFailed:\"Authentication failed\",accessDenied:\"Access denied: your account is not permitted to log in to this site.\",regClosed:\"Registration requires an invitation.\",federatedBy:\"Authentication provided by {masterName}\",securedBy:\"Secured by DID Connect\",privacy:\"Privacy\",terms:\"Terms\",back:\"Back\",cancel:\"Cancel\",scanTitle:\"Scan with DID Wallet\",emailTitle:\"Continue with Email\",emailSubmit:\"Send Code\",emailSending:\"Sending code...\",emailSent:\"We sent a 6-digit code to {email}\",emailCodePlaceholder:\"6-digit code\",emailVerify:\"Verify\",emailResend:\"Resend code\",emailInvalid:\"Please enter a valid email address.\",emailCodeInvalid:\"Please enter the 6-digit code.\"},zh:{signIn:\"\\u767B\\u5F55\",signInTo:\"\\u767B\\u5F55\\u5230 {appName}\",subtitle:\"\\u767B\\u5F55\\u4EE5\\u7EE7\\u7EED\",passkeyButton:\"\\u4F7F\\u7528 Passkey \\u7EE7\\u7EED\",passkeySignIn:\"\\u4F7F\\u7528 Passkey \\u767B\\u5F55\",passkeyCreate:\"\\u521B\\u5EFA Passkey\",passkeyFirstTime:\"\\u7B2C\\u4E00\\u6B21\\u4F7F\\u7528\\uFF1F\\u521B\\u5EFA Passkey\",passkeyBackToSignIn:\"\\u8FD4\\u56DE\\u767B\\u5F55\",passkeyWaiting:\"\\u6B63\\u5728\\u7B49\\u5F85 Passkey\\u2026\",passkeyCreating:\"\\u6B63\\u5728\\u521B\\u5EFA Passkey\\u2026\",passkeyNoLocal:\"\\u6B64\\u8BBE\\u5907\\u4E0A\\u6CA1\\u6709\\u627E\\u5230 Passkey\\u3002\",passkeyUseAnotherDevice:\"\\u7528\\u5176\\u4ED6\\u8BBE\\u5907\\u767B\\u5F55\",passkeyCreateInstead:\"\\u6539\\u4E3A\\u521B\\u5EFA\\u65B0\\u7684 Passkey\",didWalletButton:\"\\u4F7F\\u7528 DID \\u94B1\\u5305\\u7EE7\\u7EED\",didWalletButtonShort:\"DID \\u94B1\\u5305\",scanWithWallet:\"\\u8BF7\\u4F7F\\u7528 DID \\u94B1\\u5305\\u626B\\u7801\",waitingWallet:\"\\u6B63\\u5728\\u8BF7\\u6C42\\u8FDE\\u63A5\",walletContinue:\"\\u8BF7\\u5728 DID Wallet \\u4E2D\\u7EE7\\u7EED\\u5B8C\\u6210\\u60A8\\u7684\\u64CD\\u4F5C\",noPasskey:\"\\u672A\\u627E\\u5230\\u5DF2\\u6709 Passkey\\u3002\\u8F93\\u5165\\u540D\\u79F0\\u5E76\\u521B\\u5EFA\\u4E00\\u4E2A\\u3002\",success:\"\\u6210\\u529F\",timeout:\"\\u4F1A\\u8BDD\\u8D85\\u65F6\\uFF0C\\u8BF7\\u91CD\\u8BD5\\u3002\",refreshQR:\"\\u5237\\u65B0\",namePlaceholder:\"\\u4F60\\u7684\\u540D\\u5B57\\uFF08\\u53EF\\u9009\\uFF09\",or:\"\\u6216\",emailPlaceholder:\"\\u90AE\\u7BB1\\u5730\\u5740\",emailButton:\"\\u90AE\\u7BB1\",passkeyUnsupported:\"\\u4F60\\u7684\\u6D4F\\u89C8\\u5668\\u4E0D\\u652F\\u6301 Passkey \\u8BA4\\u8BC1\\u3002\",connecting:\"\\u8FDE\\u63A5\\u4E2D...\",verifying:\"\\u9A8C\\u8BC1\\u4E2D...\",openInWallet:\"\\u5728 DID \\u94B1\\u5305\\u4E2D\\u6253\\u5F00\",authFailed:\"\\u8BA4\\u8BC1\\u5931\\u8D25\",accessDenied:\"\\u8BBF\\u95EE\\u88AB\\u62D2\\u7EDD\\uFF1A\\u4F60\\u7684\\u8D26\\u6237\\u65E0\\u6743\\u767B\\u5F55\\u6B64\\u7AD9\\u70B9\\u3002\",regClosed:\"\\u6CE8\\u518C\\u9700\\u8981\\u9080\\u8BF7\\u3002\",federatedBy:\"\\u8BA4\\u8BC1\\u670D\\u52A1\\u7531 {masterName} \\u63D0\\u4F9B\",securedBy:\"\\u7531 DID Connect \\u63D0\\u4F9B\\u5B89\\u5168\\u4FDD\\u969C\",privacy:\"\\u9690\\u79C1\",terms:\"\\u6761\\u6B3E\",back:\"\\u8FD4\\u56DE\",cancel:\"\\u53D6\\u6D88\",scanTitle:\"\\u4F7F\\u7528 DID \\u94B1\\u5305\\u626B\\u7801\",emailTitle:\"\\u4F7F\\u7528\\u90AE\\u7BB1\\u7EE7\\u7EED\",emailSubmit:\"\\u53D1\\u9001\\u9A8C\\u8BC1\\u7801\",emailSending:\"\\u6B63\\u5728\\u53D1\\u9001\\u9A8C\\u8BC1\\u7801...\",emailSent:\"\\u9A8C\\u8BC1\\u7801\\u5DF2\\u53D1\\u9001\\u81F3 {email}\",emailCodePlaceholder:\"6 \\u4F4D\\u9A8C\\u8BC1\\u7801\",emailVerify:\"\\u9A8C\\u8BC1\",emailResend:\"\\u91CD\\u65B0\\u53D1\\u9001\",emailInvalid:\"\\u8BF7\\u8F93\\u5165\\u6709\\u6548\\u7684\\u90AE\\u7BB1\\u5730\\u5740\\u3002\",emailCodeInvalid:\"\\u8BF7\\u8F93\\u5165 6 \\u4F4D\\u9A8C\\u8BC1\\u7801\\u3002\"}},ce=[{code:\"en\",label:\"English\"},{code:\"zh\",label:\"\\u4E2D\\u6587\"}];function E(o,e,r){let t=e&&be[e]?e:\"en\",n=be[t][o]??o;if(r)for(let[a,s]of Object.entries(r))n=n.replace(new RegExp(`\\\\{${a}\\\\}`,\"g\"),s);return n}var le='<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\"><path fill=\"currentColor\" d=\"M3 20v-2.8q0-.85.438-1.562T4.6 14.55q1.55-.775 3.15-1.162T11 13q.5 0 1 .038t1 .112q-.1 1.45.525 2.738T15.35 18v2zm16 3l-1.5-1.5v-4.65q-1.1-.325-1.8-1.237T15 13.5q0-1.45 1.025-2.475T18.5 10t2.475 1.025T22 13.5q0 1.125-.638 2t-1.612 1.25L21 18l-1.5 1.5L21 21zm-8-11q-1.65 0-2.825-1.175T7 8t1.175-2.825T11 4t2.825 1.175T15 8t-1.175 2.825T11 12m7.5 2q.425 0 .713-.288T19.5 13t-.288-.712T18.5 12t-.712.288T17.5 13t.288.713t.712.287\"/></svg>',we='<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\"><rect x=\"2\" y=\"4\" width=\"20\" height=\"16\" rx=\"3\" fill=\"#4598FA\"/><rect x=\"3\" y=\"6\" width=\"18\" height=\"12\" rx=\"2\" fill=\"white\" opacity=\"0.9\"/><rect x=\"0\" y=\"14\" width=\"24\" height=\"10\" rx=\"0\" fill=\"url(#dw_g)\"/><path d=\"M5.5 9.5h4.5c.3 0 .5.2.5.5v2.5c0 .3-.2.5-.5.5H5.5c-.3 0-.5-.2-.5-.5V10c0-.3.2-.5.5-.5z\" fill=\"#4598FA\"/><defs><linearGradient id=\"dw_g\" x1=\"12\" y1=\"14\" x2=\"12\" y2=\"24\" gradientUnits=\"userSpaceOnUse\"><stop stop-color=\"#77B2F6\"/><stop offset=\"1\" stop-color=\"#4598FA\"/></linearGradient></defs></svg>',Fe='<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\"><path fill=\"currentColor\" d=\"M22 7.535V17a3 3 0 0 1-2.824 2.995L19 20H5a3 3 0 0 1-2.995-2.824L2 17V7.535l9.445 6.297l.116.066a1 1 0 0 0 .878 0l.116-.066z\"/><path fill=\"currentColor\" d=\"M19 4c1.08 0 2.027.57 2.555 1.427L12 11.797l-9.555-6.37a2.999 2.999 0 0 1 2.354-1.42L5 4z\"/></svg>',Ue='<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"><path fill=\"currentColor\" d=\"M12 1L3 5v6c0 5.55 3.84 10.74 9 12c5.16-1.26 9-6.45 9-12V5zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11z\"/></svg>',xe='<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\"><path fill=\"currentColor\" d=\"M20 11H7.83l5.59-5.59L12 4l-8 8l8 8l1.41-1.41L7.83 13H20z\"/></svg>',He={google:'<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 256 262\"><path fill=\"#4285F4\" d=\"M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622l38.755 30.023l2.685.268c24.659-22.774 38.875-56.282 38.875-96.027\"/><path fill=\"#34A853\" d=\"M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055c-34.523 0-63.824-22.773-74.269-54.25l-1.531.13l-40.298 31.187l-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1\"/><path fill=\"#FBBC05\" d=\"M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82c0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602z\"/><path fill=\"#EB4335\" d=\"M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0C79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251\"/></svg>',github:'<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 256 250\" fill=\"currentColor\"><path d=\"M128.001 0C57.317 0 0 57.307 0 128.001c0 56.554 36.676 104.535 87.535 121.46c6.397 1.185 8.746-2.777 8.746-6.158c0-3.052-.12-13.135-.174-23.83c-35.61 7.742-43.124-15.103-43.124-15.103c-5.823-14.795-14.213-18.73-14.213-18.73c-11.613-7.944.876-7.78.876-7.78c12.853.902 19.621 13.19 19.621 13.19c11.417 19.568 29.945 13.911 37.249 10.64c1.149-8.272 4.466-13.92 8.127-17.116c-28.431-3.236-58.318-14.212-58.318-63.258c0-13.975 5-25.394 13.188-34.358c-1.329-3.224-5.71-16.242 1.24-33.874c0 0 10.749-3.44 35.21 13.121c10.21-2.836 21.16-4.258 32.038-4.307c10.878.049 21.837 1.47 32.066 4.307c24.431-16.56 35.165-13.12 35.165-13.12c6.967 17.63 2.584 30.65 1.255 33.873c8.207 8.964 13.173 20.383 13.173 34.358c0 49.163-29.944 59.988-58.447 63.157c4.591 3.972 8.682 11.762 8.682 23.704c0 17.126-.148 30.91-.148 35.126c0 3.407 2.304 7.398 8.792 6.14C219.37 232.5 256 184.537 256 128.002C256 57.307 198.691 0 128.001 0\"/></svg>',apple:'<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 256 315\" fill=\"currentColor\"><path d=\"M213.803 167.03c.442 47.58 41.74 63.413 42.197 63.615c-.35 1.116-6.599 22.563-21.757 44.716c-13.104 19.153-26.705 38.235-48.13 38.63c-21.05.388-27.82-12.483-51.888-12.483c-24.061 0-31.582 12.088-51.51 12.871c-20.68.783-36.428-20.71-49.64-39.793c-27-39.033-47.633-110.3-19.928-158.406c13.763-23.89 38.36-39.017 65.056-39.405c20.307-.388 39.475 13.675 51.889 13.675c12.406 0 35.699-16.895 60.186-14.414c10.25.427 39.026 4.14 57.503 31.186c-1.49.923-34.335 20.044-33.978 59.808M174.24 50.199c10.98-13.29 18.369-31.79 16.353-50.199c-15.826.636-34.962 10.546-46.314 23.828c-10.173 11.763-19.082 30.589-16.678 48.633c17.64 1.365 35.66-8.964 46.639-22.262\"/></svg>',twitter:'<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 256 256\" fill=\"currentColor\"><path d=\"M149.079 108.399L242.33 0h-22.098l-80.97 94.12L74.59 0H0l97.796 142.328L0 256h22.1l85.507-99.395L175.905 256h74.59L149.073 108.399zM118.81 143.58l-9.909-14.172l-78.84-112.773h33.943l63.625 91.011l9.909 14.173l82.705 118.3H186.3l-67.49-96.533z\"/></svg>',auth0:'<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 256 256\"><path fill=\"#EB5424\" d=\"M203.97 200.38L167.47 88l-39.5 112.38h75.98zm0-144.76L167.47 168l75.97-26.16l19.76-60.82c6.64-20.4-1.94-42.72-21.18-52.76l-38.06 27.36zM52.03 55.62L88.54 168l39.5-112.38H52.02zM52.03 200.38L88.54 88L12.56 114.16l-5.92 18.2c-6.64 20.4 1.94 42.72 21.18 52.76l24.2-17.38l.01.01v-.01l-.01.01.01-.37z\"/></svg>',facebook:'<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 256 256\"><path fill=\"#1877F2\" d=\"M256 128C256 57.308 198.692 0 128 0S0 57.308 0 128c0 63.888 46.808 116.843 108 126.445V165H75.5v-37H108V99.8c0-32.08 19.11-49.8 48.348-49.8C170.352 50 185 52.5 185 52.5V84h-16.14C152.959 84 148 93.867 148 103.99V128h35.5l-5.675 37H148v89.445c61.192-9.602 108-62.556 108-126.445\"/></svg>'},Ke='<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\"><path fill=\"currentColor\" d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10s10-4.48 10-10S17.52 2 12 2m-1 17.93c-3.95-.49-7-3.85-7-7.93c0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41c0 2.08-.8 3.97-2.1 5.39\"/></svg>';function Ve(o){return He[o.toLowerCase()]??\"\"}var Q=function(o,e){let n=o,a=te[e],s=null,i=0,d=null,m=[],y={},p=function(u,g){i=n*4+17,s=(function(c){let f=new Array(c);for(let h=0;h<c;h+=1){f[h]=new Array(c);for(let w=0;w<c;w+=1)f[h][w]=null}return f})(i),l(0,0),l(i-7,0),l(0,i-7),_(),T(),A(u,g),n>=7&&x(u),d==null&&(d=U(n,a,m)),D(d,g)},l=function(u,g){for(let c=-1;c<=7;c+=1)if(!(u+c<=-1||i<=u+c))for(let f=-1;f<=7;f+=1)g+f<=-1||i<=g+f||(0<=c&&c<=6&&(f==0||f==6)||0<=f&&f<=6&&(c==0||c==6)||2<=c&&c<=4&&2<=f&&f<=4?s[u+c][g+f]=!0:s[u+c][g+f]=!1)},b=function(){let u=0,g=0;for(let c=0;c<8;c+=1){p(!0,c);let f=q.getLostPoint(y);(c==0||u>f)&&(u=f,g=c)}return g},T=function(){for(let u=8;u<i-8;u+=1)s[u][6]==null&&(s[u][6]=u%2==0);for(let u=8;u<i-8;u+=1)s[6][u]==null&&(s[6][u]=u%2==0)},_=function(){let u=q.getPatternPosition(n);for(let g=0;g<u.length;g+=1)for(let c=0;c<u.length;c+=1){let f=u[g],h=u[c];if(s[f][h]==null)for(let w=-2;w<=2;w+=1)for(let v=-2;v<=2;v+=1)w==-2||w==2||v==-2||v==2||w==0&&v==0?s[f+w][h+v]=!0:s[f+w][h+v]=!1}},x=function(u){let g=q.getBCHTypeNumber(n);for(let c=0;c<18;c+=1){let f=!u&&(g>>c&1)==1;s[Math.floor(c/3)][c%3+i-8-3]=f}for(let c=0;c<18;c+=1){let f=!u&&(g>>c&1)==1;s[c%3+i-8-3][Math.floor(c/3)]=f}},A=function(u,g){let c=a<<3|g,f=q.getBCHTypeInfo(c);for(let h=0;h<15;h+=1){let w=!u&&(f>>h&1)==1;h<6?s[h][8]=w:h<8?s[h+1][8]=w:s[i-15+h][8]=w}for(let h=0;h<15;h+=1){let w=!u&&(f>>h&1)==1;h<8?s[8][i-h-1]=w:h<9?s[8][15-h-1+1]=w:s[8][15-h-1]=w}s[i-8][8]=!u},D=function(u,g){let c=-1,f=i-1,h=7,w=0,v=q.getMaskFunction(g);for(let I=i-1;I>0;I-=2)for(I==6&&(I-=1);;){for(let R=0;R<2;R+=1)if(s[f][I-R]==null){let N=!1;w<u.length&&(N=(u[w]>>>h&1)==1),v(f,I-R)&&(N=!N),s[f][I-R]=N,h-=1,h==-1&&(w+=1,h=7)}if(f+=c,f<0||i<=f){f-=c,c=-c;break}}},$=function(u,g){let c=0,f=0,h=0,w=new Array(g.length),v=new Array(g.length);for(let k=0;k<g.length;k+=1){let S=g[k].dataCount,H=g[k].totalCount-S;f=Math.max(f,S),h=Math.max(h,H),w[k]=new Array(S);for(let W=0;W<w[k].length;W+=1)w[k][W]=255&u.getBuffer()[W+c];c+=S;let se=q.getErrorCorrectPolynomial(H),de=ne(w[k],se.getLength()-1).mod(se);v[k]=new Array(se.getLength()-1);for(let W=0;W<v[k].length;W+=1){let he=W+de.getLength()-v[k].length;v[k][W]=he>=0?de.getAt(he):0}}let I=0;for(let k=0;k<g.length;k+=1)I+=g[k].totalCount;let R=new Array(I),N=0;for(let k=0;k<f;k+=1)for(let S=0;S<g.length;S+=1)k<w[S].length&&(R[N]=w[S][k],N+=1);for(let k=0;k<h;k+=1)for(let S=0;S<g.length;S+=1)k<v[S].length&&(R[N]=v[S][k],N+=1);return R},U=function(u,g,c){let f=Ee.getRSBlocks(u,g),h=ve();for(let v=0;v<c.length;v+=1){let I=c[v];h.put(I.getMode(),4),h.put(I.getLength(),q.getLengthInBits(I.getMode(),u)),I.write(h)}let w=0;for(let v=0;v<f.length;v+=1)w+=f[v].dataCount;if(h.getLengthInBits()>w*8)throw\"code length overflow. (\"+h.getLengthInBits()+\">\"+w*8+\")\";for(h.getLengthInBits()+4<=w*8&&h.put(0,4);h.getLengthInBits()%8!=0;)h.putBit(!1);for(;!(h.getLengthInBits()>=w*8||(h.put(236,8),h.getLengthInBits()>=w*8));)h.put(17,8);return $(h,f)};y.addData=function(u,g){g=g||\"Byte\";let c=null;switch(g){case\"Numeric\":c=We(u);break;case\"Alphanumeric\":c=je(u);break;case\"Byte\":c=qe(u);break;case\"Kanji\":c=ze(u);break;default:throw\"mode:\"+g}m.push(c),d=null},y.isDark=function(u,g){if(u<0||i<=u||g<0||i<=g)throw u+\",\"+g;return s[u][g]},y.getModuleCount=function(){return i},y.make=function(){if(n<1){let u=1;for(;u<40;u++){let g=Ee.getRSBlocks(u,a),c=ve();for(let h=0;h<m.length;h++){let w=m[h];c.put(w.getMode(),4),c.put(w.getLength(),q.getLengthInBits(w.getMode(),u)),w.write(c)}let f=0;for(let h=0;h<g.length;h++)f+=g[h].dataCount;if(c.getLengthInBits()<=f*8)break}n=u}p(!1,b())},y.createTableTag=function(u,g){u=u||2,g=typeof g>\"u\"?u*4:g;let c=\"\";c+='<table style=\"',c+=\" border-width: 0px; border-style: none;\",c+=\" border-collapse: collapse;\",c+=\" padding: 0px; margin: \"+g+\"px;\",c+='\">',c+=\"<tbody>\";for(let f=0;f<y.getModuleCount();f+=1){c+=\"<tr>\";for(let h=0;h<y.getModuleCount();h+=1)c+='<td style=\"',c+=\" border-width: 0px; border-style: none;\",c+=\" border-collapse: collapse;\",c+=\" padding: 0px; margin: 0px;\",c+=\" width: \"+u+\"px;\",c+=\" height: \"+u+\"px;\",c+=\" background-color: \",c+=y.isDark(f,h)?\"#000000\":\"#ffffff\",c+=\";\",c+='\"/>';c+=\"</tr>\"}return c+=\"</tbody>\",c+=\"</table>\",c},y.createSvgTag=function(u,g,c,f){let h={};typeof arguments[0]==\"object\"&&(h=arguments[0],u=h.cellSize,g=h.margin,c=h.alt,f=h.title),u=u||2,g=typeof g>\"u\"?u*4:g,c=typeof c==\"string\"?{text:c}:c||{},c.text=c.text||null,c.id=c.text?c.id||\"qrcode-description\":null,f=typeof f==\"string\"?{text:f}:f||{},f.text=f.text||null,f.id=f.text?f.id||\"qrcode-title\":null;let w=y.getModuleCount()*u+g*2,v,I,R,N,k=\"\",S;for(S=\"l\"+u+\",0 0,\"+u+\" -\"+u+\",0 0,-\"+u+\"z \",k+='<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\"',k+=h.scalable?\"\":' width=\"'+w+'px\" height=\"'+w+'px\"',k+=' viewBox=\"0 0 '+w+\" \"+w+'\" ',k+=' preserveAspectRatio=\"xMinYMin meet\"',k+=f.text||c.text?' role=\"img\" aria-labelledby=\"'+V([f.id,c.id].join(\" \").trim())+'\"':\"\",k+=\">\",k+=f.text?'<title id=\"'+V(f.id)+'\">'+V(f.text)+\"</title>\":\"\",k+=c.text?'<description id=\"'+V(c.id)+'\">'+V(c.text)+\"</description>\":\"\",k+='<rect width=\"100%\" height=\"100%\" fill=\"white\" cx=\"0\" cy=\"0\"/>',k+='<path d=\"',R=0;R<y.getModuleCount();R+=1)for(N=R*u+g,v=0;v<y.getModuleCount();v+=1)y.isDark(R,v)&&(I=v*u+g,k+=\"M\"+I+\",\"+N+S);return k+='\" stroke=\"transparent\" fill=\"black\"/>',k+=\"</svg>\",k},y.createDataURL=function(u,g){u=u||2,g=typeof g>\"u\"?u*4:g;let c=y.getModuleCount()*u+g*2,f=g,h=c-g;return Je(c,c,function(w,v){if(f<=w&&w<h&&f<=v&&v<h){let I=Math.floor((w-f)/u),R=Math.floor((v-f)/u);return y.isDark(R,I)?0:1}else return 1})},y.createImgTag=function(u,g,c){u=u||2,g=typeof g>\"u\"?u*4:g;let f=y.getModuleCount()*u+g*2,h=\"\";return h+=\"<img\",h+=' src=\"',h+=y.createDataURL(u,g),h+='\"',h+=' width=\"',h+=f,h+='\"',h+=' height=\"',h+=f,h+='\"',c&&(h+=' alt=\"',h+=V(c),h+='\"'),h+=\"/>\",h};let V=function(u){let g=\"\";for(let c=0;c<u.length;c+=1){let f=u.charAt(c);switch(f){case\"<\":g+=\"<\";break;case\">\":g+=\">\";break;case\"&\":g+=\"&\";break;case'\"':g+=\""\";break;default:g+=f;break}}return g},Ce=function(u){u=typeof u>\"u\"?2:u;let c=y.getModuleCount()*1+u*2,f=u,h=c-u,w,v,I,R,N,k={\"\\u2588\\u2588\":\"\\u2588\",\"\\u2588 \":\"\\u2580\",\" \\u2588\":\"\\u2584\",\" \":\" \"},S={\"\\u2588\\u2588\":\"\\u2580\",\"\\u2588 \":\"\\u2580\",\" \\u2588\":\" \",\" \":\" \"},H=\"\";for(w=0;w<c;w+=2){for(I=Math.floor((w-f)/1),R=Math.floor((w+1-f)/1),v=0;v<c;v+=1)N=\"\\u2588\",f<=v&&v<h&&f<=w&&w<h&&y.isDark(I,Math.floor((v-f)/1))&&(N=\" \"),f<=v&&v<h&&f<=w+1&&w+1<h&&y.isDark(R,Math.floor((v-f)/1))?N+=\" \":N+=\"\\u2588\",H+=u<1&&w+1>=h?S[N]:k[N];H+=`\n`}return c%2&&u>0?H.substring(0,H.length-c-1)+Array(c+1).join(\"\\u2580\"):H.substring(0,H.length-1)};return y.createASCII=function(u,g){if(u=u||1,u<2)return Ce(g);u-=1,g=typeof g>\"u\"?u*2:g;let c=y.getModuleCount()*u+g*2,f=g,h=c-g,w,v,I,R,N=Array(u+1).join(\"\\u2588\\u2588\"),k=Array(u+1).join(\" \"),S=\"\",H=\"\";for(w=0;w<c;w+=1){for(I=Math.floor((w-f)/u),H=\"\",v=0;v<c;v+=1)R=1,f<=v&&v<h&&f<=w&&w<h&&y.isDark(I,Math.floor((v-f)/u))&&(R=0),H+=R?N:k;for(I=0;I<u;I+=1)S+=H+`\n`}return S.substring(0,S.length-1)},y.renderTo2dContext=function(u,g){g=g||2;let c=y.getModuleCount();for(let f=0;f<c;f++)for(let h=0;h<c;h++)u.fillStyle=y.isDark(f,h)?\"black\":\"white\",u.fillRect(h*g,f*g,g,g)},y};Q.stringToBytes=function(o){let e=[];for(let r=0;r<o.length;r+=1){let t=o.charCodeAt(r);e.push(t&255)}return e};Q.createStringToBytes=function(o,e){let r=(function(){let n=Ge(o),a=function(){let d=n.read();if(d==-1)throw\"eof\";return d},s=0,i={};for(;;){let d=n.read();if(d==-1)break;let m=a(),y=a(),p=a(),l=String.fromCharCode(d<<8|m),b=y<<8|p;i[l]=b,s+=1}if(s!=e)throw s+\" != \"+e;return i})(),t=63;return function(n){let a=[];for(let s=0;s<n.length;s+=1){let i=n.charCodeAt(s);if(i<128)a.push(i);else{let d=r[n.charAt(s)];typeof d==\"number\"?(d&255)==d?a.push(d):(a.push(d>>>8),a.push(d&255)):a.push(t)}}return a}};var M={MODE_NUMBER:1,MODE_ALPHA_NUM:2,MODE_8BIT_BYTE:4,MODE_KANJI:8},te={L:1,M:0,Q:3,H:2},j={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7},q=(function(){let o=[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],e=1335,r=7973,t=21522,n={},a=function(s){let i=0;for(;s!=0;)i+=1,s>>>=1;return i};return n.getBCHTypeInfo=function(s){let i=s<<10;for(;a(i)-a(e)>=0;)i^=e<<a(i)-a(e);return(s<<10|i)^t},n.getBCHTypeNumber=function(s){let i=s<<12;for(;a(i)-a(r)>=0;)i^=r<<a(i)-a(r);return s<<12|i},n.getPatternPosition=function(s){return o[s-1]},n.getMaskFunction=function(s){switch(s){case j.PATTERN000:return function(i,d){return(i+d)%2==0};case j.PATTERN001:return function(i,d){return i%2==0};case j.PATTERN010:return function(i,d){return d%3==0};case j.PATTERN011:return function(i,d){return(i+d)%3==0};case j.PATTERN100:return function(i,d){return(Math.floor(i/2)+Math.floor(d/3))%2==0};case j.PATTERN101:return function(i,d){return i*d%2+i*d%3==0};case j.PATTERN110:return function(i,d){return(i*d%2+i*d%3)%2==0};case j.PATTERN111:return function(i,d){return(i*d%3+(i+d)%2)%2==0};default:throw\"bad maskPattern:\"+s}},n.getErrorCorrectPolynomial=function(s){let i=ne([1],0);for(let d=0;d<s;d+=1)i=i.multiply(ne([1,z.gexp(d)],0));return i},n.getLengthInBits=function(s,i){if(1<=i&&i<10)switch(s){case M.MODE_NUMBER:return 10;case M.MODE_ALPHA_NUM:return 9;case M.MODE_8BIT_BYTE:return 8;case M.MODE_KANJI:return 8;default:throw\"mode:\"+s}else if(i<27)switch(s){case M.MODE_NUMBER:return 12;case M.MODE_ALPHA_NUM:return 11;case M.MODE_8BIT_BYTE:return 16;case M.MODE_KANJI:return 10;default:throw\"mode:\"+s}else if(i<41)switch(s){case M.MODE_NUMBER:return 14;case M.MODE_ALPHA_NUM:return 13;case M.MODE_8BIT_BYTE:return 16;case M.MODE_KANJI:return 12;default:throw\"mode:\"+s}else throw\"type:\"+i},n.getLostPoint=function(s){let i=s.getModuleCount(),d=0;for(let p=0;p<i;p+=1)for(let l=0;l<i;l+=1){let b=0,T=s.isDark(p,l);for(let _=-1;_<=1;_+=1)if(!(p+_<0||i<=p+_))for(let x=-1;x<=1;x+=1)l+x<0||i<=l+x||_==0&&x==0||T==s.isDark(p+_,l+x)&&(b+=1);b>5&&(d+=3+b-5)}for(let p=0;p<i-1;p+=1)for(let l=0;l<i-1;l+=1){let b=0;s.isDark(p,l)&&(b+=1),s.isDark(p+1,l)&&(b+=1),s.isDark(p,l+1)&&(b+=1),s.isDark(p+1,l+1)&&(b+=1),(b==0||b==4)&&(d+=3)}for(let p=0;p<i;p+=1)for(let l=0;l<i-6;l+=1)s.isDark(p,l)&&!s.isDark(p,l+1)&&s.isDark(p,l+2)&&s.isDark(p,l+3)&&s.isDark(p,l+4)&&!s.isDark(p,l+5)&&s.isDark(p,l+6)&&(d+=40);for(let p=0;p<i;p+=1)for(let l=0;l<i-6;l+=1)s.isDark(l,p)&&!s.isDark(l+1,p)&&s.isDark(l+2,p)&&s.isDark(l+3,p)&&s.isDark(l+4,p)&&!s.isDark(l+5,p)&&s.isDark(l+6,p)&&(d+=40);let m=0;for(let p=0;p<i;p+=1)for(let l=0;l<i;l+=1)s.isDark(l,p)&&(m+=1);let y=Math.abs(100*m/i/i-50)/5;return d+=y*10,d},n})(),z=(function(){let o=new Array(256),e=new Array(256);for(let t=0;t<8;t+=1)o[t]=1<<t;for(let t=8;t<256;t+=1)o[t]=o[t-4]^o[t-5]^o[t-6]^o[t-8];for(let t=0;t<255;t+=1)e[o[t]]=t;let r={};return r.glog=function(t){if(t<1)throw\"glog(\"+t+\")\";return e[t]},r.gexp=function(t){for(;t<0;)t+=255;for(;t>=256;)t-=255;return o[t]},r})(),ne=function(o,e){if(typeof o.length>\"u\")throw o.length+\"/\"+e;let r=(function(){let n=0;for(;n<o.length&&o[n]==0;)n+=1;let a=new Array(o.length-n+e);for(let s=0;s<o.length-n;s+=1)a[s]=o[s+n];return a})(),t={};return t.getAt=function(n){return r[n]},t.getLength=function(){return r.length},t.multiply=function(n){let a=new Array(t.getLength()+n.getLength()-1);for(let s=0;s<t.getLength();s+=1)for(let i=0;i<n.getLength();i+=1)a[s+i]^=z.gexp(z.glog(t.getAt(s))+z.glog(n.getAt(i)));return ne(a,0)},t.mod=function(n){if(t.getLength()-n.getLength()<0)return t;let a=z.glog(t.getAt(0))-z.glog(n.getAt(0)),s=new Array(t.getLength());for(let i=0;i<t.getLength();i+=1)s[i]=t.getAt(i);for(let i=0;i<n.getLength();i+=1)s[i]^=z.gexp(z.glog(n.getAt(i))+a);return ne(s,0).mod(n)},t},Ee=(function(){let o=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12,7,37,13],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]],e=function(n,a){let s={};return s.totalCount=n,s.dataCount=a,s},r={},t=function(n,a){switch(a){case te.L:return o[(n-1)*4+0];case te.M:return o[(n-1)*4+1];case te.Q:return o[(n-1)*4+2];case te.H:return o[(n-1)*4+3];default:return}};return r.getRSBlocks=function(n,a){let s=t(n,a);if(typeof s>\"u\")throw\"bad rs block @ typeNumber:\"+n+\"/errorCorrectionLevel:\"+a;let i=s.length/3,d=[];for(let m=0;m<i;m+=1){let y=s[m*3+0],p=s[m*3+1],l=s[m*3+2];for(let b=0;b<y;b+=1)d.push(e(p,l))}return d},r})(),ve=function(){let o=[],e=0,r={};return r.getBuffer=function(){return o},r.getAt=function(t){let n=Math.floor(t/8);return(o[n]>>>7-t%8&1)==1},r.put=function(t,n){for(let a=0;a<n;a+=1)r.putBit((t>>>n-a-1&1)==1)},r.getLengthInBits=function(){return e},r.putBit=function(t){let n=Math.floor(e/8);o.length<=n&&o.push(0),t&&(o[n]|=128>>>e%8),e+=1},r},We=function(o){let e=M.MODE_NUMBER,r=o,t={};t.getMode=function(){return e},t.getLength=function(s){return r.length},t.write=function(s){let i=r,d=0;for(;d+2<i.length;)s.put(n(i.substring(d,d+3)),10),d+=3;d<i.length&&(i.length-d==1?s.put(n(i.substring(d,d+1)),4):i.length-d==2&&s.put(n(i.substring(d,d+2)),7))};let n=function(s){let i=0;for(let d=0;d<s.length;d+=1)i=i*10+a(s.charAt(d));return i},a=function(s){if(\"0\"<=s&&s<=\"9\")return s.charCodeAt(0)-48;throw\"illegal char :\"+s};return t},je=function(o){let e=M.MODE_ALPHA_NUM,r=o,t={};t.getMode=function(){return e},t.getLength=function(a){return r.length},t.write=function(a){let s=r,i=0;for(;i+1<s.length;)a.put(n(s.charAt(i))*45+n(s.charAt(i+1)),11),i+=2;i<s.length&&a.put(n(s.charAt(i)),6)};let n=function(a){if(\"0\"<=a&&a<=\"9\")return a.charCodeAt(0)-48;if(\"A\"<=a&&a<=\"Z\")return a.charCodeAt(0)-65+10;switch(a){case\" \":return 36;case\"$\":return 37;case\"%\":return 38;case\"*\":return 39;case\"+\":return 40;case\"-\":return 41;case\".\":return 42;case\"/\":return 43;case\":\":return 44;default:throw\"illegal char :\"+a}};return t},qe=function(o){let e=M.MODE_8BIT_BYTE,r=o,t=Q.stringToBytes(o),n={};return n.getMode=function(){return e},n.getLength=function(a){return t.length},n.write=function(a){for(let s=0;s<t.length;s+=1)a.put(t[s],8)},n},ze=function(o){let e=M.MODE_KANJI,r=o,t=Q.stringToBytes;(function(s,i){let d=t(s);if(d.length!=2||(d[0]<<8|d[1])!=i)throw\"sjis not supported.\"})(\"\\u53CB\",38726);let n=t(o),a={};return a.getMode=function(){return e},a.getLength=function(s){return~~(n.length/2)},a.write=function(s){let i=n,d=0;for(;d+1<i.length;){let m=(255&i[d])<<8|255&i[d+1];if(33088<=m&&m<=40956)m-=33088;else if(57408<=m&&m<=60351)m-=49472;else throw\"illegal char at \"+(d+1)+\"/\"+m;m=(m>>>8&255)*192+(m&255),s.put(m,13),d+=2}if(d<i.length)throw\"illegal char at \"+(d+1)},a},_e=function(){let o=[],e={};return e.writeByte=function(r){o.push(r&255)},e.writeShort=function(r){e.writeByte(r),e.writeByte(r>>>8)},e.writeBytes=function(r,t,n){t=t||0,n=n||r.length;for(let a=0;a<n;a+=1)e.writeByte(r[a+t])},e.writeString=function(r){for(let t=0;t<r.length;t+=1)e.writeByte(r.charCodeAt(t))},e.toByteArray=function(){return o},e.toString=function(){let r=\"\";r+=\"[\";for(let t=0;t<o.length;t+=1)t>0&&(r+=\",\"),r+=o[t];return r+=\"]\",r},e},Ye=function(){let o=0,e=0,r=0,t=\"\",n={},a=function(i){t+=String.fromCharCode(s(i&63))},s=function(i){if(i<0)throw\"n:\"+i;if(i<26)return 65+i;if(i<52)return 97+(i-26);if(i<62)return 48+(i-52);if(i==62)return 43;if(i==63)return 47;throw\"n:\"+i};return n.writeByte=function(i){for(o=o<<8|i&255,e+=8,r+=1;e>=6;)a(o>>>e-6),e-=6},n.flush=function(){if(e>0&&(a(o<<6-e),o=0,e=0),r%3!=0){let i=3-r%3;for(let d=0;d<i;d+=1)t+=\"=\"}},n.toString=function(){return t},n},Ge=function(o){let e=o,r=0,t=0,n=0,a={};a.read=function(){for(;n<8;){if(r>=e.length){if(n==0)return-1;throw\"unexpected end of file./\"+n}let d=e.charAt(r);if(r+=1,d==\"=\")return n=0,-1;if(d.match(/^\\s$/))continue;t=t<<6|s(d.charCodeAt(0)),n+=6}let i=t>>>n-8&255;return n-=8,i};let s=function(i){if(65<=i&&i<=90)return i-65;if(97<=i&&i<=122)return i-97+26;if(48<=i&&i<=57)return i-48+52;if(i==43)return 62;if(i==47)return 63;throw\"c:\"+i};return a},Qe=function(o,e){let r=o,t=e,n=new Array(o*e),a={};a.setPixel=function(m,y,p){n[y*r+m]=p},a.write=function(m){m.writeString(\"GIF87a\"),m.writeShort(r),m.writeShort(t),m.writeByte(128),m.writeByte(0),m.writeByte(0),m.writeByte(0),m.writeByte(0),m.writeByte(0),m.writeByte(255),m.writeByte(255),m.writeByte(255),m.writeString(\",\"),m.writeShort(0),m.writeShort(0),m.writeShort(r),m.writeShort(t),m.writeByte(0);let y=2,p=i(y);m.writeByte(y);let l=0;for(;p.length-l>255;)m.writeByte(255),m.writeBytes(p,l,255),l+=255;m.writeByte(p.length-l),m.writeBytes(p,l,p.length-l),m.writeByte(0),m.writeString(\";\")};let s=function(m){let y=m,p=0,l=0,b={};return b.write=function(T,_){if(T>>>_)throw\"length over\";for(;p+_>=8;)y.writeByte(255&(T<<p|l)),_-=8-p,T>>>=8-p,l=0,p=0;l=T<<p|l,p=p+_},b.flush=function(){p>0&&y.writeByte(l)},b},i=function(m){let y=1<<m,p=(1<<m)+1,l=m+1,b=d();for(let D=0;D<y;D+=1)b.add(String.fromCharCode(D));b.add(String.fromCharCode(y)),b.add(String.fromCharCode(p));let T=_e(),_=s(T);_.write(y,l);let x=0,A=String.fromCharCode(n[x]);for(x+=1;x<n.length;){let D=String.fromCharCode(n[x]);x+=1,b.contains(A+D)?A=A+D:(_.write(b.indexOf(A),l),b.size()<4095&&(b.size()==1<<l&&(l+=1),b.add(A+D)),A=D)}return _.write(b.indexOf(A),l),_.write(p,l),_.flush(),T.toByteArray()},d=function(){let m={},y=0,p={};return p.add=function(l){if(p.contains(l))throw\"dup key:\"+l;m[l]=y,y+=1},p.size=function(){return y},p.indexOf=function(l){return m[l]},p.contains=function(l){return typeof m[l]<\"u\"},p};return a},Je=function(o,e,r){let t=Qe(o,e);for(let i=0;i<e;i+=1)for(let d=0;d<o;d+=1)t.setPixel(d,i,r(d,i));let n=_e();t.write(n);let a=Ye(),s=n.toByteArray();for(let i=0;i<s.length;i+=1)a.writeByte(s[i]);return a.flush(),\"data:image/gif;base64,\"+a},Xe=Q,ln=Q.stringToBytes;function Ze(o,e,r=200){ue(o);let t=document.createElement(\"canvas\"),n=t.getContext?.(\"2d\")??null;if(!n){let p=document.createElement(\"a\");p.href=e,p.textContent=\"Open in DID Wallet\",p.target=\"_blank\",p.rel=\"noopener\",o.appendChild(p);return}let a=Xe(0,\"M\");a.addData(e),a.make();let s=a.getModuleCount(),i=Math.floor(r/s),d=i*s;t.width=d,t.height=d,t.style.display=\"block\",t.style.width=`${r}px`,t.style.height=`${r}px`,t.style.imageRendering=\"pixelated\";let m=\"#e8e8e8\";n.fillStyle=m,n.fillRect(0,0,d,d),n.fillStyle=\"#000000\";for(let p=0;p<s;p++)for(let l=0;l<s;l++)a.isDark(p,l)&&n.fillRect(l*i,p*i,i,i);let y=document.createElement(\"div\");y.style.cssText=\"display:inline-block;padding:10px;background:#e8e8e8;border-radius:8px;line-height:0\",y.appendChild(t),o.appendChild(y)}function ue(o){o.innerHTML=\"\"}var et=`\n${Ne}\n${Be}\n${Le}\n\n/* Layout */\nbody {\n min-height: 100vh;\n display: flex;\n align-items: center;\n justify-content: center;\n background-image: radial-gradient(ellipse at 50% 0%, rgba(108,71,255,0.08) 0%, transparent 60%);\n}\n\n/* Card \\u2014 fixed width prevents shrinking when views switch */\n.card {\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: var(--radius-lg);\n padding: 48px 40px;\n width: min(400px, calc(100vw - 32px));\n text-align: center;\n box-shadow: var(--shadow-lg), 0 0 80px -20px rgba(108,71,255,0.15);\n}\n\n/* Brand header */\n.brand-header { margin-bottom: 24px; }\n.app-logo {\n width: 48px;\n height: 48px;\n border-radius: var(--radius-sm);\n object-fit: contain;\n margin-bottom: 16px;\n}\nh1 {\n font-size: 24px;\n font-weight: 600;\n margin-bottom: 8px;\n color: var(--text-white);\n letter-spacing: -0.01em;\n line-height: 1.25;\n /* Never let a long \"Sign in to {appName}\" blow past two lines \\u2014 clamp with\n an ellipsis so the card height stays stable. */\n display: -webkit-box;\n -webkit-line-clamp: 2;\n line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n.subtitle {\n font-size: 14px;\n color: var(--text-secondary);\n margin-bottom: 0;\n line-height: 1.5;\n}\n.federated-hint {\n font-size: 12px;\n color: var(--text-secondary);\n margin-top: 6px;\n margin-bottom: 0;\n opacity: 0.7;\n}\n\n/* Input */\n.input {\n width: 100%;\n height: 36px;\n padding: 0 12px;\n font-size: 14px;\n color: var(--text);\n background: var(--bg-input);\n border: 1px solid var(--border-strong);\n border-radius: var(--radius-sm);\n outline: none;\n margin-bottom: 16px;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n}\n.input:focus { border-color: var(--blue); box-shadow: var(--shadow-focus); }\n.input::placeholder { color: var(--text-placeholder); }\n\n/* Button */\n.btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n width: 100%;\n height: 36px;\n padding: 0 16px;\n font-size: 14px;\n font-weight: 500;\n color: #fff;\n background: var(--blue);\n border: none;\n border-radius: var(--radius-sm);\n cursor: pointer;\n transition: background-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;\n line-height: 1;\n}\n.btn:hover { background: var(--blue-hover); transform: translateY(-1px); box-shadow: 0 2px 8px rgba(108,71,255,0.3); }\n.btn:disabled { opacity: 0.5; cursor: not-allowed; pointer-events: none; transform: none; box-shadow: none; }\n.btn:focus-visible { box-shadow: var(--shadow-focus); }\n.btn svg { width: 18px; height: 18px; flex-shrink: 0; }\n.btn-secondary {\n background: transparent;\n border: 1px solid var(--border-strong);\n color: var(--text);\n}\n.btn-secondary:hover { background: var(--bg-hover); border-color: rgba(255,255,255,0.20); }\n\n/* Status */\n.status {\n margin-top: 16px;\n font-size: 13px;\n min-height: 20px;\n color: var(--text-secondary);\n}\n.status.error { color: var(--red-text); }\n\n/* Name row (passkey registration) */\n.name-row { display: none; }\n.name-row.visible { display: block; }\n\n/* Passkey section + secondary text action (\"First time? Create a passkey\") */\n.passkey-section > .btn + .passkey-alt,\n.passkey-section > .name-row + .btn { margin-top: 10px; }\n.passkey-alt {\n display: block;\n width: 100%;\n margin-top: 10px;\n padding: 4px;\n background: none;\n border: none;\n font-size: 13px;\n color: var(--text-secondary);\n cursor: pointer;\n text-align: center;\n transition: color 0.15s ease;\n}\n.passkey-alt:hover { color: var(--text); text-decoration: underline; }\n.passkey-alt:focus-visible { box-shadow: var(--shadow-focus); border-radius: var(--radius-sm); outline: none; }\n\n/* Divider */\n.divider {\n display: flex;\n align-items: center;\n margin: 20px 0;\n color: var(--text-secondary);\n font-size: 13px;\n}\n.divider::before, .divider::after {\n content: '';\n flex: 1;\n border-bottom: 1px solid var(--border);\n}\n.divider::before { margin-right: 12px; }\n.divider::after { margin-left: 12px; }\n\n/* DID Wallet section (methods view) */\n.did-wallet-section { margin-top: 8px; }\n\n/* QR view */\n.qr-view {\n position: relative;\n margin: 20px auto;\n display: flex;\n justify-content: center;\n min-height: 220px;\n align-items: center;\n}\n.qr-placeholder {\n width: 220px;\n height: 220px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius);\n background: var(--bg-elevated);\n}\n.qr-spinner {\n display: inline-block;\n width: 32px; height: 32px;\n border: 3px solid var(--border-strong);\n border-top-color: var(--blue-muted);\n border-radius: 50%;\n animation: spin 0.6s linear infinite;\n}\n#did-connect-status {\n font-size: 13px;\n color: var(--text-secondary);\n}\n\n/* Connect result state */\n.connect-result {\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 120px;\n margin: 32px 0;\n animation: fadeIn 200ms ease-out;\n}\n.result-icon {\n width: 64px;\n height: 64px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 28px;\n font-weight: bold;\n}\n.result-error {\n background: rgba(239, 68, 68, 0.15);\n color: var(--red-text);\n border: 2px solid rgba(239, 68, 68, 0.3);\n}\n.result-loading {\n background: transparent;\n}\n.result-loading .qr-spinner {\n width: 36px;\n height: 36px;\n}\n.result-back-btn {\n margin-top: 16px;\n}\n@keyframes fadeIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }\n\n/* QR timeout overlay */\n.qr-timeout-overlay {\n position: absolute;\n inset: 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n background: rgba(0, 0, 0, 0.65);\n border-radius: var(--radius);\n backdrop-filter: blur(2px);\n}\n.qr-timeout-overlay .timeout-text {\n font-size: 13px;\n color: #fff;\n}\n.qr-timeout-overlay .refresh-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 6px 20px;\n font-size: 14px;\n font-weight: 500;\n color: #fff;\n background: var(--blue);\n border: none;\n border-radius: var(--radius-sm);\n cursor: pointer;\n transition: background-color 0.15s ease;\n}\n.qr-timeout-overlay .refresh-btn:hover {\n background: var(--blue-hover);\n}\n/* Connect status views (scanned / success) */\n.connect-status-icon {\n position: relative;\n width: 72px;\n height: 72px;\n margin: 16px auto;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n@keyframes connectSpin { to { transform: rotate(360deg); } }\n.connect-spinner-ring {\n position: absolute;\n inset: 0;\n width: 72px;\n height: 72px;\n border-radius: 50%;\n border: 3px solid rgba(255,255,255,0.1);\n border-top-color: var(--blue, #6c47ff);\n animation: connectSpin 0.8s linear infinite;\n}\n.connect-status-badge {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n height: 44px;\n}\n.connect-status-badge svg { width: 36px; height: 36px; }\n.connect-status-icon.connect-success svg { animation: fadeIn 0.3s ease; }\n.connect-subtitle {\n font-size: 13px;\n color: var(--text-secondary, #9394a1);\n text-align: center;\n margin: 4px 0 0;\n}\n.status.success { color: #49dc6e; font-weight: 500; }\n.deep-link-btn {\n margin-top: 12px;\n text-decoration: none;\n}\n\n/* Email section (methods view) */\n.email-section { margin-top: 8px; }\n\n/* Back button */\n.back-btn {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n background: none;\n border: none;\n color: var(--text-secondary);\n font-size: 14px;\n cursor: pointer;\n padding: 0;\n margin-bottom: 16px;\n float: left;\n}\n.back-btn:hover { color: var(--text); }\n.back-btn svg { width: 18px; height: 18px; }\n/* Escape hatch \\u2014 a quiet \"Cancel\" link, top-left of the card, mirroring the\n locale switcher (top-right). Conventional spot for a standalone auth card.\n Low in the hierarchy (tertiary color, small) so it never competes with the\n auth methods; it's an exit, not an action. */\n.home-link {\n position: absolute;\n top: 16px;\n /* Align the arrow's left edge to the content column (card's 40px padding), so\n Cancel sits on the same left line as the title and buttons \\u2014 no left padding\n of its own, which would push it off that line. */\n left: 40px;\n display: inline-flex;\n align-items: center;\n gap: 3px;\n background: none;\n border: none;\n color: var(--text-tertiary);\n font-size: 13px;\n line-height: 1;\n text-decoration: none;\n cursor: pointer;\n padding: 2px 4px 2px 0;\n transition: color 0.15s ease;\n}\n.home-link:hover { color: var(--text-secondary); }\n.home-link svg { width: 16px; height: 16px; }\n\n/* View title */\n.view-title {\n font-size: 20px;\n font-weight: 600;\n color: var(--text-white);\n margin-bottom: 8px;\n clear: both;\n padding-top: 8px;\n}\n\n/* OAuth section \\u2014 single provider (legacy) */\n.oauth-section { margin-top: 0; }\n\n/* OAuth grid \\u2014 2+ providers in 2-column layout (legacy) */\n.oauth-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 8px;\n margin-bottom: 8px;\n}\n.oauth-grid .oauth-btn:last-child:nth-child(odd) {\n grid-column: 1 / -1;\n}\n\n/* Unified secondary methods grid \\u2014 2-column layout */\n.methods-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 8px;\n}\n/* Odd last child (e.g. DID Wallet when total is odd) spans full width */\n.methods-grid .method-btn:last-child:nth-child(odd) {\n grid-column: 1 / -1;\n}\n\n.method-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n width: 100%;\n min-height: 36px;\n padding: 6px 16px;\n font-size: 14px;\n font-weight: 500;\n color: var(--text);\n background: var(--bg-elevated);\n border: 1px solid var(--border-strong);\n border-radius: var(--radius-sm);\n cursor: pointer;\n transition: background-color 0.15s ease, border-color 0.15s ease, opacity 0.2s ease;\n white-space: normal;\n}\n.method-btn:hover { background: var(--bg-hover); border-color: var(--blue-muted); }\n.method-btn:disabled { cursor: default; pointer-events: none; }\n.method-btn svg { width: 18px; height: 18px; flex-shrink: 0; }\n\n.oauth-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n width: 100%;\n height: 36px;\n padding: 0 16px;\n font-size: 14px;\n font-weight: 500;\n color: var(--text);\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: var(--radius-sm);\n cursor: pointer;\n transition: background-color 0.15s ease, opacity 0.2s ease, border-color 0.2s ease;\n margin-bottom: 0;\n}\n.oauth-section .oauth-btn { margin-bottom: 8px; }\n.oauth-btn:hover { background: var(--bg-hover); }\n.oauth-btn:disabled { cursor: default; pointer-events: none; }\n.oauth-btn svg { width: 18px; height: 18px; flex-shrink: 0; }\n.oauth-btn-loading {\n opacity: 0.8;\n border-color: var(--blue);\n color: var(--text-secondary);\n}\n@keyframes oauth-spin { to { transform: rotate(360deg); } }\n.oauth-spinner {\n display: inline-block;\n width: 14px; height: 14px;\n border: 2px solid var(--border-strong);\n border-top-color: var(--blue-muted);\n border-radius: 50%;\n animation: oauth-spin 0.6s linear infinite;\n}\n.oauth-check { color: var(--green-text); font-size: 16px; }\n\n/* Browser compatibility warning */\n.compat-warning {\n font-size: 13px;\n color: var(--text-tertiary);\n padding: 12px;\n background: var(--bg-elevated);\n border-radius: var(--radius-sm);\n margin-bottom: 16px;\n}\n\n/* Trust footer */\n.login-footer {\n margin-top: 24px;\n padding-top: 20px;\n border-top: 1px solid var(--border);\n}\n.secured-badge {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n font-size: 12px;\n color: var(--text-tertiary);\n}\n.secured-badge svg { width: 14px; height: 14px; flex-shrink: 0; }\n.footer-links {\n margin-top: 8px;\n font-size: 12px;\n color: var(--text-tertiary);\n}\n.footer-links a {\n color: var(--text-secondary);\n text-decoration: none;\n}\n.footer-links a:hover { text-decoration: underline; }\n\n/* \\u2500\\u2500\\u2500 Animations \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500 */\n@keyframes fadeIn {\n from { opacity: 0; transform: translateY(4px); }\n to { opacity: 1; transform: translateY(0); }\n}\n@keyframes fadeOut {\n from { opacity: 1; }\n to { opacity: 0; }\n}\n.card[data-view] { animation: fadeIn 200ms ease-out; }\n\n/* QR code scale-in */\n.qr-view { animation: qrScaleIn 300ms ease-out; }\n@keyframes qrScaleIn {\n from { opacity: 0; transform: scale(0.95); }\n to { opacity: 1; transform: scale(1); }\n}\n\n/* Button loading spinner */\n@keyframes spin { to { transform: rotate(360deg); } }\n.btn-spinner {\n display: inline-block;\n width: 16px; height: 16px;\n border: 2px solid rgba(255,255,255,0.3);\n border-top-color: #fff;\n border-radius: 50%;\n animation: spin 0.6s linear infinite;\n}\n\n/* Success checkmark */\n@keyframes checkDraw {\n from { stroke-dashoffset: 24; }\n to { stroke-dashoffset: 0; }\n}\n.success-check {\n display: inline-block;\n width: 20px; height: 20px;\n vertical-align: middle;\n}\n.success-check path {\n stroke-dasharray: 24;\n stroke-dashoffset: 24;\n animation: checkDraw 400ms ease-out forwards;\n}\n\n/* Light theme background gradient adjustment */\n[data-theme=\"light\"] body {\n background-image: radial-gradient(ellipse at 50% 0%, rgba(108,71,255,0.05) 0%, transparent 60%);\n}\n[data-theme=\"light\"] .card {\n box-shadow: var(--shadow-lg), 0 0 80px -20px rgba(108,71,255,0.08);\n}\n[data-theme=\"light\"] .btn:hover {\n box-shadow: 0 2px 8px rgba(108,71,255,0.15);\n}\n\n/* Language switcher */\n.locale-switcher {\n position: absolute;\n top: 16px;\n right: 16px;\n}\n.locale-btn {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 4px 10px;\n font-size: 12px;\n color: var(--text-secondary);\n background: var(--bg-elevated);\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius-full);\n cursor: pointer;\n transition: color 0.15s, border-color 0.15s;\n}\n.locale-btn:hover {\n color: var(--text);\n border-color: var(--border-strong);\n}\n.locale-btn svg { width: 14px; height: 14px; flex-shrink: 0; }\n.card { position: relative; }\n`;function Y(o){let e=new Uint8Array(o),r=\"\";for(let t=0;t<e.length;t++)r+=String.fromCharCode(e[t]);return btoa(r).replace(/\\+/g,\"-\").replace(/\\//g,\"_\").replace(/=+$/,\"\")}function ee(o){let e=o.replace(/-/g,\"+\").replace(/_/g,\"/\"),r=atob(e),t=new Uint8Array(r.length);for(let n=0;n<r.length;n++)t[n]=r.charCodeAt(n);return t.buffer}function tt(o){let e=o.response;return{id:o.id,rawId:Y(o.rawId),type:o.type,response:{attestationObject:Y(e.attestationObject),clientDataJSON:Y(e.clientDataJSON)},clientExtensionResults:o.getClientExtensionResults(),authenticatorAttachment:o.authenticatorAttachment}}function nt(o){let e=o.response;return{id:o.id,rawId:Y(o.rawId),type:o.type,response:{authenticatorData:Y(e.authenticatorData),clientDataJSON:Y(e.clientDataJSON),signature:Y(e.signature),userHandle:e.userHandle?Y(e.userHandle):void 0},clientExtensionResults:o.getClientExtensionResults(),authenticatorAttachment:o.authenticatorAttachment}}async function oe(o){let e=o.prefix||\"/.well-known/service/api/passkey\",r=o.onStatus||(()=>{}),t=o.authOnly||!1,n=o.registerOnly||!1,a=o.invitationId||void 0,s=o.texts||{},i=s.waiting||\"Waiting for passkey...\",d=s.creating||\"Creating your passkey...\",m=s.verifying||\"Verifying...\";async function y(){let x=await fetch(`${e}/auth`,{method:\"GET\"});if(!x.ok)throw new Error(\"Failed to get auth challenge\");return x.json()}async function p(x){let A=[];x&&A.push(`name=${encodeURIComponent(x)}`),a&&A.push(`invitationId=${encodeURIComponent(a)}`);let D=A.length?`?${A.join(\"&\")}`:\"\",$=await fetch(`${e}/register${D}`,{method:\"GET\"});if(!$.ok)throw new Error(\"Failed to get register challenge\");return $.json()}function l(x,A){let D=new Error(x);throw A&&(D.code=A),D}async function b(x,A){let D=await fetch(`${e}/auth`,{method:\"POST\",headers:{\"Content-Type\":\"application/json\"},body:JSON.stringify({challengeId:x,credential:A})});if(!D.ok){let $=await D.json().catch(()=>({}));l($.error||\"Authentication failed\",$.code)}return D.json()}async function T(x,A,D){let $=await fetch(`${e}/register`,{method:\"POST\",headers:{\"Content-Type\":\"application/json\"},body:JSON.stringify({challengeId:x,credential:A,name:D||void 0})});if(!$.ok){let U=await $.json().catch(()=>({}));l(U.error||\"Registration failed\",U.code)}return $.json()}if(!n){r(i,!1);try{let x=await y(),{challengeId:A}=x,D=x.authentication||x,$={publicKey:{...D,challenge:ee(D.challenge),allowCredentials:(D.allowCredentials||[]).map(V=>({...V,id:ee(V.id)}))}},U;try{U=await navigator.credentials.get($)}catch{if(t)return{success:!1,error:\"no_passkey\"}}if(U){let V=nt(U);return r(m,!1),await b(A,V),{success:!0}}}catch(x){let A=x instanceof Error?x.message:\"Authentication failed\",D=x instanceof Error?x.code:void 0;return r(A,!0),{success:!1,error:A,code:D}}}let _=await p(o.name||\"\");if(_.registrationAllowed===!1)return r(\"Registration requires an invitation.\",!0),{success:!1,error:\"registration_closed\"};r(d,!1);try{let x=_.registration||_,A={publicKey:{...x,challenge:ee(x.challenge),user:{...x.user,id:ee(x.user.id)},excludeCredentials:(x.excludeCredentials||[]).map(U=>({...U,id:ee(U.id)}))}},D=await navigator.credentials.create(A),$=tt(D);return r(m,!1),await T(_.challengeId,$,o.name||\"\"),{success:!0}}catch(x){let A=x instanceof Error?x.message:\"Passkey setup failed\",D=x instanceof Error?x.code:void 0;return r(A,!0),{success:!1,error:A,code:D}}}function G(o,e){let r=o instanceof Error?o.message:\"\",t=o instanceof Error&&\"code\"in o?o.code:\"\";return t===\"FORBIDDEN\"?E(\"accessDenied\",e):r===\"registration_closed\"||t===\"registration_closed\"?E(\"regClosed\",e):r||E(\"authFailed\",e)}var rt=\"https://web.abtwallet.io/\";function ot(){return typeof navigator>\"u\"?!1:/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)}function st(){return typeof window>\"u\"?null:window.ABT_DEV??window.ABT??null}function K(o){return o.replace(/&/g,\"&\").replace(/</g,\"<\").replace(/>/g,\">\").replace(/\"/g,\""\").replace(/'/g,\"'\")}var ke=class{container;config;http;options;tokenSession=null;styleEl=null;mounted=!1;currentView=\"methods\";cardEl=null;mediaQuery=null;mediaListener=null;constructor(o,e,r,t){if(this.container=o,this.config=e,this.http=r,this.options={methods:[\"passkey\",\"did-connect\"],locale:\"en\",...t},!this.options.invitationId&&typeof document<\"u\"){let n=document.cookie.match(/(?:^|;\\s*)pending_invitation=([^;]*)/);if(n)try{this.options.invitationId=decodeURIComponent(n[1])}catch{}}}mount(){if(this.mounted)return;this.mounted=!0,this.styleEl=document.createElement(\"style\"),this.styleEl.textContent=et,this.container.appendChild(this.styleEl),this.applyTheme();let o=this.filterMethods(this.options.methods);this.renderCard(o)}destroy(){this.tokenSession&&(this.tokenSession.destroy(),this.tokenSession=null),this.mediaQuery&&this.mediaListener&&(this.mediaQuery.removeEventListener(\"change\",this.mediaListener),this.mediaQuery=null,this.mediaListener=null),document.documentElement.removeAttribute(\"data-theme\"),this.container.innerHTML=\"\",this.styleEl=null,this.cardEl=null,this.currentView=\"methods\",this.mounted=!1}update(o){Object.assign(this.options,o),this.mounted&&(this.destroy(),this.mount())}applyTheme(){let o=this.options.theme??\"dark\";if(o===\"light\")document.documentElement.dataset.theme=\"light\";else if(o===\"auto\"&&typeof window.matchMedia==\"function\"){let e=window.matchMedia(\"(prefers-color-scheme: light)\");this.mediaQuery=e;let r=t=>{t?document.documentElement.dataset.theme=\"light\":document.documentElement.removeAttribute(\"data-theme\")};r(e.matches),this.mediaListener=t=>r(t.matches),e.addEventListener(\"change\",this.mediaListener)}}filterMethods(o){return o.filter(e=>e===\"passkey\"?typeof window<\"u\"&&window.PublicKeyCredential!==void 0&&typeof window.PublicKeyCredential==\"function\":!0)}renderCard(o){let e=document.createElement(\"div\");e.className=\"card\",this.cardEl=e,this.container.appendChild(e),this.switchView(\"methods\",o)}renderLocaleSwitcher(o){if(ce.length<2)return;let e=document.createElement(\"div\");e.className=\"locale-switcher\";let r=this.options.locale,t=ce.find(a=>a.code!==r)??ce[0],n=document.createElement(\"button\");n.type=\"button\",n.className=\"locale-btn\",n.innerHTML=`${Ke} ${K(t.label)}`,n.title=t.label,n.addEventListener(\"click\",()=>{this.options.onLocaleChange?.(t.code),this.update({locale:t.code})}),e.appendChild(n),o.appendChild(e)}switchView(o,e){if(!this.cardEl)return;let r=this.options.locale;switch(this.currentView===\"qr\"&&o!==\"qr\"&&this.tokenSession&&(this.tokenSession.destroy(),this.tokenSession=null),this.currentView=o,this.cardEl.innerHTML=\"\",this.cardEl.dataset.view=o,this.renderLocaleSwitcher(this.cardEl),o){case\"methods\":this.renderMethodsView(this.cardEl,r,e??this.filterMethods(this.options.methods));break;case\"qr\":this.renderQRView(this.cardEl,r);break;case\"email\":this.renderEmailView(this.cardEl,r);break}}renderMethodsView(o,e,r){this.renderHomeLink(o,e),this.renderBrandHeader(o,e);let t=this.options.methods.includes(\"passkey\");if(t&&!r.includes(\"passkey\")){let s=document.createElement(\"div\");s.className=\"compat-warning\",s.textContent=E(\"passkeyUnsupported\",e),o.appendChild(s)}let n=r.includes(\"passkey\");n&&this.renderPasskeySection(o,e);let a=r.filter(s=>s!==\"passkey\");if(n&&a.length>0&&this.renderDivider(o,e),a.length>0&&this.renderSecondaryGrid(o,e,r),r.length===0&&!t){let s=document.createElement(\"p\");s.className=\"subtitle\",s.textContent=\"No authentication methods available.\",o.appendChild(s)}this.renderTrustFooter(o,e)}renderQRView(o,e){this.renderBackButton(o,e);let r=document.createElement(\"h2\");r.className=\"view-title\",r.textContent=E(\"scanTitle\",e),o.appendChild(r);let t=document.createElement(\"div\");t.id=\"did-connect-qr\",t.className=\"qr-view\",t.innerHTML='<div class=\"qr-placeholder\"><span class=\"qr-spinner\"></span></div>',o.appendChild(t);let n=document.createElement(\"button\");n.className=\"btn btn-secondary deep-link-btn\",n.id=\"did-wallet-open-btn\",n.textContent=E(\"openInWallet\",e),n.disabled=!0,o.appendChild(n);let a=document.createElement(\"div\");a.id=\"did-connect-status\",a.className=\"status\",o.appendChild(a),this.renderTrustFooter(o,e),this.startDIDConnectInView(a,t,n,e)}renderEmailView(o,e){this.renderBackButton(o,e);let r=document.createElement(\"h2\");r.className=\"view-title\",r.textContent=E(\"emailTitle\",e),o.appendChild(r);let t=document.createElement(\"input\");t.className=\"input\",t.type=\"email\",t.placeholder=E(\"emailPlaceholder\",e),o.appendChild(t);let n=document.createElement(\"button\");n.className=\"btn\",n.textContent=E(\"emailSubmit\",e),o.appendChild(n);let a=document.createElement(\"div\");a.className=\"status\",o.appendChild(a);let s=document.createElement(\"div\");s.className=\"email-code-section\",s.style.display=\"none\";let i=document.createElement(\"input\");i.className=\"input\",i.type=\"text\",i.inputMode=\"numeric\",i.maxLength=6,i.autocomplete=\"one-time-code\",i.placeholder=E(\"emailCodePlaceholder\",e),s.appendChild(i);let d=document.createElement(\"button\");d.className=\"btn\",d.textContent=E(\"emailVerify\",e),s.appendChild(d);let m=document.createElement(\"button\");m.className=\"btn btn-secondary\",m.textContent=E(\"emailResend\",e),m.style.marginTop=\"8px\",s.appendChild(m),o.insertBefore(s,a),this.renderTrustFooter(o,e);let y=\"\",p=this.config.servicePrefix??\"/.well-known/service\",l=async()=>{let T=t.value.trim();if(!T?.includes(\"@\")){a.textContent=E(\"emailInvalid\",e),a.className=\"status error\";return}n.disabled=!0,a.textContent=E(\"emailSending\",e),a.className=\"status\";try{await this.http.post(`${p}/api/email/sendCode`,{email:T,locale:e}),y=T,t.style.display=\"none\",n.style.display=\"none\",s.style.display=\"\",r.textContent=E(\"emailVerify\",e),a.textContent=E(\"emailSent\",e,{email:T}),a.className=\"status\",i.focus()}catch(_){let x=_ instanceof Error?_.message:\"Failed to send email\";a.textContent=x,a.className=\"status error\",n.disabled=!1}},b=async()=>{let T=i.value.trim();if(!T||T.length!==6){a.textContent=E(\"emailCodeInvalid\",e),a.className=\"status error\";return}d.disabled=!0,a.textContent=E(\"verifying\",e),a.className=\"status\";try{await this.http.post(`${p}/api/email/login`,{code:T}),a.textContent=E(\"success\",e),a.className=\"status\",this.handleSuccess()}catch(_){_ instanceof Error&&\"code\"in _&&_.code===\"FORBIDDEN\"?this.showFullError(G(_,e)):(a.textContent=G(_,e),a.className=\"status error\",d.disabled=!1,i.value=\"\",i.focus())}};n.addEventListener(\"click\",l),t.addEventListener(\"keydown\",T=>{T.key===\"Enter\"&&l()}),d.addEventListener(\"click\",b),i.addEventListener(\"keydown\",T=>{T.key===\"Enter\"&&b()}),m.addEventListener(\"click\",async()=>{m.disabled=!0,a.textContent=E(\"emailSending\",e),a.className=\"status\";try{await this.http.post(`${p}/api/email/sendCode`,{email:y,locale:e}),a.textContent=E(\"emailSent\",e,{email:y}),a.className=\"status\"}catch(T){let _=T instanceof Error?T.message:\"Failed to resend\";a.textContent=_,a.className=\"status error\"}m.disabled=!1})}renderBackButton(o,e){let r=document.createElement(\"button\");r.className=\"back-btn\",r.innerHTML=`${xe} ${K(E(\"back\",e))}`,r.addEventListener(\"click\",()=>this.switchView(\"methods\")),o.appendChild(r)}renderHomeLink(o,e){let{homeUrl:r,onCancel:t}=this.options;if(!t&&!r)return;let n=`${xe} ${K(E(\"cancel\",e))}`;if(t){let a=document.createElement(\"button\");a.type=\"button\",a.className=\"home-link\",a.innerHTML=n,a.addEventListener(\"click\",()=>this.options.onCancel?.()),o.appendChild(a)}else{let a=document.createElement(\"a\");a.className=\"home-link\",a.href=r,a.innerHTML=n,o.appendChild(a)}}renderBrandHeader(o,e){let r=document.createElement(\"div\");if(r.className=\"brand-header\",this.options.appLogo){let n=document.createElement(\"img\");n.className=\"app-logo\",n.src=K(this.options.appLogo),n.alt=this.options.appName?K(this.options.appName):\"\",n.width=48,n.height=48,r.appendChild(n)}let t=document.createElement(\"h1\");if(this.options.appName?t.textContent=E(\"signInTo\",e,{appName:this.options.appName}):t.textContent=E(\"signIn\",e),r.appendChild(t),this.options.masterAppName){let n=document.createElement(\"p\");n.className=\"federated-hint\",n.textContent=E(\"federatedBy\",e,{masterName:this.options.masterAppName}),r.appendChild(n)}o.appendChild(r)}renderTrustFooter(o,e){let r=document.createElement(\"div\");r.className=\"login-footer\";let t=document.createElement(\"div\");t.className=\"secured-badge\",t.innerHTML=`${Ue} ${K(E(\"securedBy\",e))}`,r.appendChild(t);let{privacyUrl:n,termsUrl:a}=this.options;if(n||a){let s=document.createElement(\"div\");if(s.className=\"footer-links\",n){let i=document.createElement(\"a\");i.href=n,i.target=\"_blank\",i.rel=\"noopener noreferrer\",i.textContent=E(\"privacy\",e),s.appendChild(i)}if(n&&a&&s.appendChild(document.createTextNode(\" \\xB7 \")),a){let i=document.createElement(\"a\");i.href=a,i.target=\"_blank\",i.rel=\"noopener noreferrer\",i.textContent=E(\"terms\",e),s.appendChild(i)}r.appendChild(s)}o.appendChild(r)}renderDivider(o,e){let r=document.createElement(\"div\");r.className=\"divider\",r.textContent=E(\"or\",e),o.appendChild(r)}renderPasskeySection(o,e){let r=document.createElement(\"div\");r.className=\"passkey-section\",o.appendChild(r);let t=document.createElement(\"div\");t.className=\"status\",t.id=\"passkey-status\",o.appendChild(t);let n=(l,b)=>{t.textContent=l,t.className=`status${b?\" error\":\"\"}`},a=()=>n(\"\",!1),s={waiting:E(\"passkeyWaiting\",e),creating:E(\"passkeyCreating\",e),verifying:E(\"verifying\",e)},i=l=>l.success?(n(E(\"success\",e),!1),this.handleSuccess(),!0):l.code===\"FORBIDDEN\"||l.error===\"registration_closed\"?(this.showFullError(G({message:l.error,code:l.code||l.error},e)),!0):!1,d=()=>{r.innerHTML=\"\",n(E(\"passkeyNoLocal\",e),!1);let l=document.createElement(\"button\");l.className=\"btn\",l.id=\"passkey-btn\",l.innerHTML=`${le} ${K(E(\"passkeyUseAnotherDevice\",e))}`,r.appendChild(l),l.addEventListener(\"click\",()=>y());let b=document.createElement(\"button\");b.type=\"button\",b.className=\"passkey-alt\",b.textContent=E(\"passkeyCreateInstead\",e),r.appendChild(b),b.addEventListener(\"click\",()=>m())},m=()=>{r.innerHTML=\"\",a();let l=document.createElement(\"div\");l.className=\"name-row visible\",l.id=\"passkey-name-row\";let b=document.createElement(\"input\");b.className=\"input\",b.id=\"passkey-name-input\",b.type=\"text\",b.placeholder=E(\"namePlaceholder\",e),b.autocomplete=\"name\",l.appendChild(b),r.appendChild(l);let T=document.createElement(\"button\");T.className=\"btn\",T.id=\"passkey-btn\",T.innerHTML=`${le} ${K(E(\"passkeyCreate\",e))}`,r.appendChild(T);let _=document.createElement(\"button\");_.type=\"button\",_.className=\"passkey-alt\",_.textContent=E(\"passkeyBackToSignIn\",e),r.appendChild(_);let x=async()=>{T.disabled=!0;let A=await oe({registerOnly:!0,name:b.value.trim(),onStatus:n,texts:s,prefix:this.options.passkeyPrefix,invitationId:this.options.invitationId});i(A)||(A.error&&n(A.error,!0),T.disabled=!1)};T.addEventListener(\"click\",x),b.addEventListener(\"keydown\",A=>{A.key===\"Enter\"&&x()}),_.addEventListener(\"click\",()=>p()),b.focus()},y=async()=>{let l=r.querySelector(\"#passkey-btn\");l&&(l.disabled=!0);let b=await oe({authOnly:!0,onStatus:n,texts:s,prefix:this.options.passkeyPrefix,invitationId:this.options.invitationId});if(!i(b)){if(b.error&&b.error!==\"no_passkey\"){n(b.error,!0),l&&(l.disabled=!1);return}d()}},p=()=>{r.innerHTML=\"\",a();let l=document.createElement(\"button\");l.className=\"btn\",l.id=\"passkey-btn\",l.innerHTML=`${le} ${K(E(\"passkeySignIn\",e))}`,r.appendChild(l),l.addEventListener(\"click\",()=>y());let b=document.createElement(\"button\");b.type=\"button\",b.className=\"passkey-alt\",b.textContent=E(\"passkeyFirstTime\",e),r.appendChild(b),b.addEventListener(\"click\",()=>m())};this.options.invitationId?m():p()}startOAuthPopup(o,e){let r=this.config.servicePrefix??\"/.well-known/service\",n=`${this.options.masterOAuthOrigin?`${this.options.masterOAuthOrigin}${r}`:r}/api/oauth/${encodeURIComponent(o)}/login?returnUrl=${encodeURIComponent(window.location.href)}`,a=500,s=620,i=window.screenX+(window.innerWidth-a)/2,d=window.screenY+(window.innerHeight-s)/2,m=window.open(n,\"oauth-login:popup\",`left=${i},top=${d},width=${a},height=${s},resizable,scrollbars=yes,status=1,popup`);if(!m){window.location.href=n;return}let y=e.innerHTML;e.innerHTML='<span class=\"oauth-spinner\"></span> Authorizing...',e.disabled=!0,e.classList.add(\"oauth-btn-loading\");let p=()=>{e.innerHTML=y,e.disabled=!1,e.classList.remove(\"oauth-btn-loading\")},l=async _=>{if(!_.data||_.data.type!==\"authorization_response\")return;if(T(),m.close(),_.data.error){p();return}let{code:x,state:A}=_.data.response??_.data;if(!x){p();return}e.innerHTML='<span class=\"oauth-spinner\"></span> Signing in...';try{let D={provider:o,code:x,state:A};this.options.invitationId&&(D.invitationId=this.options.invitationId),await this.http.post(`${r}/api/oauth/login`,D),e.innerHTML='<span class=\"oauth-check\">\\u2713</span> Success',this.handleSuccess()}catch(D){let $=D instanceof Error?D.message:\"\",U=D instanceof Error&&\"code\"in D?D.code:\"\";U===\"FORBIDDEN\"||$===\"registration_closed\"||U===\"registration_closed\"?this.showFullError(G(D,this.options.locale)):p()}},b=setInterval(()=>{m.closed&&(T(),p())},500),T=()=>{clearInterval(b),window.removeEventListener(\"message\",l)};window.addEventListener(\"message\",l)}renderSecondaryGrid(o,e,r){let t=document.createElement(\"div\");if(t.className=\"methods-grid\",r.includes(\"oauth\")&&this.options.oauthProviders?.length){let n=(this.options.oauthProviders??[]).filter(a=>a.enabled);for(let a of n){let s=document.createElement(\"button\");s.className=\"method-btn\",s.dataset.provider=a.id;let i=Ve(a.id);s.innerHTML=`${i} ${K(a.name)}`,t.appendChild(s),s.addEventListener(\"click\",()=>{this.startOAuthPopup(a.id,s)})}}if(r.includes(\"email\")){let n=document.createElement(\"button\");n.className=\"method-btn\",n.id=\"email-btn\",n.innerHTML=`${Fe} ${K(E(\"emailButton\",e))}`,t.appendChild(n),n.addEventListener(\"click\",()=>{this.switchView(\"email\")})}if(r.includes(\"did-connect\")){let n=document.createElement(\"button\");n.className=\"method-btn\",n.id=\"did-wallet-btn\";let a=t.children.length>0?E(\"didWalletButtonShort\",e):E(\"didWalletButton\",e);n.innerHTML=`${we} ${K(a)}`,t.appendChild(n),n.addEventListener(\"click\",()=>{this.switchView(\"qr\")})}o.appendChild(t)}async startDIDConnectInView(o,e,r,t){o.textContent=E(\"connecting\",t);try{this.tokenSession?.destroy(),this.tokenSession=new Z(this.config,this.http),this.tokenSession.on(\"succeed\",async()=>{this.showConnectResult(e,r,o,E(\"verifying\",t));try{let a=this.config.servicePrefix??\"/.well-known/service\";await this.http.post(`${a}/api/did/connect/complete`,{token:this.tokenSession.state.token}),this.showConnectSuccess(e,r,o,t),this.handleSuccess()}catch(a){this.showConnectResult(e,r,o,G(a,t),!0)}}),this.tokenSession.on(\"timeout\",()=>{o.textContent=E(\"timeout\",t);let a=document.createElement(\"div\");a.className=\"qr-timeout-overlay\",a.innerHTML=`<span class=\"timeout-text\">${K(E(\"timeout\",t))}</span>`;let s=document.createElement(\"button\");s.className=\"refresh-btn\",s.textContent=E(\"refreshQR\",t),s.addEventListener(\"click\",()=>{a.remove(),ue(e),e.innerHTML='<div class=\"qr-placeholder\"><span class=\"qr-spinner\"></span></div>',r.disabled=!0,this.startDIDConnectInView(o,e,r,t)}),a.appendChild(s),e.appendChild(a)}),this.tokenSession.on(\"error\",a=>{this.showConnectResult(e,r,o,G(a,t),!0)}),this.tokenSession.on(\"statusChange\",a=>{a.status===\"scanned\"&&this.showConnectScanned(e,r,o,t)});let n=await this.tokenSession.create({locale:t});n.url&&(Ze(e,n.url),r.disabled=!1,r.addEventListener(\"click\",()=>{this.showConnectScanned(e,r,o,t),this.openInWallet(n.url,t)})),o.textContent=E(\"scanWithWallet\",t),this.tokenSession.startPolling()}catch(n){o.textContent=G(n,t),ue(e)}}showFullError(o){if(!this.cardEl)return;let e=this.options.locale;this.tokenSession&&(this.tokenSession.destroy(),this.tokenSession=null),this.cardEl.innerHTML=\"\",this.cardEl.dataset.view=\"error\",this.renderLocaleSwitcher(this.cardEl);let r=document.createElement(\"div\");r.className=\"connect-result\",r.innerHTML='<div class=\"result-icon result-error\">\\u2715</div>',this.cardEl.appendChild(r);let t=document.createElement(\"div\");t.className=\"status error\",t.textContent=o,this.cardEl.appendChild(t);let n=document.createElement(\"button\");n.className=\"btn btn-secondary result-back-btn\",n.textContent=E(\"back\",e),n.addEventListener(\"click\",()=>this.switchView(\"methods\")),this.cardEl.appendChild(n),this.renderTrustFooter(this.cardEl,e)}showConnectScanned(o,e,r,t=\"en\"){o.style.display=\"none\",e.style.display=\"none\";let n=o.parentElement?.querySelector(\".connect-result\");n||(n=document.createElement(\"div\"),n.className=\"connect-result\",o.parentElement?.insertBefore(n,r)),n.innerHTML=`<div class=\"connect-status-icon\">\n <div class=\"connect-spinner-ring\"></div>\n <span class=\"connect-status-badge\">${we}</span>\n </div>`,r.textContent=E(\"waitingWallet\",t),r.className=\"status\";let a=r.parentElement?.querySelector(\".connect-subtitle\");a||(a=document.createElement(\"p\"),a.className=\"connect-subtitle\",r.parentElement?.insertBefore(a,r.nextSibling)),a.textContent=E(\"walletContinue\",t);let s=r.parentElement?.querySelector(\".result-back-btn\");s||(s=document.createElement(\"button\"),s.className=\"btn btn-secondary result-back-btn\",s.textContent=E(\"back\",t),s.addEventListener(\"click\",()=>this.switchView(\"methods\")),a.parentElement?.insertBefore(s,a.nextSibling))}showConnectSuccess(o,e,r,t=\"en\"){o.style.display=\"none\",e.style.display=\"none\";let n=o.parentElement?.querySelector(\".connect-result\");n||(n=document.createElement(\"div\"),n.className=\"connect-result\",o.parentElement?.insertBefore(n,r)),n.innerHTML=`<div class=\"connect-status-icon connect-success\">\n <svg viewBox=\"0 0 64 64\" width=\"64\" height=\"64\"><circle cx=\"32\" cy=\"32\" r=\"28\" fill=\"none\" stroke=\"#49dc6e\" stroke-width=\"3\"/><polyline points=\"20 33 28 41 44 25\" fill=\"none\" stroke=\"#49dc6e\" stroke-width=\"3\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>\n </div>`,r.textContent=E(\"success\",t),r.className=\"status success\";let a=r.parentElement?.querySelector(\".connect-subtitle\"),s=r.parentElement?.querySelector(\".result-back-btn\");a?.remove(),s?.remove()}showConnectResult(o,e,r,t,n=!1){let a=this.options.locale;o.style.display=\"none\",e.style.display=\"none\";let s=o.parentElement?.querySelector(\".connect-result\");if(s||(s=document.createElement(\"div\"),s.className=\"connect-result\",o.parentElement?.insertBefore(s,r)),n){s.innerHTML='<div class=\"result-icon result-error\">\\u2715</div>',r.textContent=t,r.className=\"status error\";let i=r.parentElement?.querySelector(\".result-back-btn\");i||(i=document.createElement(\"button\"),i.className=\"btn btn-secondary result-back-btn\",i.textContent=E(\"back\",a),i.addEventListener(\"click\",()=>this.switchView(\"methods\")),r.parentElement?.insertBefore(i,r.nextSibling))}else s.innerHTML='<div class=\"result-icon result-loading\"><span class=\"qr-spinner\"></span></div>',r.textContent=t,r.className=\"status\"}openInWallet(o,e=\"en\"){let r=ot(),t=st();if(r){let n=o.replace(/^https?:\\/\\//,\"abt://\");window.location.href=n}else if(t&&typeof t.open==\"function\"){let n=this.tokenSession?.state;t.open({action:this.config.action??\"login\",locale:e,url:encodeURIComponent(o),appInfo:n?.appInfo??{},memberAppInfo:n?.memberAppInfo??{}})}else{let n=`${rt}?action=requestAuth&url=${encodeURIComponent(o)}`,a=414,s=736,i=window.screenX+window.innerWidth-a,d=window.screenY;window.open(n,\"did-wallet:popup\",`left=${i},top=${d},width=${a},height=${s},resizable,scrollbars=yes,status=1,popup`)}}handleSuccess(){this.options.onSuccess?this.options.onSuccess():setTimeout(()=>location.reload(),300)}};return Se(it);})();\n","qr.c0d203ca.js":"\"use strict\";var __QRBundle=(()=>{var K=Object.defineProperty;var tt=Object.getOwnPropertyDescriptor;var et=Object.getOwnPropertyNames;var nt=Object.prototype.hasOwnProperty;var rt=(h,w)=>{for(var d in w)K(h,d,{get:w[d],enumerable:!0})},ot=(h,w,d,o)=>{if(w&&typeof w==\"object\"||typeof w==\"function\")for(let i of et(w))!nt.call(h,i)&&i!==d&&K(h,i,{get:()=>w[i],enumerable:!(o=tt(w,i))||o.enumerable});return h};var it=h=>ot(K({},\"__esModule\",{value:!0}),h);var dt={};rt(dt,{clearQR:()=>F,renderQR:()=>Z});var U=function(h,w){let i=h,l=Q[w],e=null,t=0,c=null,x=[],y={},a=function(r,u){t=i*4+17,e=(function(n){let f=new Array(n);for(let s=0;s<n;s+=1){f[s]=new Array(n);for(let p=0;p<n;p+=1)f[s][p]=null}return f})(t),g(0,0),g(t-7,0),g(0,t-7),k(),m(),R(r,u),i>=7&&E(r),c==null&&(c=q(i,l,x)),P(c,u)},g=function(r,u){for(let n=-1;n<=7;n+=1)if(!(r+n<=-1||t<=r+n))for(let f=-1;f<=7;f+=1)u+f<=-1||t<=u+f||(0<=n&&n<=6&&(f==0||f==6)||0<=f&&f<=6&&(n==0||n==6)||2<=n&&n<=4&&2<=f&&f<=4?e[r+n][u+f]=!0:e[r+n][u+f]=!1)},_=function(){let r=0,u=0;for(let n=0;n<8;n+=1){a(!0,n);let f=O.getLostPoint(y);(n==0||r>f)&&(r=f,u=n)}return u},m=function(){for(let r=8;r<t-8;r+=1)e[r][6]==null&&(e[r][6]=r%2==0);for(let r=8;r<t-8;r+=1)e[6][r]==null&&(e[6][r]=r%2==0)},k=function(){let r=O.getPatternPosition(i);for(let u=0;u<r.length;u+=1)for(let n=0;n<r.length;n+=1){let f=r[u],s=r[n];if(e[f][s]==null)for(let p=-2;p<=2;p+=1)for(let b=-2;b<=2;b+=1)p==-2||p==2||b==-2||b==2||p==0&&b==0?e[f+p][s+b]=!0:e[f+p][s+b]=!1}},E=function(r){let u=O.getBCHTypeNumber(i);for(let n=0;n<18;n+=1){let f=!r&&(u>>n&1)==1;e[Math.floor(n/3)][n%3+t-8-3]=f}for(let n=0;n<18;n+=1){let f=!r&&(u>>n&1)==1;e[n%3+t-8-3][Math.floor(n/3)]=f}},R=function(r,u){let n=l<<3|u,f=O.getBCHTypeInfo(n);for(let s=0;s<15;s+=1){let p=!r&&(f>>s&1)==1;s<6?e[s][8]=p:s<8?e[s+1][8]=p:e[t-15+s][8]=p}for(let s=0;s<15;s+=1){let p=!r&&(f>>s&1)==1;s<8?e[8][t-s-1]=p:s<9?e[8][15-s-1+1]=p:e[8][15-s-1]=p}e[t-8][8]=!r},P=function(r,u){let n=-1,f=t-1,s=7,p=0,b=O.getMaskFunction(u);for(let B=t-1;B>0;B-=2)for(B==6&&(B-=1);;){for(let M=0;M<2;M+=1)if(e[f][B-M]==null){let T=!1;p<r.length&&(T=(r[p]>>>s&1)==1),b(f,B-M)&&(T=!T),e[f][B-M]=T,s-=1,s==-1&&(p+=1,s=7)}if(f+=n,f<0||t<=f){f-=n,n=-n;break}}},V=function(r,u){let n=0,f=0,s=0,p=new Array(u.length),b=new Array(u.length);for(let A=0;A<u.length;A+=1){let C=u[A].dataCount,L=u[A].totalCount-C;f=Math.max(f,C),s=Math.max(s,L),p[A]=new Array(C);for(let I=0;I<p[A].length;I+=1)p[A][I]=255&r.getBuffer()[I+n];n+=C;let j=O.getErrorCorrectPolynomial(L),Y=S(p[A],j.getLength()-1).mod(j);b[A]=new Array(j.getLength()-1);for(let I=0;I<b[A].length;I+=1){let G=I+Y.getLength()-b[A].length;b[A][I]=G>=0?Y.getAt(G):0}}let B=0;for(let A=0;A<u.length;A+=1)B+=u[A].totalCount;let M=new Array(B),T=0;for(let A=0;A<f;A+=1)for(let C=0;C<u.length;C+=1)A<p[C].length&&(M[T]=p[C][A],T+=1);for(let A=0;A<s;A+=1)for(let C=0;C<u.length;C+=1)A<b[C].length&&(M[T]=b[C][A],T+=1);return M},q=function(r,u,n){let f=J.getRSBlocks(r,u),s=$();for(let b=0;b<n.length;b+=1){let B=n[b];s.put(B.getMode(),4),s.put(B.getLength(),O.getLengthInBits(B.getMode(),r)),B.write(s)}let p=0;for(let b=0;b<f.length;b+=1)p+=f[b].dataCount;if(s.getLengthInBits()>p*8)throw\"code length overflow. (\"+s.getLengthInBits()+\">\"+p*8+\")\";for(s.getLengthInBits()+4<=p*8&&s.put(0,4);s.getLengthInBits()%8!=0;)s.putBit(!1);for(;!(s.getLengthInBits()>=p*8||(s.put(236,8),s.getLengthInBits()>=p*8));)s.put(17,8);return V(s,f)};y.addData=function(r,u){u=u||\"Byte\";let n=null;switch(u){case\"Numeric\":n=st(r);break;case\"Alphanumeric\":n=ft(r);break;case\"Byte\":n=ct(r);break;case\"Kanji\":n=ut(r);break;default:throw\"mode:\"+u}x.push(n),c=null},y.isDark=function(r,u){if(r<0||t<=r||u<0||t<=u)throw r+\",\"+u;return e[r][u]},y.getModuleCount=function(){return t},y.make=function(){if(i<1){let r=1;for(;r<40;r++){let u=J.getRSBlocks(r,l),n=$();for(let s=0;s<x.length;s++){let p=x[s];n.put(p.getMode(),4),n.put(p.getLength(),O.getLengthInBits(p.getMode(),r)),p.write(n)}let f=0;for(let s=0;s<u.length;s++)f+=u[s].dataCount;if(n.getLengthInBits()<=f*8)break}i=r}a(!1,_())},y.createTableTag=function(r,u){r=r||2,u=typeof u>\"u\"?r*4:u;let n=\"\";n+='<table style=\"',n+=\" border-width: 0px; border-style: none;\",n+=\" border-collapse: collapse;\",n+=\" padding: 0px; margin: \"+u+\"px;\",n+='\">',n+=\"<tbody>\";for(let f=0;f<y.getModuleCount();f+=1){n+=\"<tr>\";for(let s=0;s<y.getModuleCount();s+=1)n+='<td style=\"',n+=\" border-width: 0px; border-style: none;\",n+=\" border-collapse: collapse;\",n+=\" padding: 0px; margin: 0px;\",n+=\" width: \"+r+\"px;\",n+=\" height: \"+r+\"px;\",n+=\" background-color: \",n+=y.isDark(f,s)?\"#000000\":\"#ffffff\",n+=\";\",n+='\"/>';n+=\"</tr>\"}return n+=\"</tbody>\",n+=\"</table>\",n},y.createSvgTag=function(r,u,n,f){let s={};typeof arguments[0]==\"object\"&&(s=arguments[0],r=s.cellSize,u=s.margin,n=s.alt,f=s.title),r=r||2,u=typeof u>\"u\"?r*4:u,n=typeof n==\"string\"?{text:n}:n||{},n.text=n.text||null,n.id=n.text?n.id||\"qrcode-description\":null,f=typeof f==\"string\"?{text:f}:f||{},f.text=f.text||null,f.id=f.text?f.id||\"qrcode-title\":null;let p=y.getModuleCount()*r+u*2,b,B,M,T,A=\"\",C;for(C=\"l\"+r+\",0 0,\"+r+\" -\"+r+\",0 0,-\"+r+\"z \",A+='<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\"',A+=s.scalable?\"\":' width=\"'+p+'px\" height=\"'+p+'px\"',A+=' viewBox=\"0 0 '+p+\" \"+p+'\" ',A+=' preserveAspectRatio=\"xMinYMin meet\"',A+=f.text||n.text?' role=\"img\" aria-labelledby=\"'+H([f.id,n.id].join(\" \").trim())+'\"':\"\",A+=\">\",A+=f.text?'<title id=\"'+H(f.id)+'\">'+H(f.text)+\"</title>\":\"\",A+=n.text?'<description id=\"'+H(n.id)+'\">'+H(n.text)+\"</description>\":\"\",A+='<rect width=\"100%\" height=\"100%\" fill=\"white\" cx=\"0\" cy=\"0\"/>',A+='<path d=\"',M=0;M<y.getModuleCount();M+=1)for(T=M*r+u,b=0;b<y.getModuleCount();b+=1)y.isDark(M,b)&&(B=b*r+u,A+=\"M\"+B+\",\"+T+C);return A+='\" stroke=\"transparent\" fill=\"black\"/>',A+=\"</svg>\",A},y.createDataURL=function(r,u){r=r||2,u=typeof u>\"u\"?r*4:u;let n=y.getModuleCount()*r+u*2,f=u,s=n-u;return gt(n,n,function(p,b){if(f<=p&&p<s&&f<=b&&b<s){let B=Math.floor((p-f)/r),M=Math.floor((b-f)/r);return y.isDark(M,B)?0:1}else return 1})},y.createImgTag=function(r,u,n){r=r||2,u=typeof u>\"u\"?r*4:u;let f=y.getModuleCount()*r+u*2,s=\"\";return s+=\"<img\",s+=' src=\"',s+=y.createDataURL(r,u),s+='\"',s+=' width=\"',s+=f,s+='\"',s+=' height=\"',s+=f,s+='\"',n&&(s+=' alt=\"',s+=H(n),s+='\"'),s+=\"/>\",s};let H=function(r){let u=\"\";for(let n=0;n<r.length;n+=1){let f=r.charAt(n);switch(f){case\"<\":u+=\"<\";break;case\">\":u+=\">\";break;case\"&\":u+=\"&\";break;case'\"':u+=\""\";break;default:u+=f;break}}return u},z=function(r){r=typeof r>\"u\"?2:r;let n=y.getModuleCount()*1+r*2,f=r,s=n-r,p,b,B,M,T,A={\"\\u2588\\u2588\":\"\\u2588\",\"\\u2588 \":\"\\u2580\",\" \\u2588\":\"\\u2584\",\" \":\" \"},C={\"\\u2588\\u2588\":\"\\u2580\",\"\\u2588 \":\"\\u2580\",\" \\u2588\":\" \",\" \":\" \"},L=\"\";for(p=0;p<n;p+=2){for(B=Math.floor((p-f)/1),M=Math.floor((p+1-f)/1),b=0;b<n;b+=1)T=\"\\u2588\",f<=b&&b<s&&f<=p&&p<s&&y.isDark(B,Math.floor((b-f)/1))&&(T=\" \"),f<=b&&b<s&&f<=p+1&&p+1<s&&y.isDark(M,Math.floor((b-f)/1))?T+=\" \":T+=\"\\u2588\",L+=r<1&&p+1>=s?C[T]:A[T];L+=`\n`}return n%2&&r>0?L.substring(0,L.length-n-1)+Array(n+1).join(\"\\u2580\"):L.substring(0,L.length-1)};return y.createASCII=function(r,u){if(r=r||1,r<2)return z(u);r-=1,u=typeof u>\"u\"?r*2:u;let n=y.getModuleCount()*r+u*2,f=u,s=n-u,p,b,B,M,T=Array(r+1).join(\"\\u2588\\u2588\"),A=Array(r+1).join(\" \"),C=\"\",L=\"\";for(p=0;p<n;p+=1){for(B=Math.floor((p-f)/r),L=\"\",b=0;b<n;b+=1)M=1,f<=b&&b<s&&f<=p&&p<s&&y.isDark(B,Math.floor((b-f)/r))&&(M=0),L+=M?T:A;for(B=0;B<r;B+=1)C+=L+`\n`}return C.substring(0,C.length-1)},y.renderTo2dContext=function(r,u){u=u||2;let n=y.getModuleCount();for(let f=0;f<n;f++)for(let s=0;s<n;s++)r.fillStyle=y.isDark(f,s)?\"black\":\"white\",r.fillRect(s*u,f*u,u,u)},y};U.stringToBytes=function(h){let w=[];for(let d=0;d<h.length;d+=1){let o=h.charCodeAt(d);w.push(o&255)}return w};U.createStringToBytes=function(h,w){let d=(function(){let i=at(h),l=function(){let c=i.read();if(c==-1)throw\"eof\";return c},e=0,t={};for(;;){let c=i.read();if(c==-1)break;let x=l(),y=l(),a=l(),g=String.fromCharCode(c<<8|x),_=y<<8|a;t[g]=_,e+=1}if(e!=w)throw e+\" != \"+w;return t})(),o=63;return function(i){let l=[];for(let e=0;e<i.length;e+=1){let t=i.charCodeAt(e);if(t<128)l.push(t);else{let c=d[i.charAt(e)];typeof c==\"number\"?(c&255)==c?l.push(c):(l.push(c>>>8),l.push(c&255)):l.push(o)}}return l}};var D={MODE_NUMBER:1,MODE_ALPHA_NUM:2,MODE_8BIT_BYTE:4,MODE_KANJI:8},Q={L:1,M:0,Q:3,H:2},N={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7},O=(function(){let h=[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],w=1335,d=7973,o=21522,i={},l=function(e){let t=0;for(;e!=0;)t+=1,e>>>=1;return t};return i.getBCHTypeInfo=function(e){let t=e<<10;for(;l(t)-l(w)>=0;)t^=w<<l(t)-l(w);return(e<<10|t)^o},i.getBCHTypeNumber=function(e){let t=e<<12;for(;l(t)-l(d)>=0;)t^=d<<l(t)-l(d);return e<<12|t},i.getPatternPosition=function(e){return h[e-1]},i.getMaskFunction=function(e){switch(e){case N.PATTERN000:return function(t,c){return(t+c)%2==0};case N.PATTERN001:return function(t,c){return t%2==0};case N.PATTERN010:return function(t,c){return c%3==0};case N.PATTERN011:return function(t,c){return(t+c)%3==0};case N.PATTERN100:return function(t,c){return(Math.floor(t/2)+Math.floor(c/3))%2==0};case N.PATTERN101:return function(t,c){return t*c%2+t*c%3==0};case N.PATTERN110:return function(t,c){return(t*c%2+t*c%3)%2==0};case N.PATTERN111:return function(t,c){return(t*c%3+(t+c)%2)%2==0};default:throw\"bad maskPattern:\"+e}},i.getErrorCorrectPolynomial=function(e){let t=S([1],0);for(let c=0;c<e;c+=1)t=t.multiply(S([1,v.gexp(c)],0));return t},i.getLengthInBits=function(e,t){if(1<=t&&t<10)switch(e){case D.MODE_NUMBER:return 10;case D.MODE_ALPHA_NUM:return 9;case D.MODE_8BIT_BYTE:return 8;case D.MODE_KANJI:return 8;default:throw\"mode:\"+e}else if(t<27)switch(e){case D.MODE_NUMBER:return 12;case D.MODE_ALPHA_NUM:return 11;case D.MODE_8BIT_BYTE:return 16;case D.MODE_KANJI:return 10;default:throw\"mode:\"+e}else if(t<41)switch(e){case D.MODE_NUMBER:return 14;case D.MODE_ALPHA_NUM:return 13;case D.MODE_8BIT_BYTE:return 16;case D.MODE_KANJI:return 12;default:throw\"mode:\"+e}else throw\"type:\"+t},i.getLostPoint=function(e){let t=e.getModuleCount(),c=0;for(let a=0;a<t;a+=1)for(let g=0;g<t;g+=1){let _=0,m=e.isDark(a,g);for(let k=-1;k<=1;k+=1)if(!(a+k<0||t<=a+k))for(let E=-1;E<=1;E+=1)g+E<0||t<=g+E||k==0&&E==0||m==e.isDark(a+k,g+E)&&(_+=1);_>5&&(c+=3+_-5)}for(let a=0;a<t-1;a+=1)for(let g=0;g<t-1;g+=1){let _=0;e.isDark(a,g)&&(_+=1),e.isDark(a+1,g)&&(_+=1),e.isDark(a,g+1)&&(_+=1),e.isDark(a+1,g+1)&&(_+=1),(_==0||_==4)&&(c+=3)}for(let a=0;a<t;a+=1)for(let g=0;g<t-6;g+=1)e.isDark(a,g)&&!e.isDark(a,g+1)&&e.isDark(a,g+2)&&e.isDark(a,g+3)&&e.isDark(a,g+4)&&!e.isDark(a,g+5)&&e.isDark(a,g+6)&&(c+=40);for(let a=0;a<t;a+=1)for(let g=0;g<t-6;g+=1)e.isDark(g,a)&&!e.isDark(g+1,a)&&e.isDark(g+2,a)&&e.isDark(g+3,a)&&e.isDark(g+4,a)&&!e.isDark(g+5,a)&&e.isDark(g+6,a)&&(c+=40);let x=0;for(let a=0;a<t;a+=1)for(let g=0;g<t;g+=1)e.isDark(g,a)&&(x+=1);let y=Math.abs(100*x/t/t-50)/5;return c+=y*10,c},i})(),v=(function(){let h=new Array(256),w=new Array(256);for(let o=0;o<8;o+=1)h[o]=1<<o;for(let o=8;o<256;o+=1)h[o]=h[o-4]^h[o-5]^h[o-6]^h[o-8];for(let o=0;o<255;o+=1)w[h[o]]=o;let d={};return d.glog=function(o){if(o<1)throw\"glog(\"+o+\")\";return w[o]},d.gexp=function(o){for(;o<0;)o+=255;for(;o>=256;)o-=255;return h[o]},d})(),S=function(h,w){if(typeof h.length>\"u\")throw h.length+\"/\"+w;let d=(function(){let i=0;for(;i<h.length&&h[i]==0;)i+=1;let l=new Array(h.length-i+w);for(let e=0;e<h.length-i;e+=1)l[e]=h[e+i];return l})(),o={};return o.getAt=function(i){return d[i]},o.getLength=function(){return d.length},o.multiply=function(i){let l=new Array(o.getLength()+i.getLength()-1);for(let e=0;e<o.getLength();e+=1)for(let t=0;t<i.getLength();t+=1)l[e+t]^=v.gexp(v.glog(o.getAt(e))+v.glog(i.getAt(t)));return S(l,0)},o.mod=function(i){if(o.getLength()-i.getLength()<0)return o;let l=v.glog(o.getAt(0))-v.glog(i.getAt(0)),e=new Array(o.getLength());for(let t=0;t<o.getLength();t+=1)e[t]=o.getAt(t);for(let t=0;t<i.getLength();t+=1)e[t]^=v.gexp(v.glog(i.getAt(t))+l);return S(e,0).mod(i)},o},J=(function(){let h=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12,7,37,13],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]],w=function(i,l){let e={};return e.totalCount=i,e.dataCount=l,e},d={},o=function(i,l){switch(l){case Q.L:return h[(i-1)*4+0];case Q.M:return h[(i-1)*4+1];case Q.Q:return h[(i-1)*4+2];case Q.H:return h[(i-1)*4+3];default:return}};return d.getRSBlocks=function(i,l){let e=o(i,l);if(typeof e>\"u\")throw\"bad rs block @ typeNumber:\"+i+\"/errorCorrectionLevel:\"+l;let t=e.length/3,c=[];for(let x=0;x<t;x+=1){let y=e[x*3+0],a=e[x*3+1],g=e[x*3+2];for(let _=0;_<y;_+=1)c.push(w(a,g))}return c},d})(),$=function(){let h=[],w=0,d={};return d.getBuffer=function(){return h},d.getAt=function(o){let i=Math.floor(o/8);return(h[i]>>>7-o%8&1)==1},d.put=function(o,i){for(let l=0;l<i;l+=1)d.putBit((o>>>i-l-1&1)==1)},d.getLengthInBits=function(){return w},d.putBit=function(o){let i=Math.floor(w/8);h.length<=i&&h.push(0),o&&(h[i]|=128>>>w%8),w+=1},d},st=function(h){let w=D.MODE_NUMBER,d=h,o={};o.getMode=function(){return w},o.getLength=function(e){return d.length},o.write=function(e){let t=d,c=0;for(;c+2<t.length;)e.put(i(t.substring(c,c+3)),10),c+=3;c<t.length&&(t.length-c==1?e.put(i(t.substring(c,c+1)),4):t.length-c==2&&e.put(i(t.substring(c,c+2)),7))};let i=function(e){let t=0;for(let c=0;c<e.length;c+=1)t=t*10+l(e.charAt(c));return t},l=function(e){if(\"0\"<=e&&e<=\"9\")return e.charCodeAt(0)-48;throw\"illegal char :\"+e};return o},ft=function(h){let w=D.MODE_ALPHA_NUM,d=h,o={};o.getMode=function(){return w},o.getLength=function(l){return d.length},o.write=function(l){let e=d,t=0;for(;t+1<e.length;)l.put(i(e.charAt(t))*45+i(e.charAt(t+1)),11),t+=2;t<e.length&&l.put(i(e.charAt(t)),6)};let i=function(l){if(\"0\"<=l&&l<=\"9\")return l.charCodeAt(0)-48;if(\"A\"<=l&&l<=\"Z\")return l.charCodeAt(0)-65+10;switch(l){case\" \":return 36;case\"$\":return 37;case\"%\":return 38;case\"*\":return 39;case\"+\":return 40;case\"-\":return 41;case\".\":return 42;case\"/\":return 43;case\":\":return 44;default:throw\"illegal char :\"+l}};return o},ct=function(h){let w=D.MODE_8BIT_BYTE,d=h,o=U.stringToBytes(h),i={};return i.getMode=function(){return w},i.getLength=function(l){return o.length},i.write=function(l){for(let e=0;e<o.length;e+=1)l.put(o[e],8)},i},ut=function(h){let w=D.MODE_KANJI,d=h,o=U.stringToBytes;(function(e,t){let c=o(e);if(c.length!=2||(c[0]<<8|c[1])!=t)throw\"sjis not supported.\"})(\"\\u53CB\",38726);let i=o(h),l={};return l.getMode=function(){return w},l.getLength=function(e){return~~(i.length/2)},l.write=function(e){let t=i,c=0;for(;c+1<t.length;){let x=(255&t[c])<<8|255&t[c+1];if(33088<=x&&x<=40956)x-=33088;else if(57408<=x&&x<=60351)x-=49472;else throw\"illegal char at \"+(c+1)+\"/\"+x;x=(x>>>8&255)*192+(x&255),e.put(x,13),c+=2}if(c<t.length)throw\"illegal char at \"+(c+1)},l},W=function(){let h=[],w={};return w.writeByte=function(d){h.push(d&255)},w.writeShort=function(d){w.writeByte(d),w.writeByte(d>>>8)},w.writeBytes=function(d,o,i){o=o||0,i=i||d.length;for(let l=0;l<i;l+=1)w.writeByte(d[l+o])},w.writeString=function(d){for(let o=0;o<d.length;o+=1)w.writeByte(d.charCodeAt(o))},w.toByteArray=function(){return h},w.toString=function(){let d=\"\";d+=\"[\";for(let o=0;o<h.length;o+=1)o>0&&(d+=\",\"),d+=h[o];return d+=\"]\",d},w},lt=function(){let h=0,w=0,d=0,o=\"\",i={},l=function(t){o+=String.fromCharCode(e(t&63))},e=function(t){if(t<0)throw\"n:\"+t;if(t<26)return 65+t;if(t<52)return 97+(t-26);if(t<62)return 48+(t-52);if(t==62)return 43;if(t==63)return 47;throw\"n:\"+t};return i.writeByte=function(t){for(h=h<<8|t&255,w+=8,d+=1;w>=6;)l(h>>>w-6),w-=6},i.flush=function(){if(w>0&&(l(h<<6-w),h=0,w=0),d%3!=0){let t=3-d%3;for(let c=0;c<t;c+=1)o+=\"=\"}},i.toString=function(){return o},i},at=function(h){let w=h,d=0,o=0,i=0,l={};l.read=function(){for(;i<8;){if(d>=w.length){if(i==0)return-1;throw\"unexpected end of file./\"+i}let c=w.charAt(d);if(d+=1,c==\"=\")return i=0,-1;if(c.match(/^\\s$/))continue;o=o<<6|e(c.charCodeAt(0)),i+=6}let t=o>>>i-8&255;return i-=8,t};let e=function(t){if(65<=t&&t<=90)return t-65;if(97<=t&&t<=122)return t-97+26;if(48<=t&&t<=57)return t-48+52;if(t==43)return 62;if(t==47)return 63;throw\"c:\"+t};return l},ht=function(h,w){let d=h,o=w,i=new Array(h*w),l={};l.setPixel=function(x,y,a){i[y*d+x]=a},l.write=function(x){x.writeString(\"GIF87a\"),x.writeShort(d),x.writeShort(o),x.writeByte(128),x.writeByte(0),x.writeByte(0),x.writeByte(0),x.writeByte(0),x.writeByte(0),x.writeByte(255),x.writeByte(255),x.writeByte(255),x.writeString(\",\"),x.writeShort(0),x.writeShort(0),x.writeShort(d),x.writeShort(o),x.writeByte(0);let y=2,a=t(y);x.writeByte(y);let g=0;for(;a.length-g>255;)x.writeByte(255),x.writeBytes(a,g,255),g+=255;x.writeByte(a.length-g),x.writeBytes(a,g,a.length-g),x.writeByte(0),x.writeString(\";\")};let e=function(x){let y=x,a=0,g=0,_={};return _.write=function(m,k){if(m>>>k)throw\"length over\";for(;a+k>=8;)y.writeByte(255&(m<<a|g)),k-=8-a,m>>>=8-a,g=0,a=0;g=m<<a|g,a=a+k},_.flush=function(){a>0&&y.writeByte(g)},_},t=function(x){let y=1<<x,a=(1<<x)+1,g=x+1,_=c();for(let P=0;P<y;P+=1)_.add(String.fromCharCode(P));_.add(String.fromCharCode(y)),_.add(String.fromCharCode(a));let m=W(),k=e(m);k.write(y,g);let E=0,R=String.fromCharCode(i[E]);for(E+=1;E<i.length;){let P=String.fromCharCode(i[E]);E+=1,_.contains(R+P)?R=R+P:(k.write(_.indexOf(R),g),_.size()<4095&&(_.size()==1<<g&&(g+=1),_.add(R+P)),R=P)}return k.write(_.indexOf(R),g),k.write(a,g),k.flush(),m.toByteArray()},c=function(){let x={},y=0,a={};return a.add=function(g){if(a.contains(g))throw\"dup key:\"+g;x[g]=y,y+=1},a.size=function(){return y},a.indexOf=function(g){return x[g]},a.contains=function(g){return typeof x[g]<\"u\"},a};return l},gt=function(h,w,d){let o=ht(h,w);for(let t=0;t<w;t+=1)for(let c=0;c<h;c+=1)o.setPixel(c,t,d(c,t));let i=W();o.write(i);let l=lt(),e=i.toByteArray();for(let t=0;t<e.length;t+=1)l.writeByte(e[t]);return l.flush(),\"data:image/gif;base64,\"+l},X=U,xt=U.stringToBytes;function Z(h,w,d=200){F(h);let o=document.createElement(\"canvas\"),i=o.getContext?.(\"2d\")??null;if(!i){let a=document.createElement(\"a\");a.href=w,a.textContent=\"Open in DID Wallet\",a.target=\"_blank\",a.rel=\"noopener\",h.appendChild(a);return}let l=X(0,\"M\");l.addData(w),l.make();let e=l.getModuleCount(),t=Math.floor(d/e),c=t*e;o.width=c,o.height=c,o.style.display=\"block\",o.style.width=`${d}px`,o.style.height=`${d}px`,o.style.imageRendering=\"pixelated\";let x=\"#e8e8e8\";i.fillStyle=x,i.fillRect(0,0,c,c),i.fillStyle=\"#000000\";for(let a=0;a<e;a++)for(let g=0;g<e;g++)l.isDark(a,g)&&i.fillRect(g*t,a*t,t,t);let y=document.createElement(\"div\");y.style.cssText=\"display:inline-block;padding:10px;background:#e8e8e8;border-radius:8px;line-height:0\",y.appendChild(o),h.appendChild(y)}function F(h){h.innerHTML=\"\"}return it(dt);})();\n","did-address.7df30f28.js":"\"use strict\";var __DidAddressBundle=(()=>{var p=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var $=Object.getOwnPropertyNames;var A=Object.prototype.hasOwnProperty;var D=(e,t)=>{for(var o in t)p(e,o,{get:t[o],enumerable:!0})},E=(e,t,o,i)=>{if(t&&typeof t==\"object\"||typeof t==\"function\")for(let s of $(t))!A.call(e,s)&&s!==o&&p(e,s,{get:()=>t[s],enumerable:!(i=k(t,s))||i.enumerable});return e};var L=e=>E(p({},\"__esModule\",{value:!0}),e);var S={};D(S,{DidAddressElement:()=>n,truncateDid:()=>h});var d=\"did:abt:\";function H(e){if(!e||typeof e!=\"string\")return!1;let t=e.replace(d,\"\");return/^(0x)?[0-9a-f]{40}$/i.test(t)}function h(e,t=8,o=6){return!e||typeof e!=\"string\"?\"\":e.length<=t+o+3?e:`${e.slice(0,t)}...${e.slice(-o)}`}function m(e,t={}){if(!e||typeof e!=\"string\")return{prefix:\"DID: ABT:\",chainLabel:\"ABT\",address:\"\",compact:\"\",copyText:\"\"};let{startChars:o=8,endChars:i=6}=t,s=H(e),r=e.replace(d,\"\"),c=s?\"ETH\":\"ABT\",a=t.includePrefix??!s;return{prefix:`DID: ${c}:`,chainLabel:c,address:r,compact:h(r,o,i),copyText:a?`${d}${r}`:r}}var M=typeof HTMLElement<\"u\"?HTMLElement:class{},b='<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\"/><path d=\"M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1\"/></svg>',P='<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"/></svg>',B=`\n:host {\n display: inline-flex;\n align-items: center;\n max-width: 100%;\n overflow: hidden;\n font-family: 'SF Mono', 'JetBrains Mono', 'Menlo', 'Consolas', monospace;\n line-height: 1.5;\n}\n:host([block]) { display: flex; }\n.prefix {\n flex-shrink: 0;\n opacity: 0.6;\n margin-right: 4px;\n white-space: nowrap;\n}\n.address {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: var(--did-text-color, currentColor);\n}\n.address-compact {\n white-space: nowrap;\n color: var(--did-text-color, currentColor);\n cursor: default;\n}\n.clickable { cursor: pointer; }\n.clickable:hover { opacity: 0.8; }\n.copy-btn {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1em;\n height: 1em;\n margin-left: 6px;\n padding: 0;\n border: none;\n background: none;\n color: inherit;\n opacity: 0.5;\n cursor: pointer;\n transition: opacity 0.15s;\n}\n.copy-btn:hover { opacity: 1; }\n.copy-btn.copied { opacity: 1; color: var(--did-copy-success-color, #22c55e); }\n`,n=class extends M{static observedAttributes=[\"did\",\"compact\",\"copyable\",\"start-chars\",\"end-chars\",\"show-prefix\",\"no-tooltip\",\"no-copy\",\"block\",\"size\"];shadow;copyTimer=null;constructor(){super(),this.shadow=this.attachShadow({mode:\"open\"})}connectedCallback(){this.render()}attributeChangedCallback(){this.render()}disconnectedCallback(){this.copyTimer&&clearTimeout(this.copyTimer)}get parts(){let t=Number(this.getAttribute(\"start-chars\")),o=Number(this.getAttribute(\"end-chars\"));return m(this.getAttribute(\"did\")||\"\",{startChars:t>0?t:void 0,endChars:o>0?o:void 0})}render(){if(!(this.getAttribute(\"did\")||\"\")){this.shadow.innerHTML=\"\";return}let o=this.parts,i=this.hasAttribute(\"compact\"),s=!this.hasAttribute(\"no-copy\")&&this.getAttribute(\"copyable\")!==\"false\",r=this.hasAttribute(\"show-prefix\"),c=this.hasAttribute(\"no-tooltip\"),a=Number(this.getAttribute(\"size\"))||0,x=a>=12?`${a}px`:\"inherit\",g=i?o.compact:o.address,C=!c&&i?` title=\"${o.address}\"`:\"\",w=r?`<span class=\"prefix\">${o.prefix} </span>`:\"\",u=i?\"address-compact\":\"address\",T=s?\" clickable\":\"\",v=s?`<button class=\"copy-btn\" aria-label=\"Copy DID\">${b}</button>`:\"\";if(this.shadow.innerHTML=`\n <style>${B}:host { font-size: ${x}; }</style>\n ${w}<span class=\"${u}${T}\"${C}>${g}</span>${v}\n `,s){let f=this.shadow.querySelector(`.${u}`);f&&f.addEventListener(\"click\",l=>{l.stopPropagation(),this.copyToClipboard(o.copyText)});let y=this.shadow.querySelector(\".copy-btn\");y&&y.addEventListener(\"click\",l=>{l.stopPropagation(),this.copyToClipboard(o.copyText)})}}copyToClipboard(t){navigator.clipboard?.writeText?navigator.clipboard.writeText(t).catch(()=>this.fallbackCopy(t)):this.fallbackCopy(t),this.showCopied()}fallbackCopy(t){try{let o=document.createElement(\"input\");o.value=t,document.body.appendChild(o),o.select(),document.execCommand?.(\"copy\"),o.remove()}catch{}}showCopied(){let t=this.shadow.querySelector(\".copy-btn\");t&&(t.classList.add(\"copied\"),t.innerHTML=P,this.copyTimer&&clearTimeout(this.copyTimer),this.copyTimer=setTimeout(()=>{t.classList.remove(\"copied\"),t.innerHTML=b},1500))}};typeof customElements<\"u\"&&!customElements.get(\"did-address\")&&customElements.define(\"did-address\",n);return L(S);})();\n","header.94d9e46b.js":"\"use strict\";var __BlockletHeaderBundle=(()=>{var f=typeof HTMLElement<\"u\"?HTMLElement:class{},m='<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4m0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4\"/></svg>',x='<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M17 7l-1.41 1.41L18.17 11H8v2h10.17l-2.58 2.58L17 17l5-5zM4 5h8V3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8v-2H4z\"/></svg>',k='<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4m0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4\"/></svg>',y='<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M3 13h8V3H3zm0 8h8v-6H3zm10 0h8V11h-8zm0-18v6h8V3z\"/></svg>';var v={en:{home:\"Home\",userCenter:\"User Center\",adminConsole:\"Admin Console\",billing:\"Billing\",signOut:\"Sign Out\",signIn:\"Sign In\"},zh:{home:\"\\u9996\\u9875\",userCenter:\"\\u4E2A\\u4EBA\\u4E2D\\u5FC3\",adminConsole:\"\\u63A7\\u5236\\u53F0\",billing:\"\\u8D26\\u5355\",signOut:\"\\u9000\\u51FA\\u767B\\u5F55\",signIn:\"\\u767B\\u5F55\"}},C=`\n:host {\n display: block;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n /* Use design tokens from host page (inherited through Shadow DOM), with fallbacks */\n --header-bg: var(--bg-surface, #111827);\n --header-border: var(--border, #374151);\n --header-text: var(--text-white, #f3f4f6);\n --header-text-secondary: var(--text-tertiary, #9ca3af);\n --header-hover: var(--bg-hover, #1f2937);\n --header-primary: var(--blue-muted, #60a5fa);\n --header-height: 56px;\n --badge-bg: var(--blue-light, #1e3a5f);\n --badge-text: var(--blue-muted, #93c5fd);\n --dropdown-shadow: var(--shadow-lg, 0 4px 24px rgba(0,0,0,.4));\n --dropdown-bg: var(--bg-card, #111827);\n --dropdown-border: var(--border, #374151);\n --dropdown-divider: var(--border-subtle, #1f2937);\n --red-danger: var(--red-text, #ef4444);\n --red-danger-bg: var(--red-light, #fef2f2);\n}\n* { box-sizing: border-box; margin: 0; padding: 0; }\n.header {\n display: flex; align-items: center; justify-content: space-between;\n height: var(--header-height); padding: 0 16px;\n background: var(--header-bg); border-bottom: 1px solid var(--header-border);\n color: var(--header-text); position: relative; z-index: 100;\n}\n.header-left { display: flex; align-items: center; gap: 16px; min-width: 0; }\n.header-left a { color: var(--header-text-secondary); text-decoration: none; font-size: 13px; transition: color .15s; white-space: nowrap; }\n.header-left a:hover { color: var(--header-text); text-decoration: none; }\n.logo { height: 28px; width: auto; border-radius: 4px; flex-shrink: 0; }\n.header-right { display: flex; align-items: center; gap: 8px; }\n.avatar {\n width: 32px; height: 32px; border-radius: 50%; background: var(--header-hover);\n display: flex; align-items: center; justify-content: center; overflow: hidden;\n color: var(--header-text-secondary); flex-shrink: 0; cursor: pointer;\n}\n.avatar img { width: 100%; height: 100%; object-fit: cover; }\n.user-trigger {\n position: relative;\n display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 0;\n border: none; background: none; color: var(--header-text);\n font: inherit;\n}\n.user-name { display: none; }\n.user-arrow { font-size: 12px; color: var(--header-text-secondary); line-height: 1; }\n.badge {\n display: inline-flex; align-items: center; padding: 2px 8px; border-radius: 999px;\n font-size: 11px; font-weight: 500; background: var(--badge-bg); color: var(--badge-text);\n line-height: 1.45; white-space: nowrap;\n}\n.badge-owner { background: var(--blue-light, #1e3a5f); color: var(--blue-muted, #93c5fd); }\n.badge-admin { background: var(--info-light, #1a2e4a); color: var(--info-text, #7dd3fc); }\n.badge-member { background: var(--green-light, #14352a); color: var(--green-text, #4ade80); }\n.badge-guest { background: var(--yellow-light, #3b2e1a); color: var(--yellow-text, #fbbf24); }\n.dropdown {\n position: absolute; top: calc(100% + 8px); right: 0;\n background: var(--dropdown-bg); border: 1px solid var(--dropdown-border);\n border-radius: 12px; width: 240px; max-width: calc(100vw - 32px);\n box-shadow: var(--dropdown-shadow); display: none; z-index: 200;\n overflow: hidden;\n}\n.dropdown.open { display: block; }\n.dropdown-user {\n padding: 12px 16px; border-bottom: 1px solid var(--dropdown-divider);\n display: flex; align-items: center; gap: 8px;\n}\n.dropdown-user-name { font-weight: 600; font-size: 15px; color: var(--header-text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.dropdown-item {\n display: flex; align-items: center; gap: 12px; padding: 10px 16px;\n cursor: pointer; color: var(--header-text); font-size: 14px; transition: background .15s;\n text-decoration: none;\n}\n.dropdown-item:hover { background: var(--header-hover); }\n.dropdown-item svg { width: 18px; height: 18px; flex-shrink: 0; color: var(--header-text-secondary); }\n.dropdown-item.danger { color: var(--red-danger); }\n.dropdown-item.danger:hover { background: var(--red-danger-bg); }\n.dropdown-item.danger svg { color: var(--red-danger); }\n.dropdown-divider { height: 1px; background: var(--dropdown-divider); margin: 4px 0; }\n.login-btn {\n display: inline-flex; align-items: center; justify-content: center;\n height: 32px; padding: 0 16px; border-radius: 6px;\n border: none; background: var(--header-primary); color: #fff;\n font: inherit; font-weight: 500; cursor: pointer; font-size: 13px;\n transition: opacity .15s;\n}\n.login-btn:hover { opacity: .9; }\n.hidden { display: none !important; }\n.nav { display: flex; align-items: center; gap: 2px; margin-left: 8px; }\n.nav-item {\n padding: 6px 12px; border-radius: 6px; font-size: 13px; font-weight: 500;\n color: var(--header-text-secondary); text-decoration: none; white-space: nowrap;\n transition: color .15s, background .15s; cursor: pointer; border: none; background: none;\n font: inherit;\n}\n.nav-item:hover { color: var(--header-text); background: var(--header-hover); }\n.nav-item.active { color: var(--header-primary); background: transparent; font-weight: 600; }\n`,n=class extends f{static observedAttributes=[\"theme\",\"app-name\",\"app-logo\",\"login-url\",\"user-url\",\"team-url\",\"billing-url\",\"nav-items\",\"locale\"];shadow;_user=null;_dropdownOpen=!1;_boundClose=null;constructor(){super(),this.shadow=this.attachShadow({mode:\"open\"})}connectedCallback(){this.render(),this.fetchSession(),this._boundClose=e=>{this.shadow.contains(e.target)||this.closeDropdown()},document.addEventListener(\"click\",this._boundClose)}disconnectedCallback(){this._boundClose&&document.removeEventListener(\"click\",this._boundClose)}attributeChangedCallback(){this.render()}get appName(){return this.getAttribute(\"app-name\")||\"\"}get appLogo(){return this.getAttribute(\"app-logo\")||\"/.well-known/service/blocklet/logo\"}get loginUrl(){return this.getAttribute(\"login-url\")||\"/.well-known/service/login\"}get userUrl(){return this.getAttribute(\"user-url\")||\"/.well-known/service/user\"}get teamUrl(){return this.getAttribute(\"team-url\")||\"/.well-known/service/admin\"}get billingUrl(){return this.getAttribute(\"billing-url\")||\"\"}get locale(){return this.getAttribute(\"locale\")===\"zh\"?\"zh\":\"en\"}t(e){return v[this.locale][e]??v.en[e]??e}get navItems(){let e=this.getAttribute(\"nav-items\");if(!e)return[];try{return JSON.parse(e)}catch{return[]}}async fetchSession(){try{let e=await fetch(\"/.well-known/service/api/did/session\",{credentials:\"include\"});if(!e.ok)return;let t=await e.json();t.authenticated&&t.user&&(this._user=t.user,this.render())}catch{}}closeDropdown(){this._dropdownOpen=!1;let e=this.shadow.querySelector(\".dropdown\");e&&e.classList.remove(\"open\")}toggleDropdown(e){e.stopPropagation(),this._dropdownOpen=!this._dropdownOpen;let t=this.shadow.querySelector(\".dropdown\");t&&t.classList.toggle(\"open\",this._dropdownOpen)}abbreviateDid(e){return e.length<=20?e:`${e.slice(0,10)}...${e.slice(-6)}`}render(){let e=this._user,t=this.appLogo,u=this.appName,i=this.navItems,a=window.location.pathname,w=e?.role===\"admin\"||e?.role===\"owner\",b=i.length>0?`\n <nav class=\"nav\">\n ${i.map(r=>`<a class=\"nav-item${(r.link===\"/\"?a===\"/\":a.startsWith(r.link))?\" active\":\"\"}\" href=\"${r.link}\">${r.label}</a>`).join(\"\")}\n </nav>\n `:\"\";this.shadow.innerHTML=`\n <style>${C}</style>\n <div class=\"header\">\n <div class=\"header-left\">\n ${t?`<img class=\"logo\" src=\"${t}\" alt=\"\" />`:\"\"}\n <a href=\"/\">${u||this.t(\"home\")}</a>\n ${b}\n </div>\n <div class=\"header-right\">\n ${e?`\n <button class=\"user-trigger\" id=\"trigger\">\n <div class=\"avatar\">\n ${e.avatar?`<img src=\"${e.avatar}\" alt=\"\" />`:m}\n </div>\n <span class=\"user-arrow\">▾</span>\n </button>\n <div class=\"dropdown\" id=\"dropdown\">\n <div class=\"dropdown-user\">\n <div class=\"dropdown-user-name\">${e.fullName||e.displayName||this.abbreviateDid(e.did)}</div>\n <span class=\"badge badge-${e.role||\"member\"}\">${e.role||\"member\"}</span>\n </div>\n <div class=\"dropdown-item\" id=\"user-center-link\">${k} ${this.t(\"userCenter\")}</div>\n ${w?`<div class=\"dropdown-item\" id=\"team-link\">${y} ${this.t(\"adminConsole\")}</div>`:\"\"}\n ${this.billingUrl?`<div class=\"dropdown-item\" id=\"billing-link\"><svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M20 4H4c-1.11 0-1.99.89-1.99 2L2 18c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V6c0-1.11-.89-2-2-2m0 14H4v-6h16zm0-10H4V6h16z\"/></svg> ${this.t(\"billing\")}</div>`:\"\"}\n <div class=\"dropdown-divider\"></div>\n <div class=\"dropdown-item danger\" id=\"logout-btn\">${x} ${this.t(\"signOut\")}</div>\n </div>\n `:`\n <button class=\"login-btn\" id=\"login-btn\">${this.t(\"signIn\")}</button>\n `}\n </div>\n </div>\n `;let d=this.shadow.getElementById(\"trigger\");d&&d.addEventListener(\"click\",r=>this.toggleDropdown(r));let s=this.shadow.getElementById(\"login-btn\");s&&s.addEventListener(\"click\",()=>{window.location.href=this.loginUrl});let l=this.shadow.getElementById(\"logout-btn\");l&&l.addEventListener(\"click\",()=>{window.location.href=\"/.well-known/service/api/did/logout\"});let h=this.shadow.getElementById(\"user-center-link\");h&&h.addEventListener(\"click\",()=>{window.location.href=this.userUrl});let c=this.shadow.getElementById(\"team-link\");c&&c.addEventListener(\"click\",()=>{window.location.href=this.teamUrl});let g=this.shadow.getElementById(\"billing-link\");g&&g.addEventListener(\"click\",()=>{window.location.href=this.billingUrl}),this.shadow.querySelectorAll(\".nav-item\").forEach(r=>{r.addEventListener(\"click\",p=>{p.preventDefault();let o=r.getAttribute(\"href\");o&&o!==window.location.pathname&&(window.history.pushState({},\"\",o),window.dispatchEvent(new PopStateEvent(\"popstate\")),this.render())})})}};typeof customElements<\"u\"&&!customElements.get(\"blocklet-header\")&&customElements.define(\"blocklet-header\",n);})();\n","login.7b12c6dc.css":"\n\n :root {\n /* Backgrounds — layered depth */\n --bg-root: #0a0a0b;\n --bg-surface: #141416;\n --bg-card: #1c1c20;\n --bg-elevated: #232328;\n --bg-hover: #2b2b34;\n --bg-active: #33333e;\n --bg-input: rgba(255,255,255,0.06);\n\n /* Borders — semi-transparent for adaptability */\n --border: rgba(255,255,255,0.10);\n --border-subtle:rgba(255,255,255,0.06);\n --border-strong:rgba(255,255,255,0.15);\n --border-focus: rgba(255,255,255,0.25);\n\n /* Text hierarchy */\n --text: #f5f5f7;\n --text-secondary:#9394a1;\n --text-tertiary:#767684;\n --text-placeholder:#5e5f6e;\n --text-white: #ffffff;\n\n /* Accents */\n --blue: #6c47ff;\n --blue-hover: #5f15fe;\n --blue-light: rgba(108,71,255,0.15);\n --blue-muted: #9280ff;\n --red: #e02e2e;\n --red-light: rgba(224,46,46,0.15);\n --red-text: #f98a8a;\n --green: #15892b;\n --green-light: rgba(21,137,43,0.15);\n --green-text: #49dc6e;\n --yellow: #fd7224;\n --yellow-light: rgba(253,114,36,0.15);\n --yellow-text: #fd9357;\n --info: #236dd7;\n --info-light: rgba(35,109,215,0.15);\n --info-text: #73acfa;\n\n /* Radii */\n --radius-xs: 4px;\n --radius-sm: 6px;\n --radius: 8px;\n --radius-lg: 12px;\n --radius-full: 9999px;\n\n /* Shadows */\n --shadow-sm: 0 1px 3px rgba(0,0,0,0.3), 0 1px 2px -1px rgba(0,0,0,0.2);\n --shadow-md: 0 4px 6px -1px rgba(0,0,0,0.4), 0 2px 4px -2px rgba(0,0,0,0.3);\n --shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.5), 0 4px 6px -4px rgba(0,0,0,0.4);\n --shadow-focus: 0 0 0 1px rgba(255,255,255,0.12), 0 0 0 3px rgba(108,71,255,0.3);\n }\n\n\n [data-theme=\"light\"] {\n --bg-root: #f8f9fa;\n --bg-surface: #ffffff;\n --bg-card: #ffffff;\n --bg-elevated: #f0f1f3;\n --bg-hover: #e9eaec;\n --bg-active: #dddee1;\n --bg-input: rgba(0,0,0,0.04);\n\n --border: rgba(0,0,0,0.10);\n --border-subtle:rgba(0,0,0,0.06);\n --border-strong:rgba(0,0,0,0.15);\n --border-focus: rgba(0,0,0,0.25);\n\n --text: #1a1a1a;\n --text-secondary:#6b7280;\n --text-tertiary:#9ca3af;\n --text-placeholder:#c0c5ce;\n --text-white: #1a1a1a;\n\n --shadow-sm: 0 1px 3px rgba(0,0,0,0.08), 0 1px 2px -1px rgba(0,0,0,0.06);\n --shadow-md: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.06);\n --shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.12), 0 4px 6px -4px rgba(0,0,0,0.08);\n --shadow-focus: 0 0 0 1px rgba(0,0,0,0.08), 0 0 0 3px rgba(108,71,255,0.2);\n }\n\n\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n html { color-scheme: dark; }\n html[data-theme=\"light\"] { color-scheme: light; }\n body {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n background: var(--bg-root);\n color: var(--text);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n font-size: 14px;\n line-height: 1.43;\n }\n a { color: var(--blue-muted); text-decoration: none; }\n a:hover { text-decoration: underline; }\n .hidden { display: none; }\n\n\n/* Layout */\nbody {\n min-height: 100vh;\n display: flex;\n align-items: center;\n justify-content: center;\n background-image: radial-gradient(ellipse at 50% 0%, rgba(108,71,255,0.08) 0%, transparent 60%);\n}\n\n/* Card — fixed width prevents shrinking when views switch */\n.card {\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: var(--radius-lg);\n padding: 48px 40px;\n width: min(400px, calc(100vw - 32px));\n text-align: center;\n box-shadow: var(--shadow-lg), 0 0 80px -20px rgba(108,71,255,0.15);\n}\n\n/* Brand header */\n.brand-header { margin-bottom: 24px; }\n.app-logo {\n width: 48px;\n height: 48px;\n border-radius: var(--radius-sm);\n object-fit: contain;\n margin-bottom: 16px;\n}\nh1 {\n font-size: 24px;\n font-weight: 600;\n margin-bottom: 8px;\n color: var(--text-white);\n letter-spacing: -0.01em;\n line-height: 1.25;\n /* Never let a long \"Sign in to {appName}\" blow past two lines — clamp with\n an ellipsis so the card height stays stable. */\n display: -webkit-box;\n -webkit-line-clamp: 2;\n line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n.subtitle {\n font-size: 14px;\n color: var(--text-secondary);\n margin-bottom: 0;\n line-height: 1.5;\n}\n.federated-hint {\n font-size: 12px;\n color: var(--text-secondary);\n margin-top: 6px;\n margin-bottom: 0;\n opacity: 0.7;\n}\n\n/* Input */\n.input {\n width: 100%;\n height: 36px;\n padding: 0 12px;\n font-size: 14px;\n color: var(--text);\n background: var(--bg-input);\n border: 1px solid var(--border-strong);\n border-radius: var(--radius-sm);\n outline: none;\n margin-bottom: 16px;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n}\n.input:focus { border-color: var(--blue); box-shadow: var(--shadow-focus); }\n.input::placeholder { color: var(--text-placeholder); }\n\n/* Button */\n.btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n width: 100%;\n height: 36px;\n padding: 0 16px;\n font-size: 14px;\n font-weight: 500;\n color: #fff;\n background: var(--blue);\n border: none;\n border-radius: var(--radius-sm);\n cursor: pointer;\n transition: background-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;\n line-height: 1;\n}\n.btn:hover { background: var(--blue-hover); transform: translateY(-1px); box-shadow: 0 2px 8px rgba(108,71,255,0.3); }\n.btn:disabled { opacity: 0.5; cursor: not-allowed; pointer-events: none; transform: none; box-shadow: none; }\n.btn:focus-visible { box-shadow: var(--shadow-focus); }\n.btn svg { width: 18px; height: 18px; flex-shrink: 0; }\n.btn-secondary {\n background: transparent;\n border: 1px solid var(--border-strong);\n color: var(--text);\n}\n.btn-secondary:hover { background: var(--bg-hover); border-color: rgba(255,255,255,0.20); }\n\n/* Status */\n.status {\n margin-top: 16px;\n font-size: 13px;\n min-height: 20px;\n color: var(--text-secondary);\n}\n.status.error { color: var(--red-text); }\n\n/* Name row (passkey registration) */\n.name-row { display: none; }\n.name-row.visible { display: block; }\n\n/* Passkey section + secondary text action (\"First time? Create a passkey\") */\n.passkey-section > .btn + .passkey-alt,\n.passkey-section > .name-row + .btn { margin-top: 10px; }\n.passkey-alt {\n display: block;\n width: 100%;\n margin-top: 10px;\n padding: 4px;\n background: none;\n border: none;\n font-size: 13px;\n color: var(--text-secondary);\n cursor: pointer;\n text-align: center;\n transition: color 0.15s ease;\n}\n.passkey-alt:hover { color: var(--text); text-decoration: underline; }\n.passkey-alt:focus-visible { box-shadow: var(--shadow-focus); border-radius: var(--radius-sm); outline: none; }\n\n/* Divider */\n.divider {\n display: flex;\n align-items: center;\n margin: 20px 0;\n color: var(--text-secondary);\n font-size: 13px;\n}\n.divider::before, .divider::after {\n content: '';\n flex: 1;\n border-bottom: 1px solid var(--border);\n}\n.divider::before { margin-right: 12px; }\n.divider::after { margin-left: 12px; }\n\n/* DID Wallet section (methods view) */\n.did-wallet-section { margin-top: 8px; }\n\n/* QR view */\n.qr-view {\n position: relative;\n margin: 20px auto;\n display: flex;\n justify-content: center;\n min-height: 220px;\n align-items: center;\n}\n.qr-placeholder {\n width: 220px;\n height: 220px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius);\n background: var(--bg-elevated);\n}\n.qr-spinner {\n display: inline-block;\n width: 32px; height: 32px;\n border: 3px solid var(--border-strong);\n border-top-color: var(--blue-muted);\n border-radius: 50%;\n animation: spin 0.6s linear infinite;\n}\n#did-connect-status {\n font-size: 13px;\n color: var(--text-secondary);\n}\n\n/* Connect result state */\n.connect-result {\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 120px;\n margin: 32px 0;\n animation: fadeIn 200ms ease-out;\n}\n.result-icon {\n width: 64px;\n height: 64px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 28px;\n font-weight: bold;\n}\n.result-error {\n background: rgba(239, 68, 68, 0.15);\n color: var(--red-text);\n border: 2px solid rgba(239, 68, 68, 0.3);\n}\n.result-loading {\n background: transparent;\n}\n.result-loading .qr-spinner {\n width: 36px;\n height: 36px;\n}\n.result-back-btn {\n margin-top: 16px;\n}\n@keyframes fadeIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }\n\n/* QR timeout overlay */\n.qr-timeout-overlay {\n position: absolute;\n inset: 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n background: rgba(0, 0, 0, 0.65);\n border-radius: var(--radius);\n backdrop-filter: blur(2px);\n}\n.qr-timeout-overlay .timeout-text {\n font-size: 13px;\n color: #fff;\n}\n.qr-timeout-overlay .refresh-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 6px 20px;\n font-size: 14px;\n font-weight: 500;\n color: #fff;\n background: var(--blue);\n border: none;\n border-radius: var(--radius-sm);\n cursor: pointer;\n transition: background-color 0.15s ease;\n}\n.qr-timeout-overlay .refresh-btn:hover {\n background: var(--blue-hover);\n}\n/* Connect status views (scanned / success) */\n.connect-status-icon {\n position: relative;\n width: 72px;\n height: 72px;\n margin: 16px auto;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n@keyframes connectSpin { to { transform: rotate(360deg); } }\n.connect-spinner-ring {\n position: absolute;\n inset: 0;\n width: 72px;\n height: 72px;\n border-radius: 50%;\n border: 3px solid rgba(255,255,255,0.1);\n border-top-color: var(--blue, #6c47ff);\n animation: connectSpin 0.8s linear infinite;\n}\n.connect-status-badge {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n height: 44px;\n}\n.connect-status-badge svg { width: 36px; height: 36px; }\n.connect-status-icon.connect-success svg { animation: fadeIn 0.3s ease; }\n.connect-subtitle {\n font-size: 13px;\n color: var(--text-secondary, #9394a1);\n text-align: center;\n margin: 4px 0 0;\n}\n.status.success { color: #49dc6e; font-weight: 500; }\n.deep-link-btn {\n margin-top: 12px;\n text-decoration: none;\n}\n\n/* Email section (methods view) */\n.email-section { margin-top: 8px; }\n\n/* Back button */\n.back-btn {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n background: none;\n border: none;\n color: var(--text-secondary);\n font-size: 14px;\n cursor: pointer;\n padding: 0;\n margin-bottom: 16px;\n float: left;\n}\n.back-btn:hover { color: var(--text); }\n.back-btn svg { width: 18px; height: 18px; }\n/* Escape hatch — a quiet \"Cancel\" link, top-left of the card, mirroring the\n locale switcher (top-right). Conventional spot for a standalone auth card.\n Low in the hierarchy (tertiary color, small) so it never competes with the\n auth methods; it's an exit, not an action. */\n.home-link {\n position: absolute;\n top: 16px;\n /* Align the arrow's left edge to the content column (card's 40px padding), so\n Cancel sits on the same left line as the title and buttons — no left padding\n of its own, which would push it off that line. */\n left: 40px;\n display: inline-flex;\n align-items: center;\n gap: 3px;\n background: none;\n border: none;\n color: var(--text-tertiary);\n font-size: 13px;\n line-height: 1;\n text-decoration: none;\n cursor: pointer;\n padding: 2px 4px 2px 0;\n transition: color 0.15s ease;\n}\n.home-link:hover { color: var(--text-secondary); }\n.home-link svg { width: 16px; height: 16px; }\n\n/* View title */\n.view-title {\n font-size: 20px;\n font-weight: 600;\n color: var(--text-white);\n margin-bottom: 8px;\n clear: both;\n padding-top: 8px;\n}\n\n/* OAuth section — single provider (legacy) */\n.oauth-section { margin-top: 0; }\n\n/* OAuth grid — 2+ providers in 2-column layout (legacy) */\n.oauth-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 8px;\n margin-bottom: 8px;\n}\n.oauth-grid .oauth-btn:last-child:nth-child(odd) {\n grid-column: 1 / -1;\n}\n\n/* Unified secondary methods grid — 2-column layout */\n.methods-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 8px;\n}\n/* Odd last child (e.g. DID Wallet when total is odd) spans full width */\n.methods-grid .method-btn:last-child:nth-child(odd) {\n grid-column: 1 / -1;\n}\n\n.method-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n width: 100%;\n min-height: 36px;\n padding: 6px 16px;\n font-size: 14px;\n font-weight: 500;\n color: var(--text);\n background: var(--bg-elevated);\n border: 1px solid var(--border-strong);\n border-radius: var(--radius-sm);\n cursor: pointer;\n transition: background-color 0.15s ease, border-color 0.15s ease, opacity 0.2s ease;\n white-space: normal;\n}\n.method-btn:hover { background: var(--bg-hover); border-color: var(--blue-muted); }\n.method-btn:disabled { cursor: default; pointer-events: none; }\n.method-btn svg { width: 18px; height: 18px; flex-shrink: 0; }\n\n.oauth-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n width: 100%;\n height: 36px;\n padding: 0 16px;\n font-size: 14px;\n font-weight: 500;\n color: var(--text);\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: var(--radius-sm);\n cursor: pointer;\n transition: background-color 0.15s ease, opacity 0.2s ease, border-color 0.2s ease;\n margin-bottom: 0;\n}\n.oauth-section .oauth-btn { margin-bottom: 8px; }\n.oauth-btn:hover { background: var(--bg-hover); }\n.oauth-btn:disabled { cursor: default; pointer-events: none; }\n.oauth-btn svg { width: 18px; height: 18px; flex-shrink: 0; }\n.oauth-btn-loading {\n opacity: 0.8;\n border-color: var(--blue);\n color: var(--text-secondary);\n}\n@keyframes oauth-spin { to { transform: rotate(360deg); } }\n.oauth-spinner {\n display: inline-block;\n width: 14px; height: 14px;\n border: 2px solid var(--border-strong);\n border-top-color: var(--blue-muted);\n border-radius: 50%;\n animation: oauth-spin 0.6s linear infinite;\n}\n.oauth-check { color: var(--green-text); font-size: 16px; }\n\n/* Browser compatibility warning */\n.compat-warning {\n font-size: 13px;\n color: var(--text-tertiary);\n padding: 12px;\n background: var(--bg-elevated);\n border-radius: var(--radius-sm);\n margin-bottom: 16px;\n}\n\n/* Trust footer */\n.login-footer {\n margin-top: 24px;\n padding-top: 20px;\n border-top: 1px solid var(--border);\n}\n.secured-badge {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n font-size: 12px;\n color: var(--text-tertiary);\n}\n.secured-badge svg { width: 14px; height: 14px; flex-shrink: 0; }\n.footer-links {\n margin-top: 8px;\n font-size: 12px;\n color: var(--text-tertiary);\n}\n.footer-links a {\n color: var(--text-secondary);\n text-decoration: none;\n}\n.footer-links a:hover { text-decoration: underline; }\n\n/* ─── Animations ─────────────────────────────────────────────── */\n@keyframes fadeIn {\n from { opacity: 0; transform: translateY(4px); }\n to { opacity: 1; transform: translateY(0); }\n}\n@keyframes fadeOut {\n from { opacity: 1; }\n to { opacity: 0; }\n}\n.card[data-view] { animation: fadeIn 200ms ease-out; }\n\n/* QR code scale-in */\n.qr-view { animation: qrScaleIn 300ms ease-out; }\n@keyframes qrScaleIn {\n from { opacity: 0; transform: scale(0.95); }\n to { opacity: 1; transform: scale(1); }\n}\n\n/* Button loading spinner */\n@keyframes spin { to { transform: rotate(360deg); } }\n.btn-spinner {\n display: inline-block;\n width: 16px; height: 16px;\n border: 2px solid rgba(255,255,255,0.3);\n border-top-color: #fff;\n border-radius: 50%;\n animation: spin 0.6s linear infinite;\n}\n\n/* Success checkmark */\n@keyframes checkDraw {\n from { stroke-dashoffset: 24; }\n to { stroke-dashoffset: 0; }\n}\n.success-check {\n display: inline-block;\n width: 20px; height: 20px;\n vertical-align: middle;\n}\n.success-check path {\n stroke-dasharray: 24;\n stroke-dashoffset: 24;\n animation: checkDraw 400ms ease-out forwards;\n}\n\n/* Light theme background gradient adjustment */\n[data-theme=\"light\"] body {\n background-image: radial-gradient(ellipse at 50% 0%, rgba(108,71,255,0.05) 0%, transparent 60%);\n}\n[data-theme=\"light\"] .card {\n box-shadow: var(--shadow-lg), 0 0 80px -20px rgba(108,71,255,0.08);\n}\n[data-theme=\"light\"] .btn:hover {\n box-shadow: 0 2px 8px rgba(108,71,255,0.15);\n}\n\n/* Language switcher */\n.locale-switcher {\n position: absolute;\n top: 16px;\n right: 16px;\n}\n.locale-btn {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 4px 10px;\n font-size: 12px;\n color: var(--text-secondary);\n background: var(--bg-elevated);\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius-full);\n cursor: pointer;\n transition: color 0.15s, border-color 0.15s;\n}\n.locale-btn:hover {\n color: var(--text);\n border-color: var(--border-strong);\n}\n.locale-btn svg { width: 14px; height: 14px; flex-shrink: 0; }\n.card { position: relative; }\n","design.99dc4ddc.css":"\n :root {\n /* Backgrounds — layered depth */\n --bg-root: #0a0a0b;\n --bg-surface: #141416;\n --bg-card: #1c1c20;\n --bg-elevated: #232328;\n --bg-hover: #2b2b34;\n --bg-active: #33333e;\n --bg-input: rgba(255,255,255,0.06);\n\n /* Borders — semi-transparent for adaptability */\n --border: rgba(255,255,255,0.10);\n --border-subtle:rgba(255,255,255,0.06);\n --border-strong:rgba(255,255,255,0.15);\n --border-focus: rgba(255,255,255,0.25);\n\n /* Text hierarchy */\n --text: #f5f5f7;\n --text-secondary:#9394a1;\n --text-tertiary:#767684;\n --text-placeholder:#5e5f6e;\n --text-white: #ffffff;\n\n /* Accents */\n --blue: #6c47ff;\n --blue-hover: #5f15fe;\n --blue-light: rgba(108,71,255,0.15);\n --blue-muted: #9280ff;\n --red: #e02e2e;\n --red-light: rgba(224,46,46,0.15);\n --red-text: #f98a8a;\n --green: #15892b;\n --green-light: rgba(21,137,43,0.15);\n --green-text: #49dc6e;\n --yellow: #fd7224;\n --yellow-light: rgba(253,114,36,0.15);\n --yellow-text: #fd9357;\n --info: #236dd7;\n --info-light: rgba(35,109,215,0.15);\n --info-text: #73acfa;\n\n /* Radii */\n --radius-xs: 4px;\n --radius-sm: 6px;\n --radius: 8px;\n --radius-lg: 12px;\n --radius-full: 9999px;\n\n /* Shadows */\n --shadow-sm: 0 1px 3px rgba(0,0,0,0.3), 0 1px 2px -1px rgba(0,0,0,0.2);\n --shadow-md: 0 4px 6px -1px rgba(0,0,0,0.4), 0 2px 4px -2px rgba(0,0,0,0.3);\n --shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.5), 0 4px 6px -4px rgba(0,0,0,0.4);\n --shadow-focus: 0 0 0 1px rgba(255,255,255,0.12), 0 0 0 3px rgba(108,71,255,0.3);\n }\n\n [data-theme=\"light\"] {\n --bg-root: #f8f9fa;\n --bg-surface: #ffffff;\n --bg-card: #ffffff;\n --bg-elevated: #f0f1f3;\n --bg-hover: #e9eaec;\n --bg-active: #dddee1;\n --bg-input: rgba(0,0,0,0.04);\n\n --border: rgba(0,0,0,0.10);\n --border-subtle:rgba(0,0,0,0.06);\n --border-strong:rgba(0,0,0,0.15);\n --border-focus: rgba(0,0,0,0.25);\n\n --text: #1a1a1a;\n --text-secondary:#6b7280;\n --text-tertiary:#9ca3af;\n --text-placeholder:#c0c5ce;\n --text-white: #1a1a1a;\n\n --shadow-sm: 0 1px 3px rgba(0,0,0,0.08), 0 1px 2px -1px rgba(0,0,0,0.06);\n --shadow-md: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.06);\n --shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.12), 0 4px 6px -4px rgba(0,0,0,0.08);\n --shadow-focus: 0 0 0 1px rgba(0,0,0,0.08), 0 0 0 3px rgba(108,71,255,0.2);\n }\n\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n html { color-scheme: dark; }\n html[data-theme=\"light\"] { color-scheme: light; }\n body {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n background: var(--bg-root);\n color: var(--text);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n font-size: 14px;\n line-height: 1.43;\n }\n a { color: var(--blue-muted); text-decoration: none; }\n a:hover { text-decoration: underline; }\n .hidden { display: none; }\n","admin.c26bb17a.css":"\n .hidden { display: none !important; }\n\n /* ─── Shared component styles (extracted from shared-styles.ts) ───── */\n h1 {\n font-size: 24px;\n font-weight: 600;\n margin-bottom: 8px;\n color: var(--text-white);\n letter-spacing: -0.01em;\n line-height: 1.25;\n }\n .input {\n width: 100%;\n height: 40px;\n padding: 0 14px;\n font-size: 14px;\n color: var(--text);\n background: var(--bg-input);\n border: 1px solid var(--border-strong);\n border-radius: var(--radius-sm);\n outline: none;\n margin-bottom: 0;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n }\n textarea.input { height: auto; padding: 10px 14px; }\n .input:focus { border-color: var(--blue); box-shadow: var(--shadow-focus); }\n .input::placeholder { color: var(--text-placeholder); }\n .btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n width: 100%;\n height: 36px;\n padding: 0 16px;\n font-size: 14px;\n font-weight: 500;\n color: #fff;\n background: var(--blue);\n border: none;\n border-radius: var(--radius-sm);\n cursor: pointer;\n transition: background-color 0.15s ease;\n line-height: 1;\n }\n .btn:hover { background: var(--blue-hover); }\n .btn:disabled { opacity: 0.5; cursor: not-allowed; pointer-events: none; }\n .btn:focus-visible { box-shadow: var(--shadow-focus); }\n .btn svg { width: 18px; height: 18px; }\n .btn-secondary {\n background: transparent;\n border: 1px solid var(--border-strong);\n color: var(--text);\n }\n .btn-secondary:hover { background: var(--bg-hover); border-color: rgba(255,255,255,0.20); }\n .btn-danger { background: var(--red); }\n .btn-danger:hover { background: #c22a2a; }\n .badge {\n display: inline-flex;\n align-items: center;\n padding: 2px 8px;\n border-radius: var(--radius-full);\n font-size: 11px;\n font-weight: 500;\n line-height: 1.45;\n white-space: nowrap;\n }\n .badge-owner { background: var(--blue-light); color: var(--blue-muted); }\n .badge-admin { background: var(--info-light); color: var(--info-text); }\n .badge-member { background: var(--green-light); color: var(--green-text); }\n .badge-guest { background: var(--yellow-light); color: var(--yellow-text); }\n .badge-verified { background: var(--green-light); color: var(--green-text); }\n .badge-unverified { background: var(--yellow-light); color: var(--yellow-text); }\n\n /* ─── Admin layout ─────────────────────────────────────────────────── */\n body {\n display: block;\n min-height: 100vh;\n }\n\n /* ─── Header ─────────────────────────────────────────────────────── */\n .admin-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0 24px;\n height: 56px;\n border-bottom: 1px solid var(--border);\n background: var(--bg-surface);\n }\n .admin-header-left {\n display: flex;\n align-items: center;\n gap: 16px;\n }\n .admin-header-left a {\n font-size: 13px;\n transition: color 0.15s ease;\n }\n .admin-header-left .app-name-link {\n color: var(--text);\n }\n .admin-header-left .app-name-link:hover {\n color: var(--text);\n text-decoration: none;\n }\n .admin-header h1 {\n font-size: 15px;\n font-weight: 600;\n margin: 0;\n letter-spacing: -0.006em;\n }\n .admin-header-right {\n position: relative;\n display: flex;\n align-items: center;\n gap: 4px;\n }\n /* ─── Toolbar buttons (ghost style) ───────────────────────────── */\n .toolbar-wrapper {\n position: relative;\n }\n .toolbar-btn {\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: none;\n border-radius: var(--radius-sm);\n color: var(--text-tertiary);\n cursor: pointer;\n transition: background-color 0.15s ease, color 0.15s ease;\n -webkit-tap-highlight-color: transparent;\n }\n .toolbar-btn:hover { background: var(--bg-hover); color: var(--text); }\n .toolbar-btn:active { background: var(--bg-active); }\n .toolbar-btn svg { width: 16px; height: 16px; }\n\n /* ─── Popover (language picker) ──────────────────────────────── */\n .popover {\n position: absolute;\n right: 0;\n top: calc(100% + 6px);\n min-width: 160px;\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: var(--radius-lg);\n box-shadow: var(--shadow-lg);\n z-index: 1000;\n overflow: hidden;\n opacity: 0;\n transform: translateY(-4px) scale(0.96);\n pointer-events: none;\n transform-origin: top right;\n transition: opacity 0.15s ease, transform 0.15s ease;\n }\n .popover.open {\n opacity: 1;\n transform: translateY(0) scale(1);\n pointer-events: auto;\n }\n .popover-header {\n padding: 8px 14px;\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--text-tertiary);\n border-bottom: 1px solid var(--border-subtle);\n }\n .popover-item {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n font-size: 13px;\n color: var(--text);\n cursor: pointer;\n transition: background 0.12s;\n -webkit-tap-highlight-color: transparent;\n }\n .popover-item:hover { background: var(--bg-hover); }\n .popover-item:active { background: var(--bg-active); }\n .popover-item.selected { color: var(--blue-muted); font-weight: 500; }\n .popover-item .check-icon { width: 14px; display: flex; align-items: center; flex-shrink: 0; }\n .popover-item .check-icon svg { width: 14px; height: 14px; }\n\n /* ─── Mobile: larger touch targets, full-width popover ───────── */\n @media (max-width: 640px) {\n .admin-header { padding: 0 12px; }\n .admin-header-right { gap: 4px; }\n .toolbar-btn { width: 40px; height: 40px; }\n .popover { right: -8px; min-width: 150px; }\n .popover-item { padding: 12px 14px; font-size: 14px; }\n .user-menu-arrow { display: none; }\n .user-dropdown { right: -12px; width: 220px; }\n .dropdown-item { padding: 12px 16px; }\n }\n\n /* ─── User menu trigger ────────────────────────────────────────── */\n .user-menu-trigger {\n position: relative;\n display: flex;\n align-items: center;\n gap: 8px;\n cursor: pointer;\n }\n .user-menu-arrow {\n font-size: 12px;\n color: var(--text-tertiary);\n line-height: 1;\n }\n\n /* ─── User dropdown ────────────────────────────────────────────── */\n .user-dropdown {\n position: absolute;\n right: 0;\n top: calc(100% + 8px);\n width: 240px;\n max-width: calc(100vw - 32px);\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: var(--radius-lg);\n box-shadow: var(--shadow-lg);\n z-index: 1000;\n overflow: hidden;\n }\n .dropdown-user {\n padding: 12px 16px;\n display: flex;\n align-items: center;\n gap: 8px;\n border-bottom: 1px solid var(--border-subtle);\n }\n .dropdown-user-name {\n font-weight: 600;\n font-size: 15px;\n color: var(--text-white);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .dropdown-item {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 10px 16px;\n color: var(--text);\n font-size: 14px;\n cursor: pointer;\n transition: background 0.15s;\n text-decoration: none;\n }\n .dropdown-item:hover { background: var(--bg-hover); }\n .dropdown-item.danger { color: var(--red-text); }\n .dropdown-item.danger:hover { background: var(--red-light); }\n .dropdown-divider {\n height: 1px;\n background: var(--border-subtle);\n margin: 4px 0;\n }\n .dropdown-item svg { width: 18px; height: 18px; flex-shrink: 0; color: var(--text-secondary); }\n .dropdown-item.danger svg { color: var(--red-text); }\n\n /* ─── Tabs ───────────────────────────────────────────────────────── */\n .tabs {\n display: flex;\n gap: 0;\n border-bottom: 1px solid var(--border);\n padding: 0 24px;\n background: var(--bg-surface);\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n scrollbar-width: none;\n }\n .tabs::-webkit-scrollbar { display: none; }\n [data-tab] {\n position: relative;\n padding: 10px 16px;\n font-size: 13px;\n font-weight: 500;\n color: var(--text-tertiary);\n cursor: pointer;\n border: none;\n background: none;\n border-bottom: 2px solid transparent;\n transition: color 0.15s ease, border-color 0.15s ease;\n }\n [data-tab]:hover { color: var(--text-secondary); }\n .tab-active {\n color: var(--text-white) !important;\n border-bottom-color: var(--text-white) !important;\n }\n .tab-panel {\n padding: 32px 40px;\n max-width: 1120px;\n margin: 0 auto;\n }\n\n /* ─── Section titles ─────────────────────────────────────────────── */\n .section-title {\n font-size: 20px;\n font-weight: 600;\n color: var(--text-white);\n letter-spacing: -0.02em;\n line-height: 1.25;\n margin-bottom: 4px;\n }\n .section-desc {\n font-size: 14px;\n color: var(--text-tertiary);\n margin-bottom: 32px;\n padding-bottom: 24px;\n border-bottom: 1px solid var(--border-subtle);\n line-height: 1.5;\n }\n\n /* ─── Profile ────────────────────────────────────────────────────── */\n .profile-header {\n padding: 16px 20px;\n display: flex;\n align-items: center;\n gap: 14px;\n }\n .avatar-lg {\n width: 48px;\n height: 48px;\n font-size: 18px;\n }\n .profile-header-body {\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 8px;\n }\n .profile-header-name {\n font-weight: 600;\n font-size: 15px;\n color: var(--text-white);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .profile-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n border-top: 1px solid var(--border);\n }\n .profile-cell {\n padding: 12px 20px;\n }\n .profile-cell-label {\n display: block;\n font-size: 11px;\n font-weight: 500;\n color: var(--text-tertiary);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-bottom: 4px;\n }\n .profile-cell-value {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n color: var(--text);\n min-height: 24px;\n }\n .profile-cell-editable {\n margin: -2px -8px;\n }\n @media (max-width: 560px) {\n .profile-grid { grid-template-columns: 1fr; }\n }\n .profile-editable {\n flex: 1;\n min-width: 0;\n height: 30px;\n padding: 0 8px;\n font-size: 13px;\n color: var(--text);\n background: transparent;\n border: 1px solid transparent;\n border-radius: var(--radius-sm);\n outline: none;\n transition: border-color 0.15s, background 0.15s;\n }\n .profile-editable:hover {\n border-color: var(--border-strong);\n background: var(--bg-input);\n }\n .profile-editable:focus {\n border-color: var(--blue);\n background: var(--bg-input);\n box-shadow: var(--shadow-focus);\n }\n .btn-text {\n background: none;\n border: none;\n color: var(--blue);\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n padding: 4px 8px;\n border-radius: var(--radius-sm);\n white-space: nowrap;\n flex-shrink: 0;\n }\n .btn-text:hover { background: var(--blue-light); }\n .btn-text:disabled { opacity: 0.5; cursor: default; }\n .avatar {\n width: 48px;\n height: 48px;\n border-radius: var(--radius-full);\n background: var(--blue-light);\n color: var(--blue-muted);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n font-weight: 600;\n flex-shrink: 0;\n overflow: hidden;\n position: relative;\n }\n .avatar img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n }\n .avatar-fallback {\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .avatar-sm {\n width: 32px;\n height: 32px;\n font-size: 12px;\n }\n .avatar-editable { cursor: pointer; }\n .avatar-overlay {\n position: absolute;\n inset: 0;\n background: rgba(0,0,0,0.5);\n display: none;\n align-items: center;\n justify-content: center;\n color: white;\n font-size: 12px;\n border-radius: inherit;\n }\n .avatar-editable:hover .avatar-overlay { display: flex; }\n /* <did-address> Web Component theme bridge — CSS custom properties penetrate Shadow DOM */\n did-address {\n --did-text-color: var(--text-secondary);\n --did-copy-success-color: var(--green);\n font-size: 11px;\n }\n did-address:hover {\n --did-text-color: var(--text-white);\n }\n .profile-meta {\n margin: 16px 0;\n font-size: 13px;\n }\n\n /* ─── Settings card ──────────────────────────────────────────────── */\n .settings-card {\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n margin-bottom: 24px;\n }\n .settings-card-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n border-bottom: 1px solid var(--border-subtle);\n gap: 12px;\n }\n details.settings-card > summary.settings-card-header {\n list-style: none;\n }\n details.settings-card > summary.settings-card-header::before {\n content: \"\\25B6\";\n font-size: 10px;\n transition: transform 0.2s;\n margin-right: 4px;\n }\n details[open].settings-card > summary.settings-card-header::before {\n transform: rotate(90deg);\n }\n details.settings-card > summary.settings-card-header::-webkit-details-marker {\n display: none;\n }\n\n /* Icon action buttons for access/policy tables */\n .ap-actions {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 4px;\n white-space: nowrap;\n }\n .ap-icon-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n padding: 0;\n border: 1px solid var(--border);\n border-radius: var(--radius-sm);\n background: var(--bg-card);\n color: var(--text-secondary);\n cursor: pointer;\n transition: background 0.15s, color 0.15s, border-color 0.15s;\n flex-shrink: 0;\n }\n .ap-icon-btn:hover {\n background: var(--bg-hover);\n color: var(--text-white);\n border-color: var(--border-strong);\n }\n .ap-icon-btn-danger:hover {\n background: var(--red-light);\n color: var(--red-text);\n border-color: var(--red-text);\n }\n\n .ap-lock-name {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n color: var(--fg);\n }\n .ap-lock-name svg {\n color: var(--fg-muted);\n flex-shrink: 0;\n }\n\n tr.ap-separator td {\n padding: 0;\n border-bottom: 2px solid var(--border);\n }\n\n .ap-switch {\n position: relative;\n display: inline-block;\n width: 36px;\n height: 20px;\n cursor: pointer;\n }\n .ap-switch input {\n opacity: 0;\n width: 0;\n height: 0;\n }\n .ap-switch-slider {\n position: absolute;\n inset: 0;\n background: var(--bg-tertiary, #ccc);\n border-radius: 20px;\n transition: background 0.2s;\n }\n .ap-switch-slider::before {\n content: \"\";\n position: absolute;\n left: 2px;\n top: 2px;\n width: 16px;\n height: 16px;\n background: #fff;\n border-radius: 50%;\n transition: transform 0.2s;\n }\n .ap-switch input:checked + .ap-switch-slider {\n background: var(--color-primary, #2563eb);\n }\n .ap-switch input:checked + .ap-switch-slider::before {\n transform: translateX(16px);\n }\n\n .settings-card-header-desc {\n font-size: 12px;\n color: var(--text-tertiary);\n font-weight: 400;\n flex: 1;\n text-align: right;\n }\n .settings-card-desc {\n font-size: 12px;\n color: var(--text-tertiary);\n padding: 8px 20px 0;\n margin: 0;\n line-height: 1.5;\n }\n .settings-card-title {\n font-size: 14px;\n font-weight: 600;\n color: var(--text-white);\n }\n .settings-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 14px 20px;\n border-bottom: 1px solid var(--border-subtle);\n }\n .settings-row:last-child { border-bottom: none; }\n .settings-label {\n font-size: 13px;\n font-weight: 500;\n color: var(--text);\n }\n .settings-value {\n font-size: 13px;\n color: var(--text-secondary);\n }\n\n /* ─── Connected Accounts chips ──────────────────────────────────── */\n .ca-section {\n padding: 12px 20px;\n }\n .ca-section + .ca-section {\n padding-top: 0;\n }\n .ca-section-label {\n font-size: 11px;\n font-weight: 500;\n color: var(--text-tertiary);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-bottom: 8px;\n }\n .ca-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n }\n .ca-chip {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 6px 12px;\n border: 1px solid var(--border);\n border-radius: var(--radius-sm);\n background: var(--bg-elevated);\n font-size: 13px;\n color: var(--text);\n transition: border-color 0.15s, background 0.15s;\n position: relative;\n max-width: 260px;\n }\n .ca-chip-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n flex-shrink: 0;\n }\n .ca-chip-icon svg { width: 18px; height: 18px; }\n .ca-chip-body {\n display: flex;\n flex-direction: column;\n min-width: 0;\n }\n .ca-chip-name {\n font-weight: 500;\n font-size: 13px;\n line-height: 1.3;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .ca-chip-detail {\n font-size: 11px;\n color: var(--text-secondary);\n line-height: 1.3;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .ca-chip-badge {\n display: inline-flex;\n font-size: 10px;\n font-weight: 500;\n padding: 1px 6px;\n border-radius: var(--radius-full);\n background: var(--blue-light);\n color: var(--blue-muted);\n white-space: nowrap;\n vertical-align: middle;\n margin-left: 4px;\n }\n .ca-chip-remove {\n display: none;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n border: none;\n background: transparent;\n color: var(--text-tertiary);\n cursor: pointer;\n padding: 0;\n border-radius: var(--radius-full);\n flex-shrink: 0;\n transition: color 0.15s, background 0.15s;\n }\n .ca-chip-remove:hover { color: var(--red-text); background: var(--red-light); }\n .ca-chip:hover .ca-chip-remove { display: inline-flex; }\n\n /* ─── Bound account cards ─────────────────────────────────────── */\n .ca-bound-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));\n gap: 12px;\n }\n .ca-bound-card {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 16px;\n border: 1px solid var(--border);\n border-radius: var(--radius-sm);\n background: var(--bg-card);\n transition: border-color 0.15s;\n }\n .ca-bound-card:hover { border-color: var(--border-strong); }\n .ca-bound-top {\n display: flex;\n align-items: flex-start;\n gap: 12px;\n }\n .ca-bound-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n border-radius: var(--radius-sm);\n background: var(--bg-elevated);\n border: 1px solid var(--border);\n flex-shrink: 0;\n margin-top: 1px;\n }\n .ca-bound-icon svg { width: 22px; height: 22px; }\n .ca-bound-icon-lg {\n width: 48px;\n height: 48px;\n background: var(--blue-light);\n border-color: transparent;\n }\n .ca-bound-icon-lg svg { width: 28px; height: 28px; color: var(--blue-muted); }\n .ca-bound-top-body {\n flex: 1;\n min-width: 0;\n }\n .ca-bound-header {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n .ca-bound-name {\n font-weight: 600;\n font-size: 15px;\n color: var(--text-white);\n line-height: 1.3;\n }\n .ca-bound-detail {\n font-size: 13px;\n color: var(--text-secondary);\n line-height: 1.4;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .ca-bound-field {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n }\n .ca-bound-field-label {\n font-size: 11px;\n font-weight: 500;\n color: var(--text-tertiary);\n text-transform: uppercase;\n letter-spacing: 0.4px;\n flex-shrink: 0;\n width: 36px;\n }\n .ca-bound-field-value {\n flex: 1;\n min-width: 0;\n font-size: 12px;\n color: var(--text-secondary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n font-family: 'JetBrains Mono', 'SF Mono', monospace;\n }\n .ca-bound-copy {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n border: none;\n background: transparent;\n color: var(--text-tertiary);\n cursor: pointer;\n padding: 0;\n border-radius: var(--radius-sm);\n flex-shrink: 0;\n transition: color 0.15s, background 0.15s;\n }\n .ca-bound-copy:hover { color: var(--text); background: var(--bg-hover); }\n .ca-bound-copy svg { width: 14px; height: 14px; }\n .ca-bound-actions {\n display: flex;\n align-items: center;\n gap: 4px;\n flex-shrink: 0;\n }\n\n /* ─── Connected Accounts: mobile ───────────────────────────────── */\n @media (max-width: 640px) {\n .ca-bound-grid { grid-template-columns: 1fr; gap: 8px; }\n .ca-bound-card { padding: 12px; gap: 10px; }\n .ca-bound-top { gap: 10px; }\n .ca-bound-icon { width: 32px; height: 32px; }\n .ca-bound-icon svg { width: 18px; height: 18px; }\n .ca-bound-icon-lg { width: 40px; height: 40px; }\n .ca-bound-icon-lg svg { width: 22px; height: 22px; }\n .ca-bound-name { font-size: 14px; }\n .ca-bound-detail { font-size: 12px; }\n .ca-bound-field-label { display: none; }\n .ca-bound-field-value { font-size: 11px; }\n .ca-section { padding: 10px 16px; }\n .ca-chips { gap: 6px; }\n .ca-chip { padding: 5px 10px; font-size: 12px; }\n }\n .ca-chip-unbound {\n border-style: dashed;\n color: var(--text-tertiary);\n cursor: pointer;\n }\n .ca-chip-unbound:hover {\n border-color: var(--border-strong);\n background: var(--bg-hover);\n color: var(--text);\n }\n .ca-chip-add-icon {\n font-size: 14px;\n font-weight: 300;\n line-height: 1;\n color: var(--text-tertiary);\n }\n .ca-chip-unbound:hover .ca-chip-add-icon { color: var(--text-secondary); }\n .ca-passkey-add {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n border: 1px dashed var(--border);\n border-radius: var(--radius-sm);\n background: transparent;\n font-size: 13px;\n color: var(--text-tertiary);\n cursor: pointer;\n transition: border-color 0.15s, background 0.15s, color 0.15s;\n }\n .ca-passkey-add:hover {\n border-color: var(--border-strong);\n background: var(--bg-hover);\n color: var(--text);\n }\n\n /* ─── Forms ──────────────────────────────────────────────────────── */\n .form-group {\n margin-bottom: 16px;\n }\n .form-label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n font-weight: 600;\n color: var(--text-secondary);\n margin-bottom: 4px;\n }\n .form-actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n margin-top: 24px;\n }\n .form-actions .btn { width: auto; }\n .text-muted { color: var(--text-secondary); }\n\n /* ─── Tables ─────────────────────────────────────────────────────── */\n .table {\n width: 100%;\n border-collapse: collapse;\n }\n .table th, .table td {\n padding: 12px 16px;\n text-align: left;\n border-bottom: 1px solid var(--border-subtle);\n font-size: 14px;\n vertical-align: middle;\n }\n .table th {\n color: var(--text-tertiary);\n font-weight: 500;\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n background: transparent;\n border-bottom-color: var(--border);\n }\n .table th.sortable {\n cursor: pointer;\n user-select: none;\n transition: color 0.1s ease;\n }\n .table th.sortable:hover { color: var(--text-secondary); }\n .table th.sort-active { color: var(--text-white); }\n .table th .sort-arrow {\n display: inline-block;\n margin-left: 4px;\n font-size: 10px;\n opacity: 0.25;\n transition: opacity 0.1s ease;\n }\n .table th.sortable:hover .sort-arrow { opacity: 0.6; }\n .table th .sort-arrow-active { opacity: 1; }\n .table tbody tr {\n transition: background-color 0.1s ease;\n }\n .table tbody tr:hover { background: var(--bg-hover); }\n\n /* Action cell — hosts ⋯ trigger + dropdown menu */\n .action-cell {\n position: relative;\n width: 40px;\n text-align: center;\n }\n\n /* Member identity cell */\n .member-identity {\n display: flex;\n align-items: center;\n gap: 12px;\n }\n .member-name {\n font-size: 14px;\n font-weight: 500;\n color: var(--text);\n line-height: 1.3;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .member-email {\n font-size: 12px;\n color: var(--text-secondary);\n line-height: 1.3;\n margin-top: 1px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .cell-date {\n font-size: 13px;\n color: var(--text-tertiary);\n font-variant-numeric: tabular-nums;\n }\n\n /* ─── Toolbar ────────────────────────────────────────────────────── */\n .tab-toolbar {\n display: flex;\n gap: 8px;\n margin-bottom: 20px;\n align-items: center;\n }\n .search-wrapper {\n position: relative;\n width: 280px;\n }\n .search-icon {\n position: absolute;\n left: 10px;\n top: 50%;\n transform: translateY(-50%);\n color: var(--text-tertiary);\n width: 16px;\n height: 16px;\n pointer-events: none;\n }\n .search-bar {\n width: 100%;\n height: 36px;\n padding: 0 12px 0 34px;\n background: transparent;\n border: 1px solid var(--border-strong);\n border-radius: var(--radius-sm);\n font-size: 14px;\n color: var(--text);\n outline: none;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n }\n .search-bar::placeholder { color: var(--text-tertiary); }\n .search-bar:focus { border-color: var(--border-focus); box-shadow: 0 0 0 2px rgba(255,255,255,0.05); }\n .select {\n height: 36px;\n padding: 0 12px;\n font-size: 13px;\n color: var(--text);\n background: transparent;\n border: 1px solid var(--border-strong);\n border-radius: var(--radius-sm);\n outline: none;\n cursor: pointer;\n transition: border-color 0.15s ease;\n }\n .select:focus { border-color: var(--border-focus); }\n .btn-sm { height: 36px; padding: 0 12px; font-size: 13px; width: auto; }\n .btn-xs { height: 28px; padding: 0 10px; font-size: 12px; width: auto; }\n\n /* ─── OAuth provider picker ──────────────────────────────────────── */\n .provider-picker {\n display: grid;\n grid-template-columns: repeat(5, 1fr);\n gap: 8px;\n }\n .provider-card {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 10px 6px 8px;\n border: 1px solid var(--border);\n border-radius: var(--radius-sm);\n background: var(--bg-elevated);\n cursor: pointer;\n transition: border-color 0.15s, background 0.15s;\n position: relative;\n min-width: 0;\n }\n .provider-card:hover:not(.added) { border-color: var(--border-strong); background: var(--bg-hover); }\n .provider-card.selected { border-color: var(--blue); background: rgba(108,71,255,0.08); }\n .provider-card.added { opacity: 0.45; cursor: default; }\n .provider-icon { display: flex; align-items: center; justify-content: center; width: 20px; height: 20px; }\n .provider-icon svg { width: 20px; height: 20px; }\n .provider-name { font-size: 11px; color: var(--text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }\n .provider-card.selected .provider-name { color: var(--text); }\n .provider-added-badge {\n position: absolute;\n top: -6px; right: -6px;\n font-size: 9px;\n font-weight: 600;\n padding: 1px 4px;\n border-radius: 4px;\n background: var(--bg-card);\n border: 1px solid var(--border);\n color: var(--text-tertiary);\n line-height: 1.4;\n white-space: nowrap;\n }\n\n /* ─── Pagination ─────────────────────────────────────────────────── */\n .pagination {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-top: 16px;\n font-size: 13px;\n color: var(--text-tertiary);\n }\n .pagination-btns {\n display: flex;\n gap: 4px;\n }\n .page-btn {\n min-width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: none;\n background: transparent;\n color: var(--text-secondary);\n border-radius: var(--radius-sm);\n cursor: pointer;\n font-size: 13px;\n font-weight: 500;\n transition: background-color 0.1s ease;\n }\n .page-btn:hover { background: var(--bg-hover); color: var(--text); }\n .page-btn.active { background: var(--bg-active); color: var(--text-white); }\n .page-btn:disabled { opacity: 0.3; cursor: not-allowed; }\n\n /* ─── Empty state ────────────────────────────────────────────────── */\n .empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 48px 24px;\n text-align: center;\n }\n .empty-state-icon {\n width: 48px;\n height: 48px;\n border-radius: var(--radius);\n background: rgba(255,255,255,0.06);\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--text-tertiary);\n margin-bottom: 16px;\n }\n .empty-state-title {\n font-size: 15px;\n font-weight: 500;\n color: var(--text);\n margin-bottom: 4px;\n }\n .empty-state-desc {\n font-size: 13px;\n color: var(--text-secondary);\n max-width: 320px;\n line-height: 1.5;\n }\n\n /* ─── Dialogs ────────────────────────────────────────────────────── */\n dialog {\n background: var(--bg-elevated);\n border: 1px solid var(--border-strong);\n border-radius: var(--radius-lg);\n color: var(--text);\n padding: 0;\n margin: auto;\n max-width: 520px;\n width: 90%;\n box-shadow: var(--shadow-lg);\n overflow: hidden;\n animation: modalIn 0.2s ease;\n }\n dialog::backdrop { background: rgba(0,0,0,0.7); }\n .dialog-content {\n padding: 24px;\n }\n .dialog-content h3 {\n font-size: 16px;\n font-weight: 600;\n color: var(--text-white);\n margin-bottom: 4px;\n letter-spacing: -0.01em;\n }\n .dialog-desc {\n font-size: 14px;\n color: var(--text-secondary);\n margin-bottom: 20px;\n line-height: 1.5;\n }\n .dialog-actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n padding: 16px 24px;\n border-top: 1px solid var(--border);\n }\n .dialog-actions .btn { width: auto; }\n\n /* ─── Wallet bind QR (mirrors login-page .qr-view) ──────────────── */\n .wallet-qr-view {\n margin: 16px auto;\n display: flex;\n justify-content: center;\n min-height: 212px;\n align-items: center;\n }\n .wallet-qr-placeholder {\n width: 212px;\n height: 212px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius);\n background: var(--bg-elevated);\n }\n .wallet-qr-spinner {\n display: inline-block;\n width: 32px; height: 32px;\n border: 3px solid var(--border-strong);\n border-top-color: var(--blue-muted);\n border-radius: 50%;\n animation: spin 0.6s linear infinite;\n }\n @keyframes spin { to { transform: rotate(360deg); } }\n .wallet-deep-link-btn {\n margin-top: 4px;\n width: 212px;\n }\n .wallet-bind-status {\n margin-top: 12px;\n font-size: 13px;\n min-height: 18px;\n color: var(--text-secondary);\n margin-bottom: 0;\n }\n .wallet-bind-error {\n color: var(--red-text);\n padding: 10px 12px;\n background: rgba(220,53,69,0.08);\n border: 1px solid rgba(220,53,69,0.25);\n border-radius: var(--radius-sm);\n margin-top: 8px;\n }\n\n @keyframes modalIn {\n from { opacity: 0; transform: scale(0.98); }\n to { opacity: 1; transform: scale(1); }\n }\n\n /* ─── Action menus ───────────────────────────────────────────────── */\n .action-trigger {\n width: 28px;\n height: 28px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--radius-sm);\n border: none;\n background: transparent;\n color: var(--text-tertiary);\n cursor: pointer;\n font-size: 16px;\n transition: background-color 0.15s ease, color 0.15s ease;\n flex-shrink: 0;\n }\n .action-trigger:hover { background: var(--bg-hover); color: var(--text-secondary); }\n .action-menu {\n display: none;\n position: absolute;\n right: 8px;\n top: 100%;\n z-index: 50;\n min-width: 180px;\n padding: 4px;\n background: var(--bg-elevated);\n border: 1px solid var(--border-strong);\n border-radius: var(--radius);\n box-shadow: var(--shadow-md);\n }\n .action-menu.open { display: block; }\n .action-menu-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 8px;\n border-radius: var(--radius-xs);\n font-size: 13px;\n color: var(--text);\n cursor: pointer;\n border: none;\n background: none;\n width: 100%;\n text-align: left;\n transition: background-color 0.1s ease;\n }\n .action-menu-item:hover { background: var(--bg-hover); }\n .action-menu-item.danger { color: var(--red-text); }\n .action-menu-item.danger:hover { background: var(--red-light); }\n .action-menu-sep {\n height: 1px;\n background: var(--border);\n margin: 4px 0;\n }\n\n /* ─── Status badges ──────────────────────────────────────────────── */\n .badge-active { background: var(--green-light); color: var(--green-text); }\n .badge-pending { background: var(--yellow-light); color: var(--yellow-text); }\n .badge-blocked { background: var(--red-light); color: var(--red-text); }\n .badge-expired { background: rgba(255,255,255,0.06); color: var(--text-tertiary); }\n .badge-neutral { background: rgba(255,255,255,0.06); color: var(--text-secondary); }\n\n /* ─── Toast ──────────────────────────────────────────────────────── */\n .toast-container {\n position: fixed;\n bottom: 24px;\n right: 24px;\n z-index: 100;\n display: flex;\n flex-direction: column;\n gap: 8px;\n }\n .toast {\n padding: 10px 16px;\n border-radius: var(--radius);\n font-size: 13px;\n font-weight: 500;\n color: var(--text);\n background: var(--bg-elevated);\n border: 1px solid var(--border-strong);\n box-shadow: var(--shadow-md);\n animation: toastIn 0.2s ease;\n }\n .toast.success { border-left: 3px solid var(--green); }\n .toast.error { background: var(--red); color: #fff; border-color: var(--red); }\n @keyframes toastIn {\n from { opacity: 0; transform: translateY(8px); }\n to { opacity: 1; transform: translateY(0); }\n }\n\n /* ─── Toggle switch ────────────────────────────────────────────── */\n .toggle-switch {\n position: relative;\n display: inline-block;\n width: 40px;\n height: 22px;\n cursor: pointer;\n }\n .toggle-switch input { opacity: 0; width: 0; height: 0; position: absolute; }\n .toggle-slider {\n position: absolute;\n inset: 0;\n background: var(--bg-hover);\n border-radius: 11px;\n transition: background-color 0.2s ease;\n }\n .toggle-slider::before {\n content: \"\";\n position: absolute;\n left: 2px;\n top: 2px;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: var(--text-secondary);\n transition: transform 0.2s ease, background-color 0.2s ease;\n }\n .toggle-switch input:checked + .toggle-slider { background: var(--blue); }\n .toggle-switch input:checked + .toggle-slider::before {\n transform: translateX(18px);\n background: #fff;\n }\n\n /* ─── Range slider ─────────────────────────────────────────────── */\n .range-slider {\n -webkit-appearance: none;\n width: 100%;\n height: 6px;\n border-radius: 3px;\n background: var(--bg-hover);\n outline: none;\n margin: 8px 0;\n }\n .range-slider::-webkit-slider-thumb {\n -webkit-appearance: none;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: var(--blue);\n cursor: pointer;\n }\n .range-slider::-moz-range-thumb {\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: var(--blue);\n cursor: pointer;\n border: none;\n }\n\n /* ─── Branding: Section layout ─────────────────────── */\n .br-section {\n margin-bottom: 56px;\n }\n .br-section-header {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n margin-bottom: 20px;\n }\n .br-section-title {\n font-size: 16px;\n font-weight: 600;\n color: var(--text);\n letter-spacing: -0.01em;\n }\n .br-section-category {\n font-size: 10px;\n font-weight: 600;\n color: var(--text-tertiary);\n text-transform: uppercase;\n letter-spacing: 0.08em;\n }\n .br-two-col {\n display: grid;\n grid-template-columns: 1fr 2fr;\n gap: 32px;\n }\n .br-col-desc {\n font-size: 13px;\n color: var(--text-secondary);\n line-height: 1.6;\n }\n .br-field-hint {\n font-size: 11px;\n color: var(--text-tertiary);\n margin-top: 4px;\n margin-bottom: 12px;\n }\n /* Info icon tooltip */\n .info-tip {\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n border-radius: 50%;\n background: rgba(255,255,255,0.08);\n color: var(--text-tertiary);\n font-size: 10px;\n font-weight: 600;\n cursor: help;\n flex-shrink: 0;\n }\n .info-tip::after {\n content: attr(data-tip);\n position: absolute;\n bottom: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n background: var(--bg-card);\n border: 1px solid var(--border-strong);\n color: var(--text-secondary);\n font-size: 12px;\n font-weight: 400;\n padding: 8px 14px;\n border-radius: var(--radius);\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.15s ease;\n z-index: 10;\n box-shadow: var(--shadow-md);\n min-width: 220px;\n max-width: 360px;\n white-space: normal;\n line-height: 1.5;\n text-align: left;\n }\n .info-tip:hover::after { opacity: 1; }\n .br-panel {\n background: var(--bg-elevated);\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius-lg);\n padding: 20px 24px;\n }\n .accent-dot {\n width: 28px;\n height: 28px;\n border-radius: 50%;\n cursor: pointer;\n border: 2px solid transparent;\n transition: transform 0.1s ease, border-color 0.15s ease;\n }\n .accent-dot:hover { transform: scale(1.15); }\n .accent-dot.selected { border-color: var(--text-white); }\n\n /* ─── Branding: Language toggles ──────────────────── */\n .lang-toggles {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n align-items: center;\n }\n .lang-toggle {\n position: relative;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 20px;\n background: var(--bg-elevated);\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius);\n font-size: 13px;\n font-weight: 500;\n color: var(--text-secondary);\n cursor: pointer;\n transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;\n }\n .lang-toggle:hover { border-color: var(--border-strong); color: var(--text); }\n .lang-toggle.active {\n border-color: var(--border-strong);\n color: var(--text-white);\n background: rgba(255,255,255,0.04);\n }\n .lang-toggle.active::after {\n content: \"✓\";\n position: absolute;\n top: -1px;\n right: -1px;\n width: 16px;\n height: 16px;\n background: var(--blue);\n border-radius: 0 var(--radius) 0 var(--radius);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 10px;\n font-weight: 700;\n color: #fff;\n line-height: 1;\n }\n\n /* ─── Branding: Logo grid (redesigned) ───────────── */\n .logo-grid {\n display: grid;\n grid-template-columns: repeat(4, 1fr);\n gap: 16px;\n }\n .logo-item {\n position: relative;\n background: var(--bg-elevated);\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius-lg);\n overflow: hidden;\n display: flex;\n flex-direction: column;\n padding: 16px;\n transition: border-color 0.15s ease;\n min-height: 170px;\n }\n .logo-item:hover { border-color: rgba(255,255,255,0.15); }\n .logo-item-header {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n margin-bottom: auto;\n }\n .logo-item-icon {\n color: var(--text-tertiary);\n font-size: 20px;\n line-height: 1;\n }\n .logo-item-spec {\n font-size: 10px;\n font-weight: 600;\n color: var(--text-tertiary);\n text-transform: uppercase;\n letter-spacing: 0.03em;\n }\n .logo-preview {\n width: 100%;\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n }\n .logo-preview img {\n max-width: 100%;\n max-height: 80px;\n object-fit: contain;\n }\n .logo-item-footer {\n margin-top: 12px;\n }\n .logo-name {\n font-size: 14px;\n font-weight: 600;\n color: var(--text-white);\n margin-bottom: 2px;\n }\n .logo-subtitle {\n font-size: 12px;\n color: var(--text-tertiary);\n }\n .logo-placeholder {\n font-size: 24px;\n color: var(--text-tertiary);\n opacity: 0.3;\n }\n /* Hover overlay for upload — glassmorphism */\n .logo-hover-upload {\n position: absolute;\n inset: 0;\n background: rgba(20,20,30,0.65);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 6px;\n opacity: 0;\n transition: opacity 0.2s ease;\n cursor: pointer;\n border-radius: inherit;\n }\n .logo-item:hover .logo-hover-upload { opacity: 1; }\n .logo-hover-icon {\n width: 36px;\n height: 36px;\n border-radius: 50%;\n background: rgba(255,255,255,0.1);\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--text-white);\n margin-bottom: 2px;\n }\n .logo-hover-icon svg { width: 18px; height: 18px; }\n .logo-hover-text {\n font-size: 12px;\n font-weight: 500;\n color: var(--text-secondary);\n letter-spacing: 0.02em;\n }\n /* Uploaded state: replace/discard shown on hover via overlay */\n .logo-uploaded-overlay {\n position: absolute;\n inset: 0;\n background: rgba(20,20,30,0.65);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 12px;\n opacity: 0;\n transition: opacity 0.2s ease;\n border-radius: inherit;\n z-index: 2;\n }\n .logo-item:hover .logo-uploaded-overlay { opacity: 1; }\n .logo-overlay-btn {\n font-size: 12px;\n font-weight: 600;\n padding: 6px 14px;\n border-radius: var(--radius-sm);\n cursor: pointer;\n border: 1px solid;\n transition: background 0.15s ease;\n }\n .logo-overlay-btn-replace { color: var(--blue); border-color: var(--blue); background: transparent; }\n .logo-overlay-btn-replace:hover { background: rgba(99,102,241,0.12); }\n .logo-overlay-btn-discard { color: var(--red-text); border-color: rgba(224,46,46,0.4); background: transparent; }\n .logo-overlay-btn-discard:hover { background: rgba(224,46,46,0.1); }\n .btn-text { background: none; border: none; color: var(--blue); font-size: 11px; font-weight: 600; cursor: pointer; padding: 0; }\n .btn-text:hover { text-decoration: underline; }\n .btn-text-danger { color: var(--red-text); }\n\n /* ─── Branding: Sticky save bar ──────────────────── */\n .br-save-bar {\n position: fixed;\n bottom: 0;\n left: 0;\n right: 0;\n z-index: 40;\n background: rgba(28,28,32,0.85);\n backdrop-filter: blur(12px);\n border-top: 1px solid var(--border-subtle);\n padding: 16px 40px;\n display: none;\n }\n .br-save-bar.visible { display: block; }\n .br-save-bar-inner {\n max-width: 1120px;\n margin: 0 auto;\n display: flex;\n align-items: center;\n justify-content: space-between;\n width: 100%;\n }\n .br-save-hint {\n font-size: 12px;\n color: var(--text-tertiary);\n display: flex;\n align-items: center;\n gap: 8px;\n }\n .br-save-actions {\n display: flex;\n gap: 8px;\n }\n .br-save-actions .btn { width: auto; }\n\n /* ─── Branding: Accent color swatches ────────────── */\n .accent-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));\n gap: 10px;\n }\n .accent-swatch {\n position: relative;\n height: 64px;\n border-radius: var(--radius);\n display: flex;\n flex-direction: column;\n justify-content: flex-end;\n padding: 6px 8px;\n cursor: pointer;\n transition: transform 0.1s ease, box-shadow 0.15s ease;\n border: 2px solid transparent;\n }\n .accent-swatch:hover { transform: scale(1.02); }\n .accent-swatch.selected { border-color: var(--text-white); }\n .accent-swatch .accent-check {\n position: absolute;\n top: 4px;\n right: 4px;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: rgba(0,0,0,0.5);\n display: none;\n align-items: center;\n justify-content: center;\n color: white;\n font-size: 12px;\n }\n .accent-swatch.selected .accent-check { display: flex; }\n .accent-label { font-size: 9px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.8; }\n .accent-hex { font-size: 10px; font-weight: 700; }\n\n /* ─── Branding tab: Language grid ───────────────────── */\n .lang-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));\n gap: 4px 16px;\n }\n .lang-item {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n padding: 4px 0;\n cursor: pointer;\n }\n\n /* ─── Crop dialog ──────────────────────────────────── */\n dialog.dialog-wide { max-width: 660px; }\n .crop-container {\n width: 100%;\n height: 400px;\n background: var(--bg);\n border-radius: var(--radius-sm);\n overflow: hidden;\n }\n .crop-toolbar {\n display: flex;\n justify-content: center;\n gap: 6px;\n padding: 12px 0 4px;\n }\n .crop-tool-btn {\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--bg-input);\n border: 1px solid var(--border-strong);\n border-radius: var(--radius-sm);\n color: var(--text-secondary);\n cursor: pointer;\n transition: background-color 0.15s ease, color 0.15s ease;\n }\n .crop-tool-btn:hover { background: var(--bg-hover); color: var(--text); }\n .crop-tool-btn svg { width: 18px; height: 18px; }\n\n /* Crop dialog progress */\n .crop-progress {\n width: 120px;\n height: 4px;\n background: var(--bg-hover);\n border-radius: 2px;\n overflow: hidden;\n }\n .crop-progress-bar {\n width: 30%;\n height: 100%;\n background: var(--blue);\n border-radius: 2px;\n animation: cropProgress 1.5s ease infinite;\n }\n @keyframes cropProgress {\n 0% { width: 10%; margin-left: 0; }\n 50% { width: 40%; margin-left: 30%; }\n 100% { width: 10%; margin-left: 90%; }\n }\n\n /* cropperjs dark theme overrides */\n .cropper-view-box { outline-color: var(--blue) !important; }\n .cropper-line { background-color: var(--blue) !important; }\n .cropper-point { background-color: var(--blue) !important; width: 8px !important; height: 8px !important; }\n .cropper-modal { background-color: rgba(0,0,0,0.6) !important; }\n .cropper-dashed { border-color: rgba(255,255,255,0.3) !important; }\n\n /* ─── Branding: Mobile responsive ──────────────────── */\n @media (max-width: 768px) {\n .br-two-col { grid-template-columns: 1fr; gap: 16px; }\n .logo-grid { grid-template-columns: repeat(2, 1fr); }\n .br-save-bar { padding: 12px 16px; }\n .br-save-bar-inner { flex-direction: column; gap: 12px; text-align: center; }\n .br-save-actions { justify-content: center; }\n .tab-panel { padding: 20px 16px; }\n .accent-grid { grid-template-columns: repeat(3, 1fr); }\n .br-panel > div[style*=\"grid-template-columns: 1fr 1fr\"] { grid-template-columns: 1fr !important; }\n }\n @media (max-width: 480px) {\n .logo-grid { grid-template-columns: 1fr; }\n .accent-grid { grid-template-columns: repeat(2, 1fr); }\n }\n\n /* ─── Appearance tab: Color input ───────────────────── */\n .color-input {\n width: 40px;\n height: 28px;\n padding: 0;\n border: 1px solid var(--border-strong);\n border-radius: 4px;\n cursor: pointer;\n background: none;\n }\n\n /* ─── Auto theme: follow system preference ───────────────────────── */\n @media (prefers-color-scheme: light) {\n [data-theme=\"auto\"] {\n --bg-root: #f8f9fa;\n --bg-surface: #ffffff;\n --bg-card: #ffffff;\n --bg-elevated: #f0f1f3;\n --bg-hover: #e9eaec;\n --bg-active: #dddee1;\n --bg-input: rgba(0,0,0,0.04);\n --border: rgba(0,0,0,0.10);\n --border-subtle: rgba(0,0,0,0.06);\n --border-strong: rgba(0,0,0,0.15);\n --border-focus: rgba(0,0,0,0.25);\n --text: #1a1a1a;\n --text-secondary: #6b7280;\n --text-tertiary: #9ca3af;\n --text-placeholder: #c0c5ce;\n --text-white: #1a1a1a;\n --shadow-sm: 0 1px 3px rgba(0,0,0,0.08), 0 1px 2px -1px rgba(0,0,0,0.06);\n --shadow-md: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.06);\n --shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.12), 0 4px 6px -4px rgba(0,0,0,0.08);\n --shadow-focus: 0 0 0 1px rgba(0,0,0,0.08), 0 0 0 3px rgba(108,71,255,0.2);\n color-scheme: light;\n }\n }\n\n /* ─── Skeleton loading ──────────────────────────────────────────────── */\n @keyframes shimmer {\n 0% { background-position: 200% 0; }\n 100% { background-position: -200% 0; }\n }\n .skel {\n display: block;\n background: linear-gradient(90deg, var(--bg-hover) 25%, var(--bg-active, rgba(255,255,255,0.06)) 50%, var(--bg-hover) 75%);\n background-size: 200% 100%;\n animation: shimmer 1.4s infinite;\n border-radius: 4px;\n }\n .skel-circle { border-radius: 50%; }\n .skel-row td { padding: 12px 16px; }\n\n /* ─── Responsive: column visibility helpers ─────────────────────────── */\n /* col-hide-lg: hide on tablet (≤900px) */\n @media (max-width: 900px) {\n .col-hide-lg { display: none; }\n }\n /* col-hide-md: hide on small tablet (≤768px) */\n @media (max-width: 768px) {\n .col-hide-md { display: none; }\n }\n /* col-hide-sm: hide on mobile (≤640px) */\n @media (max-width: 640px) {\n .col-hide-sm { display: none; }\n }\n\n /* ─── Mobile sidebar drawer ──────────────────────────────────────────── */\n .sidebar-toggle { display: none; }\n .sidebar-overlay {\n display: none;\n position: fixed;\n inset: 0;\n background: rgba(0,0,0,0.5);\n z-index: 200;\n opacity: 0;\n transition: opacity 0.25s ease;\n }\n .sidebar-overlay-visible {\n display: block;\n opacity: 1;\n }\n .sidebar {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n bottom: 0;\n width: 280px;\n max-width: 80vw;\n background: var(--bg-surface);\n border-right: 1px solid var(--border);\n z-index: 210;\n transform: translateX(-100%);\n transition: transform 0.25s ease;\n overflow-y: auto;\n -webkit-overflow-scrolling: touch;\n }\n .sidebar-open { transform: translateX(0); }\n .sidebar-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px;\n border-bottom: 1px solid var(--border);\n }\n .sidebar-header h2 {\n font-size: 15px;\n font-weight: 600;\n color: var(--text-white);\n margin: 0;\n }\n .sidebar-nav {\n padding: 8px;\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n .sidebar-item {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 10px 12px;\n border: none;\n background: transparent;\n border-radius: var(--radius-sm);\n font-size: 14px;\n font-weight: 500;\n color: var(--text-secondary);\n cursor: pointer;\n transition: background 0.15s, color 0.15s;\n text-align: left;\n width: 100%;\n -webkit-tap-highlight-color: transparent;\n }\n .sidebar-item:hover { background: var(--bg-hover); color: var(--text); }\n .sidebar-item:active { background: var(--bg-active); }\n .sidebar-item svg { width: 18px; height: 18px; flex-shrink: 0; color: var(--text-tertiary); transition: color 0.15s; }\n .sidebar-item:hover svg { color: var(--text-secondary); }\n .sidebar-item-active {\n background: var(--bg-hover);\n color: var(--text-white);\n }\n .sidebar-item-active svg { color: var(--text-white); }\n\n /* ─── Responsive: show sidebar toggle, hide tabs on mobile ──────────── */\n @media (max-width: 640px) {\n .sidebar-toggle { display: flex; }\n .sidebar { display: block; }\n .tabs { display: none; }\n }\n\n /* ─── Responsive: toolbar / filter bar ──────────────────────────────── */\n @media (max-width: 640px) {\n .tab-toolbar {\n flex-wrap: wrap;\n gap: 6px;\n }\n .search-wrapper {\n width: 100%;\n min-width: 0;\n }\n .tab-toolbar .select {\n flex: 1;\n min-width: 0;\n font-size: 12px;\n padding: 0 8px;\n height: 34px;\n }\n .tab-toolbar .btn-sm {\n font-size: 12px;\n padding: 0 10px;\n height: 34px;\n }\n }\n\n /* ─── Responsive: header ────────────────────────────────────────────── */\n @media (max-width: 640px) {\n .admin-header-left {\n gap: 6px;\n min-width: 0;\n overflow: hidden;\n }\n .admin-header-left a {\n max-width: 80px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n flex-shrink: 1;\n }\n .admin-header h1 {\n font-size: 13px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .header-dot { display: none; }\n }\n\n /* ─── Responsive: table compact ─────────────────────────────────────── */\n @media (max-width: 640px) {\n .table th, .table td {\n padding: 10px 8px;\n font-size: 13px;\n }\n .table th {\n font-size: 10px;\n }\n .member-identity {\n gap: 8px;\n }\n .member-name {\n font-size: 13px;\n max-width: 120px;\n }\n .member-email {\n font-size: 11px;\n max-width: 100px;\n }\n .action-cell { width: 32px; }\n }\n\n /* ─── Responsive: pagination ────────────────────────────────────────── */\n @media (max-width: 640px) {\n .pagination {\n flex-direction: column;\n gap: 8px;\n align-items: center;\n font-size: 12px;\n }\n .pagination-info {\n font-size: 12px;\n }\n .page-btn {\n min-width: 28px;\n height: 28px;\n font-size: 12px;\n }\n }\n\n /* ─── Responsive: section titles ────────────────────────────────────── */\n @media (max-width: 640px) {\n .section-title { font-size: 17px; }\n .section-desc { font-size: 13px; margin-bottom: 20px; padding-bottom: 16px; }\n }\n\n /* ─── Responsive: settings card ─────────────────────────────────────── */\n @media (max-width: 640px) {\n .settings-card { border-radius: var(--radius-sm); }\n .settings-card-header {\n gap: 8px;\n padding: 12px 14px;\n }\n .settings-card-header-desc { text-align: left; }\n .settings-row {\n gap: 6px;\n padding: 12px 14px;\n }\n .settings-row.settings-row-stack {\n flex-direction: column;\n align-items: flex-start;\n }\n }\n\n /* ─── Responsive: table horizontal scroll on very small screens ─── */\n @media (max-width: 480px) {\n .settings-card {\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n }\n .settings-card .table {\n min-width: 360px;\n }\n }\n\n /* ─── Settings: OAuth card grid ─────────────────────────────── */\n .st-oauth-grid {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n padding: 8px 20px 16px;\n }\n .st-oauth-card {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 14px;\n border: 1px solid var(--border);\n border-radius: var(--radius-md);\n cursor: pointer;\n transition: border-color 0.15s, background 0.15s;\n min-width: 110px;\n }\n .st-oauth-card:hover {\n border-color: var(--border-strong);\n background: var(--bg-hover);\n }\n .st-oauth-card-icon {\n display: inline-flex;\n width: 20px;\n height: 20px;\n flex-shrink: 0;\n }\n .st-oauth-card-name {\n font-size: 13px;\n font-weight: 500;\n color: var(--text);\n }\n .st-oauth-dot {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n flex-shrink: 0;\n margin-left: auto;\n }\n .st-oauth-dot-on { background: var(--green-text); }\n .st-oauth-dot-off { background: var(--text-tertiary); }\n\n /* ─── Settings: dual panel (login methods + email) ──────────── */\n .st-dual-panel {\n display: flex;\n gap: 16px;\n }\n @media (max-width: 640px) {\n .st-dual-panel {\n flex-direction: column;\n }\n }\n\n /* ─── Settings: email hint ─────────────────────────────────── */\n .st-hint {\n font-size: 11px;\n color: var(--yellow-text, #92400e);\n font-weight: 400;\n margin-left: 4px;\n }\n\n /* ─── Federation ──────────────────────────────────────────── */\n .fed-status-badge {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 12px;\n font-weight: 500;\n padding: 2px 8px;\n border-radius: var(--radius-full);\n }\n .fed-status-badge.master {\n background: var(--blue-light);\n color: var(--blue-muted);\n }\n .fed-status-badge.member {\n background: var(--green-light);\n color: var(--green-text);\n }\n .fed-star {\n color: var(--yellow);\n font-size: 14px;\n }\n .fed-site-row {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 10px 20px;\n border-bottom: 1px solid var(--border);\n }\n .fed-site-row:last-child { border-bottom: none; }\n .fed-site-row { flex-wrap: wrap; }\n .fed-site-name { flex: 1 1 0%; min-width: 120px; font-weight: 500; font-size: 14px; color: var(--text); display: flex; align-items: center; gap: 6px; overflow: hidden; flex-wrap: nowrap; }\n .fed-site-name > span { flex-shrink: 0; }\n .fed-site-name > span.fed-site-label { flex-shrink: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .fed-site-url { flex: 0 0 500px; min-width: 0; font-size: 13px; color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .fed-site-url a { color: var(--text-secondary); text-decoration: none; }\n .fed-site-url a:hover { color: var(--blue); text-decoration: underline; }\n .fed-site-role { width: 80px; font-size: 12px; color: var(--text-tertiary); }\n .fed-site-actions { display: flex; align-items: center; gap: 6px; justify-content: flex-end; min-width: 64px; flex-shrink: 0; }\n @media (max-width: 768px) {\n .fed-site-row { flex-wrap: wrap; }\n .fed-site-name { flex: 1 1 100%; }\n .fed-site-url { flex: 1 1 auto; }\n }\n .fed-delegation-status { display: flex; align-items: center; gap: 8px; padding: 12px 20px; font-size: 13px; color: var(--text-secondary); }\n .fed-delegation-status .checkmark { color: var(--green); font-weight: 600; }\n .fed-init-buttons { display: flex; gap: 12px; justify-content: center; padding: 8px 20px 16px; }\n .fed-init-buttons .btn { width: auto; min-width: 160px; }\n","admin-core.c0b5af61.js":"\nvar toastContainer = null;\n\nfunction ensureToastContainer() {\n if (toastContainer) return;\n toastContainer = document.createElement(\"div\");\n toastContainer.style.cssText = \"position:fixed;bottom:20px;right:20px;z-index:9999;display:flex;flex-direction:column;gap:8px;\";\n document.body.appendChild(toastContainer);\n}\n\nfunction showToast(message, type) {\n ensureToastContainer();\n var toast = document.createElement(\"div\");\n var bg = type === \"error\" ? \"var(--red-bg)\" : type === \"success\" ? \"var(--green-bg)\" : \"var(--bg-card)\";\n var color = type === \"error\" ? \"var(--red)\" : type === \"success\" ? \"var(--green)\" : \"var(--text)\";\n var border = type === \"error\" ? \"var(--red)\" : type === \"success\" ? \"var(--green)\" : \"var(--border)\";\n toast.style.cssText = \"padding:12px 20px;border-radius:8px;font-size:14px;background:\" + bg + \";color:\" + color + \";border:1px solid \" + border + \";box-shadow:0 4px 12px rgba(0,0,0,0.3);opacity:0;transition:opacity 0.2s;\";\n toast.textContent = message;\n toastContainer.appendChild(toast);\n requestAnimationFrame(function() { toast.style.opacity = \"1\"; });\n setTimeout(function() {\n toast.style.opacity = \"0\";\n setTimeout(function() { toast.remove(); }, 200);\n }, 3000);\n}\n\n;\n\nfunction relativeTime(iso) {\n var diff = Date.now() - new Date(iso).getTime();\n var abs = Math.abs(diff);\n var future = diff < 0;\n var s = Math.floor(abs / 1000);\n var m = Math.floor(s / 60);\n var h = Math.floor(m / 60);\n var d = Math.floor(h / 24);\n var label;\n if (d > 0) label = d + \"d \" + (h % 24) + \"h\";\n else if (h > 0) label = h + \"h \" + (m % 60) + \"m\";\n else if (m > 0) label = m + \" min\";\n else label = \"just now\";\n if (label === \"just now\") return label;\n return future ? \"in \" + label : label + \" ago\";\n}\n\nfunction absoluteTime(iso) {\n var d = new Date(iso);\n var pad = function(n) { return n < 10 ? \"0\" + n : \"\" + n; };\n return d.getFullYear() + \"-\" + pad(d.getMonth() + 1) + \"-\" + pad(d.getDate())\n + \" \" + pad(d.getHours()) + \":\" + pad(d.getMinutes()) + \":\" + pad(d.getSeconds());\n}\n\nfunction copyToClipboard(text) {\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(text).then(function() {\n showToast(\"Copied!\", \"success\");\n }).catch(function() {\n fallbackCopy(text);\n });\n } else {\n fallbackCopy(text);\n }\n}\n\nfunction fallbackCopy(text) {\n var input = document.createElement(\"input\");\n input.value = text;\n document.body.appendChild(input);\n input.select();\n document.execCommand(\"copy\");\n input.remove();\n showToast(\"Copied!\", \"success\");\n}\n\n// Delegate to the canonical implementation from @arcblock/did-connect-core\n// (exposed globally by the <did-address> IIFE bundle as __DidAddressBundle.truncateDid)\nfunction truncateDid(did) {\n return __DidAddressBundle.truncateDid(did);\n}\n\nfunction initials(name) {\n if (!name) return \"?\";\n var parts = name.trim().split(/\\s+/);\n if (parts.length >= 2) return (parts[0][0] + parts[1][0]).toUpperCase();\n return name.slice(0, 2).toUpperCase();\n}\n\nfunction escapeHtml(str) {\n var div = document.createElement(\"div\");\n div.textContent = str;\n return div.innerHTML;\n}\n\n// Generate a shimmer skeleton table: cols = column count, rows = row count\nfunction skeletonTable(cols, rows) {\n var widths = [70, 45, 60, 55, 65, 50, 60, 30];\n var cells = '';\n for (var i = 0; i < cols; i++) {\n cells += '<td><span class=\"skel\" style=\"width:' + widths[i % widths.length] + '%;height:13px;\"></span></td>';\n }\n var row = '<tr class=\"skel-row\">' + cells + '</tr>';\n var tbody = '';\n for (var j = 0; j < rows; j++) tbody += row;\n return '<table class=\"table\"><tbody>' + tbody + '</tbody></table>';\n}\n\n/** Generate shimmer skeleton rows for list-style containers. */\nfunction skeletonRows(rows) {\n var widths = [45, 65, 55];\n var html = '';\n for (var i = 0; i < rows; i++) {\n html += '<div class=\"settings-row\"><span class=\"skel\" style=\"width:' + widths[i % widths.length] + '%;height:13px;\"></span></div>';\n }\n return html;\n}\n\nfunction renderAvatar(avatarUrl, fullName, size) {\n size = size || \"\";\n var cls = \"avatar\" + (size ? \" avatar-\" + size : \"\");\n if (avatarUrl) {\n var sep = avatarUrl.indexOf(\"?\") >= 0 ? \"&\" : \"?\";\n var src = avatarUrl + sep + \"t=\" + Date.now() + (size === \"sm\" ? \"&s=64\" : \"\");\n return '<div class=\"' + cls + '\">'\n + '<img src=\"' + escapeHtml(src) + '\" '\n + 'onerror=\"this.style.display=\\'none\\';this.nextSibling.style.display=\\'flex\\'\">'\n + '<span class=\"avatar-fallback\" style=\"display:none\">' + escapeHtml(initials(fullName)) + '</span>'\n + '</div>';\n }\n return '<div class=\"' + cls + '\"><span class=\"avatar-fallback\">' + escapeHtml(initials(fullName)) + '</span></div>';\n}\n\n;\n\nasync function api(method, path, body) {\n try {\n var opts = {\n method: method,\n headers: {},\n };\n if (body !== undefined) {\n opts.headers[\"Content-Type\"] = \"application/json\";\n opts.body = JSON.stringify(body);\n }\n var res = await fetch(window.__BASE_PATH + path, opts);\n var data = await res.json();\n\n if (res.status === 401) {\n location.href = \"/.well-known/service/api/did/logout\";\n return { ok: false, error: \"Session expired\", code: \"UNAUTHENTICATED\" };\n }\n if (res.status === 403 && data.code === \"BLOCKED\") {\n location.href = \"/.well-known/service/api/did/logout\";\n return { ok: false, error: \"Account blocked\", code: \"BLOCKED\" };\n }\n if (!res.ok) {\n showToast(data.error || \"Request failed\", \"error\");\n return { ok: false, error: data.error, code: data.code };\n }\n return data;\n } catch (err) {\n showToast(\"Network error\", \"error\");\n return { ok: false, error: err.message, code: \"NETWORK_ERROR\" };\n }\n}\n\n;\n\nfunction showDialog(id) {\n var d = document.getElementById(id);\n if (d && d.showModal) d.showModal();\n}\n\nfunction closeDialog(id) {\n var d = document.getElementById(id || \"generic-dialog\");\n if (d && d.close) d.close();\n}\n\nfunction openDialog(title, bodyHtml, confirmLabel, onConfirm) {\n var d = document.getElementById(\"generic-dialog\");\n if (!d) {\n d = document.createElement(\"dialog\");\n d.id = \"generic-dialog\";\n d.innerHTML = '<div class=\"dialog-content\"><h3 id=\"gd-title\"></h3><div id=\"gd-body\"></div></div><div class=\"dialog-actions\"><button class=\"btn btn-secondary\" id=\"gd-cancel\">Cancel</button><button class=\"btn btn-primary\" id=\"gd-ok\">Confirm</button></div>';\n document.body.appendChild(d);\n }\n document.getElementById(\"gd-title\").textContent = title;\n document.getElementById(\"gd-body\").innerHTML = bodyHtml;\n var okBtn = document.getElementById(\"gd-ok\");\n okBtn.textContent = confirmLabel || \"Confirm\";\n okBtn.className = (confirmLabel === \"Delete\") ? \"btn btn-danger\" : \"btn btn-primary\";\n okBtn.onclick = function() { onConfirm(); };\n document.getElementById(\"gd-cancel\").onclick = function() { d.close(); };\n d.showModal();\n}\n\nfunction confirmDialog(opts) {\n return new Promise(function(resolve) {\n var d = document.getElementById(\"confirm-dialog\");\n if (!d) {\n d = document.createElement(\"dialog\");\n d.id = \"confirm-dialog\";\n d.innerHTML = '<div class=\"dialog-content\"><h3 id=\"confirm-title\"></h3><p id=\"confirm-message\" class=\"dialog-desc\"></p></div><div class=\"dialog-actions\"><button class=\"btn btn-secondary\" id=\"confirm-cancel\">Cancel</button><button class=\"btn\" id=\"confirm-ok\">Confirm</button></div>';\n document.body.appendChild(d);\n }\n document.getElementById(\"confirm-title\").textContent = opts.title || \"Confirm\";\n document.getElementById(\"confirm-message\").textContent = opts.message || \"\";\n var okBtn = document.getElementById(\"confirm-ok\");\n okBtn.textContent = opts.confirmLabel || \"Confirm\";\n okBtn.className = opts.danger ? \"btn btn-danger\" : \"btn\";\n document.getElementById(\"confirm-cancel\").textContent = opts.cancelLabel || \"Cancel\";\n\n function cleanup() {\n okBtn.onclick = null;\n document.getElementById(\"confirm-cancel\").onclick = null;\n d.close();\n }\n okBtn.onclick = function() { cleanup(); resolve(true); };\n document.getElementById(\"confirm-cancel\").onclick = function() { cleanup(); resolve(false); };\n d.onclose = function() { resolve(false); };\n d.showModal();\n });\n}\n\nfunction promptDialog(opts) {\n return new Promise(function(resolve) {\n var d = document.getElementById(\"prompt-dialog\");\n if (!d) {\n d = document.createElement(\"dialog\");\n d.id = \"prompt-dialog\";\n d.innerHTML = '<div class=\"dialog-content\"><h3 id=\"prompt-title\"></h3><p id=\"prompt-message\" class=\"dialog-desc\"></p><input class=\"input\" id=\"prompt-input\" style=\"margin-top:12px;\" /></div><div class=\"dialog-actions\"><button class=\"btn btn-secondary\" id=\"prompt-cancel\">Cancel</button><button class=\"btn\" id=\"prompt-ok\">Confirm</button></div>';\n document.body.appendChild(d);\n }\n document.getElementById(\"prompt-title\").textContent = opts.title || \"\";\n document.getElementById(\"prompt-message\").textContent = opts.message || \"\";\n var input = document.getElementById(\"prompt-input\");\n input.type = opts.inputType || \"text\";\n input.placeholder = opts.placeholder || \"\";\n input.value = opts.defaultValue || \"\";\n var okBtn = document.getElementById(\"prompt-ok\");\n okBtn.textContent = opts.confirmLabel || __t(\"common.confirm\");\n\n function cleanup() { d.close(); }\n okBtn.onclick = function() { var v = input.value.trim(); cleanup(); resolve(v || null); };\n document.getElementById(\"prompt-cancel\").onclick = function() { cleanup(); resolve(null); };\n d.onclose = function() { resolve(null); };\n d.showModal();\n input.focus();\n });\n}\n\n;\n\nvar currentTab = null;\nvar tabHandlers = {};\n\nfunction registerTab(name, handler) {\n tabHandlers[name] = handler;\n}\n\nfunction switchTab(name) {\n if (currentTab === name) return;\n currentTab = name;\n\n // Update tab buttons (desktop)\n document.querySelectorAll(\"[data-tab]\").forEach(function(el) {\n el.classList.toggle(\"tab-active\", el.getAttribute(\"data-tab\") === name);\n });\n\n // Update sidebar items (mobile)\n document.querySelectorAll(\"[data-sidebar-tab]\").forEach(function(el) {\n el.classList.toggle(\"sidebar-item-active\", el.getAttribute(\"data-sidebar-tab\") === name);\n });\n\n // Update tab panels\n document.querySelectorAll(\"[data-panel]\").forEach(function(el) {\n el.classList.toggle(\"hidden\", el.getAttribute(\"data-panel\") !== name);\n });\n\n // Update hash without triggering hashchange\n if (location.hash !== \"#\" + name) {\n history.replaceState(null, \"\", \"#\" + name);\n }\n\n // Call tab handler for lazy loading\n if (tabHandlers[name]) {\n tabHandlers[name]();\n }\n}\n\nfunction initRouter(defaultTab) {\n var hash = location.hash.slice(1);\n var validTabs = Object.keys(tabHandlers);\n var initial = validTabs.indexOf(hash) >= 0 ? hash : defaultTab;\n switchTab(initial);\n\n window.addEventListener(\"hashchange\", function() {\n var h = location.hash.slice(1);\n if (tabHandlers[h]) switchTab(h);\n });\n}\n\n;\n\nvar profileData = null;\n\nasync function loadProfile() {\n document.getElementById(\"profile-card-skeleton\").style.display = \"\";\n document.getElementById(\"profile-card-content\").style.display = \"none\";\n var data = await api(\"GET\", \"/profile\");\n document.getElementById(\"profile-card-skeleton\").style.display = \"none\";\n document.getElementById(\"profile-card-content\").style.display = \"\";\n if (!data.ok) return;\n profileData = data.user;\n renderProfile(data.user);\n}\n\nfunction renderProfile(user) {\n var avatarEl = document.getElementById(\"profile-avatar\");\n if (user.avatar) {\n avatarEl.innerHTML = '<img src=\"' + escapeHtml(user.avatar) + '\" onerror=\"this.style.display=\\'none\\';this.nextSibling.style.display=\\'flex\\'\">'\n + '<span class=\"avatar-fallback\" style=\"display:none\">' + escapeHtml(initials(user.fullName)) + '</span>'\n + '<div class=\"avatar-overlay\"><span>Edit</span></div>';\n } else {\n avatarEl.innerHTML = '<span class=\"avatar-fallback\">' + escapeHtml(initials(user.fullName)) + '</span>'\n + '<div class=\"avatar-overlay\"><span>Edit</span></div>';\n }\n\n document.getElementById(\"profile-display-name\").textContent = user.fullName || __t(\"profile.unnamed\");\n\n var role = user.role || \"guest\";\n var badge = document.getElementById(\"profile-role-badge\");\n badge.textContent = __t(\"common.\" + role);\n badge.className = \"badge badge-\" + role;\n\n document.getElementById(\"profile-did\").setAttribute(\"did\", user.did);\n\n document.getElementById(\"profile-name\").value = user.fullName || \"\";\n cancelEditName();\n\n document.getElementById(\"profile-email-display\").textContent = user.email || \"—\";\n\n var emailBadge = document.getElementById(\"profile-email-badge\");\n var emailVerifyBtn = document.getElementById(\"profile-email-verify-btn\");\n if (user.email) {\n emailBadge.style.display = \"\";\n if (user.emailVerified) {\n emailBadge.textContent = __t(\"profile.emailVerified\");\n emailBadge.className = \"badge badge-verified\";\n emailVerifyBtn.style.display = \"none\";\n } else {\n emailBadge.textContent = __t(\"profile.emailUnverified\");\n emailBadge.className = \"badge badge-unverified\";\n emailVerifyBtn.style.display = \"\";\n }\n } else {\n emailBadge.style.display = \"none\";\n emailVerifyBtn.style.display = \"none\";\n }\n\n if (user.createdAt) {\n document.getElementById(\"profile-joined-cell\").style.display = \"\";\n document.getElementById(\"profile-joined-value\").textContent = absoluteTime(user.createdAt);\n }\n}\n\nfunction startEditName() {\n document.getElementById(\"profile-header-info-view\").style.display = \"none\";\n document.getElementById(\"profile-name-editor\").style.display = \"\";\n document.getElementById(\"profile-name\").focus();\n}\n\nfunction cancelEditName() {\n document.getElementById(\"profile-name-editor\").style.display = \"none\";\n document.getElementById(\"profile-header-info-view\").style.display = \"\";\n if (profileData) document.getElementById(\"profile-name\").value = profileData.fullName || \"\";\n}\n\nasync function saveProfile() {\n var body = {\n fullName: document.getElementById(\"profile-name\").value.trim(),\n };\n var data = await api(\"PUT\", \"/profile\", body);\n if (data.ok) {\n profileData = data.user;\n renderProfile(data.user);\n showToast(__t(\"profile.updated\"), \"success\");\n var headerName = document.getElementById(\"header-user-name\");\n if (headerName) headerName.textContent = data.user.fullName || truncateDid(data.user.did);\n }\n}\n\nasync function changeEmail() {\n var currentEmail = profileData ? profileData.email || \"\" : \"\";\n var newEmail = await promptDialog({\n title: __t(\"profile.changeEmailTitle\"),\n message: __t(\"profile.changeEmailMsg\"),\n placeholder: __t(\"profile.emailPlaceholder\"),\n defaultValue: currentEmail,\n inputType: \"email\",\n });\n if (!newEmail || newEmail === currentEmail) return;\n var result = await api(\"PUT\", \"/profile/email\", { email: newEmail });\n if (result.ok) {\n profileData = result.user;\n renderProfile(result.user);\n showToast(__t(\"profile.emailUpdated\"), \"success\");\n }\n}\n\nvar _lastVerifySentAt = 0;\n\nfunction verifyEmail() {\n var email = profileData ? profileData.email : \"\";\n if (!email) return;\n\n var verifyBtn = document.getElementById(\"profile-email-verify-btn\");\n verifyBtn.disabled = true;\n\n // Build or reuse dialog\n var d = document.getElementById(\"verify-email-dialog\");\n if (!d) {\n d = document.createElement(\"dialog\");\n d.id = \"verify-email-dialog\";\n d.innerHTML = '<div class=\"dialog-content\">'\n + '<h3 id=\"verify-title\"></h3>'\n + '<p id=\"verify-message\" class=\"dialog-desc\"></p>'\n + '<input class=\"input\" id=\"verify-input\" placeholder=\"000000\" style=\"margin-top:12px;\" />'\n + '</div>'\n + '<div class=\"dialog-actions\">'\n + '<button class=\"btn-text\" id=\"verify-resend\" style=\"margin-right:auto\"></button>'\n + '<button class=\"btn btn-secondary\" id=\"verify-cancel\">' + __t(\"common.cancel\") + '</button>'\n + '<button class=\"btn\" id=\"verify-ok\">' + __t(\"common.confirm\") + '</button>'\n + '</div>';\n document.body.appendChild(d);\n }\n\n var titleEl = document.getElementById(\"verify-title\");\n var msgEl = document.getElementById(\"verify-message\");\n var inputEl = document.getElementById(\"verify-input\");\n var okBtn = document.getElementById(\"verify-ok\");\n var cancelBtn = document.getElementById(\"verify-cancel\");\n var resendBtn = document.getElementById(\"verify-resend\");\n var countdownTimer = null;\n\n titleEl.textContent = __t(\"profile.verifyCode\");\n inputEl.value = \"\";\n\n function cleanup() {\n if (countdownTimer) clearInterval(countdownTimer);\n verifyBtn.disabled = false;\n d.close();\n }\n\n function remainingCooldown() {\n if (!_lastVerifySentAt) return 0;\n return Math.max(0, Math.ceil((60000 - (Date.now() - _lastVerifySentAt)) / 1000));\n }\n\n function syncResendBtn() {\n var remain = remainingCooldown();\n resendBtn.style.display = \"\";\n if (remain > 0) {\n resendBtn.disabled = true;\n resendBtn.textContent = __t(\"profile.emailResendCountdown\", { seconds: remain });\n } else {\n resendBtn.disabled = false;\n resendBtn.textContent = __t(\"profile.emailResendBtn\");\n }\n }\n\n function startCountdown() {\n if (countdownTimer) clearInterval(countdownTimer);\n syncResendBtn();\n countdownTimer = setInterval(function() {\n if (remainingCooldown() <= 0) {\n clearInterval(countdownTimer);\n countdownTimer = null;\n }\n syncResendBtn();\n }, 1000);\n }\n\n function sendCode() {\n msgEl.textContent = __t(\"profile.emailSending\");\n inputEl.disabled = true;\n okBtn.disabled = true;\n resendBtn.style.display = \"none\";\n if (countdownTimer) { clearInterval(countdownTimer); countdownTimer = null; }\n\n api(\"POST\", \"/profile/email/verify\").then(function(sendResult) {\n if (!sendResult.ok) {\n msgEl.textContent = sendResult.error || __t(\"profile.emailSendFailed\");\n // If there's remaining cooldown, show countdown; otherwise show resend\n if (remainingCooldown() > 0) {\n startCountdown();\n } else {\n resendBtn.style.display = \"\";\n resendBtn.disabled = false;\n resendBtn.textContent = __t(\"profile.emailResendBtn\");\n }\n return;\n }\n _lastVerifySentAt = Date.now();\n msgEl.textContent = __t(\"profile.verifyCodeMsg\", { email: email });\n inputEl.disabled = false;\n okBtn.disabled = false;\n inputEl.focus();\n startCountdown();\n });\n }\n\n cancelBtn.onclick = function() { cleanup(); };\n d.onclose = function() {\n if (countdownTimer) clearInterval(countdownTimer);\n verifyBtn.disabled = false;\n };\n\n okBtn.onclick = function() {\n var code = inputEl.value.trim();\n if (!code) return;\n okBtn.disabled = true;\n api(\"PUT\", \"/profile/email/verify\", { code: code }).then(function(result) {\n if (result.ok) {\n cleanup();\n profileData = result.user;\n renderProfile(result.user);\n showToast(__t(\"profile.emailVerifiedSuccess\"), \"success\");\n } else {\n okBtn.disabled = false;\n msgEl.textContent = result.error || __t(\"profile.emailVerifyFailed\");\n }\n });\n };\n\n resendBtn.onclick = function() { sendCode(); };\n\n d.showModal();\n\n // If still in cooldown from a previous send, skip sending and show code input directly\n var remain = remainingCooldown();\n if (remain > 0) {\n msgEl.textContent = __t(\"profile.verifyCodeMsg\", { email: email });\n inputEl.disabled = false;\n okBtn.disabled = false;\n inputEl.focus();\n startCountdown();\n } else {\n sendCode();\n }\n}\n\nfunction triggerAvatarUpload() {\n document.getElementById(\"avatar-file-input\").click();\n}\n\nfunction resizeImage(file, maxSize) {\n return new Promise(function(resolve, reject) {\n var img = new Image();\n img.onload = function() {\n var canvas = document.createElement(\"canvas\");\n canvas.width = canvas.height = maxSize;\n var ctx = canvas.getContext(\"2d\");\n var s = Math.min(img.width, img.height);\n var sx = (img.width - s) / 2;\n var sy = (img.height - s) / 2;\n ctx.drawImage(img, sx, sy, s, s, 0, 0, maxSize, maxSize);\n resolve(canvas.toDataURL(\"image/jpeg\", 0.85));\n URL.revokeObjectURL(img.src);\n };\n img.onerror = function() {\n URL.revokeObjectURL(img.src);\n reject(new Error(\"Failed to load image\"));\n };\n img.src = URL.createObjectURL(file);\n });\n}\n\nasync function handleAvatarFile(input) {\n var file = input.files[0];\n if (!file) return;\n if (file.size > 10 * 1024 * 1024) {\n showToast(\"Image must be under 10MB\", \"error\");\n input.value = \"\";\n return;\n }\n try {\n var base64 = await resizeImage(file, 200);\n var data = await api(\"PUT\", \"/profile/avatar\", { avatar: base64 });\n if (data.ok) {\n showToast(\"Avatar updated\", \"success\");\n loadProfile();\n }\n } catch (e) {\n showToast(\"Failed to process image\", \"error\");\n }\n input.value = \"\";\n}\n\nasync function removeAvatar() {\n var ok = await confirmDialog({ title: \"Remove Avatar\", message: \"Remove your avatar? Your profile will show a default avatar.\", confirmLabel: \"Remove\" });\n if (!ok) return;\n var data = await api(\"DELETE\", \"/profile/avatar\");\n if (data.ok) {\n showToast(\"Avatar removed\", \"success\");\n loadProfile();\n }\n}\n\nregisterTab(\"profile\", function() {\n loadProfile();\n if (typeof loadConnectedAccounts === \"function\") loadConnectedAccounts();\n});\n\n;\n\nvar __connectedAccounts = [];\nvar __oauthProviders = [];\nvar __providerIcons = {\"passkey\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"18\\\" height=\\\"18\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\"><path fill=\\\"currentColor\\\" d=\\\"M3 20v-2.8q0-.85.438-1.562T4.6 14.55q1.55-.775 3.15-1.162T11 13q.5 0 1 .038t1 .112q-.1 1.45.525 2.738T15.35 18v2zm16 3l-1.5-1.5v-4.65q-1.1-.325-1.8-1.237T15 13.5q0-1.45 1.025-2.475T18.5 10t2.475 1.025T22 13.5q0 1.125-.638 2t-1.612 1.25L21 18l-1.5 1.5L21 21zm-8-11q-1.65 0-2.825-1.175T7 8t1.175-2.825T11 4t2.825 1.175T15 8t-1.175 2.825T11 12m7.5 2q.425 0 .713-.288T19.5 13t-.288-.712T18.5 12t-.712.288T17.5 13t.288.713t.712.287\\\"/></svg>\",\"wallet\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"18\\\" height=\\\"18\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\"><rect x=\\\"2\\\" y=\\\"4\\\" width=\\\"20\\\" height=\\\"16\\\" rx=\\\"3\\\" fill=\\\"#4598FA\\\"/><rect x=\\\"3\\\" y=\\\"6\\\" width=\\\"18\\\" height=\\\"12\\\" rx=\\\"2\\\" fill=\\\"white\\\" opacity=\\\"0.9\\\"/><rect x=\\\"0\\\" y=\\\"14\\\" width=\\\"24\\\" height=\\\"10\\\" rx=\\\"0\\\" fill=\\\"url(#dw_g)\\\"/><path d=\\\"M5.5 9.5h4.5c.3 0 .5.2.5.5v2.5c0 .3-.2.5-.5.5H5.5c-.3 0-.5-.2-.5-.5V10c0-.3.2-.5.5-.5z\\\" fill=\\\"#4598FA\\\"/><defs><linearGradient id=\\\"dw_g\\\" x1=\\\"12\\\" y1=\\\"14\\\" x2=\\\"12\\\" y2=\\\"24\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop stop-color=\\\"#77B2F6\\\"/><stop offset=\\\"1\\\" stop-color=\\\"#4598FA\\\"/></linearGradient></defs></svg>\",\"did-connect\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"18\\\" height=\\\"18\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\"><rect x=\\\"2\\\" y=\\\"4\\\" width=\\\"20\\\" height=\\\"16\\\" rx=\\\"3\\\" fill=\\\"#4598FA\\\"/><rect x=\\\"3\\\" y=\\\"6\\\" width=\\\"18\\\" height=\\\"12\\\" rx=\\\"2\\\" fill=\\\"white\\\" opacity=\\\"0.9\\\"/><rect x=\\\"0\\\" y=\\\"14\\\" width=\\\"24\\\" height=\\\"10\\\" rx=\\\"0\\\" fill=\\\"url(#dw_g)\\\"/><path d=\\\"M5.5 9.5h4.5c.3 0 .5.2.5.5v2.5c0 .3-.2.5-.5.5H5.5c-.3 0-.5-.2-.5-.5V10c0-.3.2-.5.5-.5z\\\" fill=\\\"#4598FA\\\"/><defs><linearGradient id=\\\"dw_g\\\" x1=\\\"12\\\" y1=\\\"14\\\" x2=\\\"12\\\" y2=\\\"24\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop stop-color=\\\"#77B2F6\\\"/><stop offset=\\\"1\\\" stop-color=\\\"#4598FA\\\"/></linearGradient></defs></svg>\",\"email\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"18\\\" height=\\\"18\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\"><path fill=\\\"currentColor\\\" d=\\\"M22 7.535V17a3 3 0 0 1-2.824 2.995L19 20H5a3 3 0 0 1-2.995-2.824L2 17V7.535l9.445 6.297l.116.066a1 1 0 0 0 .878 0l.116-.066z\\\"/><path fill=\\\"currentColor\\\" d=\\\"M19 4c1.08 0 2.027.57 2.555 1.427L12 11.797l-9.555-6.37a2.999 2.999 0 0 1 2.354-1.42L5 4z\\\"/></svg>\",\"google\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"18\\\" height=\\\"18\\\" viewBox=\\\"0 0 256 262\\\"><path fill=\\\"#4285F4\\\" d=\\\"M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622l38.755 30.023l2.685.268c24.659-22.774 38.875-56.282 38.875-96.027\\\"/><path fill=\\\"#34A853\\\" d=\\\"M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055c-34.523 0-63.824-22.773-74.269-54.25l-1.531.13l-40.298 31.187l-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1\\\"/><path fill=\\\"#FBBC05\\\" d=\\\"M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82c0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602z\\\"/><path fill=\\\"#EB4335\\\" d=\\\"M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0C79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251\\\"/></svg>\",\"github\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"18\\\" height=\\\"18\\\" viewBox=\\\"0 0 256 250\\\" fill=\\\"currentColor\\\"><path d=\\\"M128.001 0C57.317 0 0 57.307 0 128.001c0 56.554 36.676 104.535 87.535 121.46c6.397 1.185 8.746-2.777 8.746-6.158c0-3.052-.12-13.135-.174-23.83c-35.61 7.742-43.124-15.103-43.124-15.103c-5.823-14.795-14.213-18.73-14.213-18.73c-11.613-7.944.876-7.78.876-7.78c12.853.902 19.621 13.19 19.621 13.19c11.417 19.568 29.945 13.911 37.249 10.64c1.149-8.272 4.466-13.92 8.127-17.116c-28.431-3.236-58.318-14.212-58.318-63.258c0-13.975 5-25.394 13.188-34.358c-1.329-3.224-5.71-16.242 1.24-33.874c0 0 10.749-3.44 35.21 13.121c10.21-2.836 21.16-4.258 32.038-4.307c10.878.049 21.837 1.47 32.066 4.307c24.431-16.56 35.165-13.12 35.165-13.12c6.967 17.63 2.584 30.65 1.255 33.873c8.207 8.964 13.173 20.383 13.173 34.358c0 49.163-29.944 59.988-58.447 63.157c4.591 3.972 8.682 11.762 8.682 23.704c0 17.126-.148 30.91-.148 35.126c0 3.407 2.304 7.398 8.792 6.14C219.37 232.5 256 184.537 256 128.002C256 57.307 198.691 0 128.001 0\\\"/></svg>\",\"apple\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"18\\\" height=\\\"18\\\" viewBox=\\\"0 0 256 315\\\" fill=\\\"currentColor\\\"><path d=\\\"M213.803 167.03c.442 47.58 41.74 63.413 42.197 63.615c-.35 1.116-6.599 22.563-21.757 44.716c-13.104 19.153-26.705 38.235-48.13 38.63c-21.05.388-27.82-12.483-51.888-12.483c-24.061 0-31.582 12.088-51.51 12.871c-20.68.783-36.428-20.71-49.64-39.793c-27-39.033-47.633-110.3-19.928-158.406c13.763-23.89 38.36-39.017 65.056-39.405c20.307-.388 39.475 13.675 51.889 13.675c12.406 0 35.699-16.895 60.186-14.414c10.25.427 39.026 4.14 57.503 31.186c-1.49.923-34.335 20.044-33.978 59.808M174.24 50.199c10.98-13.29 18.369-31.79 16.353-50.199c-15.826.636-34.962 10.546-46.314 23.828c-10.173 11.763-19.082 30.589-16.678 48.633c17.64 1.365 35.66-8.964 46.639-22.262\\\"/></svg>\",\"twitter\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"18\\\" height=\\\"18\\\" viewBox=\\\"0 0 256 256\\\" fill=\\\"currentColor\\\"><path d=\\\"M149.079 108.399L242.33 0h-22.098l-80.97 94.12L74.59 0H0l97.796 142.328L0 256h22.1l85.507-99.395L175.905 256h74.59L149.073 108.399zM118.81 143.58l-9.909-14.172l-78.84-112.773h33.943l63.625 91.011l9.909 14.173l82.705 118.3H186.3l-67.49-96.533z\\\"/></svg>\",\"auth0\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"18\\\" height=\\\"18\\\" viewBox=\\\"0 0 256 256\\\"><path fill=\\\"#EB5424\\\" d=\\\"M203.97 200.38L167.47 88l-39.5 112.38h75.98zm0-144.76L167.47 168l75.97-26.16l19.76-60.82c6.64-20.4-1.94-42.72-21.18-52.76l-38.06 27.36zM52.03 55.62L88.54 168l39.5-112.38H52.02zM52.03 200.38L88.54 88L12.56 114.16l-5.92 18.2c-6.64 20.4 1.94 42.72 21.18 52.76l24.2-17.38l.01.01v-.01l-.01.01.01-.37z\\\"/></svg>\"};\nvar __hiddenProviders = [\"facebook\",\"auth0-legacy\"];\n\nasync function loadConnectedAccounts() {\n var card = document.getElementById(\"connected-accounts-card\");\n var list = document.getElementById(\"connected-accounts-list\");\n\n // Show card with skeleton chips while loading\n card.style.display = \"\";\n list.innerHTML = '<div style=\"padding:12px 20px 16px;display:flex;gap:8px;flex-wrap:wrap;\">'\n + '<span class=\"skel\" style=\"width:80px;height:32px;border-radius:16px;\"></span>'.repeat(4)\n + '</div>';\n\n // Load session data and OAuth configs in parallel\n var results = await Promise.all([\n api(\"GET\", \"/../did/session\"),\n api(\"GET\", \"/../oauth/configs\").catch(function() { return { providers: [] }; }),\n ]);\n\n var session = results[0];\n var oauthData = results[1];\n\n var userObj = session.user || session;\n if (!session.authenticated || !userObj.connectedAccounts) {\n card.style.display = \"none\";\n return;\n }\n\n __connectedAccounts = userObj.connectedAccounts;\n __oauthProviders = (oauthData.providers || []).map(function(p) { return p.name || p; })\n .filter(function(p) { return __hiddenProviders.indexOf(p) === -1; });\n\n card.style.display = \"\";\n renderConnectedAccounts();\n}\n\nfunction renderConnectedAccounts() {\n var list = document.getElementById(\"connected-accounts-list\");\n var html = \"\";\n\n // Partition accounts by type\n var walletAccounts = [];\n var passkeyAccounts = [];\n var otherAccounts = [];\n for (var i = 0; i < __connectedAccounts.length; i++) {\n var a = __connectedAccounts[i];\n if (__hiddenProviders.indexOf(a.provider) !== -1) continue;\n if (a.provider === \"wallet\" || a.provider === \"did-connect\") {\n walletAccounts.push(a);\n } else if (a.provider === \"passkey\") {\n passkeyAccounts.push(a);\n } else {\n otherAccounts.push(a);\n }\n }\n // Sort non-wallet accounts: main first\n otherAccounts.sort(function(x, y) {\n if (x.isMain && !y.isMain) return -1;\n if (!x.isMain && y.isMain) return 1;\n return 0;\n });\n\n // --- Section 1: DID Wallet (always first) ---\n html += '<div class=\"ca-section\">';\n html += '<div class=\"ca-section-label\">DID Wallet</div>';\n if (walletAccounts.length > 0) {\n html += '<div class=\"ca-bound-grid\">';\n for (var w = 0; w < walletAccounts.length; w++) {\n html += renderBoundCard(walletAccounts[w]);\n }\n html += '</div>';\n // Already has a wallet — no connect button (one wallet per user)\n } else {\n html += '<button class=\"ca-passkey-add\" onclick=\"connectWallet()\">';\n html += '<span class=\"ca-chip-add-icon\">+</span>';\n html += '<span class=\"ca-chip-icon\">' + getProviderIconSvg(\"did-connect\") + '</span>';\n html += __t(\"profile.connectWallet\");\n html += '</button>';\n }\n html += '</div>';\n\n // --- Section 2: Other login methods ---\n if (otherAccounts.length > 0) {\n html += '<div class=\"ca-section\">';\n html += '<div class=\"ca-section-label\">' + __t(\"profile.loginMethods\") + '</div>';\n html += '<div class=\"ca-bound-grid\">';\n for (var b = 0; b < otherAccounts.length; b++) {\n html += renderBoundCard(otherAccounts[b]);\n }\n html += '</div></div>';\n }\n\n // --- Section 3: Passkeys (large cards + add chip) ---\n html += '<div class=\"ca-section\">';\n html += '<div class=\"ca-section-label\">' + __t(\"profile.passkeys\") + '</div>';\n if (passkeyAccounts.length > 0) {\n html += '<div class=\"ca-bound-grid\">';\n for (var pk = 0; pk < passkeyAccounts.length; pk++) {\n html += renderBoundCard(passkeyAccounts[pk]);\n }\n html += '</div>';\n }\n html += '<div style=\"padding:' + (passkeyAccounts.length > 0 ? '8' : '0') + 'px 0 0\">';\n html += '<button class=\"ca-passkey-add\" onclick=\"addPasskey()\">';\n html += '<span class=\"ca-chip-add-icon\">+</span>';\n html += '<span class=\"ca-chip-icon\">' + getProviderIconSvg(\"passkey\") + '</span>';\n html += __t(\"profile.addPasskey\");\n html += '</button>';\n html += '</div></div>';\n\n // --- Section 4: Available to connect (unbound OAuth, at the bottom) ---\n var boundProviders = {};\n for (var j = 0; j < __connectedAccounts.length; j++) {\n boundProviders[__connectedAccounts[j].provider] = true;\n }\n var unboundOAuth = __oauthProviders.filter(function(p) { return !boundProviders[p]; });\n if (unboundOAuth.length > 0) {\n html += '<div class=\"ca-section\">';\n html += '<div class=\"ca-section-label\">' + __t(\"profile.availableToConnect\") + '</div>';\n html += '<div class=\"ca-chips\">';\n for (var u = 0; u < unboundOAuth.length; u++) {\n var p = unboundOAuth[u];\n html += '<div class=\"ca-chip ca-chip-unbound\" onclick=\"connectOAuth(\\'' + escapeHtml(p) + '\\')\" title=\"' + __t(\"profile.connect\") + \" \" + escapeHtml(getProviderLabel(p)) + '\">';\n html += '<span class=\"ca-chip-add-icon\">+</span>';\n html += '<span class=\"ca-chip-icon\">' + getProviderIconSvg(p) + '</span>';\n html += '<span class=\"ca-chip-name\">' + escapeHtml(getProviderLabel(p)) + '</span>';\n html += '</div>';\n }\n html += '</div></div>';\n }\n\n list.innerHTML = html;\n}\n\nvar __copyIcon = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\"/><path d=\"M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1\"/></svg>';\n\nfunction renderBoundCard(a) {\n var icon = getProviderIconSvg(a.provider);\n var label = getProviderLabel(a.provider);\n var isPasskey = a.provider === \"passkey\";\n\n var html = '<div class=\"ca-bound-card\">';\n\n // Top row: icon + name/detail + action buttons (top-right)\n html += '<div class=\"ca-bound-top\">';\n if (isPasskey) {\n html += '<div class=\"ca-bound-icon ca-bound-icon-lg\">' + icon + '</div>';\n } else {\n html += '<div class=\"ca-bound-icon\">' + icon + '</div>';\n }\n html += '<div class=\"ca-bound-top-body\">';\n html += '<div class=\"ca-bound-header\">';\n html += '<span class=\"ca-bound-name\">' + escapeHtml(label) + '</span>';\n if (a.isMain) {\n html += '<span class=\"ca-chip-badge\">' + __t(\"profile.mainAccount\") + '</span>';\n } else if (a.provider === \"wallet\" || a.provider === \"did-connect\") {\n html += '<span class=\"ca-chip-badge\">' + __t(\"profile.walletAccount\") + '</span>';\n }\n html += '</div>';\n var info = getAccountUserInfo(a);\n if (info) {\n html += '<div class=\"ca-bound-detail\">' + info + '</div>';\n }\n html += '</div>';\n\n // Action buttons at top-right\n if (!a.isMain && a.provider !== \"wallet\" && a.provider !== \"did-connect\") {\n html += '<div class=\"ca-bound-actions\">';\n if (isPasskey) {\n html += '<button class=\"btn-text\" onclick=\"openRenamePasskeyDialog(\\'' + escapeHtml(a.did) + '\\', \\'' + escapeHtml(getPasskeyName(a)) + '\\')\">' + __t(\"profile.renamePasskey\") + '</button>';\n html += '<button class=\"btn-text\" style=\"color:var(--red-text)\" onclick=\"removePasskey(\\'' + escapeHtml(a.did) + '\\')\">' + __t(\"profile.remove\") + '</button>';\n } else {\n html += '<button class=\"btn-text\" style=\"color:var(--red-text)\" onclick=\"unbindOAuth(\\'' + escapeHtml(a.provider) + '\\')\">' + __t(\"profile.remove\") + '</button>';\n }\n html += '</div>';\n }\n html += '</div>';\n\n // DID field with copy\n if (a.did) {\n html += '<div class=\"ca-bound-field\">';\n html += '<span class=\"ca-bound-field-label\">DID</span>';\n html += '<did-address did=\"' + escapeHtml(a.did) + '\"></did-address>';\n html += '</div>';\n }\n\n html += '</div>';\n return html;\n}\n\nfunction getPasskeyName(a) {\n return (a.userInfo && a.userInfo.name) ? a.userInfo.name : \"Passkey\";\n}\n\nfunction getAccountUserInfo(a) {\n var parts = [];\n if (a.userInfo && a.userInfo.name) parts.push(escapeHtml(a.userInfo.name));\n if (a.userInfo && a.userInfo.email) parts.push(escapeHtml(a.userInfo.email));\n if (parts.length > 0) return parts.join(' · ');\n if (a.provider === \"passkey\") {\n var name = a.userInfo && a.userInfo.name ? a.userInfo.name : null;\n return name ? escapeHtml(name) : \"\\u2022\\u2022\\u2022\\u2022\" + (a.id ? a.id.slice(-4) : \"\");\n }\n return \"\";\n}\n\nfunction getProviderIconSvg(provider) {\n var svg = __providerIcons[provider];\n if (svg) return svg;\n return '<span style=\"display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:4px;background:var(--bg-secondary);font-size:11px;font-weight:600\">' + provider.charAt(0).toUpperCase() + '</span>';\n}\n\nfunction getProviderLabel(provider) {\n var labels = { passkey: \"Passkey\", google: \"Google\", github: \"GitHub\", apple: \"Apple\", twitter: \"Twitter\", wallet: \"DID Wallet\", \"did-connect\": \"DID Wallet\", email: \"Email\" };\n return labels[provider] || (provider.charAt(0).toUpperCase() + provider.slice(1));\n}\n\nfunction connectOAuth(provider) {\n var returnUrl = encodeURIComponent(window.location.href);\n var url = window.__BASE_PATH + \"/../oauth/\" + provider + \"/login?returnUrl=\" + returnUrl;\n var redirectUri = location.origin + \"/.well-known/service/oauth/callback/\" + provider;\n var w = 500, h = 600;\n var left = (screen.width - w) / 2, top = (screen.height - h) / 2;\n var popup = window.open(url, \"oauth_bind\", \"width=\" + w + \",height=\" + h + \",left=\" + left + \",top=\" + top);\n if (!popup) {\n showToast(\"Popup blocked — please allow popups and try again\", \"error\");\n return;\n }\n\n // Listen for postMessage from OAuth callback page\n function onMessage(event) {\n if (!event.data || event.data.type !== \"authorization_response\") return;\n window.removeEventListener(\"message\", onMessage);\n clearInterval(closeTimer);\n\n var resp = event.data.response;\n if (!resp || !resp.code) {\n showToast(\"OAuth failed — no authorization code received\", \"error\");\n try { popup.close(); } catch(e) {}\n return;\n }\n\n // Call bind API with the authorization code\n api(\"POST\", \"/../oauth/bind\", {\n provider: provider,\n code: resp.code,\n redirectUri: redirectUri,\n }).then(function(data) {\n if (data.ok) {\n showToast(__t(\"profile.accountConnected\"), \"success\");\n loadConnectedAccounts();\n }\n }).catch(function() {}).finally(function() {\n try { popup.close(); } catch(e) {}\n });\n }\n window.addEventListener(\"message\", onMessage);\n\n // Fallback: if popup closes without postMessage (COOP / manual close)\n var closeTimer = setInterval(function() {\n if (popup.closed) {\n clearInterval(closeTimer);\n window.removeEventListener(\"message\", onMessage);\n // Reload in case server-side exchange fallback was used\n loadConnectedAccounts();\n }\n }, 500);\n}\n\nasync function unbindOAuth(provider) {\n var ok = await confirmDialog({\n title: __t(\"profile.removeConfirmTitle\"),\n message: __t(\"profile.removeConfirmMsg\"),\n confirmLabel: __t(\"profile.remove\"),\n });\n if (!ok) return;\n var data = await api(\"POST\", \"/../oauth/unbind\", { provider: provider });\n if (data.ok) {\n showToast(__t(\"profile.accountRemoved\"), \"success\");\n loadConnectedAccounts();\n }\n}\n\nasync function addPasskey() {\n try {\n // 1. Get registration options. The server flattens the WebAuthn options to\n // the top level (challenge/user/rp alongside challengeId), matching the\n // login flow; tolerate a nested { registration } shape too.\n var opts = await api(\"POST\", \"/../passkey/connect/register\", {});\n var publicKey = opts.registration || opts;\n if (!opts.challengeId || !publicKey || !publicKey.challenge) {\n showToast(\"Failed to get registration options\", \"error\");\n return;\n }\n\n // 2. Create credential via WebAuthn\n // Decode challenge from base64url\n publicKey.challenge = base64urlToBuffer(publicKey.challenge);\n publicKey.user.id = base64urlToBuffer(publicKey.user.id);\n if (publicKey.excludeCredentials) {\n for (var i = 0; i < publicKey.excludeCredentials.length; i++) {\n publicKey.excludeCredentials[i].id = base64urlToBuffer(publicKey.excludeCredentials[i].id);\n }\n }\n\n var credential = await navigator.credentials.create({ publicKey: publicKey });\n if (!credential) return;\n\n // 3. Serialize credential for server\n var response = credential.response;\n var credentialData = {\n id: credential.id,\n rawId: bufferToBase64url(credential.rawId),\n type: credential.type,\n response: {\n attestationObject: bufferToBase64url(response.attestationObject),\n clientDataJSON: bufferToBase64url(response.clientDataJSON),\n transports: response.getTransports ? response.getTransports() : [],\n },\n };\n\n // 4. Verify with server\n var result = await api(\"POST\", \"/../passkey/connect/verify\", {\n challengeId: opts.challengeId,\n credential: credentialData,\n });\n\n if (result.ok) {\n showToast(__t(\"profile.passkeyAdded\"), \"success\");\n loadConnectedAccounts();\n loadProfile(); // refresh passkey count\n }\n } catch (e) {\n if (e.name !== \"NotAllowedError\") {\n showToast(e.message || \"Failed to add passkey\", \"error\");\n }\n }\n}\n\nasync function removePasskey(passkeyDid) {\n var ok = await confirmDialog({\n title: __t(\"profile.removeConfirmTitle\"),\n message: __t(\"profile.removePasskeyConfirmMsg\"),\n confirmLabel: __t(\"profile.remove\"),\n });\n if (!ok) return;\n var data = await api(\"POST\", \"/../passkey/disconnect\", { did: passkeyDid });\n if (data.ok) {\n showToast(__t(\"profile.passkeyRemoved\"), \"success\");\n loadConnectedAccounts();\n loadProfile(); // refresh passkey count\n }\n}\n\nasync function openRenamePasskeyDialog(passkeyDid, currentName) {\n var result = await promptDialog({\n title: __t(\"profile.renamePasskeyTitle\"),\n message: __t(\"profile.passkeyNameLabel\"),\n placeholder: __t(\"profile.passkeyNamePlaceholder\"),\n defaultValue: currentName || \"\",\n confirmLabel: __t(\"common.save\"),\n });\n if (result === null || result === undefined) return;\n var name = result.trim();\n if (!name) return;\n var data = await api(\"POST\", \"/../passkey/rename\", { did: passkeyDid, name: name });\n if (data.ok) {\n showToast(__t(\"profile.passkeyRenamed\"), \"success\");\n loadConnectedAccounts();\n }\n}\n\n// ─── DID Wallet open helpers (mirrors LoginPage.openInWallet) ─────────\nvar __WEB_WALLET_URL = \"https://web.abtwallet.io/\";\n\nfunction __isMobile() {\n return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);\n}\n\nfunction __getWalletExtension() {\n return window.ABT_DEV || window.ABT || null;\n}\n\n/**\n * Open the DID auth URL in the wallet — platform-aware:\n * Mobile → navigate to abt:// deep link\n * Extension → call extension.open({ action:'login', url, appInfo, memberAppInfo })\n * Desktop → open web wallet popup\n *\n * action must be \"login\" — the extension only recognises \"login\", not \"bind\".\n * The bind vs login distinction lives in the URL path (/bind/auth vs /login/auth).\n */\nfunction __openInWallet(url, appInfo, memberAppInfo) {\n var extension = __getWalletExtension();\n if (__isMobile()) {\n window.location.href = url.replace(/^https?:\\/\\//, \"abt://\");\n } else if (extension && typeof extension.open === \"function\") {\n extension.open({\n action: \"login\",\n locale: document.documentElement.lang || \"en\",\n url: encodeURIComponent(url),\n appInfo: appInfo || {},\n memberAppInfo: memberAppInfo || {},\n });\n } else {\n var walletUrl = __WEB_WALLET_URL + \"?action=requestAuth&url=\" + encodeURIComponent(url);\n var w = 414, h = 736;\n var left = window.screenX + window.innerWidth - w, top = window.screenY;\n window.open(walletUrl, \"did-wallet:bind\", \"left=\" + left + \",top=\" + top + \",width=\" + w + \",height=\" + h + \",resizable,scrollbars=yes,status=1,popup\");\n }\n}\n\n// ─── DID Wallet Connect (bind) ──────────────────────────────────────\nasync function connectWallet() {\n // 1. Create bind session\n var tokenData;\n try {\n tokenData = await api(\"GET\", \"/../did/bind/token?provider=wallet\");\n } catch(e) {\n showToast(\"Failed to create bind session\", \"error\");\n return;\n }\n if (!tokenData || !tokenData.token) {\n showToast(\"Failed to create bind session\", \"error\");\n return;\n }\n\n var token = tokenData.token;\n var connectUrl = tokenData.url || \"\";\n var appInfo = tokenData.appInfo || {};\n var memberAppInfo = tokenData.memberAppInfo || {};\n\n // 2. Build/reset dialog — mirrors login-page QR view structure\n var d = document.getElementById(\"wallet-bind-dialog\");\n if (!d) {\n d = document.createElement(\"dialog\");\n d.id = \"wallet-bind-dialog\";\n document.body.appendChild(d);\n }\n // Re-render dialog content each time (fresh token / fresh state)\n d.innerHTML = '<div class=\"dialog-content\" style=\"text-align:center;\">'\n + '<h3 style=\"margin-bottom:4px;\">' + __t(\"profile.connectWallet\") + '</h3>'\n + '<div id=\"wallet-bind-qr\" class=\"wallet-qr-view\">'\n + '<div class=\"wallet-qr-placeholder\"><span class=\"wallet-qr-spinner\"></span></div>'\n + '</div>'\n + '<button id=\"wallet-bind-open\" class=\"btn btn-secondary wallet-deep-link-btn\" disabled>'\n + getProviderIconSvg(\"did-connect\") + ' ' + __t(\"profile.openInWallet\")\n + '</button>'\n + '<p id=\"wallet-bind-status\" class=\"wallet-bind-status\">' + __t(\"profile.scanWithWallet\") + '</p>'\n + '</div>'\n + '<div class=\"dialog-actions\">'\n + '<button class=\"btn btn-secondary\" id=\"wallet-bind-cancel\">' + __t(\"common.cancel\") + '</button>'\n + '</div>';\n\n var statusEl = document.getElementById(\"wallet-bind-status\");\n var qrEl = document.getElementById(\"wallet-bind-qr\");\n var openBtn = document.getElementById(\"wallet-bind-open\");\n var cancelBtn = document.getElementById(\"wallet-bind-cancel\");\n\n // Render QR code (replaces placeholder spinner)\n if (connectUrl && typeof __QRBundle !== \"undefined\" && __QRBundle.renderQR) {\n __QRBundle.renderQR(qrEl, connectUrl, 212);\n }\n\n // Enable open button once URL is ready\n openBtn.disabled = false;\n openBtn.onclick = function() { __openInWallet(connectUrl, appInfo, memberAppInfo); };\n\n var pollTimer = null;\n var pollCount = 0;\n var maxPolls = 120; // 2 minutes at 1.5s interval\n\n function cleanup() {\n if (pollTimer) clearInterval(pollTimer);\n pollTimer = null;\n d.close();\n }\n\n cancelBtn.onclick = function() { cleanup(); };\n d.onclose = function() { if (pollTimer) clearInterval(pollTimer); };\n\n d.showModal();\n\n // Helper: collapse QR area and show terminal status\n function showTerminalStatus(msg, isError) {\n qrEl.style.display = \"none\";\n openBtn.style.display = \"none\";\n statusEl.className = \"wallet-bind-status\" + (isError ? \" wallet-bind-error\" : \"\");\n statusEl.textContent = msg;\n }\n\n // 3. Poll for status\n pollTimer = setInterval(async function() {\n pollCount++;\n if (pollCount > maxPolls) {\n clearInterval(pollTimer);\n pollTimer = null;\n showTerminalStatus(__t(\"profile.connectTimeout\"), true);\n return;\n }\n try {\n var status = await api(\"GET\", \"/../did/bind/status?_t_=\" + encodeURIComponent(token));\n if (status.status === \"scanned\") {\n statusEl.textContent = __t(\"profile.confirmInWallet\");\n } else if (status.status === \"succeed\") {\n clearInterval(pollTimer);\n pollTimer = null;\n // Collapse QR; show \"Binding...\" while we call bind-complete\n qrEl.style.display = \"none\";\n openBtn.style.display = \"none\";\n statusEl.textContent = __t(\"profile.binding\");\n // 4. Call bind-complete\n var result = await api(\"POST\", \"/../did/connect/bind-complete\", { token: token });\n if (result.ok) {\n cleanup();\n showToast(__t(\"profile.walletConnected\"), \"success\");\n loadConnectedAccounts();\n } else {\n showTerminalStatus(result.error || \"Bind failed\", true);\n }\n } else if (status.status === \"error\") {\n clearInterval(pollTimer);\n pollTimer = null;\n showTerminalStatus(status.error || \"Error\", true);\n }\n } catch(e) { /* ignore poll errors */ }\n }, 1500);\n}\n\n// base64url helpers for WebAuthn\nfunction base64urlToBuffer(str) {\n str = str.replace(/-/g, \"+\").replace(/_/g, \"/\");\n while (str.length % 4) str += \"=\";\n var bin = atob(str);\n var buf = new Uint8Array(bin.length);\n for (var i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);\n return buf.buffer;\n}\n\nfunction bufferToBase64url(buf) {\n var bytes = new Uint8Array(buf);\n var str = \"\";\n for (var i = 0; i < bytes.length; i++) str += String.fromCharCode(bytes[i]);\n return btoa(str).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n;\n\nvar akPage = 1;\nvar akPageSize = 20;\nvar akSearchTimer = null;\n\nfunction debounceAkSearch() {\n clearTimeout(akSearchTimer);\n akSearchTimer = setTimeout(function() { loadAccessKeys(1); }, 300);\n}\n\nasync function loadAccessKeys(page) {\n akPage = page || 1;\n var search = document.getElementById(\"ak-search\").value.trim();\n var qs = \"?page=\" + akPage + \"&pageSize=\" + akPageSize;\n if (search) qs += \"&search=\" + encodeURIComponent(search);\n document.getElementById(\"ak-table-wrap\").innerHTML = skeletonTable(7, 5);\n document.getElementById(\"ak-pagination\").innerHTML = \"\";\n var data = await apiRaw(\"GET\", \"/.well-known/service/api/access-keys\" + qs);\n if (!data || data.error) {\n document.getElementById(\"ak-table-wrap\").innerHTML = '<div class=\"empty-state\"><div class=\"empty-state-title\">' + __t(\"accessKeys.loadFailed\") + '</div></div>';\n return;\n }\n renderAccessKeys(data.keys, { total: data.total, page: data.page, pageSize: data.pageSize });\n}\n\nfunction renderAccessKeys(keys, paging) {\n var wrap = document.getElementById(\"ak-table-wrap\");\n if (!keys || keys.length === 0) {\n wrap.innerHTML = '<div class=\"empty-state\"><div class=\"empty-state-icon\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4\"/></svg></div><div class=\"empty-state-title\">' + __t(\"accessKeys.noKeys\") + '</div><div class=\"empty-state-desc\">' + __t(\"accessKeys.noKeysHint\") + '</div></div>';\n document.getElementById(\"ak-pagination\").innerHTML = \"\";\n return;\n }\n\n var html = '<table class=\"table\"><thead><tr><th>' + __t(\"accessKeys.col.keyId\") + '</th><th>' + __t(\"accessKeys.col.role\") + '</th><th class=\"col-hide-sm\">' + __t(\"accessKeys.col.remark\") + '</th><th class=\"col-hide-md\">' + __t(\"accessKeys.col.createdBy\") + '</th><th>' + __t(\"accessKeys.col.expires\") + '</th><th class=\"col-hide-md\">' + __t(\"accessKeys.col.lastUsed\") + '</th><th>' + __t(\"common.actions\") + '</th></tr></thead><tbody>';\n keys.forEach(function(k) {\n var keyIdHtml = '<did-address did=\"' + escapeHtml(k.accessKeyId) + '\" compact></did-address>';\n var roleBadge = '<span class=\"badge badge-' + k.role + '\">' + __t(\"common.\" + k.role) + '</span>';\n var remark = k.remark ? escapeHtml(k.remark.length > 40 ? k.remark.slice(0, 40) + \"...\" : k.remark) : '<span style=\"color:var(--text-secondary)\">—</span>';\n var creator = k.createdByName\n ? escapeHtml(k.createdByName)\n : '<did-address did=\"' + escapeHtml(k.createdBy) + '\" compact></did-address>';\n var expiry = k.expireAt ? (new Date(k.expireAt) < new Date() ? '<span style=\"color:var(--error)\">' + __t(\"common.expired\") + '</span>' : relativeTime(k.expireAt)) : __t(\"common.never\");\n var lastUsed = k.lastUsedAt ? relativeTime(k.lastUsedAt) : __t(\"common.never\");\n var deleteLabel = k.remark ? escapeHtml(k.remark) : truncateDid(k.accessKeyId);\n\n html += '<tr>';\n html += '<td><code style=\"font-size:12px\">' + keyIdHtml + '</code></td>';\n html += '<td>' + roleBadge + '</td>';\n html += '<td class=\"col-hide-sm\">' + remark + '</td>';\n html += '<td class=\"col-hide-md\">' + creator + '</td>';\n html += '<td>' + expiry + '</td>';\n html += '<td class=\"col-hide-md\">' + lastUsed + '</td>';\n html += '<td class=\"action-cell\"><button class=\"action-trigger\" onclick=\"event.stopPropagation();toggleActionMenu(this)\">⋯</button><div class=\"action-menu\"><button class=\"action-menu-item\" onclick=\"event.stopPropagation();openEditAkDialog(\\''+k.accessKeyId+'\\')\">' + __t(\"common.edit\") + '</button><div class=\"action-menu-sep\"></div><button class=\"action-menu-item danger\" onclick=\"event.stopPropagation();confirmDeleteAk(\\''+k.accessKeyId+'\\',\\''+escapeHtml(deleteLabel)+'\\')\">' + __t(\"common.delete\") + '</button></div></td>';\n html += '</tr>';\n });\n html += '</tbody></table>';\n wrap.innerHTML = html;\n\n renderPagination(\"ak-pagination\", paging, loadAccessKeys);\n}\n\nfunction openCreateAkDialog() {\n var callerRole = __caller.role;\n var roleOptions = '<option value=\"guest\">Guest</option>';\n if (callerRole === \"member\" || callerRole === \"admin\" || callerRole === \"owner\") roleOptions += '<option value=\"member\"' + (callerRole === \"member\" ? ' selected' : '') + '>Member</option>';\n if (callerRole === \"admin\" || callerRole === \"owner\") roleOptions += '<option value=\"admin\"' + (callerRole === \"admin\" ? ' selected' : '') + '>Admin</option>';\n if (callerRole === \"owner\") roleOptions += '<option value=\"owner\">Owner</option>';\n\n var expiryOptions = '<option value=\"\">' + __t(\"accessKeys.expirations.never\") + '</option><option value=\"7d\">' + __t(\"accessKeys.expirations.days7\") + '</option><option value=\"30d\">' + __t(\"accessKeys.expirations.days30\") + '</option><option value=\"90d\">' + __t(\"accessKeys.expirations.days90\") + '</option><option value=\"custom\">' + __t(\"accessKeys.expirations.custom\") + '</option>';\n\n var html = '<div class=\"form-group\"><label class=\"form-label\">' + __t(\"accessKeys.role\") + '</label><select class=\"select\" id=\"ak-create-role\">' + roleOptions + '</select></div>';\n html += '<div class=\"form-group\"><label class=\"form-label\">' + __t(\"accessKeys.remark\") + '</label><input type=\"text\" class=\"input\" id=\"ak-create-remark\" placeholder=\"' + __t(\"accessKeys.remarkPlaceholder\") + '\" maxlength=\"200\" /></div>';\n html += '<div class=\"form-group\"><label class=\"form-label\">' + __t(\"accessKeys.expiration\") + '</label><select class=\"select\" id=\"ak-create-expiry\" onchange=\"toggleAkCustomExpiry()\">' + expiryOptions + '</select></div>';\n html += '<div class=\"form-group hidden\" id=\"ak-custom-expiry-wrap\"><label class=\"form-label\">' + __t(\"accessKeys.customDate\") + '</label><input type=\"datetime-local\" class=\"input\" id=\"ak-create-custom-expiry\" /></div>';\n\n openDialog(__t(\"accessKeys.createTitle\"), html, __t(\"common.save\"), async function() {\n var role = document.getElementById(\"ak-create-role\").value;\n var remark = document.getElementById(\"ak-create-remark\").value;\n var expirySelect = document.getElementById(\"ak-create-expiry\").value;\n var expireAt = null;\n\n if (expirySelect === \"custom\") {\n var customDate = document.getElementById(\"ak-create-custom-expiry\").value;\n if (!customDate) { showToast(__t(\"accessKeys.selectExpDate\"), \"error\"); return; }\n expireAt = new Date(customDate).toISOString();\n } else if (expirySelect) {\n var days = parseInt(expirySelect);\n expireAt = new Date(Date.now() + days * 86400000).toISOString();\n }\n\n var result = await apiRaw(\"POST\", \"/.well-known/service/api/access-keys\", { role: role, remark: remark, expireAt: expireAt });\n if (!result || result.error) {\n showToast(result ? result.error : __t(\"accessKeys.createFailed\"), \"error\");\n return;\n }\n\n closeDialog();\n showSecretDialog(result.accessKeySecret);\n loadAccessKeys(1);\n });\n}\n\nfunction toggleAkCustomExpiry() {\n var sel = document.getElementById(\"ak-create-expiry\").value;\n var wrap = document.getElementById(\"ak-custom-expiry-wrap\");\n if (sel === \"custom\") wrap.classList.remove(\"hidden\");\n else wrap.classList.add(\"hidden\");\n}\n\nfunction showSecretDialog(secret) {\n var html = '<div style=\"margin-bottom:12px;color:var(--error);font-weight:500\">' + __t(\"accessKeys.secretWarning\") + '</div>';\n var copyIconSvg = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"/><path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"/></svg>';\n var copyBtnStyle = 'position:absolute;top:4px;right:4px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;background:var(--bg-base);border:1px solid var(--border);border-radius:4px;cursor:pointer;color:var(--text-secondary);transition:color 0.15s,border-color 0.15s';\n html += '<div style=\"position:relative\"><code id=\"ak-secret-display\" style=\"display:block;padding:12px 40px 12px 12px;background:var(--bg-base);border:1px solid var(--border);border-radius:6px;font-size:13px;word-break:break-all;user-select:all;overflow-wrap:anywhere\">' + escapeHtml(secret) + '</code>';\n html += '<button id=\"ak-copy-btn\" style=\"' + copyBtnStyle + '\" onclick=\"copyAkSecret()\" title=\"' + __t(\"common.copy\") + '\">' + copyIconSvg + '</button></div>';\n var curlCmd = 'curl -H \"Authorization: Bearer ' + secret + '\" ' + location.origin + '/.well-known/service/api/did/session';\n html += '<div style=\"margin-top:12px;font-size:13px;color:var(--text-secondary)\"><strong>' + __t(\"accessKeys.usage\") + '</strong></div>';\n html += '<div style=\"position:relative;margin-top:6px\"><code id=\"ak-curl-display\" style=\"display:block;padding:12px 40px 12px 12px;background:var(--bg-base);border:1px solid var(--border);border-radius:6px;font-size:12px;word-break:break-all;overflow-wrap:anywhere;user-select:all\">' + escapeHtml(curlCmd) + '</code>';\n html += '<button id=\"ak-curl-copy-btn\" style=\"' + copyBtnStyle + '\" onclick=\"copyAkCurl()\" title=\"' + __t(\"common.copy\") + '\">' + copyIconSvg + '</button></div>';\n\n openDialog(__t(\"accessKeys.secretTitle\"), html, __t(\"accessKeys.done\"), function() {\n document.getElementById(\"ak-secret-display\").textContent = \"\";\n closeDialog();\n });\n}\n\nfunction copyAkCurl() {\n var text = document.getElementById(\"ak-curl-display\").textContent;\n var btn = document.getElementById(\"ak-curl-copy-btn\");\n var origHtml = btn.innerHTML;\n function onCopied() {\n btn.innerHTML = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M5 13l4 4L19 7\"/></svg>';\n btn.style.color = \"var(--green)\";\n setTimeout(function() { btn.innerHTML = origHtml; btn.style.color = \"\"; }, 2000);\n }\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(text).then(onCopied).catch(function() {\n fallbackCopy(text);\n onCopied();\n });\n } else {\n fallbackCopy(text);\n onCopied();\n }\n}\n\nfunction copyAkSecret() {\n var text = document.getElementById(\"ak-secret-display\").textContent;\n var btn = document.getElementById(\"ak-copy-btn\");\n var origHtml = btn.innerHTML;\n function onCopied() {\n btn.innerHTML = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M5 13l4 4L19 7\"/></svg>';\n btn.style.color = \"var(--green)\";\n setTimeout(function() { btn.innerHTML = origHtml; btn.style.color = \"\"; }, 2000);\n }\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(text).then(onCopied).catch(function() {\n fallbackCopy(text);\n onCopied();\n });\n } else {\n fallbackCopy(text);\n onCopied();\n }\n}\n\nasync function openEditAkDialog(accessKeyId) {\n var data = await apiRaw(\"GET\", \"/.well-known/service/api/access-keys/\" + encodeURIComponent(accessKeyId));\n if (!data || data.error) { showToast(__t(\"accessKeys.loadKeyFailed\"), \"error\"); return; }\n\n var html = '<div class=\"form-group\"><label class=\"form-label\">' + __t(\"accessKeys.col.keyId\") + '</label><code style=\"font-size:12px\">' + escapeHtml(data.accessKeyId) + '</code></div>';\n html += '<div class=\"form-group\"><label class=\"form-label\">' + __t(\"accessKeys.role\") + '</label><span class=\"badge badge-' + data.role + '\">' + __t(\"common.\" + data.role) + '</span></div>';\n html += '<div class=\"form-group\"><label class=\"form-label\">' + __t(\"accessKeys.remark\") + '</label><input type=\"text\" class=\"input\" id=\"ak-edit-remark\" value=\"' + escapeHtml(data.remark || \"\") + '\" maxlength=\"200\" /></div>';\n html += '<div class=\"form-group\"><label class=\"form-label\">' + __t(\"accessKeys.expiration\") + '</label><input type=\"datetime-local\" class=\"input\" id=\"ak-edit-expiry\" value=\"' + (data.expireAt ? data.expireAt.slice(0,16) : \"\") + '\" /> <label style=\"margin-top:4px;display:block;font-size:13px\"><input type=\"checkbox\" id=\"ak-edit-no-expiry\"' + (data.expireAt ? \"\" : \" checked\") + ' onchange=\"toggleAkEditExpiry()\" /> ' + __t(\"accessKeys.neverExpires\") + '</label></div>';\n\n openDialog(__t(\"accessKeys.editTitle\"), html, __t(\"common.save\"), async function() {\n var remark = document.getElementById(\"ak-edit-remark\").value;\n var noExpiry = document.getElementById(\"ak-edit-no-expiry\").checked;\n var expireAt = noExpiry ? null : (document.getElementById(\"ak-edit-expiry\").value ? new Date(document.getElementById(\"ak-edit-expiry\").value).toISOString() : undefined);\n\n var body = { remark: remark };\n if (expireAt !== undefined) body.expireAt = expireAt;\n\n var result = await apiRaw(\"PUT\", \"/.well-known/service/api/access-keys/\" + encodeURIComponent(accessKeyId), body);\n if (!result || result.error) {\n showToast(result ? result.error : __t(\"accessKeys.updateFailed\"), \"error\");\n return;\n }\n closeDialog();\n showToast(__t(\"accessKeys.keyUpdated\"), \"success\");\n loadAccessKeys(akPage);\n });\n}\n\nfunction toggleAkEditExpiry() {\n var noExpiry = document.getElementById(\"ak-edit-no-expiry\").checked;\n document.getElementById(\"ak-edit-expiry\").disabled = noExpiry;\n}\n\nfunction confirmDeleteAk(accessKeyId, label) {\n openDialog(__t(\"accessKeys.deleteTitle\"), '<p>' + __t(\"accessKeys.deleteMsg\", { label: escapeHtml(label) }) + '</p><p style=\"color:var(--error)\">' + __t(\"accessKeys.deleteWarning\") + '</p>', __t(\"common.delete\"), async function() {\n var result = await apiRaw(\"DELETE\", \"/.well-known/service/api/access-keys/\" + encodeURIComponent(accessKeyId));\n if (result && result.error) {\n showToast(result.error, \"error\");\n return;\n }\n closeDialog();\n showToast(__t(\"accessKeys.keyDeleted\"), \"success\");\n loadAccessKeys(akPage);\n });\n}\n\n// Direct API call (bypasses team handler's /team prefix)\nasync function apiRaw(method, path, body) {\n try {\n var opts = { method: method, headers: { \"Accept\": \"application/json\" } };\n if (body !== undefined) {\n opts.headers[\"Content-Type\"] = \"application/json\";\n opts.body = JSON.stringify(body);\n }\n var res = await fetch(path, opts);\n if (res.status === 204) return {};\n return await res.json();\n } catch(e) {\n return null;\n }\n}\n\nregisterTab(\"access-keys\", function() { loadAccessKeys(1); });\n","admin-extra.7ca9c16b.js":"\nvar membersPage = 1;\nvar membersPageSize = 20;\nvar memberSearchTimeout = null;\nvar currentMembersList = [];\nvar selectedMember = null;\nvar __membersAggregated = false;\n// Sort state: empty string = default (server decides + self-first on client).\n// Three-state click cycle: (none) → desc → asc → (none).\n// Persisted in location.search as sortBy/sortOrder so it survives refresh + share.\nvar membersSortBy = \"\";\nvar membersSortOrder = \"\";\nvar MEMBERS_SORT_VALID = [\"name\", \"role\", \"status\", \"source\", \"joined\"];\n\n/** Populate module sort state from the current URL query string. Idempotent. */\nfunction readMembersSortFromURL() {\n membersSortBy = \"\";\n membersSortOrder = \"\";\n var params = new URLSearchParams(location.search);\n var by = params.get(\"sortBy\");\n var order = params.get(\"sortOrder\");\n if (by && MEMBERS_SORT_VALID.indexOf(by) >= 0) {\n membersSortBy = by;\n membersSortOrder = (order === \"asc\" || order === \"desc\") ? order : \"desc\";\n }\n}\n\n/** Write current sort state back to location.search without reloading. */\nfunction writeMembersSortToURL() {\n var params = new URLSearchParams(location.search);\n if (membersSortBy) {\n params.set(\"sortBy\", membersSortBy);\n params.set(\"sortOrder\", membersSortOrder || \"desc\");\n } else {\n params.delete(\"sortBy\");\n params.delete(\"sortOrder\");\n }\n var qs = params.toString();\n var newUrl = location.pathname + (qs ? \"?\" + qs : \"\") + location.hash;\n history.replaceState(null, \"\", newUrl);\n}\n\n/** Cycle sort state for a column key and reload. */\nfunction toggleSort(key) {\n if (membersSortBy !== key) {\n membersSortBy = key;\n membersSortOrder = \"desc\";\n } else if (membersSortOrder === \"desc\") {\n membersSortOrder = \"asc\";\n } else {\n // Third click — back to default.\n membersSortBy = \"\";\n membersSortOrder = \"\";\n }\n writeMembersSortToURL();\n loadMembers(1);\n}\n\n/** Render a <th> with click-to-sort behavior. */\nfunction sortableTh(key, label, extraClass) {\n var isActive = membersSortBy === key;\n var arrow;\n if (isActive) {\n arrow = membersSortOrder === \"asc\"\n ? '<span class=\"sort-arrow sort-arrow-active\">\\u2191</span>'\n : '<span class=\"sort-arrow sort-arrow-active\">\\u2193</span>';\n } else {\n arrow = '<span class=\"sort-arrow\">\\u2195</span>';\n }\n var cls = \"sortable\" + (isActive ? \" sort-active\" : \"\") + (extraClass ? \" \" + extraClass : \"\");\n return '<th class=\"' + cls + '\" onclick=\"toggleSort(\\'' + key + '\\')\">' + label + arrow + '</th>';\n}\n\nfunction providerLabel(sp) {\n if (!sp) return \"\\u2014\";\n var key = \"members.providers.\" + sp;\n var val = __t(key);\n return val !== key ? val : sp;\n}\n\nfunction debouncedMemberSearch() {\n clearTimeout(memberSearchTimeout);\n memberSearchTimeout = setTimeout(function() { loadMembers(1); }, 300);\n}\n\n// Load registered instances for the filter dropdown (system admin only)\nvar __instanceNames = {};\nvar __instanceFilterInitialized = false;\nasync function initInstanceFilter() {\n if (__instanceFilterInitialized) return;\n try {\n var data = await api(\"GET\", \"/../admin/instances\");\n if (data.ok && data.instances && data.instances.length > 0) {\n __instanceFilterInitialized = true;\n var sel = document.getElementById(\"members-instance-filter\");\n data.instances.forEach(function(inst) {\n __instanceNames[inst.instanceDid] = inst.appName || inst.instanceDid.substring(0, 16);\n var opt = document.createElement(\"option\");\n opt.value = inst.instanceDid;\n opt.textContent = __instanceNames[inst.instanceDid];\n sel.appendChild(opt);\n });\n sel.style.display = \"\";\n }\n } catch(e) { /* not admin or no instances */ }\n}\n\n// Also support specific instance filter via query param\n// When ?instance=z1xxx, team handler needs to use that instanceDid\n\nasync function loadMembers(page) {\n membersPage = page || 1;\n var instanceFilter = document.getElementById(\"members-instance-filter\").value;\n var qs = \"?page=\" + membersPage + \"&pageSize=\" + membersPageSize;\n // System view (\"\") → use defaultInstanceDid so we query membership, not global users\n if (instanceFilter === \"\" && __pageData.instanceDid) {\n qs += \"&instance=\" + encodeURIComponent(__pageData.instanceDid);\n } else if (instanceFilter) {\n qs += \"&instance=\" + encodeURIComponent(instanceFilter);\n }\n var search = document.getElementById(\"members-search\").value.trim();\n if (search) qs += \"&search=\" + encodeURIComponent(search);\n var role = document.getElementById(\"members-role-filter\").value;\n if (role) qs += \"&role=\" + encodeURIComponent(role);\n var status = document.getElementById(\"members-status-filter\").value;\n if (status !== \"\") qs += \"&approved=\" + status;\n var source = document.getElementById(\"members-source-filter\").value;\n if (source) qs += \"&sourceProvider=\" + encodeURIComponent(source);\n if (membersSortBy) {\n qs += \"&sortBy=\" + encodeURIComponent(membersSortBy);\n qs += \"&sortOrder=\" + encodeURIComponent(membersSortOrder || \"desc\");\n }\n\n document.getElementById(\"members-table-wrap\").innerHTML = skeletonTable(8, 6);\n document.getElementById(\"members-pagination\").innerHTML = \"\";\n var data = await api(\"GET\", \"/members\" + qs);\n if (!data.ok) {\n document.getElementById(\"members-table-wrap\").innerHTML = '<div class=\"empty-state\"><div class=\"empty-state-icon\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 8v4m0 4h.01\"/></svg></div><div class=\"empty-state-title\">' + __t(\"members.loadFailed\") + '</div></div>';\n return;\n }\n __membersAggregated = !!data.aggregated;\n // Only pin the caller to the top when using the default (server) order.\n // Under explicit sort, honor the user's intent without re-shuffling.\n if (membersSortBy) {\n currentMembersList = data.users || [];\n } else {\n currentMembersList = (data.users || []).slice().sort(function(a, b) {\n if (a.did === __caller.did) return -1;\n if (b.did === __caller.did) return 1;\n return (a.createdAt || 0) - (b.createdAt || 0);\n });\n }\n renderMembersTable(currentMembersList, data.paging);\n updateTransferSection(data.users);\n}\n\nfunction renderMembersTable(users, paging) {\n var wrap = document.getElementById(\"members-table-wrap\");\n if (!users || users.length === 0) {\n wrap.innerHTML = '<div class=\"empty-state\"><div class=\"empty-state-icon\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/></svg></div><div class=\"empty-state-title\">' + __t(\"members.noMembers\") + '</div><div class=\"empty-state-desc\">' + __t(\"members.noMembersHint\") + '</div></div>';\n document.getElementById(\"members-pagination\").innerHTML = \"\";\n return;\n }\n\n var instanceSel = document.getElementById(\"members-instance-filter\");\n var instanceVisible = instanceSel && instanceSel.style.display !== 'none';\n var showInstance = instanceVisible && instanceSel.value === \"all\";\n var html = '<table class=\"table\"><thead><tr>';\n html += sortableTh(\"name\", __t(\"members.col.member\"));\n html += sortableTh(\"role\", __t(\"members.col.role\"));\n if (showInstance) html += '<th>' + __t(\"members.col.instance\") + '</th>';\n html += sortableTh(\"source\", __t(\"members.col.source\"), \"col-hide-sm\");\n html += sortableTh(\"status\", __t(\"members.col.status\"), \"col-hide-sm\");\n html += sortableTh(\"joined\", __t(\"members.col.joined\"), \"col-hide-md\");\n // Registered/Last Login From columns removed — domain info not meaningful in Worker context\n html += '<th></th></tr></thead><tbody>';\n users.forEach(function(u) {\n var userDid = u.did || u.user_did || '';\n var av = renderAvatar(u.avatar, u.fullName, \"sm\");\n var name = u.fullName ? escapeHtml(u.fullName) : '<span class=\"text-muted\">(unnamed)</span>';\n var did = '<did-address did=\"' + escapeHtml(userDid) + '\" compact></did-address>';\n var roleBadge = '<span class=\"badge badge-' + (u.role || \"guest\") + '\">' + __t(\"common.\" + (u.role || \"guest\")) + '</span>';\n var statusBadge = u.approved !== undefined ? (u.approved ? '<span class=\"badge badge-active\">' + __t(\"common.active\") + '</span>' : '<span class=\"badge badge-blocked\">' + __t(\"common.blocked\") + '</span>') : '<span class=\"text-muted\">—</span>';\n var joined = (u.createdAt || u.joined_at) ? '<span class=\"cell-date\">' + escapeHtml(relativeTime(u.createdAt || u.joined_at)) + '</span>' : '<span class=\"text-muted\">—</span>';\n html += '<tr style=\"cursor:pointer\" onclick=\"openMemberDetail(\\'' + escapeHtml(userDid) + '\\')\">';\n html += '<td><div class=\"member-identity\">' + av + '<div><div class=\"member-name\">' + name + '</div><div class=\"member-email\">' + did + '</div></div></div></td>';\n html += '<td>' + roleBadge + '</td>';\n if (showInstance) {\n if (__membersAggregated && u.instance_count > 1) {\n html += '<td><a href=\"javascript:void(0)\" onclick=\"event.stopPropagation();openMemberDetail(\\'' + escapeHtml(userDid) + '\\')\" style=\"color:var(--blue);font-size:14px;text-decoration:none\">' + u.instance_count + ' ' + __t(\"members.instances\") + '</a></td>';\n } else {\n var instDid = (__membersAggregated && u.instances && u.instances.length === 1) ? u.instances[0].instance_did : (u.instance_did || '');\n var instName = __instanceNames[instDid] || '';\n if (!instName) instName = instDid ? __t(\"members.currentInstance\") : '—';\n html += '<td><div>' + escapeHtml(instName);\n if (instDid) html += '<div style=\"font-size:11px;color:var(--text-secondary);margin-top:2px\" onclick=\"event.stopPropagation()\"><did-address did=\"' + escapeHtml(instDid) + '\" compact></did-address></div>';\n html += '</div></td>';\n }\n }\n var sourceBadge = '<span class=\"text-muted\">' + escapeHtml(providerLabel(u.sourceProvider)) + '</span>';\n html += '<td class=\"col-hide-sm\">' + sourceBadge + '</td>';\n html += '<td class=\"col-hide-sm\">' + statusBadge + '</td>';\n html += '<td class=\"col-hide-md\">' + joined + '</td>';\n // Actions: only for current instance members, not cross-instance\n var canOperate = !instanceVisible || (instanceVisible && instanceSel.value === '');\n // In aggregated mode, only allow actions on users whose instance matches current system\n if (__membersAggregated) canOperate = false;\n html += '<td class=\"action-cell\">' + (canOperate ? renderMemberActions(u) : '') + '</td>';\n html += '</tr>';\n });\n html += '</tbody></table>';\n wrap.innerHTML = html;\n\n renderPagination(\"members-pagination\", paging, loadMembers);\n}\n\nfunction renderMemberActions(u) {\n var isOwner = __caller.role === \"owner\";\n var isAdmin = __caller.role === \"admin\";\n var isSelf = u.did === __caller.did;\n var ROLE_LEVEL = { owner: 3, admin: 2, member: 1, guest: 0 };\n var callerLevel = ROLE_LEVEL[__caller.role] || 0;\n var targetLevel = ROLE_LEVEL[u.role] || 0;\n // Can only act on members with lower privilege\n var canManage = !isSelf && targetLevel < callerLevel;\n var items = [];\n\n // Change role: can manage lower-level members\n if (canManage) {\n items.push('<button class=\"action-menu-item\" onclick=\"event.stopPropagation();openChangeRoleForDid(\\'' + escapeHtml(u.did) + '\\')\">' + __t(\"members.changeRole\") + '</button>');\n }\n if (canManage && u.approved) {\n items.push('<button class=\"action-menu-item\" onclick=\"event.stopPropagation();blockMember(\\'' + escapeHtml(u.did) + '\\',\\'' + escapeHtml(u.fullName || u.did) + '\\')\">' + __t(\"members.block\") + '</button>');\n }\n if (canManage && !u.approved) {\n items.push('<button class=\"action-menu-item\" onclick=\"event.stopPropagation();unblockMember(\\'' + escapeHtml(u.did) + '\\',\\'' + escapeHtml(u.fullName || u.did) + '\\')\">' + __t(\"members.unblock\") + '</button>');\n }\n if (items.length > 0 && canManage) {\n items.push('<div class=\"action-menu-sep\"></div>');\n }\n if (canManage) {\n items.push('<button class=\"action-menu-item danger\" onclick=\"event.stopPropagation();removeMember(\\'' + escapeHtml(u.did) + '\\',\\'' + escapeHtml(u.fullName || u.did) + '\\')\">' + __t(\"members.remove\") + '</button>');\n }\n\n if (items.length === 0) return \"\";\n return '<button class=\"action-trigger\" onclick=\"event.stopPropagation();toggleActionMenu(this)\">⋯</button><div class=\"action-menu\">' + items.join(\"\") + '</div>';\n}\n\nfunction toggleActionMenu(trigger) {\n var menu = trigger.nextElementSibling;\n var wasOpen = menu.classList.contains(\"open\");\n // Close all menus first\n document.querySelectorAll(\".action-menu.open\").forEach(function(m) { m.classList.remove(\"open\"); });\n if (!wasOpen) menu.classList.toggle(\"open\");\n}\n\nasync function openMemberDetail(did) {\n var u = currentMembersList.find(function(m) { return m.did === did; });\n if (!u) return;\n selectedMember = u;\n\n // Fetch full member info (with connectedAccounts) from API.\n // Falls back to in-memory list data if the request fails.\n var detail = u;\n try {\n var resp = await api(\"GET\", \"/members/\" + encodeURIComponent(did));\n if (resp && resp.ok && resp.user) {\n // Merge: in-memory list has fields the API may omit (instances, passkeyCount in some paths).\n detail = Object.assign({}, u, resp.user);\n selectedMember = detail;\n }\n } catch (e) {}\n\n var body = document.getElementById(\"member-detail-body\");\n var html = '<div class=\"profile-header\">' + renderAvatar(detail.avatar, detail.fullName) + '<div>';\n html += '<div style=\"font-weight:500;font-size:16px;color:var(--text-white)\">' + escapeHtml(detail.fullName || \"(unnamed)\") + '</div>';\n html += '<did-address did=\"' + escapeHtml(detail.did) + '\" compact></did-address>';\n html += '</div></div>';\n\n html += '<div class=\"settings-card\" style=\"margin-top:16px\">';\n html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailRole\") + '</span><span class=\"settings-value\"><span class=\"badge badge-' + (detail.role || \"guest\") + '\">' + __t(\"common.\" + (detail.role || \"guest\")) + '</span></span></div>';\n html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailStatus\") + '</span><span class=\"settings-value\">' + (detail.approved ? '<span class=\"badge badge-active\">' + __t(\"common.active\") + '</span>' : '<span class=\"badge badge-blocked\">' + __t(\"common.blocked\") + '</span>') + '</span></div>';\n html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailSource\") + '</span><span class=\"settings-value\">' + escapeHtml(providerLabel(detail.sourceProvider)) + '</span></div>';\n if (detail.email) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailEmail\") + '</span><span class=\"settings-value\">' + escapeHtml(detail.email) + '</span></div>';\n if (detail.inviterName) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailInvitedBy\") + '</span><span class=\"settings-value\">' + escapeHtml(detail.inviterName) + '</span></div>';\n if (detail.createdAt) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailJoined\") + '</span><span class=\"settings-value\">' + escapeHtml(absoluteTime(detail.createdAt)) + '</span></div>';\n if (detail.lastLoginAt) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailLastLogin\") + '</span><span class=\"settings-value\">' + escapeHtml(absoluteTime(detail.lastLoginAt)) + '</span></div>';\n if (detail.sourceDomain) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailRegisteredFrom\") + '</span><span class=\"settings-value\">' + escapeHtml(detail.sourceDomain) + '</span></div>';\n if (detail.lastLoginDomain) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailLastLoginFrom\") + '</span><span class=\"settings-value\">' + escapeHtml(detail.lastLoginDomain) + '</span></div>';\n if (detail.passkeyCount !== undefined) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailPasskeys\") + '</span><span class=\"settings-value\">' + detail.passkeyCount + '</span></div>';\n html += '</div>';\n\n // Connected Accounts (read-only)\n if (detail.connectedAccounts && detail.connectedAccounts.length > 0) {\n html += '<div class=\"settings-card\" style=\"margin-top:16px\">';\n html += '<div class=\"settings-card-header\"><span>' + __t(\"members.detailConnectedAccounts\") + ' (' + detail.connectedAccounts.length + ')</span></div>';\n html += '<div style=\"max-height:240px;overflow-y:auto\">';\n detail.connectedAccounts.forEach(function(a) {\n var label = providerLabel(a.provider);\n var info = '';\n if (a.userInfo && a.userInfo.name) info = a.userInfo.name;\n else if (a.userInfo && a.userInfo.email) info = a.userInfo.email;\n else if (a.id) info = a.id;\n html += '<div class=\"settings-row\" style=\"gap:12px;align-items:flex-start\">';\n html += '<div style=\"flex:1;min-width:0\">';\n html += '<div style=\"font-size:14px;font-weight:500;color:var(--text)\">' + escapeHtml(label);\n if (a.isMain) html += ' <span class=\"badge badge-active\" style=\"font-size:10px;padding:1px 6px;\">' + __t(\"profile.mainAccount\") + '</span>';\n html += '</div>';\n if (info) html += '<div style=\"font-size:12px;color:var(--text-secondary);margin-top:2px\">' + escapeHtml(info) + '</div>';\n if (a.did) html += '<div style=\"font-size:11px;color:var(--text-secondary);margin-top:2px\"><did-address did=\"' + escapeHtml(a.did) + '\" compact></did-address></div>';\n html += '</div>';\n if (a.lastLoginAt) html += '<div style=\"font-size:12px;color:var(--text-secondary);flex-shrink:0\">' + escapeHtml(relativeTime(a.lastLoginAt)) + '</div>';\n html += '</div>';\n });\n html += '</div></div>';\n }\n\n // Instance memberships (aggregated mode)\n if (detail.instances && detail.instances.length > 0) {\n html += '<div class=\"settings-card\" style=\"margin-top:16px\">';\n html += '<div class=\"settings-card-header\"><span>' + __t(\"members.instanceMemberships\") + ' (' + detail.instances.length + ')</span></div>';\n html += '<div style=\"max-height:240px;overflow-y:auto\">';\n detail.instances.forEach(function(inst) {\n var instName = __instanceNames[inst.instance_did] || '';\n var instDid = inst.instance_did || '';\n html += '<div class=\"settings-row\" style=\"gap:12px\">';\n html += '<div style=\"flex:1;min-width:0\"><div style=\"font-size:14px;font-weight:500;color:var(--text)\">' + escapeHtml(instName || __t(\"members.currentInstance\")) + '</div>';\n if (instDid) html += '<div style=\"font-size:11px;color:var(--text-secondary);margin-top:2px\"><did-address did=\"' + escapeHtml(instDid) + '\" compact></did-address></div>';\n html += '</div>';\n html += '<div style=\"display:flex;align-items:center;gap:8px;flex-shrink:0\"><span class=\"badge badge-' + inst.role + '\">' + __t(\"common.\" + inst.role) + '</span>';\n if (inst.joined_at) html += '<span style=\"font-size:12px;color:var(--text-secondary)\">' + escapeHtml(relativeTime(inst.joined_at)) + '</span>';\n html += '</div></div>';\n });\n html += '</div></div>';\n }\n\n body.innerHTML = html;\n\n // Change role: mirror list's canOperate + renderMemberActions logic exactly.\n // Use post-merge detail so role/did reflect the latest API response.\n var changeRoleBtn = document.getElementById(\"member-detail-change-role\");\n var instanceSel = document.getElementById(\"members-instance-filter\");\n var instanceVisible = instanceSel && instanceSel.style.display !== 'none';\n var canOperate = !instanceVisible || (instanceVisible && instanceSel.value === '');\n if (__membersAggregated) canOperate = false;\n var isSelf = detail.did === __caller.did;\n var ROLE_LEVEL = { owner: 3, admin: 2, member: 1, guest: 0 };\n var callerLevel = ROLE_LEVEL[__caller.role] || 0;\n var targetLevel = ROLE_LEVEL[detail.role] || 0;\n var canChangeRole = canOperate && !isSelf && targetLevel < callerLevel;\n if (canChangeRole) {\n changeRoleBtn.classList.remove(\"hidden\");\n } else {\n changeRoleBtn.classList.add(\"hidden\");\n }\n\n showDialog(\"member-detail-dialog\");\n}\n\nfunction openChangeRoleForDid(did) {\n var u = currentMembersList.find(function(m) { return m.did === did; });\n if (!u) return;\n selectedMember = u;\n openChangeRoleDialog();\n}\n\nfunction openChangeRoleDialog() {\n if (!selectedMember) return;\n document.getElementById(\"change-role-title\").textContent = __t(\"members.changeRole\");\n document.getElementById(\"change-role-subtitle\").textContent = __t(\"members.changeRoleFor\", { name: selectedMember.fullName || truncateDid(selectedMember.did) });\n // Show/hide role options based on caller's level (only owner can assign admin)\n var radios = document.querySelectorAll('input[name=\"new-role\"]');\n radios.forEach(function(r) {\n r.checked = r.value === selectedMember.role;\n var label = r.closest('label');\n if (label) {\n if (r.value === 'admin' && __caller.role !== 'owner') label.style.display = 'none';\n else label.style.display = '';\n }\n });\n closeDialog(\"member-detail-dialog\");\n showDialog(\"change-role-dialog\");\n}\n\nasync function saveRoleChange() {\n if (!selectedMember) return;\n var selected = document.querySelector('input[name=\"new-role\"]:checked');\n if (!selected) return;\n var newRole = selected.value;\n var data = await api(\"PUT\", \"/members/\" + encodeURIComponent(selectedMember.did) + \"/role\", { role: newRole });\n if (data.ok) {\n showToast(__t(\"members.roleUpdated\"), \"success\");\n closeDialog(\"change-role-dialog\");\n loadMembers(membersPage);\n }\n}\n\nasync function blockMember(did, name) {\n var ok = await confirmDialog({ title: __t(\"members.blockTitle\"), message: __t(\"members.blockMsg\", { name: name }), confirmLabel: __t(\"members.blockBtn\"), danger: true });\n if (!ok) return;\n var data = await api(\"PUT\", \"/members/\" + encodeURIComponent(did) + \"/approval\", { approved: false });\n if (data.ok) { showToast(__t(\"members.blockSuccess\"), \"success\"); loadMembers(membersPage); }\n}\n\nasync function unblockMember(did, name) {\n var ok = await confirmDialog({ title: __t(\"members.unblockTitle\"), message: __t(\"members.unblockMsg\", { name: name }), confirmLabel: __t(\"members.unblock\") });\n if (!ok) return;\n var data = await api(\"PUT\", \"/members/\" + encodeURIComponent(did) + \"/approval\", { approved: true });\n if (data.ok) { showToast(__t(\"members.unblockSuccess\"), \"success\"); loadMembers(membersPage); }\n}\n\nasync function removeMember(did, name) {\n var ok = await confirmDialog({ title: __t(\"members.removeTitle\"), message: __t(\"members.removeMsg\", { name: name }), confirmLabel: __t(\"members.remove\"), danger: true });\n if (!ok) return;\n var data = await api(\"DELETE\", \"/members/\" + encodeURIComponent(did));\n if (data.ok) { showToast(__t(\"members.removeSuccess\"), \"success\"); loadMembers(membersPage); }\n}\n\nfunction updateTransferSection(users) {\n var section = document.getElementById(\"ownership-transfer-section\");\n if (__caller.role !== \"owner\") {\n section.classList.add(\"hidden\");\n return;\n }\n section.classList.remove(\"hidden\");\n var select = document.getElementById(\"transfer-target\");\n var html = '<option value=\"\">' + __t(\"members.transferSelect\") + '</option>';\n users.forEach(function(u) {\n if (u.did !== __caller.did && u.approved && u.role !== \"owner\") {\n html += '<option value=\"' + escapeHtml(u.did) + '\">' + escapeHtml(u.fullName || truncateDid(u.did)) + ' (' + u.role + ')</option>';\n }\n });\n select.innerHTML = html;\n}\n\nasync function handleTransferOwnership() {\n var target = document.getElementById(\"transfer-target\").value;\n if (!target) { showToast(__t(\"members.selectMemberFirst\"), \"error\"); return; }\n var targetUser = currentMembersList.find(function(u) { return u.did === target; });\n var name = targetUser ? (targetUser.fullName || truncateDid(target)) : truncateDid(target);\n var ok = await confirmDialog({\n title: __t(\"members.transferTitle\"),\n message: __t(\"members.transferMsg\", { name: name }),\n confirmLabel: __t(\"members.transferConfirm\"),\n danger: true,\n });\n if (!ok) return;\n var data = await api(\"POST\", \"/transfer-ownership\", { targetDid: target });\n if (data.ok) {\n showToast(__t(\"members.transferSuccess\"), \"success\");\n setTimeout(function() { location.reload(); }, 1000);\n }\n}\n\n// Close action menus on outside click\ndocument.addEventListener(\"click\", function() {\n document.querySelectorAll(\".action-menu.open\").forEach(function(m) { m.classList.remove(\"open\"); });\n});\n\nregisterTab(\"members\", function() {\n initInstanceFilter().then(function() {\n // Auto-select instance from URL query (e.g., ?instance=z1xxx)\n var urlInstance = new URLSearchParams(window.location.search).get(\"instance\");\n if (urlInstance) {\n var sel = document.getElementById(\"members-instance-filter\");\n if (sel) sel.value = urlInstance;\n }\n // Restore sort state from URL (sortBy / sortOrder) on tab enter.\n readMembersSortFromURL();\n loadMembers(1);\n });\n});\n\n;\n\nvar invitationsPage = 1;\nvar invitationsPageSize = 20;\n\n\nfunction updateRemarkCounter() {\n document.getElementById(\"remark-counter\").textContent = document.getElementById(\"invite-remark\").value.length;\n}\n\nasync function loadInvitations(page) {\n invitationsPage = page || 1;\n var qs = \"?page=\" + invitationsPage + \"&pageSize=\" + invitationsPageSize;\n document.getElementById(\"invitations-table-wrap\").innerHTML = skeletonTable(6, 4);\n document.getElementById(\"invitations-pagination\").innerHTML = \"\";\n var data = await api(\"GET\", \"/invitations\" + qs);\n if (!data.ok) {\n document.getElementById(\"invitations-table-wrap\").innerHTML = '<div class=\"empty-state\"><div class=\"empty-state-icon\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 8v4m0 4h.01\"/></svg></div><div class=\"empty-state-title\">' + __t(\"invitations.loadFailed\") + '</div></div>';\n return;\n }\n renderInvitationsTable(data.invitations, data.paging);\n}\n\nfunction renderInvitationsTable(invitations, paging) {\n var wrap = document.getElementById(\"invitations-table-wrap\");\n if (!invitations || invitations.length === 0) {\n wrap.innerHTML = '<div class=\"empty-state\"><div class=\"empty-state-icon\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M22 12h-4l-3 9L9 3l-3 9H2\"/></svg></div><div class=\"empty-state-title\">' + __t(\"invitations.noInvitations\") + '</div><div class=\"empty-state-desc\">' + __t(\"invitations.noInvitationsHint\") + '</div></div>';\n document.getElementById(\"invitations-pagination\").innerHTML = \"\";\n return;\n }\n\n var html = '<table class=\"table\"><thead><tr><th>' + __t(\"invitations.col.role\") + '</th><th class=\"col-hide-sm\">' + __t(\"invitations.col.remark\") + '</th><th>' + __t(\"invitations.col.uses\") + '</th><th>' + __t(\"invitations.col.expires\") + '</th><th class=\"col-hide-md\">' + __t(\"invitations.col.createdBy\") + '</th><th></th></tr></thead><tbody>';\n invitations.forEach(function(inv) {\n var roleBadge = '<span class=\"badge badge-' + inv.role + '\">' + __t(\"common.\" + inv.role) + '</span>';\n var remark = inv.remark ? escapeHtml(inv.remark.length > 30 ? inv.remark.slice(0, 30) + \"...\" : inv.remark) : '<span class=\"text-muted\">—</span>';\n var uses = inv.useCount + \" / \" + inv.maxUses;\n if (inv.useCount >= inv.maxUses) uses += ' <span class=\"badge badge-blocked\">' + __t(\"invitations.closed\") + '</span>';\n var expired = new Date(inv.expireAt) < new Date();\n var expireText = expired ? '<span class=\"badge badge-expired\">expired</span>' : '<span class=\"cell-date\">' + escapeHtml(relativeTime(inv.expireAt)) + '</span>';\n var isActive = !expired && inv.status !== \"closed\" && inv.useCount < inv.maxUses;\n var inviter = inv.inviterName ? escapeHtml(inv.inviterName) : '<span class=\"text-muted\">—</span>';\n\n var actions = '';\n if (isActive) {\n actions += '<button class=\"action-menu-item\" onclick=\"event.stopPropagation();copyToClipboard(\\'' + escapeHtml(inv.link) + '\\')\">' + __t(\"invitations.copyLink\") + '</button>';\n }\n var canDelete = __caller.role === \"owner\" || inv.inviterDid === __caller.did;\n if (canDelete) {\n if (actions) actions += '<div class=\"action-menu-sep\"></div>';\n actions += '<button class=\"action-menu-item danger\" onclick=\"event.stopPropagation();deleteInvitation(\\'' + escapeHtml(inv.id) + '\\')\">' + __t(\"common.delete\") + '</button>';\n }\n var actionMenu = actions ? '<td class=\"action-cell\"><button class=\"action-trigger\" onclick=\"event.stopPropagation();toggleActionMenu(this)\">⋯</button><div class=\"action-menu\">' + actions + '</div></td>' : '<td></td>';\n\n html += '<tr><td>' + roleBadge + '</td><td class=\"col-hide-sm\">' + remark + '</td><td>' + uses + '</td><td>' + expireText + '</td><td class=\"col-hide-md\">' + inviter + '</td>' + actionMenu + '</tr>';\n });\n html += '</tbody></table>';\n wrap.innerHTML = html;\n\n renderPagination(\"invitations-pagination\", paging, loadInvitations);\n}\n\nasync function createInvitation() {\n var btn = document.getElementById(\"create-invite-btn\");\n btn.disabled = true;\n\n var role = document.getElementById(\"invite-role\").value;\n var remark = document.getElementById(\"invite-remark\").value.trim();\n var expireHours = parseInt(document.getElementById(\"invite-expire\").value, 10);\n var maxUses = parseInt(document.getElementById(\"invite-max-uses\").value, 10) || 1;\n maxUses = Math.min(100, Math.max(1, maxUses));\n\n var data = await api(\"POST\", \"/invitations\", { role: role, remark: remark, expireHours: expireHours, maxUses: maxUses });\n btn.disabled = false;\n\n if (data.ok && data.invitation) {\n closeDialog(\"create-invitation-dialog\");\n copyToClipboard(data.invitation.link);\n showToast(__t(\"invitations.created\"), \"success\");\n // Reset form\n document.getElementById(\"invite-remark\").value = \"\";\n document.getElementById(\"invite-max-uses\").value = \"1\";\n updateRemarkCounter();\n loadInvitations(invitationsPage);\n }\n}\n\nasync function deleteInvitation(id) {\n var ok = await confirmDialog({ title: __t(\"invitations.deleteTitle\"), message: __t(\"invitations.deleteMsg\"), confirmLabel: __t(\"common.delete\"), danger: true });\n if (!ok) return;\n var data = await api(\"DELETE\", \"/invitations/\" + encodeURIComponent(id));\n if (data.ok) { showToast(__t(\"invitations.deleted\"), \"success\"); loadInvitations(invitationsPage); }\n}\n\n// Initialize role options based on caller role\nfunction initInviteRoleOptions() {\n var select = document.getElementById(\"invite-role\");\n if (__caller.role === \"owner\") {\n select.innerHTML = '<option value=\"guest\">Guest</option><option value=\"member\">Member</option><option value=\"admin\">Admin</option>';\n }\n}\n\nregisterTab(\"invitations\", function() {\n initInviteRoleOptions();\n loadInvitations(1);\n});\n\n;\n\nvar auditPage = 1;\nvar auditPageSize = 50;\n\nvar AUDIT_ACTION_I18N = {\n \"login\": \"audit.actions.login\",\n \"user.register\": \"audit.actions.register\",\n \"user.login\": \"audit.actions.login\",\n \"user.accept_invitation\": \"audit.actions.acceptInvitation\",\n \"user.remove\": \"audit.actions.removeMember\",\n \"user.block\": \"audit.actions.blockMember\",\n \"user.unblock\": \"audit.actions.unblockMember\",\n \"user.role_change\": \"audit.actions.roleChange\",\n \"user.transfer_ownership\": \"audit.actions.transferOwnership\",\n \"user.profile_update\": \"audit.actions.profileUpdate\",\n \"user.avatar_update\": \"audit.actions.avatarUpdate\",\n \"user.avatar_delete\": \"audit.actions.avatarDelete\",\n \"user.email_change\": \"audit.actions.emailChange\",\n \"user.register_blocked\": \"audit.actions.registerBlocked\",\n \"invitation.create\": \"audit.actions.createInvitation\",\n \"invitation.delete\": \"audit.actions.deleteInvitation\",\n \"oauth.bind\": \"audit.actions.oauthBind\",\n \"oauth.unbind\": \"audit.actions.oauthUnbind\",\n \"accessKey.create\": \"audit.actions.accessKeyCreate\",\n \"accessKey.update\": \"audit.actions.accessKeyUpdate\",\n \"accessKey.delete\": \"audit.actions.accessKeyDelete\",\n \"membership.create\": \"audit.actions.membershipCreate\",\n \"membership.update_role\": \"audit.actions.membershipUpdateRole\",\n \"membership.remove\": \"audit.actions.membershipRemove\",\n \"passkey.connect\": \"audit.actions.passkeyConnect\",\n \"passkey.disconnect\": \"audit.actions.passkeyDisconnect\",\n \"access_policy.create\": \"audit.actions.accessPolicyCreate\",\n \"access_policy.update\": \"audit.actions.accessPolicyUpdate\",\n \"access_policy.delete\": \"audit.actions.accessPolicyDelete\",\n \"security_rule.create\": \"audit.actions.securityRuleCreate\",\n \"security_rule.update\": \"audit.actions.securityRuleUpdate\",\n \"security_rule.delete\": \"audit.actions.securityRuleDelete\",\n \"settings.oauth_update\": \"audit.actions.settingsOauthUpdate\",\n \"settings.oauth_delete\": \"audit.actions.settingsOauthDelete\",\n \"settings.builtin_providers_update\": \"audit.actions.settingsBuiltinProvidersUpdate\",\n \"settings.session_update\": \"audit.actions.settingsSessionUpdate\",\n \"settings.email_update\": \"audit.actions.settingsEmailUpdate\",\n};\n\nfunction translateAction(action) {\n var key = AUDIT_ACTION_I18N[action];\n return key ? __t(key) : action;\n}\n\nvar _ICONS = {\n login: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4\"/><polyline points=\"10 17 15 12 10 7\"/><line x1=\"15\" y1=\"12\" x2=\"3\" y2=\"12\"/></svg>',\n register: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/><line x1=\"19\" y1=\"8\" x2=\"19\" y2=\"14\"/><line x1=\"22\" y1=\"11\" x2=\"16\" y2=\"11\"/></svg>',\n block: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"4.93\" y1=\"4.93\" x2=\"19.07\" y2=\"19.07\"/></svg>',\n unblock: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\" ry=\"2\"/><path d=\"M7 11V7a5 5 0 0 1 9.9-1\"/></svg>',\n remove: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/><line x1=\"22\" y1=\"11\" x2=\"16\" y2=\"11\"/></svg>',\n trash: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\"/><path d=\"M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2\"/></svg>',\n edit: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"/><path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"/></svg>',\n create: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"16\"/><line x1=\"8\" y1=\"12\" x2=\"16\" y2=\"12\"/></svg>',\n key: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4\"/></svg>',\n link: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\"/><path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\"/></svg>',\n unlink: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\"/><path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\"/><line x1=\"2\" y1=\"2\" x2=\"22\" y2=\"22\"/></svg>',\n mail: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z\"/><polyline points=\"22,6 12,13 2,6\"/></svg>',\n settings: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"3\"/><path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z\"/></svg>',\n shield: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z\"/></svg>',\n lock: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\" ry=\"2\"/><path d=\"M7 11V7a5 5 0 0 1 10 0v4\"/></svg>',\n image: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/><circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\"/><polyline points=\"21 15 16 10 5 21\"/></svg>',\n at: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-3.92 7.94\"/></svg>',\n user: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2\"/><circle cx=\"12\" cy=\"7\" r=\"4\"/></svg>',\n crown: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M2 4l4.5 9L12 4l5.5 9L22 4l-3 13H5L2 4z\"/><line x1=\"5\" y1=\"17\" x2=\"19\" y2=\"17\"/></svg>',\n role: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M7 16V4m0 0L3 8m4-4 4 4\"/><path d=\"M17 8v12m0 0 4-4m-4 4-4-4\"/></svg>',\n users: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/><path d=\"M23 21v-2a4 4 0 0 0-3-3.87\"/><path d=\"M16 3.13a4 4 0 0 1 0 7.75\"/></svg>',\n};\n\nfunction actionIcon(action) {\n if (!action) return \"\";\n switch (action) {\n case \"login\":\n case \"user.login\": return _ICONS.login;\n case \"user.register\": return _ICONS.register;\n case \"user.register_blocked\":\n case \"user.block\": return _ICONS.block;\n case \"user.unblock\": return _ICONS.unblock;\n case \"user.remove\":\n case \"membership.remove\": return _ICONS.remove;\n case \"user.role_change\":\n case \"membership.update_role\": return _ICONS.role;\n case \"user.transfer_ownership\": return _ICONS.crown;\n case \"user.profile_update\": return _ICONS.user;\n case \"user.avatar_update\":\n case \"user.avatar_delete\": return _ICONS.image;\n case \"user.email_change\": return _ICONS.at;\n case \"user.accept_invitation\":\n case \"invitation.create\":\n case \"invitation.delete\": return _ICONS.mail;\n case \"membership.create\": return _ICONS.users;\n case \"oauth.bind\": return _ICONS.link;\n case \"oauth.unbind\": return _ICONS.unlink;\n case \"passkey.connect\":\n case \"passkey.disconnect\":\n case \"accessKey.create\":\n case \"accessKey.update\":\n case \"accessKey.delete\": return _ICONS.key;\n case \"access_policy.create\":\n case \"access_policy.update\":\n case \"access_policy.delete\": return _ICONS.shield;\n case \"security_rule.create\":\n case \"security_rule.update\":\n case \"security_rule.delete\": return _ICONS.lock;\n case \"settings.oauth_update\":\n case \"settings.oauth_delete\":\n case \"settings.builtin_providers_update\":\n case \"settings.session_update\":\n case \"settings.email_update\": return _ICONS.settings;\n default:\n if (action.indexOf(\"delete\") !== -1) return _ICONS.trash;\n if (action.indexOf(\"create\") !== -1) return _ICONS.create;\n if (action.indexOf(\"update\") !== -1) return _ICONS.edit;\n return \"\";\n }\n}\n\nfunction actionBadgeClass(action) {\n if (!action) return \"badge-neutral\";\n var a = action.toLowerCase();\n // Destructive: delete / remove / block / unbind / disconnect / register_blocked\n if (a.indexOf(\"delete\") !== -1 || a.indexOf(\"remove\") !== -1 ||\n a.indexOf(\".block\") !== -1 || a.indexOf(\"unbind\") !== -1 ||\n a.indexOf(\"disconnect\") !== -1 || a.indexOf(\"register_blocked\") !== -1) {\n return \"badge-blocked\";\n }\n // Auth events: login / register\n if (a === \"login\" || a === \"user.login\" || a === \"user.register\") {\n return \"badge-member\";\n }\n // Restore: unblock\n if (a.indexOf(\"unblock\") !== -1) {\n return \"badge-admin\";\n }\n // Modifications: update / change / role_change / transfer / settings.*\n if (a.indexOf(\"update\") !== -1 || a.indexOf(\"change\") !== -1 ||\n a.indexOf(\"role_change\") !== -1 || a.indexOf(\"transfer\") !== -1 ||\n a.indexOf(\"settings.\") !== -1) {\n return \"badge-guest\";\n }\n // Create / bind / connect / accept / add\n return \"badge-owner\";\n}\n\nasync function loadAuditLogs(page) {\n auditPage = page || 1;\n var action = document.getElementById(\"audit-action-filter\").value;\n var qs = \"?page=\" + auditPage + \"&pageSize=\" + auditPageSize;\n if (action) qs += \"&action=\" + encodeURIComponent(action);\n document.getElementById(\"audit-table-wrap\").innerHTML = skeletonTable(4, 8);\n document.getElementById(\"audit-pagination\").innerHTML = \"\";\n var data = await api(\"GET\", \"/audit-logs\" + qs);\n if (!data.ok) {\n document.getElementById(\"audit-table-wrap\").innerHTML = '<div class=\"empty-state\"><div class=\"empty-state-icon\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 8v4m0 4h.01\"/></svg></div><div class=\"empty-state-title\">' + __t(\"audit.loadFailed\") + '</div></div>';\n return;\n }\n renderAuditLogs(data.logs, data.paging);\n}\n\nfunction renderAuditLogs(logs, paging) {\n var wrap = document.getElementById(\"audit-table-wrap\");\n if (!logs || logs.length === 0) {\n wrap.innerHTML = '<div class=\"empty-state\"><div class=\"empty-state-icon\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"/><polyline points=\"14 2 14 8 20 8\"/><line x1=\"16\" y1=\"13\" x2=\"8\" y2=\"13\"/><line x1=\"16\" y1=\"17\" x2=\"8\" y2=\"17\"/></svg></div><div class=\"empty-state-title\">' + __t(\"audit.noLogs\") + '</div><div class=\"empty-state-desc\">' + __t(\"audit.noLogsHint\") + '</div></div>';\n document.getElementById(\"audit-pagination\").innerHTML = \"\";\n return;\n }\n\n var html = '<table class=\"table\"><thead><tr><th>' + __t(\"audit.col.time\") + '</th><th>' + __t(\"audit.col.action\") + '</th><th>' + __t(\"audit.col.operator\") + '</th><th>' + __t(\"audit.col.target\") + '</th></tr></thead><tbody>';\n logs.forEach(function(log) {\n var time = relativeTime(log.createdAt);\n var absTime = absoluteTime(log.createdAt);\n var operatorName = log.operatorName\n ? escapeHtml(log.operatorName)\n : (log.operatorDid ? '<did-address did=\"' + escapeHtml(log.operatorDid) + '\" compact></did-address>' : \"—\");\n var operatorRoleBadge = log.operatorRole\n ? ' <span class=\"badge badge-' + escapeHtml(log.operatorRole) + '\" style=\"font-size:10px;padding:1px 6px;\">' + escapeHtml(__t(\"common.\" + log.operatorRole)) + '</span>'\n : \"\";\n var target = log.targetName\n ? escapeHtml(log.targetName)\n : (log.targetDid ? '<did-address did=\"' + escapeHtml(log.targetDid) + '\" compact></did-address>' : \"—\");\n\n // For register_blocked: show IP, method, reason in detail column\n var detail = \"\";\n if (log.action === \"user.register_blocked\") {\n var meta = {};\n try { meta = typeof log.metadata === \"string\" ? JSON.parse(log.metadata) : (log.metadata || {}); } catch(e) {}\n var parts = [];\n if (log.ip) parts.push('<span class=\"badge badge-neutral\" style=\"font-size:10px;padding:1px 6px;\">' + escapeHtml(log.ip) + '</span>');\n if (meta.source) parts.push('<span style=\"color:var(--fg-muted);font-size:12px\">' + escapeHtml(meta.source) + '</span>');\n if (meta.method) parts.push('<span class=\"badge ' + (meta.method === \"invite\" ? \"badge-guest\" : \"badge-blocked\") + '\" style=\"font-size:10px;padding:1px 6px;\">' + escapeHtml(meta.method) + '</span>');\n if (meta.reason) parts.push('<span style=\"color:var(--fg-muted);font-size:12px\">' + escapeHtml(meta.reason) + '</span>');\n detail = parts.length > 0 ? parts.join(\" \") : \"—\";\n }\n\n html += '<tr><td><span class=\"cell-date\" title=\"' + escapeHtml(absTime) + '\">' + escapeHtml(time) + '</span></td>';\n html += '<td><span class=\"badge ' + actionBadgeClass(log.action) + '\" style=\"gap:3px\">' + actionIcon(log.action) + escapeHtml(translateAction(log.action)) + '</span></td>';\n html += '<td>' + operatorName + operatorRoleBadge + '</td>';\n html += '<td>' + (detail || target) + '</td></tr>';\n });\n html += '</tbody></table>';\n wrap.innerHTML = html;\n\n renderPagination(\"audit-pagination\", paging, loadAuditLogs);\n}\n\nregisterTab(\"audit-logs\", function() { loadAuditLogs(1); });\n\n;\n\n// ─── Access Tab State ──────────────────────────────────────────────────────\nvar apPolicies = [];\nvar apRules = [];\nvar apDefaultRule = null;\nvar apEditingPolicyId = null;\nvar apEditingRuleId = null;\n\n// ─── Icon constants ────────────────────────────────────────────────────────\nvar AP_ICON_EDIT = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z\"/><path d=\"m15 5 4 4\"/></svg>';\nvar AP_ICON_DELETE = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 6h18\"/><path d=\"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6\"/><path d=\"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2\"/><line x1=\"10\" x2=\"10\" y1=\"11\" y2=\"17\"/><line x1=\"14\" x2=\"14\" y1=\"11\" y2=\"17\"/></svg>';\nvar AP_ICON_LOCK = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"18\" height=\"11\" x=\"3\" y=\"11\" rx=\"2\" ry=\"2\"/><path d=\"M7 11V7a5 5 0 0 1 10 0v4\"/></svg>';\n\n// ─── Access Type helpers ───────────────────────────────────────────────────\nvar AP_TYPE_LABELS = {\n public: __t(\"access.typeLabels.public\"),\n invited: __t(\"access.typeLabels.invited\"),\n owner: __t(\"access.typeLabels.owner\"),\n admin: __t(\"access.typeLabels.admin\"),\n roles: __t(\"access.typeLabels.roles\"),\n roles_reverse: __t(\"access.typeLabels.rolesReverse\")\n};\n\nvar AP_TYPE_DESCS = {\n public: __t(\"access.typeDescs.public\"),\n invited: __t(\"access.typeDescs.invited\"),\n admin: __t(\"access.typeDescs.admin\"),\n owner: __t(\"access.typeDescs.owner\")\n};\n\nfunction apAccessTypeLabel(policy) {\n if (policy.accessType === \"roles\" || policy.accessType === \"roles_reverse\") {\n return AP_TYPE_LABELS[policy.accessType] + \": \" + (policy.roles || []).join(\", \");\n }\n return AP_TYPE_LABELS[policy.accessType] || policy.accessType;\n}\n\nfunction apIsBuiltin(policy) {\n return !!policy.isProtected;\n}\n\nfunction apPolicyAccessType(policyId) {\n var p = apPolicies.find(function(x) { return x.id === policyId; });\n return p ? p.accessType : \"public\";\n}\n\nfunction apIconBtn(icon, title, onclick, variant) {\n var cls = variant === \"danger\" ? \"ap-icon-btn ap-icon-btn-danger\" : \"ap-icon-btn\";\n return '<button class=\"' + cls + '\" title=\"' + escapeHtml(title) + '\" onclick=\"event.stopPropagation();' + onclick + '\">' + icon + '</button>';\n}\n\n// ─── Load Data ─────────────────────────────────────────────────────────────\nasync function apLoadAll() {\n document.getElementById(\"ap-policies-wrap\").innerHTML = skeletonTable(3, 3);\n document.getElementById(\"ap-rules-wrap\").innerHTML = skeletonTable(5, 2);\n\n var pRes = await api(\"GET\", \"/access-policies\");\n if (pRes.ok) apPolicies = pRes.policies;\n\n var rRes = await api(\"GET\", \"/security-rules\");\n if (rRes.ok) apRules = rRes.rules;\n\n // Find the default rule\n apDefaultRule = apRules.find(function(r) { return r.id.indexOf(\"default\") === 0; }) || null;\n\n apRenderPolicies();\n apRenderRules();\n}\n\n// ─── Render All Policies (built-in + custom in one table) ─────────────────\nfunction apRenderPolicies() {\n var wrap = document.getElementById(\"ap-policies-wrap\");\n\n if (!apPolicies.length) {\n wrap.innerHTML = '<div class=\"empty-state\" style=\"padding:16px\"><div class=\"empty-state-title\">' + __t(\"access.noPolicies\") + '</div></div>';\n return;\n }\n\n // Built-in first, then custom\n var builtins = apPolicies.filter(function(p) { return apIsBuiltin(p); });\n var customs = apPolicies.filter(function(p) { return !apIsBuiltin(p); });\n var sorted = builtins.concat(customs);\n\n var html = '<table class=\"table\"><thead><tr>'\n + '<th>' + __t(\"access.name\") + '</th>'\n + '<th>' + __t(\"access.accessType\") + '</th>'\n + '<th style=\"width:80px;text-align:center\">' + __t(\"common.actions\") + '</th>'\n + '</tr></thead><tbody>';\n\n builtins.forEach(function(p) {\n html += \"<tr>\";\n html += '<td><span class=\"ap-lock-name\">' + AP_ICON_LOCK + ' ' + escapeHtml(p.name) + '</span>';\n if (p.description) html += '<div class=\"text-muted\" style=\"font-size:12px;margin-top:2px\">' + escapeHtml(p.description) + '</div>';\n html += \"</td>\";\n html += \"<td>\" + escapeHtml(apAccessTypeLabel(p)) + \"</td>\";\n html += \"<td></td>\";\n html += \"</tr>\";\n });\n\n if (builtins.length > 0 && customs.length > 0) {\n html += '<tr class=\"ap-separator\"><td colspan=\"3\"></td></tr>';\n }\n\n customs.forEach(function(p) {\n html += \"<tr>\";\n html += \"<td>\" + escapeHtml(p.name);\n if (p.description) html += '<div class=\"text-muted\" style=\"font-size:12px;margin-top:2px\">' + escapeHtml(p.description) + '</div>';\n html += \"</td>\";\n html += \"<td>\" + escapeHtml(apAccessTypeLabel(p)) + \"</td>\";\n html += '<td class=\"ap-actions\">';\n html += apIconBtn(AP_ICON_EDIT, __t(\"common.edit\"), \"openPolicyDialog('edit','\" + escapeHtml(p.id) + \"')\");\n html += apIconBtn(AP_ICON_DELETE, __t(\"common.delete\"), \"apDeletePolicy('\" + escapeHtml(p.id) + \"')\", \"danger\");\n html += \"</td></tr>\";\n });\n\n html += \"</tbody></table>\";\n wrap.innerHTML = html;\n}\n\n// ─── Render Path Rules ────────────────────────────────────────────────────\nfunction apRenderRules() {\n var wrap = document.getElementById(\"ap-rules-wrap\");\n var nonDefaultRules = apRules.filter(function(r) { return r.id.indexOf(\"default\") !== 0; });\n\n // Sort non-default rules by priority ascending\n nonDefaultRules.sort(function(a, b) { return a.priority - b.priority; });\n\n var html = '<table class=\"table\"><thead><tr>'\n + '<th style=\"width:80px\">#</th>'\n + '<th>' + __t(\"access.urlPattern\") + '</th>'\n + '<th>' + __t(\"access.accessPolicy\") + '</th>'\n + '<th style=\"width:80px\">' + __t(\"common.status\") + '</th>'\n + '<th style=\"width:80px;text-align:center\">' + __t(\"common.actions\") + '</th>'\n + '</tr></thead><tbody>';\n\n // Default rule — first row, special style\n if (apDefaultRule) {\n var policyOptions = apPolicies.map(function(p) {\n var label = escapeHtml(p.name) + ' \\u2014 ' + escapeHtml(apAccessTypeLabel(p));\n var selected = p.id === apDefaultRule.accessPolicyId ? ' selected' : '';\n return '<option value=\"' + escapeHtml(p.id) + '\"' + selected + '>' + label + '</option>';\n }).join(\"\");\n\n html += '<tr style=\"background:var(--bg-elevated)\">';\n html += '<td><span class=\"badge badge-active\">' + __t(\"access.defaultLabel\") + '</span></td>';\n html += '<td><code>*</code></td>';\n html += '<td><select class=\"select select-sm\" onchange=\"apSaveDefaultRulePolicy(this.value)\" style=\"max-width:280px\">' + policyOptions + '</select></td>';\n html += '<td></td>';\n html += '<td></td>';\n html += '</tr>';\n }\n\n // Empty state for non-default rules\n if (nonDefaultRules.length === 0 && apDefaultRule) {\n html += '<tr><td colspan=\"5\" class=\"text-muted\" style=\"text-align:center;padding:16px\">' + __t(\"access.noRules\") + '</td></tr>';\n }\n\n nonDefaultRules.forEach(function(r, i) {\n html += \"<tr>\";\n html += \"<td>#\" + (i + 1) + \"</td>\";\n html += \"<td><code>\" + escapeHtml(r.pathPattern) + \"</code></td>\";\n html += \"<td>\" + escapeHtml(r.accessPolicyName) + \"</td>\";\n html += '<td><label class=\"ap-switch\" title=\"' + (r.enabled ? __t(\"access.clickToDisable\") : __t(\"access.clickToEnable\")) + '\"><input type=\"checkbox\"' + (r.enabled ? ' checked' : '') + ' onchange=\"apToggleRule('' + escapeHtml(r.id) + '',' + (r.enabled ? 0 : 1) + ')\"><span class=\"ap-switch-slider\"></span></label></td>';\n html += '<td class=\"ap-actions\">';\n html += apIconBtn(AP_ICON_EDIT, __t(\"common.edit\"), \"openRuleDialog('edit','\" + escapeHtml(r.id) + \"')\");\n html += apIconBtn(AP_ICON_DELETE, __t(\"common.delete\"), \"apDeleteRule('\" + escapeHtml(r.id) + \"')\", \"danger\");\n html += \"</td></tr>\";\n });\n\n html += \"</tbody></table>\";\n wrap.innerHTML = html;\n}\n\nasync function apSaveDefaultRulePolicy(policyId) {\n if (!apDefaultRule) return;\n var res = await api(\"PUT\", \"/security-rules/\" + apDefaultRule.id, {\n accessPolicyId: policyId,\n remark: apDefaultRule.remark || \"Default fallback rule\"\n });\n if (res.ok) {\n showToast(__t(\"access.ruleSaved\"), \"success\");\n apDefaultRule.accessPolicyId = policyId;\n }\n}\n\nasync function apToggleRule(id, enabled) {\n var r = apRules.find(function(x) { return x.id === id; });\n if (!r) return;\n var res = await api(\"PUT\", \"/security-rules/\" + id, {\n accessPolicyId: r.accessPolicyId,\n pathPattern: r.pathPattern,\n priority: r.priority,\n enabled: enabled,\n remark: r.remark || \"\"\n });\n if (res.ok) {\n r.enabled = enabled;\n apRenderRules();\n showToast(__t(\"access.ruleSaved\"), \"success\");\n }\n}\n\n// ─── Policy Dialog (custom only) ──────────────────────────────────────────\nfunction openPolicyDialog(mode, id) {\n apEditingPolicyId = (mode === \"edit\") ? id : null;\n document.getElementById(\"ap-policy-dialog-title\").textContent = mode === \"edit\" ? __t(\"access.editPolicyTitle\") : __t(\"access.policyTitle\");\n\n if (mode === \"edit\") {\n var p = apPolicies.find(function(x) { return x.id === id; });\n if (!p) return;\n document.getElementById(\"ap-policy-name\").value = p.name;\n document.getElementById(\"ap-policy-type\").value = p.accessType || \"roles\";\n document.getElementById(\"ap-policy-desc\").value = p.description || \"\";\n document.querySelectorAll(\".ap-role-cb\").forEach(function(cb) {\n cb.checked = (p.roles || []).includes(cb.value);\n });\n } else {\n document.getElementById(\"ap-policy-name\").value = \"\";\n document.getElementById(\"ap-policy-type\").value = \"roles\";\n document.getElementById(\"ap-policy-desc\").value = \"\";\n document.querySelectorAll(\".ap-role-cb\").forEach(function(cb) { cb.checked = false; });\n }\n showDialog(\"ap-policy-dialog\");\n}\n\nasync function apSavePolicy() {\n var body = {\n name: document.getElementById(\"ap-policy-name\").value.trim(),\n accessType: document.getElementById(\"ap-policy-type\").value,\n description: document.getElementById(\"ap-policy-desc\").value.trim(),\n };\n body.roles = [];\n document.querySelectorAll(\".ap-role-cb:checked\").forEach(function(cb) { body.roles.push(cb.value); });\n if (body.roles.length === 0) { showToast(__t(\"access.roleRequired\"), \"error\"); return; }\n if (!body.name) { showToast(__t(\"access.nameRequired\"), \"error\"); return; }\n\n var res;\n if (apEditingPolicyId) {\n res = await api(\"PUT\", \"/access-policies/\" + apEditingPolicyId, body);\n } else {\n res = await api(\"POST\", \"/access-policies\", body);\n }\n if (res.ok) {\n showToast(__t(\"access.policySaved\"), \"success\");\n closeDialog(\"ap-policy-dialog\");\n apLoadAll();\n }\n}\n\nasync function apDeletePolicy(id) {\n var ok = await confirmDialog({ title: __t(\"access.deletePolicy\"), message: __t(\"access.deletePolicyMsg\"), danger: true });\n if (!ok) return;\n var res = await api(\"DELETE\", \"/access-policies/\" + id);\n if (res.ok) { showToast(__t(\"access.policyDeleted\"), \"success\"); apLoadAll(); }\n}\n\n// ─── Rule Dialog ───────────────────────────────────────────────────────────\nfunction openRuleDialog(mode, id) {\n apEditingRuleId = (mode === \"edit\") ? id : null;\n document.getElementById(\"ap-rule-dialog-title\").textContent = mode === \"edit\" ? __t(\"access.editRuleTitle\") : __t(\"access.ruleTitle\");\n\n // Populate policy dropdown with all policies\n var sel = document.getElementById(\"ap-rule-policy\");\n sel.innerHTML = apPolicies.map(function(p) {\n return '<option value=\"' + escapeHtml(p.id) + '\">' + escapeHtml(p.name) + ' \\u2014 ' + escapeHtml(apAccessTypeLabel(p)) + '</option>';\n }).join(\"\");\n\n document.getElementById(\"ap-rule-pattern\").disabled = false;\n document.getElementById(\"ap-rule-priority\").disabled = false;\n\n if (mode === \"edit\") {\n var r = apRules.find(function(x) { return x.id === id; });\n if (!r) return;\n document.getElementById(\"ap-rule-pattern\").value = r.pathPattern;\n document.getElementById(\"ap-rule-priority\").value = r.priority;\n document.getElementById(\"ap-rule-remark\").value = r.remark || \"\";\n sel.value = r.accessPolicyId;\n } else {\n document.getElementById(\"ap-rule-pattern\").value = \"\";\n document.getElementById(\"ap-rule-priority\").value = \"0\";\n document.getElementById(\"ap-rule-remark\").value = \"\";\n }\n showDialog(\"ap-rule-dialog\");\n}\n\nasync function apSaveRule() {\n var body = {\n accessPolicyId: document.getElementById(\"ap-rule-policy\").value,\n remark: document.getElementById(\"ap-rule-remark\").value.trim(),\n pathPattern: document.getElementById(\"ap-rule-pattern\").value.trim(),\n priority: parseInt(document.getElementById(\"ap-rule-priority\").value, 10) || 0,\n };\n if (!body.pathPattern) { showToast(__t(\"access.patternRequired\"), \"error\"); return; }\n\n var res;\n if (apEditingRuleId) {\n res = await api(\"PUT\", \"/security-rules/\" + apEditingRuleId, body);\n } else {\n res = await api(\"POST\", \"/security-rules\", body);\n }\n if (res.ok) {\n showToast(__t(\"access.ruleSaved\"), \"success\");\n closeDialog(\"ap-rule-dialog\");\n apLoadAll();\n }\n}\n\nasync function apDeleteRule(id) {\n var ok = await confirmDialog({ title: __t(\"access.deleteRule\"), message: __t(\"access.deleteRuleMsg\"), danger: true });\n if (!ok) return;\n var res = await api(\"DELETE\", \"/security-rules/\" + id);\n if (res.ok) { showToast(__t(\"access.ruleDeleted\"), \"success\"); apLoadAll(); }\n}\n\n// ─── Register Tab ──────────────────────────────────────────────────────────\nregisterTab(\"access\", function() { apLoadAll(); });\n\n;\n\n// ─── Crop aspect ratios per logo type ──────────────────────────\nvar CROP_RATIOS = {\n 'square': 1, 'square-dark': 1, 'favicon': 1,\n 'rect': NaN, 'rect-dark': NaN,\n 'splash-portrait': 9/16, 'splash-landscape': 16/9,\n 'og-image': 40/21\n};\n// Target dimensions and output format per logo type\nvar CROP_OUTPUT = {\n 'square': { maxW: 512, maxH: 512, fmt: 'image/webp', q: 0.9, ext: 'webp' },\n 'square-dark': { maxW: 512, maxH: 512, fmt: 'image/webp', q: 0.9, ext: 'webp' },\n 'favicon': { maxW: 128, maxH: 128, fmt: 'image/png', q: 1, ext: 'png' },\n 'rect': { maxW: 800, maxH: 400, fmt: 'image/webp', q: 0.9, ext: 'webp' },\n 'rect-dark': { maxW: 800, maxH: 400, fmt: 'image/webp', q: 0.9, ext: 'webp' },\n 'og-image': { maxW: 1200, maxH: 630, fmt: 'image/jpeg', q: 0.85, ext: 'jpg' },\n 'splash-portrait': { maxW: 1080, maxH: 1920, fmt: 'image/webp', q: 0.85, ext: 'webp' },\n 'splash-landscape': { maxW: 1920, maxH: 1080, fmt: 'image/webp', q: 0.85, ext: 'webp' }\n};\nvar MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB\nvar ALLOWED_TYPES = ['image/png', 'image/jpeg', 'image/webp', 'image/svg+xml'];\n\n// ─── Lazy-load cropperjs from CDN ──────────────────────────────\nvar __cropperLoaded = false;\nfunction loadCropperJS() {\n if (__cropperLoaded) return Promise.resolve();\n return new Promise(function(resolve, reject) {\n var link = document.createElement('link');\n link.rel = 'stylesheet';\n link.href = 'https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.6.2/cropper.min.css';\n document.head.appendChild(link);\n var script = document.createElement('script');\n script.src = 'https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.6.2/cropper.min.js';\n script.onload = function() { __cropperLoaded = true; resolve(); };\n script.onerror = function() { reject(new Error('CDN load failed')); };\n document.head.appendChild(script);\n });\n}\n\n// ─── Cropper state ─────────────────────────────────────────────\nvar __cropper = null;\nvar __cropType = null;\n\n// ─── Handle file selection (entry point from branding tab) ─────\nfunction handleLogoFile(type, input) {\n var file = input.files && input.files[0];\n input.value = ''; // reset so same file can be re-selected\n if (!file) return;\n\n // Validate file size\n if (file.size > MAX_FILE_SIZE) {\n showToast(__t('branding.fileTooLarge'), 'error');\n return;\n }\n\n // Validate file type\n if (ALLOWED_TYPES.indexOf(file.type) === -1) {\n showToast(__t('branding.invalidFormat'), 'error');\n return;\n }\n\n // SVG: skip cropper, upload directly\n if (file.type === 'image/svg+xml' || file.name.toLowerCase().endsWith('.svg')) {\n doUploadLogo(type, file);\n return;\n }\n\n // Open cropper\n openCropper(type, file);\n}\n\nfunction openCropper(type, file) {\n __cropType = type;\n\n loadCropperJS().then(function() {\n var reader = new FileReader();\n reader.onload = function(e) {\n var dialog = document.getElementById('crop-dialog');\n var img = document.getElementById('crop-image');\n if (!dialog || !img) return;\n\n img.src = e.target.result;\n\n // Destroy previous cropper instance if any\n if (__cropper) { __cropper.destroy(); __cropper = null; }\n\n dialog.showModal();\n\n // Initialize cropperjs after dialog is visible (needs layout)\n setTimeout(function() {\n var ratio = CROP_RATIOS[type];\n __cropper = new Cropper(img, {\n aspectRatio: isNaN(ratio) ? NaN : ratio,\n viewMode: 1,\n autoCrop: true,\n autoCropArea: 1,\n responsive: true,\n background: false,\n guides: true,\n center: true,\n highlight: true,\n cropBoxMovable: true,\n cropBoxResizable: true,\n toggleDragModeOnDblclick: false,\n });\n }, 100);\n };\n reader.readAsDataURL(file);\n }).catch(function(err) {\n // CDN load failed — fallback to direct upload\n console.warn('Cropper load failed:', err);\n showToast(__t('branding.cropperLoadFailed'), 'error');\n doUploadLogo(type, file);\n });\n}\n\nfunction confirmCrop() {\n if (!__cropper || !__cropType) return;\n var output = CROP_OUTPUT[__cropType] || { maxW: 512, maxH: 512, fmt: 'image/webp', q: 0.9, ext: 'webp' };\n\n // Get cropped canvas at target dimensions (resize during crop for best quality)\n var canvas = __cropper.getCroppedCanvas({\n maxWidth: output.maxW,\n maxHeight: output.maxH,\n imageSmoothingEnabled: true,\n imageSmoothingQuality: 'high',\n });\n if (!canvas) {\n showToast(__t('branding.uploadError'), 'error');\n closeCropDialog();\n return;\n }\n\n // Show uploading state in dialog\n var actions = document.querySelector('#crop-dialog .dialog-actions');\n var content = document.querySelector('#crop-dialog .crop-toolbar');\n if (actions) actions.innerHTML = '<div style=\"display:flex;align-items:center;gap:12px;width:100%;justify-content:center;\"><div class=\"crop-progress\" style=\"flex:1;max-width:200px;\"><div class=\"crop-progress-bar\"></div></div><span style=\"font-size:13px;color:var(--text-secondary);\">' + __t('common.upload') + '...</span></div>';\n if (content) content.style.opacity = '0.5';\n\n canvas.toBlob(function(blob) {\n if (!blob) {\n showToast(__t('branding.uploadError'), 'error');\n closeCropDialog();\n return;\n }\n var file = new File([blob], __cropType + '.' + output.ext, { type: output.fmt });\n doUploadLogo(__cropType, file).then(function() {\n closeCropDialog();\n }).catch(function() {\n closeCropDialog();\n });\n }, output.fmt, output.q);\n}\n\nfunction cancelCrop() {\n closeCropDialog();\n}\n\nfunction closeCropDialog() {\n var dialog = document.getElementById('crop-dialog');\n if (dialog && dialog.open) dialog.close();\n if (__cropper) { __cropper.destroy(); __cropper = null; }\n __cropType = null;\n // Restore dialog actions and toolbar to initial state\n var actions = document.querySelector('#crop-dialog .dialog-actions');\n if (actions) actions.innerHTML = '<button class=\"btn btn-secondary\" onclick=\"cancelCrop()\">' + __t('branding.cropCancel') + '</button><button class=\"btn\" onclick=\"confirmCrop()\">' + __t('branding.cropConfirm') + '</button>';\n var toolbar = document.querySelector('#crop-dialog .crop-toolbar');\n if (toolbar) toolbar.style.opacity = '';\n}\n\n// ─── Toolbar actions ───────────────────────────────────────────\nfunction cropZoomIn() { if (__cropper) __cropper.zoom(0.1); }\nfunction cropZoomOut() { if (__cropper) __cropper.zoom(-0.1); }\nfunction cropRotateLeft() { if (__cropper) __cropper.rotate(-90); }\nfunction cropRotateRight() { if (__cropper) __cropper.rotate(90); }\nfunction cropFlipH() {\n if (!__cropper) return;\n var d = __cropper.getData();\n __cropper.scaleX(d.scaleX === -1 ? 1 : -1);\n}\nfunction cropFlipV() {\n if (!__cropper) return;\n var d = __cropper.getData();\n __cropper.scaleY(d.scaleY === -1 ? 1 : -1);\n}\n\n;\n\n// ─── Branding Tab ────────────────────────────────────────────────────\n\nvar __brandingApiBase = \"/.well-known/service/api\";\n\nasync function brandingApi(method, path, body) {\n try {\n var opts = { method: method, headers: {} };\n if (body !== undefined) {\n opts.headers[\"Content-Type\"] = \"application/json\";\n opts.body = JSON.stringify(body);\n }\n var res = await fetch(__brandingApiBase + path, opts);\n if (!res.ok) {\n var data = await res.json().catch(function() { return {}; });\n showToast(data.error || __t(\"common.requestFailed\"), \"error\");\n return { ok: false, error: data.error };\n }\n return await res.json();\n } catch (err) {\n showToast(__t(\"common.networkError\"), \"error\");\n return { ok: false, error: err.message };\n }\n}\n\nvar __brandingData = {};\nvar __brandingDirty = false;\nvar __pendingLogos = {}; // { camelType: r2Key } for new uploads, { camelType: null } for deletions\n\nfunction markBrandingDirty() {\n __brandingDirty = true;\n var bar = document.getElementById(\"br-save-bar\");\n if (bar) bar.classList.add(\"visible\");\n}\n\nfunction hideSaveBar() {\n __brandingDirty = false;\n var bar = document.getElementById(\"br-save-bar\");\n if (bar) bar.classList.remove(\"visible\");\n}\n\nasync function discardBrandingChanges() {\n __pendingLogos = {};\n await loadBranding();\n}\n\nasync function loadBranding() {\n var data = await brandingApi(\"GET\", \"/branding\");\n if (!data) return;\n __brandingData = data;\n\n // App Info\n var br = data.branding || {};\n document.getElementById(\"br-name\").value = br.name || \"\";\n document.getElementById(\"br-desc\").value = br.description || \"\";\n document.getElementById(\"br-url\").value = br.url || \"\";\n var pc = br.passportColor || \"#1DC1C7\";\n document.getElementById(\"br-passport-color\").value = pc;\n document.getElementById(\"br-copyright-owner\").value = (br.copyright && br.copyright.owner) || \"\";\n document.getElementById(\"br-copyright-year\").value = (br.copyright && br.copyright.year) || \"\";\n\n // Accent color\n updateAccentUI(pc);\n\n // Logos\n var logos = data.logos || {};\n var LOGO_URLS = { \"square\": \"/logo\", \"square-dark\": \"/logo-dark\", \"rect\": \"/logo-rect\", \"rect-dark\": \"/logo-rect-dark\", \"favicon\": \"/logo-favicon\", \"og-image\": \"/og-image\", \"splash-portrait\": \"/splash/portrait\", \"splash-landscape\": \"/splash/landscape\" };\n [{\"type\":\"square\",\"nameKey\":\"branding.logoAppLight\",\"subKey\":\"branding.subLight\",\"icon\":\"<svg width=\\\"20\\\" height=\\\"20\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"1.5\\\"><circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"5\\\"/><line x1=\\\"12\\\" y1=\\\"1\\\" x2=\\\"12\\\" y2=\\\"3\\\"/><line x1=\\\"12\\\" y1=\\\"21\\\" x2=\\\"12\\\" y2=\\\"23\\\"/><line x1=\\\"4.22\\\" y1=\\\"4.22\\\" x2=\\\"5.64\\\" y2=\\\"5.64\\\"/><line x1=\\\"18.36\\\" y1=\\\"18.36\\\" x2=\\\"19.78\\\" y2=\\\"19.78\\\"/><line x1=\\\"1\\\" y1=\\\"12\\\" x2=\\\"3\\\" y2=\\\"12\\\"/><line x1=\\\"21\\\" y1=\\\"12\\\" x2=\\\"23\\\" y2=\\\"12\\\"/><line x1=\\\"4.22\\\" y1=\\\"19.78\\\" x2=\\\"5.64\\\" y2=\\\"18.36\\\"/><line x1=\\\"18.36\\\" y1=\\\"5.64\\\" x2=\\\"19.78\\\" y2=\\\"4.22\\\"/></svg>\",\"spec\":\"512×512\"},{\"type\":\"square-dark\",\"nameKey\":\"branding.logoAppDark\",\"subKey\":\"branding.subDark\",\"icon\":\"<svg width=\\\"20\\\" height=\\\"20\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"1.5\\\"><path d=\\\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\\\"/></svg>\",\"spec\":\"512×512\"},{\"type\":\"rect\",\"nameKey\":\"branding.logoWebLight\",\"subKey\":\"branding.subLight\",\"icon\":\"<svg width=\\\"20\\\" height=\\\"20\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"1.5\\\"><circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"5\\\"/><line x1=\\\"12\\\" y1=\\\"1\\\" x2=\\\"12\\\" y2=\\\"3\\\"/><line x1=\\\"12\\\" y1=\\\"21\\\" x2=\\\"12\\\" y2=\\\"23\\\"/><line x1=\\\"4.22\\\" y1=\\\"4.22\\\" x2=\\\"5.64\\\" y2=\\\"5.64\\\"/><line x1=\\\"18.36\\\" y1=\\\"18.36\\\" x2=\\\"19.78\\\" y2=\\\"19.78\\\"/><line x1=\\\"1\\\" y1=\\\"12\\\" x2=\\\"3\\\" y2=\\\"12\\\"/><line x1=\\\"21\\\" y1=\\\"12\\\" x2=\\\"23\\\" y2=\\\"12\\\"/><line x1=\\\"4.22\\\" y1=\\\"19.78\\\" x2=\\\"5.64\\\" y2=\\\"18.36\\\"/><line x1=\\\"18.36\\\" y1=\\\"5.64\\\" x2=\\\"19.78\\\" y2=\\\"4.22\\\"/></svg>\",\"spec\":\"SVG/PNG\"},{\"type\":\"rect-dark\",\"nameKey\":\"branding.logoWebDark\",\"subKey\":\"branding.subDark\",\"icon\":\"<svg width=\\\"20\\\" height=\\\"20\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"1.5\\\"><path d=\\\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\\\"/></svg>\",\"spec\":\"SVG/PNG\"},{\"type\":\"favicon\",\"nameKey\":\"branding.logoFavicon\",\"subKey\":\"branding.subBrowser\",\"icon\":\"<svg width=\\\"20\\\" height=\\\"20\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"1.5\\\"><path d=\\\"M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z\\\"/></svg>\",\"spec\":\"32×32 ICO\"},{\"type\":\"splash-portrait\",\"nameKey\":\"branding.logoSplashP\",\"subKey\":\"branding.subPortrait\",\"icon\":\"<svg width=\\\"20\\\" height=\\\"20\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"1.5\\\"><rect x=\\\"5\\\" y=\\\"2\\\" width=\\\"14\\\" height=\\\"20\\\" rx=\\\"2\\\"/><circle cx=\\\"12\\\" cy=\\\"18\\\" r=\\\"1\\\" fill=\\\"currentColor\\\" stroke=\\\"none\\\"/></svg>\",\"spec\":\"9:16\"},{\"type\":\"splash-landscape\",\"nameKey\":\"branding.logoSplashL\",\"subKey\":\"branding.subLandscape\",\"icon\":\"<svg width=\\\"20\\\" height=\\\"20\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"1.5\\\"><rect x=\\\"2\\\" y=\\\"4\\\" width=\\\"20\\\" height=\\\"16\\\" rx=\\\"2\\\"/><circle cx=\\\"18\\\" cy=\\\"12\\\" r=\\\"1\\\" fill=\\\"currentColor\\\" stroke=\\\"none\\\"/></svg>\",\"spec\":\"16:9\"},{\"type\":\"og-image\",\"nameKey\":\"branding.logoOg\",\"subKey\":\"branding.subSocial\",\"icon\":\"<svg width=\\\"20\\\" height=\\\"20\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"1.5\\\"><circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"10\\\"/><line x1=\\\"2\\\" y1=\\\"12\\\" x2=\\\"22\\\" y2=\\\"12\\\"/><path d=\\\"M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z\\\"/></svg>\",\"spec\":\"1200×630\"}].forEach(function(l) {\n var key = l.type.replace(/-([a-z])/g, function(_, c) { return c.toUpperCase(); });\n var preview = document.getElementById(\"br-logo-preview-\" + l.type);\n var actions = document.getElementById(\"br-logo-actions-\" + l.type);\n var item = document.getElementById(\"br-logo-\" + l.type);\n // Remove any existing uploaded overlay\n var oldOverlay = item && item.querySelector(\".logo-uploaded-overlay\");\n if (oldOverlay) oldOverlay.remove();\n\n if (logos[key]) {\n var url = \"/.well-known/service/blocklet\" + (LOGO_URLS[l.type] || \"/logo\");\n preview.innerHTML = '<img src=\"' + url + '?t=' + Date.now() + '\" />';\n // Add hover overlay with Replace + Discard buttons\n if (item) {\n var overlay = document.createElement(\"div\");\n overlay.className = \"logo-uploaded-overlay\";\n // Build with DOM to avoid escaping issues\n var replaceLabel = document.createElement(\"label\");\n replaceLabel.className = \"logo-overlay-btn logo-overlay-btn-replace\";\n replaceLabel.textContent = __t(\"branding.replace\");\n var replaceInput = document.createElement(\"input\");\n replaceInput.type = \"file\";\n replaceInput.accept = \"image/png,image/jpeg,image/webp,image/svg+xml\";\n replaceInput.style.display = \"none\";\n (function(logoType) {\n replaceInput.onchange = function() { handleLogoFile(logoType, replaceInput); };\n })(l.type);\n replaceLabel.appendChild(replaceInput);\n var discardBtn = document.createElement(\"button\");\n discardBtn.className = \"logo-overlay-btn logo-overlay-btn-discard\";\n discardBtn.textContent = __t(\"branding.removeLogo\");\n (function(logoType) {\n discardBtn.onclick = function() { deleteLogo(logoType); };\n })(l.type);\n overlay.appendChild(replaceLabel);\n overlay.appendChild(discardBtn);\n item.appendChild(overlay);\n }\n } else {\n preview.innerHTML = '';\n }\n });\n\n // Languages\n var langs = data.languages || [];\n var codes = langs.map(function(l) { return l.code; });\n document.querySelectorAll(\".lang-toggle\").forEach(function(btn) {\n if (codes.indexOf(btn.dataset.lang) !== -1) btn.classList.add(\"active\");\n else btn.classList.remove(\"active\");\n });\n\n // Apple App IDs\n var appleInput = document.getElementById(\"br-apple-app-ids\");\n if (appleInput) {\n var appleIds = data.appleAppIds || [];\n appleInput.value = Array.isArray(appleIds) ? appleIds.join(\", \") : \"\";\n }\n\n hideSaveBar();\n}\n\n// Language toggle — changes are saved with the main Save Configuration button\nfunction toggleLang(code) {\n var btn = document.querySelector('.lang-toggle[data-lang=\"' + code + '\"]');\n if (!btn) return;\n // If deselecting, ensure at least one language remains active\n if (btn.classList.contains(\"active\")) {\n var activeCount = document.querySelectorAll(\".lang-toggle.active\").length;\n if (activeCount <= 1) {\n showToast(\"At least one language is required\", \"error\");\n return;\n }\n }\n btn.classList.toggle(\"active\");\n markBrandingDirty();\n}\n\n// Accent color (compact UI)\nfunction updateAccentUI(color) {\n var picker = document.getElementById(\"br-accent-picker\");\n var hex = document.getElementById(\"br-accent-hex\");\n var hidden = document.getElementById(\"br-passport-color\");\n if (picker) picker.value = color;\n if (hex) hex.value = color;\n if (hidden) hidden.value = color;\n document.querySelectorAll(\".accent-dot\").forEach(function(d) {\n if (d.dataset.color === color) d.classList.add(\"selected\");\n else d.classList.remove(\"selected\");\n });\n}\n\nfunction onAccentInput(color) {\n if (!color || !color.match(/^#[0-9a-fA-F]{6}$/)) return;\n updateAccentUI(color);\n markBrandingDirty();\n}\n\nfunction pickAccent(color) {\n updateAccentUI(color);\n markBrandingDirty();\n}\n\nasync function saveBrandingInfo() {\n var copyright = {};\n var owner = document.getElementById(\"br-copyright-owner\").value.trim();\n var year = document.getElementById(\"br-copyright-year\").value.trim();\n if (owner) copyright.owner = owner;\n if (year) copyright.year = year;\n\n var body = {\n branding: {\n name: document.getElementById(\"br-name\").value.trim(),\n description: document.getElementById(\"br-desc\").value.trim(),\n url: document.getElementById(\"br-url\").value.trim(),\n passportColor: document.getElementById(\"br-passport-color\").value,\n copyright: copyright\n }\n };\n\n // Also save languages\n var langs = [];\n var allLangs = [{\"code\":\"en\",\"name\":\"English\"},{\"code\":\"zh\",\"name\":\"简体中文\"}];\n document.querySelectorAll(\".lang-toggle.active\").forEach(function(btn) {\n var found = allLangs.find(function(l) { return l.code === btn.dataset.lang; });\n if (found) langs.push(found);\n });\n body.languages = langs;\n\n // Include pending logo changes\n if (Object.keys(__pendingLogos).length > 0) {\n body.logos = __pendingLogos;\n }\n\n // Apple App IDs\n var appleRaw = (document.getElementById(\"br-apple-app-ids\").value || \"\").trim();\n if (appleRaw) {\n body.appleAppIds = appleRaw.split(\",\").map(function(s) { return s.trim(); }).filter(Boolean);\n } else {\n body.appleAppIds = [];\n }\n\n var res = await brandingApi(\"PUT\", \"/branding\", body);\n if (res && res.ok) {\n __pendingLogos = {};\n showToast(__t(\"branding.updated\"));\n // Refresh favicon and header logo with cache-busting\n var cacheBust = \"?t=\" + Date.now();\n var icon = document.querySelector('link[rel=\"icon\"]');\n if (icon) icon.href = \"/.well-known/service/blocklet/logo-favicon\" + cacheBust;\n var headerLogo = document.querySelector('.admin-header-left img');\n if (headerLogo) headerLogo.src = headerLogo.src.split('?')[0] + cacheBust;\n await loadBranding();\n } else {\n showToast(__t(\"branding.updateFailed\"), \"error\");\n }\n}\n\nasync function doUploadLogo(type, file) {\n if (!file) return;\n var fd = new FormData();\n fd.append(\"file\", file);\n try {\n var resp = await fetch(__brandingApiBase + \"/branding/logo/\" + type, {\n method: \"POST\",\n body: fd,\n credentials: \"same-origin\"\n });\n var data = await resp.json();\n if (data.ok && data.r2Key) {\n // Store in pending state — not saved to D1 yet\n var camelType = type.replace(/-([a-z])/g, function(_, c) { return c.toUpperCase(); });\n __pendingLogos[camelType] = data.r2Key;\n // Show preview using the logo serve endpoint (R2 key is now uploaded)\n // We need to temporarily update the logos display\n var LOGO_URLS = { \"square\": \"/logo\", \"square-dark\": \"/logo-dark\", \"rect\": \"/logo-rect\", \"rect-dark\": \"/logo-rect-dark\", \"favicon\": \"/logo-favicon\", \"og-image\": \"/og-image\", \"splash-portrait\": \"/splash/portrait\", \"splash-landscape\": \"/splash/landscape\" };\n var preview = document.getElementById(\"br-logo-preview-\" + type);\n if (preview) {\n // Use a blob URL from the uploaded file for instant preview\n var blobUrl = URL.createObjectURL(file);\n preview.innerHTML = '<img src=\"' + blobUrl + '\" />';\n }\n // Add uploaded overlay if not exists\n var item = document.getElementById(\"br-logo-\" + type);\n if (item) {\n var oldOverlay = item.querySelector(\".logo-uploaded-overlay\");\n if (oldOverlay) oldOverlay.remove();\n var overlay = document.createElement(\"div\");\n overlay.className = \"logo-uploaded-overlay\";\n var rl = document.createElement(\"label\");\n rl.className = \"logo-overlay-btn logo-overlay-btn-replace\";\n rl.textContent = __t(\"branding.replace\");\n var ri = document.createElement(\"input\");\n ri.type = \"file\"; ri.accept = \"image/png,image/jpeg,image/webp,image/svg+xml\"; ri.style.display = \"none\";\n (function(t) { ri.onchange = function() { handleLogoFile(t, ri); }; })(type);\n rl.appendChild(ri);\n var db = document.createElement(\"button\");\n db.className = \"logo-overlay-btn logo-overlay-btn-discard\";\n db.textContent = __t(\"branding.removeLogo\");\n (function(t) { db.onclick = function() { deleteLogo(t); }; })(type);\n overlay.appendChild(rl);\n overlay.appendChild(db);\n item.appendChild(overlay);\n }\n showToast(__t(\"branding.logoUploaded\"));\n markBrandingDirty();\n } else {\n showToast(data.error || \"Upload failed\", \"error\");\n }\n } catch(e) {\n showToast(__t(\"branding.uploadError\"), \"error\");\n }\n}\n\nasync function deleteLogo(type) {\n if (!confirm(__t(\"branding.deleteLogo\"))) return;\n var camelType = type.replace(/-([a-z])/g, function(_, c) { return c.toUpperCase(); });\n __pendingLogos[camelType] = null; // mark for deletion on save\n // Clear preview\n var preview = document.getElementById(\"br-logo-preview-\" + type);\n if (preview) preview.innerHTML = '';\n var item = document.getElementById(\"br-logo-\" + type);\n if (item) {\n var overlay = item.querySelector(\".logo-uploaded-overlay\");\n if (overlay) overlay.remove();\n }\n showToast(__t(\"branding.logoDeleted\"));\n markBrandingDirty();\n}\n\nasync function saveBrandingLanguages() {\n var langs = [];\n var allLangs = [{\"code\":\"en\",\"name\":\"English\"},{\"code\":\"zh\",\"name\":\"简体中文\"}];\n document.querySelectorAll(\".lang-toggle.active\").forEach(function(btn) {\n var found = allLangs.find(function(l) { return l.code === btn.dataset.lang; });\n if (found) langs.push(found);\n });\n var res = await brandingApi(\"PUT\", \"/branding\", { languages: langs });\n if (res && res.ok) showToast(__t(\"branding.languagesUpdated\"));\n else showToast(__t(\"branding.langUpdateFailed\"), \"error\");\n}\n\nregisterTab(\"branding\", loadBranding);\n\n;\n\n// ─── Appearance Tab ──────────────────────────────────────────────────\n\nvar __appearanceApiBase = \"/.well-known/service/api\";\n\nasync function appearanceApi(method, path, body) {\n try {\n var opts = { method: method, headers: {} };\n if (body !== undefined) {\n opts.headers[\"Content-Type\"] = \"application/json\";\n opts.body = JSON.stringify(body);\n }\n var res = await fetch(__appearanceApiBase + path, opts);\n if (!res.ok) {\n var data = await res.json().catch(function() { return {}; });\n showToast(data.error || __t(\"common.requestFailed\"), \"error\");\n return { ok: false, error: data.error };\n }\n return await res.json();\n } catch (err) {\n showToast(__t(\"common.networkError\"), \"error\");\n return { ok: false, error: err.message };\n }\n}\n\nvar __themeData = {};\nvar __themeJsonOriginal = \"\";\nvar __navItems = [];\n\nasync function loadAppearance() {\n document.getElementById(\"ap-nav-list\").innerHTML = skeletonRows(3);\n // Load theme\n var tRes = await appearanceApi(\"GET\", \"/theme\");\n if (tRes) {\n __themeData = tRes.theme || {};\n populateThemeForm(__themeData);\n }\n\n // Load navigation\n var nRes = await appearanceApi(\"GET\", \"/navigation\");\n if (nRes) {\n __navItems = nRes.navigation || [];\n renderNavList();\n }\n}\n\nfunction populateThemeForm(theme) {\n // Try to extract from concepts-based format\n var concept = null;\n if (theme.concepts && theme.currentConceptId) {\n concept = theme.concepts.find(function(c) { return c.id === theme.currentConceptId; });\n }\n\n var prefer = concept ? (concept.prefer || \"system\") : (theme.prefer || \"system\");\n document.getElementById(\"ap-theme-mode\").value = prefer;\n\n var light = concept ? (concept.themeConfig && concept.themeConfig.light || {}) : (theme.light || {});\n var dark = concept ? (concept.themeConfig && concept.themeConfig.dark || {}) : (theme.dark || {});\n\n // Light palette\n setColorSafe(\"ap-light-primary\", light.palette && light.palette.primary && light.palette.primary.main, \"#1976d2\");\n setColorSafe(\"ap-light-secondary\", light.palette && light.palette.secondary && light.palette.secondary.main, \"#9c27b0\");\n setColorSafe(\"ap-light-bg\", light.palette && light.palette.background && light.palette.background.default, \"#ffffff\");\n setColorSafe(\"ap-light-text\", light.palette && light.palette.text && light.palette.text.primary, \"#212121\");\n\n // Dark palette\n setColorSafe(\"ap-dark-primary\", dark.palette && dark.palette.primary && dark.palette.primary.main, \"#90caf9\");\n setColorSafe(\"ap-dark-secondary\", dark.palette && dark.palette.secondary && dark.palette.secondary.main, \"#ce93d8\");\n setColorSafe(\"ap-dark-bg\", dark.palette && dark.palette.background && dark.palette.background.default, \"#121212\");\n setColorSafe(\"ap-dark-text\", dark.palette && dark.palette.text && dark.palette.text.primary, \"#ffffff\");\n\n // Lock\n document.getElementById(\"ap-theme-lock\").checked = !!(theme.meta && theme.meta.locked);\n\n // JSON editor\n var jsonStr = JSON.stringify(theme, null, 2);\n document.getElementById(\"ap-theme-json\").value = jsonStr;\n __themeJsonOriginal = jsonStr;\n}\n\nfunction setColorSafe(id, value, fallback) {\n var el = document.getElementById(id);\n if (!el) return;\n // color inputs need hex format\n if (value && value.charAt(0) === \"#\" && (value.length === 4 || value.length === 7)) {\n el.value = value;\n } else {\n el.value = fallback;\n }\n}\n\nfunction buildThemeFromForm() {\n var prefer = document.getElementById(\"ap-theme-mode\").value;\n var locked = document.getElementById(\"ap-theme-lock\").checked;\n\n // Only use JSON editor if user manually modified it\n var jsonText = document.getElementById(\"ap-theme-json\").value.trim();\n var jsonModified = jsonText && jsonText !== __themeJsonOriginal;\n if (jsonModified) {\n try {\n var parsed = JSON.parse(jsonText);\n // Add prefer and locked from form controls\n if (parsed.concepts && parsed.currentConceptId) {\n var c = parsed.concepts.find(function(c) { return c.id === parsed.currentConceptId; });\n if (c) c.prefer = prefer;\n } else {\n parsed.prefer = prefer;\n }\n parsed.meta = { locked: locked };\n return parsed;\n } catch(e) {\n showToast(__t(\"appearance.invalidJson\"), \"error\");\n return null;\n }\n }\n\n // Build concepts-based theme from color pickers\n var conceptId = (__themeData.currentConceptId) || \"default\";\n return {\n concepts: [{\n id: conceptId,\n name: \"Custom\",\n prefer: prefer,\n themeConfig: {\n light: {\n palette: {\n primary: { main: document.getElementById(\"ap-light-primary\").value },\n secondary: { main: document.getElementById(\"ap-light-secondary\").value },\n background: { default: document.getElementById(\"ap-light-bg\").value },\n text: { primary: document.getElementById(\"ap-light-text\").value }\n }\n },\n dark: {\n palette: {\n primary: { main: document.getElementById(\"ap-dark-primary\").value },\n secondary: { main: document.getElementById(\"ap-dark-secondary\").value },\n background: { default: document.getElementById(\"ap-dark-bg\").value },\n text: { primary: document.getElementById(\"ap-dark-text\").value }\n }\n },\n common: {}\n }\n }],\n currentConceptId: conceptId,\n meta: { locked: locked }\n };\n}\n\nasync function saveTheme() {\n var theme = buildThemeFromForm();\n if (!theme) return;\n var res = await appearanceApi(\"PUT\", \"/theme\", { theme: theme });\n if (res && res.ok) showToast(__t(\"appearance.themeUpdated\"));\n else showToast((res && res.error) || \"Failed to update theme\", \"error\");\n}\n\n// ─── Navigation ──────────────────────────────────────────────────────\n\nfunction renderNavList() {\n var list = document.getElementById(\"ap-nav-list\");\n if (!list) return;\n if (__navItems.length === 0) {\n list.innerHTML = '<p style=\"color:var(--color-text-muted);font-size:13px;padding:8px 0;\">' + __t(\"appearance.noNavItems\") + '</p>';\n return;\n }\n list.innerHTML = __navItems.map(function(item, i) {\n return '<div class=\"nav-item\" style=\"display:flex;align-items:center;gap:8px;padding:6px 0;border-bottom:1px solid var(--color-border);\">' +\n '<span style=\"min-width:24px;text-align:center;color:var(--color-text-muted);\">' + (i+1) + '</span>' +\n '<span style=\"flex:1;font-size:14px;\">' + escapeHtml(item.title || \"(untitled)\") + '</span>' +\n '<span style=\"color:var(--color-text-muted);font-size:12px;\">' + escapeHtml(item.link || \"\") + '</span>' +\n '<button class=\"btn btn-sm\" onclick=\"editNavItem(' + i + ')\" style=\"padding:2px 8px;\">' + __t(\"common.edit\") + '</button>' +\n '<button class=\"btn btn-sm\" onclick=\"moveNavItem(' + i + ',-1)\" style=\"padding:2px 6px;\" ' + (i === 0 ? \"disabled\" : \"\") + '>' + __t(\"appearance.up\") + '</button>' +\n '<button class=\"btn btn-sm\" onclick=\"moveNavItem(' + i + ',1)\" style=\"padding:2px 6px;\" ' + (i === __navItems.length - 1 ? \"disabled\" : \"\") + '>' + __t(\"appearance.down\") + '</button>' +\n '<button class=\"btn btn-sm btn-danger\" onclick=\"removeNavItem(' + i + ')\" style=\"padding:2px 8px;\">' + __t(\"common.delete\") + '</button>' +\n '</div>';\n }).join(\"\");\n}\n\nfunction escapeHtml(str) {\n return String(str).replace(/&/g,\"&\").replace(/</g,\"<\").replace(/>/g,\">\").replace(/\"/g,\""\");\n}\n\nfunction addNavItem() {\n var title = prompt(__t(\"appearance.navTitlePrompt\"));\n if (title === null) return;\n var link = prompt(__t(\"appearance.navLinkPrompt\"), \"/\");\n if (link === null) return;\n __navItems.push({ title: title, link: link });\n renderNavList();\n}\n\nfunction editNavItem(i) {\n var item = __navItems[i];\n if (!item) return;\n var title = prompt(__t(\"appearance.navTitlePrompt\"), item.title || \"\");\n if (title === null) return;\n var link = prompt(__t(\"appearance.navLinkPrompt\"), item.link || \"\");\n if (link === null) return;\n item.title = title;\n item.link = link;\n renderNavList();\n}\n\nfunction removeNavItem(i) {\n if (!confirm(__t(\"appearance.deleteNavItem\"))) return;\n __navItems.splice(i, 1);\n renderNavList();\n}\n\nfunction moveNavItem(i, dir) {\n var j = i + dir;\n if (j < 0 || j >= __navItems.length) return;\n var tmp = __navItems[i];\n __navItems[i] = __navItems[j];\n __navItems[j] = tmp;\n renderNavList();\n}\n\nasync function saveNavigation() {\n var res = await appearanceApi(\"PUT\", \"/navigation\", { navigation: __navItems });\n if (res && res.ok) showToast(__t(\"appearance.navUpdated\"));\n else showToast((res && res.error) || \"Failed to update navigation\", \"error\");\n}\n\nregisterTab(\"appearance\", loadAppearance);\n\n;\n\n// ─── Settings tab ─────────────────────────────────────────────────\n\nvar __settingsData = null;\nvar __oauthEditMode = false;\nvar __providerMeta = {\"google\":{\"name\":\"Google\",\"icon\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"18\\\" height=\\\"18\\\" viewBox=\\\"0 0 256 262\\\"><path fill=\\\"#4285F4\\\" d=\\\"M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622l38.755 30.023l2.685.268c24.659-22.774 38.875-56.282 38.875-96.027\\\"/><path fill=\\\"#34A853\\\" d=\\\"M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055c-34.523 0-63.824-22.773-74.269-54.25l-1.531.13l-40.298 31.187l-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1\\\"/><path fill=\\\"#FBBC05\\\" d=\\\"M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82c0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602z\\\"/><path fill=\\\"#EB4335\\\" d=\\\"M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0C79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251\\\"/></svg>\"},\"github\":{\"name\":\"GitHub\",\"icon\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"18\\\" height=\\\"18\\\" viewBox=\\\"0 0 256 250\\\" fill=\\\"currentColor\\\"><path d=\\\"M128.001 0C57.317 0 0 57.307 0 128.001c0 56.554 36.676 104.535 87.535 121.46c6.397 1.185 8.746-2.777 8.746-6.158c0-3.052-.12-13.135-.174-23.83c-35.61 7.742-43.124-15.103-43.124-15.103c-5.823-14.795-14.213-18.73-14.213-18.73c-11.613-7.944.876-7.78.876-7.78c12.853.902 19.621 13.19 19.621 13.19c11.417 19.568 29.945 13.911 37.249 10.64c1.149-8.272 4.466-13.92 8.127-17.116c-28.431-3.236-58.318-14.212-58.318-63.258c0-13.975 5-25.394 13.188-34.358c-1.329-3.224-5.71-16.242 1.24-33.874c0 0 10.749-3.44 35.21 13.121c10.21-2.836 21.16-4.258 32.038-4.307c10.878.049 21.837 1.47 32.066 4.307c24.431-16.56 35.165-13.12 35.165-13.12c6.967 17.63 2.584 30.65 1.255 33.873c8.207 8.964 13.173 20.383 13.173 34.358c0 49.163-29.944 59.988-58.447 63.157c4.591 3.972 8.682 11.762 8.682 23.704c0 17.126-.148 30.91-.148 35.126c0 3.407 2.304 7.398 8.792 6.14C219.37 232.5 256 184.537 256 128.002C256 57.307 198.691 0 128.001 0\\\"/></svg>\"},\"apple\":{\"name\":\"Apple\",\"icon\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"18\\\" height=\\\"18\\\" viewBox=\\\"0 0 256 315\\\" fill=\\\"currentColor\\\"><path d=\\\"M213.803 167.03c.442 47.58 41.74 63.413 42.197 63.615c-.35 1.116-6.599 22.563-21.757 44.716c-13.104 19.153-26.705 38.235-48.13 38.63c-21.05.388-27.82-12.483-51.888-12.483c-24.061 0-31.582 12.088-51.51 12.871c-20.68.783-36.428-20.71-49.64-39.793c-27-39.033-47.633-110.3-19.928-158.406c13.763-23.89 38.36-39.017 65.056-39.405c20.307-.388 39.475 13.675 51.889 13.675c12.406 0 35.699-16.895 60.186-14.414c10.25.427 39.026 4.14 57.503 31.186c-1.49.923-34.335 20.044-33.978 59.808M174.24 50.199c10.98-13.29 18.369-31.79 16.353-50.199c-15.826.636-34.962 10.546-46.314 23.828c-10.173 11.763-19.082 30.589-16.678 48.633c17.64 1.365 35.66-8.964 46.639-22.262\\\"/></svg>\"},\"twitter\":{\"name\":\"Twitter\",\"icon\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"18\\\" height=\\\"18\\\" viewBox=\\\"0 0 256 256\\\" fill=\\\"currentColor\\\"><path d=\\\"M149.079 108.399L242.33 0h-22.098l-80.97 94.12L74.59 0H0l97.796 142.328L0 256h22.1l85.507-99.395L175.905 256h74.59L149.073 108.399zM118.81 143.58l-9.909-14.172l-78.84-112.773h33.943l63.625 91.011l9.909 14.173l82.705 118.3H186.3l-67.49-96.533z\\\"/></svg>\"},\"auth0\":{\"name\":\"Auth0\",\"icon\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"18\\\" height=\\\"18\\\" viewBox=\\\"0 0 256 256\\\"><path fill=\\\"#EB5424\\\" d=\\\"M203.97 200.38L167.47 88l-39.5 112.38h75.98zm0-144.76L167.47 168l75.97-26.16l19.76-60.82c6.64-20.4-1.94-42.72-21.18-52.76l-38.06 27.36zM52.03 55.62L88.54 168l39.5-112.38H52.02zM52.03 200.38L88.54 88L12.56 114.16l-5.92 18.2c-6.64 20.4 1.94 42.72 21.18 52.76l24.2-17.38l.01.01v-.01l-.01.01.01-.37z\\\"/></svg>\"}};\n\nfunction loadSettings() {\n document.getElementById(\"st-oauth-list\").innerHTML = skeletonRows(3);\n api(\"GET\", \"/settings\").then(function(data) {\n if (!data.ok) return;\n __settingsData = data.settings;\n renderOAuthProviders();\n renderBuiltinProviders();\n renderSessionConfig();\n renderEmailConfig();\n fedLoad();\n });\n}\n\n// ─── OAuth Providers (card grid) ─────────────────────────────────\n\nfunction renderOAuthProviders() {\n var wrap = document.getElementById(\"st-oauth-list\");\n if (!wrap || !__settingsData) return;\n var providers = __settingsData.oauthProviders || {};\n var keys = Object.keys(providers);\n if (keys.length === 0) {\n wrap.innerHTML = '<div class=\"settings-row\" style=\"color:var(--text-tertiary)\">' + __t(\"settings.noOAuth\") + '</div>';\n return;\n }\n keys.sort(function(a, b) {\n return (providers[a].order || 999) - (providers[b].order || 999);\n });\n var html = \"\";\n for (var i = 0; i < keys.length; i++) {\n var name = keys[i];\n var p = providers[name];\n var meta = __providerMeta[name];\n var displayName = meta ? meta.name : (name.charAt(0).toUpperCase() + name.slice(1));\n var iconHtml = meta && meta.icon ? '<span class=\"st-oauth-card-icon\">' + meta.icon + '</span>' : \"\";\n var statusDot = p.enabled !== false\n ? '<span class=\"st-oauth-dot st-oauth-dot-on\"></span>'\n : '<span class=\"st-oauth-dot st-oauth-dot-off\"></span>';\n html += '<div class=\"st-oauth-card\" onclick=\"openOAuthDialog(\\'edit\\',\\'' + escapeHtml(name) + '\\')\" title=\"' + escapeHtml(displayName) + '\">'\n + iconHtml\n + '<span class=\"st-oauth-card-name\">' + escapeHtml(displayName) + '</span>'\n + statusDot\n + '</div>';\n }\n wrap.innerHTML = html;\n}\n\nfunction escapeHtml(s) {\n var d = document.createElement(\"div\");\n d.textContent = s;\n return d.innerHTML;\n}\n\nfunction renderProviderPicker(selectedProvider) {\n var picker = document.getElementById(\"st-oauth-provider-picker\");\n if (!picker) return;\n var configuredProviders = (__settingsData && __settingsData.oauthProviders) ? __settingsData.oauthProviders : {};\n var html = \"\";\n var keys = Object.keys(__providerMeta);\n for (var i = 0; i < keys.length; i++) {\n var id = keys[i];\n var meta = __providerMeta[id];\n var isSelected = id === selectedProvider;\n var isAdded = !__oauthEditMode && !!configuredProviders[id];\n var cls = \"provider-card\";\n if (isSelected) cls += \" selected\";\n if (isAdded) cls += \" added\";\n var badge = isAdded ? '<span class=\"provider-added-badge\">' + __t(\"settings.added\") + '</span>' : \"\";\n var onclick = isAdded ? \"\" : 'onclick=\"selectOAuthProvider(\\'' + id + '\\')\"';\n html += '<div class=\"' + cls + '\" data-provider=\"' + id + '\" ' + onclick + '>'\n + '<span class=\"provider-icon\">' + meta.icon + '</span>'\n + '<span class=\"provider-name\">' + escapeHtml(meta.name) + '</span>'\n + badge\n + '</div>';\n }\n picker.innerHTML = html;\n}\n\nfunction selectOAuthProvider(id) {\n document.getElementById(\"st-oauth-provider\").value = id;\n renderProviderPicker(id);\n onOAuthProviderChange();\n}\n\nfunction openOAuthDialog(mode, providerName) {\n __oauthEditMode = mode === \"edit\";\n var title = document.getElementById(\"st-oauth-dialog-title\");\n title.textContent = __oauthEditMode ? __t(\"settings.editOAuthTitle\") : __t(\"settings.addOAuthTitle\");\n\n var defaultProvider = \"google\";\n if (__oauthEditMode && providerName) {\n defaultProvider = providerName;\n } else if (__settingsData && __settingsData.oauthProviders) {\n var configured = __settingsData.oauthProviders;\n var keys = Object.keys(__providerMeta);\n for (var i = 0; i < keys.length; i++) {\n if (!configured[keys[i]]) { defaultProvider = keys[i]; break; }\n }\n }\n document.getElementById(\"st-oauth-provider\").value = defaultProvider;\n\n var picker = document.getElementById(\"st-oauth-provider-picker\");\n var display = document.getElementById(\"st-oauth-provider-display\");\n if (__oauthEditMode) {\n picker.style.display = \"none\";\n var meta = __providerMeta[defaultProvider] || { name: defaultProvider, icon: \"\" };\n display.style.display = \"flex\";\n display.innerHTML = '<span style=\"display:inline-flex;width:20px;height:20px\">' + meta.icon + '</span>'\n + '<span style=\"font-size:14px;font-weight:500;color:var(--text)\">' + escapeHtml(meta.name) + '</span>';\n } else {\n picker.style.display = \"\";\n display.style.display = \"none\";\n renderProviderPicker(defaultProvider);\n }\n\n document.getElementById(\"st-oauth-clientid\").value = \"\";\n document.getElementById(\"st-oauth-secret\").value = \"\";\n document.getElementById(\"st-oauth-teamid\").value = \"\";\n document.getElementById(\"st-oauth-keyid\").value = \"\";\n document.getElementById(\"st-oauth-privatekey\").value = \"\";\n document.getElementById(\"st-oauth-serviceid\").value = \"\";\n document.getElementById(\"st-oauth-bundleid\").value = \"\";\n document.getElementById(\"st-oauth-domain\").value = \"\";\n document.getElementById(\"st-oauth-enabled\").checked = true;\n document.getElementById(\"st-oauth-order\").value = \"0\";\n\n if (__oauthEditMode && providerName && __settingsData) {\n var p = __settingsData.oauthProviders[providerName];\n if (p) {\n document.getElementById(\"st-oauth-clientid\").value = p.clientId || \"\";\n document.getElementById(\"st-oauth-secret\").value = p.clientSecret || \"\";\n document.getElementById(\"st-oauth-teamid\").value = p.teamId || \"\";\n document.getElementById(\"st-oauth-keyid\").value = p.keyId || \"\";\n document.getElementById(\"st-oauth-privatekey\").value = p.privateKey || \"\";\n document.getElementById(\"st-oauth-serviceid\").value = p.serviceId || \"\";\n document.getElementById(\"st-oauth-bundleid\").value = p.bundleId || \"\";\n document.getElementById(\"st-oauth-domain\").value = p.domain || \"\";\n document.getElementById(\"st-oauth-enabled\").checked = p.enabled !== false;\n document.getElementById(\"st-oauth-order\").value = String(p.order ?? 0);\n }\n }\n\n onOAuthProviderChange();\n updateCallbackUrl();\n showDialog(\"st-oauth-dialog\");\n}\n\nfunction onOAuthProviderChange() {\n var provider = document.getElementById(\"st-oauth-provider\").value;\n var appleFields = document.querySelectorAll('[data-oauth-field=\"apple\"]');\n var auth0Fields = document.querySelectorAll('[data-oauth-field=\"auth0\"]');\n for (var i = 0; i < appleFields.length; i++) {\n appleFields[i].style.display = provider === \"apple\" ? \"\" : \"none\";\n }\n for (var i = 0; i < auth0Fields.length; i++) {\n auth0Fields[i].style.display = provider === \"auth0\" ? \"\" : \"none\";\n }\n updateCallbackUrl();\n}\n\nfunction updateCallbackUrl() {\n var provider = document.getElementById(\"st-oauth-provider\").value;\n var el = document.getElementById(\"st-oauth-callback\");\n if (el) el.textContent = location.origin + \"/.well-known/service/oauth/callback/\" + provider;\n}\n\nfunction copyCallbackUrl() {\n var el = document.getElementById(\"st-oauth-callback\");\n var btn = document.getElementById(\"st-copy-callback-btn\");\n if (el && btn && navigator.clipboard) {\n var originalText = btn.textContent;\n navigator.clipboard.writeText(el.textContent).then(function() {\n btn.innerHTML = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" style=\"vertical-align:middle\"><path d=\"M5 13l4 4L19 7\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg> ' + __t(\"common.copied\");\n btn.disabled = true;\n setTimeout(function() { btn.textContent = originalText; btn.disabled = false; }, 2000);\n });\n }\n}\n\nfunction saveOAuthProvider() {\n var provider = document.getElementById(\"st-oauth-provider\").value;\n var body = {\n clientId: document.getElementById(\"st-oauth-clientid\").value,\n clientSecret: document.getElementById(\"st-oauth-secret\").value,\n enabled: document.getElementById(\"st-oauth-enabled\").checked,\n order: parseInt(document.getElementById(\"st-oauth-order\").value, 10) || 0,\n };\n\n if (provider === \"apple\") {\n body.teamId = document.getElementById(\"st-oauth-teamid\").value;\n body.keyId = document.getElementById(\"st-oauth-keyid\").value;\n body.privateKey = document.getElementById(\"st-oauth-privatekey\").value;\n body.serviceId = document.getElementById(\"st-oauth-serviceid\").value;\n body.bundleId = document.getElementById(\"st-oauth-bundleid\").value;\n }\n if (provider === \"auth0\") {\n body.domain = document.getElementById(\"st-oauth-domain\").value;\n }\n\n api(\"PUT\", \"/settings/oauth/\" + provider, body).then(function(data) {\n if (data.ok) {\n closeDialog(\"st-oauth-dialog\");\n showToast(__t(\"settings.oauthSaved\"), \"success\");\n loadSettings();\n }\n });\n}\n\nasync function deleteOAuthProvider(name) {\n var ok = await confirmDialog({ title: __t(\"settings.deleteProvider\", { name: name }), message: __t(\"settings.deleteProviderMsg\"), confirmLabel: __t(\"common.delete\"), danger: true });\n if (!ok) return;\n var data = await api(\"DELETE\", \"/settings/oauth/\" + name);\n if (data.ok) {\n showToast(__t(\"settings.providerDeleted\"), \"success\");\n loadSettings();\n }\n}\n\n// ─── Built-in Providers ──────────────────────────────────────────\n\nfunction renderBuiltinProviders() {\n if (!__settingsData) return;\n var bp = __settingsData.builtinProviders || {};\n var email = __settingsData.email || {};\n var emailConfigured = !!(email.resendApiKey && email.fromAddress);\n\n document.getElementById(\"st-bp-passkey\").checked = bp.passkey ? bp.passkey.enabled !== false : true;\n document.getElementById(\"st-bp-email\").checked = bp.email ? bp.email.enabled !== false : true;\n document.getElementById(\"st-bp-wallet\").checked = bp.wallet ? bp.wallet.enabled !== false : true;\n\n // Show hint if email not configured\n var hint = document.getElementById(\"st-email-hint\");\n var emailToggle = document.getElementById(\"st-bp-email\");\n if (hint && emailToggle) {\n if (!emailConfigured) {\n hint.classList.remove(\"hidden\");\n emailToggle.disabled = true;\n emailToggle.checked = false;\n } else {\n hint.classList.add(\"hidden\");\n emailToggle.disabled = false;\n }\n }\n}\n\nfunction saveBuiltinProviders() {\n var body = {\n passkey: { enabled: document.getElementById(\"st-bp-passkey\").checked },\n email: { enabled: document.getElementById(\"st-bp-email\").checked },\n wallet: { enabled: document.getElementById(\"st-bp-wallet\").checked },\n };\n api(\"PUT\", \"/settings/builtin-providers\", body).then(function(data) {\n if (data.ok) showToast(__t(\"settings.saved\"), \"success\");\n });\n}\n\n// ─── Session ─────────────────────────────────────────────────────\n\nfunction updateSessionLabel() {\n var ttl = document.getElementById(\"st-session-ttl\").value;\n document.getElementById(\"st-session-lifetime-label\").textContent = __t(\"settings.jwtLifetime\", { days: ttl });\n document.getElementById(\"st-session-ttl\").title = __t(\"settings.jwtLifetime\", { days: ttl });\n}\n\nfunction renderSessionConfig() {\n if (!__settingsData) return;\n var s = __settingsData.session || {};\n var ttl = s.jwtTtlDays || 7;\n document.getElementById(\"st-session-ttl\").value = ttl;\n updateSessionLabel();\n}\n\nfunction saveSessionConfig() {\n var ttl = parseInt(document.getElementById(\"st-session-ttl\").value, 10);\n api(\"PUT\", \"/settings/session\", { jwtTtlDays: ttl }).then(function(data) {\n if (data.ok) showToast(__t(\"settings.sessionSaved\"), \"success\");\n });\n}\n\n// ─── Email ───────────────────────────────────────────────────────\n\nfunction renderEmailConfig() {\n if (!__settingsData) return;\n var e = __settingsData.email || {};\n document.getElementById(\"st-email-apikey\").value = e.resendApiKey || \"\";\n document.getElementById(\"st-email-from\").value = e.fromAddress || \"\";\n}\n\nfunction saveEmailConfig() {\n var body = {\n resendApiKey: document.getElementById(\"st-email-apikey\").value,\n fromAddress: document.getElementById(\"st-email-from\").value,\n };\n api(\"PUT\", \"/settings/email\", body).then(function(data) {\n if (data.ok) {\n showToast(__t(\"settings.emailSaved\"), \"success\");\n // Re-render builtin providers to update email toggle state\n loadSettings();\n }\n });\n}\n\nregisterTab(\"settings\", loadSettings);\n\n\n// ─── Federation (Unified Login) ──────────────────────────────\n\nvar __fedData = null;\nvar __fedLoading = false;\n\nvar FED_ICON_REMOVE = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 6h18\"/><path d=\"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6\"/><path d=\"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2\"/></svg>';\nvar FED_ICON_REFRESH = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\"/><path d=\"M3 3v5h5\"/><path d=\"M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16\"/><path d=\"M21 21v-5h-5\"/></svg>';\nvar FED_ICON_CHECK = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"/></svg>';\nvar FED_ICON_UNLINK = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M15 7h3a5 5 0 0 1 0 10h-3m-6 0H6a5 5 0 0 1 0-10h3\"/><line x1=\"2\" y1=\"2\" x2=\"22\" y2=\"22\"/></svg>';\nvar FED_ICON_DISABLE = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"4.93\" y1=\"4.93\" x2=\"19.07\" y2=\"19.07\"/></svg>';\nvar FED_ICON_ENABLE = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18.36 6.64a9 9 0 1 1-12.73 0\"/><line x1=\"12\" y1=\"2\" x2=\"12\" y2=\"12\"/></svg>';\n\nfunction fedIconBtn(icon, title, onclick, variant) {\n var cls = variant === \"danger\" ? \"ap-icon-btn ap-icon-btn-danger\" : variant === \"success\" ? \"ap-icon-btn ap-icon-btn-success\" : \"ap-icon-btn\";\n return '<button class=\"' + cls + '\" title=\"' + fedEscAttr(title) + '\" onclick=\"' + onclick + '\">' + icon + '</button>';\n}\n\nfunction fedNormalizeUrl(input) {\n var s = (input || \"\").trim();\n if (!s) return \"\";\n if (!/^https?:\\/\\//i.test(s)) s = \"https://\" + s;\n try { return new URL(s).origin; } catch(e) { return s; }\n}\n\nfunction fedFormatUrl(el) {\n clearTimeout(el._fedTimer);\n el._fedTimer = setTimeout(function() {\n var v = el.value.trim();\n if (!v) return;\n var norm = fedNormalizeUrl(v);\n if (norm && norm !== v && norm !== \"https://\") {\n el.value = norm;\n }\n }, 800);\n}\n\nfunction fedLoad() {\n api(\"GET\", \"/admin/federation\").then(function(data) {\n if (!data || data.error) {\n __fedData = { role: null, enabled: false, sites: [], delegation: { exists: false } };\n } else {\n __fedData = data;\n }\n fedRender();\n });\n}\n\nfunction fedRender() {\n var el = document.getElementById(\"fed-section\");\n if (!el || !__fedData) return;\n if (!__fedData.role) {\n el.innerHTML = fedRenderUnconfigured();\n } else if (__fedData.role === \"master\") {\n el.innerHTML = fedRenderMaster();\n } else {\n el.innerHTML = fedRenderMember();\n }\n}\n\nfunction fedRenderUnconfigured() {\n return '<p class=\"settings-card-desc\">' + __t(\"federation.desc\") + '</p>'\n + '<div class=\"fed-init-buttons\">'\n + '<button class=\"btn\" id=\"fed-enable-btn\" onclick=\"fedEnable()\"' + (__fedLoading ? ' disabled' : '') + '>'\n + (__fedLoading ? '<span class=\"spinner\"></span> ' : '') + __t(\"federation.enableMaster\") + '</button>'\n + '<button class=\"btn btn-secondary\" onclick=\"fedOpenJoin()\"' + (__fedLoading ? ' disabled' : '') + '>'\n + __t(\"federation.joinMember\") + '</button>'\n + '</div>';\n}\n\nfunction fedRenderMaster() {\n var html = '<div style=\"padding:12px 20px;display:flex;align-items:center;gap:8px\">'\n + '<span class=\"fed-status-badge master\">★ ' + __t(\"federation.masterSite\") + '</span>'\n + '<span style=\"font-size:13px;color:var(--text-secondary)\">' + __t(\"federation.masterDesc\") + '</span>'\n + '</div>';\n\n // Sites header with refresh button\n html += '<div style=\"padding:0 20px 8px;display:flex;justify-content:space-between;align-items:center\">'\n + '<span style=\"font-size:13px;font-weight:600;color:var(--text)\">' + __t(\"federation.sites\") + '</span>'\n + '<button class=\"ap-icon-btn\" title=\"' + __t(\"federation.refresh\") + '\" onclick=\"fedRefresh()\" id=\"fed-refresh-btn\">' + FED_ICON_REFRESH + '</button>'\n + '</div>';\n\n // Sort: master first, then approved, then pending\n var sites = (__fedData.sites || []).slice().sort(function(a, b) {\n if (a.isMaster) return -1;\n if (b.isMaster) return 1;\n if (a.status === \"pending\" && b.status !== \"pending\") return 1;\n if (b.status === \"pending\" && a.status !== \"pending\") return -1;\n return 0;\n });\n var currentOrigin = location.origin;\n for (var i = 0; i < sites.length; i++) {\n var s = sites[i];\n var star = s.isMaster ? '<span class=\"fed-star\">★</span> ' : '';\n var isMe = s.url && fedNormalizeUrl(s.url) === currentOrigin;\n var meTag = isMe ? ' <span style=\"font-size:10px;padding:1px 5px;border-radius:999px;background:var(--blue-light,#dbeafe);color:var(--blue-muted,#1d4ed8);font-weight:500\">' + __t(\"federation.currentSite\") + '</span>' : '';\n var isPending = s.status === \"pending\";\n var statusBadge = isPending\n ? ' <span style=\"font-size:11px;padding:1px 6px;border-radius:999px;background:var(--yellow-light,#fef3c7);color:var(--yellow-text,#92400e);font-weight:500\">' + __t(\"federation.pending\") + '</span>'\n : '';\n\n var actions = '';\n if (s.isMaster) {\n // Disabled placeholder buttons for alignment with member rows\n actions = '<button class=\"ap-icon-btn\" disabled style=\"opacity:0.25;cursor:default\">' + FED_ICON_DISABLE + '</button>'\n + '<button class=\"ap-icon-btn\" disabled style=\"opacity:0.25;cursor:default\">' + FED_ICON_UNLINK + '</button>';\n } else if (isPending) {\n actions = fedIconBtn(FED_ICON_CHECK, __t(\"federation.approve\"), \"fedApproveSite('\" + s.did + \"')\", \"success\")\n + fedIconBtn(FED_ICON_REMOVE, __t(\"federation.reject\"), \"fedRejectSite('\" + s.did + \"','\" + fedEscAttr(s.name) + \"')\", \"danger\");\n } else {\n // Approved member: disable/enable toggle + unbind\n if (s.enabled === false) {\n actions = fedIconBtn(FED_ICON_ENABLE, __t(\"federation.enable\"), \"fedToggleMember('\" + s.did + \"',true)\")\n + fedIconBtn(FED_ICON_UNLINK, __t(\"federation.unbind\"), \"fedUnbindSite('\" + s.did + \"','\" + fedEscAttr(s.name) + \"')\", \"danger\");\n } else {\n actions = fedIconBtn(FED_ICON_DISABLE, __t(\"federation.disable\"), \"fedToggleMember('\" + s.did + \"',false)\", \"danger\")\n + fedIconBtn(FED_ICON_UNLINK, __t(\"federation.unbind\"), \"fedUnbindSite('\" + s.did + \"','\" + fedEscAttr(s.name) + \"')\", \"danger\");\n }\n }\n // Disabled badge for approved but disabled members\n if (!isPending && !s.isMaster && s.enabled === false) {\n statusBadge = ' <span style=\"font-size:11px;padding:1px 6px;border-radius:999px;background:var(--bg-tertiary,#e5e7eb);color:var(--text-tertiary)\">' + __t(\"federation.disabled\") + '</span>';\n }\n\n html += '<div class=\"fed-site-row\">'\n + '<div class=\"fed-site-name\">' + star + '<span class=\"fed-site-label\">' + fedEscHtml(s.name) + '</span>' + meTag + statusBadge + '</div>'\n + '<div class=\"fed-site-url\" title=\"' + fedEscAttr(s.url) + '\"><a href=\"' + fedEscAttr(s.url) + '\" target=\"_blank\">' + fedEscHtml(s.url) + '</a></div>'\n + '<div class=\"fed-site-actions\">' + actions + '</div>'\n + '</div>';\n }\n\n // Disband\n html += '<div style=\"padding:16px 20px;border-top:1px solid var(--border)\">'\n + '<button class=\"btn btn-danger btn-sm\" style=\"width:auto\" onclick=\"fedDisband()\">' + __t(\"federation.disband\") + '</button>'\n + '</div>';\n\n return html;\n}\n\nfunction fedRenderMember() {\n var enabled = __fedData.enabled;\n var isPending = !enabled && __fedData.role === \"member\";\n\n // Header: badge + status + toggle (no toggle for pending)\n var badgeClass = isPending ? 'style=\"background:var(--yellow-light,#fef3c7);color:var(--yellow-text,#92400e)\"' : '';\n var badgeText = isPending ? __t(\"federation.pending\") : __t(\"federation.member\");\n var statusText = isPending ? __t(\"federation.pendingDesc\") : (enabled ? __t(\"federation.enabled\") : __t(\"federation.disabled\"));\n\n var html = '<div style=\"padding:12px 20px;display:flex;align-items:center;gap:12px;justify-content:space-between\">'\n + '<div style=\"display:flex;align-items:center;gap:8px\">'\n + '<span class=\"fed-status-badge member\" ' + badgeClass + '>' + badgeText + '</span>'\n + '<span style=\"font-size:13px;color:var(--text-secondary)\">' + statusText + '</span>'\n + '</div>';\n if (!isPending) {\n html += '<label class=\"toggle-switch\">'\n + '<input type=\"checkbox\" ' + (enabled ? 'checked' : '') + ' onchange=\"fedToggle(this.checked)\" />'\n + '<span class=\"toggle-slider\"></span>'\n + '</label>';\n }\n html += '</div>';\n\n // Master URL\n if (__fedData.masterUrl) {\n html += '<div style=\"padding:0 20px 8px;font-size:13px;color:var(--text-secondary)\">'\n + __t(\"federation.delegatedTo\") + ' <a href=\"' + fedEscAttr(__fedData.masterUrl) + '\" target=\"_blank\" style=\"color:var(--blue)\">' + fedEscHtml(__fedData.masterUrl) + '</a>'\n + '</div>';\n }\n\n // Site list — show all sites with role labels; pending sites get \"pending\" instead of \"member\"\n if (__fedData.sites && __fedData.sites.length > 0) {\n // Sort: master first, then approved, then pending\n var memberSites = __fedData.sites.slice().sort(function(a, b) {\n if (a.isMaster) return -1;\n if (b.isMaster) return 1;\n if (a.status === \"pending\" && b.status !== \"pending\") return 1;\n if (b.status === \"pending\" && a.status !== \"pending\") return -1;\n return 0;\n });\n html += '<div style=\"padding:8px 20px 4px;display:flex;justify-content:space-between;align-items:center\">'\n + '<span style=\"font-size:13px;font-weight:600;color:var(--text)\">' + __t(\"federation.sites\") + '</span>'\n + '<button class=\"ap-icon-btn\" title=\"' + __t(\"federation.refresh\") + '\" onclick=\"fedRefresh()\" id=\"fed-refresh-btn\">' + FED_ICON_REFRESH + '</button>'\n + '</div>';\n var currentOrigin = location.origin;\n for (var i = 0; i < memberSites.length; i++) {\n var s = memberSites[i];\n var star = s.isMaster ? '<span class=\"fed-star\">★</span> ' : '';\n var isMe = s.url && fedNormalizeUrl(s.url) === currentOrigin;\n var meTag = isMe ? ' <span style=\"font-size:10px;padding:1px 5px;border-radius:999px;background:var(--blue-light,#dbeafe);color:var(--blue-muted,#1d4ed8);font-weight:500\">' + __t(\"federation.currentSite\") + '</span>' : '';\n var isPendingSite = s.status === \"pending\";\n var role = s.isMaster ? __t(\"federation.master\")\n : (isPendingSite\n ? '<span style=\"color:var(--yellow-text,#92400e);font-weight:500\">' + __t(\"federation.pending\") + '</span>'\n : __t(\"federation.member\"));\n html += '<div class=\"fed-site-row\">'\n + '<div class=\"fed-site-name\">' + star + '<span class=\"fed-site-label\">' + fedEscHtml(s.name) + '</span>' + meTag + '</div>'\n + '<div class=\"fed-site-url\" title=\"' + fedEscAttr(s.url) + '\"><a href=\"' + fedEscAttr(s.url) + '\" target=\"_blank\">' + fedEscHtml(s.url) + '</a></div>'\n + '<div class=\"fed-site-role\">' + role + '</div>'\n + '</div>';\n }\n }\n\n html += '<div style=\"padding:16px 20px;border-top:1px solid var(--border)\">'\n + '<button class=\"btn btn-danger btn-sm\" style=\"width:auto\" onclick=\"fedLeave()\">' + __t(\"federation.leave\") + '</button>'\n + '</div>';\n\n return html;\n}\n\nfunction fedEscHtml(str) {\n if (!str) return '';\n return str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/\"/g,'"');\n}\nfunction fedEscAttr(str) {\n if (!str) return '';\n return str.replace(/&/g,'&').replace(/\"/g,'"').replace(/'/g,''');\n}\n\nfunction fedSetLoading(loading) {\n __fedLoading = loading;\n fedRender();\n}\n\nfunction fedEnable() {\n confirmDialog({\n title: __t(\"federation.enableConfirm\"),\n message: __t(\"federation.enableMasterDesc\"),\n confirmLabel: __t(\"federation.enableMaster\"),\n }).then(function(ok) {\n if (!ok) return;\n fedSetLoading(true);\n api(\"POST\", \"/admin/federation/enable\", { url: location.origin, name: document.title || \"App\" }).then(function(data) {\n __fedLoading = false;\n if (data && !data.error) { __fedData = data; fedRender(); showToast(__t(\"federation.enabled\"), \"success\"); }\n else { showToast(data.error || \"Failed\", \"error\"); fedRender(); }\n });\n });\n}\n\nfunction fedOpenJoin() {\n document.getElementById(\"fed-join-url\").value = \"\";\n showDialog(\"fed-join-dialog\");\n}\n\nfunction fedJoinSubmit() {\n var masterUrl = fedNormalizeUrl(document.getElementById(\"fed-join-url\").value);\n if (!masterUrl) { showToast(__t(\"federation.masterUrl\") + \" is required\", \"error\"); return; }\n\n var btn = document.getElementById(\"fed-join-btn\");\n btn.disabled = true;\n btn.innerHTML = '<span class=\"spinner\"></span>';\n\n api(\"POST\", \"/admin/federation/join\", { masterUrl: masterUrl }).then(function(data) {\n btn.disabled = false;\n btn.textContent = __t(\"federation.joinMember\");\n if (data && !data.error) {\n __fedData = data;\n fedRender();\n closeDialog(\"fed-join-dialog\");\n showToast(__t(\"federation.pendingApproval\"), \"success\");\n } else {\n showToast(data.error || __t(\"federation.targetNotEnabled\"), \"error\");\n }\n });\n}\n\nfunction fedToggle(enabled) {\n if (!enabled) {\n confirmDialog({\n title: __t(\"federation.disableTitle\"),\n message: __t(\"federation.disableDesc\") + \" \" + __t(\"federation.walletSafe\") + \" \" + __t(\"federation.sessionWarning\"),\n confirmLabel: __t(\"common.confirm\"),\n danger: true,\n }).then(function(ok) {\n if (!ok) { fedRender(); return; }\n api(\"PUT\", \"/admin/federation/toggle\", { enabled: false }).then(function(data) {\n if (data && !data.error) { __fedData = data; fedRender(); }\n });\n });\n var cb = document.querySelector(\"#fed-section input[type=checkbox]\");\n if (cb) cb.checked = true;\n } else {\n api(\"PUT\", \"/admin/federation/toggle\", { enabled: true }).then(function(data) {\n if (data && !data.error) { __fedData = data; fedRender(); }\n });\n }\n}\n\nfunction fedApproveSite(did) {\n api(\"POST\", \"/admin/federation/sites/\" + encodeURIComponent(did) + \"/approve\").then(function(data) {\n if (data && !data.error) {\n __fedData = data;\n fedRender();\n showToast(__t(\"federation.approved\"), \"success\");\n } else {\n showToast(data.error || \"Failed\", \"error\");\n }\n });\n}\n\nfunction fedRejectSite(did, name) {\n confirmDialog({\n title: __t(\"federation.rejectTitle\"),\n message: __t(\"federation.rejectDesc\"),\n confirmLabel: __t(\"federation.reject\"),\n danger: true,\n }).then(function(ok) {\n if (!ok) return;\n api(\"DELETE\", \"/admin/federation/sites/\" + encodeURIComponent(did)).then(function(data) {\n if (data && !data.error) { __fedData = data; fedRender(); showToast(__t(\"federation.reject\") + \" \\u2713\", \"success\"); }\n });\n });\n}\n\nfunction fedRefresh() {\n var btn = document.getElementById(\"fed-refresh-btn\");\n if (btn) { btn.style.opacity = \"0.5\"; btn.style.pointerEvents = \"none\"; }\n fedLoad();\n setTimeout(function() { if (btn) { btn.style.opacity = \"\"; btn.style.pointerEvents = \"\"; } }, 500);\n}\n\n// ─── Actions ─────────────────────────────────────────────────\n\nfunction fedToggleMember(did, enabled) {\n if (!enabled) {\n confirmDialog({\n title: __t(\"federation.disableTitle\"),\n message: __t(\"federation.disableDesc\") + \" \" + __t(\"federation.walletSafe\") + \" \" + __t(\"federation.sessionWarning\"),\n confirmLabel: __t(\"federation.disable\"),\n danger: true,\n }).then(function(ok) {\n if (!ok) return;\n api(\"PUT\", \"/admin/federation/sites/\" + encodeURIComponent(did) + \"/toggle\", { enabled: false }).then(function(data) {\n if (data && !data.error) { __fedData = data; fedRender(); showToast(__t(\"federation.disabled\"), \"success\"); }\n });\n });\n } else {\n api(\"PUT\", \"/admin/federation/sites/\" + encodeURIComponent(did) + \"/toggle\", { enabled: true }).then(function(data) {\n if (data && !data.error) { __fedData = data; fedRender(); showToast(__t(\"federation.enabled\"), \"success\"); }\n });\n }\n}\n\nfunction fedUnbindSite(did, name) {\n confirmDialog({\n title: __t(\"federation.unbindTitle\"),\n message: __t(\"federation.unbindDesc\") + \" \" + __t(\"federation.walletSafe\") + \" \" + __t(\"federation.sessionWarning\"),\n confirmLabel: __t(\"federation.unbind\"),\n danger: true,\n }).then(function(ok) {\n if (!ok) return;\n api(\"DELETE\", \"/admin/federation/sites/\" + encodeURIComponent(did)).then(function(data) {\n if (data && !data.error) { __fedData = data; fedRender(); showToast(__t(\"federation.unbind\") + \" \\u2713\", \"success\"); }\n });\n });\n}\n\nfunction fedDisband() {\n confirmDialog({\n title: __t(\"federation.disbandTitle\"),\n message: __t(\"federation.disbandDesc\") + \" \" + __t(\"federation.sessionWarning\"),\n confirmLabel: __t(\"federation.disband\"),\n danger: true,\n }).then(function(ok) {\n if (!ok) return;\n api(\"POST\", \"/admin/federation/disband\").then(function(data) {\n if (data && !data.error) { __fedData = data; fedRender(); showToast(__t(\"federation.disband\") + \" \\u2713\", \"success\"); }\n });\n });\n}\n\nfunction fedLeave() {\n confirmDialog({\n title: __t(\"federation.leaveTitle\"),\n message: __t(\"federation.leaveDesc\") + \" \" + __t(\"federation.sessionWarning\"),\n confirmLabel: __t(\"federation.leave\"),\n danger: true,\n }).then(function(ok) {\n if (!ok) return;\n api(\"POST\", \"/admin/federation/leave\").then(function(data) {\n if (data && !data.error) { __fedData = data; fedRender(); showToast(__t(\"federation.leave\") + \" \\u2713\", \"success\"); }\n });\n });\n}\n\n"};
|
|
2
|
+
export default {"login.d3f05790.js":"\"use strict\";var __LoginBundle=(()=>{var ie=Object.defineProperty;var Te=Object.getOwnPropertyDescriptor;var De=Object.getOwnPropertyNames;var Ae=Object.prototype.hasOwnProperty;var Ie=(o,e)=>{for(var r in e)ie(o,r,{get:e[r],enumerable:!0})},Re=(o,e,r,t)=>{if(e&&typeof e==\"object\"||typeof e==\"function\")for(let n of De(e))!Ae.call(o,n)&&n!==r&&ie(o,n,{get:()=>e[n],enumerable:!(t=Te(e,n))||t.enumerable});return o};var Se=o=>Re(ie({},\"__esModule\",{value:!0}),o);var it={};Ie(it,{FetchHttpAdapter:()=>J,LoginPage:()=>ke,runPasskeyAuth:()=>oe});var F={CREATED:\"created\",SCANNED:\"scanned\",SUCCEED:\"succeed\",ERROR:\"error\",TIMEOUT:\"timeout\",BUSY:\"busy\",FORBIDDEN:\"forbidden\"};var O={API_PREFIX:\"/api/did\",SERVICE_PREFIX:\"/.well-known/service\",ACTION:\"login\",TOKEN_TIMEOUT:3e5,CHECK_INTERVAL:2e3,TOKEN_KEY:\"_t_\",TOKEN_DELIVERY:\"cookie\",CREATE_THROTTLE:500,AUTO_CHECK_INTERVAL:6e4};var re=\"did:abt:\";function pe(o){if(!o||typeof o!=\"string\")return!1;let e=o.replace(re,\"\");return/^(0x)?[0-9a-f]{40}$/i.test(e)}function fe(o,e=8,r=6){return!o||typeof o!=\"string\"?\"\":o.length<=e+r+3?o:`${o.slice(0,e)}...${o.slice(-r)}`}function ae(o,e={}){if(!o||typeof o!=\"string\")return{prefix:\"DID: ABT:\",chainLabel:\"ABT\",address:\"\",compact:\"\",copyText:\"\"};let{startChars:r=8,endChars:t=6}=e,n=pe(o),a=o.replace(re,\"\"),s=n?\"ETH\":\"ABT\",i=e.includePrefix??!n;return{prefix:`DID: ${s}:`,chainLabel:s,address:a,compact:fe(a,r,t),copyText:i?`${re}${a}`:a}}var L={TOKEN_EXPIRED:\"TOKEN_EXPIRED\",TOKEN_NOT_FOUND:\"TOKEN_NOT_FOUND\",TOKEN_TIMEOUT:\"TOKEN_TIMEOUT\",ALREADY_EXIST:\"ALREADY_EXIST\",ALREADY_BIND:\"ALREADY_BIND\",NETWORK_ERROR:\"NETWORK_ERROR\",INVALID_RESPONSE:\"INVALID_RESPONSE\",UNAUTHORIZED:\"UNAUTHORIZED\",FORBIDDEN:\"FORBIDDEN\"},B=class extends Error{code;constructor(e,r){super(r),this.name=\"ConnectError\",this.code=e}};var J=class{_defaultHeaders;_onRequest;constructor(e){this._defaultHeaders=e?.headers??{},this._onRequest=e?.onRequest}async get(e,r){this._validateUrl(e);let t=r?.params?this._serializeParams(r.params):\"\",n=t?`${e}${e.includes(\"?\")?\"&\":\"?\"}${t}`:e,s={method:\"GET\",headers:{...this._defaultHeaders,...r?.headers},signal:r?.signal};return this._onRequest&&(s=this._onRequest(n,s)),this._execute(n,s)}async post(e,r,t){this._validateUrl(e);let n=t?.params?this._serializeParams(t.params):\"\",a=n?`${e}${e.includes(\"?\")?\"&\":\"?\"}${n}`:e,i={method:\"POST\",headers:{\"Content-Type\":\"application/json\",...this._defaultHeaders,...t?.headers},body:r!==void 0?JSON.stringify(r):void 0,signal:t?.signal};return this._onRequest&&(i=this._onRequest(a,i)),this._execute(a,i)}_validateUrl(e){if(!e?.trim())throw new B(L.NETWORK_ERROR,\"Request URL must not be empty\");if(e.trim().toLowerCase().startsWith(\"javascript:\"))throw new B(L.NETWORK_ERROR,\"Invalid URL protocol\")}async _execute(e,r){let t;try{t=await globalThis.fetch(e,r)}catch(s){throw s?.name===\"AbortError\"?s:new B(L.NETWORK_ERROR,`Network request failed: ${s?.message??\"Unknown error\"}`)}if(!t.ok){let s=\"\",i=\"\";try{let m=await t.text();try{let y=JSON.parse(m);y.error&&(s=y.error),y.code&&(i=y.code)}catch{s=m}}catch{}let d=i===\"FORBIDDEN\"?L.FORBIDDEN:L.NETWORK_ERROR;throw new B(d,s||`HTTP ${t.status} ${t.statusText||\"Error\"}`)}if(t.status===204)return null;let n=await t.text();if(!n.trim())return null;let a=t.headers.get(\"content-type\")??\"\";if(!a.includes(\"application/json\")&&!a.includes(\"+json\"))throw new B(L.INVALID_RESPONSE,`Expected JSON response but received: ${a||\"unknown content type\"}`);try{return JSON.parse(n)}catch{throw new B(L.INVALID_RESPONSE,\"Failed to parse JSON response\")}}_serializeParams(e){let r=[];for(let t of Object.keys(e)){let n=e[t];if(n!=null)if(Array.isArray(n))for(let a of n)a!=null&&r.push(`${encodeURIComponent(t)}=${encodeURIComponent(String(a))}`);else typeof n==\"boolean\"?r.push(`${encodeURIComponent(t)}=${n?\"true\":\"false\"}`):r.push(`${encodeURIComponent(t)}=${encodeURIComponent(String(n))}`)}return r.join(\"&\")}};function X(o){let e=o.apiPrefix??O.API_PREFIX,r=o.action??O.ACTION,t=e.replace(/\\/+$/,\"\"),n=r.replace(/^\\/+/,\"\");return`${t}/${n}`}function me(){return{token:null,url:null,status:null,error:null,appInfo:null,memberAppInfo:null,loading:!1}}var Z=class{_state;_config;_http;_pollTimer=null;_checkCount=0;_maxCheckCount;_realtimeUnsub=null;_realtime;_storage;_lastCreateTime=0;_listeners=new Map;_destroyed=!1;constructor(e,r,t){this._config=this._fillDefaults(e),this._http=r,this._realtime=t?.realtime,this._storage=t?.storage,this._state=me(),this._config.checkInterval<=0?this._maxCheckCount=1/0:this._maxCheckCount=Math.ceil(this._config.tokenTimeout/this._config.checkInterval)}get state(){return Object.freeze({...this._state})}async create(e){if(Date.now()-this._lastCreateTime<O.CREATE_THROTTLE)return this.state;(this._pollTimer!==null||this._realtimeUnsub!==null)&&this.stopPolling(),this._state.loading=!0,this._emit(\"statusChange\",this.state);let r=`${X(this._config)}/token`,t={};e?(e.locale!=null&&(t.locale=e.locale),e.provider!=null?t.provider=e.provider:t.provider=\"wallet\",e.encKey!=null&&(t.__encKey=e.encKey),e.autoConnect!=null&&(t.autoConnect=e.autoConnect),e.visitorId!=null&&(t.visitorId=e.visitorId),e.sourceToken!=null&&(t.sourceToken=e.sourceToken),e.forceConnected!=null&&(t.forceConnected=e.forceConnected),e.connectUrl!=null&&(t.connectUrl=e.connectUrl),e.extraParams&&Object.assign(t,e.extraParams)):t.provider=\"wallet\";try{let n=await this._http.get(r,{params:t});return this._state.token=n.token??null,this._state.url=n.url??null,this._state.status=F.CREATED,this._state.appInfo=n.appInfo??null,this._state.memberAppInfo=n.memberAppInfo??null,this._state.loading=!1,this._state.error=null,this._state.successResult=void 0,this._state.connectedDid=void 0,this._state.mfaCode=void 0,this._state.saveConnect=void 0,this._lastCreateTime=Date.now(),this._emit(\"statusChange\",this.state),this.state}catch(n){throw this._state.loading=!1,this._state.error=n?.message??\"Failed to create token\",this._state.status=F.ERROR,this._emit(\"statusChange\",this.state),this._emit(\"error\",new B(L.NETWORK_ERROR,this._state.error)),n}}async checkStatus(){if(!this._state.token)throw new B(L.TOKEN_NOT_FOUND,\"No token available. Call create() first.\");let e=`${X(this._config)}/status`,r={[O.TOKEN_KEY]:this._state.token};this._config.appPid;try{let t=await this._http.get(e,{params:r}),n=t.status;return n&&(this._state.status=n),t.error!==void 0&&(this._state.error=t.error),t.connectedDid!==void 0&&(this._state.connectedDid=t.connectedDid),t.mfaCode!==void 0&&(this._state.mfaCode=t.mfaCode),t.saveConnect!==void 0&&(this._state.saveConnect=t.saveConnect),n===F.FORBIDDEN?(this._state.status=F.ERROR,this._state.error=t.error||\"Access forbidden\",this._emit(\"error\",new B(L.UNAUTHORIZED,this._state.error)),this.stopPolling()):n===F.SUCCEED?(this._config.tokenDelivery===\"response\"&&(this._state.successResult=t),this._emit(\"succeed\",this.state),this.stopPolling()):n===F.ERROR?(this._emit(\"error\",new B(L.INVALID_RESPONSE,this._state.error||\"Unknown error\")),this.stopPolling()):n===F.TIMEOUT&&(this._emit(\"timeout\",this.state),this.stopPolling()),this._emit(\"statusChange\",this.state),this.state}catch(t){throw t}}async cancel(){if(!this._state.token)return;let e=`${X(this._config)}/timeout`,r={[O.TOKEN_KEY]:this._state.token};try{await this._http.get(e,{params:r})}catch{}this.stopPolling()}startPolling(){if(this._state.token){if(this._checkCount=0,this._realtime)try{let e=`relay:${this._config.appPid}:${this._state.token}`;this._realtimeUnsub=this._realtime.subscribe(e,r=>{this._handleRealtimeMessage(r)});return}catch{this._realtimeUnsub=null}this._config.checkInterval>0&&(this._pollTimer=setInterval(()=>{this._pollOnce()},this._config.checkInterval))}}stopPolling(){this._pollTimer!==null&&(clearInterval(this._pollTimer),this._pollTimer=null),this._realtimeUnsub&&(this._realtimeUnsub(),this._realtimeUnsub=null)}reset(){this.stopPolling(),this._state=me(),this._checkCount=0}destroy(){this._destroyed||(this.stopPolling(),this._state.token=null,this._state.successResult=void 0,this._listeners.clear(),this._destroyed=!0)}on(e,r){let t=this._listeners.get(e);return t||(t=new Set,this._listeners.set(e,t)),t.add(r),this}off(e,r){let t=this._listeners.get(e);return t&&(t.delete(r),t.size===0&&this._listeners.delete(e)),this}_emit(e,...r){let t=this._listeners.get(e);if(!(!t||t.size===0))for(let n of[...t])try{n(...r)}catch{}}_pollOnce(){if(this._checkCount++,this._checkCount>=this._maxCheckCount){this._handleTimeout();return}this.checkStatus().catch(()=>{})}_handleTimeout(){this.stopPolling(),this._state.status=F.TIMEOUT,this._emit(\"timeout\",this.state),this._emit(\"statusChange\",this.state)}_handleRealtimeMessage(e){if(!e||typeof e!=\"object\")return;let r=e.status;r&&(this._state.status=r,e.error!==void 0&&(this._state.error=e.error),e.connectedDid!==void 0&&(this._state.connectedDid=e.connectedDid),e.mfaCode!==void 0&&(this._state.mfaCode=e.mfaCode),e.saveConnect!==void 0&&(this._state.saveConnect=e.saveConnect),r===F.FORBIDDEN?(this._state.status=F.ERROR,this._state.error=e.error||\"Access forbidden\",this._emit(\"error\",new B(L.UNAUTHORIZED,this._state.error)),this.stopPolling()):r===F.SUCCEED?(this._config.tokenDelivery===\"response\"&&(this._state.successResult=e),this._emit(\"succeed\",this.state),this.stopPolling()):r===F.ERROR?(this._emit(\"error\",new B(L.INVALID_RESPONSE,this._state.error||\"Unknown error\")),this.stopPolling()):r===F.TIMEOUT&&(this._emit(\"timeout\",this.state),this.stopPolling()),this._emit(\"statusChange\",this.state))}_fillDefaults(e){return{appPid:e.appPid,appName:e.appName,appDescription:e.appDescription??\"\",appIcon:e.appIcon??\"\",appUrl:e.appUrl??\"\",apiPrefix:e.apiPrefix??O.API_PREFIX,action:e.action??O.ACTION,servicePrefix:e.servicePrefix??O.SERVICE_PREFIX,tokenTimeout:e.tokenTimeout??O.TOKEN_TIMEOUT,checkInterval:e.checkInterval??O.CHECK_INTERVAL,tokenDelivery:e.tokenDelivery??O.TOKEN_DELIVERY}}};var C={bg:{root:\"#0a0a0b\",surface:\"#141416\",card:\"#1c1c20\",elevated:\"#232328\",hover:\"#2b2b34\",active:\"#33333e\",input:\"rgba(255,255,255,0.06)\"},border:{default:\"rgba(255,255,255,0.10)\",subtle:\"rgba(255,255,255,0.06)\",strong:\"rgba(255,255,255,0.15)\",focus:\"rgba(255,255,255,0.25)\"},text:{primary:\"#f5f5f7\",secondary:\"#9394a1\",tertiary:\"#767684\",placeholder:\"#5e5f6e\",white:\"#ffffff\"},color:{blue:\"#6c47ff\",blueHover:\"#5f15fe\",blueLight:\"rgba(108,71,255,0.15)\",blueMuted:\"#9280ff\",red:\"#e02e2e\",redLight:\"rgba(224,46,46,0.15)\",redText:\"#f98a8a\",green:\"#15892b\",greenLight:\"rgba(21,137,43,0.15)\",greenText:\"#49dc6e\",yellow:\"#fd7224\",yellowLight:\"rgba(253,114,36,0.15)\",yellowText:\"#fd9357\",info:\"#236dd7\",infoLight:\"rgba(35,109,215,0.15)\",infoText:\"#73acfa\"},radius:{xs:\"4px\",sm:\"6px\",md:\"8px\",lg:\"12px\",full:\"9999px\"},shadow:{sm:\"0 1px 3px rgba(0,0,0,0.3), 0 1px 2px -1px rgba(0,0,0,0.2)\",md:\"0 4px 6px -1px rgba(0,0,0,0.4), 0 2px 4px -2px rgba(0,0,0,0.3)\",lg:\"0 10px 15px -3px rgba(0,0,0,0.5), 0 4px 6px -4px rgba(0,0,0,0.4)\",focus:\"0 0 0 1px rgba(255,255,255,0.12), 0 0 0 3px rgba(108,71,255,0.3)\"},font:{family:\"'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif\",mono:\"'JetBrains Mono', 'SF Mono', monospace\",sizeBase:\"14px\",lineHeight:\"1.43\"}},P={bg:{root:\"#f8f9fa\",surface:\"#ffffff\",card:\"#ffffff\",elevated:\"#f0f1f3\",hover:\"#e9eaec\",active:\"#dddee1\",input:\"rgba(0,0,0,0.04)\"},border:{default:\"rgba(0,0,0,0.10)\",subtle:\"rgba(0,0,0,0.06)\",strong:\"rgba(0,0,0,0.15)\",focus:\"rgba(0,0,0,0.25)\"},text:{primary:\"#1a1a1a\",secondary:\"#6b7280\",tertiary:\"#9ca3af\",placeholder:\"#c0c5ce\",white:\"#1a1a1a\"},shadow:{sm:\"0 1px 3px rgba(0,0,0,0.08), 0 1px 2px -1px rgba(0,0,0,0.06)\",md:\"0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.06)\",lg:\"0 10px 15px -3px rgba(0,0,0,0.12), 0 4px 6px -4px rgba(0,0,0,0.08)\",focus:\"0 0 0 1px rgba(0,0,0,0.08), 0 0 0 3px rgba(108,71,255,0.2)\"}},Ne=`\n :root {\n /* Backgrounds \\u2014 layered depth */\n --bg-root: ${C.bg.root};\n --bg-surface: ${C.bg.surface};\n --bg-card: ${C.bg.card};\n --bg-elevated: ${C.bg.elevated};\n --bg-hover: ${C.bg.hover};\n --bg-active: ${C.bg.active};\n --bg-input: ${C.bg.input};\n\n /* Borders \\u2014 semi-transparent for adaptability */\n --border: ${C.border.default};\n --border-subtle:${C.border.subtle};\n --border-strong:${C.border.strong};\n --border-focus: ${C.border.focus};\n\n /* Text hierarchy */\n --text: ${C.text.primary};\n --text-secondary:${C.text.secondary};\n --text-tertiary:${C.text.tertiary};\n --text-placeholder:${C.text.placeholder};\n --text-white: ${C.text.white};\n\n /* Accents */\n --blue: ${C.color.blue};\n --blue-hover: ${C.color.blueHover};\n --blue-light: ${C.color.blueLight};\n --blue-muted: ${C.color.blueMuted};\n --red: ${C.color.red};\n --red-light: ${C.color.redLight};\n --red-text: ${C.color.redText};\n --green: ${C.color.green};\n --green-light: ${C.color.greenLight};\n --green-text: ${C.color.greenText};\n --yellow: ${C.color.yellow};\n --yellow-light: ${C.color.yellowLight};\n --yellow-text: ${C.color.yellowText};\n --info: ${C.color.info};\n --info-light: ${C.color.infoLight};\n --info-text: ${C.color.infoText};\n\n /* Radii */\n --radius-xs: ${C.radius.xs};\n --radius-sm: ${C.radius.sm};\n --radius: ${C.radius.md};\n --radius-lg: ${C.radius.lg};\n --radius-full: ${C.radius.full};\n\n /* Shadows */\n --shadow-sm: ${C.shadow.sm};\n --shadow-md: ${C.shadow.md};\n --shadow-lg: ${C.shadow.lg};\n --shadow-focus: ${C.shadow.focus};\n }\n`,Be=`\n [data-theme=\"light\"] {\n --bg-root: ${P.bg.root};\n --bg-surface: ${P.bg.surface};\n --bg-card: ${P.bg.card};\n --bg-elevated: ${P.bg.elevated};\n --bg-hover: ${P.bg.hover};\n --bg-active: ${P.bg.active};\n --bg-input: ${P.bg.input};\n\n --border: ${P.border.default};\n --border-subtle:${P.border.subtle};\n --border-strong:${P.border.strong};\n --border-focus: ${P.border.focus};\n\n --text: ${P.text.primary};\n --text-secondary:${P.text.secondary};\n --text-tertiary:${P.text.tertiary};\n --text-placeholder:${P.text.placeholder};\n --text-white: ${P.text.white};\n\n --shadow-sm: ${P.shadow.sm};\n --shadow-md: ${P.shadow.md};\n --shadow-lg: ${P.shadow.lg};\n --shadow-focus: ${P.shadow.focus};\n }\n`,Le=`\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n html { color-scheme: dark; }\n html[data-theme=\"light\"] { color-scheme: light; }\n body {\n font-family: ${C.font.family};\n background: var(--bg-root);\n color: var(--text);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n font-size: ${C.font.sizeBase};\n line-height: ${C.font.lineHeight};\n }\n a { color: var(--blue-muted); text-decoration: none; }\n a:hover { text-decoration: underline; }\n .hidden { display: none; }\n`,Pe=typeof HTMLElement<\"u\"?HTMLElement:class{},ye='<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\"/><path d=\"M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1\"/></svg>',Oe='<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"/></svg>',$e=`\n:host {\n display: inline-flex;\n align-items: center;\n max-width: 100%;\n overflow: hidden;\n font-family: 'SF Mono', 'JetBrains Mono', 'Menlo', 'Consolas', monospace;\n line-height: 1.5;\n}\n:host([block]) { display: flex; }\n.prefix {\n flex-shrink: 0;\n opacity: 0.6;\n margin-right: 4px;\n white-space: nowrap;\n}\n.address {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: var(--did-text-color, currentColor);\n}\n.address-compact {\n white-space: nowrap;\n color: var(--did-text-color, currentColor);\n cursor: default;\n}\n.clickable { cursor: pointer; }\n.clickable:hover { opacity: 0.8; }\n.copy-btn {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1em;\n height: 1em;\n margin-left: 6px;\n padding: 0;\n border: none;\n background: none;\n color: inherit;\n opacity: 0.5;\n cursor: pointer;\n transition: opacity 0.15s;\n}\n.copy-btn:hover { opacity: 1; }\n.copy-btn.copied { opacity: 1; color: var(--did-copy-success-color, #22c55e); }\n`,Me=class extends Pe{static observedAttributes=[\"did\",\"compact\",\"copyable\",\"start-chars\",\"end-chars\",\"show-prefix\",\"no-tooltip\",\"no-copy\",\"block\",\"size\"];shadow;copyTimer=null;constructor(){super(),this.shadow=this.attachShadow({mode:\"open\"})}connectedCallback(){this.render()}attributeChangedCallback(){this.render()}disconnectedCallback(){this.copyTimer&&clearTimeout(this.copyTimer)}get parts(){let o=Number(this.getAttribute(\"start-chars\")),e=Number(this.getAttribute(\"end-chars\"));return ae(this.getAttribute(\"did\")||\"\",{startChars:o>0?o:void 0,endChars:e>0?e:void 0})}render(){if(!(this.getAttribute(\"did\")||\"\")){this.shadow.innerHTML=\"\";return}let e=this.parts,r=this.hasAttribute(\"compact\"),t=!this.hasAttribute(\"no-copy\")&&this.getAttribute(\"copyable\")!==\"false\",n=this.hasAttribute(\"show-prefix\"),a=this.hasAttribute(\"no-tooltip\"),s=Number(this.getAttribute(\"size\"))||0,i=s>=12?`${s}px`:\"inherit\",d=r?e.compact:e.address,m=!a&&r?` title=\"${e.address}\"`:\"\",y=n?`<span class=\"prefix\">${e.prefix} </span>`:\"\",p=r?\"address-compact\":\"address\",l=t?\" clickable\":\"\",b=t?`<button class=\"copy-btn\" aria-label=\"Copy DID\">${ye}</button>`:\"\";if(this.shadow.innerHTML=`\n <style>${$e}:host { font-size: ${i}; }</style>\n ${y}<span class=\"${p}${l}\"${m}>${d}</span>${b}\n `,t){let T=this.shadow.querySelector(`.${p}`);T&&T.addEventListener(\"click\",x=>{x.stopPropagation(),this.copyToClipboard(e.copyText)});let _=this.shadow.querySelector(\".copy-btn\");_&&_.addEventListener(\"click\",x=>{x.stopPropagation(),this.copyToClipboard(e.copyText)})}}copyToClipboard(o){navigator.clipboard?.writeText?navigator.clipboard.writeText(o).catch(()=>this.fallbackCopy(o)):this.fallbackCopy(o),this.showCopied()}fallbackCopy(o){try{let e=document.createElement(\"input\");e.value=o,document.body.appendChild(e),e.select(),document.execCommand?.(\"copy\"),e.remove()}catch{}}showCopied(){let o=this.shadow.querySelector(\".copy-btn\");o&&(o.classList.add(\"copied\"),o.innerHTML=Oe,this.copyTimer&&clearTimeout(this.copyTimer),this.copyTimer=setTimeout(()=>{o.classList.remove(\"copied\"),o.innerHTML=ye},1500))}};typeof customElements<\"u\"&&!customElements.get(\"did-address\")&&customElements.define(\"did-address\",Me);var be={en:{signIn:\"Sign in\",signInTo:\"Sign in to {appName}\",subtitle:\"Sign in to continue\",passkeyButton:\"Continue with Passkey\",passkeySignIn:\"Sign in with Passkey\",passkeyCreate:\"Create Passkey\",passkeyFirstTime:\"First time? Create a passkey\",passkeyBackToSignIn:\"Back to sign in\",passkeyWaiting:\"Waiting for passkey\\u2026\",passkeyCreating:\"Creating your passkey\\u2026\",passkeyNoLocal:\"No passkey found on this device.\",passkeyUseAnotherDevice:\"Use another device\",passkeyCreateInstead:\"Create a new passkey instead\",didWalletButton:\"Continue with DID Wallet\",didWalletButtonShort:\"DID Wallet\",scanWithWallet:\"Scan with your DID Wallet\",waitingWallet:\"Requesting connection\",walletContinue:\"Please continue in DID Wallet\",noPasskey:\"No existing passkey found. Enter a name and create one.\",success:\"Success!\",timeout:\"Session timed out. Try again.\",refreshQR:\"Refresh\",namePlaceholder:\"Your name (optional)\",or:\"or\",emailPlaceholder:\"Email address\",emailButton:\"Email\",passkeyUnsupported:\"Your browser does not support Passkey authentication.\",connecting:\"Connecting...\",verifying:\"Verifying...\",openInWallet:\"Open in DID Wallet\",authFailed:\"Authentication failed\",accessDenied:\"Access denied: your account is not permitted to log in to this site.\",regClosed:\"Registration requires an invitation.\",federatedBy:\"Authentication provided by {masterName}\",securedBy:\"Secured by DID Connect\",privacy:\"Privacy\",terms:\"Terms\",back:\"Back\",cancel:\"Cancel\",scanTitle:\"Scan with DID Wallet\",emailTitle:\"Continue with Email\",emailSubmit:\"Send Code\",emailSending:\"Sending code...\",emailSent:\"We sent a 6-digit code to {email}\",emailCodePlaceholder:\"6-digit code\",emailVerify:\"Verify\",emailResend:\"Resend code\",emailInvalid:\"Please enter a valid email address.\",emailCodeInvalid:\"Please enter the 6-digit code.\"},zh:{signIn:\"\\u767B\\u5F55\",signInTo:\"\\u767B\\u5F55\\u5230 {appName}\",subtitle:\"\\u767B\\u5F55\\u4EE5\\u7EE7\\u7EED\",passkeyButton:\"\\u4F7F\\u7528 Passkey \\u7EE7\\u7EED\",passkeySignIn:\"\\u4F7F\\u7528 Passkey \\u767B\\u5F55\",passkeyCreate:\"\\u521B\\u5EFA Passkey\",passkeyFirstTime:\"\\u7B2C\\u4E00\\u6B21\\u4F7F\\u7528\\uFF1F\\u521B\\u5EFA Passkey\",passkeyBackToSignIn:\"\\u8FD4\\u56DE\\u767B\\u5F55\",passkeyWaiting:\"\\u6B63\\u5728\\u7B49\\u5F85 Passkey\\u2026\",passkeyCreating:\"\\u6B63\\u5728\\u521B\\u5EFA Passkey\\u2026\",passkeyNoLocal:\"\\u6B64\\u8BBE\\u5907\\u4E0A\\u6CA1\\u6709\\u627E\\u5230 Passkey\\u3002\",passkeyUseAnotherDevice:\"\\u7528\\u5176\\u4ED6\\u8BBE\\u5907\\u767B\\u5F55\",passkeyCreateInstead:\"\\u6539\\u4E3A\\u521B\\u5EFA\\u65B0\\u7684 Passkey\",didWalletButton:\"\\u4F7F\\u7528 DID \\u94B1\\u5305\\u7EE7\\u7EED\",didWalletButtonShort:\"DID \\u94B1\\u5305\",scanWithWallet:\"\\u8BF7\\u4F7F\\u7528 DID \\u94B1\\u5305\\u626B\\u7801\",waitingWallet:\"\\u6B63\\u5728\\u8BF7\\u6C42\\u8FDE\\u63A5\",walletContinue:\"\\u8BF7\\u5728 DID Wallet \\u4E2D\\u7EE7\\u7EED\\u5B8C\\u6210\\u60A8\\u7684\\u64CD\\u4F5C\",noPasskey:\"\\u672A\\u627E\\u5230\\u5DF2\\u6709 Passkey\\u3002\\u8F93\\u5165\\u540D\\u79F0\\u5E76\\u521B\\u5EFA\\u4E00\\u4E2A\\u3002\",success:\"\\u6210\\u529F\",timeout:\"\\u4F1A\\u8BDD\\u8D85\\u65F6\\uFF0C\\u8BF7\\u91CD\\u8BD5\\u3002\",refreshQR:\"\\u5237\\u65B0\",namePlaceholder:\"\\u4F60\\u7684\\u540D\\u5B57\\uFF08\\u53EF\\u9009\\uFF09\",or:\"\\u6216\",emailPlaceholder:\"\\u90AE\\u7BB1\\u5730\\u5740\",emailButton:\"\\u90AE\\u7BB1\",passkeyUnsupported:\"\\u4F60\\u7684\\u6D4F\\u89C8\\u5668\\u4E0D\\u652F\\u6301 Passkey \\u8BA4\\u8BC1\\u3002\",connecting:\"\\u8FDE\\u63A5\\u4E2D...\",verifying:\"\\u9A8C\\u8BC1\\u4E2D...\",openInWallet:\"\\u5728 DID \\u94B1\\u5305\\u4E2D\\u6253\\u5F00\",authFailed:\"\\u8BA4\\u8BC1\\u5931\\u8D25\",accessDenied:\"\\u8BBF\\u95EE\\u88AB\\u62D2\\u7EDD\\uFF1A\\u4F60\\u7684\\u8D26\\u6237\\u65E0\\u6743\\u767B\\u5F55\\u6B64\\u7AD9\\u70B9\\u3002\",regClosed:\"\\u6CE8\\u518C\\u9700\\u8981\\u9080\\u8BF7\\u3002\",federatedBy:\"\\u8BA4\\u8BC1\\u670D\\u52A1\\u7531 {masterName} \\u63D0\\u4F9B\",securedBy:\"\\u7531 DID Connect \\u63D0\\u4F9B\\u5B89\\u5168\\u4FDD\\u969C\",privacy:\"\\u9690\\u79C1\",terms:\"\\u6761\\u6B3E\",back:\"\\u8FD4\\u56DE\",cancel:\"\\u53D6\\u6D88\",scanTitle:\"\\u4F7F\\u7528 DID \\u94B1\\u5305\\u626B\\u7801\",emailTitle:\"\\u4F7F\\u7528\\u90AE\\u7BB1\\u7EE7\\u7EED\",emailSubmit:\"\\u53D1\\u9001\\u9A8C\\u8BC1\\u7801\",emailSending:\"\\u6B63\\u5728\\u53D1\\u9001\\u9A8C\\u8BC1\\u7801...\",emailSent:\"\\u9A8C\\u8BC1\\u7801\\u5DF2\\u53D1\\u9001\\u81F3 {email}\",emailCodePlaceholder:\"6 \\u4F4D\\u9A8C\\u8BC1\\u7801\",emailVerify:\"\\u9A8C\\u8BC1\",emailResend:\"\\u91CD\\u65B0\\u53D1\\u9001\",emailInvalid:\"\\u8BF7\\u8F93\\u5165\\u6709\\u6548\\u7684\\u90AE\\u7BB1\\u5730\\u5740\\u3002\",emailCodeInvalid:\"\\u8BF7\\u8F93\\u5165 6 \\u4F4D\\u9A8C\\u8BC1\\u7801\\u3002\"}},ce=[{code:\"en\",label:\"English\"},{code:\"zh\",label:\"\\u4E2D\\u6587\"}];function E(o,e,r){let t=e&&be[e]?e:\"en\",n=be[t][o]??o;if(r)for(let[a,s]of Object.entries(r))n=n.replace(new RegExp(`\\\\{${a}\\\\}`,\"g\"),s);return n}var le='<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\"><path fill=\"currentColor\" d=\"M3 20v-2.8q0-.85.438-1.562T4.6 14.55q1.55-.775 3.15-1.162T11 13q.5 0 1 .038t1 .112q-.1 1.45.525 2.738T15.35 18v2zm16 3l-1.5-1.5v-4.65q-1.1-.325-1.8-1.237T15 13.5q0-1.45 1.025-2.475T18.5 10t2.475 1.025T22 13.5q0 1.125-.638 2t-1.612 1.25L21 18l-1.5 1.5L21 21zm-8-11q-1.65 0-2.825-1.175T7 8t1.175-2.825T11 4t2.825 1.175T15 8t-1.175 2.825T11 12m7.5 2q.425 0 .713-.288T19.5 13t-.288-.712T18.5 12t-.712.288T17.5 13t.288.713t.712.287\"/></svg>',we='<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\"><rect x=\"2\" y=\"4\" width=\"20\" height=\"16\" rx=\"3\" fill=\"#4598FA\"/><rect x=\"3\" y=\"6\" width=\"18\" height=\"12\" rx=\"2\" fill=\"white\" opacity=\"0.9\"/><rect x=\"0\" y=\"14\" width=\"24\" height=\"10\" rx=\"0\" fill=\"url(#dw_g)\"/><path d=\"M5.5 9.5h4.5c.3 0 .5.2.5.5v2.5c0 .3-.2.5-.5.5H5.5c-.3 0-.5-.2-.5-.5V10c0-.3.2-.5.5-.5z\" fill=\"#4598FA\"/><defs><linearGradient id=\"dw_g\" x1=\"12\" y1=\"14\" x2=\"12\" y2=\"24\" gradientUnits=\"userSpaceOnUse\"><stop stop-color=\"#77B2F6\"/><stop offset=\"1\" stop-color=\"#4598FA\"/></linearGradient></defs></svg>',Fe='<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\"><path fill=\"currentColor\" d=\"M22 7.535V17a3 3 0 0 1-2.824 2.995L19 20H5a3 3 0 0 1-2.995-2.824L2 17V7.535l9.445 6.297l.116.066a1 1 0 0 0 .878 0l.116-.066z\"/><path fill=\"currentColor\" d=\"M19 4c1.08 0 2.027.57 2.555 1.427L12 11.797l-9.555-6.37a2.999 2.999 0 0 1 2.354-1.42L5 4z\"/></svg>',Ue='<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"><path fill=\"currentColor\" d=\"M12 1L3 5v6c0 5.55 3.84 10.74 9 12c5.16-1.26 9-6.45 9-12V5zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11z\"/></svg>',xe='<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\"><path fill=\"currentColor\" d=\"M20 11H7.83l5.59-5.59L12 4l-8 8l8 8l1.41-1.41L7.83 13H20z\"/></svg>',He={google:'<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 256 262\"><path fill=\"#4285F4\" d=\"M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622l38.755 30.023l2.685.268c24.659-22.774 38.875-56.282 38.875-96.027\"/><path fill=\"#34A853\" d=\"M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055c-34.523 0-63.824-22.773-74.269-54.25l-1.531.13l-40.298 31.187l-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1\"/><path fill=\"#FBBC05\" d=\"M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82c0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602z\"/><path fill=\"#EB4335\" d=\"M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0C79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251\"/></svg>',github:'<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 256 250\" fill=\"currentColor\"><path d=\"M128.001 0C57.317 0 0 57.307 0 128.001c0 56.554 36.676 104.535 87.535 121.46c6.397 1.185 8.746-2.777 8.746-6.158c0-3.052-.12-13.135-.174-23.83c-35.61 7.742-43.124-15.103-43.124-15.103c-5.823-14.795-14.213-18.73-14.213-18.73c-11.613-7.944.876-7.78.876-7.78c12.853.902 19.621 13.19 19.621 13.19c11.417 19.568 29.945 13.911 37.249 10.64c1.149-8.272 4.466-13.92 8.127-17.116c-28.431-3.236-58.318-14.212-58.318-63.258c0-13.975 5-25.394 13.188-34.358c-1.329-3.224-5.71-16.242 1.24-33.874c0 0 10.749-3.44 35.21 13.121c10.21-2.836 21.16-4.258 32.038-4.307c10.878.049 21.837 1.47 32.066 4.307c24.431-16.56 35.165-13.12 35.165-13.12c6.967 17.63 2.584 30.65 1.255 33.873c8.207 8.964 13.173 20.383 13.173 34.358c0 49.163-29.944 59.988-58.447 63.157c4.591 3.972 8.682 11.762 8.682 23.704c0 17.126-.148 30.91-.148 35.126c0 3.407 2.304 7.398 8.792 6.14C219.37 232.5 256 184.537 256 128.002C256 57.307 198.691 0 128.001 0\"/></svg>',apple:'<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 256 315\" fill=\"currentColor\"><path d=\"M213.803 167.03c.442 47.58 41.74 63.413 42.197 63.615c-.35 1.116-6.599 22.563-21.757 44.716c-13.104 19.153-26.705 38.235-48.13 38.63c-21.05.388-27.82-12.483-51.888-12.483c-24.061 0-31.582 12.088-51.51 12.871c-20.68.783-36.428-20.71-49.64-39.793c-27-39.033-47.633-110.3-19.928-158.406c13.763-23.89 38.36-39.017 65.056-39.405c20.307-.388 39.475 13.675 51.889 13.675c12.406 0 35.699-16.895 60.186-14.414c10.25.427 39.026 4.14 57.503 31.186c-1.49.923-34.335 20.044-33.978 59.808M174.24 50.199c10.98-13.29 18.369-31.79 16.353-50.199c-15.826.636-34.962 10.546-46.314 23.828c-10.173 11.763-19.082 30.589-16.678 48.633c17.64 1.365 35.66-8.964 46.639-22.262\"/></svg>',twitter:'<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 256 256\" fill=\"currentColor\"><path d=\"M149.079 108.399L242.33 0h-22.098l-80.97 94.12L74.59 0H0l97.796 142.328L0 256h22.1l85.507-99.395L175.905 256h74.59L149.073 108.399zM118.81 143.58l-9.909-14.172l-78.84-112.773h33.943l63.625 91.011l9.909 14.173l82.705 118.3H186.3l-67.49-96.533z\"/></svg>',auth0:'<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 256 256\"><path fill=\"#EB5424\" d=\"M203.97 200.38L167.47 88l-39.5 112.38h75.98zm0-144.76L167.47 168l75.97-26.16l19.76-60.82c6.64-20.4-1.94-42.72-21.18-52.76l-38.06 27.36zM52.03 55.62L88.54 168l39.5-112.38H52.02zM52.03 200.38L88.54 88L12.56 114.16l-5.92 18.2c-6.64 20.4 1.94 42.72 21.18 52.76l24.2-17.38l.01.01v-.01l-.01.01.01-.37z\"/></svg>',facebook:'<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"18\" height=\"18\" viewBox=\"0 0 256 256\"><path fill=\"#1877F2\" d=\"M256 128C256 57.308 198.692 0 128 0S0 57.308 0 128c0 63.888 46.808 116.843 108 126.445V165H75.5v-37H108V99.8c0-32.08 19.11-49.8 48.348-49.8C170.352 50 185 52.5 185 52.5V84h-16.14C152.959 84 148 93.867 148 103.99V128h35.5l-5.675 37H148v89.445c61.192-9.602 108-62.556 108-126.445\"/></svg>'},Ke='<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\"><path fill=\"currentColor\" d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10s10-4.48 10-10S17.52 2 12 2m-1 17.93c-3.95-.49-7-3.85-7-7.93c0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41c0 2.08-.8 3.97-2.1 5.39\"/></svg>';function Ve(o){return He[o.toLowerCase()]??\"\"}var Q=function(o,e){let n=o,a=te[e],s=null,i=0,d=null,m=[],y={},p=function(u,g){i=n*4+17,s=(function(c){let f=new Array(c);for(let h=0;h<c;h+=1){f[h]=new Array(c);for(let w=0;w<c;w+=1)f[h][w]=null}return f})(i),l(0,0),l(i-7,0),l(0,i-7),_(),T(),A(u,g),n>=7&&x(u),d==null&&(d=U(n,a,m)),D(d,g)},l=function(u,g){for(let c=-1;c<=7;c+=1)if(!(u+c<=-1||i<=u+c))for(let f=-1;f<=7;f+=1)g+f<=-1||i<=g+f||(0<=c&&c<=6&&(f==0||f==6)||0<=f&&f<=6&&(c==0||c==6)||2<=c&&c<=4&&2<=f&&f<=4?s[u+c][g+f]=!0:s[u+c][g+f]=!1)},b=function(){let u=0,g=0;for(let c=0;c<8;c+=1){p(!0,c);let f=q.getLostPoint(y);(c==0||u>f)&&(u=f,g=c)}return g},T=function(){for(let u=8;u<i-8;u+=1)s[u][6]==null&&(s[u][6]=u%2==0);for(let u=8;u<i-8;u+=1)s[6][u]==null&&(s[6][u]=u%2==0)},_=function(){let u=q.getPatternPosition(n);for(let g=0;g<u.length;g+=1)for(let c=0;c<u.length;c+=1){let f=u[g],h=u[c];if(s[f][h]==null)for(let w=-2;w<=2;w+=1)for(let v=-2;v<=2;v+=1)w==-2||w==2||v==-2||v==2||w==0&&v==0?s[f+w][h+v]=!0:s[f+w][h+v]=!1}},x=function(u){let g=q.getBCHTypeNumber(n);for(let c=0;c<18;c+=1){let f=!u&&(g>>c&1)==1;s[Math.floor(c/3)][c%3+i-8-3]=f}for(let c=0;c<18;c+=1){let f=!u&&(g>>c&1)==1;s[c%3+i-8-3][Math.floor(c/3)]=f}},A=function(u,g){let c=a<<3|g,f=q.getBCHTypeInfo(c);for(let h=0;h<15;h+=1){let w=!u&&(f>>h&1)==1;h<6?s[h][8]=w:h<8?s[h+1][8]=w:s[i-15+h][8]=w}for(let h=0;h<15;h+=1){let w=!u&&(f>>h&1)==1;h<8?s[8][i-h-1]=w:h<9?s[8][15-h-1+1]=w:s[8][15-h-1]=w}s[i-8][8]=!u},D=function(u,g){let c=-1,f=i-1,h=7,w=0,v=q.getMaskFunction(g);for(let I=i-1;I>0;I-=2)for(I==6&&(I-=1);;){for(let R=0;R<2;R+=1)if(s[f][I-R]==null){let N=!1;w<u.length&&(N=(u[w]>>>h&1)==1),v(f,I-R)&&(N=!N),s[f][I-R]=N,h-=1,h==-1&&(w+=1,h=7)}if(f+=c,f<0||i<=f){f-=c,c=-c;break}}},$=function(u,g){let c=0,f=0,h=0,w=new Array(g.length),v=new Array(g.length);for(let k=0;k<g.length;k+=1){let S=g[k].dataCount,H=g[k].totalCount-S;f=Math.max(f,S),h=Math.max(h,H),w[k]=new Array(S);for(let W=0;W<w[k].length;W+=1)w[k][W]=255&u.getBuffer()[W+c];c+=S;let se=q.getErrorCorrectPolynomial(H),de=ne(w[k],se.getLength()-1).mod(se);v[k]=new Array(se.getLength()-1);for(let W=0;W<v[k].length;W+=1){let he=W+de.getLength()-v[k].length;v[k][W]=he>=0?de.getAt(he):0}}let I=0;for(let k=0;k<g.length;k+=1)I+=g[k].totalCount;let R=new Array(I),N=0;for(let k=0;k<f;k+=1)for(let S=0;S<g.length;S+=1)k<w[S].length&&(R[N]=w[S][k],N+=1);for(let k=0;k<h;k+=1)for(let S=0;S<g.length;S+=1)k<v[S].length&&(R[N]=v[S][k],N+=1);return R},U=function(u,g,c){let f=Ee.getRSBlocks(u,g),h=ve();for(let v=0;v<c.length;v+=1){let I=c[v];h.put(I.getMode(),4),h.put(I.getLength(),q.getLengthInBits(I.getMode(),u)),I.write(h)}let w=0;for(let v=0;v<f.length;v+=1)w+=f[v].dataCount;if(h.getLengthInBits()>w*8)throw\"code length overflow. (\"+h.getLengthInBits()+\">\"+w*8+\")\";for(h.getLengthInBits()+4<=w*8&&h.put(0,4);h.getLengthInBits()%8!=0;)h.putBit(!1);for(;!(h.getLengthInBits()>=w*8||(h.put(236,8),h.getLengthInBits()>=w*8));)h.put(17,8);return $(h,f)};y.addData=function(u,g){g=g||\"Byte\";let c=null;switch(g){case\"Numeric\":c=We(u);break;case\"Alphanumeric\":c=je(u);break;case\"Byte\":c=qe(u);break;case\"Kanji\":c=ze(u);break;default:throw\"mode:\"+g}m.push(c),d=null},y.isDark=function(u,g){if(u<0||i<=u||g<0||i<=g)throw u+\",\"+g;return s[u][g]},y.getModuleCount=function(){return i},y.make=function(){if(n<1){let u=1;for(;u<40;u++){let g=Ee.getRSBlocks(u,a),c=ve();for(let h=0;h<m.length;h++){let w=m[h];c.put(w.getMode(),4),c.put(w.getLength(),q.getLengthInBits(w.getMode(),u)),w.write(c)}let f=0;for(let h=0;h<g.length;h++)f+=g[h].dataCount;if(c.getLengthInBits()<=f*8)break}n=u}p(!1,b())},y.createTableTag=function(u,g){u=u||2,g=typeof g>\"u\"?u*4:g;let c=\"\";c+='<table style=\"',c+=\" border-width: 0px; border-style: none;\",c+=\" border-collapse: collapse;\",c+=\" padding: 0px; margin: \"+g+\"px;\",c+='\">',c+=\"<tbody>\";for(let f=0;f<y.getModuleCount();f+=1){c+=\"<tr>\";for(let h=0;h<y.getModuleCount();h+=1)c+='<td style=\"',c+=\" border-width: 0px; border-style: none;\",c+=\" border-collapse: collapse;\",c+=\" padding: 0px; margin: 0px;\",c+=\" width: \"+u+\"px;\",c+=\" height: \"+u+\"px;\",c+=\" background-color: \",c+=y.isDark(f,h)?\"#000000\":\"#ffffff\",c+=\";\",c+='\"/>';c+=\"</tr>\"}return c+=\"</tbody>\",c+=\"</table>\",c},y.createSvgTag=function(u,g,c,f){let h={};typeof arguments[0]==\"object\"&&(h=arguments[0],u=h.cellSize,g=h.margin,c=h.alt,f=h.title),u=u||2,g=typeof g>\"u\"?u*4:g,c=typeof c==\"string\"?{text:c}:c||{},c.text=c.text||null,c.id=c.text?c.id||\"qrcode-description\":null,f=typeof f==\"string\"?{text:f}:f||{},f.text=f.text||null,f.id=f.text?f.id||\"qrcode-title\":null;let w=y.getModuleCount()*u+g*2,v,I,R,N,k=\"\",S;for(S=\"l\"+u+\",0 0,\"+u+\" -\"+u+\",0 0,-\"+u+\"z \",k+='<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\"',k+=h.scalable?\"\":' width=\"'+w+'px\" height=\"'+w+'px\"',k+=' viewBox=\"0 0 '+w+\" \"+w+'\" ',k+=' preserveAspectRatio=\"xMinYMin meet\"',k+=f.text||c.text?' role=\"img\" aria-labelledby=\"'+V([f.id,c.id].join(\" \").trim())+'\"':\"\",k+=\">\",k+=f.text?'<title id=\"'+V(f.id)+'\">'+V(f.text)+\"</title>\":\"\",k+=c.text?'<description id=\"'+V(c.id)+'\">'+V(c.text)+\"</description>\":\"\",k+='<rect width=\"100%\" height=\"100%\" fill=\"white\" cx=\"0\" cy=\"0\"/>',k+='<path d=\"',R=0;R<y.getModuleCount();R+=1)for(N=R*u+g,v=0;v<y.getModuleCount();v+=1)y.isDark(R,v)&&(I=v*u+g,k+=\"M\"+I+\",\"+N+S);return k+='\" stroke=\"transparent\" fill=\"black\"/>',k+=\"</svg>\",k},y.createDataURL=function(u,g){u=u||2,g=typeof g>\"u\"?u*4:g;let c=y.getModuleCount()*u+g*2,f=g,h=c-g;return Je(c,c,function(w,v){if(f<=w&&w<h&&f<=v&&v<h){let I=Math.floor((w-f)/u),R=Math.floor((v-f)/u);return y.isDark(R,I)?0:1}else return 1})},y.createImgTag=function(u,g,c){u=u||2,g=typeof g>\"u\"?u*4:g;let f=y.getModuleCount()*u+g*2,h=\"\";return h+=\"<img\",h+=' src=\"',h+=y.createDataURL(u,g),h+='\"',h+=' width=\"',h+=f,h+='\"',h+=' height=\"',h+=f,h+='\"',c&&(h+=' alt=\"',h+=V(c),h+='\"'),h+=\"/>\",h};let V=function(u){let g=\"\";for(let c=0;c<u.length;c+=1){let f=u.charAt(c);switch(f){case\"<\":g+=\"<\";break;case\">\":g+=\">\";break;case\"&\":g+=\"&\";break;case'\"':g+=\""\";break;default:g+=f;break}}return g},Ce=function(u){u=typeof u>\"u\"?2:u;let c=y.getModuleCount()*1+u*2,f=u,h=c-u,w,v,I,R,N,k={\"\\u2588\\u2588\":\"\\u2588\",\"\\u2588 \":\"\\u2580\",\" \\u2588\":\"\\u2584\",\" \":\" \"},S={\"\\u2588\\u2588\":\"\\u2580\",\"\\u2588 \":\"\\u2580\",\" \\u2588\":\" \",\" \":\" \"},H=\"\";for(w=0;w<c;w+=2){for(I=Math.floor((w-f)/1),R=Math.floor((w+1-f)/1),v=0;v<c;v+=1)N=\"\\u2588\",f<=v&&v<h&&f<=w&&w<h&&y.isDark(I,Math.floor((v-f)/1))&&(N=\" \"),f<=v&&v<h&&f<=w+1&&w+1<h&&y.isDark(R,Math.floor((v-f)/1))?N+=\" \":N+=\"\\u2588\",H+=u<1&&w+1>=h?S[N]:k[N];H+=`\n`}return c%2&&u>0?H.substring(0,H.length-c-1)+Array(c+1).join(\"\\u2580\"):H.substring(0,H.length-1)};return y.createASCII=function(u,g){if(u=u||1,u<2)return Ce(g);u-=1,g=typeof g>\"u\"?u*2:g;let c=y.getModuleCount()*u+g*2,f=g,h=c-g,w,v,I,R,N=Array(u+1).join(\"\\u2588\\u2588\"),k=Array(u+1).join(\" \"),S=\"\",H=\"\";for(w=0;w<c;w+=1){for(I=Math.floor((w-f)/u),H=\"\",v=0;v<c;v+=1)R=1,f<=v&&v<h&&f<=w&&w<h&&y.isDark(I,Math.floor((v-f)/u))&&(R=0),H+=R?N:k;for(I=0;I<u;I+=1)S+=H+`\n`}return S.substring(0,S.length-1)},y.renderTo2dContext=function(u,g){g=g||2;let c=y.getModuleCount();for(let f=0;f<c;f++)for(let h=0;h<c;h++)u.fillStyle=y.isDark(f,h)?\"black\":\"white\",u.fillRect(h*g,f*g,g,g)},y};Q.stringToBytes=function(o){let e=[];for(let r=0;r<o.length;r+=1){let t=o.charCodeAt(r);e.push(t&255)}return e};Q.createStringToBytes=function(o,e){let r=(function(){let n=Ge(o),a=function(){let d=n.read();if(d==-1)throw\"eof\";return d},s=0,i={};for(;;){let d=n.read();if(d==-1)break;let m=a(),y=a(),p=a(),l=String.fromCharCode(d<<8|m),b=y<<8|p;i[l]=b,s+=1}if(s!=e)throw s+\" != \"+e;return i})(),t=63;return function(n){let a=[];for(let s=0;s<n.length;s+=1){let i=n.charCodeAt(s);if(i<128)a.push(i);else{let d=r[n.charAt(s)];typeof d==\"number\"?(d&255)==d?a.push(d):(a.push(d>>>8),a.push(d&255)):a.push(t)}}return a}};var M={MODE_NUMBER:1,MODE_ALPHA_NUM:2,MODE_8BIT_BYTE:4,MODE_KANJI:8},te={L:1,M:0,Q:3,H:2},j={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7},q=(function(){let o=[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],e=1335,r=7973,t=21522,n={},a=function(s){let i=0;for(;s!=0;)i+=1,s>>>=1;return i};return n.getBCHTypeInfo=function(s){let i=s<<10;for(;a(i)-a(e)>=0;)i^=e<<a(i)-a(e);return(s<<10|i)^t},n.getBCHTypeNumber=function(s){let i=s<<12;for(;a(i)-a(r)>=0;)i^=r<<a(i)-a(r);return s<<12|i},n.getPatternPosition=function(s){return o[s-1]},n.getMaskFunction=function(s){switch(s){case j.PATTERN000:return function(i,d){return(i+d)%2==0};case j.PATTERN001:return function(i,d){return i%2==0};case j.PATTERN010:return function(i,d){return d%3==0};case j.PATTERN011:return function(i,d){return(i+d)%3==0};case j.PATTERN100:return function(i,d){return(Math.floor(i/2)+Math.floor(d/3))%2==0};case j.PATTERN101:return function(i,d){return i*d%2+i*d%3==0};case j.PATTERN110:return function(i,d){return(i*d%2+i*d%3)%2==0};case j.PATTERN111:return function(i,d){return(i*d%3+(i+d)%2)%2==0};default:throw\"bad maskPattern:\"+s}},n.getErrorCorrectPolynomial=function(s){let i=ne([1],0);for(let d=0;d<s;d+=1)i=i.multiply(ne([1,z.gexp(d)],0));return i},n.getLengthInBits=function(s,i){if(1<=i&&i<10)switch(s){case M.MODE_NUMBER:return 10;case M.MODE_ALPHA_NUM:return 9;case M.MODE_8BIT_BYTE:return 8;case M.MODE_KANJI:return 8;default:throw\"mode:\"+s}else if(i<27)switch(s){case M.MODE_NUMBER:return 12;case M.MODE_ALPHA_NUM:return 11;case M.MODE_8BIT_BYTE:return 16;case M.MODE_KANJI:return 10;default:throw\"mode:\"+s}else if(i<41)switch(s){case M.MODE_NUMBER:return 14;case M.MODE_ALPHA_NUM:return 13;case M.MODE_8BIT_BYTE:return 16;case M.MODE_KANJI:return 12;default:throw\"mode:\"+s}else throw\"type:\"+i},n.getLostPoint=function(s){let i=s.getModuleCount(),d=0;for(let p=0;p<i;p+=1)for(let l=0;l<i;l+=1){let b=0,T=s.isDark(p,l);for(let _=-1;_<=1;_+=1)if(!(p+_<0||i<=p+_))for(let x=-1;x<=1;x+=1)l+x<0||i<=l+x||_==0&&x==0||T==s.isDark(p+_,l+x)&&(b+=1);b>5&&(d+=3+b-5)}for(let p=0;p<i-1;p+=1)for(let l=0;l<i-1;l+=1){let b=0;s.isDark(p,l)&&(b+=1),s.isDark(p+1,l)&&(b+=1),s.isDark(p,l+1)&&(b+=1),s.isDark(p+1,l+1)&&(b+=1),(b==0||b==4)&&(d+=3)}for(let p=0;p<i;p+=1)for(let l=0;l<i-6;l+=1)s.isDark(p,l)&&!s.isDark(p,l+1)&&s.isDark(p,l+2)&&s.isDark(p,l+3)&&s.isDark(p,l+4)&&!s.isDark(p,l+5)&&s.isDark(p,l+6)&&(d+=40);for(let p=0;p<i;p+=1)for(let l=0;l<i-6;l+=1)s.isDark(l,p)&&!s.isDark(l+1,p)&&s.isDark(l+2,p)&&s.isDark(l+3,p)&&s.isDark(l+4,p)&&!s.isDark(l+5,p)&&s.isDark(l+6,p)&&(d+=40);let m=0;for(let p=0;p<i;p+=1)for(let l=0;l<i;l+=1)s.isDark(l,p)&&(m+=1);let y=Math.abs(100*m/i/i-50)/5;return d+=y*10,d},n})(),z=(function(){let o=new Array(256),e=new Array(256);for(let t=0;t<8;t+=1)o[t]=1<<t;for(let t=8;t<256;t+=1)o[t]=o[t-4]^o[t-5]^o[t-6]^o[t-8];for(let t=0;t<255;t+=1)e[o[t]]=t;let r={};return r.glog=function(t){if(t<1)throw\"glog(\"+t+\")\";return e[t]},r.gexp=function(t){for(;t<0;)t+=255;for(;t>=256;)t-=255;return o[t]},r})(),ne=function(o,e){if(typeof o.length>\"u\")throw o.length+\"/\"+e;let r=(function(){let n=0;for(;n<o.length&&o[n]==0;)n+=1;let a=new Array(o.length-n+e);for(let s=0;s<o.length-n;s+=1)a[s]=o[s+n];return a})(),t={};return t.getAt=function(n){return r[n]},t.getLength=function(){return r.length},t.multiply=function(n){let a=new Array(t.getLength()+n.getLength()-1);for(let s=0;s<t.getLength();s+=1)for(let i=0;i<n.getLength();i+=1)a[s+i]^=z.gexp(z.glog(t.getAt(s))+z.glog(n.getAt(i)));return ne(a,0)},t.mod=function(n){if(t.getLength()-n.getLength()<0)return t;let a=z.glog(t.getAt(0))-z.glog(n.getAt(0)),s=new Array(t.getLength());for(let i=0;i<t.getLength();i+=1)s[i]=t.getAt(i);for(let i=0;i<n.getLength();i+=1)s[i]^=z.gexp(z.glog(n.getAt(i))+a);return ne(s,0).mod(n)},t},Ee=(function(){let o=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12,7,37,13],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]],e=function(n,a){let s={};return s.totalCount=n,s.dataCount=a,s},r={},t=function(n,a){switch(a){case te.L:return o[(n-1)*4+0];case te.M:return o[(n-1)*4+1];case te.Q:return o[(n-1)*4+2];case te.H:return o[(n-1)*4+3];default:return}};return r.getRSBlocks=function(n,a){let s=t(n,a);if(typeof s>\"u\")throw\"bad rs block @ typeNumber:\"+n+\"/errorCorrectionLevel:\"+a;let i=s.length/3,d=[];for(let m=0;m<i;m+=1){let y=s[m*3+0],p=s[m*3+1],l=s[m*3+2];for(let b=0;b<y;b+=1)d.push(e(p,l))}return d},r})(),ve=function(){let o=[],e=0,r={};return r.getBuffer=function(){return o},r.getAt=function(t){let n=Math.floor(t/8);return(o[n]>>>7-t%8&1)==1},r.put=function(t,n){for(let a=0;a<n;a+=1)r.putBit((t>>>n-a-1&1)==1)},r.getLengthInBits=function(){return e},r.putBit=function(t){let n=Math.floor(e/8);o.length<=n&&o.push(0),t&&(o[n]|=128>>>e%8),e+=1},r},We=function(o){let e=M.MODE_NUMBER,r=o,t={};t.getMode=function(){return e},t.getLength=function(s){return r.length},t.write=function(s){let i=r,d=0;for(;d+2<i.length;)s.put(n(i.substring(d,d+3)),10),d+=3;d<i.length&&(i.length-d==1?s.put(n(i.substring(d,d+1)),4):i.length-d==2&&s.put(n(i.substring(d,d+2)),7))};let n=function(s){let i=0;for(let d=0;d<s.length;d+=1)i=i*10+a(s.charAt(d));return i},a=function(s){if(\"0\"<=s&&s<=\"9\")return s.charCodeAt(0)-48;throw\"illegal char :\"+s};return t},je=function(o){let e=M.MODE_ALPHA_NUM,r=o,t={};t.getMode=function(){return e},t.getLength=function(a){return r.length},t.write=function(a){let s=r,i=0;for(;i+1<s.length;)a.put(n(s.charAt(i))*45+n(s.charAt(i+1)),11),i+=2;i<s.length&&a.put(n(s.charAt(i)),6)};let n=function(a){if(\"0\"<=a&&a<=\"9\")return a.charCodeAt(0)-48;if(\"A\"<=a&&a<=\"Z\")return a.charCodeAt(0)-65+10;switch(a){case\" \":return 36;case\"$\":return 37;case\"%\":return 38;case\"*\":return 39;case\"+\":return 40;case\"-\":return 41;case\".\":return 42;case\"/\":return 43;case\":\":return 44;default:throw\"illegal char :\"+a}};return t},qe=function(o){let e=M.MODE_8BIT_BYTE,r=o,t=Q.stringToBytes(o),n={};return n.getMode=function(){return e},n.getLength=function(a){return t.length},n.write=function(a){for(let s=0;s<t.length;s+=1)a.put(t[s],8)},n},ze=function(o){let e=M.MODE_KANJI,r=o,t=Q.stringToBytes;(function(s,i){let d=t(s);if(d.length!=2||(d[0]<<8|d[1])!=i)throw\"sjis not supported.\"})(\"\\u53CB\",38726);let n=t(o),a={};return a.getMode=function(){return e},a.getLength=function(s){return~~(n.length/2)},a.write=function(s){let i=n,d=0;for(;d+1<i.length;){let m=(255&i[d])<<8|255&i[d+1];if(33088<=m&&m<=40956)m-=33088;else if(57408<=m&&m<=60351)m-=49472;else throw\"illegal char at \"+(d+1)+\"/\"+m;m=(m>>>8&255)*192+(m&255),s.put(m,13),d+=2}if(d<i.length)throw\"illegal char at \"+(d+1)},a},_e=function(){let o=[],e={};return e.writeByte=function(r){o.push(r&255)},e.writeShort=function(r){e.writeByte(r),e.writeByte(r>>>8)},e.writeBytes=function(r,t,n){t=t||0,n=n||r.length;for(let a=0;a<n;a+=1)e.writeByte(r[a+t])},e.writeString=function(r){for(let t=0;t<r.length;t+=1)e.writeByte(r.charCodeAt(t))},e.toByteArray=function(){return o},e.toString=function(){let r=\"\";r+=\"[\";for(let t=0;t<o.length;t+=1)t>0&&(r+=\",\"),r+=o[t];return r+=\"]\",r},e},Ye=function(){let o=0,e=0,r=0,t=\"\",n={},a=function(i){t+=String.fromCharCode(s(i&63))},s=function(i){if(i<0)throw\"n:\"+i;if(i<26)return 65+i;if(i<52)return 97+(i-26);if(i<62)return 48+(i-52);if(i==62)return 43;if(i==63)return 47;throw\"n:\"+i};return n.writeByte=function(i){for(o=o<<8|i&255,e+=8,r+=1;e>=6;)a(o>>>e-6),e-=6},n.flush=function(){if(e>0&&(a(o<<6-e),o=0,e=0),r%3!=0){let i=3-r%3;for(let d=0;d<i;d+=1)t+=\"=\"}},n.toString=function(){return t},n},Ge=function(o){let e=o,r=0,t=0,n=0,a={};a.read=function(){for(;n<8;){if(r>=e.length){if(n==0)return-1;throw\"unexpected end of file./\"+n}let d=e.charAt(r);if(r+=1,d==\"=\")return n=0,-1;if(d.match(/^\\s$/))continue;t=t<<6|s(d.charCodeAt(0)),n+=6}let i=t>>>n-8&255;return n-=8,i};let s=function(i){if(65<=i&&i<=90)return i-65;if(97<=i&&i<=122)return i-97+26;if(48<=i&&i<=57)return i-48+52;if(i==43)return 62;if(i==47)return 63;throw\"c:\"+i};return a},Qe=function(o,e){let r=o,t=e,n=new Array(o*e),a={};a.setPixel=function(m,y,p){n[y*r+m]=p},a.write=function(m){m.writeString(\"GIF87a\"),m.writeShort(r),m.writeShort(t),m.writeByte(128),m.writeByte(0),m.writeByte(0),m.writeByte(0),m.writeByte(0),m.writeByte(0),m.writeByte(255),m.writeByte(255),m.writeByte(255),m.writeString(\",\"),m.writeShort(0),m.writeShort(0),m.writeShort(r),m.writeShort(t),m.writeByte(0);let y=2,p=i(y);m.writeByte(y);let l=0;for(;p.length-l>255;)m.writeByte(255),m.writeBytes(p,l,255),l+=255;m.writeByte(p.length-l),m.writeBytes(p,l,p.length-l),m.writeByte(0),m.writeString(\";\")};let s=function(m){let y=m,p=0,l=0,b={};return b.write=function(T,_){if(T>>>_)throw\"length over\";for(;p+_>=8;)y.writeByte(255&(T<<p|l)),_-=8-p,T>>>=8-p,l=0,p=0;l=T<<p|l,p=p+_},b.flush=function(){p>0&&y.writeByte(l)},b},i=function(m){let y=1<<m,p=(1<<m)+1,l=m+1,b=d();for(let D=0;D<y;D+=1)b.add(String.fromCharCode(D));b.add(String.fromCharCode(y)),b.add(String.fromCharCode(p));let T=_e(),_=s(T);_.write(y,l);let x=0,A=String.fromCharCode(n[x]);for(x+=1;x<n.length;){let D=String.fromCharCode(n[x]);x+=1,b.contains(A+D)?A=A+D:(_.write(b.indexOf(A),l),b.size()<4095&&(b.size()==1<<l&&(l+=1),b.add(A+D)),A=D)}return _.write(b.indexOf(A),l),_.write(p,l),_.flush(),T.toByteArray()},d=function(){let m={},y=0,p={};return p.add=function(l){if(p.contains(l))throw\"dup key:\"+l;m[l]=y,y+=1},p.size=function(){return y},p.indexOf=function(l){return m[l]},p.contains=function(l){return typeof m[l]<\"u\"},p};return a},Je=function(o,e,r){let t=Qe(o,e);for(let i=0;i<e;i+=1)for(let d=0;d<o;d+=1)t.setPixel(d,i,r(d,i));let n=_e();t.write(n);let a=Ye(),s=n.toByteArray();for(let i=0;i<s.length;i+=1)a.writeByte(s[i]);return a.flush(),\"data:image/gif;base64,\"+a},Xe=Q,ln=Q.stringToBytes;function Ze(o,e,r=200){ue(o);let t=document.createElement(\"canvas\"),n=t.getContext?.(\"2d\")??null;if(!n){let p=document.createElement(\"a\");p.href=e,p.textContent=\"Open in DID Wallet\",p.target=\"_blank\",p.rel=\"noopener\",o.appendChild(p);return}let a=Xe(0,\"M\");a.addData(e),a.make();let s=a.getModuleCount(),i=Math.floor(r/s),d=i*s;t.width=d,t.height=d,t.style.display=\"block\",t.style.width=`${r}px`,t.style.height=`${r}px`,t.style.imageRendering=\"pixelated\";let m=\"#e8e8e8\";n.fillStyle=m,n.fillRect(0,0,d,d),n.fillStyle=\"#000000\";for(let p=0;p<s;p++)for(let l=0;l<s;l++)a.isDark(p,l)&&n.fillRect(l*i,p*i,i,i);let y=document.createElement(\"div\");y.style.cssText=\"display:inline-block;padding:10px;background:#e8e8e8;border-radius:8px;line-height:0\",y.appendChild(t),o.appendChild(y)}function ue(o){o.innerHTML=\"\"}var et=`\n${Ne}\n${Be}\n${Le}\n\n/* Layout */\nbody {\n min-height: 100vh;\n display: flex;\n align-items: center;\n justify-content: center;\n background-image: radial-gradient(ellipse at 50% 0%, rgba(108,71,255,0.08) 0%, transparent 60%);\n}\n\n/* Card \\u2014 fixed width prevents shrinking when views switch */\n.card {\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: var(--radius-lg);\n padding: 48px 40px;\n width: min(400px, calc(100vw - 32px));\n text-align: center;\n box-shadow: var(--shadow-lg), 0 0 80px -20px rgba(108,71,255,0.15);\n}\n\n/* Brand header */\n.brand-header { margin-bottom: 24px; }\n.app-logo {\n width: 48px;\n height: 48px;\n border-radius: var(--radius-sm);\n object-fit: contain;\n margin-bottom: 16px;\n}\nh1 {\n font-size: 24px;\n font-weight: 600;\n margin-bottom: 8px;\n color: var(--text-white);\n letter-spacing: -0.01em;\n line-height: 1.25;\n /* Never let a long \"Sign in to {appName}\" blow past two lines \\u2014 clamp with\n an ellipsis so the card height stays stable. */\n display: -webkit-box;\n -webkit-line-clamp: 2;\n line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n.subtitle {\n font-size: 14px;\n color: var(--text-secondary);\n margin-bottom: 0;\n line-height: 1.5;\n}\n.federated-hint {\n font-size: 12px;\n color: var(--text-secondary);\n margin-top: 6px;\n margin-bottom: 0;\n opacity: 0.7;\n}\n\n/* Input */\n.input {\n width: 100%;\n height: 36px;\n padding: 0 12px;\n font-size: 14px;\n color: var(--text);\n background: var(--bg-input);\n border: 1px solid var(--border-strong);\n border-radius: var(--radius-sm);\n outline: none;\n margin-bottom: 16px;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n}\n.input:focus { border-color: var(--blue); box-shadow: var(--shadow-focus); }\n.input::placeholder { color: var(--text-placeholder); }\n\n/* Button */\n.btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n width: 100%;\n height: 36px;\n padding: 0 16px;\n font-size: 14px;\n font-weight: 500;\n color: #fff;\n background: var(--blue);\n border: none;\n border-radius: var(--radius-sm);\n cursor: pointer;\n transition: background-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;\n line-height: 1;\n}\n.btn:hover { background: var(--blue-hover); transform: translateY(-1px); box-shadow: 0 2px 8px rgba(108,71,255,0.3); }\n.btn:disabled { opacity: 0.5; cursor: not-allowed; pointer-events: none; transform: none; box-shadow: none; }\n.btn:focus-visible { box-shadow: var(--shadow-focus); }\n.btn svg { width: 18px; height: 18px; flex-shrink: 0; }\n.btn-secondary {\n background: transparent;\n border: 1px solid var(--border-strong);\n color: var(--text);\n}\n.btn-secondary:hover { background: var(--bg-hover); border-color: rgba(255,255,255,0.20); }\n\n/* Status */\n.status {\n margin-top: 16px;\n font-size: 13px;\n min-height: 20px;\n color: var(--text-secondary);\n}\n.status.error { color: var(--red-text); }\n\n/* Name row (passkey registration) */\n.name-row { display: none; }\n.name-row.visible { display: block; }\n\n/* Passkey section + secondary text action (\"First time? Create a passkey\") */\n.passkey-section > .btn + .passkey-alt,\n.passkey-section > .name-row + .btn { margin-top: 10px; }\n.passkey-alt {\n display: block;\n width: 100%;\n margin-top: 10px;\n padding: 4px;\n background: none;\n border: none;\n font-size: 13px;\n color: var(--text-secondary);\n cursor: pointer;\n text-align: center;\n transition: color 0.15s ease;\n}\n.passkey-alt:hover { color: var(--text); text-decoration: underline; }\n.passkey-alt:focus-visible { box-shadow: var(--shadow-focus); border-radius: var(--radius-sm); outline: none; }\n\n/* Divider */\n.divider {\n display: flex;\n align-items: center;\n margin: 20px 0;\n color: var(--text-secondary);\n font-size: 13px;\n}\n.divider::before, .divider::after {\n content: '';\n flex: 1;\n border-bottom: 1px solid var(--border);\n}\n.divider::before { margin-right: 12px; }\n.divider::after { margin-left: 12px; }\n\n/* DID Wallet section (methods view) */\n.did-wallet-section { margin-top: 8px; }\n\n/* QR view */\n.qr-view {\n position: relative;\n margin: 20px auto;\n display: flex;\n justify-content: center;\n min-height: 220px;\n align-items: center;\n}\n.qr-placeholder {\n width: 220px;\n height: 220px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius);\n background: var(--bg-elevated);\n}\n.qr-spinner {\n display: inline-block;\n width: 32px; height: 32px;\n border: 3px solid var(--border-strong);\n border-top-color: var(--blue-muted);\n border-radius: 50%;\n animation: spin 0.6s linear infinite;\n}\n#did-connect-status {\n font-size: 13px;\n color: var(--text-secondary);\n}\n\n/* Connect result state */\n.connect-result {\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 120px;\n margin: 32px 0;\n animation: fadeIn 200ms ease-out;\n}\n.result-icon {\n width: 64px;\n height: 64px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 28px;\n font-weight: bold;\n}\n.result-error {\n background: rgba(239, 68, 68, 0.15);\n color: var(--red-text);\n border: 2px solid rgba(239, 68, 68, 0.3);\n}\n.result-loading {\n background: transparent;\n}\n.result-loading .qr-spinner {\n width: 36px;\n height: 36px;\n}\n.result-back-btn {\n margin-top: 16px;\n}\n@keyframes fadeIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }\n\n/* QR timeout overlay */\n.qr-timeout-overlay {\n position: absolute;\n inset: 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n background: rgba(0, 0, 0, 0.65);\n border-radius: var(--radius);\n backdrop-filter: blur(2px);\n}\n.qr-timeout-overlay .timeout-text {\n font-size: 13px;\n color: #fff;\n}\n.qr-timeout-overlay .refresh-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 6px 20px;\n font-size: 14px;\n font-weight: 500;\n color: #fff;\n background: var(--blue);\n border: none;\n border-radius: var(--radius-sm);\n cursor: pointer;\n transition: background-color 0.15s ease;\n}\n.qr-timeout-overlay .refresh-btn:hover {\n background: var(--blue-hover);\n}\n/* Connect status views (scanned / success) */\n.connect-status-icon {\n position: relative;\n width: 72px;\n height: 72px;\n margin: 16px auto;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n@keyframes connectSpin { to { transform: rotate(360deg); } }\n.connect-spinner-ring {\n position: absolute;\n inset: 0;\n width: 72px;\n height: 72px;\n border-radius: 50%;\n border: 3px solid rgba(255,255,255,0.1);\n border-top-color: var(--blue, #6c47ff);\n animation: connectSpin 0.8s linear infinite;\n}\n.connect-status-badge {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n height: 44px;\n}\n.connect-status-badge svg { width: 36px; height: 36px; }\n.connect-status-icon.connect-success svg { animation: fadeIn 0.3s ease; }\n.connect-subtitle {\n font-size: 13px;\n color: var(--text-secondary, #9394a1);\n text-align: center;\n margin: 4px 0 0;\n}\n.status.success { color: #49dc6e; font-weight: 500; }\n.deep-link-btn {\n margin-top: 12px;\n text-decoration: none;\n}\n\n/* Email section (methods view) */\n.email-section { margin-top: 8px; }\n\n/* Back button */\n.back-btn {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n background: none;\n border: none;\n color: var(--text-secondary);\n font-size: 14px;\n cursor: pointer;\n padding: 0;\n margin-bottom: 16px;\n float: left;\n}\n.back-btn:hover { color: var(--text); }\n.back-btn svg { width: 18px; height: 18px; }\n/* Escape hatch \\u2014 a quiet \"Cancel\" link, top-left of the card, mirroring the\n locale switcher (top-right). Conventional spot for a standalone auth card.\n Low in the hierarchy (tertiary color, small) so it never competes with the\n auth methods; it's an exit, not an action. */\n.home-link {\n position: absolute;\n top: 16px;\n /* Align the arrow's left edge to the content column (card's 40px padding), so\n Cancel sits on the same left line as the title and buttons \\u2014 no left padding\n of its own, which would push it off that line. */\n left: 40px;\n display: inline-flex;\n align-items: center;\n gap: 3px;\n background: none;\n border: none;\n color: var(--text-tertiary);\n font-size: 13px;\n line-height: 1;\n text-decoration: none;\n cursor: pointer;\n padding: 2px 4px 2px 0;\n transition: color 0.15s ease;\n}\n.home-link:hover { color: var(--text-secondary); }\n.home-link svg { width: 16px; height: 16px; }\n\n/* View title */\n.view-title {\n font-size: 20px;\n font-weight: 600;\n color: var(--text-white);\n margin-bottom: 8px;\n clear: both;\n padding-top: 8px;\n}\n\n/* OAuth section \\u2014 single provider (legacy) */\n.oauth-section { margin-top: 0; }\n\n/* OAuth grid \\u2014 2+ providers in 2-column layout (legacy) */\n.oauth-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 8px;\n margin-bottom: 8px;\n}\n.oauth-grid .oauth-btn:last-child:nth-child(odd) {\n grid-column: 1 / -1;\n}\n\n/* Unified secondary methods grid \\u2014 2-column layout */\n.methods-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 8px;\n}\n/* Odd last child (e.g. DID Wallet when total is odd) spans full width */\n.methods-grid .method-btn:last-child:nth-child(odd) {\n grid-column: 1 / -1;\n}\n\n.method-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n width: 100%;\n min-height: 36px;\n padding: 6px 16px;\n font-size: 14px;\n font-weight: 500;\n color: var(--text);\n background: var(--bg-elevated);\n border: 1px solid var(--border-strong);\n border-radius: var(--radius-sm);\n cursor: pointer;\n transition: background-color 0.15s ease, border-color 0.15s ease, opacity 0.2s ease;\n white-space: normal;\n}\n.method-btn:hover { background: var(--bg-hover); border-color: var(--blue-muted); }\n.method-btn:disabled { cursor: default; pointer-events: none; }\n.method-btn svg { width: 18px; height: 18px; flex-shrink: 0; }\n\n.oauth-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n width: 100%;\n height: 36px;\n padding: 0 16px;\n font-size: 14px;\n font-weight: 500;\n color: var(--text);\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: var(--radius-sm);\n cursor: pointer;\n transition: background-color 0.15s ease, opacity 0.2s ease, border-color 0.2s ease;\n margin-bottom: 0;\n}\n.oauth-section .oauth-btn { margin-bottom: 8px; }\n.oauth-btn:hover { background: var(--bg-hover); }\n.oauth-btn:disabled { cursor: default; pointer-events: none; }\n.oauth-btn svg { width: 18px; height: 18px; flex-shrink: 0; }\n.oauth-btn-loading {\n opacity: 0.8;\n border-color: var(--blue);\n color: var(--text-secondary);\n}\n@keyframes oauth-spin { to { transform: rotate(360deg); } }\n.oauth-spinner {\n display: inline-block;\n width: 14px; height: 14px;\n border: 2px solid var(--border-strong);\n border-top-color: var(--blue-muted);\n border-radius: 50%;\n animation: oauth-spin 0.6s linear infinite;\n}\n.oauth-check { color: var(--green-text); font-size: 16px; }\n\n/* Browser compatibility warning */\n.compat-warning {\n font-size: 13px;\n color: var(--text-tertiary);\n padding: 12px;\n background: var(--bg-elevated);\n border-radius: var(--radius-sm);\n margin-bottom: 16px;\n}\n\n/* Trust footer */\n.login-footer {\n margin-top: 24px;\n padding-top: 20px;\n border-top: 1px solid var(--border);\n}\n.secured-badge {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n font-size: 12px;\n color: var(--text-tertiary);\n}\n.secured-badge svg { width: 14px; height: 14px; flex-shrink: 0; }\n.footer-links {\n margin-top: 8px;\n font-size: 12px;\n color: var(--text-tertiary);\n}\n.footer-links a {\n color: var(--text-secondary);\n text-decoration: none;\n}\n.footer-links a:hover { text-decoration: underline; }\n\n/* \\u2500\\u2500\\u2500 Animations \\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500 */\n@keyframes fadeIn {\n from { opacity: 0; transform: translateY(4px); }\n to { opacity: 1; transform: translateY(0); }\n}\n@keyframes fadeOut {\n from { opacity: 1; }\n to { opacity: 0; }\n}\n.card[data-view] { animation: fadeIn 200ms ease-out; }\n\n/* QR code scale-in */\n.qr-view { animation: qrScaleIn 300ms ease-out; }\n@keyframes qrScaleIn {\n from { opacity: 0; transform: scale(0.95); }\n to { opacity: 1; transform: scale(1); }\n}\n\n/* Button loading spinner */\n@keyframes spin { to { transform: rotate(360deg); } }\n.btn-spinner {\n display: inline-block;\n width: 16px; height: 16px;\n border: 2px solid rgba(255,255,255,0.3);\n border-top-color: #fff;\n border-radius: 50%;\n animation: spin 0.6s linear infinite;\n}\n\n/* Success checkmark */\n@keyframes checkDraw {\n from { stroke-dashoffset: 24; }\n to { stroke-dashoffset: 0; }\n}\n.success-check {\n display: inline-block;\n width: 20px; height: 20px;\n vertical-align: middle;\n}\n.success-check path {\n stroke-dasharray: 24;\n stroke-dashoffset: 24;\n animation: checkDraw 400ms ease-out forwards;\n}\n\n/* Light theme background gradient adjustment */\n[data-theme=\"light\"] body {\n background-image: radial-gradient(ellipse at 50% 0%, rgba(108,71,255,0.05) 0%, transparent 60%);\n}\n[data-theme=\"light\"] .card {\n box-shadow: var(--shadow-lg), 0 0 80px -20px rgba(108,71,255,0.08);\n}\n[data-theme=\"light\"] .btn:hover {\n box-shadow: 0 2px 8px rgba(108,71,255,0.15);\n}\n\n/* Language switcher */\n.locale-switcher {\n position: absolute;\n top: 16px;\n right: 16px;\n}\n.locale-btn {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 4px 10px;\n font-size: 12px;\n color: var(--text-secondary);\n background: var(--bg-elevated);\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius-full);\n cursor: pointer;\n transition: color 0.15s, border-color 0.15s;\n}\n.locale-btn:hover {\n color: var(--text);\n border-color: var(--border-strong);\n}\n.locale-btn svg { width: 14px; height: 14px; flex-shrink: 0; }\n.card { position: relative; }\n`;function Y(o){let e=new Uint8Array(o),r=\"\";for(let t=0;t<e.length;t++)r+=String.fromCharCode(e[t]);return btoa(r).replace(/\\+/g,\"-\").replace(/\\//g,\"_\").replace(/=+$/,\"\")}function ee(o){let e=o.replace(/-/g,\"+\").replace(/_/g,\"/\"),r=atob(e),t=new Uint8Array(r.length);for(let n=0;n<r.length;n++)t[n]=r.charCodeAt(n);return t.buffer}function tt(o){let e=o.response;return{id:o.id,rawId:Y(o.rawId),type:o.type,response:{attestationObject:Y(e.attestationObject),clientDataJSON:Y(e.clientDataJSON)},clientExtensionResults:o.getClientExtensionResults(),authenticatorAttachment:o.authenticatorAttachment}}function nt(o){let e=o.response;return{id:o.id,rawId:Y(o.rawId),type:o.type,response:{authenticatorData:Y(e.authenticatorData),clientDataJSON:Y(e.clientDataJSON),signature:Y(e.signature),userHandle:e.userHandle?Y(e.userHandle):void 0},clientExtensionResults:o.getClientExtensionResults(),authenticatorAttachment:o.authenticatorAttachment}}async function oe(o){let e=o.prefix||\"/.well-known/service/api/passkey\",r=o.onStatus||(()=>{}),t=o.authOnly||!1,n=o.registerOnly||!1,a=o.invitationId||void 0,s=o.texts||{},i=s.waiting||\"Waiting for passkey...\",d=s.creating||\"Creating your passkey...\",m=s.verifying||\"Verifying...\";async function y(){let x=await fetch(`${e}/auth`,{method:\"GET\"});if(!x.ok)throw new Error(\"Failed to get auth challenge\");return x.json()}async function p(x){let A=[];x&&A.push(`name=${encodeURIComponent(x)}`),a&&A.push(`invitationId=${encodeURIComponent(a)}`);let D=A.length?`?${A.join(\"&\")}`:\"\",$=await fetch(`${e}/register${D}`,{method:\"GET\"});if(!$.ok)throw new Error(\"Failed to get register challenge\");return $.json()}function l(x,A){let D=new Error(x);throw A&&(D.code=A),D}async function b(x,A){let D=await fetch(`${e}/auth`,{method:\"POST\",headers:{\"Content-Type\":\"application/json\"},body:JSON.stringify({challengeId:x,credential:A})});if(!D.ok){let $=await D.json().catch(()=>({}));l($.error||\"Authentication failed\",$.code)}return D.json()}async function T(x,A,D){let $=await fetch(`${e}/register`,{method:\"POST\",headers:{\"Content-Type\":\"application/json\"},body:JSON.stringify({challengeId:x,credential:A,name:D||void 0})});if(!$.ok){let U=await $.json().catch(()=>({}));l(U.error||\"Registration failed\",U.code)}return $.json()}if(!n){r(i,!1);try{let x=await y(),{challengeId:A}=x,D=x.authentication||x,$={publicKey:{...D,challenge:ee(D.challenge),allowCredentials:(D.allowCredentials||[]).map(V=>({...V,id:ee(V.id)}))}},U;try{U=await navigator.credentials.get($)}catch{if(t)return{success:!1,error:\"no_passkey\"}}if(U){let V=nt(U);return r(m,!1),await b(A,V),{success:!0}}}catch(x){let A=x instanceof Error?x.message:\"Authentication failed\",D=x instanceof Error?x.code:void 0;return r(A,!0),{success:!1,error:A,code:D}}}let _=await p(o.name||\"\");if(_.registrationAllowed===!1)return r(\"Registration requires an invitation.\",!0),{success:!1,error:\"registration_closed\"};r(d,!1);try{let x=_.registration||_,A={publicKey:{...x,challenge:ee(x.challenge),user:{...x.user,id:ee(x.user.id)},excludeCredentials:(x.excludeCredentials||[]).map(U=>({...U,id:ee(U.id)}))}},D=await navigator.credentials.create(A),$=tt(D);return r(m,!1),await T(_.challengeId,$,o.name||\"\"),{success:!0}}catch(x){let A=x instanceof Error?x.message:\"Passkey setup failed\",D=x instanceof Error?x.code:void 0;return r(A,!0),{success:!1,error:A,code:D}}}function G(o,e){let r=o instanceof Error?o.message:\"\",t=o instanceof Error&&\"code\"in o?o.code:\"\";return t===\"FORBIDDEN\"?E(\"accessDenied\",e):r===\"registration_closed\"||t===\"registration_closed\"?E(\"regClosed\",e):r||E(\"authFailed\",e)}var rt=\"https://web.abtwallet.io/\";function ot(){return typeof navigator>\"u\"?!1:/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)}function st(){return typeof window>\"u\"?null:window.ABT_DEV??window.ABT??null}function K(o){return o.replace(/&/g,\"&\").replace(/</g,\"<\").replace(/>/g,\">\").replace(/\"/g,\""\").replace(/'/g,\"'\")}var ke=class{container;config;http;options;tokenSession=null;styleEl=null;mounted=!1;currentView=\"methods\";cardEl=null;mediaQuery=null;mediaListener=null;constructor(o,e,r,t){if(this.container=o,this.config=e,this.http=r,this.options={methods:[\"passkey\",\"did-connect\"],locale:\"en\",...t},!this.options.invitationId&&typeof document<\"u\"){let n=document.cookie.match(/(?:^|;\\s*)pending_invitation=([^;]*)/);if(n)try{this.options.invitationId=decodeURIComponent(n[1])}catch{}}}mount(){if(this.mounted)return;this.mounted=!0,this.styleEl=document.createElement(\"style\"),this.styleEl.textContent=et,this.container.appendChild(this.styleEl),this.applyTheme();let o=this.filterMethods(this.options.methods);this.renderCard(o)}destroy(){this.tokenSession&&(this.tokenSession.destroy(),this.tokenSession=null),this.mediaQuery&&this.mediaListener&&(this.mediaQuery.removeEventListener(\"change\",this.mediaListener),this.mediaQuery=null,this.mediaListener=null),document.documentElement.removeAttribute(\"data-theme\"),this.container.innerHTML=\"\",this.styleEl=null,this.cardEl=null,this.currentView=\"methods\",this.mounted=!1}update(o){Object.assign(this.options,o),this.mounted&&(this.destroy(),this.mount())}applyTheme(){let o=this.options.theme??\"dark\";if(o===\"light\")document.documentElement.dataset.theme=\"light\";else if(o===\"auto\"&&typeof window.matchMedia==\"function\"){let e=window.matchMedia(\"(prefers-color-scheme: light)\");this.mediaQuery=e;let r=t=>{t?document.documentElement.dataset.theme=\"light\":document.documentElement.removeAttribute(\"data-theme\")};r(e.matches),this.mediaListener=t=>r(t.matches),e.addEventListener(\"change\",this.mediaListener)}}filterMethods(o){return o.filter(e=>e===\"passkey\"?typeof window<\"u\"&&window.PublicKeyCredential!==void 0&&typeof window.PublicKeyCredential==\"function\":!0)}renderCard(o){let e=document.createElement(\"div\");e.className=\"card\",this.cardEl=e,this.container.appendChild(e),this.switchView(\"methods\",o)}renderLocaleSwitcher(o){if(ce.length<2)return;let e=document.createElement(\"div\");e.className=\"locale-switcher\";let r=this.options.locale,t=ce.find(a=>a.code!==r)??ce[0],n=document.createElement(\"button\");n.type=\"button\",n.className=\"locale-btn\",n.innerHTML=`${Ke} ${K(t.label)}`,n.title=t.label,n.addEventListener(\"click\",()=>{this.options.onLocaleChange?.(t.code),this.update({locale:t.code})}),e.appendChild(n),o.appendChild(e)}switchView(o,e){if(!this.cardEl)return;let r=this.options.locale;switch(this.currentView===\"qr\"&&o!==\"qr\"&&this.tokenSession&&(this.tokenSession.destroy(),this.tokenSession=null),this.currentView=o,this.cardEl.innerHTML=\"\",this.cardEl.dataset.view=o,this.renderLocaleSwitcher(this.cardEl),o){case\"methods\":this.renderMethodsView(this.cardEl,r,e??this.filterMethods(this.options.methods));break;case\"qr\":this.renderQRView(this.cardEl,r);break;case\"email\":this.renderEmailView(this.cardEl,r);break}}renderMethodsView(o,e,r){this.renderHomeLink(o,e),this.renderBrandHeader(o,e);let t=this.options.methods.includes(\"passkey\");if(t&&!r.includes(\"passkey\")){let s=document.createElement(\"div\");s.className=\"compat-warning\",s.textContent=E(\"passkeyUnsupported\",e),o.appendChild(s)}let n=r.includes(\"passkey\");n&&this.renderPasskeySection(o,e);let a=r.filter(s=>s!==\"passkey\");if(n&&a.length>0&&this.renderDivider(o,e),a.length>0&&this.renderSecondaryGrid(o,e,r),r.length===0&&!t){let s=document.createElement(\"p\");s.className=\"subtitle\",s.textContent=\"No authentication methods available.\",o.appendChild(s)}this.renderTrustFooter(o,e)}renderQRView(o,e){this.renderBackButton(o,e);let r=document.createElement(\"h2\");r.className=\"view-title\",r.textContent=E(\"scanTitle\",e),o.appendChild(r);let t=document.createElement(\"div\");t.id=\"did-connect-qr\",t.className=\"qr-view\",t.innerHTML='<div class=\"qr-placeholder\"><span class=\"qr-spinner\"></span></div>',o.appendChild(t);let n=document.createElement(\"button\");n.className=\"btn btn-secondary deep-link-btn\",n.id=\"did-wallet-open-btn\",n.textContent=E(\"openInWallet\",e),n.disabled=!0,o.appendChild(n);let a=document.createElement(\"div\");a.id=\"did-connect-status\",a.className=\"status\",o.appendChild(a),this.renderTrustFooter(o,e),this.startDIDConnectInView(a,t,n,e)}renderEmailView(o,e){this.renderBackButton(o,e);let r=document.createElement(\"h2\");r.className=\"view-title\",r.textContent=E(\"emailTitle\",e),o.appendChild(r);let t=document.createElement(\"input\");t.className=\"input\",t.type=\"email\",t.placeholder=E(\"emailPlaceholder\",e),o.appendChild(t);let n=document.createElement(\"button\");n.className=\"btn\",n.textContent=E(\"emailSubmit\",e),o.appendChild(n);let a=document.createElement(\"div\");a.className=\"status\",o.appendChild(a);let s=document.createElement(\"div\");s.className=\"email-code-section\",s.style.display=\"none\";let i=document.createElement(\"input\");i.className=\"input\",i.type=\"text\",i.inputMode=\"numeric\",i.maxLength=6,i.autocomplete=\"one-time-code\",i.placeholder=E(\"emailCodePlaceholder\",e),s.appendChild(i);let d=document.createElement(\"button\");d.className=\"btn\",d.textContent=E(\"emailVerify\",e),s.appendChild(d);let m=document.createElement(\"button\");m.className=\"btn btn-secondary\",m.textContent=E(\"emailResend\",e),m.style.marginTop=\"8px\",s.appendChild(m),o.insertBefore(s,a),this.renderTrustFooter(o,e);let y=\"\",p=this.config.servicePrefix??\"/.well-known/service\",l=async()=>{let T=t.value.trim();if(!T?.includes(\"@\")){a.textContent=E(\"emailInvalid\",e),a.className=\"status error\";return}n.disabled=!0,a.textContent=E(\"emailSending\",e),a.className=\"status\";try{await this.http.post(`${p}/api/email/sendCode`,{email:T,locale:e}),y=T,t.style.display=\"none\",n.style.display=\"none\",s.style.display=\"\",r.textContent=E(\"emailVerify\",e),a.textContent=E(\"emailSent\",e,{email:T}),a.className=\"status\",i.focus()}catch(_){let x=_ instanceof Error?_.message:\"Failed to send email\";a.textContent=x,a.className=\"status error\",n.disabled=!1}},b=async()=>{let T=i.value.trim();if(!T||T.length!==6){a.textContent=E(\"emailCodeInvalid\",e),a.className=\"status error\";return}d.disabled=!0,a.textContent=E(\"verifying\",e),a.className=\"status\";try{await this.http.post(`${p}/api/email/login`,{code:T}),a.textContent=E(\"success\",e),a.className=\"status\",this.handleSuccess()}catch(_){_ instanceof Error&&\"code\"in _&&_.code===\"FORBIDDEN\"?this.showFullError(G(_,e)):(a.textContent=G(_,e),a.className=\"status error\",d.disabled=!1,i.value=\"\",i.focus())}};n.addEventListener(\"click\",l),t.addEventListener(\"keydown\",T=>{T.key===\"Enter\"&&l()}),d.addEventListener(\"click\",b),i.addEventListener(\"keydown\",T=>{T.key===\"Enter\"&&b()}),m.addEventListener(\"click\",async()=>{m.disabled=!0,a.textContent=E(\"emailSending\",e),a.className=\"status\";try{await this.http.post(`${p}/api/email/sendCode`,{email:y,locale:e}),a.textContent=E(\"emailSent\",e,{email:y}),a.className=\"status\"}catch(T){let _=T instanceof Error?T.message:\"Failed to resend\";a.textContent=_,a.className=\"status error\"}m.disabled=!1})}renderBackButton(o,e){let r=document.createElement(\"button\");r.className=\"back-btn\",r.innerHTML=`${xe} ${K(E(\"back\",e))}`,r.addEventListener(\"click\",()=>this.switchView(\"methods\")),o.appendChild(r)}renderHomeLink(o,e){let{homeUrl:r,onCancel:t}=this.options;if(!t&&!r)return;let n=`${xe} ${K(E(\"cancel\",e))}`;if(t){let a=document.createElement(\"button\");a.type=\"button\",a.className=\"home-link\",a.innerHTML=n,a.addEventListener(\"click\",()=>this.options.onCancel?.()),o.appendChild(a)}else{let a=document.createElement(\"a\");a.className=\"home-link\",a.href=r,a.innerHTML=n,o.appendChild(a)}}renderBrandHeader(o,e){let r=document.createElement(\"div\");if(r.className=\"brand-header\",this.options.appLogo){let n=document.createElement(\"img\");n.className=\"app-logo\",n.src=K(this.options.appLogo),n.alt=this.options.appName?K(this.options.appName):\"\",n.width=48,n.height=48,r.appendChild(n)}let t=document.createElement(\"h1\");if(this.options.appName?t.textContent=E(\"signInTo\",e,{appName:this.options.appName}):t.textContent=E(\"signIn\",e),r.appendChild(t),this.options.masterAppName){let n=document.createElement(\"p\");n.className=\"federated-hint\",n.textContent=E(\"federatedBy\",e,{masterName:this.options.masterAppName}),r.appendChild(n)}o.appendChild(r)}renderTrustFooter(o,e){let r=document.createElement(\"div\");r.className=\"login-footer\";let t=document.createElement(\"div\");t.className=\"secured-badge\",t.innerHTML=`${Ue} ${K(E(\"securedBy\",e))}`,r.appendChild(t);let{privacyUrl:n,termsUrl:a}=this.options;if(n||a){let s=document.createElement(\"div\");if(s.className=\"footer-links\",n){let i=document.createElement(\"a\");i.href=n,i.target=\"_blank\",i.rel=\"noopener noreferrer\",i.textContent=E(\"privacy\",e),s.appendChild(i)}if(n&&a&&s.appendChild(document.createTextNode(\" \\xB7 \")),a){let i=document.createElement(\"a\");i.href=a,i.target=\"_blank\",i.rel=\"noopener noreferrer\",i.textContent=E(\"terms\",e),s.appendChild(i)}r.appendChild(s)}o.appendChild(r)}renderDivider(o,e){let r=document.createElement(\"div\");r.className=\"divider\",r.textContent=E(\"or\",e),o.appendChild(r)}renderPasskeySection(o,e){let r=document.createElement(\"div\");r.className=\"passkey-section\",o.appendChild(r);let t=document.createElement(\"div\");t.className=\"status\",t.id=\"passkey-status\",o.appendChild(t);let n=(l,b)=>{t.textContent=l,t.className=`status${b?\" error\":\"\"}`},a=()=>n(\"\",!1),s={waiting:E(\"passkeyWaiting\",e),creating:E(\"passkeyCreating\",e),verifying:E(\"verifying\",e)},i=l=>l.success?(n(E(\"success\",e),!1),this.handleSuccess(),!0):l.code===\"FORBIDDEN\"||l.error===\"registration_closed\"?(this.showFullError(G({message:l.error,code:l.code||l.error},e)),!0):!1,d=()=>{r.innerHTML=\"\",n(E(\"passkeyNoLocal\",e),!1);let l=document.createElement(\"button\");l.className=\"btn\",l.id=\"passkey-btn\",l.innerHTML=`${le} ${K(E(\"passkeyUseAnotherDevice\",e))}`,r.appendChild(l),l.addEventListener(\"click\",()=>y());let b=document.createElement(\"button\");b.type=\"button\",b.className=\"passkey-alt\",b.textContent=E(\"passkeyCreateInstead\",e),r.appendChild(b),b.addEventListener(\"click\",()=>m())},m=()=>{r.innerHTML=\"\",a();let l=document.createElement(\"div\");l.className=\"name-row visible\",l.id=\"passkey-name-row\";let b=document.createElement(\"input\");b.className=\"input\",b.id=\"passkey-name-input\",b.type=\"text\",b.placeholder=E(\"namePlaceholder\",e),b.autocomplete=\"name\",l.appendChild(b),r.appendChild(l);let T=document.createElement(\"button\");T.className=\"btn\",T.id=\"passkey-btn\",T.innerHTML=`${le} ${K(E(\"passkeyCreate\",e))}`,r.appendChild(T);let _=document.createElement(\"button\");_.type=\"button\",_.className=\"passkey-alt\",_.textContent=E(\"passkeyBackToSignIn\",e),r.appendChild(_);let x=async()=>{T.disabled=!0;let A=await oe({registerOnly:!0,name:b.value.trim(),onStatus:n,texts:s,prefix:this.options.passkeyPrefix,invitationId:this.options.invitationId});i(A)||(A.error&&n(A.error,!0),T.disabled=!1)};T.addEventListener(\"click\",x),b.addEventListener(\"keydown\",A=>{A.key===\"Enter\"&&x()}),_.addEventListener(\"click\",()=>p()),b.focus()},y=async()=>{let l=r.querySelector(\"#passkey-btn\");l&&(l.disabled=!0);let b=await oe({authOnly:!0,onStatus:n,texts:s,prefix:this.options.passkeyPrefix,invitationId:this.options.invitationId});if(!i(b)){if(b.error&&b.error!==\"no_passkey\"){n(b.error,!0),l&&(l.disabled=!1);return}d()}},p=()=>{r.innerHTML=\"\",a();let l=document.createElement(\"button\");l.className=\"btn\",l.id=\"passkey-btn\",l.innerHTML=`${le} ${K(E(\"passkeySignIn\",e))}`,r.appendChild(l),l.addEventListener(\"click\",()=>y());let b=document.createElement(\"button\");b.type=\"button\",b.className=\"passkey-alt\",b.textContent=E(\"passkeyFirstTime\",e),r.appendChild(b),b.addEventListener(\"click\",()=>m())};this.options.invitationId?m():p()}startOAuthPopup(o,e){let r=this.config.servicePrefix??\"/.well-known/service\",n=`${this.options.masterOAuthOrigin?`${this.options.masterOAuthOrigin}${r}`:r}/api/oauth/${encodeURIComponent(o)}/login?returnUrl=${encodeURIComponent(window.location.href)}`,a=500,s=620,i=window.screenX+(window.innerWidth-a)/2,d=window.screenY+(window.innerHeight-s)/2,m=window.open(n,\"oauth-login:popup\",`left=${i},top=${d},width=${a},height=${s},resizable,scrollbars=yes,status=1,popup`);if(!m){window.location.href=n;return}let y=e.innerHTML;e.innerHTML='<span class=\"oauth-spinner\"></span> Authorizing...',e.disabled=!0,e.classList.add(\"oauth-btn-loading\");let p=()=>{e.innerHTML=y,e.disabled=!1,e.classList.remove(\"oauth-btn-loading\")},l=async _=>{if(!_.data||_.data.type!==\"authorization_response\")return;if(T(),m.close(),_.data.error){p();return}let{code:x,state:A}=_.data.response??_.data;if(!x){p();return}e.innerHTML='<span class=\"oauth-spinner\"></span> Signing in...';try{let D={provider:o,code:x,state:A};this.options.invitationId&&(D.invitationId=this.options.invitationId),await this.http.post(`${r}/api/oauth/login`,D),e.innerHTML='<span class=\"oauth-check\">\\u2713</span> Success',this.handleSuccess()}catch(D){let $=D instanceof Error?D.message:\"\",U=D instanceof Error&&\"code\"in D?D.code:\"\";U===\"FORBIDDEN\"||$===\"registration_closed\"||U===\"registration_closed\"?this.showFullError(G(D,this.options.locale)):p()}},b=setInterval(()=>{m.closed&&(T(),p())},500),T=()=>{clearInterval(b),window.removeEventListener(\"message\",l)};window.addEventListener(\"message\",l)}renderSecondaryGrid(o,e,r){let t=document.createElement(\"div\");if(t.className=\"methods-grid\",r.includes(\"oauth\")&&this.options.oauthProviders?.length){let n=(this.options.oauthProviders??[]).filter(a=>a.enabled);for(let a of n){let s=document.createElement(\"button\");s.className=\"method-btn\",s.dataset.provider=a.id;let i=Ve(a.id);s.innerHTML=`${i} ${K(a.name)}`,t.appendChild(s),s.addEventListener(\"click\",()=>{this.startOAuthPopup(a.id,s)})}}if(r.includes(\"email\")){let n=document.createElement(\"button\");n.className=\"method-btn\",n.id=\"email-btn\",n.innerHTML=`${Fe} ${K(E(\"emailButton\",e))}`,t.appendChild(n),n.addEventListener(\"click\",()=>{this.switchView(\"email\")})}if(r.includes(\"did-connect\")){let n=document.createElement(\"button\");n.className=\"method-btn\",n.id=\"did-wallet-btn\";let a=t.children.length>0?E(\"didWalletButtonShort\",e):E(\"didWalletButton\",e);n.innerHTML=`${we} ${K(a)}`,t.appendChild(n),n.addEventListener(\"click\",()=>{this.switchView(\"qr\")})}o.appendChild(t)}async startDIDConnectInView(o,e,r,t){o.textContent=E(\"connecting\",t);try{this.tokenSession?.destroy(),this.tokenSession=new Z(this.config,this.http),this.tokenSession.on(\"succeed\",async()=>{this.showConnectResult(e,r,o,E(\"verifying\",t));try{let a=this.config.servicePrefix??\"/.well-known/service\";await this.http.post(`${a}/api/did/connect/complete`,{token:this.tokenSession.state.token}),this.showConnectSuccess(e,r,o,t),this.handleSuccess()}catch(a){this.showConnectResult(e,r,o,G(a,t),!0)}}),this.tokenSession.on(\"timeout\",()=>{o.textContent=E(\"timeout\",t);let a=document.createElement(\"div\");a.className=\"qr-timeout-overlay\",a.innerHTML=`<span class=\"timeout-text\">${K(E(\"timeout\",t))}</span>`;let s=document.createElement(\"button\");s.className=\"refresh-btn\",s.textContent=E(\"refreshQR\",t),s.addEventListener(\"click\",()=>{a.remove(),ue(e),e.innerHTML='<div class=\"qr-placeholder\"><span class=\"qr-spinner\"></span></div>',r.disabled=!0,this.startDIDConnectInView(o,e,r,t)}),a.appendChild(s),e.appendChild(a)}),this.tokenSession.on(\"error\",a=>{this.showConnectResult(e,r,o,G(a,t),!0)}),this.tokenSession.on(\"statusChange\",a=>{a.status===\"scanned\"&&this.showConnectScanned(e,r,o,t)});let n=await this.tokenSession.create({locale:t});n.url&&(Ze(e,n.url),r.disabled=!1,r.addEventListener(\"click\",()=>{this.showConnectScanned(e,r,o,t),this.openInWallet(n.url,t)})),o.textContent=E(\"scanWithWallet\",t),this.tokenSession.startPolling()}catch(n){o.textContent=G(n,t),ue(e)}}showFullError(o){if(!this.cardEl)return;let e=this.options.locale;this.tokenSession&&(this.tokenSession.destroy(),this.tokenSession=null),this.cardEl.innerHTML=\"\",this.cardEl.dataset.view=\"error\",this.renderLocaleSwitcher(this.cardEl);let r=document.createElement(\"div\");r.className=\"connect-result\",r.innerHTML='<div class=\"result-icon result-error\">\\u2715</div>',this.cardEl.appendChild(r);let t=document.createElement(\"div\");t.className=\"status error\",t.textContent=o,this.cardEl.appendChild(t);let n=document.createElement(\"button\");n.className=\"btn btn-secondary result-back-btn\",n.textContent=E(\"back\",e),n.addEventListener(\"click\",()=>this.switchView(\"methods\")),this.cardEl.appendChild(n),this.renderTrustFooter(this.cardEl,e)}showConnectScanned(o,e,r,t=\"en\"){o.style.display=\"none\",e.style.display=\"none\";let n=o.parentElement?.querySelector(\".connect-result\");n||(n=document.createElement(\"div\"),n.className=\"connect-result\",o.parentElement?.insertBefore(n,r)),n.innerHTML=`<div class=\"connect-status-icon\">\n <div class=\"connect-spinner-ring\"></div>\n <span class=\"connect-status-badge\">${we}</span>\n </div>`,r.textContent=E(\"waitingWallet\",t),r.className=\"status\";let a=r.parentElement?.querySelector(\".connect-subtitle\");a||(a=document.createElement(\"p\"),a.className=\"connect-subtitle\",r.parentElement?.insertBefore(a,r.nextSibling)),a.textContent=E(\"walletContinue\",t);let s=r.parentElement?.querySelector(\".result-back-btn\");s||(s=document.createElement(\"button\"),s.className=\"btn btn-secondary result-back-btn\",s.textContent=E(\"back\",t),s.addEventListener(\"click\",()=>this.switchView(\"methods\")),a.parentElement?.insertBefore(s,a.nextSibling))}showConnectSuccess(o,e,r,t=\"en\"){o.style.display=\"none\",e.style.display=\"none\";let n=o.parentElement?.querySelector(\".connect-result\");n||(n=document.createElement(\"div\"),n.className=\"connect-result\",o.parentElement?.insertBefore(n,r)),n.innerHTML=`<div class=\"connect-status-icon connect-success\">\n <svg viewBox=\"0 0 64 64\" width=\"64\" height=\"64\"><circle cx=\"32\" cy=\"32\" r=\"28\" fill=\"none\" stroke=\"#49dc6e\" stroke-width=\"3\"/><polyline points=\"20 33 28 41 44 25\" fill=\"none\" stroke=\"#49dc6e\" stroke-width=\"3\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>\n </div>`,r.textContent=E(\"success\",t),r.className=\"status success\";let a=r.parentElement?.querySelector(\".connect-subtitle\"),s=r.parentElement?.querySelector(\".result-back-btn\");a?.remove(),s?.remove()}showConnectResult(o,e,r,t,n=!1){let a=this.options.locale;o.style.display=\"none\",e.style.display=\"none\";let s=o.parentElement?.querySelector(\".connect-result\");if(s||(s=document.createElement(\"div\"),s.className=\"connect-result\",o.parentElement?.insertBefore(s,r)),n){s.innerHTML='<div class=\"result-icon result-error\">\\u2715</div>',r.textContent=t,r.className=\"status error\";let i=r.parentElement?.querySelector(\".result-back-btn\");i||(i=document.createElement(\"button\"),i.className=\"btn btn-secondary result-back-btn\",i.textContent=E(\"back\",a),i.addEventListener(\"click\",()=>this.switchView(\"methods\")),r.parentElement?.insertBefore(i,r.nextSibling))}else s.innerHTML='<div class=\"result-icon result-loading\"><span class=\"qr-spinner\"></span></div>',r.textContent=t,r.className=\"status\"}openInWallet(o,e=\"en\"){let r=ot(),t=st();if(r){let n=o.replace(/^https?:\\/\\//,\"abt://\");window.location.href=n}else if(t&&typeof t.open==\"function\"){let n=this.tokenSession?.state;t.open({action:this.config.action??\"login\",locale:e,url:encodeURIComponent(o),appInfo:n?.appInfo??{},memberAppInfo:n?.memberAppInfo??{}})}else{let n=`${rt}?action=requestAuth&url=${encodeURIComponent(o)}`,a=414,s=736,i=window.screenX+window.innerWidth-a,d=window.screenY;window.open(n,\"did-wallet:popup\",`left=${i},top=${d},width=${a},height=${s},resizable,scrollbars=yes,status=1,popup`)}}handleSuccess(){this.options.onSuccess?this.options.onSuccess():setTimeout(()=>location.reload(),300)}};return Se(it);})();\n","qr.c0d203ca.js":"\"use strict\";var __QRBundle=(()=>{var K=Object.defineProperty;var tt=Object.getOwnPropertyDescriptor;var et=Object.getOwnPropertyNames;var nt=Object.prototype.hasOwnProperty;var rt=(h,w)=>{for(var d in w)K(h,d,{get:w[d],enumerable:!0})},ot=(h,w,d,o)=>{if(w&&typeof w==\"object\"||typeof w==\"function\")for(let i of et(w))!nt.call(h,i)&&i!==d&&K(h,i,{get:()=>w[i],enumerable:!(o=tt(w,i))||o.enumerable});return h};var it=h=>ot(K({},\"__esModule\",{value:!0}),h);var dt={};rt(dt,{clearQR:()=>F,renderQR:()=>Z});var U=function(h,w){let i=h,l=Q[w],e=null,t=0,c=null,x=[],y={},a=function(r,u){t=i*4+17,e=(function(n){let f=new Array(n);for(let s=0;s<n;s+=1){f[s]=new Array(n);for(let p=0;p<n;p+=1)f[s][p]=null}return f})(t),g(0,0),g(t-7,0),g(0,t-7),k(),m(),R(r,u),i>=7&&E(r),c==null&&(c=q(i,l,x)),P(c,u)},g=function(r,u){for(let n=-1;n<=7;n+=1)if(!(r+n<=-1||t<=r+n))for(let f=-1;f<=7;f+=1)u+f<=-1||t<=u+f||(0<=n&&n<=6&&(f==0||f==6)||0<=f&&f<=6&&(n==0||n==6)||2<=n&&n<=4&&2<=f&&f<=4?e[r+n][u+f]=!0:e[r+n][u+f]=!1)},_=function(){let r=0,u=0;for(let n=0;n<8;n+=1){a(!0,n);let f=O.getLostPoint(y);(n==0||r>f)&&(r=f,u=n)}return u},m=function(){for(let r=8;r<t-8;r+=1)e[r][6]==null&&(e[r][6]=r%2==0);for(let r=8;r<t-8;r+=1)e[6][r]==null&&(e[6][r]=r%2==0)},k=function(){let r=O.getPatternPosition(i);for(let u=0;u<r.length;u+=1)for(let n=0;n<r.length;n+=1){let f=r[u],s=r[n];if(e[f][s]==null)for(let p=-2;p<=2;p+=1)for(let b=-2;b<=2;b+=1)p==-2||p==2||b==-2||b==2||p==0&&b==0?e[f+p][s+b]=!0:e[f+p][s+b]=!1}},E=function(r){let u=O.getBCHTypeNumber(i);for(let n=0;n<18;n+=1){let f=!r&&(u>>n&1)==1;e[Math.floor(n/3)][n%3+t-8-3]=f}for(let n=0;n<18;n+=1){let f=!r&&(u>>n&1)==1;e[n%3+t-8-3][Math.floor(n/3)]=f}},R=function(r,u){let n=l<<3|u,f=O.getBCHTypeInfo(n);for(let s=0;s<15;s+=1){let p=!r&&(f>>s&1)==1;s<6?e[s][8]=p:s<8?e[s+1][8]=p:e[t-15+s][8]=p}for(let s=0;s<15;s+=1){let p=!r&&(f>>s&1)==1;s<8?e[8][t-s-1]=p:s<9?e[8][15-s-1+1]=p:e[8][15-s-1]=p}e[t-8][8]=!r},P=function(r,u){let n=-1,f=t-1,s=7,p=0,b=O.getMaskFunction(u);for(let B=t-1;B>0;B-=2)for(B==6&&(B-=1);;){for(let M=0;M<2;M+=1)if(e[f][B-M]==null){let T=!1;p<r.length&&(T=(r[p]>>>s&1)==1),b(f,B-M)&&(T=!T),e[f][B-M]=T,s-=1,s==-1&&(p+=1,s=7)}if(f+=n,f<0||t<=f){f-=n,n=-n;break}}},V=function(r,u){let n=0,f=0,s=0,p=new Array(u.length),b=new Array(u.length);for(let A=0;A<u.length;A+=1){let C=u[A].dataCount,L=u[A].totalCount-C;f=Math.max(f,C),s=Math.max(s,L),p[A]=new Array(C);for(let I=0;I<p[A].length;I+=1)p[A][I]=255&r.getBuffer()[I+n];n+=C;let j=O.getErrorCorrectPolynomial(L),Y=S(p[A],j.getLength()-1).mod(j);b[A]=new Array(j.getLength()-1);for(let I=0;I<b[A].length;I+=1){let G=I+Y.getLength()-b[A].length;b[A][I]=G>=0?Y.getAt(G):0}}let B=0;for(let A=0;A<u.length;A+=1)B+=u[A].totalCount;let M=new Array(B),T=0;for(let A=0;A<f;A+=1)for(let C=0;C<u.length;C+=1)A<p[C].length&&(M[T]=p[C][A],T+=1);for(let A=0;A<s;A+=1)for(let C=0;C<u.length;C+=1)A<b[C].length&&(M[T]=b[C][A],T+=1);return M},q=function(r,u,n){let f=J.getRSBlocks(r,u),s=$();for(let b=0;b<n.length;b+=1){let B=n[b];s.put(B.getMode(),4),s.put(B.getLength(),O.getLengthInBits(B.getMode(),r)),B.write(s)}let p=0;for(let b=0;b<f.length;b+=1)p+=f[b].dataCount;if(s.getLengthInBits()>p*8)throw\"code length overflow. (\"+s.getLengthInBits()+\">\"+p*8+\")\";for(s.getLengthInBits()+4<=p*8&&s.put(0,4);s.getLengthInBits()%8!=0;)s.putBit(!1);for(;!(s.getLengthInBits()>=p*8||(s.put(236,8),s.getLengthInBits()>=p*8));)s.put(17,8);return V(s,f)};y.addData=function(r,u){u=u||\"Byte\";let n=null;switch(u){case\"Numeric\":n=st(r);break;case\"Alphanumeric\":n=ft(r);break;case\"Byte\":n=ct(r);break;case\"Kanji\":n=ut(r);break;default:throw\"mode:\"+u}x.push(n),c=null},y.isDark=function(r,u){if(r<0||t<=r||u<0||t<=u)throw r+\",\"+u;return e[r][u]},y.getModuleCount=function(){return t},y.make=function(){if(i<1){let r=1;for(;r<40;r++){let u=J.getRSBlocks(r,l),n=$();for(let s=0;s<x.length;s++){let p=x[s];n.put(p.getMode(),4),n.put(p.getLength(),O.getLengthInBits(p.getMode(),r)),p.write(n)}let f=0;for(let s=0;s<u.length;s++)f+=u[s].dataCount;if(n.getLengthInBits()<=f*8)break}i=r}a(!1,_())},y.createTableTag=function(r,u){r=r||2,u=typeof u>\"u\"?r*4:u;let n=\"\";n+='<table style=\"',n+=\" border-width: 0px; border-style: none;\",n+=\" border-collapse: collapse;\",n+=\" padding: 0px; margin: \"+u+\"px;\",n+='\">',n+=\"<tbody>\";for(let f=0;f<y.getModuleCount();f+=1){n+=\"<tr>\";for(let s=0;s<y.getModuleCount();s+=1)n+='<td style=\"',n+=\" border-width: 0px; border-style: none;\",n+=\" border-collapse: collapse;\",n+=\" padding: 0px; margin: 0px;\",n+=\" width: \"+r+\"px;\",n+=\" height: \"+r+\"px;\",n+=\" background-color: \",n+=y.isDark(f,s)?\"#000000\":\"#ffffff\",n+=\";\",n+='\"/>';n+=\"</tr>\"}return n+=\"</tbody>\",n+=\"</table>\",n},y.createSvgTag=function(r,u,n,f){let s={};typeof arguments[0]==\"object\"&&(s=arguments[0],r=s.cellSize,u=s.margin,n=s.alt,f=s.title),r=r||2,u=typeof u>\"u\"?r*4:u,n=typeof n==\"string\"?{text:n}:n||{},n.text=n.text||null,n.id=n.text?n.id||\"qrcode-description\":null,f=typeof f==\"string\"?{text:f}:f||{},f.text=f.text||null,f.id=f.text?f.id||\"qrcode-title\":null;let p=y.getModuleCount()*r+u*2,b,B,M,T,A=\"\",C;for(C=\"l\"+r+\",0 0,\"+r+\" -\"+r+\",0 0,-\"+r+\"z \",A+='<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\"',A+=s.scalable?\"\":' width=\"'+p+'px\" height=\"'+p+'px\"',A+=' viewBox=\"0 0 '+p+\" \"+p+'\" ',A+=' preserveAspectRatio=\"xMinYMin meet\"',A+=f.text||n.text?' role=\"img\" aria-labelledby=\"'+H([f.id,n.id].join(\" \").trim())+'\"':\"\",A+=\">\",A+=f.text?'<title id=\"'+H(f.id)+'\">'+H(f.text)+\"</title>\":\"\",A+=n.text?'<description id=\"'+H(n.id)+'\">'+H(n.text)+\"</description>\":\"\",A+='<rect width=\"100%\" height=\"100%\" fill=\"white\" cx=\"0\" cy=\"0\"/>',A+='<path d=\"',M=0;M<y.getModuleCount();M+=1)for(T=M*r+u,b=0;b<y.getModuleCount();b+=1)y.isDark(M,b)&&(B=b*r+u,A+=\"M\"+B+\",\"+T+C);return A+='\" stroke=\"transparent\" fill=\"black\"/>',A+=\"</svg>\",A},y.createDataURL=function(r,u){r=r||2,u=typeof u>\"u\"?r*4:u;let n=y.getModuleCount()*r+u*2,f=u,s=n-u;return gt(n,n,function(p,b){if(f<=p&&p<s&&f<=b&&b<s){let B=Math.floor((p-f)/r),M=Math.floor((b-f)/r);return y.isDark(M,B)?0:1}else return 1})},y.createImgTag=function(r,u,n){r=r||2,u=typeof u>\"u\"?r*4:u;let f=y.getModuleCount()*r+u*2,s=\"\";return s+=\"<img\",s+=' src=\"',s+=y.createDataURL(r,u),s+='\"',s+=' width=\"',s+=f,s+='\"',s+=' height=\"',s+=f,s+='\"',n&&(s+=' alt=\"',s+=H(n),s+='\"'),s+=\"/>\",s};let H=function(r){let u=\"\";for(let n=0;n<r.length;n+=1){let f=r.charAt(n);switch(f){case\"<\":u+=\"<\";break;case\">\":u+=\">\";break;case\"&\":u+=\"&\";break;case'\"':u+=\""\";break;default:u+=f;break}}return u},z=function(r){r=typeof r>\"u\"?2:r;let n=y.getModuleCount()*1+r*2,f=r,s=n-r,p,b,B,M,T,A={\"\\u2588\\u2588\":\"\\u2588\",\"\\u2588 \":\"\\u2580\",\" \\u2588\":\"\\u2584\",\" \":\" \"},C={\"\\u2588\\u2588\":\"\\u2580\",\"\\u2588 \":\"\\u2580\",\" \\u2588\":\" \",\" \":\" \"},L=\"\";for(p=0;p<n;p+=2){for(B=Math.floor((p-f)/1),M=Math.floor((p+1-f)/1),b=0;b<n;b+=1)T=\"\\u2588\",f<=b&&b<s&&f<=p&&p<s&&y.isDark(B,Math.floor((b-f)/1))&&(T=\" \"),f<=b&&b<s&&f<=p+1&&p+1<s&&y.isDark(M,Math.floor((b-f)/1))?T+=\" \":T+=\"\\u2588\",L+=r<1&&p+1>=s?C[T]:A[T];L+=`\n`}return n%2&&r>0?L.substring(0,L.length-n-1)+Array(n+1).join(\"\\u2580\"):L.substring(0,L.length-1)};return y.createASCII=function(r,u){if(r=r||1,r<2)return z(u);r-=1,u=typeof u>\"u\"?r*2:u;let n=y.getModuleCount()*r+u*2,f=u,s=n-u,p,b,B,M,T=Array(r+1).join(\"\\u2588\\u2588\"),A=Array(r+1).join(\" \"),C=\"\",L=\"\";for(p=0;p<n;p+=1){for(B=Math.floor((p-f)/r),L=\"\",b=0;b<n;b+=1)M=1,f<=b&&b<s&&f<=p&&p<s&&y.isDark(B,Math.floor((b-f)/r))&&(M=0),L+=M?T:A;for(B=0;B<r;B+=1)C+=L+`\n`}return C.substring(0,C.length-1)},y.renderTo2dContext=function(r,u){u=u||2;let n=y.getModuleCount();for(let f=0;f<n;f++)for(let s=0;s<n;s++)r.fillStyle=y.isDark(f,s)?\"black\":\"white\",r.fillRect(s*u,f*u,u,u)},y};U.stringToBytes=function(h){let w=[];for(let d=0;d<h.length;d+=1){let o=h.charCodeAt(d);w.push(o&255)}return w};U.createStringToBytes=function(h,w){let d=(function(){let i=at(h),l=function(){let c=i.read();if(c==-1)throw\"eof\";return c},e=0,t={};for(;;){let c=i.read();if(c==-1)break;let x=l(),y=l(),a=l(),g=String.fromCharCode(c<<8|x),_=y<<8|a;t[g]=_,e+=1}if(e!=w)throw e+\" != \"+w;return t})(),o=63;return function(i){let l=[];for(let e=0;e<i.length;e+=1){let t=i.charCodeAt(e);if(t<128)l.push(t);else{let c=d[i.charAt(e)];typeof c==\"number\"?(c&255)==c?l.push(c):(l.push(c>>>8),l.push(c&255)):l.push(o)}}return l}};var D={MODE_NUMBER:1,MODE_ALPHA_NUM:2,MODE_8BIT_BYTE:4,MODE_KANJI:8},Q={L:1,M:0,Q:3,H:2},N={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7},O=(function(){let h=[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],w=1335,d=7973,o=21522,i={},l=function(e){let t=0;for(;e!=0;)t+=1,e>>>=1;return t};return i.getBCHTypeInfo=function(e){let t=e<<10;for(;l(t)-l(w)>=0;)t^=w<<l(t)-l(w);return(e<<10|t)^o},i.getBCHTypeNumber=function(e){let t=e<<12;for(;l(t)-l(d)>=0;)t^=d<<l(t)-l(d);return e<<12|t},i.getPatternPosition=function(e){return h[e-1]},i.getMaskFunction=function(e){switch(e){case N.PATTERN000:return function(t,c){return(t+c)%2==0};case N.PATTERN001:return function(t,c){return t%2==0};case N.PATTERN010:return function(t,c){return c%3==0};case N.PATTERN011:return function(t,c){return(t+c)%3==0};case N.PATTERN100:return function(t,c){return(Math.floor(t/2)+Math.floor(c/3))%2==0};case N.PATTERN101:return function(t,c){return t*c%2+t*c%3==0};case N.PATTERN110:return function(t,c){return(t*c%2+t*c%3)%2==0};case N.PATTERN111:return function(t,c){return(t*c%3+(t+c)%2)%2==0};default:throw\"bad maskPattern:\"+e}},i.getErrorCorrectPolynomial=function(e){let t=S([1],0);for(let c=0;c<e;c+=1)t=t.multiply(S([1,v.gexp(c)],0));return t},i.getLengthInBits=function(e,t){if(1<=t&&t<10)switch(e){case D.MODE_NUMBER:return 10;case D.MODE_ALPHA_NUM:return 9;case D.MODE_8BIT_BYTE:return 8;case D.MODE_KANJI:return 8;default:throw\"mode:\"+e}else if(t<27)switch(e){case D.MODE_NUMBER:return 12;case D.MODE_ALPHA_NUM:return 11;case D.MODE_8BIT_BYTE:return 16;case D.MODE_KANJI:return 10;default:throw\"mode:\"+e}else if(t<41)switch(e){case D.MODE_NUMBER:return 14;case D.MODE_ALPHA_NUM:return 13;case D.MODE_8BIT_BYTE:return 16;case D.MODE_KANJI:return 12;default:throw\"mode:\"+e}else throw\"type:\"+t},i.getLostPoint=function(e){let t=e.getModuleCount(),c=0;for(let a=0;a<t;a+=1)for(let g=0;g<t;g+=1){let _=0,m=e.isDark(a,g);for(let k=-1;k<=1;k+=1)if(!(a+k<0||t<=a+k))for(let E=-1;E<=1;E+=1)g+E<0||t<=g+E||k==0&&E==0||m==e.isDark(a+k,g+E)&&(_+=1);_>5&&(c+=3+_-5)}for(let a=0;a<t-1;a+=1)for(let g=0;g<t-1;g+=1){let _=0;e.isDark(a,g)&&(_+=1),e.isDark(a+1,g)&&(_+=1),e.isDark(a,g+1)&&(_+=1),e.isDark(a+1,g+1)&&(_+=1),(_==0||_==4)&&(c+=3)}for(let a=0;a<t;a+=1)for(let g=0;g<t-6;g+=1)e.isDark(a,g)&&!e.isDark(a,g+1)&&e.isDark(a,g+2)&&e.isDark(a,g+3)&&e.isDark(a,g+4)&&!e.isDark(a,g+5)&&e.isDark(a,g+6)&&(c+=40);for(let a=0;a<t;a+=1)for(let g=0;g<t-6;g+=1)e.isDark(g,a)&&!e.isDark(g+1,a)&&e.isDark(g+2,a)&&e.isDark(g+3,a)&&e.isDark(g+4,a)&&!e.isDark(g+5,a)&&e.isDark(g+6,a)&&(c+=40);let x=0;for(let a=0;a<t;a+=1)for(let g=0;g<t;g+=1)e.isDark(g,a)&&(x+=1);let y=Math.abs(100*x/t/t-50)/5;return c+=y*10,c},i})(),v=(function(){let h=new Array(256),w=new Array(256);for(let o=0;o<8;o+=1)h[o]=1<<o;for(let o=8;o<256;o+=1)h[o]=h[o-4]^h[o-5]^h[o-6]^h[o-8];for(let o=0;o<255;o+=1)w[h[o]]=o;let d={};return d.glog=function(o){if(o<1)throw\"glog(\"+o+\")\";return w[o]},d.gexp=function(o){for(;o<0;)o+=255;for(;o>=256;)o-=255;return h[o]},d})(),S=function(h,w){if(typeof h.length>\"u\")throw h.length+\"/\"+w;let d=(function(){let i=0;for(;i<h.length&&h[i]==0;)i+=1;let l=new Array(h.length-i+w);for(let e=0;e<h.length-i;e+=1)l[e]=h[e+i];return l})(),o={};return o.getAt=function(i){return d[i]},o.getLength=function(){return d.length},o.multiply=function(i){let l=new Array(o.getLength()+i.getLength()-1);for(let e=0;e<o.getLength();e+=1)for(let t=0;t<i.getLength();t+=1)l[e+t]^=v.gexp(v.glog(o.getAt(e))+v.glog(i.getAt(t)));return S(l,0)},o.mod=function(i){if(o.getLength()-i.getLength()<0)return o;let l=v.glog(o.getAt(0))-v.glog(i.getAt(0)),e=new Array(o.getLength());for(let t=0;t<o.getLength();t+=1)e[t]=o.getAt(t);for(let t=0;t<i.getLength();t+=1)e[t]^=v.gexp(v.glog(i.getAt(t))+l);return S(e,0).mod(i)},o},J=(function(){let h=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12,7,37,13],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]],w=function(i,l){let e={};return e.totalCount=i,e.dataCount=l,e},d={},o=function(i,l){switch(l){case Q.L:return h[(i-1)*4+0];case Q.M:return h[(i-1)*4+1];case Q.Q:return h[(i-1)*4+2];case Q.H:return h[(i-1)*4+3];default:return}};return d.getRSBlocks=function(i,l){let e=o(i,l);if(typeof e>\"u\")throw\"bad rs block @ typeNumber:\"+i+\"/errorCorrectionLevel:\"+l;let t=e.length/3,c=[];for(let x=0;x<t;x+=1){let y=e[x*3+0],a=e[x*3+1],g=e[x*3+2];for(let _=0;_<y;_+=1)c.push(w(a,g))}return c},d})(),$=function(){let h=[],w=0,d={};return d.getBuffer=function(){return h},d.getAt=function(o){let i=Math.floor(o/8);return(h[i]>>>7-o%8&1)==1},d.put=function(o,i){for(let l=0;l<i;l+=1)d.putBit((o>>>i-l-1&1)==1)},d.getLengthInBits=function(){return w},d.putBit=function(o){let i=Math.floor(w/8);h.length<=i&&h.push(0),o&&(h[i]|=128>>>w%8),w+=1},d},st=function(h){let w=D.MODE_NUMBER,d=h,o={};o.getMode=function(){return w},o.getLength=function(e){return d.length},o.write=function(e){let t=d,c=0;for(;c+2<t.length;)e.put(i(t.substring(c,c+3)),10),c+=3;c<t.length&&(t.length-c==1?e.put(i(t.substring(c,c+1)),4):t.length-c==2&&e.put(i(t.substring(c,c+2)),7))};let i=function(e){let t=0;for(let c=0;c<e.length;c+=1)t=t*10+l(e.charAt(c));return t},l=function(e){if(\"0\"<=e&&e<=\"9\")return e.charCodeAt(0)-48;throw\"illegal char :\"+e};return o},ft=function(h){let w=D.MODE_ALPHA_NUM,d=h,o={};o.getMode=function(){return w},o.getLength=function(l){return d.length},o.write=function(l){let e=d,t=0;for(;t+1<e.length;)l.put(i(e.charAt(t))*45+i(e.charAt(t+1)),11),t+=2;t<e.length&&l.put(i(e.charAt(t)),6)};let i=function(l){if(\"0\"<=l&&l<=\"9\")return l.charCodeAt(0)-48;if(\"A\"<=l&&l<=\"Z\")return l.charCodeAt(0)-65+10;switch(l){case\" \":return 36;case\"$\":return 37;case\"%\":return 38;case\"*\":return 39;case\"+\":return 40;case\"-\":return 41;case\".\":return 42;case\"/\":return 43;case\":\":return 44;default:throw\"illegal char :\"+l}};return o},ct=function(h){let w=D.MODE_8BIT_BYTE,d=h,o=U.stringToBytes(h),i={};return i.getMode=function(){return w},i.getLength=function(l){return o.length},i.write=function(l){for(let e=0;e<o.length;e+=1)l.put(o[e],8)},i},ut=function(h){let w=D.MODE_KANJI,d=h,o=U.stringToBytes;(function(e,t){let c=o(e);if(c.length!=2||(c[0]<<8|c[1])!=t)throw\"sjis not supported.\"})(\"\\u53CB\",38726);let i=o(h),l={};return l.getMode=function(){return w},l.getLength=function(e){return~~(i.length/2)},l.write=function(e){let t=i,c=0;for(;c+1<t.length;){let x=(255&t[c])<<8|255&t[c+1];if(33088<=x&&x<=40956)x-=33088;else if(57408<=x&&x<=60351)x-=49472;else throw\"illegal char at \"+(c+1)+\"/\"+x;x=(x>>>8&255)*192+(x&255),e.put(x,13),c+=2}if(c<t.length)throw\"illegal char at \"+(c+1)},l},W=function(){let h=[],w={};return w.writeByte=function(d){h.push(d&255)},w.writeShort=function(d){w.writeByte(d),w.writeByte(d>>>8)},w.writeBytes=function(d,o,i){o=o||0,i=i||d.length;for(let l=0;l<i;l+=1)w.writeByte(d[l+o])},w.writeString=function(d){for(let o=0;o<d.length;o+=1)w.writeByte(d.charCodeAt(o))},w.toByteArray=function(){return h},w.toString=function(){let d=\"\";d+=\"[\";for(let o=0;o<h.length;o+=1)o>0&&(d+=\",\"),d+=h[o];return d+=\"]\",d},w},lt=function(){let h=0,w=0,d=0,o=\"\",i={},l=function(t){o+=String.fromCharCode(e(t&63))},e=function(t){if(t<0)throw\"n:\"+t;if(t<26)return 65+t;if(t<52)return 97+(t-26);if(t<62)return 48+(t-52);if(t==62)return 43;if(t==63)return 47;throw\"n:\"+t};return i.writeByte=function(t){for(h=h<<8|t&255,w+=8,d+=1;w>=6;)l(h>>>w-6),w-=6},i.flush=function(){if(w>0&&(l(h<<6-w),h=0,w=0),d%3!=0){let t=3-d%3;for(let c=0;c<t;c+=1)o+=\"=\"}},i.toString=function(){return o},i},at=function(h){let w=h,d=0,o=0,i=0,l={};l.read=function(){for(;i<8;){if(d>=w.length){if(i==0)return-1;throw\"unexpected end of file./\"+i}let c=w.charAt(d);if(d+=1,c==\"=\")return i=0,-1;if(c.match(/^\\s$/))continue;o=o<<6|e(c.charCodeAt(0)),i+=6}let t=o>>>i-8&255;return i-=8,t};let e=function(t){if(65<=t&&t<=90)return t-65;if(97<=t&&t<=122)return t-97+26;if(48<=t&&t<=57)return t-48+52;if(t==43)return 62;if(t==47)return 63;throw\"c:\"+t};return l},ht=function(h,w){let d=h,o=w,i=new Array(h*w),l={};l.setPixel=function(x,y,a){i[y*d+x]=a},l.write=function(x){x.writeString(\"GIF87a\"),x.writeShort(d),x.writeShort(o),x.writeByte(128),x.writeByte(0),x.writeByte(0),x.writeByte(0),x.writeByte(0),x.writeByte(0),x.writeByte(255),x.writeByte(255),x.writeByte(255),x.writeString(\",\"),x.writeShort(0),x.writeShort(0),x.writeShort(d),x.writeShort(o),x.writeByte(0);let y=2,a=t(y);x.writeByte(y);let g=0;for(;a.length-g>255;)x.writeByte(255),x.writeBytes(a,g,255),g+=255;x.writeByte(a.length-g),x.writeBytes(a,g,a.length-g),x.writeByte(0),x.writeString(\";\")};let e=function(x){let y=x,a=0,g=0,_={};return _.write=function(m,k){if(m>>>k)throw\"length over\";for(;a+k>=8;)y.writeByte(255&(m<<a|g)),k-=8-a,m>>>=8-a,g=0,a=0;g=m<<a|g,a=a+k},_.flush=function(){a>0&&y.writeByte(g)},_},t=function(x){let y=1<<x,a=(1<<x)+1,g=x+1,_=c();for(let P=0;P<y;P+=1)_.add(String.fromCharCode(P));_.add(String.fromCharCode(y)),_.add(String.fromCharCode(a));let m=W(),k=e(m);k.write(y,g);let E=0,R=String.fromCharCode(i[E]);for(E+=1;E<i.length;){let P=String.fromCharCode(i[E]);E+=1,_.contains(R+P)?R=R+P:(k.write(_.indexOf(R),g),_.size()<4095&&(_.size()==1<<g&&(g+=1),_.add(R+P)),R=P)}return k.write(_.indexOf(R),g),k.write(a,g),k.flush(),m.toByteArray()},c=function(){let x={},y=0,a={};return a.add=function(g){if(a.contains(g))throw\"dup key:\"+g;x[g]=y,y+=1},a.size=function(){return y},a.indexOf=function(g){return x[g]},a.contains=function(g){return typeof x[g]<\"u\"},a};return l},gt=function(h,w,d){let o=ht(h,w);for(let t=0;t<w;t+=1)for(let c=0;c<h;c+=1)o.setPixel(c,t,d(c,t));let i=W();o.write(i);let l=lt(),e=i.toByteArray();for(let t=0;t<e.length;t+=1)l.writeByte(e[t]);return l.flush(),\"data:image/gif;base64,\"+l},X=U,xt=U.stringToBytes;function Z(h,w,d=200){F(h);let o=document.createElement(\"canvas\"),i=o.getContext?.(\"2d\")??null;if(!i){let a=document.createElement(\"a\");a.href=w,a.textContent=\"Open in DID Wallet\",a.target=\"_blank\",a.rel=\"noopener\",h.appendChild(a);return}let l=X(0,\"M\");l.addData(w),l.make();let e=l.getModuleCount(),t=Math.floor(d/e),c=t*e;o.width=c,o.height=c,o.style.display=\"block\",o.style.width=`${d}px`,o.style.height=`${d}px`,o.style.imageRendering=\"pixelated\";let x=\"#e8e8e8\";i.fillStyle=x,i.fillRect(0,0,c,c),i.fillStyle=\"#000000\";for(let a=0;a<e;a++)for(let g=0;g<e;g++)l.isDark(a,g)&&i.fillRect(g*t,a*t,t,t);let y=document.createElement(\"div\");y.style.cssText=\"display:inline-block;padding:10px;background:#e8e8e8;border-radius:8px;line-height:0\",y.appendChild(o),h.appendChild(y)}function F(h){h.innerHTML=\"\"}return it(dt);})();\n","did-address.7df30f28.js":"\"use strict\";var __DidAddressBundle=(()=>{var p=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var $=Object.getOwnPropertyNames;var A=Object.prototype.hasOwnProperty;var D=(e,t)=>{for(var o in t)p(e,o,{get:t[o],enumerable:!0})},E=(e,t,o,i)=>{if(t&&typeof t==\"object\"||typeof t==\"function\")for(let s of $(t))!A.call(e,s)&&s!==o&&p(e,s,{get:()=>t[s],enumerable:!(i=k(t,s))||i.enumerable});return e};var L=e=>E(p({},\"__esModule\",{value:!0}),e);var S={};D(S,{DidAddressElement:()=>n,truncateDid:()=>h});var d=\"did:abt:\";function H(e){if(!e||typeof e!=\"string\")return!1;let t=e.replace(d,\"\");return/^(0x)?[0-9a-f]{40}$/i.test(t)}function h(e,t=8,o=6){return!e||typeof e!=\"string\"?\"\":e.length<=t+o+3?e:`${e.slice(0,t)}...${e.slice(-o)}`}function m(e,t={}){if(!e||typeof e!=\"string\")return{prefix:\"DID: ABT:\",chainLabel:\"ABT\",address:\"\",compact:\"\",copyText:\"\"};let{startChars:o=8,endChars:i=6}=t,s=H(e),r=e.replace(d,\"\"),c=s?\"ETH\":\"ABT\",a=t.includePrefix??!s;return{prefix:`DID: ${c}:`,chainLabel:c,address:r,compact:h(r,o,i),copyText:a?`${d}${r}`:r}}var M=typeof HTMLElement<\"u\"?HTMLElement:class{},b='<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\"/><path d=\"M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1\"/></svg>',P='<svg width=\"1em\" height=\"1em\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"/></svg>',B=`\n:host {\n display: inline-flex;\n align-items: center;\n max-width: 100%;\n overflow: hidden;\n font-family: 'SF Mono', 'JetBrains Mono', 'Menlo', 'Consolas', monospace;\n line-height: 1.5;\n}\n:host([block]) { display: flex; }\n.prefix {\n flex-shrink: 0;\n opacity: 0.6;\n margin-right: 4px;\n white-space: nowrap;\n}\n.address {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: var(--did-text-color, currentColor);\n}\n.address-compact {\n white-space: nowrap;\n color: var(--did-text-color, currentColor);\n cursor: default;\n}\n.clickable { cursor: pointer; }\n.clickable:hover { opacity: 0.8; }\n.copy-btn {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1em;\n height: 1em;\n margin-left: 6px;\n padding: 0;\n border: none;\n background: none;\n color: inherit;\n opacity: 0.5;\n cursor: pointer;\n transition: opacity 0.15s;\n}\n.copy-btn:hover { opacity: 1; }\n.copy-btn.copied { opacity: 1; color: var(--did-copy-success-color, #22c55e); }\n`,n=class extends M{static observedAttributes=[\"did\",\"compact\",\"copyable\",\"start-chars\",\"end-chars\",\"show-prefix\",\"no-tooltip\",\"no-copy\",\"block\",\"size\"];shadow;copyTimer=null;constructor(){super(),this.shadow=this.attachShadow({mode:\"open\"})}connectedCallback(){this.render()}attributeChangedCallback(){this.render()}disconnectedCallback(){this.copyTimer&&clearTimeout(this.copyTimer)}get parts(){let t=Number(this.getAttribute(\"start-chars\")),o=Number(this.getAttribute(\"end-chars\"));return m(this.getAttribute(\"did\")||\"\",{startChars:t>0?t:void 0,endChars:o>0?o:void 0})}render(){if(!(this.getAttribute(\"did\")||\"\")){this.shadow.innerHTML=\"\";return}let o=this.parts,i=this.hasAttribute(\"compact\"),s=!this.hasAttribute(\"no-copy\")&&this.getAttribute(\"copyable\")!==\"false\",r=this.hasAttribute(\"show-prefix\"),c=this.hasAttribute(\"no-tooltip\"),a=Number(this.getAttribute(\"size\"))||0,x=a>=12?`${a}px`:\"inherit\",g=i?o.compact:o.address,C=!c&&i?` title=\"${o.address}\"`:\"\",w=r?`<span class=\"prefix\">${o.prefix} </span>`:\"\",u=i?\"address-compact\":\"address\",T=s?\" clickable\":\"\",v=s?`<button class=\"copy-btn\" aria-label=\"Copy DID\">${b}</button>`:\"\";if(this.shadow.innerHTML=`\n <style>${B}:host { font-size: ${x}; }</style>\n ${w}<span class=\"${u}${T}\"${C}>${g}</span>${v}\n `,s){let f=this.shadow.querySelector(`.${u}`);f&&f.addEventListener(\"click\",l=>{l.stopPropagation(),this.copyToClipboard(o.copyText)});let y=this.shadow.querySelector(\".copy-btn\");y&&y.addEventListener(\"click\",l=>{l.stopPropagation(),this.copyToClipboard(o.copyText)})}}copyToClipboard(t){navigator.clipboard?.writeText?navigator.clipboard.writeText(t).catch(()=>this.fallbackCopy(t)):this.fallbackCopy(t),this.showCopied()}fallbackCopy(t){try{let o=document.createElement(\"input\");o.value=t,document.body.appendChild(o),o.select(),document.execCommand?.(\"copy\"),o.remove()}catch{}}showCopied(){let t=this.shadow.querySelector(\".copy-btn\");t&&(t.classList.add(\"copied\"),t.innerHTML=P,this.copyTimer&&clearTimeout(this.copyTimer),this.copyTimer=setTimeout(()=>{t.classList.remove(\"copied\"),t.innerHTML=b},1500))}};typeof customElements<\"u\"&&!customElements.get(\"did-address\")&&customElements.define(\"did-address\",n);return L(S);})();\n","header.94d9e46b.js":"\"use strict\";var __BlockletHeaderBundle=(()=>{var f=typeof HTMLElement<\"u\"?HTMLElement:class{},m='<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4m0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4\"/></svg>',x='<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M17 7l-1.41 1.41L18.17 11H8v2h10.17l-2.58 2.58L17 17l5-5zM4 5h8V3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8v-2H4z\"/></svg>',k='<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4m0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4\"/></svg>',y='<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M3 13h8V3H3zm0 8h8v-6H3zm10 0h8V11h-8zm0-18v6h8V3z\"/></svg>';var v={en:{home:\"Home\",userCenter:\"User Center\",adminConsole:\"Admin Console\",billing:\"Billing\",signOut:\"Sign Out\",signIn:\"Sign In\"},zh:{home:\"\\u9996\\u9875\",userCenter:\"\\u4E2A\\u4EBA\\u4E2D\\u5FC3\",adminConsole:\"\\u63A7\\u5236\\u53F0\",billing:\"\\u8D26\\u5355\",signOut:\"\\u9000\\u51FA\\u767B\\u5F55\",signIn:\"\\u767B\\u5F55\"}},C=`\n:host {\n display: block;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 14px;\n /* Use design tokens from host page (inherited through Shadow DOM), with fallbacks */\n --header-bg: var(--bg-surface, #111827);\n --header-border: var(--border, #374151);\n --header-text: var(--text-white, #f3f4f6);\n --header-text-secondary: var(--text-tertiary, #9ca3af);\n --header-hover: var(--bg-hover, #1f2937);\n --header-primary: var(--blue-muted, #60a5fa);\n --header-height: 56px;\n --badge-bg: var(--blue-light, #1e3a5f);\n --badge-text: var(--blue-muted, #93c5fd);\n --dropdown-shadow: var(--shadow-lg, 0 4px 24px rgba(0,0,0,.4));\n --dropdown-bg: var(--bg-card, #111827);\n --dropdown-border: var(--border, #374151);\n --dropdown-divider: var(--border-subtle, #1f2937);\n --red-danger: var(--red-text, #ef4444);\n --red-danger-bg: var(--red-light, #fef2f2);\n}\n* { box-sizing: border-box; margin: 0; padding: 0; }\n.header {\n display: flex; align-items: center; justify-content: space-between;\n height: var(--header-height); padding: 0 16px;\n background: var(--header-bg); border-bottom: 1px solid var(--header-border);\n color: var(--header-text); position: relative; z-index: 100;\n}\n.header-left { display: flex; align-items: center; gap: 16px; min-width: 0; }\n.header-left a { color: var(--header-text-secondary); text-decoration: none; font-size: 13px; transition: color .15s; white-space: nowrap; }\n.header-left a:hover { color: var(--header-text); text-decoration: none; }\n.logo { height: 28px; width: auto; border-radius: 4px; flex-shrink: 0; }\n.header-right { display: flex; align-items: center; gap: 8px; }\n.avatar {\n width: 32px; height: 32px; border-radius: 50%; background: var(--header-hover);\n display: flex; align-items: center; justify-content: center; overflow: hidden;\n color: var(--header-text-secondary); flex-shrink: 0; cursor: pointer;\n}\n.avatar img { width: 100%; height: 100%; object-fit: cover; }\n.user-trigger {\n position: relative;\n display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 0;\n border: none; background: none; color: var(--header-text);\n font: inherit;\n}\n.user-name { display: none; }\n.user-arrow { font-size: 12px; color: var(--header-text-secondary); line-height: 1; }\n.badge {\n display: inline-flex; align-items: center; padding: 2px 8px; border-radius: 999px;\n font-size: 11px; font-weight: 500; background: var(--badge-bg); color: var(--badge-text);\n line-height: 1.45; white-space: nowrap;\n}\n.badge-owner { background: var(--blue-light, #1e3a5f); color: var(--blue-muted, #93c5fd); }\n.badge-admin { background: var(--info-light, #1a2e4a); color: var(--info-text, #7dd3fc); }\n.badge-member { background: var(--green-light, #14352a); color: var(--green-text, #4ade80); }\n.badge-guest { background: var(--yellow-light, #3b2e1a); color: var(--yellow-text, #fbbf24); }\n.dropdown {\n position: absolute; top: calc(100% + 8px); right: 0;\n background: var(--dropdown-bg); border: 1px solid var(--dropdown-border);\n border-radius: 12px; width: 240px; max-width: calc(100vw - 32px);\n box-shadow: var(--dropdown-shadow); display: none; z-index: 200;\n overflow: hidden;\n}\n.dropdown.open { display: block; }\n.dropdown-user {\n padding: 12px 16px; border-bottom: 1px solid var(--dropdown-divider);\n display: flex; align-items: center; gap: 8px;\n}\n.dropdown-user-name { font-weight: 600; font-size: 15px; color: var(--header-text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.dropdown-item {\n display: flex; align-items: center; gap: 12px; padding: 10px 16px;\n cursor: pointer; color: var(--header-text); font-size: 14px; transition: background .15s;\n text-decoration: none;\n}\n.dropdown-item:hover { background: var(--header-hover); }\n.dropdown-item svg { width: 18px; height: 18px; flex-shrink: 0; color: var(--header-text-secondary); }\n.dropdown-item.danger { color: var(--red-danger); }\n.dropdown-item.danger:hover { background: var(--red-danger-bg); }\n.dropdown-item.danger svg { color: var(--red-danger); }\n.dropdown-divider { height: 1px; background: var(--dropdown-divider); margin: 4px 0; }\n.login-btn {\n display: inline-flex; align-items: center; justify-content: center;\n height: 32px; padding: 0 16px; border-radius: 6px;\n border: none; background: var(--header-primary); color: #fff;\n font: inherit; font-weight: 500; cursor: pointer; font-size: 13px;\n transition: opacity .15s;\n}\n.login-btn:hover { opacity: .9; }\n.hidden { display: none !important; }\n.nav { display: flex; align-items: center; gap: 2px; margin-left: 8px; }\n.nav-item {\n padding: 6px 12px; border-radius: 6px; font-size: 13px; font-weight: 500;\n color: var(--header-text-secondary); text-decoration: none; white-space: nowrap;\n transition: color .15s, background .15s; cursor: pointer; border: none; background: none;\n font: inherit;\n}\n.nav-item:hover { color: var(--header-text); background: var(--header-hover); }\n.nav-item.active { color: var(--header-primary); background: transparent; font-weight: 600; }\n`,n=class extends f{static observedAttributes=[\"theme\",\"app-name\",\"app-logo\",\"login-url\",\"user-url\",\"team-url\",\"billing-url\",\"nav-items\",\"locale\"];shadow;_user=null;_dropdownOpen=!1;_boundClose=null;constructor(){super(),this.shadow=this.attachShadow({mode:\"open\"})}connectedCallback(){this.render(),this.fetchSession(),this._boundClose=e=>{this.shadow.contains(e.target)||this.closeDropdown()},document.addEventListener(\"click\",this._boundClose)}disconnectedCallback(){this._boundClose&&document.removeEventListener(\"click\",this._boundClose)}attributeChangedCallback(){this.render()}get appName(){return this.getAttribute(\"app-name\")||\"\"}get appLogo(){return this.getAttribute(\"app-logo\")||\"/.well-known/service/blocklet/logo\"}get loginUrl(){return this.getAttribute(\"login-url\")||\"/.well-known/service/login\"}get userUrl(){return this.getAttribute(\"user-url\")||\"/.well-known/service/user\"}get teamUrl(){return this.getAttribute(\"team-url\")||\"/.well-known/service/admin\"}get billingUrl(){return this.getAttribute(\"billing-url\")||\"\"}get locale(){return this.getAttribute(\"locale\")===\"zh\"?\"zh\":\"en\"}t(e){return v[this.locale][e]??v.en[e]??e}get navItems(){let e=this.getAttribute(\"nav-items\");if(!e)return[];try{return JSON.parse(e)}catch{return[]}}async fetchSession(){try{let e=await fetch(\"/.well-known/service/api/did/session\",{credentials:\"include\"});if(!e.ok)return;let t=await e.json();t.authenticated&&t.user&&(this._user=t.user,this.render())}catch{}}closeDropdown(){this._dropdownOpen=!1;let e=this.shadow.querySelector(\".dropdown\");e&&e.classList.remove(\"open\")}toggleDropdown(e){e.stopPropagation(),this._dropdownOpen=!this._dropdownOpen;let t=this.shadow.querySelector(\".dropdown\");t&&t.classList.toggle(\"open\",this._dropdownOpen)}abbreviateDid(e){return e.length<=20?e:`${e.slice(0,10)}...${e.slice(-6)}`}render(){let e=this._user,t=this.appLogo,u=this.appName,i=this.navItems,a=window.location.pathname,w=e?.role===\"admin\"||e?.role===\"owner\",b=i.length>0?`\n <nav class=\"nav\">\n ${i.map(r=>`<a class=\"nav-item${(r.link===\"/\"?a===\"/\":a.startsWith(r.link))?\" active\":\"\"}\" href=\"${r.link}\">${r.label}</a>`).join(\"\")}\n </nav>\n `:\"\";this.shadow.innerHTML=`\n <style>${C}</style>\n <div class=\"header\">\n <div class=\"header-left\">\n ${t?`<img class=\"logo\" src=\"${t}\" alt=\"\" />`:\"\"}\n <a href=\"/\">${u||this.t(\"home\")}</a>\n ${b}\n </div>\n <div class=\"header-right\">\n ${e?`\n <button class=\"user-trigger\" id=\"trigger\">\n <div class=\"avatar\">\n ${e.avatar?`<img src=\"${e.avatar}\" alt=\"\" />`:m}\n </div>\n <span class=\"user-arrow\">▾</span>\n </button>\n <div class=\"dropdown\" id=\"dropdown\">\n <div class=\"dropdown-user\">\n <div class=\"dropdown-user-name\">${e.fullName||e.displayName||this.abbreviateDid(e.did)}</div>\n <span class=\"badge badge-${e.role||\"member\"}\">${e.role||\"member\"}</span>\n </div>\n <div class=\"dropdown-item\" id=\"user-center-link\">${k} ${this.t(\"userCenter\")}</div>\n ${w?`<div class=\"dropdown-item\" id=\"team-link\">${y} ${this.t(\"adminConsole\")}</div>`:\"\"}\n ${this.billingUrl?`<div class=\"dropdown-item\" id=\"billing-link\"><svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M20 4H4c-1.11 0-1.99.89-1.99 2L2 18c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V6c0-1.11-.89-2-2-2m0 14H4v-6h16zm0-10H4V6h16z\"/></svg> ${this.t(\"billing\")}</div>`:\"\"}\n <div class=\"dropdown-divider\"></div>\n <div class=\"dropdown-item danger\" id=\"logout-btn\">${x} ${this.t(\"signOut\")}</div>\n </div>\n `:`\n <button class=\"login-btn\" id=\"login-btn\">${this.t(\"signIn\")}</button>\n `}\n </div>\n </div>\n `;let d=this.shadow.getElementById(\"trigger\");d&&d.addEventListener(\"click\",r=>this.toggleDropdown(r));let s=this.shadow.getElementById(\"login-btn\");s&&s.addEventListener(\"click\",()=>{window.location.href=this.loginUrl});let l=this.shadow.getElementById(\"logout-btn\");l&&l.addEventListener(\"click\",()=>{window.location.href=\"/.well-known/service/api/did/logout\"});let h=this.shadow.getElementById(\"user-center-link\");h&&h.addEventListener(\"click\",()=>{window.location.href=this.userUrl});let c=this.shadow.getElementById(\"team-link\");c&&c.addEventListener(\"click\",()=>{window.location.href=this.teamUrl});let g=this.shadow.getElementById(\"billing-link\");g&&g.addEventListener(\"click\",()=>{window.location.href=this.billingUrl}),this.shadow.querySelectorAll(\".nav-item\").forEach(r=>{r.addEventListener(\"click\",p=>{p.preventDefault();let o=r.getAttribute(\"href\");o&&o!==window.location.pathname&&(window.history.pushState({},\"\",o),window.dispatchEvent(new PopStateEvent(\"popstate\")),this.render())})})}};typeof customElements<\"u\"&&!customElements.get(\"blocklet-header\")&&customElements.define(\"blocklet-header\",n);})();\n","login.7b12c6dc.css":"\n\n :root {\n /* Backgrounds — layered depth */\n --bg-root: #0a0a0b;\n --bg-surface: #141416;\n --bg-card: #1c1c20;\n --bg-elevated: #232328;\n --bg-hover: #2b2b34;\n --bg-active: #33333e;\n --bg-input: rgba(255,255,255,0.06);\n\n /* Borders — semi-transparent for adaptability */\n --border: rgba(255,255,255,0.10);\n --border-subtle:rgba(255,255,255,0.06);\n --border-strong:rgba(255,255,255,0.15);\n --border-focus: rgba(255,255,255,0.25);\n\n /* Text hierarchy */\n --text: #f5f5f7;\n --text-secondary:#9394a1;\n --text-tertiary:#767684;\n --text-placeholder:#5e5f6e;\n --text-white: #ffffff;\n\n /* Accents */\n --blue: #6c47ff;\n --blue-hover: #5f15fe;\n --blue-light: rgba(108,71,255,0.15);\n --blue-muted: #9280ff;\n --red: #e02e2e;\n --red-light: rgba(224,46,46,0.15);\n --red-text: #f98a8a;\n --green: #15892b;\n --green-light: rgba(21,137,43,0.15);\n --green-text: #49dc6e;\n --yellow: #fd7224;\n --yellow-light: rgba(253,114,36,0.15);\n --yellow-text: #fd9357;\n --info: #236dd7;\n --info-light: rgba(35,109,215,0.15);\n --info-text: #73acfa;\n\n /* Radii */\n --radius-xs: 4px;\n --radius-sm: 6px;\n --radius: 8px;\n --radius-lg: 12px;\n --radius-full: 9999px;\n\n /* Shadows */\n --shadow-sm: 0 1px 3px rgba(0,0,0,0.3), 0 1px 2px -1px rgba(0,0,0,0.2);\n --shadow-md: 0 4px 6px -1px rgba(0,0,0,0.4), 0 2px 4px -2px rgba(0,0,0,0.3);\n --shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.5), 0 4px 6px -4px rgba(0,0,0,0.4);\n --shadow-focus: 0 0 0 1px rgba(255,255,255,0.12), 0 0 0 3px rgba(108,71,255,0.3);\n }\n\n\n [data-theme=\"light\"] {\n --bg-root: #f8f9fa;\n --bg-surface: #ffffff;\n --bg-card: #ffffff;\n --bg-elevated: #f0f1f3;\n --bg-hover: #e9eaec;\n --bg-active: #dddee1;\n --bg-input: rgba(0,0,0,0.04);\n\n --border: rgba(0,0,0,0.10);\n --border-subtle:rgba(0,0,0,0.06);\n --border-strong:rgba(0,0,0,0.15);\n --border-focus: rgba(0,0,0,0.25);\n\n --text: #1a1a1a;\n --text-secondary:#6b7280;\n --text-tertiary:#9ca3af;\n --text-placeholder:#c0c5ce;\n --text-white: #1a1a1a;\n\n --shadow-sm: 0 1px 3px rgba(0,0,0,0.08), 0 1px 2px -1px rgba(0,0,0,0.06);\n --shadow-md: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.06);\n --shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.12), 0 4px 6px -4px rgba(0,0,0,0.08);\n --shadow-focus: 0 0 0 1px rgba(0,0,0,0.08), 0 0 0 3px rgba(108,71,255,0.2);\n }\n\n\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n html { color-scheme: dark; }\n html[data-theme=\"light\"] { color-scheme: light; }\n body {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n background: var(--bg-root);\n color: var(--text);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n font-size: 14px;\n line-height: 1.43;\n }\n a { color: var(--blue-muted); text-decoration: none; }\n a:hover { text-decoration: underline; }\n .hidden { display: none; }\n\n\n/* Layout */\nbody {\n min-height: 100vh;\n display: flex;\n align-items: center;\n justify-content: center;\n background-image: radial-gradient(ellipse at 50% 0%, rgba(108,71,255,0.08) 0%, transparent 60%);\n}\n\n/* Card — fixed width prevents shrinking when views switch */\n.card {\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: var(--radius-lg);\n padding: 48px 40px;\n width: min(400px, calc(100vw - 32px));\n text-align: center;\n box-shadow: var(--shadow-lg), 0 0 80px -20px rgba(108,71,255,0.15);\n}\n\n/* Brand header */\n.brand-header { margin-bottom: 24px; }\n.app-logo {\n width: 48px;\n height: 48px;\n border-radius: var(--radius-sm);\n object-fit: contain;\n margin-bottom: 16px;\n}\nh1 {\n font-size: 24px;\n font-weight: 600;\n margin-bottom: 8px;\n color: var(--text-white);\n letter-spacing: -0.01em;\n line-height: 1.25;\n /* Never let a long \"Sign in to {appName}\" blow past two lines — clamp with\n an ellipsis so the card height stays stable. */\n display: -webkit-box;\n -webkit-line-clamp: 2;\n line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n.subtitle {\n font-size: 14px;\n color: var(--text-secondary);\n margin-bottom: 0;\n line-height: 1.5;\n}\n.federated-hint {\n font-size: 12px;\n color: var(--text-secondary);\n margin-top: 6px;\n margin-bottom: 0;\n opacity: 0.7;\n}\n\n/* Input */\n.input {\n width: 100%;\n height: 36px;\n padding: 0 12px;\n font-size: 14px;\n color: var(--text);\n background: var(--bg-input);\n border: 1px solid var(--border-strong);\n border-radius: var(--radius-sm);\n outline: none;\n margin-bottom: 16px;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n}\n.input:focus { border-color: var(--blue); box-shadow: var(--shadow-focus); }\n.input::placeholder { color: var(--text-placeholder); }\n\n/* Button */\n.btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n width: 100%;\n height: 36px;\n padding: 0 16px;\n font-size: 14px;\n font-weight: 500;\n color: #fff;\n background: var(--blue);\n border: none;\n border-radius: var(--radius-sm);\n cursor: pointer;\n transition: background-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;\n line-height: 1;\n}\n.btn:hover { background: var(--blue-hover); transform: translateY(-1px); box-shadow: 0 2px 8px rgba(108,71,255,0.3); }\n.btn:disabled { opacity: 0.5; cursor: not-allowed; pointer-events: none; transform: none; box-shadow: none; }\n.btn:focus-visible { box-shadow: var(--shadow-focus); }\n.btn svg { width: 18px; height: 18px; flex-shrink: 0; }\n.btn-secondary {\n background: transparent;\n border: 1px solid var(--border-strong);\n color: var(--text);\n}\n.btn-secondary:hover { background: var(--bg-hover); border-color: rgba(255,255,255,0.20); }\n\n/* Status */\n.status {\n margin-top: 16px;\n font-size: 13px;\n min-height: 20px;\n color: var(--text-secondary);\n}\n.status.error { color: var(--red-text); }\n\n/* Name row (passkey registration) */\n.name-row { display: none; }\n.name-row.visible { display: block; }\n\n/* Passkey section + secondary text action (\"First time? Create a passkey\") */\n.passkey-section > .btn + .passkey-alt,\n.passkey-section > .name-row + .btn { margin-top: 10px; }\n.passkey-alt {\n display: block;\n width: 100%;\n margin-top: 10px;\n padding: 4px;\n background: none;\n border: none;\n font-size: 13px;\n color: var(--text-secondary);\n cursor: pointer;\n text-align: center;\n transition: color 0.15s ease;\n}\n.passkey-alt:hover { color: var(--text); text-decoration: underline; }\n.passkey-alt:focus-visible { box-shadow: var(--shadow-focus); border-radius: var(--radius-sm); outline: none; }\n\n/* Divider */\n.divider {\n display: flex;\n align-items: center;\n margin: 20px 0;\n color: var(--text-secondary);\n font-size: 13px;\n}\n.divider::before, .divider::after {\n content: '';\n flex: 1;\n border-bottom: 1px solid var(--border);\n}\n.divider::before { margin-right: 12px; }\n.divider::after { margin-left: 12px; }\n\n/* DID Wallet section (methods view) */\n.did-wallet-section { margin-top: 8px; }\n\n/* QR view */\n.qr-view {\n position: relative;\n margin: 20px auto;\n display: flex;\n justify-content: center;\n min-height: 220px;\n align-items: center;\n}\n.qr-placeholder {\n width: 220px;\n height: 220px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius);\n background: var(--bg-elevated);\n}\n.qr-spinner {\n display: inline-block;\n width: 32px; height: 32px;\n border: 3px solid var(--border-strong);\n border-top-color: var(--blue-muted);\n border-radius: 50%;\n animation: spin 0.6s linear infinite;\n}\n#did-connect-status {\n font-size: 13px;\n color: var(--text-secondary);\n}\n\n/* Connect result state */\n.connect-result {\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 120px;\n margin: 32px 0;\n animation: fadeIn 200ms ease-out;\n}\n.result-icon {\n width: 64px;\n height: 64px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 28px;\n font-weight: bold;\n}\n.result-error {\n background: rgba(239, 68, 68, 0.15);\n color: var(--red-text);\n border: 2px solid rgba(239, 68, 68, 0.3);\n}\n.result-loading {\n background: transparent;\n}\n.result-loading .qr-spinner {\n width: 36px;\n height: 36px;\n}\n.result-back-btn {\n margin-top: 16px;\n}\n@keyframes fadeIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }\n\n/* QR timeout overlay */\n.qr-timeout-overlay {\n position: absolute;\n inset: 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n background: rgba(0, 0, 0, 0.65);\n border-radius: var(--radius);\n backdrop-filter: blur(2px);\n}\n.qr-timeout-overlay .timeout-text {\n font-size: 13px;\n color: #fff;\n}\n.qr-timeout-overlay .refresh-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 6px 20px;\n font-size: 14px;\n font-weight: 500;\n color: #fff;\n background: var(--blue);\n border: none;\n border-radius: var(--radius-sm);\n cursor: pointer;\n transition: background-color 0.15s ease;\n}\n.qr-timeout-overlay .refresh-btn:hover {\n background: var(--blue-hover);\n}\n/* Connect status views (scanned / success) */\n.connect-status-icon {\n position: relative;\n width: 72px;\n height: 72px;\n margin: 16px auto;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n@keyframes connectSpin { to { transform: rotate(360deg); } }\n.connect-spinner-ring {\n position: absolute;\n inset: 0;\n width: 72px;\n height: 72px;\n border-radius: 50%;\n border: 3px solid rgba(255,255,255,0.1);\n border-top-color: var(--blue, #6c47ff);\n animation: connectSpin 0.8s linear infinite;\n}\n.connect-status-badge {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 44px;\n height: 44px;\n}\n.connect-status-badge svg { width: 36px; height: 36px; }\n.connect-status-icon.connect-success svg { animation: fadeIn 0.3s ease; }\n.connect-subtitle {\n font-size: 13px;\n color: var(--text-secondary, #9394a1);\n text-align: center;\n margin: 4px 0 0;\n}\n.status.success { color: #49dc6e; font-weight: 500; }\n.deep-link-btn {\n margin-top: 12px;\n text-decoration: none;\n}\n\n/* Email section (methods view) */\n.email-section { margin-top: 8px; }\n\n/* Back button */\n.back-btn {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n background: none;\n border: none;\n color: var(--text-secondary);\n font-size: 14px;\n cursor: pointer;\n padding: 0;\n margin-bottom: 16px;\n float: left;\n}\n.back-btn:hover { color: var(--text); }\n.back-btn svg { width: 18px; height: 18px; }\n/* Escape hatch — a quiet \"Cancel\" link, top-left of the card, mirroring the\n locale switcher (top-right). Conventional spot for a standalone auth card.\n Low in the hierarchy (tertiary color, small) so it never competes with the\n auth methods; it's an exit, not an action. */\n.home-link {\n position: absolute;\n top: 16px;\n /* Align the arrow's left edge to the content column (card's 40px padding), so\n Cancel sits on the same left line as the title and buttons — no left padding\n of its own, which would push it off that line. */\n left: 40px;\n display: inline-flex;\n align-items: center;\n gap: 3px;\n background: none;\n border: none;\n color: var(--text-tertiary);\n font-size: 13px;\n line-height: 1;\n text-decoration: none;\n cursor: pointer;\n padding: 2px 4px 2px 0;\n transition: color 0.15s ease;\n}\n.home-link:hover { color: var(--text-secondary); }\n.home-link svg { width: 16px; height: 16px; }\n\n/* View title */\n.view-title {\n font-size: 20px;\n font-weight: 600;\n color: var(--text-white);\n margin-bottom: 8px;\n clear: both;\n padding-top: 8px;\n}\n\n/* OAuth section — single provider (legacy) */\n.oauth-section { margin-top: 0; }\n\n/* OAuth grid — 2+ providers in 2-column layout (legacy) */\n.oauth-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 8px;\n margin-bottom: 8px;\n}\n.oauth-grid .oauth-btn:last-child:nth-child(odd) {\n grid-column: 1 / -1;\n}\n\n/* Unified secondary methods grid — 2-column layout */\n.methods-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 8px;\n}\n/* Odd last child (e.g. DID Wallet when total is odd) spans full width */\n.methods-grid .method-btn:last-child:nth-child(odd) {\n grid-column: 1 / -1;\n}\n\n.method-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n width: 100%;\n min-height: 36px;\n padding: 6px 16px;\n font-size: 14px;\n font-weight: 500;\n color: var(--text);\n background: var(--bg-elevated);\n border: 1px solid var(--border-strong);\n border-radius: var(--radius-sm);\n cursor: pointer;\n transition: background-color 0.15s ease, border-color 0.15s ease, opacity 0.2s ease;\n white-space: normal;\n}\n.method-btn:hover { background: var(--bg-hover); border-color: var(--blue-muted); }\n.method-btn:disabled { cursor: default; pointer-events: none; }\n.method-btn svg { width: 18px; height: 18px; flex-shrink: 0; }\n\n.oauth-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n width: 100%;\n height: 36px;\n padding: 0 16px;\n font-size: 14px;\n font-weight: 500;\n color: var(--text);\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: var(--radius-sm);\n cursor: pointer;\n transition: background-color 0.15s ease, opacity 0.2s ease, border-color 0.2s ease;\n margin-bottom: 0;\n}\n.oauth-section .oauth-btn { margin-bottom: 8px; }\n.oauth-btn:hover { background: var(--bg-hover); }\n.oauth-btn:disabled { cursor: default; pointer-events: none; }\n.oauth-btn svg { width: 18px; height: 18px; flex-shrink: 0; }\n.oauth-btn-loading {\n opacity: 0.8;\n border-color: var(--blue);\n color: var(--text-secondary);\n}\n@keyframes oauth-spin { to { transform: rotate(360deg); } }\n.oauth-spinner {\n display: inline-block;\n width: 14px; height: 14px;\n border: 2px solid var(--border-strong);\n border-top-color: var(--blue-muted);\n border-radius: 50%;\n animation: oauth-spin 0.6s linear infinite;\n}\n.oauth-check { color: var(--green-text); font-size: 16px; }\n\n/* Browser compatibility warning */\n.compat-warning {\n font-size: 13px;\n color: var(--text-tertiary);\n padding: 12px;\n background: var(--bg-elevated);\n border-radius: var(--radius-sm);\n margin-bottom: 16px;\n}\n\n/* Trust footer */\n.login-footer {\n margin-top: 24px;\n padding-top: 20px;\n border-top: 1px solid var(--border);\n}\n.secured-badge {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n font-size: 12px;\n color: var(--text-tertiary);\n}\n.secured-badge svg { width: 14px; height: 14px; flex-shrink: 0; }\n.footer-links {\n margin-top: 8px;\n font-size: 12px;\n color: var(--text-tertiary);\n}\n.footer-links a {\n color: var(--text-secondary);\n text-decoration: none;\n}\n.footer-links a:hover { text-decoration: underline; }\n\n/* ─── Animations ─────────────────────────────────────────────── */\n@keyframes fadeIn {\n from { opacity: 0; transform: translateY(4px); }\n to { opacity: 1; transform: translateY(0); }\n}\n@keyframes fadeOut {\n from { opacity: 1; }\n to { opacity: 0; }\n}\n.card[data-view] { animation: fadeIn 200ms ease-out; }\n\n/* QR code scale-in */\n.qr-view { animation: qrScaleIn 300ms ease-out; }\n@keyframes qrScaleIn {\n from { opacity: 0; transform: scale(0.95); }\n to { opacity: 1; transform: scale(1); }\n}\n\n/* Button loading spinner */\n@keyframes spin { to { transform: rotate(360deg); } }\n.btn-spinner {\n display: inline-block;\n width: 16px; height: 16px;\n border: 2px solid rgba(255,255,255,0.3);\n border-top-color: #fff;\n border-radius: 50%;\n animation: spin 0.6s linear infinite;\n}\n\n/* Success checkmark */\n@keyframes checkDraw {\n from { stroke-dashoffset: 24; }\n to { stroke-dashoffset: 0; }\n}\n.success-check {\n display: inline-block;\n width: 20px; height: 20px;\n vertical-align: middle;\n}\n.success-check path {\n stroke-dasharray: 24;\n stroke-dashoffset: 24;\n animation: checkDraw 400ms ease-out forwards;\n}\n\n/* Light theme background gradient adjustment */\n[data-theme=\"light\"] body {\n background-image: radial-gradient(ellipse at 50% 0%, rgba(108,71,255,0.05) 0%, transparent 60%);\n}\n[data-theme=\"light\"] .card {\n box-shadow: var(--shadow-lg), 0 0 80px -20px rgba(108,71,255,0.08);\n}\n[data-theme=\"light\"] .btn:hover {\n box-shadow: 0 2px 8px rgba(108,71,255,0.15);\n}\n\n/* Language switcher */\n.locale-switcher {\n position: absolute;\n top: 16px;\n right: 16px;\n}\n.locale-btn {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 4px 10px;\n font-size: 12px;\n color: var(--text-secondary);\n background: var(--bg-elevated);\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius-full);\n cursor: pointer;\n transition: color 0.15s, border-color 0.15s;\n}\n.locale-btn:hover {\n color: var(--text);\n border-color: var(--border-strong);\n}\n.locale-btn svg { width: 14px; height: 14px; flex-shrink: 0; }\n.card { position: relative; }\n","design.99dc4ddc.css":"\n :root {\n /* Backgrounds — layered depth */\n --bg-root: #0a0a0b;\n --bg-surface: #141416;\n --bg-card: #1c1c20;\n --bg-elevated: #232328;\n --bg-hover: #2b2b34;\n --bg-active: #33333e;\n --bg-input: rgba(255,255,255,0.06);\n\n /* Borders — semi-transparent for adaptability */\n --border: rgba(255,255,255,0.10);\n --border-subtle:rgba(255,255,255,0.06);\n --border-strong:rgba(255,255,255,0.15);\n --border-focus: rgba(255,255,255,0.25);\n\n /* Text hierarchy */\n --text: #f5f5f7;\n --text-secondary:#9394a1;\n --text-tertiary:#767684;\n --text-placeholder:#5e5f6e;\n --text-white: #ffffff;\n\n /* Accents */\n --blue: #6c47ff;\n --blue-hover: #5f15fe;\n --blue-light: rgba(108,71,255,0.15);\n --blue-muted: #9280ff;\n --red: #e02e2e;\n --red-light: rgba(224,46,46,0.15);\n --red-text: #f98a8a;\n --green: #15892b;\n --green-light: rgba(21,137,43,0.15);\n --green-text: #49dc6e;\n --yellow: #fd7224;\n --yellow-light: rgba(253,114,36,0.15);\n --yellow-text: #fd9357;\n --info: #236dd7;\n --info-light: rgba(35,109,215,0.15);\n --info-text: #73acfa;\n\n /* Radii */\n --radius-xs: 4px;\n --radius-sm: 6px;\n --radius: 8px;\n --radius-lg: 12px;\n --radius-full: 9999px;\n\n /* Shadows */\n --shadow-sm: 0 1px 3px rgba(0,0,0,0.3), 0 1px 2px -1px rgba(0,0,0,0.2);\n --shadow-md: 0 4px 6px -1px rgba(0,0,0,0.4), 0 2px 4px -2px rgba(0,0,0,0.3);\n --shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.5), 0 4px 6px -4px rgba(0,0,0,0.4);\n --shadow-focus: 0 0 0 1px rgba(255,255,255,0.12), 0 0 0 3px rgba(108,71,255,0.3);\n }\n\n [data-theme=\"light\"] {\n --bg-root: #f8f9fa;\n --bg-surface: #ffffff;\n --bg-card: #ffffff;\n --bg-elevated: #f0f1f3;\n --bg-hover: #e9eaec;\n --bg-active: #dddee1;\n --bg-input: rgba(0,0,0,0.04);\n\n --border: rgba(0,0,0,0.10);\n --border-subtle:rgba(0,0,0,0.06);\n --border-strong:rgba(0,0,0,0.15);\n --border-focus: rgba(0,0,0,0.25);\n\n --text: #1a1a1a;\n --text-secondary:#6b7280;\n --text-tertiary:#9ca3af;\n --text-placeholder:#c0c5ce;\n --text-white: #1a1a1a;\n\n --shadow-sm: 0 1px 3px rgba(0,0,0,0.08), 0 1px 2px -1px rgba(0,0,0,0.06);\n --shadow-md: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.06);\n --shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.12), 0 4px 6px -4px rgba(0,0,0,0.08);\n --shadow-focus: 0 0 0 1px rgba(0,0,0,0.08), 0 0 0 3px rgba(108,71,255,0.2);\n }\n\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n html { color-scheme: dark; }\n html[data-theme=\"light\"] { color-scheme: light; }\n body {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n background: var(--bg-root);\n color: var(--text);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n font-size: 14px;\n line-height: 1.43;\n }\n a { color: var(--blue-muted); text-decoration: none; }\n a:hover { text-decoration: underline; }\n .hidden { display: none; }\n","admin.c26bb17a.css":"\n .hidden { display: none !important; }\n\n /* ─── Shared component styles (extracted from shared-styles.ts) ───── */\n h1 {\n font-size: 24px;\n font-weight: 600;\n margin-bottom: 8px;\n color: var(--text-white);\n letter-spacing: -0.01em;\n line-height: 1.25;\n }\n .input {\n width: 100%;\n height: 40px;\n padding: 0 14px;\n font-size: 14px;\n color: var(--text);\n background: var(--bg-input);\n border: 1px solid var(--border-strong);\n border-radius: var(--radius-sm);\n outline: none;\n margin-bottom: 0;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n }\n textarea.input { height: auto; padding: 10px 14px; }\n .input:focus { border-color: var(--blue); box-shadow: var(--shadow-focus); }\n .input::placeholder { color: var(--text-placeholder); }\n .btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n width: 100%;\n height: 36px;\n padding: 0 16px;\n font-size: 14px;\n font-weight: 500;\n color: #fff;\n background: var(--blue);\n border: none;\n border-radius: var(--radius-sm);\n cursor: pointer;\n transition: background-color 0.15s ease;\n line-height: 1;\n }\n .btn:hover { background: var(--blue-hover); }\n .btn:disabled { opacity: 0.5; cursor: not-allowed; pointer-events: none; }\n .btn:focus-visible { box-shadow: var(--shadow-focus); }\n .btn svg { width: 18px; height: 18px; }\n .btn-secondary {\n background: transparent;\n border: 1px solid var(--border-strong);\n color: var(--text);\n }\n .btn-secondary:hover { background: var(--bg-hover); border-color: rgba(255,255,255,0.20); }\n .btn-danger { background: var(--red); }\n .btn-danger:hover { background: #c22a2a; }\n .badge {\n display: inline-flex;\n align-items: center;\n padding: 2px 8px;\n border-radius: var(--radius-full);\n font-size: 11px;\n font-weight: 500;\n line-height: 1.45;\n white-space: nowrap;\n }\n .badge-owner { background: var(--blue-light); color: var(--blue-muted); }\n .badge-admin { background: var(--info-light); color: var(--info-text); }\n .badge-member { background: var(--green-light); color: var(--green-text); }\n .badge-guest { background: var(--yellow-light); color: var(--yellow-text); }\n .badge-verified { background: var(--green-light); color: var(--green-text); }\n .badge-unverified { background: var(--yellow-light); color: var(--yellow-text); }\n\n /* ─── Admin layout ─────────────────────────────────────────────────── */\n body {\n display: block;\n min-height: 100vh;\n }\n\n /* ─── Header ─────────────────────────────────────────────────────── */\n .admin-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0 24px;\n height: 56px;\n border-bottom: 1px solid var(--border);\n background: var(--bg-surface);\n }\n .admin-header-left {\n display: flex;\n align-items: center;\n gap: 16px;\n }\n .admin-header-left a {\n font-size: 13px;\n transition: color 0.15s ease;\n }\n .admin-header-left .app-name-link {\n color: var(--text);\n }\n .admin-header-left .app-name-link:hover {\n color: var(--text);\n text-decoration: none;\n }\n .admin-header h1 {\n font-size: 15px;\n font-weight: 600;\n margin: 0;\n letter-spacing: -0.006em;\n }\n .admin-header-right {\n position: relative;\n display: flex;\n align-items: center;\n gap: 4px;\n }\n /* ─── Toolbar buttons (ghost style) ───────────────────────────── */\n .toolbar-wrapper {\n position: relative;\n }\n .toolbar-btn {\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: none;\n border-radius: var(--radius-sm);\n color: var(--text-tertiary);\n cursor: pointer;\n transition: background-color 0.15s ease, color 0.15s ease;\n -webkit-tap-highlight-color: transparent;\n }\n .toolbar-btn:hover { background: var(--bg-hover); color: var(--text); }\n .toolbar-btn:active { background: var(--bg-active); }\n .toolbar-btn svg { width: 16px; height: 16px; }\n\n /* ─── Popover (language picker) ──────────────────────────────── */\n .popover {\n position: absolute;\n right: 0;\n top: calc(100% + 6px);\n min-width: 160px;\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: var(--radius-lg);\n box-shadow: var(--shadow-lg);\n z-index: 1000;\n overflow: hidden;\n opacity: 0;\n transform: translateY(-4px) scale(0.96);\n pointer-events: none;\n transform-origin: top right;\n transition: opacity 0.15s ease, transform 0.15s ease;\n }\n .popover.open {\n opacity: 1;\n transform: translateY(0) scale(1);\n pointer-events: auto;\n }\n .popover-header {\n padding: 8px 14px;\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--text-tertiary);\n border-bottom: 1px solid var(--border-subtle);\n }\n .popover-item {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n font-size: 13px;\n color: var(--text);\n cursor: pointer;\n transition: background 0.12s;\n -webkit-tap-highlight-color: transparent;\n }\n .popover-item:hover { background: var(--bg-hover); }\n .popover-item:active { background: var(--bg-active); }\n .popover-item.selected { color: var(--blue-muted); font-weight: 500; }\n .popover-item .check-icon { width: 14px; display: flex; align-items: center; flex-shrink: 0; }\n .popover-item .check-icon svg { width: 14px; height: 14px; }\n\n /* ─── Mobile: larger touch targets, full-width popover ───────── */\n @media (max-width: 640px) {\n .admin-header { padding: 0 12px; }\n .admin-header-right { gap: 4px; }\n .toolbar-btn { width: 40px; height: 40px; }\n .popover { right: -8px; min-width: 150px; }\n .popover-item { padding: 12px 14px; font-size: 14px; }\n .user-menu-arrow { display: none; }\n .user-dropdown { right: -12px; width: 220px; }\n .dropdown-item { padding: 12px 16px; }\n }\n\n /* ─── User menu trigger ────────────────────────────────────────── */\n .user-menu-trigger {\n position: relative;\n display: flex;\n align-items: center;\n gap: 8px;\n cursor: pointer;\n }\n .user-menu-arrow {\n font-size: 12px;\n color: var(--text-tertiary);\n line-height: 1;\n }\n\n /* ─── User dropdown ────────────────────────────────────────────── */\n .user-dropdown {\n position: absolute;\n right: 0;\n top: calc(100% + 8px);\n width: 240px;\n max-width: calc(100vw - 32px);\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: var(--radius-lg);\n box-shadow: var(--shadow-lg);\n z-index: 1000;\n overflow: hidden;\n }\n .dropdown-user {\n padding: 12px 16px;\n display: flex;\n align-items: center;\n gap: 8px;\n border-bottom: 1px solid var(--border-subtle);\n }\n .dropdown-user-name {\n font-weight: 600;\n font-size: 15px;\n color: var(--text-white);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .dropdown-item {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 10px 16px;\n color: var(--text);\n font-size: 14px;\n cursor: pointer;\n transition: background 0.15s;\n text-decoration: none;\n }\n .dropdown-item:hover { background: var(--bg-hover); }\n .dropdown-item.danger { color: var(--red-text); }\n .dropdown-item.danger:hover { background: var(--red-light); }\n .dropdown-divider {\n height: 1px;\n background: var(--border-subtle);\n margin: 4px 0;\n }\n .dropdown-item svg { width: 18px; height: 18px; flex-shrink: 0; color: var(--text-secondary); }\n .dropdown-item.danger svg { color: var(--red-text); }\n\n /* ─── Tabs ───────────────────────────────────────────────────────── */\n .tabs {\n display: flex;\n gap: 0;\n border-bottom: 1px solid var(--border);\n padding: 0 24px;\n background: var(--bg-surface);\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n scrollbar-width: none;\n }\n .tabs::-webkit-scrollbar { display: none; }\n [data-tab] {\n position: relative;\n padding: 10px 16px;\n font-size: 13px;\n font-weight: 500;\n color: var(--text-tertiary);\n cursor: pointer;\n border: none;\n background: none;\n border-bottom: 2px solid transparent;\n transition: color 0.15s ease, border-color 0.15s ease;\n }\n [data-tab]:hover { color: var(--text-secondary); }\n .tab-active {\n color: var(--text-white) !important;\n border-bottom-color: var(--text-white) !important;\n }\n .tab-panel {\n padding: 32px 40px;\n max-width: 1120px;\n margin: 0 auto;\n }\n\n /* ─── Section titles ─────────────────────────────────────────────── */\n .section-title {\n font-size: 20px;\n font-weight: 600;\n color: var(--text-white);\n letter-spacing: -0.02em;\n line-height: 1.25;\n margin-bottom: 4px;\n }\n .section-desc {\n font-size: 14px;\n color: var(--text-tertiary);\n margin-bottom: 32px;\n padding-bottom: 24px;\n border-bottom: 1px solid var(--border-subtle);\n line-height: 1.5;\n }\n\n /* ─── Profile ────────────────────────────────────────────────────── */\n .profile-header {\n padding: 16px 20px;\n display: flex;\n align-items: center;\n gap: 14px;\n }\n .avatar-lg {\n width: 48px;\n height: 48px;\n font-size: 18px;\n }\n .profile-header-body {\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 8px;\n }\n .profile-header-name {\n font-weight: 600;\n font-size: 15px;\n color: var(--text-white);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .profile-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n border-top: 1px solid var(--border);\n }\n .profile-cell {\n padding: 12px 20px;\n }\n .profile-cell-label {\n display: block;\n font-size: 11px;\n font-weight: 500;\n color: var(--text-tertiary);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-bottom: 4px;\n }\n .profile-cell-value {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n color: var(--text);\n min-height: 24px;\n }\n .profile-cell-editable {\n margin: -2px -8px;\n }\n @media (max-width: 560px) {\n .profile-grid { grid-template-columns: 1fr; }\n }\n .profile-editable {\n flex: 1;\n min-width: 0;\n height: 30px;\n padding: 0 8px;\n font-size: 13px;\n color: var(--text);\n background: transparent;\n border: 1px solid transparent;\n border-radius: var(--radius-sm);\n outline: none;\n transition: border-color 0.15s, background 0.15s;\n }\n .profile-editable:hover {\n border-color: var(--border-strong);\n background: var(--bg-input);\n }\n .profile-editable:focus {\n border-color: var(--blue);\n background: var(--bg-input);\n box-shadow: var(--shadow-focus);\n }\n .btn-text {\n background: none;\n border: none;\n color: var(--blue);\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n padding: 4px 8px;\n border-radius: var(--radius-sm);\n white-space: nowrap;\n flex-shrink: 0;\n }\n .btn-text:hover { background: var(--blue-light); }\n .btn-text:disabled { opacity: 0.5; cursor: default; }\n .avatar {\n width: 48px;\n height: 48px;\n border-radius: var(--radius-full);\n background: var(--blue-light);\n color: var(--blue-muted);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n font-weight: 600;\n flex-shrink: 0;\n overflow: hidden;\n position: relative;\n }\n .avatar img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n }\n .avatar-fallback {\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .avatar-sm {\n width: 32px;\n height: 32px;\n font-size: 12px;\n }\n .avatar-editable { cursor: pointer; }\n .avatar-overlay {\n position: absolute;\n inset: 0;\n background: rgba(0,0,0,0.5);\n display: none;\n align-items: center;\n justify-content: center;\n color: white;\n font-size: 12px;\n border-radius: inherit;\n }\n .avatar-editable:hover .avatar-overlay { display: flex; }\n /* <did-address> Web Component theme bridge — CSS custom properties penetrate Shadow DOM */\n did-address {\n --did-text-color: var(--text-secondary);\n --did-copy-success-color: var(--green);\n font-size: 11px;\n }\n did-address:hover {\n --did-text-color: var(--text-white);\n }\n .profile-meta {\n margin: 16px 0;\n font-size: 13px;\n }\n\n /* ─── Settings card ──────────────────────────────────────────────── */\n .settings-card {\n background: var(--bg-card);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n margin-bottom: 24px;\n }\n .settings-card-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n border-bottom: 1px solid var(--border-subtle);\n gap: 12px;\n }\n details.settings-card > summary.settings-card-header {\n list-style: none;\n }\n details.settings-card > summary.settings-card-header::before {\n content: \"\\25B6\";\n font-size: 10px;\n transition: transform 0.2s;\n margin-right: 4px;\n }\n details[open].settings-card > summary.settings-card-header::before {\n transform: rotate(90deg);\n }\n details.settings-card > summary.settings-card-header::-webkit-details-marker {\n display: none;\n }\n\n /* Icon action buttons for access/policy tables */\n .ap-actions {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 4px;\n white-space: nowrap;\n }\n .ap-icon-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n padding: 0;\n border: 1px solid var(--border);\n border-radius: var(--radius-sm);\n background: var(--bg-card);\n color: var(--text-secondary);\n cursor: pointer;\n transition: background 0.15s, color 0.15s, border-color 0.15s;\n flex-shrink: 0;\n }\n .ap-icon-btn:hover {\n background: var(--bg-hover);\n color: var(--text-white);\n border-color: var(--border-strong);\n }\n .ap-icon-btn-danger:hover {\n background: var(--red-light);\n color: var(--red-text);\n border-color: var(--red-text);\n }\n\n .ap-lock-name {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n color: var(--fg);\n }\n .ap-lock-name svg {\n color: var(--fg-muted);\n flex-shrink: 0;\n }\n\n tr.ap-separator td {\n padding: 0;\n border-bottom: 2px solid var(--border);\n }\n\n .ap-switch {\n position: relative;\n display: inline-block;\n width: 36px;\n height: 20px;\n cursor: pointer;\n }\n .ap-switch input {\n opacity: 0;\n width: 0;\n height: 0;\n }\n .ap-switch-slider {\n position: absolute;\n inset: 0;\n background: var(--bg-tertiary, #ccc);\n border-radius: 20px;\n transition: background 0.2s;\n }\n .ap-switch-slider::before {\n content: \"\";\n position: absolute;\n left: 2px;\n top: 2px;\n width: 16px;\n height: 16px;\n background: #fff;\n border-radius: 50%;\n transition: transform 0.2s;\n }\n .ap-switch input:checked + .ap-switch-slider {\n background: var(--color-primary, #2563eb);\n }\n .ap-switch input:checked + .ap-switch-slider::before {\n transform: translateX(16px);\n }\n\n .settings-card-header-desc {\n font-size: 12px;\n color: var(--text-tertiary);\n font-weight: 400;\n flex: 1;\n text-align: right;\n }\n .settings-card-desc {\n font-size: 12px;\n color: var(--text-tertiary);\n padding: 8px 20px 0;\n margin: 0;\n line-height: 1.5;\n }\n .settings-card-title {\n font-size: 14px;\n font-weight: 600;\n color: var(--text-white);\n }\n .settings-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 14px 20px;\n border-bottom: 1px solid var(--border-subtle);\n }\n .settings-row:last-child { border-bottom: none; }\n .settings-label {\n font-size: 13px;\n font-weight: 500;\n color: var(--text);\n }\n .settings-value {\n font-size: 13px;\n color: var(--text-secondary);\n }\n\n /* ─── Connected Accounts chips ──────────────────────────────────── */\n .ca-section {\n padding: 12px 20px;\n }\n .ca-section + .ca-section {\n padding-top: 0;\n }\n .ca-section-label {\n font-size: 11px;\n font-weight: 500;\n color: var(--text-tertiary);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-bottom: 8px;\n }\n .ca-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n }\n .ca-chip {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 6px 12px;\n border: 1px solid var(--border);\n border-radius: var(--radius-sm);\n background: var(--bg-elevated);\n font-size: 13px;\n color: var(--text);\n transition: border-color 0.15s, background 0.15s;\n position: relative;\n max-width: 260px;\n }\n .ca-chip-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n flex-shrink: 0;\n }\n .ca-chip-icon svg { width: 18px; height: 18px; }\n .ca-chip-body {\n display: flex;\n flex-direction: column;\n min-width: 0;\n }\n .ca-chip-name {\n font-weight: 500;\n font-size: 13px;\n line-height: 1.3;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .ca-chip-detail {\n font-size: 11px;\n color: var(--text-secondary);\n line-height: 1.3;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .ca-chip-badge {\n display: inline-flex;\n font-size: 10px;\n font-weight: 500;\n padding: 1px 6px;\n border-radius: var(--radius-full);\n background: var(--blue-light);\n color: var(--blue-muted);\n white-space: nowrap;\n vertical-align: middle;\n margin-left: 4px;\n }\n .ca-chip-remove {\n display: none;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n border: none;\n background: transparent;\n color: var(--text-tertiary);\n cursor: pointer;\n padding: 0;\n border-radius: var(--radius-full);\n flex-shrink: 0;\n transition: color 0.15s, background 0.15s;\n }\n .ca-chip-remove:hover { color: var(--red-text); background: var(--red-light); }\n .ca-chip:hover .ca-chip-remove { display: inline-flex; }\n\n /* ─── Bound account cards ─────────────────────────────────────── */\n .ca-bound-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));\n gap: 12px;\n }\n .ca-bound-card {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 16px;\n border: 1px solid var(--border);\n border-radius: var(--radius-sm);\n background: var(--bg-card);\n transition: border-color 0.15s;\n }\n .ca-bound-card:hover { border-color: var(--border-strong); }\n .ca-bound-top {\n display: flex;\n align-items: flex-start;\n gap: 12px;\n }\n .ca-bound-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n border-radius: var(--radius-sm);\n background: var(--bg-elevated);\n border: 1px solid var(--border);\n flex-shrink: 0;\n margin-top: 1px;\n }\n .ca-bound-icon svg { width: 22px; height: 22px; }\n .ca-bound-icon-lg {\n width: 48px;\n height: 48px;\n background: var(--blue-light);\n border-color: transparent;\n }\n .ca-bound-icon-lg svg { width: 28px; height: 28px; color: var(--blue-muted); }\n .ca-bound-top-body {\n flex: 1;\n min-width: 0;\n }\n .ca-bound-header {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n .ca-bound-name {\n font-weight: 600;\n font-size: 15px;\n color: var(--text-white);\n line-height: 1.3;\n }\n .ca-bound-detail {\n font-size: 13px;\n color: var(--text-secondary);\n line-height: 1.4;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .ca-bound-field {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n }\n .ca-bound-field-label {\n font-size: 11px;\n font-weight: 500;\n color: var(--text-tertiary);\n text-transform: uppercase;\n letter-spacing: 0.4px;\n flex-shrink: 0;\n width: 36px;\n }\n .ca-bound-field-value {\n flex: 1;\n min-width: 0;\n font-size: 12px;\n color: var(--text-secondary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n font-family: 'JetBrains Mono', 'SF Mono', monospace;\n }\n .ca-bound-copy {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n border: none;\n background: transparent;\n color: var(--text-tertiary);\n cursor: pointer;\n padding: 0;\n border-radius: var(--radius-sm);\n flex-shrink: 0;\n transition: color 0.15s, background 0.15s;\n }\n .ca-bound-copy:hover { color: var(--text); background: var(--bg-hover); }\n .ca-bound-copy svg { width: 14px; height: 14px; }\n .ca-bound-actions {\n display: flex;\n align-items: center;\n gap: 4px;\n flex-shrink: 0;\n }\n\n /* ─── Connected Accounts: mobile ───────────────────────────────── */\n @media (max-width: 640px) {\n .ca-bound-grid { grid-template-columns: 1fr; gap: 8px; }\n .ca-bound-card { padding: 12px; gap: 10px; }\n .ca-bound-top { gap: 10px; }\n .ca-bound-icon { width: 32px; height: 32px; }\n .ca-bound-icon svg { width: 18px; height: 18px; }\n .ca-bound-icon-lg { width: 40px; height: 40px; }\n .ca-bound-icon-lg svg { width: 22px; height: 22px; }\n .ca-bound-name { font-size: 14px; }\n .ca-bound-detail { font-size: 12px; }\n .ca-bound-field-label { display: none; }\n .ca-bound-field-value { font-size: 11px; }\n .ca-section { padding: 10px 16px; }\n .ca-chips { gap: 6px; }\n .ca-chip { padding: 5px 10px; font-size: 12px; }\n }\n .ca-chip-unbound {\n border-style: dashed;\n color: var(--text-tertiary);\n cursor: pointer;\n }\n .ca-chip-unbound:hover {\n border-color: var(--border-strong);\n background: var(--bg-hover);\n color: var(--text);\n }\n .ca-chip-add-icon {\n font-size: 14px;\n font-weight: 300;\n line-height: 1;\n color: var(--text-tertiary);\n }\n .ca-chip-unbound:hover .ca-chip-add-icon { color: var(--text-secondary); }\n .ca-passkey-add {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n border: 1px dashed var(--border);\n border-radius: var(--radius-sm);\n background: transparent;\n font-size: 13px;\n color: var(--text-tertiary);\n cursor: pointer;\n transition: border-color 0.15s, background 0.15s, color 0.15s;\n }\n .ca-passkey-add:hover {\n border-color: var(--border-strong);\n background: var(--bg-hover);\n color: var(--text);\n }\n\n /* ─── Forms ──────────────────────────────────────────────────────── */\n .form-group {\n margin-bottom: 16px;\n }\n .form-label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n font-weight: 600;\n color: var(--text-secondary);\n margin-bottom: 4px;\n }\n .form-actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n margin-top: 24px;\n }\n .form-actions .btn { width: auto; }\n .text-muted { color: var(--text-secondary); }\n\n /* ─── Tables ─────────────────────────────────────────────────────── */\n .table {\n width: 100%;\n border-collapse: collapse;\n }\n .table th, .table td {\n padding: 12px 16px;\n text-align: left;\n border-bottom: 1px solid var(--border-subtle);\n font-size: 14px;\n vertical-align: middle;\n }\n .table th {\n color: var(--text-tertiary);\n font-weight: 500;\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n background: transparent;\n border-bottom-color: var(--border);\n }\n .table th.sortable {\n cursor: pointer;\n user-select: none;\n transition: color 0.1s ease;\n }\n .table th.sortable:hover { color: var(--text-secondary); }\n .table th.sort-active { color: var(--text-white); }\n .table th .sort-arrow {\n display: inline-block;\n margin-left: 4px;\n font-size: 10px;\n opacity: 0.25;\n transition: opacity 0.1s ease;\n }\n .table th.sortable:hover .sort-arrow { opacity: 0.6; }\n .table th .sort-arrow-active { opacity: 1; }\n .table tbody tr {\n transition: background-color 0.1s ease;\n }\n .table tbody tr:hover { background: var(--bg-hover); }\n\n /* Action cell — hosts ⋯ trigger + dropdown menu */\n .action-cell {\n position: relative;\n width: 40px;\n text-align: center;\n }\n\n /* Member identity cell */\n .member-identity {\n display: flex;\n align-items: center;\n gap: 12px;\n }\n .member-name {\n font-size: 14px;\n font-weight: 500;\n color: var(--text);\n line-height: 1.3;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .member-email {\n font-size: 12px;\n color: var(--text-secondary);\n line-height: 1.3;\n margin-top: 1px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .cell-date {\n font-size: 13px;\n color: var(--text-tertiary);\n font-variant-numeric: tabular-nums;\n }\n\n /* ─── Toolbar ────────────────────────────────────────────────────── */\n .tab-toolbar {\n display: flex;\n gap: 8px;\n margin-bottom: 20px;\n align-items: center;\n }\n .search-wrapper {\n position: relative;\n width: 280px;\n }\n .search-icon {\n position: absolute;\n left: 10px;\n top: 50%;\n transform: translateY(-50%);\n color: var(--text-tertiary);\n width: 16px;\n height: 16px;\n pointer-events: none;\n }\n .search-bar {\n width: 100%;\n height: 36px;\n padding: 0 12px 0 34px;\n background: transparent;\n border: 1px solid var(--border-strong);\n border-radius: var(--radius-sm);\n font-size: 14px;\n color: var(--text);\n outline: none;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n }\n .search-bar::placeholder { color: var(--text-tertiary); }\n .search-bar:focus { border-color: var(--border-focus); box-shadow: 0 0 0 2px rgba(255,255,255,0.05); }\n .select {\n height: 36px;\n padding: 0 12px;\n font-size: 13px;\n color: var(--text);\n background: transparent;\n border: 1px solid var(--border-strong);\n border-radius: var(--radius-sm);\n outline: none;\n cursor: pointer;\n transition: border-color 0.15s ease;\n }\n .select:focus { border-color: var(--border-focus); }\n .btn-sm { height: 36px; padding: 0 12px; font-size: 13px; width: auto; }\n .btn-xs { height: 28px; padding: 0 10px; font-size: 12px; width: auto; }\n\n /* ─── OAuth provider picker ──────────────────────────────────────── */\n .provider-picker {\n display: grid;\n grid-template-columns: repeat(5, 1fr);\n gap: 8px;\n }\n .provider-card {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 10px 6px 8px;\n border: 1px solid var(--border);\n border-radius: var(--radius-sm);\n background: var(--bg-elevated);\n cursor: pointer;\n transition: border-color 0.15s, background 0.15s;\n position: relative;\n min-width: 0;\n }\n .provider-card:hover:not(.added) { border-color: var(--border-strong); background: var(--bg-hover); }\n .provider-card.selected { border-color: var(--blue); background: rgba(108,71,255,0.08); }\n .provider-card.added { opacity: 0.45; cursor: default; }\n .provider-icon { display: flex; align-items: center; justify-content: center; width: 20px; height: 20px; }\n .provider-icon svg { width: 20px; height: 20px; }\n .provider-name { font-size: 11px; color: var(--text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }\n .provider-card.selected .provider-name { color: var(--text); }\n .provider-added-badge {\n position: absolute;\n top: -6px; right: -6px;\n font-size: 9px;\n font-weight: 600;\n padding: 1px 4px;\n border-radius: 4px;\n background: var(--bg-card);\n border: 1px solid var(--border);\n color: var(--text-tertiary);\n line-height: 1.4;\n white-space: nowrap;\n }\n\n /* ─── Pagination ─────────────────────────────────────────────────── */\n .pagination {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-top: 16px;\n font-size: 13px;\n color: var(--text-tertiary);\n }\n .pagination-btns {\n display: flex;\n gap: 4px;\n }\n .page-btn {\n min-width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: none;\n background: transparent;\n color: var(--text-secondary);\n border-radius: var(--radius-sm);\n cursor: pointer;\n font-size: 13px;\n font-weight: 500;\n transition: background-color 0.1s ease;\n }\n .page-btn:hover { background: var(--bg-hover); color: var(--text); }\n .page-btn.active { background: var(--bg-active); color: var(--text-white); }\n .page-btn:disabled { opacity: 0.3; cursor: not-allowed; }\n\n /* ─── Empty state ────────────────────────────────────────────────── */\n .empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 48px 24px;\n text-align: center;\n }\n .empty-state-icon {\n width: 48px;\n height: 48px;\n border-radius: var(--radius);\n background: rgba(255,255,255,0.06);\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--text-tertiary);\n margin-bottom: 16px;\n }\n .empty-state-title {\n font-size: 15px;\n font-weight: 500;\n color: var(--text);\n margin-bottom: 4px;\n }\n .empty-state-desc {\n font-size: 13px;\n color: var(--text-secondary);\n max-width: 320px;\n line-height: 1.5;\n }\n\n /* ─── Dialogs ────────────────────────────────────────────────────── */\n dialog {\n background: var(--bg-elevated);\n border: 1px solid var(--border-strong);\n border-radius: var(--radius-lg);\n color: var(--text);\n padding: 0;\n margin: auto;\n max-width: 520px;\n width: 90%;\n box-shadow: var(--shadow-lg);\n overflow: hidden;\n animation: modalIn 0.2s ease;\n }\n dialog::backdrop { background: rgba(0,0,0,0.7); }\n .dialog-content {\n padding: 24px;\n }\n .dialog-content h3 {\n font-size: 16px;\n font-weight: 600;\n color: var(--text-white);\n margin-bottom: 4px;\n letter-spacing: -0.01em;\n }\n .dialog-desc {\n font-size: 14px;\n color: var(--text-secondary);\n margin-bottom: 20px;\n line-height: 1.5;\n }\n .dialog-actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n padding: 16px 24px;\n border-top: 1px solid var(--border);\n }\n .dialog-actions .btn { width: auto; }\n\n /* ─── Wallet bind QR (mirrors login-page .qr-view) ──────────────── */\n .wallet-qr-view {\n margin: 16px auto;\n display: flex;\n justify-content: center;\n min-height: 212px;\n align-items: center;\n }\n .wallet-qr-placeholder {\n width: 212px;\n height: 212px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius);\n background: var(--bg-elevated);\n }\n .wallet-qr-spinner {\n display: inline-block;\n width: 32px; height: 32px;\n border: 3px solid var(--border-strong);\n border-top-color: var(--blue-muted);\n border-radius: 50%;\n animation: spin 0.6s linear infinite;\n }\n @keyframes spin { to { transform: rotate(360deg); } }\n .wallet-deep-link-btn {\n margin-top: 4px;\n width: 212px;\n }\n .wallet-bind-status {\n margin-top: 12px;\n font-size: 13px;\n min-height: 18px;\n color: var(--text-secondary);\n margin-bottom: 0;\n }\n .wallet-bind-error {\n color: var(--red-text);\n padding: 10px 12px;\n background: rgba(220,53,69,0.08);\n border: 1px solid rgba(220,53,69,0.25);\n border-radius: var(--radius-sm);\n margin-top: 8px;\n }\n\n @keyframes modalIn {\n from { opacity: 0; transform: scale(0.98); }\n to { opacity: 1; transform: scale(1); }\n }\n\n /* ─── Action menus ───────────────────────────────────────────────── */\n .action-trigger {\n width: 28px;\n height: 28px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--radius-sm);\n border: none;\n background: transparent;\n color: var(--text-tertiary);\n cursor: pointer;\n font-size: 16px;\n transition: background-color 0.15s ease, color 0.15s ease;\n flex-shrink: 0;\n }\n .action-trigger:hover { background: var(--bg-hover); color: var(--text-secondary); }\n .action-menu {\n display: none;\n position: absolute;\n right: 8px;\n top: 100%;\n z-index: 50;\n min-width: 180px;\n padding: 4px;\n background: var(--bg-elevated);\n border: 1px solid var(--border-strong);\n border-radius: var(--radius);\n box-shadow: var(--shadow-md);\n }\n .action-menu.open { display: block; }\n .action-menu-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 8px;\n border-radius: var(--radius-xs);\n font-size: 13px;\n color: var(--text);\n cursor: pointer;\n border: none;\n background: none;\n width: 100%;\n text-align: left;\n transition: background-color 0.1s ease;\n }\n .action-menu-item:hover { background: var(--bg-hover); }\n .action-menu-item.danger { color: var(--red-text); }\n .action-menu-item.danger:hover { background: var(--red-light); }\n .action-menu-sep {\n height: 1px;\n background: var(--border);\n margin: 4px 0;\n }\n\n /* ─── Status badges ──────────────────────────────────────────────── */\n .badge-active { background: var(--green-light); color: var(--green-text); }\n .badge-pending { background: var(--yellow-light); color: var(--yellow-text); }\n .badge-blocked { background: var(--red-light); color: var(--red-text); }\n .badge-expired { background: rgba(255,255,255,0.06); color: var(--text-tertiary); }\n .badge-neutral { background: rgba(255,255,255,0.06); color: var(--text-secondary); }\n\n /* ─── Toast ──────────────────────────────────────────────────────── */\n .toast-container {\n position: fixed;\n bottom: 24px;\n right: 24px;\n z-index: 100;\n display: flex;\n flex-direction: column;\n gap: 8px;\n }\n .toast {\n padding: 10px 16px;\n border-radius: var(--radius);\n font-size: 13px;\n font-weight: 500;\n color: var(--text);\n background: var(--bg-elevated);\n border: 1px solid var(--border-strong);\n box-shadow: var(--shadow-md);\n animation: toastIn 0.2s ease;\n }\n .toast.success { border-left: 3px solid var(--green); }\n .toast.error { background: var(--red); color: #fff; border-color: var(--red); }\n @keyframes toastIn {\n from { opacity: 0; transform: translateY(8px); }\n to { opacity: 1; transform: translateY(0); }\n }\n\n /* ─── Toggle switch ────────────────────────────────────────────── */\n .toggle-switch {\n position: relative;\n display: inline-block;\n width: 40px;\n height: 22px;\n cursor: pointer;\n }\n .toggle-switch input { opacity: 0; width: 0; height: 0; position: absolute; }\n .toggle-slider {\n position: absolute;\n inset: 0;\n background: var(--bg-hover);\n border-radius: 11px;\n transition: background-color 0.2s ease;\n }\n .toggle-slider::before {\n content: \"\";\n position: absolute;\n left: 2px;\n top: 2px;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: var(--text-secondary);\n transition: transform 0.2s ease, background-color 0.2s ease;\n }\n .toggle-switch input:checked + .toggle-slider { background: var(--blue); }\n .toggle-switch input:checked + .toggle-slider::before {\n transform: translateX(18px);\n background: #fff;\n }\n\n /* ─── Range slider ─────────────────────────────────────────────── */\n .range-slider {\n -webkit-appearance: none;\n width: 100%;\n height: 6px;\n border-radius: 3px;\n background: var(--bg-hover);\n outline: none;\n margin: 8px 0;\n }\n .range-slider::-webkit-slider-thumb {\n -webkit-appearance: none;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: var(--blue);\n cursor: pointer;\n }\n .range-slider::-moz-range-thumb {\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: var(--blue);\n cursor: pointer;\n border: none;\n }\n\n /* ─── Branding: Section layout ─────────────────────── */\n .br-section {\n margin-bottom: 56px;\n }\n .br-section-header {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n margin-bottom: 20px;\n }\n .br-section-title {\n font-size: 16px;\n font-weight: 600;\n color: var(--text);\n letter-spacing: -0.01em;\n }\n .br-section-category {\n font-size: 10px;\n font-weight: 600;\n color: var(--text-tertiary);\n text-transform: uppercase;\n letter-spacing: 0.08em;\n }\n .br-two-col {\n display: grid;\n grid-template-columns: 1fr 2fr;\n gap: 32px;\n }\n .br-col-desc {\n font-size: 13px;\n color: var(--text-secondary);\n line-height: 1.6;\n }\n .br-field-hint {\n font-size: 11px;\n color: var(--text-tertiary);\n margin-top: 4px;\n margin-bottom: 12px;\n }\n /* Info icon tooltip */\n .info-tip {\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 14px;\n height: 14px;\n border-radius: 50%;\n background: rgba(255,255,255,0.08);\n color: var(--text-tertiary);\n font-size: 10px;\n font-weight: 600;\n cursor: help;\n flex-shrink: 0;\n }\n .info-tip::after {\n content: attr(data-tip);\n position: absolute;\n bottom: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n background: var(--bg-card);\n border: 1px solid var(--border-strong);\n color: var(--text-secondary);\n font-size: 12px;\n font-weight: 400;\n padding: 8px 14px;\n border-radius: var(--radius);\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.15s ease;\n z-index: 10;\n box-shadow: var(--shadow-md);\n min-width: 220px;\n max-width: 360px;\n white-space: normal;\n line-height: 1.5;\n text-align: left;\n }\n .info-tip:hover::after { opacity: 1; }\n .br-panel {\n background: var(--bg-elevated);\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius-lg);\n padding: 20px 24px;\n }\n .accent-dot {\n width: 28px;\n height: 28px;\n border-radius: 50%;\n cursor: pointer;\n border: 2px solid transparent;\n transition: transform 0.1s ease, border-color 0.15s ease;\n }\n .accent-dot:hover { transform: scale(1.15); }\n .accent-dot.selected { border-color: var(--text-white); }\n\n /* ─── Branding: Language toggles ──────────────────── */\n .lang-toggles {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n align-items: center;\n }\n .lang-toggle {\n position: relative;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 20px;\n background: var(--bg-elevated);\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius);\n font-size: 13px;\n font-weight: 500;\n color: var(--text-secondary);\n cursor: pointer;\n transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;\n }\n .lang-toggle:hover { border-color: var(--border-strong); color: var(--text); }\n .lang-toggle.active {\n border-color: var(--border-strong);\n color: var(--text-white);\n background: rgba(255,255,255,0.04);\n }\n .lang-toggle.active::after {\n content: \"✓\";\n position: absolute;\n top: -1px;\n right: -1px;\n width: 16px;\n height: 16px;\n background: var(--blue);\n border-radius: 0 var(--radius) 0 var(--radius);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 10px;\n font-weight: 700;\n color: #fff;\n line-height: 1;\n }\n\n /* ─── Branding: Logo grid (redesigned) ───────────── */\n .logo-grid {\n display: grid;\n grid-template-columns: repeat(4, 1fr);\n gap: 16px;\n }\n .logo-item {\n position: relative;\n background: var(--bg-elevated);\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius-lg);\n overflow: hidden;\n display: flex;\n flex-direction: column;\n padding: 16px;\n transition: border-color 0.15s ease;\n min-height: 170px;\n }\n .logo-item:hover { border-color: rgba(255,255,255,0.15); }\n .logo-item-header {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n margin-bottom: auto;\n }\n .logo-item-icon {\n color: var(--text-tertiary);\n font-size: 20px;\n line-height: 1;\n }\n .logo-item-spec {\n font-size: 10px;\n font-weight: 600;\n color: var(--text-tertiary);\n text-transform: uppercase;\n letter-spacing: 0.03em;\n }\n .logo-preview {\n width: 100%;\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n }\n .logo-preview img {\n max-width: 100%;\n max-height: 80px;\n object-fit: contain;\n }\n .logo-item-footer {\n margin-top: 12px;\n }\n .logo-name {\n font-size: 14px;\n font-weight: 600;\n color: var(--text-white);\n margin-bottom: 2px;\n }\n .logo-subtitle {\n font-size: 12px;\n color: var(--text-tertiary);\n }\n .logo-placeholder {\n font-size: 24px;\n color: var(--text-tertiary);\n opacity: 0.3;\n }\n /* Hover overlay for upload — glassmorphism */\n .logo-hover-upload {\n position: absolute;\n inset: 0;\n background: rgba(20,20,30,0.65);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 6px;\n opacity: 0;\n transition: opacity 0.2s ease;\n cursor: pointer;\n border-radius: inherit;\n }\n .logo-item:hover .logo-hover-upload { opacity: 1; }\n .logo-hover-icon {\n width: 36px;\n height: 36px;\n border-radius: 50%;\n background: rgba(255,255,255,0.1);\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--text-white);\n margin-bottom: 2px;\n }\n .logo-hover-icon svg { width: 18px; height: 18px; }\n .logo-hover-text {\n font-size: 12px;\n font-weight: 500;\n color: var(--text-secondary);\n letter-spacing: 0.02em;\n }\n /* Uploaded state: replace/discard shown on hover via overlay */\n .logo-uploaded-overlay {\n position: absolute;\n inset: 0;\n background: rgba(20,20,30,0.65);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 12px;\n opacity: 0;\n transition: opacity 0.2s ease;\n border-radius: inherit;\n z-index: 2;\n }\n .logo-item:hover .logo-uploaded-overlay { opacity: 1; }\n .logo-overlay-btn {\n font-size: 12px;\n font-weight: 600;\n padding: 6px 14px;\n border-radius: var(--radius-sm);\n cursor: pointer;\n border: 1px solid;\n transition: background 0.15s ease;\n }\n .logo-overlay-btn-replace { color: var(--blue); border-color: var(--blue); background: transparent; }\n .logo-overlay-btn-replace:hover { background: rgba(99,102,241,0.12); }\n .logo-overlay-btn-discard { color: var(--red-text); border-color: rgba(224,46,46,0.4); background: transparent; }\n .logo-overlay-btn-discard:hover { background: rgba(224,46,46,0.1); }\n .btn-text { background: none; border: none; color: var(--blue); font-size: 11px; font-weight: 600; cursor: pointer; padding: 0; }\n .btn-text:hover { text-decoration: underline; }\n .btn-text-danger { color: var(--red-text); }\n\n /* ─── Branding: Sticky save bar ──────────────────── */\n .br-save-bar {\n position: fixed;\n bottom: 0;\n left: 0;\n right: 0;\n z-index: 40;\n background: rgba(28,28,32,0.85);\n backdrop-filter: blur(12px);\n border-top: 1px solid var(--border-subtle);\n padding: 16px 40px;\n display: none;\n }\n .br-save-bar.visible { display: block; }\n .br-save-bar-inner {\n max-width: 1120px;\n margin: 0 auto;\n display: flex;\n align-items: center;\n justify-content: space-between;\n width: 100%;\n }\n .br-save-hint {\n font-size: 12px;\n color: var(--text-tertiary);\n display: flex;\n align-items: center;\n gap: 8px;\n }\n .br-save-actions {\n display: flex;\n gap: 8px;\n }\n .br-save-actions .btn { width: auto; }\n\n /* ─── Branding: Accent color swatches ────────────── */\n .accent-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));\n gap: 10px;\n }\n .accent-swatch {\n position: relative;\n height: 64px;\n border-radius: var(--radius);\n display: flex;\n flex-direction: column;\n justify-content: flex-end;\n padding: 6px 8px;\n cursor: pointer;\n transition: transform 0.1s ease, box-shadow 0.15s ease;\n border: 2px solid transparent;\n }\n .accent-swatch:hover { transform: scale(1.02); }\n .accent-swatch.selected { border-color: var(--text-white); }\n .accent-swatch .accent-check {\n position: absolute;\n top: 4px;\n right: 4px;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: rgba(0,0,0,0.5);\n display: none;\n align-items: center;\n justify-content: center;\n color: white;\n font-size: 12px;\n }\n .accent-swatch.selected .accent-check { display: flex; }\n .accent-label { font-size: 9px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.8; }\n .accent-hex { font-size: 10px; font-weight: 700; }\n\n /* ─── Branding tab: Language grid ───────────────────── */\n .lang-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));\n gap: 4px 16px;\n }\n .lang-item {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n padding: 4px 0;\n cursor: pointer;\n }\n\n /* ─── Crop dialog ──────────────────────────────────── */\n dialog.dialog-wide { max-width: 660px; }\n .crop-container {\n width: 100%;\n height: 400px;\n background: var(--bg);\n border-radius: var(--radius-sm);\n overflow: hidden;\n }\n .crop-toolbar {\n display: flex;\n justify-content: center;\n gap: 6px;\n padding: 12px 0 4px;\n }\n .crop-tool-btn {\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--bg-input);\n border: 1px solid var(--border-strong);\n border-radius: var(--radius-sm);\n color: var(--text-secondary);\n cursor: pointer;\n transition: background-color 0.15s ease, color 0.15s ease;\n }\n .crop-tool-btn:hover { background: var(--bg-hover); color: var(--text); }\n .crop-tool-btn svg { width: 18px; height: 18px; }\n\n /* Crop dialog progress */\n .crop-progress {\n width: 120px;\n height: 4px;\n background: var(--bg-hover);\n border-radius: 2px;\n overflow: hidden;\n }\n .crop-progress-bar {\n width: 30%;\n height: 100%;\n background: var(--blue);\n border-radius: 2px;\n animation: cropProgress 1.5s ease infinite;\n }\n @keyframes cropProgress {\n 0% { width: 10%; margin-left: 0; }\n 50% { width: 40%; margin-left: 30%; }\n 100% { width: 10%; margin-left: 90%; }\n }\n\n /* cropperjs dark theme overrides */\n .cropper-view-box { outline-color: var(--blue) !important; }\n .cropper-line { background-color: var(--blue) !important; }\n .cropper-point { background-color: var(--blue) !important; width: 8px !important; height: 8px !important; }\n .cropper-modal { background-color: rgba(0,0,0,0.6) !important; }\n .cropper-dashed { border-color: rgba(255,255,255,0.3) !important; }\n\n /* ─── Branding: Mobile responsive ──────────────────── */\n @media (max-width: 768px) {\n .br-two-col { grid-template-columns: 1fr; gap: 16px; }\n .logo-grid { grid-template-columns: repeat(2, 1fr); }\n .br-save-bar { padding: 12px 16px; }\n .br-save-bar-inner { flex-direction: column; gap: 12px; text-align: center; }\n .br-save-actions { justify-content: center; }\n .tab-panel { padding: 20px 16px; }\n .accent-grid { grid-template-columns: repeat(3, 1fr); }\n .br-panel > div[style*=\"grid-template-columns: 1fr 1fr\"] { grid-template-columns: 1fr !important; }\n }\n @media (max-width: 480px) {\n .logo-grid { grid-template-columns: 1fr; }\n .accent-grid { grid-template-columns: repeat(2, 1fr); }\n }\n\n /* ─── Appearance tab: Color input ───────────────────── */\n .color-input {\n width: 40px;\n height: 28px;\n padding: 0;\n border: 1px solid var(--border-strong);\n border-radius: 4px;\n cursor: pointer;\n background: none;\n }\n\n /* ─── Auto theme: follow system preference ───────────────────────── */\n @media (prefers-color-scheme: light) {\n [data-theme=\"auto\"] {\n --bg-root: #f8f9fa;\n --bg-surface: #ffffff;\n --bg-card: #ffffff;\n --bg-elevated: #f0f1f3;\n --bg-hover: #e9eaec;\n --bg-active: #dddee1;\n --bg-input: rgba(0,0,0,0.04);\n --border: rgba(0,0,0,0.10);\n --border-subtle: rgba(0,0,0,0.06);\n --border-strong: rgba(0,0,0,0.15);\n --border-focus: rgba(0,0,0,0.25);\n --text: #1a1a1a;\n --text-secondary: #6b7280;\n --text-tertiary: #9ca3af;\n --text-placeholder: #c0c5ce;\n --text-white: #1a1a1a;\n --shadow-sm: 0 1px 3px rgba(0,0,0,0.08), 0 1px 2px -1px rgba(0,0,0,0.06);\n --shadow-md: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.06);\n --shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.12), 0 4px 6px -4px rgba(0,0,0,0.08);\n --shadow-focus: 0 0 0 1px rgba(0,0,0,0.08), 0 0 0 3px rgba(108,71,255,0.2);\n color-scheme: light;\n }\n }\n\n /* ─── Skeleton loading ──────────────────────────────────────────────── */\n @keyframes shimmer {\n 0% { background-position: 200% 0; }\n 100% { background-position: -200% 0; }\n }\n .skel {\n display: block;\n background: linear-gradient(90deg, var(--bg-hover) 25%, var(--bg-active, rgba(255,255,255,0.06)) 50%, var(--bg-hover) 75%);\n background-size: 200% 100%;\n animation: shimmer 1.4s infinite;\n border-radius: 4px;\n }\n .skel-circle { border-radius: 50%; }\n .skel-row td { padding: 12px 16px; }\n\n /* ─── Responsive: column visibility helpers ─────────────────────────── */\n /* col-hide-lg: hide on tablet (≤900px) */\n @media (max-width: 900px) {\n .col-hide-lg { display: none; }\n }\n /* col-hide-md: hide on small tablet (≤768px) */\n @media (max-width: 768px) {\n .col-hide-md { display: none; }\n }\n /* col-hide-sm: hide on mobile (≤640px) */\n @media (max-width: 640px) {\n .col-hide-sm { display: none; }\n }\n\n /* ─── Mobile sidebar drawer ──────────────────────────────────────────── */\n .sidebar-toggle { display: none; }\n .sidebar-overlay {\n display: none;\n position: fixed;\n inset: 0;\n background: rgba(0,0,0,0.5);\n z-index: 200;\n opacity: 0;\n transition: opacity 0.25s ease;\n }\n .sidebar-overlay-visible {\n display: block;\n opacity: 1;\n }\n .sidebar {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n bottom: 0;\n width: 280px;\n max-width: 80vw;\n background: var(--bg-surface);\n border-right: 1px solid var(--border);\n z-index: 210;\n transform: translateX(-100%);\n transition: transform 0.25s ease;\n overflow-y: auto;\n -webkit-overflow-scrolling: touch;\n }\n .sidebar-open { transform: translateX(0); }\n .sidebar-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px;\n border-bottom: 1px solid var(--border);\n }\n .sidebar-header h2 {\n font-size: 15px;\n font-weight: 600;\n color: var(--text-white);\n margin: 0;\n }\n .sidebar-nav {\n padding: 8px;\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n .sidebar-item {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 10px 12px;\n border: none;\n background: transparent;\n border-radius: var(--radius-sm);\n font-size: 14px;\n font-weight: 500;\n color: var(--text-secondary);\n cursor: pointer;\n transition: background 0.15s, color 0.15s;\n text-align: left;\n width: 100%;\n -webkit-tap-highlight-color: transparent;\n }\n .sidebar-item:hover { background: var(--bg-hover); color: var(--text); }\n .sidebar-item:active { background: var(--bg-active); }\n .sidebar-item svg { width: 18px; height: 18px; flex-shrink: 0; color: var(--text-tertiary); transition: color 0.15s; }\n .sidebar-item:hover svg { color: var(--text-secondary); }\n .sidebar-item-active {\n background: var(--bg-hover);\n color: var(--text-white);\n }\n .sidebar-item-active svg { color: var(--text-white); }\n\n /* ─── Responsive: show sidebar toggle, hide tabs on mobile ──────────── */\n @media (max-width: 640px) {\n .sidebar-toggle { display: flex; }\n .sidebar { display: block; }\n .tabs { display: none; }\n }\n\n /* ─── Responsive: toolbar / filter bar ──────────────────────────────── */\n @media (max-width: 640px) {\n .tab-toolbar {\n flex-wrap: wrap;\n gap: 6px;\n }\n .search-wrapper {\n width: 100%;\n min-width: 0;\n }\n .tab-toolbar .select {\n flex: 1;\n min-width: 0;\n font-size: 12px;\n padding: 0 8px;\n height: 34px;\n }\n .tab-toolbar .btn-sm {\n font-size: 12px;\n padding: 0 10px;\n height: 34px;\n }\n }\n\n /* ─── Responsive: header ────────────────────────────────────────────── */\n @media (max-width: 640px) {\n .admin-header-left {\n gap: 6px;\n min-width: 0;\n overflow: hidden;\n }\n .admin-header-left a {\n max-width: 80px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n flex-shrink: 1;\n }\n .admin-header h1 {\n font-size: 13px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .header-dot { display: none; }\n }\n\n /* ─── Responsive: table compact ─────────────────────────────────────── */\n @media (max-width: 640px) {\n .table th, .table td {\n padding: 10px 8px;\n font-size: 13px;\n }\n .table th {\n font-size: 10px;\n }\n .member-identity {\n gap: 8px;\n }\n .member-name {\n font-size: 13px;\n max-width: 120px;\n }\n .member-email {\n font-size: 11px;\n max-width: 100px;\n }\n .action-cell { width: 32px; }\n }\n\n /* ─── Responsive: pagination ────────────────────────────────────────── */\n @media (max-width: 640px) {\n .pagination {\n flex-direction: column;\n gap: 8px;\n align-items: center;\n font-size: 12px;\n }\n .pagination-info {\n font-size: 12px;\n }\n .page-btn {\n min-width: 28px;\n height: 28px;\n font-size: 12px;\n }\n }\n\n /* ─── Responsive: section titles ────────────────────────────────────── */\n @media (max-width: 640px) {\n .section-title { font-size: 17px; }\n .section-desc { font-size: 13px; margin-bottom: 20px; padding-bottom: 16px; }\n }\n\n /* ─── Responsive: settings card ─────────────────────────────────────── */\n @media (max-width: 640px) {\n .settings-card { border-radius: var(--radius-sm); }\n .settings-card-header {\n gap: 8px;\n padding: 12px 14px;\n }\n .settings-card-header-desc { text-align: left; }\n .settings-row {\n gap: 6px;\n padding: 12px 14px;\n }\n .settings-row.settings-row-stack {\n flex-direction: column;\n align-items: flex-start;\n }\n }\n\n /* ─── Responsive: table horizontal scroll on very small screens ─── */\n @media (max-width: 480px) {\n .settings-card {\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n }\n .settings-card .table {\n min-width: 360px;\n }\n }\n\n /* ─── Settings: OAuth card grid ─────────────────────────────── */\n .st-oauth-grid {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n padding: 8px 20px 16px;\n }\n .st-oauth-card {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 14px;\n border: 1px solid var(--border);\n border-radius: var(--radius-md);\n cursor: pointer;\n transition: border-color 0.15s, background 0.15s;\n min-width: 110px;\n }\n .st-oauth-card:hover {\n border-color: var(--border-strong);\n background: var(--bg-hover);\n }\n .st-oauth-card-icon {\n display: inline-flex;\n width: 20px;\n height: 20px;\n flex-shrink: 0;\n }\n .st-oauth-card-name {\n font-size: 13px;\n font-weight: 500;\n color: var(--text);\n }\n .st-oauth-dot {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n flex-shrink: 0;\n margin-left: auto;\n }\n .st-oauth-dot-on { background: var(--green-text); }\n .st-oauth-dot-off { background: var(--text-tertiary); }\n\n /* ─── Settings: dual panel (login methods + email) ──────────── */\n .st-dual-panel {\n display: flex;\n gap: 16px;\n }\n @media (max-width: 640px) {\n .st-dual-panel {\n flex-direction: column;\n }\n }\n\n /* ─── Settings: email hint ─────────────────────────────────── */\n .st-hint {\n font-size: 11px;\n color: var(--yellow-text, #92400e);\n font-weight: 400;\n margin-left: 4px;\n }\n\n /* ─── Federation ──────────────────────────────────────────── */\n .fed-status-badge {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 12px;\n font-weight: 500;\n padding: 2px 8px;\n border-radius: var(--radius-full);\n }\n .fed-status-badge.master {\n background: var(--blue-light);\n color: var(--blue-muted);\n }\n .fed-status-badge.member {\n background: var(--green-light);\n color: var(--green-text);\n }\n .fed-star {\n color: var(--yellow);\n font-size: 14px;\n }\n .fed-site-row {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 10px 20px;\n border-bottom: 1px solid var(--border);\n }\n .fed-site-row:last-child { border-bottom: none; }\n .fed-site-row { flex-wrap: wrap; }\n .fed-site-name { flex: 1 1 0%; min-width: 120px; font-weight: 500; font-size: 14px; color: var(--text); display: flex; align-items: center; gap: 6px; overflow: hidden; flex-wrap: nowrap; }\n .fed-site-name > span { flex-shrink: 0; }\n .fed-site-name > span.fed-site-label { flex-shrink: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .fed-site-url { flex: 0 0 500px; min-width: 0; font-size: 13px; color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .fed-site-url a { color: var(--text-secondary); text-decoration: none; }\n .fed-site-url a:hover { color: var(--blue); text-decoration: underline; }\n .fed-site-role { width: 80px; font-size: 12px; color: var(--text-tertiary); }\n .fed-site-actions { display: flex; align-items: center; gap: 6px; justify-content: flex-end; min-width: 64px; flex-shrink: 0; }\n @media (max-width: 768px) {\n .fed-site-row { flex-wrap: wrap; }\n .fed-site-name { flex: 1 1 100%; }\n .fed-site-url { flex: 1 1 auto; }\n }\n .fed-delegation-status { display: flex; align-items: center; gap: 8px; padding: 12px 20px; font-size: 13px; color: var(--text-secondary); }\n .fed-delegation-status .checkmark { color: var(--green); font-weight: 600; }\n .fed-init-buttons { display: flex; gap: 12px; justify-content: center; padding: 8px 20px 16px; }\n .fed-init-buttons .btn { width: auto; min-width: 160px; }\n","admin-core.c0b5af61.js":"\nvar toastContainer = null;\n\nfunction ensureToastContainer() {\n if (toastContainer) return;\n toastContainer = document.createElement(\"div\");\n toastContainer.style.cssText = \"position:fixed;bottom:20px;right:20px;z-index:9999;display:flex;flex-direction:column;gap:8px;\";\n document.body.appendChild(toastContainer);\n}\n\nfunction showToast(message, type) {\n ensureToastContainer();\n var toast = document.createElement(\"div\");\n var bg = type === \"error\" ? \"var(--red-bg)\" : type === \"success\" ? \"var(--green-bg)\" : \"var(--bg-card)\";\n var color = type === \"error\" ? \"var(--red)\" : type === \"success\" ? \"var(--green)\" : \"var(--text)\";\n var border = type === \"error\" ? \"var(--red)\" : type === \"success\" ? \"var(--green)\" : \"var(--border)\";\n toast.style.cssText = \"padding:12px 20px;border-radius:8px;font-size:14px;background:\" + bg + \";color:\" + color + \";border:1px solid \" + border + \";box-shadow:0 4px 12px rgba(0,0,0,0.3);opacity:0;transition:opacity 0.2s;\";\n toast.textContent = message;\n toastContainer.appendChild(toast);\n requestAnimationFrame(function() { toast.style.opacity = \"1\"; });\n setTimeout(function() {\n toast.style.opacity = \"0\";\n setTimeout(function() { toast.remove(); }, 200);\n }, 3000);\n}\n\n;\n\nfunction relativeTime(iso) {\n var diff = Date.now() - new Date(iso).getTime();\n var abs = Math.abs(diff);\n var future = diff < 0;\n var s = Math.floor(abs / 1000);\n var m = Math.floor(s / 60);\n var h = Math.floor(m / 60);\n var d = Math.floor(h / 24);\n var label;\n if (d > 0) label = d + \"d \" + (h % 24) + \"h\";\n else if (h > 0) label = h + \"h \" + (m % 60) + \"m\";\n else if (m > 0) label = m + \" min\";\n else label = \"just now\";\n if (label === \"just now\") return label;\n return future ? \"in \" + label : label + \" ago\";\n}\n\nfunction absoluteTime(iso) {\n var d = new Date(iso);\n var pad = function(n) { return n < 10 ? \"0\" + n : \"\" + n; };\n return d.getFullYear() + \"-\" + pad(d.getMonth() + 1) + \"-\" + pad(d.getDate())\n + \" \" + pad(d.getHours()) + \":\" + pad(d.getMinutes()) + \":\" + pad(d.getSeconds());\n}\n\nfunction copyToClipboard(text) {\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(text).then(function() {\n showToast(\"Copied!\", \"success\");\n }).catch(function() {\n fallbackCopy(text);\n });\n } else {\n fallbackCopy(text);\n }\n}\n\nfunction fallbackCopy(text) {\n var input = document.createElement(\"input\");\n input.value = text;\n document.body.appendChild(input);\n input.select();\n document.execCommand(\"copy\");\n input.remove();\n showToast(\"Copied!\", \"success\");\n}\n\n// Delegate to the canonical implementation from @arcblock/did-connect-core\n// (exposed globally by the <did-address> IIFE bundle as __DidAddressBundle.truncateDid)\nfunction truncateDid(did) {\n return __DidAddressBundle.truncateDid(did);\n}\n\nfunction initials(name) {\n if (!name) return \"?\";\n var parts = name.trim().split(/\\s+/);\n if (parts.length >= 2) return (parts[0][0] + parts[1][0]).toUpperCase();\n return name.slice(0, 2).toUpperCase();\n}\n\nfunction escapeHtml(str) {\n var div = document.createElement(\"div\");\n div.textContent = str;\n return div.innerHTML;\n}\n\n// Generate a shimmer skeleton table: cols = column count, rows = row count\nfunction skeletonTable(cols, rows) {\n var widths = [70, 45, 60, 55, 65, 50, 60, 30];\n var cells = '';\n for (var i = 0; i < cols; i++) {\n cells += '<td><span class=\"skel\" style=\"width:' + widths[i % widths.length] + '%;height:13px;\"></span></td>';\n }\n var row = '<tr class=\"skel-row\">' + cells + '</tr>';\n var tbody = '';\n for (var j = 0; j < rows; j++) tbody += row;\n return '<table class=\"table\"><tbody>' + tbody + '</tbody></table>';\n}\n\n/** Generate shimmer skeleton rows for list-style containers. */\nfunction skeletonRows(rows) {\n var widths = [45, 65, 55];\n var html = '';\n for (var i = 0; i < rows; i++) {\n html += '<div class=\"settings-row\"><span class=\"skel\" style=\"width:' + widths[i % widths.length] + '%;height:13px;\"></span></div>';\n }\n return html;\n}\n\nfunction renderAvatar(avatarUrl, fullName, size) {\n size = size || \"\";\n var cls = \"avatar\" + (size ? \" avatar-\" + size : \"\");\n if (avatarUrl) {\n var sep = avatarUrl.indexOf(\"?\") >= 0 ? \"&\" : \"?\";\n var src = avatarUrl + sep + \"t=\" + Date.now() + (size === \"sm\" ? \"&s=64\" : \"\");\n return '<div class=\"' + cls + '\">'\n + '<img src=\"' + escapeHtml(src) + '\" '\n + 'onerror=\"this.style.display=\\'none\\';this.nextSibling.style.display=\\'flex\\'\">'\n + '<span class=\"avatar-fallback\" style=\"display:none\">' + escapeHtml(initials(fullName)) + '</span>'\n + '</div>';\n }\n return '<div class=\"' + cls + '\"><span class=\"avatar-fallback\">' + escapeHtml(initials(fullName)) + '</span></div>';\n}\n\n;\n\nasync function api(method, path, body) {\n try {\n var opts = {\n method: method,\n headers: {},\n };\n if (body !== undefined) {\n opts.headers[\"Content-Type\"] = \"application/json\";\n opts.body = JSON.stringify(body);\n }\n var res = await fetch(window.__BASE_PATH + path, opts);\n var data = await res.json();\n\n if (res.status === 401) {\n location.href = \"/.well-known/service/api/did/logout\";\n return { ok: false, error: \"Session expired\", code: \"UNAUTHENTICATED\" };\n }\n if (res.status === 403 && data.code === \"BLOCKED\") {\n location.href = \"/.well-known/service/api/did/logout\";\n return { ok: false, error: \"Account blocked\", code: \"BLOCKED\" };\n }\n if (!res.ok) {\n showToast(data.error || \"Request failed\", \"error\");\n return { ok: false, error: data.error, code: data.code };\n }\n return data;\n } catch (err) {\n showToast(\"Network error\", \"error\");\n return { ok: false, error: err.message, code: \"NETWORK_ERROR\" };\n }\n}\n\n;\n\nfunction showDialog(id) {\n var d = document.getElementById(id);\n if (d && d.showModal) d.showModal();\n}\n\nfunction closeDialog(id) {\n var d = document.getElementById(id || \"generic-dialog\");\n if (d && d.close) d.close();\n}\n\nfunction openDialog(title, bodyHtml, confirmLabel, onConfirm) {\n var d = document.getElementById(\"generic-dialog\");\n if (!d) {\n d = document.createElement(\"dialog\");\n d.id = \"generic-dialog\";\n d.innerHTML = '<div class=\"dialog-content\"><h3 id=\"gd-title\"></h3><div id=\"gd-body\"></div></div><div class=\"dialog-actions\"><button class=\"btn btn-secondary\" id=\"gd-cancel\">Cancel</button><button class=\"btn btn-primary\" id=\"gd-ok\">Confirm</button></div>';\n document.body.appendChild(d);\n }\n document.getElementById(\"gd-title\").textContent = title;\n document.getElementById(\"gd-body\").innerHTML = bodyHtml;\n var okBtn = document.getElementById(\"gd-ok\");\n okBtn.textContent = confirmLabel || \"Confirm\";\n okBtn.className = (confirmLabel === \"Delete\") ? \"btn btn-danger\" : \"btn btn-primary\";\n okBtn.onclick = function() { onConfirm(); };\n document.getElementById(\"gd-cancel\").onclick = function() { d.close(); };\n d.showModal();\n}\n\nfunction confirmDialog(opts) {\n return new Promise(function(resolve) {\n var d = document.getElementById(\"confirm-dialog\");\n if (!d) {\n d = document.createElement(\"dialog\");\n d.id = \"confirm-dialog\";\n d.innerHTML = '<div class=\"dialog-content\"><h3 id=\"confirm-title\"></h3><p id=\"confirm-message\" class=\"dialog-desc\"></p></div><div class=\"dialog-actions\"><button class=\"btn btn-secondary\" id=\"confirm-cancel\">Cancel</button><button class=\"btn\" id=\"confirm-ok\">Confirm</button></div>';\n document.body.appendChild(d);\n }\n document.getElementById(\"confirm-title\").textContent = opts.title || \"Confirm\";\n document.getElementById(\"confirm-message\").textContent = opts.message || \"\";\n var okBtn = document.getElementById(\"confirm-ok\");\n okBtn.textContent = opts.confirmLabel || \"Confirm\";\n okBtn.className = opts.danger ? \"btn btn-danger\" : \"btn\";\n document.getElementById(\"confirm-cancel\").textContent = opts.cancelLabel || \"Cancel\";\n\n function cleanup() {\n okBtn.onclick = null;\n document.getElementById(\"confirm-cancel\").onclick = null;\n d.close();\n }\n okBtn.onclick = function() { cleanup(); resolve(true); };\n document.getElementById(\"confirm-cancel\").onclick = function() { cleanup(); resolve(false); };\n d.onclose = function() { resolve(false); };\n d.showModal();\n });\n}\n\nfunction promptDialog(opts) {\n return new Promise(function(resolve) {\n var d = document.getElementById(\"prompt-dialog\");\n if (!d) {\n d = document.createElement(\"dialog\");\n d.id = \"prompt-dialog\";\n d.innerHTML = '<div class=\"dialog-content\"><h3 id=\"prompt-title\"></h3><p id=\"prompt-message\" class=\"dialog-desc\"></p><input class=\"input\" id=\"prompt-input\" style=\"margin-top:12px;\" /></div><div class=\"dialog-actions\"><button class=\"btn btn-secondary\" id=\"prompt-cancel\">Cancel</button><button class=\"btn\" id=\"prompt-ok\">Confirm</button></div>';\n document.body.appendChild(d);\n }\n document.getElementById(\"prompt-title\").textContent = opts.title || \"\";\n document.getElementById(\"prompt-message\").textContent = opts.message || \"\";\n var input = document.getElementById(\"prompt-input\");\n input.type = opts.inputType || \"text\";\n input.placeholder = opts.placeholder || \"\";\n input.value = opts.defaultValue || \"\";\n var okBtn = document.getElementById(\"prompt-ok\");\n okBtn.textContent = opts.confirmLabel || __t(\"common.confirm\");\n\n function cleanup() { d.close(); }\n okBtn.onclick = function() { var v = input.value.trim(); cleanup(); resolve(v || null); };\n document.getElementById(\"prompt-cancel\").onclick = function() { cleanup(); resolve(null); };\n d.onclose = function() { resolve(null); };\n d.showModal();\n input.focus();\n });\n}\n\n;\n\nvar currentTab = null;\nvar tabHandlers = {};\n\nfunction registerTab(name, handler) {\n tabHandlers[name] = handler;\n}\n\nfunction switchTab(name) {\n if (currentTab === name) return;\n currentTab = name;\n\n // Update tab buttons (desktop)\n document.querySelectorAll(\"[data-tab]\").forEach(function(el) {\n el.classList.toggle(\"tab-active\", el.getAttribute(\"data-tab\") === name);\n });\n\n // Update sidebar items (mobile)\n document.querySelectorAll(\"[data-sidebar-tab]\").forEach(function(el) {\n el.classList.toggle(\"sidebar-item-active\", el.getAttribute(\"data-sidebar-tab\") === name);\n });\n\n // Update tab panels\n document.querySelectorAll(\"[data-panel]\").forEach(function(el) {\n el.classList.toggle(\"hidden\", el.getAttribute(\"data-panel\") !== name);\n });\n\n // Update hash without triggering hashchange\n if (location.hash !== \"#\" + name) {\n history.replaceState(null, \"\", \"#\" + name);\n }\n\n // Call tab handler for lazy loading\n if (tabHandlers[name]) {\n tabHandlers[name]();\n }\n}\n\nfunction initRouter(defaultTab) {\n var hash = location.hash.slice(1);\n var validTabs = Object.keys(tabHandlers);\n var initial = validTabs.indexOf(hash) >= 0 ? hash : defaultTab;\n switchTab(initial);\n\n window.addEventListener(\"hashchange\", function() {\n var h = location.hash.slice(1);\n if (tabHandlers[h]) switchTab(h);\n });\n}\n\n;\n\nvar profileData = null;\n\nasync function loadProfile() {\n document.getElementById(\"profile-card-skeleton\").style.display = \"\";\n document.getElementById(\"profile-card-content\").style.display = \"none\";\n var data = await api(\"GET\", \"/profile\");\n document.getElementById(\"profile-card-skeleton\").style.display = \"none\";\n document.getElementById(\"profile-card-content\").style.display = \"\";\n if (!data.ok) return;\n profileData = data.user;\n renderProfile(data.user);\n}\n\nfunction renderProfile(user) {\n var avatarEl = document.getElementById(\"profile-avatar\");\n if (user.avatar) {\n avatarEl.innerHTML = '<img src=\"' + escapeHtml(user.avatar) + '\" onerror=\"this.style.display=\\'none\\';this.nextSibling.style.display=\\'flex\\'\">'\n + '<span class=\"avatar-fallback\" style=\"display:none\">' + escapeHtml(initials(user.fullName)) + '</span>'\n + '<div class=\"avatar-overlay\"><span>Edit</span></div>';\n } else {\n avatarEl.innerHTML = '<span class=\"avatar-fallback\">' + escapeHtml(initials(user.fullName)) + '</span>'\n + '<div class=\"avatar-overlay\"><span>Edit</span></div>';\n }\n\n document.getElementById(\"profile-display-name\").textContent = user.fullName || __t(\"profile.unnamed\");\n\n var role = user.role || \"guest\";\n var badge = document.getElementById(\"profile-role-badge\");\n badge.textContent = __t(\"common.\" + role);\n badge.className = \"badge badge-\" + role;\n\n document.getElementById(\"profile-did\").setAttribute(\"did\", user.did);\n\n document.getElementById(\"profile-name\").value = user.fullName || \"\";\n cancelEditName();\n\n document.getElementById(\"profile-email-display\").textContent = user.email || \"—\";\n\n var emailBadge = document.getElementById(\"profile-email-badge\");\n var emailVerifyBtn = document.getElementById(\"profile-email-verify-btn\");\n if (user.email) {\n emailBadge.style.display = \"\";\n if (user.emailVerified) {\n emailBadge.textContent = __t(\"profile.emailVerified\");\n emailBadge.className = \"badge badge-verified\";\n emailVerifyBtn.style.display = \"none\";\n } else {\n emailBadge.textContent = __t(\"profile.emailUnverified\");\n emailBadge.className = \"badge badge-unverified\";\n emailVerifyBtn.style.display = \"\";\n }\n } else {\n emailBadge.style.display = \"none\";\n emailVerifyBtn.style.display = \"none\";\n }\n\n if (user.createdAt) {\n document.getElementById(\"profile-joined-cell\").style.display = \"\";\n document.getElementById(\"profile-joined-value\").textContent = absoluteTime(user.createdAt);\n }\n}\n\nfunction startEditName() {\n document.getElementById(\"profile-header-info-view\").style.display = \"none\";\n document.getElementById(\"profile-name-editor\").style.display = \"\";\n document.getElementById(\"profile-name\").focus();\n}\n\nfunction cancelEditName() {\n document.getElementById(\"profile-name-editor\").style.display = \"none\";\n document.getElementById(\"profile-header-info-view\").style.display = \"\";\n if (profileData) document.getElementById(\"profile-name\").value = profileData.fullName || \"\";\n}\n\nasync function saveProfile() {\n var body = {\n fullName: document.getElementById(\"profile-name\").value.trim(),\n };\n var data = await api(\"PUT\", \"/profile\", body);\n if (data.ok) {\n profileData = data.user;\n renderProfile(data.user);\n showToast(__t(\"profile.updated\"), \"success\");\n var headerName = document.getElementById(\"header-user-name\");\n if (headerName) headerName.textContent = data.user.fullName || truncateDid(data.user.did);\n }\n}\n\nasync function changeEmail() {\n var currentEmail = profileData ? profileData.email || \"\" : \"\";\n var newEmail = await promptDialog({\n title: __t(\"profile.changeEmailTitle\"),\n message: __t(\"profile.changeEmailMsg\"),\n placeholder: __t(\"profile.emailPlaceholder\"),\n defaultValue: currentEmail,\n inputType: \"email\",\n });\n if (!newEmail || newEmail === currentEmail) return;\n var result = await api(\"PUT\", \"/profile/email\", { email: newEmail });\n if (result.ok) {\n profileData = result.user;\n renderProfile(result.user);\n showToast(__t(\"profile.emailUpdated\"), \"success\");\n }\n}\n\nvar _lastVerifySentAt = 0;\n\nfunction verifyEmail() {\n var email = profileData ? profileData.email : \"\";\n if (!email) return;\n\n var verifyBtn = document.getElementById(\"profile-email-verify-btn\");\n verifyBtn.disabled = true;\n\n // Build or reuse dialog\n var d = document.getElementById(\"verify-email-dialog\");\n if (!d) {\n d = document.createElement(\"dialog\");\n d.id = \"verify-email-dialog\";\n d.innerHTML = '<div class=\"dialog-content\">'\n + '<h3 id=\"verify-title\"></h3>'\n + '<p id=\"verify-message\" class=\"dialog-desc\"></p>'\n + '<input class=\"input\" id=\"verify-input\" placeholder=\"000000\" style=\"margin-top:12px;\" />'\n + '</div>'\n + '<div class=\"dialog-actions\">'\n + '<button class=\"btn-text\" id=\"verify-resend\" style=\"margin-right:auto\"></button>'\n + '<button class=\"btn btn-secondary\" id=\"verify-cancel\">' + __t(\"common.cancel\") + '</button>'\n + '<button class=\"btn\" id=\"verify-ok\">' + __t(\"common.confirm\") + '</button>'\n + '</div>';\n document.body.appendChild(d);\n }\n\n var titleEl = document.getElementById(\"verify-title\");\n var msgEl = document.getElementById(\"verify-message\");\n var inputEl = document.getElementById(\"verify-input\");\n var okBtn = document.getElementById(\"verify-ok\");\n var cancelBtn = document.getElementById(\"verify-cancel\");\n var resendBtn = document.getElementById(\"verify-resend\");\n var countdownTimer = null;\n\n titleEl.textContent = __t(\"profile.verifyCode\");\n inputEl.value = \"\";\n\n function cleanup() {\n if (countdownTimer) clearInterval(countdownTimer);\n verifyBtn.disabled = false;\n d.close();\n }\n\n function remainingCooldown() {\n if (!_lastVerifySentAt) return 0;\n return Math.max(0, Math.ceil((60000 - (Date.now() - _lastVerifySentAt)) / 1000));\n }\n\n function syncResendBtn() {\n var remain = remainingCooldown();\n resendBtn.style.display = \"\";\n if (remain > 0) {\n resendBtn.disabled = true;\n resendBtn.textContent = __t(\"profile.emailResendCountdown\", { seconds: remain });\n } else {\n resendBtn.disabled = false;\n resendBtn.textContent = __t(\"profile.emailResendBtn\");\n }\n }\n\n function startCountdown() {\n if (countdownTimer) clearInterval(countdownTimer);\n syncResendBtn();\n countdownTimer = setInterval(function() {\n if (remainingCooldown() <= 0) {\n clearInterval(countdownTimer);\n countdownTimer = null;\n }\n syncResendBtn();\n }, 1000);\n }\n\n function sendCode() {\n msgEl.textContent = __t(\"profile.emailSending\");\n inputEl.disabled = true;\n okBtn.disabled = true;\n resendBtn.style.display = \"none\";\n if (countdownTimer) { clearInterval(countdownTimer); countdownTimer = null; }\n\n api(\"POST\", \"/profile/email/verify\").then(function(sendResult) {\n if (!sendResult.ok) {\n msgEl.textContent = sendResult.error || __t(\"profile.emailSendFailed\");\n // If there's remaining cooldown, show countdown; otherwise show resend\n if (remainingCooldown() > 0) {\n startCountdown();\n } else {\n resendBtn.style.display = \"\";\n resendBtn.disabled = false;\n resendBtn.textContent = __t(\"profile.emailResendBtn\");\n }\n return;\n }\n _lastVerifySentAt = Date.now();\n msgEl.textContent = __t(\"profile.verifyCodeMsg\", { email: email });\n inputEl.disabled = false;\n okBtn.disabled = false;\n inputEl.focus();\n startCountdown();\n });\n }\n\n cancelBtn.onclick = function() { cleanup(); };\n d.onclose = function() {\n if (countdownTimer) clearInterval(countdownTimer);\n verifyBtn.disabled = false;\n };\n\n okBtn.onclick = function() {\n var code = inputEl.value.trim();\n if (!code) return;\n okBtn.disabled = true;\n api(\"PUT\", \"/profile/email/verify\", { code: code }).then(function(result) {\n if (result.ok) {\n cleanup();\n profileData = result.user;\n renderProfile(result.user);\n showToast(__t(\"profile.emailVerifiedSuccess\"), \"success\");\n } else {\n okBtn.disabled = false;\n msgEl.textContent = result.error || __t(\"profile.emailVerifyFailed\");\n }\n });\n };\n\n resendBtn.onclick = function() { sendCode(); };\n\n d.showModal();\n\n // If still in cooldown from a previous send, skip sending and show code input directly\n var remain = remainingCooldown();\n if (remain > 0) {\n msgEl.textContent = __t(\"profile.verifyCodeMsg\", { email: email });\n inputEl.disabled = false;\n okBtn.disabled = false;\n inputEl.focus();\n startCountdown();\n } else {\n sendCode();\n }\n}\n\nfunction triggerAvatarUpload() {\n document.getElementById(\"avatar-file-input\").click();\n}\n\nfunction resizeImage(file, maxSize) {\n return new Promise(function(resolve, reject) {\n var img = new Image();\n img.onload = function() {\n var canvas = document.createElement(\"canvas\");\n canvas.width = canvas.height = maxSize;\n var ctx = canvas.getContext(\"2d\");\n var s = Math.min(img.width, img.height);\n var sx = (img.width - s) / 2;\n var sy = (img.height - s) / 2;\n ctx.drawImage(img, sx, sy, s, s, 0, 0, maxSize, maxSize);\n resolve(canvas.toDataURL(\"image/jpeg\", 0.85));\n URL.revokeObjectURL(img.src);\n };\n img.onerror = function() {\n URL.revokeObjectURL(img.src);\n reject(new Error(\"Failed to load image\"));\n };\n img.src = URL.createObjectURL(file);\n });\n}\n\nasync function handleAvatarFile(input) {\n var file = input.files[0];\n if (!file) return;\n if (file.size > 10 * 1024 * 1024) {\n showToast(\"Image must be under 10MB\", \"error\");\n input.value = \"\";\n return;\n }\n try {\n var base64 = await resizeImage(file, 200);\n var data = await api(\"PUT\", \"/profile/avatar\", { avatar: base64 });\n if (data.ok) {\n showToast(\"Avatar updated\", \"success\");\n loadProfile();\n }\n } catch (e) {\n showToast(\"Failed to process image\", \"error\");\n }\n input.value = \"\";\n}\n\nasync function removeAvatar() {\n var ok = await confirmDialog({ title: \"Remove Avatar\", message: \"Remove your avatar? Your profile will show a default avatar.\", confirmLabel: \"Remove\" });\n if (!ok) return;\n var data = await api(\"DELETE\", \"/profile/avatar\");\n if (data.ok) {\n showToast(\"Avatar removed\", \"success\");\n loadProfile();\n }\n}\n\nregisterTab(\"profile\", function() {\n loadProfile();\n if (typeof loadConnectedAccounts === \"function\") loadConnectedAccounts();\n});\n\n;\n\nvar __connectedAccounts = [];\nvar __oauthProviders = [];\nvar __providerIcons = {\"passkey\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"18\\\" height=\\\"18\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\"><path fill=\\\"currentColor\\\" d=\\\"M3 20v-2.8q0-.85.438-1.562T4.6 14.55q1.55-.775 3.15-1.162T11 13q.5 0 1 .038t1 .112q-.1 1.45.525 2.738T15.35 18v2zm16 3l-1.5-1.5v-4.65q-1.1-.325-1.8-1.237T15 13.5q0-1.45 1.025-2.475T18.5 10t2.475 1.025T22 13.5q0 1.125-.638 2t-1.612 1.25L21 18l-1.5 1.5L21 21zm-8-11q-1.65 0-2.825-1.175T7 8t1.175-2.825T11 4t2.825 1.175T15 8t-1.175 2.825T11 12m7.5 2q.425 0 .713-.288T19.5 13t-.288-.712T18.5 12t-.712.288T17.5 13t.288.713t.712.287\\\"/></svg>\",\"wallet\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"18\\\" height=\\\"18\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\"><rect x=\\\"2\\\" y=\\\"4\\\" width=\\\"20\\\" height=\\\"16\\\" rx=\\\"3\\\" fill=\\\"#4598FA\\\"/><rect x=\\\"3\\\" y=\\\"6\\\" width=\\\"18\\\" height=\\\"12\\\" rx=\\\"2\\\" fill=\\\"white\\\" opacity=\\\"0.9\\\"/><rect x=\\\"0\\\" y=\\\"14\\\" width=\\\"24\\\" height=\\\"10\\\" rx=\\\"0\\\" fill=\\\"url(#dw_g)\\\"/><path d=\\\"M5.5 9.5h4.5c.3 0 .5.2.5.5v2.5c0 .3-.2.5-.5.5H5.5c-.3 0-.5-.2-.5-.5V10c0-.3.2-.5.5-.5z\\\" fill=\\\"#4598FA\\\"/><defs><linearGradient id=\\\"dw_g\\\" x1=\\\"12\\\" y1=\\\"14\\\" x2=\\\"12\\\" y2=\\\"24\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop stop-color=\\\"#77B2F6\\\"/><stop offset=\\\"1\\\" stop-color=\\\"#4598FA\\\"/></linearGradient></defs></svg>\",\"did-connect\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"18\\\" height=\\\"18\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\"><rect x=\\\"2\\\" y=\\\"4\\\" width=\\\"20\\\" height=\\\"16\\\" rx=\\\"3\\\" fill=\\\"#4598FA\\\"/><rect x=\\\"3\\\" y=\\\"6\\\" width=\\\"18\\\" height=\\\"12\\\" rx=\\\"2\\\" fill=\\\"white\\\" opacity=\\\"0.9\\\"/><rect x=\\\"0\\\" y=\\\"14\\\" width=\\\"24\\\" height=\\\"10\\\" rx=\\\"0\\\" fill=\\\"url(#dw_g)\\\"/><path d=\\\"M5.5 9.5h4.5c.3 0 .5.2.5.5v2.5c0 .3-.2.5-.5.5H5.5c-.3 0-.5-.2-.5-.5V10c0-.3.2-.5.5-.5z\\\" fill=\\\"#4598FA\\\"/><defs><linearGradient id=\\\"dw_g\\\" x1=\\\"12\\\" y1=\\\"14\\\" x2=\\\"12\\\" y2=\\\"24\\\" gradientUnits=\\\"userSpaceOnUse\\\"><stop stop-color=\\\"#77B2F6\\\"/><stop offset=\\\"1\\\" stop-color=\\\"#4598FA\\\"/></linearGradient></defs></svg>\",\"email\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"18\\\" height=\\\"18\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\"><path fill=\\\"currentColor\\\" d=\\\"M22 7.535V17a3 3 0 0 1-2.824 2.995L19 20H5a3 3 0 0 1-2.995-2.824L2 17V7.535l9.445 6.297l.116.066a1 1 0 0 0 .878 0l.116-.066z\\\"/><path fill=\\\"currentColor\\\" d=\\\"M19 4c1.08 0 2.027.57 2.555 1.427L12 11.797l-9.555-6.37a2.999 2.999 0 0 1 2.354-1.42L5 4z\\\"/></svg>\",\"google\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"18\\\" height=\\\"18\\\" viewBox=\\\"0 0 256 262\\\"><path fill=\\\"#4285F4\\\" d=\\\"M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622l38.755 30.023l2.685.268c24.659-22.774 38.875-56.282 38.875-96.027\\\"/><path fill=\\\"#34A853\\\" d=\\\"M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055c-34.523 0-63.824-22.773-74.269-54.25l-1.531.13l-40.298 31.187l-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1\\\"/><path fill=\\\"#FBBC05\\\" d=\\\"M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82c0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602z\\\"/><path fill=\\\"#EB4335\\\" d=\\\"M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0C79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251\\\"/></svg>\",\"github\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"18\\\" height=\\\"18\\\" viewBox=\\\"0 0 256 250\\\" fill=\\\"currentColor\\\"><path d=\\\"M128.001 0C57.317 0 0 57.307 0 128.001c0 56.554 36.676 104.535 87.535 121.46c6.397 1.185 8.746-2.777 8.746-6.158c0-3.052-.12-13.135-.174-23.83c-35.61 7.742-43.124-15.103-43.124-15.103c-5.823-14.795-14.213-18.73-14.213-18.73c-11.613-7.944.876-7.78.876-7.78c12.853.902 19.621 13.19 19.621 13.19c11.417 19.568 29.945 13.911 37.249 10.64c1.149-8.272 4.466-13.92 8.127-17.116c-28.431-3.236-58.318-14.212-58.318-63.258c0-13.975 5-25.394 13.188-34.358c-1.329-3.224-5.71-16.242 1.24-33.874c0 0 10.749-3.44 35.21 13.121c10.21-2.836 21.16-4.258 32.038-4.307c10.878.049 21.837 1.47 32.066 4.307c24.431-16.56 35.165-13.12 35.165-13.12c6.967 17.63 2.584 30.65 1.255 33.873c8.207 8.964 13.173 20.383 13.173 34.358c0 49.163-29.944 59.988-58.447 63.157c4.591 3.972 8.682 11.762 8.682 23.704c0 17.126-.148 30.91-.148 35.126c0 3.407 2.304 7.398 8.792 6.14C219.37 232.5 256 184.537 256 128.002C256 57.307 198.691 0 128.001 0\\\"/></svg>\",\"apple\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"18\\\" height=\\\"18\\\" viewBox=\\\"0 0 256 315\\\" fill=\\\"currentColor\\\"><path d=\\\"M213.803 167.03c.442 47.58 41.74 63.413 42.197 63.615c-.35 1.116-6.599 22.563-21.757 44.716c-13.104 19.153-26.705 38.235-48.13 38.63c-21.05.388-27.82-12.483-51.888-12.483c-24.061 0-31.582 12.088-51.51 12.871c-20.68.783-36.428-20.71-49.64-39.793c-27-39.033-47.633-110.3-19.928-158.406c13.763-23.89 38.36-39.017 65.056-39.405c20.307-.388 39.475 13.675 51.889 13.675c12.406 0 35.699-16.895 60.186-14.414c10.25.427 39.026 4.14 57.503 31.186c-1.49.923-34.335 20.044-33.978 59.808M174.24 50.199c10.98-13.29 18.369-31.79 16.353-50.199c-15.826.636-34.962 10.546-46.314 23.828c-10.173 11.763-19.082 30.589-16.678 48.633c17.64 1.365 35.66-8.964 46.639-22.262\\\"/></svg>\",\"twitter\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"18\\\" height=\\\"18\\\" viewBox=\\\"0 0 256 256\\\" fill=\\\"currentColor\\\"><path d=\\\"M149.079 108.399L242.33 0h-22.098l-80.97 94.12L74.59 0H0l97.796 142.328L0 256h22.1l85.507-99.395L175.905 256h74.59L149.073 108.399zM118.81 143.58l-9.909-14.172l-78.84-112.773h33.943l63.625 91.011l9.909 14.173l82.705 118.3H186.3l-67.49-96.533z\\\"/></svg>\",\"auth0\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"18\\\" height=\\\"18\\\" viewBox=\\\"0 0 256 256\\\"><path fill=\\\"#EB5424\\\" d=\\\"M203.97 200.38L167.47 88l-39.5 112.38h75.98zm0-144.76L167.47 168l75.97-26.16l19.76-60.82c6.64-20.4-1.94-42.72-21.18-52.76l-38.06 27.36zM52.03 55.62L88.54 168l39.5-112.38H52.02zM52.03 200.38L88.54 88L12.56 114.16l-5.92 18.2c-6.64 20.4 1.94 42.72 21.18 52.76l24.2-17.38l.01.01v-.01l-.01.01.01-.37z\\\"/></svg>\"};\nvar __hiddenProviders = [\"facebook\",\"auth0-legacy\"];\n\nasync function loadConnectedAccounts() {\n var card = document.getElementById(\"connected-accounts-card\");\n var list = document.getElementById(\"connected-accounts-list\");\n\n // Show card with skeleton chips while loading\n card.style.display = \"\";\n list.innerHTML = '<div style=\"padding:12px 20px 16px;display:flex;gap:8px;flex-wrap:wrap;\">'\n + '<span class=\"skel\" style=\"width:80px;height:32px;border-radius:16px;\"></span>'.repeat(4)\n + '</div>';\n\n // Load session data and OAuth configs in parallel\n var results = await Promise.all([\n api(\"GET\", \"/../did/session\"),\n api(\"GET\", \"/../oauth/configs\").catch(function() { return { providers: [] }; }),\n ]);\n\n var session = results[0];\n var oauthData = results[1];\n\n var userObj = session.user || session;\n if (!session.authenticated || !userObj.connectedAccounts) {\n card.style.display = \"none\";\n return;\n }\n\n __connectedAccounts = userObj.connectedAccounts;\n __oauthProviders = (oauthData.providers || []).map(function(p) { return p.name || p; })\n .filter(function(p) { return __hiddenProviders.indexOf(p) === -1; });\n\n card.style.display = \"\";\n renderConnectedAccounts();\n}\n\nfunction renderConnectedAccounts() {\n var list = document.getElementById(\"connected-accounts-list\");\n var html = \"\";\n\n // Partition accounts by type\n var walletAccounts = [];\n var passkeyAccounts = [];\n var otherAccounts = [];\n for (var i = 0; i < __connectedAccounts.length; i++) {\n var a = __connectedAccounts[i];\n if (__hiddenProviders.indexOf(a.provider) !== -1) continue;\n if (a.provider === \"wallet\" || a.provider === \"did-connect\") {\n walletAccounts.push(a);\n } else if (a.provider === \"passkey\") {\n passkeyAccounts.push(a);\n } else {\n otherAccounts.push(a);\n }\n }\n // Sort non-wallet accounts: main first\n otherAccounts.sort(function(x, y) {\n if (x.isMain && !y.isMain) return -1;\n if (!x.isMain && y.isMain) return 1;\n return 0;\n });\n\n // --- Section 1: DID Wallet (always first) ---\n html += '<div class=\"ca-section\">';\n html += '<div class=\"ca-section-label\">DID Wallet</div>';\n if (walletAccounts.length > 0) {\n html += '<div class=\"ca-bound-grid\">';\n for (var w = 0; w < walletAccounts.length; w++) {\n html += renderBoundCard(walletAccounts[w]);\n }\n html += '</div>';\n // Already has a wallet — no connect button (one wallet per user)\n } else {\n html += '<button class=\"ca-passkey-add\" onclick=\"connectWallet()\">';\n html += '<span class=\"ca-chip-add-icon\">+</span>';\n html += '<span class=\"ca-chip-icon\">' + getProviderIconSvg(\"did-connect\") + '</span>';\n html += __t(\"profile.connectWallet\");\n html += '</button>';\n }\n html += '</div>';\n\n // --- Section 2: Other login methods ---\n if (otherAccounts.length > 0) {\n html += '<div class=\"ca-section\">';\n html += '<div class=\"ca-section-label\">' + __t(\"profile.loginMethods\") + '</div>';\n html += '<div class=\"ca-bound-grid\">';\n for (var b = 0; b < otherAccounts.length; b++) {\n html += renderBoundCard(otherAccounts[b]);\n }\n html += '</div></div>';\n }\n\n // --- Section 3: Passkeys (large cards + add chip) ---\n html += '<div class=\"ca-section\">';\n html += '<div class=\"ca-section-label\">' + __t(\"profile.passkeys\") + '</div>';\n if (passkeyAccounts.length > 0) {\n html += '<div class=\"ca-bound-grid\">';\n for (var pk = 0; pk < passkeyAccounts.length; pk++) {\n html += renderBoundCard(passkeyAccounts[pk]);\n }\n html += '</div>';\n }\n html += '<div style=\"padding:' + (passkeyAccounts.length > 0 ? '8' : '0') + 'px 0 0\">';\n html += '<button class=\"ca-passkey-add\" onclick=\"addPasskey()\">';\n html += '<span class=\"ca-chip-add-icon\">+</span>';\n html += '<span class=\"ca-chip-icon\">' + getProviderIconSvg(\"passkey\") + '</span>';\n html += __t(\"profile.addPasskey\");\n html += '</button>';\n html += '</div></div>';\n\n // --- Section 4: Available to connect (unbound OAuth, at the bottom) ---\n var boundProviders = {};\n for (var j = 0; j < __connectedAccounts.length; j++) {\n boundProviders[__connectedAccounts[j].provider] = true;\n }\n var unboundOAuth = __oauthProviders.filter(function(p) { return !boundProviders[p]; });\n if (unboundOAuth.length > 0) {\n html += '<div class=\"ca-section\">';\n html += '<div class=\"ca-section-label\">' + __t(\"profile.availableToConnect\") + '</div>';\n html += '<div class=\"ca-chips\">';\n for (var u = 0; u < unboundOAuth.length; u++) {\n var p = unboundOAuth[u];\n html += '<div class=\"ca-chip ca-chip-unbound\" onclick=\"connectOAuth(\\'' + escapeHtml(p) + '\\')\" title=\"' + __t(\"profile.connect\") + \" \" + escapeHtml(getProviderLabel(p)) + '\">';\n html += '<span class=\"ca-chip-add-icon\">+</span>';\n html += '<span class=\"ca-chip-icon\">' + getProviderIconSvg(p) + '</span>';\n html += '<span class=\"ca-chip-name\">' + escapeHtml(getProviderLabel(p)) + '</span>';\n html += '</div>';\n }\n html += '</div></div>';\n }\n\n list.innerHTML = html;\n}\n\nvar __copyIcon = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\"/><path d=\"M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1\"/></svg>';\n\nfunction renderBoundCard(a) {\n var icon = getProviderIconSvg(a.provider);\n var label = getProviderLabel(a.provider);\n var isPasskey = a.provider === \"passkey\";\n\n var html = '<div class=\"ca-bound-card\">';\n\n // Top row: icon + name/detail + action buttons (top-right)\n html += '<div class=\"ca-bound-top\">';\n if (isPasskey) {\n html += '<div class=\"ca-bound-icon ca-bound-icon-lg\">' + icon + '</div>';\n } else {\n html += '<div class=\"ca-bound-icon\">' + icon + '</div>';\n }\n html += '<div class=\"ca-bound-top-body\">';\n html += '<div class=\"ca-bound-header\">';\n html += '<span class=\"ca-bound-name\">' + escapeHtml(label) + '</span>';\n if (a.isMain) {\n html += '<span class=\"ca-chip-badge\">' + __t(\"profile.mainAccount\") + '</span>';\n } else if (a.provider === \"wallet\" || a.provider === \"did-connect\") {\n html += '<span class=\"ca-chip-badge\">' + __t(\"profile.walletAccount\") + '</span>';\n }\n html += '</div>';\n var info = getAccountUserInfo(a);\n if (info) {\n html += '<div class=\"ca-bound-detail\">' + info + '</div>';\n }\n html += '</div>';\n\n // Action buttons at top-right\n if (!a.isMain && a.provider !== \"wallet\" && a.provider !== \"did-connect\") {\n html += '<div class=\"ca-bound-actions\">';\n if (isPasskey) {\n html += '<button class=\"btn-text\" onclick=\"openRenamePasskeyDialog(\\'' + escapeHtml(a.did) + '\\', \\'' + escapeHtml(getPasskeyName(a)) + '\\')\">' + __t(\"profile.renamePasskey\") + '</button>';\n html += '<button class=\"btn-text\" style=\"color:var(--red-text)\" onclick=\"removePasskey(\\'' + escapeHtml(a.did) + '\\')\">' + __t(\"profile.remove\") + '</button>';\n } else {\n html += '<button class=\"btn-text\" style=\"color:var(--red-text)\" onclick=\"unbindOAuth(\\'' + escapeHtml(a.provider) + '\\')\">' + __t(\"profile.remove\") + '</button>';\n }\n html += '</div>';\n }\n html += '</div>';\n\n // DID field with copy\n if (a.did) {\n html += '<div class=\"ca-bound-field\">';\n html += '<span class=\"ca-bound-field-label\">DID</span>';\n html += '<did-address did=\"' + escapeHtml(a.did) + '\"></did-address>';\n html += '</div>';\n }\n\n html += '</div>';\n return html;\n}\n\nfunction getPasskeyName(a) {\n return (a.userInfo && a.userInfo.name) ? a.userInfo.name : \"Passkey\";\n}\n\nfunction getAccountUserInfo(a) {\n var parts = [];\n if (a.userInfo && a.userInfo.name) parts.push(escapeHtml(a.userInfo.name));\n if (a.userInfo && a.userInfo.email) parts.push(escapeHtml(a.userInfo.email));\n if (parts.length > 0) return parts.join(' · ');\n if (a.provider === \"passkey\") {\n var name = a.userInfo && a.userInfo.name ? a.userInfo.name : null;\n return name ? escapeHtml(name) : \"\\u2022\\u2022\\u2022\\u2022\" + (a.id ? a.id.slice(-4) : \"\");\n }\n return \"\";\n}\n\nfunction getProviderIconSvg(provider) {\n var svg = __providerIcons[provider];\n if (svg) return svg;\n return '<span style=\"display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:4px;background:var(--bg-secondary);font-size:11px;font-weight:600\">' + provider.charAt(0).toUpperCase() + '</span>';\n}\n\nfunction getProviderLabel(provider) {\n var labels = { passkey: \"Passkey\", google: \"Google\", github: \"GitHub\", apple: \"Apple\", twitter: \"Twitter\", wallet: \"DID Wallet\", \"did-connect\": \"DID Wallet\", email: \"Email\" };\n return labels[provider] || (provider.charAt(0).toUpperCase() + provider.slice(1));\n}\n\nfunction connectOAuth(provider) {\n var returnUrl = encodeURIComponent(window.location.href);\n var url = window.__BASE_PATH + \"/../oauth/\" + provider + \"/login?returnUrl=\" + returnUrl;\n var redirectUri = location.origin + \"/.well-known/service/oauth/callback/\" + provider;\n var w = 500, h = 600;\n var left = (screen.width - w) / 2, top = (screen.height - h) / 2;\n var popup = window.open(url, \"oauth_bind\", \"width=\" + w + \",height=\" + h + \",left=\" + left + \",top=\" + top);\n if (!popup) {\n showToast(\"Popup blocked — please allow popups and try again\", \"error\");\n return;\n }\n\n // Listen for postMessage from OAuth callback page\n function onMessage(event) {\n if (!event.data || event.data.type !== \"authorization_response\") return;\n window.removeEventListener(\"message\", onMessage);\n clearInterval(closeTimer);\n\n var resp = event.data.response;\n if (!resp || !resp.code) {\n showToast(\"OAuth failed — no authorization code received\", \"error\");\n try { popup.close(); } catch(e) {}\n return;\n }\n\n // Call bind API with the authorization code\n api(\"POST\", \"/../oauth/bind\", {\n provider: provider,\n code: resp.code,\n redirectUri: redirectUri,\n }).then(function(data) {\n if (data.ok) {\n showToast(__t(\"profile.accountConnected\"), \"success\");\n loadConnectedAccounts();\n }\n }).catch(function() {}).finally(function() {\n try { popup.close(); } catch(e) {}\n });\n }\n window.addEventListener(\"message\", onMessage);\n\n // Fallback: if popup closes without postMessage (COOP / manual close)\n var closeTimer = setInterval(function() {\n if (popup.closed) {\n clearInterval(closeTimer);\n window.removeEventListener(\"message\", onMessage);\n // Reload in case server-side exchange fallback was used\n loadConnectedAccounts();\n }\n }, 500);\n}\n\nasync function unbindOAuth(provider) {\n var ok = await confirmDialog({\n title: __t(\"profile.removeConfirmTitle\"),\n message: __t(\"profile.removeConfirmMsg\"),\n confirmLabel: __t(\"profile.remove\"),\n });\n if (!ok) return;\n var data = await api(\"POST\", \"/../oauth/unbind\", { provider: provider });\n if (data.ok) {\n showToast(__t(\"profile.accountRemoved\"), \"success\");\n loadConnectedAccounts();\n }\n}\n\nasync function addPasskey() {\n try {\n // 1. Get registration options. The server flattens the WebAuthn options to\n // the top level (challenge/user/rp alongside challengeId), matching the\n // login flow; tolerate a nested { registration } shape too.\n var opts = await api(\"POST\", \"/../passkey/connect/register\", {});\n var publicKey = opts.registration || opts;\n if (!opts.challengeId || !publicKey || !publicKey.challenge) {\n showToast(\"Failed to get registration options\", \"error\");\n return;\n }\n\n // 2. Create credential via WebAuthn\n // Decode challenge from base64url\n publicKey.challenge = base64urlToBuffer(publicKey.challenge);\n publicKey.user.id = base64urlToBuffer(publicKey.user.id);\n if (publicKey.excludeCredentials) {\n for (var i = 0; i < publicKey.excludeCredentials.length; i++) {\n publicKey.excludeCredentials[i].id = base64urlToBuffer(publicKey.excludeCredentials[i].id);\n }\n }\n\n var credential = await navigator.credentials.create({ publicKey: publicKey });\n if (!credential) return;\n\n // 3. Serialize credential for server\n var response = credential.response;\n var credentialData = {\n id: credential.id,\n rawId: bufferToBase64url(credential.rawId),\n type: credential.type,\n response: {\n attestationObject: bufferToBase64url(response.attestationObject),\n clientDataJSON: bufferToBase64url(response.clientDataJSON),\n transports: response.getTransports ? response.getTransports() : [],\n },\n };\n\n // 4. Verify with server\n var result = await api(\"POST\", \"/../passkey/connect/verify\", {\n challengeId: opts.challengeId,\n credential: credentialData,\n });\n\n if (result.ok) {\n showToast(__t(\"profile.passkeyAdded\"), \"success\");\n loadConnectedAccounts();\n loadProfile(); // refresh passkey count\n }\n } catch (e) {\n if (e.name !== \"NotAllowedError\") {\n showToast(e.message || \"Failed to add passkey\", \"error\");\n }\n }\n}\n\nasync function removePasskey(passkeyDid) {\n var ok = await confirmDialog({\n title: __t(\"profile.removeConfirmTitle\"),\n message: __t(\"profile.removePasskeyConfirmMsg\"),\n confirmLabel: __t(\"profile.remove\"),\n });\n if (!ok) return;\n var data = await api(\"POST\", \"/../passkey/disconnect\", { did: passkeyDid });\n if (data.ok) {\n showToast(__t(\"profile.passkeyRemoved\"), \"success\");\n loadConnectedAccounts();\n loadProfile(); // refresh passkey count\n }\n}\n\nasync function openRenamePasskeyDialog(passkeyDid, currentName) {\n var result = await promptDialog({\n title: __t(\"profile.renamePasskeyTitle\"),\n message: __t(\"profile.passkeyNameLabel\"),\n placeholder: __t(\"profile.passkeyNamePlaceholder\"),\n defaultValue: currentName || \"\",\n confirmLabel: __t(\"common.save\"),\n });\n if (result === null || result === undefined) return;\n var name = result.trim();\n if (!name) return;\n var data = await api(\"POST\", \"/../passkey/rename\", { did: passkeyDid, name: name });\n if (data.ok) {\n showToast(__t(\"profile.passkeyRenamed\"), \"success\");\n loadConnectedAccounts();\n }\n}\n\n// ─── DID Wallet open helpers (mirrors LoginPage.openInWallet) ─────────\nvar __WEB_WALLET_URL = \"https://web.abtwallet.io/\";\n\nfunction __isMobile() {\n return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);\n}\n\nfunction __getWalletExtension() {\n return window.ABT_DEV || window.ABT || null;\n}\n\n/**\n * Open the DID auth URL in the wallet — platform-aware:\n * Mobile → navigate to abt:// deep link\n * Extension → call extension.open({ action:'login', url, appInfo, memberAppInfo })\n * Desktop → open web wallet popup\n *\n * action must be \"login\" — the extension only recognises \"login\", not \"bind\".\n * The bind vs login distinction lives in the URL path (/bind/auth vs /login/auth).\n */\nfunction __openInWallet(url, appInfo, memberAppInfo) {\n var extension = __getWalletExtension();\n if (__isMobile()) {\n window.location.href = url.replace(/^https?:\\/\\//, \"abt://\");\n } else if (extension && typeof extension.open === \"function\") {\n extension.open({\n action: \"login\",\n locale: document.documentElement.lang || \"en\",\n url: encodeURIComponent(url),\n appInfo: appInfo || {},\n memberAppInfo: memberAppInfo || {},\n });\n } else {\n var walletUrl = __WEB_WALLET_URL + \"?action=requestAuth&url=\" + encodeURIComponent(url);\n var w = 414, h = 736;\n var left = window.screenX + window.innerWidth - w, top = window.screenY;\n window.open(walletUrl, \"did-wallet:bind\", \"left=\" + left + \",top=\" + top + \",width=\" + w + \",height=\" + h + \",resizable,scrollbars=yes,status=1,popup\");\n }\n}\n\n// ─── DID Wallet Connect (bind) ──────────────────────────────────────\nasync function connectWallet() {\n // 1. Create bind session\n var tokenData;\n try {\n tokenData = await api(\"GET\", \"/../did/bind/token?provider=wallet\");\n } catch(e) {\n showToast(\"Failed to create bind session\", \"error\");\n return;\n }\n if (!tokenData || !tokenData.token) {\n showToast(\"Failed to create bind session\", \"error\");\n return;\n }\n\n var token = tokenData.token;\n var connectUrl = tokenData.url || \"\";\n var appInfo = tokenData.appInfo || {};\n var memberAppInfo = tokenData.memberAppInfo || {};\n\n // 2. Build/reset dialog — mirrors login-page QR view structure\n var d = document.getElementById(\"wallet-bind-dialog\");\n if (!d) {\n d = document.createElement(\"dialog\");\n d.id = \"wallet-bind-dialog\";\n document.body.appendChild(d);\n }\n // Re-render dialog content each time (fresh token / fresh state)\n d.innerHTML = '<div class=\"dialog-content\" style=\"text-align:center;\">'\n + '<h3 style=\"margin-bottom:4px;\">' + __t(\"profile.connectWallet\") + '</h3>'\n + '<div id=\"wallet-bind-qr\" class=\"wallet-qr-view\">'\n + '<div class=\"wallet-qr-placeholder\"><span class=\"wallet-qr-spinner\"></span></div>'\n + '</div>'\n + '<button id=\"wallet-bind-open\" class=\"btn btn-secondary wallet-deep-link-btn\" disabled>'\n + getProviderIconSvg(\"did-connect\") + ' ' + __t(\"profile.openInWallet\")\n + '</button>'\n + '<p id=\"wallet-bind-status\" class=\"wallet-bind-status\">' + __t(\"profile.scanWithWallet\") + '</p>'\n + '</div>'\n + '<div class=\"dialog-actions\">'\n + '<button class=\"btn btn-secondary\" id=\"wallet-bind-cancel\">' + __t(\"common.cancel\") + '</button>'\n + '</div>';\n\n var statusEl = document.getElementById(\"wallet-bind-status\");\n var qrEl = document.getElementById(\"wallet-bind-qr\");\n var openBtn = document.getElementById(\"wallet-bind-open\");\n var cancelBtn = document.getElementById(\"wallet-bind-cancel\");\n\n // Render QR code (replaces placeholder spinner)\n if (connectUrl && typeof __QRBundle !== \"undefined\" && __QRBundle.renderQR) {\n __QRBundle.renderQR(qrEl, connectUrl, 212);\n }\n\n // Enable open button once URL is ready\n openBtn.disabled = false;\n openBtn.onclick = function() { __openInWallet(connectUrl, appInfo, memberAppInfo); };\n\n var pollTimer = null;\n var pollCount = 0;\n var maxPolls = 120; // 2 minutes at 1.5s interval\n\n function cleanup() {\n if (pollTimer) clearInterval(pollTimer);\n pollTimer = null;\n d.close();\n }\n\n cancelBtn.onclick = function() { cleanup(); };\n d.onclose = function() { if (pollTimer) clearInterval(pollTimer); };\n\n d.showModal();\n\n // Helper: collapse QR area and show terminal status\n function showTerminalStatus(msg, isError) {\n qrEl.style.display = \"none\";\n openBtn.style.display = \"none\";\n statusEl.className = \"wallet-bind-status\" + (isError ? \" wallet-bind-error\" : \"\");\n statusEl.textContent = msg;\n }\n\n // 3. Poll for status\n pollTimer = setInterval(async function() {\n pollCount++;\n if (pollCount > maxPolls) {\n clearInterval(pollTimer);\n pollTimer = null;\n showTerminalStatus(__t(\"profile.connectTimeout\"), true);\n return;\n }\n try {\n var status = await api(\"GET\", \"/../did/bind/status?_t_=\" + encodeURIComponent(token));\n if (status.status === \"scanned\") {\n statusEl.textContent = __t(\"profile.confirmInWallet\");\n } else if (status.status === \"succeed\") {\n clearInterval(pollTimer);\n pollTimer = null;\n // Collapse QR; show \"Binding...\" while we call bind-complete\n qrEl.style.display = \"none\";\n openBtn.style.display = \"none\";\n statusEl.textContent = __t(\"profile.binding\");\n // 4. Call bind-complete\n var result = await api(\"POST\", \"/../did/connect/bind-complete\", { token: token });\n if (result.ok) {\n cleanup();\n showToast(__t(\"profile.walletConnected\"), \"success\");\n loadConnectedAccounts();\n } else {\n showTerminalStatus(result.error || \"Bind failed\", true);\n }\n } else if (status.status === \"error\") {\n clearInterval(pollTimer);\n pollTimer = null;\n showTerminalStatus(status.error || \"Error\", true);\n }\n } catch(e) { /* ignore poll errors */ }\n }, 1500);\n}\n\n// base64url helpers for WebAuthn\nfunction base64urlToBuffer(str) {\n str = str.replace(/-/g, \"+\").replace(/_/g, \"/\");\n while (str.length % 4) str += \"=\";\n var bin = atob(str);\n var buf = new Uint8Array(bin.length);\n for (var i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);\n return buf.buffer;\n}\n\nfunction bufferToBase64url(buf) {\n var bytes = new Uint8Array(buf);\n var str = \"\";\n for (var i = 0; i < bytes.length; i++) str += String.fromCharCode(bytes[i]);\n return btoa(str).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n;\n\nvar akPage = 1;\nvar akPageSize = 20;\nvar akSearchTimer = null;\n\nfunction debounceAkSearch() {\n clearTimeout(akSearchTimer);\n akSearchTimer = setTimeout(function() { loadAccessKeys(1); }, 300);\n}\n\nasync function loadAccessKeys(page) {\n akPage = page || 1;\n var search = document.getElementById(\"ak-search\").value.trim();\n var qs = \"?page=\" + akPage + \"&pageSize=\" + akPageSize;\n if (search) qs += \"&search=\" + encodeURIComponent(search);\n document.getElementById(\"ak-table-wrap\").innerHTML = skeletonTable(7, 5);\n document.getElementById(\"ak-pagination\").innerHTML = \"\";\n var data = await apiRaw(\"GET\", \"/.well-known/service/api/access-keys\" + qs);\n if (!data || data.error) {\n document.getElementById(\"ak-table-wrap\").innerHTML = '<div class=\"empty-state\"><div class=\"empty-state-title\">' + __t(\"accessKeys.loadFailed\") + '</div></div>';\n return;\n }\n renderAccessKeys(data.keys, { total: data.total, page: data.page, pageSize: data.pageSize });\n}\n\nfunction renderAccessKeys(keys, paging) {\n var wrap = document.getElementById(\"ak-table-wrap\");\n if (!keys || keys.length === 0) {\n wrap.innerHTML = '<div class=\"empty-state\"><div class=\"empty-state-icon\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4\"/></svg></div><div class=\"empty-state-title\">' + __t(\"accessKeys.noKeys\") + '</div><div class=\"empty-state-desc\">' + __t(\"accessKeys.noKeysHint\") + '</div></div>';\n document.getElementById(\"ak-pagination\").innerHTML = \"\";\n return;\n }\n\n var html = '<table class=\"table\"><thead><tr><th>' + __t(\"accessKeys.col.keyId\") + '</th><th>' + __t(\"accessKeys.col.role\") + '</th><th class=\"col-hide-sm\">' + __t(\"accessKeys.col.remark\") + '</th><th class=\"col-hide-md\">' + __t(\"accessKeys.col.createdBy\") + '</th><th>' + __t(\"accessKeys.col.expires\") + '</th><th class=\"col-hide-md\">' + __t(\"accessKeys.col.lastUsed\") + '</th><th>' + __t(\"common.actions\") + '</th></tr></thead><tbody>';\n keys.forEach(function(k) {\n var keyIdHtml = '<did-address did=\"' + escapeHtml(k.accessKeyId) + '\" compact></did-address>';\n var roleBadge = '<span class=\"badge badge-' + k.role + '\">' + __t(\"common.\" + k.role) + '</span>';\n var remark = k.remark ? escapeHtml(k.remark.length > 40 ? k.remark.slice(0, 40) + \"...\" : k.remark) : '<span style=\"color:var(--text-secondary)\">—</span>';\n var creator = k.createdByName\n ? escapeHtml(k.createdByName)\n : '<did-address did=\"' + escapeHtml(k.createdBy) + '\" compact></did-address>';\n var expiry = k.expireAt ? (new Date(k.expireAt) < new Date() ? '<span style=\"color:var(--error)\">' + __t(\"common.expired\") + '</span>' : relativeTime(k.expireAt)) : __t(\"common.never\");\n var lastUsed = k.lastUsedAt ? relativeTime(k.lastUsedAt) : __t(\"common.never\");\n var deleteLabel = k.remark ? escapeHtml(k.remark) : truncateDid(k.accessKeyId);\n\n html += '<tr>';\n html += '<td><code style=\"font-size:12px\">' + keyIdHtml + '</code></td>';\n html += '<td>' + roleBadge + '</td>';\n html += '<td class=\"col-hide-sm\">' + remark + '</td>';\n html += '<td class=\"col-hide-md\">' + creator + '</td>';\n html += '<td>' + expiry + '</td>';\n html += '<td class=\"col-hide-md\">' + lastUsed + '</td>';\n html += '<td class=\"action-cell\"><button class=\"action-trigger\" onclick=\"event.stopPropagation();toggleActionMenu(this)\">⋯</button><div class=\"action-menu\"><button class=\"action-menu-item\" onclick=\"event.stopPropagation();openEditAkDialog(\\''+k.accessKeyId+'\\')\">' + __t(\"common.edit\") + '</button><div class=\"action-menu-sep\"></div><button class=\"action-menu-item danger\" onclick=\"event.stopPropagation();confirmDeleteAk(\\''+k.accessKeyId+'\\',\\''+escapeHtml(deleteLabel)+'\\')\">' + __t(\"common.delete\") + '</button></div></td>';\n html += '</tr>';\n });\n html += '</tbody></table>';\n wrap.innerHTML = html;\n\n renderPagination(\"ak-pagination\", paging, loadAccessKeys);\n}\n\nfunction openCreateAkDialog() {\n var callerRole = __caller.role;\n var roleOptions = '<option value=\"guest\">Guest</option>';\n if (callerRole === \"member\" || callerRole === \"admin\" || callerRole === \"owner\") roleOptions += '<option value=\"member\"' + (callerRole === \"member\" ? ' selected' : '') + '>Member</option>';\n if (callerRole === \"admin\" || callerRole === \"owner\") roleOptions += '<option value=\"admin\"' + (callerRole === \"admin\" ? ' selected' : '') + '>Admin</option>';\n if (callerRole === \"owner\") roleOptions += '<option value=\"owner\">Owner</option>';\n\n var expiryOptions = '<option value=\"\">' + __t(\"accessKeys.expirations.never\") + '</option><option value=\"7d\">' + __t(\"accessKeys.expirations.days7\") + '</option><option value=\"30d\">' + __t(\"accessKeys.expirations.days30\") + '</option><option value=\"90d\">' + __t(\"accessKeys.expirations.days90\") + '</option><option value=\"custom\">' + __t(\"accessKeys.expirations.custom\") + '</option>';\n\n var html = '<div class=\"form-group\"><label class=\"form-label\">' + __t(\"accessKeys.role\") + '</label><select class=\"select\" id=\"ak-create-role\">' + roleOptions + '</select></div>';\n html += '<div class=\"form-group\"><label class=\"form-label\">' + __t(\"accessKeys.remark\") + '</label><input type=\"text\" class=\"input\" id=\"ak-create-remark\" placeholder=\"' + __t(\"accessKeys.remarkPlaceholder\") + '\" maxlength=\"200\" /></div>';\n html += '<div class=\"form-group\"><label class=\"form-label\">' + __t(\"accessKeys.expiration\") + '</label><select class=\"select\" id=\"ak-create-expiry\" onchange=\"toggleAkCustomExpiry()\">' + expiryOptions + '</select></div>';\n html += '<div class=\"form-group hidden\" id=\"ak-custom-expiry-wrap\"><label class=\"form-label\">' + __t(\"accessKeys.customDate\") + '</label><input type=\"datetime-local\" class=\"input\" id=\"ak-create-custom-expiry\" /></div>';\n\n openDialog(__t(\"accessKeys.createTitle\"), html, __t(\"common.save\"), async function() {\n var role = document.getElementById(\"ak-create-role\").value;\n var remark = document.getElementById(\"ak-create-remark\").value;\n var expirySelect = document.getElementById(\"ak-create-expiry\").value;\n var expireAt = null;\n\n if (expirySelect === \"custom\") {\n var customDate = document.getElementById(\"ak-create-custom-expiry\").value;\n if (!customDate) { showToast(__t(\"accessKeys.selectExpDate\"), \"error\"); return; }\n expireAt = new Date(customDate).toISOString();\n } else if (expirySelect) {\n var days = parseInt(expirySelect);\n expireAt = new Date(Date.now() + days * 86400000).toISOString();\n }\n\n var result = await apiRaw(\"POST\", \"/.well-known/service/api/access-keys\", { role: role, remark: remark, expireAt: expireAt });\n if (!result || result.error) {\n showToast(result ? result.error : __t(\"accessKeys.createFailed\"), \"error\");\n return;\n }\n\n closeDialog();\n showSecretDialog(result.accessKeySecret);\n loadAccessKeys(1);\n });\n}\n\nfunction toggleAkCustomExpiry() {\n var sel = document.getElementById(\"ak-create-expiry\").value;\n var wrap = document.getElementById(\"ak-custom-expiry-wrap\");\n if (sel === \"custom\") wrap.classList.remove(\"hidden\");\n else wrap.classList.add(\"hidden\");\n}\n\nfunction showSecretDialog(secret) {\n var html = '<div style=\"margin-bottom:12px;color:var(--error);font-weight:500\">' + __t(\"accessKeys.secretWarning\") + '</div>';\n var copyIconSvg = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"/><path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"/></svg>';\n var copyBtnStyle = 'position:absolute;top:4px;right:4px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;background:var(--bg-base);border:1px solid var(--border);border-radius:4px;cursor:pointer;color:var(--text-secondary);transition:color 0.15s,border-color 0.15s';\n html += '<div style=\"position:relative\"><code id=\"ak-secret-display\" style=\"display:block;padding:12px 40px 12px 12px;background:var(--bg-base);border:1px solid var(--border);border-radius:6px;font-size:13px;word-break:break-all;user-select:all;overflow-wrap:anywhere\">' + escapeHtml(secret) + '</code>';\n html += '<button id=\"ak-copy-btn\" style=\"' + copyBtnStyle + '\" onclick=\"copyAkSecret()\" title=\"' + __t(\"common.copy\") + '\">' + copyIconSvg + '</button></div>';\n var curlCmd = 'curl -H \"Authorization: Bearer ' + secret + '\" ' + location.origin + '/.well-known/service/api/did/session';\n html += '<div style=\"margin-top:12px;font-size:13px;color:var(--text-secondary)\"><strong>' + __t(\"accessKeys.usage\") + '</strong></div>';\n html += '<div style=\"position:relative;margin-top:6px\"><code id=\"ak-curl-display\" style=\"display:block;padding:12px 40px 12px 12px;background:var(--bg-base);border:1px solid var(--border);border-radius:6px;font-size:12px;word-break:break-all;overflow-wrap:anywhere;user-select:all\">' + escapeHtml(curlCmd) + '</code>';\n html += '<button id=\"ak-curl-copy-btn\" style=\"' + copyBtnStyle + '\" onclick=\"copyAkCurl()\" title=\"' + __t(\"common.copy\") + '\">' + copyIconSvg + '</button></div>';\n\n openDialog(__t(\"accessKeys.secretTitle\"), html, __t(\"accessKeys.done\"), function() {\n document.getElementById(\"ak-secret-display\").textContent = \"\";\n closeDialog();\n });\n}\n\nfunction copyAkCurl() {\n var text = document.getElementById(\"ak-curl-display\").textContent;\n var btn = document.getElementById(\"ak-curl-copy-btn\");\n var origHtml = btn.innerHTML;\n function onCopied() {\n btn.innerHTML = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M5 13l4 4L19 7\"/></svg>';\n btn.style.color = \"var(--green)\";\n setTimeout(function() { btn.innerHTML = origHtml; btn.style.color = \"\"; }, 2000);\n }\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(text).then(onCopied).catch(function() {\n fallbackCopy(text);\n onCopied();\n });\n } else {\n fallbackCopy(text);\n onCopied();\n }\n}\n\nfunction copyAkSecret() {\n var text = document.getElementById(\"ak-secret-display\").textContent;\n var btn = document.getElementById(\"ak-copy-btn\");\n var origHtml = btn.innerHTML;\n function onCopied() {\n btn.innerHTML = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M5 13l4 4L19 7\"/></svg>';\n btn.style.color = \"var(--green)\";\n setTimeout(function() { btn.innerHTML = origHtml; btn.style.color = \"\"; }, 2000);\n }\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(text).then(onCopied).catch(function() {\n fallbackCopy(text);\n onCopied();\n });\n } else {\n fallbackCopy(text);\n onCopied();\n }\n}\n\nasync function openEditAkDialog(accessKeyId) {\n var data = await apiRaw(\"GET\", \"/.well-known/service/api/access-keys/\" + encodeURIComponent(accessKeyId));\n if (!data || data.error) { showToast(__t(\"accessKeys.loadKeyFailed\"), \"error\"); return; }\n\n var html = '<div class=\"form-group\"><label class=\"form-label\">' + __t(\"accessKeys.col.keyId\") + '</label><code style=\"font-size:12px\">' + escapeHtml(data.accessKeyId) + '</code></div>';\n html += '<div class=\"form-group\"><label class=\"form-label\">' + __t(\"accessKeys.role\") + '</label><span class=\"badge badge-' + data.role + '\">' + __t(\"common.\" + data.role) + '</span></div>';\n html += '<div class=\"form-group\"><label class=\"form-label\">' + __t(\"accessKeys.remark\") + '</label><input type=\"text\" class=\"input\" id=\"ak-edit-remark\" value=\"' + escapeHtml(data.remark || \"\") + '\" maxlength=\"200\" /></div>';\n html += '<div class=\"form-group\"><label class=\"form-label\">' + __t(\"accessKeys.expiration\") + '</label><input type=\"datetime-local\" class=\"input\" id=\"ak-edit-expiry\" value=\"' + (data.expireAt ? data.expireAt.slice(0,16) : \"\") + '\" /> <label style=\"margin-top:4px;display:block;font-size:13px\"><input type=\"checkbox\" id=\"ak-edit-no-expiry\"' + (data.expireAt ? \"\" : \" checked\") + ' onchange=\"toggleAkEditExpiry()\" /> ' + __t(\"accessKeys.neverExpires\") + '</label></div>';\n\n openDialog(__t(\"accessKeys.editTitle\"), html, __t(\"common.save\"), async function() {\n var remark = document.getElementById(\"ak-edit-remark\").value;\n var noExpiry = document.getElementById(\"ak-edit-no-expiry\").checked;\n var expireAt = noExpiry ? null : (document.getElementById(\"ak-edit-expiry\").value ? new Date(document.getElementById(\"ak-edit-expiry\").value).toISOString() : undefined);\n\n var body = { remark: remark };\n if (expireAt !== undefined) body.expireAt = expireAt;\n\n var result = await apiRaw(\"PUT\", \"/.well-known/service/api/access-keys/\" + encodeURIComponent(accessKeyId), body);\n if (!result || result.error) {\n showToast(result ? result.error : __t(\"accessKeys.updateFailed\"), \"error\");\n return;\n }\n closeDialog();\n showToast(__t(\"accessKeys.keyUpdated\"), \"success\");\n loadAccessKeys(akPage);\n });\n}\n\nfunction toggleAkEditExpiry() {\n var noExpiry = document.getElementById(\"ak-edit-no-expiry\").checked;\n document.getElementById(\"ak-edit-expiry\").disabled = noExpiry;\n}\n\nfunction confirmDeleteAk(accessKeyId, label) {\n openDialog(__t(\"accessKeys.deleteTitle\"), '<p>' + __t(\"accessKeys.deleteMsg\", { label: escapeHtml(label) }) + '</p><p style=\"color:var(--error)\">' + __t(\"accessKeys.deleteWarning\") + '</p>', __t(\"common.delete\"), async function() {\n var result = await apiRaw(\"DELETE\", \"/.well-known/service/api/access-keys/\" + encodeURIComponent(accessKeyId));\n if (result && result.error) {\n showToast(result.error, \"error\");\n return;\n }\n closeDialog();\n showToast(__t(\"accessKeys.keyDeleted\"), \"success\");\n loadAccessKeys(akPage);\n });\n}\n\n// Direct API call (bypasses team handler's /team prefix)\nasync function apiRaw(method, path, body) {\n try {\n var opts = { method: method, headers: { \"Accept\": \"application/json\" } };\n if (body !== undefined) {\n opts.headers[\"Content-Type\"] = \"application/json\";\n opts.body = JSON.stringify(body);\n }\n var res = await fetch(path, opts);\n if (res.status === 204) return {};\n return await res.json();\n } catch(e) {\n return null;\n }\n}\n\nregisterTab(\"access-keys\", function() { loadAccessKeys(1); });\n","admin-extra.afcb3651.js":"\nvar membersPage = 1;\nvar membersPageSize = 20;\nvar memberSearchTimeout = null;\nvar currentMembersList = [];\nvar selectedMember = null;\nvar __membersAggregated = false;\n// Sort state: empty string = default (server decides + self-first on client).\n// Three-state click cycle: (none) → desc → asc → (none).\n// Persisted in location.search as sortBy/sortOrder so it survives refresh + share.\nvar membersSortBy = \"\";\nvar membersSortOrder = \"\";\nvar MEMBERS_SORT_VALID = [\"name\", \"role\", \"status\", \"source\", \"joined\"];\n\n/** Populate module sort state from the current URL query string. Idempotent. */\nfunction readMembersSortFromURL() {\n membersSortBy = \"\";\n membersSortOrder = \"\";\n var params = new URLSearchParams(location.search);\n var by = params.get(\"sortBy\");\n var order = params.get(\"sortOrder\");\n if (by && MEMBERS_SORT_VALID.indexOf(by) >= 0) {\n membersSortBy = by;\n membersSortOrder = (order === \"asc\" || order === \"desc\") ? order : \"desc\";\n }\n}\n\n/** Write current sort state back to location.search without reloading. */\nfunction writeMembersSortToURL() {\n var params = new URLSearchParams(location.search);\n if (membersSortBy) {\n params.set(\"sortBy\", membersSortBy);\n params.set(\"sortOrder\", membersSortOrder || \"desc\");\n } else {\n params.delete(\"sortBy\");\n params.delete(\"sortOrder\");\n }\n var qs = params.toString();\n var newUrl = location.pathname + (qs ? \"?\" + qs : \"\") + location.hash;\n history.replaceState(null, \"\", newUrl);\n}\n\n/** Cycle sort state for a column key and reload. */\nfunction toggleSort(key) {\n if (membersSortBy !== key) {\n membersSortBy = key;\n membersSortOrder = \"desc\";\n } else if (membersSortOrder === \"desc\") {\n membersSortOrder = \"asc\";\n } else {\n // Third click — back to default.\n membersSortBy = \"\";\n membersSortOrder = \"\";\n }\n writeMembersSortToURL();\n loadMembers(1);\n}\n\n/** Render a <th> with click-to-sort behavior. */\nfunction sortableTh(key, label, extraClass) {\n var isActive = membersSortBy === key;\n var arrow;\n if (isActive) {\n arrow = membersSortOrder === \"asc\"\n ? '<span class=\"sort-arrow sort-arrow-active\">\\u2191</span>'\n : '<span class=\"sort-arrow sort-arrow-active\">\\u2193</span>';\n } else {\n arrow = '<span class=\"sort-arrow\">\\u2195</span>';\n }\n var cls = \"sortable\" + (isActive ? \" sort-active\" : \"\") + (extraClass ? \" \" + extraClass : \"\");\n return '<th class=\"' + cls + '\" onclick=\"toggleSort(\\'' + key + '\\')\">' + label + arrow + '</th>';\n}\n\nfunction providerLabel(sp) {\n if (!sp) return \"\\u2014\";\n var key = \"members.providers.\" + sp;\n var val = __t(key);\n return val !== key ? val : sp;\n}\n\nfunction debouncedMemberSearch() {\n clearTimeout(memberSearchTimeout);\n memberSearchTimeout = setTimeout(function() { loadMembers(1); }, 300);\n}\n\n// Load registered instances for the filter dropdown (system admin only)\nvar __instanceNames = {};\nvar __instanceFilterInitialized = false;\nasync function initInstanceFilter() {\n if (__instanceFilterInitialized) return;\n try {\n var data = await api(\"GET\", \"/../admin/instances\");\n if (data.ok && data.instances && data.instances.length > 0) {\n __instanceFilterInitialized = true;\n var sel = document.getElementById(\"members-instance-filter\");\n data.instances.forEach(function(inst) {\n __instanceNames[inst.instanceDid] = inst.appName || inst.instanceDid.substring(0, 16);\n var opt = document.createElement(\"option\");\n opt.value = inst.instanceDid;\n opt.textContent = __instanceNames[inst.instanceDid];\n sel.appendChild(opt);\n });\n sel.style.display = \"\";\n }\n } catch(e) { /* not admin or no instances */ }\n}\n\n// Also support specific instance filter via query param\n// When ?instance=z1xxx, team handler needs to use that instanceDid\n\nasync function loadMembers(page) {\n membersPage = page || 1;\n var instanceFilter = document.getElementById(\"members-instance-filter\").value;\n var qs = \"?page=\" + membersPage + \"&pageSize=\" + membersPageSize;\n // System view (\"\") → use defaultInstanceDid so we query membership, not global users\n if (instanceFilter === \"\" && __pageData.instanceDid) {\n qs += \"&instance=\" + encodeURIComponent(__pageData.instanceDid);\n } else if (instanceFilter) {\n qs += \"&instance=\" + encodeURIComponent(instanceFilter);\n }\n var search = document.getElementById(\"members-search\").value.trim();\n if (search) qs += \"&search=\" + encodeURIComponent(search);\n var role = document.getElementById(\"members-role-filter\").value;\n if (role) qs += \"&role=\" + encodeURIComponent(role);\n var status = document.getElementById(\"members-status-filter\").value;\n if (status !== \"\") qs += \"&approved=\" + status;\n var source = document.getElementById(\"members-source-filter\").value;\n if (source) qs += \"&sourceProvider=\" + encodeURIComponent(source);\n if (membersSortBy) {\n qs += \"&sortBy=\" + encodeURIComponent(membersSortBy);\n qs += \"&sortOrder=\" + encodeURIComponent(membersSortOrder || \"desc\");\n }\n\n document.getElementById(\"members-table-wrap\").innerHTML = skeletonTable(8, 6);\n document.getElementById(\"members-pagination\").innerHTML = \"\";\n var data = await api(\"GET\", \"/members\" + qs);\n if (!data.ok) {\n document.getElementById(\"members-table-wrap\").innerHTML = '<div class=\"empty-state\"><div class=\"empty-state-icon\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 8v4m0 4h.01\"/></svg></div><div class=\"empty-state-title\">' + __t(\"members.loadFailed\") + '</div></div>';\n return;\n }\n __membersAggregated = !!data.aggregated;\n // Only pin the caller to the top when using the default (server) order.\n // Under explicit sort, honor the user's intent without re-shuffling.\n if (membersSortBy) {\n currentMembersList = data.users || [];\n } else {\n currentMembersList = (data.users || []).slice().sort(function(a, b) {\n if (a.did === __caller.did) return -1;\n if (b.did === __caller.did) return 1;\n return (a.createdAt || 0) - (b.createdAt || 0);\n });\n }\n renderMembersTable(currentMembersList, data.paging);\n updateTransferSection(data.users);\n}\n\nfunction renderMembersTable(users, paging) {\n var wrap = document.getElementById(\"members-table-wrap\");\n if (!users || users.length === 0) {\n wrap.innerHTML = '<div class=\"empty-state\"><div class=\"empty-state-icon\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/></svg></div><div class=\"empty-state-title\">' + __t(\"members.noMembers\") + '</div><div class=\"empty-state-desc\">' + __t(\"members.noMembersHint\") + '</div></div>';\n document.getElementById(\"members-pagination\").innerHTML = \"\";\n return;\n }\n\n var instanceSel = document.getElementById(\"members-instance-filter\");\n var instanceVisible = instanceSel && instanceSel.style.display !== 'none';\n var showInstance = instanceVisible && instanceSel.value === \"all\";\n var html = '<table class=\"table\"><thead><tr>';\n html += sortableTh(\"name\", __t(\"members.col.member\"));\n html += sortableTh(\"role\", __t(\"members.col.role\"));\n if (showInstance) html += '<th>' + __t(\"members.col.instance\") + '</th>';\n html += sortableTh(\"source\", __t(\"members.col.source\"), \"col-hide-sm\");\n html += sortableTh(\"status\", __t(\"members.col.status\"), \"col-hide-sm\");\n html += sortableTh(\"joined\", __t(\"members.col.joined\"), \"col-hide-md\");\n // Registered/Last Login From columns removed — domain info not meaningful in Worker context\n html += '<th></th></tr></thead><tbody>';\n users.forEach(function(u) {\n var userDid = u.did || u.user_did || '';\n var av = renderAvatar(u.avatar, u.fullName, \"sm\");\n var name = u.fullName ? escapeHtml(u.fullName) : '<span class=\"text-muted\">(unnamed)</span>';\n var did = '<did-address did=\"' + escapeHtml(userDid) + '\" compact></did-address>';\n var roleBadge = '<span class=\"badge badge-' + (u.role || \"guest\") + '\">' + __t(\"common.\" + (u.role || \"guest\")) + '</span>';\n var statusBadge = u.approved !== undefined ? (u.approved ? '<span class=\"badge badge-active\">' + __t(\"common.active\") + '</span>' : '<span class=\"badge badge-blocked\">' + __t(\"common.blocked\") + '</span>') : '<span class=\"text-muted\">—</span>';\n var joined = (u.createdAt || u.joined_at) ? '<span class=\"cell-date\">' + escapeHtml(relativeTime(u.createdAt || u.joined_at)) + '</span>' : '<span class=\"text-muted\">—</span>';\n html += '<tr style=\"cursor:pointer\" onclick=\"openMemberDetail(\\'' + escapeHtml(userDid) + '\\')\">';\n html += '<td><div class=\"member-identity\">' + av + '<div><div class=\"member-name\">' + name + '</div><div class=\"member-email\">' + did + '</div></div></div></td>';\n html += '<td>' + roleBadge + '</td>';\n if (showInstance) {\n if (__membersAggregated && u.instance_count > 1) {\n html += '<td><a href=\"javascript:void(0)\" onclick=\"event.stopPropagation();openMemberDetail(\\'' + escapeHtml(userDid) + '\\')\" style=\"color:var(--blue);font-size:14px;text-decoration:none\">' + u.instance_count + ' ' + __t(\"members.instances\") + '</a></td>';\n } else {\n var instDid = (__membersAggregated && u.instances && u.instances.length === 1) ? u.instances[0].instance_did : (u.instance_did || '');\n var instName = __instanceNames[instDid] || '';\n if (!instName) instName = instDid ? __t(\"members.currentInstance\") : '—';\n html += '<td><div>' + escapeHtml(instName);\n if (instDid) html += '<div style=\"font-size:11px;color:var(--text-secondary);margin-top:2px\" onclick=\"event.stopPropagation()\"><did-address did=\"' + escapeHtml(instDid) + '\" compact></did-address></div>';\n html += '</div></td>';\n }\n }\n var sourceBadge = '<span class=\"text-muted\">' + escapeHtml(providerLabel(u.sourceProvider)) + '</span>';\n html += '<td class=\"col-hide-sm\">' + sourceBadge + '</td>';\n html += '<td class=\"col-hide-sm\">' + statusBadge + '</td>';\n html += '<td class=\"col-hide-md\">' + joined + '</td>';\n // Actions: only for current instance members, not cross-instance\n var canOperate = !instanceVisible || (instanceVisible && instanceSel.value === '');\n // In aggregated mode, only allow actions on users whose instance matches current system\n if (__membersAggregated) canOperate = false;\n html += '<td class=\"action-cell\">' + (canOperate ? renderMemberActions(u) : '') + '</td>';\n html += '</tr>';\n });\n html += '</tbody></table>';\n wrap.innerHTML = html;\n\n renderPagination(\"members-pagination\", paging, loadMembers);\n}\n\nfunction renderMemberActions(u) {\n var isOwner = __caller.role === \"owner\";\n var isAdmin = __caller.role === \"admin\";\n var isSelf = u.did === __caller.did;\n var ROLE_LEVEL = { owner: 3, admin: 2, member: 1, guest: 0 };\n var callerLevel = ROLE_LEVEL[__caller.role] || 0;\n var targetLevel = ROLE_LEVEL[u.role] || 0;\n // Can only act on members with lower privilege\n var canManage = !isSelf && targetLevel < callerLevel;\n var items = [];\n\n // Change role: can manage lower-level members\n if (canManage) {\n items.push('<button class=\"action-menu-item\" onclick=\"event.stopPropagation();openChangeRoleForDid(\\'' + escapeHtml(u.did) + '\\')\">' + __t(\"members.changeRole\") + '</button>');\n }\n if (canManage && u.approved) {\n items.push('<button class=\"action-menu-item\" onclick=\"event.stopPropagation();blockMember(\\'' + escapeHtml(u.did) + '\\',\\'' + escapeHtml(u.fullName || u.did) + '\\')\">' + __t(\"members.block\") + '</button>');\n }\n if (canManage && !u.approved) {\n items.push('<button class=\"action-menu-item\" onclick=\"event.stopPropagation();unblockMember(\\'' + escapeHtml(u.did) + '\\',\\'' + escapeHtml(u.fullName || u.did) + '\\')\">' + __t(\"members.unblock\") + '</button>');\n }\n if (items.length > 0 && canManage) {\n items.push('<div class=\"action-menu-sep\"></div>');\n }\n if (canManage) {\n items.push('<button class=\"action-menu-item danger\" onclick=\"event.stopPropagation();removeMember(\\'' + escapeHtml(u.did) + '\\',\\'' + escapeHtml(u.fullName || u.did) + '\\')\">' + __t(\"members.remove\") + '</button>');\n }\n\n if (items.length === 0) return \"\";\n return '<button class=\"action-trigger\" onclick=\"event.stopPropagation();toggleActionMenu(this)\">⋯</button><div class=\"action-menu\">' + items.join(\"\") + '</div>';\n}\n\nfunction toggleActionMenu(trigger) {\n var menu = trigger.nextElementSibling;\n var wasOpen = menu.classList.contains(\"open\");\n // Close all menus first\n document.querySelectorAll(\".action-menu.open\").forEach(function(m) { m.classList.remove(\"open\"); });\n if (!wasOpen) menu.classList.toggle(\"open\");\n}\n\nasync function openMemberDetail(did) {\n var u = currentMembersList.find(function(m) { return m.did === did; });\n if (!u) return;\n selectedMember = u;\n\n // Fetch full member info (with connectedAccounts) from API.\n // Falls back to in-memory list data if the request fails.\n var detail = u;\n try {\n var resp = await api(\"GET\", \"/members/\" + encodeURIComponent(did));\n if (resp && resp.ok && resp.user) {\n // Merge: in-memory list has fields the API may omit (instances, passkeyCount in some paths).\n detail = Object.assign({}, u, resp.user);\n selectedMember = detail;\n }\n } catch (e) {}\n\n var body = document.getElementById(\"member-detail-body\");\n var html = '<div class=\"profile-header\">' + renderAvatar(detail.avatar, detail.fullName) + '<div>';\n html += '<div style=\"font-weight:500;font-size:16px;color:var(--text-white)\">' + escapeHtml(detail.fullName || \"(unnamed)\") + '</div>';\n html += '<did-address did=\"' + escapeHtml(detail.did) + '\" compact></did-address>';\n html += '</div></div>';\n\n html += '<div class=\"settings-card\" style=\"margin-top:16px\">';\n html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailRole\") + '</span><span class=\"settings-value\"><span class=\"badge badge-' + (detail.role || \"guest\") + '\">' + __t(\"common.\" + (detail.role || \"guest\")) + '</span></span></div>';\n html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailStatus\") + '</span><span class=\"settings-value\">' + (detail.approved ? '<span class=\"badge badge-active\">' + __t(\"common.active\") + '</span>' : '<span class=\"badge badge-blocked\">' + __t(\"common.blocked\") + '</span>') + '</span></div>';\n html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailSource\") + '</span><span class=\"settings-value\">' + escapeHtml(providerLabel(detail.sourceProvider)) + '</span></div>';\n if (detail.email) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailEmail\") + '</span><span class=\"settings-value\">' + escapeHtml(detail.email) + '</span></div>';\n if (detail.inviterName) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailInvitedBy\") + '</span><span class=\"settings-value\">' + escapeHtml(detail.inviterName) + '</span></div>';\n if (detail.createdAt) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailJoined\") + '</span><span class=\"settings-value\">' + escapeHtml(absoluteTime(detail.createdAt)) + '</span></div>';\n if (detail.lastLoginAt) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailLastLogin\") + '</span><span class=\"settings-value\">' + escapeHtml(absoluteTime(detail.lastLoginAt)) + '</span></div>';\n if (detail.sourceDomain) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailRegisteredFrom\") + '</span><span class=\"settings-value\">' + escapeHtml(detail.sourceDomain) + '</span></div>';\n if (detail.lastLoginDomain) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailLastLoginFrom\") + '</span><span class=\"settings-value\">' + escapeHtml(detail.lastLoginDomain) + '</span></div>';\n if (detail.passkeyCount !== undefined) html += '<div class=\"settings-row\"><span class=\"settings-label\">' + __t(\"members.detailPasskeys\") + '</span><span class=\"settings-value\">' + detail.passkeyCount + '</span></div>';\n html += '</div>';\n\n // Connected Accounts (read-only)\n if (detail.connectedAccounts && detail.connectedAccounts.length > 0) {\n html += '<div class=\"settings-card\" style=\"margin-top:16px\">';\n html += '<div class=\"settings-card-header\"><span>' + __t(\"members.detailConnectedAccounts\") + ' (' + detail.connectedAccounts.length + ')</span></div>';\n html += '<div style=\"max-height:240px;overflow-y:auto\">';\n detail.connectedAccounts.forEach(function(a) {\n var label = providerLabel(a.provider);\n var info = '';\n if (a.userInfo && a.userInfo.name) info = a.userInfo.name;\n else if (a.userInfo && a.userInfo.email) info = a.userInfo.email;\n else if (a.id) info = a.id;\n html += '<div class=\"settings-row\" style=\"gap:12px;align-items:flex-start\">';\n html += '<div style=\"flex:1;min-width:0\">';\n html += '<div style=\"font-size:14px;font-weight:500;color:var(--text)\">' + escapeHtml(label);\n if (a.isMain) html += ' <span class=\"badge badge-active\" style=\"font-size:10px;padding:1px 6px;\">' + __t(\"profile.mainAccount\") + '</span>';\n html += '</div>';\n if (info) html += '<div style=\"font-size:12px;color:var(--text-secondary);margin-top:2px\">' + escapeHtml(info) + '</div>';\n if (a.did) html += '<div style=\"font-size:11px;color:var(--text-secondary);margin-top:2px\"><did-address did=\"' + escapeHtml(a.did) + '\" compact></did-address></div>';\n html += '</div>';\n if (a.lastLoginAt) html += '<div style=\"font-size:12px;color:var(--text-secondary);flex-shrink:0\">' + escapeHtml(relativeTime(a.lastLoginAt)) + '</div>';\n html += '</div>';\n });\n html += '</div></div>';\n }\n\n // Instance memberships (aggregated mode)\n if (detail.instances && detail.instances.length > 0) {\n html += '<div class=\"settings-card\" style=\"margin-top:16px\">';\n html += '<div class=\"settings-card-header\"><span>' + __t(\"members.instanceMemberships\") + ' (' + detail.instances.length + ')</span></div>';\n html += '<div style=\"max-height:240px;overflow-y:auto\">';\n detail.instances.forEach(function(inst) {\n var instName = __instanceNames[inst.instance_did] || '';\n var instDid = inst.instance_did || '';\n html += '<div class=\"settings-row\" style=\"gap:12px\">';\n html += '<div style=\"flex:1;min-width:0\"><div style=\"font-size:14px;font-weight:500;color:var(--text)\">' + escapeHtml(instName || __t(\"members.currentInstance\")) + '</div>';\n if (instDid) html += '<div style=\"font-size:11px;color:var(--text-secondary);margin-top:2px\"><did-address did=\"' + escapeHtml(instDid) + '\" compact></did-address></div>';\n html += '</div>';\n html += '<div style=\"display:flex;align-items:center;gap:8px;flex-shrink:0\"><span class=\"badge badge-' + inst.role + '\">' + __t(\"common.\" + inst.role) + '</span>';\n if (inst.joined_at) html += '<span style=\"font-size:12px;color:var(--text-secondary)\">' + escapeHtml(relativeTime(inst.joined_at)) + '</span>';\n html += '</div></div>';\n });\n html += '</div></div>';\n }\n\n body.innerHTML = html;\n\n // Change role: mirror list's canOperate + renderMemberActions logic exactly.\n // Use post-merge detail so role/did reflect the latest API response.\n var changeRoleBtn = document.getElementById(\"member-detail-change-role\");\n var instanceSel = document.getElementById(\"members-instance-filter\");\n var instanceVisible = instanceSel && instanceSel.style.display !== 'none';\n var canOperate = !instanceVisible || (instanceVisible && instanceSel.value === '');\n if (__membersAggregated) canOperate = false;\n var isSelf = detail.did === __caller.did;\n var ROLE_LEVEL = { owner: 3, admin: 2, member: 1, guest: 0 };\n var callerLevel = ROLE_LEVEL[__caller.role] || 0;\n var targetLevel = ROLE_LEVEL[detail.role] || 0;\n var canChangeRole = canOperate && !isSelf && targetLevel < callerLevel;\n if (canChangeRole) {\n changeRoleBtn.classList.remove(\"hidden\");\n } else {\n changeRoleBtn.classList.add(\"hidden\");\n }\n\n showDialog(\"member-detail-dialog\");\n}\n\nfunction openChangeRoleForDid(did) {\n var u = currentMembersList.find(function(m) { return m.did === did; });\n if (!u) return;\n selectedMember = u;\n openChangeRoleDialog();\n}\n\nfunction openChangeRoleDialog() {\n if (!selectedMember) return;\n document.getElementById(\"change-role-title\").textContent = __t(\"members.changeRole\");\n document.getElementById(\"change-role-subtitle\").textContent = __t(\"members.changeRoleFor\", { name: selectedMember.fullName || truncateDid(selectedMember.did) });\n // Show/hide role options based on caller's level (only owner can assign admin)\n var radios = document.querySelectorAll('input[name=\"new-role\"]');\n radios.forEach(function(r) {\n r.checked = r.value === selectedMember.role;\n var label = r.closest('label');\n if (label) {\n if (r.value === 'admin' && __caller.role !== 'owner') label.style.display = 'none';\n else label.style.display = '';\n }\n });\n closeDialog(\"member-detail-dialog\");\n showDialog(\"change-role-dialog\");\n}\n\nasync function saveRoleChange() {\n if (!selectedMember) return;\n var selected = document.querySelector('input[name=\"new-role\"]:checked');\n if (!selected) return;\n var newRole = selected.value;\n var data = await api(\"PUT\", \"/members/\" + encodeURIComponent(selectedMember.did) + \"/role\", { role: newRole });\n if (data.ok) {\n showToast(__t(\"members.roleUpdated\"), \"success\");\n closeDialog(\"change-role-dialog\");\n loadMembers(membersPage);\n }\n}\n\nasync function blockMember(did, name) {\n var ok = await confirmDialog({ title: __t(\"members.blockTitle\"), message: __t(\"members.blockMsg\", { name: name }), confirmLabel: __t(\"members.blockBtn\"), danger: true });\n if (!ok) return;\n var data = await api(\"PUT\", \"/members/\" + encodeURIComponent(did) + \"/approval\", { approved: false });\n if (data.ok) { showToast(__t(\"members.blockSuccess\"), \"success\"); loadMembers(membersPage); }\n}\n\nasync function unblockMember(did, name) {\n var ok = await confirmDialog({ title: __t(\"members.unblockTitle\"), message: __t(\"members.unblockMsg\", { name: name }), confirmLabel: __t(\"members.unblock\") });\n if (!ok) return;\n var data = await api(\"PUT\", \"/members/\" + encodeURIComponent(did) + \"/approval\", { approved: true });\n if (data.ok) { showToast(__t(\"members.unblockSuccess\"), \"success\"); loadMembers(membersPage); }\n}\n\nasync function removeMember(did, name) {\n var ok = await confirmDialog({ title: __t(\"members.removeTitle\"), message: __t(\"members.removeMsg\", { name: name }), confirmLabel: __t(\"members.remove\"), danger: true });\n if (!ok) return;\n var data = await api(\"DELETE\", \"/members/\" + encodeURIComponent(did));\n if (data.ok) { showToast(__t(\"members.removeSuccess\"), \"success\"); loadMembers(membersPage); }\n}\n\nfunction updateTransferSection(users) {\n var section = document.getElementById(\"ownership-transfer-section\");\n if (__caller.role !== \"owner\") {\n section.classList.add(\"hidden\");\n return;\n }\n section.classList.remove(\"hidden\");\n var select = document.getElementById(\"transfer-target\");\n var html = '<option value=\"\">' + __t(\"members.transferSelect\") + '</option>';\n users.forEach(function(u) {\n if (u.did !== __caller.did && u.approved && u.role !== \"owner\") {\n html += '<option value=\"' + escapeHtml(u.did) + '\">' + escapeHtml(u.fullName || truncateDid(u.did)) + ' (' + u.role + ')</option>';\n }\n });\n select.innerHTML = html;\n}\n\nasync function handleTransferOwnership() {\n var target = document.getElementById(\"transfer-target\").value;\n if (!target) { showToast(__t(\"members.selectMemberFirst\"), \"error\"); return; }\n var targetUser = currentMembersList.find(function(u) { return u.did === target; });\n var name = targetUser ? (targetUser.fullName || truncateDid(target)) : truncateDid(target);\n var ok = await confirmDialog({\n title: __t(\"members.transferTitle\"),\n message: __t(\"members.transferMsg\", { name: name }),\n confirmLabel: __t(\"members.transferConfirm\"),\n danger: true,\n });\n if (!ok) return;\n var data = await api(\"POST\", \"/transfer-ownership\", { targetDid: target });\n if (data.ok) {\n showToast(__t(\"members.transferSuccess\"), \"success\");\n setTimeout(function() { location.reload(); }, 1000);\n }\n}\n\n// Close action menus on outside click\ndocument.addEventListener(\"click\", function() {\n document.querySelectorAll(\".action-menu.open\").forEach(function(m) { m.classList.remove(\"open\"); });\n});\n\nregisterTab(\"members\", function() {\n initInstanceFilter().then(function() {\n // Auto-select instance from URL query (e.g., ?instance=z1xxx)\n var urlInstance = new URLSearchParams(window.location.search).get(\"instance\");\n if (urlInstance) {\n var sel = document.getElementById(\"members-instance-filter\");\n if (sel) sel.value = urlInstance;\n }\n // Restore sort state from URL (sortBy / sortOrder) on tab enter.\n readMembersSortFromURL();\n loadMembers(1);\n });\n});\n\n;\n\nvar invitationsPage = 1;\nvar invitationsPageSize = 20;\n\n\nfunction updateRemarkCounter() {\n document.getElementById(\"remark-counter\").textContent = document.getElementById(\"invite-remark\").value.length;\n}\n\nasync function loadInvitations(page) {\n invitationsPage = page || 1;\n var qs = \"?page=\" + invitationsPage + \"&pageSize=\" + invitationsPageSize;\n document.getElementById(\"invitations-table-wrap\").innerHTML = skeletonTable(6, 4);\n document.getElementById(\"invitations-pagination\").innerHTML = \"\";\n var data = await api(\"GET\", \"/invitations\" + qs);\n if (!data.ok) {\n document.getElementById(\"invitations-table-wrap\").innerHTML = '<div class=\"empty-state\"><div class=\"empty-state-icon\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 8v4m0 4h.01\"/></svg></div><div class=\"empty-state-title\">' + __t(\"invitations.loadFailed\") + '</div></div>';\n return;\n }\n renderInvitationsTable(data.invitations, data.paging);\n}\n\nfunction renderInvitationsTable(invitations, paging) {\n var wrap = document.getElementById(\"invitations-table-wrap\");\n if (!invitations || invitations.length === 0) {\n wrap.innerHTML = '<div class=\"empty-state\"><div class=\"empty-state-icon\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M22 12h-4l-3 9L9 3l-3 9H2\"/></svg></div><div class=\"empty-state-title\">' + __t(\"invitations.noInvitations\") + '</div><div class=\"empty-state-desc\">' + __t(\"invitations.noInvitationsHint\") + '</div></div>';\n document.getElementById(\"invitations-pagination\").innerHTML = \"\";\n return;\n }\n\n var html = '<table class=\"table\"><thead><tr><th>' + __t(\"invitations.col.role\") + '</th><th class=\"col-hide-sm\">' + __t(\"invitations.col.remark\") + '</th><th>' + __t(\"invitations.col.uses\") + '</th><th>' + __t(\"invitations.col.expires\") + '</th><th class=\"col-hide-md\">' + __t(\"invitations.col.createdBy\") + '</th><th></th></tr></thead><tbody>';\n invitations.forEach(function(inv) {\n var roleBadge = '<span class=\"badge badge-' + inv.role + '\">' + __t(\"common.\" + inv.role) + '</span>';\n var remark = inv.remark ? escapeHtml(inv.remark.length > 30 ? inv.remark.slice(0, 30) + \"...\" : inv.remark) : '<span class=\"text-muted\">—</span>';\n var uses = inv.useCount + \" / \" + inv.maxUses;\n if (inv.useCount >= inv.maxUses) uses += ' <span class=\"badge badge-blocked\">' + __t(\"invitations.closed\") + '</span>';\n var expired = new Date(inv.expireAt) < new Date();\n var expireText = expired ? '<span class=\"badge badge-expired\">expired</span>' : '<span class=\"cell-date\">' + escapeHtml(relativeTime(inv.expireAt)) + '</span>';\n var isActive = !expired && inv.status !== \"closed\" && inv.useCount < inv.maxUses;\n var inviter = inv.inviterName ? escapeHtml(inv.inviterName) : '<span class=\"text-muted\">—</span>';\n\n var actions = '';\n if (isActive) {\n actions += '<button class=\"action-menu-item\" onclick=\"event.stopPropagation();copyToClipboard(\\'' + escapeHtml(inv.link) + '\\')\">' + __t(\"invitations.copyLink\") + '</button>';\n }\n var canDelete = __caller.role === \"owner\" || inv.inviterDid === __caller.did;\n if (canDelete) {\n if (actions) actions += '<div class=\"action-menu-sep\"></div>';\n actions += '<button class=\"action-menu-item danger\" onclick=\"event.stopPropagation();deleteInvitation(\\'' + escapeHtml(inv.id) + '\\')\">' + __t(\"common.delete\") + '</button>';\n }\n var actionMenu = actions ? '<td class=\"action-cell\"><button class=\"action-trigger\" onclick=\"event.stopPropagation();toggleActionMenu(this)\">⋯</button><div class=\"action-menu\">' + actions + '</div></td>' : '<td></td>';\n\n html += '<tr><td>' + roleBadge + '</td><td class=\"col-hide-sm\">' + remark + '</td><td>' + uses + '</td><td>' + expireText + '</td><td class=\"col-hide-md\">' + inviter + '</td>' + actionMenu + '</tr>';\n });\n html += '</tbody></table>';\n wrap.innerHTML = html;\n\n renderPagination(\"invitations-pagination\", paging, loadInvitations);\n}\n\nasync function createInvitation() {\n var btn = document.getElementById(\"create-invite-btn\");\n btn.disabled = true;\n\n var role = document.getElementById(\"invite-role\").value;\n var remark = document.getElementById(\"invite-remark\").value.trim();\n var expireHours = parseInt(document.getElementById(\"invite-expire\").value, 10);\n var maxUses = parseInt(document.getElementById(\"invite-max-uses\").value, 10) || 1;\n maxUses = Math.min(100, Math.max(1, maxUses));\n\n var data = await api(\"POST\", \"/invitations\", { role: role, remark: remark, expireHours: expireHours, maxUses: maxUses });\n btn.disabled = false;\n\n if (data.ok && data.invitation) {\n closeDialog(\"create-invitation-dialog\");\n copyToClipboard(data.invitation.link);\n showToast(__t(\"invitations.created\"), \"success\");\n // Reset form\n document.getElementById(\"invite-remark\").value = \"\";\n document.getElementById(\"invite-max-uses\").value = \"1\";\n updateRemarkCounter();\n loadInvitations(invitationsPage);\n }\n}\n\nasync function deleteInvitation(id) {\n var ok = await confirmDialog({ title: __t(\"invitations.deleteTitle\"), message: __t(\"invitations.deleteMsg\"), confirmLabel: __t(\"common.delete\"), danger: true });\n if (!ok) return;\n var data = await api(\"DELETE\", \"/invitations/\" + encodeURIComponent(id));\n if (data.ok) { showToast(__t(\"invitations.deleted\"), \"success\"); loadInvitations(invitationsPage); }\n}\n\n// Initialize role options based on caller role\nfunction initInviteRoleOptions() {\n var select = document.getElementById(\"invite-role\");\n if (__caller.role === \"owner\") {\n select.innerHTML = '<option value=\"guest\">Guest</option><option value=\"member\">Member</option><option value=\"admin\">Admin</option>';\n }\n}\n\nregisterTab(\"invitations\", function() {\n initInviteRoleOptions();\n loadInvitations(1);\n});\n\n;\n\nvar auditPage = 1;\nvar auditPageSize = 50;\n\nvar AUDIT_ACTION_I18N = {\n \"login\": \"audit.actions.login\",\n \"user.register\": \"audit.actions.register\",\n \"user.login\": \"audit.actions.login\",\n \"user.accept_invitation\": \"audit.actions.acceptInvitation\",\n \"user.remove\": \"audit.actions.removeMember\",\n \"user.block\": \"audit.actions.blockMember\",\n \"user.unblock\": \"audit.actions.unblockMember\",\n \"user.role_change\": \"audit.actions.roleChange\",\n \"user.transfer_ownership\": \"audit.actions.transferOwnership\",\n \"user.profile_update\": \"audit.actions.profileUpdate\",\n \"user.avatar_update\": \"audit.actions.avatarUpdate\",\n \"user.avatar_delete\": \"audit.actions.avatarDelete\",\n \"user.email_change\": \"audit.actions.emailChange\",\n \"user.register_blocked\": \"audit.actions.registerBlocked\",\n \"invitation.create\": \"audit.actions.createInvitation\",\n \"invitation.delete\": \"audit.actions.deleteInvitation\",\n \"oauth.bind\": \"audit.actions.oauthBind\",\n \"oauth.unbind\": \"audit.actions.oauthUnbind\",\n \"accessKey.create\": \"audit.actions.accessKeyCreate\",\n \"accessKey.update\": \"audit.actions.accessKeyUpdate\",\n \"accessKey.delete\": \"audit.actions.accessKeyDelete\",\n \"membership.create\": \"audit.actions.membershipCreate\",\n \"membership.update_role\": \"audit.actions.membershipUpdateRole\",\n \"membership.remove\": \"audit.actions.membershipRemove\",\n \"passkey.connect\": \"audit.actions.passkeyConnect\",\n \"passkey.disconnect\": \"audit.actions.passkeyDisconnect\",\n \"access_policy.create\": \"audit.actions.accessPolicyCreate\",\n \"access_policy.update\": \"audit.actions.accessPolicyUpdate\",\n \"access_policy.delete\": \"audit.actions.accessPolicyDelete\",\n \"security_rule.create\": \"audit.actions.securityRuleCreate\",\n \"security_rule.update\": \"audit.actions.securityRuleUpdate\",\n \"security_rule.delete\": \"audit.actions.securityRuleDelete\",\n \"settings.oauth_update\": \"audit.actions.settingsOauthUpdate\",\n \"settings.oauth_delete\": \"audit.actions.settingsOauthDelete\",\n \"settings.builtin_providers_update\": \"audit.actions.settingsBuiltinProvidersUpdate\",\n \"settings.session_update\": \"audit.actions.settingsSessionUpdate\",\n \"settings.email_update\": \"audit.actions.settingsEmailUpdate\",\n};\n\nfunction translateAction(action) {\n var key = AUDIT_ACTION_I18N[action];\n return key ? __t(key) : action;\n}\n\nvar _ICONS = {\n login: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4\"/><polyline points=\"10 17 15 12 10 7\"/><line x1=\"15\" y1=\"12\" x2=\"3\" y2=\"12\"/></svg>',\n register: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/><line x1=\"19\" y1=\"8\" x2=\"19\" y2=\"14\"/><line x1=\"22\" y1=\"11\" x2=\"16\" y2=\"11\"/></svg>',\n block: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"4.93\" y1=\"4.93\" x2=\"19.07\" y2=\"19.07\"/></svg>',\n unblock: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\" ry=\"2\"/><path d=\"M7 11V7a5 5 0 0 1 9.9-1\"/></svg>',\n remove: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/><line x1=\"22\" y1=\"11\" x2=\"16\" y2=\"11\"/></svg>',\n trash: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\"/><path d=\"M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2\"/></svg>',\n edit: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"/><path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"/></svg>',\n create: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"16\"/><line x1=\"8\" y1=\"12\" x2=\"16\" y2=\"12\"/></svg>',\n key: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4\"/></svg>',\n link: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\"/><path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\"/></svg>',\n unlink: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\"/><path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\"/><line x1=\"2\" y1=\"2\" x2=\"22\" y2=\"22\"/></svg>',\n mail: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z\"/><polyline points=\"22,6 12,13 2,6\"/></svg>',\n settings: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"3\"/><path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z\"/></svg>',\n shield: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z\"/></svg>',\n lock: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\" ry=\"2\"/><path d=\"M7 11V7a5 5 0 0 1 10 0v4\"/></svg>',\n image: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/><circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\"/><polyline points=\"21 15 16 10 5 21\"/></svg>',\n at: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-3.92 7.94\"/></svg>',\n user: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2\"/><circle cx=\"12\" cy=\"7\" r=\"4\"/></svg>',\n crown: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M2 4l4.5 9L12 4l5.5 9L22 4l-3 13H5L2 4z\"/><line x1=\"5\" y1=\"17\" x2=\"19\" y2=\"17\"/></svg>',\n role: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M7 16V4m0 0L3 8m4-4 4 4\"/><path d=\"M17 8v12m0 0 4-4m-4 4-4-4\"/></svg>',\n users: '<svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/><path d=\"M23 21v-2a4 4 0 0 0-3-3.87\"/><path d=\"M16 3.13a4 4 0 0 1 0 7.75\"/></svg>',\n};\n\nfunction actionIcon(action) {\n if (!action) return \"\";\n switch (action) {\n case \"login\":\n case \"user.login\": return _ICONS.login;\n case \"user.register\": return _ICONS.register;\n case \"user.register_blocked\":\n case \"user.block\": return _ICONS.block;\n case \"user.unblock\": return _ICONS.unblock;\n case \"user.remove\":\n case \"membership.remove\": return _ICONS.remove;\n case \"user.role_change\":\n case \"membership.update_role\": return _ICONS.role;\n case \"user.transfer_ownership\": return _ICONS.crown;\n case \"user.profile_update\": return _ICONS.user;\n case \"user.avatar_update\":\n case \"user.avatar_delete\": return _ICONS.image;\n case \"user.email_change\": return _ICONS.at;\n case \"user.accept_invitation\":\n case \"invitation.create\":\n case \"invitation.delete\": return _ICONS.mail;\n case \"membership.create\": return _ICONS.users;\n case \"oauth.bind\": return _ICONS.link;\n case \"oauth.unbind\": return _ICONS.unlink;\n case \"passkey.connect\":\n case \"passkey.disconnect\":\n case \"accessKey.create\":\n case \"accessKey.update\":\n case \"accessKey.delete\": return _ICONS.key;\n case \"access_policy.create\":\n case \"access_policy.update\":\n case \"access_policy.delete\": return _ICONS.shield;\n case \"security_rule.create\":\n case \"security_rule.update\":\n case \"security_rule.delete\": return _ICONS.lock;\n case \"settings.oauth_update\":\n case \"settings.oauth_delete\":\n case \"settings.builtin_providers_update\":\n case \"settings.session_update\":\n case \"settings.email_update\": return _ICONS.settings;\n default:\n if (action.indexOf(\"delete\") !== -1) return _ICONS.trash;\n if (action.indexOf(\"create\") !== -1) return _ICONS.create;\n if (action.indexOf(\"update\") !== -1) return _ICONS.edit;\n return \"\";\n }\n}\n\nfunction actionBadgeClass(action) {\n if (!action) return \"badge-neutral\";\n var a = action.toLowerCase();\n // Destructive: delete / remove / block / unbind / disconnect / register_blocked\n if (a.indexOf(\"delete\") !== -1 || a.indexOf(\"remove\") !== -1 ||\n a.indexOf(\".block\") !== -1 || a.indexOf(\"unbind\") !== -1 ||\n a.indexOf(\"disconnect\") !== -1 || a.indexOf(\"register_blocked\") !== -1) {\n return \"badge-blocked\";\n }\n // Auth events: login / register\n if (a === \"login\" || a === \"user.login\" || a === \"user.register\") {\n return \"badge-member\";\n }\n // Restore: unblock\n if (a.indexOf(\"unblock\") !== -1) {\n return \"badge-admin\";\n }\n // Modifications: update / change / role_change / transfer / settings.*\n if (a.indexOf(\"update\") !== -1 || a.indexOf(\"change\") !== -1 ||\n a.indexOf(\"role_change\") !== -1 || a.indexOf(\"transfer\") !== -1 ||\n a.indexOf(\"settings.\") !== -1) {\n return \"badge-guest\";\n }\n // Create / bind / connect / accept / add\n return \"badge-owner\";\n}\n\nasync function loadAuditLogs(page) {\n auditPage = page || 1;\n var action = document.getElementById(\"audit-action-filter\").value;\n var qs = \"?page=\" + auditPage + \"&pageSize=\" + auditPageSize;\n if (action) qs += \"&action=\" + encodeURIComponent(action);\n document.getElementById(\"audit-table-wrap\").innerHTML = skeletonTable(4, 8);\n document.getElementById(\"audit-pagination\").innerHTML = \"\";\n var data = await api(\"GET\", \"/audit-logs\" + qs);\n if (!data.ok) {\n document.getElementById(\"audit-table-wrap\").innerHTML = '<div class=\"empty-state\"><div class=\"empty-state-icon\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 8v4m0 4h.01\"/></svg></div><div class=\"empty-state-title\">' + __t(\"audit.loadFailed\") + '</div></div>';\n return;\n }\n renderAuditLogs(data.logs, data.paging);\n}\n\nfunction renderAuditLogs(logs, paging) {\n var wrap = document.getElementById(\"audit-table-wrap\");\n if (!logs || logs.length === 0) {\n wrap.innerHTML = '<div class=\"empty-state\"><div class=\"empty-state-icon\"><svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"/><polyline points=\"14 2 14 8 20 8\"/><line x1=\"16\" y1=\"13\" x2=\"8\" y2=\"13\"/><line x1=\"16\" y1=\"17\" x2=\"8\" y2=\"17\"/></svg></div><div class=\"empty-state-title\">' + __t(\"audit.noLogs\") + '</div><div class=\"empty-state-desc\">' + __t(\"audit.noLogsHint\") + '</div></div>';\n document.getElementById(\"audit-pagination\").innerHTML = \"\";\n return;\n }\n\n var html = '<table class=\"table\"><thead><tr><th>' + __t(\"audit.col.time\") + '</th><th>' + __t(\"audit.col.action\") + '</th><th>' + __t(\"audit.col.operator\") + '</th><th>' + __t(\"audit.col.target\") + '</th></tr></thead><tbody>';\n logs.forEach(function(log) {\n var time = relativeTime(log.createdAt);\n var absTime = absoluteTime(log.createdAt);\n var operatorName = log.operatorName\n ? escapeHtml(log.operatorName)\n : (log.operatorDid ? '<did-address did=\"' + escapeHtml(log.operatorDid) + '\" compact></did-address>' : \"—\");\n var operatorRoleBadge = log.operatorRole\n ? ' <span class=\"badge badge-' + escapeHtml(log.operatorRole) + '\" style=\"font-size:10px;padding:1px 6px;\">' + escapeHtml(__t(\"common.\" + log.operatorRole)) + '</span>'\n : \"\";\n var target = log.targetName\n ? escapeHtml(log.targetName)\n : (log.targetDid ? '<did-address did=\"' + escapeHtml(log.targetDid) + '\" compact></did-address>' : \"—\");\n\n // For register_blocked: show IP, method, reason in detail column\n var detail = \"\";\n if (log.action === \"user.register_blocked\") {\n var meta = {};\n try { meta = typeof log.metadata === \"string\" ? JSON.parse(log.metadata) : (log.metadata || {}); } catch(e) {}\n var parts = [];\n if (log.ip) parts.push('<span class=\"badge badge-neutral\" style=\"font-size:10px;padding:1px 6px;\">' + escapeHtml(log.ip) + '</span>');\n if (meta.source) parts.push('<span style=\"color:var(--fg-muted);font-size:12px\">' + escapeHtml(meta.source) + '</span>');\n if (meta.method) parts.push('<span class=\"badge ' + (meta.method === \"invite\" ? \"badge-guest\" : \"badge-blocked\") + '\" style=\"font-size:10px;padding:1px 6px;\">' + escapeHtml(meta.method) + '</span>');\n if (meta.reason) parts.push('<span style=\"color:var(--fg-muted);font-size:12px\">' + escapeHtml(meta.reason) + '</span>');\n detail = parts.length > 0 ? parts.join(\" \") : \"—\";\n }\n\n html += '<tr><td><span class=\"cell-date\" title=\"' + escapeHtml(absTime) + '\">' + escapeHtml(time) + '</span></td>';\n html += '<td><span class=\"badge ' + actionBadgeClass(log.action) + '\" style=\"gap:3px\">' + actionIcon(log.action) + escapeHtml(translateAction(log.action)) + '</span></td>';\n html += '<td>' + operatorName + operatorRoleBadge + '</td>';\n html += '<td>' + (detail || target) + '</td></tr>';\n });\n html += '</tbody></table>';\n wrap.innerHTML = html;\n\n renderPagination(\"audit-pagination\", paging, loadAuditLogs);\n}\n\nregisterTab(\"audit-logs\", function() { loadAuditLogs(1); });\n\n;\n\n// ─── Access Tab State ──────────────────────────────────────────────────────\nvar apPolicies = [];\nvar apRules = [];\nvar apDefaultRule = null;\nvar apEditingPolicyId = null;\nvar apEditingRuleId = null;\n\n// ─── Icon constants ────────────────────────────────────────────────────────\nvar AP_ICON_EDIT = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z\"/><path d=\"m15 5 4 4\"/></svg>';\nvar AP_ICON_DELETE = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 6h18\"/><path d=\"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6\"/><path d=\"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2\"/><line x1=\"10\" x2=\"10\" y1=\"11\" y2=\"17\"/><line x1=\"14\" x2=\"14\" y1=\"11\" y2=\"17\"/></svg>';\nvar AP_ICON_LOCK = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect width=\"18\" height=\"11\" x=\"3\" y=\"11\" rx=\"2\" ry=\"2\"/><path d=\"M7 11V7a5 5 0 0 1 10 0v4\"/></svg>';\n\n// ─── Access Type helpers ───────────────────────────────────────────────────\n// NOTE: __t must be called lazily (inside a function), never at top level. The\n// admin script bundles execute before the inline i18n runtime defines __t, so a\n// top-level __t() call throws ReferenceError and halts the rest of the bundle.\nfunction apTypeLabel(type) {\n var labels = {\n public: __t(\"access.typeLabels.public\"),\n invited: __t(\"access.typeLabels.invited\"),\n owner: __t(\"access.typeLabels.owner\"),\n admin: __t(\"access.typeLabels.admin\"),\n roles: __t(\"access.typeLabels.roles\"),\n roles_reverse: __t(\"access.typeLabels.rolesReverse\")\n };\n return labels[type] || type;\n}\n\nfunction apAccessTypeLabel(policy) {\n if (policy.accessType === \"roles\" || policy.accessType === \"roles_reverse\") {\n return apTypeLabel(policy.accessType) + \": \" + (policy.roles || []).join(\", \");\n }\n return apTypeLabel(policy.accessType);\n}\n\nfunction apIsBuiltin(policy) {\n return !!policy.isProtected;\n}\n\nfunction apPolicyAccessType(policyId) {\n var p = apPolicies.find(function(x) { return x.id === policyId; });\n return p ? p.accessType : \"public\";\n}\n\nfunction apIconBtn(icon, title, onclick, variant) {\n var cls = variant === \"danger\" ? \"ap-icon-btn ap-icon-btn-danger\" : \"ap-icon-btn\";\n return '<button class=\"' + cls + '\" title=\"' + escapeHtml(title) + '\" onclick=\"event.stopPropagation();' + onclick + '\">' + icon + '</button>';\n}\n\n// ─── Load Data ─────────────────────────────────────────────────────────────\nasync function apLoadAll() {\n document.getElementById(\"ap-policies-wrap\").innerHTML = skeletonTable(3, 3);\n document.getElementById(\"ap-rules-wrap\").innerHTML = skeletonTable(5, 2);\n\n var pRes = await api(\"GET\", \"/access-policies\");\n if (pRes.ok) apPolicies = pRes.policies;\n\n var rRes = await api(\"GET\", \"/security-rules\");\n if (rRes.ok) apRules = rRes.rules;\n\n // Find the default rule\n apDefaultRule = apRules.find(function(r) { return r.id.indexOf(\"default\") === 0; }) || null;\n\n apRenderPolicies();\n apRenderRules();\n}\n\n// ─── Render All Policies (built-in + custom in one table) ─────────────────\nfunction apRenderPolicies() {\n var wrap = document.getElementById(\"ap-policies-wrap\");\n\n if (!apPolicies.length) {\n wrap.innerHTML = '<div class=\"empty-state\" style=\"padding:16px\"><div class=\"empty-state-title\">' + __t(\"access.noPolicies\") + '</div></div>';\n return;\n }\n\n // Built-in first, then custom\n var builtins = apPolicies.filter(function(p) { return apIsBuiltin(p); });\n var customs = apPolicies.filter(function(p) { return !apIsBuiltin(p); });\n var sorted = builtins.concat(customs);\n\n var html = '<table class=\"table\"><thead><tr>'\n + '<th>' + __t(\"access.name\") + '</th>'\n + '<th>' + __t(\"access.accessType\") + '</th>'\n + '<th style=\"width:80px;text-align:center\">' + __t(\"common.actions\") + '</th>'\n + '</tr></thead><tbody>';\n\n builtins.forEach(function(p) {\n html += \"<tr>\";\n html += '<td><span class=\"ap-lock-name\">' + AP_ICON_LOCK + ' ' + escapeHtml(p.name) + '</span>';\n if (p.description) html += '<div class=\"text-muted\" style=\"font-size:12px;margin-top:2px\">' + escapeHtml(p.description) + '</div>';\n html += \"</td>\";\n html += \"<td>\" + escapeHtml(apAccessTypeLabel(p)) + \"</td>\";\n html += \"<td></td>\";\n html += \"</tr>\";\n });\n\n if (builtins.length > 0 && customs.length > 0) {\n html += '<tr class=\"ap-separator\"><td colspan=\"3\"></td></tr>';\n }\n\n customs.forEach(function(p) {\n html += \"<tr>\";\n html += \"<td>\" + escapeHtml(p.name);\n if (p.description) html += '<div class=\"text-muted\" style=\"font-size:12px;margin-top:2px\">' + escapeHtml(p.description) + '</div>';\n html += \"</td>\";\n html += \"<td>\" + escapeHtml(apAccessTypeLabel(p)) + \"</td>\";\n html += '<td class=\"ap-actions\">';\n html += apIconBtn(AP_ICON_EDIT, __t(\"common.edit\"), \"openPolicyDialog('edit','\" + escapeHtml(p.id) + \"')\");\n html += apIconBtn(AP_ICON_DELETE, __t(\"common.delete\"), \"apDeletePolicy('\" + escapeHtml(p.id) + \"')\", \"danger\");\n html += \"</td></tr>\";\n });\n\n html += \"</tbody></table>\";\n wrap.innerHTML = html;\n}\n\n// ─── Render Path Rules ────────────────────────────────────────────────────\nfunction apRenderRules() {\n var wrap = document.getElementById(\"ap-rules-wrap\");\n var nonDefaultRules = apRules.filter(function(r) { return r.id.indexOf(\"default\") !== 0; });\n\n // Sort non-default rules by priority ascending\n nonDefaultRules.sort(function(a, b) { return a.priority - b.priority; });\n\n var html = '<table class=\"table\"><thead><tr>'\n + '<th style=\"width:80px\">#</th>'\n + '<th>' + __t(\"access.urlPattern\") + '</th>'\n + '<th>' + __t(\"access.accessPolicy\") + '</th>'\n + '<th style=\"width:80px\">' + __t(\"common.status\") + '</th>'\n + '<th style=\"width:80px;text-align:center\">' + __t(\"common.actions\") + '</th>'\n + '</tr></thead><tbody>';\n\n // Default rule — first row, special style\n if (apDefaultRule) {\n var policyOptions = apPolicies.map(function(p) {\n var label = escapeHtml(p.name) + ' \\u2014 ' + escapeHtml(apAccessTypeLabel(p));\n var selected = p.id === apDefaultRule.accessPolicyId ? ' selected' : '';\n return '<option value=\"' + escapeHtml(p.id) + '\"' + selected + '>' + label + '</option>';\n }).join(\"\");\n\n html += '<tr style=\"background:var(--bg-elevated)\">';\n html += '<td><span class=\"badge badge-active\">' + __t(\"access.defaultLabel\") + '</span></td>';\n html += '<td><code>*</code></td>';\n html += '<td><select class=\"select select-sm\" onchange=\"apSaveDefaultRulePolicy(this.value)\" style=\"max-width:280px\">' + policyOptions + '</select></td>';\n html += '<td></td>';\n html += '<td></td>';\n html += '</tr>';\n }\n\n // Empty state for non-default rules\n if (nonDefaultRules.length === 0 && apDefaultRule) {\n html += '<tr><td colspan=\"5\" class=\"text-muted\" style=\"text-align:center;padding:16px\">' + __t(\"access.noRules\") + '</td></tr>';\n }\n\n nonDefaultRules.forEach(function(r, i) {\n html += \"<tr>\";\n html += \"<td>#\" + (i + 1) + \"</td>\";\n html += \"<td><code>\" + escapeHtml(r.pathPattern) + \"</code></td>\";\n html += \"<td>\" + escapeHtml(r.accessPolicyName) + \"</td>\";\n html += '<td><label class=\"ap-switch\" title=\"' + (r.enabled ? __t(\"access.clickToDisable\") : __t(\"access.clickToEnable\")) + '\"><input type=\"checkbox\"' + (r.enabled ? ' checked' : '') + ' onchange=\"apToggleRule('' + escapeHtml(r.id) + '',' + (r.enabled ? 0 : 1) + ')\"><span class=\"ap-switch-slider\"></span></label></td>';\n html += '<td class=\"ap-actions\">';\n html += apIconBtn(AP_ICON_EDIT, __t(\"common.edit\"), \"openRuleDialog('edit','\" + escapeHtml(r.id) + \"')\");\n html += apIconBtn(AP_ICON_DELETE, __t(\"common.delete\"), \"apDeleteRule('\" + escapeHtml(r.id) + \"')\", \"danger\");\n html += \"</td></tr>\";\n });\n\n html += \"</tbody></table>\";\n wrap.innerHTML = html;\n}\n\nasync function apSaveDefaultRulePolicy(policyId) {\n if (!apDefaultRule) return;\n var res = await api(\"PUT\", \"/security-rules/\" + apDefaultRule.id, {\n accessPolicyId: policyId,\n remark: apDefaultRule.remark || \"Default fallback rule\"\n });\n if (res.ok) {\n showToast(__t(\"access.ruleSaved\"), \"success\");\n apDefaultRule.accessPolicyId = policyId;\n }\n}\n\nasync function apToggleRule(id, enabled) {\n var r = apRules.find(function(x) { return x.id === id; });\n if (!r) return;\n var res = await api(\"PUT\", \"/security-rules/\" + id, {\n accessPolicyId: r.accessPolicyId,\n pathPattern: r.pathPattern,\n priority: r.priority,\n enabled: enabled,\n remark: r.remark || \"\"\n });\n if (res.ok) {\n r.enabled = enabled;\n apRenderRules();\n showToast(__t(\"access.ruleSaved\"), \"success\");\n }\n}\n\n// ─── Policy Dialog (custom only) ──────────────────────────────────────────\nfunction openPolicyDialog(mode, id) {\n apEditingPolicyId = (mode === \"edit\") ? id : null;\n document.getElementById(\"ap-policy-dialog-title\").textContent = mode === \"edit\" ? __t(\"access.editPolicyTitle\") : __t(\"access.policyTitle\");\n\n if (mode === \"edit\") {\n var p = apPolicies.find(function(x) { return x.id === id; });\n if (!p) return;\n document.getElementById(\"ap-policy-name\").value = p.name;\n document.getElementById(\"ap-policy-type\").value = p.accessType || \"roles\";\n document.getElementById(\"ap-policy-desc\").value = p.description || \"\";\n document.querySelectorAll(\".ap-role-cb\").forEach(function(cb) {\n cb.checked = (p.roles || []).includes(cb.value);\n });\n } else {\n document.getElementById(\"ap-policy-name\").value = \"\";\n document.getElementById(\"ap-policy-type\").value = \"roles\";\n document.getElementById(\"ap-policy-desc\").value = \"\";\n document.querySelectorAll(\".ap-role-cb\").forEach(function(cb) { cb.checked = false; });\n }\n showDialog(\"ap-policy-dialog\");\n}\n\nasync function apSavePolicy() {\n var body = {\n name: document.getElementById(\"ap-policy-name\").value.trim(),\n accessType: document.getElementById(\"ap-policy-type\").value,\n description: document.getElementById(\"ap-policy-desc\").value.trim(),\n };\n body.roles = [];\n document.querySelectorAll(\".ap-role-cb:checked\").forEach(function(cb) { body.roles.push(cb.value); });\n if (body.roles.length === 0) { showToast(__t(\"access.roleRequired\"), \"error\"); return; }\n if (!body.name) { showToast(__t(\"access.nameRequired\"), \"error\"); return; }\n\n var res;\n if (apEditingPolicyId) {\n res = await api(\"PUT\", \"/access-policies/\" + apEditingPolicyId, body);\n } else {\n res = await api(\"POST\", \"/access-policies\", body);\n }\n if (res.ok) {\n showToast(__t(\"access.policySaved\"), \"success\");\n closeDialog(\"ap-policy-dialog\");\n apLoadAll();\n }\n}\n\nasync function apDeletePolicy(id) {\n var ok = await confirmDialog({ title: __t(\"access.deletePolicy\"), message: __t(\"access.deletePolicyMsg\"), danger: true });\n if (!ok) return;\n var res = await api(\"DELETE\", \"/access-policies/\" + id);\n if (res.ok) { showToast(__t(\"access.policyDeleted\"), \"success\"); apLoadAll(); }\n}\n\n// ─── Rule Dialog ───────────────────────────────────────────────────────────\nfunction openRuleDialog(mode, id) {\n apEditingRuleId = (mode === \"edit\") ? id : null;\n document.getElementById(\"ap-rule-dialog-title\").textContent = mode === \"edit\" ? __t(\"access.editRuleTitle\") : __t(\"access.ruleTitle\");\n\n // Populate policy dropdown with all policies\n var sel = document.getElementById(\"ap-rule-policy\");\n sel.innerHTML = apPolicies.map(function(p) {\n return '<option value=\"' + escapeHtml(p.id) + '\">' + escapeHtml(p.name) + ' \\u2014 ' + escapeHtml(apAccessTypeLabel(p)) + '</option>';\n }).join(\"\");\n\n document.getElementById(\"ap-rule-pattern\").disabled = false;\n document.getElementById(\"ap-rule-priority\").disabled = false;\n\n if (mode === \"edit\") {\n var r = apRules.find(function(x) { return x.id === id; });\n if (!r) return;\n document.getElementById(\"ap-rule-pattern\").value = r.pathPattern;\n document.getElementById(\"ap-rule-priority\").value = r.priority;\n document.getElementById(\"ap-rule-remark\").value = r.remark || \"\";\n sel.value = r.accessPolicyId;\n } else {\n document.getElementById(\"ap-rule-pattern\").value = \"\";\n document.getElementById(\"ap-rule-priority\").value = \"0\";\n document.getElementById(\"ap-rule-remark\").value = \"\";\n }\n showDialog(\"ap-rule-dialog\");\n}\n\nasync function apSaveRule() {\n var body = {\n accessPolicyId: document.getElementById(\"ap-rule-policy\").value,\n remark: document.getElementById(\"ap-rule-remark\").value.trim(),\n pathPattern: document.getElementById(\"ap-rule-pattern\").value.trim(),\n priority: parseInt(document.getElementById(\"ap-rule-priority\").value, 10) || 0,\n };\n if (!body.pathPattern) { showToast(__t(\"access.patternRequired\"), \"error\"); return; }\n\n var res;\n if (apEditingRuleId) {\n res = await api(\"PUT\", \"/security-rules/\" + apEditingRuleId, body);\n } else {\n res = await api(\"POST\", \"/security-rules\", body);\n }\n if (res.ok) {\n showToast(__t(\"access.ruleSaved\"), \"success\");\n closeDialog(\"ap-rule-dialog\");\n apLoadAll();\n }\n}\n\nasync function apDeleteRule(id) {\n var ok = await confirmDialog({ title: __t(\"access.deleteRule\"), message: __t(\"access.deleteRuleMsg\"), danger: true });\n if (!ok) return;\n var res = await api(\"DELETE\", \"/security-rules/\" + id);\n if (res.ok) { showToast(__t(\"access.ruleDeleted\"), \"success\"); apLoadAll(); }\n}\n\n// ─── Register Tab ──────────────────────────────────────────────────────────\nregisterTab(\"access\", function() { apLoadAll(); });\n\n;\n\n// ─── Crop aspect ratios per logo type ──────────────────────────\nvar CROP_RATIOS = {\n 'square': 1, 'square-dark': 1, 'favicon': 1,\n 'rect': NaN, 'rect-dark': NaN,\n 'splash-portrait': 9/16, 'splash-landscape': 16/9,\n 'og-image': 40/21\n};\n// Target dimensions and output format per logo type\nvar CROP_OUTPUT = {\n 'square': { maxW: 512, maxH: 512, fmt: 'image/webp', q: 0.9, ext: 'webp' },\n 'square-dark': { maxW: 512, maxH: 512, fmt: 'image/webp', q: 0.9, ext: 'webp' },\n 'favicon': { maxW: 128, maxH: 128, fmt: 'image/png', q: 1, ext: 'png' },\n 'rect': { maxW: 800, maxH: 400, fmt: 'image/webp', q: 0.9, ext: 'webp' },\n 'rect-dark': { maxW: 800, maxH: 400, fmt: 'image/webp', q: 0.9, ext: 'webp' },\n 'og-image': { maxW: 1200, maxH: 630, fmt: 'image/jpeg', q: 0.85, ext: 'jpg' },\n 'splash-portrait': { maxW: 1080, maxH: 1920, fmt: 'image/webp', q: 0.85, ext: 'webp' },\n 'splash-landscape': { maxW: 1920, maxH: 1080, fmt: 'image/webp', q: 0.85, ext: 'webp' }\n};\nvar MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB\nvar ALLOWED_TYPES = ['image/png', 'image/jpeg', 'image/webp', 'image/svg+xml'];\n\n// ─── Lazy-load cropperjs from CDN ──────────────────────────────\nvar __cropperLoaded = false;\nfunction loadCropperJS() {\n if (__cropperLoaded) return Promise.resolve();\n return new Promise(function(resolve, reject) {\n var link = document.createElement('link');\n link.rel = 'stylesheet';\n link.href = 'https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.6.2/cropper.min.css';\n document.head.appendChild(link);\n var script = document.createElement('script');\n script.src = 'https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.6.2/cropper.min.js';\n script.onload = function() { __cropperLoaded = true; resolve(); };\n script.onerror = function() { reject(new Error('CDN load failed')); };\n document.head.appendChild(script);\n });\n}\n\n// ─── Cropper state ─────────────────────────────────────────────\nvar __cropper = null;\nvar __cropType = null;\n\n// ─── Handle file selection (entry point from branding tab) ─────\nfunction handleLogoFile(type, input) {\n var file = input.files && input.files[0];\n input.value = ''; // reset so same file can be re-selected\n if (!file) return;\n\n // Validate file size\n if (file.size > MAX_FILE_SIZE) {\n showToast(__t('branding.fileTooLarge'), 'error');\n return;\n }\n\n // Validate file type\n if (ALLOWED_TYPES.indexOf(file.type) === -1) {\n showToast(__t('branding.invalidFormat'), 'error');\n return;\n }\n\n // SVG: skip cropper, upload directly\n if (file.type === 'image/svg+xml' || file.name.toLowerCase().endsWith('.svg')) {\n doUploadLogo(type, file);\n return;\n }\n\n // Open cropper\n openCropper(type, file);\n}\n\nfunction openCropper(type, file) {\n __cropType = type;\n\n loadCropperJS().then(function() {\n var reader = new FileReader();\n reader.onload = function(e) {\n var dialog = document.getElementById('crop-dialog');\n var img = document.getElementById('crop-image');\n if (!dialog || !img) return;\n\n img.src = e.target.result;\n\n // Destroy previous cropper instance if any\n if (__cropper) { __cropper.destroy(); __cropper = null; }\n\n dialog.showModal();\n\n // Initialize cropperjs after dialog is visible (needs layout)\n setTimeout(function() {\n var ratio = CROP_RATIOS[type];\n __cropper = new Cropper(img, {\n aspectRatio: isNaN(ratio) ? NaN : ratio,\n viewMode: 1,\n autoCrop: true,\n autoCropArea: 1,\n responsive: true,\n background: false,\n guides: true,\n center: true,\n highlight: true,\n cropBoxMovable: true,\n cropBoxResizable: true,\n toggleDragModeOnDblclick: false,\n });\n }, 100);\n };\n reader.readAsDataURL(file);\n }).catch(function(err) {\n // CDN load failed — fallback to direct upload\n console.warn('Cropper load failed:', err);\n showToast(__t('branding.cropperLoadFailed'), 'error');\n doUploadLogo(type, file);\n });\n}\n\nfunction confirmCrop() {\n if (!__cropper || !__cropType) return;\n var output = CROP_OUTPUT[__cropType] || { maxW: 512, maxH: 512, fmt: 'image/webp', q: 0.9, ext: 'webp' };\n\n // Get cropped canvas at target dimensions (resize during crop for best quality)\n var canvas = __cropper.getCroppedCanvas({\n maxWidth: output.maxW,\n maxHeight: output.maxH,\n imageSmoothingEnabled: true,\n imageSmoothingQuality: 'high',\n });\n if (!canvas) {\n showToast(__t('branding.uploadError'), 'error');\n closeCropDialog();\n return;\n }\n\n // Show uploading state in dialog\n var actions = document.querySelector('#crop-dialog .dialog-actions');\n var content = document.querySelector('#crop-dialog .crop-toolbar');\n if (actions) actions.innerHTML = '<div style=\"display:flex;align-items:center;gap:12px;width:100%;justify-content:center;\"><div class=\"crop-progress\" style=\"flex:1;max-width:200px;\"><div class=\"crop-progress-bar\"></div></div><span style=\"font-size:13px;color:var(--text-secondary);\">' + __t('common.upload') + '...</span></div>';\n if (content) content.style.opacity = '0.5';\n\n canvas.toBlob(function(blob) {\n if (!blob) {\n showToast(__t('branding.uploadError'), 'error');\n closeCropDialog();\n return;\n }\n var file = new File([blob], __cropType + '.' + output.ext, { type: output.fmt });\n doUploadLogo(__cropType, file).then(function() {\n closeCropDialog();\n }).catch(function() {\n closeCropDialog();\n });\n }, output.fmt, output.q);\n}\n\nfunction cancelCrop() {\n closeCropDialog();\n}\n\nfunction closeCropDialog() {\n var dialog = document.getElementById('crop-dialog');\n if (dialog && dialog.open) dialog.close();\n if (__cropper) { __cropper.destroy(); __cropper = null; }\n __cropType = null;\n // Restore dialog actions and toolbar to initial state\n var actions = document.querySelector('#crop-dialog .dialog-actions');\n if (actions) actions.innerHTML = '<button class=\"btn btn-secondary\" onclick=\"cancelCrop()\">' + __t('branding.cropCancel') + '</button><button class=\"btn\" onclick=\"confirmCrop()\">' + __t('branding.cropConfirm') + '</button>';\n var toolbar = document.querySelector('#crop-dialog .crop-toolbar');\n if (toolbar) toolbar.style.opacity = '';\n}\n\n// ─── Toolbar actions ───────────────────────────────────────────\nfunction cropZoomIn() { if (__cropper) __cropper.zoom(0.1); }\nfunction cropZoomOut() { if (__cropper) __cropper.zoom(-0.1); }\nfunction cropRotateLeft() { if (__cropper) __cropper.rotate(-90); }\nfunction cropRotateRight() { if (__cropper) __cropper.rotate(90); }\nfunction cropFlipH() {\n if (!__cropper) return;\n var d = __cropper.getData();\n __cropper.scaleX(d.scaleX === -1 ? 1 : -1);\n}\nfunction cropFlipV() {\n if (!__cropper) return;\n var d = __cropper.getData();\n __cropper.scaleY(d.scaleY === -1 ? 1 : -1);\n}\n\n;\n\n// ─── Branding Tab ────────────────────────────────────────────────────\n\nvar __brandingApiBase = \"/.well-known/service/api\";\n\nasync function brandingApi(method, path, body) {\n try {\n var opts = { method: method, headers: {} };\n if (body !== undefined) {\n opts.headers[\"Content-Type\"] = \"application/json\";\n opts.body = JSON.stringify(body);\n }\n var res = await fetch(__brandingApiBase + path, opts);\n if (!res.ok) {\n var data = await res.json().catch(function() { return {}; });\n showToast(data.error || __t(\"common.requestFailed\"), \"error\");\n return { ok: false, error: data.error };\n }\n return await res.json();\n } catch (err) {\n showToast(__t(\"common.networkError\"), \"error\");\n return { ok: false, error: err.message };\n }\n}\n\nvar __brandingData = {};\nvar __brandingDirty = false;\nvar __pendingLogos = {}; // { camelType: r2Key } for new uploads, { camelType: null } for deletions\n\nfunction markBrandingDirty() {\n __brandingDirty = true;\n var bar = document.getElementById(\"br-save-bar\");\n if (bar) bar.classList.add(\"visible\");\n}\n\nfunction hideSaveBar() {\n __brandingDirty = false;\n var bar = document.getElementById(\"br-save-bar\");\n if (bar) bar.classList.remove(\"visible\");\n}\n\nasync function discardBrandingChanges() {\n __pendingLogos = {};\n await loadBranding();\n}\n\nasync function loadBranding() {\n var data = await brandingApi(\"GET\", \"/branding\");\n if (!data) return;\n __brandingData = data;\n\n // App Info\n var br = data.branding || {};\n document.getElementById(\"br-name\").value = br.name || \"\";\n document.getElementById(\"br-desc\").value = br.description || \"\";\n document.getElementById(\"br-url\").value = br.url || \"\";\n var pc = br.passportColor || \"#1DC1C7\";\n document.getElementById(\"br-passport-color\").value = pc;\n document.getElementById(\"br-copyright-owner\").value = (br.copyright && br.copyright.owner) || \"\";\n document.getElementById(\"br-copyright-year\").value = (br.copyright && br.copyright.year) || \"\";\n\n // Accent color\n updateAccentUI(pc);\n\n // Logos\n var logos = data.logos || {};\n var LOGO_URLS = { \"square\": \"/logo\", \"square-dark\": \"/logo-dark\", \"rect\": \"/logo-rect\", \"rect-dark\": \"/logo-rect-dark\", \"favicon\": \"/logo-favicon\", \"og-image\": \"/og-image\", \"splash-portrait\": \"/splash/portrait\", \"splash-landscape\": \"/splash/landscape\" };\n [{\"type\":\"square\",\"nameKey\":\"branding.logoAppLight\",\"subKey\":\"branding.subLight\",\"icon\":\"<svg width=\\\"20\\\" height=\\\"20\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"1.5\\\"><circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"5\\\"/><line x1=\\\"12\\\" y1=\\\"1\\\" x2=\\\"12\\\" y2=\\\"3\\\"/><line x1=\\\"12\\\" y1=\\\"21\\\" x2=\\\"12\\\" y2=\\\"23\\\"/><line x1=\\\"4.22\\\" y1=\\\"4.22\\\" x2=\\\"5.64\\\" y2=\\\"5.64\\\"/><line x1=\\\"18.36\\\" y1=\\\"18.36\\\" x2=\\\"19.78\\\" y2=\\\"19.78\\\"/><line x1=\\\"1\\\" y1=\\\"12\\\" x2=\\\"3\\\" y2=\\\"12\\\"/><line x1=\\\"21\\\" y1=\\\"12\\\" x2=\\\"23\\\" y2=\\\"12\\\"/><line x1=\\\"4.22\\\" y1=\\\"19.78\\\" x2=\\\"5.64\\\" y2=\\\"18.36\\\"/><line x1=\\\"18.36\\\" y1=\\\"5.64\\\" x2=\\\"19.78\\\" y2=\\\"4.22\\\"/></svg>\",\"spec\":\"512×512\"},{\"type\":\"square-dark\",\"nameKey\":\"branding.logoAppDark\",\"subKey\":\"branding.subDark\",\"icon\":\"<svg width=\\\"20\\\" height=\\\"20\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"1.5\\\"><path d=\\\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\\\"/></svg>\",\"spec\":\"512×512\"},{\"type\":\"rect\",\"nameKey\":\"branding.logoWebLight\",\"subKey\":\"branding.subLight\",\"icon\":\"<svg width=\\\"20\\\" height=\\\"20\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"1.5\\\"><circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"5\\\"/><line x1=\\\"12\\\" y1=\\\"1\\\" x2=\\\"12\\\" y2=\\\"3\\\"/><line x1=\\\"12\\\" y1=\\\"21\\\" x2=\\\"12\\\" y2=\\\"23\\\"/><line x1=\\\"4.22\\\" y1=\\\"4.22\\\" x2=\\\"5.64\\\" y2=\\\"5.64\\\"/><line x1=\\\"18.36\\\" y1=\\\"18.36\\\" x2=\\\"19.78\\\" y2=\\\"19.78\\\"/><line x1=\\\"1\\\" y1=\\\"12\\\" x2=\\\"3\\\" y2=\\\"12\\\"/><line x1=\\\"21\\\" y1=\\\"12\\\" x2=\\\"23\\\" y2=\\\"12\\\"/><line x1=\\\"4.22\\\" y1=\\\"19.78\\\" x2=\\\"5.64\\\" y2=\\\"18.36\\\"/><line x1=\\\"18.36\\\" y1=\\\"5.64\\\" x2=\\\"19.78\\\" y2=\\\"4.22\\\"/></svg>\",\"spec\":\"SVG/PNG\"},{\"type\":\"rect-dark\",\"nameKey\":\"branding.logoWebDark\",\"subKey\":\"branding.subDark\",\"icon\":\"<svg width=\\\"20\\\" height=\\\"20\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"1.5\\\"><path d=\\\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\\\"/></svg>\",\"spec\":\"SVG/PNG\"},{\"type\":\"favicon\",\"nameKey\":\"branding.logoFavicon\",\"subKey\":\"branding.subBrowser\",\"icon\":\"<svg width=\\\"20\\\" height=\\\"20\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"1.5\\\"><path d=\\\"M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z\\\"/></svg>\",\"spec\":\"32×32 ICO\"},{\"type\":\"splash-portrait\",\"nameKey\":\"branding.logoSplashP\",\"subKey\":\"branding.subPortrait\",\"icon\":\"<svg width=\\\"20\\\" height=\\\"20\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"1.5\\\"><rect x=\\\"5\\\" y=\\\"2\\\" width=\\\"14\\\" height=\\\"20\\\" rx=\\\"2\\\"/><circle cx=\\\"12\\\" cy=\\\"18\\\" r=\\\"1\\\" fill=\\\"currentColor\\\" stroke=\\\"none\\\"/></svg>\",\"spec\":\"9:16\"},{\"type\":\"splash-landscape\",\"nameKey\":\"branding.logoSplashL\",\"subKey\":\"branding.subLandscape\",\"icon\":\"<svg width=\\\"20\\\" height=\\\"20\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"1.5\\\"><rect x=\\\"2\\\" y=\\\"4\\\" width=\\\"20\\\" height=\\\"16\\\" rx=\\\"2\\\"/><circle cx=\\\"18\\\" cy=\\\"12\\\" r=\\\"1\\\" fill=\\\"currentColor\\\" stroke=\\\"none\\\"/></svg>\",\"spec\":\"16:9\"},{\"type\":\"og-image\",\"nameKey\":\"branding.logoOg\",\"subKey\":\"branding.subSocial\",\"icon\":\"<svg width=\\\"20\\\" height=\\\"20\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" stroke=\\\"currentColor\\\" stroke-width=\\\"1.5\\\"><circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"10\\\"/><line x1=\\\"2\\\" y1=\\\"12\\\" x2=\\\"22\\\" y2=\\\"12\\\"/><path d=\\\"M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z\\\"/></svg>\",\"spec\":\"1200×630\"}].forEach(function(l) {\n var key = l.type.replace(/-([a-z])/g, function(_, c) { return c.toUpperCase(); });\n var preview = document.getElementById(\"br-logo-preview-\" + l.type);\n var actions = document.getElementById(\"br-logo-actions-\" + l.type);\n var item = document.getElementById(\"br-logo-\" + l.type);\n // Remove any existing uploaded overlay\n var oldOverlay = item && item.querySelector(\".logo-uploaded-overlay\");\n if (oldOverlay) oldOverlay.remove();\n\n if (logos[key]) {\n var url = \"/.well-known/service/blocklet\" + (LOGO_URLS[l.type] || \"/logo\");\n preview.innerHTML = '<img src=\"' + url + '?t=' + Date.now() + '\" />';\n // Add hover overlay with Replace + Discard buttons\n if (item) {\n var overlay = document.createElement(\"div\");\n overlay.className = \"logo-uploaded-overlay\";\n // Build with DOM to avoid escaping issues\n var replaceLabel = document.createElement(\"label\");\n replaceLabel.className = \"logo-overlay-btn logo-overlay-btn-replace\";\n replaceLabel.textContent = __t(\"branding.replace\");\n var replaceInput = document.createElement(\"input\");\n replaceInput.type = \"file\";\n replaceInput.accept = \"image/png,image/jpeg,image/webp,image/svg+xml\";\n replaceInput.style.display = \"none\";\n (function(logoType) {\n replaceInput.onchange = function() { handleLogoFile(logoType, replaceInput); };\n })(l.type);\n replaceLabel.appendChild(replaceInput);\n var discardBtn = document.createElement(\"button\");\n discardBtn.className = \"logo-overlay-btn logo-overlay-btn-discard\";\n discardBtn.textContent = __t(\"branding.removeLogo\");\n (function(logoType) {\n discardBtn.onclick = function() { deleteLogo(logoType); };\n })(l.type);\n overlay.appendChild(replaceLabel);\n overlay.appendChild(discardBtn);\n item.appendChild(overlay);\n }\n } else {\n preview.innerHTML = '';\n }\n });\n\n // Languages\n var langs = data.languages || [];\n var codes = langs.map(function(l) { return l.code; });\n document.querySelectorAll(\".lang-toggle\").forEach(function(btn) {\n if (codes.indexOf(btn.dataset.lang) !== -1) btn.classList.add(\"active\");\n else btn.classList.remove(\"active\");\n });\n\n // Apple App IDs\n var appleInput = document.getElementById(\"br-apple-app-ids\");\n if (appleInput) {\n var appleIds = data.appleAppIds || [];\n appleInput.value = Array.isArray(appleIds) ? appleIds.join(\", \") : \"\";\n }\n\n hideSaveBar();\n}\n\n// Language toggle — changes are saved with the main Save Configuration button\nfunction toggleLang(code) {\n var btn = document.querySelector('.lang-toggle[data-lang=\"' + code + '\"]');\n if (!btn) return;\n // If deselecting, ensure at least one language remains active\n if (btn.classList.contains(\"active\")) {\n var activeCount = document.querySelectorAll(\".lang-toggle.active\").length;\n if (activeCount <= 1) {\n showToast(\"At least one language is required\", \"error\");\n return;\n }\n }\n btn.classList.toggle(\"active\");\n markBrandingDirty();\n}\n\n// Accent color (compact UI)\nfunction updateAccentUI(color) {\n var picker = document.getElementById(\"br-accent-picker\");\n var hex = document.getElementById(\"br-accent-hex\");\n var hidden = document.getElementById(\"br-passport-color\");\n if (picker) picker.value = color;\n if (hex) hex.value = color;\n if (hidden) hidden.value = color;\n document.querySelectorAll(\".accent-dot\").forEach(function(d) {\n if (d.dataset.color === color) d.classList.add(\"selected\");\n else d.classList.remove(\"selected\");\n });\n}\n\nfunction onAccentInput(color) {\n if (!color || !color.match(/^#[0-9a-fA-F]{6}$/)) return;\n updateAccentUI(color);\n markBrandingDirty();\n}\n\nfunction pickAccent(color) {\n updateAccentUI(color);\n markBrandingDirty();\n}\n\nasync function saveBrandingInfo() {\n var copyright = {};\n var owner = document.getElementById(\"br-copyright-owner\").value.trim();\n var year = document.getElementById(\"br-copyright-year\").value.trim();\n if (owner) copyright.owner = owner;\n if (year) copyright.year = year;\n\n var body = {\n branding: {\n name: document.getElementById(\"br-name\").value.trim(),\n description: document.getElementById(\"br-desc\").value.trim(),\n url: document.getElementById(\"br-url\").value.trim(),\n passportColor: document.getElementById(\"br-passport-color\").value,\n copyright: copyright\n }\n };\n\n // Also save languages\n var langs = [];\n var allLangs = [{\"code\":\"en\",\"name\":\"English\"},{\"code\":\"zh\",\"name\":\"简体中文\"}];\n document.querySelectorAll(\".lang-toggle.active\").forEach(function(btn) {\n var found = allLangs.find(function(l) { return l.code === btn.dataset.lang; });\n if (found) langs.push(found);\n });\n body.languages = langs;\n\n // Include pending logo changes\n if (Object.keys(__pendingLogos).length > 0) {\n body.logos = __pendingLogos;\n }\n\n // Apple App IDs\n var appleRaw = (document.getElementById(\"br-apple-app-ids\").value || \"\").trim();\n if (appleRaw) {\n body.appleAppIds = appleRaw.split(\",\").map(function(s) { return s.trim(); }).filter(Boolean);\n } else {\n body.appleAppIds = [];\n }\n\n var res = await brandingApi(\"PUT\", \"/branding\", body);\n if (res && res.ok) {\n __pendingLogos = {};\n showToast(__t(\"branding.updated\"));\n // Refresh favicon and header logo with cache-busting\n var cacheBust = \"?t=\" + Date.now();\n var icon = document.querySelector('link[rel=\"icon\"]');\n if (icon) icon.href = \"/.well-known/service/blocklet/logo-favicon\" + cacheBust;\n var headerLogo = document.querySelector('.admin-header-left img');\n if (headerLogo) headerLogo.src = headerLogo.src.split('?')[0] + cacheBust;\n await loadBranding();\n } else {\n showToast(__t(\"branding.updateFailed\"), \"error\");\n }\n}\n\nasync function doUploadLogo(type, file) {\n if (!file) return;\n var fd = new FormData();\n fd.append(\"file\", file);\n try {\n var resp = await fetch(__brandingApiBase + \"/branding/logo/\" + type, {\n method: \"POST\",\n body: fd,\n credentials: \"same-origin\"\n });\n var data = await resp.json();\n if (data.ok && data.r2Key) {\n // Store in pending state — not saved to D1 yet\n var camelType = type.replace(/-([a-z])/g, function(_, c) { return c.toUpperCase(); });\n __pendingLogos[camelType] = data.r2Key;\n // Show preview using the logo serve endpoint (R2 key is now uploaded)\n // We need to temporarily update the logos display\n var LOGO_URLS = { \"square\": \"/logo\", \"square-dark\": \"/logo-dark\", \"rect\": \"/logo-rect\", \"rect-dark\": \"/logo-rect-dark\", \"favicon\": \"/logo-favicon\", \"og-image\": \"/og-image\", \"splash-portrait\": \"/splash/portrait\", \"splash-landscape\": \"/splash/landscape\" };\n var preview = document.getElementById(\"br-logo-preview-\" + type);\n if (preview) {\n // Use a blob URL from the uploaded file for instant preview\n var blobUrl = URL.createObjectURL(file);\n preview.innerHTML = '<img src=\"' + blobUrl + '\" />';\n }\n // Add uploaded overlay if not exists\n var item = document.getElementById(\"br-logo-\" + type);\n if (item) {\n var oldOverlay = item.querySelector(\".logo-uploaded-overlay\");\n if (oldOverlay) oldOverlay.remove();\n var overlay = document.createElement(\"div\");\n overlay.className = \"logo-uploaded-overlay\";\n var rl = document.createElement(\"label\");\n rl.className = \"logo-overlay-btn logo-overlay-btn-replace\";\n rl.textContent = __t(\"branding.replace\");\n var ri = document.createElement(\"input\");\n ri.type = \"file\"; ri.accept = \"image/png,image/jpeg,image/webp,image/svg+xml\"; ri.style.display = \"none\";\n (function(t) { ri.onchange = function() { handleLogoFile(t, ri); }; })(type);\n rl.appendChild(ri);\n var db = document.createElement(\"button\");\n db.className = \"logo-overlay-btn logo-overlay-btn-discard\";\n db.textContent = __t(\"branding.removeLogo\");\n (function(t) { db.onclick = function() { deleteLogo(t); }; })(type);\n overlay.appendChild(rl);\n overlay.appendChild(db);\n item.appendChild(overlay);\n }\n showToast(__t(\"branding.logoUploaded\"));\n markBrandingDirty();\n } else {\n showToast(data.error || \"Upload failed\", \"error\");\n }\n } catch(e) {\n showToast(__t(\"branding.uploadError\"), \"error\");\n }\n}\n\nasync function deleteLogo(type) {\n if (!confirm(__t(\"branding.deleteLogo\"))) return;\n var camelType = type.replace(/-([a-z])/g, function(_, c) { return c.toUpperCase(); });\n __pendingLogos[camelType] = null; // mark for deletion on save\n // Clear preview\n var preview = document.getElementById(\"br-logo-preview-\" + type);\n if (preview) preview.innerHTML = '';\n var item = document.getElementById(\"br-logo-\" + type);\n if (item) {\n var overlay = item.querySelector(\".logo-uploaded-overlay\");\n if (overlay) overlay.remove();\n }\n showToast(__t(\"branding.logoDeleted\"));\n markBrandingDirty();\n}\n\nasync function saveBrandingLanguages() {\n var langs = [];\n var allLangs = [{\"code\":\"en\",\"name\":\"English\"},{\"code\":\"zh\",\"name\":\"简体中文\"}];\n document.querySelectorAll(\".lang-toggle.active\").forEach(function(btn) {\n var found = allLangs.find(function(l) { return l.code === btn.dataset.lang; });\n if (found) langs.push(found);\n });\n var res = await brandingApi(\"PUT\", \"/branding\", { languages: langs });\n if (res && res.ok) showToast(__t(\"branding.languagesUpdated\"));\n else showToast(__t(\"branding.langUpdateFailed\"), \"error\");\n}\n\nregisterTab(\"branding\", loadBranding);\n\n;\n\n// ─── Appearance Tab ──────────────────────────────────────────────────\n\nvar __appearanceApiBase = \"/.well-known/service/api\";\n\nasync function appearanceApi(method, path, body) {\n try {\n var opts = { method: method, headers: {} };\n if (body !== undefined) {\n opts.headers[\"Content-Type\"] = \"application/json\";\n opts.body = JSON.stringify(body);\n }\n var res = await fetch(__appearanceApiBase + path, opts);\n if (!res.ok) {\n var data = await res.json().catch(function() { return {}; });\n showToast(data.error || __t(\"common.requestFailed\"), \"error\");\n return { ok: false, error: data.error };\n }\n return await res.json();\n } catch (err) {\n showToast(__t(\"common.networkError\"), \"error\");\n return { ok: false, error: err.message };\n }\n}\n\nvar __themeData = {};\nvar __themeJsonOriginal = \"\";\nvar __navItems = [];\n\nasync function loadAppearance() {\n document.getElementById(\"ap-nav-list\").innerHTML = skeletonRows(3);\n // Load theme\n var tRes = await appearanceApi(\"GET\", \"/theme\");\n if (tRes) {\n __themeData = tRes.theme || {};\n populateThemeForm(__themeData);\n }\n\n // Load navigation\n var nRes = await appearanceApi(\"GET\", \"/navigation\");\n if (nRes) {\n __navItems = nRes.navigation || [];\n renderNavList();\n }\n}\n\nfunction populateThemeForm(theme) {\n // Try to extract from concepts-based format\n var concept = null;\n if (theme.concepts && theme.currentConceptId) {\n concept = theme.concepts.find(function(c) { return c.id === theme.currentConceptId; });\n }\n\n var prefer = concept ? (concept.prefer || \"system\") : (theme.prefer || \"system\");\n document.getElementById(\"ap-theme-mode\").value = prefer;\n\n var light = concept ? (concept.themeConfig && concept.themeConfig.light || {}) : (theme.light || {});\n var dark = concept ? (concept.themeConfig && concept.themeConfig.dark || {}) : (theme.dark || {});\n\n // Light palette\n setColorSafe(\"ap-light-primary\", light.palette && light.palette.primary && light.palette.primary.main, \"#1976d2\");\n setColorSafe(\"ap-light-secondary\", light.palette && light.palette.secondary && light.palette.secondary.main, \"#9c27b0\");\n setColorSafe(\"ap-light-bg\", light.palette && light.palette.background && light.palette.background.default, \"#ffffff\");\n setColorSafe(\"ap-light-text\", light.palette && light.palette.text && light.palette.text.primary, \"#212121\");\n\n // Dark palette\n setColorSafe(\"ap-dark-primary\", dark.palette && dark.palette.primary && dark.palette.primary.main, \"#90caf9\");\n setColorSafe(\"ap-dark-secondary\", dark.palette && dark.palette.secondary && dark.palette.secondary.main, \"#ce93d8\");\n setColorSafe(\"ap-dark-bg\", dark.palette && dark.palette.background && dark.palette.background.default, \"#121212\");\n setColorSafe(\"ap-dark-text\", dark.palette && dark.palette.text && dark.palette.text.primary, \"#ffffff\");\n\n // Lock\n document.getElementById(\"ap-theme-lock\").checked = !!(theme.meta && theme.meta.locked);\n\n // JSON editor\n var jsonStr = JSON.stringify(theme, null, 2);\n document.getElementById(\"ap-theme-json\").value = jsonStr;\n __themeJsonOriginal = jsonStr;\n}\n\nfunction setColorSafe(id, value, fallback) {\n var el = document.getElementById(id);\n if (!el) return;\n // color inputs need hex format\n if (value && value.charAt(0) === \"#\" && (value.length === 4 || value.length === 7)) {\n el.value = value;\n } else {\n el.value = fallback;\n }\n}\n\nfunction buildThemeFromForm() {\n var prefer = document.getElementById(\"ap-theme-mode\").value;\n var locked = document.getElementById(\"ap-theme-lock\").checked;\n\n // Only use JSON editor if user manually modified it\n var jsonText = document.getElementById(\"ap-theme-json\").value.trim();\n var jsonModified = jsonText && jsonText !== __themeJsonOriginal;\n if (jsonModified) {\n try {\n var parsed = JSON.parse(jsonText);\n // Add prefer and locked from form controls\n if (parsed.concepts && parsed.currentConceptId) {\n var c = parsed.concepts.find(function(c) { return c.id === parsed.currentConceptId; });\n if (c) c.prefer = prefer;\n } else {\n parsed.prefer = prefer;\n }\n parsed.meta = { locked: locked };\n return parsed;\n } catch(e) {\n showToast(__t(\"appearance.invalidJson\"), \"error\");\n return null;\n }\n }\n\n // Build concepts-based theme from color pickers\n var conceptId = (__themeData.currentConceptId) || \"default\";\n return {\n concepts: [{\n id: conceptId,\n name: \"Custom\",\n prefer: prefer,\n themeConfig: {\n light: {\n palette: {\n primary: { main: document.getElementById(\"ap-light-primary\").value },\n secondary: { main: document.getElementById(\"ap-light-secondary\").value },\n background: { default: document.getElementById(\"ap-light-bg\").value },\n text: { primary: document.getElementById(\"ap-light-text\").value }\n }\n },\n dark: {\n palette: {\n primary: { main: document.getElementById(\"ap-dark-primary\").value },\n secondary: { main: document.getElementById(\"ap-dark-secondary\").value },\n background: { default: document.getElementById(\"ap-dark-bg\").value },\n text: { primary: document.getElementById(\"ap-dark-text\").value }\n }\n },\n common: {}\n }\n }],\n currentConceptId: conceptId,\n meta: { locked: locked }\n };\n}\n\nasync function saveTheme() {\n var theme = buildThemeFromForm();\n if (!theme) return;\n var res = await appearanceApi(\"PUT\", \"/theme\", { theme: theme });\n if (res && res.ok) showToast(__t(\"appearance.themeUpdated\"));\n else showToast((res && res.error) || \"Failed to update theme\", \"error\");\n}\n\n// ─── Navigation ──────────────────────────────────────────────────────\n\nfunction renderNavList() {\n var list = document.getElementById(\"ap-nav-list\");\n if (!list) return;\n if (__navItems.length === 0) {\n list.innerHTML = '<p style=\"color:var(--color-text-muted);font-size:13px;padding:8px 0;\">' + __t(\"appearance.noNavItems\") + '</p>';\n return;\n }\n list.innerHTML = __navItems.map(function(item, i) {\n return '<div class=\"nav-item\" style=\"display:flex;align-items:center;gap:8px;padding:6px 0;border-bottom:1px solid var(--color-border);\">' +\n '<span style=\"min-width:24px;text-align:center;color:var(--color-text-muted);\">' + (i+1) + '</span>' +\n '<span style=\"flex:1;font-size:14px;\">' + escapeHtml(item.title || \"(untitled)\") + '</span>' +\n '<span style=\"color:var(--color-text-muted);font-size:12px;\">' + escapeHtml(item.link || \"\") + '</span>' +\n '<button class=\"btn btn-sm\" onclick=\"editNavItem(' + i + ')\" style=\"padding:2px 8px;\">' + __t(\"common.edit\") + '</button>' +\n '<button class=\"btn btn-sm\" onclick=\"moveNavItem(' + i + ',-1)\" style=\"padding:2px 6px;\" ' + (i === 0 ? \"disabled\" : \"\") + '>' + __t(\"appearance.up\") + '</button>' +\n '<button class=\"btn btn-sm\" onclick=\"moveNavItem(' + i + ',1)\" style=\"padding:2px 6px;\" ' + (i === __navItems.length - 1 ? \"disabled\" : \"\") + '>' + __t(\"appearance.down\") + '</button>' +\n '<button class=\"btn btn-sm btn-danger\" onclick=\"removeNavItem(' + i + ')\" style=\"padding:2px 8px;\">' + __t(\"common.delete\") + '</button>' +\n '</div>';\n }).join(\"\");\n}\n\nfunction escapeHtml(str) {\n return String(str).replace(/&/g,\"&\").replace(/</g,\"<\").replace(/>/g,\">\").replace(/\"/g,\""\");\n}\n\nfunction addNavItem() {\n var title = prompt(__t(\"appearance.navTitlePrompt\"));\n if (title === null) return;\n var link = prompt(__t(\"appearance.navLinkPrompt\"), \"/\");\n if (link === null) return;\n __navItems.push({ title: title, link: link });\n renderNavList();\n}\n\nfunction editNavItem(i) {\n var item = __navItems[i];\n if (!item) return;\n var title = prompt(__t(\"appearance.navTitlePrompt\"), item.title || \"\");\n if (title === null) return;\n var link = prompt(__t(\"appearance.navLinkPrompt\"), item.link || \"\");\n if (link === null) return;\n item.title = title;\n item.link = link;\n renderNavList();\n}\n\nfunction removeNavItem(i) {\n if (!confirm(__t(\"appearance.deleteNavItem\"))) return;\n __navItems.splice(i, 1);\n renderNavList();\n}\n\nfunction moveNavItem(i, dir) {\n var j = i + dir;\n if (j < 0 || j >= __navItems.length) return;\n var tmp = __navItems[i];\n __navItems[i] = __navItems[j];\n __navItems[j] = tmp;\n renderNavList();\n}\n\nasync function saveNavigation() {\n var res = await appearanceApi(\"PUT\", \"/navigation\", { navigation: __navItems });\n if (res && res.ok) showToast(__t(\"appearance.navUpdated\"));\n else showToast((res && res.error) || \"Failed to update navigation\", \"error\");\n}\n\nregisterTab(\"appearance\", loadAppearance);\n\n;\n\n// ─── Settings tab ─────────────────────────────────────────────────\n\nvar __settingsData = null;\nvar __oauthEditMode = false;\nvar __providerMeta = {\"google\":{\"name\":\"Google\",\"icon\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"18\\\" height=\\\"18\\\" viewBox=\\\"0 0 256 262\\\"><path fill=\\\"#4285F4\\\" d=\\\"M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622l38.755 30.023l2.685.268c24.659-22.774 38.875-56.282 38.875-96.027\\\"/><path fill=\\\"#34A853\\\" d=\\\"M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055c-34.523 0-63.824-22.773-74.269-54.25l-1.531.13l-40.298 31.187l-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1\\\"/><path fill=\\\"#FBBC05\\\" d=\\\"M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82c0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602z\\\"/><path fill=\\\"#EB4335\\\" d=\\\"M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0C79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251\\\"/></svg>\"},\"github\":{\"name\":\"GitHub\",\"icon\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"18\\\" height=\\\"18\\\" viewBox=\\\"0 0 256 250\\\" fill=\\\"currentColor\\\"><path d=\\\"M128.001 0C57.317 0 0 57.307 0 128.001c0 56.554 36.676 104.535 87.535 121.46c6.397 1.185 8.746-2.777 8.746-6.158c0-3.052-.12-13.135-.174-23.83c-35.61 7.742-43.124-15.103-43.124-15.103c-5.823-14.795-14.213-18.73-14.213-18.73c-11.613-7.944.876-7.78.876-7.78c12.853.902 19.621 13.19 19.621 13.19c11.417 19.568 29.945 13.911 37.249 10.64c1.149-8.272 4.466-13.92 8.127-17.116c-28.431-3.236-58.318-14.212-58.318-63.258c0-13.975 5-25.394 13.188-34.358c-1.329-3.224-5.71-16.242 1.24-33.874c0 0 10.749-3.44 35.21 13.121c10.21-2.836 21.16-4.258 32.038-4.307c10.878.049 21.837 1.47 32.066 4.307c24.431-16.56 35.165-13.12 35.165-13.12c6.967 17.63 2.584 30.65 1.255 33.873c8.207 8.964 13.173 20.383 13.173 34.358c0 49.163-29.944 59.988-58.447 63.157c4.591 3.972 8.682 11.762 8.682 23.704c0 17.126-.148 30.91-.148 35.126c0 3.407 2.304 7.398 8.792 6.14C219.37 232.5 256 184.537 256 128.002C256 57.307 198.691 0 128.001 0\\\"/></svg>\"},\"apple\":{\"name\":\"Apple\",\"icon\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"18\\\" height=\\\"18\\\" viewBox=\\\"0 0 256 315\\\" fill=\\\"currentColor\\\"><path d=\\\"M213.803 167.03c.442 47.58 41.74 63.413 42.197 63.615c-.35 1.116-6.599 22.563-21.757 44.716c-13.104 19.153-26.705 38.235-48.13 38.63c-21.05.388-27.82-12.483-51.888-12.483c-24.061 0-31.582 12.088-51.51 12.871c-20.68.783-36.428-20.71-49.64-39.793c-27-39.033-47.633-110.3-19.928-158.406c13.763-23.89 38.36-39.017 65.056-39.405c20.307-.388 39.475 13.675 51.889 13.675c12.406 0 35.699-16.895 60.186-14.414c10.25.427 39.026 4.14 57.503 31.186c-1.49.923-34.335 20.044-33.978 59.808M174.24 50.199c10.98-13.29 18.369-31.79 16.353-50.199c-15.826.636-34.962 10.546-46.314 23.828c-10.173 11.763-19.082 30.589-16.678 48.633c17.64 1.365 35.66-8.964 46.639-22.262\\\"/></svg>\"},\"twitter\":{\"name\":\"Twitter\",\"icon\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"18\\\" height=\\\"18\\\" viewBox=\\\"0 0 256 256\\\" fill=\\\"currentColor\\\"><path d=\\\"M149.079 108.399L242.33 0h-22.098l-80.97 94.12L74.59 0H0l97.796 142.328L0 256h22.1l85.507-99.395L175.905 256h74.59L149.073 108.399zM118.81 143.58l-9.909-14.172l-78.84-112.773h33.943l63.625 91.011l9.909 14.173l82.705 118.3H186.3l-67.49-96.533z\\\"/></svg>\"},\"auth0\":{\"name\":\"Auth0\",\"icon\":\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"18\\\" height=\\\"18\\\" viewBox=\\\"0 0 256 256\\\"><path fill=\\\"#EB5424\\\" d=\\\"M203.97 200.38L167.47 88l-39.5 112.38h75.98zm0-144.76L167.47 168l75.97-26.16l19.76-60.82c6.64-20.4-1.94-42.72-21.18-52.76l-38.06 27.36zM52.03 55.62L88.54 168l39.5-112.38H52.02zM52.03 200.38L88.54 88L12.56 114.16l-5.92 18.2c-6.64 20.4 1.94 42.72 21.18 52.76l24.2-17.38l.01.01v-.01l-.01.01.01-.37z\\\"/></svg>\"}};\n\nfunction loadSettings() {\n document.getElementById(\"st-oauth-list\").innerHTML = skeletonRows(3);\n api(\"GET\", \"/settings\").then(function(data) {\n if (!data.ok) return;\n __settingsData = data.settings;\n renderOAuthProviders();\n renderBuiltinProviders();\n renderSessionConfig();\n renderEmailConfig();\n fedLoad();\n });\n}\n\n// ─── OAuth Providers (card grid) ─────────────────────────────────\n\nfunction renderOAuthProviders() {\n var wrap = document.getElementById(\"st-oauth-list\");\n if (!wrap || !__settingsData) return;\n var providers = __settingsData.oauthProviders || {};\n var keys = Object.keys(providers);\n if (keys.length === 0) {\n wrap.innerHTML = '<div class=\"settings-row\" style=\"color:var(--text-tertiary)\">' + __t(\"settings.noOAuth\") + '</div>';\n return;\n }\n keys.sort(function(a, b) {\n return (providers[a].order || 999) - (providers[b].order || 999);\n });\n var html = \"\";\n for (var i = 0; i < keys.length; i++) {\n var name = keys[i];\n var p = providers[name];\n var meta = __providerMeta[name];\n var displayName = meta ? meta.name : (name.charAt(0).toUpperCase() + name.slice(1));\n var iconHtml = meta && meta.icon ? '<span class=\"st-oauth-card-icon\">' + meta.icon + '</span>' : \"\";\n var statusDot = p.enabled !== false\n ? '<span class=\"st-oauth-dot st-oauth-dot-on\"></span>'\n : '<span class=\"st-oauth-dot st-oauth-dot-off\"></span>';\n html += '<div class=\"st-oauth-card\" onclick=\"openOAuthDialog(\\'edit\\',\\'' + escapeHtml(name) + '\\')\" title=\"' + escapeHtml(displayName) + '\">'\n + iconHtml\n + '<span class=\"st-oauth-card-name\">' + escapeHtml(displayName) + '</span>'\n + statusDot\n + '</div>';\n }\n wrap.innerHTML = html;\n}\n\nfunction escapeHtml(s) {\n var d = document.createElement(\"div\");\n d.textContent = s;\n return d.innerHTML;\n}\n\nfunction renderProviderPicker(selectedProvider) {\n var picker = document.getElementById(\"st-oauth-provider-picker\");\n if (!picker) return;\n var configuredProviders = (__settingsData && __settingsData.oauthProviders) ? __settingsData.oauthProviders : {};\n var html = \"\";\n var keys = Object.keys(__providerMeta);\n for (var i = 0; i < keys.length; i++) {\n var id = keys[i];\n var meta = __providerMeta[id];\n var isSelected = id === selectedProvider;\n var isAdded = !__oauthEditMode && !!configuredProviders[id];\n var cls = \"provider-card\";\n if (isSelected) cls += \" selected\";\n if (isAdded) cls += \" added\";\n var badge = isAdded ? '<span class=\"provider-added-badge\">' + __t(\"settings.added\") + '</span>' : \"\";\n var onclick = isAdded ? \"\" : 'onclick=\"selectOAuthProvider(\\'' + id + '\\')\"';\n html += '<div class=\"' + cls + '\" data-provider=\"' + id + '\" ' + onclick + '>'\n + '<span class=\"provider-icon\">' + meta.icon + '</span>'\n + '<span class=\"provider-name\">' + escapeHtml(meta.name) + '</span>'\n + badge\n + '</div>';\n }\n picker.innerHTML = html;\n}\n\nfunction selectOAuthProvider(id) {\n document.getElementById(\"st-oauth-provider\").value = id;\n renderProviderPicker(id);\n onOAuthProviderChange();\n}\n\nfunction openOAuthDialog(mode, providerName) {\n __oauthEditMode = mode === \"edit\";\n var title = document.getElementById(\"st-oauth-dialog-title\");\n title.textContent = __oauthEditMode ? __t(\"settings.editOAuthTitle\") : __t(\"settings.addOAuthTitle\");\n\n var defaultProvider = \"google\";\n if (__oauthEditMode && providerName) {\n defaultProvider = providerName;\n } else if (__settingsData && __settingsData.oauthProviders) {\n var configured = __settingsData.oauthProviders;\n var keys = Object.keys(__providerMeta);\n for (var i = 0; i < keys.length; i++) {\n if (!configured[keys[i]]) { defaultProvider = keys[i]; break; }\n }\n }\n document.getElementById(\"st-oauth-provider\").value = defaultProvider;\n\n var picker = document.getElementById(\"st-oauth-provider-picker\");\n var display = document.getElementById(\"st-oauth-provider-display\");\n if (__oauthEditMode) {\n picker.style.display = \"none\";\n var meta = __providerMeta[defaultProvider] || { name: defaultProvider, icon: \"\" };\n display.style.display = \"flex\";\n display.innerHTML = '<span style=\"display:inline-flex;width:20px;height:20px\">' + meta.icon + '</span>'\n + '<span style=\"font-size:14px;font-weight:500;color:var(--text)\">' + escapeHtml(meta.name) + '</span>';\n } else {\n picker.style.display = \"\";\n display.style.display = \"none\";\n renderProviderPicker(defaultProvider);\n }\n\n document.getElementById(\"st-oauth-clientid\").value = \"\";\n document.getElementById(\"st-oauth-secret\").value = \"\";\n document.getElementById(\"st-oauth-teamid\").value = \"\";\n document.getElementById(\"st-oauth-keyid\").value = \"\";\n document.getElementById(\"st-oauth-privatekey\").value = \"\";\n document.getElementById(\"st-oauth-serviceid\").value = \"\";\n document.getElementById(\"st-oauth-bundleid\").value = \"\";\n document.getElementById(\"st-oauth-domain\").value = \"\";\n document.getElementById(\"st-oauth-enabled\").checked = true;\n document.getElementById(\"st-oauth-order\").value = \"0\";\n\n if (__oauthEditMode && providerName && __settingsData) {\n var p = __settingsData.oauthProviders[providerName];\n if (p) {\n document.getElementById(\"st-oauth-clientid\").value = p.clientId || \"\";\n document.getElementById(\"st-oauth-secret\").value = p.clientSecret || \"\";\n document.getElementById(\"st-oauth-teamid\").value = p.teamId || \"\";\n document.getElementById(\"st-oauth-keyid\").value = p.keyId || \"\";\n document.getElementById(\"st-oauth-privatekey\").value = p.privateKey || \"\";\n document.getElementById(\"st-oauth-serviceid\").value = p.serviceId || \"\";\n document.getElementById(\"st-oauth-bundleid\").value = p.bundleId || \"\";\n document.getElementById(\"st-oauth-domain\").value = p.domain || \"\";\n document.getElementById(\"st-oauth-enabled\").checked = p.enabled !== false;\n document.getElementById(\"st-oauth-order\").value = String(p.order ?? 0);\n }\n }\n\n onOAuthProviderChange();\n updateCallbackUrl();\n showDialog(\"st-oauth-dialog\");\n}\n\nfunction onOAuthProviderChange() {\n var provider = document.getElementById(\"st-oauth-provider\").value;\n var appleFields = document.querySelectorAll('[data-oauth-field=\"apple\"]');\n var auth0Fields = document.querySelectorAll('[data-oauth-field=\"auth0\"]');\n for (var i = 0; i < appleFields.length; i++) {\n appleFields[i].style.display = provider === \"apple\" ? \"\" : \"none\";\n }\n for (var i = 0; i < auth0Fields.length; i++) {\n auth0Fields[i].style.display = provider === \"auth0\" ? \"\" : \"none\";\n }\n updateCallbackUrl();\n}\n\nfunction updateCallbackUrl() {\n var provider = document.getElementById(\"st-oauth-provider\").value;\n var el = document.getElementById(\"st-oauth-callback\");\n if (el) el.textContent = location.origin + \"/.well-known/service/oauth/callback/\" + provider;\n}\n\nfunction copyCallbackUrl() {\n var el = document.getElementById(\"st-oauth-callback\");\n var btn = document.getElementById(\"st-copy-callback-btn\");\n if (el && btn && navigator.clipboard) {\n var originalText = btn.textContent;\n navigator.clipboard.writeText(el.textContent).then(function() {\n btn.innerHTML = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" style=\"vertical-align:middle\"><path d=\"M5 13l4 4L19 7\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg> ' + __t(\"common.copied\");\n btn.disabled = true;\n setTimeout(function() { btn.textContent = originalText; btn.disabled = false; }, 2000);\n });\n }\n}\n\nfunction saveOAuthProvider() {\n var provider = document.getElementById(\"st-oauth-provider\").value;\n var body = {\n clientId: document.getElementById(\"st-oauth-clientid\").value,\n clientSecret: document.getElementById(\"st-oauth-secret\").value,\n enabled: document.getElementById(\"st-oauth-enabled\").checked,\n order: parseInt(document.getElementById(\"st-oauth-order\").value, 10) || 0,\n };\n\n if (provider === \"apple\") {\n body.teamId = document.getElementById(\"st-oauth-teamid\").value;\n body.keyId = document.getElementById(\"st-oauth-keyid\").value;\n body.privateKey = document.getElementById(\"st-oauth-privatekey\").value;\n body.serviceId = document.getElementById(\"st-oauth-serviceid\").value;\n body.bundleId = document.getElementById(\"st-oauth-bundleid\").value;\n }\n if (provider === \"auth0\") {\n body.domain = document.getElementById(\"st-oauth-domain\").value;\n }\n\n api(\"PUT\", \"/settings/oauth/\" + provider, body).then(function(data) {\n if (data.ok) {\n closeDialog(\"st-oauth-dialog\");\n showToast(__t(\"settings.oauthSaved\"), \"success\");\n loadSettings();\n }\n });\n}\n\nasync function deleteOAuthProvider(name) {\n var ok = await confirmDialog({ title: __t(\"settings.deleteProvider\", { name: name }), message: __t(\"settings.deleteProviderMsg\"), confirmLabel: __t(\"common.delete\"), danger: true });\n if (!ok) return;\n var data = await api(\"DELETE\", \"/settings/oauth/\" + name);\n if (data.ok) {\n showToast(__t(\"settings.providerDeleted\"), \"success\");\n loadSettings();\n }\n}\n\n// ─── Built-in Providers ──────────────────────────────────────────\n\nfunction renderBuiltinProviders() {\n if (!__settingsData) return;\n var bp = __settingsData.builtinProviders || {};\n var email = __settingsData.email || {};\n var emailConfigured = !!(email.resendApiKey && email.fromAddress);\n\n document.getElementById(\"st-bp-passkey\").checked = bp.passkey ? bp.passkey.enabled !== false : true;\n document.getElementById(\"st-bp-email\").checked = bp.email ? bp.email.enabled !== false : true;\n document.getElementById(\"st-bp-wallet\").checked = bp.wallet ? bp.wallet.enabled !== false : true;\n\n // Show hint if email not configured\n var hint = document.getElementById(\"st-email-hint\");\n var emailToggle = document.getElementById(\"st-bp-email\");\n if (hint && emailToggle) {\n if (!emailConfigured) {\n hint.classList.remove(\"hidden\");\n emailToggle.disabled = true;\n emailToggle.checked = false;\n } else {\n hint.classList.add(\"hidden\");\n emailToggle.disabled = false;\n }\n }\n}\n\nfunction saveBuiltinProviders() {\n var body = {\n passkey: { enabled: document.getElementById(\"st-bp-passkey\").checked },\n email: { enabled: document.getElementById(\"st-bp-email\").checked },\n wallet: { enabled: document.getElementById(\"st-bp-wallet\").checked },\n };\n api(\"PUT\", \"/settings/builtin-providers\", body).then(function(data) {\n if (data.ok) showToast(__t(\"settings.saved\"), \"success\");\n });\n}\n\n// ─── Session ─────────────────────────────────────────────────────\n\nfunction updateSessionLabel() {\n var ttl = document.getElementById(\"st-session-ttl\").value;\n document.getElementById(\"st-session-lifetime-label\").textContent = __t(\"settings.jwtLifetime\", { days: ttl });\n document.getElementById(\"st-session-ttl\").title = __t(\"settings.jwtLifetime\", { days: ttl });\n}\n\nfunction renderSessionConfig() {\n if (!__settingsData) return;\n var s = __settingsData.session || {};\n var ttl = s.jwtTtlDays || 7;\n document.getElementById(\"st-session-ttl\").value = ttl;\n updateSessionLabel();\n}\n\nfunction saveSessionConfig() {\n var ttl = parseInt(document.getElementById(\"st-session-ttl\").value, 10);\n api(\"PUT\", \"/settings/session\", { jwtTtlDays: ttl }).then(function(data) {\n if (data.ok) showToast(__t(\"settings.sessionSaved\"), \"success\");\n });\n}\n\n// ─── Email ───────────────────────────────────────────────────────\n\nfunction renderEmailConfig() {\n if (!__settingsData) return;\n var e = __settingsData.email || {};\n document.getElementById(\"st-email-apikey\").value = e.resendApiKey || \"\";\n document.getElementById(\"st-email-from\").value = e.fromAddress || \"\";\n}\n\nfunction saveEmailConfig() {\n var body = {\n resendApiKey: document.getElementById(\"st-email-apikey\").value,\n fromAddress: document.getElementById(\"st-email-from\").value,\n };\n api(\"PUT\", \"/settings/email\", body).then(function(data) {\n if (data.ok) {\n showToast(__t(\"settings.emailSaved\"), \"success\");\n // Re-render builtin providers to update email toggle state\n loadSettings();\n }\n });\n}\n\nregisterTab(\"settings\", loadSettings);\n\n\n// ─── Federation (Unified Login) ──────────────────────────────\n\nvar __fedData = null;\nvar __fedLoading = false;\n\nvar FED_ICON_REMOVE = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 6h18\"/><path d=\"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6\"/><path d=\"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2\"/></svg>';\nvar FED_ICON_REFRESH = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\"/><path d=\"M3 3v5h5\"/><path d=\"M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16\"/><path d=\"M21 21v-5h-5\"/></svg>';\nvar FED_ICON_CHECK = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"/></svg>';\nvar FED_ICON_UNLINK = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M15 7h3a5 5 0 0 1 0 10h-3m-6 0H6a5 5 0 0 1 0-10h3\"/><line x1=\"2\" y1=\"2\" x2=\"22\" y2=\"22\"/></svg>';\nvar FED_ICON_DISABLE = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"4.93\" y1=\"4.93\" x2=\"19.07\" y2=\"19.07\"/></svg>';\nvar FED_ICON_ENABLE = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18.36 6.64a9 9 0 1 1-12.73 0\"/><line x1=\"12\" y1=\"2\" x2=\"12\" y2=\"12\"/></svg>';\n\nfunction fedIconBtn(icon, title, onclick, variant) {\n var cls = variant === \"danger\" ? \"ap-icon-btn ap-icon-btn-danger\" : variant === \"success\" ? \"ap-icon-btn ap-icon-btn-success\" : \"ap-icon-btn\";\n return '<button class=\"' + cls + '\" title=\"' + fedEscAttr(title) + '\" onclick=\"' + onclick + '\">' + icon + '</button>';\n}\n\nfunction fedNormalizeUrl(input) {\n var s = (input || \"\").trim();\n if (!s) return \"\";\n if (!/^https?:\\/\\//i.test(s)) s = \"https://\" + s;\n try { return new URL(s).origin; } catch(e) { return s; }\n}\n\nfunction fedFormatUrl(el) {\n clearTimeout(el._fedTimer);\n el._fedTimer = setTimeout(function() {\n var v = el.value.trim();\n if (!v) return;\n var norm = fedNormalizeUrl(v);\n if (norm && norm !== v && norm !== \"https://\") {\n el.value = norm;\n }\n }, 800);\n}\n\nfunction fedLoad() {\n api(\"GET\", \"/admin/federation\").then(function(data) {\n if (!data || data.error) {\n __fedData = { role: null, enabled: false, sites: [], delegation: { exists: false } };\n } else {\n __fedData = data;\n }\n fedRender();\n });\n}\n\nfunction fedRender() {\n var el = document.getElementById(\"fed-section\");\n if (!el || !__fedData) return;\n if (!__fedData.role) {\n el.innerHTML = fedRenderUnconfigured();\n } else if (__fedData.role === \"master\") {\n el.innerHTML = fedRenderMaster();\n } else {\n el.innerHTML = fedRenderMember();\n }\n}\n\nfunction fedRenderUnconfigured() {\n return '<p class=\"settings-card-desc\">' + __t(\"federation.desc\") + '</p>'\n + '<div class=\"fed-init-buttons\">'\n + '<button class=\"btn\" id=\"fed-enable-btn\" onclick=\"fedEnable()\"' + (__fedLoading ? ' disabled' : '') + '>'\n + (__fedLoading ? '<span class=\"spinner\"></span> ' : '') + __t(\"federation.enableMaster\") + '</button>'\n + '<button class=\"btn btn-secondary\" onclick=\"fedOpenJoin()\"' + (__fedLoading ? ' disabled' : '') + '>'\n + __t(\"federation.joinMember\") + '</button>'\n + '</div>';\n}\n\nfunction fedRenderMaster() {\n var html = '<div style=\"padding:12px 20px;display:flex;align-items:center;gap:8px\">'\n + '<span class=\"fed-status-badge master\">★ ' + __t(\"federation.masterSite\") + '</span>'\n + '<span style=\"font-size:13px;color:var(--text-secondary)\">' + __t(\"federation.masterDesc\") + '</span>'\n + '</div>';\n\n // Sites header with refresh button\n html += '<div style=\"padding:0 20px 8px;display:flex;justify-content:space-between;align-items:center\">'\n + '<span style=\"font-size:13px;font-weight:600;color:var(--text)\">' + __t(\"federation.sites\") + '</span>'\n + '<button class=\"ap-icon-btn\" title=\"' + __t(\"federation.refresh\") + '\" onclick=\"fedRefresh()\" id=\"fed-refresh-btn\">' + FED_ICON_REFRESH + '</button>'\n + '</div>';\n\n // Sort: master first, then approved, then pending\n var sites = (__fedData.sites || []).slice().sort(function(a, b) {\n if (a.isMaster) return -1;\n if (b.isMaster) return 1;\n if (a.status === \"pending\" && b.status !== \"pending\") return 1;\n if (b.status === \"pending\" && a.status !== \"pending\") return -1;\n return 0;\n });\n var currentOrigin = location.origin;\n for (var i = 0; i < sites.length; i++) {\n var s = sites[i];\n var star = s.isMaster ? '<span class=\"fed-star\">★</span> ' : '';\n var isMe = s.url && fedNormalizeUrl(s.url) === currentOrigin;\n var meTag = isMe ? ' <span style=\"font-size:10px;padding:1px 5px;border-radius:999px;background:var(--blue-light,#dbeafe);color:var(--blue-muted,#1d4ed8);font-weight:500\">' + __t(\"federation.currentSite\") + '</span>' : '';\n var isPending = s.status === \"pending\";\n var statusBadge = isPending\n ? ' <span style=\"font-size:11px;padding:1px 6px;border-radius:999px;background:var(--yellow-light,#fef3c7);color:var(--yellow-text,#92400e);font-weight:500\">' + __t(\"federation.pending\") + '</span>'\n : '';\n\n var actions = '';\n if (s.isMaster) {\n // Disabled placeholder buttons for alignment with member rows\n actions = '<button class=\"ap-icon-btn\" disabled style=\"opacity:0.25;cursor:default\">' + FED_ICON_DISABLE + '</button>'\n + '<button class=\"ap-icon-btn\" disabled style=\"opacity:0.25;cursor:default\">' + FED_ICON_UNLINK + '</button>';\n } else if (isPending) {\n actions = fedIconBtn(FED_ICON_CHECK, __t(\"federation.approve\"), \"fedApproveSite('\" + s.did + \"')\", \"success\")\n + fedIconBtn(FED_ICON_REMOVE, __t(\"federation.reject\"), \"fedRejectSite('\" + s.did + \"','\" + fedEscAttr(s.name) + \"')\", \"danger\");\n } else {\n // Approved member: disable/enable toggle + unbind\n if (s.enabled === false) {\n actions = fedIconBtn(FED_ICON_ENABLE, __t(\"federation.enable\"), \"fedToggleMember('\" + s.did + \"',true)\")\n + fedIconBtn(FED_ICON_UNLINK, __t(\"federation.unbind\"), \"fedUnbindSite('\" + s.did + \"','\" + fedEscAttr(s.name) + \"')\", \"danger\");\n } else {\n actions = fedIconBtn(FED_ICON_DISABLE, __t(\"federation.disable\"), \"fedToggleMember('\" + s.did + \"',false)\", \"danger\")\n + fedIconBtn(FED_ICON_UNLINK, __t(\"federation.unbind\"), \"fedUnbindSite('\" + s.did + \"','\" + fedEscAttr(s.name) + \"')\", \"danger\");\n }\n }\n // Disabled badge for approved but disabled members\n if (!isPending && !s.isMaster && s.enabled === false) {\n statusBadge = ' <span style=\"font-size:11px;padding:1px 6px;border-radius:999px;background:var(--bg-tertiary,#e5e7eb);color:var(--text-tertiary)\">' + __t(\"federation.disabled\") + '</span>';\n }\n\n html += '<div class=\"fed-site-row\">'\n + '<div class=\"fed-site-name\">' + star + '<span class=\"fed-site-label\">' + fedEscHtml(s.name) + '</span>' + meTag + statusBadge + '</div>'\n + '<div class=\"fed-site-url\" title=\"' + fedEscAttr(s.url) + '\"><a href=\"' + fedEscAttr(s.url) + '\" target=\"_blank\">' + fedEscHtml(s.url) + '</a></div>'\n + '<div class=\"fed-site-actions\">' + actions + '</div>'\n + '</div>';\n }\n\n // Disband\n html += '<div style=\"padding:16px 20px;border-top:1px solid var(--border)\">'\n + '<button class=\"btn btn-danger btn-sm\" style=\"width:auto\" onclick=\"fedDisband()\">' + __t(\"federation.disband\") + '</button>'\n + '</div>';\n\n return html;\n}\n\nfunction fedRenderMember() {\n var enabled = __fedData.enabled;\n var isPending = !enabled && __fedData.role === \"member\";\n\n // Header: badge + status + toggle (no toggle for pending)\n var badgeClass = isPending ? 'style=\"background:var(--yellow-light,#fef3c7);color:var(--yellow-text,#92400e)\"' : '';\n var badgeText = isPending ? __t(\"federation.pending\") : __t(\"federation.member\");\n var statusText = isPending ? __t(\"federation.pendingDesc\") : (enabled ? __t(\"federation.enabled\") : __t(\"federation.disabled\"));\n\n var html = '<div style=\"padding:12px 20px;display:flex;align-items:center;gap:12px;justify-content:space-between\">'\n + '<div style=\"display:flex;align-items:center;gap:8px\">'\n + '<span class=\"fed-status-badge member\" ' + badgeClass + '>' + badgeText + '</span>'\n + '<span style=\"font-size:13px;color:var(--text-secondary)\">' + statusText + '</span>'\n + '</div>';\n if (!isPending) {\n html += '<label class=\"toggle-switch\">'\n + '<input type=\"checkbox\" ' + (enabled ? 'checked' : '') + ' onchange=\"fedToggle(this.checked)\" />'\n + '<span class=\"toggle-slider\"></span>'\n + '</label>';\n }\n html += '</div>';\n\n // Master URL\n if (__fedData.masterUrl) {\n html += '<div style=\"padding:0 20px 8px;font-size:13px;color:var(--text-secondary)\">'\n + __t(\"federation.delegatedTo\") + ' <a href=\"' + fedEscAttr(__fedData.masterUrl) + '\" target=\"_blank\" style=\"color:var(--blue)\">' + fedEscHtml(__fedData.masterUrl) + '</a>'\n + '</div>';\n }\n\n // Site list — show all sites with role labels; pending sites get \"pending\" instead of \"member\"\n if (__fedData.sites && __fedData.sites.length > 0) {\n // Sort: master first, then approved, then pending\n var memberSites = __fedData.sites.slice().sort(function(a, b) {\n if (a.isMaster) return -1;\n if (b.isMaster) return 1;\n if (a.status === \"pending\" && b.status !== \"pending\") return 1;\n if (b.status === \"pending\" && a.status !== \"pending\") return -1;\n return 0;\n });\n html += '<div style=\"padding:8px 20px 4px;display:flex;justify-content:space-between;align-items:center\">'\n + '<span style=\"font-size:13px;font-weight:600;color:var(--text)\">' + __t(\"federation.sites\") + '</span>'\n + '<button class=\"ap-icon-btn\" title=\"' + __t(\"federation.refresh\") + '\" onclick=\"fedRefresh()\" id=\"fed-refresh-btn\">' + FED_ICON_REFRESH + '</button>'\n + '</div>';\n var currentOrigin = location.origin;\n for (var i = 0; i < memberSites.length; i++) {\n var s = memberSites[i];\n var star = s.isMaster ? '<span class=\"fed-star\">★</span> ' : '';\n var isMe = s.url && fedNormalizeUrl(s.url) === currentOrigin;\n var meTag = isMe ? ' <span style=\"font-size:10px;padding:1px 5px;border-radius:999px;background:var(--blue-light,#dbeafe);color:var(--blue-muted,#1d4ed8);font-weight:500\">' + __t(\"federation.currentSite\") + '</span>' : '';\n var isPendingSite = s.status === \"pending\";\n var role = s.isMaster ? __t(\"federation.master\")\n : (isPendingSite\n ? '<span style=\"color:var(--yellow-text,#92400e);font-weight:500\">' + __t(\"federation.pending\") + '</span>'\n : __t(\"federation.member\"));\n html += '<div class=\"fed-site-row\">'\n + '<div class=\"fed-site-name\">' + star + '<span class=\"fed-site-label\">' + fedEscHtml(s.name) + '</span>' + meTag + '</div>'\n + '<div class=\"fed-site-url\" title=\"' + fedEscAttr(s.url) + '\"><a href=\"' + fedEscAttr(s.url) + '\" target=\"_blank\">' + fedEscHtml(s.url) + '</a></div>'\n + '<div class=\"fed-site-role\">' + role + '</div>'\n + '</div>';\n }\n }\n\n html += '<div style=\"padding:16px 20px;border-top:1px solid var(--border)\">'\n + '<button class=\"btn btn-danger btn-sm\" style=\"width:auto\" onclick=\"fedLeave()\">' + __t(\"federation.leave\") + '</button>'\n + '</div>';\n\n return html;\n}\n\nfunction fedEscHtml(str) {\n if (!str) return '';\n return str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/\"/g,'"');\n}\nfunction fedEscAttr(str) {\n if (!str) return '';\n return str.replace(/&/g,'&').replace(/\"/g,'"').replace(/'/g,''');\n}\n\nfunction fedSetLoading(loading) {\n __fedLoading = loading;\n fedRender();\n}\n\nfunction fedEnable() {\n confirmDialog({\n title: __t(\"federation.enableConfirm\"),\n message: __t(\"federation.enableMasterDesc\"),\n confirmLabel: __t(\"federation.enableMaster\"),\n }).then(function(ok) {\n if (!ok) return;\n fedSetLoading(true);\n api(\"POST\", \"/admin/federation/enable\", { url: location.origin, name: document.title || \"App\" }).then(function(data) {\n __fedLoading = false;\n if (data && !data.error) { __fedData = data; fedRender(); showToast(__t(\"federation.enabled\"), \"success\"); }\n else { showToast(data.error || \"Failed\", \"error\"); fedRender(); }\n });\n });\n}\n\nfunction fedOpenJoin() {\n document.getElementById(\"fed-join-url\").value = \"\";\n showDialog(\"fed-join-dialog\");\n}\n\nfunction fedJoinSubmit() {\n var masterUrl = fedNormalizeUrl(document.getElementById(\"fed-join-url\").value);\n if (!masterUrl) { showToast(__t(\"federation.masterUrl\") + \" is required\", \"error\"); return; }\n\n var btn = document.getElementById(\"fed-join-btn\");\n btn.disabled = true;\n btn.innerHTML = '<span class=\"spinner\"></span>';\n\n api(\"POST\", \"/admin/federation/join\", { masterUrl: masterUrl }).then(function(data) {\n btn.disabled = false;\n btn.textContent = __t(\"federation.joinMember\");\n if (data && !data.error) {\n __fedData = data;\n fedRender();\n closeDialog(\"fed-join-dialog\");\n showToast(__t(\"federation.pendingApproval\"), \"success\");\n } else {\n showToast(data.error || __t(\"federation.targetNotEnabled\"), \"error\");\n }\n });\n}\n\nfunction fedToggle(enabled) {\n if (!enabled) {\n confirmDialog({\n title: __t(\"federation.disableTitle\"),\n message: __t(\"federation.disableDesc\") + \" \" + __t(\"federation.walletSafe\") + \" \" + __t(\"federation.sessionWarning\"),\n confirmLabel: __t(\"common.confirm\"),\n danger: true,\n }).then(function(ok) {\n if (!ok) { fedRender(); return; }\n api(\"PUT\", \"/admin/federation/toggle\", { enabled: false }).then(function(data) {\n if (data && !data.error) { __fedData = data; fedRender(); }\n });\n });\n var cb = document.querySelector(\"#fed-section input[type=checkbox]\");\n if (cb) cb.checked = true;\n } else {\n api(\"PUT\", \"/admin/federation/toggle\", { enabled: true }).then(function(data) {\n if (data && !data.error) { __fedData = data; fedRender(); }\n });\n }\n}\n\nfunction fedApproveSite(did) {\n api(\"POST\", \"/admin/federation/sites/\" + encodeURIComponent(did) + \"/approve\").then(function(data) {\n if (data && !data.error) {\n __fedData = data;\n fedRender();\n showToast(__t(\"federation.approved\"), \"success\");\n } else {\n showToast(data.error || \"Failed\", \"error\");\n }\n });\n}\n\nfunction fedRejectSite(did, name) {\n confirmDialog({\n title: __t(\"federation.rejectTitle\"),\n message: __t(\"federation.rejectDesc\"),\n confirmLabel: __t(\"federation.reject\"),\n danger: true,\n }).then(function(ok) {\n if (!ok) return;\n api(\"DELETE\", \"/admin/federation/sites/\" + encodeURIComponent(did)).then(function(data) {\n if (data && !data.error) { __fedData = data; fedRender(); showToast(__t(\"federation.reject\") + \" \\u2713\", \"success\"); }\n });\n });\n}\n\nfunction fedRefresh() {\n var btn = document.getElementById(\"fed-refresh-btn\");\n if (btn) { btn.style.opacity = \"0.5\"; btn.style.pointerEvents = \"none\"; }\n fedLoad();\n setTimeout(function() { if (btn) { btn.style.opacity = \"\"; btn.style.pointerEvents = \"\"; } }, 500);\n}\n\n// ─── Actions ─────────────────────────────────────────────────\n\nfunction fedToggleMember(did, enabled) {\n if (!enabled) {\n confirmDialog({\n title: __t(\"federation.disableTitle\"),\n message: __t(\"federation.disableDesc\") + \" \" + __t(\"federation.walletSafe\") + \" \" + __t(\"federation.sessionWarning\"),\n confirmLabel: __t(\"federation.disable\"),\n danger: true,\n }).then(function(ok) {\n if (!ok) return;\n api(\"PUT\", \"/admin/federation/sites/\" + encodeURIComponent(did) + \"/toggle\", { enabled: false }).then(function(data) {\n if (data && !data.error) { __fedData = data; fedRender(); showToast(__t(\"federation.disabled\"), \"success\"); }\n });\n });\n } else {\n api(\"PUT\", \"/admin/federation/sites/\" + encodeURIComponent(did) + \"/toggle\", { enabled: true }).then(function(data) {\n if (data && !data.error) { __fedData = data; fedRender(); showToast(__t(\"federation.enabled\"), \"success\"); }\n });\n }\n}\n\nfunction fedUnbindSite(did, name) {\n confirmDialog({\n title: __t(\"federation.unbindTitle\"),\n message: __t(\"federation.unbindDesc\") + \" \" + __t(\"federation.walletSafe\") + \" \" + __t(\"federation.sessionWarning\"),\n confirmLabel: __t(\"federation.unbind\"),\n danger: true,\n }).then(function(ok) {\n if (!ok) return;\n api(\"DELETE\", \"/admin/federation/sites/\" + encodeURIComponent(did)).then(function(data) {\n if (data && !data.error) { __fedData = data; fedRender(); showToast(__t(\"federation.unbind\") + \" \\u2713\", \"success\"); }\n });\n });\n}\n\nfunction fedDisband() {\n confirmDialog({\n title: __t(\"federation.disbandTitle\"),\n message: __t(\"federation.disbandDesc\") + \" \" + __t(\"federation.sessionWarning\"),\n confirmLabel: __t(\"federation.disband\"),\n danger: true,\n }).then(function(ok) {\n if (!ok) return;\n api(\"POST\", \"/admin/federation/disband\").then(function(data) {\n if (data && !data.error) { __fedData = data; fedRender(); showToast(__t(\"federation.disband\") + \" \\u2713\", \"success\"); }\n });\n });\n}\n\nfunction fedLeave() {\n confirmDialog({\n title: __t(\"federation.leaveTitle\"),\n message: __t(\"federation.leaveDesc\") + \" \" + __t(\"federation.sessionWarning\"),\n confirmLabel: __t(\"federation.leave\"),\n danger: true,\n }).then(function(ok) {\n if (!ok) return;\n api(\"POST\", \"/admin/federation/leave\").then(function(data) {\n if (data && !data.error) { __fedData = data; fedRender(); showToast(__t(\"federation.leave\") + \" \\u2713\", \"success\"); }\n });\n });\n}\n\n"};
|