ai-zero-token 2.0.2 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/README.md +13 -5
- package/README.zh-CN.md +13 -5
- package/admin-ui/dist/assets/accounts-DymL4WIa.js +2 -0
- package/admin-ui/dist/assets/{docs-CihX3Xsm.js → docs-DtO-AOWU.js} +1 -1
- package/admin-ui/dist/assets/{image-bed-BGjlDLks.js → image-bed-yIVQ4dKs.js} +1 -1
- package/admin-ui/dist/assets/index-By4r-wy3.css +1 -0
- package/admin-ui/dist/assets/index-DRe-tByu.js +10 -0
- package/admin-ui/dist/assets/{launch-BWw7Odq7.js → launch-CQXYrl-h.js} +1 -1
- package/admin-ui/dist/assets/{logs-DDdgDVwo.js → logs-awABDg1C.js} +1 -1
- package/admin-ui/dist/assets/{network-detect-cUdjg4zk.js → network-detect-sSrnwZqf.js} +1 -1
- package/admin-ui/dist/assets/{overview-CsjVVcvi.js → overview-BbSON0Jl.js} +1 -1
- package/admin-ui/dist/assets/settings-DvRiHS7i.js +1 -0
- package/admin-ui/dist/assets/{tester-BIvH_8DY.js → tester-CftPgRE9.js} +1 -1
- package/admin-ui/dist/index.html +2 -2
- package/build/mac-install-guide.txt +25 -0
- package/dist/core/services/auth-service.js +88 -12
- package/dist/core/services/config-service.js +87 -23
- package/dist/core/services/version-service.js +1 -1
- package/dist/core/store/profile-store.js +73 -32
- package/dist/core/store/settings-store.js +14 -0
- package/dist/server/app.js +63 -16
- package/docs/DESKTOP_RELEASE.md +35 -3
- package/package.json +31 -2
- package/admin-ui/dist/assets/accounts-Ddq82u6R.js +0 -1
- package/admin-ui/dist/assets/index-CX8e0-BB.js +0 -10
- package/admin-ui/dist/assets/index-ywn2Jwpu.css +0 -1
- package/admin-ui/dist/assets/settings-Be99HpDD.js +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.0.3 - 2026-05-08
|
|
4
|
+
|
|
5
|
+
- Added ZIP batch account import with preflight validation for bundled account JSON files.
|
|
6
|
+
- Added account export audit metadata, including export count, latest export time, and export type.
|
|
7
|
+
- Added account-card export status badges so exported accounts are visible in the account list.
|
|
8
|
+
- Added selected-account batch deletion from the account management page.
|
|
9
|
+
- Improved global quota refresh performance with configurable concurrency.
|
|
10
|
+
- Added a runtime setting for quota refresh concurrency, configurable from 1 to 32.
|
|
11
|
+
- Restored the prominent global update banner with separate desktop and npm update paths.
|
|
12
|
+
- Fixed account export UI state so export status updates immediately after exporting.
|
|
13
|
+
|
|
3
14
|
## 2.0.2 - 2026-05-07
|
|
4
15
|
|
|
5
16
|
- Added a GitHub image-bed workflow to the React desktop UI, including upload history, configuration storage, and gateway service support.
|
package/README.md
CHANGED
|
@@ -18,10 +18,10 @@ AI Zero Token provides a local CLI, web console, and HTTP gateway that expose sa
|
|
|
18
18
|
- `POST /v1/images/edits`
|
|
19
19
|
- ChatGPT/Codex OAuth login with local token refresh.
|
|
20
20
|
- Multi-account management in the web console.
|
|
21
|
-
- Account JSON import/export, including selected batch export.
|
|
21
|
+
- Account JSON import/export, including ZIP batch import, selected batch export, and export audit indicators.
|
|
22
22
|
- Apply a saved account to local Codex by backing up and updating `~/.codex/auth.json`.
|
|
23
23
|
- `gpt-image-2` image generation and JSON image editing through the ChatGPT internal Responses path.
|
|
24
|
-
- Optional quota-exhaustion auto switch to the next saved API account with available quota.
|
|
24
|
+
- Optional quota-exhaustion auto switch to the next saved API account with available quota, plus configurable quota refresh concurrency.
|
|
25
25
|
- Optional upstream proxy configuration for OAuth, model refresh, and gateway forwarding.
|
|
26
26
|
- Local model discovery from the Codex model cache with manual refresh support.
|
|
27
27
|
|
|
@@ -84,13 +84,16 @@ http://127.0.0.1:8787/v1
|
|
|
84
84
|
The web console is the recommended entry point:
|
|
85
85
|
|
|
86
86
|
- Log in with OpenAI Codex OAuth.
|
|
87
|
-
- Import one or more account JSON files.
|
|
87
|
+
- Import one or more account JSON files, or validate and import a ZIP archive that contains multiple account JSON files.
|
|
88
88
|
- Switch the active account.
|
|
89
|
-
- Export one account or selected accounts.
|
|
89
|
+
- Export one account or selected accounts, with export status shown on account cards.
|
|
90
|
+
- Delete selected accounts in batches.
|
|
90
91
|
- Apply a saved account to local Codex.
|
|
91
92
|
- Configure the default text model and upstream proxy.
|
|
92
93
|
- Enable automatic account switching when the active API account has exhausted its recorded quota.
|
|
94
|
+
- Tune global quota refresh concurrency for larger account pools.
|
|
93
95
|
- Test `models`, `responses`, `chat.completions`, `images.generations`, and `images.edits`.
|
|
96
|
+
- See a global update banner when a newer version is available, with separate GitHub desktop release and npm update paths.
|
|
94
97
|
|
|
95
98
|

|
|
96
99
|
|
|
@@ -179,9 +182,12 @@ The web console supports:
|
|
|
179
182
|
|
|
180
183
|
- OAuth login.
|
|
181
184
|
- JSON import from a single profile, an array, or a `profiles` bundle.
|
|
185
|
+
- ZIP batch import for archives containing multiple account JSON files, with validation before import.
|
|
182
186
|
- Single-account export.
|
|
183
187
|
- Selected batch export using checkboxes.
|
|
184
|
-
-
|
|
188
|
+
- Export audit status, including export count, latest export time, and export type.
|
|
189
|
+
- Single-account and selected batch deletion.
|
|
190
|
+
- Active-account switching.
|
|
185
191
|
|
|
186
192
|
Exported account JSON includes authentication tokens and should be treated as a secret.
|
|
187
193
|
|
|
@@ -229,6 +235,8 @@ AI_ZERO_TOKEN_HOME=/path/to/home azt start
|
|
|
229
235
|
|
|
230
236
|
The web console settings are persisted in the same local state directory. The quota auto-switch option is stored as `autoSwitch.enabled`; when enabled, the gateway uses the latest saved quota snapshot and moves API traffic away from the active account once that snapshot shows a quota window is exhausted.
|
|
231
237
|
|
|
238
|
+
The global quota refresh concurrency can be configured in the web console settings. The default is `16`; lower it if upstream rate limits increase, or raise it for larger local account pools.
|
|
239
|
+
|
|
232
240
|
The default request body limit is `32 MiB`, which is intended to make JSON base64 image references practical for local image editing. You can override it with:
|
|
233
241
|
|
|
234
242
|
```bash
|
package/README.zh-CN.md
CHANGED
|
@@ -18,10 +18,10 @@ AI Zero Token 是一个本地优先的 OpenAI 兼容网关,用于把 ChatGPT/C
|
|
|
18
18
|
- `POST /v1/images/edits`
|
|
19
19
|
- 支持 ChatGPT/Codex OAuth 登录和本地 token 自动刷新。
|
|
20
20
|
- 管理页支持多账号保存、切换和删除。
|
|
21
|
-
- 支持账号 JSON
|
|
21
|
+
- 支持账号 JSON 导入/导出、ZIP 批量导入、复选框批量导出和导出记录提示。
|
|
22
22
|
- 支持把已保存账号应用到本机 Codex,并自动备份 `~/.codex/auth.json`。
|
|
23
23
|
- 支持 `gpt-image-2` 文生图和 JSON 图生图。
|
|
24
|
-
- 支持 API
|
|
24
|
+
- 支持 API 账号额度耗尽后自动切换到下一个仍有额度的账号,并可配置全局额度刷新并发数。
|
|
25
25
|
- 支持上游代理配置,覆盖 OAuth、模型刷新和接口转发。
|
|
26
26
|
- 模型列表优先读取本机 Codex 模型缓存,并支持手动刷新。
|
|
27
27
|
|
|
@@ -84,13 +84,16 @@ http://127.0.0.1:8787/v1
|
|
|
84
84
|
管理页是推荐入口,可以完成:
|
|
85
85
|
|
|
86
86
|
- OpenAI Codex OAuth 登录。
|
|
87
|
-
- 导入一个或多个账号 JSON
|
|
87
|
+
- 导入一个或多个账号 JSON,或校验并导入包含多个账号 JSON 的 ZIP 压缩包。
|
|
88
88
|
- 切换当前账号。
|
|
89
|
-
-
|
|
89
|
+
- 导出单个账号或勾选导出多个账号,并在账号卡片上显示导出状态。
|
|
90
|
+
- 勾选后批量删除账号。
|
|
90
91
|
- 将已保存账号应用到本机 Codex。
|
|
91
92
|
- 配置默认文本模型和上游代理。
|
|
92
93
|
- 开启当前 API 账号额度耗尽后的自动切换。
|
|
94
|
+
- 为账号池较多的场景调整全局额度刷新并发数。
|
|
93
95
|
- 测试 `models`、`responses`、`chat.completions`、`images.generations`、`images.edits`。
|
|
96
|
+
- 当检测到新版本时显示全局顶部更新提示,分别引导桌面端下载 GitHub Release 和 npm 用户更新包。
|
|
94
97
|
|
|
95
98
|

|
|
96
99
|
|
|
@@ -179,9 +182,12 @@ AI Zero Token 的账号状态默认保存在:
|
|
|
179
182
|
|
|
180
183
|
- OAuth 登录。
|
|
181
184
|
- 从单个 profile、数组或 `profiles` bundle 导入。
|
|
185
|
+
- 从包含多个账号 JSON 的 ZIP 压缩包批量导入,导入前会先校验。
|
|
182
186
|
- 导出单个账号。
|
|
183
187
|
- 使用复选框批量导出已选择账号。
|
|
184
|
-
-
|
|
188
|
+
- 显示导出记录,包括导出次数、最近导出时间和导出方式。
|
|
189
|
+
- 删除单个账号或批量删除已选择账号。
|
|
190
|
+
- 切换当前账号。
|
|
185
191
|
|
|
186
192
|
导出的账号 JSON 包含认证 token,等同于登录凭据,只应在可信环境中使用。
|
|
187
193
|
|
|
@@ -229,6 +235,8 @@ AI_ZERO_TOKEN_HOME=/path/to/home azt start
|
|
|
229
235
|
|
|
230
236
|
管理页里的配置会保存在同一个本地状态目录。额度耗尽自动切换会保存为 `autoSwitch.enabled`;开启后,网关会根据最近一次保存的额度快照判断当前 API 账号是否耗尽,并把 API 流量切到下一个仍有额度的账号。
|
|
231
237
|
|
|
238
|
+
全局额度刷新并发数可以在管理页设置里调整,默认 `16`。账号很多时可以调高;遇到上游限流或失败增多时建议调低。
|
|
239
|
+
|
|
232
240
|
默认请求体上限是 `32 MiB`,用于让 JSON base64 图片在本地图片编辑场景里更实用。可以用下面的环境变量覆盖:
|
|
233
241
|
|
|
234
242
|
```bash
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{a as e,r as t,t as n}from"./jsx-runtime-DqpGtLhh.js";import{t as r}from"./earth-DFdZaQIi.js";import{t as i}from"./refresh-cw-CAAH2rqe.js";import{t as a}from"./search-B2hz41D3.js";import{C as o,_ as s,a as c,b as l,d as u,f as d,g as f,h as p,i as m,m as h,n as g,o as _,p as v,r as y,s as b,t as x,u as S,v as C,w,y as T}from"./profiles-DMOjJORP.js";import{_ as E,d as D,p as O,r as k,x as A}from"./index-DRe-tByu.js";import{t as j}from"./InfoRow-0ULI9iI3.js";var M=e(t(),1),N=n();function P(e){let t=e.config?.codex?.accountId,n=e.profiles.length<=0?``:e.profiles.length===1?`profile-count-1`:e.profiles.length===2?`profile-count-2`:e.profiles.length===3?`profile-count-3`:`profile-count-many`;return(0,N.jsxs)(`section`,{className:`card`,id:`accounts`,children:[(0,N.jsxs)(`div`,{className:`section-head`,children:[(0,N.jsxs)(`div`,{children:[(0,N.jsx)(`h2`,{children:`账号额度预览`}),(0,N.jsx)(`p`,{children:`账号信息采用卡片式布局展示,支持搜索、状态筛选和额度排序。`})]}),(0,N.jsxs)(`div`,{className:`section-actions`,children:[(0,N.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onLocate,children:`定位当前账号`}),(0,N.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onExportSelected,children:`导出所选`}),(0,N.jsx)(`button`,{className:`btn-danger`,type:`button`,onClick:e.onRemoveSelected,disabled:e.selectedCount===0||e.busy===`bulk-remove`,children:`删除所选`}),(0,N.jsx)(`button`,{className:`btn-primary`,type:`button`,onClick:e.onAddAccount,children:`新增账号`}),(0,N.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:e.onRefreshStatus,children:`刷新状态`}),(0,N.jsx)(`button`,{className:`btn-danger`,type:`button`,onClick:e.onClearAccounts,children:`清空账号`})]})]}),(0,N.jsxs)(`div`,{className:`filter-row`,children:[(0,N.jsxs)(`label`,{className:`search-box`,children:[(0,N.jsx)(a,{size:16}),(0,N.jsx)(`input`,{value:e.filter.search,onChange:t=>e.onFilter({...e.filter,search:t.target.value}),placeholder:`搜索邮箱、账号 ID 或 Profile ID`})]}),(0,N.jsxs)(`select`,{className:`control`,value:e.filter.status,onChange:t=>e.onFilter({...e.filter,status:t.target.value}),children:[(0,N.jsx)(`option`,{value:`all`,children:`全部状态`}),(0,N.jsx)(`option`,{value:`active`,children:`使用中`}),(0,N.jsx)(`option`,{value:`healthy`,children:`健康`}),(0,N.jsx)(`option`,{value:`warning`,children:`即将耗尽`}),(0,N.jsx)(`option`,{value:`exhausted`,children:`额度耗尽`}),(0,N.jsx)(`option`,{value:`invalid`,children:`登录失效`}),(0,N.jsx)(`option`,{value:`expired`,children:`已过期`})]}),(0,N.jsxs)(`select`,{className:`control`,value:e.filter.sort,onChange:t=>e.onFilter({...e.filter,sort:t.target.value}),children:[(0,N.jsx)(`option`,{value:`quota-desc`,children:`默认排序`}),(0,N.jsx)(`option`,{value:`latency-asc`,children:`按额度更新时间`}),(0,N.jsx)(`option`,{value:`expiry-asc`,children:`按过期时间`}),(0,N.jsx)(`option`,{value:`name-asc`,children:`按名称排序`}),(0,N.jsx)(`option`,{value:`quota-asc`,children:`按剩余额度升序`}),(0,N.jsx)(`option`,{value:`plan-desc`,children:`按套餐排序`}),(0,N.jsx)(`option`,{value:`email-asc`,children:`按邮箱排序`})]}),(0,N.jsxs)(`span`,{className:`account-selected-count`,children:[`已选择 `,e.selectedCount,` 个`]})]}),(0,N.jsx)(`div`,{className:`account-grid ${n}`,children:e.profiles.length===0?(0,N.jsx)(`div`,{className:`empty-state`,children:`还没有匹配的账号。可以新增账号或调整筛选条件。`}):e.profiles.map(n=>{let a=d(n),o=u(n),p=T(n),y=!!e.expandedProfiles[n.profileId],b=!!(t&&n.accountId===t),S=l(n,b),w=_(n),D=c(n),O=n.exportAudit,k=O?.exported?`已导出 ${O.count} 次`:`未导出`,M=typeof e.busy==`string`&&e.busy.startsWith(`profile:`)&&e.busy.endsWith(n.profileId),P=e.busy===`profile:sync-quota:${n.profileId}`;return(0,N.jsxs)(`article`,{className:`account-card plan-${g(n)} ${w?`is-auth-invalid`:``}`,"data-profile-card":n.profileId,title:w?x(n):void 0,children:[S&&(0,N.jsx)(`span`,{className:`usage-corner ${S.className}`,children:(0,N.jsx)(`span`,{children:S.label})}),(0,N.jsxs)(`div`,{className:`account-head`,children:[(0,N.jsxs)(`div`,{className:`account-title`,children:[(0,N.jsxs)(`div`,{className:`account-name`,children:[(0,N.jsx)(`span`,{className:`avatar`,children:v(n)}),(0,N.jsx)(`strong`,{children:h(n,e.showEmails)}),(0,N.jsx)(`button`,{"aria-label":`刷新额度`,className:`account-icon-btn`,disabled:M,onClick:()=>e.onAction(`sync-quota`,n),title:`刷新额度`,type:`button`,children:P?(0,N.jsx)(E,{className:`spin`,size:14}):(0,N.jsx)(i,{size:14})})]}),(0,N.jsxs)(`div`,{className:`badge-row`,children:[(0,N.jsx)(`span`,{className:`badge brand`,children:m(n)}),(0,N.jsx)(`span`,{className:`badge ${a.tone}`,children:a.label}),(0,N.jsx)(`span`,{className:`badge ${D.ok?`green`:`orange`}`,children:`gpt-image-2`}),(0,N.jsx)(`span`,{className:`badge ${O?.exported?`orange`:`muted`}`,children:k})]})]}),(0,N.jsxs)(`label`,{className:`account-select`,children:[(0,N.jsx)(`input`,{type:`checkbox`,checked:!!e.selectedProfiles[n.profileId],onChange:t=>e.onSelect(n.profileId,t.target.checked)}),(0,N.jsx)(`span`,{children:`选择`})]})]}),(0,N.jsxs)(`div`,{className:`account-metrics`,children:[(0,N.jsx)(L,{label:s(n,`primary`),value:o,tone:f(o)}),(0,N.jsx)(L,{label:s(n,`secondary`),value:p,tone:f(p)})]}),(0,N.jsxs)(`div`,{className:`usage-status-row`,children:[(0,N.jsxs)(`span`,{className:`usage-status ${n.isActive?`is-active`:``}`,children:[(0,N.jsx)(r,{size:14}),(0,N.jsx)(`span`,{children:`API`}),(0,N.jsx)(`span`,{className:`usage-dot ${n.isActive?`active`:``}`}),(0,N.jsx)(`span`,{className:`usage-state-text`,children:n.isActive?`使用中`:`未使用`})]}),(0,N.jsxs)(`span`,{className:`usage-status ${b?`is-active`:``}`,children:[(0,N.jsx)(A,{size:14}),(0,N.jsx)(`span`,{children:`Codex`}),(0,N.jsx)(`span`,{className:`usage-dot ${b?`active`:``}`}),(0,N.jsx)(`span`,{className:`usage-state-text`,children:b?`使用中`:`未使用`})]})]}),(0,N.jsxs)(`div`,{className:`compact-meta-row`,children:[(0,N.jsxs)(`div`,{className:`compact-reset-list`,children:[(0,N.jsxs)(`div`,{className:`compact-meta-item`,children:[(0,N.jsx)(`label`,{children:s(n,`primary`)}),(0,N.jsx)(`strong`,{children:C(n,`primary`)})]}),(0,N.jsxs)(`div`,{className:`compact-meta-item`,children:[(0,N.jsx)(`label`,{children:s(n,`secondary`)}),(0,N.jsx)(`strong`,{children:C(n,`secondary`)})]})]}),(0,N.jsx)(`div`,{className:`compact-meta-actions`,children:(0,N.jsxs)(`button`,{className:`details-toggle ${y?`is-expanded`:``}`,type:`button`,onClick:()=>e.onToggle(n.profileId),children:[(0,N.jsx)(`span`,{children:y?`收起详情`:`查看详情`}),(0,N.jsx)(I,{})]})})]}),y&&(0,N.jsxs)(`div`,{className:`meta-grid`,children:[(0,N.jsx)(j,{label:`套餐`,value:m(n)}),(0,N.jsx)(j,{label:`Account ID`,value:(e.showEmails,n.accountId),code:!0}),(0,N.jsx)(j,{label:`Profile ID`,value:(e.showEmails,n.profileId),code:!0}),(0,N.jsx)(j,{label:`认证状态`,value:x(n)}),(0,N.jsx)(j,{label:`生图能力`,value:D.ok?`gpt-image-2 可用`:D.detail}),(0,N.jsx)(j,{label:`导出记录`,value:F(O)}),(0,N.jsx)(j,{label:`过期时间`,value:n.expiresAt?new Date(n.expiresAt).toLocaleString(`zh-CN`):`-`}),(0,N.jsx)(j,{label:`额度快照`,value:n.quota?.capturedAt?new Date(n.quota.capturedAt).toLocaleString(`zh-CN`):`-`})]}),(0,N.jsxs)(`div`,{className:`account-actions`,children:[(0,N.jsx)(`button`,{className:`btn-secondary ${n.isActive?`is-current`:``}`,type:`button`,onClick:()=>e.onAction(`activate`,n),disabled:n.isActive||M||w,children:w?`网关不可用`:n.isActive?`网关使用中`:`应用网关`}),(0,N.jsx)(`button`,{className:`btn-secondary ${b?`is-current codex`:``}`,type:`button`,onClick:()=>e.onAction(`apply-codex`,n),disabled:b||M||w,children:w?`Codex 不可用`:b?`Codex 使用中`:`应用 Codex`}),(0,N.jsx)(`button`,{className:`btn-secondary`,type:`button`,onClick:()=>e.onAction(`export`,n),disabled:M,children:`导出`}),(0,N.jsx)(`button`,{className:`btn-danger`,type:`button`,onClick:()=>e.onAction(`remove`,n),disabled:M,children:`删除`})]})]},n.profileId)})})]})}function F(e){if(!e?.exported)return`未导出`;let t=e.lastExportKind===`single`?`单账号导出`:e.lastExportKind===`batch`?`批量导出`:`全部导出`;return`${e.count} 次,最近 ${o(e.lastExportedAt)},方式 ${t}`}function I(){return(0,N.jsx)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,"aria-hidden":`true`,children:(0,N.jsx)(`path`,{d:`m6 9 6 6 6-6`})})}function L(e){return(0,N.jsxs)(`div`,{className:`quota-row`,children:[(0,N.jsxs)(`div`,{className:`quota-line`,children:[(0,N.jsxs)(`span`,{children:[e.label,` · 已用 `,e.value,`% / 剩余 `,100-e.value,`%`]}),(0,N.jsxs)(`strong`,{children:[`剩余 `,100-e.value,`%`]})]}),(0,N.jsx)(`div`,{className:`progress-track`,children:(0,N.jsx)(`div`,{className:`progress-bar ${e.tone}`,style:{width:`${e.value}%`}})})]})}function R(e){let[t,n]=(0,M.useState)({}),[r,i]=(0,M.useState)({}),[a,o]=(0,M.useState)({search:``,status:`all`,sort:`quota-desc`}),s=(0,M.useMemo)(()=>{let t=e.config?.profiles?[...e.config.profiles]:[],n=a.search.trim().toLowerCase(),r=t.filter(t=>{let r=[h(t,!0).toLowerCase(),t.accountId,t.profileId,t.email||``].join(` `).toLowerCase(),i=d(t),o=!!(e.codexAccountId&&t.accountId===e.codexAccountId);return n&&!r.includes(n)?!1:a.status===`active`?t.isActive||o:a.status===`healthy`?i.key===`healthy`:a.status===`warning`?i.key===`warning`:a.status===`exhausted`?i.key===`exhausted`:a.status===`expired`?i.key===`expired`:a.status===`invalid`?i.key===`invalid`:!0});return r.sort((t,n)=>{let r=p(t,e.codexAccountId)-p(n,e.codexAccountId);if(r!==0)return r;let i=y(n)-y(t);if(i!==0)return i;let o=S(n)-S(t);return o===0?a.sort===`latency-asc`?(n.quota?.capturedAt||0)-(t.quota?.capturedAt||0):a.sort===`expiry-asc`?(t.expiresAt||2**53-1)-(n.expiresAt||2**53-1):a.sort===`name-asc`?h(t,!0).localeCompare(h(n,!0),`zh-CN`):a.sort===`quota-asc`?100-u(n)-(100-u(t)):a.sort===`plan-desc`?y(n)-y(t):a.sort===`email-asc`?h(t,!0).localeCompare(h(n,!0)):u(n)-u(t):o}),r},[a,e.codexAccountId,e.config?.profiles]),c=Object.values(t).filter(Boolean).length,l=Object.keys(t).filter(e=>t[e]);async function f(t,n){let r=await O(`/_gateway/admin/profiles/export`,{method:`POST`,headers:{"Content-Type":`application/json`},body:w(n?{profileIds:n}:{profileId:t})});D(`ai-zero-token-${n?`profiles-${n.length}`:t||`active`}.json`,r.profile),r.config?e.setConfig(r.config):await e.refreshConfig({silent:!0}),e.setStatus(n?`已导出 ${n.length} 个账号。`:`账号配置已导出。`)}async function m(t,n){if(!(t===`remove`&&!window.confirm(`确认删除 ${h(n,e.showEmails)}?`))){if((t===`activate`||t===`apply-codex`)&&_(n)){e.setStatus(`${h(n,e.showEmails)} 登录已失效,不能应用到${t===`activate`?`网关`:`Codex`}。`);return}if((t===`activate`||t===`apply-codex`)&&b(n)){let r=t===`activate`?`网关`:`Codex`;if(!window.confirm(`${h(n,e.showEmails)} 的额度看起来已耗尽,仍要应用到${r}吗?`))return}if(t===`export`){await f(n.profileId);return}e.setBusy(`profile:${t}:${n.profileId}`);try{let r=await O({activate:`/_gateway/admin/profiles/activate`,"apply-codex":`/_gateway/admin/codex/apply`,"sync-quota":`/_gateway/admin/profiles/sync-quota`,remove:`/_gateway/admin/profiles/remove`}[t],{method:`POST`,headers:{"Content-Type":`application/json`},body:w({profileId:n.profileId})});e.setConfig(`config`in r?r.config:r),e.setStatus(t===`activate`?`已应用到网关。`:t===`apply-codex`?`已应用到本机 Codex。`:t===`sync-quota`?`额度信息已同步。`:`账号已删除。`)}catch(t){e.setStatus(k(t))}finally{e.setBusy(null)}}}async function g(){let t=l;if(t.length===0){e.setStatus(`请先勾选要删除的账号。`);return}let r=e.config?.profiles.filter(e=>t.includes(e.profileId)).slice(0,3).map(t=>h(t,e.showEmails)),i=r?.length?`\n\n${r.join(`
|
|
2
|
+
`)}${t.length>r.length?`\n等 ${t.length} 个账号`:``}`:``;if(window.confirm(`确认删除所选 ${t.length} 个账号?此操作不可撤销。${i}`)){e.setBusy(`bulk-remove`),e.setStatus(`正在删除 ${t.length} 个账号...`);try{let r=await O(`/_gateway/admin/profiles/remove-batch`,{method:`POST`,headers:{"Content-Type":`application/json`},body:w({profileIds:t})});e.setConfig(r),n({}),e.setStatus(`已删除 ${r.removedProfileCount??t.length} 个账号。`)}catch(t){e.setStatus(`删除所选失败: ${k(t)}`)}finally{e.setBusy(null)}}}return(0,N.jsx)(P,{config:e.config,profiles:s,showEmails:e.showEmails,filter:a,selectedProfiles:t,expandedProfiles:r,selectedCount:c,busy:e.busy,onFilter:o,onSelect:(e,t)=>n(n=>({...n,[e]:t})),onToggle:e=>i(t=>({...t,[e]:!t[e]})),onAction:m,onLocate:()=>e.activeProfile&&document.querySelector(`[data-profile-card="${e.activeProfile.profileId}"]`)?.scrollIntoView({behavior:`smooth`,block:`center`}),onExportSelected:()=>{let t=l;if(t.length===0){e.setStatus(`请先勾选要导出的账号。`);return}f(void 0,t).catch(t=>e.setStatus(t instanceof Error?t.message:String(t)))},onRemoveSelected:()=>void g(),onAddAccount:()=>e.setAccountModalOpen(!0),onRefreshStatus:()=>e.refreshConfig({runtime:!0}),onClearAccounts:()=>e.logout()})}export{R as AccountsPage};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{a as e,n as t,r as n,t as r}from"./jsx-runtime-DqpGtLhh.js";import{t as i}from"./server-BrjJPb9D.js";import{b as a,f as o,g as s,n as c,v as l,y as u}from"./index-
|
|
1
|
+
import{a as e,n as t,r as n,t as r}from"./jsx-runtime-DqpGtLhh.js";import{t as i}from"./server-BrjJPb9D.js";import{b as a,f as o,g as s,n as c,v as l,y as u}from"./index-DRe-tByu.js";var d=t(`arrow-right`,[[`path`,{d:`M5 12h14`,key:`1ays0h`}],[`path`,{d:`m12 5 7 7-7 7`,key:`xquz4c`}]]),f=`# AI-Zero-Token Local Gateway Skill
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,n as t,r as n,t as r}from"./jsx-runtime-DqpGtLhh.js";import{t as i}from"./circle-check-ZYtn9GqY.js";import{t as a}from"./upload-CwXb7Q1b.js";import{C as o,S as s}from"./profiles-DMOjJORP.js";import{_ as c,b as l,n as u,o as d,p as f,r as p}from"./index-CX8e0-BB.js";var m=t(`chevron-down`,[[`path`,{d:`m6 9 6 6 6-6`,key:`qrunsl`}]]),h=t(`link-2`,[[`path`,{d:`M9 17H7A5 5 0 0 1 7 7h2`,key:`8i5ue5`}],[`path`,{d:`M15 7h2a5 5 0 1 1 0 10h-2`,key:`1b9ql8`}],[`line`,{x1:`8`,x2:`16`,y1:`12`,y2:`12`,key:`1jonct`}]]),g=t(`trash-2`,[[`path`,{d:`M10 11v6`,key:`nco0om`}],[`path`,{d:`M14 11v6`,key:`outv1u`}],[`path`,{d:`M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6`,key:`miytrc`}],[`path`,{d:`M3 6h18`,key:`d0wm0j`}],[`path`,{d:`M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2`,key:`e791ji`}]]),_=e(n(),1),v=r();function y(e,t){return e?`${e.owner}/${e.repository} · ${e.branch}`:t?.hasToken?`已保存,待验证`:`尚未保存 token`}function b(e,t){if(!e)return t;try{let t=JSON.parse(e);if(typeof t.error?.message==`string`&&t.error.message.trim())return t.error.message}catch{}return e||t}async function x(e,t){let n=await d(e);return new Promise((r,i)=>{let a=new XMLHttpRequest;a.open(`POST`,`/_gateway/image-bed/upload`),a.setRequestHeader(`Content-Type`,`application/json`),a.responseType=`text`,a.upload.onprogress=e=>{e.lengthComputable&&e.total>0&&t(e.loaded/e.total*100)},a.onerror=()=>i(Error(`上传请求失败。`)),a.onload=()=>{let e=a.responseText||``;if(a.status<200||a.status>=300){i(Error(b(e,`HTTP ${a.status}`)));return}try{r(JSON.parse(e))}catch{i(Error(`上传响应解析失败。`))}},a.send(JSON.stringify({filename:e.name,dataUrl:n}))})}function S(e){let[t,n]=(0,_.useState)(null),[r,d]=(0,_.useState)(``),[b,S]=(0,_.useState)(!0),[C,w]=(0,_.useState)(null),[T,E]=(0,_.useState)([]),[D,O]=(0,_.useState)(12),[k,A]=(0,_.useState)(`正在读取图床配置...`),[j,M]=(0,_.useState)(!1),[N,P]=(0,_.useState)(null),[F,I]=(0,_.useState)(null),L=(0,_.useRef)(null),R=(0,_.useMemo)(()=>y(C,t),[C,t]),z=(0,_.useMemo)(()=>T.slice(0,D),[T,D]);(0,_.useEffect)(()=>{let e=!0;async function t(){try{let t=await f(`/_gateway/image-bed/config`);if(!e)return;n(t),S(!t.hasToken),A(t.hasToken?`GitHub token 已保存,正在验证连接...`:`请先保存一个 GitHub token。`),t.hasToken&&await V(!1)}catch(t){if(!e)return;A(p(t))}try{let t=await f(`/_gateway/image-bed/history?limit=100`);if(!e)return;E(t.items)}catch(t){if(!e)return;A(e=>`${e} ${p(t)}`)}}return t(),()=>{e=!1}},[]);async function B(){E((await f(`/_gateway/image-bed/history?limit=100`)).items)}async function V(t=!0){t&&e.setBusy(`image-bed-save`);try{let t=await f(`/_gateway/image-bed/validate`,{method:`POST`});return w(t),A(`已连接到 ${t.owner}/${t.repository},默认分支 ${t.branch}。`),e.setStatus(`图床连接正常:${t.owner}/${t.repository}`),t}catch(t){let n=p(t);throw w(null),A(`连接失败: ${n}`),e.setStatus(n),t}finally{t&&e.setBusy(null)}}async function H(){let t=r.trim();if(!t){A(`请先填写 GitHub token。`);return}e.setBusy(`image-bed-save`);try{n(await f(`/_gateway/image-bed/config`,{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify({token:t})})),d(``),S(!1),A(`GitHub token 已保存,正在验证...`),e.setStatus(`GitHub token 已保存。`);try{await V(!1),A(`GitHub token 已保存并验证。`)}catch(e){S(!0),A(`已保存,但验证失败: ${p(e)}`)}}catch(t){let n=p(t);A(`保存失败: ${n}`),e.setStatus(n)}finally{e.setBusy(null)}}async function U(){e.setBusy(`image-bed-save`);try{n(await f(`/_gateway/image-bed/config`,{method:`DELETE`})),w(null),d(``),S(!0),A(`GitHub token 已清除。`),e.setStatus(`GitHub token 已清除。`)}catch(t){let n=p(t);A(`清除失败: ${n}`),e.setStatus(n)}finally{e.setBusy(null)}}async function W(){S(!0),d(``)}async function G(e,t){if(!e.type.startsWith(`image/`))throw Error(`文件 ${e.name} 不是图片。`);return x(e,t)}async function K(n){let r=Array.from(n).filter(e=>e.type.startsWith(`image/`));if(r.length===0){A(`请选择图片文件。`);return}if(!t?.hasToken){A(`请先保存 GitHub token,再上传图片。`);return}e.setBusy(`image-bed-upload`);try{let t=[];for(let n=0;n<r.length;n+=1){let i=r[n];P({phase:`reading`,fileName:i.name,fileIndex:n+1,totalFiles:r.length,percent:n/r.length*100}),A(`正在读取 ${n+1}/${r.length}: ${i.name}`),e.setStatus(`正在读取 ${n+1}/${r.length}: ${i.name}`);let a=await G(i,e=>{P({phase:`uploading`,fileName:i.name,fileIndex:n+1,totalFiles:r.length,percent:(n+e/100)/r.length*100})}),o={id:a.path,createdAt:Date.now(),filename:a.filename,path:a.path,url:a.url,htmlUrl:a.htmlUrl,downloadUrl:a.downloadUrl,owner:a.owner,repository:a.repository,branch:a.branch,size:a.size,mimeType:a.mimeType,previewUrl:a.url,sha:a.sha};t.unshift(o),E(e=>[o,...e.filter(e=>e.id!==o.id)].slice(0,100))}O(e=>Math.max(e,Math.min(12,T.length+t.length))),A(`已上传 ${r.length} 张图片。`),e.setStatus(`已上传 ${r.length} 张图片。`),await B()}catch(t){let n=p(t);A(`上传失败: ${n}`),e.setStatus(n)}finally{P(null),e.setBusy(null)}}async function q(t){let n=await u(t)?`链接已复制。`:`链接复制失败。`;A(n),e.setStatus(n)}function J(e){let t=Array.from(e.currentTarget.files||[]);e.currentTarget.value=``,t.length!==0&&K(t)}async function Y(){await f(`/_gateway/image-bed/history`,{method:`DELETE`}),E([]),O(12),A(`历史记录已清空。`),e.setStatus(`历史记录已清空。`)}async function X(t){if(window.confirm(`确认从 GitHub 仓库删除 ${t.filename} 吗?删除后原链接会失效。`)){I(t.id),e.setBusy(`image-bed-delete`);try{E((await f(`/_gateway/image-bed/history/${encodeURIComponent(t.id)}`,{method:`DELETE`})).items),A(`已删除 ${t.filename}。`),e.setStatus(`已从图床删除 ${t.filename}。`)}catch(t){let n=p(t);A(`删除失败: ${n}`),e.setStatus(n)}finally{I(null),e.setBusy(null)}}}let Z=e.busy===`image-bed-save`||e.busy===`image-bed-upload`||e.busy===`image-bed-delete`,Q=T[0];return(0,v.jsxs)(`section`,{className:`image-bed-page`,children:[(0,v.jsxs)(`div`,{className:`image-bed-workbench`,children:[(0,v.jsxs)(`section`,{className:`image-bed-upload-panel ${j?`is-dragging`:``}`,children:[(0,v.jsxs)(`div`,{className:`image-bed-upload-copy`,children:[(0,v.jsx)(`span`,{children:t?.hasToken?`GitHub 图床已准备`:`先配置 GitHub token`}),(0,v.jsx)(`h2`,{children:`拖入图片,直接拿公网链接`}),(0,v.jsxs)(`p`,{children:[`文件会写入公开仓库 `,(0,v.jsx)(`strong`,{children:t?.repository||`azt-img-bed`}),` 的 `,(0,v.jsx)(`strong`,{children:t?.pathPrefix||`images`}),` 目录,上传结果会保存在本机历史里。`]})]}),(0,v.jsxs)(`div`,{className:`upload-dropzone ${j?`is-dragging`:``} ${t?.hasToken?``:`is-disabled`}`,role:`button`,tabIndex:0,onClick:()=>{if(!t?.hasToken){A(`请先保存 GitHub token,再上传图片。`);return}L.current&&(L.current.value=``,L.current.click())},onKeyDown:e=>{if(e.key===`Enter`||e.key===` `){if(e.preventDefault(),!t?.hasToken){A(`请先保存 GitHub token,再上传图片。`);return}L.current&&(L.current.value=``,L.current.click())}},onDragOver:e=>{e.preventDefault(),M(!0)},onDragLeave:()=>M(!1),onDrop:e=>{e.preventDefault(),M(!1),K(e.dataTransfer.files)},children:[(0,v.jsx)(`input`,{ref:L,className:`upload-dropzone-input`,type:`file`,accept:`image/*`,multiple:!0,onChange:J}),(0,v.jsx)(`div`,{className:`upload-dropzone-icon`,children:(0,v.jsx)(a,{size:22})}),(0,v.jsx)(`strong`,{children:`选择图片或拖拽到这里`}),(0,v.jsx)(`span`,{children:t?.hasToken?`支持批量上传,完成后自动生成可访问链接。`:`保存并验证 token 后即可上传。`})]}),N?(0,v.jsxs)(`div`,{className:`upload-progress-block`,"aria-live":`polite`,children:[(0,v.jsxs)(`div`,{className:`upload-progress-head`,children:[(0,v.jsx)(`strong`,{children:N.phase===`reading`?`正在读取`:`正在上传`}),(0,v.jsxs)(`span`,{children:[N.fileIndex,`/`,N.totalFiles,` · `,N.fileName]})]}),(0,v.jsx)(`div`,{className:`upload-progress-track`,role:`progressbar`,"aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":Math.round(N.percent),children:(0,v.jsx)(`div`,{className:`upload-progress-fill`,style:{width:`${Math.max(4,Math.min(100,N.percent))}%`}})})]}):Q?(0,v.jsxs)(`div`,{className:`image-bed-latest`,children:[(0,v.jsx)(`button`,{type:`button`,className:`image-bed-latest-preview`,onClick:()=>void q(Q.url),title:`点击复制链接`,children:(0,v.jsx)(`img`,{loading:`lazy`,decoding:`async`,src:Q.previewUrl,alt:Q.filename})}),(0,v.jsxs)(`div`,{className:`image-bed-latest-info`,children:[(0,v.jsx)(`span`,{children:`最近上传`}),(0,v.jsx)(`strong`,{children:Q.filename}),(0,v.jsx)(`code`,{children:Q.url})]}),(0,v.jsxs)(`button`,{className:`btn-primary`,type:`button`,onClick:()=>void q(Q.url),children:[(0,v.jsx)(l,{size:16}),`复制链接`]})]}):(0,v.jsx)(`div`,{className:`image-bed-upload-note`,children:k})]}),(0,v.jsxs)(`aside`,{className:`image-bed-side-stack`,children:[(0,v.jsxs)(`section`,{className:`image-bed-side-card`,children:[(0,v.jsxs)(`div`,{className:`image-bed-section-head`,children:[(0,v.jsx)(`h4`,{children:`连接`}),(0,v.jsx)(`span`,{className:`image-bed-status-dot ${C?`is-ok`:t?.hasToken?`is-warn`:``}`})]}),(0,v.jsx)(`strong`,{className:`image-bed-connection-label`,children:R}),(0,v.jsx)(`p`,{children:k})]}),(0,v.jsxs)(`section`,{className:`image-bed-side-card`,children:[(0,v.jsxs)(`div`,{className:`image-bed-section-head`,children:[(0,v.jsx)(`h4`,{children:`Token`}),!b&&t?.hasToken&&(0,v.jsx)(`button`,{className:`image-bed-link-button`,type:`button`,onClick:()=>void W(),children:`修改`})]}),b||!t?.hasToken?(0,v.jsxs)(v.Fragment,{children:[(0,v.jsxs)(`label`,{className:`field`,children:[(0,v.jsx)(`span`,{children:`GitHub token`}),(0,v.jsx)(`input`,{className:`input`,value:r,onChange:e=>d(e.target.value),placeholder:`github_pat_...`,spellCheck:!1,autoComplete:`off`}),(0,v.jsxs)(`p`,{className:`image-bed-token-hint`,children:[`推荐使用 fine-grained token,只给公开仓库 `,(0,v.jsx)(`code`,{children:`azt-img-bed`}),` 的 Contents 读写权限。`]})]}),(0,v.jsxs)(`div`,{className:`image-bed-token-actions`,children:[(0,v.jsxs)(`button`,{className:`btn-primary`,type:`button`,onClick:()=>void H(),disabled:Z||!r.trim(),children:[e.busy===`image-bed-save`?(0,v.jsx)(c,{className:`spin`,size:16}):(0,v.jsx)(i,{size:16}),`保存并验证`]}),(0,v.jsxs)(`button`,{className:`btn-secondary`,type:`button`,onClick:U,disabled:!t?.hasToken,children:[(0,v.jsx)(g,{size:16}),`清除`]})]})]}):(0,v.jsxs)(`div`,{className:`image-bed-token-summary`,children:[(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`span`,{children:`已保存到本机`}),(0,v.jsx)(`strong`,{children:`GitHub token`})]}),(0,v.jsx)(`button`,{className:`btn-secondary icon-only`,type:`button`,onClick:U,title:`清除 Token`,children:(0,v.jsx)(g,{size:16})})]})]}),(0,v.jsxs)(`section`,{className:`image-bed-side-card image-bed-target-card`,children:[(0,v.jsx)(`h4`,{children:`目标`}),(0,v.jsxs)(`div`,{className:`image-bed-target-list`,children:[(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`span`,{children:`仓库`}),(0,v.jsx)(`strong`,{children:t?.repository||`azt-img-bed`})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`span`,{children:`分支`}),(0,v.jsx)(`strong`,{children:t?.defaultBranch||`auto`})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`span`,{children:`目录`}),(0,v.jsx)(`strong`,{children:t?.pathPrefix||`images`})]})]})]})]})]}),(0,v.jsxs)(`section`,{className:`image-bed-gallery-section`,children:[(0,v.jsxs)(`div`,{className:`image-bed-section-head`,children:[(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`h4`,{children:`上传历史`}),(0,v.jsx)(`p`,{children:`本机保存最近 100 条,只加载当前可见缩略图。`})]}),(0,v.jsx)(`button`,{className:`btn-secondary icon-only`,type:`button`,onClick:()=>void Y(),disabled:T.length===0,title:`清空历史`,children:(0,v.jsx)(g,{size:16})})]}),T.length===0?(0,v.jsx)(`div`,{className:`image-bed-empty`,children:`还没有上传记录。上传完成后,这里会以图库形式展示预览和链接。`}):(0,v.jsx)(`div`,{className:`image-bed-results-grid`,children:z.map(e=>(0,v.jsxs)(`figure`,{className:`image-bed-result-card`,children:[(0,v.jsx)(`button`,{type:`button`,className:`image-bed-preview-button`,onClick:()=>void q(e.url),title:`点击复制链接`,children:(0,v.jsx)(`img`,{loading:`lazy`,decoding:`async`,src:e.previewUrl,alt:e.filename})}),(0,v.jsxs)(`figcaption`,{children:[(0,v.jsx)(`strong`,{children:e.filename}),(0,v.jsxs)(`span`,{children:[s(e.size),` · `,e.mimeType]}),(0,v.jsx)(`code`,{children:o(e.createdAt)})]}),(0,v.jsxs)(`div`,{className:`image-bed-result-actions`,children:[(0,v.jsx)(`button`,{className:`image-bed-card-action`,type:`button`,onClick:()=>void q(e.url),title:`复制链接`,"aria-label":`复制链接`,children:(0,v.jsx)(l,{size:15})}),(0,v.jsx)(`a`,{className:`image-bed-card-action`,href:e.url,target:`_blank`,rel:`noreferrer`,title:`打开原图`,"aria-label":`打开原图`,children:(0,v.jsx)(h,{size:15})}),(0,v.jsx)(`button`,{className:`image-bed-card-action is-danger`,type:`button`,onClick:()=>void X(e),title:`删除图床文件`,"aria-label":`删除图床文件`,disabled:F===e.id,children:F===e.id?(0,v.jsx)(c,{className:`spin`,size:15}):(0,v.jsx)(g,{size:15})})]})]},e.path))}),T.length>D&&(0,v.jsxs)(`button`,{className:`btn-secondary image-bed-load-more`,type:`button`,onClick:()=>O(e=>Math.min(e+12,T.length)),children:[(0,v.jsx)(m,{size:16}),`加载更多`]})]}),(0,v.jsxs)(`details`,{className:`image-bed-help-section`,children:[(0,v.jsx)(`summary`,{children:`GitHub token 创建说明`}),(0,v.jsxs)(`p`,{className:`image-bed-help-intro`,children:[`这个图床固定使用当前 GitHub 账号下的公开仓库 `,(0,v.jsx)(`code`,{children:`azt-img-bed`}),`,图片会写入 `,(0,v.jsx)(`code`,{children:`images`}),` 目录并返回 raw.githubusercontent.com 原图链接。`]}),(0,v.jsxs)(`ol`,{className:`image-bed-steps`,children:[(0,v.jsxs)(`li`,{children:[(0,v.jsx)(`strong`,{children:`先建公开仓库。`}),` 在 GitHub 新建仓库 `,(0,v.jsx)(`code`,{children:`azt-img-bed`}),`,Visibility 选 `,(0,v.jsx)(`strong`,{children:`Public`}),`。如果仓库已经存在,确认它属于这个 token 对应的个人账号,并且不是 Private。`]}),(0,v.jsxs)(`li`,{children:[(0,v.jsx)(`strong`,{children:`创建 fine-grained token。`}),` 打开 GitHub 的 `,(0,v.jsx)(`a`,{href:`https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens`,target:`_blank`,rel:`noreferrer`,children:`Personal access tokens`}),` 页面,选择 `,(0,v.jsx)(`strong`,{children:`Fine-grained tokens`}),`,点 `,(0,v.jsx)(`strong`,{children:`Generate new token`}),`。`]}),(0,v.jsxs)(`li`,{children:[(0,v.jsx)(`strong`,{children:`限制仓库范围。`}),` Token name 可填 `,(0,v.jsx)(`code`,{children:`AI Zero Token image bed`}),`,Expiration 按需设置,Resource owner 选自己的账号;Repository access 选择 `,(0,v.jsx)(`strong`,{children:`Only select repositories`}),`,只勾选 `,(0,v.jsx)(`code`,{children:`azt-img-bed`}),`。`]}),(0,v.jsxs)(`li`,{children:[(0,v.jsx)(`strong`,{children:`给最小权限。`}),` 在 Repository permissions 里把 `,(0,v.jsx)(`strong`,{children:`Contents`}),` 设置为 `,(0,v.jsx)(`strong`,{children:`Read and write`}),`;`,(0,v.jsx)(`strong`,{children:`Metadata`}),` 保持默认 Read-only 即可,其它权限不需要打开。`]}),(0,v.jsxs)(`li`,{children:[(0,v.jsx)(`strong`,{children:`复制并验证。`}),` GitHub 只会展示一次生成后的 token,复制后回到这里粘贴,点击 `,(0,v.jsx)(`strong`,{children:`保存并验证`}),`。验证通过后就可以拖图上传。`]})]}),(0,v.jsxs)(`dl`,{className:`image-bed-help-facts`,children:[(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`dt`,{children:`Token 格式`}),(0,v.jsxs)(`dd`,{children:[`fine-grained token 通常以 `,(0,v.jsx)(`code`,{children:`github_pat_`}),` 开头。`]})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`dt`,{children:`验证失败`}),(0,v.jsxs)(`dd`,{children:[`如果提示未找到仓库,优先检查仓库名是否为 `,(0,v.jsx)(`code`,{children:`azt-img-bed`}),`、仓库是否 Public、Repository access 是否选中了这个仓库。`]})]})]}),(0,v.jsx)(`p`,{className:`hint`,children:`Token 要像密码一样保管,不要发给别人。这个页面只会把它保存到你本机状态目录。`})]})]})}export{S as ImageBedPage};
|
|
1
|
+
import{a as e,n as t,r as n,t as r}from"./jsx-runtime-DqpGtLhh.js";import{t as i}from"./circle-check-ZYtn9GqY.js";import{t as a}from"./upload-CwXb7Q1b.js";import{C as o,S as s}from"./profiles-DMOjJORP.js";import{_ as c,b as l,n as u,o as d,p as f,r as p}from"./index-DRe-tByu.js";var m=t(`chevron-down`,[[`path`,{d:`m6 9 6 6 6-6`,key:`qrunsl`}]]),h=t(`link-2`,[[`path`,{d:`M9 17H7A5 5 0 0 1 7 7h2`,key:`8i5ue5`}],[`path`,{d:`M15 7h2a5 5 0 1 1 0 10h-2`,key:`1b9ql8`}],[`line`,{x1:`8`,x2:`16`,y1:`12`,y2:`12`,key:`1jonct`}]]),g=t(`trash-2`,[[`path`,{d:`M10 11v6`,key:`nco0om`}],[`path`,{d:`M14 11v6`,key:`outv1u`}],[`path`,{d:`M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6`,key:`miytrc`}],[`path`,{d:`M3 6h18`,key:`d0wm0j`}],[`path`,{d:`M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2`,key:`e791ji`}]]),_=e(n(),1),v=r();function y(e,t){return e?`${e.owner}/${e.repository} · ${e.branch}`:t?.hasToken?`已保存,待验证`:`尚未保存 token`}function b(e,t){if(!e)return t;try{let t=JSON.parse(e);if(typeof t.error?.message==`string`&&t.error.message.trim())return t.error.message}catch{}return e||t}async function x(e,t){let n=await d(e);return new Promise((r,i)=>{let a=new XMLHttpRequest;a.open(`POST`,`/_gateway/image-bed/upload`),a.setRequestHeader(`Content-Type`,`application/json`),a.responseType=`text`,a.upload.onprogress=e=>{e.lengthComputable&&e.total>0&&t(e.loaded/e.total*100)},a.onerror=()=>i(Error(`上传请求失败。`)),a.onload=()=>{let e=a.responseText||``;if(a.status<200||a.status>=300){i(Error(b(e,`HTTP ${a.status}`)));return}try{r(JSON.parse(e))}catch{i(Error(`上传响应解析失败。`))}},a.send(JSON.stringify({filename:e.name,dataUrl:n}))})}function S(e){let[t,n]=(0,_.useState)(null),[r,d]=(0,_.useState)(``),[b,S]=(0,_.useState)(!0),[C,w]=(0,_.useState)(null),[T,E]=(0,_.useState)([]),[D,O]=(0,_.useState)(12),[k,A]=(0,_.useState)(`正在读取图床配置...`),[j,M]=(0,_.useState)(!1),[N,P]=(0,_.useState)(null),[F,I]=(0,_.useState)(null),L=(0,_.useRef)(null),R=(0,_.useMemo)(()=>y(C,t),[C,t]),z=(0,_.useMemo)(()=>T.slice(0,D),[T,D]);(0,_.useEffect)(()=>{let e=!0;async function t(){try{let t=await f(`/_gateway/image-bed/config`);if(!e)return;n(t),S(!t.hasToken),A(t.hasToken?`GitHub token 已保存,正在验证连接...`:`请先保存一个 GitHub token。`),t.hasToken&&await V(!1)}catch(t){if(!e)return;A(p(t))}try{let t=await f(`/_gateway/image-bed/history?limit=100`);if(!e)return;E(t.items)}catch(t){if(!e)return;A(e=>`${e} ${p(t)}`)}}return t(),()=>{e=!1}},[]);async function B(){E((await f(`/_gateway/image-bed/history?limit=100`)).items)}async function V(t=!0){t&&e.setBusy(`image-bed-save`);try{let t=await f(`/_gateway/image-bed/validate`,{method:`POST`});return w(t),A(`已连接到 ${t.owner}/${t.repository},默认分支 ${t.branch}。`),e.setStatus(`图床连接正常:${t.owner}/${t.repository}`),t}catch(t){let n=p(t);throw w(null),A(`连接失败: ${n}`),e.setStatus(n),t}finally{t&&e.setBusy(null)}}async function H(){let t=r.trim();if(!t){A(`请先填写 GitHub token。`);return}e.setBusy(`image-bed-save`);try{n(await f(`/_gateway/image-bed/config`,{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify({token:t})})),d(``),S(!1),A(`GitHub token 已保存,正在验证...`),e.setStatus(`GitHub token 已保存。`);try{await V(!1),A(`GitHub token 已保存并验证。`)}catch(e){S(!0),A(`已保存,但验证失败: ${p(e)}`)}}catch(t){let n=p(t);A(`保存失败: ${n}`),e.setStatus(n)}finally{e.setBusy(null)}}async function U(){e.setBusy(`image-bed-save`);try{n(await f(`/_gateway/image-bed/config`,{method:`DELETE`})),w(null),d(``),S(!0),A(`GitHub token 已清除。`),e.setStatus(`GitHub token 已清除。`)}catch(t){let n=p(t);A(`清除失败: ${n}`),e.setStatus(n)}finally{e.setBusy(null)}}async function W(){S(!0),d(``)}async function G(e,t){if(!e.type.startsWith(`image/`))throw Error(`文件 ${e.name} 不是图片。`);return x(e,t)}async function K(n){let r=Array.from(n).filter(e=>e.type.startsWith(`image/`));if(r.length===0){A(`请选择图片文件。`);return}if(!t?.hasToken){A(`请先保存 GitHub token,再上传图片。`);return}e.setBusy(`image-bed-upload`);try{let t=[];for(let n=0;n<r.length;n+=1){let i=r[n];P({phase:`reading`,fileName:i.name,fileIndex:n+1,totalFiles:r.length,percent:n/r.length*100}),A(`正在读取 ${n+1}/${r.length}: ${i.name}`),e.setStatus(`正在读取 ${n+1}/${r.length}: ${i.name}`);let a=await G(i,e=>{P({phase:`uploading`,fileName:i.name,fileIndex:n+1,totalFiles:r.length,percent:(n+e/100)/r.length*100})}),o={id:a.path,createdAt:Date.now(),filename:a.filename,path:a.path,url:a.url,htmlUrl:a.htmlUrl,downloadUrl:a.downloadUrl,owner:a.owner,repository:a.repository,branch:a.branch,size:a.size,mimeType:a.mimeType,previewUrl:a.url,sha:a.sha};t.unshift(o),E(e=>[o,...e.filter(e=>e.id!==o.id)].slice(0,100))}O(e=>Math.max(e,Math.min(12,T.length+t.length))),A(`已上传 ${r.length} 张图片。`),e.setStatus(`已上传 ${r.length} 张图片。`),await B()}catch(t){let n=p(t);A(`上传失败: ${n}`),e.setStatus(n)}finally{P(null),e.setBusy(null)}}async function q(t){let n=await u(t)?`链接已复制。`:`链接复制失败。`;A(n),e.setStatus(n)}function J(e){let t=Array.from(e.currentTarget.files||[]);e.currentTarget.value=``,t.length!==0&&K(t)}async function Y(){await f(`/_gateway/image-bed/history`,{method:`DELETE`}),E([]),O(12),A(`历史记录已清空。`),e.setStatus(`历史记录已清空。`)}async function X(t){if(window.confirm(`确认从 GitHub 仓库删除 ${t.filename} 吗?删除后原链接会失效。`)){I(t.id),e.setBusy(`image-bed-delete`);try{E((await f(`/_gateway/image-bed/history/${encodeURIComponent(t.id)}`,{method:`DELETE`})).items),A(`已删除 ${t.filename}。`),e.setStatus(`已从图床删除 ${t.filename}。`)}catch(t){let n=p(t);A(`删除失败: ${n}`),e.setStatus(n)}finally{I(null),e.setBusy(null)}}}let Z=e.busy===`image-bed-save`||e.busy===`image-bed-upload`||e.busy===`image-bed-delete`,Q=T[0];return(0,v.jsxs)(`section`,{className:`image-bed-page`,children:[(0,v.jsxs)(`div`,{className:`image-bed-workbench`,children:[(0,v.jsxs)(`section`,{className:`image-bed-upload-panel ${j?`is-dragging`:``}`,children:[(0,v.jsxs)(`div`,{className:`image-bed-upload-copy`,children:[(0,v.jsx)(`span`,{children:t?.hasToken?`GitHub 图床已准备`:`先配置 GitHub token`}),(0,v.jsx)(`h2`,{children:`拖入图片,直接拿公网链接`}),(0,v.jsxs)(`p`,{children:[`文件会写入公开仓库 `,(0,v.jsx)(`strong`,{children:t?.repository||`azt-img-bed`}),` 的 `,(0,v.jsx)(`strong`,{children:t?.pathPrefix||`images`}),` 目录,上传结果会保存在本机历史里。`]})]}),(0,v.jsxs)(`div`,{className:`upload-dropzone ${j?`is-dragging`:``} ${t?.hasToken?``:`is-disabled`}`,role:`button`,tabIndex:0,onClick:()=>{if(!t?.hasToken){A(`请先保存 GitHub token,再上传图片。`);return}L.current&&(L.current.value=``,L.current.click())},onKeyDown:e=>{if(e.key===`Enter`||e.key===` `){if(e.preventDefault(),!t?.hasToken){A(`请先保存 GitHub token,再上传图片。`);return}L.current&&(L.current.value=``,L.current.click())}},onDragOver:e=>{e.preventDefault(),M(!0)},onDragLeave:()=>M(!1),onDrop:e=>{e.preventDefault(),M(!1),K(e.dataTransfer.files)},children:[(0,v.jsx)(`input`,{ref:L,className:`upload-dropzone-input`,type:`file`,accept:`image/*`,multiple:!0,onChange:J}),(0,v.jsx)(`div`,{className:`upload-dropzone-icon`,children:(0,v.jsx)(a,{size:22})}),(0,v.jsx)(`strong`,{children:`选择图片或拖拽到这里`}),(0,v.jsx)(`span`,{children:t?.hasToken?`支持批量上传,完成后自动生成可访问链接。`:`保存并验证 token 后即可上传。`})]}),N?(0,v.jsxs)(`div`,{className:`upload-progress-block`,"aria-live":`polite`,children:[(0,v.jsxs)(`div`,{className:`upload-progress-head`,children:[(0,v.jsx)(`strong`,{children:N.phase===`reading`?`正在读取`:`正在上传`}),(0,v.jsxs)(`span`,{children:[N.fileIndex,`/`,N.totalFiles,` · `,N.fileName]})]}),(0,v.jsx)(`div`,{className:`upload-progress-track`,role:`progressbar`,"aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":Math.round(N.percent),children:(0,v.jsx)(`div`,{className:`upload-progress-fill`,style:{width:`${Math.max(4,Math.min(100,N.percent))}%`}})})]}):Q?(0,v.jsxs)(`div`,{className:`image-bed-latest`,children:[(0,v.jsx)(`button`,{type:`button`,className:`image-bed-latest-preview`,onClick:()=>void q(Q.url),title:`点击复制链接`,children:(0,v.jsx)(`img`,{loading:`lazy`,decoding:`async`,src:Q.previewUrl,alt:Q.filename})}),(0,v.jsxs)(`div`,{className:`image-bed-latest-info`,children:[(0,v.jsx)(`span`,{children:`最近上传`}),(0,v.jsx)(`strong`,{children:Q.filename}),(0,v.jsx)(`code`,{children:Q.url})]}),(0,v.jsxs)(`button`,{className:`btn-primary`,type:`button`,onClick:()=>void q(Q.url),children:[(0,v.jsx)(l,{size:16}),`复制链接`]})]}):(0,v.jsx)(`div`,{className:`image-bed-upload-note`,children:k})]}),(0,v.jsxs)(`aside`,{className:`image-bed-side-stack`,children:[(0,v.jsxs)(`section`,{className:`image-bed-side-card`,children:[(0,v.jsxs)(`div`,{className:`image-bed-section-head`,children:[(0,v.jsx)(`h4`,{children:`连接`}),(0,v.jsx)(`span`,{className:`image-bed-status-dot ${C?`is-ok`:t?.hasToken?`is-warn`:``}`})]}),(0,v.jsx)(`strong`,{className:`image-bed-connection-label`,children:R}),(0,v.jsx)(`p`,{children:k})]}),(0,v.jsxs)(`section`,{className:`image-bed-side-card`,children:[(0,v.jsxs)(`div`,{className:`image-bed-section-head`,children:[(0,v.jsx)(`h4`,{children:`Token`}),!b&&t?.hasToken&&(0,v.jsx)(`button`,{className:`image-bed-link-button`,type:`button`,onClick:()=>void W(),children:`修改`})]}),b||!t?.hasToken?(0,v.jsxs)(v.Fragment,{children:[(0,v.jsxs)(`label`,{className:`field`,children:[(0,v.jsx)(`span`,{children:`GitHub token`}),(0,v.jsx)(`input`,{className:`input`,value:r,onChange:e=>d(e.target.value),placeholder:`github_pat_...`,spellCheck:!1,autoComplete:`off`}),(0,v.jsxs)(`p`,{className:`image-bed-token-hint`,children:[`推荐使用 fine-grained token,只给公开仓库 `,(0,v.jsx)(`code`,{children:`azt-img-bed`}),` 的 Contents 读写权限。`]})]}),(0,v.jsxs)(`div`,{className:`image-bed-token-actions`,children:[(0,v.jsxs)(`button`,{className:`btn-primary`,type:`button`,onClick:()=>void H(),disabled:Z||!r.trim(),children:[e.busy===`image-bed-save`?(0,v.jsx)(c,{className:`spin`,size:16}):(0,v.jsx)(i,{size:16}),`保存并验证`]}),(0,v.jsxs)(`button`,{className:`btn-secondary`,type:`button`,onClick:U,disabled:!t?.hasToken,children:[(0,v.jsx)(g,{size:16}),`清除`]})]})]}):(0,v.jsxs)(`div`,{className:`image-bed-token-summary`,children:[(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`span`,{children:`已保存到本机`}),(0,v.jsx)(`strong`,{children:`GitHub token`})]}),(0,v.jsx)(`button`,{className:`btn-secondary icon-only`,type:`button`,onClick:U,title:`清除 Token`,children:(0,v.jsx)(g,{size:16})})]})]}),(0,v.jsxs)(`section`,{className:`image-bed-side-card image-bed-target-card`,children:[(0,v.jsx)(`h4`,{children:`目标`}),(0,v.jsxs)(`div`,{className:`image-bed-target-list`,children:[(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`span`,{children:`仓库`}),(0,v.jsx)(`strong`,{children:t?.repository||`azt-img-bed`})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`span`,{children:`分支`}),(0,v.jsx)(`strong`,{children:t?.defaultBranch||`auto`})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`span`,{children:`目录`}),(0,v.jsx)(`strong`,{children:t?.pathPrefix||`images`})]})]})]})]})]}),(0,v.jsxs)(`section`,{className:`image-bed-gallery-section`,children:[(0,v.jsxs)(`div`,{className:`image-bed-section-head`,children:[(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`h4`,{children:`上传历史`}),(0,v.jsx)(`p`,{children:`本机保存最近 100 条,只加载当前可见缩略图。`})]}),(0,v.jsx)(`button`,{className:`btn-secondary icon-only`,type:`button`,onClick:()=>void Y(),disabled:T.length===0,title:`清空历史`,children:(0,v.jsx)(g,{size:16})})]}),T.length===0?(0,v.jsx)(`div`,{className:`image-bed-empty`,children:`还没有上传记录。上传完成后,这里会以图库形式展示预览和链接。`}):(0,v.jsx)(`div`,{className:`image-bed-results-grid`,children:z.map(e=>(0,v.jsxs)(`figure`,{className:`image-bed-result-card`,children:[(0,v.jsx)(`button`,{type:`button`,className:`image-bed-preview-button`,onClick:()=>void q(e.url),title:`点击复制链接`,children:(0,v.jsx)(`img`,{loading:`lazy`,decoding:`async`,src:e.previewUrl,alt:e.filename})}),(0,v.jsxs)(`figcaption`,{children:[(0,v.jsx)(`strong`,{children:e.filename}),(0,v.jsxs)(`span`,{children:[s(e.size),` · `,e.mimeType]}),(0,v.jsx)(`code`,{children:o(e.createdAt)})]}),(0,v.jsxs)(`div`,{className:`image-bed-result-actions`,children:[(0,v.jsx)(`button`,{className:`image-bed-card-action`,type:`button`,onClick:()=>void q(e.url),title:`复制链接`,"aria-label":`复制链接`,children:(0,v.jsx)(l,{size:15})}),(0,v.jsx)(`a`,{className:`image-bed-card-action`,href:e.url,target:`_blank`,rel:`noreferrer`,title:`打开原图`,"aria-label":`打开原图`,children:(0,v.jsx)(h,{size:15})}),(0,v.jsx)(`button`,{className:`image-bed-card-action is-danger`,type:`button`,onClick:()=>void X(e),title:`删除图床文件`,"aria-label":`删除图床文件`,disabled:F===e.id,children:F===e.id?(0,v.jsx)(c,{className:`spin`,size:15}):(0,v.jsx)(g,{size:15})})]})]},e.path))}),T.length>D&&(0,v.jsxs)(`button`,{className:`btn-secondary image-bed-load-more`,type:`button`,onClick:()=>O(e=>Math.min(e+12,T.length)),children:[(0,v.jsx)(m,{size:16}),`加载更多`]})]}),(0,v.jsxs)(`details`,{className:`image-bed-help-section`,children:[(0,v.jsx)(`summary`,{children:`GitHub token 创建说明`}),(0,v.jsxs)(`p`,{className:`image-bed-help-intro`,children:[`这个图床固定使用当前 GitHub 账号下的公开仓库 `,(0,v.jsx)(`code`,{children:`azt-img-bed`}),`,图片会写入 `,(0,v.jsx)(`code`,{children:`images`}),` 目录并返回 raw.githubusercontent.com 原图链接。`]}),(0,v.jsxs)(`ol`,{className:`image-bed-steps`,children:[(0,v.jsxs)(`li`,{children:[(0,v.jsx)(`strong`,{children:`先建公开仓库。`}),` 在 GitHub 新建仓库 `,(0,v.jsx)(`code`,{children:`azt-img-bed`}),`,Visibility 选 `,(0,v.jsx)(`strong`,{children:`Public`}),`。如果仓库已经存在,确认它属于这个 token 对应的个人账号,并且不是 Private。`]}),(0,v.jsxs)(`li`,{children:[(0,v.jsx)(`strong`,{children:`创建 fine-grained token。`}),` 打开 GitHub 的 `,(0,v.jsx)(`a`,{href:`https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens`,target:`_blank`,rel:`noreferrer`,children:`Personal access tokens`}),` 页面,选择 `,(0,v.jsx)(`strong`,{children:`Fine-grained tokens`}),`,点 `,(0,v.jsx)(`strong`,{children:`Generate new token`}),`。`]}),(0,v.jsxs)(`li`,{children:[(0,v.jsx)(`strong`,{children:`限制仓库范围。`}),` Token name 可填 `,(0,v.jsx)(`code`,{children:`AI Zero Token image bed`}),`,Expiration 按需设置,Resource owner 选自己的账号;Repository access 选择 `,(0,v.jsx)(`strong`,{children:`Only select repositories`}),`,只勾选 `,(0,v.jsx)(`code`,{children:`azt-img-bed`}),`。`]}),(0,v.jsxs)(`li`,{children:[(0,v.jsx)(`strong`,{children:`给最小权限。`}),` 在 Repository permissions 里把 `,(0,v.jsx)(`strong`,{children:`Contents`}),` 设置为 `,(0,v.jsx)(`strong`,{children:`Read and write`}),`;`,(0,v.jsx)(`strong`,{children:`Metadata`}),` 保持默认 Read-only 即可,其它权限不需要打开。`]}),(0,v.jsxs)(`li`,{children:[(0,v.jsx)(`strong`,{children:`复制并验证。`}),` GitHub 只会展示一次生成后的 token,复制后回到这里粘贴,点击 `,(0,v.jsx)(`strong`,{children:`保存并验证`}),`。验证通过后就可以拖图上传。`]})]}),(0,v.jsxs)(`dl`,{className:`image-bed-help-facts`,children:[(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`dt`,{children:`Token 格式`}),(0,v.jsxs)(`dd`,{children:[`fine-grained token 通常以 `,(0,v.jsx)(`code`,{children:`github_pat_`}),` 开头。`]})]}),(0,v.jsxs)(`div`,{children:[(0,v.jsx)(`dt`,{children:`验证失败`}),(0,v.jsxs)(`dd`,{children:[`如果提示未找到仓库,优先检查仓库名是否为 `,(0,v.jsx)(`code`,{children:`azt-img-bed`}),`、仓库是否 Public、Repository access 是否选中了这个仓库。`]})]})]}),(0,v.jsx)(`p`,{className:`hint`,children:`Token 要像密码一样保管,不要发给别人。这个页面只会把它保存到你本机状态目录。`})]})]})}export{S as ImageBedPage};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--bg:#f8fafc;--panel:#fff;--panel-soft:#f8fafc;--line:#e2e8f0;--line-strong:#cbd5e1;--text:#0f172a;--text-soft:#334155;--text-muted:#64748b;--brand:#635bff;--brand-strong:#4f46e5;--brand-soft:#635bff1a;--blue:#3b82f6;--blue-soft:#3b82f61f;--green:#22c55e;--green-soft:#22c55e1f;--orange:#f59e0b;--orange-soft:#f59e0b1f;--red:#ef4444;--red-soft:#ef44441f;--plan-color:#94a3b8;--plan-soft:#94a3b81f;--plan-border:var(--line);--shadow:0 2px 10px #0f172a0f;--shadow-sm:var(--shadow);--radius:16px;--radius-sm:12px;--radius-xs:10px}*{box-sizing:border-box}html{background:var(--bg)}body{min-width:0;min-height:100vh;color:var(--text);background:radial-gradient(circle at top left, #635bff14, transparent 28%), radial-gradient(circle at right top, #3b82f60f, transparent 32%), var(--bg);margin:0;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,sans-serif}button,input,select,textarea{font:inherit}button,a{-webkit-tap-highlight-color:transparent}button{cursor:pointer}a{color:inherit;text-decoration:none}.app-shell{grid-template-columns:236px minmax(0,1fr);align-items:start;gap:14px;width:min(100vw - 32px,1540px);max-width:none;margin:6px auto 18px;display:grid}.sidebar,.card,.trend-card,.summary-card,.account-card,.log-table-wrap,.tester-tabs,.service-card,.update-panel{border:1px solid var(--line);box-shadow:var(--shadow-sm);background:#fffffff0}.sidebar{border-radius:18px;gap:14px;min-width:0;max-height:calc(100vh - 20px);padding:14px 12px;display:grid;position:sticky;top:10px;overflow:auto}.brand{align-items:center;gap:12px;padding:2px 4px 4px;display:flex}.brand-mark{width:34px;height:34px;color:var(--brand);background:linear-gradient(#635bff24,#635bff0a);border:1px solid #635bff33;border-radius:12px;flex:none;place-items:center;display:grid;overflow:hidden}.brand-mark img{width:100%;height:100%;display:block}.brand strong{font-size:14px;line-height:1.25;display:block}.brand span{color:var(--text-muted);margin-top:5px;font-size:10px;line-height:1.5;display:block}.nav{gap:4px;display:grid}.nav-item{width:100%;min-height:36px;color:var(--text-soft);text-align:left;background:0 0;border:1px solid #0000;border-radius:11px;align-items:center;gap:10px;padding:0 11px;display:flex}.nav-item.is-active,.nav-item:hover{color:var(--brand);background:#635bff14;border-color:#635bff1a;font-weight:650}.sidebar-links{gap:12px;margin-top:auto}.sidebar-status{gap:12px;padding:11px}.sidebar-status-summary{grid-template-columns:16px minmax(0,1fr);align-items:center;gap:9px;min-width:0;display:grid}.sidebar-status-summary strong{font-size:14px;line-height:1.25;display:block}.sidebar-status-summary span:last-child{color:var(--text-muted);text-overflow:ellipsis;white-space:nowrap;margin-top:2px;font-size:11px;line-height:1.3;display:block;overflow:hidden}.sidebar-base-url{border:1px solid var(--line);width:100%;min-width:0;color:var(--text);text-align:left;background:#f8fafc;border-radius:11px;gap:5px;padding:9px;display:grid}.sidebar-base-url span{color:var(--text-muted);font-size:10px;font-weight:800}.sidebar-base-url code{min-width:0;color:var(--text);overflow-wrap:anywhere;font-size:11px;line-height:1.45}.sidebar-kv-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;display:grid}.sidebar-kv{background:#f8fafcd1;border-radius:11px;min-width:0;padding:8px 9px}.sidebar-kv.wide{grid-column:1/-1}.sidebar-kv span{color:var(--text-muted);font-size:10px;font-weight:800;line-height:1.2;display:block}.sidebar-kv strong{color:var(--text);overflow-wrap:anywhere;margin-top:4px;font-size:12px;line-height:1.35;display:block}.sidebar-link-list{gap:7px;display:grid}.sidebar-link{border:1px solid var(--line);width:100%;min-height:34px;color:var(--text-soft);text-align:left;background:#fff;border-radius:11px;justify-content:flex-start;align-items:center;gap:8px;padding:0 12px;font-size:12px;font-weight:700;display:inline-flex}.sidebar-link:hover{border-color:var(--line-strong);background:#f8fafc}.main{gap:16px;min-width:0;display:grid}.page-actions{flex-wrap:wrap;justify-content:flex-end;gap:10px;margin-bottom:2px;display:flex}.topbar{grid-template-columns:minmax(0,1fr) auto;align-items:start;gap:12px;display:grid}.page-title h1{margin:0;font-size:27px;line-height:1.15}.page-kicker{color:var(--brand);letter-spacing:0;margin-bottom:8px;font-size:12px;font-weight:850;display:inline-flex}.page-title p{color:var(--text-muted);margin:8px 0 0;font-size:14px;line-height:1.6}.top-actions,.section-actions,.button-row,.example-row{flex-wrap:wrap;justify-content:flex-end;gap:10px;display:flex}.top-actions .btn-primary,.top-actions .btn-secondary,.top-actions .btn-danger{border-radius:10px;min-height:36px;padding:0 12px;font-size:12px}.route-loading{place-items:center;min-height:56vh;display:grid}.route-loading-card{border:1px solid var(--line);min-width:220px;box-shadow:var(--shadow-sm);text-align:center;background:#ffffffeb;border-radius:14px;grid-template-columns:1fr;justify-items:center;gap:12px;padding:14px 16px;display:grid}.route-loading-card strong{font-size:14px;line-height:1.3;display:block}.route-loading-card p{color:var(--text-muted);margin:4px 0 0;font-size:12px;line-height:1.4}.route-loading-bar{background:linear-gradient(90deg,#635bff3d,#22c55eb8,#f97316e0) 0 0/200% 100%;border-radius:999px;width:92px;height:4px;animation:1.1s ease-in-out infinite route-loading-bar}@keyframes route-loading-bar{0%{background-position:0%}50%{background-position:100%}to{background-position:0%}}.btn-primary,.btn-secondary,.btn-danger{border:1px solid var(--line);white-space:nowrap;min-height:40px;color:var(--text-soft);background:#fff;border-radius:12px;justify-content:center;align-items:center;gap:8px;padding:0 14px;font-size:13px;font-weight:700;display:inline-flex;box-shadow:0 1px #0f172a05}.btn-primary{color:#fff;border-color:var(--brand);background:var(--brand)}.btn-primary:hover{background:var(--brand-strong)}.btn-secondary:hover{border-color:var(--line-strong);background:#f8fafc}.btn-danger{color:#ef4444;background:var(--red-soft);border-color:#ef44442e}.btn-danger:hover{background:#ef444429}.btn-primary:disabled,.btn-secondary:disabled,.btn-danger:disabled{cursor:not-allowed;opacity:.58}.icon-only{width:42px;padding:0}.summary-grid{grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;display:grid}.overview-summary-grid{grid-template-columns:repeat(3,minmax(0,1fr))}.summary-card{background:linear-gradient(145deg, #fffffffa, #f8fbfff5), var(--surface);border-color:#94a3b83d;border-radius:12px;grid-template-columns:24px minmax(0,1fr);align-content:start;gap:9px;min-height:84px;padding:12px 13px;display:grid;position:relative;overflow:hidden;box-shadow:0 14px 28px #0f172a12,inset 0 1px #ffffffe6}.summary-card:before{content:"";background:var(--brand);opacity:.75;width:3px;position:absolute;inset:0 auto 0 0}.summary-card:after{content:"";pointer-events:none;background:radial-gradient(circle at 12% 0,#4053ff14,#0000 34%);position:absolute;inset:0}.summary-card.tone-blue:before{background:var(--blue)}.summary-card.tone-green:before{background:var(--green)}.summary-card.tone-orange:before{background:var(--orange)}.summary-card.tone-red:before{background:var(--red)}.summary-card.tone-slate:before{background:var(--text-muted)}.summary-card>div:last-child{z-index:1;gap:4px;min-width:0;display:grid;position:relative}.summary-card span{color:var(--text-muted);font-size:11px;font-weight:760;line-height:1.2}.summary-card strong{letter-spacing:0;overflow-wrap:break-word;min-width:0;font-size:19px;line-height:1.08;display:block}.summary-card.compact-value strong{overflow-wrap:anywhere;font-size:16px;line-height:1.15}.usage-summary{gap:5px;min-width:0;display:grid}.usage-summary-row{grid-template-columns:42px minmax(0,1fr);align-items:center;gap:7px;min-width:0;display:grid}.usage-summary-row span{min-width:0;height:auto;color:var(--blue);background:0 0;border-radius:0;justify-content:flex-start;align-items:center;font-size:10px;font-weight:850;display:inline-flex}.usage-summary-row span:before{content:"";width:5px;height:5px;box-shadow:0 0 0 3px color-mix(in srgb, currentColor 14%, transparent);background:currentColor;border-radius:999px;margin-right:4px}.usage-summary-row:nth-child(2) span{color:var(--green)}.summary-card.compact-value .usage-summary-row strong{white-space:nowrap;text-overflow:ellipsis;overflow-wrap:normal;min-width:0;font-size:12px;line-height:1.25;overflow:hidden}.summary-card p{color:var(--text-muted);margin:0;font-size:11px;line-height:1.32}.summary-card p:empty{display:none}.summary-icon{z-index:1;width:20px;height:20px;color:var(--brand);background:var(--brand-soft);border-radius:999px;place-items:center;display:grid;position:relative;box-shadow:inset 0 0 0 1px #ffffffb8}.summary-icon.blue{color:var(--blue);background:var(--blue-soft)}.summary-icon.green{color:var(--green);background:var(--green-soft)}.summary-icon.orange{color:var(--orange);background:var(--orange-soft)}.main-grid{grid-template-columns:minmax(0,1.8fr) minmax(360px,.95fr);align-items:start;gap:22px;display:grid}.overview-grid,.tester-route-grid{grid-template-columns:minmax(0,1.18fr) minmax(420px,.82fr);align-items:start;gap:22px;display:grid}.tester-route-grid{grid-template-columns:minmax(0,1fr)}.tester-route-grid .tester-card{width:100%}.card,.trend-card,.log-table-wrap,.update-panel{border-radius:20px;padding:20px}.section-head{justify-content:space-between;align-items:flex-start;gap:16px;margin-bottom:18px;display:flex}.section-head.compact{margin-bottom:14px}.section-head h2,.section-head h3{margin:0;font-size:20px;line-height:1.25}.section-head p{color:var(--text-muted);margin:7px 0 0;font-size:13px;line-height:1.6}.trend-card{gap:14px;display:grid}.chart-wrap{border:1px solid var(--line);background:linear-gradient(#f8fafc99,#fff);border-radius:16px;width:100%;padding:14px;overflow:hidden}.chart-legend{color:var(--text-muted);flex-wrap:wrap;gap:14px;font-size:12px;display:flex}.legend-item{align-items:center;gap:8px;display:inline-flex}.legend-swatch{border-radius:999px;width:10px;height:10px}.legend-swatch.purple{background:#635bff}.legend-swatch.blue{background:#3b82f6}.trend-svg{width:100%;height:210px;margin-top:10px;display:block}.trend-labels{color:var(--text-muted);grid-template-columns:repeat(6,minmax(0,1fr));gap:6px;margin-top:10px;font-size:11px;line-height:1.4;display:grid}.filter-row{flex-wrap:wrap;align-items:center;gap:10px;margin-bottom:18px;display:flex}.search-box{border:1px solid var(--line);min-width:0;min-height:40px;color:var(--text-muted);background:#fff;border-radius:12px;flex:220px;align-items:center;gap:10px;padding:0 12px;display:flex}.search-box input{width:100%;min-width:0;color:var(--text);background:0 0;border:0;outline:none}.input,.control,.textarea{border:1px solid var(--line);width:100%;color:var(--text);background:#fff;border-radius:12px;outline:none}.input,.control{min-height:40px;padding:0 12px}.filter-row .control{flex:0 0 156px;min-width:156px}.textarea{resize:vertical;min-height:180px;padding:14px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;line-height:1.55}.field{color:var(--text-soft);gap:8px;font-size:13px;font-weight:700;display:grid}.hint,.status-inline{color:var(--text-muted);margin:9px 0;font-size:13px;line-height:1.6}.account-selected-count{color:var(--text-muted);white-space:nowrap;font-size:12px;font-weight:700}.service-card{border-radius:16px;gap:12px;padding:14px;display:grid}.sidebar-status{border:1px solid var(--line);background:#fff}.sidebar-status.tone-green{border-color:#22c55e2e}.sidebar-status.tone-orange{background:#fffbebf2;border-color:#f59e0b3d}.sidebar-status.tone-red{background:#fef2f2f2;border-color:#ef444433}.sidebar-status .service-head span{color:var(--text-muted);font-size:11px;font-weight:700}.sidebar-meta-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;display:grid}.sidebar-meta{background:#f8fafce0;border-radius:11px;min-width:0;padding:8px 10px}.sidebar-meta span{color:var(--text-muted);font-size:10px;font-weight:800;line-height:1.2;display:block}.sidebar-meta strong{color:var(--text);overflow-wrap:anywhere;margin-top:4px;font-size:12px;line-height:1.35;display:block}.endpoint-card .compact-grid{grid-template-columns:1fr;gap:12px}.service-head{justify-content:space-between;align-items:center;gap:12px;display:flex}.service-list{gap:10px;display:grid}.compact-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.service-row{min-width:0}.service-row label{color:var(--text-muted);margin-bottom:4px;font-size:12px;display:block}.service-row strong,.service-row code{color:var(--text);word-break:break-word;overflow-wrap:anywhere;font-size:13px;line-height:1.5}.meta-grid .service-row label{margin-bottom:0;font-size:10px;line-height:1.4}.meta-grid .service-row strong,.meta-grid .service-row code{color:var(--text-soft);font-size:11px;line-height:1.45}.status-dot{background:var(--green);border-radius:999px;width:8px;height:8px;box-shadow:0 0 0 4px #22c55e24}.status-dot.offline{background:var(--orange);box-shadow:0 0 0 4px #f59e0b24}.update-panel{background:linear-gradient(135deg,#fffbebfa,#fffffff5),#fff;border-color:#f59e0b40;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:12px;padding:12px 14px;display:grid}.strong-update-panel{box-shadow:0 10px 22px #b4530917, var(--shadow-sm);border-color:#f59e0b57}.update-mark{color:#b45309;background:#fff7ed;border:1px solid #f59e0b3d;border-radius:12px;place-items:center;width:36px;height:36px;display:grid;box-shadow:inset 0 1px #ffffffc7}.update-copy{gap:4px;min-width:0;display:grid}.update-title-row{flex-wrap:wrap;align-items:baseline;gap:10px;display:flex}.update-title-row strong{font-size:16px;line-height:1.25}.update-copy p{color:var(--text-muted);flex-wrap:wrap;align-items:center;gap:6px;margin:0;font-size:13px;line-height:1.45;display:flex}.update-title-row span{color:var(--text-muted);font-size:13px;font-weight:700}.update-panel code{color:#92400e;overflow-wrap:anywhere;background:#fff;border:1px solid #f59e0b29;border-radius:8px;max-width:100%;padding:4px 7px;font-size:12px;line-height:1.35}.update-actions{flex-wrap:wrap;justify-content:flex-end;align-items:center;gap:10px;display:flex}.update-panel .btn-primary,.update-panel .btn-secondary{white-space:nowrap;flex:none;align-items:center;gap:7px;min-height:36px;padding-inline:12px;text-decoration:none;display:inline-flex}.modal-backdrop,.drawer-backdrop,.loading-cover{z-index:20;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);background:#0f172a6b;place-items:center;padding:20px;display:grid;position:fixed;inset:0}.loading-cover{z-index:30;color:var(--text);background:#f8fafcc2;gap:12px}.modal-card{border:1px solid var(--line);width:min(760px,100%);max-height:min(760px,100vh - 40px);box-shadow:var(--shadow);background:#fff;border-radius:18px;overflow:auto}.modal-card.wide{width:min(980px,100%)}.modal-head{z-index:1;border-bottom:1px solid var(--line);background:#fffffff5;justify-content:space-between;align-items:center;gap:12px;padding:18px 20px;display:flex;position:sticky;top:0}.modal-head h3{margin:0}.modal-body{padding:20px}.modal-grid{gap:16px;display:grid}.modal-section{border:1px solid var(--line);background:#f8fafc;border-radius:14px;gap:12px;padding:16px;display:grid}.modal-section h4{margin:0}.modal-section p,.contact-notes span{color:var(--text-muted);margin:0;font-size:13px;line-height:1.6}.import-textarea{min-height:220px}.zip-import-box,.zip-import-preview{border:1px solid var(--line);background:#f8fafcd1;border-radius:12px;gap:10px;padding:12px;display:grid}.zip-import-box{grid-template-columns:minmax(0,1fr) auto;align-items:center}.zip-import-box strong,.zip-import-preview strong{color:var(--text);font-size:13px;line-height:1.25}.zip-import-box p,.zip-import-preview p,.zip-import-preview span{color:var(--text-muted);margin:0;font-size:12px;line-height:1.5}.zip-import-trigger{cursor:pointer;position:relative;overflow:hidden}.zip-import-trigger input{opacity:0;pointer-events:none;position:absolute;inset:0}.zip-import-preview.ready{background:#f0fdf4bd;border-color:#22c55e38}.zip-import-preview.error{background:#fef2f2c2;border-color:#ef444438}.zip-import-preview .btn-primary{width:fit-content}.contact-notes{gap:14px;display:grid}.contact-note,.contact-qr{gap:6px;display:grid}.contact-note{border:1px solid var(--line);background:var(--panel-soft);border-radius:16px;padding:14px 16px}.contact-note strong{font-size:14px;line-height:1.4}.contact-note span,.contact-note a,.contact-note code{color:var(--text-soft);word-break:break-word;font-size:13px;line-height:1.6}.contact-qr{border:1px solid var(--line);background:#fff;border-radius:20px;gap:10px;padding:14px}.contact-qr img{border:1px solid var(--line);background:var(--panel-soft);border-radius:16px;width:100%;max-width:420px;height:auto;display:block}.contact-qr span{color:var(--text-muted);font-size:12px;line-height:1.6}.image-preview-stage{background:#0f172a;border-radius:14px;place-items:center;min-height:320px;display:grid;overflow:hidden}.image-preview-stage img{max-width:100%;max-height:min(70vh,680px);display:block}.preview-modal-meta{color:var(--text-soft);word-break:break-word;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px;font-size:13px;line-height:1.7;display:flex}.spin{animation:.9s linear infinite spin}@keyframes spin{to{transform:rotate(360deg)}}.launch-page{grid-template-columns:minmax(420px,.82fr) minmax(520px,1.18fr);align-items:stretch;gap:16px;min-height:calc(100vh - 172px);display:grid}.launch-copy,.launch-visual{border:1px solid var(--line);min-width:0;box-shadow:var(--shadow-sm);background:#fffffff0;border-radius:20px}.launch-copy{align-content:center;gap:20px;padding:28px;display:grid}.launch-identity{gap:18px;display:grid}.launch-app-icon{background:linear-gradient(180deg, #ffffffe6, #f8fafcd1), var(--surface);border:1px solid #635bff2e;border-radius:24px;place-items:center;width:96px;height:96px;display:grid;box-shadow:0 22px 44px #0f172a1f,0 0 0 8px #635bff0f}.launch-app-icon img{width:100%;height:100%;display:block}.launch-copy h2{max-width:560px;color:var(--text);letter-spacing:0;margin:0;font-size:44px;line-height:1.02}.launch-copy p{max-width:520px;color:var(--text-soft);margin:0;font-size:15px;line-height:1.8}.launch-metrics{grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;display:grid}.launch-metrics div{border:1px solid var(--line);background:#f8fafcd6;border-radius:14px;gap:8px;min-width:0;padding:14px;display:grid}.launch-metrics svg{color:var(--brand)}.launch-metrics span{color:var(--text-muted);font-size:11px;font-weight:800}.launch-metrics strong{min-width:0;color:var(--text);overflow-wrap:anywhere;font-size:18px;line-height:1.2}.launch-actions{flex-wrap:wrap;gap:10px;display:flex}.launch-actions .btn-secondary,.launch-actions a.btn-secondary{text-decoration:none}.launch-download-note{color:var(--text-muted);margin:-2px 0 0;font-size:12px;line-height:1.6}.launch-pills{flex-wrap:wrap;gap:8px;display:flex}.launch-pills span{min-height:28px;color:var(--text-muted);background:#635bff14;border-radius:999px;align-items:center;padding:0 10px;font-size:12px;font-weight:700;display:inline-flex}.launch-status{border-top:1px solid var(--line);grid-template-columns:repeat(2,minmax(0,1fr));gap:14px 18px;margin-top:8px;padding-top:20px;display:grid}.launch-visual{background:radial-gradient(circle at 24% 22%,#635bff26,#0000 34%),radial-gradient(circle at 82% 78%,#22c55e1f,#0000 32%),linear-gradient(#fffffff5,#f8fafcf0),#fff;place-items:center;padding:18px;display:grid;overflow:hidden}.launch-visual-stage{aspect-ratio:1.2;place-items:center;width:min(760px,100%);display:grid;position:relative}.launch-visual-mark{z-index:2;border-radius:34px;width:min(160px,24%);position:absolute;top:5%;left:4%;box-shadow:0 24px 54px #0f172a2e,0 0 0 10px #ffffffc7}.launch-visual-dashboard{object-fit:contain;filter:drop-shadow(0 28px 50px #0f172a24);width:92%;max-height:min(560px,100vh - 250px);display:block}.left-column,.right-column{gap:18px;min-width:0;display:grid}.account-grid{justify-content:stretch;align-items:start;gap:16px;width:100%;display:grid}.account-grid.profile-count-1{grid-template-columns:minmax(320px,520px)}.account-grid.profile-count-2{grid-template-columns:repeat(2,minmax(320px,1fr))}.account-grid.profile-count-3{grid-template-columns:repeat(3,minmax(300px,1fr))}.account-grid.profile-count-many{grid-template-columns:repeat(auto-fit,minmax(300px,1fr))}.account-card{--plan-color:#94a3b8;--plan-soft:#94a3b81f;--plan-border:var(--line);--usage-color:#16a34a;--usage-soft:#16a34a1f;border-color:var(--plan-border);border-radius:16px;grid-template-rows:none;align-content:start;align-self:start;gap:12px;min-width:0;padding:14px;display:grid;position:relative;overflow:hidden}.account-card:before{content:"";background:var(--plan-color);height:3px;position:absolute;inset:0 0 auto}.account-card.plan-free{--plan-color:#94a3b8;--plan-soft:#94a3b81f;--plan-border:var(--line)}.account-card.plan-plus{--plan-color:#635bff;--plan-soft:#635bff1c;--plan-border:#635bff33}.account-card.plan-pro{--plan-color:#4f46e5;--plan-soft:#4f46e51c;--plan-border:#4f46e538}.account-card.plan-team{--plan-color:#0f766e;--plan-soft:#0f766e1c;--plan-border:#0f766e38}.account-card.plan-premium{--plan-color:#d97706;--plan-soft:#d977061f;--plan-border:#d9770657;box-shadow:0 10px 26px #b453091a, var(--shadow)}.account-card.plan-enterprise{--plan-color:#a16207;--plan-soft:#a1620724;--plan-border:#47556947;box-shadow:0 12px 28px #0f172a1a, var(--shadow)}.account-card.is-auth-invalid{--plan-color:#ef4444;--plan-soft:#ef444417;--plan-border:#ef444452;border-color:var(--plan-border);box-shadow:0 12px 28px #b91c1c14, var(--shadow);background:linear-gradient(#fef2f2e6,#fffffffa 46%),#fff}.account-card.is-auth-invalid:before{background:#ef4444;height:3px}.account-card.is-auth-invalid .usage-corner{color:#991b1b;background:linear-gradient(135deg,#fef2f2,#fee2e2);border-color:#ef444447;box-shadow:0 8px 18px #b91c1c1a,inset 0 1px #ffffffd1}.account-card.is-auth-invalid .avatar{color:#dc2626;background:#fff;border-color:#ef444475;box-shadow:0 0 0 3px #ef44441a}.account-card.is-auth-invalid .progress-bar{opacity:.72}.account-card.is-auth-invalid .usage-status-row,.account-card.is-auth-invalid .progress-track{background:#fef2f2ad}.account-card.is-auth-invalid .account-actions .btn-secondary:disabled{opacity:.86;color:#991b1b;background:#fff;border-color:#ef44442e}.account-head{justify-content:space-between;align-items:flex-start;gap:12px;padding-top:8px;display:flex}.account-title{flex:1;gap:6px;min-width:0;display:grid}.account-name{align-items:center;gap:8px;min-width:0;display:flex}.account-name strong{-webkit-line-clamp:2;text-overflow:ellipsis;white-space:normal;overflow-wrap:anywhere;-webkit-box-orient:vertical;min-width:0;font-size:13px;line-height:1.35;display:-webkit-box;overflow:hidden}.account-icon-btn{border:1px solid var(--line);width:24px;height:24px;color:var(--text-muted);background:#fff;border-radius:999px;flex:none;justify-content:center;align-items:center;padding:0;display:flex}.account-icon-btn:hover{color:var(--plan-color);border-color:var(--plan-color);background:var(--plan-soft)}.avatar{background:var(--panel-soft);border:1px solid var(--plan-color);width:24px;height:24px;box-shadow:0 0 0 3px var(--plan-soft);color:var(--plan-color);border-radius:999px;flex:none;place-items:center;font-size:11px;font-weight:700;display:grid}.badge-row{flex-wrap:wrap;gap:6px;display:flex}.account-select{border:1px solid var(--line);min-height:28px;color:var(--text-muted);cursor:pointer;-webkit-user-select:none;user-select:none;white-space:nowrap;background:#fff;border-radius:8px;align-items:center;gap:6px;margin-top:28px;padding:0 8px;font-size:12px;font-weight:600;display:inline-flex}.account-select input{width:14px;height:14px;margin:0}.badge{white-space:nowrap;border-radius:999px;justify-content:center;align-items:center;min-height:22px;padding:0 8px;font-size:11px;font-weight:600;display:inline-flex}.badge.brand{color:var(--plan-color);background:var(--plan-soft)}.usage-corner{color:#047857;letter-spacing:0;pointer-events:none;z-index:1;background:linear-gradient(135deg,#ecfdf5,#d1fae5);border:1px solid #10b98147;border-radius:999px;align-items:center;gap:5px;min-height:24px;padding:0 10px 0 8px;font-size:10px;font-weight:800;line-height:22px;display:inline-flex;position:absolute;top:10px;right:12px;box-shadow:0 8px 18px #10b9811f,inset 0 1px #ffffffd1}.usage-corner:before{content:"";background:currentColor;border-radius:999px;flex:none;width:6px;height:6px;box-shadow:0 0 0 3px #10b9811f}.usage-corner span{line-height:1}.usage-corner.codex-only{color:#1d4ed8;background:linear-gradient(135deg,#eff6ff,#dbeafe);border-color:#2563eb3d;box-shadow:0 8px 18px #2563eb1f,inset 0 1px #ffffffd1}.usage-corner.codex-only:before{box-shadow:0 0 0 3px #2563eb1f}.usage-corner.dual{color:#4f46e5;background:linear-gradient(135deg,#f5f3ff,#ede9fe);border-color:#635bff40;box-shadow:0 8px 18px #635bff21,inset 0 1px #ffffffd1}.usage-corner.dual:before{box-shadow:0 0 0 3px #635bff1f}.badge.green{color:#15803d;background:var(--green-soft)}.badge.orange{color:#b45309;background:var(--orange-soft)}.badge.red{color:#dc2626;background:var(--red-soft)}.badge.muted{color:var(--text-muted);background:#f1f5f9}.account-metrics{gap:10px;display:grid}.quota-row{gap:6px;display:grid}.quota-line{color:var(--text-soft);justify-content:space-between;align-items:center;gap:10px;font-size:11px;line-height:1.45;display:flex}.quota-line span{min-width:0}.quota-line strong{color:var(--text);flex-shrink:0;font-size:12px}.progress-track{background:#eef2f7;border-radius:999px;width:100%;height:5px;overflow:hidden}.progress-bar{border-radius:inherit;background:var(--brand);height:100%}.progress-bar.blue{background:var(--blue)}.progress-bar.orange{background:var(--orange)}.progress-bar.red{background:#f43f5e}.usage-status-row{background:var(--panel-soft);color:var(--text-muted);border-radius:10px;flex-wrap:nowrap;justify-content:space-between;align-items:center;gap:8px;padding:8px 10px;font-size:11px;line-height:1.4;display:flex}.usage-status{white-space:nowrap;align-items:center;gap:5px;min-width:0;font-weight:700;display:inline-flex}.usage-status svg{width:12px;height:12px;color:var(--text-muted);flex:none}.usage-dot{background:#cbd5e1;border-radius:999px;flex:none;width:6px;height:6px}.usage-dot.active{background:#22c55e;box-shadow:0 0 0 3px #22c55e1f}.usage-state-text{color:var(--text-muted);font-weight:700}.usage-status.is-active .usage-state-text{color:#15803d}.compact-meta-row{min-width:0;color:var(--text-muted);gap:8px;font-size:11px;line-height:1.45;display:grid}.compact-reset-list{flex-wrap:nowrap;align-items:center;gap:10px;min-width:0;display:flex}.compact-meta-item{flex:1 1 0;align-items:baseline;gap:5px;min-width:0;display:flex}.compact-meta-item label{color:var(--text-muted);white-space:nowrap;font-size:10px;line-height:1.4}.compact-meta-item strong{color:var(--text-soft);text-align:left;overflow-wrap:anywhere;font-size:11px;line-height:1.4}.compact-meta-actions{justify-content:center;align-items:center;gap:10px;margin-top:2px;display:flex}.compact-meta-actions:before,.compact-meta-actions:after{content:"";background:var(--line);flex:auto;min-width:18px;height:1px}.details-toggle{min-height:24px;color:var(--brand);white-space:nowrap;cursor:pointer;background:0 0;border:0;justify-content:center;align-items:center;gap:5px;padding:0 6px;font-size:11px;font-weight:700;display:inline-flex}.details-toggle:hover{color:#4338ca}.details-toggle svg{width:12px;height:12px;transition:transform .16s}.details-toggle.is-expanded svg{transform:rotate(180deg)}.meta-grid{border-top:1px solid var(--line);grid-template-columns:repeat(2,minmax(0,1fr));gap:8px 12px;padding-top:10px;display:grid}.meta-item{gap:3px;min-width:0;display:grid}.meta-grid .service-row label{color:var(--text-muted);font-size:10px;line-height:1.4}.meta-grid .service-row strong,.meta-grid .service-row span,.meta-grid .service-row code{color:var(--text-soft);word-break:break-word;overflow-wrap:anywhere;font-size:11px;line-height:1.45}.account-actions{flex-wrap:wrap;gap:8px;margin-top:0;display:flex}.account-actions .btn-secondary,.account-actions .btn-danger{border-radius:10px;flex:120px;min-height:36px;padding:0 12px;font-size:12px}.account-actions .btn-secondary.is-current{opacity:1;color:#047857;cursor:default;background:linear-gradient(135deg,#f0fdf4,#dcfce7);border-color:#10b9815c;position:relative;box-shadow:inset 0 1px #fffc,0 6px 14px #10b98114}.account-actions .btn-secondary.is-current:before{content:"";background:#22c55e;border-radius:999px;flex:none;width:7px;height:7px;box-shadow:0 0 0 3px #22c55e1f}.account-actions .btn-secondary.is-current.codex{color:#1d4ed8;background:linear-gradient(135deg,#eff6ff,#dbeafe);border-color:#2563eb52;box-shadow:inset 0 1px #fffc,0 6px 14px #2563eb14}.account-actions .btn-secondary.is-current.codex:before{background:#3b82f6;box-shadow:0 0 0 3px #3b82f61f}.tester-card{gap:16px;display:grid}.tester-tabs,.tester-result-tabs{border-radius:14px;gap:4px;padding:6px;display:flex;overflow-x:auto}.tester-tabs{grid-template-columns:repeat(5,minmax(92px,1fr));gap:6px;display:grid}.tester-result-tabs{box-shadow:none;background:0 0;border:0;padding:0}.tab-btn{min-height:38px;color:var(--text-muted);white-space:nowrap;background:0 0;border:0;border-radius:10px;padding:0 14px;font-weight:800}.tab-btn.is-active{color:var(--brand);background:#fff;box-shadow:0 6px 16px #0f172a0f}.tester-workbench{grid-template-columns:minmax(360px,.92fr) minmax(360px,1.08fr);align-items:stretch;gap:16px;display:grid}.tester-pane{border:1px solid var(--line);background:linear-gradient(#fff,#f8fafc9e);border-radius:16px;align-content:start;gap:12px;min-width:0;padding:14px;display:grid}.tester-request-pane{grid-template-rows:auto auto minmax(180px,1fr) auto auto}.tester-body-field{grid-template-rows:auto minmax(0,1fr);min-height:0;display:grid}.tester-textarea{resize:vertical;height:100%;min-height:180px}.tester-actions-bar{grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:10px;display:grid}.tester-actions-group{gap:10px;min-width:0;display:grid}.tester-actions-bar .example-row{justify-content:flex-start;gap:8px}.tester-copy-row{flex-wrap:wrap;gap:8px;display:flex}.tester-copy-row-top{margin-top:-4px}.tester-actions-bar .btn-secondary{border-radius:10px;min-height:34px;padding:0 10px;font-size:12px}.tester-response-pane{grid-template-rows:auto minmax(0,1fr)}.tester-result-head{grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:12px;display:grid}.tester-result-head .status-inline{text-align:right;white-space:nowrap;text-overflow:ellipsis;max-width:220px;margin:0;overflow:hidden}.edit-upload-row{grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:10px 12px;margin-top:-2px;display:grid}.edit-upload-mode{gap:6px;min-width:0;display:grid}.edit-upload-mode>span{color:var(--text-muted);font-size:11px;font-weight:800;line-height:1.25}.edit-upload-toggle{flex-wrap:wrap;gap:6px;display:flex}.edit-upload-toggle .tab-btn{min-height:34px;padding:0 12px}.upload-btn{cursor:pointer;white-space:nowrap}.upload-btn input{display:none}.edit-upload-row span{color:var(--text-muted);grid-column:1/-1;font-size:12px;line-height:1.5}.pre{color:#e2e8f0;white-space:pre-wrap;overflow-wrap:anywhere;background:#0f172a;border-radius:12px;width:100%;min-height:312px;max-height:420px;margin:0;padding:16px;font-size:12px;line-height:1.6;overflow:auto}.preview-panel{min-height:312px}.preview-empty,.empty-state{border:1px dashed var(--line-strong);text-align:center;min-height:140px;color:var(--text-muted);background:#f8fafc;border-radius:14px;place-items:center;padding:20px;display:grid}.preview-grid{grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:12px;display:grid}.preview-card{gap:8px;margin:0;display:grid}.preview-card button{border:1px solid var(--line);background:#fff;border-radius:12px;padding:0;overflow:hidden}.preview-card img{aspect-ratio:1;object-fit:cover;width:100%;display:block}.preview-card figcaption{color:var(--text-muted);overflow-wrap:anywhere;font-size:12px}.preview-actions{flex-wrap:wrap;gap:8px;display:flex}.preview-actions a{border:1px solid var(--line);min-height:34px;color:var(--text-soft);border-radius:10px;justify-content:center;align-items:center;padding:0 12px;font-size:12px;font-weight:600;display:inline-flex}.pre.tester-log{min-height:200px}.image-bed-page{gap:16px;display:grid}.image-bed-workbench{grid-template-columns:minmax(0,1.45fr) minmax(320px,.8fr);align-items:start;gap:14px;display:grid}.image-bed-upload-panel,.image-bed-side-card,.image-bed-gallery-section,.image-bed-help-section{border:1px solid var(--line);box-shadow:var(--shadow-sm);background:#fffffff2}.image-bed-upload-panel{background:linear-gradient(135deg,#fffffffa,#f6f8ffeb),#fff;border-radius:18px;grid-template-rows:auto minmax(220px,1fr) auto;gap:16px;min-height:440px;padding:22px;display:grid;overflow:hidden}.image-bed-upload-panel.is-dragging{border-color:#635bff6b;box-shadow:0 18px 45px #635bff1f}.image-bed-upload-copy{gap:7px;max-width:680px;display:grid}.image-bed-upload-copy span{color:var(--brand);font-size:12px;font-weight:850;line-height:1.3}.image-bed-upload-copy h2{color:var(--text);margin:0;font-size:28px;line-height:1.16}.image-bed-upload-copy p{color:var(--text-muted);margin:0;font-size:13px;line-height:1.65}.image-bed-upload-copy strong{color:var(--text);font-weight:800}.upload-dropzone{min-height:220px;color:var(--text-soft);text-align:center;cursor:pointer;background:linear-gradient(#ffffffb8,#f8fafcb8),#f8fafce0;border:1px dashed #635bff52;border-radius:16px;align-content:center;place-items:center;gap:10px;padding:24px;transition:border-color .16s,background .16s,transform .16s;display:grid;position:relative}.upload-dropzone:hover,.upload-dropzone.is-dragging{border-color:var(--brand);background:#635bff12;transform:translateY(-1px)}.upload-dropzone.is-disabled{opacity:.84}.upload-dropzone input{opacity:0;pointer-events:none;width:1px;height:1px;position:absolute}.upload-dropzone-icon{width:52px;height:52px;color:var(--brand);background:#635bff1a;border:1px solid #635bff29;border-radius:16px;place-items:center;display:grid}.upload-dropzone strong{color:var(--text);font-size:18px;line-height:1.3}.upload-dropzone span{max-width:360px;color:var(--text-muted);font-size:13px;line-height:1.5}.upload-progress-block,.image-bed-latest,.image-bed-upload-note{background:#635bff0d;border:1px solid #635bff24;border-radius:15px;min-height:72px}.upload-progress-block{gap:10px;padding:14px 16px;display:grid}.upload-progress-head{justify-content:space-between;align-items:baseline;gap:12px;display:flex}.upload-progress-head strong{color:var(--text);font-size:13px;line-height:1.35}.upload-progress-head span{color:var(--text-muted);overflow-wrap:anywhere;font-size:11px;line-height:1.35}.upload-progress-track{background:#94a3b833;border-radius:999px;height:10px;overflow:hidden}.upload-progress-fill{border-radius:inherit;background:linear-gradient(90deg, var(--brand), #10b981);height:100%;transition:width .18s}.image-bed-latest{grid-template-columns:54px minmax(0,1fr) auto;align-items:center;gap:12px;padding:10px;display:grid}.image-bed-latest-preview{background:#f8fafc;border:0;border-radius:13px;width:54px;height:54px;padding:0;overflow:hidden}.image-bed-latest-preview img{object-fit:cover;width:100%;height:100%;display:block}.image-bed-latest-info{gap:3px;min-width:0;display:grid}.image-bed-latest-info span{color:var(--text-muted);font-size:10px;font-weight:850;line-height:1.2}.image-bed-latest-info strong,.image-bed-latest-info code{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.image-bed-latest-info strong{color:var(--text);font-size:13px}.image-bed-latest-info code{color:var(--text-muted);font-size:11px}.image-bed-upload-note{color:var(--text-muted);align-items:center;padding:14px 16px;font-size:13px;line-height:1.5;display:flex}.image-bed-side-stack{gap:14px;display:grid}.image-bed-side-card{border-radius:16px;align-content:start;gap:13px;padding:18px;display:grid}.image-bed-side-card h4,.image-bed-gallery-section h4{color:var(--text);margin:0;font-size:16px;line-height:1.3}.image-bed-side-card p{color:var(--text-muted);margin:0;font-size:12px;line-height:1.6}.image-bed-section-head{justify-content:space-between;align-items:center;gap:10px;display:flex}.image-bed-section-head p{color:var(--text-muted);margin:4px 0 0;font-size:12px;line-height:1.45}.image-bed-status-dot{background:#cbd5e1;border-radius:999px;width:10px;height:10px;box-shadow:0 0 0 4px #cbd5e138}.image-bed-status-dot.is-ok{background:#10b981;box-shadow:0 0 0 4px #10b98129}.image-bed-status-dot.is-warn{background:#f59e0b;box-shadow:0 0 0 4px #f59e0b29}.image-bed-connection-label{color:var(--text);overflow-wrap:anywhere;font-size:15px;line-height:1.4}.image-bed-link-button{color:var(--brand);background:0 0;border:0;padding:0;font-size:12px;font-weight:800}.image-bed-token-actions{grid-template-columns:1fr auto;gap:10px;display:grid}.image-bed-token-hint{color:var(--text-muted);margin:-2px 0 0;font-size:11px;font-weight:650;line-height:1.5}.image-bed-token-hint code{color:var(--text);font-weight:800}.image-bed-token-summary{border:1px solid var(--line);background:#f8fafccc;border-radius:14px;justify-content:space-between;align-items:center;gap:12px;min-height:58px;padding:12px;display:flex}.image-bed-token-summary span{color:var(--text-muted);font-size:10px;font-weight:850;line-height:1.25;display:block}.image-bed-token-summary strong{color:var(--text);margin-top:4px;font-size:14px;line-height:1.35;display:block}.image-bed-target-list{grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;display:grid}.image-bed-target-list div{background:#f8fafcd1;border-radius:12px;min-width:0;padding:10px}.image-bed-target-list span{color:var(--text-muted);font-size:10px;font-weight:850;line-height:1.25;display:block}.image-bed-target-list strong{color:var(--text);overflow-wrap:anywhere;margin-top:5px;font-size:12px;line-height:1.35;display:block}.image-bed-gallery-section{border-radius:18px;gap:14px;padding:18px;display:grid}.image-bed-empty{min-height:136px;color:var(--text-muted);text-align:center;background:#f8fafcd1;border-radius:15px;place-items:center;padding:24px;font-size:13px;line-height:1.6;display:grid}.image-bed-results-grid{grid-template-columns:repeat(auto-fill,minmax(132px,150px));align-items:start;gap:12px;display:grid}.image-bed-result-card{border:1px solid var(--line);background:#fff;border-radius:14px;align-content:start;gap:7px;margin:0;display:grid;position:relative;overflow:hidden;box-shadow:0 8px 18px #0f172a0d}.image-bed-preview-button{aspect-ratio:1/.78;background:#f8fafc;border:0;padding:0;position:relative;overflow:hidden}.image-bed-preview-button img{object-fit:cover;width:100%;height:100%;transition:transform .18s;display:block}.image-bed-preview-button:hover img{transform:scale(1.03)}.image-bed-result-card figcaption{gap:3px;min-width:0;padding:0 10px 10px;display:grid}.image-bed-result-card figcaption strong{color:var(--text);text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:1.4;overflow:hidden}.image-bed-result-card figcaption span,.image-bed-result-card figcaption code{color:var(--text-muted);overflow-wrap:anywhere;font-size:10px;line-height:1.4}.image-bed-result-actions{gap:6px;display:flex;position:absolute;top:7px;right:7px}.image-bed-card-action{width:30px;height:30px;color:var(--text-soft);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);background:#ffffffe0;border:1px solid #ffffffb8;border-radius:10px;justify-content:center;align-items:center;display:inline-flex;box-shadow:0 8px 18px #0f172a1a}.image-bed-card-action:hover{color:var(--brand);background:#fff;border-color:#635bff33}.image-bed-card-action.is-danger{color:#ef4444}.image-bed-card-action.is-danger:hover{color:#dc2626;border-color:#ef444438}.image-bed-card-action:disabled{cursor:not-allowed;opacity:.62}.image-bed-load-more{width:100%}.image-bed-help-section{border-radius:16px;padding:0;overflow:hidden}.image-bed-help-section summary{cursor:pointer;color:var(--text);padding:15px 18px;font-size:14px;font-weight:800}.image-bed-help-intro{color:var(--text-muted);margin:0;padding:0 20px 14px;font-size:13px;line-height:1.6}.image-bed-help-intro code{color:var(--text);font-weight:800}.image-bed-steps{color:var(--text-soft);gap:11px;margin:0;padding:0 20px 16px 36px;font-size:13px;line-height:1.6;display:grid}.image-bed-steps li{min-width:0}.image-bed-steps strong,.image-bed-steps code{color:var(--text);font-weight:750}.image-bed-help-facts{border-top:1px solid var(--line);border-bottom:1px solid var(--line);margin:0 20px 16px;display:grid}.image-bed-help-facts div{border-top:1px solid #94a3b829;grid-template-columns:110px minmax(0,1fr);gap:12px;min-width:0;padding:12px 0;display:grid}.image-bed-help-facts div:first-child{border-top:0}.image-bed-help-facts dt{color:var(--text);font-size:12px;font-weight:850;line-height:1.4}.image-bed-help-facts dd{color:var(--text-muted);margin:0;font-size:12px;line-height:1.55}.image-bed-help-facts code{color:var(--text);font-weight:800}.image-bed-help-section .hint{margin:0;padding:0 20px 18px}@media (width<=1120px){.image-bed-workbench{grid-template-columns:1fr}.image-bed-side-stack{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (width<=760px){.image-bed-upload-panel,.image-bed-gallery-section{border-radius:16px;padding:16px}.image-bed-upload-copy h2{font-size:23px}.image-bed-side-stack,.image-bed-target-list,.image-bed-token-actions,.image-bed-help-facts div,.image-bed-latest,.image-bed-result-actions{grid-template-columns:1fr}.image-bed-latest-preview{aspect-ratio:1/.62;width:100%;height:auto}}.network-page{gap:18px;display:grid}.network-command-center{box-shadow:var(--shadow);background:linear-gradient(135deg,#fff 0%,#f8fbff 58%,#f4fbf7 100%);border:1px solid #94a3b838;border-radius:20px;grid-template-columns:minmax(0,1fr) 260px;align-items:stretch;gap:18px;padding:22px;display:grid;overflow:hidden}.network-command-center.green{border-color:#22c55e33}.network-command-center.orange{border-color:#f59e0b38}.network-command-center.red{border-color:#ef444438}.network-command-copy{align-content:center;gap:10px;min-width:0;display:grid}.network-eyebrow{width:fit-content;color:var(--brand);align-items:center;gap:7px;font-size:12px;font-weight:850;line-height:1.2;display:inline-flex}.network-command-copy h2{color:var(--text);letter-spacing:0;margin:0;font-size:31px;line-height:1.12}.network-command-copy p{max-width:760px;color:var(--text-muted);margin:0;font-size:13px;line-height:1.65}.network-command-status{flex-wrap:wrap;align-items:center;gap:8px 12px;margin-top:2px;display:flex}.network-command-status>span:not(.network-chip){color:var(--text-muted);align-items:center;gap:6px;font-size:12px;font-weight:720;display:inline-flex}.network-toolbar-status{color:var(--text-soft)}.access-score-card{background:#ffffffc7;border:1px solid #94a3b82e;border-radius:16px;align-content:center;gap:10px;min-width:0;padding:18px;display:grid;box-shadow:inset 0 1px #ffffffe0}.access-score-card>span{color:var(--text-muted);font-size:11px;font-weight:850;line-height:1.2}.access-score-card strong{color:var(--text);letter-spacing:0;font-size:38px;line-height:1}.access-score-card p{color:var(--text-muted);margin:0;font-size:12px;font-weight:700;line-height:1.45}.access-score-bar{background:#94a3b829;border-radius:999px;width:100%;height:8px;position:relative;overflow:hidden}.access-score-bar i{border-radius:inherit;background:linear-gradient(90deg,#22c55e,#3b82f6);min-width:0;max-width:100%;height:100%;display:block}.access-score-card .btn-secondary{width:100%}.network-error{color:var(--red);background:#ef444412;border:1px solid #ef444429;border-radius:12px;margin:0;padding:10px 12px;font-size:12px;font-weight:750;line-height:1.5}.signal-grid{grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;display:grid}.signal-card{background:#fff;border:1px solid #94a3b833;border-radius:16px;grid-template-columns:34px minmax(0,1fr);align-content:start;gap:12px;min-width:0;min-height:122px;padding:15px;display:grid;box-shadow:0 10px 22px #0f172a0d}.signal-card-icon{width:34px;height:34px;color:var(--blue);background:var(--blue-soft);border-radius:11px;place-items:center;display:grid}.signal-card span,.network-metric span,.network-block span{color:var(--text-muted);font-size:11px;font-weight:820;line-height:1.2;display:block}.signal-card strong,.network-metric strong,.network-block strong{min-width:0;color:var(--text);overflow-wrap:anywhere;margin-top:6px;font-size:15px;line-height:1.25;display:block}.signal-card p,.network-metric p,.network-block p{color:var(--text-muted);overflow-wrap:anywhere;margin:7px 0 0;font-size:12px;line-height:1.45}.signal-card.tone-green,.network-metric.tone-green,.network-block.tone-green{background:linear-gradient(#fff,#f0fdf4c7);border-color:#22c55e2e}.signal-card.tone-green .signal-card-icon{color:var(--green);background:var(--green-soft)}.signal-card.tone-orange,.network-metric.tone-orange,.network-block.tone-orange{background:linear-gradient(#fff,#fffbebd6);border-color:#f59e0b33}.signal-card.tone-orange .signal-card-icon{color:var(--orange);background:var(--orange-soft)}.signal-card.tone-red,.network-metric.tone-red,.network-block.tone-red{background:linear-gradient(#fff,#fef2f2db);border-color:#ef444433}.signal-card.tone-red .signal-card-icon{color:var(--red);background:var(--red-soft)}.signal-card.tone-slate,.network-metric.tone-slate,.network-block.tone-slate{background:linear-gradient(#fff,#f8fafce6)}.network-workspace{grid-template-columns:minmax(560px,1.16fr) minmax(360px,.84fr);align-items:start;gap:18px;display:grid}.network-section{gap:14px;display:grid}.network-platform-panel{padding:20px}.network-side{gap:18px;min-width:0;display:grid}.platform-summary-strip{flex-wrap:wrap;justify-content:flex-end;gap:8px;margin-left:auto;display:inline-flex}.platform-summary-chip{background:#fff;border:1px solid #94a3b82e;border-radius:12px;gap:3px;min-width:66px;padding:9px 11px;display:grid}.platform-summary-chip strong{color:var(--text);font-size:20px;font-weight:900;line-height:1}.platform-summary-chip span{color:var(--text-muted);font-size:10px;font-weight:820;line-height:1.1}.platform-summary-chip.green{background:#f0fdf4cc;border-color:#22c55e33}.platform-summary-chip.orange{background:#fffbebdb;border-color:#f59e0b33}.platform-summary-chip.red{background:#fef2f2db;border-color:#ef444433}.platform-access-header,.platform-access-row{grid-template-columns:minmax(150px,1.05fr) 108px 92px minmax(140px,1fr);align-items:center;column-gap:16px;display:grid}.platform-access-header{color:var(--text-muted);padding:0 16px;font-size:11px;font-weight:830;line-height:1}.platform-access-list{background:#fff;border:1px solid #94a3b82e;border-radius:15px;gap:0;display:grid;overflow:hidden}.platform-access-row{background:#fff;border-top:1px solid #94a3b821;min-width:0;padding:14px 16px 14px 18px;transition:background .14s;position:relative}.platform-access-row:first-child{border-top:0}.platform-access-row:hover{background:#f8fafcd1}.platform-access-row:before{content:"";background:#94a3b861;border-radius:999px;width:3px;position:absolute;inset:12px auto 12px 0}.platform-access-row.green{border-left:0}.platform-access-row.green:before{background:var(--green)}.platform-access-row.orange{border-left:0}.platform-access-row.orange:before{background:var(--orange)}.platform-access-row.red{border-left:0}.platform-access-row.red:before{background:var(--red)}.platform-access-main{align-items:center;gap:12px;min-width:0;display:flex}.platform-access-main>div{gap:3px;min-width:0;display:grid}.platform-access-main strong{color:var(--text);font-size:15px;font-weight:850;line-height:1.2}.platform-access-main span,.platform-access-result>span,.platform-access-latency span{color:var(--text-muted);overflow-wrap:anywhere;font-size:12px;font-weight:700;line-height:1.35}.platform-access-result,.platform-access-latency{justify-items:start;gap:5px;min-width:0;display:grid}.platform-access-latency strong{color:var(--text);font-size:14px;font-weight:850;line-height:1.2}.platform-access-detail{min-width:0}.platform-access-detail span{color:var(--text-muted);overflow-wrap:anywhere;font-size:12px;font-weight:720;line-height:1.45;display:block}.platform-icon{color:#fff;background:linear-gradient(135deg,#64748b,#334155);border-radius:12px;flex:none;place-items:center;width:38px;height:38px;font-size:13px;font-weight:900;display:grid;box-shadow:inset 0 1px #ffffff47}.platform-icon img{filter:brightness(0)invert();width:19px;height:19px;display:block}.platform-icon.green{background:linear-gradient(135deg,#22c55e,#16a34a)}.platform-icon.orange{background:linear-gradient(135deg,#f59e0b,#d97706)}.platform-icon.red{background:linear-gradient(135deg,#ef4444,#dc2626)}.platform-icon.platform-google{background:conic-gradient(from 35deg,#4285f4,#34a853 32%,#fbbc05 58%,#ea4335 78%,#4285f4)}.platform-icon.platform-chatgpt{background:linear-gradient(135deg,#10a37f,#0f766e 62%,#064e3b)}.platform-icon.platform-claude{background:linear-gradient(135deg,#d97706,#b45309 58%,#78350f)}.platform-icon.platform-youtube{background:linear-gradient(135deg,#f03,#dc2626 58%,#991b1b)}.platform-icon.platform-x{background:linear-gradient(135deg,#020617,#111827 58%,#475569)}.access-badge,.network-chip{white-space:nowrap;border:1px solid #0000;border-radius:999px;align-items:center;gap:6px;width:fit-content;min-height:26px;padding:0 10px;font-size:11px;font-weight:830;line-height:1;display:inline-flex}.access-badge.green,.network-chip.green{color:var(--green);background:#22c55e17;border-color:#22c55e2b}.access-badge.orange,.network-chip.orange{color:var(--orange);background:#f59e0b17;border-color:#f59e0b2e}.access-badge.red,.network-chip.red{color:var(--red);background:#ef444417;border-color:#ef44442e}.access-badge.blue,.network-chip.blue{color:var(--blue);background:#3b82f617;border-color:#3b82f62e}.access-badge.slate,.network-chip.slate{color:var(--text-muted);background:#64748b17;border-color:#64748b29}.network-list{gap:10px;display:grid}.network-list.compact-list{margin-top:2px}.network-list-item{background:#ffffffd1;border:1px solid #94a3b829;border-radius:13px;gap:8px;min-width:0;padding:12px;display:grid}.network-list-item p{color:var(--text-soft);overflow-wrap:anywhere;margin:0;font-size:12px;line-height:1.48}.network-dual{grid-template-columns:1fr;gap:10px;display:grid}.network-three-up{grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;display:grid}.network-metric,.network-block{background:#fff;border:1px solid #94a3b82e;border-radius:13px;min-width:0;padding:12px}.network-environment-panel{padding:20px}.dns-chip-row{flex-wrap:wrap;gap:8px;display:flex}.dns-chip{border:1px solid #0000;border-radius:999px;align-items:center;min-height:27px;padding:0 10px;font-size:11px;font-weight:800;display:inline-flex}.dns-chip.green{color:var(--green);background:#22c55e14;border-color:#22c55e29}.dns-chip.orange{color:var(--orange);background:#f59e0b14;border-color:#f59e0b29}.network-empty-state{min-height:132px;color:var(--text-muted);text-align:center;background:#f8fafcb8;border:1px dashed #94a3b847;border-radius:14px;place-items:center;padding:18px;font-size:13px;font-weight:760;display:grid}.network-page .section-head.compact h3{font-size:18px}.network-page .section-head.compact{margin-bottom:0}@media (width<=1180px){.network-command-center,.signal-grid,.network-workspace{grid-template-columns:1fr}.platform-summary-strip{justify-content:flex-start;margin-left:0}}@media (width<=860px){.network-command-center{padding:18px}.network-command-copy h2{font-size:25px}.platform-access-header{display:none}.platform-access-row{grid-template-columns:1fr;align-items:start;row-gap:10px;padding:14px 15px 14px 18px}.network-dual,.network-three-up{grid-template-columns:1fr}}.table-scroller{border:1px solid var(--line);background:#fff;border-radius:14px;overflow-x:auto}table{border-collapse:collapse;width:100%;min-width:720px}th,td{border-bottom:1px solid var(--line);text-align:left;vertical-align:top;color:var(--text-soft);padding:13px 12px;font-size:13px}th{color:var(--text-muted);background:#f8fafc;font-size:12px;font-weight:800}tbody tr:last-child td{border-bottom:0}td code{color:var(--text)}.method-pill,.status-pill{white-space:nowrap;border-radius:999px;justify-content:center;align-items:center;min-height:24px;padding:0 8px;font-size:11px;font-weight:800;display:inline-flex}.method-pill{color:#334155;background:#f1f5f9}.method-post{color:#4f46e5;background:#635bff1a}.method-get,.status-pill.is-ok{color:#047857;background:#22c55e1f}.status-pill.is-error{color:#dc2626;background:#ef44441f}.table-footer{color:var(--text-muted);margin-top:10px;font-size:12px}.log-toolbar{flex-wrap:wrap;gap:10px;margin-bottom:16px;display:flex}.log-search{flex:280px}.filter-chip{border:1px solid var(--line);background:#fff;border-radius:12px;align-items:center;gap:8px;min-height:40px;padding:0 12px;display:inline-flex}.filter-chip select{color:var(--text-soft);background:0 0;border:0;outline:none;font-size:13px;font-weight:700}.table-scroller tbody tr{cursor:pointer}.table-scroller tbody tr.is-selected{background:#635bff0a}.log-detail-panel{border:1px solid var(--line);background:#fffffff0;border-radius:16px;margin-top:16px;padding:18px 20px}.log-detail-head{justify-content:space-between;align-items:flex-start;gap:12px;margin-bottom:14px;display:flex}.log-detail-head h3{margin:0}.log-detail-head p{color:var(--text-muted);margin:6px 0 0;font-size:13px}.log-detail-grid{grid-template-columns:minmax(220px,.7fr) minmax(0,1.3fr);gap:14px;display:grid}.log-detail-meta{gap:10px;display:grid}.log-detail-meta div{gap:4px;display:grid}.log-detail-meta span{color:var(--text-muted);font-size:12px}.log-detail-meta strong{color:var(--text-soft);font-size:13px}.log-detail-pre{min-height:220px;max-height:320px}.switch-line{color:var(--text-soft);align-items:flex-start;gap:10px;font-size:13px;font-weight:700;line-height:1.5;display:flex}.settings-page{gap:18px;display:grid}.settings-page-head{justify-content:space-between;align-items:flex-start;gap:16px;display:flex}.settings-page-kicker{color:var(--brand);align-items:center;gap:6px;font-size:12px;font-weight:850;display:inline-flex}.settings-page-head h2{margin:8px 0 0;font-size:28px;line-height:1.15}.settings-page-head p{color:var(--text-muted);margin:8px 0 0;font-size:14px;line-height:1.6}.settings-page-actions{flex-wrap:wrap;justify-content:flex-end;gap:10px;display:flex}.settings-page-footer-actions{justify-content:flex-end;margin-top:2px}.settings-grid{grid-template-columns:repeat(4,minmax(220px,1fr));gap:14px;display:grid}.settings-section{border:1px solid var(--line);box-shadow:var(--shadow-sm);background:#fffffff0;border-radius:16px;gap:13px;padding:18px 20px;display:grid}.settings-section h4{margin:0;font-size:16px}@media (width<=1380px){.main-grid{grid-template-columns:1fr}.right-column{grid-template-columns:minmax(0,1fr)}.tester-card{order:-1}}@media (width<=1120px){.app-shell{grid-template-columns:1fr;width:calc(100vw - 20px);padding:10px}.sidebar{max-height:none;position:relative;top:0}.nav{grid-template-columns:repeat(6,minmax(max-content,1fr));padding-bottom:2px;overflow-x:auto}.nav-item{justify-content:center}.sidebar-status{display:none}.topbar{grid-template-columns:1fr}.top-actions{justify-content:flex-start}}@media (width<=900px){.app-shell{gap:12px;padding:0}.sidebar,.card,.trend-card,.log-table-wrap,.update-panel{border-left:0;border-right:0;border-radius:0}.sidebar{padding:16px 12px}.brand span,.sidebar-status{display:none}.nav{grid-template-columns:none;grid-auto-columns:max-content;grid-auto-flow:column}.nav-item{width:auto}.main{gap:12px}.topbar{padding:0 12px}.page-title h1{font-size:25px}.top-actions{flex-wrap:nowrap;padding-bottom:4px;overflow-x:auto}.summary-grid{grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;padding:0 12px}.summary-card{min-height:82px;padding:14px}.summary-card strong{font-size:19px}.main-grid{gap:12px}.section-head,.update-panel,.settings-drawer-footer{display:grid}.update-panel{grid-template-columns:auto minmax(0,1fr);align-items:start}.update-actions{grid-column:1/-1;grid-template-columns:1fr;justify-content:stretch;display:grid}.update-panel .btn-primary,.update-panel .btn-secondary{justify-content:center;width:100%}.section-actions,.button-row,.example-row{justify-content:flex-start}.filter-row{display:flex}.filter-row .control{flex:100%;min-width:0}.account-grid,.account-grid.profile-count-1,.account-grid.profile-count-2,.account-grid.profile-count-3,.account-grid.profile-count-many,.meta-grid{grid-template-columns:1fr}.account-card{border-radius:14px;padding:16px}.account-head{flex-direction:column;align-items:stretch}.account-select{width:max-content;margin-top:0}.compact-grid{grid-template-columns:1fr}.account-actions{display:flex}.tester-textarea{min-height:150px}.pre{max-height:320px}.modal-backdrop,.drawer-backdrop{padding:0}.modal-card,.settings-drawer{border-radius:0;width:100%;height:100%;max-height:none}.modal-body,.settings-drawer-body,.settings-drawer-head,.settings-drawer-footer{padding:16px}}@media (width<=460px){.summary-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.btn-primary,.btn-secondary,.btn-danger{min-height:40px;padding:0 12px}.card,.trend-card,.log-table-wrap{padding:16px 12px}}@media (width<=1120px){body{min-width:0}.app-shell{grid-template-columns:1fr;gap:14px;width:calc(100vw - 24px);padding:0}.sidebar{border-radius:24px;gap:14px;max-height:none;padding:22px 18px;position:relative;top:0}.brand span,.sidebar-status{display:none}.nav{grid-template-columns:repeat(5,minmax(max-content,1fr));grid-auto-columns:max-content;grid-auto-flow:column;padding-bottom:2px;overflow-x:auto}.nav-item{justify-content:center;width:auto}.topbar{grid-template-columns:1fr;padding:0}.page-title p{max-width:680px}.top-actions{flex-wrap:wrap;justify-content:flex-start;overflow:visible}.summary-grid{grid-template-columns:repeat(2,minmax(0,1fr));padding:0}.overview-grid,.tester-route-grid,.main-grid{grid-template-columns:1fr}.sidebar,.card,.trend-card,.log-table-wrap,.update-panel{border-left:1px solid var(--line);border-right:1px solid var(--line);border-radius:20px}.launch-page{grid-template-columns:1fr}.launch-visual{min-height:420px}.account-grid.profile-count-2{grid-template-columns:repeat(2,minmax(340px,1fr))}.account-grid.profile-count-3{grid-template-columns:repeat(3,minmax(320px,1fr))}.account-grid.profile-count-many{grid-template-columns:repeat(auto-fit,minmax(340px,1fr))}.meta-grid,.compact-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
|