@arcblock/did-connect-service 4.1.43 → 4.1.44
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 +3 -3
- package/dist/assets/{admin-core.d42d34ce.js → admin-core.6e3d6ce9.js} +22 -19
- package/dist/assets/{admin-extra.da2c9785.js → admin-extra.82aff2c7.js} +1 -1
- package/dist/assets/{admin.d99b416e.css → admin.314473a7.css} +3 -1
- package/dist/handlers/auth-handler.d.ts.map +1 -1
- package/dist/handlers/auth-handler.js +2 -0
- package/dist/handlers/auth-handler.js.map +1 -1
- package/dist/handlers/did-connect-handler.d.ts.map +1 -1
- package/dist/handlers/did-connect-handler.js +2 -0
- package/dist/handlers/did-connect-handler.js.map +1 -1
- package/dist/handlers/email-login-handler.d.ts.map +1 -1
- package/dist/handlers/email-login-handler.js +1 -0
- package/dist/handlers/email-login-handler.js.map +1 -1
- package/dist/handlers/oauth-handler.d.ts.map +1 -1
- package/dist/handlers/oauth-handler.js +4 -0
- package/dist/handlers/oauth-handler.js.map +1 -1
- package/dist/handlers/passkey-handler.d.ts.map +1 -1
- package/dist/handlers/passkey-handler.js +6 -2
- package/dist/handlers/passkey-handler.js.map +1 -1
- package/dist/handlers/team-handler.d.ts.map +1 -1
- package/dist/handlers/team-handler.js +1 -0
- package/dist/handlers/team-handler.js.map +1 -1
- package/dist/pages/admin/i18n.d.ts.map +1 -1
- package/dist/pages/admin/i18n.js +0 -4
- package/dist/pages/admin/i18n.js.map +1 -1
- package/dist/pages/admin/index.d.ts.map +1 -1
- package/dist/pages/admin/index.js +16 -13
- package/dist/pages/admin/index.js.map +1 -1
- package/dist/pages/admin/scripts/utils.d.ts +1 -1
- package/dist/pages/admin/scripts/utils.d.ts.map +1 -1
- package/dist/pages/admin/scripts/utils.js +21 -0
- package/dist/pages/admin/scripts/utils.js.map +1 -1
- package/dist/pages/admin/styles.d.ts +1 -1
- package/dist/pages/admin/styles.d.ts.map +1 -1
- package/dist/pages/admin/styles.js +3 -1
- package/dist/pages/admin/styles.js.map +1 -1
- package/dist/pages/admin/tab-members.d.ts +1 -1
- package/dist/pages/admin/tab-members.d.ts.map +1 -1
- package/dist/pages/admin/tab-members.js +1 -1
- package/dist/pages/admin/tab-profile-accounts.d.ts.map +1 -1
- package/dist/pages/admin/tab-profile-accounts.js +1 -19
- package/dist/pages/admin/tab-profile-accounts.js.map +1 -1
- package/dist/store/d1-store.d.ts +12 -1
- package/dist/store/d1-store.d.ts.map +1 -1
- package/dist/store/d1-store.js +69 -12
- package/dist/store/d1-store.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/migrations/0022_connected_account_last_login_domain.sql +6 -0
- package/package.json +3 -3
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
* Includes shared component styles formerly from shared-styles.ts
|
|
6
6
|
* (body, h1, .input, .btn, .btn-secondary, .btn-danger, .badge variants).
|
|
7
7
|
*/
|
|
8
|
-
export declare const ADMIN_STYLES = "\n .hidden { display: none !important; }\n\n /* \u2500\u2500\u2500 Shared component styles (extracted from shared-styles.ts) \u2500\u2500\u2500\u2500\u2500 */\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 /* In-flight submit (openDialog): keep the label readable and show a spinner. */\n .btn.btn-loading { opacity: 0.8; }\n .btn.btn-loading::before {\n content: \"\";\n flex: none;\n width: 14px; height: 14px;\n border: 2px solid currentColor;\n border-right-color: transparent;\n border-radius: 50%;\n animation: spin 0.6s linear infinite;\n }\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 /* \u2500\u2500\u2500 Admin layout \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\u2500\u2500\u2500\u2500 */\n body {\n display: block;\n min-height: 100vh;\n }\n\n /* \u2500\u2500\u2500 Header \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 .brand-home {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n color: inherit;\n text-decoration: none;\n }\n .admin-header-left .brand-home:hover {\n text-decoration: none;\n }\n .admin-header-left .brand-logo {\n height: 28px;\n width: auto;\n border-radius: 4px;\n flex-shrink: 0;\n }\n .admin-header-left .app-name-link {\n color: var(--text);\n }\n .admin-header-left .brand-home:hover .app-name-link {\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 /* \u2500\u2500\u2500 Toolbar buttons (ghost style) \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 .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 /* \u2500\u2500\u2500 Popover (language picker) \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 .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 /* \u2500\u2500\u2500 Mobile: larger touch targets, full-width popover \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 User menu trigger \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 .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 /* \u2500\u2500\u2500 User dropdown \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 .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 /* \u2500\u2500\u2500 Tabs \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 Section titles \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 .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 /* \u2500\u2500\u2500 Profile \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 \u2014 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 /* \u2500\u2500\u2500 Settings card \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\u2500 */\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 /* \u2500\u2500\u2500 Connected Accounts chips \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 .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 /* \u2500\u2500\u2500 Bound account cards \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 .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-times {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n .ca-bound-time {\n display: flex;\n align-items: baseline;\n gap: 8px;\n min-width: 0;\n }\n .ca-bound-time-label {\n flex: 0 0 auto;\n min-width: 4.5rem;\n font-size: 11px;\n font-weight: 500;\n color: var(--text-tertiary);\n letter-spacing: 0.2px;\n white-space: nowrap;\n }\n .ca-bound-time-value {\n font-size: 12px;\n color: var(--text-secondary);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n }\n .ca-bound-actions {\n display: flex;\n align-items: center;\n gap: 4px;\n flex-shrink: 0;\n }\n\n /* \u2500\u2500\u2500 Connected Accounts: mobile \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 @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-bound-time-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 /* \u2500\u2500\u2500 Forms \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 Tables \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 \u2014 hosts \u22EF 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 /* \u2500\u2500\u2500 Toolbar \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 OAuth provider picker \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 .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 /* \u2500\u2500\u2500 Pagination \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\u2500\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 Empty state \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\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 Dialogs \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 max-height: calc(100vh - 32px);\n max-height: calc(100dvh - 32px);\n overflow: hidden;\n animation: modalIn 0.2s ease;\n }\n dialog[open] {\n display: flex;\n flex-direction: column;\n }\n dialog::backdrop { background: rgba(0,0,0,0.7); }\n .dialog-content {\n min-height: 0;\n overflow-y: auto;\n overscroll-behavior: contain;\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 flex: 0 0 auto;\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 dialog.merge-confirm-dialog { max-width: 560px; }\n .merge-method-list { display: grid; gap: 10px; margin-top: 16px; }\n .merge-method-list .btn,\n .merge-method-btn {\n width: 100%;\n justify-content: flex-start;\n }\n .merge-method-btn .ca-chip-icon,\n .merge-method-btn svg {\n width: 18px;\n height: 18px;\n flex-shrink: 0;\n }\n .ca-merge-prompt {\n margin: 0 20px 12px;\n padding: 12px 14px;\n border: 1px solid var(--border-strong);\n border-radius: var(--radius-sm);\n background: var(--blue-light);\n }\n .ca-merge-prompt-title {\n font-size: 14px;\n font-weight: 600;\n color: var(--text-white);\n margin: 0 0 4px;\n }\n .ca-merge-prompt-body {\n font-size: 13px;\n color: var(--text-secondary);\n line-height: 1.45;\n margin: 0 0 10px;\n }\n .ca-merge-prompt-actions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n }\n .ca-merge-prompt .btn { width: auto; min-width: 96px; }\n .merge-email-fields { display: grid; gap: 10px; margin-top: 12px; text-align: left; }\n .merge-confirm-section { margin: 14px 0 0; }\n .merge-confirm-section h4 {\n font-size: 12px;\n font-weight: 600;\n color: var(--text-white);\n margin: 0 0 6px;\n }\n .merge-confirm-list {\n margin: 0;\n padding-left: 18px;\n font-size: 13px;\n color: var(--text);\n line-height: 1.55;\n }\n .merge-confirm-note {\n font-size: 13px;\n color: var(--text);\n line-height: 1.55;\n margin: 0;\n }\n .merge-confirm-did {\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;\n font-size: 12px;\n word-break: break-all;\n }\n .merge-confirm-keep-other {\n font-size: 13px;\n color: var(--text-secondary);\n margin: 0 0 4px;\n line-height: 1.5;\n }\n .merge-confirm-irreversible {\n color: var(--red-text);\n font-weight: 600;\n font-size: 13px;\n margin: 16px 0 0;\n }\n .merge-confirm-too-large {\n color: var(--red-text);\n font-size: 13px;\n margin: 8px 0 0;\n line-height: 1.5;\n }\n\n /* \u2500\u2500\u2500 Wallet bind QR (mirrors login-page .qr-view) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 Action menus \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\u2500\u2500 */\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 /* \u2500\u2500\u2500 Status badges \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\u2500 */\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 /* \u2500\u2500\u2500 Toast \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 Toggle switch \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 .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 /* \u2500\u2500\u2500 Range slider \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 .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 /* \u2500\u2500\u2500 Branding: Section layout \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 Branding: Language toggles \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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: \"\u2713\";\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 /* \u2500\u2500\u2500 Branding: Logo grid (redesigned) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 \u2014 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 /* \u2500\u2500\u2500 Branding: per-field source badge (did#249) \u2500\u2500\u2500 */\n .br-src-slot:empty { display: none; }\n .br-src-row { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }\n .br-src-row:empty { display: none; }\n .br-src {\n display: inline-block;\n font-size: 10px;\n font-weight: 600;\n line-height: 1.5;\n padding: 1px 6px;\n margin-left: 6px;\n border-radius: 999px;\n border: 1px solid var(--border-subtle);\n color: var(--text-tertiary);\n white-space: nowrap;\n vertical-align: middle;\n }\n .br-src-row .br-src { margin-left: 0; }\n .br-src-instance { color: var(--blue); border-color: var(--blue-muted); }\n .br-src-package { color: var(--green-text); border-color: var(--green-light); }\n .br-src-note { font-style: italic; }\n .br-restore { margin-left: 8px; font-size: 11px; vertical-align: middle; }\n\n /* \u2500\u2500\u2500 Branding: Sticky save bar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 Branding: Accent color swatches \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 Branding tab: Language grid \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 Crop dialog \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 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 /* \u2500\u2500\u2500 Branding: Mobile responsive \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 Appearance tab: Color input \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 Auto theme: follow system preference \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 @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 /* \u2500\u2500\u2500 Skeleton loading \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\u2500 */\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 /* \u2500\u2500\u2500 Responsive: column visibility helpers \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 /* col-hide-lg: hide on tablet (\u2264900px) */\n @media (max-width: 900px) {\n .col-hide-lg { display: none; }\n }\n /* col-hide-md: hide on small tablet (\u2264768px) */\n @media (max-width: 768px) {\n .col-hide-md { display: none; }\n }\n /* col-hide-sm: hide on mobile (\u2264640px) */\n @media (max-width: 640px) {\n .col-hide-sm { display: none; }\n }\n\n /* \u2500\u2500\u2500 Mobile sidebar drawer \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 .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 /* \u2500\u2500\u2500 Responsive: show sidebar toggle, hide tabs on mobile \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n @media (max-width: 640px) {\n .sidebar-toggle { display: flex; }\n .sidebar { display: block; }\n .tabs { display: none; }\n }\n\n /* \u2500\u2500\u2500 Responsive: toolbar / filter bar \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 @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 /* \u2500\u2500\u2500 Responsive: header \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 @media (max-width: 640px) {\n .admin-header-left {\n gap: 6px;\n min-width: 0;\n overflow: hidden;\n }\n .admin-header-left .brand-home {\n flex-shrink: 1;\n }\n .admin-header-left .app-name-link {\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 /* \u2500\u2500\u2500 Responsive: table compact \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 @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 /* \u2500\u2500\u2500 Responsive: pagination \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 @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 /* \u2500\u2500\u2500 Responsive: section titles \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 @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 /* \u2500\u2500\u2500 Responsive: settings card \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 @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 /* \u2500\u2500\u2500 Responsive: table horizontal scroll on very small screens \u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 Settings: OAuth card grid \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 .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 /* \u2500\u2500\u2500 Settings: dual panel (login methods + email) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 Settings: email hint \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 .st-hint {\n font-size: 11px;\n color: var(--yellow-text, #92400e);\n font-weight: 400;\n margin-left: 4px;\n }\n\n /* \u2500\u2500\u2500 Federation \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 .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";
|
|
8
|
+
export declare const ADMIN_STYLES = "\n .hidden { display: none !important; }\n\n /* \u2500\u2500\u2500 Shared component styles (extracted from shared-styles.ts) \u2500\u2500\u2500\u2500\u2500 */\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 /* In-flight submit (openDialog): keep the label readable and show a spinner. */\n .btn.btn-loading { opacity: 0.8; }\n .btn.btn-loading::before {\n content: \"\";\n flex: none;\n width: 14px; height: 14px;\n border: 2px solid currentColor;\n border-right-color: transparent;\n border-radius: 50%;\n animation: spin 0.6s linear infinite;\n }\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 /* \u2500\u2500\u2500 Admin layout \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\u2500\u2500\u2500\u2500 */\n body {\n display: block;\n min-height: 100vh;\n }\n\n /* \u2500\u2500\u2500 Header \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 .brand-home {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n color: inherit;\n text-decoration: none;\n }\n .admin-header-left .brand-home:hover {\n text-decoration: none;\n }\n .admin-header-left .brand-logo {\n height: 28px;\n width: auto;\n border-radius: 4px;\n flex-shrink: 0;\n }\n .admin-header-left .app-name-link {\n color: var(--text);\n }\n .admin-header-left .brand-home:hover .app-name-link {\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 /* \u2500\u2500\u2500 Toolbar buttons (ghost style) \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 .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 /* \u2500\u2500\u2500 Popover (language picker) \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 .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 /* \u2500\u2500\u2500 Mobile: larger touch targets, full-width popover \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 User menu trigger \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 .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 /* \u2500\u2500\u2500 User dropdown \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 .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 /* \u2500\u2500\u2500 Tabs \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 Section titles \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 .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 /* \u2500\u2500\u2500 Profile \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 \u2014 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 /* \u2500\u2500\u2500 Settings card \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\u2500 */\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 /* \u2500\u2500\u2500 Connected Accounts chips \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 .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 /* \u2500\u2500\u2500 Bound account cards \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 .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-times {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n .ca-bound-time {\n display: flex;\n align-items: baseline;\n gap: 8px;\n min-width: 0;\n }\n .ca-bound-time-label {\n flex: 0 0 auto;\n min-width: 4.5rem;\n font-size: 11px;\n font-weight: 500;\n color: var(--text-tertiary);\n letter-spacing: 0.2px;\n white-space: nowrap;\n }\n .ca-bound-time-value {\n font-size: 12px;\n color: var(--text-secondary);\n font-variant-numeric: tabular-nums;\n min-width: 0;\n overflow-wrap: anywhere;\n }\n .settings-row .ca-bound-times { margin-top: 6px; }\n .ca-bound-actions {\n display: flex;\n align-items: center;\n gap: 4px;\n flex-shrink: 0;\n }\n\n /* \u2500\u2500\u2500 Connected Accounts: mobile \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 @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-bound-time-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 /* \u2500\u2500\u2500 Forms \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 Tables \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 \u2014 hosts \u22EF 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 /* \u2500\u2500\u2500 Toolbar \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 OAuth provider picker \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 .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 /* \u2500\u2500\u2500 Pagination \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\u2500\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 Empty state \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\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 Dialogs \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 max-height: calc(100vh - 32px);\n max-height: calc(100dvh - 32px);\n overflow: hidden;\n animation: modalIn 0.2s ease;\n }\n dialog[open] {\n display: flex;\n flex-direction: column;\n }\n dialog::backdrop { background: rgba(0,0,0,0.7); }\n .dialog-content {\n min-height: 0;\n overflow-y: auto;\n overscroll-behavior: contain;\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 flex: 0 0 auto;\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 dialog.merge-confirm-dialog { max-width: 560px; }\n .merge-method-list { display: grid; gap: 10px; margin-top: 16px; }\n .merge-method-list .btn,\n .merge-method-btn {\n width: 100%;\n justify-content: flex-start;\n }\n .merge-method-btn .ca-chip-icon,\n .merge-method-btn svg {\n width: 18px;\n height: 18px;\n flex-shrink: 0;\n }\n .ca-merge-prompt {\n margin: 0 20px 12px;\n padding: 12px 14px;\n border: 1px solid var(--border-strong);\n border-radius: var(--radius-sm);\n background: var(--blue-light);\n }\n .ca-merge-prompt-title {\n font-size: 14px;\n font-weight: 600;\n color: var(--text-white);\n margin: 0 0 4px;\n }\n .ca-merge-prompt-body {\n font-size: 13px;\n color: var(--text-secondary);\n line-height: 1.45;\n margin: 0 0 10px;\n }\n .ca-merge-prompt-actions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n }\n .ca-merge-prompt .btn { width: auto; min-width: 96px; }\n .merge-email-fields { display: grid; gap: 10px; margin-top: 12px; text-align: left; }\n .merge-confirm-section { margin: 14px 0 0; }\n .merge-confirm-section h4 {\n font-size: 12px;\n font-weight: 600;\n color: var(--text-white);\n margin: 0 0 6px;\n }\n .merge-confirm-list {\n margin: 0;\n padding-left: 18px;\n font-size: 13px;\n color: var(--text);\n line-height: 1.55;\n }\n .merge-confirm-note {\n font-size: 13px;\n color: var(--text);\n line-height: 1.55;\n margin: 0;\n }\n .merge-confirm-did {\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;\n font-size: 12px;\n word-break: break-all;\n }\n .merge-confirm-keep-other {\n font-size: 13px;\n color: var(--text-secondary);\n margin: 0 0 4px;\n line-height: 1.5;\n }\n .merge-confirm-irreversible {\n color: var(--red-text);\n font-weight: 600;\n font-size: 13px;\n margin: 16px 0 0;\n }\n .merge-confirm-too-large {\n color: var(--red-text);\n font-size: 13px;\n margin: 8px 0 0;\n line-height: 1.5;\n }\n\n /* \u2500\u2500\u2500 Wallet bind QR (mirrors login-page .qr-view) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 Action menus \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\u2500\u2500 */\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 /* \u2500\u2500\u2500 Status badges \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\u2500 */\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 /* \u2500\u2500\u2500 Toast \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 Toggle switch \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 .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 /* \u2500\u2500\u2500 Range slider \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 .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 /* \u2500\u2500\u2500 Branding: Section layout \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 Branding: Language toggles \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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: \"\u2713\";\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 /* \u2500\u2500\u2500 Branding: Logo grid (redesigned) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 \u2014 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 /* \u2500\u2500\u2500 Branding: per-field source badge (did#249) \u2500\u2500\u2500 */\n .br-src-slot:empty { display: none; }\n .br-src-row { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }\n .br-src-row:empty { display: none; }\n .br-src {\n display: inline-block;\n font-size: 10px;\n font-weight: 600;\n line-height: 1.5;\n padding: 1px 6px;\n margin-left: 6px;\n border-radius: 999px;\n border: 1px solid var(--border-subtle);\n color: var(--text-tertiary);\n white-space: nowrap;\n vertical-align: middle;\n }\n .br-src-row .br-src { margin-left: 0; }\n .br-src-instance { color: var(--blue); border-color: var(--blue-muted); }\n .br-src-package { color: var(--green-text); border-color: var(--green-light); }\n .br-src-note { font-style: italic; }\n .br-restore { margin-left: 8px; font-size: 11px; vertical-align: middle; }\n\n /* \u2500\u2500\u2500 Branding: Sticky save bar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 Branding: Accent color swatches \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 Branding tab: Language grid \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 Crop dialog \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 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 /* \u2500\u2500\u2500 Branding: Mobile responsive \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 Appearance tab: Color input \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 Auto theme: follow system preference \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 @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 /* \u2500\u2500\u2500 Skeleton loading \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\u2500 */\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 /* \u2500\u2500\u2500 Responsive: column visibility helpers \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 /* col-hide-lg: hide on tablet (\u2264900px) */\n @media (max-width: 900px) {\n .col-hide-lg { display: none; }\n }\n /* col-hide-md: hide on small tablet (\u2264768px) */\n @media (max-width: 768px) {\n .col-hide-md { display: none; }\n }\n /* col-hide-sm: hide on mobile (\u2264640px) */\n @media (max-width: 640px) {\n .col-hide-sm { display: none; }\n }\n\n /* \u2500\u2500\u2500 Mobile sidebar drawer \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 .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 /* \u2500\u2500\u2500 Responsive: show sidebar toggle, hide tabs on mobile \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n @media (max-width: 640px) {\n .sidebar-toggle { display: flex; }\n .sidebar { display: block; }\n .tabs { display: none; }\n }\n\n /* \u2500\u2500\u2500 Responsive: toolbar / filter bar \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 @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 /* \u2500\u2500\u2500 Responsive: header \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 @media (max-width: 640px) {\n .admin-header-left {\n gap: 6px;\n min-width: 0;\n overflow: hidden;\n }\n .admin-header-left .brand-home {\n flex-shrink: 1;\n }\n .admin-header-left .app-name-link {\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 /* \u2500\u2500\u2500 Responsive: table compact \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 @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 /* \u2500\u2500\u2500 Responsive: pagination \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 @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 /* \u2500\u2500\u2500 Responsive: section titles \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 @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 /* \u2500\u2500\u2500 Responsive: settings card \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 @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 /* \u2500\u2500\u2500 Responsive: table horizontal scroll on very small screens \u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 Settings: OAuth card grid \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 .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 /* \u2500\u2500\u2500 Settings: dual panel (login methods + email) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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 /* \u2500\u2500\u2500 Settings: email hint \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 .st-hint {\n font-size: 11px;\n color: var(--yellow-text, #92400e);\n font-weight: 400;\n margin-left: 4px;\n }\n\n /* \u2500\u2500\u2500 Federation \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 .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";
|
|
9
9
|
//# sourceMappingURL=styles.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../../src/pages/admin/styles.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,YAAY,
|
|
1
|
+
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../../src/pages/admin/styles.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,YAAY,0j1EA21ExB,CAAC"}
|
|
@@ -886,8 +886,10 @@ export const ADMIN_STYLES = /* css */ `
|
|
|
886
886
|
font-size: 12px;
|
|
887
887
|
color: var(--text-secondary);
|
|
888
888
|
font-variant-numeric: tabular-nums;
|
|
889
|
-
|
|
889
|
+
min-width: 0;
|
|
890
|
+
overflow-wrap: anywhere;
|
|
890
891
|
}
|
|
892
|
+
.settings-row .ca-bound-times { margin-top: 6px; }
|
|
891
893
|
.ca-bound-actions {
|
|
892
894
|
display: flex;
|
|
893
895
|
align-items: center;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"styles.js","sourceRoot":"","sources":["../../../src/pages/admin/styles.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,YAAY,GAAG,SAAS,CAAC
|
|
1
|
+
{"version":3,"file":"styles.js","sourceRoot":"","sources":["../../../src/pages/admin/styles.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,YAAY,GAAG,SAAS,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA21ErC,CAAC"}
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
type TFn = (key: string, params?: Record<string, string>) => string;
|
|
5
5
|
export declare function membersTabHtml(t: TFn): string;
|
|
6
|
-
export declare const MEMBERS_TAB_SCRIPT = "\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) \u2192 desc \u2192 asc \u2192 (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 \u2014 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 // Best-effort: this endpoint is system-admin-only. Instance-scoped owners/admins\n // get 403 here, which is expected \u2014 fail silently (no error toast).\n var data = await api(\"GET\", \"/../admin/instances\", undefined, { silent: true });\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 (\"\") \u2192 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 // Aggregated (cross-instance \"All Instances\") view can't tell which instance\n // a role change should apply to, so per-row actions are disabled there \u2014 see\n // canOperate below. Surface why, instead of silently hiding the buttons.\n var aggregatedHint = __membersAggregated ? '<p class=\"text-muted\" style=\"font-size:13px;margin-bottom:12px;\">' + __t(\"members.aggregatedHint\") + '</p>' : '';\n if (!users || users.length === 0) {\n wrap.innerHTML = aggregatedHint + '<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 = aggregatedHint + '<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 \u2014 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\">\u2014</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\">\u2014</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\") : '\u2014';\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) + '\\')\">' + __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) + '\\')\">' + __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) + '\\')\">' + __t(\"members.remove\") + '</button>');\n }\n\n if (items.length === 0) return \"\";\n return '<button class=\"action-trigger\" onclick=\"event.stopPropagation();toggleActionMenu(this)\">\u22EF</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\n// name is looked up (not passed via onclick) so a member's self-set fullName\n// never has to be embedded as a JS-string literal inside an inline onclick=\n// attribute \u2014 HTML entity-escaping alone can't fully neutralize that context,\n// since the browser decodes entities before parsing onclick as script.\nasync function blockMember(did) {\n var u = currentMembersList.find(function(m) { return m.did === did; });\n var name = (u && u.fullName) || did;\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) {\n var u = currentMembersList.find(function(m) { return m.did === did; });\n var name = (u && u.fullName) || did;\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) {\n var u = currentMembersList.find(function(m) { return m.did === did; });\n var name = (u && u.fullName) || did;\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// Button-driven mutations must not double-submit (submitOnce \u2014 utils.ts).\nsaveRoleChange = submitOnce(saveRoleChange);\nblockMember = submitOnce(blockMember);\nunblockMember = submitOnce(unblockMember);\nremoveMember = submitOnce(removeMember);\nhandleTransferOwnership = submitOnce(handleTransferOwnership);\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";
|
|
6
|
+
export declare const MEMBERS_TAB_SCRIPT = "\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) \u2192 desc \u2192 asc \u2192 (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 \u2014 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 // Best-effort: this endpoint is system-admin-only. Instance-scoped owners/admins\n // get 403 here, which is expected \u2014 fail silently (no error toast).\n var data = await api(\"GET\", \"/../admin/instances\", undefined, { silent: true });\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 (\"\") \u2192 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 // Aggregated (cross-instance \"All Instances\") view can't tell which instance\n // a role change should apply to, so per-row actions are disabled there \u2014 see\n // canOperate below. Surface why, instead of silently hiding the buttons.\n var aggregatedHint = __membersAggregated ? '<p class=\"text-muted\" style=\"font-size:13px;margin-bottom:12px;\">' + __t(\"members.aggregatedHint\") + '</p>' : '';\n if (!users || users.length === 0) {\n wrap.innerHTML = aggregatedHint + '<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 = aggregatedHint + '<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 \u2014 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\">\u2014</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\">\u2014</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\") : '\u2014';\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) + '\\')\">' + __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) + '\\')\">' + __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) + '\\')\">' + __t(\"members.remove\") + '</button>');\n }\n\n if (items.length === 0) return \"\";\n return '<button class=\"action-trigger\" onclick=\"event.stopPropagation();toggleActionMenu(this)\">\u22EF</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 += renderAccountTimeRows(a);\n html += '</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\n// name is looked up (not passed via onclick) so a member's self-set fullName\n// never has to be embedded as a JS-string literal inside an inline onclick=\n// attribute \u2014 HTML entity-escaping alone can't fully neutralize that context,\n// since the browser decodes entities before parsing onclick as script.\nasync function blockMember(did) {\n var u = currentMembersList.find(function(m) { return m.did === did; });\n var name = (u && u.fullName) || did;\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) {\n var u = currentMembersList.find(function(m) { return m.did === did; });\n var name = (u && u.fullName) || did;\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) {\n var u = currentMembersList.find(function(m) { return m.did === did; });\n var name = (u && u.fullName) || did;\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// Button-driven mutations must not double-submit (submitOnce \u2014 utils.ts).\nsaveRoleChange = submitOnce(saveRoleChange);\nblockMember = submitOnce(blockMember);\nunblockMember = submitOnce(unblockMember);\nremoveMember = submitOnce(removeMember);\nhandleTransferOwnership = submitOnce(handleTransferOwnership);\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";
|
|
7
7
|
export {};
|
|
8
8
|
//# sourceMappingURL=tab-members.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tab-members.d.ts","sourceRoot":"","sources":["../../../src/pages/admin/tab-members.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,KAAK,GAAG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC;AAEpE,wBAAgB,cAAc,CAAC,CAAC,EAAE,GAAG,GAAG,MAAM,CAiG7C;AAED,eAAO,MAAM,kBAAkB,
|
|
1
|
+
{"version":3,"file":"tab-members.d.ts","sourceRoot":"","sources":["../../../src/pages/admin/tab-members.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,KAAK,GAAG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC;AAEpE,wBAAgB,cAAc,CAAC,CAAC,EAAE,GAAG,GAAG,MAAM,CAiG7C;AAED,eAAO,MAAM,kBAAkB,w61BA+e9B,CAAC"}
|
|
@@ -415,8 +415,8 @@ async function openMemberDetail(did) {
|
|
|
415
415
|
html += '</div>';
|
|
416
416
|
if (info) html += '<div style="font-size:12px;color:var(--text-secondary);margin-top:2px">' + escapeHtml(info) + '</div>';
|
|
417
417
|
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>';
|
|
418
|
+
html += renderAccountTimeRows(a);
|
|
418
419
|
html += '</div>';
|
|
419
|
-
if (a.lastLoginAt) html += '<div style="font-size:12px;color:var(--text-secondary);flex-shrink:0">' + escapeHtml(relativeTime(a.lastLoginAt)) + '</div>';
|
|
420
420
|
html += '</div>';
|
|
421
421
|
});
|
|
422
422
|
html += '</div></div>';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tab-profile-accounts.d.ts","sourceRoot":"","sources":["../../../src/pages/admin/tab-profile-accounts.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH,KAAK,GAAG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC;AAqBpE,wBAAgB,yBAAyB,CAAC,CAAC,EAAE,GAAG,GAAG,MAAM,CAUxD;AAID,eAAO,MAAM,yBAAyB,
|
|
1
|
+
{"version":3,"file":"tab-profile-accounts.d.ts","sourceRoot":"","sources":["../../../src/pages/admin/tab-profile-accounts.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH,KAAK,GAAG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC;AAqBpE,wBAAgB,yBAAyB,CAAC,CAAC,EAAE,GAAG,GAAG,MAAM,CAUxD;AAID,eAAO,MAAM,yBAAyB,QAgiClC,CAAC"}
|