
:root{color:#17202a;background:#f4f6f8;font-family:"Segoe UI","Microsoft YaHei",sans-serif;font-size:15px;letter-spacing:0}*{box-sizing:border-box}body{margin:0;min-width:320px;background:#f4f6f8}button,input{font:inherit;letter-spacing:0}button{cursor:pointer}.login-view{min-height:100vh;display:grid;place-items:center;padding:24px;background:#17202a}.login-panel{width:min(100%,380px);padding:32px;background:#fff;border-top:4px solid #168c79;box-shadow:0 20px 50px rgba(0,0,0,.24)}.brand-mark{width:52px;height:52px;display:grid;place-items:center;margin-bottom:24px;color:#fff;background:#17202a;font-weight:750}.brand-mark.small{width:40px;height:40px;margin:0;background:#168c79;font-size:13px}.eyebrow{margin:0 0 5px;color:#607080;font-size:11px;font-weight:700;text-transform:uppercase}h1,h2{margin:0;letter-spacing:0}.login-panel h1{margin-bottom:28px;font-size:25px}label{display:block;margin-bottom:7px;color:#394957;font-size:13px;font-weight:650}.password-label{margin-top:14px}input[type=text],input[type=password]{width:100%;height:42px;padding:0 11px;border:1px solid #b8c2cc;border-radius:4px;outline:none}input:focus{border-color:#168c79;box-shadow:0 0 0 3px rgba(22,140,121,.14)}.primary-button,.secondary-button{min-height:40px;border-radius:4px;font-weight:650}.primary-button{width:100%;margin-top:16px;border:0;color:#fff;background:#168c79}.primary-button:hover{background:#117665}.primary-button.compact{width:auto;min-width:112px;margin-top:0;padding:0 16px}.secondary-button{padding:0 14px;border:1px solid #c6ced6;color:#34424e;background:#fff}.secondary-button:hover{background:#f7f9fa}.secondary-button.dark{border-color:#53616d;color:#fff;background:transparent}.form-error{margin:12px 0 0;color:#b42318;font-size:13px}.portal{min-height:100vh}.topbar{min-height:72px;display:flex;align-items:center;justify-content:space-between;gap:20px;padding:14px clamp(18px,4vw,56px);color:#fff;background:#17202a}.brand-lockup{display:flex;align-items:center;gap:13px}.brand-lockup .eyebrow{color:#a9bac8}.brand-lockup h1{font-size:18px}.toolbar{display:flex;align-items:center;gap:10px}.current-user{font-weight:650}.impersonated{padding:4px 8px;border:1px solid #e7b65a;border-radius:3px;color:#ffd98f;font-size:12px}.icon-button{width:40px;height:40px;border:1px solid #53616d;border-radius:4px;color:#fff;background:transparent;font-size:23px;line-height:1}.icon-button.loading{animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.error-banner{padding:12px clamp(18px,4vw,56px);color:#7a271a;background:#fee4e2;border-bottom:1px solid #fecdca}.summary-band{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));padding:0 clamp(18px,4vw,56px);background:#fff;border-bottom:1px solid #d9e0e6}.summary-item{min-height:100px;padding:22px 20px;border-right:1px solid #e3e8ec}.summary-item:first-child{padding-left:0}.summary-item:last-child{border-right:0}.summary-item span{display:block;margin-bottom:9px;color:#667684;font-size:12px}.summary-item strong{font-size:24px}.content-section{padding:34px clamp(18px,4vw,56px) 10px}.content-section.bottom{padding-bottom:40px}.section-heading{display:flex;align-items:end;justify-content:space-between;gap:18px;margin-bottom:15px}.section-heading h2{font-size:19px}.table-wrap{overflow-x:auto;background:#fff;border:1px solid #d9e0e6}table{width:100%;border-collapse:collapse}th,td{padding:13px 15px;text-align:left;border-bottom:1px solid #e5eaee;overflow-wrap:anywhere}th{color:#607080;background:#f9fafb;font-size:11px;text-transform:uppercase}td{font-size:13px}.node-name{font-weight:680}.muted{color:#7a8995}.nowrap{white-space:nowrap}.status-label{display:inline-flex;align-items:center;gap:7px;white-space:nowrap}.status-dot{width:8px;height:8px;border-radius:50%;background:#8a98a5}.status-dot.healthy{background:#168c79}.status-dot.warning{background:#dc8b19}.status-dot.offline{background:#d04437}.row-actions{display:flex;gap:7px}.table-action{min-height:30px;padding:0 9px;border:1px solid #b8c2cc;border-radius:4px;background:#fff;font-size:12px}.table-action.danger{color:#a9362b;border-color:#d7a8a3}.empty-state{margin:0;padding:36px;text-align:center;color:#73818d}dialog{width:min(640px,calc(100% - 32px));max-height:calc(100vh - 32px);padding:0;border:0;border-radius:6px;box-shadow:0 24px 70px rgba(0,0,0,.32)}dialog::backdrop{background:rgba(23,32,42,.68)}.dialog-form{padding:24px;background:#fff}.dialog-heading{display:flex;justify-content:space-between;gap:16px;margin-bottom:22px}.dialog-heading h2{font-size:20px}.dialog-close{width:36px;height:36px;border:1px solid #c6ced6;border-radius:4px;background:#fff}.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:15px}.form-grid label{margin:0}.form-grid input[type=text]{margin-top:7px}.form-grid .wide{grid-column:1/-1}.check-label{display:flex!important;align-items:center;gap:8px;grid-column:1/-1}.check-label input{width:18px;height:18px}.dialog-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:22px}.credential-list{margin:0}.credential-list dt{margin-top:14px;color:#607080;font-size:12px}.credential-list dd{margin:5px 0 0;padding:11px;background:#f4f6f8;font-family:Consolas,monospace;overflow-wrap:anywhere}[hidden]{display:none!important}@media(max-width:800px){.summary-band{grid-template-columns:repeat(2,minmax(0,1fr))}.summary-item:nth-child(2n){border-right:0}table{min-width:760px}}@media(max-width:560px){.topbar{align-items:flex-start}.toolbar{flex-wrap:wrap;justify-content:flex-end}.current-user{width:100%;text-align:right}.summary-item{padding:17px 14px}.summary-item:first-child{padding-left:14px}.section-heading{align-items:flex-start;flex-direction:column}.form-grid{grid-template-columns:1fr}.form-grid .wide,.check-label{grid-column:auto}}.summary-band{grid-template-columns:repeat(4,minmax(0,1fr))}.no-plan-state{min-height:calc(100vh - 72px);display:grid;place-items:center;background:#fff}.no-plan-state strong{font-size:28px}.no-plan-state[hidden]{display:none!important}input[type=number],select{width:100%;height:42px;padding:0 11px;border:1px solid #b8c2cc;border-radius:4px;outline:none;background:#fff}.form-grid input[type=number],.form-grid select{margin-top:7px}select:focus{border-color:#168c79;box-shadow:0 0 0 3px rgba(22,140,121,.14)}.package-form{padding:22px;background:#fff;border:1px solid #d9e0e6}.package-controls{display:flex;align-items:center;gap:12px}.package-controls input[type=file]{min-width:0;flex:1;padding:10px;border:1px solid #b8c2cc;border-radius:4px;background:#f9fafb}.package-status{margin:12px 0 0;color:#117665;font-size:13px}@media(max-width:800px){.summary-band{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:560px){.summary-band{grid-template-columns:1fr}.package-controls{align-items:stretch;flex-direction:column}.package-controls .primary-button{width:100%}}
:root {
  color: #18232d;
  background: #edf1f3;
  font-family: "Segoe UI Variable", "Segoe UI", "Microsoft YaHei", sans-serif;
  font-size: 15px;
  letter-spacing: 0;
  color-scheme: light;
  --ink: #18232d;
  --muted: #657581;
  --line: #d7e0e5;
  --line-strong: #bdcbd2;
  --surface: #ffffff;
  --surface-soft: #f5f8f9;
  --nav: #111a21;
  --nav-soft: #1c2932;
  --teal: #128f82;
  --teal-dark: #0c7067;
  --cyan: #1976b9;
  --amber: #d28a1d;
  --red: #c9473d;
}
* { box-sizing: border-box; }
html { min-width: 320px; background: #edf1f3; }
body { margin: 0; min-width: 320px; background: #edf1f3; color: var(--ink); }
button, input, select { font: inherit; letter-spacing: 0; }
button { cursor: pointer; }
button:disabled { cursor: not-allowed; opacity: .48; }
[hidden] { display: none !important; }
h1, h2 { margin: 0; color: var(--ink); font-weight: 700; letter-spacing: 0; }
.eyebrow { margin: 0 0 5px; color: #247b75; font-size: 10px; font-weight: 750; letter-spacing: 0; text-transform: uppercase; }
.login-view { min-height: 100vh; display: grid; place-items: center; padding: 24px; background: var(--nav); border-top: 3px solid #24b8a7; }
.login-panel { width: min(100%, 420px); padding: 34px; background: var(--surface); border: 1px solid #30414d; border-radius: 8px; box-shadow: 0 28px 80px rgba(0,0,0,.36); }
.login-panel .brand-mark { margin-bottom: 25px; }
.login-panel h1 { margin-bottom: 28px; font-size: 25px; }
.brand-mark { width: 52px; height: 52px; display: grid; place-items: center; color: #eafdf9; background: #17252e; border: 1px solid #3a5a66; border-left: 3px solid #24b8a7; border-radius: 6px; font-weight: 800; }
.brand-mark.small { width: 42px; height: 42px; margin: 0; color: #fff; background: #17323a; border-color: #326572; border-left-color: #2bd0bc; font-size: 12px; }
label { display: block; margin-bottom: 7px; color: #33434e; font-size: 12px; font-weight: 680; }
.password-label { margin-top: 15px; }
input, select { width: 100%; height: 43px; padding: 0 12px; color: var(--ink); background: #fff; border: 1px solid var(--line-strong); border-radius: 5px; outline: none; }
input:hover, select:hover { border-color: #91a7b2; }
input:focus, select:focus { border-color: var(--teal); box-shadow: 0 0 0 3px rgba(18,143,130,.14); }
.primary-button, .secondary-button { min-height: 40px; padding: 0 15px; border-radius: 5px; font-weight: 700; transition: background-color .15s ease, border-color .15s ease, color .15s ease, transform .15s ease; }
.primary-button { width: auto; margin: 0; color: #fff; background: var(--teal); border: 1px solid var(--teal); }
.primary-button:hover { background: var(--teal-dark); border-color: var(--teal-dark); }
.login-panel .primary-button { width: 100%; margin-top: 18px; }
.primary-button.compact { min-width: 116px; padding: 0 16px; }
.secondary-button { color: #334550; background: #fff; border: 1px solid var(--line-strong); }
.secondary-button:hover { color: #17252e; background: #f4f8f9; border-color: #8da2ac; }
.form-error { margin: 12px 0 0; color: #ad3129; font-size: 12px; }
.form-error.success { color: #13715f; }
.dashboard, .portal { min-height: 100vh; background: #edf1f3; }
.topbar { min-height: 76px; position: sticky; top: 0; z-index: 30; padding: 0; color: #fff; background: var(--nav); border-bottom: 1px solid #2a4652; box-shadow: 0 8px 22px rgba(10,20,27,.13); }
.topbar-inner { width: min(1600px, 100%); min-height: 76px; margin: 0 auto; padding: 12px 30px; display: flex; align-items: center; justify-content: space-between; gap: 24px; }
.brand-lockup { display: flex; align-items: center; gap: 13px; min-width: 0; }
.brand-lockup .eyebrow { color: #71cfc3; }
.brand-lockup h1 { color: #f5fbfc; font-size: 17px; }
.toolbar { display: flex; align-items: center; justify-content: flex-end; gap: 9px; min-width: 0; }
.updated-at, .software-version, .current-user { color: #aebdc5; font-size: 12px; }
.current-user { max-width: 180px; overflow: hidden; color: #e9f2f4; font-weight: 700; text-overflow: ellipsis; white-space: nowrap; }
.software-version { padding: 4px 8px; color: #8fd5cc; background: #17252e; border: 1px solid #34505a; border-radius: 4px; }
.icon-button { width: 40px; height: 40px; flex: 0 0 40px; padding: 0; color: #e7f4f5; background: #18252e; border: 1px solid #3a505b; border-radius: 5px; font-size: 21px; line-height: 1; }
.icon-button:hover { color: #fff; background: #24343e; border-color: #5c7782; }
.icon-button.loading { animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
.topbar .secondary-button { min-height: 40px; color: #e8f1f3; background: transparent; border-color: #405965; }
.topbar .secondary-button:hover { color: #fff; background: #22313a; border-color: #68808a; }
.error-banner { position: relative; z-index: 15; padding: 11px max(24px, calc((100% - 1540px) / 2)); color: #8f2d26; background: #fff0ee; border-bottom: 1px solid #efc2bd; font-size: 13px; }
.workspace { width: min(1600px, 100%); margin: 0 auto; padding: 24px 30px 56px; }
.summary-band { display: grid; gap: 12px; padding: 0; background: transparent; border: 0; }
.summary-item { min-width: 0; min-height: 104px; padding: 19px 20px; background: var(--surface); border: 1px solid var(--line); border-top: 2px solid var(--teal); border-radius: 7px; box-shadow: 0 5px 14px rgba(28,47,58,.045); }
.summary-item:nth-child(3n+2) { border-top-color: var(--cyan); }
.summary-item:nth-child(3n) { border-top-color: var(--amber); }
.summary-item span { display: block; margin-bottom: 10px; color: var(--muted); font-size: 11px; font-weight: 650; }
.summary-item strong { display: flex; align-items: center; gap: 8px; min-width: 0; color: var(--ink); font-size: 24px; font-weight: 720; overflow-wrap: anywhere; }
.content-section { padding: 30px 0 0; }
.content-section.bottom { padding-bottom: 0; }
.section-heading { min-height: 42px; display: flex; align-items: flex-end; justify-content: space-between; gap: 18px; margin-bottom: 12px; }
.section-heading h2 { font-size: 18px; }
.section-actions { display: flex; align-items: center; gap: 16px; }
.legend { display: flex; flex-wrap: wrap; gap: 13px; color: var(--muted); font-size: 11px; }
.legend span { display: inline-flex; align-items: center; gap: 6px; }
.table-wrap { position: relative; overflow-x: auto; background: var(--surface); border: 1px solid var(--line); border-radius: 7px; box-shadow: 0 6px 18px rgba(29,48,60,.045); scrollbar-color: #a8bac3 #edf2f4; scrollbar-width: thin; }
table { width: 100%; border-collapse: separate; border-spacing: 0; table-layout: auto; }
th, td { padding: 12px 14px; text-align: left; vertical-align: middle; border-bottom: 1px solid #e4eaed; overflow-wrap: anywhere; }
th { color: #5e707b; background: #f5f8f9; font-size: 10px; font-weight: 750; text-transform: uppercase; white-space: nowrap; }
td { color: #2c3c46; font-size: 12px; }
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: #f6faf9; }
.node-name { color: #172630; font-weight: 720; white-space: nowrap; }
.muted { color: #7b8992; }
.nowrap { white-space: nowrap; }
.status-label { min-height: 25px; display: inline-flex; align-items: center; gap: 7px; padding: 0 8px; color: #344650; background: #f2f6f7; border: 1px solid #dce5e8; border-radius: 4px; white-space: nowrap; }
.status-dot { width: 7px; height: 7px; display: inline-block; flex: 0 0 auto; border-radius: 50%; background: #8797a0; }
.status-dot.healthy { background: #10a18f; box-shadow: 0 0 0 3px rgba(16,161,143,.12); }
.status-dot.warning { background: var(--amber); box-shadow: 0 0 0 3px rgba(210,138,29,.12); }
.status-dot.offline { background: var(--red); box-shadow: 0 0 0 3px rgba(201,71,61,.11); }
.row-actions { display: flex; flex-wrap: wrap; gap: 6px; }
.table-action { min-height: 30px; padding: 0 9px; color: #34505d; background: #fff; border: 1px solid #b8c7ce; border-radius: 4px; font-size: 11px; font-weight: 650; white-space: nowrap; }
.table-action:hover { color: #173943; background: #f1f7f7; border-color: #7ca2a8; }
.table-action.neutral { color: #176b63; border-color: #91c5bd; }
.table-action.danger { color: #a63a32; border-color: #dfaaa5; }
.table-action.danger:hover { background: #fff3f1; border-color: #cf7d75; }
.empty-state { margin: 0; padding: 38px 20px; color: #73838c; text-align: center; font-size: 12px; }
dialog { width: min(680px, calc(100% - 32px)); max-height: calc(100vh - 32px); padding: 0; color: var(--ink); background: #fff; border: 1px solid #84959e; border-radius: 8px; box-shadow: 0 28px 80px rgba(9,20,27,.38); }
dialog::backdrop { background: rgba(9,17,22,.72); }
.edge-form, .dialog-form { padding: 25px; background: #fff; }
.dialog-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 22px; padding-bottom: 16px; border-bottom: 1px solid #e1e8eb; }
.dialog-heading h2 { font-size: 19px; }
.dialog-close { width: 36px; height: 36px; flex: 0 0 36px; padding: 0; color: #445660; background: #f7f9fa; border: 1px solid #c5d1d6; border-radius: 5px; font-size: 16px; }
.dialog-close:hover { background: #edf3f4; border-color: #95a9b2; }
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; }
.form-grid label { margin: 0; }
.form-grid input, .form-grid select { margin-top: 7px; }
.form-grid .wide { grid-column: 1 / -1; }
.check-label { display: flex !important; align-items: center; gap: 8px; grid-column: 1 / -1; min-height: 40px; margin: 0; }
.check-label input { width: 18px; height: 18px; margin: 0; accent-color: var(--teal); }
.dialog-actions { display: flex; justify-content: flex-end; gap: 9px; margin-top: 22px; }
.assigned-plan-block { margin-bottom: 18px; padding: 14px; background: var(--surface-soft); border: 1px solid var(--line); }
.assigned-plan-block > strong { display: block; margin-bottom: 10px; font-size: 12px; }
.assigned-plan-list { display: grid; gap: 7px; }
.assigned-plan-item { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 9px 10px; background: #fff; border: 1px solid var(--line); font-size: 12px; }
.assigned-plan-item span { min-width: 0; overflow-wrap: anywhere; }
.assigned-plan-block > .muted { margin: 0; font-size: 12px; }
.marketplace-authorization-dialog { width: min(1040px, calc(100% - 32px)); }
.marketplace-authorization-list { margin-top: 24px; padding-top: 20px; border-top: 1px solid var(--line); }
.marketplace-authorization-list > strong { display: block; margin-bottom: 10px; font-size: 13px; }
.marketplace-authorization-list .empty-state { padding: 24px; border: 1px solid var(--line); border-top: 0; }
.credential-list { margin: 0; }
.credential-list dt { margin-top: 14px; color: var(--muted); font-size: 11px; font-weight: 650; }
.credential-list dd { margin: 5px 0 0; padding: 12px; color: #17343d; background: #f1f6f6; border: 1px solid #d9e5e6; border-radius: 5px; font-family: Consolas, "Cascadia Mono", monospace; font-size: 12px; overflow-wrap: anywhere; }
.edge-metric { display: grid; gap: 3px; min-width: 116px; white-space: nowrap; }
.edge-metric span { color: #31515d; font-size: 11px; }
.edge-metric small, .field-hint { color: var(--muted); font-size: 11px; }
.field-hint { margin: 13px 0 0; line-height: 1.6; }
@media (max-width: 900px) {
  .topbar-inner { padding-inline: 20px; }
  .workspace { padding: 20px 20px 44px; }
  .updated-at { display: none; }
  .table-wrap table { min-width: 760px; }
}
@media (max-width: 600px) {
  .login-view { padding: 16px; }
  .login-panel { padding: 26px 22px; }
  .topbar { position: relative; }
  .topbar-inner { min-height: 0; align-items: flex-start; padding: 12px 16px; }
  .brand-lockup h1 { font-size: 15px; }
  .toolbar { flex-wrap: wrap; }
  .software-version, .updated-at { display: none; }
  .workspace { padding: 16px 14px 36px; }
  .summary-item { min-height: 88px; padding: 16px; }
  .summary-item strong { font-size: 21px; }
  .section-heading { align-items: flex-start; flex-direction: column; }
  .section-actions { width: 100%; align-items: flex-start; justify-content: space-between; }
  .form-grid { grid-template-columns: 1fr; }
  .form-grid .wide, .check-label { grid-column: auto; }
  .dialog-actions { flex-wrap: wrap; }
}

.portal .summary-band { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.portal .summary-item { padding: 19px 20px; border-right: 1px solid var(--line); }
.portal .summary-item:last-child strong { font-size: 18px; }
.impersonated { padding: 5px 8px; color: #ffd994; background: #2b271c; border: 1px solid #6e5a30; border-radius: 4px; font-size: 11px; white-space: nowrap; }
.no-plan-state { min-height: calc(100vh - 124px); display: grid; place-items: center; background: #fff; border: 1px solid var(--line); border-radius: 7px; }
.no-plan-state strong { color: #5d6d77; font-size: 22px; }
.no-plan-state[hidden] { display: none !important; }
.package-form { padding: 20px; background: #fff; border: 1px solid var(--line); border-radius: 7px; box-shadow: 0 6px 18px rgba(29,48,60,.045); }
.package-controls { display: flex; align-items: center; gap: 10px; }
.package-controls input[type=file] { min-width: 0; flex: 1; height: auto; padding: 9px; background: #f6f8f9; border: 1px solid var(--line-strong); border-radius: 5px; }
.package-controls input[type=file]::file-selector-button { min-height: 30px; margin-right: 10px; padding: 0 11px; color: #31505b; background: #fff; border: 1px solid #b6c5cc; border-radius: 4px; font: inherit; font-size: 11px; font-weight: 650; cursor: pointer; }
.package-status { margin: 11px 0 0; color: var(--teal-dark); font-size: 12px; }
.log-section .table-wrap { max-height: 520px; overflow: auto; }
.log-section thead th { position: sticky; top: 0; z-index: 2; box-shadow: 0 1px 0 #dce5e8; }
@media (max-width: 900px) {
  .portal .summary-band { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 600px) {
  .portal .summary-band { grid-template-columns: 1fr; }
  .portal .summary-item { padding: 16px; }
  .portal .toolbar { max-width: 55%; }
  .portal .current-user { width: 100%; text-align: right; }
  .package-controls { align-items: stretch; flex-direction: column; }
  .package-controls .primary-button { width: 100%; }
  .log-section .table-wrap { max-height: 430px; }
}

:root {
  background: #f0f0f0;
  --ink: #252b30;
  --muted: #68747d;
  --line: #e1e4e6;
  --line-strong: #c8cdd1;
  --surface-soft: #f5f6f7;
  --nav: #2e2e2e;
  --nav-soft: #3b3b3b;
  --teal: #f39a18;
  --teal-dark: #d87d00;
  --cyan: #36a2d8;
  --amber: #e7ad55;
}
html, body, .dashboard, .portal { background: #f0f0f0; }
.eyebrow { color: #d67c00; }
.login-view { background: #2d2d2d; border-top-color: #f39a18; }
.login-panel { border-color: #4b4b4b; border-top: 3px solid #f39a18; border-radius: 3px; }
.brand-mark { color: #fff; background: #333; border-color: #555; border-left-color: #f39a18; border-radius: 3px; }
.brand-mark.small { color: #ffc15d; background: #333; border-color: #555; border-left-color: #f39a18; }
.topbar { min-height: 64px; background: #2e2e2e; border-bottom: 0; box-shadow: none; }
.topbar-inner { width: 100%; min-height: 64px; max-width: none; padding: 0 18px; }
.brand-lockup { min-width: 180px; }
.brand-lockup .eyebrow { color: #f4a634; }
.brand-lockup h1 { color: #f7f7f7; }
.software-version { color: #ffd28c; background: #383838; border-color: #565656; }
.icon-button { color: #f2f2f2; background: #383838; border-color: #595959; }
.icon-button:hover { background: #464646; border-color: #777; }
.topbar .secondary-button { color: #f3f3f3; border-color: #666; }
.topbar .secondary-button:hover { background: #444; border-color: #888; }
.sidebar { width: 198px; position: fixed; top: 64px; bottom: 0; left: 0; z-index: 25; overflow-y: auto; color: #e8e8e8; background: #333; border-right: 1px solid #454545; }
.sidebar-group { border-bottom: 1px solid #4a4a4a; }
.sidebar-group p { margin: 0; padding: 14px 17px; color: #fff; background: #484848; font-size: 12px; font-weight: 720; }
.sidebar nav { display: block; padding: 5px 0; }
.sidebar a { min-height: 41px; display: flex; align-items: center; padding: 0 17px; color: #d3d3d3; border-left: 3px solid transparent; text-decoration: none; font-size: 12px; transition: color .15s ease, background-color .15s ease, border-color .15s ease; }
.sidebar a:hover { color: #fff; background: #3e3e3e; }
.sidebar a.active { color: #fff; background: #3d3d3d; border-left-color: #f39a18; }
.workspace { width: auto; max-width: none; margin: 0 0 0 198px; padding: 18px 18px 48px; }
.error-banner { margin-left: 198px; padding: 11px 18px; }
.summary-band, .content-section { scroll-margin-top: 78px; }
.summary-band { gap: 14px; }
.summary-item { min-height: 108px; padding: 18px 19px; background: #fff; border: 1px solid #e4e4e4; border-top: 1px solid #e4e4e4; border-radius: 3px; box-shadow: 0 1px 2px rgba(0,0,0,.035); }
.summary-item:nth-child(3n+2), .summary-item:nth-child(3n) { border-top-color: #e4e4e4; }
.summary-item strong { color: #1f2529; }
.summary-item strong::first-letter { color: #f08d00; }
.content-section { padding-top: 26px; }
.section-heading { min-height: 40px; margin-bottom: 0; padding: 0 14px 11px; background: #fff; border: 1px solid #e4e4e4; border-bottom: 0; border-radius: 3px 3px 0 0; }
.section-heading .eyebrow { margin-top: 10px; }
.section-heading h2 { font-size: 17px; font-weight: 650; }
.table-wrap { border-color: #e4e4e4; border-radius: 0 0 3px 3px; box-shadow: 0 1px 2px rgba(0,0,0,.03); }
th { color: #5d676e; background: #f5f6f7; }
tbody tr:hover { background: #fffaf3; }
.primary-button { background: #f39a18; border-color: #f39a18; }
.primary-button:hover { background: #d87d00; border-color: #d87d00; }
input:focus, select:focus { border-color: #ed9419; box-shadow: 0 0 0 3px rgba(243,154,24,.14); }
.status-dot.healthy { background: #25a98f; }
.status-dot.warning { background: #e7a52f; }
.status-label { border-radius: 2px; }
.table-action, .primary-button, .secondary-button, input, select, .icon-button, .dialog-close { border-radius: 2px; }
.package-form { border-radius: 0 0 3px 3px; }
dialog { border-radius: 3px; }
@media (max-width: 900px) {
  .sidebar { width: auto; position: static; display: flex; overflow-x: auto; overflow-y: hidden; border-right: 0; border-bottom: 1px solid #444; }
  .sidebar-group { display: flex; flex: 0 0 auto; border: 0; }
  .sidebar-group p { display: none; }
  .sidebar nav { display: flex; padding: 0; }
  .sidebar a { min-height: 44px; padding: 0 14px; border-left: 0; border-bottom: 2px solid transparent; white-space: nowrap; }
  .sidebar a.active { border-bottom-color: #f39a18; }
  .workspace { margin-left: 0; padding: 16px 14px 38px; }
  .error-banner { margin-left: 0; }
  .summary-band, .content-section { scroll-margin-top: 16px; }
}
@media (max-width: 600px) {
  .topbar { position: relative; }
  .topbar-inner { padding: 11px 14px; }
  .brand-lockup { min-width: 0; }
  .workspace { padding-top: 14px; }
  .summary-band { gap: 10px; }
  .section-heading { padding-inline: 12px; }
}

:root {
  --console-blue: #356cff;
  --console-blue-dark: #2457df;
  --console-text: #202936;
  --console-muted: #5f6875;
  --console-line: #e1e5eb;
  --console-soft: #f5f6f9;
}
html, body, .portal { background: #f5f6f8; }
.portal { color: var(--console-text); }
.portal .topbar {
  min-height: 50px;
  position: static;
  color: var(--console-text);
  background: #f5f6f8;
  border: 0;
  box-shadow: none;
}
.portal .topbar-inner {
  width: 100%;
  min-height: 50px;
  max-width: none;
  padding: 0 22px;
}
.portal .brand-lockup { min-width: 0; }
.portal .brand-lockup h1 { color: #121923; font-size: 16px; font-weight: 500; }
.portal .toolbar { gap: 8px; }
.portal .current-user { color: #4c5663; font-size: 12px; }
.portal .impersonated { color: #9a6500; background: #fff8e6; border-color: #ead59b; }
.portal .icon-button {
  width: 32px;
  height: 32px;
  flex-basis: 32px;
  color: #566170;
  background: #fff;
  border-color: #d7dce3;
  border-radius: 2px;
  font-size: 18px;
}
.portal .icon-button:hover { color: var(--console-blue); background: #fff; border-color: #aebde8; }
.portal .topbar .secondary-button {
  min-height: 32px;
  padding: 0 12px;
  color: #4b5664;
  background: #fff;
  border-color: #d7dce3;
  border-radius: 2px;
  font-size: 12px;
}
.portal .topbar .secondary-button:hover { color: var(--console-blue); background: #fff; border-color: #aebde8; }
.portal .back-button { color: var(--console-blue); border-color: #b9c7ef; }
.service-list-view { min-height: calc(100vh - 50px); padding: 10px 22px 22px; }
.service-catalog {
  min-height: 188px;
  padding: 16px;
  background: #fff;
  border: 1px solid var(--console-line);
}
.service-filter-form { display: flex; align-items: center; gap: 10px; margin-bottom: 18px; }
.service-filter-form select { width: 96px; flex: 0 0 96px; }
.service-filter-form input { width: 208px; flex: 0 1 208px; }
.service-filter-form input,
.service-filter-form select { height: 33px; border-radius: 0; font-size: 12px; }
.service-filter-form .primary-button { min-height: 33px; min-width: 58px; padding: 0 15px; }
.service-filter-form .purchase-button { min-width: 100px; margin-left: auto; }
.service-table-wrap { overflow-x: auto; border: 0; scrollbar-width: thin; }
.service-table { min-width: 1260px; table-layout: auto; border-collapse: collapse; }
.service-table th { height: 40px; padding: 0 13px; color: #222b36; background: #f3f4f7; font-size: 12px; font-weight: 600; text-align: left; white-space: nowrap; }
.service-table td { height: 64px; padding: 9px 13px; color: #27313d; border-bottom: 1px solid var(--console-line); font-size: 12px; white-space: nowrap; }
.service-table tbody tr:hover { background: #f9fbff; }
.service-product { display: grid; gap: 5px; }
.service-product strong { color: #25303b; font-size: 12px; font-weight: 600; }
.service-product small { color: #8b949f; font-size: 11px; }
.service-serial { max-width: 185px; overflow: hidden; text-overflow: ellipsis; }
.service-bandwidth { display: grid; gap: 7px; color: var(--console-blue); }
.service-state-normal { color: #19a83d; }
.service-state-disabled { color: #c27b17; }
.service-manage-button {
  min-height: 28px;
  padding: 0;
  color: var(--console-blue);
  background: transparent;
  border: 0;
  font-size: 12px;
}
.service-manage-button:hover { color: var(--console-blue-dark); text-decoration: underline; }
.management-view { min-height: 0; }
.console-shell {
	min-height: 0;
  margin: 0 6px 8px;
  padding: 12px 10px 22px;
  background: #fff;
  border: 1px solid var(--console-line);
}
.module-tabs {
  min-height: 40px;
  display: flex;
  align-items: stretch;
  overflow-x: auto;
  overflow-y: hidden;
  padding: 0 4px;
  background: var(--console-soft);
  scrollbar-width: thin;
}
.module-tabs a {
  min-width: max-content;
  display: flex;
  align-items: center;
  padding: 0 17px;
  color: #4b535e;
  border-top: 2px solid transparent;
  text-decoration: none;
  font-size: 12px;
  white-space: nowrap;
}
.module-tabs a:hover { color: var(--console-blue); background: #fafbff; }
.module-tabs a.active { color: var(--console-blue); background: #fff; border-top-color: var(--console-blue); }
.visually-hidden { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
.console-panel[hidden] { display: none !important; }
.forwarding-panel { padding-top: 0 !important; }
.gateway-panel,
.sdk-panel,
.windows-sdk-panel { padding-top: 0 !important; }
.sdk-integration-notice { min-height: 39px; display: flex; align-items: center; gap: 8px; padding: 8px 10px; color: #27344a; background: #eef3ff; border: 1px solid var(--console-blue); font-size: 12px; }
.sdk-integration-info { width: 14px; height: 14px; flex: 0 0 14px; display: grid; place-items: center; color: #fff; background: var(--console-blue); border-radius: 50%; font: 700 10px/1 Arial,sans-serif; }
.sdk-integration-actions { display: flex; align-items: center; gap: 10px; padding-top: 14px; }
.sdk-platform-downloads { display: flex; align-items: center; gap: 8px; }
.sdk-integration-actions .primary-button,
.sdk-integration-actions .secondary-button { min-height: 33px; margin: 0; padding: 0 15px; font-size: 12px; }
.sdk-integration-actions .primary-button { min-width: 112px; }
.windows-sdk-panel .package-status { margin: 10px 0 0; color: #26734d; font-size: 12px; }
.windows-sdk-panel .form-error { margin-top: 10px; }
.sdk-credential-dialog { width: min(560px,calc(100% - 32px)); }
.sdk-credential-heading { align-items: center; margin-bottom: 22px; }
.sdk-credential-heading h2 { font-size: 15px; }
.sdk-credential-close { width: 30px; height: 30px; padding: 0; border: 0; color: #9099a2; background: transparent; font-size: 25px; line-height: 1; }
.sdk-credential-close:hover { color: #27344a; }
.sdk-credential-list { display: grid; grid-template-columns: 112px minmax(0,1fr); align-items: start; gap: 16px 10px; margin: 0; }
.sdk-credential-list dt { padding-top: 2px; text-align: right; color: #3c4650; font-size: 12px; }
.sdk-credential-list dd { min-width: 0; margin: 0; color: #27313a; font: 12px/1.65 Consolas,"Microsoft YaHei",monospace; overflow-wrap: anywhere; }
.sdk-credential-warning { margin: 14px 0 0 122px; color: #ef2d2d; font-size: 12px; line-height: 1.65; }
.sdk-credential-dialog .dialog-actions { margin-top: 24px; }
.gateway-toolbar { min-height: 33px; display: flex; align-items: center; gap: 10px; margin-bottom: 17px; }
.gateway-toolbar .primary-button,
.sdk-panel .primary-button { min-height: 33px; min-width: 58px; padding: 0 15px; }
.gateway-toolbar .secondary-button { min-height: 33px; padding: 0 15px; font-size: 12px; }
.realip-download-toolbar { margin-top: 17px; margin-bottom: 0; }
.gateway-table-wrap { min-height: 0; border: 0 !important; }
.gateway-table { min-width: 880px; border-collapse: collapse; }
.gateway-table th { height: 41px; padding: 0 17px; color: #202a35; background: #f2f3f6; font-size: 12px; font-weight: 600; text-transform: none; white-space: nowrap; }
.gateway-table td { height: 58px; padding: 9px 17px; font-size: 12px; white-space: nowrap; }
.gateway-table .row-actions { align-items: center; }
.gateway-table .gateway-code-copy { min-height: 27px; padding: 0 8px; color: var(--console-blue); border-color: #b9c8f4; }
.gateway-table .gateway-code-copy:hover { color: #fff; background: var(--console-blue); border-color: var(--console-blue); }
.gateway-device-cell { display: grid; gap: 4px; }
.gateway-device-cell strong { font-weight: 650; }
.gateway-device-meta { display: none; color: #7a8491; font-size: 10px; font-weight: 400; overflow-wrap: anywhere; }
.gateway-empty { min-height: 160px; display: grid; place-items: center; color: #687380; background: #fff; }
.gateway-empty[hidden] { display: none !important; }
.package-platform-tabs { display: grid; grid-template-columns: repeat(3,minmax(120px,190px)); gap: 0; margin-bottom: 23px; border-bottom: 1px solid var(--console-line); }
.package-platform-tab { position: relative; min-height: 42px; padding: 0 18px; color: #5f6975; background: #fff; border: 0; font-size: 12px; font-weight: 600; }
.package-platform-tab:hover { color: var(--console-blue); background: #f8f9fc; }
.package-platform-tab.active { color: var(--console-blue); background: #f7f9ff; }
.package-platform-tab.active::after { content: ""; position: absolute; right: 0; bottom: -1px; left: 0; height: 2px; background: var(--console-blue); }
.package-workflow[hidden] { display: none !important; }
.mobile-sdk-integration { padding-bottom: 2px; }
.mobile-sdk-integration .package-status { margin: 10px 0 0; color: #26734d; }
.mobile-sdk-integration .form-error { margin-top: 10px; }
.local-package-layout { margin-top: 13px; border-top: 1px solid var(--console-line); border-bottom: 1px solid var(--console-line); }
.local-package-tool,.local-package-job,.local-package-finish { min-height: 92px; display: grid; grid-template-columns: 30px minmax(0,1fr) auto; align-items: center; gap: 14px; padding: 17px 0; }
.local-package-job,.local-package-finish { border-top: 1px solid var(--console-line); }
.local-package-finish { grid-template-columns: 30px minmax(0,1fr); }
.local-package-step { width: 28px; height: 28px; display: grid; place-items: center; color: #fff; background: var(--console-blue); border-radius: 50%; font-size: 12px; font-weight: 700; }
.local-package-tool strong,.local-package-job strong,.local-package-finish strong { display: block; color: #2f3b49; font-size: 13px; font-weight: 650; }
.local-package-tool p,.local-package-job p,.local-package-finish p { margin: 5px 0 0; color: #74808c; font-size: 11px; line-height: 1.55; }
.local-package-download { display: inline-flex; align-items: center; justify-content: center; min-height: 33px; padding: 0 14px; text-decoration: none; white-space: nowrap; }
.local-package-code-row { width: min(560px,100%); display: grid; grid-template-columns: minmax(0,1fr) auto; margin-top: 11px; }
.local-package-code-row[hidden] { display: none !important; }
.local-package-code-row code { min-height: 34px; display: flex; align-items: center; padding: 6px 10px; overflow-wrap: anywhere; color: #253d68; background: #f5f7fb; border: 1px solid #cdd6e2; border-right: 0; font-size: 12px; }
.local-package-code-row .secondary-button { min-height: 34px; padding: 0 12px; border-radius: 0; }
.local-package-expiry { color: #ab6416 !important; }
.sdk-panel .package-status { margin: 10px 0 0; color: #26734d; }
.bandwidth-panel { padding-top: 0 !important; }
.bandwidth-filter-form { display: grid; grid-template-columns: minmax(150px, .75fr) auto minmax(390px, 1.6fr) auto; align-items: end; gap: 14px; margin: 0; padding: 14px 16px; background: #f7f8fa; border: 1px solid var(--console-line); }
.bandwidth-scope-label { min-width: 0; margin: 0; }
.bandwidth-scope-label > span,
.bandwidth-custom-range label > span { display: block; margin-bottom: 5px; color: #75818d; font-size: 9px; font-weight: 500; }
.bandwidth-scope-label select { width: 100%; height: 33px; border-radius: 0; font-size: 12px; }
.bandwidth-range-buttons { height: 33px; display: flex; gap: 0; }
.range-button { min-width: 74px; height: 33px; padding: 0 13px; color: #26303b; background: #fff; border: 1px solid var(--console-line); font-size: 12px; }
.bandwidth-range-buttons .range-button + .range-button { border-left: 0; }
.range-button.active { color: #fff; background: var(--console-blue); border-color: var(--console-blue); }
.bandwidth-custom-range { min-width: 0; display: grid; grid-template-columns: minmax(160px, 1fr) auto minmax(160px, 1fr); align-items: end; gap: 8px; }
.bandwidth-custom-range label { min-width: 0; margin: 0; }
.bandwidth-filter-form input[type=datetime-local] { width: 100%; min-width: 0; height: 33px; padding: 0 9px; border: 1px solid var(--console-line); border-radius: 0; font-size: 11px; }
.bandwidth-time-separator { align-self: end; padding-bottom: 9px; color: #89939d; font-size: 9px; }
.bandwidth-filter-form > .primary-button { height: 33px; min-height: 33px; }
.bandwidth-workspace { min-width: 0; display: grid; grid-template-columns: 248px minmax(0, 1fr); margin-top: 14px; background: #fff; border: 1px solid var(--console-line); }
.bandwidth-readout { min-width: 0; display: grid; grid-template-rows: repeat(2,minmax(0,1fr)); background: #f7f8fa; border-right: 1px solid var(--console-line); }
.bandwidth-readout-direction { min-height: 142px; padding: 20px; }
.bandwidth-readout-direction + .bandwidth-readout-direction { border-top: 1px solid var(--console-line); }
.bandwidth-readout-direction h4 { margin: 0 0 16px; color: #2e6ae6; font-size: 11px; font-weight: 650; }
.download-readout h4 { color: #c7652b; }
.bandwidth-readout-direction dl { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin: 0; }
.bandwidth-readout-direction dt { margin-bottom: 6px; color: #808b96; font-size: 9px; }
.bandwidth-readout-direction dd { min-width: 0; margin: 0; color: #202936; font-size: 14px; font-weight: 550; overflow-wrap: anywhere; }
.bandwidth-plot-stack { min-width: 0; }
.bandwidth-panel .bandwidth-plot-stack .trend-section { min-width: 0; display: grid; grid-template-columns: 154px minmax(0, 1fr); margin: 0; }
.bandwidth-panel .bandwidth-plot-stack .trend-section + .trend-section { margin: 0; border-top: 1px solid var(--console-line); }
.bandwidth-panel .bandwidth-plot-stack .trend-heading { min-width: 0; display: flex; align-items: flex-start; flex-direction: column; justify-content: space-between; gap: 16px; margin: 0; padding: 22px 18px; background: #fff; border-right: 1px solid var(--console-line); }
.bandwidth-panel .bandwidth-plot-stack .trend-heading h3 { margin: 0 0 4px; font-size: 12px; }
.bandwidth-panel .bandwidth-plot-stack .trend-heading p { margin: 0; color: #89939e; font-size: 9px; line-height: 1.5; }
.bandwidth-panel .bandwidth-plot-stack .chart-legend { align-items: flex-start; flex-direction: column; gap: 8px; font-size: 10px; }
.bandwidth-panel .bandwidth-plot-stack .chart-legend span::before { width: 18px; }
.bandwidth-panel .bandwidth-plot-stack .chart-scroll { padding: 4px 8px 0 0; }
.bandwidth-panel .bandwidth-trend-panel .trend-chart,
.bandwidth-panel .connection-trend-panel .trend-chart { height: 286px; }
.trend-section { margin-top: 18px; }
.trend-section + .trend-section { margin-top: 28px; }
.trend-heading { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin: 0 0 8px; padding: 0 6px; }
.trend-heading h3 { margin: 0; color: #262f39; font-size: 14px; font-weight: 600; }
.chart-legend { display: flex; align-items: center; gap: 20px; color: #46515e; font-size: 12px; }
.chart-legend span { display: inline-flex; align-items: center; gap: 7px; }
.chart-legend span::before { width: 26px; height: 2px; content: ""; background: currentColor; }
.upload-legend { color: #4e82ff; }
.download-legend { color: #ff9148; }
.connection-legend { color: #27b980; }
.daily-device-legend { color: #4e82ff; }
.peak-connection-legend { color: #356cff; }
.chart-scroll { width: 100%; overflow-x: auto; }
.trend-chart { width: 100%; min-width: 880px; height: 260px; display: block; background: #fff; }
.trend-chart .chart-grid { stroke: #dfe3e8; stroke-width: 1; }
.trend-chart .chart-label { fill: #5b6570; font-size: 11px; }
.trend-chart .chart-line { fill: none; stroke-width: 2; vector-effect: non-scaling-stroke; }
.trend-chart .chart-empty { fill: #8a939d; font-size: 13px; text-anchor: middle; }
.connection-statistics-panel,
.connection-details-panel { padding-top: 0 !important; }
.connection-statistics-filter { display: grid; grid-template-columns: auto minmax(390px,1fr) auto; align-items: end; gap: 14px; margin: 0; padding: 14px 16px; background: #f7f8fa; border: 1px solid var(--console-line); }
.connection-statistics-custom-range label > span { display: block; margin-bottom: 5px; color: #75818d; font-size: 9px; font-weight: 500; }
.connection-statistics-filter input[type=datetime-local] { width: 100%; min-width: 0; height: 33px; padding: 0 9px; border: 1px solid var(--console-line); border-radius: 0; font-size: 11px; }
.connection-statistics-filter > .primary-button { height: 33px; min-height: 33px; }
.daily-device-readout h4 { color: #4e82ff; }
.connection-peak-readout h4 { color: #356cff; }
.connection-statistics-panel .connection-statistics-plot-stack .trend-section { min-width: 0; display: grid; grid-template-columns: 154px minmax(0,1fr); margin: 0; }
.connection-statistics-panel .connection-statistics-plot-stack .trend-section + .trend-section { margin: 0; border-top: 1px solid var(--console-line); }
.connection-statistics-panel .connection-statistics-plot-stack .trend-heading { min-width: 0; display: flex; align-items: flex-start; flex-direction: column; justify-content: space-between; gap: 16px; margin: 0; padding: 22px 18px; background: #fff; border-right: 1px solid var(--console-line); }
.connection-statistics-panel .connection-statistics-plot-stack .trend-heading h3 { margin: 0 0 4px; font-size: 12px; }
.connection-statistics-panel .connection-statistics-plot-stack .trend-heading p { margin: 0; color: #89939e; font-size: 9px; line-height: 1.5; }
.connection-statistics-panel .connection-statistics-plot-stack .chart-legend { align-items: flex-start; flex-direction: column; gap: 8px; font-size: 10px; }
.connection-statistics-panel .connection-statistics-plot-stack .chart-legend span::before { width: 18px; }
.connection-statistics-panel .connection-statistics-plot-stack .chart-scroll { padding: 4px 8px 0 0; }
.connection-statistics-panel .daily-device-trend .trend-chart,
.connection-statistics-panel .peak-connection-trend .trend-chart { height: 286px; }
.connection-detail-filter { display: grid; grid-template-columns: repeat(2,minmax(150px,1fr)) minmax(110px,.65fr) minmax(120px,.7fr) auto; gap: 14px; align-items: end; margin: 0; padding: 14px 16px; background: #f7f8fa; border: 1px solid var(--console-line); }
.connection-detail-filter label { min-width: 0; margin: 0; }
.connection-detail-filter label > span { display: block; margin-bottom: 5px; color: #75818d; font-size: 9px; font-weight: 500; }
.connection-detail-filter input,
.connection-detail-filter select { width: 100%; height: 33px; border-radius: 0; font-size: 12px; }
.connection-detail-filter > .primary-button { min-width: 60px; height: 33px; min-height: 33px; }
.connection-detail-readout .connection-result-readout h4 { color: #27a578; }
.connection-detail-readout .connection-result-readout dl { row-gap: 20px; }
.connection-action-readout h4 { color: #586575; }
.connection-detail-actions { display: grid; grid-template-columns: 1fr; gap: 9px; }
.connection-detail-actions .primary-button,
.connection-detail-actions .secondary-button { width: 100%; min-height: 33px; }
.connection-details-panel .connection-detail-table-stack .trend-section { min-width: 0; display: grid; grid-template-columns: 154px minmax(0,1fr); margin: 0; }
.connection-details-panel .connection-detail-table-stack .trend-heading { min-width: 0; display: flex; align-items: flex-start; flex-direction: column; justify-content: flex-start; gap: 16px; margin: 0; padding: 22px 18px; background: #fff; border-right: 1px solid var(--console-line); }
.connection-details-panel .connection-detail-table-stack .trend-heading h3 { margin: 0 0 4px; font-size: 12px; }
.connection-details-panel .connection-detail-table-stack .trend-heading p { margin: 0; color: #89939e; font-size: 9px; line-height: 1.5; }
.connection-detail-table-surface { min-width: 0; }
.connection-detail-table-wrap { min-height: 276px; border: 0 !important; }
.connection-detail-table { min-width: 1050px; }
.connection-detail-table input[type=checkbox] { width: 16px; height: 16px; min-height: 0; margin: 0; accent-color: var(--console-blue); vertical-align: middle; }
.connection-detail-table th { height: 41px; white-space: nowrap; }
.connection-detail-table td { height: 54px; white-space: nowrap; }
.connection-detail-table .blocked-client { color: #c12f2a; font-weight: 650; }
.analysis-subsection { margin-top: 28px; padding-top: 24px; border-top: 1px solid var(--console-line); }
.mapping-filter-form { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 17px; }
.mapping-filter-form input,
.mapping-filter-form select { width: 180px; height: 33px; border-radius: 0; font-size: 12px; }
.mapping-filter-form select { width: 120px; }
.mapping-filter-form .primary-button { min-height: 33px; min-width: 58px; padding: 0 15px; }
.mapping-filter-form .primary-button:disabled { color: #8a929d; background: #e8ebf0; border-color: #e1e4e9; cursor: not-allowed; }
.forwarding-table-wrap { min-height: 0; border: 0 !important; }
.forwarding-table { min-width: 1460px; border-collapse: collapse; }
.forwarding-table th { height: 41px; padding: 0 17px; color: #202a35; background: #f2f3f6; font-size: 12px; font-weight: 600; text-transform: none; white-space: nowrap; }
.forwarding-table th:first-child { line-height: 0; }
.forwarding-table td { height: 58px; padding: 9px 17px; font-size: 12px; white-space: nowrap; }
.forwarding-table input[type=checkbox] { width: 16px; height: 16px; min-height: 0; display: block; margin: 0; accent-color: var(--console-blue); }
.mapping-remark { max-width: 180px; overflow: hidden; text-overflow: ellipsis; }
.forwarding-empty { min-height: 160px; display: grid; place-items: center; color: #687380; background: #fff; }
.forwarding-empty[hidden] { display: none !important; }
.portal .error-banner {
  margin: 0 22px 10px;
  padding: 10px 14px;
  color: #a3312a;
  background: #fff1f0;
  border: 1px solid #efc5c2;
  font-size: 12px;
}
.portal .workspace { width: auto; max-width: none; margin: 0; padding: 10px 0 40px; }
.portal .summary-band {
  min-height: 168px;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0;
  padding: 0;
  background: #fff;
  border: 1px solid var(--console-line);
  scroll-margin-top: 14px;
}
.portal .summary-item {
  min-height: 166px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 26px 42px;
  background: #fff;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}
.portal .summary-item span { margin-bottom: 12px; color: #151b24; font-size: 12px; font-weight: 500; }
.portal .summary-item strong { display: flex; align-items: baseline; color: #252c36; font-size: 21px; font-weight: 400; }
.portal .summary-item strong b { color: var(--console-blue); font-size: 32px; font-weight: 400; }
.portal .summary-item strong small { margin-left: 2px; color: #313945; font-size: 17px; font-weight: 400; }
.portal .summary-item strong small span { display: inline; margin: 0; color: inherit; font-size: inherit; }
.portal .summary-item.bandwidth-plan-summary strong { flex-wrap: wrap; gap: 2px 4px; font-size: 13px; line-height: 1.3; }
.portal .summary-item.bandwidth-plan-summary strong + strong { margin-top: 8px; }
.portal .summary-item.bandwidth-plan-summary strong b { font-size: 18px; white-space: nowrap; }
.portal .summary-item.bandwidth-plan-summary strong small { margin: 0; font-size: 12px; white-space: normal; }
.plan-overview { margin-top: 18px; scroll-margin-top: 14px; }
.plan-heading-row { min-height: 47px; display: flex; align-items: center; padding: 0; }
.plan-heading-row h2 { color: #1b222c; font-size: 15px; font-weight: 600; }
.plan-information {
  margin: 0;
  background: #fff;
  border: 1px solid var(--console-line);
}
.plan-identity-row {
  min-width: 0;
  display: grid;
  grid-template-columns: minmax(230px, .78fr) minmax(0, 2.1fr);
  border-bottom: 1px solid var(--console-line);
}
.plan-product-information {
  min-width: 0;
  min-height: 84px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 18px 26px;
  border-right: 1px solid var(--console-line);
}
.plan-product-information > span { margin: 0 0 7px; color: #7c8793; font-size: 10px; }
.plan-product-information > strong { min-width: 0; color: #202936; font-size: 19px; font-weight: 600; overflow-wrap: anywhere; }
.plan-meta-information { min-width: 0; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); margin: 0; }
.plan-meta-information > div { min-width: 0; display: flex; flex-direction: column; justify-content: center; padding: 18px 22px; }
.plan-meta-information > div + div { border-left: 1px solid #e7eaee; }
.plan-meta-information dt,
.plan-service-information dt { margin-bottom: 7px; color: #7c8793; font-size: 10px; }
.plan-meta-information dd,
.plan-service-information dd { min-width: 0; margin: 0; color: #293440; font-size: 12px; overflow-wrap: anywhere; }
.plan-quota-section { min-width: 0; display: grid; grid-template-columns: 180px minmax(0, 1fr); border-bottom: 1px solid var(--console-line); }
.plan-quota-section > header { display: flex; flex-direction: column; justify-content: center; padding: 20px 26px; background: #f7f8fa; border-right: 1px solid var(--console-line); }
.plan-quota-section h3 { margin: 0 0 4px; color: #293440; font-size: 12px; font-weight: 650; }
.plan-quota-section p { margin: 0; color: #8b959f; font-size: 9px; }
.plan-quota-information { min-width: 0; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); margin: 0; }
.plan-quota-information > div { min-width: 0; min-height: 88px; display: flex; flex-direction: column; justify-content: center; padding: 15px 18px; }
.plan-quota-information > div + div { border-left: 1px solid #e7eaee; }
.plan-quota-information dt { margin-bottom: 9px; color: #6f7b87; font-size: 10px; }
.plan-quota-information dd { min-width: 0; display: flex; align-items: baseline; gap: 5px; margin: 0; color: #202936; overflow-wrap: anywhere; }
.plan-quota-value { color: var(--console-blue); font-size: 21px; font-weight: 550; }
.plan-quota-information dd small { color: #7c8793; font-size: 9px; }
.plan-bandwidth-quota { display: grid !important; grid-template-columns: 1fr 1fr; gap: 10px !important; }
.plan-bandwidth-value { min-width: 0; display: flex; align-items: baseline; gap: 4px; }
.plan-bandwidth-value + .plan-bandwidth-value { padding-left: 10px; border-left: 1px solid #e2e6eb; }
.plan-bandwidth-value b { color: var(--console-blue); font-size: 17px; font-weight: 550; }
.plan-bandwidth-value small { white-space: nowrap; }
.plan-protection-section { min-width: 0; display: grid; grid-template-columns: 180px minmax(0, 1fr); border-bottom: 1px solid var(--console-line); }
.plan-protection-section > header { display: flex; flex-direction: column; justify-content: center; padding: 20px 26px; background: #f7f8fa; border-right: 1px solid var(--console-line); }
.plan-protection-section h3 { margin: 0 0 4px; color: #293440; font-size: 12px; font-weight: 650; }
.plan-protection-section header p { margin: 0; color: #8b959f; font-size: 9px; }
.plan-protection-controls { min-width: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.plan-protection-control { min-width: 0; min-height: 104px; display: flex; flex-direction: column; justify-content: center; padding: 15px 18px; }
.plan-protection-control + .plan-protection-control { border-left: 1px solid #e7eaee; }
.plan-protection-control > label { margin: 0 0 8px; color: #505b68; font-size: 11px; }
.plan-protection-input { min-width: 0; display: grid; grid-template-columns: minmax(82px, 132px) auto auto; align-items: center; gap: 8px; }
.plan-protection-input input { width: 100%; min-width: 0; height: 33px; padding: 0 9px; }
.plan-protection-input span { color: #687582; font-size: 10px; white-space: nowrap; }
.plan-protection-input .table-action { min-width: 54px; min-height: 33px; }
.plan-protection-control > small { margin-top: 6px; color: #8b959f; font-size: 9px; }
.plan-protection-message { min-height: 15px; margin: 5px 0 0; color: #19805f; font-size: 10px; }
.plan-protection-message.error { color: #b42318; }
.plan-service-information { min-width: 0; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); margin: 0; }
.plan-service-information > div { min-width: 0; min-height: 58px; display: grid; grid-template-columns: minmax(84px, .72fr) 1fr; align-items: center; padding: 0 22px; }
.plan-service-information > div + div { border-left: 1px solid #e7eaee; }
.plan-service-information dt { margin: 0; }
.plan-service-normal { color: #19805f !important; font-weight: 600; }
.portal .content-section { padding: 0; scroll-margin-top: 14px; }
.portal .section-heading {
  min-height: 47px;
  margin: 0;
  padding: 0;
  background: #fff;
  border: 0;
}
.portal .section-heading .eyebrow { display: none; }
.portal .section-heading h2 { color: #1b222c; font-size: 15px; font-weight: 600; }
.portal .table-wrap,
.portal .package-form {
  border: 1px solid var(--console-line);
  border-radius: 0;
  box-shadow: none;
}
.portal th { color: #505b68; background: #f7f8fa; font-size: 11px; font-weight: 600; text-transform: none; }
.portal th, .portal td { padding: 13px 14px; border-bottom-color: var(--console-line); }
.portal tbody tr:hover { background: #f8faff; }
.portal .primary-button {
  color: #fff;
  background: var(--console-blue);
  border-color: var(--console-blue);
  border-radius: 2px;
}
.portal .primary-button:hover { background: var(--console-blue-dark); border-color: var(--console-blue-dark); }
.portal .secondary-button,
.portal .table-action,
.portal input,
.portal select,
.portal dialog,
.portal .dialog-close { border-radius: 2px; }
.portal input:focus, .portal select:focus { border-color: var(--console-blue); box-shadow: 0 0 0 3px rgba(53,108,255,.12); }
.portal .status-label { border-radius: 2px; }
.portal .package-form { padding: 20px; }
.portal .no-plan-state { min-height: 420px; border: 1px solid var(--console-line); border-radius: 0; }
.login-view { background: #f5f6f8; border-top: 0; }
.login-panel { border: 1px solid var(--console-line); border-top: 3px solid var(--console-blue); border-radius: 2px; box-shadow: 0 12px 36px rgba(32,49,84,.12); }
.login-panel .brand-mark { color: #fff; background: var(--console-blue); border: 0; border-radius: 2px; }
.login-panel .eyebrow { color: var(--console-blue); }
.login-panel .primary-button { background: var(--console-blue); border-color: var(--console-blue); }
@media (max-width: 1000px) {
  .portal .summary-item { padding-inline: 26px; }
  .bandwidth-filter-form { grid-template-columns: minmax(0, 1fr) auto; }
  .bandwidth-custom-range { grid-column: 1; }
  .connection-statistics-filter { grid-template-columns: minmax(0,1fr) auto; }
  .connection-range-buttons { grid-column: 1; grid-row: 1; }
  .connection-statistics-custom-range { grid-column: 1; grid-row: 2; }
  .connection-statistics-filter > .primary-button { grid-column: 2; grid-row: 2; }
  .connection-detail-filter { grid-template-columns: repeat(2,minmax(0,1fr)) auto; }
  .connection-detail-filter > label:nth-child(3),
  .connection-detail-filter > label:nth-child(4) { grid-row: 2; }
  .connection-detail-filter > .primary-button { grid-column: 3; grid-row: 2; }
  .bandwidth-workspace { grid-template-columns: 1fr; }
  .bandwidth-readout { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr; border-right: 0; border-bottom: 1px solid var(--console-line); }
  .bandwidth-readout-direction { min-height: 116px; }
  .bandwidth-readout-direction + .bandwidth-readout-direction { border-top: 0; border-left: 1px solid var(--console-line); }
  .plan-identity-row { grid-template-columns: 1fr; }
  .plan-product-information { min-height: 76px; border-right: 0; border-bottom: 1px solid #e7eaee; }
  .plan-quota-section { grid-template-columns: 1fr; }
  .plan-quota-section > header { min-height: 58px; padding-block: 12px; border-right: 0; border-bottom: 1px solid #e7eaee; }
  .plan-quota-information { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .plan-quota-information > div:nth-child(odd) { border-left: 0; }
  .plan-quota-information > div:nth-child(n+3) { border-top: 1px solid #e7eaee; }
  .plan-protection-section { grid-template-columns: 1fr; }
  .plan-protection-section > header { min-height: 58px; padding-block: 12px; border-right: 0; border-bottom: 1px solid #e7eaee; }
}
@media (max-width: 700px) {
  .portal .topbar-inner { padding: 9px 14px; }
  .portal .toolbar { max-width: 68%; flex-wrap: wrap; }
  .service-list-view { padding: 8px 10px 10px; }
  .gateway-panel,
  .sdk-panel,
  .windows-sdk-panel { padding: 0 0 28px !important; }
  .sdk-integration-notice { align-items: flex-start; margin: 0; line-height: 1.6; }
  .sdk-integration-info { margin-top: 2px; }
  .sdk-integration-actions { display: grid; grid-template-columns: 1fr; }
	.package-platform-tabs { grid-template-columns: repeat(3,minmax(0,1fr)); }
	.package-platform-tab { min-width: 0; padding-inline: 7px; }
  .sdk-integration-job > div { grid-template-columns: 1fr; }
  .sdk-integration-job small { grid-column: 1; }
	.sdk-platform-downloads { display: grid; grid-template-columns: 1fr; }
	.sdk-platform-downloads .primary-button,
	.sdk-platform-downloads .secondary-button { width: 100%; }
  .sdk-integration-actions .primary-button,
  .sdk-integration-actions .secondary-button { width: 100%; }
  .sdk-credential-list { grid-template-columns: 1fr; gap: 5px; }
  .sdk-credential-list dt { margin-top: 8px; text-align: left; }
  .sdk-credential-warning { margin-left: 0; }
  .gateway-toolbar { display: grid; grid-template-columns: 1fr; }
  .gateway-toolbar .primary-button,
  .gateway-toolbar .secondary-button { width: 100%; }
  .gateway-table { width: 100% !important; min-width: 100% !important; table-layout: fixed; }
  .gateway-table th:nth-child(2),
  .gateway-table td:nth-child(2),
  .gateway-table th:nth-child(3),
  .gateway-table td:nth-child(3) { display: none; }
  .gateway-table th,
  .gateway-table td { height: auto; padding: 10px 8px; white-space: normal; }
  .gateway-table th:nth-child(1) { width: 34%; }
  .gateway-table th:nth-child(4) { width: 27%; }
  .gateway-table th:nth-child(5) { width: 39%; }
  .gateway-device-meta { display: block; }
  .gateway-table .row-actions { flex-wrap: wrap; gap: 5px; }
  .gateway-table .table-action { min-height: 27px; padding: 0 7px; }
  .plan-protection-controls { grid-template-columns: 1fr; }
  .plan-protection-control + .plan-protection-control { border-left: 0; border-top: 1px solid #e7eaee; }
  .plan-protection-input { grid-template-columns: minmax(76px, 1fr) auto auto; }
  .local-package-tool,.local-package-job { grid-template-columns: 30px minmax(0,1fr); }
  .local-package-download,.local-package-job > .primary-button { grid-column: 2; justify-self: start; }
  .bandwidth-panel { padding: 0 0 28px !important; }
  .connection-statistics-panel,
  .connection-details-panel { padding: 0 0 28px !important; }
  .bandwidth-filter-form { grid-template-columns: 1fr; gap: 10px; padding: 12px; }
  .bandwidth-range-buttons { display: grid; grid-template-columns: repeat(3, 1fr); }
  .connection-range-buttons { grid-template-columns: repeat(2,1fr); }
  .range-button { min-width: 0; padding: 0 6px; }
  .bandwidth-custom-range { grid-column: auto; grid-template-columns: 1fr; }
  .bandwidth-filter-form input[type=datetime-local] { width: 100%; min-width: 0; }
  .bandwidth-time-separator { display: none; }
  .bandwidth-filter-form .primary-button { width: 100%; }
  .connection-statistics-filter { grid-template-columns: 1fr; gap: 10px; padding: 12px; }
  .connection-range-buttons,
  .connection-statistics-custom-range,
  .connection-statistics-filter > .primary-button { width: 100%; grid-column: auto; grid-row: auto; }
  .connection-statistics-custom-range { grid-template-columns: 1fr; }
  .connection-statistics-filter > .primary-button { width: 100%; }
  .bandwidth-readout { grid-template-columns: 1fr; grid-template-rows: auto auto; }
  .bandwidth-readout-direction + .bandwidth-readout-direction { border-top: 1px solid var(--console-line); border-left: 0; }
  .bandwidth-panel .bandwidth-plot-stack .trend-section { grid-template-columns: 1fr; }
  .connection-statistics-panel .connection-statistics-plot-stack .trend-section,
  .connection-details-panel .connection-detail-table-stack .trend-section { grid-template-columns: 1fr; }
  .bandwidth-panel .bandwidth-plot-stack .trend-heading { min-height: 66px; align-items: center; flex-direction: row; padding: 12px 16px; border-right: 0; border-bottom: 1px solid var(--console-line); }
  .connection-statistics-panel .connection-statistics-plot-stack .trend-heading,
  .connection-details-panel .connection-detail-table-stack .trend-heading { min-height: 66px; padding: 12px 16px; border-right: 0; border-bottom: 1px solid var(--console-line); }
  .connection-statistics-panel .connection-statistics-plot-stack .trend-heading { align-items: center; flex-direction: row; }
  .bandwidth-panel .bandwidth-plot-stack .chart-legend { align-items: center; flex-direction: row; }
  .connection-statistics-panel .connection-statistics-plot-stack .chart-legend { align-items: center; flex-direction: row; }
  .bandwidth-panel .bandwidth-plot-stack .chart-scroll { padding: 4px 4px 0; }
  .connection-statistics-panel .connection-statistics-plot-stack .chart-scroll { padding: 4px 4px 0; }
  .bandwidth-panel .bandwidth-plot-stack .trend-chart { min-width: 700px; }
  .connection-statistics-panel .connection-statistics-plot-stack .trend-chart { min-width: 700px; }
  .trend-heading { align-items: flex-start; flex-direction: column; }
  .connection-detail-filter { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; padding: 12px; }
  .connection-detail-filter input,
  .connection-detail-filter select { min-width: 0; }
  .connection-detail-filter > label:nth-child(3),
  .connection-detail-filter > label:nth-child(4) { grid-row: auto; }
  .connection-detail-filter > .primary-button { width: 100%; grid-column: 1 / -1; grid-row: auto; }
  .service-catalog { padding: 10px; }
  .service-filter-form { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
  .service-filter-form select,
  .service-filter-form input { width: 100%; min-width: 0; }
  .service-filter-form .primary-button { width: 100%; }
  .service-filter-form .purchase-button { margin-left: 0; }
  .mapping-filter-form { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
  .mapping-filter-form input,
  .mapping-filter-form select,
  .mapping-filter-form .primary-button { width: 100%; }
  .portal .error-banner { margin-inline: 10px; }
  .console-shell { margin: 0 4px 8px; padding: 10px 6px 18px; }
  .module-tabs a { padding-inline: 14px; }
  .portal .workspace { padding-top: 8px; }
  .portal .summary-band { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .portal .summary-item { min-height: 118px; padding: 20px; border-bottom: 1px solid var(--console-line); }
  .portal .summary-item:nth-child(odd) { border-right: 1px solid var(--console-line); }
  .portal .summary-item:nth-last-child(-n+2) { border-bottom: 0; }
  .plan-meta-information { grid-template-columns: 1fr; }
  .plan-meta-information > div { min-height: 58px; padding: 12px 18px; }
  .plan-meta-information > div + div { border-left: 0; border-top: 1px solid #e7eaee; }
  .plan-quota-information > div { padding-inline: 14px; }
  .plan-bandwidth-quota { grid-template-columns: 1fr; gap: 4px !important; }
  .plan-bandwidth-value + .plan-bandwidth-value { padding: 4px 0 0; border-top: 1px solid #e2e6eb; border-left: 0; }
  .plan-service-information { grid-template-columns: 1fr; }
  .plan-service-information > div { min-height: 52px; grid-template-columns: 112px 1fr; padding-inline: 18px; }
  .plan-service-information > div + div { border-left: 0; border-top: 1px solid #e7eaee; }
  .portal .section-heading { align-items: center; flex-direction: row; }
}
@media (max-width: 430px) {
  .portal .brand-lockup h1 { font-size: 15px; }
  .portal .toolbar { max-width: 62%; }
  .portal .current-user { width: 100%; text-align: right; }
  .portal .back-button { order: 4; }
  .portal .summary-item { min-height: 108px; padding: 16px; }
  .portal .summary-item strong b { font-size: 27px; }
  .portal .summary-item strong small { font-size: 14px; }
  .plan-service-information > div { grid-template-columns: 100px 1fr; }
}

.rule-editor-dialog {
  width: min(1180px, calc(100vw - 32px));
  max-height: calc(100vh - 32px);
  color: #202936;
  background: #fff;
  border: 1px solid #dfe4eb;
  border-radius: 2px;
  box-shadow: 0 18px 60px rgba(31, 43, 67, .24);
  overflow: auto;
}
.rule-editor-dialog::backdrop { background: rgba(31, 39, 52, .48); }
.rule-editor-dialog .dialog-form { min-height: 620px; padding: 0; }
.rule-editor-dialog .dialog-heading {
  min-height: 50px;
  align-items: center;
  margin: 0;
  padding: 0 16px 0 20px;
  background: #f6f7f9;
  border-bottom: 1px solid #dfe4eb;
}
.rule-editor-dialog .dialog-heading h2 { color: #202936; font-size: 16px; font-weight: 500; }
.rule-editor-dialog .dialog-close { width: 30px; height: 30px; color: #697382; border: 0; background: transparent; }
.rule-editor-dialog .dialog-close:hover { color: #356cff; background: #eef2ff; }
.rule-editor-notice {
  min-height: 36px;
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 14px 16px 0;
  padding: 7px 10px;
  color: #34415a;
  background: #f0f4ff;
  border: 1px solid #7797ff;
  font-size: 12px;
}
.rule-editor-info {
  width: 14px;
  height: 14px;
  display: inline-grid;
  flex: 0 0 14px;
  place-items: center;
  color: #356cff;
  border: 1px solid #356cff;
  border-radius: 50%;
  font: 700 10px/1 Arial, sans-serif;
}
.rule-editor-layout { display: grid; grid-template-columns: minmax(0, 1fr) 350px; margin-top: 16px; border-top: 1px solid #e4e8ee; }
.rule-editor-layout.single-column { grid-template-columns: minmax(0, 1fr); }
.rule-editor-main { min-width: 0; padding: 22px 28px 28px; }
.rule-editor-notes { min-width: 0; padding: 20px; background: #fbfbfc; border-left: 1px solid #e1e5eb; }
.rule-editor-notes h3 { margin: 0 0 10px; color: #242d39; font-size: 13px; font-weight: 650; }
.rule-editor-notes p { margin: 0 0 7px; color: #4a5564; font-size: 12px; line-height: 1.55; overflow-wrap: anywhere; }
.rule-editor-notes .rule-note-important { color: #e23b35; }
.rule-form-row {
  min-height: 40px;
  display: grid;
  grid-template-columns: 128px minmax(220px, 375px);
  align-items: center;
  gap: 14px;
  margin-bottom: 10px;
}
.rule-form-row > label,
.rule-form-label { margin: 0; color: #28313d; font-size: 12px; font-weight: 500; text-align: right; }
.rule-form-row input,
.rule-form-row select,
.rule-form-row textarea {
  width: 100%;
  min-width: 0;
  margin: 0;
  color: #27313d;
  background: #fff;
  border: 1px solid #d5dbe4;
  border-radius: 0;
  outline: none;
  font-size: 12px;
}
.rule-form-row input,
.rule-form-row select { height: 34px; padding: 0 11px; }
.rule-form-row textarea { min-height: 68px; padding: 9px 11px 22px; resize: vertical; line-height: 1.5; }
.rule-form-row input:focus,
.rule-form-row select:focus,
.rule-form-row textarea:focus { border-color: #356cff; box-shadow: 0 0 0 2px rgba(53,108,255,.1); }
.rule-form-row input::placeholder,
.rule-form-row textarea::placeholder { color: #a0a9b6; }
.rule-form-row .check-row { min-height: 34px; display: flex; align-items: center; gap: 8px; color: #34404f; text-align: left; cursor: pointer; }
.rule-form-row .check-row input { width: 16px; min-width: 16px; height: 16px; padding: 0; accent-color: #356cff; }
.rule-form-row input[readonly] { color: #526071; background: #f3f5f8; }
.virtual-ip-control { min-width: 0; display: grid; grid-template-columns: minmax(0,1fr) 58px; align-items: stretch; gap: 8px; }
.virtual-ip-control .secondary-button { min-height: 34px; padding: 0 10px; border-radius: 0; font-size: 12px; }
.rule-type-segments { display: flex; align-items: center; }
.rule-type-segments span,
.rule-type-segments button { min-width: 62px; height: 34px; display: inline-grid; place-items: center; margin: 0 -1px 0 0; padding: 0 12px; color: #34404f; background: #fff; border: 1px solid #d5dbe4; border-radius: 0; font: 12px/1 "Microsoft YaHei",sans-serif; cursor: pointer; }
.rule-type-segments span.active,
.rule-type-segments button[aria-pressed="true"] { position: relative; z-index: 1; color: #2457df; border-color: #356cff; }
.rule-type-segments span.active::after,
.rule-type-segments button[aria-pressed="true"]::after { content: ""; position: absolute; right: 0; bottom: 0; border-right: 8px solid #356cff; border-top: 8px solid transparent; }
.rule-remark-row { align-items: start; }
.rule-remark-row > label { padding-top: 9px; }
.rule-remark-control { position: relative; }
.rule-remark-control > span { position: absolute; right: 9px; bottom: 7px; color: #7d8794; font-size: 11px; }
.rule-remark-control b { color: inherit; font-weight: 500; }
.rule-editor-main .form-error { max-width: 517px; margin: 8px 0 0 142px; }
.rule-editor-main .dialog-actions { justify-content: flex-start; margin: 18px 0 0 142px; }
.rule-editor-main .dialog-actions .primary-button,
.rule-editor-main .dialog-actions .secondary-button { min-height: 34px; min-width: 70px; border-radius: 0; font-size: 12px; }
.rule-editor-main .dialog-actions .primary-button { order: 0; color: #fff; background: #356cff; border: 1px solid #356cff; }
.rule-editor-main .dialog-actions .primary-button:hover { background: #2457df; border-color: #2457df; }
.rule-editor-main .dialog-actions .secondary-button { order: 1; color: #34404f; background: #fff; border: 1px solid #cbd2dc; }
.gateway-download-main .rule-form-row { grid-template-columns: 128px minmax(220px, 650px); }
.gateway-download-package { min-height: 52px; display: flex; justify-content: center; flex-direction: column; gap: 5px; padding: 9px 11px; background: #fbfbfc; border: 1px solid #d5dbe4; }
.gateway-download-package strong { color: #27313d; font-size: 12px; font-weight: 600; }
.gateway-download-package small { color: #7d8794; font-size: 11px; }
.gateway-download-command { display: grid; grid-template-columns: minmax(0,1fr) auto; align-items: stretch; gap: 8px; }
.gateway-download-command code { min-width: 0; min-height: 52px; padding: 10px 11px; overflow-wrap: anywhere; color: #27313d; background: #fbfbfc; border: 1px solid #d5dbe4; font: 12px/1.55 Consolas,"Microsoft YaHei",sans-serif; }
.gateway-download-command .secondary-button { min-width: 64px; border-radius: 0; }
.gateway-download-tutorial-row { align-items: start; }
.gateway-download-tutorial-row .rule-form-label { padding-top: 10px; }
.gateway-download-tutorial { width: 100%; min-height: 280px; max-height: 420px; margin: 0; padding: 13px 15px; overflow: auto; color: #27313d; background: #fbfbfc; border: 1px solid #d5dbe4; white-space: pre-wrap; overflow-wrap: anywhere; font: 12px/1.75 Consolas,"Microsoft YaHei",sans-serif; }
.gateway-download-link { display: inline-flex; align-items: center; justify-content: center; text-decoration: none; }
@media (max-width: 820px) {
  .rule-editor-dialog { width: calc(100vw - 16px); max-height: calc(100vh - 16px); }
  .rule-editor-dialog .dialog-form { min-height: 0; }
  .rule-editor-notice { margin: 10px 10px 0; }
  .rule-editor-layout { grid-template-columns: 1fr; margin-top: 10px; }
  .rule-editor-main { padding: 18px 14px 20px; }
  .rule-editor-notes { padding: 16px 14px; border-top: 1px solid #e1e5eb; border-left: 0; }
  .rule-form-row { grid-template-columns: 112px minmax(0, 1fr); }
  .gateway-download-main .rule-form-row { grid-template-columns: 112px minmax(0, 1fr); }
  .rule-editor-main .form-error,
  .rule-editor-main .dialog-actions { margin-left: 126px; }
}
@media (max-width: 520px) {
  .rule-editor-dialog { width: calc(100vw - 8px); max-height: calc(100vh - 8px); }
  .rule-editor-dialog .dialog-heading { padding-left: 14px; }
  .rule-editor-notice { align-items: flex-start; }
  .rule-form-row { grid-template-columns: 1fr; gap: 6px; margin-bottom: 13px; }
  .gateway-download-main .rule-form-row { grid-template-columns: 1fr; }
  .rule-form-row > label,
  .rule-form-label { text-align: left; }
  .rule-remark-row > label { padding-top: 0; }
  .rule-editor-main .form-error,
  .rule-editor-main .dialog-actions { margin-left: 0; }
  .rule-editor-main .dialog-actions { display: grid; grid-template-columns: 1fr 1fr; }
  .rule-editor-main .dialog-actions .primary-button,
  .rule-editor-main .dialog-actions .secondary-button { width: 100%; }
}

.realip-panel { padding-top: 0 !important; }
.realip-filter-form input { width: 220px; }
.realip-filter-form select { width: 132px; }
.realip-table-wrap { min-height: 0; max-height: 520px; overflow: auto; border-radius: 0; }
.realip-table { min-width: 1180px; }
.realip-device { display: grid; gap: 3px; }
.realip-device strong { color: #25303b; font-size: 12px; font-weight: 600; }
.realip-device small { color: #74808c; font: 11px Consolas, monospace; }
.realip-source-values { display: flex; flex-wrap: wrap; gap: 4px; min-width: 150px; }
.realip-source-value { padding: 3px 7px; border: 1px solid #d5dbe3; border-radius: 3px; color: #34404f; background: #f7f8fa; font: 11px Consolas, monospace; white-space: nowrap; }
.realip-source-pending { color: #b06413; font-size: 12px; white-space: nowrap; }
.realip-source-dialog { width: min(980px, calc(100% - 24px)); }
.realip-source-dialog .dialog-form { min-height: 520px; }
.realip-source-device-name { min-height: 34px; display: flex; align-items: center; margin: 0 0 12px; padding: 0 11px; color: #4d5865; background: #f7f8fa; border: 1px solid #d9dee5; font-size: 12px; }
.realip-source-main .form-error,
.realip-source-main .dialog-actions { margin-left: 0; }
.realip-source-options { max-height: 330px; overflow-y: auto; border: 1px solid #d9dee5; }
.realip-source-option { display: grid; grid-template-columns: 20px minmax(0, 1fr) auto; align-items: center; gap: 10px; min-height: 52px; margin: 0; padding: 9px 12px; border-bottom: 1px solid #e5e9ee; color: #293542; background: #fff; }
.realip-source-option:last-child { border-bottom: 0; }
.realip-source-option input { width: 16px; height: 16px; margin: 0; }
.realip-source-option strong { font: 12px Consolas, monospace; }
.realip-source-option small { color: #75808d; font-size: 11px; white-space: nowrap; }
.realip-source-option.disabled { color: #89939d; background: #f5f6f8; }
.realip-source-empty { margin: 0; padding: 24px 16px; color: #6f7c89; text-align: center; border: 1px solid #d9dee5; }
@media (max-width: 700px) {
  .realip-filter-form input,
  .realip-filter-form select { width: 100%; min-width: 0; }
  .realip-source-option { grid-template-columns: 20px minmax(0, 1fr); }
  .realip-source-option small { grid-column: 2; white-space: normal; }
}
.anti-cheat-panel{padding-top:0!important}.anti-cheat-heading{display:flex;align-items:flex-end;justify-content:space-between;gap:18px;margin-bottom:16px}.anti-cheat-heading h2{font-size:16px}.anti-cheat-heading p{margin:6px 0 0;color:#7a8793;font-size:12px}.anti-cheat-mode-control{display:flex;align-items:center;gap:9px;margin:0;color:#607080;white-space:nowrap}.anti-cheat-mode-control span{font-size:12px}.anti-cheat-mode-control select{width:150px;height:38px;margin:0}.anti-cheat-table-wrap{min-height:180px}.anti-cheat-table{min-width:760px}.anti-cheat-table th,.anti-cheat-table td{padding:12px 14px}.anti-cheat-event-count{margin:9px 0 0;color:#7a8793;font-size:12px}.anti-cheat-action{font-weight:650}.anti-cheat-action.disconnected{color:#a9362b}.anti-cheat-action.observed{color:#9a6500}@media(max-width:640px){.anti-cheat-heading{align-items:flex-start;flex-direction:column}.anti-cheat-mode-control{width:100%;justify-content:space-between}.anti-cheat-mode-control select{width:180px}}
.scratch-ticket-list{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}.scratch-ticket{display:grid;gap:10px;padding:16px;border:1px solid var(--console-line);border-top:3px solid #356cff;background:#fff}.scratch-ticket.used{border-top-color:#20a17f}.scratch-ticket.expired{border-top-color:#9aa4ad}.scratch-ticket header{display:flex;justify-content:space-between;gap:10px}.scratch-ticket h3{margin:0;font-size:16px}.scratch-ticket dl{display:grid;grid-template-columns:auto 1fr;gap:6px 12px;margin:0;font-size:12px}.scratch-ticket dt{color:#71808b}.scratch-ticket dd{margin:0;overflow-wrap:anywhere}.scratch-result{min-height:42px;display:grid;place-items:center;padding:8px;border:0;color:#fff;background:#596773;font-weight:700}.scratch-ticket.pending .scratch-result{background:#356cff;cursor:pointer}.scratch-ticket.won .scratch-result{background:#168c79}.scratch-ticket.no_prize .scratch-result{background:#687582}.scratch-ticket.expired .scratch-result{background:#9aa4ad}@media(max-width:900px){.scratch-ticket-list{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:600px){.scratch-ticket-list{grid-template-columns:1fr}}
.auth-links{display:flex;justify-content:space-between;gap:12px;margin-top:16px}.auth-links button{padding:0;border:0;color:#356cff;background:transparent;font-size:13px}.code-row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px}.code-row .secondary-button{height:42px;white-space:nowrap}.account-tabs{display:flex;gap:0;margin-bottom:12px;background:#fff;border:1px solid var(--console-line);overflow-x:auto}.account-tabs button{min-height:42px;padding:0 18px;border:0;border-right:1px solid var(--console-line);color:#53616d;background:#fff;white-space:nowrap}.account-tabs button.active{color:#356cff;box-shadow:inset 0 3px #356cff}.account-panel{padding:20px;background:#fff;border:1px solid var(--console-line)}.account-heading{display:flex;align-items:end;justify-content:space-between;gap:16px;margin-bottom:18px}.account-heading h2{font-size:19px}.account-heading>strong{font-size:20px}.store-products{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}.store-product-card{padding:18px;border:1px solid #dce3e8;border-radius:6px;background:#fff}.store-product-card h3{margin:0 0 8px;font-size:17px}.store-product-limits{display:grid;grid-template-columns:1fr 1fr;gap:7px;margin:14px 0;color:#61707c;font-size:12px}.store-product-price{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:15px;padding-top:14px;border-top:1px solid #e5eaee}.store-product-price strong,.purchase-price{color:#2457df;font-size:20px}.purchase-summary{padding:12px;background:#f6f8fa;border:1px solid #e0e6ea}.purchase-price{margin:16px 0 0;font-weight:700}.service-actions{display:flex;gap:6px}.service-actions .service-manage-button{min-width:56px}.wallet-positive{color:#168c79}.wallet-negative{color:#b42318}.identity-summary{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));margin:0;border:1px solid var(--console-line)}.identity-summary div{min-width:0;padding:14px;border-right:1px solid var(--console-line)}.identity-summary div:last-child{border-right:0}.identity-summary dt{color:#71808b;font-size:11px}.identity-summary dd{margin:6px 0 0;font-weight:700;overflow-wrap:anywhere}.identity-form{margin-top:18px;padding-top:18px;border-top:1px solid var(--console-line)}.identity-form-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px}.identity-form-grid label{display:grid;gap:7px}.identity-form-grid .wide{grid-column:1/-1}.identity-consent{display:flex!important;grid-template-columns:auto 1fr!important;align-items:center}.identity-consent input{width:auto}.identity-latest{margin:14px 0 0;padding:10px 0;color:#5d6b76;border-bottom:1px solid var(--console-line)}.payment-method-fieldset{margin:17px 0 0;padding:0;border:0}.payment-method-fieldset legend{margin-bottom:8px;color:#394957;font-size:13px;font-weight:650}.payment-method-options{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));border:1px solid #cfd6dd;border-radius:4px;overflow:hidden}.payment-method-options label{min-width:0;margin:0;border-right:1px solid #cfd6dd}.payment-method-options label:last-child{border-right:0}.payment-method-options input{position:absolute;opacity:0;pointer-events:none}.payment-method-options span{min-height:42px;display:grid;place-items:center;padding:0 8px;color:#4b5966;background:#fff;font-size:13px;white-space:nowrap}.payment-method-options input:checked+span{color:#fff;background:#356cff}.payment-method-options input:focus-visible+span{box-shadow:inset 0 0 0 3px #a8bdf7}.payment-waiting{display:grid;justify-items:center;gap:10px;padding:8px 0 0;text-align:center}.payment-waiting img{width:220px;height:220px;border:1px solid #dbe1e6}.payment-waiting strong{font-size:17px}.payment-waiting p{margin:0}.payment-waiting-status{color:#4c5a67}@media(max-width:900px){.store-products{grid-template-columns:repeat(2,minmax(0,1fr))}.identity-summary{grid-template-columns:repeat(2,minmax(0,1fr))}.identity-summary div:nth-child(2){border-right:0}.identity-summary div:nth-child(-n+2){border-bottom:1px solid var(--console-line)}}@media(max-width:600px){.auth-panel{padding:24px}.store-products{grid-template-columns:1fr}.account-panel{padding:14px}.account-heading{align-items:flex-start;flex-direction:column}.code-row{grid-template-columns:1fr}.code-row .secondary-button{width:100%}.account-tabs button{flex:1 0 auto;padding:0 7px;font-size:12px}.identity-form-grid{grid-template-columns:1fr}.identity-form-grid .wide{grid-column:auto}.identity-summary{grid-template-columns:1fr}.identity-summary div{border-right:0;border-bottom:1px solid var(--console-line)}.identity-summary div:last-child{border-bottom:0}.payment-method-options{grid-template-columns:1fr}.payment-method-options label{border-right:0;border-bottom:1px solid #cfd6dd}.payment-method-options label:last-child{border-bottom:0}.payment-waiting img{width:190px;height:190px}}

.purchase-dialog { width: min(1000px, calc(100% - 24px)); max-width: 1000px; background: #fff; border: 1px solid #d8dde5; border-radius: 2px; box-shadow: 0 18px 48px rgba(28,39,54,.22); }
.purchase-dialog .dialog-form { padding: 0; background: #fff; }
.purchase-dialog .dialog-heading { min-height: 52px; align-items: center; margin: 0; padding: 0 18px; border-bottom: 1px solid #e3e7ed; }
.purchase-dialog .dialog-heading .eyebrow { display: none; }
.purchase-dialog .dialog-heading h2 { color: #1f2937; font-size: 16px; font-weight: 500; }
.purchase-dialog .dialog-close { width: 30px; height: 30px; border: 0; border-radius: 0; color: #6c7785; }
.purchase-dialog .rule-editor-notice { margin: 14px 18px 0; }
.purchase-dialog .rule-editor-layout { display: block; margin-top: 12px; border-top: 1px solid #e4e8ee; }
.purchase-dialog .rule-editor-main { padding: 20px 22px 18px; }
.purchase-dialog .rule-editor-notes { display: none; }
.purchase-dialog .payment-method-fieldset { max-width: 520px; margin: 16px 0 0 128px; }
.purchase-dialog .payment-waiting { margin: 0; padding: 8px 0; justify-items: center; text-align: center; }
.purchase-dialog .dialog-actions { justify-content: flex-start; margin: 18px 0 0 128px; gap: 10px; }
.purchase-dialog .dialog-actions button { min-width: 88px; height: 34px; min-height: 34px; border-radius: 0; font-size: 12px; }
.purchase-dialog .dialog-actions #submit-purchase { order: 0; color: #fff; background: #356cff; border: 1px solid #356cff; }
.purchase-dialog .dialog-actions #cancel-purchase { order: 1; color: #34404f; background: #fff; border: 1px solid #cbd2dc; }
@media(max-width:760px){.purchase-dialog{width:calc(100% - 16px)}.purchase-dialog .payment-method-fieldset,.purchase-dialog .dialog-actions{margin-left:0}.purchase-dialog .dialog-actions{display:flex;flex-wrap:wrap}}

.table-pager{min-height:48px;display:flex;align-items:center;justify-content:flex-end;gap:12px;padding:8px 12px;color:#687582;background:#fff;border:1px solid #dbe1e6;border-top:0;font-size:12px}.table-pager select{width:68px;height:32px;padding:0 7px}.table-pager-label{display:flex;align-items:center;gap:7px;margin:0;color:#687582;font-size:12px;font-weight:500;white-space:nowrap}.table-pager-pages{display:flex;align-items:center;gap:7px;white-space:nowrap}.table-pager-button{width:32px;height:32px;padding:0;border:1px solid #c8d0d8;border-radius:3px;color:#465361;background:#fff;font-size:18px;line-height:1}.table-pager-button:hover:not(:disabled){color:#2457df;border-color:#7f9ff1}.table-pager-button:disabled{color:#aeb7bf;background:#f4f6f8;cursor:not-allowed}.access-detail-page{padding-top:0!important;padding-bottom:40px!important;background:#f4f6f8}.access-detail-heading{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;margin-bottom:16px}.access-detail-heading h2{font-size:20px}.access-detail-heading>p{margin:0;color:#7a8793;font-size:12px}.access-filter-bar{display:grid;grid-template-columns:minmax(180px,1.2fr) auto minmax(185px,1fr) minmax(185px,1fr) auto;align-items:end;gap:10px;padding:14px;background:#fff;border:1px solid #dbe1e6}.access-filter-bar>select{height:38px}.access-filter-bar label{margin:0}.access-filter-bar label span{display:block;margin-bottom:5px;color:#667481;font-size:11px}.access-filter-bar input{width:100%;height:38px;padding:0 9px;border:1px solid #c8d0d8;border-radius:3px;color:#2f3a45;background:#fff}.access-filter-bar .primary-button{height:38px;min-height:38px}.access-range-buttons{display:flex;height:38px;border:1px solid #c8d0d8}.access-range-buttons button{min-width:58px;padding:0 11px;border:0;border-right:1px solid #c8d0d8;color:#52606d;background:#fff;white-space:nowrap}.access-range-buttons button:last-child{border-right:0}.access-range-buttons button.active{color:#fff;background:#356cff}.access-summary-strip{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));margin-top:0;background:#fff;border:1px solid #dbe1e6;border-top:0}.access-summary-strip>div{min-height:70px;padding:13px 17px;border-right:1px solid #e2e7eb}.access-summary-strip>div:last-child{border-right:0}.access-summary-strip span{display:block;margin-bottom:6px;color:#77838e;font-size:11px}.access-summary-strip strong{color:#27323d;font-size:18px}.access-chart-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:0;margin-top:0}.access-panel{min-width:0;background:#fff;border:1px solid #dbe1e6;border-radius:0}.access-chart-grid .access-panel{border-top:0}.access-chart-grid .access-panel+.access-panel{border-left:0}.access-panel>header{display:flex;align-items:center;justify-content:space-between;gap:10px;min-height:49px;padding:0 15px;border-bottom:1px solid #e5e9ed}.access-panel>header h3{margin:0;color:#2c3742;font-size:14px}.access-panel>header span{color:#89949e;font-size:11px;white-space:nowrap}.access-chart{height:255px}.access-region-panel,.access-table-panel{margin-top:0;border-top:0}.access-region-layout{display:grid;grid-template-columns:minmax(0,1.65fr) minmax(330px,1fr);min-height:430px}.access-map{height:430px;border-right:1px solid #e5e9ed}.access-ranking-wrap{max-height:430px;overflow:auto}.access-ranking-table{min-width:0}.access-ranking-table th,.access-ranking-table td{padding:11px 13px}.access-ranking-table th{position:sticky;top:0;z-index:1}.access-ranking-table td:last-child{color:#356cff;font-weight:650}.access-table-wrap{overflow:auto}.access-detail-table{min-width:1160px}.access-detail-table th,.access-detail-table td{padding:11px 13px}.access-state{display:inline-flex;align-items:center;gap:6px;white-space:nowrap}.access-state::before{width:7px;height:7px;border-radius:50%;background:#9aa5af;content:""}.access-state.online{color:#147a69}.access-state.online::before{background:#19a184}.access-truncated{color:#b06413!important}.access-loading{position:relative}.access-loading::after{position:absolute;inset:49px 0 0;z-index:3;display:grid;place-items:center;color:#65727e;background:rgba(255,255,255,.82);content:"正在加载访问数据..."}@media(max-width:1120px){.access-filter-bar{grid-template-columns:1fr auto 1fr 1fr}.access-filter-bar>.primary-button{grid-column:4}.access-chart-grid{grid-template-columns:1fr 1fr}.access-chart-grid .access-panel:last-child{grid-column:1/-1;border-left:1px solid #dbe1e6;border-top:0}.access-region-layout{grid-template-columns:1fr}.access-map{border-right:0;border-bottom:1px solid #e5e9ed}.access-ranking-wrap{max-height:300px}}@media(max-width:760px){.table-pager{justify-content:space-between;flex-wrap:wrap;gap:8px}.table-pager-total{order:3;width:100%}.access-detail-page{padding:0 10px 28px!important}.access-detail-heading{align-items:flex-start;flex-direction:column}.access-filter-bar{grid-template-columns:1fr}.access-filter-bar>*{width:100%}.access-filter-bar>.primary-button{grid-column:auto}.access-range-buttons button{flex:1}.access-summary-strip{grid-template-columns:1fr 1fr}.access-summary-strip>div:nth-child(2){border-right:0}.access-summary-strip>div:nth-child(-n+2){border-bottom:1px solid #e2e7eb}.access-chart-grid{grid-template-columns:1fr}.access-chart-grid .access-panel+.access-panel{border-left:1px solid #dbe1e6}.access-chart-grid .access-panel:last-child{grid-column:auto}.access-chart{height:230px}.access-panel>header{align-items:flex-start;flex-direction:column;justify-content:center;padding:9px 12px}.access-panel>header span{white-space:normal}.access-region-layout{min-height:0}.access-map{height:330px}.access-ranking-wrap{max-height:270px}.access-summary-strip strong{font-size:16px}}

.portal #user-overview.overview-metrics {
  min-height: 176px;
  display: grid;
  grid-template-columns: minmax(250px, .82fr) minmax(250px, .9fr) minmax(390px, 1.35fr);
  padding: 0;
  color: #202936;
  background: #fff;
  border: 1px solid var(--console-line);
}
.overview-quick-stats { min-width: 0; display: grid; grid-template-rows: 1fr 1fr; border-right: 1px solid var(--console-line); }
.overview-quick-stat { min-width: 0; display: flex; align-items: center; padding: 17px 24px; }
.overview-quick-stat + .overview-quick-stat { border-top: 1px solid var(--console-line); }
.overview-quick-stat > div > span { display: block; margin: 0 0 4px; color: #677483; font-size: 11px; }
.overview-quick-stat strong { display: flex; align-items: baseline; gap: 5px; color: #202936; font-weight: 500; }
.overview-quick-stat strong b { color: #202936; font-size: 25px; font-weight: 550; }
.overview-quick-stat strong small { color: #687583; font-size: 11px; font-weight: 400; }
.overview-quick-stat strong small span { display: inline; margin: 0; color: inherit; font-size: inherit; }
.overview-connection-metric,
.overview-bandwidth-metric { min-width: 0; padding: 23px 26px; }
.overview-connection-metric { display: grid; grid-template-rows: auto 1fr; border-right: 1px solid var(--console-line); }
.overview-connection-metric header,
.overview-bandwidth-metric header { display: block; }
.overview-connection-metric header > div > span,
.overview-bandwidth-metric header > div > span { display: block; margin: 0 0 2px; color: #293440; font-size: 12px; font-weight: 650; }
.overview-connection-metric header > div > small,
.overview-bandwidth-metric header > div > small { display: block; color: #8a949f; font-size: 10px; }
.overview-connection-metric > strong { display: flex; align-items: baseline; gap: 6px; align-self: center; color: #202936; font-weight: 500; }
.overview-connection-metric > strong b { color: #2e6ae6; font-size: 36px; font-weight: 500; }
.overview-connection-metric > strong small { color: #63707d; font-size: 12px; font-weight: 400; }
.overview-connection-metric footer { display: flex; align-items: baseline; gap: 5px; padding-top: 9px; color: #6f7b87; border-top: 1px solid #e8ebef; font-size: 10px; }
.overview-connection-metric footer > span { margin: 0 auto 0 0; color: #6f7b87; font-size: 10px; }
.overview-connection-metric footer b { color: #26323e; font-size: 13px; font-weight: 600; }
.overview-connection-metric footer small { font-size: 10px; }
.overview-connection-metric footer small span { display: inline; margin: 0; color: inherit; font-size: inherit; }
.overview-bandwidth-metric { display: grid; grid-template-rows: auto 1fr; gap: 18px; }
.overview-bandwidth-lanes { min-width: 0; display: grid; grid-template-columns: 1fr 1fr; gap: 25px; align-items: end; }
.bandwidth-lane { min-width: 0; }
.bandwidth-lane > div { min-width: 0; display: grid; grid-template-columns: auto 1fr; align-items: baseline; gap: 3px 9px; }
.bandwidth-lane > div > span { grid-row: 1 / span 2; align-self: center; margin: 0; padding: 4px 6px; color: #42505e; background: #f1f3f6; font-size: 10px; font-weight: 650; }
.bandwidth-lane > div > strong { min-width: 0; overflow: hidden; color: #202936; font-size: 17px; font-weight: 550; text-overflow: ellipsis; white-space: nowrap; }
.bandwidth-lane > div > small { color: #89939e; font-size: 9px; white-space: nowrap; }
.bandwidth-lane > div > small span { display: inline; margin: 0; color: inherit; font-size: inherit; }
.bandwidth-meter { height: 4px; display: block; margin: 10px 0 0; overflow: hidden; background: #e7eaf0; }
.bandwidth-meter i { width: 0; height: 100%; display: block; margin: 0; background: #2e6ae6; transition: width .25s ease; }
.download-lane .bandwidth-meter i { background: #25a17b; }
.component-intro { display: grid; grid-template-columns: 150px minmax(0, 1fr) minmax(0, 1fr); align-items: start; gap: 24px; margin: 0 0 16px; padding: 16px 18px; color: #53606d; background: #f7f9fc; border-top: 2px solid var(--console-blue); border-bottom: 1px solid #dce2e8; }
.component-intro header { min-width: 0; }
.component-intro header span { display: block; margin-bottom: 5px; color: #778390; font-size: 10px; font-weight: 600; }
.component-intro h3 { margin: 0; color: #26313c; font-size: 15px; font-weight: 650; }
.component-intro p { margin: 0; font-size: 12px; line-height: 1.75; }
.component-intro strong { color: #34404c; font-weight: 650; }
.account-balance-row { min-height: 38px; display: flex; align-items: flex-start; justify-content: flex-end; margin-bottom: 12px; }
.account-balance-row strong { color: #252c36; font-size: 20px; font-weight: 600; }
.mapping-filter-form .danger-button { color: #fff; background: #d43c35; border-color: #d43c35; }
.mapping-filter-form .danger-button:hover { background: #b52e28; border-color: #b52e28; }
.mapping-filter-form .danger-button:disabled { color: #8a929d; background: #e8ebf0; border-color: #e1e4e9; }
.gateway-download-toolbar { margin-top: 17px; margin-bottom: 0; }
.gateway-filter-form input { width: 220px; }
.gateway-assignment-dialog .dialog-form { min-height: 0; }
.gateway-assignment-dialog .rule-editor-layout { grid-template-columns: minmax(0, 1fr) 350px; }
.identity-status-row { min-height: 40px; display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
.identity-status-row > span { color: #6c7883; font-size: 12px; }
.identity-resume-button { margin-top: 14px; }
.identity-qr-dialog { width: min(420px, calc(100% - 24px)); }
.identity-qr-content { display: grid; justify-items: center; gap: 14px; text-align: center; }
.identity-qr-content img { width: 256px; height: 256px; max-width: 100%; aspect-ratio: 1; border: 1px solid #d8e0e6; }
.identity-qr-content p { min-height: 20px; margin: 0; color: #5d6974; }
.identity-scan-link { min-height: 40px; display: inline-flex; align-items: center; justify-content: center; text-decoration: none; }
@media (max-width: 1000px) {
  .portal #user-overview.overview-metrics { grid-template-columns: 1fr 1fr; }
  .overview-quick-stats { border-bottom: 1px solid var(--console-line); }
  .overview-connection-metric { border-right: 0; border-bottom: 1px solid var(--console-line); }
  .overview-bandwidth-metric { grid-column: 1 / -1; }
}
@media (max-width: 700px) {
  .portal #user-overview.overview-metrics { grid-template-columns: 1fr; }
  .overview-quick-stats { border-right: 0; }
  .overview-connection-metric { border-right: 0; }
  .overview-bandwidth-metric { grid-column: auto; }
  .overview-bandwidth-lanes { grid-template-columns: 1fr; gap: 16px; }
  .overview-connection-metric,
  .overview-bandwidth-metric { padding: 20px; }
  .component-intro { grid-template-columns: 1fr; gap: 8px; padding: 14px; }
  .gateway-filter-form input,
  .gateway-filter-form select { width: 100%; }
  .gateway-assignment-dialog .rule-editor-layout { grid-template-columns: 1fr; }
  .identity-qr-content img { width: min(256px, 72vw); height: auto; }
}

.account-panel[data-account-panel="store"] { padding: 0; }
.store-shell { min-width: 0; color: #293542; background: #fff; }
.store-heading { min-height: 72px; display: flex; align-items: center; justify-content: space-between; gap: 18px; padding: 14px 22px; border-bottom: 1px solid #e1e6ec; }
.store-heading > div { display: flex; align-items: baseline; gap: 10px; }
.store-heading span { color: #697684; font-size: 12px; }
.store-heading strong { color: #2457df; font-size: 24px; font-weight: 700; }
.store-heading .primary-button { min-width: 92px; height: 36px; min-height: 36px; border-radius: 3px; background: #356cff; }
.store-heading .primary-button:hover { background: #2457df; }
.store-section { display: grid; grid-template-columns: 108px minmax(0, 1fr); gap: 18px; padding: 22px 28px; border-bottom: 1px solid #e5e9ee; }
.store-section > h2 { padding-top: 8px; color: #354250; font-size: 13px; font-weight: 650; }
.store-products { display: flex; flex-wrap: wrap; gap: 10px; }
.store-plan-tab { min-width: 96px; min-height: 38px; padding: 0 16px; border: 1px solid #cbd3dc; border-radius: 3px; color: #43515f; background: #fff; font-size: 13px; }
.store-plan-tab:hover { color: #2457df; border-color: #8fa8ee; }
.store-plan-tab.active { color: #fff; border-color: #356cff; background: #356cff; box-shadow: inset 0 0 0 1px #356cff; }
.store-spec-list,.store-definition-list { margin: 0; }
.store-spec-list { display: grid; grid-template-columns: repeat(4, minmax(110px, 1fr)); border: 1px solid #dce2e8; }
.store-spec-list > div { min-width: 0; padding: 13px 14px; border-right: 1px solid #e3e8ed; }
.store-spec-list > div:last-child { border-right: 0; }
.store-spec-list dt { color: #7a8793; font-size: 11px; }
.store-spec-list dd { margin: 7px 0 0; color: #2457df; font-size: 15px; font-weight: 700; overflow-wrap: anywhere; }
.store-definition-list { display: grid; gap: 8px; padding: 2px 0; }
.store-definition-list > div { display: grid; grid-template-columns: 74px minmax(0, 1fr); gap: 10px; }
.store-definition-list dt { color: #536271; font-size: 12px; font-weight: 650; }
.store-definition-list dd { margin: 0; color: #84909b; font-size: 12px; line-height: 1.55; }
.store-terms { display: flex; flex-wrap: wrap; }
.store-term-button { position: relative; min-width: 94px; min-height: 38px; margin-left: -1px; padding: 0 15px; border: 1px solid #cbd3dc; color: #43515f; background: #fff; font-size: 13px; }
.store-term-button:first-child { margin-left: 0; }
.store-term-button:hover { z-index: 1; color: #2457df; border-color: #8fa8ee; }
.store-term-button.active { z-index: 2; color: #fff; border-color: #356cff; background: #356cff; }
.store-checkout-bar { display: flex; align-items: center; gap: 26px; padding: 22px 28px 26px 154px; }
.store-total { min-width: 230px; display: grid; grid-template-columns: auto auto; align-items: baseline; justify-content: start; column-gap: 16px; }
.store-total > span { color: #354250; font-size: 13px; }
.store-total > strong { color: #2457df; font-size: 26px; font-weight: 500; }
.store-total > small { grid-column: 2; min-height: 18px; margin-top: 3px; color: #71808e; font-size: 11px; }
.store-total > small.insufficient { color: #b42318; }
.store-checkout-bar .primary-button { width: auto; min-width: 120px; min-height: 42px; margin: 0; padding: 0 22px; border-radius: 3px; background: #356cff; }
.store-checkout-bar .primary-button:hover { background: #2457df; }
.store-checkout-bar .primary-button:disabled { color: #8793a0; background: #e7ebf0; cursor: not-allowed; }
.purchase-wallet-row,.recharge-balance-row { max-width: 520px; min-height: 44px; display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 14px 0 0 128px; padding: 0 12px; color: #5f6d7a; background: #f7f9fb; border: 1px solid #dce2e8; font-size: 12px; }
.purchase-wallet-row strong,.recharge-balance-row strong { color: #2457df; font-size: 16px; }
.recharge-dialog { width: min(860px, calc(100% - 24px)); max-width: 860px; border-radius: 3px; }
.recharge-dialog .dialog-form { padding: 0; }
.recharge-dialog .dialog-heading { min-height: 54px; align-items: center; margin: 0; padding: 0 18px; border-bottom: 1px solid #e3e7ed; }
.recharge-dialog .dialog-heading .eyebrow { display: none; }
.recharge-dialog .dialog-heading h2 { font-size: 16px; font-weight: 500; }
.recharge-dialog .dialog-close { width: 30px; height: 30px; border: 0; border-radius: 0; }
.recharge-dialog .rule-editor-notice { margin: 14px 18px 0; }
.recharge-dialog .rule-editor-layout { margin-top: 12px; border-top: 1px solid #e4e8ee; }
.recharge-dialog .rule-editor-main { padding: 20px 22px; }
.recharge-balance-row { margin: 0 0 16px; }
.recharge-amount-label { max-width: 520px; display: grid; grid-template-columns: 110px minmax(0, 1fr) 24px; align-items: center; gap: 10px; margin: 0; }
.recharge-amount-label input { min-width: 0; }
.recharge-presets { max-width: 520px; display: grid; grid-template-columns: repeat(4, 1fr); margin-top: 10px; }
.recharge-presets button { min-height: 36px; margin-left: -1px; border: 1px solid #cbd3dc; color: #4a5968; background: #fff; }
.recharge-presets button:first-child { margin-left: 0; }
.recharge-presets button:hover,.recharge-presets button.active { z-index: 1; color: #2457df; border-color: #356cff; background: #f2f6ff; }
.recharge-dialog .payment-method-fieldset { max-width: 520px; }
.recharge-dialog .dialog-actions { justify-content: flex-start; }
.recharge-dialog .dialog-actions button { width: auto; min-width: 96px; margin: 0; }
.recharge-dialog .payment-waiting { min-height: 310px; align-content: center; }
.recharge-dialog .form-error { max-width: 520px; }
@media(max-width:980px){.store-spec-list{grid-template-columns:repeat(2,minmax(110px,1fr))}.store-spec-list>div{border-bottom:1px solid #e3e8ed}.store-spec-list>div:nth-child(2n){border-right:0}.store-spec-list>div:nth-last-child(-n+2){border-bottom:0}.store-checkout-bar{padding-left:28px}}
@media(max-width:700px){.store-heading{align-items:flex-start;flex-direction:column}.store-heading .primary-button{width:100%}.store-section{grid-template-columns:1fr;gap:10px;padding:18px 14px}.store-section>h2{padding-top:0}.store-plan-tab{flex:1 1 calc(50% - 5px);min-width:0}.store-definition-list>div{grid-template-columns:68px minmax(0,1fr)}.store-terms{display:grid;grid-template-columns:1fr 1fr}.store-term-button{min-width:0;margin:-1px 0 0 -1px}.store-term-button:nth-child(odd){margin-left:0}.store-term-button:nth-child(-n+2){margin-top:0}.store-checkout-bar{align-items:stretch;flex-direction:column;padding:18px 14px}.store-total{min-width:0}.store-checkout-bar .primary-button{width:100%}.purchase-wallet-row{margin-left:0}.recharge-dialog{width:calc(100% - 12px)}.recharge-dialog .rule-editor-layout{grid-template-columns:1fr}.recharge-dialog .rule-editor-notes{border-left:0;border-top:1px solid #e1e5eb}.recharge-amount-label{grid-template-columns:1fr 24px}.recharge-amount-label{font-size:0}.recharge-amount-label input{font-size:13px}.recharge-presets{grid-template-columns:1fr 1fr}.recharge-presets button{margin:-1px 0 0 -1px}.recharge-presets button:nth-child(odd){margin-left:0}.recharge-presets button:nth-child(-n+2){margin-top:0}.recharge-dialog .payment-method-options{grid-template-columns:1fr 1fr}.recharge-dialog .payment-method-options label{border-right:1px solid #cfd6dd;border-bottom:0}.recharge-dialog .payment-method-options label:last-child{border-right:0}}

.login-view {
  min-height: 100svh;
  display: grid;
  place-items: center;
  padding: 24px;
  overflow: hidden;
  color: #17202a;
  background: #eaf0f8;
  font-family: "Microsoft YaHei UI", "Microsoft YaHei", "PingFang SC", "Noto Sans CJK SC", sans-serif;
}
.login-layout {
  width: min(1320px, 100%);
  min-height: min(760px, calc(100svh - 48px));
  display: grid;
  grid-template-columns: minmax(0, 1.18fr) minmax(430px, .82fr);
  overflow: hidden;
  background: #fff;
  border: 1px solid #cfd9e7;
  border-radius: 6px;
  box-shadow: 0 24px 64px rgba(31, 52, 87, .18);
}
.login-intro {
  position: relative;
  min-width: 0;
  display: grid;
  grid-template-rows: auto auto auto auto 1fr;
  align-content: center;
  gap: 26px;
  padding: 54px 48px 36px;
  overflow: hidden;
  color: #293542;
  background: #f4f5f7;
}
.login-intro::before, .login-intro::after { display: none; }
.login-brand {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 14px;
}
.login-brand > div { min-width: 0; }
.login-brand h1 { max-width: 100%; color: #1688ff; font-family: "Microsoft YaHei UI", "Microsoft YaHei", sans-serif; font-size: 28px; font-weight: 700; line-height: 1.2; overflow-wrap: anywhere; }
.login-intro-copy { position: relative; z-index: 1; padding-top: 30px; }
.login-intro-copy h2 { max-width: 620px; color: #1688ff; font-family: "Microsoft YaHei UI", "Microsoft YaHei", sans-serif; font-size: 40px; font-weight: 700; line-height: 1.35; }
.login-intro-copy > p:last-child { margin: 10px 0 0; color: #263341; font-size: 16px; font-weight: 400; line-height: 1.7; }
.login-capability-list { position: relative; z-index: 1; display: grid; gap: 12px; margin: 12px 0 0; padding: 0; color: #6e7f98; font-size: 15px; list-style: none; }
.login-capability-list li { position: relative; padding-left: 20px; }
.login-capability-list li::before { content: ""; position: absolute; left: 2px; top: 10px; width: 5px; height: 5px; background: #78838f; border-radius: 50%; }
.intro-login-button { position: relative; z-index: 1; width: 160px; height: 46px; margin-top: 4px; border: 0; border-radius: 0; color: #fff; background: #1688ff; font-size: 15px; font-weight: 600; }
.intro-login-button:hover { background: #0875e8; }
.intro-login-button:focus-visible { outline: 3px solid rgba(22, 136, 255, .22); outline-offset: 3px; }
.login-intro footer { position: relative; z-index: 1; align-self: end; color: #a3acb8; font-size: 11px; }
.auth-workspace {
  min-width: 0;
  display: grid;
  grid-template-rows: auto 1fr auto;
  padding: 34px clamp(34px, 5vw, 68px) 28px;
  overflow-y: auto;
  color: #263341;
  background: #fff;
}
.auth-workspace-heading { display: flex; align-items: center; gap: 10px; color: #536271; font-size: 13px; font-weight: 400; }
.login-panel {
  width: min(100%, 430px);
  align-self: center;
  justify-self: center;
  padding: 34px 0;
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}
.login-panel .auth-section-label { margin: 0 0 9px; color: #2457df; font-size: 12px; font-weight: 500; }
.login-panel h2 { color: #17202a; font-family: "Microsoft YaHei UI", "Microsoft YaHei", sans-serif; font-size: 28px; font-weight: 700; line-height: 1.35; }
.login-panel .auth-caption { margin: 10px 0 28px; color: #7a8794; font-size: 13px; line-height: 1.6; }
.auth-fields { min-width: 0; }
.auth-fields label { margin: 16px 0 7px; color: #43515f; font-size: 13px; font-weight: 400; }
.auth-fields label:first-child { margin-top: 0; }
.auth-panel input[type="text"],
.auth-panel input[type="email"],
.auth-panel input[type="password"] {
  width: 100%;
  height: 48px;
  min-width: 0;
  padding: 0 13px;
  color: #25313e;
  background: #fff;
  border: 1px solid #cbd4df;
  border-radius: 3px;
  outline: 0;
  font-size: 14px;
}
.auth-panel input::placeholder { color: #a0aab5; }
.auth-panel input:focus { border-color: #2457df; box-shadow: 0 0 0 3px rgba(36, 87, 223, .12); }
.auth-panel .code-row { gap: 8px; }
.auth-panel .code-row .secondary-button { height: 48px; min-height: 48px; border-radius: 3px; color: #2457df; border-color: #aebeea; background: #f5f8ff; }
.auth-panel .code-row .secondary-button:hover { color: #1744bb; background: #e9efff; }
.auth-panel .form-error { margin: 13px 0 0; padding: 9px 11px; color: #a4342a; background: #fff4f2; border-left: 3px solid #d65245; font-size: 12px; }
.auth-panel .auth-submit { height: 48px; min-height: 48px; margin-top: 20px; border-radius: 3px; color: #fff; background: #2457df; border: 1px solid #2457df; }
.auth-panel .auth-submit:hover { background: #1744bb; border-color: #1744bb; }
.auth-panel .auth-submit:focus-visible,
.auth-links button:focus-visible { outline: 3px solid rgba(36, 87, 223, .24); outline-offset: 3px; }
.auth-panel .auth-submit:disabled { color: #8390a0; background: #e4e8ee; border-color: #e4e8ee; cursor: wait; }
.auth-panel .auth-links { margin-top: 18px; }
.auth-panel .auth-links button { color: #2457df; font-size: 12px; }
.auth-panel .auth-links button:hover { color: #1744bb; text-decoration: underline; }
.auth-links-single { justify-content: flex-start; }
.auth-workspace-footer { display: flex; justify-content: space-between; gap: 16px; padding-top: 14px; color: #8b96a2; border-top: 1px solid #e1e6ec; font-size: 9px; }
@media (max-width: 900px) {
  .login-view { padding: 0; overflow: visible; }
  .login-layout { min-height: 100svh; grid-template-columns: 1fr; border: 0; border-radius: 0; box-shadow: none; }
  .login-intro { min-height: 370px; grid-template-rows: auto auto auto auto auto; gap: 17px; padding: 24px; }
  .login-intro-copy { padding-top: 12px; }
  .login-intro-copy h2 { max-width: 520px; font-size: 28px; }
  .login-capability-list { grid-template-columns: 1fr 1fr; gap: 9px 14px; margin-top: 2px; font-size: 12px; }
  .intro-login-button { width: 140px; height: 42px; }
  .login-intro footer { display: block; }
  .auth-workspace { min-height: calc(100svh - 370px); padding: 22px 24px 18px; }
  .login-panel { padding: 32px 0; }
}
@media (max-width: 520px) {
  .login-intro { min-height: 350px; padding: 20px 18px 16px; }
  .login-brand h1 { font-size: 22px; }
  .login-intro-copy { padding-top: 8px; }
  .login-intro-copy h2 { font-size: 25px; }
  .login-intro-copy > p:last-child { margin-top: 8px; font-size: 13px; }
  .login-capability-list { font-size: 11px; }
  .intro-login-button { width: 128px; }
  .login-intro footer { font-size: 9px; }
  .auth-workspace { min-height: calc(100svh - 350px); padding: 18px; }
  .auth-workspace-heading { padding-bottom: 2px; }
  .login-panel { padding: 28px 0 22px; }
  .login-panel h2 { font-size: 24px; }
  .login-panel .auth-caption { margin-bottom: 22px; }
  .auth-panel .code-row { grid-template-columns: 1fr; }
  .auth-panel .code-row .secondary-button { width: 100%; }
}

(() => {
  "use strict";
  const adminSessionKey = "gameshield.adminSession";
  const byId = (id) => document.getElementById(id);
  const loginView = byId("login-view");
  const dashboardView = byId("dashboard-view");
  const loginForm = byId("login-form");
  const usernameInput = byId("admin-username");
  const passwordInput = byId("admin-password");
  const loginError = byId("login-error");
  const errorBanner = byId("error-banner");
  const refreshButton = byId("refresh-button");
  const edgeDialog = byId("edge-dialog");
  const edgeForm = byId("edge-form");
  const edgeFormError = byId("edge-form-error");
  const edgeCapacityDialog = byId("edge-capacity-dialog");
  const edgeCapacityForm = byId("edge-capacity-form");
  const userDialog = byId("user-dialog");
  const userForm = byId("user-form");
  const userFormError = byId("user-form-error");
	const userPasswordDialog = byId("user-password-dialog");
	const userPasswordForm = byId("user-password-form");
	const planDialog = byId("plan-dialog");
	const planForm = byId("plan-form");
	const assignPlanDialog = byId("assign-plan-dialog");
	const subscriptionDialog = byId("subscription-dialog");
	const subscriptionForm = byId("subscription-form");
	const smtpForm = byId("smtp-form");
	const gatewayDownloadSettingsForm = byId("gateway-download-settings-form");
	const realIPDownloadSettingsForm = byId("realip-download-settings-form");
	const portalSettingsForm = byId("portal-settings-form");
	const baiduSubmissionSettingsForm = byId("baidu-submission-settings-form");
	const baiduSubmissionForm = byId("baidu-submission-form");
	const articleForm = byId("article-form");
	const adminPasswordForm = byId("admin-password-form");
	const paymentSettingsForm = byId("payment-settings-form");
	const identitySettingsForm = byId("identity-settings-form");
	const identityReviewDialog = byId("identity-review-dialog");
	const identityReviewForm = byId("identity-review-form");
	const walletDialog = byId("wallet-dialog");
	const walletForm = byId("wallet-form");
	const marketplaceAuthorizationDialog = byId("marketplace-authorization-dialog");
	const marketplaceAuthorizationForm = byId("marketplace-authorization-form");
	const marketplaceAuthorizationActionDialog = byId("marketplace-authorization-action-dialog");
	const marketplaceAuthorizationActionForm = byId("marketplace-authorization-action-form");
	const tenantDialog = byId("tenant-dialog");
	const tenantForm = byId("tenant-form");
	const tenantLicenseDialog = byId("tenant-license-dialog");
	const tenantLicenseForm = byId("tenant-license-form");
	const tenantDomainDialog = byId("tenant-domain-dialog");
	const tenantDomainForm = byId("tenant-domain-form");
	const oneTimeSecretDialog = byId("one-time-secret-dialog");

  let adminToken = sessionStorage.getItem(adminSessionKey) || "";
	let currentPlans = [];
	let currentSubscriptions = [];
	let editingPlan = null;
	let editingSubscription = null;
	let assigningUser = null;
	let editingEdgeCapacity = null;
	let selectedEdgeIDs = new Set();
	let edgePage = 1;
	const edgePageSize = 15;
	let edgeReleases = [];
	let edgeRollouts = [];
	let releaseCatalog = { center: {}, artifacts: [] };
	let distributorReleases = [];
	let distributorInstallerInfo = { available: false };
	let marketplacePlans = [];
	let marketplaceListings = [];
	let marketplaceComplaints = [];
	let marketplaceWithdrawals = [];
	let adminArticles = [];
	let currentTenants = [];
	let allEdges = [];
	let currentEdges = [];
	let selectedEdgeDistributor = null;
	let currentTenantLicenses = [];
	let selectedTenant = null;
	let adjustingWalletUser = null;
	let managingMarketplaceAuthorizationUser = null;
	let marketplaceAuthorizations = [];
	let marketplaceAuthorizationAction = null;
	let changingUserPassword = null;
	let reviewingIdentity = null;
	const adminViewAliases = { "admin-overview": "overview", "tenant-heading": "distributors", "marketplace-heading": "marketplace", "user-heading": "users", "edge-heading": "nodes", "shield-heading": "rules", "gateway-download-settings-heading": "gateway-downloads", "realip-download-settings-heading": "realip-downloads", "smtp-heading": "email", "portal-settings-heading": "other" };
	function activateAdminView(view, replaceHash = false) {
	  const allowed = new Set(["overview", "distributors", "marketplace", "users", "nodes", "rules", "gateway-downloads", "realip-downloads", "email", "other"]);
	  if (!allowed.has(view)) view = "overview";
	  document.querySelectorAll("[data-admin-view]").forEach((link) => link.classList.toggle("active", link.dataset.adminView === view));
	  document.querySelectorAll("[data-admin-panel]").forEach((panel) => { panel.hidden = panel.dataset.adminPanel !== view; });
	  if (replaceHash) history.replaceState(null, "", "#" + view);
	}

  function basicAuthorization(username, password) {
    const bytes = new TextEncoder().encode(username + ":" + password);
    let binary = "";
    for (const value of bytes) binary += String.fromCharCode(value);
    return "Basic " + btoa(binary);
  }

  function adminAuthorization() {
    return "Bearer " + adminToken;
  }

	function clearAdminSession(message = "", tone = "error") {
    sessionStorage.removeItem(adminSessionKey);
    adminToken = "";
    dashboardView.hidden = true;
    loginView.hidden = false;
		loginError.textContent = message;
		loginError.hidden = !message;
		loginError.classList.toggle("success", tone === "success");
    passwordInput.value = "";
    passwordInput.focus();
  }

  async function api(path, options = {}) {
    const headers = { Authorization: adminAuthorization(), Accept: "application/json" };
    if (options.body) headers["Content-Type"] = "application/json";
    const response = await fetch(path, {
      method: options.method || "GET",
      headers,
      body: options.body ? JSON.stringify(options.body) : undefined,
      cache: "no-store"
    });
    if (response.status === 401) throw new Error("unauthorized");
	const payload = response.status === 204 ? {} : await response.json().catch(() => ({}));
	if (!response.ok) { const error = new Error(payload.error || ("HTTP " + response.status)); error.detail = payload.detail || null; throw error; }
    return payload;
  }

  async function downloadEdgePackage() {
    const button = byId("edge-package-download");
    button.disabled = true;
    errorBanner.hidden = true;
    try {
      const response = await fetch("/v1/admin/edge-package", {
        headers: { Authorization: adminAuthorization() },
        cache: "no-store"
      });
      if (response.status === 401) throw new Error("unauthorized");
      if (!response.ok) {
        const payload = await response.json().catch(() => ({}));
        throw new Error(payload.error || ("HTTP " + response.status));
      }
      const url = URL.createObjectURL(await response.blob());
      const link = document.createElement("a");
      link.href = url;
	  const disposition = response.headers.get("Content-Disposition") || "";
	  const encodedFilename = /filename\*=UTF-8''([^;]+)/i.exec(disposition);
	  const plainFilename = /filename="([^"]+)"/i.exec(disposition);
	  let filename = "高防节点.exe";
	  if (encodedFilename) { try { filename = decodeURIComponent(encodedFilename[1]); } catch (_) {} }
	  else if (plainFilename) filename = plainFilename[1];
	  link.download = filename;
      document.body.append(link);
      link.click();
      link.remove();
      setTimeout(() => URL.revokeObjectURL(url), 1000);
    } catch (error) {
      if (error.message === "unauthorized") {
		clearAdminSession("登录已失效，请重新登录");
      } else {
        const messages = { edge_package_unavailable: "高防节点安装包尚未发布", edge_package_error: "读取高防节点安装包失败" };
        errorBanner.textContent = messages[error.message] || ("下载安装包失败：" + error.message);
        errorBanner.hidden = false;
      }
    } finally {
      button.disabled = false;
    }
  }

  function renderDistributorInstaller(info) {
	distributorInstallerInfo = info && info.available ? info : { available: false };
	const button = byId("distributor-installer-download");
	button.disabled = !distributorInstallerInfo.available;
	button.textContent = distributorInstallerInfo.available ? ("一键下载 游戏盾分销端-" + distributorInstallerInfo.version) : "分销端尚未发布";
  }

  function renderEdgeInstaller(info) {
	const available = info && info.available;
	const button = byId("edge-package-download");
	button.disabled = !available;
	button.textContent = available && info.version ? ("下载安装包 " + info.version) : available ? "下载安装包" : "安装包尚未发布";
  }

  async function downloadDistributorInstaller() {
	const button = byId("distributor-installer-download");
	if (!distributorInstallerInfo.available) return;
	button.disabled = true;
	errorBanner.hidden = true;
	try {
	  const response = await fetch("/v1/admin/distributor-installer", { headers: { Authorization: adminAuthorization() }, cache: "no-store" });
	  if (response.status === 401) throw new Error("unauthorized");
	  if (!response.ok) {
		const payload = await response.json().catch(() => ({}));
		throw new Error(payload.error || ("HTTP " + response.status));
	  }
	  const url = URL.createObjectURL(await response.blob());
	  const link = document.createElement("a");
	  link.href = url;
	  link.download = distributorInstallerInfo.filename || ("游戏盾分销端-" + distributorInstallerInfo.version + ".exe");
	  document.body.append(link);
	  link.click();
	  link.remove();
	  setTimeout(() => URL.revokeObjectURL(url), 1000);
	} catch (error) {
	  if (error.message === "unauthorized") clearAdminSession("登录已失效，请重新登录");
	  else {
		const messages = { distributor_installer_unavailable: "分销端安装器尚未发布", distributor_installer_error: "读取分销端安装器失败" };
		errorBanner.textContent = messages[error.message] || ("下载分销端失败：" + error.message);
		errorBanner.hidden = false;
	  }
	} finally {
	  button.disabled = !distributorInstallerInfo.available;
	}
  }

  function marketplaceStatus(value) {
    const labels = { pending: "待审核", active: "已上架", rejected: "已拒绝", suspended: "已下架", expired: "已到期", paid: "已支付" };
    return statusCell(labels[value] || value, value === "active" || value === "paid" ? "healthy" : value === "pending" ? "warning" : "offline");
  }

  function renderMarketplacePlans(items) {
    marketplacePlans = items || [];
    const body = byId("marketplace-plan-body"); body.replaceChildren(); byId("marketplace-plan-empty").hidden = marketplacePlans.length !== 0;
    for (const plan of marketplacePlans) {
      const row = document.createElement("tr");
      cell(row, plan.sort_order); cell(row, plan.name, "node-name"); cell(row, Number(plan.purchase_limit || 0) === 0 ? "不限" : plan.purchase_limit + " 个"); cell(row, [plan.upgrade_enabled ? "可升级" : "禁升级", plan.renewal_enabled ? "可续费" : "禁续费"].join(" / "), "nowrap"); cell(row, plan.term_days + " 天"); cell(row, formatMoney(plan.price_cents));
      cell(row, plan.max_users); cell(row, plan.max_nodes); cell(row, Number(plan.max_shields || 0) === 0 ? "不限" : plan.max_shields); cell(row, plan.sdk_enabled ? statusCell("支持", "healthy") : "不支持");
      const integrations = [plan.android_sdk_integration_enabled ? "Android" : "", plan.ios_sdk_integration_enabled ? "iOS" : ""].filter(Boolean).join(" / ");
      cell(row, integrations || "未开通", integrations ? "nowrap" : "muted"); cell(row, plan.dynamic_public_ip_enabled ? statusCell("支持", "healthy") : "不支持");
      const ruleFeatures = [plan.real_ip_enabled ? "真实 IP" : "", plan.mobile_platform_enabled ? "移动端" : "", plan.both_platform_enabled ? "PC+移动" : ""].filter(Boolean).join(" / ");
      cell(row, ruleFeatures || "未授权", ruleFeatures ? "nowrap" : "muted"); cell(row, plan.referral_enabled ? statusCell("支持", "healthy") : "不支持"); cell(row, plan.scratch_lottery_enabled ? statusCell("支持", "healthy") : "不支持");
      cell(row, plan.enabled ? statusCell("在售", "healthy") : statusCell("停用", "offline"));
      const actions = document.createElement("div"); actions.className = "row-actions";
      const edit = document.createElement("button"); edit.type = "button"; edit.className = "table-action"; edit.textContent = "编辑";
      edit.addEventListener("click", () => fillMarketplacePlan(plan)); actions.append(edit); cell(row, actions); body.append(row);
    }
  }

  function fillMarketplacePlan(plan = null) {
    byId("marketplace-plan-form").reset();
    byId("marketplace-plan-id").value = plan ? plan.id : "";
    byId("marketplace-plan-name").value = plan ? plan.name : "";
    byId("marketplace-plan-sort-order").value = plan ? Number(plan.sort_order || 1) : marketplacePlans.length + 1;
    byId("marketplace-plan-purchase-limit").value = plan ? Number(plan.purchase_limit || 0) : 0;
	byId("marketplace-plan-upgrade-enabled").checked = !plan || Boolean(plan.upgrade_enabled);
	byId("marketplace-plan-renewal-enabled").checked = !plan || Boolean(plan.renewal_enabled);
    byId("marketplace-plan-description").value = plan ? plan.description : "";
    byId("marketplace-plan-days").value = plan ? plan.term_days : 30;
    byId("marketplace-plan-price").value = plan ? (Number(plan.price_cents) / 100).toFixed(2) : "0.00";
    byId("marketplace-plan-users").value = plan ? plan.max_users : 100;
    byId("marketplace-plan-nodes").value = plan ? plan.max_nodes : 5;
    byId("marketplace-plan-shields").value = plan ? Number(plan.max_shields || 0) : 0;
    byId("marketplace-plan-sdk").checked = Boolean(plan && plan.sdk_enabled);
    byId("marketplace-plan-android-sdk-integration").checked = Boolean(plan && plan.android_sdk_integration_enabled);
    byId("marketplace-plan-ios-sdk-integration").checked = Boolean(plan && plan.ios_sdk_integration_enabled);
	byId("marketplace-plan-dynamic-public-ip").checked = Boolean(plan && plan.dynamic_public_ip_enabled);
	byId("marketplace-plan-home-tunnel").checked = Boolean(plan && plan.home_tunnel_enabled);
    byId("marketplace-plan-real-ip").checked = Boolean(plan && plan.real_ip_enabled);
    byId("marketplace-plan-mobile-platform").checked = Boolean(plan && plan.mobile_platform_enabled);
    byId("marketplace-plan-both-platform").checked = Boolean(plan && plan.both_platform_enabled);
    byId("marketplace-plan-referral").checked = Boolean(plan && plan.referral_enabled);
    byId("marketplace-plan-scratch-lottery").checked = Boolean(plan && plan.scratch_lottery_enabled);
    byId("marketplace-plan-enabled").checked = !plan || Boolean(plan.enabled);
    byId("marketplace-plan-error").hidden = true;
  }

  function renderMarketplaceListings(items) {
    marketplaceListings = items || [];
    const body = byId("marketplace-listing-body"); body.replaceChildren(); byId("marketplace-listing-empty").hidden = marketplaceListings.length !== 0;
    for (const listing of marketplaceListings) {
      const row = document.createElement("tr"), revision = listing.pending_revision; cell(row, listing.provider_name); cell(row, listing.address, "nowrap");
      cell(row, listing.region + " / " + listing.route_name + (revision ? " → " + revision.region + " / " + revision.route_name : ""));
      cell(row, listing.declared_bandwidth_mbps + " Mbps" + (revision ? " → " + revision.declared_bandwidth_mbps + " Mbps" : ""));
      cell(row, listing.shared_bandwidth_limit_mbps ? listing.shared_bandwidth_limit_mbps + " Mbps / " + listing.shared_limit_percent + "%" : "待审核");
      cell(row, listing.supports_udp_declared ? (listing.supports_udp_verified ? "已验证" : "申报支持") : "不支持");
      cell(row, listing.lease_days + " 天 / " + formatMoney(listing.price_cents));
      const listingState = revision ? "资料修改待审" : listing.status === "active" && !listing.healthy ? "离线" : marketplaceStatus(listing.status);
      const stateCell = cell(row, listingState);
      if (listing.status === "active" && !listing.healthy) stateCell.title = "节点心跳已超时，当前不能购买；连续离线超过 24 小时将自动隐藏";
      const actions = document.createElement("div"); actions.className = "row-actions";
      if (revision) {
        const approveRevision = document.createElement("button"); approveRevision.type = "button"; approveRevision.className = "table-action"; approveRevision.textContent = "审核修改";
        approveRevision.addEventListener("click", () => reviewMarketplaceListingRevision(listing, "approved")); actions.append(approveRevision);
        const rejectRevision = document.createElement("button"); rejectRevision.type = "button"; rejectRevision.className = "table-action danger"; rejectRevision.textContent = "驳回修改";
        rejectRevision.addEventListener("click", () => reviewMarketplaceListingRevision(listing, "rejected")); actions.append(rejectRevision);
      } else if (listing.status === "pending" || listing.status === "rejected" || listing.status === "suspended") {
        const approve = document.createElement("button"); approve.type = "button"; approve.className = "table-action"; approve.textContent = "审核上架";
        approve.addEventListener("click", () => reviewMarketplaceListing(listing, "active")); actions.append(approve);
      }
      if (listing.status === "pending") {
        const reject = document.createElement("button"); reject.type = "button"; reject.className = "table-action danger"; reject.textContent = "拒绝";
        reject.addEventListener("click", () => reviewMarketplaceListing(listing, "rejected")); actions.append(reject);
      } else if (listing.status === "active") {
        const suspend = document.createElement("button"); suspend.type = "button"; suspend.className = "table-action danger"; suspend.textContent = "下架";
        suspend.addEventListener("click", () => reviewMarketplaceListing(listing, "suspended")); actions.append(suspend);
      }
      cell(row, actions); body.append(row);
    }
  }

  async function reviewMarketplaceListing(listing, status) {
    let bandwidth = listing.audited_bandwidth_mbps || listing.declared_bandwidth_mbps;
    let udp = false;
    let note = "";
    if (status === "active") {
      const input = prompt("请输入审核通过的实际带宽 Mbps", String(bandwidth));
      if (input === null) return; bandwidth = Number(input);
      if (!Number.isInteger(bandwidth) || bandwidth <= 0) return alert("审核带宽不正确");
      udp = listing.supports_udp_declared && confirm("是否已经验证该节点支持 20000/UDP？");
      note = prompt("审核备注", "人工审核通过") || "";
    } else {
	  if (status === "suspended" && !confirm("下架会立即停止该节点的所有租用，并按完整 24 小时结算；不足一天的部分不计收益并自动退款。确认继续吗？")) return;
      note = prompt(status === "rejected" ? "请输入拒绝原因" : "请输入下架原因", "") || "";
      if (!note.trim()) return;
    }
    await api("/v1/admin/marketplace/listings/" + encodeURIComponent(listing.id), { method: "PATCH", body: { status, audited_bandwidth_mbps: bandwidth, supports_udp_verified: udp, review_note: note.trim() } });
    await refresh();
  }

  async function reviewMarketplaceListingRevision(listing, status) {
    const revision = listing.pending_revision; if (!revision) return;
    let bandwidth = revision.declared_bandwidth_mbps, note = "";
    if (status === "approved") {
      const input = prompt("请输入审核通过的实际带宽 Mbps", String(bandwidth));
      if (input === null) return; bandwidth = Number(input);
      if (!Number.isInteger(bandwidth) || bandwidth <= 0) return alert("审核带宽不正确");
      note = prompt("修改审核备注", "节点资料修改审核通过") || "";
    } else {
      note = prompt("请输入驳回修改的原因", "") || "";
      if (!note.trim()) return;
    }
    await api("/v1/admin/marketplace/listings/" + encodeURIComponent(listing.id) + "/revisions/" + encodeURIComponent(revision.id), { method:"PATCH", body:{ status, audited_bandwidth_mbps:bandwidth, review_note:note.trim() } });
    await refresh();
  }

  function renderMarketplaceComplaints(items) {
    marketplaceComplaints = items || [];
    const body = byId("marketplace-complaint-body"); body.replaceChildren(); byId("marketplace-complaint-empty").hidden = marketplaceComplaints.length !== 0;
    for (const complaint of marketplaceComplaints) {
      const row = document.createElement("tr"); cell(row, complaint.node_id); cell(row, complaint.tenant_id); cell(row, complaint.category);
      cell(row, complaint.description); cell(row, complaint.penalty_cents ? formatMoney(complaint.penalty_cents) : "-"); cell(row, marketplaceStatus(complaint.status));
      const actions = document.createElement("div"); actions.className = "row-actions";
      if (complaint.status === "pending") {
        const confirmButton = document.createElement("button"); confirmButton.type = "button"; confirmButton.className = "table-action"; confirmButton.textContent = "确认有效";
        confirmButton.addEventListener("click", () => reviewMarketplaceComplaint(complaint, "confirmed")); actions.append(confirmButton);
        const rejectButton = document.createElement("button"); rejectButton.type = "button"; rejectButton.className = "table-action danger"; rejectButton.textContent = "驳回";
        rejectButton.addEventListener("click", () => reviewMarketplaceComplaint(complaint, "rejected")); actions.append(rejectButton);
      }
      cell(row, actions); body.append(row);
    }
  }

  async function reviewMarketplaceComplaint(complaint, status) {
    const note = prompt(status === "confirmed" ? "请输入确认依据" : "请输入驳回原因", ""); if (note === null || !note.trim()) return;
    const severe = status === "confirmed" && confirm("是否属于严重质量问题？严重投诉超过 5 个不同租户会自动下架节点。");
    await api("/v1/admin/marketplace/complaints/" + encodeURIComponent(complaint.id), { method: "PATCH", body: { status, severe, review_note: note.trim() } });
    await refresh();
  }

  function renderMarketplaceWithdrawals(items) {
    marketplaceWithdrawals = items || [];
    const body = byId("marketplace-withdrawal-body"); body.replaceChildren(); byId("marketplace-withdrawal-empty").hidden = marketplaceWithdrawals.length !== 0;
    for (const withdrawal of marketplaceWithdrawals) {
      const row = document.createElement("tr"); cell(row, withdrawal.username); cell(row, formatMoney(withdrawal.gross_cents)); cell(row, formatMoney(withdrawal.fee_cents));
      cell(row, formatMoney(withdrawal.net_cents)); cell(row, withdrawal.payout_method + " / " + withdrawal.payout_account); cell(row, marketplaceStatus(withdrawal.status));
      const actions = document.createElement("div"); actions.className = "row-actions";
      if (withdrawal.status === "pending") {
        const paid = document.createElement("button"); paid.type = "button"; paid.className = "table-action"; paid.textContent = "确认已支付";
        paid.addEventListener("click", () => reviewMarketplaceWithdrawal(withdrawal, "paid")); actions.append(paid);
        const reject = document.createElement("button"); reject.type = "button"; reject.className = "table-action danger"; reject.textContent = "驳回";
        reject.addEventListener("click", () => reviewMarketplaceWithdrawal(withdrawal, "rejected")); actions.append(reject);
      }
      cell(row, actions); body.append(row);
    }
  }

  async function reviewMarketplaceWithdrawal(withdrawal, status) {
    if (status === "paid" && !confirm("确认已经向该账户支付 " + formatMoney(withdrawal.net_cents) + "？")) return;
    const note = prompt(status === "paid" ? "支付备注" : "驳回原因", "") || ""; if (status === "rejected" && !note.trim()) return;
    await api("/v1/admin/marketplace/withdrawals/" + encodeURIComponent(withdrawal.id), { method: "PATCH", body: { status, review_note: note.trim() } });
    await refresh();
  }

  function setText(id, value) {
    byId(id).textContent = String(value);
  }

  function statusCell(label, state) {
    const span = document.createElement("span");
    span.className = "status-label";
    const dot = document.createElement("i");
    dot.className = "status-dot " + state;
    span.append(dot, document.createTextNode(label));
    return span;
  }

  function cell(row, value, className) {
    const td = document.createElement("td");
    if (value instanceof Node) td.appendChild(value);
    else td.textContent = value == null || value === "" ? "-" : String(value);
    if (className) td.className = className;
    row.appendChild(td);
    return td;
  }

  function formatHeartbeat(epochSeconds) {
    if (!epochSeconds) return "从未";
    return new Intl.DateTimeFormat("zh-CN", {
      year: "numeric", month: "2-digit", day: "2-digit",
      hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false
    }).format(new Date(epochSeconds * 1000));
  }

  function formatDuration(seconds) {
    const value = Math.max(0, Number(seconds) || 0);
    const days = Math.floor(value / 86400);
    const hours = Math.floor((value % 86400) / 3600);
    const minutes = Math.floor((value % 3600) / 60);
    if (days) return days + " 天 " + hours + " 小时";
    if (hours) return hours + " 小时 " + minutes + " 分钟";
    return minutes + " 分钟";
  }

  function formatRate(bitsPerSecond) {
    const value = Math.max(0, Number(bitsPerSecond) || 0);
    if (value >= 1000000000) return (value / 1000000000).toFixed(value >= 10000000000 ? 1 : 2) + " Gbps";
    if (value >= 1000000) return (value / 1000000).toFixed(value >= 10000000 ? 1 : 2) + " Mbps";
    if (value >= 1000) return (value / 1000).toFixed(value >= 10000 ? 1 : 2) + " Kbps";
    return Math.round(value) + " bps";
  }

  function formatBytes(bytes) {
	const value = Math.max(0, Number(bytes) || 0);
	if (value >= 1073741824) return (value / 1073741824).toFixed(2) + " GB";
	if (value >= 1048576) return (value / 1048576).toFixed(2) + " MB";
	if (value >= 1024) return (value / 1024).toFixed(1) + " KB";
	return Math.round(value) + " B";
  }

	function formatMoney(cents) { return "¥" + (Number(cents || 0) / 100).toFixed(2); }
	function formatSignedMoney(cents) { const value = Number(cents || 0); return (value > 0 ? "+" : value < 0 ? "-" : "") + formatMoney(Math.abs(value)); }
	function newIdempotencyKey(prefix) { return prefix + "-" + (crypto.randomUUID ? crypto.randomUUID() : Date.now() + "-" + Math.random().toString(16).slice(2)); }

  function toLocalInputValue(epochSeconds) {
    const date = epochSeconds ? new Date(epochSeconds * 1000) : new Date(Date.now() + 30 * 86400000);
    const local = new Date(date.getTime() - date.getTimezoneOffset() * 60000);
    return local.toISOString().slice(0, 16);
  }

  function subscriptionState(subscription) {
    if (subscription.status === "paused") return statusCell("手动暂停", "warning");
    if (subscription.status === "expired") return statusCell("已到期", "offline");
    if (subscription.status === "disabled") return statusCell("套餐停用", "offline");
    return statusCell(subscription.app_id ? "正常" : "待配置", subscription.app_id ? "healthy" : "warning");
  }

  function renderEdges(edges) {
    const body = byId("edge-table-body");
    const empty = byId("edge-empty");
    const pager = byId("edge-pager");
    body.replaceChildren();
    empty.hidden = edges.length !== 0;
    pager.replaceChildren();
    pager.hidden = edges.length === 0;
    const totalPages = Math.max(1, Math.ceil(edges.length / edgePageSize));
    edgePage = Math.min(Math.max(1, edgePage), totalPages);
    const pageStart = (edgePage - 1) * edgePageSize;
    const pageEdges = edges.slice(pageStart, pageStart + edgePageSize);
    for (const edge of pageEdges) {
      const row = document.createElement("tr");
      const node = document.createElement("div"); node.className = "edge-metric node-name";
	  const selector = document.createElement("input"); selector.type = "checkbox"; selector.className = "edge-select"; selector.checked = selectedEdgeIDs.has(edge.node_id); selector.disabled = !edge.supports_remote_updates; selector.title = selector.disabled ? "请先手动安装新版节点管理器" : "选择升级节点";
	  selector.addEventListener("change", () => { if (selector.checked) selectedEdgeIDs.add(edge.node_id); else selectedEdgeIDs.delete(edge.node_id); updateEdgeSelection(); });
      const nodeID = document.createElement("span"); nodeID.textContent = edge.node_id;
      const nodeVersion = document.createElement("small"); nodeVersion.textContent = edge.software_version ? ("v" + edge.software_version + (edge.build_commit ? " · " + edge.build_commit : "")) : "旧版节点 · 等待升级";
	  const nodeLine = document.createElement("span"); nodeLine.append(selector, nodeID); node.append(nodeLine, nodeVersion); cell(row, node);
	  cell(row, edge.owner_official ? "中心自有" : (edge.owner_tenant_name || "归属信息缺失"), edge.owner_official ? "muted nowrap" : "node-name nowrap");
      cell(row, edge.address, "nowrap");
      cell(row, "玩家 " + (edge.player_port || 20000) + " / 管理 " + (edge.management_port || 65499) + " / 回源 " + (edge.gateway_tunnel_port || 21001), "nowrap");
      const bandwidth = document.createElement("div"); bandwidth.className = "edge-metric";
      const upload = document.createElement("span"); upload.textContent = "上行 " + formatRate(edge.upload_bps);
      const download = document.createElement("span"); download.textContent = "下行 " + formatRate(edge.download_bps);
      const reported = document.createElement("small"); reported.textContent = edge.metrics_at ? "更新 " + formatHeartbeat(edge.metrics_at) : "等待新版节点上报";
      bandwidth.append(upload, download, reported); cell(row, bandwidth);
      const utilization = edge.bandwidth_limit_mbps > 0 ? Number(edge.bandwidth_utilization_percent || 0).toFixed(1) + "%" : "-";
      cell(row, utilization, "nowrap");
      cell(row, edge.metrics_at ? edge.active_connections : "-", "nowrap");
      const capacity = document.createElement("div"); capacity.className = "edge-metric";
      const capacityValue = document.createElement("span"); capacityValue.textContent = edge.bandwidth_limit_mbps > 0 ? edge.bandwidth_limit_mbps + " Mbps" : "未限制";
      const threshold = document.createElement("small"); threshold.textContent = "阈值 " + edge.bandwidth_threshold_percent + "%";
      capacity.append(capacityValue, threshold); cell(row, capacity);
      cell(row, edge.healthy ? statusCell("健康", "healthy") : statusCell("离线", "offline"));
      const configFresh = Number(edge.last_config_applied_at || 0) > 0 && Math.floor(Date.now() / 1000) - Number(edge.last_config_applied_at) <= 45;
      const routeState = !edge.enabled ? statusCell("已禁用", "offline") : edge.draining ? statusCell("排空", "warning") : !edge.healthy ? statusCell("待心跳", "warning") : edge.last_sync_error ? statusCell("同步异常", "offline") : !edge.software_version ? statusCell("旧版待升级", "warning") : !configFresh ? statusCell("等待配置确认", "warning") : edge.bandwidth_saturated ? statusCell("带宽繁忙", "warning") : statusCell("配置已同步", "healthy");
      if (edge.last_sync_error) routeState.title = edge.last_sync_error;
      cell(row, routeState);
      cell(row, formatHeartbeat(edge.last_heartbeat), "muted nowrap");
      const actions = document.createElement("div"); actions.className = "row-actions";
      const capacityButton = document.createElement("button"); capacityButton.type = "button"; capacityButton.className = "table-action neutral"; capacityButton.textContent = "设置容量"; capacityButton.addEventListener("click", () => openEdgeCapacity(edge));
      const removeButton = document.createElement("button");
      removeButton.type = "button";
      removeButton.className = "table-action";
      removeButton.textContent = "移除";
      removeButton.addEventListener("click", async () => {
        if (!window.confirm("确认移除节点 " + edge.node_id + "？")) return;
        removeButton.disabled = true;
        try {
          await api("/v1/admin/edges/" + encodeURIComponent(edge.node_id), { method: "DELETE" });
          await refresh();
        } catch (error) {
		  const removeErrors = { edge_marketplace_in_use: "该节点存在商城租用或结算记录，不能直接移除，请先暂停节点并处理租用关系" };
		  errorBanner.textContent = "移除节点失败：" + (removeErrors[error.message] || error.message);
          errorBanner.hidden = false;
          removeButton.disabled = false;
        }
      });
      actions.append(capacityButton, removeButton); cell(row, actions);
      body.appendChild(row);
    }
    if (edges.length) {
      const summary = document.createElement("span");
      summary.className = "edge-pager-summary";
      summary.textContent = "共 " + edges.length + " 个节点，每页 15 个";
      const previous = document.createElement("button");
      previous.type = "button";
      previous.className = "edge-pager-button";
      previous.textContent = "上一页";
      previous.disabled = edgePage === 1;
      previous.addEventListener("click", () => { edgePage--; renderEdges(currentEdges); });
      const status = document.createElement("span");
      status.className = "edge-pager-status";
      status.textContent = edgePage + " / " + totalPages + " 页";
      const next = document.createElement("button");
      next.type = "button";
      next.className = "edge-pager-button";
      next.textContent = "下一页";
      next.disabled = edgePage === totalPages;
      next.addEventListener("click", () => { edgePage++; renderEdges(currentEdges); });
      pager.append(summary, previous, status, next);
    }
  }

  function renderEdgeDistributors(items, edges) {
    const body = byId("edge-distributor-body");
    const empty = byId("edge-distributor-empty");
    body.replaceChildren();
    const tenants = (items || []).filter((tenant) => !tenant.official);
    const ownerCounts = new Map();
    for (const edge of (edges || [])) {
      if (edge.owner_tenant_id) ownerCounts.set(edge.owner_tenant_id, (ownerCounts.get(edge.owner_tenant_id) || 0) + 1);
    }
    empty.hidden = tenants.length !== 0;
    for (const tenant of tenants) {
      const row = document.createElement("tr");
      cell(row, tenant.name, "node-name");
      cell(row, tenant.slug, "muted nowrap");
      cell(row, tenantStatusCell(tenant.status));
      cell(row, String(ownerCounts.get(tenant.id) || 0) + " / " + String(tenant.max_nodes || 0), "nowrap");
      cell(row, String(tenant.user_count || 0) + " / " + String(tenant.max_users || 0), "nowrap");
      const button = document.createElement("button");
      button.type = "button";
      button.className = "table-action";
      button.textContent = "查看节点";
      button.addEventListener("click", () => openEdgeDistributor(tenant));
      cell(row, button);
      body.append(row);
    }
    const officialEdges = (edges || []).filter((edge) => edge.owner_official);
    if (officialEdges.length) {
      const row = document.createElement("tr");
      cell(row, "中心自有", "node-name");
      cell(row, "official", "muted nowrap");
      cell(row, statusCell("正常", "healthy"));
      cell(row, String(officialEdges.length), "nowrap");
      cell(row, "-", "muted");
      const button = document.createElement("button");
      button.type = "button";
      button.className = "table-action";
      button.textContent = "查看节点";
      button.addEventListener("click", () => openEdgeDistributor({ id: "tenant_official", name: "中心自有", official: true }));
      cell(row, button);
      body.append(row);
    }
  }

  async function openEdgeDistributor(tenant) {
    selectedEdgeDistributor = tenant;
    selectedEdgeIDs.clear();
    updateEdgeSelection();
    byId("edge-distributor-view").hidden = true;
    byId("edge-node-view").hidden = false;
    setText("edge-selected-distributor", tenant.name + " · 高防节点");
    edgePage = 1;
    try {
      if (!selectedEdgeDistributor || selectedEdgeDistributor.id !== tenant.id) return;
      currentEdges = allEdges.filter((edge) => tenant.official ? edge.owner_official : edge.owner_tenant_id === tenant.id);
      renderEdges(currentEdges);
    } catch (error) {
      errorBanner.textContent = "读取分销商节点失败：" + error.message;
      errorBanner.hidden = false;
    }
  }

  function closeEdgeDistributor() {
    selectedEdgeDistributor = null;
    byId("edge-distributor-view").hidden = false;
    byId("edge-node-view").hidden = true;
    currentEdges = [];
    byId("edge-table-body").replaceChildren();
    byId("edge-pager").hidden = true;
  }

  function updateEdgeSelection() { byId("edge-upgrade-selection").textContent = "已选 " + selectedEdgeIDs.size + " 个"; }

  function renderReleaseCatalog(payload) {
	releaseCatalog = payload || { center: {}, artifacts: [] };
	const body=byId("release-catalog-body"),empty=byId("release-catalog-empty");body.replaceChildren();
	const componentLabels={center:"中心服务",edge:"高防节点核心","edge-installer":"高防节点安装包",distributor:"分销核心","distributor-installer":"分销端安装包"};
	const center=releaseCatalog.center||{},rows=[];
	if(center.version)rows.push({component:"center",display_version:center.display_version,version:center.version,build_commit:center.build_commit,sha256:"",available:true,optional_update:false,status:"运行中"});
	rows.push(...(releaseCatalog.artifacts||[]));
	for(const item of rows){const row=document.createElement("tr");cell(row,componentLabels[item.component]||item.component,"node-name");cell(row,item.display_version||"-");cell(row,item.version||"-");cell(row,item.build_commit||"-","muted");const hashCell=cell(row,item.sha256?item.sha256.slice(0,16)+"…":"-","muted");if(item.sha256)hashCell.title=item.sha256;const status=item.component==="center"?"运行中":item.status==="revoked"?"已撤销":item.optional_update?"可选更新":item.available?"已发布":"不可用";cell(row,status);body.append(row)}
	empty.hidden=rows.length>0;setText("release-catalog-updated",releaseCatalog.generated_at?"更新于 "+formatHeartbeat(releaseCatalog.generated_at):"");
  }

  function renderEdgeUpgrades() {
	const select = byId("edge-release-select"); const selected = select.value; select.replaceChildren();
	for (const release of edgeReleases) { const option=document.createElement("option");option.value=release.release_id;option.textContent="V"+String(release.version).split(".")[0]+" · "+release.version+" · "+release.sha256.slice(0,12);select.append(option); }
	if (!edgeReleases.length) { const option=document.createElement("option");option.value="";option.textContent="尚未发布";select.append(option); }
	if (edgeReleases.some((release)=>release.release_id===selected)) select.value=selected;
	const progress=byId("edge-upgrade-progress");progress.replaceChildren();progress.hidden=!edgeRollouts.length;
	for (const item of edgeRollouts.slice(0,5)) { const rollout=item.rollout;const jobs=item.jobs||[];const counts={};for(const job of jobs)counts[job.status]=(counts[job.status]||0)+1;const release=edgeReleases.find((entry)=>entry.release_id===rollout.release_id),version=release?.version||rollout.release_id,label=release?("V"+String(version).split(".")[0]+" · "+version):version;const line=document.createElement("div");line.textContent=label+" · "+rollout.status+" · 成功 "+(counts.succeeded||0)+" / "+jobs.length+(counts.failed||counts.rolled_back?" · 失败/回滚 "+((counts.failed||0)+(counts.rolled_back||0)):"");if(["scheduled","running","paused"].includes(rollout.status)){const cancel=document.createElement("button");cancel.type="button";cancel.className="table-action";cancel.textContent="取消";cancel.addEventListener("click",async()=>{await api("/v1/admin/edge-upgrades/"+encodeURIComponent(rollout.rollout_id)+"/cancel",{method:"POST"});await refresh()});line.append(" ",cancel)}progress.append(line);}
  }

  async function publishEdgeRelease() {
	const button=byId("edge-release-upload");const version=byId("edge-release-version").value.trim();const file=byId("edge-release-file").files[0];if(!version||!file){errorBanner.textContent="请选择节点 EXE 并填写版本号";errorBanner.hidden=false;return}button.disabled=true;
	try{const form=new FormData();form.append("version",version);form.append("artifact",file,file.name);const response=await fetch("/v1/admin/edge-releases",{method:"POST",headers:{Authorization:adminAuthorization()},body:form});const payload=await response.json().catch(()=>({}));if(!response.ok)throw new Error(payload.error||("HTTP "+response.status));byId("edge-release-file").value="";await refresh()}catch(error){errorBanner.textContent="发布节点版本失败："+error.message;errorBanner.hidden=false}finally{button.disabled=false}
  }

  async function startEdgeUpgrade() {
	const releaseID=byId("edge-release-select").value;const batchSize=Number(byId("edge-upgrade-batch-size").value);if(!releaseID||!selectedEdgeIDs.size){errorBanner.textContent="请选择目标版本和至少一个支持远程升级的节点";errorBanner.hidden=false;return}if(!confirm("确认升级所选 "+selectedEdgeIDs.size+" 个节点？节点会按批次排空连接、升级并自动回滚失败版本。"))return;const button=byId("edge-upgrade-start");button.disabled=true;try{await api("/v1/admin/edge-upgrades",{method:"POST",body:{release_id:releaseID,node_ids:Array.from(selectedEdgeIDs),batch_size:batchSize}});selectedEdgeIDs.clear();await refresh()}catch(error){errorBanner.textContent="创建升级任务失败："+error.message;errorBanner.hidden=false}finally{button.disabled=false}
  }

  function renderDistributorReleases() {
	const body=byId("distributor-release-body");const empty=byId("distributor-release-empty");body.replaceChildren();empty.hidden=distributorReleases.length>0;
	for(const release of distributorReleases){const row=document.createElement("tr");cell(row,"V"+String(release.version).split(".")[0]+" · "+release.version,"node-name");const hashCell=cell(row,release.sha256.slice(0,16)+"…","muted");hashCell.title=release.sha256;cell(row,formatBytes(release.size));cell(row,release.published_at?formatHeartbeat(release.published_at):"-");cell(row,release.status==="published"?"已发布":"已撤销");const actions=document.createElement("div");actions.className="row-actions";const button=document.createElement("button");button.type="button";button.className="table-action";button.textContent=release.status==="published"?"撤销":"重新发布";button.addEventListener("click",async()=>{if(!confirm((release.status==="published"?"撤销":"重新发布")+"分销版本 "+release.version+"？"))return;button.disabled=true;try{await api("/v1/admin/distributor-releases/"+encodeURIComponent(release.release_id),{method:"PATCH",body:{status:release.status==="published"?"revoked":"published"}});await refresh()}catch(error){errorBanner.textContent="修改分销版本失败："+error.message;errorBanner.hidden=false;button.disabled=false}});actions.append(button);cell(row,actions);body.append(row)}
  }

  async function publishDistributorRelease() {
	const button=byId("distributor-release-upload");const version=byId("distributor-release-version").value.trim();const file=byId("distributor-release-file").files[0];if(!version||!file){errorBanner.textContent="请选择分销核心 EXE 并填写语义版本号";errorBanner.hidden=false;return}button.disabled=true;
	try{const form=new FormData();form.append("version",version);form.append("artifact",file,file.name);const response=await fetch("/v1/admin/distributor-releases",{method:"POST",headers:{Authorization:adminAuthorization()},body:form});const payload=await response.json().catch(()=>({}));if(!response.ok)throw new Error(payload.error||("HTTP "+response.status));byId("distributor-release-file").value="";byId("distributor-release-version").value="";await refresh()}catch(error){errorBanner.textContent="发布分销版本失败："+error.message;errorBanner.hidden=false}finally{button.disabled=false}
  }

  function openEdgeCapacity(edge) {
    editingEdgeCapacity = edge;
    byId("edge-capacity-summary").textContent = edge.node_id + " · " + edge.address;
    byId("edge-bandwidth-limit").value = edge.bandwidth_limit_mbps || 0;
    byId("edge-bandwidth-threshold").value = edge.bandwidth_threshold_percent || 80;
    byId("edge-capacity-error").hidden = true;
    edgeCapacityDialog.showModal();
    byId("edge-bandwidth-limit").focus();
  }

  function closeEdgeCapacity() {
    edgeCapacityDialog.close();
    editingEdgeCapacity = null;
  }

  function renderShieldRows(apps, details) {
    const body = byId("shield-table-body");
    const empty = byId("shield-empty");
    body.replaceChildren();
    let count = 0;
    for (const app of apps) {
      const detail = details.get(app.id) || { groups: [], origins: [] };
      const origins = new Map(detail.origins.map((origin) => [origin.group_name, origin]));
      for (const group of detail.groups) {
        count++;
        const origin = origins.get(group.name);
        const row = document.createElement("tr");
        cell(row, app.name, "node-name");
        cell(row, app.id, "muted");
        cell(row, group.name);
        cell(row, origin ? (origin.protocol || "tcp").toUpperCase() : "-", origin ? "nowrap" : "muted");
        cell(row, origin ? origin.address : "未配置", origin ? "" : "muted");
        cell(row, origin ? ({"proxy-v2":"PROXY v2","toa":"Windows TOA","linux-transparent":"Linux 透明"}[origin.real_ip_mode] || (origin.proxy_protocol ? "PROXY v2" : "关闭")) : "-");
        const enabled = app.enabled && group.enabled && origin && origin.enabled;
        cell(row, enabled ? statusCell("保护中", "healthy") : statusCell("未就绪", "warning"));
        body.appendChild(row);
      }
    }
    empty.hidden = count !== 0;
    setText("group-count", count);
  }

	function renderPlans(plans) {
	  const body = byId("plan-table-body");
	  body.replaceChildren();
	  byId("plan-empty").hidden = plans.length !== 0;
	  for (const plan of plans) {
		const row = document.createElement("tr");
		cell(row, plan.name, "node-name");
		const labels = {1:"月",3:"季",6:"半年",12:"年"};
		const prices = (plan.prices || []).filter((price) => price.enabled).map((price) => labels[price.term_months] + " " + formatMoney(price.price_cents)).join(" / ");
		cell(row, prices || "未上架", prices ? "nowrap" : "muted");
		cell(row, plan.max_upload_mbps + " Mbps");
		cell(row, plan.max_download_mbps + " Mbps");
		cell(row, plan.max_daily_devices);
		cell(row, plan.max_origin_ips);
		cell(row, plan.max_mappings);
		cell(row, plan.dynamic_public_ip_enabled ? statusCell("已开通", "healthy") : "-", plan.dynamic_public_ip_enabled ? "" : "muted");
		const ruleFeatures = [plan.real_ip_enabled ? "真实 IP" : "", plan.mobile_platform_enabled ? "移动端" : "", plan.both_platform_enabled ? "PC+移动" : ""].filter(Boolean).join(" / ");
		cell(row, ruleFeatures || "未授权", ruleFeatures ? "nowrap" : "muted");
		cell(row, plan.registration_default ? statusCell("赠送", "healthy") : "-", plan.registration_default ? "" : "muted");
		cell(row, plan.enabled ? statusCell("启用", "healthy") : statusCell("停用", "offline"));
		const edit = document.createElement("button");
		edit.type = "button"; edit.className = "table-action"; edit.textContent = "编辑";
		edit.addEventListener("click", () => openPlanDialog(plan));
		cell(row, edit); body.append(row);
	  }
	}

	function renderSubscriptions(subscriptions) {
	  const body = byId("subscription-table-body");
	  body.replaceChildren();
	  byId("subscription-empty").hidden = subscriptions.length !== 0;
	  for (const subscription of subscriptions) {
		const row = document.createElement("tr");
		cell(row, subscription.subscription_id, "node-name");
		cell(row, subscription.username, "nowrap");
		cell(row, subscription.plan_name);
		cell(row, subscription.app_id || "待配置", subscription.app_id ? "muted" : "nowrap");
		cell(row, formatHeartbeat(subscription.assigned_at), "nowrap");
		const expiry = subscription.expires_at ? "到期 " + formatHeartbeat(subscription.expires_at) + " / 删除 " + formatHeartbeat(subscription.grace_ends_at) : "永久有效";
		cell(row, expiry, "nowrap");
		cell(row, subscriptionState(subscription));
		const actions = document.createElement("div"); actions.className = "row-actions";
		const expiryButton = document.createElement("button"); expiryButton.type = "button"; expiryButton.className = "table-action"; expiryButton.textContent = subscription.status === "expired" ? "续期" : "设置时间"; expiryButton.addEventListener("click", () => openSubscriptionDialog(subscription));
		const pauseButton = document.createElement("button"); pauseButton.type = "button"; pauseButton.className = "table-action"; pauseButton.textContent = subscription.paused ? "恢复" : "暂停"; pauseButton.disabled = subscription.status === "expired" || subscription.status === "disabled";
		pauseButton.addEventListener("click", async () => { pauseButton.disabled = true; try { await api("/v1/admin/subscriptions/" + encodeURIComponent(subscription.subscription_id), { method: "PATCH", body: { paused: !subscription.paused } }); await refresh(); } catch (error) { errorBanner.textContent = "修改套餐状态失败：" + error.message; errorBanner.hidden = false; pauseButton.disabled = false; } });
		const removeButton = document.createElement("button"); removeButton.type = "button"; removeButton.className = "table-action danger"; removeButton.textContent = "删除";
		removeButton.addEventListener("click", async () => { if (!confirm("确认永久删除套餐 " + subscription.subscription_id + "？对应游戏盾、转发规则、网关绑定和统计数据都会一起删除，无法恢复。")) return; removeButton.disabled = true; try { await api("/v1/admin/subscriptions/" + encodeURIComponent(subscription.subscription_id), { method: "DELETE" }); await refresh(); } catch (error) { errorBanner.textContent = "删除用户套餐失败：" + error.message; errorBanner.hidden = false; removeButton.disabled = false; } });
		actions.append(expiryButton, pauseButton, removeButton); cell(row, actions); body.append(row);
	  }
	}

  function renderUsers(users) {
    const body = byId("user-table-body");
    const empty = byId("user-empty");
    body.replaceChildren();
    empty.hidden = users.length !== 0;
    for (const user of users) {
      const row = document.createElement("tr");
      cell(row, user.username, "node-name");
	  cell(row, user.email || "未绑定", user.email ? "nowrap" : "muted");
	  cell(row, formatMoney(user.balance_cents), "nowrap");
	  cell(row, user.plan_count > 1 ? user.plan_count + " 个套餐 · " + user.plan_name : (user.plan_name || statusCell("无套餐", "warning")));
      cell(row, user.app_count || 0);
      cell(row, user.mapping_count || 0);
      cell(row, user.enabled ? statusCell("启用", "healthy") : statusCell("停用", "offline"));
      cell(row, new Intl.DateTimeFormat("zh-CN", { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hour12: false }).format(new Date(user.created_at * 1000)), "muted nowrap");
      const actions = document.createElement("div");
      actions.className = "row-actions";
      const enterButton = document.createElement("button");
      enterButton.type = "button";
      enterButton.className = "table-action neutral";
      enterButton.textContent = "进入后台";
      enterButton.disabled = !user.enabled;
      enterButton.addEventListener("click", async () => {
        const popup = window.open("about:blank", "_blank");
        enterButton.disabled = true;
        try {
          const payload = await api("/v1/admin/users/" + encodeURIComponent(user.id) + "/impersonate", { method: "POST" });
          const target = "/user/#impersonate=" + encodeURIComponent(payload.impersonation_code);
          if (popup) popup.location.replace(target); else window.location.href = target;
        } catch (error) {
          if (popup) popup.close();
          errorBanner.textContent = "进入用户后台失败：" + error.message;
          errorBanner.hidden = false;
          enterButton.disabled = !user.enabled;
        }
      });
      const stateButton = document.createElement("button");
      stateButton.type = "button";
      stateButton.className = "table-action";
      stateButton.textContent = user.enabled ? "停用" : "启用";
      stateButton.addEventListener("click", async () => {
        stateButton.disabled = true;
        try {
          await api("/v1/admin/users/" + encodeURIComponent(user.id), { method: "PATCH", body: { enabled: !user.enabled } });
          await refresh();
        } catch (error) {
          errorBanner.textContent = "修改用户状态失败：" + error.message;
          errorBanner.hidden = false;
          stateButton.disabled = false;
        }
      });
	  const passwordButton = document.createElement("button");
	  passwordButton.type = "button";
	  passwordButton.className = "table-action";
	  passwordButton.textContent = "修改密码";
	  passwordButton.addEventListener("click", () => openUserPasswordDialog(user));
	  const planButton = document.createElement("button");
	  planButton.type = "button"; planButton.className = "table-action"; planButton.textContent = "新增套餐";
	  planButton.addEventListener("click", () => openAssignPlan(user));
	  const marketplaceAuthorizationButton = document.createElement("button");
	  marketplaceAuthorizationButton.type = "button"; marketplaceAuthorizationButton.className = "table-action neutral"; marketplaceAuthorizationButton.textContent = "飞马套餐";
	  marketplaceAuthorizationButton.addEventListener("click", () => openMarketplaceAuthorizationDialog(user));
	  const walletButton = document.createElement("button");
	  walletButton.type = "button"; walletButton.className = "table-action"; walletButton.textContent = "余额";
	  walletButton.addEventListener("click", () => openWalletDialog(user));
	  const deleteButton = document.createElement("button");
	  deleteButton.type = "button"; deleteButton.className = "table-action danger"; deleteButton.textContent = "删除账户";
	  deleteButton.addEventListener("click", async () => {
		if (!confirm("确认永久删除玩家账户 “" + user.username + "”？余额、订单、实名、套餐、游戏盾、规则、网关和设备记录都会一起删除，无法恢复。")) return;
		deleteButton.disabled = true;
		try {
		  await api("/v1/admin/users/" + encodeURIComponent(user.id), { method: "DELETE" });
		  await refresh();
		} catch (error) {
		  errorBanner.textContent = "删除玩家账户失败：" + error.message;
		  errorBanner.hidden = false;
		  deleteButton.disabled = false;
		}
	  });
	  actions.append(enterButton, passwordButton, planButton, marketplaceAuthorizationButton, walletButton, stateButton, deleteButton);
      cell(row, actions);
      body.appendChild(row);
    }
  }

	function tenantStatusLabel(value) {
	  return { active: "正常", pending: "待验证", suspended: "已暂停", closed: "已关闭", revoked: "已吊销" }[value] || value;
	}

	function tenantStatusCell(value) {
	  return statusCell(tenantStatusLabel(value), value === "active" ? "healthy" : value === "suspended" ? "warning" : "offline");
	}

	function renderTenants(items) {
	  currentTenants = (items || []).filter((tenant) => !tenant.official);
	  const body = byId("tenant-table-body");
	  const empty = byId("tenant-empty");
	  body.replaceChildren();
	  empty.hidden = currentTenants.length !== 0;
	  setText("tenant-total-count", currentTenants.length);
	  setText("tenant-active-count", currentTenants.filter((tenant) => tenant.status === "active").length);
	  setText("tenant-suspended-count", currentTenants.filter((tenant) => tenant.status !== "active").length);
	  setText("tenant-user-count", currentTenants.reduce((total, tenant) => total + Number(tenant.user_count || 0), 0));
	  for (const tenant of currentTenants) {
		const row = document.createElement("tr");
		cell(row, tenant.name, "node-name");
		cell(row, tenant.slug, "nowrap");
		cell(row, tenantStatusCell(tenant.status));
		cell(row, tenant.marketplace_plan_name || "中心手动授权", tenant.marketplace_plan_name ? "node-name nowrap" : "muted nowrap");
		const expiryText = !tenant.current_license_id ? "未生成授权" : tenant.license_expires_at ? formatHeartbeat(tenant.license_expires_at) : "永久";
		const expiryClass = tenant.license_expires_at && tenant.license_expires_at <= Math.floor(Date.now() / 1000) ? "warning nowrap" : "nowrap";
		cell(row, expiryText, expiryClass);
		cell(row, tenant.user_count + " / " + tenant.max_users, tenant.user_count >= tenant.max_users ? "warning" : "nowrap");
		cell(row, tenant.node_count + " / " + tenant.max_nodes, tenant.node_count >= tenant.max_nodes ? "warning" : "nowrap");
		const shieldLimit = Number(tenant.max_shields || 0); cell(row, Number(tenant.shield_count || 0) + " / " + (shieldLimit === 0 ? "不限" : shieldLimit), shieldLimit > 0 && Number(tenant.shield_count || 0) >= shieldLimit ? "warning" : "nowrap");
		const integrations = [tenant.android_sdk_integration_enabled ? "Android" : "", tenant.ios_sdk_integration_enabled ? "iOS" : ""].filter(Boolean).join(" / ");
		cell(row, integrations || "未授权", integrations ? "nowrap" : "muted nowrap");
		cell(row, formatHeartbeat(tenant.updated_at), "muted nowrap");
		const actions = document.createElement("div"); actions.className = "row-actions";
		const manage = document.createElement("button"); manage.type = "button"; manage.className = "table-action"; manage.textContent = "管理";
		manage.addEventListener("click", () => openTenantDetail(tenant)); actions.append(manage);
		if (tenant.status === "active") actions.append(tenantActionButton("暂停", () => quickTenantStatus(tenant, "suspended")));
		else actions.append(tenantActionButton("恢复", () => quickTenantStatus(tenant, "active")));
		actions.append(tenantActionButton("删除", () => deleteTenantFromCenter(tenant), "danger"));
		cell(row, actions); body.append(row);
	  }
	  if (selectedTenant) {
		const refreshed = currentTenants.find((tenant) => tenant.id === selectedTenant.id);
		if (refreshed) { selectedTenant = refreshed; fillTenantSettings(); }
		else closeTenantDetail();
	  }
	}

	async function quickTenantStatus(tenant, status) {
	  let reason = "";
	  if (status === "suspended") {
		reason = prompt("请输入暂停原因", tenant.suspension_reason || "中心手动暂停");
		if (reason === null || !reason.trim()) return;
	  }
	  await api("/v1/admin/tenants/" + encodeURIComponent(tenant.id), { method: "PATCH", body: { status, reason: reason.trim() } });
	  await refresh();
	}

	async function deleteTenantFromCenter(tenant) {
	  if (!confirm("确认删除分销商“" + tenant.name + "”？授权、登录和业务访问会立即失效。")) return;
	  const confirmation = prompt("二次确认：请输入完整分销商名称", "");
	  if (confirmation !== tenant.name) { if (confirmation !== null) alert("分销商名称不一致，已取消删除"); return; }
	  const reason = prompt("请输入删除原因", "中心手动删除");
	  if (reason === null || !reason.trim()) return;
	  await api("/v1/admin/tenants/" + encodeURIComponent(tenant.id), { method: "DELETE", body: { confirm_name: confirmation, reason: reason.trim() } });
	  if (selectedTenant && selectedTenant.id === tenant.id) closeTenantDetail();
	  await refresh();
	}

	function fillTenantSettings() {
	  if (!selectedTenant) return;
	  setText("tenant-detail-title", selectedTenant.name);
	  setText("tenant-detail-meta", selectedTenant.slug + " · 创建于 " + formatHeartbeat(selectedTenant.created_at));
	  byId("tenant-max-users").value = selectedTenant.max_users;
	  byId("tenant-max-nodes").value = selectedTenant.max_nodes;
	  byId("tenant-max-shields").value = Number(selectedTenant.max_shields || 0);
	  byId("tenant-sdk-enabled").checked = Boolean(selectedTenant.sdk_enabled);
	  byId("tenant-android-sdk-integration-enabled").checked = Boolean(selectedTenant.android_sdk_integration_enabled);
	  byId("tenant-ios-sdk-integration-enabled").checked = Boolean(selectedTenant.ios_sdk_integration_enabled);
	  byId("tenant-referral-enabled").checked = Boolean(selectedTenant.referral_enabled);
	  byId("tenant-scratch-lottery-enabled").checked = Boolean(selectedTenant.scratch_lottery_enabled);
	  byId("tenant-dynamic-public-ip-enabled").checked = Boolean(selectedTenant.dynamic_public_ip_enabled);
	  byId("tenant-home-tunnel-enabled").checked = Boolean(selectedTenant.home_tunnel_enabled);
	  byId("tenant-real-ip-enabled").checked = Boolean(selectedTenant.real_ip_enabled);
	  byId("tenant-mobile-platform-enabled").checked = Boolean(selectedTenant.mobile_platform_enabled);
	  byId("tenant-both-platform-enabled").checked = Boolean(selectedTenant.both_platform_enabled);
	  byId("tenant-marketplace-owner").value = "";
	  byId("tenant-status").value = selectedTenant.status;
	  byId("tenant-status-reason").value = selectedTenant.suspension_reason || "";
	  byId("tenant-settings-error").hidden = true;
	}

	async function openTenantDetail(tenant) {
	  selectedTenant = tenant;
	  fillTenantSettings();
	  byId("tenant-detail").hidden = false;
	  await loadTenantResources();
	  byId("tenant-detail").scrollIntoView({ behavior: "smooth", block: "start" });
	}

	function closeTenantDetail() {
	  selectedTenant = null;
	  byId("tenant-detail").hidden = true;
	  byId("tenant-license-body").replaceChildren();
	  byId("tenant-domain-body").replaceChildren();
	  byId("tenant-node-body").replaceChildren();
	}

	async function loadTenantResources() {
	  if (!selectedTenant) return;
	  const tenantID = selectedTenant.id;
	  try {
		const base = "/v1/admin/tenants/" + encodeURIComponent(tenantID);
		const [licensePayload, domainPayload, nodePayload, ownerPayload] = await Promise.all([api(base + "/licenses"), api(base + "/domains"), api(base + "/nodes"), api(base + "/marketplace-owner")]);
		if (!selectedTenant || selectedTenant.id !== tenantID) return;
		byId("tenant-marketplace-owner").value = ownerPayload.user_id || "";
		renderTenantLicenses(licensePayload.licenses || []);
		renderTenantDomains(domainPayload.domains || []);
		renderTenantNodes(nodePayload.nodes || []);
	  } catch (error) {
		errorBanner.textContent = "读取分销商详情失败：" + error.message;
		errorBanner.hidden = false;
	  }
	}

	function renderTenantLicenses(licenses) {
	  currentTenantLicenses = licenses;
	  const body = byId("tenant-license-body"); body.replaceChildren(); byId("tenant-license-empty").hidden = licenses.length !== 0;
	  byId("add-tenant-license").disabled = licenses.length > 0;
	  byId("add-tenant-license").title = licenses.length > 0 ? "每个分销商只能保留一个部署授权，请先删除现有授权" : "";
	  for (const license of licenses) {
		const row = document.createElement("tr");
		cell(row, license.expected_ip, "nowrap"); cell(row, license.expected_domain, "nowrap");
		cell(row, license.expires_at ? formatHeartbeat(license.expires_at) : "永久", "nowrap"); cell(row, tenantStatusCell(license.status));
		cell(row, license.suspension_reason || "-", license.suspension_reason ? "warning" : "muted"); cell(row, formatHeartbeat(license.created_at), "nowrap");
		const actions = document.createElement("div"); actions.className = "row-actions";
		if (license.status === "active") actions.append(tenantActionButton("暂停", () => changeTenantLicenseStatus(license, "suspended")));
		if (license.status === "suspended") actions.append(tenantActionButton("恢复", () => changeTenantLicenseStatus(license, "active")));
		actions.append(tenantActionButton("复制授权码", () => copyTenantLicenseActivationCode(license)));
		actions.append(tenantActionButton("修改到期", () => editTenantLicenseExpiry(license)));
		actions.append(tenantActionButton("重置机器绑定", () => resetTenantLicenseBinding(license), "danger"));
		actions.append(tenantActionButton("删除", () => deleteTenantLicense(license), "danger"));
		cell(row, actions); body.append(row);
	  }
	}

	async function copyTenantLicenseActivationCode(license) {
	  const result = await api("/v1/admin/tenant-licenses/" + encodeURIComponent(license.id) + "/activation-code");
	  try { await navigator.clipboard.writeText(result.activation_code); }
	  catch (_) {
		const field = document.createElement("textarea"); field.value = result.activation_code; field.style.position = "fixed"; field.style.opacity = "0"; document.body.append(field); field.select(); document.execCommand("copy"); field.remove();
	  }
	  errorBanner.textContent = "授权码已复制"; errorBanner.classList.add("success-message"); errorBanner.hidden = false;
	}

	async function editTenantLicenseExpiry(license) {
	  const current = license.expires_at ? new Date(license.expires_at * 1000).toISOString().slice(0, 16).replace("T", " ") : "永久";
	  const value = prompt("请输入新的到期时间（YYYY-MM-DD HH:mm），输入 0 表示永久", current);
	  if (value === null) return;
	  const expiresAt = value.trim() === "0" ? 0 : Math.floor(new Date(value.trim().replace(" ", "T")).getTime() / 1000);
	  if (!Number.isFinite(expiresAt) || (expiresAt > 0 && expiresAt <= Math.floor(Date.now() / 1000))) throw new Error("invalid_license_expiry");
	  await api("/v1/admin/tenant-licenses/" + encodeURIComponent(license.id), { method: "PATCH", body: { expires_at: expiresAt } });
	  await loadTenantResources();
	}

	async function resetTenantLicenseBinding(license) {
		if (!confirm("确认重置这条授权的机器绑定？原激活码、用户、节点、游戏盾和日志会保留；当前分销端需要使用同一个激活码重新激活。")) return;
		await api("/v1/admin/tenant-licenses/" + encodeURIComponent(license.id) + "/reset-binding", { method: "POST" });
		await loadTenantResources();
	}

	async function deleteTenantLicense(license) {
	  if (!confirm("确认删除部署授权？当前分销服务会立即失效，删除后才能重新生成激活码。")) return;
	  await api("/v1/admin/tenant-licenses/" + encodeURIComponent(license.id), { method: "DELETE" });
	  await loadTenantResources();
	}

	function tenantActionButton(label, action, tone = "") {
	  const button = document.createElement("button"); button.type = "button"; button.className = "table-action" + (tone ? " " + tone : ""); button.textContent = label;
	  button.addEventListener("click", async () => { button.disabled = true; try { await action(); } catch (error) { const messages = { invalid_license_expiry: "授权到期时间无效", tenant_delete_confirmation_required: "删除确认信息不正确", activation_code_unavailable: "旧授权未保存激活码，请撤销后重新生成", activation_code_decrypt_failed: "授权码读取失败，请检查中心加密密钥" }; errorBanner.classList.remove("success-message"); errorBanner.textContent = messages[error.message] || ("操作失败：" + error.message); errorBanner.hidden = false; } finally { if (button.isConnected) button.disabled = false; } });
	  return button;
	}

	async function changeTenantLicenseStatus(license, nextStatus) {
	  let reason = "";
	  if (nextStatus !== "active") {
		reason = prompt("请输入暂停原因", license.suspension_reason || "中心手动操作");
		if (reason === null) return;
		reason = reason.trim();
		if (!reason) { errorBanner.textContent = "暂停授权必须填写原因"; errorBanner.hidden = false; return; }
	  }
	  try { await api("/v1/admin/tenant-licenses/" + encodeURIComponent(license.id), { method: "PATCH", body: { status: nextStatus, reason } }); await loadTenantResources(); }
	  catch (error) { errorBanner.textContent = "修改部署授权失败：" + error.message; errorBanner.hidden = false; }
	}

	function renderTenantDomains(domains) {
	  const body = byId("tenant-domain-body"); body.replaceChildren(); byId("tenant-domain-empty").hidden = domains.length !== 0;
	  const purposes = { user: "用户端与管理端", admin: "历史管理域名" };
	  for (const domain of domains) {
		const row = document.createElement("tr"); cell(row, domain.hostname, "node-name nowrap"); cell(row, purposes[domain.purpose] || domain.purpose);
		cell(row, tenantStatusCell(domain.status)); cell(row, domain.primary ? statusCell("主域名", "healthy") : "-"); cell(row, domain.verified_at ? formatHeartbeat(domain.verified_at) : "未验证", "nowrap");
		const actions = document.createElement("div"); actions.className = "row-actions";
		if (domain.status !== "active") actions.append(tenantActionButton("激活", () => changeTenantDomainStatus(domain, "active", false)));
		if (domain.status === "active" && !domain.primary) actions.append(tenantActionButton("设为主域名", () => changeTenantDomainStatus(domain, "active", true)));
		if (domain.status === "active") actions.append(tenantActionButton("停用", () => changeTenantDomainStatus(domain, "suspended", false), "danger"));
		cell(row, actions); body.append(row);
	  }
	}

	async function changeTenantDomainStatus(domain, status, primary) {
	  if (!selectedTenant) return;
	  if (status === "suspended" && !confirm("确认停用域名 " + domain.hostname + "？该域名上的用户入口将立即不可用。")) return;
	  try {
		await api("/v1/admin/tenants/" + encodeURIComponent(selectedTenant.id) + "/domains/" + encodeURIComponent(domain.id), { method: "PATCH", body: { status, primary } });
		await loadTenantResources();
	  } catch (error) { errorBanner.textContent = "修改控制台访问域名失败：" + error.message; errorBanner.hidden = false; }
	}

	function renderTenantNodes(assignments) {
	  const body = byId("tenant-node-body"); body.replaceChildren(); byId("tenant-node-empty").hidden = allEdges.length !== 0;
	  const assigned = new Map(assignments.map((node) => [node.node_id, node]));
	  let enabledCount = 0;
	  for (const edge of allEdges) {
		const assignment = assigned.get(edge.node_id); const allowed = Boolean(assignment && assignment.assigned); if (allowed) enabledCount++;
		const row = document.createElement("tr"); cell(row, edge.server_name || edge.node_id, "node-name"); cell(row, edge.address, "nowrap"); cell(row, edge.region || "-");
		cell(row, edge.healthy ? statusCell("健康", "healthy") : statusCell("离线", "offline")); cell(row, edge.owner_official ? "中心自有" : (edge.owner_tenant_name || "归属未知"));
		const toggle = document.createElement("input"); toggle.type = "checkbox"; toggle.className = "tenant-node-toggle"; toggle.checked = allowed; toggle.disabled = !selectedTenant || selectedTenant.status !== "active";
		toggle.setAttribute("aria-label", "允许使用 " + (edge.server_name || edge.node_id));
		toggle.addEventListener("change", async () => { toggle.disabled = true; try { await api("/v1/admin/tenants/" + encodeURIComponent(selectedTenant.id) + "/nodes/" + encodeURIComponent(edge.node_id), { method: "PUT", body: { enabled: toggle.checked } }); await loadTenantResources(); } catch (error) { toggle.checked = !toggle.checked; errorBanner.textContent = "修改节点分配失败：" + error.message; errorBanner.hidden = false; toggle.disabled = false; } });
		cell(row, toggle); body.append(row);
	  }
	  setText("tenant-node-summary", "已允许 " + enabledCount + " / " + allEdges.length + " 个节点");
	}

	function showOneTimeSecret(title, context, value) {
	  setText("one-time-secret-title", title); setText("one-time-secret-context", context); byId("one-time-secret-value").value = value; byId("copy-one-time-secret").textContent = "复制"; oneTimeSecretDialog.showModal();
	}

	function closeOneTimeSecret() { byId("one-time-secret-value").value = ""; oneTimeSecretDialog.close(); }

	async function copyOneTimeSecret() {
	  const value = byId("one-time-secret-value").value; if (!value) return;
	  try { await navigator.clipboard.writeText(value); }
	  catch (_) { const field = byId("one-time-secret-value"); field.select(); document.execCommand("copy"); }
	  byId("copy-one-time-secret").textContent = "已复制";
	}

	function renderSMTPSettings(settings) {
	  byId("smtp-host").value = settings.host || "";
	  byId("smtp-port").value = settings.port || 587;
	  byId("smtp-username").value = settings.username || "";
	  byId("smtp-password").value = "";
	  byId("smtp-from-email").value = settings.from_email || "";
	  byId("smtp-from-name").value = settings.from_name || "Game Shield";
	  byId("smtp-tls-mode").value = settings.tls_mode || "starttls";
	  byId("smtp-enabled").checked = Boolean(settings.enabled);
	  byId("smtp-password-state").textContent = settings.password_configured ? "登录密码已安全保存；留空会保留现有密码。" : "尚未保存登录密码。";
	}

	function renderGatewayDownloadSettings(settings) {
	  byId("windows-gateway-download-url").value = settings.windows_download_url || "";
	  byId("windows-gateway-tutorial").value = settings.windows_tutorial || "";
	  byId("windows-gateway-enabled").checked = Boolean(settings.windows_enabled);
	  byId("linux-gateway-download-url").value = settings.linux_download_url || "";
	  byId("linux-gateway-install-script-url").value = settings.linux_install_script_url || "";
	  byId("linux-gateway-tutorial").value = settings.linux_tutorial || "";
	  byId("linux-gateway-enabled").checked = Boolean(settings.linux_enabled);
	}
	function renderPortalSettings(settings) { byId("user-portal-name").value = settings.user_portal_name || "Game Shield"; byId("marketplace-upload-notice").value = settings.marketplace_upload_notice || ""; }
	function renderBaiduSubmissionSettings(settings) {
	  byId("baidu-submission-site").value = settings.site || "https://www.fmdun.com";
	  byId("baidu-submission-token").value = "";
	  byId("baidu-submission-enabled").checked = Boolean(settings.enabled);
	  const state = byId("baidu-submission-state");
	  state.textContent = settings.token_configured ? (settings.enabled ? "已启用 · Token 已配置" : "已配置 · 未启用") : "尚未配置 Token";
	  if (settings.last_at) state.textContent += " · 上次提交 " + formatHeartbeat(settings.last_at) + " · 成功 " + Number(settings.last_count || 0) + " · 剩余额度 " + Number(settings.last_remaining || 0);
	  if (settings.last_error) state.title = settings.last_error; else state.removeAttribute("title");
	}
	function renderArticles(items) {
	  adminArticles = items || [];
	  const body = byId("article-body"); body.replaceChildren(); byId("article-empty").hidden = adminArticles.length !== 0;
	  for (const article of adminArticles) {
	    const row = document.createElement("tr");
	    cell(row, article.title || "-", "node-name"); cell(row, article.category || "-"); cell(row, article.featured ? "推荐" : "-");
	    cell(row, article.status === "published" ? "已发布" : "草稿");
	    cell(row, article.baidu_submit_error ? "失败" : (article.baidu_submit_at ? "已提交" : "待提交"));
	    cell(row, formatHeartbeat(article.updated_at), "nowrap");
	    const actions = document.createElement("div"); actions.className = "row-actions";
	    const edit = document.createElement("button"); edit.type = "button"; edit.className = "table-action"; edit.textContent = "编辑"; edit.addEventListener("click", () => {
	      const category = article.category || "安全防护", categorySelect = byId("article-category"); if (![...categorySelect.options].some((option) => option.value === category)) categorySelect.add(new Option(category, category)); byId("article-id").value = article.id || ""; byId("article-slug").value = article.slug || ""; byId("article-title").value = article.title || ""; categorySelect.value = category; byId("article-author").value = article.author || ""; byId("article-summary").value = article.summary || ""; byId("article-seo-title").value = article.seo_title || ""; byId("article-seo-description").value = article.seo_description || ""; byId("article-featured").checked = Boolean(article.featured); byId("article-keywords").value = (article.keywords || []).join("\\n"); byId("article-content").value = article.content || ""; byId("article-status").value = article.status || "draft"; byId("article-title").focus();
	    });
	    const remove = document.createElement("button"); remove.type = "button"; remove.className = "table-action danger"; remove.textContent = "删除"; remove.addEventListener("click", async () => { if (!window.confirm("确定删除这篇文章吗？")) return; try { await api("/v1/admin/articles/" + encodeURIComponent(article.id), { method: "DELETE" }); await refresh(); } catch (error) { window.alert("删除文章失败：" + error.message); } });
	    actions.append(edit, remove); cell(row, actions); body.append(row);
	  }
	}
	function renderPaymentSettings(settings) {
	  byId("alipay-mode").value = settings.alipay_mode || "page";
	  byId("alipay-environment").value = settings.alipay_environment || "production";
	  byId("alipay-app-id").value = settings.alipay_app_id || "";
	  byId("alipay-notify-url").value = settings.alipay_notify_url || "";
	  byId("alipay-private-key").value = ""; byId("alipay-public-key").value = "";
	  byId("alipay-enabled").checked = Boolean(settings.alipay_enabled);
	  byId("alipay-key-state").textContent = settings.alipay_private_key_configured && settings.alipay_public_key_configured ? "应用私钥和支付宝公钥已安全保存" : "支付密钥尚未配置完整";
	  byId("wechat-mch-id").value = settings.wechat_mch_id || "";
	  byId("wechat-app-id").value = settings.wechat_app_id || "";
	  byId("wechat-key").value = ""; byId("wechat-app-secret").value = "";
	  byId("wechat-enabled").checked = Boolean(settings.wechat_enabled);
	  byId("wechat-key-state").textContent = settings.wechat_key_configured && settings.wechat_app_secret_configured ? "Key 和 AppSecret 已安全保存" : "支付密钥尚未配置完整";
	  const enabled = []; if (settings.alipay_enabled) enabled.push("支付宝"); if (settings.wechat_enabled) enabled.push("微信支付");
	  byId("payment-settings-state").textContent = enabled.length ? ("已启用：" + enabled.join("、")) : "尚未启用在线支付";
	}
	function renderIdentitySettings(payload) {
	  const settings = payload.settings || {};
	  byId("identity-baidu-endpoint").value = settings.baidu_api_endpoint || "https://aip.baidubce.com/rest/2.0/face/v4/mingjing/verify";
	  byId("identity-scan-public-url").value = settings.scan_public_url || "";
	  byId("identity-baidu-api-key").value = ""; byId("identity-baidu-secret-key").value = "";
	  byId("identity-free-attempts").value = Number(settings.free_attempts ?? 1);
	  byId("identity-attempt-price").value = (Number(settings.attempt_price_cents || 0) / 100).toFixed(2);
	  byId("identity-match-threshold").value = Number(settings.match_threshold ?? 80);
	  byId("identity-enabled").checked = Boolean(settings.enabled);
	  byId("identity-settings-state").textContent = (settings.baidu_api_key_configured && settings.baidu_secret_key_configured) ? "API 凭据已保存" : "API 凭据未配置";
	}
	function renderIdentityVerifications(items) {
	  const body = byId("identity-verification-body"); body.replaceChildren(); byId("identity-verification-empty").hidden = items.length !== 0; byId("identity-record-count").textContent = items.length + " 条";
	  const labels = { processing:"核验中", approved:"已通过", rejected:"已驳回", technical_error:"技术失败", reset:"已重置" }; const tones = { processing:"warning", approved:"healthy", rejected:"offline", technical_error:"warning", reset:"warning" };
	  for (const item of items) {
		const row = document.createElement("tr"); cell(row, item.username, "node-name"); cell(row, item.name_masked + " · " + item.id_number_masked, "nowrap"); cell(row, statusCell(labels[item.status] || item.status, tones[item.status] || "warning")); cell(row, item.provider_status ? (labels[item.provider_status] || item.provider_status) : "-"); cell(row, item.score ? Number(item.score).toFixed(1) : "-"); cell(row, "第 " + item.attempt_number + " 次 · " + formatMoney(item.charged_cents - item.refunded_cents), "nowrap"); cell(row, formatHeartbeat(item.created_at), "nowrap");
		const actions = document.createElement("div"); actions.className = "row-actions"; const review = document.createElement("button"); review.type = "button"; review.className = "table-action"; review.textContent = "审核"; review.addEventListener("click", () => openIdentityReview(item)); actions.append(review); cell(row, actions); body.append(row);
	  }
	}
	function openIdentityReview(item) {
	  reviewingIdentity = item; identityReviewForm.reset(); byId("identity-review-error").hidden = true; byId("identity-review-summary").textContent = item.username + " · " + item.name_masked + " · " + item.id_number_masked + " · 百度分数 " + Number(item.score || 0).toFixed(1); byId("identity-review-status").value = item.status === "approved" ? "approved" : "rejected"; byId("identity-review-refund").disabled = Number(item.charged_cents || 0) <= Number(item.refunded_cents || 0); identityReviewDialog.showModal(); byId("identity-review-reason").focus();
	}
	function closeIdentityReview() { identityReviewDialog.close(); reviewingIdentity = null; }

	function renderRealIPDownloadSettings(settings) {
	  byId("windows-realip-download-url").value = settings.windows_download_url || "";
	  byId("windows-realip-description").value = settings.windows_description || "";
	  byId("windows-realip-enabled").checked = Boolean(settings.windows_enabled);
	  byId("linux-realip-download-url").value = settings.linux_download_url || "";
	  byId("linux-realip-install-script-url").value = settings.linux_install_script_url || "";
	  byId("linux-realip-description").value = settings.linux_description || "";
	  byId("linux-realip-enabled").checked = Boolean(settings.linux_enabled);
	}

	async function openWalletDialog(user) {
	  adjustingWalletUser = user;
	  walletForm.reset();
	  byId("wallet-user-summary").textContent = user.username + " · 当前余额 " + formatMoney(user.balance_cents);
	  byId("wallet-form-error").hidden = true;
	  walletDialog.showModal();
	  const payload = await api("/v1/admin/users/" + encodeURIComponent(user.id) + "/wallet-ledger");
	  const body = byId("admin-wallet-ledger-body"); body.replaceChildren();
	  for (const entry of payload.entries || []) {
		const row = document.createElement("tr"); cell(row, formatHeartbeat(entry.created_at), "nowrap"); cell(row, entry.entry_type); cell(row, formatSignedMoney(entry.amount_cents)); cell(row, formatMoney(entry.balance_after_cents)); cell(row, entry.note || "-"); body.append(row);
	  }
	  byId("wallet-adjustment-amount").focus();
	}

	function closeWalletDialog() { walletDialog.close(); adjustingWalletUser = null; }

  async function refresh() {
    refreshButton.disabled = true;
    refreshButton.classList.add("loading");
    errorBanner.hidden = true;
    try {
	  const [status, releaseCatalogPayload, edgePayload, releasePayload, rolloutPayload, edgeInstallerPayload, distributorReleasePayload, distributorInstallerPayload, tenantPayload, appPayload, smtpPayload, gatewayDownloadPayload, realIPDownloadPayload, portalSettingsPayload, baiduSubmissionPayload, articlePayload, paymentSettingsPayload, identitySettingsPayload, identityVerificationsPayload, marketplacePlanPayload, marketplaceListingPayload, marketplaceComplaintPayload, marketplaceWithdrawalPayload] = await Promise.all([
		api("/v1/admin/status"), api("/v1/admin/releases"), api("/v1/admin/edges"), api("/v1/admin/edge-releases"), api("/v1/admin/edge-upgrades"), api("/v1/admin/edge-package/info"), api("/v1/admin/distributor-releases"), api("/v1/admin/distributor-installer/info"), api("/v1/admin/tenants"), api("/v1/admin/apps"), api("/v1/admin/smtp-settings"), api("/v1/admin/gateway-download-settings"), api("/v1/admin/realip-download-settings"), api("/v1/admin/portal-settings"), api("/v1/admin/baidu-submission-settings"), api("/v1/admin/articles"), api("/v1/admin/payment-settings"), api("/v1/admin/identity-settings"), api("/v1/admin/identity-verifications"), api("/v1/admin/marketplace/plans"), api("/v1/admin/marketplace/listings"), api("/v1/admin/marketplace/complaints"), api("/v1/admin/marketplace/withdrawals")
      ]);
      const apps = appPayload.apps || [];
	  const usersPayload = await api("/v1/admin/users");
	  renderUsers(usersPayload.users || []);
	  renderSMTPSettings(smtpPayload.settings || {});
	  renderGatewayDownloadSettings(gatewayDownloadPayload.settings || {});
	  renderRealIPDownloadSettings(realIPDownloadPayload.settings || {});
	  renderPortalSettings(portalSettingsPayload.settings || {});
	  renderBaiduSubmissionSettings(baiduSubmissionPayload.settings || {});
	  renderArticles(articlePayload.articles || []);
	  renderPaymentSettings(paymentSettingsPayload.settings || {});
	  renderIdentitySettings(identitySettingsPayload || {});
	  renderIdentityVerifications(identityVerificationsPayload.verifications || []);
	  renderMarketplacePlans(marketplacePlanPayload.plans || []);
	  renderMarketplaceListings(marketplaceListingPayload.listings || []);
	  renderMarketplaceComplaints(marketplaceComplaintPayload.complaints || []);
	  renderMarketplaceWithdrawals(marketplaceWithdrawalPayload.withdrawals || []);
	  renderReleaseCatalog(releaseCatalogPayload);
      const detailPairs = await Promise.all(apps.map(async (app) => {
        const appID = encodeURIComponent(app.id);
        const [groups, origins] = await Promise.all([
          api("/v1/admin/groups?app_id=" + appID),
          api("/v1/admin/origins?app_id=" + appID)
        ]);
        return [app.id, { groups: groups.groups || [], origins: origins.origins || [] }];
      }));
      const serviceStatus = byId("service-status");
      serviceStatus.replaceChildren(statusCell(status.status === "ready" ? "运行正常" : status.status, status.status === "ready" ? "healthy" : "warning"));
      setText("routable-count", status.routable_edges);
      setText("edge-count", status.edge_nodes);
      setText("app-count", apps.length);
      const centerVersion=releaseCatalogPayload.center||{};const centerLabel=centerVersion.display_version?(centerVersion.display_version+" · "+centerVersion.version):(status.software_version||"unknown");
      setText("software-version", centerLabel);
      setText("overview-version", centerLabel);
      setText("overview-uptime", formatDuration(status.uptime_seconds));
      setText("overview-heartbeat-timeout", (status.routing?.heartbeat_timeout_seconds || 0) + " 秒");
      setText("overview-manifest-ttl", (status.routing?.manifest_ttl_seconds || 0) + " 秒");
      setText("overview-max-routes", status.routing?.max_routes || 0);
	  allEdges = edgePayload.edges || [];
	  currentEdges = allEdges;
	  renderTenants(tenantPayload.tenants || []);
	  renderEdgeDistributors(tenantPayload.tenants || [], allEdges);
	  if (selectedEdgeDistributor) {
		const refreshedDistributor = currentTenants.find((tenant) => tenant.id === selectedEdgeDistributor.id);
		if (refreshedDistributor) await openEdgeDistributor(refreshedDistributor);
		else closeEdgeDistributor();
	  } else {
		closeEdgeDistributor();
	  }
	  edgeReleases=releasePayload.releases||[];edgeRollouts=rolloutPayload.rollouts||[];renderEdgeUpgrades();updateEdgeSelection();
	  renderEdgeInstaller(edgeInstallerPayload);
	  distributorReleases=distributorReleasePayload.releases||[];renderDistributorReleases();
	  renderDistributorInstaller(distributorInstallerPayload);
      renderShieldRows(apps, new Map(detailPairs));
	  if (selectedTenant) await loadTenantResources();
      setText("updated-at", "更新于 " + new Intl.DateTimeFormat("zh-CN", { hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false }).format(new Date()));
    } catch (error) {
      if (error.message === "unauthorized") {
		clearAdminSession("登录已失效，请重新登录");
      } else {
        errorBanner.textContent = "读取状态失败：" + error.message;
        errorBanner.hidden = false;
      }
    } finally {
      refreshButton.disabled = false;
      refreshButton.classList.remove("loading");
    }
  }

  loginForm.addEventListener("submit", async (event) => {
    event.preventDefault();
	const username = usernameInput.value.trim();
	const password = passwordInput.value;
	if (!username || !password) return;
    loginError.hidden = true;
	try {
	  const response = await fetch("/v1/admin/session", { method: "POST", headers: { Authorization: basicAuthorization(username, password), Accept: "application/json" }, cache: "no-store" });
	  const payload = await response.json().catch(() => ({}));
	  if (!response.ok || !payload.access_token) throw new Error(payload.error || "unauthorized");
	  adminToken = payload.access_token;
	  sessionStorage.setItem(adminSessionKey, adminToken);
	  passwordInput.value = "";
	  loginView.hidden = true;
	  dashboardView.hidden = false;
	  await refresh();
	} catch (_) {
	  clearAdminSession("账号或密码错误");
	}
  });

  refreshButton.addEventListener("click", refresh);
  byId("edge-package-download").addEventListener("click", downloadEdgePackage);
  byId("edge-distributor-back").addEventListener("click", closeEdgeDistributor);
  byId("distributor-installer-download").addEventListener("click", downloadDistributorInstaller);
  byId("edge-release-upload").addEventListener("click", publishEdgeRelease);
  byId("edge-upgrade-start").addEventListener("click", startEdgeUpgrade);
  byId("distributor-release-upload").addEventListener("click", publishDistributorRelease);
  byId("add-tenant-button").addEventListener("click", () => { tenantForm.reset(); byId("tenant-form-error").hidden = true; tenantDialog.showModal(); byId("tenant-name").focus(); });
  byId("close-tenant-dialog").addEventListener("click", () => tenantDialog.close());
  byId("cancel-tenant-button").addEventListener("click", () => tenantDialog.close());
  byId("close-tenant-detail").addEventListener("click", closeTenantDetail);
  byId("add-tenant-license").addEventListener("click", () => { if (!selectedTenant) return; tenantLicenseForm.reset(); setText("tenant-license-summary", selectedTenant.name + " · 激活和心跳将同时核对服务器公网 IP 与授权运行域名"); byId("tenant-license-form-error").hidden = true; tenantLicenseDialog.showModal(); byId("tenant-license-ip").focus(); });
  byId("close-tenant-license-dialog").addEventListener("click", () => tenantLicenseDialog.close());
  byId("cancel-tenant-license-button").addEventListener("click", () => tenantLicenseDialog.close());
  byId("add-tenant-domain").addEventListener("click", () => { if (!selectedTenant) return; tenantDomainForm.reset(); setText("tenant-domain-summary", selectedTenant.name); byId("tenant-domain-form-error").hidden = true; tenantDomainDialog.showModal(); byId("tenant-domain-hostname").focus(); });
  byId("close-tenant-domain-dialog").addEventListener("click", () => tenantDomainDialog.close());
  byId("cancel-tenant-domain-button").addEventListener("click", () => tenantDomainDialog.close());
  byId("close-one-time-secret").addEventListener("click", closeOneTimeSecret);
  byId("confirm-one-time-secret").addEventListener("click", closeOneTimeSecret);
  byId("copy-one-time-secret").addEventListener("click", copyOneTimeSecret);
  for (const prefix of ["tenant-create-", "tenant-"]) {
	const sdk = byId(prefix + "sdk-enabled");
	const android = byId(prefix + "android-sdk-integration-enabled");
	const ios = byId(prefix + "ios-sdk-integration-enabled");
	for (const integration of [android, ios]) integration.addEventListener("change", () => { if (integration.checked) sdk.checked = true; });
	sdk.addEventListener("change", () => { if (!sdk.checked) { android.checked = false; ios.checked = false; } });
  }
  tenantForm.addEventListener("submit", async (event) => {
	event.preventDefault(); const submit = byId("submit-tenant-button"); submit.disabled = true; byId("tenant-form-error").hidden = true;
	try {
	  const tenant = await api("/v1/admin/tenants", { method: "POST", body: { name: byId("tenant-name").value.trim(), max_users: Number(byId("tenant-create-max-users").value), max_nodes: Number(byId("tenant-create-max-nodes").value), max_shields: Number(byId("tenant-create-max-shields").value), sdk_enabled: byId("tenant-create-sdk-enabled").checked, android_sdk_integration_enabled: byId("tenant-create-android-sdk-integration-enabled").checked, ios_sdk_integration_enabled: byId("tenant-create-ios-sdk-integration-enabled").checked, referral_enabled: byId("tenant-create-referral-enabled").checked, scratch_lottery_enabled: byId("tenant-create-scratch-lottery-enabled").checked, dynamic_public_ip_enabled: byId("tenant-create-dynamic-public-ip-enabled").checked, home_tunnel_enabled: byId("tenant-create-home-tunnel-enabled").checked, real_ip_enabled: byId("tenant-create-real-ip-enabled").checked, mobile_platform_enabled: byId("tenant-create-mobile-platform-enabled").checked, both_platform_enabled: byId("tenant-create-both-platform-enabled").checked } });
	  tenantDialog.close(); selectedTenant = tenant; byId("tenant-detail").hidden = false; await refresh();
	} catch (error) {
	  const messages = { tenant_conflict: "分销商标识已存在或填写不符合要求", invalid_tenant_quotas: "请检查客户和节点额度" };
	  byId("tenant-form-error").textContent = messages[error.message] || ("创建分销商失败：" + error.message); byId("tenant-form-error").hidden = false;
	} finally { submit.disabled = false; }
  });
  byId("tenant-settings-form").addEventListener("submit", async (event) => {
	event.preventDefault(); if (!selectedTenant) return; const submit = byId("save-tenant-settings"); submit.disabled = true; const errorBox = byId("tenant-settings-error"); errorBox.hidden = true;
	const status = byId("tenant-status").value; const reason = byId("tenant-status-reason").value.trim();
	try {
	  if (status !== "active" && !reason) throw new Error("tenant_reason_required");
	  if (status !== selectedTenant.status && status !== "active" && !confirm("确认" + (status === "closed" ? "关闭" : "暂停") + "分销商 “" + selectedTenant.name + "”？其控制台、用户业务和运行授权将立即停止。")) return;
	  selectedTenant = await api("/v1/admin/tenants/" + encodeURIComponent(selectedTenant.id), { method: "PATCH", body: { status, reason, max_users: Number(byId("tenant-max-users").value), max_nodes: Number(byId("tenant-max-nodes").value), max_shields: Number(byId("tenant-max-shields").value), sdk_enabled: byId("tenant-sdk-enabled").checked, android_sdk_integration_enabled: byId("tenant-android-sdk-integration-enabled").checked, ios_sdk_integration_enabled: byId("tenant-ios-sdk-integration-enabled").checked, referral_enabled: byId("tenant-referral-enabled").checked, scratch_lottery_enabled: byId("tenant-scratch-lottery-enabled").checked, dynamic_public_ip_enabled: byId("tenant-dynamic-public-ip-enabled").checked, home_tunnel_enabled: byId("tenant-home-tunnel-enabled").checked, real_ip_enabled: byId("tenant-real-ip-enabled").checked, mobile_platform_enabled: byId("tenant-mobile-platform-enabled").checked, both_platform_enabled: byId("tenant-both-platform-enabled").checked } });
	  await refresh();
	} catch (error) {
	  const messages = { tenant_reason_required: "暂停或关闭分销商必须填写原因", invalid_tenant_quotas: "请检查客户和节点额度", invalid_tenant_status: "无法修改分销商状态" };
	  errorBox.textContent = messages[error.message] || ("保存分销设置失败：" + error.message); errorBox.hidden = false;
	} finally { submit.disabled = false; }
  });
  byId("bind-tenant-marketplace-owner").addEventListener("click", async () => {
	if (!selectedTenant) return;
	const button = byId("bind-tenant-marketplace-owner"); const errorBox = byId("tenant-settings-error"); const userID = byId("tenant-marketplace-owner").value.trim();
	button.disabled = true; errorBox.hidden = true;
	try {
	  if (!userID) throw new Error("invalid_marketplace_owner");
	  await api("/v1/admin/tenants/" + encodeURIComponent(selectedTenant.id) + "/marketplace-owner", { method: "PUT", body: { user_id: userID } });
	  errorBox.textContent = "商城账户绑定成功"; errorBox.classList.add("success-message"); errorBox.hidden = false;
	} catch (error) {
	  errorBox.classList.remove("success-message"); errorBox.textContent = error.message === "invalid_marketplace_owner" ? "请输入有效的商城账户用户 ID" : ("绑定商城账户失败：" + error.message); errorBox.hidden = false;
	} finally { button.disabled = false; }
  });
  byId("marketplace-plan-reset").addEventListener("click", () => fillMarketplacePlan());
  for (const id of ["marketplace-plan-android-sdk-integration", "marketplace-plan-ios-sdk-integration"]) {
	byId(id).addEventListener("change", () => { if (byId(id).checked) byId("marketplace-plan-sdk").checked = true; });
  }
  byId("marketplace-plan-sdk").addEventListener("change", () => {
	if (!byId("marketplace-plan-sdk").checked) {
	  byId("marketplace-plan-android-sdk-integration").checked = false;
	  byId("marketplace-plan-ios-sdk-integration").checked = false;
	}
  });
  byId("marketplace-plan-form").addEventListener("submit", async (event) => {
	event.preventDefault(); const submit = byId("save-marketplace-plan"); const errorBox = byId("marketplace-plan-error");
	const id = byId("marketplace-plan-id").value.trim(); const priceCents = Math.round(Number(byId("marketplace-plan-price").value) * 100);
	const body = { name: byId("marketplace-plan-name").value.trim(), description: byId("marketplace-plan-description").value.trim(), sort_order: Number(byId("marketplace-plan-sort-order").value), purchase_limit: Number(byId("marketplace-plan-purchase-limit").value), upgrade_enabled: byId("marketplace-plan-upgrade-enabled").checked, renewal_enabled: byId("marketplace-plan-renewal-enabled").checked, term_days: Number(byId("marketplace-plan-days").value), price_cents: priceCents, max_users: Number(byId("marketplace-plan-users").value), max_nodes: Number(byId("marketplace-plan-nodes").value), max_shields: Number(byId("marketplace-plan-shields").value), sdk_enabled: byId("marketplace-plan-sdk").checked, android_sdk_integration_enabled: byId("marketplace-plan-android-sdk-integration").checked, ios_sdk_integration_enabled: byId("marketplace-plan-ios-sdk-integration").checked, dynamic_public_ip_enabled: byId("marketplace-plan-dynamic-public-ip").checked, home_tunnel_enabled: byId("marketplace-plan-home-tunnel").checked, real_ip_enabled: byId("marketplace-plan-real-ip").checked, mobile_platform_enabled: byId("marketplace-plan-mobile-platform").checked, both_platform_enabled: byId("marketplace-plan-both-platform").checked, referral_enabled: byId("marketplace-plan-referral").checked, scratch_lottery_enabled: byId("marketplace-plan-scratch-lottery").checked, enabled: byId("marketplace-plan-enabled").checked };
	submit.disabled = true; errorBox.hidden = true;
	try {
	  if (!Number.isSafeInteger(priceCents) || priceCents < 0) throw new Error("invalid_marketplace_plan");
	  await api(id ? "/v1/admin/marketplace/plans/" + encodeURIComponent(id) : "/v1/admin/marketplace/plans", { method: id ? "PUT" : "POST", body });
	  fillMarketplacePlan(); await refresh();
	} catch (error) {
	  errorBox.textContent = error.message === "invalid_marketplace_plan" ? "请检查套餐名称、期限、价格和数量上限" : ("保存授权套餐失败：" + error.message); errorBox.hidden = false;
	} finally { submit.disabled = false; }
  });
  tenantLicenseForm.addEventListener("submit", async (event) => {
	event.preventDefault(); if (!selectedTenant) return; const submit = byId("submit-tenant-license-button"); submit.disabled = true; const errorBox = byId("tenant-license-form-error"); errorBox.hidden = true;
	try {
	  const rawExpiry = byId("tenant-license-expiry").value; const expiresAt = rawExpiry ? Math.floor(new Date(rawExpiry).getTime() / 1000) : 0;
	  if (rawExpiry && (!Number.isFinite(expiresAt) || expiresAt <= Math.floor(Date.now() / 1000))) throw new Error("invalid_license_expiry");
	  const payload = await api("/v1/admin/tenants/" + encodeURIComponent(selectedTenant.id) + "/licenses", { method: "POST", body: { expected_ip: byId("tenant-license-ip").value.trim(), expected_domain: byId("tenant-license-domain").value.trim().toLowerCase(), expires_at: expiresAt } });
	  tenantLicenseDialog.close(); await loadTenantResources(); showOneTimeSecret("部署激活码", selectedTenant.name + " · " + payload.license.expected_ip + " · " + payload.license.expected_domain, payload.activation_code);
	} catch (error) {
	  const messages = { invalid_license: "公网 IP 或授权运行域名无效，或该绑定已存在", invalid_license_expiry: "授权到期时间必须晚于当前时间" };
	  errorBox.textContent = messages[error.message] || ("生成激活码失败：" + error.message); errorBox.hidden = false;
	} finally { submit.disabled = false; }
  });
  tenantDomainForm.addEventListener("submit", async (event) => {
	event.preventDefault(); if (!selectedTenant) return; const submit = byId("submit-tenant-domain-button"); submit.disabled = true; const errorBox = byId("tenant-domain-form-error"); errorBox.hidden = true;
	try {
	  const payload = await api("/v1/admin/tenants/" + encodeURIComponent(selectedTenant.id) + "/domains", { method: "POST", body: { hostname: byId("tenant-domain-hostname").value.trim().toLowerCase(), purpose: byId("tenant-domain-purpose").value } });
	  tenantDomainDialog.close(); await loadTenantResources(); showOneTimeSecret("DNS 验证令牌", selectedTenant.name + " · " + payload.domain.hostname, payload.dns_verification_token);
	} catch (error) {
	  const messages = { domain_conflict: "域名无效、已被占用或用途不正确" };
	  errorBox.textContent = messages[error.message] || ("添加控制台访问域名失败：" + error.message); errorBox.hidden = false;
	} finally { submit.disabled = false; }
  });
  document.querySelectorAll("[data-admin-view]").forEach((link) => {
    link.addEventListener("click", (event) => {
      event.preventDefault();
      activateAdminView(link.dataset.adminView, true);
    });
  });
  byId("add-edge-button").addEventListener("click", () => {
    edgeFormError.hidden = true;
    edgeDialog.showModal();
    byId("edge-host").focus();
  });
  function closeEdgeDialog() {
	byId("edge-password").value = "";
	byId("edge-tls-fingerprint").value = "";
	edgeDialog.close();
  }
  byId("close-edge-dialog").addEventListener("click", closeEdgeDialog);
  byId("cancel-edge-button").addEventListener("click", closeEdgeDialog);
  byId("close-edge-capacity-dialog").addEventListener("click", closeEdgeCapacity);
  byId("cancel-edge-capacity-button").addEventListener("click", closeEdgeCapacity);
  edgeCapacityForm.addEventListener("submit", async (event) => {
    event.preventDefault();
    if (!editingEdgeCapacity) return;
    const submit = byId("submit-edge-capacity-button");
    const bandwidthLimitMbps = Number(byId("edge-bandwidth-limit").value);
    const thresholdPercent = Number(byId("edge-bandwidth-threshold").value);
    submit.disabled = true;
    byId("edge-capacity-error").hidden = true;
    try {
      await api("/v1/admin/edges/" + encodeURIComponent(editingEdgeCapacity.node_id) + "/capacity", { method: "PATCH", body: {
        bandwidth_limit_mbps: bandwidthLimitMbps,
        bandwidth_threshold_percent: thresholdPercent
      }});
      closeEdgeCapacity();
      await refresh();
    } catch (error) {
      byId("edge-capacity-error").textContent = error.message === "invalid_edge_capacity" ? "请输入有效的总带宽和 1-100% 调度阈值" : ("保存节点容量失败：" + error.message);
      byId("edge-capacity-error").hidden = false;
    } finally {
      submit.disabled = false;
    }
  });
  edgeForm.addEventListener("submit", async (event) => {
    event.preventDefault();
    const submit = byId("submit-edge-button");
    const host = byId("edge-host").value.trim();
    const normalizedHost = host.replace(/^\[|\]$/g, "");
    const playerPort = byId("edge-player-port").value;
    const managementPort = byId("edge-management-port").value;
    const hostWithPort = (port) => normalizedHost.includes(":") ? "[" + normalizedHost + "]:" + port : normalizedHost + ":" + port;
    submit.disabled = true;
    edgeFormError.hidden = true;
    try {
      await api("/v1/admin/edges/enroll", { method: "POST", body: {
        node_id: "",
        address: hostWithPort(playerPort),
        server_name: host,
        region: "default",
        priority: 100,
		player_port: Number(playerPort), management_port: Number(managementPort), gateway_tunnel_port: Number(byId("edge-tunnel-port").value),
		management_address: hostWithPort(managementPort),
		tls_fingerprint: byId("edge-tls-fingerprint").value.trim(),
        username: "edge-admin",
        password: byId("edge-password").value
      }});
      closeEdgeDialog();
      await refresh();
    } catch (error) {
      const messages = {
        edge_unreachable: "节点已保留为离线状态；请确认节点程序已启动且管理端口已放行后重连",
        edge_auth_failed: "节点已保留；接入密码错误，请使用节点部署窗口或配置中对应的固定密码",
        edge_certificate_mismatch: "节点管理证书与指纹不一致，请核对目标 IP",
        edge_certificate_unavailable: "节点已接入，但玩家端证书暂时无法读取，请确认玩家通信端口已放行后重试",
        edge_already_enrolled: "节点已被接入；如需迁移，请先在原控制台删除该节点",
        edge_conflict: "节点 IP 或节点编号已存在",
        invalid_edge_enrollment: "节点或控制端配置无效，请检查 IP、端口和证书",
        edge_enrollment_failed: "节点记录已保留，但节点返回异常，接入尚未完成"
      };
      edgeFormError.textContent = messages[error.message] || ("添加节点失败：" + error.message);
      edgeFormError.hidden = false;
      await refresh();
    } finally {
      submit.disabled = false;
    }
  });
  byId("add-user-button").addEventListener("click", () => {
    userForm.reset();
    userFormError.hidden = true;
    userDialog.showModal();
    byId("user-username").focus();
  });
  function closeUserDialog() {
    byId("user-password").value = "";
    userDialog.close();
  }
  byId("close-user-dialog").addEventListener("click", closeUserDialog);
  byId("cancel-user-button").addEventListener("click", closeUserDialog);
  userForm.addEventListener("submit", async (event) => {
    event.preventDefault();
    const submit = byId("submit-user-button");
    submit.disabled = true;
    userFormError.hidden = true;
    try {
      await api("/v1/admin/users", { method: "POST", body: {
        username: byId("user-username").value.trim(),
		email: byId("user-email").value.trim(),
        password: byId("user-password").value
      }});
      closeUserDialog();
      await refresh();
    } catch (error) {
      userFormError.textContent = error.message === "username_conflict" ? "该账户已存在" : error.message === "invalid_user" ? "账户需为 3-32 位，密码至少 8 位" : "创建用户失败：" + error.message;
      userFormError.hidden = false;
    } finally {
      submit.disabled = false;
    }
  });

  function openUserPasswordDialog(user) {
    changingUserPassword = user;
    byId("user-password-summary").textContent = "账户：" + user.username + (user.email ? " · " + user.email : "");
    byId("user-new-password").value = "";
    byId("user-new-password-confirmation").value = "";
    byId("user-password-form-error").hidden = true;
    userPasswordDialog.showModal();
    byId("user-new-password").focus();
  }
  function closeUserPasswordDialog() {
    changingUserPassword = null;
    userPasswordForm.reset();
    userPasswordDialog.close();
  }
  byId("close-user-password-dialog").addEventListener("click", closeUserPasswordDialog);
  byId("cancel-user-password-button").addEventListener("click", closeUserPasswordDialog);
  userPasswordForm.addEventListener("submit", async (event) => {
    event.preventDefault();
    if (!changingUserPassword) return;
    const submit = byId("submit-user-password-button");
    const errorBox = byId("user-password-form-error");
    submit.disabled = true;
    errorBox.hidden = true;
    try {
      await api("/v1/admin/users/" + encodeURIComponent(changingUserPassword.id) + "/password", { method: "PUT", body: { password: byId("user-new-password").value, password_confirmation: byId("user-new-password-confirmation").value } });
      closeUserPasswordDialog();
      errorBanner.textContent = "节点用户密码已修改，原登录会话已失效";
      errorBanner.classList.add("success-message");
      errorBanner.hidden = false;
    } catch (error) {
      const messages = { invalid_password: "密码长度需为 8-128 位", password_confirmation_mismatch: "两次输入的密码不一致", user_not_found: "节点用户不存在" };
      errorBox.textContent = messages[error.message] || ("修改密码失败：" + error.message);
      errorBox.hidden = false;
    } finally {
      submit.disabled = false;
    }
  });

	byId("close-wallet-dialog").addEventListener("click", closeWalletDialog);
	byId("cancel-wallet-button").addEventListener("click", closeWalletDialog);
	walletForm.addEventListener("submit", async (event) => {
	  event.preventDefault(); if (!adjustingWalletUser) return;
	  const submit = byId("submit-wallet-button"); submit.disabled = true; byId("wallet-form-error").hidden = true;
	  const amountCents = Math.round(Number(byId("wallet-adjustment-amount").value) * 100);
	  try { if (!Number.isSafeInteger(amountCents) || amountCents === 0) throw new Error("invalid_wallet_adjustment"); await api("/v1/admin/users/" + encodeURIComponent(adjustingWalletUser.id) + "/wallet-adjustments", { method: "POST", body: { amount_cents: amountCents, note: byId("wallet-adjustment-note").value.trim(), idempotency_key: newIdempotencyKey("admin") } }); closeWalletDialog(); await refresh(); }
	  catch (error) { const messages = { invalid_wallet_adjustment: "请输入非零调整金额和备注", insufficient_balance: "扣款后余额不能小于 0" }; byId("wallet-form-error").textContent = messages[error.message] || ("余额调整失败：" + error.message); byId("wallet-form-error").hidden = false; }
	  finally { submit.disabled = false; }
	});

	smtpForm.addEventListener("submit", async (event) => {
	  event.preventDefault(); const submit = byId("save-smtp-button"); submit.disabled = true; byId("smtp-form-error").hidden = true;
	  const password = byId("smtp-password").value;
	  const body = { host: byId("smtp-host").value.trim(), port: Number(byId("smtp-port").value), username: byId("smtp-username").value.trim(), from_email: byId("smtp-from-email").value.trim(), from_name: byId("smtp-from-name").value.trim(), tls_mode: byId("smtp-tls-mode").value, enabled: byId("smtp-enabled").checked };
	  if (password) body.password = password;
	  try { const payload = await api("/v1/admin/smtp-settings", { method: "PUT", body }); renderSMTPSettings(payload.settings || {}); }
	  catch (error) { const messages = { invalid_smtp_settings: "请检查 SMTP 服务器、端口、发件邮箱和连接安全设置", smtp_tls_mode_mismatch: "465 端口必须选择 TLS 直连", smtp_credentials_required: "启用邮件前必须填写并保存 SMTP 授权码" }; byId("smtp-form-error").textContent = messages[error.message] || ("保存邮件设置失败：" + error.message); byId("smtp-form-error").hidden = false; }
	  finally { submit.disabled = false; }
	});
	gatewayDownloadSettingsForm.addEventListener("submit", async (event) => {
	  event.preventDefault();
	  const submit = byId("save-gateway-download-settings");
	  const errorBox = byId("gateway-download-settings-error");
	  submit.disabled = true;
	  errorBox.hidden = true;
	  const body = {
		windows_download_url: byId("windows-gateway-download-url").value.trim(),
		windows_tutorial: byId("windows-gateway-tutorial").value.trim(),
		windows_enabled: byId("windows-gateway-enabled").checked,
		linux_download_url: byId("linux-gateway-download-url").value.trim(),
		linux_install_script_url: byId("linux-gateway-install-script-url").value.trim(),
		linux_tutorial: byId("linux-gateway-tutorial").value.trim(),
		linux_enabled: byId("linux-gateway-enabled").checked
	  };
	  try {
		const payload = await api("/v1/admin/gateway-download-settings", { method: "PUT", body });
		renderGatewayDownloadSettings(payload.settings || {});
	  } catch (error) {
		const messages = { invalid_gateway_download_settings: "启用平台前，请填写有效的 HTTP/HTTPS 下载地址和部署教程" };
		errorBox.textContent = messages[error.message] || ("保存网关下载设置失败：" + error.message);
		errorBox.hidden = false;
	  } finally { submit.disabled = false; }
	});
	realIPDownloadSettingsForm.addEventListener("submit", async (event) => {
	  event.preventDefault();
	  const submit = byId("save-realip-download-settings");
	  const errorBox = byId("realip-download-settings-error");
	  submit.disabled = true;
	  errorBox.hidden = true;
	  const body = {
		windows_download_url: byId("windows-realip-download-url").value.trim(),
		windows_description: byId("windows-realip-description").value.trim(),
		windows_enabled: byId("windows-realip-enabled").checked,
		linux_download_url: byId("linux-realip-download-url").value.trim(),
		linux_install_script_url: byId("linux-realip-install-script-url").value.trim(),
		linux_description: byId("linux-realip-description").value.trim(),
		linux_enabled: byId("linux-realip-enabled").checked
	  };
	  try {
		const payload = await api("/v1/admin/realip-download-settings", { method: "PUT", body });
		renderRealIPDownloadSettings(payload.settings || {});
	  } catch (error) {
		const messages = { invalid_realip_download_settings: "启用平台前，请填写有效的 HTTP/HTTPS 下载地址和包介绍；Linux 还需要 HTTPS 一键安装脚本" };
		errorBox.textContent = messages[error.message] || ("保存真实 IP 下载设置失败：" + error.message);
		errorBox.hidden = false;
	  } finally { submit.disabled = false; }
	});
	portalSettingsForm.addEventListener("submit", async (event) => {
	  event.preventDefault(); const submit = byId("save-portal-settings"); const errorBox = byId("portal-settings-error"); submit.disabled = true; errorBox.hidden = true;
	  try { const payload = await api("/v1/admin/portal-settings", { method: "PUT", body: { user_portal_name: byId("user-portal-name").value.trim(), marketplace_upload_notice: byId("marketplace-upload-notice").value.trim() } }); renderPortalSettings(payload.settings || {}); }
	  catch (error) { errorBox.textContent = error.message === "invalid_portal_settings" ? "请填写用户后台名称和 1 至 4000 字的节点上传提醒" : ("保存其他设置失败：" + error.message); errorBox.hidden = false; }
	  finally { submit.disabled = false; }
	});
	articleForm.addEventListener("submit", async (event) => {
	  event.preventDefault(); const submit = byId("save-article"); const errorBox = byId("article-form-error"); submit.disabled = true; errorBox.hidden = true;
	  const id = byId("article-id").value.trim(); const body = { slug: byId("article-slug").value.trim(), title: byId("article-title").value.trim(), summary: byId("article-summary").value.trim(), content: byId("article-content").value.trim(), category: byId("article-category").value, author: byId("article-author").value.trim(), featured: byId("article-featured").checked, seo_title: byId("article-seo-title").value.trim(), seo_description: byId("article-seo-description").value.trim(), status: byId("article-status").value, keywords: byId("article-keywords").value.split(/\r?\n/).map((x) => x.trim()).filter(Boolean) };
	  try { await api("/v1/admin/articles" + (id ? "/" + encodeURIComponent(id) : ""), { method: id ? "PUT" : "POST", body }); byId("article-form").reset(); byId("article-id").value = ""; await refresh(); }
	  catch (error) { const messages = { invalid_article: "请填写合法的 Slug、标题和正文，正文不能超过 50 万字", invalid_article_status: "文章状态无效", article_slug_conflict: "Slug 已存在，请换一个" }; errorBox.textContent = messages[error.message] || ("保存文章失败：" + error.message); errorBox.hidden = false; }
	  finally { submit.disabled = false; }
	});
	byId("reset-article").addEventListener("click", () => { articleForm.reset(); byId("article-id").value = ""; byId("article-form-error").hidden = true; });
	baiduSubmissionSettingsForm.addEventListener("submit", async (event) => {
	  event.preventDefault(); const submit = byId("save-baidu-submission-settings"); const errorBox = byId("baidu-submission-settings-error"); submit.disabled = true; errorBox.hidden = true;
	  const token = byId("baidu-submission-token").value.trim(); const body = { enabled: byId("baidu-submission-enabled").checked, site: byId("baidu-submission-site").value.trim() }; if (token) body.token = token;
	  try { const payload = await api("/v1/admin/baidu-submission-settings", { method: "PUT", body }); renderBaiduSubmissionSettings(payload.settings || {}); }
	  catch (error) { const messages = { invalid_baidu_submission_site: "站点地址必须是 HTTPS 根地址", invalid_baidu_submission_token: "百度 Token 格式无效", baidu_submission_token_required: "启用前必须先填写百度推送 Token" }; errorBox.textContent = messages[error.message] || ("保存百度收录设置失败：" + error.message); errorBox.hidden = false; }
	  finally { submit.disabled = false; }
	});
	baiduSubmissionForm.addEventListener("submit", async (event) => {
	  event.preventDefault(); const submit = byId("submit-baidu-urls"); const resultBox = byId("baidu-submission-result"); const errorBox = byId("baidu-submission-error"); submit.disabled = true; resultBox.hidden = true; errorBox.hidden = true;
	  const urls = byId("baidu-submission-urls").value.split(/\r?\n/).map((value) => value.trim()).filter(Boolean);
	  try { const payload = await api("/v1/admin/baidu-submission", { method: "POST", body: { urls } }); resultBox.textContent = "已提交 " + Number(payload.submitted || 0) + " 个 URL，百度剩余额度 " + Number(payload.remain || 0); resultBox.hidden = false; await refresh(); }
	  catch (error) { const messages = { baidu_submission_disabled: "请先启用并保存百度普通收录", baidu_submission_token_required: "请先配置百度推送 Token", invalid_baidu_submission_url: "只能提交站点下的 HTTPS 公开 URL", baidu_submission_request_failed: "连接百度收录接口失败", baidu_submission_rejected: "百度拒绝了本次提交" }; const detail = error.detail || {}; const detailMessage = detail.message || (Number(detail.error) === 401 ? "Token 无效或与当前站点不匹配" : Number(detail.error) === 400 ? "站点尚未在百度站长平台验证，或站点地址填写不一致" : ""); errorBox.textContent = (messages[error.message] || ("提交百度收录失败：" + error.message)) + (detailMessage ? "：" + detailMessage : ""); errorBox.hidden = false; }
	  finally { submit.disabled = false; }
	});
	adminPasswordForm.addEventListener("submit", async (event) => {
	  event.preventDefault();
	  const submit = byId("save-admin-password"); const errorBox = byId("admin-password-error");
	  const oldPassword = byId("admin-current-password").value; const newPassword = byId("admin-new-password").value; const confirmation = byId("admin-confirm-password").value;
	  submit.disabled = true; errorBox.hidden = true;
	  try {
		await api("/v1/admin/password", { method: "PUT", body: { old_password: oldPassword, new_password: newPassword, password_confirmation: confirmation } });
		adminPasswordForm.reset();
		clearAdminSession("管理员密码已修改，请使用新密码重新登录", "success");
	  } catch (error) {
		const messages = { invalid_current_password:"旧密码错误", invalid_new_password:"新密码必须为 12-128 个字符", password_confirmation_mismatch:"两次输入的新密码不一致", password_unchanged:"新密码不能与旧密码相同", password_change_unavailable:"当前部署未启用密码修改", password_change_failed:"密码保存失败，请稍后重试" };
		errorBox.textContent = messages[error.message] || ("修改密码失败：" + error.message); errorBox.hidden = false;
	  } finally { submit.disabled = false; }
	});
	paymentSettingsForm.addEventListener("submit", async (event) => {
	  event.preventDefault(); const submit = byId("save-payment-settings"); const errorBox = byId("payment-settings-error"); submit.disabled = true; errorBox.hidden = true; byId("payment-test-result").hidden = true;
	  const body = { alipay_enabled:byId("alipay-enabled").checked, alipay_mode:byId("alipay-mode").value, alipay_environment:byId("alipay-environment").value, alipay_app_id:byId("alipay-app-id").value.trim(), alipay_notify_url:byId("alipay-notify-url").value.trim(), wechat_enabled:byId("wechat-enabled").checked, wechat_mch_id:byId("wechat-mch-id").value.trim(), wechat_app_id:byId("wechat-app-id").value.trim() };
	  const alipayPrivate = byId("alipay-private-key").value.trim(); const alipayPublic = byId("alipay-public-key").value.trim(); const wechatKey = byId("wechat-key").value.trim(); const wechatAppSecret = byId("wechat-app-secret").value.trim();
	  if (alipayPrivate) body.alipay_private_key = alipayPrivate; if (alipayPublic) body.alipay_public_key = alipayPublic; if (wechatKey) body.wechat_key = wechatKey; if (wechatAppSecret) body.wechat_app_secret = wechatAppSecret;
	  try { const payload = await api("/v1/admin/payment-settings", { method:"PUT", body }); renderPaymentSettings(payload.settings || {}); byId("alipay-private-key").value = alipayPrivate; byId("alipay-public-key").value = alipayPublic; byId("wechat-key").value = wechatKey; byId("wechat-app-secret").value = wechatAppSecret; }
	  catch (error) { const messages = { invalid_payment_settings:"请检查支付环境和商户信息", wechat_identifiers_swapped:"微信商户号和 AppID 填反了：商户号应为 8-10 位数字，AppID 应以 wx 开头", invalid_wechat_identifiers:"微信商户号或 AppID 格式错误", payment_credentials_required:"启用支付渠道前必须填写完整的接口信息和密钥", invalid_wechat_key:"微信 Key 无效", invalid_wechat_app_secret:"微信 AppSecret 无效" }; errorBox.textContent = messages[error.message] || ("保存支付设置失败：" + error.message); errorBox.hidden = false; }
	  finally { submit.disabled = false; }
	});
	for (const provider of ["alipay", "wechat"]) byId("test-" + provider + "-settings").addEventListener("click", async () => {
	  const button = byId("test-" + provider + "-settings"); const result = byId("payment-test-result"); button.disabled = true; result.hidden = true;
	  try { await api("/v1/admin/payment-settings/" + provider + "/test", { method:"POST", body:{} }); result.textContent = (provider === "alipay" ? "支付宝" : "微信支付") + "接口连接成功"; result.hidden = false; }
	  catch (error) { const messages = { payment_provider_not_configured:"请先保存并启用该支付渠道", payment_test_failed:"接口连接失败，请检查商户信息、密钥和服务器网络", invalid_alipay_credentials:"支付宝验签失败，请核对应用私钥、支付宝公钥和 App ID", invalid_alipay_signature:"应用私钥与支付宝开放平台登记的应用公钥不匹配" }; result.textContent = messages[error.message] || ("连接测试失败：" + error.message); result.hidden = false; }
	  finally { button.disabled = false; }
	});
	identitySettingsForm.addEventListener("submit", async (event) => {
	  event.preventDefault(); const submit = byId("save-identity-settings"); const errorBox = byId("identity-settings-error"); submit.disabled = true; errorBox.hidden = true; byId("identity-test-result").hidden = true;
	  const priceCents = Math.round(Number(byId("identity-attempt-price").value) * 100);
	  const body = { enabled:byId("identity-enabled").checked, baidu_api_endpoint:byId("identity-baidu-endpoint").value.trim(), scan_public_url:byId("identity-scan-public-url").value.trim(), free_attempts:Number(byId("identity-free-attempts").value), attempt_price_cents:priceCents, match_threshold:Number(byId("identity-match-threshold").value) };
	  const apiKey = byId("identity-baidu-api-key").value.trim(); const secretKey = byId("identity-baidu-secret-key").value.trim(); if (apiKey) body.baidu_api_key = apiKey; if (secretKey) body.baidu_secret_key = secretKey;
	  try { if (!Number.isSafeInteger(priceCents)) throw new Error("invalid_identity_settings"); const payload = await api("/v1/admin/identity-settings", { method:"PUT", body }); renderIdentitySettings(payload || {}); }
	  catch (error) { const messages = { invalid_identity_settings:"请检查百度接口、免费次数、单次费用和通过分数", identity_credentials_required:"启用前必须填写 API Key 和 Secret Key" }; errorBox.textContent = messages[error.message] || ("保存实名认证设置失败：" + error.message); errorBox.hidden = false; }
	  finally { submit.disabled = false; }
	});
	byId("test-identity-settings").addEventListener("click", async () => {
	  const button = byId("test-identity-settings"); const result = byId("identity-test-result"); button.disabled = true; result.hidden = true;
	  try { await api("/v1/admin/identity-settings/baidu/test", { method:"POST", body:{} }); result.textContent = "百度云 API 凭据连接成功"; result.hidden = false; }
	  catch (error) { const messages = { identity_credentials_required:"请先保存 API Key 和 Secret Key", identity_test_failed:"百度云凭据验证失败" }; result.textContent = messages[error.message] || ("连接测试失败：" + error.message); result.hidden = false; }
	  finally { button.disabled = false; }
	});
	byId("close-identity-review").addEventListener("click", closeIdentityReview); byId("cancel-identity-review").addEventListener("click", closeIdentityReview);
	identityReviewForm.addEventListener("submit", async (event) => {
	  event.preventDefault(); if (!reviewingIdentity) return; const submit = byId("submit-identity-review"); const errorBox = byId("identity-review-error"); submit.disabled = true; errorBox.hidden = true;
	  const body = { status:byId("identity-review-status").value, reason:byId("identity-review-reason").value.trim(), name:byId("identity-review-name").value.trim(), id_number:byId("identity-review-id-number").value.trim(), refund_charge:byId("identity-review-refund").checked };
	  try { await api("/v1/admin/identity-verifications/" + encodeURIComponent(reviewingIdentity.id), { method:"PATCH", body }); closeIdentityReview(); await refresh(); }
	  catch (error) { const messages = { invalid_identity_review:"请填写审核原因；修正实名信息时姓名和身份证号必须同时填写", identity_verification_not_found:"实名核验记录不存在" }; errorBox.textContent = messages[error.message] || ("人工审核失败：" + error.message); errorBox.hidden = false; }
	  finally { submit.disabled = false; }
	});
	byId("smtp-test-form").addEventListener("submit", async (event) => {
	  event.preventDefault(); const button = byId("send-smtp-test"); const result = byId("smtp-test-result"); button.disabled = true; result.hidden = true;
	  try { await api("/v1/admin/smtp-settings/test", { method: "POST", body: { email: byId("smtp-test-email").value.trim() } }); result.textContent = "测试邮件已发送"; result.hidden = false; }
	  catch (error) { const messages = { smtp_not_configured: "请先保存完整的 SMTP 设置", smtp_tls_mode_mismatch: "465 端口必须选择 TLS 直连", smtp_connect_failed: "无法连接 SMTP 服务器，请检查服务器、端口和网络放行", smtp_tls_failed: "TLS 握手失败，请检查连接安全模式", smtp_auth_failed: "SMTP 认证失败，请检查登录账户和授权码", smtp_sender_rejected: "SMTP 拒绝发件地址，请确认发件邮箱已获授权", smtp_recipient_rejected: "SMTP 拒绝测试收件地址", smtp_delivery_failed: "SMTP 已连接，但邮件提交失败", smtp_test_failed: "邮件发送失败，请检查 SMTP 配置" }; result.textContent = messages[error.message] || ("测试失败：" + error.message); result.hidden = false; }
	  finally { button.disabled = false; }
	});
	byId("smtp-port").addEventListener("change", () => { const port = Number(byId("smtp-port").value); if (port === 465) byId("smtp-tls-mode").value = "tls"; else if (port === 587 && byId("smtp-tls-mode").value === "tls") byId("smtp-tls-mode").value = "starttls"; });

	function openPlanDialog(plan = null) {
	  editingPlan = plan;
	  planForm.reset();
	  byId("plan-dialog-title").textContent = plan ? "编辑套餐" : "创建套餐";
	  byId("plan-form-error").hidden = true;
	  byId("plan-name-input").value = plan ? plan.name : "";
	  byId("plan-upload-mbps").value = plan ? plan.max_upload_mbps : 100;
	  byId("plan-download-mbps").value = plan ? plan.max_download_mbps : 300;
	  byId("plan-daily-devices").value = plan ? plan.max_daily_devices : 100;
	  byId("plan-origins").value = plan ? plan.max_origin_ips : 1;
	  byId("plan-mappings").value = plan ? plan.max_mappings : 5;
	  const prices = new Map(((plan && plan.prices) || []).map((price) => [price.term_months, price]));
	  for (const term of [1,3,6,12]) { const price = prices.get(term); byId("plan-price-" + term).value = price ? (price.price_cents / 100).toFixed(2) : "0.00"; byId("plan-price-enabled-" + term).checked = price ? price.enabled : false; }
	  byId("plan-registration-default").checked = plan ? Boolean(plan.registration_default) : false;
	  byId("plan-dynamic-public-ip").checked = plan ? Boolean(plan.dynamic_public_ip_enabled) : false;
	  byId("plan-home-tunnel").checked = plan ? Boolean(plan.home_tunnel_enabled) : false;
	  byId("plan-real-ip").checked = plan ? Boolean(plan.real_ip_enabled) : false;
	  byId("plan-mobile-platform").checked = plan ? Boolean(plan.mobile_platform_enabled) : false;
	  byId("plan-both-platform").checked = plan ? Boolean(plan.both_platform_enabled) : false;
	  byId("plan-enabled").checked = plan ? plan.enabled : true;
	  planDialog.showModal();
	  byId("plan-name-input").focus();
	}
	function closePlanDialog() { planDialog.close(); editingPlan = null; }
	byId("add-plan-button").addEventListener("click", () => openPlanDialog());
	byId("close-plan-dialog").addEventListener("click", closePlanDialog);
	byId("cancel-plan-button").addEventListener("click", closePlanDialog);
	planForm.addEventListener("submit", async (event) => {
	  event.preventDefault(); const submit = byId("submit-plan-button"); submit.disabled = true; byId("plan-form-error").hidden = true;
	  const body = { name: byId("plan-name-input").value.trim(), max_upload_mbps: Number(byId("plan-upload-mbps").value), max_download_mbps: Number(byId("plan-download-mbps").value), max_daily_devices: Number(byId("plan-daily-devices").value), max_origin_ips: Number(byId("plan-origins").value), max_mappings: Number(byId("plan-mappings").value), dynamic_public_ip_enabled: byId("plan-dynamic-public-ip").checked, home_tunnel_enabled: byId("plan-home-tunnel").checked, real_ip_enabled: byId("plan-real-ip").checked, mobile_platform_enabled: byId("plan-mobile-platform").checked, both_platform_enabled: byId("plan-both-platform").checked, registration_default: byId("plan-registration-default").checked, enabled: byId("plan-enabled").checked };
	  const prices = [1,3,6,12].map((term) => ({ term_months: term, price_cents: Math.round(Number(byId("plan-price-" + term).value) * 100), enabled: byId("plan-price-enabled-" + term).checked }));
	  try { if (prices.some((price) => !Number.isSafeInteger(price.price_cents) || price.price_cents < 0)) throw new Error("invalid_plan_prices"); const result = await api(editingPlan ? "/v1/admin/plans/" + encodeURIComponent(editingPlan.id) : "/v1/admin/plans", { method: editingPlan ? "PUT" : "POST", body }); const planID = editingPlan ? editingPlan.id : result.plan.id; await api("/v1/admin/plans/" + encodeURIComponent(planID) + "/prices", { method: "PUT", body: { prices } }); closePlanDialog(); await refresh(); }
	  catch (error) { const messages = { invalid_plan: "请检查套餐名称和额度", invalid_plan_prices: "请检查四个周期的商城价格", plan_name_conflict: "套餐名称已存在" }; byId("plan-form-error").textContent = messages[error.message] || ("保存套餐失败：" + error.message); byId("plan-form-error").hidden = false; }
	  finally { submit.disabled = false; }
	});

	function openSubscriptionDialog(subscription) {
	  editingSubscription = subscription;
	  byId("subscription-dialog-summary").textContent = subscription.username + " · " + subscription.plan_name + " · " + subscription.subscription_id;
	  byId("subscription-expiry").value = toLocalInputValue(subscription.expires_at);
	  byId("subscription-form-error").hidden = true;
	  subscriptionDialog.showModal();
	  byId("subscription-expiry").focus();
	}
	function closeSubscriptionDialog() { subscriptionDialog.close(); editingSubscription = null; }
	byId("close-subscription-dialog").addEventListener("click", closeSubscriptionDialog);
	byId("cancel-subscription-button").addEventListener("click", closeSubscriptionDialog);
	subscriptionForm.addEventListener("submit", async (event) => {
	  event.preventDefault(); if (!editingSubscription) return;
	  const submit = byId("submit-subscription-button"); submit.disabled = true; byId("subscription-form-error").hidden = true;
	  const expiresAt = Math.floor(new Date(byId("subscription-expiry").value).getTime() / 1000);
	  try { if (!Number.isFinite(expiresAt) || expiresAt <= 0) throw new Error("invalid_subscription_expiry"); await api("/v1/admin/subscriptions/" + encodeURIComponent(editingSubscription.subscription_id), { method: "PATCH", body: { expires_at: expiresAt } }); closeSubscriptionDialog(); await refresh(); }
	  catch (error) { byId("subscription-form-error").textContent = error.message === "invalid_subscription_expiry" ? "请选择有效的到期时间" : "保存到期时间失败：" + error.message; byId("subscription-form-error").hidden = false; }
	  finally { submit.disabled = false; }
	});

	async function openMarketplaceAuthorizationDialog(user) {
	  managingMarketplaceAuthorizationUser = user;
	  marketplaceAuthorizationForm.reset();
	  byId("marketplace-authorization-dialog-title").textContent = user.username + " 的飞马游戏盾社区套餐";
	  byId("marketplace-authorization-form-error").hidden = true;
	  const select = byId("marketplace-authorization-plan");
	  select.replaceChildren();
	  for (const plan of marketplacePlans.filter((item) => item.enabled)) {
		select.add(new Option(plan.name + " · " + plan.term_days + " 天 · 用户 " + plan.max_users + " / 节点 " + plan.max_nodes + " / 盾 " + (Number(plan.max_shields || 0) === 0 ? "不限" : plan.max_shields), plan.id));
	  }
	  byId("submit-marketplace-authorization").disabled = select.options.length === 0;
	  marketplaceAuthorizationDialog.showModal();
	  await loadMarketplaceAuthorizations();
	}

	async function loadMarketplaceAuthorizations() {
	  if (!managingMarketplaceAuthorizationUser) return;
	  const body = byId("marketplace-authorization-body");
	  body.replaceChildren();
	  try {
		const payload = await api("/v1/admin/users/" + encodeURIComponent(managingMarketplaceAuthorizationUser.id) + "/marketplace-authorizations");
		marketplaceAuthorizations = payload.authorizations || [];
		byId("marketplace-authorization-empty").hidden = marketplaceAuthorizations.length !== 0;
		for (const authorization of marketplaceAuthorizations) {
		  const row = document.createElement("tr");
		  cell(row, authorization.plan_name, "node-name");
		  cell(row, authorization.expected_ip + "\n" + authorization.expected_domain, "nowrap");
		  cell(row, "用户 " + authorization.used_users + "/" + authorization.max_users + " · 节点 " + authorization.used_nodes + "/" + authorization.max_nodes + " · 盾 " + authorization.used_shields + "/" + (Number(authorization.max_shields || 0) === 0 ? "不限" : authorization.max_shields), "nowrap");
		  cell(row, formatHeartbeat(authorization.expires_at), "nowrap");
		  const expired = Number(authorization.expires_at || 0) <= Math.floor(Date.now() / 1000);
		  const active = authorization.tenant_status === "active" && authorization.license_status === "active";
		  cell(row, statusCell(expired ? "已到期" : active ? "正常" : "已暂停", expired || !active ? "offline" : "healthy"));
		  const actions = document.createElement("div"); actions.className = "row-actions";
		  const expiry = document.createElement("button"); expiry.type = "button"; expiry.className = "table-action"; expiry.textContent = "改时间"; expiry.addEventListener("click", () => openMarketplaceAuthorizationAction("expiry", authorization));
		  const renew = document.createElement("button"); renew.type = "button"; renew.className = "table-action"; renew.textContent = "续费"; renew.addEventListener("click", () => openMarketplaceAuthorizationAction("renew", authorization));
		  const upgrade = document.createElement("button"); upgrade.type = "button"; upgrade.className = "table-action"; upgrade.textContent = "升级"; upgrade.disabled = expired; upgrade.addEventListener("click", () => openMarketplaceAuthorizationAction("upgrade", authorization));
		  const copy = document.createElement("button"); copy.type = "button"; copy.className = "table-action neutral"; copy.textContent = "复制激活码"; copy.addEventListener("click", async () => {
			copy.disabled = true;
			try { const result = await api("/v1/admin/tenant-licenses/" + encodeURIComponent(authorization.license_id) + "/activation-code"); await navigator.clipboard.writeText(result.activation_code); copy.textContent = "已复制"; setTimeout(() => { copy.textContent = "复制激活码"; copy.disabled = false; }, 1200); }
			catch (error) { byId("marketplace-authorization-form-error").textContent = "读取激活码失败：" + error.message; byId("marketplace-authorization-form-error").hidden = false; copy.disabled = false; }
		  });
		  actions.append(expiry, renew, upgrade, copy); cell(row, actions); body.append(row);
		}
	  } catch (error) {
		byId("marketplace-authorization-form-error").textContent = "读取飞马套餐失败：" + error.message;
		byId("marketplace-authorization-form-error").hidden = false;
	  }
	}

	function closeMarketplaceAuthorizationDialog() {
	  marketplaceAuthorizationDialog.close();
	  managingMarketplaceAuthorizationUser = null;
	  marketplaceAuthorizations = [];
	}

	function marketplacePlanCanUpgradeUI(current, target) {
	  const currentShields = Number(current.max_shields || 0), targetShields = Number(target.max_shields || 0);
	  const quotasValid = Number(target.max_users) >= Number(current.max_users) && Number(target.max_nodes) >= Number(current.max_nodes) && (currentShields === 0 ? targetShields === 0 : targetShields === 0 || targetShields >= currentShields);
	  const features = ["sdk_enabled", "android_sdk_integration_enabled", "ios_sdk_integration_enabled", "dynamic_public_ip_enabled", "home_tunnel_enabled", "real_ip_enabled", "mobile_platform_enabled", "both_platform_enabled", "referral_enabled", "scratch_lottery_enabled"];
	  if (!quotasValid || features.some((feature) => current[feature] && !target[feature])) return false;
	  return Number(target.max_users) > Number(current.max_users) || Number(target.max_nodes) > Number(current.max_nodes) || (currentShields > 0 && (targetShields === 0 || targetShields > currentShields)) || features.some((feature) => !current[feature] && target[feature]);
	}

	function openMarketplaceAuthorizationAction(action, authorization) {
	  marketplaceAuthorizationAction = { action, authorization };
	  marketplaceAuthorizationActionForm.reset();
	  byId("marketplace-authorization-action-error").hidden = true;
	  byId("marketplace-authorization-action-summary").textContent = authorization.plan_name + " · " + authorization.expected_domain + " · 到期 " + formatHeartbeat(authorization.expires_at);
	  const expiryField = byId("marketplace-authorization-expiry-field");
	  const upgradeField = byId("marketplace-authorization-upgrade-field");
	  expiryField.hidden = action !== "expiry";
	  upgradeField.hidden = action !== "upgrade";
	  byId("marketplace-authorization-action-expiry").required = action === "expiry";
	  byId("marketplace-authorization-upgrade-plan").required = action === "upgrade";
	  byId("marketplace-authorization-action-expiry").value = toLocalInputValue(authorization.expires_at);
	  const titles = { expiry: "修改飞马套餐到期时间", renew: "人工续费飞马套餐", upgrade: "升级飞马套餐" };
	  byId("marketplace-authorization-action-title").textContent = titles[action];
	  byId("submit-marketplace-authorization-action").textContent = action === "expiry" ? "保存时间" : action === "renew" ? "确认续费" : "确认升级";
	  const select = byId("marketplace-authorization-upgrade-plan"); select.replaceChildren();
	  if (action === "upgrade") for (const plan of marketplacePlans.filter((item) => item.enabled && item.id !== authorization.plan_id && marketplacePlanCanUpgradeUI(authorization, item))) select.add(new Option(plan.name + " · 用户 " + plan.max_users + " / 节点 " + plan.max_nodes + " / 盾 " + (Number(plan.max_shields || 0) === 0 ? "不限" : plan.max_shields), plan.id));
	  byId("submit-marketplace-authorization-action").disabled = action === "upgrade" && select.options.length === 0;
	  marketplaceAuthorizationActionDialog.showModal();
	  byId("marketplace-authorization-action-reason").focus();
	}

	function closeMarketplaceAuthorizationAction() {
	  marketplaceAuthorizationActionDialog.close();
	  marketplaceAuthorizationAction = null;
	}

	byId("close-marketplace-authorization-dialog").addEventListener("click", closeMarketplaceAuthorizationDialog);
	byId("close-marketplace-authorization-action").addEventListener("click", closeMarketplaceAuthorizationAction);
	byId("cancel-marketplace-authorization-action").addEventListener("click", closeMarketplaceAuthorizationAction);
	marketplaceAuthorizationForm.addEventListener("submit", async (event) => {
	  event.preventDefault(); if (!managingMarketplaceAuthorizationUser) return;
	  const submit = byId("submit-marketplace-authorization"); submit.disabled = true; byId("marketplace-authorization-form-error").hidden = true;
	  const expiryValue = byId("marketplace-authorization-expiry").value;
	  const expiresAt = expiryValue ? Math.floor(new Date(expiryValue).getTime() / 1000) : 0;
	  try {
		if (expiryValue && (!Number.isFinite(expiresAt) || expiresAt <= Math.floor(Date.now() / 1000))) throw new Error("invalid_expiry");
		const result = await api("/v1/admin/users/" + encodeURIComponent(managingMarketplaceAuthorizationUser.id) + "/marketplace-authorizations", { method: "POST", body: { plan_id: byId("marketplace-authorization-plan").value, expected_ip: byId("marketplace-authorization-ip").value.trim(), expected_domain: byId("marketplace-authorization-domain").value.trim(), expires_at: expiresAt, reason: byId("marketplace-authorization-reason").value.trim() } });
		marketplaceAuthorizationForm.reset();
		if (result.activation_code) {
		  let copied = false;
		  try { await navigator.clipboard.writeText(result.activation_code); copied = true; } catch (_) {}
		  window.alert("飞马套餐已开通" + (copied ? "，激活码已复制到剪贴板。" : "，请记录下面的激活码。") + "\n\n" + result.activation_code);
		}
		await loadMarketplaceAuthorizations();
	  } catch (error) {
		byId("marketplace-authorization-form-error").textContent = error.message === "invalid_expiry" ? "自定义到期时间必须晚于当前时间" : "新增飞马套餐失败：" + error.message;
		byId("marketplace-authorization-form-error").hidden = false;
	  } finally { submit.disabled = false; }
	});

	marketplaceAuthorizationActionForm.addEventListener("submit", async (event) => {
	  event.preventDefault(); if (!managingMarketplaceAuthorizationUser || !marketplaceAuthorizationAction) return;
	  const submit = byId("submit-marketplace-authorization-action"); submit.disabled = true; byId("marketplace-authorization-action-error").hidden = true;
	  const { action, authorization } = marketplaceAuthorizationAction;
	  const base = "/v1/admin/users/" + encodeURIComponent(managingMarketplaceAuthorizationUser.id) + "/marketplace-authorizations/" + encodeURIComponent(authorization.license_id);
	  const reason = byId("marketplace-authorization-action-reason").value.trim();
	  try {
		if (action === "expiry") {
		  const expiresAt = Math.floor(new Date(byId("marketplace-authorization-action-expiry").value).getTime() / 1000);
		  if (!Number.isFinite(expiresAt) || expiresAt <= 0) throw new Error("invalid_expiry");
		  await api(base, { method: "PATCH", body: { expires_at: expiresAt, reason } });
		} else if (action === "renew") await api(base + "/renew", { method: "POST", body: { reason } });
		else await api(base + "/upgrade", { method: "POST", body: { plan_id: byId("marketplace-authorization-upgrade-plan").value, reason } });
		closeMarketplaceAuthorizationAction();
		await loadMarketplaceAuthorizations();
	  } catch (error) {
		const labels = { invalid_expiry: "请选择有效的到期时间", invalid_marketplace_request: "目标套餐不是有效的升级套餐，或当前授权已经到期" };
		byId("marketplace-authorization-action-error").textContent = labels[error.message] || ("操作失败：" + error.message);
		byId("marketplace-authorization-action-error").hidden = false;
	  } finally { submit.disabled = false; }
	});

	async function openAssignPlan(user) {
	  assigningUser = user; byId("assign-plan-dialog-title").textContent = "为 " + user.username + " 新增套餐"; byId("assign-plan-error").hidden = true;
	  const select = byId("assign-plan-select"); select.replaceChildren();
	  for (const plan of currentPlans.filter((item) => item.enabled)) select.add(new Option(plan.name + "（上行 " + plan.max_upload_mbps + " Mbps / 下行 " + plan.max_download_mbps + " Mbps / 设备 " + plan.max_daily_devices + "）", plan.id));
	  byId("submit-assign-plan-button").disabled = select.options.length === 0;
	  byId("assign-plan-expiry").value = toLocalInputValue(0);
	  assignPlanDialog.showModal();
	  try { await loadAssignedPlans(user); } catch (error) { byId("assign-plan-error").textContent = "读取已分配套餐失败：" + error.message; byId("assign-plan-error").hidden = false; }
	  select.focus();
	}
	async function loadAssignedPlans(user) {
	  const payload = await api("/v1/admin/users/" + encodeURIComponent(user.id) + "/plans");
	  const plans = payload.plans || []; const list = byId("assigned-plan-list"); list.replaceChildren(); byId("assigned-plan-empty").hidden = plans.length !== 0;
	  for (const plan of plans) {
		const item = document.createElement("div"); item.className = "assigned-plan-item";
		const label = document.createElement("span"); label.textContent = plan.plan_name + " · 套餐编号 " + plan.subscription_id + (plan.app_id ? " · 已绑定游戏盾" : " · 待配置") + " · " + (plan.expires_at ? "到期 " + formatHeartbeat(plan.expires_at) : "永久有效");
		const remove = document.createElement("button"); remove.type = "button"; remove.className = "table-action danger"; remove.textContent = "移除";
		remove.addEventListener("click", async () => { if (!confirm("确认永久删除套餐 “" + plan.plan_name + "”？对应游戏盾、转发规则、网关绑定和统计数据都会一起删除，无法恢复。")) return; remove.disabled = true; try { await api("/v1/admin/users/" + encodeURIComponent(user.id) + "/plans/" + encodeURIComponent(plan.subscription_id), { method: "DELETE" }); await loadAssignedPlans(user); await refresh(); } catch (error) { byId("assign-plan-error").textContent = "移除套餐失败：" + error.message; byId("assign-plan-error").hidden = false; remove.disabled = false; } });
		item.append(label, remove); list.append(item);
	  }
	}
	function closeAssignPlan() { assignPlanDialog.close(); assigningUser = null; }
	byId("close-assign-plan-dialog").addEventListener("click", closeAssignPlan);
	byId("cancel-assign-plan-button").addEventListener("click", closeAssignPlan);
	byId("assign-plan-form").addEventListener("submit", async (event) => {
	  event.preventDefault(); if (!assigningUser) return; const submit = byId("submit-assign-plan-button"); submit.disabled = true; byId("assign-plan-error").hidden = true;
	  const expiresAt = Math.floor(new Date(byId("assign-plan-expiry").value).getTime() / 1000);
	  try { if (!Number.isFinite(expiresAt) || expiresAt <= Math.floor(Date.now() / 1000)) throw new Error("invalid_subscription_expiry"); await api("/v1/admin/users/" + encodeURIComponent(assigningUser.id) + "/plan", { method: "PUT", body: { plan_id: byId("assign-plan-select").value, expires_at: expiresAt } }); closeAssignPlan(); await refresh(); }
	  catch (error) { byId("assign-plan-error").textContent = error.message === "invalid_subscription_expiry" ? "套餐到期时间必须晚于当前时间" : "分配套餐失败：" + error.message; byId("assign-plan-error").hidden = false; }
	  finally { submit.disabled = false; }
	});
  byId("logout-button").addEventListener("click", async () => {
	try { await api("/v1/admin/session", { method: "DELETE" }); } catch (_) {}
    usernameInput.value = "admin";
	clearAdminSession();
	usernameInput.focus();
  });

  const initialAdminView = adminViewAliases[location.hash.slice(1)] || location.hash.slice(1) || "overview";
  activateAdminView(initialAdminView);
  if (adminToken) {
    loginView.hidden = true;
    dashboardView.hidden = false;
    refresh();
  }
	document.body.classList.remove("auth-pending");
})();
