@mars-sea/dsh-commandcode-provider 0.15.4 → 0.16.0

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/lib/client.js CHANGED
@@ -1856,6 +1856,8 @@ window.__ModuleLoader__.load({
1856
1856
  generation = 0;
1857
1857
  inFlight = false;
1858
1858
  disposed = false;
1859
+ /** When the last fetch SETTLED successfully, for {@link USAGE_AUTO_REFRESH_COALESCE_MS}. */
1860
+ settledAt;
1859
1861
  constructor(remote) {
1860
1862
  this.remote = remote;
1861
1863
  }
@@ -1877,9 +1879,16 @@ window.__ModuleLoader__.load({
1877
1879
  /**
1878
1880
  * Fetch (or refetch) the report. Concurrent refreshes collapse onto one
1879
1881
  * request; a superseded fetch's late result is dropped, never published.
1882
+ *
1883
+ * `auto` marks a refresh nobody asked for — a surface appearing, or the
1884
+ * background tick. Those also reuse a result fetched within
1885
+ * {@link USAGE_AUTO_REFRESH_COALESCE_MS}, because two surfaces mounting a
1886
+ * second apart are asking the same question. Every other caller is a user
1887
+ * action and always goes to the wire.
1880
1888
  */
1881
- async refresh() {
1889
+ async refresh(options) {
1882
1890
  if (this.disposed || this.inFlight) return;
1891
+ if (options?.auto === true && this.current.status === "ready" && this.settledAt !== void 0 && Date.now() - this.settledAt < 5e3) return;
1883
1892
  const generation = ++this.generation;
1884
1893
  this.inFlight = true;
1885
1894
  this.current = {
@@ -1891,13 +1900,16 @@ window.__ModuleLoader__.load({
1891
1900
  try {
1892
1901
  const response = await this.remote.report();
1893
1902
  if (this.disposed || generation !== this.generation) return;
1894
- if (response.ok) this.current = {
1895
- status: "ready",
1896
- report: response.value,
1897
- error: void 0,
1898
- fetchedAt: Date.now()
1899
- };
1900
- else this.current = {
1903
+ if (response.ok) {
1904
+ const fetchedAt = Date.now();
1905
+ this.settledAt = fetchedAt;
1906
+ this.current = {
1907
+ status: "ready",
1908
+ report: response.value,
1909
+ error: void 0,
1910
+ fetchedAt
1911
+ };
1912
+ } else this.current = {
1901
1913
  ...this.current,
1902
1914
  status: "error",
1903
1915
  error: response.error.message
@@ -2887,6 +2899,23 @@ window.__ModuleLoader__.load({
2887
2899
  ]
2888
2900
  };
2889
2901
  //#endregion
2902
+ //#region src/client/random-id.ts
2903
+ /**
2904
+ * 页面身份(issue #102)。`crypto.randomUUID` 是安全上下文专属:局域网里用
2905
+ * `http://<ip>:3080` 打开设置页时它是 `undefined`,而这里在控制器构造期就调用,
2906
+ * 异常会落到设置页插槽的错误边界上,整块内容变成空白。`crypto.getRandomValues`
2907
+ * 在非安全上下文同样可用,因此只在 `randomUUID` 不可用时按 RFC 4122 自行拼一个 v4。
2908
+ */
2909
+ function randomId() {
2910
+ const source = globalThis.crypto;
2911
+ if (typeof source.randomUUID === "function") return source.randomUUID();
2912
+ const bytes = source.getRandomValues(/* @__PURE__ */ new Uint8Array(16));
2913
+ bytes[6] = bytes[6] & 15 | 64;
2914
+ bytes[8] = bytes[8] & 63 | 128;
2915
+ const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
2916
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
2917
+ }
2918
+ //#endregion
2890
2919
  //#region src/client/enrollment.ts
2891
2920
  const live = (state) => [
2892
2921
  "creating",
@@ -2912,7 +2941,7 @@ window.__ModuleLoader__.load({
2912
2941
  watch;
2913
2942
  ready;
2914
2943
  openedPage = false;
2915
- constructor(remote, changed, pollMs = 750, newId = () => crypto.randomUUID(), openPage = openLoginPage) {
2944
+ constructor(remote, changed, pollMs = 750, newId = randomId, openPage = openLoginPage) {
2916
2945
  this.remote = remote;
2917
2946
  this.changed = changed;
2918
2947
  this.pollMs = pollMs;
@@ -3243,7 +3272,6 @@ window.__ModuleLoader__.load({
3243
3272
  usageCost: "花费",
3244
3273
  usageCompleted: "成功",
3245
3274
  usageBillingPeriod: "官方账期用量",
3246
- usagePeriodUnknown: "官方用量汇总,统计周期未确认",
3247
3275
  usageTokens: "Token",
3248
3276
  usageTokensIn: "入",
3249
3277
  usageTokensOut: "出",
@@ -3330,7 +3358,7 @@ window.__ModuleLoader__.load({
3330
3358
  accountEnrollmentCleanupRetry: "重试清理",
3331
3359
  accountRefSeparator: ":",
3332
3360
  accountModels: "专用模型",
3333
- accountModelsHint: "选中的模型优先由此账户处理,不可用时仍会自动切换;一个模型只归属一个账户。",
3361
+ accountModelsHint: "选中即专属:优先此账户处理,不可用时自动切换",
3334
3362
  accountModelOwner: "属于 {name}",
3335
3363
  modelPick: "选择模型…",
3336
3364
  modelCount: "已选 {count} 个模型",
@@ -3515,7 +3543,6 @@ window.__ModuleLoader__.load({
3515
3543
  usageCost: "Spend",
3516
3544
  usageCompleted: "Completed",
3517
3545
  usageBillingPeriod: "Official billing-period usage",
3518
- usagePeriodUnknown: "Official usage summary; the billing period is unconfirmed",
3519
3546
  usageTokens: "Tokens",
3520
3547
  usageTokensIn: "in",
3521
3548
  usageTokensOut: "out",
@@ -3602,7 +3629,7 @@ window.__ModuleLoader__.load({
3602
3629
  accountEnrollmentCleanupRetry: "Retry cleanup",
3603
3630
  accountRefSeparator: ": ",
3604
3631
  accountModels: "Dedicated models",
3605
- accountModelsHint: "These models prefer this account and still switch when it is unavailable; a model belongs to one account.",
3632
+ accountModelsHint: "Selected models are dedicated to this account and still switch when unavailable",
3606
3633
  accountModelOwner: "on {name}",
3607
3634
  modelPick: "Choose models…",
3608
3635
  modelCount: "{count} models selected",
@@ -3837,7 +3864,7 @@ window.__ModuleLoader__.load({
3837
3864
  }
3838
3865
  //#endregion
3839
3866
  //#region package.json
3840
- var version = "0.15.4";
3867
+ var version = "0.16.0";
3841
3868
  var repository = {
3842
3869
  "type": "git",
3843
3870
  "url": "git+https://github.com/Mars-Sea/dsh-commandcode-provider.git"
@@ -4863,7 +4890,7 @@ window.__ModuleLoader__.load({
4863
4890
  "aria-valuenow": Math.round(ratio * 100),
4864
4891
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4865
4892
  className: exceeded ? "cc-usageBarFill cc-usageBarFillWarn" : "cc-usageBarFill",
4866
- style: { width: `${ratio * 100}%` }
4893
+ style: { transform: `translateX(${(ratio - 1) * 100}%)` }
4867
4894
  })
4868
4895
  }),
4869
4896
  reset !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
@@ -4877,6 +4904,19 @@ window.__ModuleLoader__.load({
4877
4904
  ]
4878
4905
  });
4879
4906
  }
4907
+ /**
4908
+ * Left text of the balance label row: the billing period, stated once per card.
4909
+ * 用量汇总与订阅起点对齐时给完整区间;对不上(或用量汇总没取到)时退回订阅
4910
+ * 自身的截止日,它不依赖对齐也可靠。余额没取到时没有「月额度剩余」可挂,完整
4911
+ * 区间前补回「官方账期用量」,避免一串裸日期没有上下文。
4912
+ */
4913
+ function balanceLabelText(report, t) {
4914
+ const plan = report.plan;
4915
+ const usagePeriod = formatUsageBillingPeriod(report.usage?.periodStart, plan?.currentPeriodStart, plan?.currentPeriodEnd);
4916
+ const periodText = usagePeriod !== "" ? usagePeriod : plan !== void 0 && plan.currentPeriodEnd > 0 ? `${t("usagePeriodEnd")} ${new Date(plan.currentPeriodEnd).toLocaleDateString()}` : "";
4917
+ if (report.credits !== void 0) return periodText !== "" ? `${t("usageMonthlyLeft")} · ${periodText}` : t("usageMonthlyLeft");
4918
+ return usagePeriod !== "" ? `${t("usageBillingPeriod")} ${usagePeriod}` : periodText;
4919
+ }
4880
4920
  /** One pool account's facts (identity, totals, credits, window limits). */
4881
4921
  function AccountReport({ entry, fetchedAt, t, headless = false }) {
4882
4922
  const report = entry.report;
@@ -4886,9 +4926,12 @@ window.__ModuleLoader__.load({
4886
4926
  const plan = report.plan;
4887
4927
  const planName = plan?.name ?? "";
4888
4928
  const planStatus = plan !== void 0 && plan.status !== "" && plan.status !== "active" ? plan.status : "";
4889
- const showPeriod = plan !== void 0 && plan.currentPeriodEnd > 0;
4890
4929
  const showPartial = report.failures.length > 0 && report.blocked === void 0;
4891
- const usagePeriod = formatUsageBillingPeriod(report.usage?.periodStart, plan?.currentPeriodStart, plan?.currentPeriodEnd);
4930
+ const label = balanceLabelText(report, t);
4931
+ const updatedText = fetchedAt === void 0 ? "" : `${t("usageUpdated")} ${new Date(fetchedAt).toLocaleTimeString(void 0, {
4932
+ hour: "2-digit",
4933
+ minute: "2-digit"
4934
+ })}`;
4892
4935
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4893
4936
  className: "cc-accountReport",
4894
4937
  children: [
@@ -4927,26 +4970,33 @@ window.__ModuleLoader__.load({
4927
4970
  children: report.failures.join(" · ")
4928
4971
  }) : null]
4929
4972
  }) : null,
4930
- credits !== void 0 ? (() => {
4931
- const monthlyKnown = credits.monthlyReported !== false;
4932
- const depleted = monthlyKnown && credits.monthlyCredits <= 0;
4973
+ (() => {
4974
+ const monthlyKnown = credits !== void 0 && credits.monthlyReported !== false;
4975
+ const depleted = credits !== void 0 && monthlyKnown && credits.monthlyCredits <= 0;
4976
+ if (label === "" && updatedText === "") return null;
4933
4977
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4934
4978
  className: depleted ? "cc-balance cc-balanceEmpty" : "cc-balance",
4935
4979
  children: [
4936
4980
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4937
- className: "cc-balanceMain",
4981
+ className: "cc-balanceRow",
4938
4982
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4939
4983
  className: "cc-balanceLabel",
4940
- children: t("usageMonthlyLeft")
4941
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4984
+ children: label
4985
+ }), updatedText !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4986
+ className: "cc-balanceLabel",
4987
+ children: updatedText
4988
+ }) : null]
4989
+ }),
4990
+ credits !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4991
+ className: "cc-balanceRow",
4992
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4942
4993
  className: "cc-balanceValue",
4943
4994
  children: monthlyKnown ? formatMoney(Math.max(0, credits.monthlyCredits)) : "—"
4995
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4996
+ className: "cc-balanceSub",
4997
+ children: `${t("usagePurchased")} ${credits.purchasedReported === false ? "—" : formatMoney(credits.purchasedCredits)} · ${t("usageFree")} ${credits.freeReported === false ? "—" : formatMoney(credits.freeCredits)}`
4944
4998
  })]
4945
- }),
4946
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4947
- className: "cc-balanceSub",
4948
- children: `${t("usagePurchased")} ${credits.purchasedReported === false ? "—" : formatMoney(credits.purchasedCredits)} · ${t("usageFree")} ${credits.freeReported === false ? "—" : formatMoney(credits.freeCredits)}`
4949
- }),
4999
+ }) : null,
4950
5000
  depleted ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4951
5001
  className: "cc-balanceNote",
4952
5002
  role: "status",
@@ -4954,11 +5004,7 @@ window.__ModuleLoader__.load({
4954
5004
  }) : null
4955
5005
  ]
4956
5006
  });
4957
- })() : null,
4958
- report.usage !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4959
- className: "cc-usageUpdated",
4960
- children: usagePeriod ? `${t("usageBillingPeriod")} ${usagePeriod}` : t("usagePeriodUnknown")
4961
- }) : null,
5007
+ })(),
4962
5008
  report.usage !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4963
5009
  className: "cc-usageStats cc-usageStatsActivity",
4964
5010
  children: [
@@ -5000,35 +5046,10 @@ window.__ModuleLoader__.load({
5000
5046
  t
5001
5047
  }) : null]
5002
5048
  }) : null,
5003
- showPeriod || showPartial || fetchedAt !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5004
- className: "cc-usageMeta",
5005
- children: [
5006
- showPeriod ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
5007
- className: "cc-usageUpdated",
5008
- children: [
5009
- t("usagePeriodEnd"),
5010
- " ",
5011
- new Date(plan.currentPeriodEnd).toLocaleDateString()
5012
- ]
5013
- }) : null,
5014
- showPartial ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5015
- className: "cc-usagePartial",
5016
- title: report.failures.join("; "),
5017
- children: t("usagePartial")
5018
- }) : null,
5019
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cc-usageMetaSpacer" }),
5020
- fetchedAt !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
5021
- className: "cc-usageUpdated",
5022
- children: [
5023
- t("usageUpdated"),
5024
- " ",
5025
- new Date(fetchedAt).toLocaleTimeString(void 0, {
5026
- hour: "2-digit",
5027
- minute: "2-digit"
5028
- })
5029
- ]
5030
- }) : null
5031
- ]
5049
+ showPartial ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5050
+ className: "cc-usagePartial",
5051
+ title: report.failures.join("; "),
5052
+ children: t("usagePartial")
5032
5053
  }) : null
5033
5054
  ]
5034
5055
  });
@@ -5201,6 +5222,128 @@ window.__ModuleLoader__.load({
5201
5222
  });
5202
5223
  }
5203
5224
  /**
5225
+ * The glanceable header strip: account health, monthly remainder, service
5226
+ * status, model scope. Every string reuses one the groups already compute
5227
+ * (group summaries, balance labels, the service badge), so the strip adds no
5228
+ * locale keys and cannot disagree with the card below it. Cells are plain
5229
+ * text, not links — the groups beneath are the place to act.
5230
+ */
5231
+ function StatusStrip({ t, state, usage, snapshot, probe }) {
5232
+ const rows = accountRows(state, usage, t);
5233
+ const issues = accountIssues(rows.map((row) => row.usage));
5234
+ const defaultRow = rows.find((row) => row.id === "default");
5235
+ const credits = defaultRow?.usage?.report?.credits;
5236
+ const monthly = credits !== void 0 && credits.monthlyReported !== false ? credits : void 0;
5237
+ const depleted = monthly !== void 0 && monthly.monthlyCredits <= 0;
5238
+ const planName = defaultRow?.usage?.report?.plan?.name ?? "";
5239
+ const overridden = modelsOverriddenCount(state);
5240
+ const view = serviceStatusView(snapshot, probe, t, Date.now());
5241
+ const serviceDot = view.badge.tone === "ok" ? "cc-tabDot cc-tabDotOk" : view.badge.tone === "warn" ? "cc-tabDot cc-tabDotWarn" : view.badge.tone === "error" ? "cc-tabDot cc-tabDotError" : "cc-tabDot cc-tabDotBlank";
5242
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5243
+ className: "cc-statusStrip",
5244
+ children: [
5245
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5246
+ className: "cc-statusCell",
5247
+ children: [
5248
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5249
+ className: "cc-statusHead",
5250
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5251
+ className: issues.severe ? "cc-tabDot cc-tabDotError" : issues.count > 0 ? "cc-tabDot cc-tabDotWarn" : "cc-tabDot cc-tabDotOk",
5252
+ "aria-hidden": "true"
5253
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5254
+ className: "cc-statusLabel",
5255
+ children: t("accountsTitle")
5256
+ })]
5257
+ }),
5258
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5259
+ className: "cc-statusStripValue",
5260
+ children: rows.length === 1 ? t("accountsSummaryOne") : t("accountsSummaryMany", { count: rows.length })
5261
+ }),
5262
+ issues.count > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5263
+ className: "cc-statusSub",
5264
+ children: t("accountsSummaryIssues", { count: issues.count })
5265
+ }) : planName !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5266
+ className: "cc-statusSub",
5267
+ children: planName
5268
+ }) : null
5269
+ ]
5270
+ }),
5271
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5272
+ className: "cc-statusCell",
5273
+ children: [
5274
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5275
+ className: "cc-statusHead",
5276
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5277
+ className: depleted ? "cc-tabDot cc-tabDotError" : monthly !== void 0 ? "cc-tabDot cc-tabDotOk" : "cc-tabDot cc-tabDotBlank",
5278
+ "aria-hidden": "true"
5279
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5280
+ className: "cc-statusLabel",
5281
+ children: t("usageMonthlyLeft")
5282
+ })]
5283
+ }),
5284
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5285
+ className: "cc-statusStripValue",
5286
+ children: monthly !== void 0 ? formatMoney(Math.max(0, monthly.monthlyCredits)) : "—"
5287
+ }),
5288
+ credits !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5289
+ className: "cc-statusSub",
5290
+ children: `${t("usagePurchased")} ${credits.purchasedReported === false ? "—" : formatMoney(credits.purchasedCredits)} · ${t("usageFree")} ${credits.freeReported === false ? "—" : formatMoney(credits.freeCredits)}`
5291
+ }) : null
5292
+ ]
5293
+ }),
5294
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5295
+ className: "cc-statusCell",
5296
+ children: [
5297
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5298
+ className: "cc-statusHead",
5299
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5300
+ className: serviceDot,
5301
+ "aria-hidden": "true"
5302
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5303
+ className: "cc-statusLabel",
5304
+ children: t("serviceStatusTitle")
5305
+ })]
5306
+ }),
5307
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5308
+ className: "cc-statusStripValue",
5309
+ children: view.badge.label
5310
+ }),
5311
+ view.updated !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5312
+ className: "cc-statusSub",
5313
+ children: view.updated
5314
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5315
+ className: "cc-statusSub",
5316
+ children: view.local
5317
+ })
5318
+ ]
5319
+ }),
5320
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5321
+ className: "cc-statusCell",
5322
+ children: [
5323
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
5324
+ className: "cc-statusHead",
5325
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5326
+ className: "cc-tabDot",
5327
+ "aria-hidden": "true"
5328
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5329
+ className: "cc-statusLabel",
5330
+ children: t("modelsTitle")
5331
+ })]
5332
+ }),
5333
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5334
+ className: "cc-statusStripValue",
5335
+ children: state.visibleModelsAll ? t("visibleModelsSummaryAll") : t("visibleModelsSummary", { count: state.visibleModels.length })
5336
+ }),
5337
+ overridden > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5338
+ className: "cc-statusSub",
5339
+ children: overriddenText(overridden, t)
5340
+ }) : null
5341
+ ]
5342
+ })
5343
+ ]
5344
+ });
5345
+ }
5346
+ /**
5204
5347
  * One top-level group of the page (决策记录第十章第4条). All five groups share
5205
5348
  * this one shape so they cannot drift apart: the heading is an `h3` wrapping a
5206
5349
  * single full-width button — chevron first, then the title and the summary
@@ -5211,14 +5354,15 @@ window.__ModuleLoader__.load({
5211
5354
  * The body stays mounted and is hidden with the `hidden` attribute rather than
5212
5355
  * unrendered: `aria-controls` then always points at a real element, and the
5213
5356
  * save bar's locate action can focus an invalid field right after opening its
5214
- * group. Folding is presentation only — staged drafts, validation and the save
5215
- * path are untouched (第六章第1、2条).
5357
+ * group. Folding is presentation only and instant toggling is deliberate (it
5358
+ * happens too often to be worth a wait); staged drafts, validation and the
5359
+ * save path are untouched (第六章第1、2条).
5216
5360
  */
5217
- function FoldableGroup({ id, title, divided = true, expanded, onToggle, badges, action, children }) {
5361
+ function FoldableGroup({ id, title, expanded, onToggle, badges, action, children }) {
5218
5362
  const titleId = `cc-group-${id}-title`;
5219
5363
  const bodyId = `cc-group-${id}-body`;
5220
5364
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
5221
- className: divided ? "cc-group cc-groupDivided" : "cc-group",
5365
+ className: "cc-group",
5222
5366
  "aria-labelledby": titleId,
5223
5367
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5224
5368
  className: "cc-groupHead",
@@ -5850,7 +5994,7 @@ window.__ModuleLoader__.load({
5850
5994
  "aria-hidden": "true",
5851
5995
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5852
5996
  className: limit.exceeded ? "cc-miniMeterFill cc-usageBarFillWarn" : "cc-miniMeterFill",
5853
- style: { width: `${ratio * 100}%` }
5997
+ style: { transform: `translateX(${(ratio - 1) * 100}%)` }
5854
5998
  })
5855
5999
  }),
5856
6000
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -6459,8 +6603,13 @@ window.__ModuleLoader__.load({
6459
6603
  * The unified account card: every account the pool rotates through, one row
6460
6604
  * each, with its status and quota inline — so one account's facts no longer
6461
6605
  * have to be cross-read across separate surfaces.
6606
+ *
6607
+ * Exported for the render test (`tests/accounts-card-render.test.ts`) that pins
6608
+ * the first-paint auto-refresh wiring against a real React tree: the page mounts
6609
+ * this card, and a test that only checks the injected function exists cannot see
6610
+ * the card reaching for it.
6462
6611
  */
6463
- function AccountsCard({ t, state, usage, login, disabled, actions, onRefresh, openAnalytics, createEnrollment, expanded, onToggle }) {
6612
+ function AccountsCard({ t, state, usage, login, disabled, actions, onRefresh, onRefreshAuto, openAnalytics, createEnrollment, expanded, onToggle }) {
6464
6613
  const [adding, setAdding] = (0, react.useState)(false);
6465
6614
  const [enrollment] = (0, react.useState)(createEnrollment);
6466
6615
  const snapshot = (0, react.useSyncExternalStore)(enrollment.store.subscribe, enrollment.store.getSnapshot);
@@ -6471,8 +6620,8 @@ window.__ModuleLoader__.load({
6471
6620
  const closeAdd = (0, react.useCallback)(() => setAdding(false), []);
6472
6621
  const { loading, shouldRefresh } = usageCardState(usage);
6473
6622
  (0, react.useEffect)(() => {
6474
- if (shouldRefresh) onRefresh();
6475
- }, [shouldRefresh, onRefresh]);
6623
+ if (shouldRefresh) onRefreshAuto();
6624
+ }, [shouldRefresh, onRefreshAuto]);
6476
6625
  const rows = accountRows(state, usage, t).filter((row) => row.ref === void 0 || !state.accountEnrollmentRefs.includes(row.ref));
6477
6626
  const multi = rows.length > 1;
6478
6627
  const pinnedRow = rows.find((row) => row.id === state.activeAccount);
@@ -6480,7 +6629,6 @@ window.__ModuleLoader__.load({
6480
6629
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(FoldableGroup, {
6481
6630
  id: "accounts",
6482
6631
  title: t("accountsTitle"),
6483
- divided: false,
6484
6632
  expanded,
6485
6633
  onToggle,
6486
6634
  badges: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupSummary, { parts: [rows.length === 1 ? t("accountsSummaryOne") : t("accountsSummaryMany", { count: rows.length })] }), issues.count > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -6948,6 +7096,13 @@ window.__ModuleLoader__.load({
6948
7096
  className: "cc-intro",
6949
7097
  children: t("intro")
6950
7098
  }),
7099
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusStrip, {
7100
+ t,
7101
+ state,
7102
+ usage,
7103
+ snapshot: status.snapshot,
7104
+ probe: status.probe
7105
+ }),
6951
7106
  !state.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Notice, {
6952
7107
  icon: "lock",
6953
7108
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("readOnly") })
@@ -6960,6 +7115,7 @@ window.__ModuleLoader__.load({
6960
7115
  disabled,
6961
7116
  actions,
6962
7117
  onRefresh: props.refreshUsage,
7118
+ onRefreshAuto: props.refreshUsageAuto,
6963
7119
  openAnalytics: props.openAnalytics,
6964
7120
  createEnrollment: props.createEnrollment,
6965
7121
  ...foldProps("accounts")
@@ -7899,6 +8055,14 @@ window.__ModuleLoader__.load({
7899
8055
  set: (callback, ms) => setTimeout(callback, ms),
7900
8056
  clear: (handle) => clearTimeout(handle)
7901
8057
  };
8058
+ const DOCUMENT_VISIBILITY = {
8059
+ isVisible: () => typeof document === "undefined" ? true : document.visibilityState !== "hidden",
8060
+ subscribe: (onChange) => {
8061
+ if (typeof document === "undefined") return () => void 0;
8062
+ document.addEventListener("visibilitychange", onChange);
8063
+ return () => document.removeEventListener("visibilitychange", onChange);
8064
+ }
8065
+ };
7902
8066
  /** Live mount count of {@link startPanelAutoRefresh}. */
7903
8067
  let references = 0;
7904
8068
  /** Owner ticket of the running loop, if any. */
@@ -7908,6 +8072,16 @@ window.__ModuleLoader__.load({
7908
8072
  /** Pending timer handle of the running loop. */
7909
8073
  let handle;
7910
8074
  /**
8075
+ * Unsubscribe of the visibility listener owned by the RUNNING loop.
8076
+ *
8077
+ * Module-level on purpose: only the surface that started the loop receives the
8078
+ * subscribe handle, but that surface is not necessarily the last to unmount.
8079
+ * Keeping the handle on the same layer as `activeTicket`/`handle` lets any
8080
+ * teardown order release it; a function-local handle leaked the listener
8081
+ * whenever the starter unmounted first.
8082
+ */
8083
+ let visibilityUnsubscribe;
8084
+ /**
7911
8085
  * Start the shared auto refresh. One fetch when the surface appears (the sidebar
7912
8086
  * row is the point of the panel — it must be current, not wait for a click), then
7913
8087
  * a tick every {@link PANEL_AUTO_REFRESH_MS} while a surface stays mounted.
@@ -7917,22 +8091,37 @@ window.__ModuleLoader__.load({
7917
8091
  * tick goes through `usage.refresh()`, which collapses a concurrent fetch onto
7918
8092
  * the in-flight one. Returns the disposer that drops this surface's reference.
7919
8093
  *
8094
+ * A hidden document is still mounted, so its ticks are SKIPPED: a background
8095
+ * tab must not keep paying for a four-call report nobody reads. The tick is
8096
+ * rescheduled either way, so the loop survives, and becoming visible again
8097
+ * fetches at once — the data on screen is by then older than a tick would have
8098
+ * left it. The first paint still fetches unconditionally: it is the moment the
8099
+ * row appears, and there is nothing to show without it.
8100
+ *
7920
8101
  * @param usage - the shared usage controller.
7921
8102
  * @param isConfigured - whether a credential exists right now (re-read per tick).
7922
8103
  * @param timer - timer seam for tests.
8104
+ * @param visibility - visibility seam; see {@link AutoRefreshVisibility}.
7923
8105
  */
7924
- function startPanelAutoRefresh(usage, isConfigured, timer = REAL_TIMER) {
8106
+ function startPanelAutoRefresh(usage, isConfigured, timer = REAL_TIMER, visibility = DOCUMENT_VISIBILITY) {
7925
8107
  references += 1;
7926
8108
  const ticket = ++ticketSeq;
8109
+ const fetch = () => {
8110
+ if (isConfigured()) usage.refresh({ auto: true });
8111
+ };
7927
8112
  const tick = () => {
7928
8113
  if (activeTicket !== ticket) return;
7929
- if (isConfigured()) usage.refresh();
8114
+ if (visibility.isVisible()) fetch();
7930
8115
  handle = timer.set(tick, PANEL_AUTO_REFRESH_MS);
7931
8116
  };
7932
8117
  if (activeTicket === void 0) {
7933
8118
  activeTicket = ticket;
7934
8119
  handle = timer.set(tick, PANEL_AUTO_REFRESH_MS);
7935
- if (isConfigured()) usage.refresh();
8120
+ fetch();
8121
+ visibilityUnsubscribe = visibility.subscribe?.(() => {
8122
+ if (activeTicket !== ticket || !visibility.isVisible()) return;
8123
+ fetch();
8124
+ });
7936
8125
  }
7937
8126
  let stopped = false;
7938
8127
  return () => {
@@ -7941,6 +8130,8 @@ window.__ModuleLoader__.load({
7941
8130
  references -= 1;
7942
8131
  if (references > 0) return;
7943
8132
  activeTicket = void 0;
8133
+ visibilityUnsubscribe?.();
8134
+ visibilityUnsubscribe = void 0;
7944
8135
  if (handle !== void 0) {
7945
8136
  timer.clear(handle);
7946
8137
  handle = void 0;
@@ -10229,13 +10420,26 @@ window.__ModuleLoader__.load({
10229
10420
  * The settings-page stylesheet.
10230
10421
  *
10231
10422
  * Every size is the design canvas's (the cc-kit.css of the "CommandCode 设置页
10232
- * 重设计" artboards, 2026-10-21), with its colour tokens mapped onto the
10233
- * harness theme aliases so dark mode keeps working: a row is 12px of vertical
10234
- * padding with no line between rows, title 14/22, description 12/18, a 24px
10235
- * gap to the control; a group is 16px of padding under a 1px border-l2 rule,
10236
- * its heading 36px high; the page title is 18/26 and group titles 14/22, both
10237
- * 600. The page renders with grayscale antialiasing and descriptive text in
10238
- * label-secondary.
10423
+ * 重设计" artboards, 2026-10-21, as revised by the 2026-10-29 card redesign):
10424
+ * a row is 14px of vertical padding with a .5px hairline between rows, title
10425
+ * 14/22, description 12/18, a 24px gap to the control; a group is a 16px-radius
10426
+ * card on the raised surface with a 1px border-l2 stroke, its heading 52px
10427
+ * high; the page title is 22/28 and group titles 15/22, both 600. The page
10428
+ * renders with grayscale antialiasing and descriptive text in label-secondary.
10429
+ * A status strip under the header (account health, monthly remainder, service
10430
+ * status, model scope) reuses the groups' own summary strings, so it adds no
10431
+ * locale keys and cannot disagree with the card below it.
10432
+ *
10433
+ * Motion (2026-10-29): anything pressable scales on :active (0.97, the 20–28px
10434
+ * icon buttons 0.95) over 160ms of one strong ease-out,
10435
+ * cubic-bezier(.23,1,.32,1), which the fold chevron, save bar, usage bar,
10436
+ * card entrance and notice entrance share; colour and border changes fade over
10437
+ * 150ms `ease`. Only transform, opacity and colours animate — never layout.
10438
+ * Every :hover rule sits in the one `(hover:hover) and (pointer:fine)` block at
10439
+ * the end, so a tap on a touch screen does not leave a stuck hover colour;
10440
+ * :active stays outside it, since a press needs feedback on touch too. Folding
10441
+ * a group or an account card stays instant: it happens too often to be worth a
10442
+ * wait, and a hidden body keeps its `aria-controls` target mounted.
10239
10443
  *
10240
10444
  * Shapes follow one rule — clickable things are pills, fillable things are
10241
10445
  * 8px. Buttons are drawn here (PageButton → cc-btn: md 32px, sm 28px, lg 36px
@@ -10249,27 +10453,61 @@ window.__ModuleLoader__.load({
10249
10453
  * shares cc-hint / cc-badgeMuted / cc-input and keeps their colours.
10250
10454
  */
10251
10455
  const PAGE_CSS = `
10252
- .cc-section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;display:flex;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}
10253
- .cc-title{margin:0;color:var(--dsw-alias-label-primary);font-size:18px;font-weight:600;line-height:26px}
10456
+ .cc-section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;display:flex;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;--cc-ease-out:cubic-bezier(.23,1,.32,1)}
10457
+ .cc-title{margin:0;color:var(--dsw-alias-label-primary);font-size:22px;font-weight:700;line-height:28px}
10254
10458
  .cc-intro{margin:4px 0 0;color:var(--dsw-alias-label-secondary);font-size:14px;line-height:22px}
10459
+ /* The glanceable header strip: four cells (account health, monthly remainder,
10460
+ * service status, model scope) on one card. Labels reuse cc-statusLabel and
10461
+ * dots reuse cc-tabDot, so no new colour language is introduced; the strip
10462
+ * folds to 2 × 2 in a narrow panel. */
10463
+ .cc-statusStrip{grid-template-columns:repeat(4,minmax(0,1fr));gap:0;display:grid;margin-top:16px;border:1px solid var(--dsw-alias-border-l2);border-radius:16px;background:var(--dsw-alias-bg-layer-1);box-shadow:0 1px 2px rgba(0,0,0,.04)}
10464
+ .cc-statusCell{flex-direction:column;gap:2px;min-width:0;display:flex;padding:14px 16px}
10465
+ .cc-statusCell+.cc-statusCell{border-left:1px solid var(--dsw-alias-border-l2)}
10466
+ .cc-statusHead{align-items:center;gap:6px;display:flex}
10467
+ .cc-statusStripValue{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:22px}
10468
+ .cc-statusSub{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
10469
+ @media (max-width:560px){.cc-statusStrip{grid-template-columns:repeat(2,minmax(0,1fr))}.cc-statusCell:nth-child(3){border-left:0}.cc-statusCell:nth-child(n+3){border-top:1px solid var(--dsw-alias-border-l2)}}
10255
10470
  .cc-spacer{flex:1}
10256
- .cc-group{flex-direction:column;display:flex;padding:16px 0}
10257
- .cc-group:not(.cc-groupDivided){padding-top:24px}
10258
- .cc-groupDivided{border-top:1px solid var(--dsw-alias-border-l2)}
10259
- .cc-groupHead{align-items:center;gap:8px;display:flex;min-height:36px}
10260
- .cc-groupTitle{margin:0;color:var(--dsw-alias-label-primary);font-size:14px;font-weight:600;line-height:22px}
10471
+ /* Groups are cards now (2026-10-29 redesign): a 16px-radius raised surface
10472
+ * with a border-l2 stroke, 16px between cards, no divider rules. The entrance
10473
+ * is a @starting-style rise with a 40ms stagger per card — transitions, not
10474
+ * keyframes, so a re-render never replays it. */
10475
+ .cc-group{flex-direction:column;display:flex;margin-top:16px;border:1px solid var(--dsw-alias-border-l2);border-radius:16px;background:var(--dsw-alias-bg-layer-1);box-shadow:0 1px 2px rgba(0,0,0,.04);transition:opacity 320ms var(--cc-ease-out),transform 320ms var(--cc-ease-out)}
10476
+ @starting-style{.cc-group{opacity:0;transform:translateY(8px)}}
10477
+ .cc-section>section:nth-of-type(1){transition-delay:0ms}
10478
+ .cc-section>section:nth-of-type(2){transition-delay:40ms}
10479
+ .cc-section>section:nth-of-type(3){transition-delay:80ms}
10480
+ .cc-section>section:nth-of-type(4){transition-delay:120ms}
10481
+ .cc-section>section:nth-of-type(5){transition-delay:160ms}
10482
+ .cc-groupHead{align-items:center;gap:8px;display:flex;min-height:52px;padding:0 16px}
10483
+ .cc-groupTitle{margin:0;color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:22px}
10261
10484
  .cc-groupDesc{margin:2px 0 0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
10262
- .cc-rows>.cc-groupDesc{margin:0}
10485
+ .cc-rows>.cc-groupDesc{margin:0 16px}
10486
+ /* A block straight under a group's description (the account list, its
10487
+ * notices, the override table) keeps the same 12px as the account-mode line
10488
+ * and the actions row inside the card; a setting row brings its own 12px
10489
+ * padding instead. */
10490
+ .cc-rows>.cc-groupDesc+.cc-notices,.cc-rows>.cc-groupDesc+.cc-accountList,.cc-rows>.cc-groupDesc+.cc-contextTable{margin-top:12px}
10263
10491
  .cc-groupHeading{flex:1;min-width:0;margin:0;font:inherit;display:flex}
10264
10492
  /* The whole heading row is the fold toggle: chevron, title, summary badges. */
10265
- .cc-disclosure{box-sizing:border-box;flex-wrap:wrap;flex:1;align-items:center;gap:6px 8px;min-width:0;min-height:36px;padding:0;border:0;background:0 0;color:inherit;font:inherit;text-align:left;cursor:pointer;border-radius:8px;display:flex}
10266
- .cc-foldChevron{color:var(--dsw-alias-label-tertiary);transition:transform .15s ease}
10493
+ .cc-disclosure{box-sizing:border-box;flex-wrap:wrap;flex:1;align-items:center;gap:6px 8px;min-width:0;min-height:52px;padding:0;border:0;background:0 0;color:inherit;font:inherit;text-align:left;cursor:pointer;border-radius:8px;display:flex}
10494
+ .cc-foldChevron{color:var(--dsw-alias-label-tertiary);transition:transform .16s var(--cc-ease-out),color .15s ease}
10267
10495
  .cc-foldChevronClosed{transform:rotate(-90deg)}
10496
+ /* Folding stays instant (2026-10-29): the body stays mounted and is hidden with
10497
+ * the hidden attribute, so aria-controls always points at a real element
10498
+ * and the save bar's locate action can focus an invalid field right after
10499
+ * opening its group. */
10268
10500
  .cc-groupBody{flex-direction:column;display:flex;padding-top:4px}
10269
10501
  .cc-groupBody[hidden]{display:none}
10270
10502
  .cc-disclosure:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
10271
- .cc-rows{flex-direction:column;display:flex}
10272
- .cc-row{flex-wrap:wrap;align-items:center;gap:8px 24px;display:flex;padding:12px 0}
10503
+ .cc-rows{flex-direction:column;display:flex;padding-top:4px}
10504
+ /* Card inset by default: every direct body child sits 16px off the card edge,
10505
+ * and only rows opt out — they carry their own 16px side padding so their
10506
+ * hairlines run the card's full width. */
10507
+ .cc-rows>*{margin-left:16px;margin-right:16px}
10508
+ .cc-rows>.cc-row{margin-left:0;margin-right:0}
10509
+ .cc-row{flex-wrap:wrap;align-items:center;gap:8px 24px;display:flex;padding:14px 16px}
10510
+ .cc-rows>.cc-row+.cc-row{border-top:.5px solid var(--dsw-alias-border-l2)}
10273
10511
  .cc-rowFlush{padding:0}
10274
10512
  /* The text column starts at 200px (not 300px) so the widest controls — the
10275
10513
  * 280px API base input, the allowlist's button + 200px selector — still sit
@@ -10288,16 +10526,14 @@ window.__ModuleLoader__.load({
10288
10526
  /* The row-level field, drawn to the design canvas: 32px, 8px corners, a 1px
10289
10527
  * border-l3 stroke on the raised surface. A number field draws its unit inside
10290
10528
  * the same box (cc-unitBox), right of the value. */
10291
- .cc-fieldInput{box-sizing:border-box;flex:none;height:32px;min-width:0;padding:0 10px;border:1px solid var(--dsw-alias-border-l3);border-radius:8px;background:var(--dsw-alias-bg-layer-1);font:inherit;color:var(--dsw-alias-label-primary);font-size:14px;line-height:22px}
10529
+ .cc-fieldInput{box-sizing:border-box;flex:none;height:32px;min-width:0;padding:0 10px;border:1px solid var(--dsw-alias-border-l3);border-radius:8px;background:var(--dsw-alias-bg-layer-1);font:inherit;color:var(--dsw-alias-label-primary);font-size:14px;line-height:22px;transition:border-color .15s ease}
10292
10530
  .cc-fieldInput::placeholder{color:var(--dsw-alias-label-dimmed)}
10293
- .cc-fieldInput:hover:not(:disabled){border-color:var(--dsw-alias-label-tertiary)}
10294
10531
  .cc-fieldInput:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
10295
10532
  .cc-fieldInput:disabled{opacity:.5;cursor:default}
10296
- .cc-fieldInputInvalid,.cc-fieldInputInvalid:hover{border-color:var(--dsw-alias-state-error-primary)}
10297
- .cc-unitBox{box-sizing:border-box;flex:none;align-items:center;display:inline-flex;width:160px;height:32px;border:1px solid var(--dsw-alias-border-l3);border-radius:8px;background:var(--dsw-alias-bg-layer-1);overflow:hidden}
10298
- .cc-unitBox:hover{border-color:var(--dsw-alias-label-tertiary)}
10533
+ .cc-fieldInputInvalid{border-color:var(--dsw-alias-state-error-primary)}
10534
+ .cc-unitBox{box-sizing:border-box;flex:none;align-items:center;display:inline-flex;width:160px;height:32px;border:1px solid var(--dsw-alias-border-l3);border-radius:8px;background:var(--dsw-alias-bg-layer-1);overflow:hidden;transition:border-color .15s ease}
10299
10535
  .cc-unitBox:focus-within{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
10300
- .cc-unitBoxInvalid,.cc-unitBoxInvalid:hover{border-color:var(--dsw-alias-state-error-primary)}
10536
+ .cc-unitBoxInvalid{border-color:var(--dsw-alias-state-error-primary)}
10301
10537
  .cc-unitBox:has(>.cc-fieldInput:disabled){opacity:.5}
10302
10538
  .cc-unitBox>.cc-fieldInput{flex:1;width:auto;height:30px;border:0;border-radius:0;background:0 0;outline:none;text-align:right;font-variant-numeric:tabular-nums}
10303
10539
  .cc-unit{flex:none;padding:0 10px 0 2px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;white-space:nowrap}
@@ -10305,8 +10541,8 @@ window.__ModuleLoader__.load({
10305
10541
  * round reset button. Reset lives here rather than after the control, so no
10306
10542
  * row reserves a reset column and the page keeps one right edge. */
10307
10543
  .cc-overrideTag{align-items:center;gap:4px;display:inline-flex}
10308
- .cc-resetInline{flex:none;align-items:center;justify-content:center;display:inline-flex;width:20px;height:20px;padding:0;border:0;border-radius:999px;corner-shape:round;background:0 0;color:var(--dsw-alias-label-tertiary);cursor:pointer}
10309
- .cc-resetInline:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
10544
+ .cc-resetInline{flex:none;align-items:center;justify-content:center;display:inline-flex;width:20px;height:20px;padding:0;border:0;border-radius:999px;corner-shape:round;background:0 0;color:var(--dsw-alias-label-tertiary);cursor:pointer;transition:transform .16s var(--cc-ease-out),background-color .15s ease,color .15s ease}
10545
+ .cc-resetInline:active:not(:disabled){transform:scale(.95)}
10310
10546
  .cc-resetInline:disabled{cursor:default;opacity:.4}
10311
10547
  .cc-resetInline:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
10312
10548
  .cc-icon{flex:none;width:16px;height:16px}
@@ -10314,18 +10550,15 @@ window.__ModuleLoader__.load({
10314
10550
  * shape, md 32px / 14px text / 14px icon, sm 28px / 12px text, lg 36px for
10315
10551
  * the save bar; primary is the brand fill, secondary the quiet grey fill,
10316
10552
  * ghost label-secondary text, danger the error fill. */
10317
- .cc-btn{box-sizing:border-box;flex:none;align-items:center;justify-content:center;gap:6px;display:inline-flex;height:32px;padding:0 14px;border:0;border-radius:999px;corner-shape:round;font:inherit;font-size:14px;line-height:22px;white-space:nowrap;cursor:pointer;text-decoration:none}
10553
+ .cc-btn{box-sizing:border-box;flex:none;align-items:center;justify-content:center;gap:6px;display:inline-flex;height:32px;padding:0 14px;border:0;border-radius:999px;corner-shape:round;font:inherit;font-size:14px;line-height:22px;white-space:nowrap;cursor:pointer;text-decoration:none;transition:transform .16s var(--cc-ease-out),background-color .15s ease,color .15s ease}
10554
+ .cc-btn:active:not(:disabled){transform:scale(.97)}
10318
10555
  .cc-btnSm{height:28px;padding:0 10px;gap:4px;font-size:12px;line-height:18px}
10319
10556
  .cc-btnLg{height:36px;padding:0 16px}
10320
10557
  .cc-btn .cc-btnIcon{width:14px;height:14px}
10321
10558
  .cc-btnPrimary{background:var(--dsw-alias-button-primary-fill,var(--dsw-alias-brand-primary));color:var(--dsw-alias-label-primary-foreground,#fff)}
10322
- .cc-btnPrimary:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover,var(--dsw-alias-button-primary-fill,var(--dsw-alias-brand-primary)))}
10323
10559
  .cc-btnSecondary{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary)}
10324
- .cc-btnSecondary:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
10325
10560
  .cc-btnGhost{background:0 0;color:var(--dsw-alias-label-secondary)}
10326
- .cc-btnGhost:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
10327
10561
  .cc-btnDanger{background:var(--dsw-alias-state-error-primary);color:#fff}
10328
- .cc-btnDanger:hover:not(:disabled){background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 88%,#000)}
10329
10562
  .cc-btn:disabled{cursor:default;opacity:.4}
10330
10563
  .cc-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
10331
10564
  .cc-groupSummary{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:400;line-height:18px;white-space:nowrap}
@@ -10342,7 +10575,6 @@ window.__ModuleLoader__.load({
10342
10575
  .cc-fieldHead{align-items:center;gap:8px;display:flex}
10343
10576
  .cc-label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}
10344
10577
  .cc-reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:18px}
10345
- .cc-reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}
10346
10578
  .cc-reset:disabled{cursor:default;opacity:.4}
10347
10579
  .cc-input{box-sizing:border-box;height:32px;min-width:0;padding:0 10px;border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;background:var(--dsw-alias-bg-layer-1);font:inherit;color:var(--dsw-alias-label-primary);font-size:14px;line-height:22px}
10348
10580
  .cc-input::placeholder{color:var(--dsw-alias-label-dimmed)}
@@ -10351,8 +10583,7 @@ window.__ModuleLoader__.load({
10351
10583
  .cc-inputInvalid{border-color:var(--dsw-alias-state-error-primary)}
10352
10584
  .cc-hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}
10353
10585
  .cc-modelSelectAnchor{flex-direction:column;align-items:stretch;gap:6px;display:inline-flex;width:200px}
10354
- .cc-selector{box-sizing:border-box;justify-content:space-between;align-items:center;gap:8px;display:inline-flex;height:32px;padding:0 10px;border:1px solid var(--dsw-alias-border-l3);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;line-height:22px;text-align:left;cursor:pointer}
10355
- .cc-selector:hover:not(:disabled){border-color:var(--dsw-alias-label-tertiary)}
10586
+ .cc-selector{box-sizing:border-box;justify-content:space-between;align-items:center;gap:8px;display:inline-flex;height:32px;padding:0 10px;border:1px solid var(--dsw-alias-border-l3);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;line-height:22px;text-align:left;cursor:pointer;transition:border-color .15s ease}
10356
10587
  .cc-selector:disabled{cursor:default;opacity:.4}
10357
10588
  .cc-selector:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
10358
10589
  .cc-selectorText{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
@@ -10375,27 +10606,28 @@ window.__ModuleLoader__.load({
10375
10606
  .cc-check:checked::after{content:'';position:absolute;top:3px;left:5px;width:3px;height:7px;border:solid var(--dsw-alias-label-primary-foreground,#fff);border-width:0 1.5px 1.5px 0;transform:rotate(45deg)}
10376
10607
  .cc-checkName{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
10377
10608
  /* Modelled on the platform's own Switch primitive (ui-primitives
10378
- * Switch.module.css): a 36x20 capsule padding its track by 2px and sliding a
10379
- * 16px thumb across the 32px content box. Three things are load-bearing.
10609
+ * Switch.module.css): a 40x24 capsule padding its track by 2px and sliding a
10610
+ * 20px thumb across the 36px content box. Three things are load-bearing.
10380
10611
  * (1) corner-shape:round on BOTH track and thumb — the theme sets a global
10381
10612
  * superellipse corner shape, which squares a capsule's ends off around a round
10382
10613
  * thumb. (2) No literal colours: brand-primary INVERTS between the modes, so a
10383
10614
  * hardcoded white thumb vanishes on the checked track in dark mode; the brand
10384
10615
  * foreground is what the platform paints on that fill. (3) border-l3 as the
10385
- * off track — border-l2 leaves it indistinguishable from the page. */
10386
- .cc-toggle{box-sizing:border-box;appearance:none;flex-shrink:0;width:36px;height:20px;margin:0;padding:2px;border:0;border-radius:10px;corner-shape:round;background:var(--dsw-alias-border-l3);cursor:pointer;position:relative}
10616
+ * off track — border-l2 leaves it indistinguishable from the page. The track
10617
+ * and thumb share one duration so the switch reads as one object. */
10618
+ .cc-toggle{box-sizing:border-box;appearance:none;flex-shrink:0;width:40px;height:24px;margin:0;padding:2px;border:0;border-radius:12px;corner-shape:round;background:var(--dsw-alias-border-l3);cursor:pointer;position:relative;transition:background-color .12s ease}
10387
10619
  .cc-toggle:checked{background:var(--dsw-alias-brand-primary)}
10388
- .cc-toggle::after{content:'';display:block;width:16px;height:16px;border-radius:50%;corner-shape:round;background:var(--dsw-alias-label-primary-foreground);transition:transform .12s ease}
10620
+ .cc-toggle::after{content:'';display:block;width:20px;height:20px;border-radius:50%;corner-shape:round;background:var(--dsw-alias-label-primary-foreground);transition:transform .12s ease}
10389
10621
  .cc-toggle:checked::after{transform:translateX(16px)}
10390
10622
  .cc-toggle:not(:checked)::after{background:var(--dsw-alias-switch-thumb,var(--dsw-alias-label-primary-foreground))}
10391
10623
  .cc-toggle:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
10392
10624
  .cc-toggle:disabled{cursor:default;opacity:.5}
10393
- .cc-accountMode{align-items:center;gap:8px;display:flex;margin:12px 0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
10625
+ .cc-accountMode{align-items:center;gap:8px;display:flex;margin:12px 16px 0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
10394
10626
  .cc-accountList{flex-direction:column;gap:8px;display:flex}
10395
- .cc-accountList+.cc-panel{margin-top:8px}
10627
+ .cc-accountList+.cc-panel{margin:8px 16px 16px}
10396
10628
  /* One row under the list: Add account (outline) leads, the analytics exit
10397
10629
  * (ghost) trails; the exit's long label clamps instead of wrapping the row. */
10398
- .cc-accountActions{flex-wrap:wrap;align-items:center;gap:8px;display:flex;margin-top:12px}
10630
+ .cc-accountActions{flex-wrap:wrap;align-items:center;gap:8px;display:flex;margin:12px 16px 16px}
10399
10631
  .cc-analyticsEntry{max-width:100%;min-width:0}
10400
10632
  .cc-analyticsEntryText{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
10401
10633
  .cc-chevronOpen{transform:rotate(180deg)}
@@ -10406,14 +10638,14 @@ window.__ModuleLoader__.load({
10406
10638
  .cc-accountToggle{flex-wrap:wrap;align-items:center;gap:6px 8px;display:flex;flex:1;min-width:0;min-height:44px;padding:0 0 0 16px;background:0 0;border:0;border-radius:12px;color:inherit;cursor:pointer;font:inherit;text-align:left}
10407
10639
  .cc-accountToggle:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px;border-radius:4px}
10408
10640
  .cc-accountName{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}
10409
- .cc-iconButton{flex-shrink:0;align-items:center;justify-content:center;display:inline-flex;width:28px;height:28px;padding:0;background:0 0;border:0;border-radius:8px;color:var(--dsw-alias-label-tertiary);cursor:pointer}
10410
- .cc-iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
10641
+ .cc-iconButton{flex-shrink:0;align-items:center;justify-content:center;display:inline-flex;width:28px;height:28px;padding:0;background:0 0;border:0;border-radius:8px;color:var(--dsw-alias-label-tertiary);cursor:pointer;transition:transform .16s var(--cc-ease-out),background-color .15s ease,color .15s ease}
10642
+ .cc-iconButton:active:not(:disabled){transform:scale(.95)}
10411
10643
  .cc-iconButton:disabled{cursor:default;opacity:.4}
10412
- .cc-iconButton:focus-visible{outline:none;box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}
10644
+ .cc-iconButton:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
10413
10645
  /* Per-model context-length overrides (issue #86) as the design's small table:
10414
10646
  * a card holding a head row over a model / value / remove grid, a line
10415
10647
  * between rows; a row's error wraps onto its own line across the grid. */
10416
- .cc-contextTable{flex-direction:column;display:flex;margin:0 0 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px}
10648
+ .cc-contextTable{flex-direction:column;display:flex;margin:0 16px 16px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px}
10417
10649
  .cc-contextItem{grid-template-columns:minmax(0,1fr) 144px 28px;align-items:center;gap:8px;display:grid;padding:8px 8px 8px 12px}
10418
10650
  .cc-contextItem+.cc-contextItem{border-top:1px solid var(--dsw-alias-border-l2)}
10419
10651
  .cc-contextHead{grid-template-columns:minmax(0,1fr) 144px 28px;align-items:center;gap:8px;display:grid;height:32px;padding:0 8px 0 12px;border-bottom:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
@@ -10433,7 +10665,10 @@ window.__ModuleLoader__.load({
10433
10665
  .cc-miniMeter{align-items:center;gap:8px;display:inline-flex;font-size:12px;line-height:18px}
10434
10666
  .cc-miniMeterLabel{color:var(--dsw-alias-label-secondary)}
10435
10667
  .cc-miniMeterTrack{overflow:hidden;background:var(--dsw-alias-bg-module-platform);border-radius:999px;width:64px;height:4px}
10436
- .cc-miniMeterFill{display:block;background:var(--dsw-alias-brand-primary);border-radius:999px;height:100%}
10668
+ /* The fill is always full width and slides in from the left under the track's
10669
+ * overflow clip: animating transform skips layout, and unlike scaleX it keeps
10670
+ * the fill's rounded ends. */
10671
+ .cc-miniMeterFill{display:block;width:100%;background:var(--dsw-alias-brand-primary);border-radius:999px;height:100%;transition:transform .24s var(--cc-ease-out)}
10437
10672
  .cc-miniMeterValue{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums}
10438
10673
  .cc-accountDetails{container-type:inline-size;border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:16px;display:flex;padding:16px}
10439
10674
  .cc-inlineForm{flex-direction:column;gap:8px;display:flex}
@@ -10445,8 +10680,8 @@ window.__ModuleLoader__.load({
10445
10680
  /* The add-account method switch, drawn as the design's segmented control: a
10446
10681
  * pill track on the raised surface, the chosen segment in the brand fill. */
10447
10682
  .cc-seg{align-self:flex-start;display:inline-flex;padding:2px;border-radius:999px;corner-shape:round;background:var(--dsw-alias-bg-layer-1);box-shadow:inset 0 0 0 1px var(--dsw-alias-border-l2)}
10448
- .cc-segItem{height:28px;padding:0 12px;border:0;border-radius:999px;corner-shape:round;background:0 0;color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;line-height:18px;cursor:pointer}
10449
- .cc-segItem:hover:not(:disabled):not(.cc-segOn){color:var(--dsw-alias-label-primary)}
10683
+ .cc-segItem{height:28px;padding:0 12px;border:0;border-radius:999px;corner-shape:round;background:0 0;color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;line-height:18px;cursor:pointer;transition:transform .16s var(--cc-ease-out),background-color .15s ease,color .15s ease}
10684
+ .cc-segItem:active:not(:disabled):not(.cc-segOn){transform:scale(.97)}
10450
10685
  .cc-segItem:disabled{cursor:default;opacity:.4}
10451
10686
  .cc-segItem:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
10452
10687
  .cc-segOn{background:var(--dsw-alias-button-primary-fill,var(--dsw-alias-brand-primary));color:var(--dsw-alias-label-primary-foreground,#fff)}
@@ -10463,13 +10698,14 @@ window.__ModuleLoader__.load({
10463
10698
  .cc-usageStats{grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:8px;display:grid}
10464
10699
  .cc-usageStatsActivity{grid-template-columns:repeat(4,minmax(0,1fr))}
10465
10700
  @container (max-width:460px){.cc-usageStatsActivity{grid-template-columns:repeat(2,minmax(0,1fr))}}
10466
- .cc-balance{flex-wrap:wrap;justify-content:space-between;align-items:flex-end;gap:4px 16px;display:flex}
10467
- .cc-balanceMain{flex-direction:column;gap:2px;display:flex}
10701
+ .cc-balance{flex-direction:column;gap:2px;display:flex}
10702
+ /* 两行左右分栏:标签行左账期、右更新时间;数值行左剩余、右已购与赠送。 */
10703
+ .cc-balanceRow{flex-wrap:wrap;justify-content:space-between;align-items:baseline;gap:4px 16px;display:flex}
10468
10704
  .cc-balanceLabel{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
10469
10705
  .cc-balanceValue{color:var(--dsw-alias-label-primary);font-size:20px;font-weight:600;line-height:28px;font-variant-numeric:tabular-nums}
10470
10706
  .cc-balanceSub{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;font-variant-numeric:tabular-nums}
10471
10707
  .cc-balanceEmpty .cc-balanceValue{color:var(--dsw-alias-state-error-primary)}
10472
- .cc-balanceNote{flex-basis:100%;margin:0;color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}
10708
+ .cc-balanceNote{margin:0;color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}
10473
10709
  .cc-usageStat{min-width:0;flex-direction:column;gap:2px;display:flex;padding:10px 12px;border-radius:8px;background:var(--dsw-alias-bg-module-platform)}
10474
10710
  .cc-usageStatLabel{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
10475
10711
  .cc-usageStatValue{color:var(--dsw-alias-label-primary);font-size:16px;font-weight:500;line-height:24px;font-variant-numeric:tabular-nums}
@@ -10481,19 +10717,22 @@ window.__ModuleLoader__.load({
10481
10717
  .cc-usageWindowValue{color:var(--dsw-alias-label-primary);font-size:12px;line-height:18px;font-variant-numeric:tabular-nums}
10482
10718
  .cc-usageExceeded{color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}
10483
10719
  .cc-usageBar{overflow:hidden;background:var(--dsw-alias-bg-module-platform);border-radius:999px;height:4px}
10484
- .cc-usageBarFill{background:var(--dsw-alias-brand-primary);border-radius:999px;height:100%;transition:width .3s ease}
10720
+ /* The fill is always full width and slides in from the left under the track's
10721
+ * overflow clip: animating transform skips layout, and unlike scaleX it keeps
10722
+ * the fill's rounded ends. */
10723
+ .cc-usageBarFill{width:100%;background:var(--dsw-alias-brand-primary);border-radius:999px;height:100%;transition:transform .24s var(--cc-ease-out)}
10485
10724
  .cc-usageBarFillWarn{background:var(--dsw-alias-state-error-primary)}
10486
10725
  .cc-usageWindowReset{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:18px}
10487
- .cc-usageMeta{align-items:center;gap:8px;display:flex}
10488
- .cc-usageMetaSpacer{flex:1}
10489
- .cc-usageUpdated{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:18px}
10490
10726
  .cc-usagePartial{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px;line-height:18px}
10491
10727
  .cc-notices{flex-direction:column;gap:8px;display:flex;margin-bottom:12px}
10492
10728
  .cc-section>.cc-notice{margin-top:12px}
10493
10729
  /* One block for every result, warning and blocked state: neutral (read-only,
10494
10730
  * in progress), warn (partly done, needs a retry) and error (failed). The
10495
- * icon and title take the tone; the body stays label-secondary for contrast. */
10496
- .cc-notice{box-sizing:border-box;align-items:flex-start;gap:10px;display:flex;padding:10px 12px;border-radius:12px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
10731
+ * icon and title take the tone; the body stays label-secondary for contrast.
10732
+ * A notice settles in from 4px above as it first renders, so a result never
10733
+ * jolts the content below it; browsers without @starting-style just show it. */
10734
+ .cc-notice{box-sizing:border-box;align-items:flex-start;gap:10px;display:flex;padding:10px 12px;border-radius:12px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;transition:opacity .2s var(--cc-ease-out),transform .2s var(--cc-ease-out)}
10735
+ @starting-style{.cc-notice{opacity:0;transform:translateY(-4px)}}
10497
10736
  .cc-noticeIcon{flex:none;width:16px;height:16px;margin-top:1px;color:var(--dsw-alias-label-tertiary)}
10498
10737
  .cc-noticeBody{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex;word-break:break-word}
10499
10738
  .cc-noticeBody>p{margin:0}
@@ -10508,21 +10747,17 @@ window.__ModuleLoader__.load({
10508
10747
  .cc-noticeError>.cc-noticeIcon,.cc-noticeError .cc-noticeTitle{color:var(--dsw-alias-state-error-primary)}
10509
10748
  .cc-version{margin:32px 0 0;text-align:center;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
10510
10749
  .cc-versionLink{color:var(--dsw-alias-state-warn-label,var(--dsw-alias-label-secondary));text-decoration:none}
10511
- .cc-versionLink:hover{color:var(--dsw-alias-label-primary);text-decoration:underline;text-underline-position:under}
10512
10750
  .cc-providerCard{flex-direction:column;display:flex}
10513
10751
  .cc-footer{justify-content:flex-end;align-items:center;gap:8px;display:flex}
10514
10752
  .cc-failed{display:inline-block;max-width:260px;min-width:0;color:var(--dsw-alias-state-error-primary);margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:18px}
10515
10753
  .cc-loginLink{align-items:center;gap:4px;display:inline-flex;color:var(--dsw-alias-link,var(--dsw-alias-brand-primary));text-decoration:none;font-size:12px;line-height:18px}
10516
10754
  .cc-loginLink>.cc-iconInline{width:12px;height:12px}
10517
- .cc-loginLink:hover{text-decoration:underline;text-underline-position:under}
10518
10755
  /* The status row's dot + verdict sit beside the row title; the primitive's own
10519
10756
  * state tokens colour the dot, so only the neutral label and the failure note
10520
10757
  * need styling here. */
10521
10758
  .cc-statusBadge{align-items:center;gap:6px;display:inline-flex;white-space:nowrap}
10522
10759
  .cc-statusLabel{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
10523
10760
  .cc-statusNotice{color:var(--dsw-alias-state-warn-label,var(--dsw-alias-label-tertiary));margin:0;font-size:12px;line-height:18px}
10524
- /* One muted line in the status description: the local-connectivity probe and
10525
- * the publication-lag note both use it, so the dot and the text stay aligned. */
10526
10761
  /* The service-status row's two sources: a key column (Official / This device)
10527
10762
  * beside a value line that wraps under itself, never under the key. */
10528
10763
  .cc-statusLines{grid-template-columns:auto minmax(0,1fr);align-items:baseline;gap:4px 12px;display:grid;margin:2px 0 0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
@@ -10537,7 +10772,6 @@ window.__ModuleLoader__.load({
10537
10772
  .cc-statusTitleLink{align-items:center;gap:4px;display:inline-flex;margin-left:auto}
10538
10773
  .cc-statusBad{color:var(--dsw-alias-state-warn-label,var(--dsw-alias-state-warn-primary,#b45309))}
10539
10774
  .cc-statusLink{color:var(--dsw-alias-link,var(--dsw-alias-brand-primary));text-decoration:none;font-size:12px;line-height:18px;white-space:nowrap}
10540
- .cc-statusLink:hover{text-decoration:underline;text-underline-position:under}
10541
10775
  .cc-loginDone{color:var(--dsw-alias-state-success-primary,var(--dsw-alias-label-secondary))}
10542
10776
  .cc-loginError{color:var(--dsw-alias-state-error-primary)}
10543
10777
  /* The floating save bar: a centered bar pinned to the bottom of the
@@ -10545,15 +10779,16 @@ window.__ModuleLoader__.load({
10545
10779
  * page, so the bar overlays the scrolling content instead of taking a row of
10546
10780
  * its own; while shown, the section reserves room at its end so the last line
10547
10781
  * can scroll clear. Hidden, it sinks and fades, and visibility (switched after
10548
- * the transition) takes its buttons out of the tab order. Its surface is an
10549
- * elevated layer (bg-layer-1 over the settings panel's layer-2) with the
10782
+ * the transition) takes its buttons out of the tab order. It leaves faster than
10783
+ * it arrives and never overshoots: the bar shows on every edit. Its surface is
10784
+ * an elevated layer (bg-layer-1 over the settings panel's layer-2) with the
10550
10785
  * platform's prominent elevation and no stroke, and its corners are concentric
10551
- * with the capsule buttons inside it (36px pills inset by 4px in a 44px pill)
10786
+ * with the capsule buttons inside it (36px pills inset by 6px in a 48px pill)
10552
10787
  * so the gap around each button is even. */
10553
10788
  .cc-sectionWithBar{padding-bottom:80px}
10554
10789
  .cc-saveBarDock{position:sticky;bottom:0;z-index:2;height:0;pointer-events:none}
10555
- .cc-saveBar{--cc-saveBar-tone:var(--dsw-alias-state-warn-primary,#d97706);position:absolute;left:50%;bottom:20px;box-sizing:border-box;width:max-content;max-width:calc(100% - 24px);align-items:center;gap:10px;display:flex;height:44px;padding:4px 4px 4px 16px;border:0;border-radius:22px;corner-shape:round;background:var(--dsw-alias-bg-layer-1);box-shadow:var(--dsw-elevation-prominent,0 12px 32px -8px rgba(0,0,0,.24),0 2px 8px rgba(0,0,0,.08));opacity:0;visibility:hidden;transform:translate(-50%,12px);transition:opacity .16s ease,transform .16s ease,visibility 0s linear .16s}
10556
- .cc-saveBarShown{opacity:1;visibility:visible;transform:translate(-50%,0);pointer-events:auto;transition:opacity .2s ease,transform .24s cubic-bezier(.2,.9,.3,1.1),visibility 0s}
10790
+ .cc-saveBar{--cc-saveBar-tone:var(--dsw-alias-state-warn-primary,#d97706);position:absolute;left:50%;bottom:20px;box-sizing:border-box;width:max-content;max-width:calc(100% - 24px);align-items:center;gap:10px;display:flex;height:48px;padding:6px 6px 6px 16px;border:0;border-radius:24px;corner-shape:round;background:var(--dsw-alias-bg-layer-1);box-shadow:var(--dsw-elevation-prominent,0 12px 32px -8px rgba(0,0,0,.24),0 2px 8px rgba(0,0,0,.08));opacity:0;visibility:hidden;transform:translate(-50%,12px);transition:opacity .16s var(--cc-ease-out),transform .16s var(--cc-ease-out),visibility 0s linear .16s}
10791
+ .cc-saveBarShown{opacity:1;visibility:visible;transform:translate(-50%,0);pointer-events:auto;transition:opacity .22s var(--cc-ease-out),transform .22s var(--cc-ease-out),visibility 0s}
10557
10792
  .cc-saveBar-error{--cc-saveBar-tone:var(--dsw-alias-state-error-primary)}
10558
10793
  .cc-saveBar-success{--cc-saveBar-tone:var(--dsw-alias-state-success-primary,#16a34a);padding-right:18px}
10559
10794
  .cc-saveBarIcon{flex-shrink:0;align-items:center;justify-content:center;display:inline-flex;width:16px;height:16px;color:var(--cc-saveBar-tone)}
@@ -10561,7 +10796,36 @@ window.__ModuleLoader__.load({
10561
10796
  .cc-saveBarText{min-width:0;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary);font-size:14px;line-height:22px}
10562
10797
  .cc-saveBar-error .cc-saveBarText{color:var(--dsw-alias-state-error-primary)}
10563
10798
  .cc-saveBarActions{flex-shrink:0;align-items:center;gap:4px;display:flex;margin-left:8px}
10564
- @media (prefers-reduced-motion:reduce){.cc-foldChevron,.cc-toggle::after,.cc-usageBarFill,.cc-saveBar,.cc-saveBarShown{transition:none}}
10799
+ /* Hover only where a real pointer hovers. Order matters inside: the invalid
10800
+ * borders come after the plain hover borders at equal specificity, so an
10801
+ * invalid field keeps its error border under the pointer. */
10802
+ @media (hover:hover) and (pointer:fine){
10803
+ .cc-fieldInput:hover:not(:disabled),.cc-unitBox:hover,.cc-selector:hover:not(:disabled){border-color:var(--dsw-alias-label-tertiary)}
10804
+ .cc-fieldInputInvalid:hover:not(:disabled),.cc-unitBoxInvalid:hover{border-color:var(--dsw-alias-state-error-primary)}
10805
+ .cc-resetInline:hover:not(:disabled),.cc-iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
10806
+ .cc-btnPrimary:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover,var(--dsw-alias-button-primary-fill,var(--dsw-alias-brand-primary)))}
10807
+ .cc-btnSecondary:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
10808
+ .cc-btnGhost:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
10809
+ .cc-btnDanger:hover:not(:disabled){background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 88%,#000)}
10810
+ .cc-segItem:hover:not(:disabled):not(.cc-segOn),.cc-reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}
10811
+ /* A whole-row toggle (group heading, account card head) shows it is one
10812
+ * target by lifting its chevron; a fill would weigh too much across the row. */
10813
+ .cc-disclosure:hover .cc-foldChevron,.cc-accountToggle:hover .cc-foldChevron{color:var(--dsw-alias-label-primary)}
10814
+ .cc-versionLink:hover{color:var(--dsw-alias-label-primary);text-decoration:underline;text-underline-position:under}
10815
+ .cc-loginLink:hover,.cc-statusLink:hover{text-decoration:underline;text-underline-position:under}
10816
+ }
10817
+ /* Reduced motion drops movement and scale but keeps the fades and colour
10818
+ * changes, which carry meaning without motion. Card entrance keeps its fade
10819
+ * (the transform is neutralised), and the fold is already instant. */
10820
+ @media (prefers-reduced-motion:reduce){
10821
+ .cc-foldChevron,.cc-toggle::after,.cc-usageBarFill,.cc-miniMeterFill{transition:none}
10822
+ .cc-btn,.cc-iconButton,.cc-resetInline,.cc-segItem{transition:background-color .15s ease,color .15s ease}
10823
+ .cc-btn:active:not(:disabled),.cc-iconButton:active:not(:disabled),.cc-resetInline:active:not(:disabled),.cc-segItem:active:not(:disabled):not(.cc-segOn){transform:none}
10824
+ .cc-notice{transition:opacity .2s ease}
10825
+ .cc-group{transition:opacity .2s ease;transform:none!important}
10826
+ .cc-saveBar{transition:opacity .16s ease,visibility 0s linear .16s}
10827
+ .cc-saveBarShown{transition:opacity .2s ease,visibility 0s}
10828
+ }
10565
10829
  `;
10566
10830
  //#endregion
10567
10831
  //#region src/client/panel-styles.ts
@@ -11176,6 +11440,7 @@ window.__ModuleLoader__.load({
11176
11440
  discard: () => controller.discard(),
11177
11441
  refreshCredentials: () => void controller.refreshCredentials(),
11178
11442
  refreshUsage: () => void usageController.refresh(),
11443
+ refreshUsageAuto: () => void usageController.refresh({ auto: true }),
11179
11444
  ...typeof ctx.get("layout")?.selectPanel === "function" ? { openAnalytics } : {},
11180
11445
  beginLogin: (targetRef) => void loginController.begin(targetRef),
11181
11446
  cancelLogin: () => void loginController.cancel(),