sideby 0.0.0-stage → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +111 -2
  3. package/README.zh-CN.md +112 -0
  4. package/dist/build.json +1 -0
  5. package/dist/src/cli/args.d.ts +13 -0
  6. package/dist/src/cli/args.js +38 -0
  7. package/dist/src/cli/commands.d.ts +50 -0
  8. package/dist/src/cli/commands.js +566 -0
  9. package/dist/src/cli/format.d.ts +16 -0
  10. package/dist/src/cli/format.js +80 -0
  11. package/dist/src/cli/json-schemas.d.ts +2 -0
  12. package/dist/src/cli/json-schemas.js +259 -0
  13. package/dist/src/cli/main.d.ts +2 -0
  14. package/dist/src/cli/main.js +276 -0
  15. package/dist/src/cli/schemas.d.ts +1 -0
  16. package/dist/src/cli/schemas.js +16 -0
  17. package/dist/src/core/account-name.d.ts +2 -0
  18. package/dist/src/core/account-name.js +2 -0
  19. package/dist/src/core/accounts.d.ts +20 -0
  20. package/dist/src/core/accounts.js +131 -0
  21. package/dist/src/core/config.d.ts +61 -0
  22. package/dist/src/core/config.js +184 -0
  23. package/dist/src/core/create.d.ts +30 -0
  24. package/dist/src/core/create.js +126 -0
  25. package/dist/src/core/doctor.d.ts +41 -0
  26. package/dist/src/core/doctor.js +171 -0
  27. package/dist/src/core/errors.d.ts +12 -0
  28. package/dist/src/core/errors.js +14 -0
  29. package/dist/src/core/file-memo.d.ts +24 -0
  30. package/dist/src/core/file-memo.js +41 -0
  31. package/dist/src/core/fs-safe.d.ts +38 -0
  32. package/dist/src/core/fs-safe.js +205 -0
  33. package/dist/src/core/handoff.d.ts +41 -0
  34. package/dist/src/core/handoff.js +54 -0
  35. package/dist/src/core/identity.d.ts +23 -0
  36. package/dist/src/core/identity.js +79 -0
  37. package/dist/src/core/last-doctor.d.ts +36 -0
  38. package/dist/src/core/last-doctor.js +69 -0
  39. package/dist/src/core/launch.d.ts +37 -0
  40. package/dist/src/core/launch.js +108 -0
  41. package/dist/src/core/login-path.d.ts +23 -0
  42. package/dist/src/core/login-path.js +96 -0
  43. package/dist/src/core/paths.d.ts +13 -0
  44. package/dist/src/core/paths.js +32 -0
  45. package/dist/src/core/quota-levels.d.ts +19 -0
  46. package/dist/src/core/quota-levels.js +34 -0
  47. package/dist/src/core/resume.d.ts +9 -0
  48. package/dist/src/core/resume.js +15 -0
  49. package/dist/src/core/secret-file.d.ts +30 -0
  50. package/dist/src/core/secret-file.js +116 -0
  51. package/dist/src/core/share-meaning.d.ts +6 -0
  52. package/dist/src/core/share-meaning.js +32 -0
  53. package/dist/src/core/share-mode-names.d.ts +1 -0
  54. package/dist/src/core/share-mode-names.js +10 -0
  55. package/dist/src/core/share-modes.d.ts +24 -0
  56. package/dist/src/core/share-modes.js +405 -0
  57. package/dist/src/core/shell-init.d.ts +31 -0
  58. package/dist/src/core/shell-init.js +84 -0
  59. package/dist/src/core/skill-check.d.ts +11 -0
  60. package/dist/src/core/skill-check.js +57 -0
  61. package/dist/src/core/usage-days.d.ts +15 -0
  62. package/dist/src/core/usage-days.js +33 -0
  63. package/dist/src/core/version.d.ts +9 -0
  64. package/dist/src/core/version.js +46 -0
  65. package/dist/src/desktop/app.d.ts +60 -0
  66. package/dist/src/desktop/app.js +316 -0
  67. package/dist/src/desktop/icon.d.ts +16 -0
  68. package/dist/src/desktop/icon.js +174 -0
  69. package/dist/src/families/claude/index.d.ts +10 -0
  70. package/dist/src/families/claude/index.js +127 -0
  71. package/dist/src/families/claude/layout.d.ts +6 -0
  72. package/dist/src/families/claude/layout.js +5 -0
  73. package/dist/src/families/claude/quota-cache.d.ts +31 -0
  74. package/dist/src/families/claude/quota-cache.js +111 -0
  75. package/dist/src/families/claude/quota.d.ts +3 -0
  76. package/dist/src/families/claude/quota.js +53 -0
  77. package/dist/src/families/claude/usage.d.ts +22 -0
  78. package/dist/src/families/claude/usage.js +146 -0
  79. package/dist/src/families/codex/index.d.ts +5 -0
  80. package/dist/src/families/codex/index.js +70 -0
  81. package/dist/src/families/codex/rollout.d.ts +67 -0
  82. package/dist/src/families/codex/rollout.js +298 -0
  83. package/dist/src/families/grok/index.d.ts +8 -0
  84. package/dist/src/families/grok/index.js +82 -0
  85. package/dist/src/families/index.d.ts +3 -0
  86. package/dist/src/families/index.js +40 -0
  87. package/dist/src/families/logos.d.ts +21 -0
  88. package/dist/src/families/logos.js +26 -0
  89. package/dist/src/families/pi/index.d.ts +3 -0
  90. package/dist/src/families/pi/index.js +54 -0
  91. package/dist/src/families/shared/sessions.d.ts +9 -0
  92. package/dist/src/families/shared/sessions.js +70 -0
  93. package/dist/src/families/shared/toml.d.ts +5 -0
  94. package/dist/src/families/shared/toml.js +16 -0
  95. package/dist/src/index.d.ts +5 -0
  96. package/dist/src/index.js +4 -0
  97. package/dist/src/panel/background.d.ts +62 -0
  98. package/dist/src/panel/background.js +274 -0
  99. package/dist/src/panel/handler.d.ts +91 -0
  100. package/dist/src/panel/handler.js +396 -0
  101. package/dist/src/panel/i18n.d.ts +287 -0
  102. package/dist/src/panel/i18n.js +570 -0
  103. package/dist/src/panel/page-logic.d.ts +154 -0
  104. package/dist/src/panel/page-logic.js +326 -0
  105. package/dist/src/panel/page-script.d.ts +2 -0
  106. package/dist/src/panel/page-script.js +1633 -0
  107. package/dist/src/panel/page-styles.d.ts +7 -0
  108. package/dist/src/panel/page-styles.js +514 -0
  109. package/dist/src/panel/page.d.ts +20 -0
  110. package/dist/src/panel/page.js +96 -0
  111. package/dist/src/panel/server.d.ts +32 -0
  112. package/dist/src/panel/server.js +133 -0
  113. package/dist/src/panel/theme.d.ts +116 -0
  114. package/dist/src/panel/theme.js +132 -0
  115. package/dist/src/plugins/account-script.d.ts +4 -0
  116. package/dist/src/plugins/account-script.js +66 -0
  117. package/dist/src/plugins/bus.d.ts +33 -0
  118. package/dist/src/plugins/bus.js +84 -0
  119. package/dist/src/plugins/loader.d.ts +58 -0
  120. package/dist/src/plugins/loader.js +250 -0
  121. package/dist/src/quota/claude-statusline.d.ts +28 -0
  122. package/dist/src/quota/claude-statusline.js +301 -0
  123. package/dist/src/quota/statusline-tap.d.ts +11 -0
  124. package/dist/src/quota/statusline-tap.js +104 -0
  125. package/dist/src/runtime.d.ts +195 -0
  126. package/dist/src/runtime.js +424 -0
  127. package/dist/src/types.d.ts +285 -0
  128. package/dist/src/types.js +3 -0
  129. package/dist/testing/demo.d.ts +8 -0
  130. package/dist/testing/demo.js +50 -0
  131. package/dist/testing/index.d.ts +36 -0
  132. package/dist/testing/index.js +118 -0
  133. package/package.json +66 -4
  134. package/schemas/alias.json +102 -0
  135. package/schemas/app-install.json +74 -0
  136. package/schemas/app-uninstall.json +54 -0
  137. package/schemas/config.json +170 -0
  138. package/schemas/doctor.json +229 -0
  139. package/schemas/error.json +25 -0
  140. package/schemas/families.json +205 -0
  141. package/schemas/list.json +177 -0
  142. package/schemas/new.json +199 -0
  143. package/schemas/next.json +127 -0
  144. package/schemas/plugins.json +74 -0
  145. package/schemas/quota-setup.json +60 -0
  146. package/schemas/quota-teardown.json +30 -0
  147. package/schemas/quota.json +203 -0
  148. package/skills/sideby/SKILL.md +61 -0
  149. package/skills/sideby/references/accounts.md +31 -0
  150. package/skills/sideby/references/diagnose.md +47 -0
  151. package/skills/sideby/references/handoff.md +41 -0
  152. package/skills/sideby/references/quota.md +14 -0
  153. package/skills/sideby/references/setup.md +31 -0
  154. package/skills/sideby/references/sharing.md +17 -0
@@ -0,0 +1,7 @@
1
+ import { type ResolvedTheme } from './theme.ts';
2
+ /**
3
+ * The page's style block: default tokens, the host's tokens over them, then the rules that read them. With
4
+ * `colorScheme: 'auto'` the page follows the system unless the viewer picked light or dark in the Panel, which sets
5
+ * `data-theme` on the root element.
6
+ */
7
+ export declare function pageStyles(theme?: ResolvedTheme): string;
@@ -0,0 +1,514 @@
1
+ // The Panel page's styles: design tokens (a host theme layers its values over them, see theme.ts) and the rules
2
+ // that read them. Every color, radius and size below comes from a token, so a host theme reaches every component.
3
+ import { resolveTheme } from './theme.js';
4
+ // Colors defined from other tokens; repeated in both schemes so they follow that scheme's base colors.
5
+ const DERIVED_COLORS = `
6
+ --primary-border: var(--primary); --primary-hover-border: var(--primary-hover); --btn-text: var(--text);
7
+ --btn-hover-border: var(--border-strong); --btn-hover-text: var(--text); --check: var(--primary);
8
+ --logo-bg: var(--text); --logo-fg: var(--bg); --logo-line: transparent;
9
+ --focus-outline: 2px solid var(--focus); --focus-ring: none;`;
10
+ // Design tokens. A host theme (theme.ts) layers its values over these: light values after LIGHT_TOKENS, dark values after
11
+ // DARK_TOKENS. DARK_TOKENS sets every color token again, so a host's light colors never leak into dark mode, while
12
+ // type, shape and layout tokens are shared by both schemes.
13
+ const LIGHT_TOKENS = `
14
+ --font: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
15
+ --font-size: 14px; --small: 12.5px; --control-size: 13.5px; --heading-size: 15px; --large: 16px; --title-size: 22px;
16
+ --title-weight: 750; --heading-weight: 650; --strong-weight: 600; --button-weight: 600; --primary-weight: var(--button-weight);
17
+ --radius: 14px; --ctl-radius: 8px; --chip-radius: 999px; --ctl-h: 34px; --ctl-h-sm: 28px; --field-h: 36px;
18
+ --content-width: 1180px; --gutter: 24px;
19
+ --bg: #f5f6f8; --surface: #ffffff; --surface-2: #f0f2f5; --border: #e2e5ea; --border-strong: #cfd4dc;
20
+ --text: #15171c; --muted: #5b6372; --faint: #8b93a1; --accent: #4f46e5; --accent-soft: #eef0ff;
21
+ --primary: #4f46e5; --primary-hover: #4338ca; --primary-active: #3730a3; --on-primary: #ffffff;
22
+ --hover: #eceef2; --active: #e2e5ea; --focus: #6366f1;
23
+ --ok: #15803d; --ok-bar: #22c55e; --ok-soft: #e8f7ee; --warn: #b45309; --warn-bar: #f59e0b; --warn-soft: #fdf3e2;
24
+ --fail: #c81e1e; --fail-bar: #ef4444; --fail-soft: #fdecec; --track: #e9ecf1;
25
+ --spark: oklch(from var(--accent) clamp(0.55, l, 0.62) max(c, 0.14) h); --spark-soft: oklch(from var(--accent) 0.83 calc(max(c, 0.14) * 0.6) h);
26
+ --shadow: 0 1px 2px rgba(16,24,40,.04), 0 2px 8px rgba(16,24,40,.05);
27
+ --float-shadow: 0 10px 30px rgba(16,24,40,.14), 0 2px 6px rgba(16,24,40,.06);${DERIVED_COLORS}`;
28
+ const DARK_TOKENS = `
29
+ --bg: #0d0f12; --surface: #15181d; --surface-2: #1c2026; --border: #262b33; --border-strong: #353c47;
30
+ --text: #e7e9ee; --muted: #a0a8b5; --faint: #6c7480; --accent: #8b87ff; --accent-soft: #23224a;
31
+ --primary: #5b54e8; --primary-hover: #6a64ef; --primary-active: #4f46e5; --on-primary: #ffffff;
32
+ --hover: #22272e; --active: #2a3038; --focus: #8b87ff;
33
+ --ok: #4ade80; --ok-bar: #22c55e; --ok-soft: #13291c; --warn: #fbbf24; --warn-bar: #f59e0b; --warn-soft: #2d2410;
34
+ --fail: #f87171; --fail-bar: #ef4444; --fail-soft: #331717; --track: #252a32;
35
+ --spark: oklch(from var(--accent) clamp(0.7, l, 0.78) max(c, 0.14) h); --spark-soft: oklch(from var(--accent) 0.45 calc(max(c, 0.14) * 0.6) h);
36
+ --shadow: 0 1px 2px rgba(0,0,0,.3); --float-shadow: 0 12px 32px rgba(0,0,0,.5), 0 2px 6px rgba(0,0,0,.3);${DERIVED_COLORS}`;
37
+ /**
38
+ * The page's style block: default tokens, the host's tokens over them, then the rules that read them. With
39
+ * `colorScheme: 'auto'` the page follows the system unless the viewer picked light or dark in the Panel, which sets
40
+ * `data-theme` on the root element.
41
+ */
42
+ export function pageStyles(theme = resolveTheme(undefined)) {
43
+ const rule = (decls, sel = ':root') => (decls.length ? `${sel} { ${decls.join('; ')}; }\n` : '');
44
+ const scheme = theme.colorScheme === 'auto' ? 'light dark' : theme.colorScheme;
45
+ let css = `:root {${LIGHT_TOKENS}\n color-scheme: ${scheme};\n}\n${rule(theme.light)}`;
46
+ if (theme.colorScheme === 'auto') {
47
+ const sel = ':root:not([data-theme=light])';
48
+ css += `@media (prefers-color-scheme: dark) {\n${sel} {${DARK_TOKENS}\n}\n${rule(theme.dark, sel)}}\n`;
49
+ css += `:root[data-theme=dark] {${DARK_TOKENS}\n color-scheme: dark;\n}\n${rule(theme.dark, ':root[data-theme=dark]')}`;
50
+ css += ':root[data-theme=light] { color-scheme: light; }\n';
51
+ }
52
+ else if (theme.colorScheme === 'dark')
53
+ css += `:root {${DARK_TOKENS}\n}\n${rule(theme.dark)}`;
54
+ return css + CSS;
55
+ }
56
+ const CSS = `
57
+ * { box-sizing: border-box; }
58
+ html { -webkit-text-size-adjust: 100%; }
59
+ body { margin: 0; background: var(--bg); color: var(--text); font: var(--font-size)/1.5 var(--font); font-variant-numeric: tabular-nums; }
60
+ [hidden] { display: none !important; }
61
+ .top-in, .wrap { max-width: var(--content-width); margin: 0 auto; }
62
+ .top-in { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 28px var(--gutter) 0; }
63
+ .wrap { padding: 20px var(--gutter) 40px; }
64
+ .header-bar .top { background: var(--surface); border-bottom: 1px solid var(--border); }
65
+ .header-bar .top-in { min-height: 84px; padding: 14px var(--gutter); }
66
+ .header-bar .wrap { padding-top: 20px; }
67
+ h1, h2, h3 { margin: 0; letter-spacing: -0.01em; }
68
+ h2 { font-size: var(--heading-size); font-weight: var(--heading-weight); }
69
+ b, strong { font-weight: var(--strong-weight); }
70
+ p { margin: 0; }
71
+ a { color: var(--accent); text-decoration: none; }
72
+ a:hover { text-decoration: underline; }
73
+ code, .mono { font-family: var(--mono); font-size: 0.92em; }
74
+ .muted { color: var(--muted); }
75
+ .faint { color: var(--faint); }
76
+ .small { font-size: var(--small); }
77
+ .sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
78
+ .sep { color: var(--faint); margin: 0 6px; }
79
+ .brand { display: flex; align-items: center; gap: 12px; min-width: 0; }
80
+ .brand h1 { font-size: var(--title-size); font-weight: var(--title-weight); line-height: 1.3; letter-spacing: -0.02em; }
81
+ .tagline { color: var(--muted); font-size: 13px; }
82
+ .logo { display: inline-flex; flex: none; width: 40px; height: 40px; border-radius: 10px; background: var(--logo-bg); box-shadow: inset 0 0 0 1px var(--logo-line); }
83
+ /* desktop/icon.ts draws the same two half discs for the app icon. */
84
+ .logo svg { display: block; width: 100%; height: 100%; fill: var(--logo-fg); }
85
+ .top-actions { display: flex; align-items: center; gap: 8px; }
86
+ .section-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; flex-wrap: wrap; }
87
+ .section-head > div:first-child { min-width: 0; }
88
+ .block { margin-bottom: 22px; }
89
+ .ic { width: 15px; height: 15px; flex: none; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
90
+
91
+ /* Summary: four quiet numbers in one surface. */
92
+ .summary { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); margin-bottom: 18px; overflow: hidden; }
93
+ .stat { display: flex; align-items: flex-end; justify-content: space-between; gap: 10px; min-width: 0; padding: 14px 18px; border-left: 1px solid var(--border); text-align: left; }
94
+ .stat:first-child { border-left: 0; }
95
+ .stat-text { min-width: 0; }
96
+ .stat-label { color: var(--muted); font-size: var(--small); display: flex; align-items: center; gap: 6px; }
97
+ .stat-value { font-size: 22px; font-weight: var(--heading-weight); letter-spacing: -0.02em; line-height: 1.35; white-space: nowrap; }
98
+ .stat-sub { color: var(--faint); font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
99
+ .stat-btn { font: inherit; color: inherit; background: none; border: 0; border-left: 1px solid var(--border); cursor: pointer; transition: background-color .12s; }
100
+ .stat-btn:hover { background: var(--hover); }
101
+ .stat-btn[aria-pressed=true] { background: var(--warn-soft); }
102
+ .stat-warn .stat-value { color: var(--warn); }
103
+ .stat-ok .stat-value { color: var(--ok); }
104
+ .label-tip { display: inline-flex; align-items: center; gap: 6px; }
105
+
106
+ /* Toolbar */
107
+ .toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 12px; }
108
+ .search { position: relative; flex: 1 1 240px; min-width: 0; display: block; }
109
+ .search-ic { position: absolute; left: 11px; top: 50%; transform: translateY(-50%); color: var(--faint); pointer-events: none; }
110
+ .search .search-input { padding-left: 34px; padding-right: 34px; height: var(--ctl-h); }
111
+ .kbd { position: absolute; right: 9px; top: 50%; transform: translateY(-50%); font: 11px/16px var(--mono); color: var(--faint); border: 1px solid var(--border); border-radius: 4px; padding: 0 5px; pointer-events: none; }
112
+ .search:focus-within .kbd, .search input:not(:placeholder-shown) ~ .kbd { display: none; }
113
+ .tools { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
114
+ .tb-select { width: auto; height: var(--ctl-h); font-size: var(--control-size); }
115
+ .seg { display: inline-flex; align-items: center; gap: 2px; height: var(--ctl-h); padding: 2px; border: 1px solid var(--border-strong); border-radius: var(--ctl-radius); background: var(--surface); }
116
+ .seg-btn { display: inline-flex; align-items: center; gap: 5px; height: 100%; padding: 0 10px; border: 0; border-radius: calc(var(--ctl-radius) - 2px); background: transparent; color: var(--muted); font: inherit; font-size: var(--control-size); cursor: pointer; }
117
+ .seg-btn:hover { color: var(--text); }
118
+ .seg-btn[aria-pressed=true] { background: var(--active); color: var(--text); font-weight: var(--strong-weight); }
119
+ .seg-btn:focus-visible { outline: var(--focus-outline); outline-offset: 1px; box-shadow: var(--focus-ring); }
120
+ .acct-head { margin-bottom: 10px; }
121
+ .acct-head h2 { display: flex; align-items: baseline; gap: 10px; }
122
+ #acct-count { font-weight: 400; }
123
+
124
+ /* Family groups */
125
+ .group { margin-bottom: 14px; min-width: 0; }
126
+ .group-list { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); }
127
+ .group-head { display: flex; align-items: center; gap: 10px; padding: 8px 12px 8px 8px; min-height: 48px; flex-wrap: wrap; }
128
+ .group-cards .group-head { padding: 0 0 8px; min-height: 0; }
129
+ .group-toggle { display: inline-flex; align-items: center; gap: 9px; min-width: 0; padding: 5px 8px 5px 4px; border: 0; border-radius: var(--ctl-radius); background: none; color: var(--text); font: inherit; cursor: pointer; }
130
+ .group-toggle:hover { background: var(--hover); }
131
+ .group-toggle:focus-visible { outline: var(--focus-outline); outline-offset: 1px; box-shadow: var(--focus-ring); }
132
+ .chev { color: var(--faint); transition: transform .15s; }
133
+ .group-toggle[aria-expanded=true] .chev { transform: rotate(90deg); }
134
+ .group-title { font-weight: var(--heading-weight); font-size: var(--heading-size); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
135
+ .group-count { color: var(--muted); font-size: 12px; background: var(--surface-2); border-radius: var(--chip-radius); padding: 0 8px; line-height: 20px; }
136
+ .group-meta { margin-left: auto; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
137
+ .group-quota { display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
138
+
139
+ /* List rows: identity, one column per quota window, usage, last used, health, menu. Every column but the first has a
140
+ fixed width, so the rows of all groups line up under the same column titles. --qn is the number of quota
141
+ columns; the page sets it on #accounts. */
142
+ #accounts { --qn: 2; --q-w: 148px; --u-w: 168px; --l-w: 82px; --h-w: 112px; --m-w: 28px; --col-gap: 20px; }
143
+ .row, .col-head { display: grid; grid-template-columns: minmax(0, 1fr) calc(var(--qn) * var(--q-w) + (var(--qn) - 1) * var(--col-gap)) var(--u-w) var(--l-w) var(--h-w) var(--m-w); column-gap: var(--col-gap); align-items: center; min-width: 0; }
144
+ .row { grid-template-areas: "id quota usage last health more" "notes notes notes notes notes notes"; padding: 11px 10px 11px 16px; border-top: 1px solid var(--border); transition: background-color .12s; }
145
+ .rows .row:hover { background: color-mix(in srgb, var(--hover) 55%, transparent); }
146
+ .row:first-child { border-top: 0; }
147
+ .row:last-child { border-radius: 0 0 var(--radius) var(--radius); }
148
+ .row-error .ident-name .name { color: var(--fail); }
149
+ .col-head { position: sticky; top: 0; z-index: 2; padding: 7px 10px 7px 16px; border-top: 1px solid var(--border); background: var(--surface); color: var(--faint); font-size: 11.5px; font-weight: var(--strong-weight); line-height: 1.3; white-space: nowrap; }
150
+ .col-head > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
151
+ .col-head .quota > span { overflow: hidden; text-overflow: ellipsis; }
152
+ .ident { grid-area: id; display: flex; align-items: center; gap: 10px; min-width: 0; }
153
+ .ident-text { min-width: 0; flex: 1; }
154
+ .ident-name { display: flex; align-items: center; gap: 6px; min-width: 0; flex-wrap: wrap; row-gap: 3px; }
155
+ .ident-name .name { font-weight: var(--strong-weight); font-size: var(--font-size); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
156
+ .ident-email { color: var(--muted); font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 1px; }
157
+ .ident-email + .ident-sub { color: var(--faint); margin-top: 0; }
158
+ .tools .tb-icon { width: var(--ctl-h); height: var(--ctl-h); border-color: var(--border-strong); background: var(--surface); }
159
+ .tools .tb-icon:hover { background: var(--hover); }
160
+ .tools .tb-icon[aria-pressed=true] { background: var(--active); color: var(--text); }
161
+ .ident-sub { color: var(--muted); font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 1px; }
162
+ .plan { text-transform: capitalize; }
163
+ .row > .quota { grid-area: quota; }
164
+ .row > .usage { grid-area: usage; }
165
+ .row > .last { grid-area: last; }
166
+ .cell-health { grid-area: health; min-width: 0; }
167
+ .row > .more { grid-area: more; }
168
+ .row-notes { grid-area: notes; display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
169
+
170
+ /* Quota meters: percent and countdown over a bar colored by level (ok, warn at 60%, fail at 85% used). */
171
+ .quota { display: grid; grid-template-columns: repeat(var(--qn), minmax(0, 1fr)); column-gap: var(--col-gap); row-gap: 4px; min-width: 0; }
172
+ .row > .quota { grid-template-columns: repeat(var(--qn), var(--q-w)); }
173
+ .meter { min-width: 0; }
174
+ .m-top { display: flex; align-items: baseline; gap: 6px; font-size: 12px; line-height: 1.35; white-space: nowrap; min-width: 0; }
175
+ .m-label { color: var(--muted); font-weight: var(--strong-weight); }
176
+ .m-pct { font-weight: var(--strong-weight); font-size: 13px; font-variant-numeric: tabular-nums; }
177
+ .m-unit { color: var(--faint); font-weight: 400; font-size: 11.5px; }
178
+ .m-reset { margin-left: auto; color: var(--faint); font-size: 11.5px; font-variant-numeric: tabular-nums; overflow: hidden; text-overflow: ellipsis; }
179
+ .m-passed { font-style: italic; }
180
+ .m-none { color: var(--faint); }
181
+ .bar { height: 6px; border-radius: 999px; background: var(--track); overflow: hidden; margin-top: 5px; }
182
+ .bar > span { display: block; height: 100%; min-width: 0; border-radius: inherit; background: var(--ok-bar); transition: width .4s ease; }
183
+ .lvl-warn .bar > span { background: var(--warn-bar); } .lvl-warn .m-pct { color: var(--warn); }
184
+ .lvl-fail .bar > span { background: var(--fail-bar); } .lvl-fail .m-pct { color: var(--fail); }
185
+ .lvl-reset .m-pct { color: var(--muted); }
186
+ .lvl-stale.lvl-ok .m-pct { color: var(--muted); }
187
+ .is-stale .bar > span, .lvl-stale .bar > span { background: var(--faint); opacity: .5; }
188
+ .q-asof { grid-column: 1 / -1; display: flex; align-items: center; gap: 4px; color: var(--faint); font-size: 11.5px; line-height: 1.3; }
189
+ .q-asof .ic { width: 12px; height: 12px; }
190
+ .q-note { grid-column: 1 / -1; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; color: var(--muted); font-size: var(--small); }
191
+ .link-btn { padding: 0; border: 0; background: none; color: var(--accent); font: inherit; font-weight: var(--strong-weight); cursor: pointer; }
192
+ .link-btn:hover:not(:disabled) { text-decoration: underline; }
193
+ .link-btn:disabled { color: var(--faint); cursor: not-allowed; }
194
+ .link-btn:focus-visible { outline: var(--focus-outline); outline-offset: 2px; border-radius: 3px; }
195
+
196
+ /* Usage: total over cache hits, with the 7 day bars beside both lines. In the wide list the bars sit at the column's
197
+ right edge, so they line up down the column whatever the totals. */
198
+ .usage { display: flex; align-items: center; gap: 12px; min-width: 0; }
199
+ .rows .usage { justify-content: space-between; }
200
+ .u-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
201
+ .u-num { font-weight: var(--strong-weight); font-size: 13.5px; line-height: 1.3; white-space: nowrap; font-variant-numeric: tabular-nums; }
202
+ .u-unit { color: var(--faint); font-weight: 400; font-size: 12px; }
203
+ .rows .u-unit { display: none; }
204
+ .u-sub, .u-note { color: var(--faint); font-size: 11.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
205
+ .u-note { font-size: var(--small); color: var(--muted); white-space: normal; }
206
+ /* One slot per day, bars grow from a shared baseline; past days are a tint of the accent, today is the accent. */
207
+ .spark { display: inline-flex; align-items: flex-end; gap: 3px; height: 26px; flex: none; }
208
+ .spark i { display: flex; align-items: flex-end; width: 5px; height: 100%; }
209
+ .spark b { display: block; width: 100%; min-height: 4px; border-radius: 1.5px; background: color-mix(in srgb, var(--accent) 32%, var(--surface)); transition: background-color .12s; }
210
+ .spark i.zero b { height: 2px; min-height: 0; background: color-mix(in srgb, var(--faint) 30%, transparent); }
211
+ .spark i.today b { background: var(--accent); }
212
+ .spark i:not(.zero):hover b { background: color-mix(in srgb, var(--accent) 70%, var(--surface)); }
213
+ .spark i.today:hover b { background: var(--accent); }
214
+ /* Where relative colors work, both shades keep the accent's hue at a set lightness and a floor on chroma, so a dark or
215
+ muted accent still gives a clear light tint for past days and a vivid bar for today. */
216
+ @supports (color: oklch(from red l c h)) {
217
+ .spark b { background: var(--spark-soft); }
218
+ .spark i.today:not(.zero) b, .spark i:not(.zero):hover b { background: var(--spark); }
219
+ }
220
+ .spark-lg { height: 36px; gap: 4px; }
221
+ .spark-lg i { width: 7px; }
222
+ .spark-lg b { border-radius: 2px; }
223
+ .last { color: var(--muted); font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
224
+ .health { font-size: 11.5px; line-height: 20px; padding: 0 8px 0 7px; gap: 5px; max-width: 100%; }
225
+ .health .dot { width: 6px; height: 6px; }
226
+
227
+ /* In the wide list the column titles name the windows; elsewhere each meter carries its label. */
228
+ .rows .m-label, .rows .m-unit { display: none; }
229
+
230
+ /* Chips and badges */
231
+ .badge { flex: none; display: inline-grid; place-items: center; width: 34px; height: 34px; border-radius: 10px; color: #fff; font-weight: 700; font-size: 12.5px; letter-spacing: .02em; }
232
+ .badge-logo { background: var(--surface-2); border: 1px solid var(--border); color: var(--text); }
233
+ .badge svg { display: block; width: 20px; height: 20px; fill: currentColor; }
234
+ .badge-sm { width: 26px; height: 26px; border-radius: 7px; font-size: 10.5px; }
235
+ .badge-sm svg { width: 16px; height: 16px; }
236
+ .badge-xs { width: 16px; height: 16px; border-radius: 4px; border: 0; background: none; }
237
+ .badge-xs svg { width: 13px; height: 13px; }
238
+ .chip { display: inline-flex; align-items: center; gap: 5px; padding: 1px 8px; border-radius: var(--chip-radius); background: var(--surface-2); border: 1px solid var(--border); color: var(--muted); font: inherit; font-size: 12px; line-height: 20px; white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
239
+ .chip-sm { font-size: 11px; line-height: 16px; padding: 0 6px; }
240
+ .chip-btn { cursor: pointer; transition: filter .12s, border-color .12s; }
241
+ .chip-btn:hover { border-color: var(--border-strong); filter: brightness(.97); }
242
+ .chip-btn:focus-visible { outline: var(--focus-outline); outline-offset: 2px; box-shadow: var(--focus-ring); }
243
+ .chip-accent { background: var(--accent-soft); border-color: transparent; color: var(--accent); }
244
+ .chip-ok { background: var(--ok-soft); border-color: transparent; color: var(--ok); }
245
+ .chip-warn { background: var(--warn-soft); border-color: transparent; color: var(--warn); }
246
+ .chip-fail { background: var(--fail-soft); border-color: transparent; color: var(--fail); }
247
+ .chip .badge-xs { margin-left: -3px; }
248
+ /* Tags next to a name: short command, API, signed out, host missing; one size for all. */
249
+ .tag { display: inline-flex; align-items: center; gap: 4px; height: 18px; padding: 0 6px; border: 0; border-radius: 5px; background: var(--surface-2); color: var(--muted); font: inherit; font-size: 11px; font-weight: var(--strong-weight); line-height: 1; white-space: nowrap; }
250
+ .tag-alias { font-family: var(--mono); font-weight: 400; }
251
+ .tag-warn { background: var(--warn-soft); color: var(--warn); }
252
+ .tag-fail { background: var(--fail-soft); color: var(--fail); }
253
+ .tag-ok { background: var(--ok-soft); color: var(--ok); }
254
+ .tag .dot { width: 6px; height: 6px; }
255
+ .tag-btn { cursor: pointer; transition: filter .12s; }
256
+ .tag-btn:hover { filter: brightness(.96); }
257
+ .tag-btn:focus-visible { outline: var(--focus-outline); outline-offset: 2px; }
258
+ .group-wait { display: inline-flex; align-items: center; gap: 7px; color: var(--muted); font-size: var(--small); }
259
+ .pulse { width: 7px; height: 7px; border-radius: 50%; background: var(--accent); flex: none; animation: pulse 1.4s ease-in-out infinite; }
260
+ @keyframes pulse { 50% { opacity: .3; } }
261
+ .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--faint); flex: none; }
262
+ .dot-ok { background: var(--ok-bar); } .dot-fail { background: var(--fail-bar); } .dot-warn { background: var(--warn-bar); }
263
+ .title-logo { display: flex; align-items: center; gap: 9px; min-width: 0; }
264
+
265
+ /* Cards view */
266
+ .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 12px; }
267
+ .panel, .card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); }
268
+ .panel { padding: 18px; }
269
+ .card { padding: 14px; display: flex; flex-direction: column; gap: 12px; min-width: 0; }
270
+ .card-error { border-color: var(--fail); }
271
+ .card-head { display: flex; align-items: flex-start; gap: 10px; min-width: 0; }
272
+ .card-head .badge { margin-top: 1px; }
273
+ .card .quota { gap: 10px 16px; }
274
+ .card .bar { height: 6px; }
275
+ .card-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; border-top: 1px solid var(--border); padding-top: 10px; margin-top: auto; min-width: 0; }
276
+ .card-side { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; flex: none; }
277
+ .card .row-notes { margin: 0; }
278
+
279
+ /* Buttons: .btn is the secondary (outlined) variant; .btn-primary is the one main action of a block; .btn-ghost is a
280
+ quiet action such as Close. Two sizes: default (--ctl-h) for block actions, .btn-sm (--ctl-h-sm) inside cards, notes
281
+ and banners. .icon-btn is a square ghost button for icons. */
282
+ .btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; height: var(--ctl-h); padding: 0 14px; border-radius: var(--ctl-radius); border: 1px solid var(--border-strong); background: var(--surface); color: var(--btn-text); font: inherit; font-size: var(--control-size); font-weight: var(--button-weight); line-height: 1; cursor: pointer; white-space: nowrap; text-decoration: none; user-select: none; transition: background-color .12s, border-color .12s, color .12s; }
283
+ a.btn:hover { text-decoration: none; }
284
+ .btn:hover:not(:disabled) { background: var(--hover); border-color: var(--btn-hover-border); color: var(--btn-hover-text); }
285
+ .btn:active:not(:disabled) { background: var(--active); }
286
+ .btn-primary { background: var(--primary); border-color: var(--primary-border); color: var(--on-primary); font-weight: var(--primary-weight); }
287
+ .btn-primary:hover:not(:disabled) { background: var(--primary-hover); border-color: var(--primary-hover-border); color: var(--on-primary); }
288
+ .btn-primary:active:not(:disabled) { background: var(--primary-active); border-color: var(--primary-hover-border); }
289
+ .btn-ghost { border-color: transparent; background: transparent; color: var(--muted); }
290
+ .btn-ghost:hover:not(:disabled) { border-color: transparent; color: var(--text); }
291
+ .btn-sm { height: var(--ctl-h-sm); padding: 0 10px; font-size: var(--small); }
292
+ .btn svg { width: 15px; height: 15px; flex: none; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
293
+ .btn:disabled { background: var(--surface-2); border-color: var(--border); color: var(--faint); cursor: not-allowed; }
294
+ .btn-ghost:disabled { background: transparent; border-color: transparent; }
295
+ /* A busy button is disabled but keeps its variant, so a running action does not look unavailable. */
296
+ .btn[aria-busy=true]:disabled { cursor: progress; color: var(--muted); }
297
+ .btn-primary[aria-busy=true]:disabled { background: var(--primary); border-color: var(--primary-border); color: var(--on-primary); opacity: .85; }
298
+ .is-spinning svg { animation: spin .8s linear infinite; }
299
+ .icon-btn { display: inline-grid; place-items: center; width: var(--ctl-h-sm); height: var(--ctl-h-sm); padding: 0; border: 1px solid transparent; border-radius: var(--ctl-radius); background: transparent; color: var(--muted); cursor: pointer; font: inherit; }
300
+ .icon-btn:hover:not(:disabled), .icon-btn[aria-expanded=true] { background: var(--hover); color: var(--text); }
301
+ .more .ic { stroke-width: 3.2; width: 17px; height: 17px; }
302
+ .icon-btn:disabled { color: var(--faint); cursor: not-allowed; }
303
+ .top-actions .icon-btn { width: var(--ctl-h); height: var(--ctl-h); border-color: var(--border-strong); background: var(--surface); }
304
+ .top-actions .icon-btn:hover { background: var(--hover); }
305
+ .lang-btn { font-size: 12.5px; font-weight: var(--button-weight); color: var(--btn-text); width: auto !important; padding: 0 10px; }
306
+ .btn:focus-visible, .icon-btn:focus-visible, input:focus-visible, select:focus-visible, a:focus-visible, .stat-btn:focus-visible { outline: var(--focus-outline); outline-offset: 2px; box-shadow: var(--focus-ring); }
307
+ .btn:focus-visible { border-color: var(--focus); }
308
+ .note a, .banner a { color: inherit; text-decoration: underline; text-underline-offset: 2px; font-weight: var(--strong-weight); }
309
+
310
+ /* Notes and banners */
311
+ .note { background: var(--surface-2); border-radius: calc(var(--radius) - 6px); padding: 8px 11px; font-size: 13px; color: var(--muted); display: flex; flex-wrap: wrap; gap: 4px 8px; align-items: center; }
312
+ .note b { color: inherit; }
313
+ .note-fail { background: var(--fail-soft); color: var(--fail); }
314
+ .note-warn { background: var(--warn-soft); color: var(--warn); }
315
+ .error-note { justify-content: space-between; gap: 12px; }
316
+ .error-note > span { min-width: 0; overflow-wrap: anywhere; }
317
+ .error-note > .note-sub { flex-basis: 100%; color: var(--text); }
318
+ .banner { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding: 11px 14px; border-radius: calc(var(--radius) - 2px); margin-bottom: 12px; border: 1px solid var(--border); background: var(--surface); }
319
+ .banner > div { min-width: 0; }
320
+ .banner-quiet { padding: 8px 14px; background: transparent; border-style: dashed; }
321
+ .banner-fail { background: var(--fail-soft); border-color: transparent; color: var(--fail); }
322
+ .banner-warn { background: var(--warn-soft); border-color: transparent; color: var(--warn); }
323
+ .banner ul { margin: 6px 0 0; padding-left: 18px; }
324
+ .disabled-why { color: var(--muted); font-size: var(--small); margin-top: 10px; }
325
+ .actions { display: flex; gap: 8px; flex-wrap: wrap; }
326
+ .spin { display: inline-block; width: 13px; height: 13px; border-radius: 50%; border: 2px solid currentColor; border-right-color: transparent; animation: spin .8s linear infinite; vertical-align: -2px; flex: none; }
327
+ @keyframes spin { to { transform: rotate(360deg); } }
328
+ .copy { display: flex; align-items: center; gap: 6px; max-width: 100%; min-width: 0; }
329
+ .copy code { background: var(--surface-2); border: 1px solid var(--border); border-radius: 7px; padding: 3px 8px; overflow-x: auto; white-space: nowrap; color: var(--text); min-width: 0; }
330
+
331
+ /* Health check */
332
+ .diff { margin: 0; max-height: 260px; overflow: auto; background: var(--surface); border: 1px solid var(--border); border-radius: calc(var(--radius) - 4px); padding: 10px 0; font: 12px/1.55 var(--mono); }
333
+ .diff span { display: block; padding: 0 12px; white-space: pre; }
334
+ .diff .add { background: var(--ok-soft); color: var(--ok); } .diff .del { background: var(--fail-soft); color: var(--fail); } .diff .hunk { color: var(--faint); }
335
+ .filter-bar { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 12px; padding: 8px 10px; margin-bottom: 4px; border-radius: var(--ctl-radius); background: var(--surface-2); }
336
+ .findings { list-style: none; margin: 12px 0 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
337
+ .more-findings { margin-top: 10px; }
338
+ .finding { display: flex; gap: 10px; padding: 10px 12px; border: 1px solid var(--border); border-radius: calc(var(--radius) - 4px); }
339
+ .finding .dot { margin-top: 7px; }
340
+ .finding.lvl-fail .dot { background: var(--fail-bar); } .finding.lvl-warn .dot { background: var(--warn-bar); }
341
+ .finding-body { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
342
+ .finding-top { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
343
+ .hint { color: var(--muted); font-size: var(--small); overflow-wrap: anywhere; }
344
+ .all-good { display: flex; align-items: center; gap: 12px; margin-top: 12px; padding: 12px 14px; border-radius: calc(var(--radius) - 4px); background: var(--ok-soft); }
345
+ .check-icon { display: inline-grid; place-items: center; width: 26px; height: 26px; border-radius: 50%; background: var(--ok); color: var(--surface); font-weight: 700; flex: none; }
346
+ .warn-icon { display: inline-grid; place-items: center; width: 26px; height: 26px; border-radius: 50%; background: var(--warn); color: var(--surface); font-weight: 700; flex: none; }
347
+ .fix-result { margin-top: 12px; padding: 10px 12px; border-radius: calc(var(--radius) - 4px); font-size: 13px; }
348
+ .fix-result.is-ok { background: var(--ok-soft); color: var(--ok); } .fix-result.is-warn { background: var(--warn-soft); color: var(--warn); }
349
+ .fix-result ul { margin: 6px 0 0; padding-left: 18px; }
350
+
351
+ /* Forms */
352
+ .form { display: flex; flex-direction: column; gap: 14px; }
353
+ .field { display: flex; flex-direction: column; gap: 6px; border: 0; padding: 0; margin: 0; min-width: 0; }
354
+ .field > span, .field > legend { font-weight: var(--strong-weight); font-size: var(--small); padding: 0; margin-bottom: 6px; }
355
+ .field > span { margin-bottom: 0; }
356
+ input[type=text], input[type=search], select { height: var(--field-h); border-radius: var(--ctl-radius); border: 1px solid var(--border-strong); background-color: var(--surface); color: var(--text); font: inherit; padding: 0 12px; width: 100%; transition: border-color .12s, box-shadow .12s; }
357
+ input[type=search] { -webkit-appearance: none; appearance: none; }
358
+ input[type=search]::-webkit-search-cancel-button { -webkit-appearance: none; }
359
+ input::placeholder { color: var(--faint); }
360
+ input[type=text]:hover:not(:disabled), input[type=search]:hover, select:hover:not(:disabled) { border-color: var(--btn-hover-border); }
361
+ input[type=text]:focus, input[type=search]:focus, select:focus { border-color: var(--focus); }
362
+ input[type=text]:disabled, select:disabled { background-color: var(--surface-2); color: var(--faint); cursor: not-allowed; }
363
+ input.invalid { border-color: var(--fail); }
364
+ /* The select draws its own chevron with two gradient strokes (the CSP allows no external images), so it follows the
365
+ text color in both schemes and on hover. */
366
+ select { --chevron: var(--muted); appearance: none; padding-right: 32px; cursor: pointer; text-overflow: ellipsis;
367
+ background-image: linear-gradient(45deg, transparent calc(50% - .8px), var(--chevron) calc(50% - .8px) calc(50% + .8px), transparent calc(50% + .8px)), linear-gradient(-45deg, transparent calc(50% - .8px), var(--chevron) calc(50% - .8px) calc(50% + .8px), transparent calc(50% + .8px));
368
+ background-position: right 16px center, right 11px center; background-size: 5px 5px; background-repeat: no-repeat; }
369
+ select:is(:hover, :focus) { --chevron: var(--text); }
370
+ .check-row { display: flex; align-items: flex-start; gap: 8px; }
371
+ .check-row .check { flex: 1; }
372
+ .check-row .tip { margin-top: 12px; }
373
+ .check { display: flex; gap: 10px; align-items: flex-start; cursor: pointer; padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--ctl-radius); transition: border-color .12s, background-color .12s; }
374
+ .check:hover { border-color: var(--btn-hover-border); }
375
+ .check:has(input:checked) { background: var(--accent-soft); border-color: transparent; }
376
+ .check input { width: 16px; height: 16px; margin: 2px 0 0; flex: none; accent-color: var(--check); cursor: pointer; }
377
+ .check span { display: flex; flex-direction: column; gap: 2px; }
378
+ .check b { font-size: var(--small); }
379
+ .field-error { color: var(--fail); font-size: var(--small); }
380
+ .field-hint { color: var(--muted); font-size: var(--small); }
381
+ .field-hint:empty { display: none; }
382
+ .field-error:empty { display: none; }
383
+ .fam-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 8px; }
384
+ .fam-card { position: relative; display: flex; align-items: center; gap: 10px; padding: 9px 10px; border: 1px solid var(--border); border-radius: var(--ctl-radius); cursor: pointer; min-width: 0; transition: border-color .12s, background-color .12s; }
385
+ .fam-card:hover { border-color: var(--btn-hover-border); }
386
+ .fam-card:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); box-shadow: inset 0 0 0 1px var(--accent); }
387
+ .fam-card:has(input:focus-visible) { outline: var(--focus-outline); outline-offset: 2px; }
388
+ .fam-card.is-off { cursor: not-allowed; opacity: .6; }
389
+ .fam-card.is-off:hover { border-color: var(--border); }
390
+ .fam-text { display: flex; flex-direction: column; min-width: 0; }
391
+ .fam-title { font-weight: var(--strong-weight); font-size: var(--small); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
392
+ .fam-sub { color: var(--muted); font-size: 11.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
393
+ .preview { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 4px 14px; margin: 0; padding: 10px 12px; border-radius: var(--ctl-radius); background: var(--surface-2); font-size: var(--small); }
394
+ .preview dt { color: var(--muted); }
395
+ .preview dd { margin: 0; overflow-wrap: anywhere; color: var(--text); }
396
+ .preview.is-draft dd { color: var(--faint); }
397
+ .created { display: flex; flex-direction: column; gap: 10px; }
398
+ .created-head { display: flex; align-items: center; gap: 10px; }
399
+ .steps { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 8px; }
400
+ .steps li::marker { color: var(--faint); }
401
+ .steps li { min-width: 0; }
402
+ .steps li > span:not(.copy) { display: block; margin-bottom: 4px; }
403
+
404
+ /* Dialog */
405
+ .dialog { padding: 0; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); color: var(--text); width: min(560px, calc(100vw - 24px)); max-height: calc(100vh - 48px); box-shadow: var(--float-shadow); }
406
+ .dialog[open] { display: flex; flex-direction: column; }
407
+ .dialog::backdrop { background: rgba(10, 12, 16, .4); }
408
+ .dlg-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 16px 18px 4px; }
409
+ .dlg-head h2 { font-size: var(--large); }
410
+ .dlg-body { padding: 10px 18px 16px; overflow: auto; display: flex; flex-direction: column; gap: 12px; }
411
+ .dlg-foot { display: flex; justify-content: flex-end; gap: 8px; padding: 12px 18px; border-top: 1px solid var(--border); background: var(--surface-2); border-radius: 0 0 var(--radius) var(--radius); }
412
+
413
+ /* Menu, tooltips, toasts */
414
+ .menu { position: fixed; z-index: 40; min-width: 230px; max-width: min(380px, calc(100vw - 16px)); padding: 4px; background: var(--surface); border: 1px solid var(--border); border-radius: calc(var(--ctl-radius) + 2px); box-shadow: var(--float-shadow); }
415
+ .menu-item { display: flex; align-items: center; gap: 10px; width: 100%; height: 32px; padding: 0 10px; border: 0; border-radius: calc(var(--ctl-radius) - 2px); background: none; color: var(--text); font: inherit; font-size: var(--control-size); text-align: left; cursor: pointer; }
416
+ .menu-item .ic { flex: none; color: var(--muted); }
417
+ .menu-item:hover:not(:disabled), .menu-item:focus-visible { background: var(--hover); outline: none; }
418
+ .menu-item:disabled { color: var(--faint); cursor: not-allowed; }
419
+ .menu-label { flex: none; white-space: nowrap; }
420
+ /* The hint gives way first: it shrinks to an ellipsis so a long label never pushes it past the menu's edge. */
421
+ .menu-hint { min-width: 0; margin-left: auto; padding-left: 12px; color: var(--faint); font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
422
+ .menu-sep { height: 1px; margin: 4px 6px; background: var(--border); }
423
+ .tip { display: inline-flex; position: relative; vertical-align: middle; }
424
+ .tip-btn { display: inline-grid; place-items: center; width: 16px; height: 16px; padding: 0; border: 1px solid var(--border-strong); border-radius: 50%; background: var(--surface); color: var(--muted); font: 700 10px/1 var(--font); cursor: help; }
425
+ .tip-btn:hover, .tip.is-open .tip-btn { color: var(--text); border-color: var(--muted); }
426
+ .tip-btn:focus-visible { outline: var(--focus-outline); outline-offset: 2px; }
427
+ .tip-body { display: none; position: fixed; z-index: 60; width: max-content; max-width: min(280px, calc(100vw - 16px)); padding: 7px 10px; border-radius: 7px; background: var(--text); color: var(--bg); font-size: 12px; font-weight: 400; line-height: 1.45; letter-spacing: 0; box-shadow: var(--float-shadow); pointer-events: none; text-align: left; white-space: normal; }
428
+ .tip.is-open .tip-body { display: block; }
429
+ .toasts { position: fixed; inset: auto auto 20px 50%; margin: 0; padding: 0; border: 0; background: none; color: inherit; overflow: visible; transform: translateX(-50%); z-index: 50; display: flex; flex-direction: column; align-items: center; gap: 8px; width: max-content; max-width: calc(100vw - 24px); pointer-events: none; }
430
+ .toast { pointer-events: auto; display: flex; align-items: center; gap: 10px; max-width: min(460px, calc(100vw - 24px)); min-height: 40px; padding: 6px 6px 6px 14px; border-radius: calc(var(--ctl-radius) + 2px); background: var(--surface); color: var(--text); border: 1px solid var(--border); box-shadow: var(--float-shadow); font-size: 13px; animation: toast-in .18s ease-out; }
431
+ .toast-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; background: var(--ok); }
432
+ .toast-fail .toast-dot { background: var(--fail); } .toast-plain .toast-dot { background: var(--faint); } .toast-info .toast-dot { background: var(--warn); }
433
+ .toast-text { min-width: 0; overflow-wrap: anywhere; }
434
+ .toast-action { flex: none; height: 28px; padding: 0 10px; border: 1px solid var(--border-strong); border-radius: var(--ctl-radius); background: var(--surface); color: var(--text); font: inherit; font-weight: var(--button-weight); cursor: pointer; }
435
+ .toast-action:hover { background: var(--hover); }
436
+ .toast-x { flex: none; display: inline-grid; place-items: center; width: 26px; height: 26px; padding: 0; border: 0; border-radius: 6px; background: none; color: var(--faint); cursor: pointer; }
437
+ .toast-x:hover { color: var(--text); background: var(--hover); }
438
+ .toast-x .ic { width: 13px; height: 13px; }
439
+ .toast.bump { animation: toast-bump .25s ease-out; }
440
+ @keyframes toast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
441
+ @keyframes toast-bump { 50% { transform: scale(1.03); } }
442
+ .conn { position: fixed; top: 14px; left: 50%; transform: translateX(-50%); z-index: 55; display: flex; align-items: center; gap: 8px; max-width: calc(100vw - 24px); padding: 8px 14px; border-radius: var(--chip-radius); background: var(--warn-soft); color: var(--warn); border: 1px solid var(--border); box-shadow: var(--float-shadow); font-size: 13px; font-weight: var(--strong-weight); }
443
+
444
+ /* Empty, loading and filtered states */
445
+ .empty { text-align: center; padding: 36px 20px; background: var(--surface); border: 1px dashed var(--border-strong); border-radius: var(--radius); display: flex; flex-direction: column; align-items: center; gap: 12px; }
446
+ .empty-sm { padding: 28px 20px; }
447
+ .empty-fail { border-color: var(--fail); border-style: solid; background: var(--fail-soft); color: var(--fail); }
448
+ .empty h3 { font-size: 17px; font-weight: var(--heading-weight); }
449
+ .empty p { max-width: 520px; overflow-wrap: anywhere; }
450
+ .host-list { list-style: none; padding: 0; margin: 4px 0; display: flex; flex-direction: column; gap: 8px; min-width: min(360px, 100%); text-align: left; }
451
+ .host-list li { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border: 1px solid var(--border); border-radius: calc(var(--radius) - 4px); }
452
+ .host-list li > span:nth-child(2) { flex: 1; font-weight: var(--strong-weight); }
453
+ .sk { display: block; border-radius: 6px; background: linear-gradient(90deg, var(--surface-2) 25%, var(--track) 50%, var(--surface-2) 75%); background-size: 200% 100%; animation: shimmer 1.3s ease-in-out infinite; }
454
+ .sk-row { display: flex; align-items: center; gap: 12px; padding: 14px 16px; border-top: 1px solid var(--border); }
455
+ .sk-row:first-child { border-top: 0; }
456
+ .sk-badge { width: 26px; height: 26px; border-radius: 7px; flex: none; }
457
+ .sk-title { height: 12px; width: 22%; }
458
+ .sk-bar { height: 8px; flex: 1; max-width: 160px; margin-left: auto; }
459
+ .sk-line { height: 12px; margin: 8px 0; }
460
+ .sk-stat { height: 40px; margin: 14px 18px; }
461
+ .w50 { width: 50%; } .w70 { width: 70%; }
462
+ @keyframes shimmer { from { background-position: 100% 0; } to { background-position: -100% 0; } }
463
+ .view-in > * { animation: view-in .22s ease-out both; }
464
+ @keyframes view-in { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: none; } }
465
+ .row.flash { animation: flash-row 2.4s ease-out; }
466
+ .card.flash { animation: flash-card 2.4s ease-out; }
467
+ @keyframes flash-row { from { background: var(--accent-soft); } to { background: transparent; } }
468
+ @keyframes flash-card { from { box-shadow: 0 0 0 2px var(--accent), var(--shadow); } to { box-shadow: 0 0 0 2px transparent, var(--shadow); } }
469
+ .checkup { margin-top: 8px; }
470
+ .foot { margin-top: 28px; text-align: center; }
471
+
472
+ @media (max-width: 1100px) {
473
+ .col-head { display: none; }
474
+ .row { grid-template-columns: minmax(0, 1fr) auto var(--m-w); grid-template-areas: "id health more" "quota quota quota" "usage usage last" "notes notes notes"; row-gap: 10px; }
475
+ .row > .quota { grid-template-columns: repeat(var(--qn), minmax(0, 1fr)); max-width: 560px; }
476
+ .rows .m-label, .rows .m-unit, .rows .u-unit { display: inline; }
477
+ .rows .usage { justify-content: flex-start; }
478
+ .row > .last { justify-self: end; }
479
+ }
480
+ @media (max-width: 760px) {
481
+ .summary { grid-template-columns: repeat(2, minmax(0, 1fr)); }
482
+ .stat:nth-child(3) { border-left: 0; }
483
+ .stat:nth-child(n+3) { border-top: 1px solid var(--border); }
484
+ }
485
+ @media (max-width: 560px) {
486
+ :root { --gutter: 14px; }
487
+ .top-in { padding: 16px var(--gutter) 0; }
488
+ .header-bar .top-in { min-height: 0; padding: 12px var(--gutter); }
489
+ .wrap { padding: 14px var(--gutter) 32px; }
490
+ .hide-sm { display: none; }
491
+ .tagline { display: none; }
492
+ .brand .logo { width: 34px; height: 34px; }
493
+ .brand h1 { font-size: 19px; }
494
+ #updated { display: none; }
495
+ .banner { flex-direction: column; }
496
+ .stat { padding: 11px 13px; align-items: flex-start; flex-direction: column; gap: 6px; }
497
+ .stat-value { font-size: 19px; }
498
+ .stat-sub { white-space: normal; }
499
+ .spark-lg { display: none; }
500
+ .search { flex-basis: 100%; }
501
+ .tools { width: 100%; }
502
+ .tools .tb-select { flex: 1 1 140px; min-width: 0; }
503
+ .toolbar > .btn-primary { flex: 1; }
504
+ .row { padding: 10px 12px; }
505
+ #accounts { --col-gap: 14px; }
506
+ .grid { grid-template-columns: 1fr; }
507
+ .panel { padding: 14px; }
508
+ .dlg-head { padding: 14px 14px 2px; } .dlg-body { padding: 10px 14px 14px; } .dlg-foot { padding: 10px 14px; }
509
+ }
510
+ @media (prefers-reduced-motion: reduce) {
511
+ .sk, .spin, .is-spinning svg, .toast, .toast.bump, .view-in > *, .row.flash, .card.flash, .pulse { animation: none; }
512
+ .bar > span, .chev, .row { transition: none; }
513
+ }
514
+ `;
@@ -0,0 +1,20 @@
1
+ import { pageStyles } from './page-styles.ts';
2
+ import { type ResolvedTheme } from './theme.ts';
3
+ export { pageStyles };
4
+ export interface PageOptions {
5
+ basePath: string;
6
+ token: string;
7
+ /** CSP nonce for the inline style and script. */
8
+ nonce?: string;
9
+ version?: string;
10
+ /** buildId() of the serving process; the page reloads when /api/state reports another one. */
11
+ build?: string;
12
+ readOnly?: boolean;
13
+ /** Host theme from resolveTheme(); the default look when absent. */
14
+ theme?: ResolvedTheme;
15
+ /** Host language from createPanelHandler's `lang`; `auto` (the default) lets the viewer choose. */
16
+ lang?: 'auto' | 'en' | 'zh';
17
+ }
18
+ /** JSON that is safe inside a `<script>` element: no `</script>`, `<!--` or line separators survive. */
19
+ export declare function jsonForScript(value: unknown): string;
20
+ export declare function renderPage(opts: PageOptions): string;