clauderipple 0.8.9 → 0.9.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.
Files changed (75) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.es.md +0 -4
  3. package/README.ja.md +0 -4
  4. package/README.ko.md +5 -1
  5. package/README.md +5 -1
  6. package/README.pt-BR.md +0 -4
  7. package/README.zh-CN.md +0 -4
  8. package/dist/app/dist/main.js +23 -3
  9. package/dist/app/package.json +1 -1
  10. package/dist/cli/src/codex.js +3 -1
  11. package/dist/cli/src/index.js +1 -1
  12. package/dist/cli/src/settings.js +5 -2
  13. package/dist/cli/src/tray.js +41 -5
  14. package/dist/mod/.claude-plugin/plugin.json +9 -1
  15. package/dist/mod/hooks/cache.ts +60 -0
  16. package/dist/mod/hooks/compact.ts +40 -0
  17. package/dist/mod/hooks/handoff.ts +43 -0
  18. package/dist/mod/hooks/i18n.ts +2716 -0
  19. package/dist/mod/hooks/judge.ts +226 -0
  20. package/dist/mod/hooks/ladder.js +179 -0
  21. package/dist/mod/hooks/ladder.ts +174 -0
  22. package/dist/mod/hooks/modes.ts +93 -0
  23. package/dist/mod/hooks/register.tsx +1458 -256
  24. package/dist/mod/hooks/session.ts +62 -0
  25. package/dist/mod/hooks/settings.js +342 -0
  26. package/dist/mod/hooks/settings.ts +321 -0
  27. package/dist/mod/hooks/stats.ts +59 -0
  28. package/dist/mod/hooks/ui/agents.tsx +138 -0
  29. package/dist/mod/hooks/ui/alerts.tsx +370 -0
  30. package/dist/mod/hooks/ui/art.ts +628 -0
  31. package/dist/mod/hooks/ui/band.tsx +836 -0
  32. package/dist/mod/hooks/ui/kit.tsx +258 -0
  33. package/dist/mod/hooks/ui/log.tsx +109 -0
  34. package/dist/mod/hooks/ui/parts.tsx +217 -0
  35. package/dist/mod/hooks/ui/ripple.ts +614 -0
  36. package/dist/mod/hooks/ui/settings.tsx +376 -0
  37. package/dist/mod/hooks/ui/setup.tsx +148 -0
  38. package/dist/mod/hooks/ui/theme.ts +217 -0
  39. package/dist/mod/hooks/ui/view.ts +371 -0
  40. package/dist/mod/hooks/worker.ts +1 -1
  41. package/dist/mod/hooks/workers.ts +217 -0
  42. package/dist/mod/tests/register.test.tsx +1730 -0
  43. package/dist/mod/tests/ui.test.tsx +853 -0
  44. package/dist/mod/tsconfig.json +2 -1
  45. package/dist/mod/types/index.d.ts +320 -1
  46. package/dist/router/src/admin.js +195 -53
  47. package/dist/router/src/agents.js +24 -5
  48. package/dist/router/src/bootstrap.js +4 -2
  49. package/dist/router/src/compat.js +10 -2
  50. package/dist/router/src/config.js +3 -0
  51. package/dist/router/src/health.js +33 -0
  52. package/dist/router/src/index.js +1 -0
  53. package/dist/router/src/ingress/models.js +4 -1
  54. package/dist/router/src/mod-settings.js +119 -0
  55. package/dist/router/src/model-policy.js +6 -0
  56. package/dist/router/src/model-scores.js +159 -0
  57. package/dist/router/src/picker.js +7 -0
  58. package/dist/router/src/providers/anthropic-account-pool.js +10 -4
  59. package/dist/router/src/providers/anthropic-accounts.js +26 -1
  60. package/dist/router/src/providers/chatgpt/catalog.js +45 -14
  61. package/dist/router/src/providers/chatgpt/index.js +9 -3
  62. package/dist/router/src/providers/chatgpt/translate.js +9 -5
  63. package/dist/router/src/providers/google/index.js +6 -2
  64. package/dist/router/src/providers/openai/index.js +4 -1
  65. package/dist/router/src/providers/openai/translate.js +1 -1
  66. package/dist/router/src/proxy.js +14 -3
  67. package/dist/router/src/version.js +1 -1
  68. package/dist/ui/app.js +932 -209
  69. package/dist/ui/favicon-32.png +0 -0
  70. package/dist/ui/favicon.svg +12 -0
  71. package/dist/ui/i18n.js +923 -237
  72. package/dist/ui/index.html +159 -87
  73. package/dist/ui/style.css +789 -408
  74. package/docs/ARCHITECTURE.md +134 -2
  75. package/package.json +7 -3
package/dist/ui/style.css CHANGED
@@ -1,481 +1,862 @@
1
+ /* ClaudeRipple settings — the System Settings idiom: a sidebar, inset grouped lists (label left,
2
+ control right), sheets for add/edit, one tint used like a system accent. Colours are tokens so
3
+ light and dark are both first-class; status colours are for status only. */
4
+
1
5
  :root {
2
6
  color-scheme: light dark;
3
- --bg: #f6f6f7;
4
- --panel: #ffffff;
5
- --border: #dcdde0;
6
- --text: #1b1c1f;
7
- --muted: #4f545c;
7
+ --bg: #f5f5f7;
8
+ --sidebar: #ececf0;
9
+ --group: #ffffff;
10
+ --group-edge: rgba(0, 0, 0, 0.05);
11
+ --label: #1d1d1f;
12
+ --label-2: #6e6e73;
13
+ --label-3: #8e8e93;
14
+ --separator: rgba(60, 60, 67, 0.13);
15
+ --fill: rgba(120, 120, 128, 0.12);
16
+ --fill-hover: rgba(120, 120, 128, 0.2);
17
+ --fill-press: rgba(120, 120, 128, 0.28);
18
+ --nav-selected: rgba(0, 0, 0, 0.075);
19
+ --field: #ffffff;
20
+ --field-edge: rgba(60, 60, 67, 0.2);
21
+ --segment-on: #ffffff;
8
22
  --accent: #6a56d6;
9
- --accent-text: #ffffff;
10
- --ok: #1a8a4a;
11
- --warn: #b5820a;
12
- --bad: #c73434;
13
- --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
14
- --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
23
+ --accent-fill: #6a56d6;
24
+ --accent-on: #ffffff;
25
+ --accent-soft: rgba(106, 86, 214, 0.1);
26
+ --focus: rgba(106, 86, 214, 0.42);
27
+ --ok: #1f8a3b;
28
+ --ok-dot: #34c759;
29
+ --warn: #b25000;
30
+ --warn-dot: #ff9500;
31
+ --bad: #d70015;
32
+ --bad-dot: #ff3b30;
33
+ --sheet-shadow: 0 24px 64px rgba(0, 0, 0, 0.2), 0 0 0 0.5px rgba(0, 0, 0, 0.1);
34
+ --hud: rgba(255, 255, 255, 0.86);
35
+ --chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='14' viewBox='0 0 10 14'%3E%3Cpath d='M2 5l3-3 3 3M2 9l3 3 3-3' fill='none' stroke='%236e6e73' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
36
+ --sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro", "Apple SD Gothic Neo", "Hiragino Sans", "PingFang SC", "Segoe UI", "Malgun Gothic", "Noto Sans KR", "Noto Sans", system-ui, sans-serif;
37
+ --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
38
+ --fs-body: 15px;
39
+ --fs-sub: 14px;
40
+ --fs-foot: 13px;
41
+ --fs-cap: 12px;
42
+ --radius: 12px;
15
43
  }
16
44
 
17
45
  @media (prefers-color-scheme: dark) {
18
46
  :root {
19
- --bg: #17181a;
20
- --panel: #1f2023;
21
- --border: #33353a;
22
- --text: #e8e9eb;
23
- --muted: #b3b7bf;
24
- --accent: #9f8cf2;
25
- --accent-text: #0c1220;
26
- --ok: #3ecb7d;
27
- --warn: #e0b436;
28
- --bad: #ef5a5a;
47
+ --bg: #1c1c1e;
48
+ --sidebar: #252528;
49
+ --group: #2c2c2e;
50
+ --group-edge: rgba(255, 255, 255, 0.05);
51
+ --label: #f5f5f7;
52
+ --label-2: #a1a1a6;
53
+ --label-3: #7c7c82;
54
+ --separator: rgba(255, 255, 255, 0.1);
55
+ --fill: rgba(120, 120, 128, 0.24);
56
+ --fill-hover: rgba(120, 120, 128, 0.32);
57
+ --fill-press: rgba(120, 120, 128, 0.4);
58
+ --nav-selected: rgba(255, 255, 255, 0.09);
59
+ --field: rgba(255, 255, 255, 0.05);
60
+ --field-edge: rgba(255, 255, 255, 0.13);
61
+ --segment-on: #636366;
62
+ --accent: #a898ff;
63
+ --accent-fill: #6f5ce0;
64
+ --accent-on: #ffffff;
65
+ --accent-soft: rgba(168, 152, 255, 0.16);
66
+ --focus: rgba(168, 152, 255, 0.5);
67
+ --ok: #30d158;
68
+ --ok-dot: #30d158;
69
+ --warn: #ff9f0a;
70
+ --warn-dot: #ff9f0a;
71
+ --bad: #ff6961;
72
+ --bad-dot: #ff453a;
73
+ --sheet-shadow: 0 24px 64px rgba(0, 0, 0, 0.5), 0 0 0 0.5px rgba(255, 255, 255, 0.12);
74
+ --hud: rgba(44, 44, 46, 0.88);
75
+ --chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='14' viewBox='0 0 10 14'%3E%3Cpath d='M2 5l3-3 3 3M2 9l3 3 3-3' fill='none' stroke='%23a1a1a6' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
29
76
  }
30
77
  }
31
78
 
32
79
  * { box-sizing: border-box; }
80
+ [hidden] { display: none !important; }
33
81
 
82
+ html { -webkit-text-size-adjust: 100%; }
34
83
  body {
35
84
  margin: 0;
36
85
  background: var(--bg);
37
- color: var(--text);
86
+ color: var(--label);
38
87
  font-family: var(--sans);
39
- font-size: 16px;
40
- display: flex;
41
- min-height: 100vh;
88
+ font-size: var(--fs-body);
89
+ line-height: 1.42;
90
+ -webkit-font-smoothing: antialiased;
91
+ -moz-osx-font-smoothing: grayscale;
92
+ text-rendering: optimizeLegibility;
42
93
  }
43
94
 
44
- nav {
45
- width: 168px;
46
- flex: 0 0 auto;
47
- border-right: 1px solid var(--border);
48
- padding: 14px 10px;
95
+ :focus { outline: none; }
96
+ :focus-visible { outline: 3px solid var(--focus); outline-offset: 1px; border-radius: 6px; }
97
+
98
+ /* ---- Window layout ------------------------------------------------------------------- */
99
+
100
+ .app { display: grid; grid-template-columns: 228px minmax(0, 1fr); min-height: 100vh; }
101
+
102
+ .sidebar {
103
+ position: sticky;
104
+ top: 0;
105
+ height: 100vh;
49
106
  display: flex;
50
107
  flex-direction: column;
51
- gap: 2px;
52
- }
53
-
54
- nav .brand {
55
- font-weight: 600;
56
- font-size: 18px;
57
- padding: 4px 8px 14px;
58
- letter-spacing: 0.2px;
108
+ padding: 18px 10px 14px;
109
+ background: var(--sidebar);
110
+ border-right: 1px solid var(--separator);
111
+ z-index: 5;
59
112
  }
60
-
61
- nav button {
62
- all: unset;
113
+ .nav-list { display: flex; flex-direction: column; gap: 1px; }
114
+ .nav-btn {
115
+ appearance: none;
116
+ display: flex;
117
+ align-items: center;
118
+ gap: 10px;
119
+ width: 100%;
120
+ min-height: 32px;
121
+ padding: 5px 10px;
122
+ border: 0;
123
+ border-radius: 7px;
124
+ background: transparent;
125
+ color: var(--label);
126
+ font: inherit;
127
+ font-size: var(--fs-sub);
128
+ text-align: left;
63
129
  cursor: pointer;
64
- padding: 7px 8px;
65
- border-radius: 6px;
66
- color: var(--muted);
67
- font-size: 15px;
68
130
  }
131
+ .nav-btn:hover { background: var(--fill); }
132
+ .nav-btn.active { background: var(--nav-selected); font-weight: 600; }
133
+ .nav-icon { width: 18px; height: 18px; flex: 0 0 auto; color: var(--accent); }
134
+ .nav-btn span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
135
+ .sidebar-footer { margin-top: auto; padding: 0 2px; }
136
+ /* A label too long for the phone tab bar carries a short form for it. */
137
+ .nav-btn .nav-short { display: none; }
138
+ .sidebar-footer select { width: 100%; }
69
139
 
70
- nav button:hover { background: var(--border); color: var(--text); }
71
- nav button.active { background: var(--accent); color: var(--accent-text); font-weight: 600; }
140
+ main { min-width: 0; padding: 36px 40px 72px; }
72
141
 
73
- nav .nav-spacer { flex: 1 1 auto; }
142
+ /* The measure belongs to the views, not to `main`: a view that wanted more than `main` allowed got
143
+ it and turned the whole page into a horizontal scroll instead (2026-09-22). */
144
+ .view { display: none; max-width: 720px; margin: 0 auto; }
145
+ .view.active { display: block; }
146
+ .providers-view, #view-logs { max-width: 1120px; }
74
147
 
75
- .lang-toggle {
76
- cursor: pointer;
77
- padding: 6px 8px;
78
- border-radius: 6px;
79
- color: var(--muted);
80
- background: transparent;
81
- font: inherit;
82
- font-size: 14px;
83
- border: 1px solid var(--border);
84
- text-align: center;
85
- }
86
- .lang-toggle:hover { background: var(--border); color: var(--text); }
148
+ /* ---- Type ---------------------------------------------------------------------------- */
87
149
 
88
- /* The measure belongs to the views, not to `main`: a view that wanted more than `main` allowed got
89
- it and turned the whole page into a horizontal scroll instead (2026-09-22). `min-width: 0` so a
90
- flex item may shrink below its content rather than pushing the nav off screen. */
91
- main {
92
- flex: 1 1 auto;
93
- min-width: 0;
94
- padding: 22px 28px;
95
- overflow-x: auto;
96
- }
150
+ h1, h2, h3 { margin: 0; letter-spacing: -0.01em; }
151
+ .page-header { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; margin-bottom: 26px; }
152
+ .page-header > div { min-width: 0; }
153
+ .page-header h1 { font-size: 26px; font-weight: 700; line-height: 1.2; letter-spacing: -0.02em; }
154
+ .page-subtitle { margin: 6px 0 0; color: var(--label-2); font-size: var(--fs-sub); line-height: 1.45; max-width: 620px; }
155
+ .page-actions { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; }
97
156
 
98
- h1 {
99
- font-size: 22px;
100
- margin: 0 0 4px;
157
+ .small, small { font-size: var(--fs-foot); color: var(--label-2); }
158
+ .hint { margin: 0; color: var(--label-2); font-size: var(--fs-foot); line-height: 1.45; }
159
+ .ok-text { color: var(--ok); }
160
+ .warn-text { color: var(--warn); }
161
+ .bad-text { color: var(--bad); }
162
+ code, .mono { font-family: var(--mono); font-size: 0.92em; }
163
+
164
+ /* ---- Inset grouped lists ------------------------------------------------------------- */
165
+
166
+ .section { margin-top: 28px; }
167
+ .section:first-child, .page-header + .section { margin-top: 0; }
168
+ .section-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; margin: 0 4px 8px; min-height: 22px; }
169
+ .section-title { font-size: var(--fs-sub); font-weight: 600; color: var(--label); }
170
+ .section-head .hint { margin-top: 2px; }
171
+
172
+ .group {
173
+ position: relative;
174
+ background: var(--group);
175
+ border-radius: var(--radius);
176
+ box-shadow: 0 0 0 1px var(--group-edge);
101
177
  }
178
+ .group-footer { margin: 8px 16px 0; color: var(--label-2); font-size: var(--fs-foot); line-height: 1.45; }
179
+ .group-footer + .group-footer { margin-top: 4px; }
180
+ .stacked-group { margin-top: 18px; }
102
181
 
103
- .subtitle {
104
- color: var(--muted);
105
- margin: 0 0 18px;
106
- font-size: 14px;
182
+ /* A row. `.row` is the older name the status and client screens build with; both read as a cell. */
183
+ .cell, .group > .row {
184
+ position: relative;
185
+ display: flex;
186
+ align-items: center;
187
+ gap: 14px;
188
+ min-height: 46px;
189
+ padding: 10px 16px;
107
190
  }
108
-
109
- .card {
110
- background: var(--panel);
111
- border: 1px solid var(--border);
112
- border-radius: 8px;
113
- padding: 14px 16px;
114
- margin-bottom: 14px;
191
+ .group > * + .cell::before,
192
+ .group > * + .row::before,
193
+ .group > * + .cell-note::before {
194
+ content: "";
195
+ position: absolute;
196
+ top: 0;
197
+ left: 16px;
198
+ right: 0;
199
+ height: 1px;
200
+ background: var(--separator);
115
201
  }
116
-
117
- /* Three levels, and they have to differ by more than weight: a card title, the sections inside it,
118
- and the prose. Titles were --muted while body text is --text, so a heading came out fainter than
119
- the paragraph under it and the sections ran together. */
120
- .card h2 {
121
- font-size: 16px;
122
- letter-spacing: 0;
123
- color: var(--text);
124
- margin: 0 0 10px;
125
- font-weight: 650;
202
+ .cell-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
203
+ .cell-label { font-size: var(--fs-body); color: var(--label); overflow-wrap: anywhere; }
204
+ .cell-label.strong { font-weight: 600; }
205
+ .cell-sub { font-size: var(--fs-foot); color: var(--label-2); line-height: 1.4; overflow-wrap: anywhere; }
206
+ .cell-value { flex: 0 1 auto; min-width: 0; display: flex; align-items: center; justify-content: flex-end; gap: 10px; color: var(--label-2); text-align: right; }
207
+ .cell-value select { max-width: 260px; }
208
+ .cell-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
209
+ .cell-note { position: relative; padding: 12px 16px; color: var(--label-2); font-size: var(--fs-foot); line-height: 1.45; }
210
+ .group > .hint { position: relative; padding: 10px 16px 12px; }
211
+ .group > * + .hint::before { content: ""; position: absolute; top: 0; left: 16px; right: 0; height: 1px; background: var(--separator); }
212
+
213
+ .cell.link { cursor: pointer; }
214
+ .cell.link:hover { background: var(--fill); }
215
+ .group > .cell:first-child { border-top-left-radius: inherit; border-top-right-radius: inherit; }
216
+ .group > .cell:last-child { border-bottom-left-radius: inherit; border-bottom-right-radius: inherit; }
217
+ .chevron { width: 8px; height: 13px; flex: 0 0 auto; color: var(--label-3); }
218
+
219
+ .row .k { flex: 1 1 auto; min-width: 0; color: var(--label); }
220
+ .row .v { color: var(--label-2); font-variant-numeric: tabular-nums; text-align: right; overflow-wrap: anywhere; }
221
+ .row > .small { color: var(--label-2); }
222
+
223
+ .cell-button {
224
+ appearance: none;
225
+ display: flex;
226
+ align-items: center;
227
+ width: 100%;
228
+ min-height: 44px;
229
+ padding: 10px 16px;
230
+ border: 0;
231
+ border-radius: var(--radius);
232
+ background: transparent;
233
+ color: var(--accent);
234
+ font: inherit;
235
+ text-align: left;
236
+ cursor: pointer;
126
237
  }
238
+ .cell-button:hover { background: var(--fill); }
239
+ .cell-button.destructive { color: var(--bad); }
240
+ .cell-button:disabled { opacity: 0.45; cursor: default; }
127
241
 
128
- .rows { display: flex; flex-direction: column; gap: 7px; }
129
- .row { display: flex; justify-content: space-between; gap: 12px; align-items: center; }
130
- .row .k { color: var(--muted); }
131
- .row .v { font-family: var(--mono); font-size: 15px; text-align: right; }
242
+ /* ---- Status -------------------------------------------------------------------------- */
132
243
 
133
- .badge {
134
- display: inline-flex;
135
- align-items: center;
136
- gap: 5px;
137
- padding: 2px 8px;
138
- border-radius: 100px;
139
- font-size: 14px;
140
- font-weight: 600;
141
- }
142
- .badge.ok { background: color-mix(in srgb, var(--ok) 18%, transparent); color: var(--ok); }
143
- .badge.bad { background: color-mix(in srgb, var(--bad) 18%, transparent); color: var(--bad); }
144
- .badge.warn { background: color-mix(in srgb, var(--warn) 18%, transparent); color: var(--warn); }
145
- .badge.dot::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
244
+ /* A dot carries the colour; the words stay a quiet secondary label. */
245
+ .badge { display: inline-flex; align-items: center; gap: 6px; color: var(--label-2); font-size: var(--fs-sub); white-space: nowrap; }
246
+ .badge.ok::before, .badge.warn::before, .badge.bad::before { content: ""; width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }
247
+ .badge.ok::before { background: var(--ok-dot); }
248
+ .badge.warn::before { background: var(--warn-dot); }
249
+ .badge.bad::before { background: var(--bad-dot); }
250
+ .badge.bad { color: var(--bad); }
251
+ .badge.first { color: var(--accent); font-weight: 600; font-size: var(--fs-foot); }
146
252
 
147
- table { width: 100%; border-collapse: collapse; }
148
- th { text-align: left; font-size: 14px; letter-spacing: 0.2px; color: var(--muted); font-weight: 600; padding: 6px 8px; border-bottom: 1px solid var(--border); }
149
- td { padding: 6px 8px; border-bottom: 1px solid var(--border); vertical-align: middle; }
150
- tr:last-child td { border-bottom: none; }
253
+ /* ---- Controls ------------------------------------------------------------------------ */
151
254
 
255
+ input, select, textarea, button { font: inherit; }
152
256
  input, select, textarea {
153
- background: var(--bg);
154
- color: var(--text);
155
- border: 1px solid var(--border);
156
- border-radius: 5px;
157
- padding: 5px 7px;
158
- font-size: 15px;
159
- font-family: inherit;
160
257
  width: 100%;
258
+ min-height: 32px;
259
+ padding: 5px 10px;
260
+ color: var(--label);
261
+ background: var(--field);
262
+ border: 1px solid var(--field-edge);
263
+ border-radius: 8px;
264
+ font-size: var(--fs-sub);
265
+ }
266
+ input::placeholder, textarea::placeholder { color: var(--label-3); }
267
+ input:focus, select:focus, textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--focus); }
268
+ textarea { resize: vertical; line-height: 1.45; padding: 7px 10px; }
269
+ select {
270
+ appearance: none;
271
+ -webkit-appearance: none;
272
+ padding-right: 28px;
273
+ background-image: var(--chevron);
274
+ background-repeat: no-repeat;
275
+ background-position: right 9px center;
276
+ background-size: 10px 14px;
277
+ text-overflow: ellipsis;
278
+ cursor: pointer;
161
279
  }
162
- input:focus, select:focus, textarea:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
280
+ select:disabled { opacity: 0.5; cursor: default; }
281
+ input[type="checkbox"], input[type="radio"] { width: auto; min-height: 0; padding: 0; accent-color: var(--accent-fill); }
282
+ input[type="checkbox"] { width: 16px; height: 16px; margin: 0; flex: 0 0 auto; }
283
+ input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; }
163
284
 
164
- button.btn {
165
- all: unset;
285
+ .btn {
286
+ appearance: none;
287
+ display: inline-flex;
288
+ align-items: center;
289
+ justify-content: center;
290
+ gap: 6px;
291
+ min-height: 30px;
292
+ padding: 4px 14px;
293
+ border: 0;
294
+ border-radius: 8px;
295
+ background: var(--accent-fill);
296
+ color: var(--accent-on);
297
+ font-size: var(--fs-sub);
298
+ font-weight: 500;
299
+ line-height: 1.2;
300
+ text-decoration: none;
301
+ white-space: nowrap;
166
302
  cursor: pointer;
167
- background: var(--accent);
168
- color: var(--accent-text);
169
- padding: 6px 14px;
170
- border-radius: 6px;
171
- font-size: 15px;
172
- font-weight: 600;
303
+ transition: background-color 0.12s ease, filter 0.12s ease;
173
304
  }
174
- button.btn:hover { filter: brightness(1.06); }
175
- button.btn.secondary { background: transparent; color: var(--text); border: 1px solid var(--border); }
176
- button.btn.danger { background: transparent; color: var(--bad); border: 1px solid var(--bad); }
177
- button.btn:disabled { opacity: 0.5; cursor: default; }
305
+ .btn:hover { filter: brightness(1.07); }
306
+ .btn:active { filter: brightness(0.95); }
307
+ .btn.secondary { background: var(--fill); color: var(--label); }
308
+ .btn.secondary:hover { background: var(--fill-hover); filter: none; }
309
+ .btn.secondary:active { background: var(--fill-press); }
310
+ .btn.danger { background: var(--fill); color: var(--bad); }
311
+ .btn.danger:hover { background: var(--fill-hover); filter: none; }
312
+ .btn.compact { min-height: 26px; padding: 3px 10px; font-size: var(--fs-foot); border-radius: 7px; }
313
+ .btn:disabled { opacity: 0.45; cursor: default; filter: none; }
178
314
 
179
315
  .icon-btn {
180
- all: unset;
316
+ appearance: none;
317
+ display: inline-grid;
318
+ place-items: center;
319
+ width: 30px;
320
+ height: 30px;
321
+ flex: 0 0 auto;
322
+ padding: 0;
323
+ border: 0;
324
+ border-radius: 8px;
325
+ background: transparent;
326
+ color: var(--label-2);
327
+ cursor: pointer;
328
+ }
329
+ .icon-btn:hover { background: var(--fill); color: var(--label); }
330
+ .icon-btn:disabled { opacity: 0.45; cursor: default; }
331
+ .icon-btn svg { width: 16px; height: 16px; }
332
+ .icon-btn.remove { color: var(--label-3); }
333
+ .icon-btn.remove:hover { color: var(--bad); }
334
+
335
+ .switch { position: relative; flex: 0 0 auto; width: 40px; height: 24px; cursor: pointer; }
336
+ .switch input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
337
+ .switch span { display: block; height: 100%; border-radius: 99px; background: var(--fill-press); transition: background-color 0.18s ease; pointer-events: none; }
338
+ .switch span::after { content: ""; position: absolute; top: 2px; left: 2px; width: 20px; height: 20px; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), 0 0 0 0.5px rgba(0, 0, 0, 0.04); transition: transform 0.18s ease; }
339
+ .switch input:checked + span { background: var(--accent-fill); }
340
+ .switch input:checked + span::after { transform: translateX(16px); }
341
+ .switch input:focus-visible + span { box-shadow: 0 0 0 3px var(--focus); }
342
+ .switch input:disabled + span { opacity: 0.5; }
343
+
344
+ /* Segmented control: logs panels and the provider detail tabs. */
345
+ .segmented, .logs-tabs, .provider-tabs {
346
+ display: inline-flex;
347
+ gap: 2px;
348
+ max-width: 100%;
349
+ padding: 2px;
350
+ border-radius: 9px;
351
+ background: var(--fill);
352
+ overflow-x: auto;
353
+ scrollbar-width: none;
354
+ }
355
+ .segmented button, .logs-tab, .provider-tabs button {
356
+ appearance: none;
357
+ min-height: 26px;
358
+ padding: 3px 16px;
359
+ border: 0;
360
+ border-radius: 7px;
361
+ background: transparent;
362
+ color: var(--label);
363
+ font-size: var(--fs-foot);
364
+ font-weight: 500;
365
+ white-space: nowrap;
181
366
  cursor: pointer;
182
- color: var(--muted);
183
- padding: 3px 7px;
184
- border-radius: 5px;
185
- font-size: 15px;
186
367
  }
187
- .icon-btn:hover { background: var(--border); color: var(--bad); }
368
+ .segmented button.active, .logs-tab.active, .provider-tabs button.active { background: var(--segment-on); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 0 0 0.5px rgba(0, 0, 0, 0.04); font-weight: 600; }
188
369
 
189
- .toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; gap: 10px; flex-wrap: wrap; }
190
- .toolbar .right { display: flex; gap: 8px; align-items: center; }
370
+ label.check { display: inline-flex; align-items: flex-start; gap: 9px; color: var(--label); font-size: var(--fs-sub); cursor: pointer; line-height: 1.4; }
371
+ label.check input { margin-top: 2px; }
372
+
373
+ .details { margin-top: 18px; }
374
+ .details > summary { display: inline-flex; align-items: center; gap: 6px; color: var(--label-2); font-size: var(--fs-sub); cursor: pointer; user-select: none; list-style: none; }
375
+ .details > summary::-webkit-details-marker { display: none; }
376
+ .details > summary::before { content: ""; width: 6px; height: 6px; border-right: 1.6px solid currentColor; border-bottom: 1.6px solid currentColor; transform: rotate(-45deg); transition: transform 0.15s ease; margin-right: 2px; }
377
+ .details[open] > summary::before { transform: rotate(45deg); }
378
+ .advanced-content { display: grid; gap: 4px; margin-top: 4px; }
379
+
380
+ /* ---- Toast (a HUD at the bottom) ------------------------------------------------------ */
191
381
 
192
382
  .toast {
193
383
  position: fixed;
194
- bottom: 20px;
195
- right: 20px;
196
- background: var(--panel);
197
- border: 1px solid var(--border);
198
- border-left: 3px solid var(--ok);
199
- padding: 10px 16px;
200
- border-radius: 6px;
201
- font-size: 15px;
202
- box-shadow: 0 6px 20px rgba(0,0,0,0.15);
384
+ left: 50%;
385
+ bottom: 24px;
386
+ z-index: 40;
387
+ max-width: min(440px, calc(100vw - 32px));
388
+ padding: 11px 16px;
389
+ border-radius: 12px;
390
+ background: var(--hud);
391
+ -webkit-backdrop-filter: saturate(180%) blur(20px);
392
+ backdrop-filter: saturate(180%) blur(20px);
393
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.16), 0 0 0 0.5px var(--separator);
394
+ color: var(--label);
395
+ font-size: var(--fs-sub);
203
396
  opacity: 0;
204
- transform: translateY(6px);
205
- transition: opacity 0.15s, transform 0.15s;
397
+ transform: translate(-50%, 8px);
398
+ transition: opacity 0.18s ease, transform 0.18s ease;
206
399
  pointer-events: none;
207
- max-width: 360px;
208
400
  }
209
- .toast.show { opacity: 1; transform: translateY(0); }
210
- .toast.error { border-left-color: var(--bad); }
401
+ .toast.show { opacity: 1; transform: translate(-50%, 0); }
402
+ .toast.error > div:first-child { color: var(--bad); font-weight: 500; }
403
+ .toast-raw { margin-top: 4px; color: var(--label-2); font: 12px/1.4 var(--mono); word-break: break-word; }
211
404
 
212
- pre#logbox {
213
- background: var(--panel);
214
- border: 1px solid var(--border);
405
+ /* ---- Status view --------------------------------------------------------------------- */
406
+
407
+ .provider-status .cell-text strong { font-weight: 500; }
408
+ .provider-status .account-usage { display: grid; gap: 1px; }
409
+
410
+ /* ---- Model mapping ------------------------------------------------------------------- */
411
+
412
+ .map-head, .map-row {
413
+ display: grid;
414
+ grid-template-columns: minmax(0, 1fr) 18px minmax(0, 1.25fr) 112px 30px;
415
+ gap: 10px;
416
+ align-items: center;
417
+ }
418
+ .map-head { padding: 0 16px 8px; color: var(--label-2); font-size: var(--fs-foot); font-weight: 500; }
419
+ .map-row { position: relative; padding: 9px 12px 9px 16px; }
420
+ .map-list > .map-row + .map-row::before { content: ""; position: absolute; top: 0; left: 16px; right: 0; height: 1px; background: var(--separator); }
421
+ .map-arrow { color: var(--label-3); text-align: center; }
422
+ .map-label { display: none; }
423
+ .map-col { min-width: 0; }
424
+ .map-col select { width: 100%; }
425
+ .map-empty { padding: 14px 16px; color: var(--label-2); font-size: var(--fs-sub); }
426
+ .slots-status { color: var(--label-2); font-size: var(--fs-foot); }
427
+ .muted-cell { position: relative; }
428
+ .muted-cell::after { content: attr(data-empty); position: absolute; inset: 0; display: grid; place-items: center; color: var(--label-3); pointer-events: none; }
429
+ .muted-cell select:disabled { opacity: 0; }
430
+
431
+ /* ---- Clients ------------------------------------------------------------------------- */
432
+
433
+ .slot-row .cell-value select { width: 240px; }
434
+ /* The picker tile carries a name, a provider and a context window. On one flex line the name is
435
+ what collapses — it was rendering as "GPT…" — so name owns the first row and the rest share the
436
+ second. */
437
+ .model-checklist { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 6px; padding: 10px; }
438
+ #client-picker-models { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
439
+ .model-check, .picker-model { min-width: 0; padding: 8px 10px; border-radius: 8px; background: var(--fill); background: color-mix(in srgb, var(--fill) 60%, transparent); }
440
+ .model-check { display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: var(--fs-sub); }
441
+ .model-check:hover { background: var(--fill); }
442
+ .model-check span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
443
+ .picker-model { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 8px; row-gap: 2px; align-items: center; }
444
+ .picker-model > .pm-main { grid-column: 1 / span 2; display: flex; align-items: center; gap: 8px; min-width: 0; cursor: pointer; font-size: var(--fs-sub); }
445
+ .picker-model > .pm-main span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
446
+ .picker-model > small { grid-column: 1; grid-row: 2; padding-left: 24px; font-size: var(--fs-cap); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
447
+ /* Most models never need their own window, so an empty box on every tile is noise. It stays quiet
448
+ until it is touched or actually holds a value. */
449
+ .picker-model > .model-window { grid-column: 2; grid-row: 2; width: 9ch; min-height: 22px; padding: 1px 6px; font-size: var(--fs-cap); text-align: right; color: var(--label-2); background: transparent; border-color: transparent; }
450
+ .picker-model > .model-window::-webkit-inner-spin-button { display: none; }
451
+ .picker-model > .model-window:hover, .picker-model > .model-window:focus, .picker-model > .model-window:not(:placeholder-shown) { color: var(--label); background: var(--field); border-color: var(--field-edge); }
452
+
453
+ /* ---- Providers ----------------------------------------------------------------------- */
454
+
455
+ .provider-workspace { display: grid; grid-template-columns: 264px minmax(0, 1fr); gap: 28px; align-items: start; }
456
+ .provider-rail { position: sticky; top: 24px; min-width: 0; }
457
+ .provider-rail-tools { display: flex; gap: 6px; align-items: center; margin-bottom: 10px; }
458
+ .provider-rail-tools input { flex: 1 1 auto; min-width: 0; }
459
+ .provider-list { display: grid; gap: 1px; padding: 5px; }
460
+ .provider-rail-item {
461
+ appearance: none;
462
+ display: grid;
463
+ grid-template-columns: minmax(0, 1fr) auto;
464
+ align-items: center;
465
+ gap: 10px;
466
+ width: 100%;
467
+ min-width: 0;
468
+ padding: 8px 11px;
469
+ border: 0;
215
470
  border-radius: 8px;
216
- padding: 12px;
217
- font-family: var(--mono);
218
- font-size: 14px;
219
- line-height: 1.5;
220
- height: 60vh;
471
+ background: transparent;
472
+ color: var(--label);
473
+ text-align: left;
474
+ cursor: pointer;
475
+ }
476
+ .provider-rail-item:hover { background: var(--fill); }
477
+ .provider-rail-item.selected { background: var(--accent-soft); }
478
+ .provider-rail-copy { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
479
+ .provider-rail-copy strong { font-weight: 500; font-size: var(--fs-sub); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
480
+ .provider-rail-copy span { color: var(--label-2); font-size: var(--fs-foot); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
481
+ .provider-rail-item .badge { font-size: 0; gap: 0; }
482
+ .provider-rail-empty { padding: 20px 12px; color: var(--label-2); font-size: var(--fs-sub); text-align: center; }
483
+ .provider-back { display: none; }
484
+
485
+ .provider-detail { min-width: 0; }
486
+ .provider-detail-empty { display: grid; place-items: center; min-height: 320px; color: var(--label-2); }
487
+ .provider-detail-header { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; margin-bottom: 18px; }
488
+ .provider-detail-header > div:first-child { min-width: 0; }
489
+ .provider-title-line { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
490
+ .provider-title-line h2 { font-size: 22px; font-weight: 700; letter-spacing: -0.015em; overflow-wrap: anywhere; }
491
+ .provider-detail-header p { margin: 3px 0 0; }
492
+ .provider-detail-actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; flex: 0 0 auto; }
493
+ .provider-tabs { margin-bottom: 22px; }
494
+ .provider-panel { display: grid; gap: 26px; }
495
+ .provider-panel > .section { margin-top: 0; }
496
+ .provider-remove { margin-top: 30px; }
497
+
498
+ .account-card-list > div.small, .account-card-list > .bad-text { padding: 14px 16px; }
499
+ .stacked-add { margin-top: 10px; }
500
+ .model-effort-tag { flex: 0 0 auto; color: var(--label-2); font-size: var(--fs-cap); white-space: nowrap; }
501
+ .model-effort-tag.has-effort { color: var(--label-2); }
502
+ .model-effort-tag.no-effort { color: var(--label-3); }
503
+ .model-check .model-effort-tag { margin-left: auto; }
504
+
505
+ .account-card { align-items: flex-start; }
506
+ .account-card .cell-text strong { font-weight: 500; font-size: var(--fs-body); overflow-wrap: anywhere; }
507
+ .account-card .hint { margin-top: 2px; }
508
+ .account-card .account-quota { margin: 0; color: var(--label); font-variant-numeric: tabular-nums; }
509
+ .account-card-badges { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; flex: 0 0 auto; padding-top: 2px; }
510
+ .account-card-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
511
+ .account-empty { padding: 16px; color: var(--label-2); font-size: var(--fs-sub); }
512
+
513
+ .notice { display: flex; flex-direction: column; gap: 3px; font-size: var(--fs-foot); line-height: 1.45; color: var(--label-2); }
514
+ .notice strong { color: var(--warn); font-weight: 600; }
515
+ .notice.warn { padding: 12px 14px; border-radius: 10px; background: color-mix(in srgb, var(--warn-dot) 10%, transparent); }
516
+
517
+ .empty-card { padding: 28px 16px; color: var(--label-2); text-align: center; font-size: var(--fs-sub); }
518
+
519
+ /* ---- Logs ---------------------------------------------------------------------------- */
520
+
521
+ .logs-header-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin: 0 4px 8px; }
522
+ .logs-header-row .section-title { display: inline; }
523
+ .logs-header-row .small { margin-left: 8px; }
524
+ .logs-summary { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); }
525
+ .logs-chip { position: relative; min-width: 0; padding: 14px 16px; }
526
+ .logs-chip + .logs-chip::before { content: ""; position: absolute; left: 0; top: 14px; bottom: 14px; width: 1px; background: var(--separator); }
527
+ .logs-chip .label { display: block; color: var(--label-2); font-size: var(--fs-foot); margin-bottom: 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
528
+ .logs-chip .value { display: block; font-size: 20px; font-weight: 600; letter-spacing: -0.01em; font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
529
+ .logs-chip .sub { display: block; margin-top: 1px; color: var(--label-2); font-size: var(--fs-cap); white-space: nowrap; }
530
+ .logs-filter { display: inline-flex; align-items: center; gap: 8px; color: var(--label-2); font-size: var(--fs-foot); }
531
+ .logs-filter select { width: auto; min-width: 130px; min-height: 28px; }
532
+ .logs-table-card { overflow-x: auto; }
533
+ .logs-table-card table { min-width: 860px; }
534
+
535
+ table { width: 100%; border-collapse: collapse; font-size: var(--fs-sub); }
536
+ th { padding: 10px 12px 8px; color: var(--label-2); font-size: var(--fs-foot); font-weight: 500; text-align: left; white-space: nowrap; border-bottom: 1px solid var(--separator); }
537
+ th:first-child, td:first-child { padding-left: 16px; }
538
+ td { padding: 9px 12px; vertical-align: middle; border-bottom: 1px solid var(--separator); font-variant-numeric: tabular-nums; }
539
+ tr:last-child td { border-bottom: 0; }
540
+ #requests-table tbody tr.request-row { cursor: pointer; }
541
+ #requests-table tbody tr.request-row:hover td { background: var(--fill); background: color-mix(in srgb, var(--fill) 55%, transparent); }
542
+ #requests-table td.c-time { color: var(--label-2); white-space: nowrap; }
543
+ .request-models { display: flex; align-items: center; gap: 6px; min-width: 0; }
544
+ .request-models .model { max-width: 240px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; }
545
+ .request-models .small { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
546
+ /* The provider is read as a name, not a colour: one quiet label for every provider. */
547
+ .provider-badge { flex: 0 0 auto; padding: 1px 7px; border-radius: 5px; background: var(--fill); color: var(--label-2); font-size: var(--fs-cap); font-weight: 500; white-space: nowrap; }
548
+ .token-cell { white-space: nowrap; }
549
+ .cache-pill { margin-left: 6px; color: var(--label-2); font-size: var(--fs-cap); }
550
+ .no-usage { color: var(--label-3); }
551
+ .status-text { font-weight: 500; white-space: nowrap; }
552
+ .status-text.ok { color: var(--ok); }
553
+ .status-text.bad { color: var(--bad); }
554
+ .status-text.resent { color: var(--label-2); font-weight: 400; }
555
+ .request-detail td { padding: 0; background: var(--fill); background: color-mix(in srgb, var(--fill) 45%, transparent); }
556
+ .request-detail-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px 16px; padding: 14px 16px; }
557
+ .request-detail-grid .detail-label { display: block; margin-bottom: 2px; color: var(--label-2); font-size: var(--fs-cap); }
558
+ .request-detail-grid .detail-value { font-family: var(--mono); font-size: var(--fs-foot); word-break: break-word; }
559
+ .request-note { grid-column: 1 / -1; }
560
+
561
+ .raw-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin: 0 4px 8px; }
562
+ pre#logbox {
563
+ margin: 0;
564
+ height: 62vh;
565
+ padding: 14px 16px;
221
566
  overflow: auto;
567
+ font: 12.5px/1.6 var(--mono);
222
568
  white-space: pre-wrap;
223
569
  word-break: break-all;
224
570
  }
225
- .tag-PASS { color: var(--muted); }
571
+ .tag-PASS { color: var(--label-2); }
226
572
  .tag-ERR { color: var(--bad); }
227
573
  .tag-WARN { color: var(--warn); }
228
- .tag-PROVIDER { color: var(--accent); }
229
574
 
230
- label.check { display: inline-flex; align-items: center; gap: 7px; color: var(--text); font-size: 15px; width: auto; cursor: pointer; }
231
- label.check input { width: auto; }
232
- .hint { color: var(--muted); font-size: 14px; line-height: 1.45; margin: 4px 0 0; }
233
- .small { font-size: 14px; color: var(--muted); }
234
-
235
- .view { display: none; max-width: 980px; }
236
- .view.active { display: block; }
237
- /* Two views are laid out in columns and hold wide tables, so the window is room they can use. A
238
- paragraph only gets harder to read that way, which is why the rest keep a measure. */
239
- .providers-view, #view-logs { max-width: none; }
240
- .actions { display: flex; align-items: center; gap: 8px; margin-top: 12px; }
575
+ /* ---- ClaudeRipple mod ---------------------------------------------------------------- */
576
+
577
+ /* One long page of cards, each card a heading over the usual grouped lists. */
578
+ a.cell { color: inherit; text-decoration: none; }
579
+ #mod-notice:not([hidden]) + #mod-content { margin-top: 28px; }
580
+ .mod-card + .mod-card { margin-top: 48px; }
581
+ .mod-card-head { margin: 0 4px 12px; }
582
+ .mod-card-head h2 { font-size: 20px; font-weight: 700; letter-spacing: -0.015em; line-height: 1.25; }
583
+ .mod-card-head .hint { margin-top: 3px; font-size: var(--fs-sub); }
584
+ .mod-card-head + .section, .mod-card-head + .group { margin-top: 0; }
585
+ .group-footer.warn-text, .cell-note.warn-text, .cell-sub.warn-text { color: var(--warn); }
586
+ .mod-card .cell-value select { width: 320px; max-width: 100%; }
587
+ .mod-card .cell-value select.narrow { width: 160px; }
588
+ .text-field { width: 320px; }
589
+
590
+ /* The models to use: one block per provider, its billing beside the name, a checkbox for each model. */
591
+ .tick-group + .tick-group { border-top: 1px solid var(--separator); }
592
+ .tick-head { padding: 12px 16px 0; color: var(--label-2); font-size: var(--fs-foot); font-weight: 500; }
593
+ .tick-group .model-check .model-effort-tag { margin-left: auto; }
594
+
595
+ /* The four workers: a name and its meaning on the left; one menu for the model and effort on the right. */
596
+ .rung-row { align-items: flex-start; }
597
+ .rung-row > .cell-text { padding-top: 5px; }
598
+ .mod-card .cell-value select.rung-model { width: 300px; }
599
+ /* Saved worker sets: tabs browse, the separate control activates. Numbering is dispatch order. */
600
+ .mode-on { display: inline-block; margin-left: 8px; padding: 1px 7px; border-radius: 999px; background: var(--accent-soft); color: var(--accent); font-size: var(--fs-cap); font-weight: 600; vertical-align: 1px; }
601
+ .mode-tabs { display: flex; flex-wrap: wrap; gap: 6px; margin: 18px 0 12px; }
602
+ .mode-tabs button { padding: 8px 12px; border: 1px solid var(--separator); border-radius: 8px; background: var(--fill); color: var(--label-2); max-width: 100%; overflow-wrap: anywhere; }
603
+ .mode-tabs button.active { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
604
+ .rung-list { min-width: 0; width: 100%; text-align: left; }
605
+ .rung-list ol { margin: 0 0 8px; padding-left: 22px; }
606
+ .rung-item { padding: 3px 0 6px; }
607
+ .rung-item::marker { color: var(--label-2); font: var(--fs-cap) var(--mono); }
608
+ .rung-actions { display: inline-flex; gap: 4px; margin-left: 6px; vertical-align: middle; }
609
+ .rung-item small { display: block; }
610
+ .mod-card .rung-item select.rung-model { width: 260px; max-width: 100%; }
611
+ .mode-until { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; max-width: 100%; }
612
+ .mode-until .text-field { width: auto; max-width: 100%; }
613
+ .mode-until .segmented { display: flex; flex-wrap: wrap; gap: 4px; max-width: 100%; }
614
+ .mode-reset-reason { flex: 1 1 100%; color: var(--label-2); font-size: var(--fs-cap); padding: 4px 6px; text-align: left; }
615
+ .mode-until button:disabled { opacity: 0.45; cursor: not-allowed; }
616
+ .offer-row { background: var(--accent-soft); }
617
+ .offer-row .cell-label { font-size: var(--fs-sub); }
618
+
619
+ /* The lean is a slider with a tick for each of its five stops, like a System Settings slider. */
620
+ .lean { display: flex; align-items: center; gap: 10px; color: var(--label-2); font-size: var(--fs-foot); }
621
+ .lean-end { white-space: nowrap; }
622
+ .lean-track { position: relative; display: grid; width: 172px; padding-bottom: 2px; }
623
+ .lean-range { -webkit-appearance: none; appearance: none; width: 100%; height: 22px; min-height: 0; margin: 0; padding: 0; border: 0; background: transparent; cursor: pointer; }
624
+ .lean-range:focus { box-shadow: none; }
625
+ .lean-range::-webkit-slider-runnable-track { height: 4px; border-radius: 2px; background: var(--fill-press); }
626
+ .lean-range::-webkit-slider-thumb { -webkit-appearance: none; width: 20px; height: 20px; margin-top: -8px; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3), 0 0 0 0.5px rgba(0, 0, 0, 0.08); }
627
+ .lean-range:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 3px var(--focus), 0 1px 3px rgba(0, 0, 0, 0.3); }
628
+ .lean-range::-moz-range-track { height: 4px; border-radius: 2px; background: var(--fill-press); }
629
+ .lean-range::-moz-range-thumb { width: 20px; height: 20px; border: 0; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3), 0 0 0 0.5px rgba(0, 0, 0, 0.08); }
630
+ .lean-ticks { display: flex; justify-content: space-between; padding: 0 9px; pointer-events: none; }
631
+ .lean-ticks span { width: 2px; height: 5px; border-radius: 1px; background: var(--label-3); }
632
+
633
+ /* ---- About --------------------------------------------------------------------------- */
634
+
635
+ /* The language menu lives in the sidebar; on a phone the sidebar is a tab bar, so About carries it. */
636
+ .narrow-only { display: none; }
637
+ .about-text { padding: 14px 16px; font-size: var(--fs-sub); line-height: 1.5; }
638
+ .about-text p { margin: 0; }
639
+ .about-text p + p { margin-top: 6px; }
640
+
641
+ /* ---- Sheets -------------------------------------------------------------------------- */
642
+
643
+ .modal-backdrop {
644
+ position: fixed;
645
+ inset: 0;
646
+ z-index: 30;
647
+ display: grid;
648
+ place-items: center;
649
+ padding: 24px;
650
+ background: rgba(0, 0, 0, 0.28);
651
+ animation: fade-in 0.16s ease;
652
+ }
653
+ .modal {
654
+ position: relative;
655
+ width: min(620px, 100%);
656
+ max-height: min(820px, calc(100vh - 48px));
657
+ overflow: auto;
658
+ overscroll-behavior: contain;
659
+ background: var(--group);
660
+ border-radius: 14px;
661
+ box-shadow: var(--sheet-shadow);
662
+ animation: sheet-in 0.2s cubic-bezier(0.2, 0.8, 0.2, 1);
663
+ }
664
+ @media (prefers-color-scheme: dark) { .modal { background: #262628; } }
665
+ #modal-content { padding: 22px 24px 24px; }
666
+ .modal h1 { font-size: 19px; font-weight: 700; margin: 0 40px 6px 0; line-height: 1.3; }
667
+ .modal h1 + .hint { margin-bottom: 4px; font-size: var(--fs-sub); }
668
+ .modal-close { position: absolute; top: 16px; right: 16px; z-index: 2; width: 26px; height: 26px; border-radius: 50%; background: var(--fill); }
669
+ .modal-close svg { width: 10px; height: 10px; }
670
+ .actions { display: flex; align-items: center; gap: 8px; margin-top: 14px; }
241
671
  .actions.end { justify-content: flex-end; }
242
- .details { color: var(--muted); font-size: 14px; margin-top: 12px; }
243
- .advanced-content { margin-top: 10px; display: grid; gap: 12px; }
244
- .details summary { cursor: pointer; user-select: none; }
245
- .detail-rows { margin-top: 10px; padding: 12px 14px; border: 1px solid var(--border); border-radius: 7px; background: var(--panel); }
246
- .picker-setting { display: flex; align-items: center; justify-content: space-between; gap: 18px; }
247
- .switch { width: 40px; height: 23px; flex: 0 0 auto; position: relative; cursor: pointer; }
248
- .switch input { position: absolute; opacity: 0; pointer-events: none; }
249
- .switch span { display: block; height: 100%; border-radius: 99px; background: var(--border); transition: background .15s; }
250
- .switch span::after { content: ""; position: absolute; width: 17px; height: 17px; top: 3px; left: 3px; border-radius: 50%; background: var(--panel); box-shadow: 0 1px 3px rgba(0,0,0,.22); transition: transform .15s; }
251
- .switch input:checked + span { background: var(--accent); }
252
- .switch input:checked + span::after { transform: translateX(17px); }
253
- .table-card { padding: 0; overflow-x: auto; }
254
- .table-card table { min-width: 670px; }
255
- .icon-cell { width: 36px; text-align: right; }
256
- .muted-cell { color: var(--muted); position: relative; }
257
- .muted-cell::after { content: attr(data-empty); position: absolute; inset: 0 8px; display: grid; place-items: center; pointer-events: none; color: var(--muted); }
258
- .muted-cell select:disabled { opacity: 0; }
259
- .model-checklist { display: grid; grid-template-columns: repeat(auto-fill, minmax(185px, 1fr)); gap: 7px; margin-top: 9px; }
260
- .model-check { display: flex; gap: 7px; align-items: center; min-width: 0; padding: 8px 9px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); cursor: pointer; }
261
- .model-check input { width: auto; flex: 0 0 auto; }
262
- .model-check span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
263
- .model-check small { margin-left: auto; color: var(--muted); font-size: 13px; white-space: nowrap; }
264
- /* The picker tile carries a name, a provider and a context window. On one flex line the name is
265
- what collapses — it was rendering as "GPT…" — so name owns the first row and the rest share the
266
- second. The plain .model-check above stays one line for the checklists that only hold a name. */
267
- .picker-model { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 8px; row-gap: 3px; align-items: center; padding: 8px 9px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); }
268
- .picker-model > .pm-main { grid-column: 1 / span 2; display: flex; align-items: center; gap: 7px; min-width: 0; cursor: pointer; }
269
- .picker-model > .pm-main input { width: auto; flex: 0 0 auto; }
270
- .picker-model > .pm-main span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
271
- .picker-model > small { grid-column: 1; grid-row: 2; color: var(--muted); font-size: 13px; }
272
- /* Most models never need their own window, so an empty box on every tile is eight boxes of noise.
273
- It stays quiet until it is touched or actually holds a value. */
274
- .picker-model > .model-window { grid-column: 2; grid-row: 2; width: 8ch; padding: 1px 5px; font-size: 13px; text-align: right; color: var(--muted); background: transparent; border-color: transparent; }
275
- .picker-model > .model-window::-webkit-inner-spin-button { display: none; }
276
- .picker-model > .model-window:hover,
277
- .picker-model > .model-window:focus,
278
- .picker-model > .model-window:not(:placeholder-shown) { color: inherit; background: var(--bg); border-color: var(--border); }
279
- #client-picker-models { grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); }
280
-
281
- /* One label column so the selects line up instead of each sizing to its own row. */
282
- .slot-rows { display: grid; gap: 9px; }
283
- .slot-row { display: grid; grid-template-columns: 132px minmax(0, 1fr) minmax(0, 1.05fr); gap: 12px; align-items: center; }
284
- .slot-row .k { color: var(--muted); }
285
- .slot-row select { width: 100%; }
286
- .slot-row .hint { margin: 0; font-size: 13px; }
287
- @media (max-width: 760px) {
288
- .slot-row { grid-template-columns: 1fr; gap: 4px; }
289
- .slot-row .k { font-size: 14px; }
672
+ .modal .actions.end {
673
+ position: sticky;
674
+ bottom: 0;
675
+ margin: 22px -24px -24px;
676
+ padding: 14px 24px;
677
+ background: inherit;
678
+ background: var(--group);
679
+ border-top: 1px solid var(--separator);
290
680
  }
291
- .page-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 18px; }
292
- .page-heading > .btn { flex: 0 0 auto; }
293
- .provider-workspace { min-height: 610px; display: grid; grid-template-columns: 260px minmax(0, 1fr); overflow: hidden; background: var(--panel); border: 1px solid var(--border); border-radius: 10px; }
294
- .provider-rail { min-width: 0; border-right: 1px solid var(--border); background: color-mix(in srgb, var(--bg) 74%, var(--panel)); }
295
- .provider-rail-tools { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 7px; padding: 11px; border-bottom: 1px solid var(--border); }
296
- .provider-rail-tools input { background: var(--panel); }
297
- .provider-rail-tools .icon-btn { border: 1px solid var(--border); background: var(--panel); color: var(--text); font-size: 19px; line-height: 1; }
298
- .provider-list { padding: 7px; display: grid; gap: 4px; }
299
- .provider-rail-item { appearance: none; width: 100%; min-width: 0; display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 8px; padding: 10px; color: var(--text); text-align: left; cursor: pointer; background: transparent; border: 1px solid transparent; border-radius: 7px; }
300
- .provider-rail-item:hover { background: var(--panel); border-color: var(--border); }
301
- .provider-rail-item.selected { background: color-mix(in srgb, var(--accent) 11%, var(--panel)); border-color: color-mix(in srgb, var(--accent) 35%, var(--border)); }
302
- .provider-rail-copy { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
303
- .provider-rail-copy strong, .provider-rail-copy span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
304
- .provider-rail-copy span { color: var(--muted); font-size: 13px; }
305
- .provider-rail-item .badge { width: 8px; height: 8px; padding: 0; font-size: 0; }
306
- .provider-rail-item .badge.dot::before { width: 8px; height: 8px; }
307
- .provider-rail-empty { padding: 24px 13px; color: var(--muted); font-size: 14px; text-align: center; }
308
- .provider-detail { min-width: 0; }
309
- .provider-detail-empty { min-height: 500px; display: grid; place-items: center; color: var(--muted); }
310
- .provider-detail-header { min-height: 80px; display: flex; justify-content: space-between; align-items: center; gap: 16px; padding: 17px 20px 14px; }
311
- .provider-title-line { display: flex; align-items: center; gap: 9px; }
312
- .provider-title-line h2 { margin: 0; font-size: 19px; }
313
- .provider-detail-header p { margin: 4px 0 0; }
314
- .provider-detail-actions { display: flex; gap: 7px; flex-wrap: wrap; justify-content: flex-end; }
315
- .provider-tabs { display: flex; gap: 4px; padding: 0 18px; border-bottom: 1px solid var(--border); }
316
- .provider-tabs button { appearance: none; cursor: pointer; padding: 9px 10px; color: var(--muted); font: inherit; background: transparent; border: 0; border-bottom: 2px solid transparent; }
317
- .provider-tabs button:hover { color: var(--text); }
318
- .provider-tabs button.active { color: var(--accent); border-bottom-color: var(--accent); font-weight: 650; }
319
- .provider-panel { padding: 18px 20px 24px; display: grid; gap: 14px; }
320
- .detail-section { padding: 16px; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; }
321
- .detail-section h3 { margin: 0 0 5px; font-size: 15px; }
322
- .section-heading { display: flex; justify-content: space-between; align-items: flex-start; gap: 14px; margin-bottom: 14px; }
323
- .section-heading .hint { max-width: 610px; }
324
- .detail-setting-row { display: flex; justify-content: space-between; align-items: center; gap: 18px; padding: 9px 0; }
325
- .setting-copy { min-width: 0; display: flex; flex-direction: column; gap: 4px; }
326
- .setting-copy > span { color: var(--muted); font-size: 14px; line-height: 1.4; }
327
- .provider-detail-error { margin-bottom: 0; }
328
- .model-chip-list { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 12px; }
329
- .model-chip { padding: 5px 9px; border-radius: 999px; color: var(--text); background: var(--bg); border: 1px solid var(--border); font-size: 14px; }
330
- .provider-model-list { display: grid; gap: 7px; }
331
- .provider-model-row { display: grid; grid-template-columns: minmax(140px, .7fr) minmax(180px, 1fr) auto; gap: 10px; align-items: center; padding: 10px 11px; border: 1px solid var(--border); border-radius: 7px; background: var(--bg); }
332
- .provider-model-row > * { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
333
- .account-count { margin: 6px 0 0; font-size: 18px; font-weight: 650; }
334
- .rotation-row { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border); }
335
- .notice { display: flex; flex-direction: column; gap: 4px; margin-top: 12px; padding: 11px 12px; border-radius: 7px; font-size: 14px; line-height: 1.45; }
336
- .notice.warn { color: var(--text); background: color-mix(in srgb, var(--warn) 12%, var(--panel)); border: 1px solid color-mix(in srgb, var(--warn) 32%, var(--border)); }
337
- .notice.warn strong { color: var(--warn); }
338
- .account-card-list { display: grid; gap: 8px; }
339
- .account-card { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; gap: 12px; align-items: center; padding: 12px 13px; background: var(--bg); border: 1px solid var(--border); border-radius: 8px; }
340
- .account-card.current { background: color-mix(in srgb, var(--accent) 6%, var(--bg)); }
341
- .account-card-copy { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
342
- .account-card-copy strong, .account-card-copy > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
343
- .account-card-copy .hint { margin: 1px 0 0; }
344
- .account-card-actions { display: flex; gap: 6px; }
345
- /* Cards with more actions than fit beside the text keep them on their own row at every width. */
346
- .account-card.stacked { grid-template-columns: minmax(0, 1fr) auto; }
347
- .account-card.stacked .account-card-actions { grid-column: 1 / -1; flex-wrap: wrap; }
348
- .account-quota { margin: 0; }
349
- .danger-section { border-color: color-mix(in srgb, var(--bad) 30%, var(--border)); }
350
- .danger-section .btn { margin-top: 12px; }
351
- .oauth-consent .notice { margin: 14px 0; }
352
- .oauth-accept { margin: 13px 0 4px; align-items: flex-start !important; }
353
- .oauth-progress { display: grid; gap: 12px; margin-top: 15px; }
354
- .oauth-progress p { margin: 0; }
355
- .login-link { color: var(--accent); font-weight: 600; }
356
- button.btn.compact { padding: 4px 9px; font-size: 14px; }
357
- .provider-status { display: grid; grid-template-columns: minmax(120px, 1fr) auto; gap: 8px; align-items: center; padding: 7px 0; border-bottom: 1px solid var(--border); }
358
- .provider-status:last-child { border-bottom: none; }
359
- .provider-status .account-usage { grid-column: 1 / -1; display: grid; gap: 2px; }
360
- .empty-card { color: var(--muted); padding: 28px 16px; border: 1px dashed var(--border); border-radius: 8px; text-align: center; }
361
- .toast-raw { margin-top: 5px; color: var(--muted); font: 10.5px/1.35 var(--mono); word-break: break-word; }
362
- .modal-backdrop { position: fixed; inset: 0; z-index: 20; display: grid; place-items: center; padding: 18px; background: color-mix(in srgb, #000 38%, transparent); }
363
- #modal-backdrop[hidden] { display: none !important; }
364
- .modal { width: min(650px, 100%); max-height: min(780px, calc(100vh - 36px)); overflow: auto; position: relative; padding: 22px; background: var(--panel); border: 1px solid var(--border); border-radius: 10px; box-shadow: 0 18px 50px rgba(0,0,0,.3); }
365
- .modal h1 { margin-bottom: 5px; }
366
- .modal-close { position: absolute; top: 11px; right: 11px; font-size: 20px; }
367
- .chooser-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 9px; margin-top: 16px; }
368
- .chooser-group { grid-column: 1 / -1; display: flex; flex-direction: column; gap: 3px; padding: 12px 13px; border: 1px solid var(--border); border-radius: 8px; background: color-mix(in srgb, var(--accent) 7%, var(--panel)); }
369
- .chooser-group strong { font-size: 15px; }
370
- .chooser-group span { color: var(--muted); font-size: 14px; line-height: 1.35; }
371
- .chooser-tile { appearance: none; cursor: pointer; min-height: 106px; padding: 13px; text-align: left; color: var(--text); background: var(--bg); border: 1px solid var(--border); border-radius: 8px; }
372
- .chooser-tile:hover { border-color: var(--accent); }
373
- .chooser-tile strong, .chooser-tile span { display: block; }
374
- .chooser-tile strong { margin-bottom: 6px; }
375
- .chooser-tile span { color: var(--muted); font-size: 14px; line-height: 1.35; }
376
- .form-field { display: flex; flex-direction: column; gap: 5px; margin-top: 14px; color: var(--text); font-size: 15px; }
681
+ @media (prefers-color-scheme: dark) { .modal .actions.end { background: #262628; } }
682
+ @keyframes fade-in { from { opacity: 0; } }
683
+ @keyframes sheet-in { from { opacity: 0; transform: translateY(10px) scale(0.985); } }
684
+ @media (prefers-reduced-motion: reduce) { .modal, .modal-backdrop { animation: none; } .toast, .switch span, .switch span::after { transition: none; } }
685
+
686
+ .form-field { display: flex; flex-direction: column; gap: 6px; margin-top: 16px; color: var(--label); font-size: var(--fs-sub); }
687
+ .form-field > span:first-child { font-weight: 600; }
377
688
  /* An author `display` beats the UA's [hidden] rule, so a field a form hides would stay on screen. */
378
689
  .form-field[hidden] { display: none; }
379
- .form-field > small { color: var(--muted); font-size: 14px; line-height: 1.4; }
690
+ .form-field > small { color: var(--label-2); font-size: var(--fs-foot); line-height: 1.45; }
691
+ .form-field > .hint { margin: 0; }
692
+ .provider-form > .btn.secondary { margin-top: 14px; }
693
+ .form-field > .btn { align-self: flex-start; }
380
694
  .key-control { display: flex; gap: 6px; }
381
- .eye-button { all: unset; flex: 0 0 auto; cursor: pointer; padding: 0 9px; border: 1px solid var(--border); border-radius: 5px; font-size: 14px; color: var(--muted); }
382
- .probe-result { min-height: 18px; margin-top: 9px; font-size: 14px; }
383
- .ok-text { color: var(--ok); }
384
- .warn-text { color: var(--warn); }.bad-text { color: var(--bad); }
385
- .advanced-content { padding-top: 3px; }
386
- .picker-check { margin-top: 15px; }
387
- .client-models { margin-top: 15px; }
388
- /* A section inside a card: full contrast like the title, a step smaller, and with room above it so
389
- the eye sees where one section ends and the next begins. */
390
- .client-models h3, .card h3 { color: var(--text); font-size: 14px; letter-spacing: 0; margin: 18px 0 6px; font-weight: 600; }
391
- .client-models h3:first-child, .card h3:first-child { margin-top: 0; }
392
- .client-divider + h3 { margin-top: 0; }
393
- .client-divider { border-top: 1px solid var(--border); margin: 18px 0 14px; }
695
+ .key-control input { flex: 1 1 auto; min-width: 0; }
696
+ .eye-button { appearance: none; flex: 0 0 auto; padding: 0 12px; border: 0; border-radius: 8px; background: var(--fill); color: var(--label); font-size: var(--fs-foot); cursor: pointer; }
697
+ .eye-button:hover { background: var(--fill-hover); }
698
+ .probe-result { min-height: 20px; margin-top: 10px; font-size: var(--fs-sub); }
699
+ .probe-result .small { margin-top: 2px; word-break: break-word; }
700
+ .picker-check { margin-top: 18px; }
701
+ .provider-form > .hint { margin-top: 8px; }
702
+ .model-picker { display: flex; flex-direction: column; gap: 8px; }
703
+ .model-picker .model-checklist { padding: 0; }
704
+ .model-picker > .small { margin: 0; }
705
+ .model-add { margin-top: 2px; }
394
706
 
395
- @media (max-width: 860px) {
396
- .provider-workspace { grid-template-columns: 220px minmax(0, 1fr); }
397
- .provider-detail-header { align-items: flex-start; }
398
- .provider-detail-actions { max-width: 220px; }
399
- .account-card { grid-template-columns: minmax(0, 1fr) auto; }
400
- .account-card-actions { grid-column: 1 / -1; }
707
+ .oauth-consent .notice { margin: 14px 0; }
708
+ .oauth-accept { margin: 4px 0 2px; }
709
+ .oauth-progress { display: grid; gap: 12px; margin-top: 12px; justify-items: start; }
710
+ .oauth-progress p { margin: 0; }
711
+ .oauth-progress .key-control { width: 100%; }
712
+ .login-link { color: var(--accent); font-weight: 500; text-decoration: none; }
713
+ .login-link:hover { text-decoration: underline; }
714
+
715
+ /* Add-provider chooser: grouped lists of what can be connected. */
716
+ .chooser-grid { display: grid; gap: 22px; margin-top: 16px; }
717
+ .chooser-section > .section-head { margin-bottom: 6px; }
718
+ .chooser-group { display: block; }
719
+ .chooser-tiles { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
720
+ .chooser-tiles.single { grid-template-columns: 1fr; }
721
+ .chooser-tile {
722
+ appearance: none;
723
+ position: relative;
724
+ display: flex;
725
+ align-items: center;
726
+ gap: 12px;
727
+ min-height: 46px;
728
+ padding: 10px 14px 10px 16px;
729
+ border: 0;
730
+ background: transparent;
731
+ color: var(--label);
732
+ text-align: left;
733
+ cursor: pointer;
401
734
  }
402
-
403
- @media (max-width: 680px) {
404
- body { display: block; }
405
- nav { width: 100%; border-right: 0; border-bottom: 1px solid var(--border); padding: 9px; flex-direction: row; overflow-x: auto; align-items: center; }
406
- nav .brand { display: none; }
407
- nav .nav-spacer { display: none; }
408
- main { padding: 18px 16px; overflow-x: visible; }
409
- .row { align-items: flex-start; flex-direction: column; gap: 4px; }
410
- .row .v { text-align: left; }
411
- .provider-status { grid-template-columns: 1fr; }
412
- .page-heading { align-items: center; }
413
- .page-heading .subtitle { margin-bottom: 14px; }
414
- .provider-workspace { display: block; min-height: 0; overflow: visible; }
415
- .provider-rail { border-right: 0; border-bottom: 1px solid var(--border); }
416
- .provider-list { display: flex; overflow-x: auto; padding: 7px; }
417
- .provider-rail-item { flex: 0 0 210px; }
418
- .provider-detail-header { min-height: 0; padding: 15px 14px 10px; flex-direction: column; align-items: stretch; }
419
- .provider-detail-actions { max-width: none; justify-content: flex-start; }
420
- .provider-tabs { padding: 0 9px; overflow-x: auto; }
421
- .provider-panel { padding: 13px; }
422
- .section-heading, .detail-setting-row { align-items: flex-start; }
423
- .section-heading { flex-direction: column; }
424
- .provider-model-row { grid-template-columns: 1fr; gap: 3px; }
425
- .account-card { grid-template-columns: minmax(0, 1fr) auto; }
426
- .account-card-actions { grid-column: 1 / -1; flex-wrap: wrap; }
427
- .modal { padding: 19px 16px; }
735
+ .chooser-tile:hover { background: var(--fill); background: color-mix(in srgb, var(--fill) 70%, transparent); }
736
+ .chooser-tile .cell-text strong { font-weight: 500; font-size: var(--fs-sub); }
737
+ .chooser-tile .cell-text span { color: var(--label-2); font-size: var(--fs-foot); line-height: 1.4; }
738
+ .chooser-tiles > .chooser-tile::before { content: ""; position: absolute; top: 0; left: 16px; right: 0; height: 1px; background: var(--separator); }
739
+ .chooser-tiles > .chooser-tile:first-child::before { display: none; }
740
+ .chooser-tiles:not(.single) > .chooser-tile:nth-child(2)::before { display: none; }
741
+ .chooser-tiles > .chooser-tile:first-child { border-top-left-radius: var(--radius); }
742
+ .chooser-tiles.single > .chooser-tile { border-radius: var(--radius); }
743
+
744
+ /* ---- Narrow windows ------------------------------------------------------------------ */
745
+
746
+ @media (max-width: 1020px) {
747
+ .provider-workspace { grid-template-columns: 228px minmax(0, 1fr); gap: 22px; }
748
+ .logs-summary { grid-template-columns: repeat(3, minmax(0, 1fr)); }
749
+ .logs-chip:nth-child(4)::before { display: none; }
750
+ .logs-chip:nth-child(n + 4) { border-top: 1px solid var(--separator); }
428
751
  }
429
752
 
430
- .model-search { width: 100%; margin-top: 8px; }
431
- .model-picker .small { margin-top: 6px; }
432
- .model-add { margin-top: 8px; }
433
- .model-add input { flex: 1; min-width: 0; }
434
- .model-effort-tag { display: inline-block; margin-left: 5px; padding: 1px 5px; border-radius: 999px; font-size: 12px; line-height: 1.35; vertical-align: middle; white-space: nowrap; }
435
- .model-effort-tag.has-effort { color: var(--ok); background: color-mix(in srgb, var(--ok) 13%, transparent); }
436
- .model-effort-tag.no-effort { color: var(--muted); background: color-mix(in srgb, var(--muted) 12%, transparent); }
437
- .model-check .model-effort-tag { margin-left: auto; }
438
- #slots-status { margin-left: 12px; }
439
- #slots-summary { margin: 0 0 10px; }
440
-
441
- /* Readability: body copy is 16px; hints and secondary text stay one step down but never below 14px. */
442
- .hint, .small, small, .subtitle { font-size: 14px; }
443
- .subtitle { font-size: 15px; }
444
- input, select, textarea, button { font-size: 15px; }
445
-
446
- .logs-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border); margin: 0 0 16px; }
447
- .logs-tab { all: unset; cursor: pointer; padding: 8px 11px; color: var(--muted); font-size: 15px; border-bottom: 2px solid transparent; }
448
- .logs-tab:hover { color: var(--text); }
449
- .logs-tab.active { color: var(--accent); font-weight: 600; border-bottom-color: var(--accent); }
450
- .logs-summary-title { display: flex; align-items: baseline; gap: 9px; margin: 0 0 8px; }
451
- .logs-summary { display: grid; grid-template-columns: repeat(5, minmax(126px, 1fr)); gap: 8px; margin-bottom: 14px; }
452
- .logs-chip { min-width: 0; border: 1px solid var(--border); border-radius: 7px; padding: 10px 11px; background: var(--panel); }
453
- .logs-chip .label { display: block; color: var(--muted); font-size: 14px; margin-bottom: 4px; }
454
- .logs-chip .value { display: block; font-size: 16px; font-weight: 650; font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
455
- .logs-chip .sub { display: block; margin-top: 2px; color: var(--muted); font-size: 13px; white-space: nowrap; }
456
- .logs-controls { justify-content: flex-start; }
457
- .logs-filter { display: inline-flex; align-items: center; gap: 7px; color: var(--text); font-size: 15px; }
458
- .logs-filter select { width: auto; min-width: 120px; }
459
- .logs-table-card { margin-bottom: 0; }
460
- .logs-table-card table { min-width: 920px; }
461
- #requests-table tbody tr.request-row { cursor: pointer; }
462
- #requests-table tbody tr.request-row:hover td { background: color-mix(in srgb, var(--accent) 7%, transparent); }
463
- #requests-table td { font-variant-numeric: tabular-nums; }
464
- .request-models { display: flex; align-items: center; gap: 5px; min-width: 185px; }
465
- .request-models .model { max-width: 170px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
466
- .provider-badge { display: inline-flex; align-items: center; padding: 2px 7px; border-radius: 99px; color: var(--provider, var(--accent)); background: color-mix(in srgb, var(--provider, var(--accent)) 16%, transparent); font-size: 14px; font-weight: 600; white-space: nowrap; }
467
- .provider-anthropic { --provider: #8b5cf6; }.provider-chatgpt { --provider: #16856b; }.provider-default { --provider: var(--accent); }
468
- .token-cell { white-space: nowrap; }
469
- .cache-pill { display: inline-block; margin-left: 4px; padding: 1px 5px; border-radius: 99px; color: var(--muted); background: var(--bg); font-size: 14px; }
470
- .no-usage { color: var(--muted); }
471
- .status-text { font-weight: 600; white-space: nowrap; }.status-text.ok { color: var(--ok); }.status-text.bad { color: var(--bad); }.status-text.resent { color: var(--muted); font-weight: 500; }
472
- .request-detail td { padding: 0; background: color-mix(in srgb, var(--accent) 4%, var(--panel)); }
473
- .request-detail-grid { display: grid; grid-template-columns: repeat(4, minmax(120px, 1fr)); gap: 10px; padding: 12px 14px; }
474
- .request-detail-grid .detail-label { display: block; color: var(--muted); font-size: 14px; margin-bottom: 3px; }
475
- .request-detail-grid .detail-value { word-break: break-word; font-family: var(--mono); font-size: 14px; }
476
- .request-note { grid-column: 1 / -1; }
477
- @media (max-width: 980px) { .logs-summary { grid-template-columns: repeat(3, minmax(130px, 1fr)); } }
478
- @media (max-width: 680px) { .logs-summary { grid-template-columns: repeat(2, minmax(130px, 1fr)); }.request-detail-grid { grid-template-columns: repeat(2, minmax(120px, 1fr)); } }
753
+ @media (max-width: 860px) {
754
+ .map-head { display: none; }
755
+ .map-row { grid-template-columns: minmax(0, 1fr) 30px; grid-template-areas: "source remove" "target target" "effort effort"; gap: 6px 10px; padding: 12px 12px 14px 16px; }
756
+ .map-row > .map-source { grid-area: source; }
757
+ .map-row > .map-target { grid-area: target; }
758
+ .map-row > .map-effort { grid-area: effort; }
759
+ .map-row > .map-remove { grid-area: remove; align-self: start; }
760
+ .map-row > .map-arrow { display: none; }
761
+ .map-label { display: block; margin: 0 0 4px 2px; color: var(--label-2); font-size: var(--fs-cap); }
762
+ .muted-cell::after { top: 20px; }
763
+
764
+ /* Providers become a list that pushes to a detail page. */
765
+ .provider-workspace { display: block; }
766
+ .provider-rail { position: static; }
767
+ .providers-view:not(.show-detail) .provider-detail { display: none; }
768
+ .providers-view.show-detail .provider-rail, .providers-view.show-detail > .page-header { display: none; }
769
+ .provider-back { appearance: none; display: inline-flex; align-items: center; gap: 4px; margin: -4px 0 14px -6px; padding: 4px 6px; border: 0; border-radius: 7px; background: none; color: var(--accent); font-size: var(--fs-body); cursor: pointer; }
770
+ .provider-back svg { width: 9px; height: 15px; }
771
+ .provider-list { padding: 0; gap: 0; }
772
+ .provider-rail-item { position: relative; padding: 11px 16px; border-radius: 0; }
773
+ .provider-rail-item + .provider-rail-item::before { content: ""; position: absolute; top: 0; left: 16px; right: 0; height: 1px; background: var(--separator); }
774
+ .provider-rail-item:first-child { border-radius: var(--radius) var(--radius) 0 0; }
775
+ .provider-rail-item:last-child { border-radius: 0 0 var(--radius) var(--radius); }
776
+ .provider-rail-item.selected { background: transparent; }
777
+ }
479
778
 
480
- nav .brand { display: flex; align-items: center; gap: 8px; }
481
- nav .brand svg { width: 22px; height: 22px; flex: 0 0 auto; }
779
+ @media (max-width: 720px) {
780
+ .app { display: block; }
781
+ /* The sidebar becomes a tab bar along the bottom, on a material so content shows through. */
782
+ .sidebar {
783
+ position: fixed;
784
+ inset: auto 0 0 0;
785
+ height: auto;
786
+ padding: 6px 4px calc(6px + env(safe-area-inset-bottom));
787
+ border-right: 0;
788
+ border-top: 1px solid var(--separator);
789
+ background: color-mix(in srgb, var(--sidebar) 82%, transparent);
790
+ -webkit-backdrop-filter: saturate(180%) blur(20px);
791
+ backdrop-filter: saturate(180%) blur(20px);
792
+ }
793
+ .nav-list { flex-direction: row; gap: 0; }
794
+ .nav-btn { flex: 1 1 0; min-width: 0; flex-direction: column; justify-content: flex-start; gap: 3px; padding: 4px 0; font-size: 10px; line-height: 1.15; text-align: center; color: var(--label-2); }
795
+ .nav-btn:hover { background: transparent; }
796
+ .nav-btn.active { background: transparent; color: var(--accent); font-weight: 600; }
797
+ .nav-btn .nav-icon { width: 22px; height: 22px; color: inherit; }
798
+ .nav-btn span { white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow-wrap: break-word; word-break: keep-all; }
799
+ .sidebar-footer { display: none; }
800
+ .nav-btn .nav-long { display: none; }
801
+ .nav-btn .nav-short { display: -webkit-box; }
802
+ .narrow-only { display: block; }
803
+
804
+ main { padding: 22px 16px calc(96px + env(safe-area-inset-bottom)); }
805
+ .page-header { flex-wrap: wrap; align-items: flex-start; margin-bottom: 20px; }
806
+ .page-header h1 { font-size: 28px; }
807
+ /* A switch or a status stays beside its label; a menu or a long value drops under it. */
808
+ .slot-row, .group > .row { flex-wrap: wrap; }
809
+ .group > .row .v { text-align: left; }
810
+ .cell-value { max-width: 55%; }
811
+ .slot-row .cell-value select { width: 100%; max-width: none; }
812
+ .slot-row .cell-value { flex: 1 1 100%; max-width: none; justify-content: stretch; }
813
+ .map-effort.muted-cell { display: none; }
814
+ .map-row { grid-template-areas: "source remove" "target target" "effort effort"; }
815
+ .provider-detail-header { flex-direction: column; align-items: stretch; gap: 12px; }
816
+ .provider-detail-actions { justify-content: flex-start; }
817
+ .provider-tabs { display: flex; }
818
+ .provider-tabs button { flex: 1 1 0; }
819
+ .logs-summary { grid-template-columns: repeat(2, minmax(0, 1fr)); }
820
+ .logs-chip:nth-child(n)::before { display: block; }
821
+ .logs-chip:nth-child(odd)::before { display: none; }
822
+ .logs-chip:nth-child(n + 3) { border-top: 1px solid var(--separator); }
823
+ .logs-chip:nth-child(5) { grid-column: 1 / -1; }
824
+ .chooser-tiles { grid-template-columns: 1fr; }
825
+ .chooser-tiles:not(.single) > .chooser-tile:nth-child(2)::before { display: block; }
826
+
827
+ /* A segmented control, slider or text field drops under its label and takes the row's width. */
828
+ .cell.wide { flex-wrap: wrap; row-gap: 8px; }
829
+ .cell.wide > .cell-value { flex: 1 1 100%; max-width: none; justify-content: flex-start; }
830
+ .cell.wide .segmented { display: flex; width: 100%; }
831
+ .cell.wide .segmented button { flex: 1 1 0; min-width: 0; padding: 3px 6px; overflow: hidden; text-overflow: ellipsis; }
832
+ .cell.wide .lean { width: 100%; }
833
+ .cell.wide .lean-track { flex: 1 1 auto; width: auto; }
834
+ .cell.wide .text-field { width: 100%; }
835
+ .mod-card .cell-value select { width: 100%; }
836
+ .mod-card .cell-value select.narrow { width: 140px; }
837
+ .mod-card .cell-value select.rung-model { width: 100%; }
838
+
839
+ /* Requests read as a list: model and status on top, the numbers on one quiet line below. */
840
+ .logs-table-card { overflow: visible; }
841
+ .logs-table-card table { min-width: 0; }
842
+ #requests-table thead { display: none; }
843
+ #requests-table, #requests-table tbody { display: block; }
844
+ #requests-table tr.request-row { position: relative; display: flex; flex-wrap: wrap; align-items: center; gap: 2px 0; padding: 10px 16px; }
845
+ #requests-table tr.request-row + tr::before, #requests-table tr.request-detail + tr::before { content: ""; position: absolute; top: 0; left: 16px; right: 0; height: 1px; background: var(--separator); }
846
+ #requests-table td { display: block; padding: 0; border: 0; }
847
+ /* The model's basis leaves room for the status only, so the numbers always start a second line. */
848
+ #requests-table td.c-model { order: 1; flex: 1 1 calc(100% - 110px); min-width: 0; padding-right: 10px; }
849
+ #requests-table td.c-status { order: 2; margin-left: auto; font-size: var(--fs-foot); }
850
+ #requests-table td.c-time, #requests-table td.c-effort, #requests-table td.c-in, #requests-table td.c-out, #requests-table td.c-dur { order: 3; color: var(--label-2); font-size: var(--fs-cap); white-space: nowrap; }
851
+ #requests-table td.c-time::after, #requests-table td.c-effort::after, #requests-table td.c-in::after, #requests-table td.c-out::after { content: "·"; margin: 0 6px; color: var(--label-3); } #requests-table .request-models .model { max-width: 60vw; }
852
+ #requests-table .request-models > .small { display: none; }
853
+ #requests-table tr.request-detail { position: relative; display: block; }
854
+ #requests-table tr.request-detail td { display: block; }
855
+ .request-detail-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
856
+
857
+ .modal-backdrop { place-items: end center; padding: 0; }
858
+ .modal { width: 100%; max-height: calc(100vh - 28px); border-radius: 14px 14px 0 0; padding-bottom: env(safe-area-inset-bottom); }
859
+ #modal-content { padding: 20px 16px 20px; }
860
+ .modal .actions.end { margin: 20px -16px -20px; padding: 12px 16px; }
861
+ .toast { bottom: calc(84px + env(safe-area-inset-bottom)); }
862
+ }