tokenbiryani 0.2.0__py3-none-any.whl
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.
- tokenbiryani/__init__.py +3 -0
- tokenbiryani/api/__init__.py +0 -0
- tokenbiryani/api/app.py +583 -0
- tokenbiryani/api/asgi.py +32 -0
- tokenbiryani/cli.py +1045 -0
- tokenbiryani/config.py +532 -0
- tokenbiryani/core/__init__.py +0 -0
- tokenbiryani/core/account.py +258 -0
- tokenbiryani/core/batch.py +135 -0
- tokenbiryani/core/breaker.py +53 -0
- tokenbiryani/core/cacheadvice.py +239 -0
- tokenbiryani/core/diagnostics.py +131 -0
- tokenbiryani/core/estimator.py +180 -0
- tokenbiryani/core/gateway.py +2395 -0
- tokenbiryani/core/handoff.py +87 -0
- tokenbiryani/core/keys.py +199 -0
- tokenbiryani/core/limits.py +440 -0
- tokenbiryani/core/oauth.py +222 -0
- tokenbiryani/core/pacing.py +320 -0
- tokenbiryani/core/queue.py +132 -0
- tokenbiryani/core/router.py +323 -0
- tokenbiryani/core/secrets.py +114 -0
- tokenbiryani/core/session.py +117 -0
- tokenbiryani/dashboard/__init__.py +56 -0
- tokenbiryani/dashboard/console.css +610 -0
- tokenbiryani/dashboard/console.html +3250 -0
- tokenbiryani/observability/__init__.py +0 -0
- tokenbiryani/observability/events.py +171 -0
- tokenbiryani/observability/usage.py +226 -0
- tokenbiryani/prices.yaml +77 -0
- tokenbiryani/providers/__init__.py +0 -0
- tokenbiryani/providers/anthropic_api.py +118 -0
- tokenbiryani/providers/base.py +173 -0
- tokenbiryani/providers/bedrock.py +182 -0
- tokenbiryani/providers/oauth.py +165 -0
- tokenbiryani/providers/oauth_credentials.py +293 -0
- tokenbiryani/providers/translate.py +35 -0
- tokenbiryani/providers/vertex.py +144 -0
- tokenbiryani/proxy/__init__.py +0 -0
- tokenbiryani/proxy/errors.py +169 -0
- tokenbiryani/proxy/sse.py +98 -0
- tokenbiryani/store/__init__.py +0 -0
- tokenbiryani/store/base.py +150 -0
- tokenbiryani/store/memory.py +149 -0
- tokenbiryani/store/redis_store.py +222 -0
- tokenbiryani/store/sqlite.py +336 -0
- tokenbiryani-0.2.0.dist-info/METADATA +697 -0
- tokenbiryani-0.2.0.dist-info/RECORD +51 -0
- tokenbiryani-0.2.0.dist-info/WHEEL +4 -0
- tokenbiryani-0.2.0.dist-info/entry_points.txt +2 -0
- tokenbiryani-0.2.0.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
tokenbiryani console — design system
|
|
3
|
+
|
|
4
|
+
Minimal by construction: one accent, hairline borders, no shadows doing work
|
|
5
|
+
a border can do, and a single 4px spacing scale. Colour is reserved for
|
|
6
|
+
state, and state colour is validated for colour-vision deficiency — the
|
|
7
|
+
accent is deliberately off that ramp so it can mean "interactive".
|
|
8
|
+
========================================================================= */
|
|
9
|
+
|
|
10
|
+
:root{
|
|
11
|
+
/* neutrals, very slightly cool so the warm accent reads as chosen */
|
|
12
|
+
--bg:#0A0C10; --surface:#111419; --surface-2:#171B22; --surface-3:#1D222B;
|
|
13
|
+
--border:#1E242D; --border-strong:#2C3540;
|
|
14
|
+
--text:#E9ECF1; --text-2:#99A2AE; --text-3:#616B79;
|
|
15
|
+
|
|
16
|
+
--accent:#E9A94A; --accent-ink:#1A1204; --accent-soft:#241C0F;
|
|
17
|
+
--ready:#2FA47A; --ready-t:#4FD09B; --ready-w:#0E2620;
|
|
18
|
+
--cool:#4A85DE; --cool-t:#7FADEE; --cool-w:#101B2B;
|
|
19
|
+
--crit:#DA6355; --crit-t:#F08878; --crit-w:#2A1614;
|
|
20
|
+
--cache:#9068DE; --cache-t:#B18CF0; --cache-w:#1C1630;
|
|
21
|
+
|
|
22
|
+
--r-xs:5px; --r-sm:8px; --r:11px; --r-lg:16px;
|
|
23
|
+
--s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:24px; --s6:32px; --s7:48px;
|
|
24
|
+
--row:14px;
|
|
25
|
+
--sans:"Inter var",Inter,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,
|
|
26
|
+
"Helvetica Neue",Arial,sans-serif;
|
|
27
|
+
--mono:"JetBrains Mono",ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;
|
|
28
|
+
--ease:cubic-bezier(.4,0,.2,1);
|
|
29
|
+
}
|
|
30
|
+
:root[data-theme="light"]{
|
|
31
|
+
--bg:#FAFAFB; --surface:#FFFFFF; --surface-2:#F4F5F7; --surface-3:#ECEEF1;
|
|
32
|
+
--border:#E6E8EC; --border-strong:#D0D5DC;
|
|
33
|
+
--text:#14181F; --text-2:#5A6472; --text-3:#8892A0;
|
|
34
|
+
--accent:#A8670F; --accent-ink:#FFFFFF; --accent-soft:#FBF2E3;
|
|
35
|
+
--ready:#12795B; --ready-t:#0D5B44; --ready-w:#E6F3EE;
|
|
36
|
+
--cool:#1F63C4; --cool-t:#17509F; --cool-w:#E7EEFB;
|
|
37
|
+
--crit:#A93125; --crit-t:#8A2318; --crit-w:#FAE9E6;
|
|
38
|
+
--cache:#7A4FD0; --cache-t:#5C39A4; --cache-w:#F1EBFC;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
*,*::before,*::after{box-sizing:border-box}
|
|
42
|
+
html,body{height:100%}
|
|
43
|
+
body{margin:0;background:var(--bg);color:var(--text);font-family:var(--sans);
|
|
44
|
+
font-size:13.5px;line-height:1.5;-webkit-font-smoothing:antialiased;
|
|
45
|
+
text-rendering:optimizeLegibility}
|
|
46
|
+
.mono,code,kbd{font-family:var(--mono);font-variant-numeric:tabular-nums}
|
|
47
|
+
h1,h2,h3,h4{margin:0;font-weight:600;letter-spacing:-.018em}
|
|
48
|
+
p{margin:0}
|
|
49
|
+
button{font:inherit;cursor:pointer;border:0;background:none;color:inherit}
|
|
50
|
+
input,select,textarea{font:inherit}
|
|
51
|
+
a{color:var(--accent);text-underline-offset:2px}
|
|
52
|
+
:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:var(--r-xs)}
|
|
53
|
+
::selection{background:var(--accent-soft);color:var(--text)}
|
|
54
|
+
.hidden{display:none!important}
|
|
55
|
+
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
|
|
56
|
+
|
|
57
|
+
/* ---------- shell ---------- */
|
|
58
|
+
.shell{display:grid;grid-template-columns:236px minmax(0,1fr);min-height:100vh}
|
|
59
|
+
@media(max-width:900px){.shell{grid-template-columns:1fr}}
|
|
60
|
+
|
|
61
|
+
.side{background:var(--surface);border-right:1px solid var(--border);
|
|
62
|
+
display:flex;flex-direction:column;gap:var(--s5);padding:var(--s5) var(--s4);
|
|
63
|
+
position:sticky;top:0;height:100vh;overflow-y:auto}
|
|
64
|
+
@media(max-width:900px){.side{position:static;height:auto;border-right:0;
|
|
65
|
+
border-bottom:1px solid var(--border)}}
|
|
66
|
+
.brand{display:flex;align-items:center;gap:var(--s2);padding:0 var(--s2)}
|
|
67
|
+
.brand .dot{width:22px;height:22px;border-radius:7px;background:var(--accent);
|
|
68
|
+
display:grid;place-items:center;font-size:12px;flex:none}
|
|
69
|
+
.brand b{font-size:14px;font-weight:650;letter-spacing:-.02em}
|
|
70
|
+
.brand span{font-size:10.5px;color:var(--text-3);display:block;letter-spacing:.02em}
|
|
71
|
+
|
|
72
|
+
.nav{display:flex;flex-direction:column;gap:2px;flex:1}
|
|
73
|
+
.nav button{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:var(--r-sm);
|
|
74
|
+
color:var(--text-2);font-size:13px;font-weight:500;text-align:left;width:100%;
|
|
75
|
+
transition:background .14s var(--ease),color .14s var(--ease)}
|
|
76
|
+
.nav button svg{width:16px;height:16px;flex:none;opacity:.8}
|
|
77
|
+
.nav button:hover{background:var(--surface-2);color:var(--text)}
|
|
78
|
+
.nav button.on{background:var(--surface-2);color:var(--text)}
|
|
79
|
+
.nav button.on svg{opacity:1;color:var(--accent)}
|
|
80
|
+
.nav .sep{margin:var(--s3) 10px var(--s1);font-size:9.5px;letter-spacing:.12em;
|
|
81
|
+
text-transform:uppercase;color:var(--text-3);font-weight:600}
|
|
82
|
+
|
|
83
|
+
.side-foot{display:flex;flex-direction:column;gap:var(--s2);
|
|
84
|
+
border-top:1px solid var(--border);padding-top:var(--s3)}
|
|
85
|
+
.side-foot .row{display:flex;gap:6px}
|
|
86
|
+
.chip{flex:1;border:1px solid var(--border);border-radius:var(--r-sm);padding:5px 8px;
|
|
87
|
+
font-size:11px;color:var(--text-2);text-align:center;
|
|
88
|
+
transition:border-color .14s var(--ease),color .14s var(--ease)}
|
|
89
|
+
.chip:hover{border-color:var(--border-strong);color:var(--text)}
|
|
90
|
+
.status{display:flex;align-items:center;gap:7px;font-size:11px;color:var(--text-3);padding:0 2px}
|
|
91
|
+
.status i{width:6px;height:6px;border-radius:50%;background:var(--text-3);flex:none}
|
|
92
|
+
.status.on i{background:var(--ready);box-shadow:0 0 0 3px var(--ready-w)}
|
|
93
|
+
.status.off i{background:var(--crit);box-shadow:0 0 0 3px var(--crit-w)}
|
|
94
|
+
|
|
95
|
+
.main{min-width:0;display:flex;flex-direction:column}
|
|
96
|
+
.topbar{display:flex;align-items:baseline;gap:var(--s3);padding:var(--s5) var(--s6) var(--s4);
|
|
97
|
+
flex-wrap:wrap}
|
|
98
|
+
.topbar h1{font-size:22px}
|
|
99
|
+
.topbar .sub{color:var(--text-3);font-size:12.5px}
|
|
100
|
+
.topbar .spacer{flex:1}
|
|
101
|
+
.view{padding:0 var(--s6) var(--s7);max-width:1320px;width:100%}
|
|
102
|
+
@media(max-width:640px){.topbar,.view{padding-left:var(--s4);padding-right:var(--s4)}}
|
|
103
|
+
.view[hidden]{display:none}
|
|
104
|
+
@media (prefers-reduced-motion:no-preference){
|
|
105
|
+
.view{animation:rise .22s var(--ease)}
|
|
106
|
+
@keyframes rise{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* ---------- primitives ---------- */
|
|
110
|
+
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);
|
|
111
|
+
overflow:hidden}
|
|
112
|
+
.card>header{display:flex;align-items:baseline;justify-content:space-between;gap:var(--s3);
|
|
113
|
+
padding:var(--s3) var(--s4);border-bottom:1px solid var(--border);flex-wrap:wrap}
|
|
114
|
+
.card>header h3{font-size:12.5px}
|
|
115
|
+
.card>header .note{font-size:11px;color:var(--text-3);font-weight:400}
|
|
116
|
+
.card .pad{padding:var(--s4)}
|
|
117
|
+
.grid{display:grid;gap:var(--s3)}
|
|
118
|
+
.g2{grid-template-columns:repeat(auto-fit,minmax(320px,1fr))}
|
|
119
|
+
.g3{grid-template-columns:repeat(auto-fit,minmax(230px,1fr))}
|
|
120
|
+
.stack{display:flex;flex-direction:column;gap:var(--s3)}
|
|
121
|
+
.row{display:flex;gap:var(--s2);align-items:center;flex-wrap:wrap}
|
|
122
|
+
|
|
123
|
+
.btn{border:1px solid var(--border-strong);border-radius:var(--r-sm);padding:7px 13px;
|
|
124
|
+
font-size:12.5px;font-weight:500;color:var(--text-2);
|
|
125
|
+
transition:border-color .14s var(--ease),color .14s var(--ease),background .14s var(--ease)}
|
|
126
|
+
.btn:hover{color:var(--text);border-color:var(--text-3)}
|
|
127
|
+
.btn.primary{background:var(--accent);border-color:var(--accent);color:var(--accent-ink);font-weight:600}
|
|
128
|
+
.btn.primary:hover{filter:brightness(1.07)}
|
|
129
|
+
.btn.ghost{border-color:transparent;color:var(--text-3)}
|
|
130
|
+
.btn.ghost:hover{background:var(--surface-2);color:var(--text)}
|
|
131
|
+
.btn.danger{border-color:var(--crit);color:var(--crit-t)}
|
|
132
|
+
.btn.danger:hover{background:var(--crit-w)}
|
|
133
|
+
.btn:disabled{opacity:.45;cursor:default;filter:none}
|
|
134
|
+
.btn.sm{padding:4px 9px;font-size:11.5px}
|
|
135
|
+
|
|
136
|
+
label.f,.f{display:flex;flex-direction:column;gap:5px;font-size:11px;font-weight:600;
|
|
137
|
+
letter-spacing:.05em;text-transform:uppercase;color:var(--text-3);min-width:0}
|
|
138
|
+
label.f input,label.f select,label.f textarea{background:var(--surface-2);
|
|
139
|
+
border:1px solid var(--border);border-radius:var(--r-sm);padding:9px 11px;color:var(--text);
|
|
140
|
+
font-size:13px;font-weight:400;letter-spacing:normal;text-transform:none;width:100%;
|
|
141
|
+
transition:border-color .14s var(--ease),background .14s var(--ease)}
|
|
142
|
+
label.f input:focus,label.f select:focus{border-color:var(--accent);outline:none;
|
|
143
|
+
background:var(--surface)}
|
|
144
|
+
label.f .hint,.f .hint{font-size:10.5px;font-weight:400;letter-spacing:normal;text-transform:none;
|
|
145
|
+
color:var(--text-3)}
|
|
146
|
+
|
|
147
|
+
.pill{display:inline-flex;align-items:center;gap:6px;font-size:10px;font-weight:650;
|
|
148
|
+
letter-spacing:.06em;text-transform:uppercase;padding:3px 8px;border-radius:99px;white-space:nowrap}
|
|
149
|
+
.pill i{width:5px;height:5px;border-radius:50%;flex:none}
|
|
150
|
+
.pill.ready{background:var(--ready-w);color:var(--ready-t)} .pill.ready i{background:var(--ready)}
|
|
151
|
+
.pill.cooling{background:var(--cool-w);color:var(--cool-t)} .pill.cooling i{background:var(--cool)}
|
|
152
|
+
.pill.disabled{background:var(--crit-w);color:var(--crit-t)} .pill.disabled i{background:var(--crit)}
|
|
153
|
+
.pill.muted{background:var(--surface-2);color:var(--text-3)} .pill.muted i{background:var(--text-3)}
|
|
154
|
+
.pill.accent{background:var(--accent-soft);color:var(--accent)} .pill.accent i{background:var(--accent)}
|
|
155
|
+
|
|
156
|
+
/* Bare .note is the quiet caption beside a heading. Give it a tier and it becomes
|
|
157
|
+
a callout — same word, because it is the same thing said louder. */
|
|
158
|
+
.note{font-size:11px;color:var(--text-3);font-weight:400}
|
|
159
|
+
.note.err,.note.ok,.note.info,.note.warn{padding:11px 14px;border-radius:var(--r-sm);
|
|
160
|
+
font-size:12.5px;border:1px solid;display:flex;gap:10px;align-items:flex-start}
|
|
161
|
+
.note.err{background:var(--crit-w);border-color:var(--crit);color:var(--crit-t)}
|
|
162
|
+
.note.ok{background:var(--ready-w);border-color:var(--ready);color:var(--ready-t)}
|
|
163
|
+
.note.info{background:var(--cool-w);border-color:var(--cool);color:var(--cool-t)}
|
|
164
|
+
.note.warn{background:var(--accent-soft);border-color:var(--accent);color:var(--accent)}
|
|
165
|
+
|
|
166
|
+
.empty{padding:var(--s7) var(--s4);text-align:center;color:var(--text-3);font-size:12.5px;
|
|
167
|
+
line-height:1.8}
|
|
168
|
+
.empty b{color:var(--text-2);font-weight:550;display:block;font-size:14px;margin-bottom:4px}
|
|
169
|
+
|
|
170
|
+
table{width:100%;border-collapse:collapse}
|
|
171
|
+
th{text-align:left;font-size:10px;letter-spacing:.1em;text-transform:uppercase;
|
|
172
|
+
color:var(--text-3);font-weight:650;padding:0 var(--s3) var(--s2);white-space:nowrap}
|
|
173
|
+
th.r{text-align:right}
|
|
174
|
+
td{border-top:1px solid var(--border);padding:var(--row) var(--s3);color:var(--text-2);
|
|
175
|
+
vertical-align:middle}
|
|
176
|
+
td.r{text-align:right;white-space:nowrap;color:var(--text)}
|
|
177
|
+
tbody tr{transition:background .12s var(--ease)}
|
|
178
|
+
tbody tr:hover td{background:var(--surface-2)}
|
|
179
|
+
tr.click{cursor:pointer}
|
|
180
|
+
tr.click:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
|
|
181
|
+
.scroll{overflow-x:auto}
|
|
182
|
+
|
|
183
|
+
/* ---------- stat tiles ---------- */
|
|
184
|
+
.tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(168px,1fr));gap:var(--s3)}
|
|
185
|
+
.tile{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);
|
|
186
|
+
padding:var(--s4);display:flex;flex-direction:column;gap:6px}
|
|
187
|
+
.tile .lb{font-size:10.5px;font-weight:650;letter-spacing:.09em;text-transform:uppercase;
|
|
188
|
+
color:var(--text-3)}
|
|
189
|
+
.tile .vl{font-size:26px;font-weight:600;letter-spacing:-.03em;line-height:1.05}
|
|
190
|
+
.tile .vl small{font-size:13px;font-weight:500;color:var(--text-3);letter-spacing:0}
|
|
191
|
+
.tile .sub{font-size:11.5px;color:var(--text-3)}
|
|
192
|
+
.tile.good .vl{color:var(--ready-t)} .tile.cache .vl{color:var(--cache-t)}
|
|
193
|
+
.tile.bad .vl{color:var(--crit-t)}
|
|
194
|
+
|
|
195
|
+
/* Chart chrome lives with the series palette at the end of this file. */
|
|
196
|
+
|
|
197
|
+
.meter{display:flex;flex-direction:column;gap:5px;min-width:150px}
|
|
198
|
+
.meter .bar{display:flex;align-items:center;gap:8px}
|
|
199
|
+
.meter .k{font-size:9.5px;color:var(--text-3);width:20px;font-family:var(--mono)}
|
|
200
|
+
.meter .track{flex:1;height:5px;background:var(--surface-3);border-radius:99px;overflow:hidden}
|
|
201
|
+
.meter .track i{display:block;height:100%;border-radius:99px;transition:width .3s var(--ease)}
|
|
202
|
+
.meter .v{font-size:10.5px;color:var(--text-2);width:70px;text-align:right;font-family:var(--mono)}
|
|
203
|
+
|
|
204
|
+
/* ---------- request stream ---------- */
|
|
205
|
+
.stream{display:flex;flex-direction:column}
|
|
206
|
+
.stream .r{display:grid;grid-template-columns:70px 108px minmax(0,1fr) 72px 64px 62px;
|
|
207
|
+
gap:var(--s3);padding:var(--row) var(--s4);border-top:1px solid var(--border);
|
|
208
|
+
font-size:11.5px;color:var(--text-2);align-items:center;cursor:pointer;
|
|
209
|
+
font-family:var(--mono);transition:background .12s var(--ease)}
|
|
210
|
+
.stream .r:hover{background:var(--surface-2)}
|
|
211
|
+
.stream .r:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
|
|
212
|
+
.stream .r.head{cursor:default;font-family:var(--sans);font-size:10px;letter-spacing:.1em;
|
|
213
|
+
text-transform:uppercase;color:var(--text-3);font-weight:650;border-top:0;padding-bottom:var(--s2)}
|
|
214
|
+
.stream .r.head:hover{background:none}
|
|
215
|
+
.stream .r .m{color:var(--text-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
216
|
+
.stream .r.brk{background:var(--cache-w)}
|
|
217
|
+
.ok{color:var(--ready-t)} .er{color:var(--crit-t)} .ca{color:var(--cache-t)} .dm{color:var(--text-3)}
|
|
218
|
+
@media(max-width:760px){.stream .r{grid-template-columns:1fr 1fr;gap:2px var(--s3)}}
|
|
219
|
+
|
|
220
|
+
/* ---------- inspector ---------- */
|
|
221
|
+
.chain{display:flex;align-items:stretch;flex-wrap:wrap;gap:0}
|
|
222
|
+
.node{background:var(--surface-2);border:1px solid var(--border-strong);border-radius:var(--r-sm);
|
|
223
|
+
padding:9px 13px;display:flex;flex-direction:column;gap:3px}
|
|
224
|
+
.node .n{font-size:12px;color:var(--text);font-family:var(--mono)}
|
|
225
|
+
.node .s{font-size:10.5px;font-family:var(--mono)}
|
|
226
|
+
.node.fail{border-color:var(--crit)} .node.fail .s{color:var(--crit-t)}
|
|
227
|
+
.node.win{border-color:var(--ready)} .node.win .s{color:var(--ready-t)}
|
|
228
|
+
.arrow{padding:0 12px;font-size:10.5px;color:var(--text-3);align-self:center;font-family:var(--mono)}
|
|
229
|
+
.kv{display:grid;grid-template-columns:repeat(auto-fit,minmax(118px,1fr));gap:1px;
|
|
230
|
+
background:var(--border);border:1px solid var(--border);border-radius:var(--r-sm);overflow:hidden}
|
|
231
|
+
.kv>div{background:var(--surface);padding:10px 13px;display:flex;flex-direction:column;gap:3px}
|
|
232
|
+
.kv .lb{font-size:9.5px;font-weight:650;letter-spacing:.09em;text-transform:uppercase;color:var(--text-3)}
|
|
233
|
+
.kv .vl{font-size:14px;color:var(--text);font-family:var(--mono)}
|
|
234
|
+
.kv .vl.ca{color:var(--cache-t)}
|
|
235
|
+
tr.won td{background:var(--ready-w)} tr.won td:first-child{color:var(--ready-t)}
|
|
236
|
+
|
|
237
|
+
/* ---------- modal ---------- */
|
|
238
|
+
.scrim{position:fixed;inset:0;background:rgba(6,8,11,.62);backdrop-filter:blur(3px);
|
|
239
|
+
display:grid;place-items:center;padding:var(--s4);z-index:50}
|
|
240
|
+
:root[data-theme="light"] .scrim{background:rgba(20,24,31,.34)}
|
|
241
|
+
.modal{background:var(--surface);border:1px solid var(--border-strong);border-radius:var(--r-lg);
|
|
242
|
+
width:min(520px,100%);max-height:88vh;overflow:auto}
|
|
243
|
+
.modal>header{display:flex;justify-content:space-between;align-items:baseline;gap:var(--s3);
|
|
244
|
+
padding:var(--s5) var(--s5) var(--s3)}
|
|
245
|
+
.modal>header h3{font-size:16px}
|
|
246
|
+
.modal .pad{padding:0 var(--s5) var(--s5);display:flex;flex-direction:column;gap:var(--s3)}
|
|
247
|
+
.modal footer{display:flex;gap:var(--s2);justify-content:flex-end;padding:var(--s3) var(--s5) var(--s5)}
|
|
248
|
+
@media (prefers-reduced-motion:no-preference){
|
|
249
|
+
.scrim{animation:fade .16s var(--ease)}
|
|
250
|
+
.modal{animation:pop .2s var(--ease)}
|
|
251
|
+
@keyframes fade{from{opacity:0}to{opacity:1}}
|
|
252
|
+
@keyframes pop{from{opacity:0;transform:translateY(8px) scale(.99)}to{opacity:1;transform:none}}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/* ---------- onboarding ----------
|
|
256
|
+
A full page, not a dialog. The rail states the whole shape of the setup up
|
|
257
|
+
front — four steps, where you are, what is left — because the thing being
|
|
258
|
+
asked for is a credential, and a wizard that reveals its length one screen at
|
|
259
|
+
a time earns none of the trust that asking for one requires. */
|
|
260
|
+
.onboard{min-height:100vh;display:grid;grid-template-columns:296px minmax(0,1fr)}
|
|
261
|
+
|
|
262
|
+
.wiz-rail{background:var(--surface);border-right:1px solid var(--border);
|
|
263
|
+
padding:var(--s5) var(--s4);display:flex;flex-direction:column;gap:var(--s5)}
|
|
264
|
+
.wiz-rail .brand{padding:0}
|
|
265
|
+
|
|
266
|
+
.wiz-steps{display:flex;flex-direction:column;gap:2px;flex:1}
|
|
267
|
+
.wiz-step{display:flex;gap:var(--s3);align-items:flex-start;padding:10px var(--s2);
|
|
268
|
+
border-radius:var(--r-sm);text-align:left;width:100%}
|
|
269
|
+
.wiz-step .n{width:22px;height:22px;flex:none;border-radius:50%;display:grid;
|
|
270
|
+
place-items:center;font-size:11px;font-weight:650;font-family:var(--mono);
|
|
271
|
+
background:var(--surface-3);color:var(--text-3);border:1px solid transparent;
|
|
272
|
+
transition:background .2s var(--ease),color .2s var(--ease)}
|
|
273
|
+
.wiz-step .t{min-width:0}
|
|
274
|
+
.wiz-step .t b{display:block;font-size:12.5px;font-weight:550;color:var(--text-3)}
|
|
275
|
+
.wiz-step .t span{display:block;font-size:11px;color:var(--text-3);line-height:1.5;
|
|
276
|
+
margin-top:2px}
|
|
277
|
+
.wiz-step.on{background:var(--surface-2)}
|
|
278
|
+
.wiz-step.on .n{background:var(--accent);color:var(--accent-ink)}
|
|
279
|
+
.wiz-step.on .t b{color:var(--text)}
|
|
280
|
+
.wiz-step.done .n{background:var(--ready-w);color:var(--ready-t);
|
|
281
|
+
border-color:var(--ready)}
|
|
282
|
+
.wiz-step.done .t b{color:var(--text-2)}
|
|
283
|
+
|
|
284
|
+
.wiz-foot{display:flex;flex-direction:column;gap:var(--s2);
|
|
285
|
+
border-top:1px solid var(--border);padding-top:var(--s4)}
|
|
286
|
+
.wiz-foot .row{display:flex;gap:6px}
|
|
287
|
+
.wiz-foot .chip{flex:1;white-space:nowrap}
|
|
288
|
+
.wiz-foot .addr{font-size:10.5px;color:var(--text-3);font-family:var(--mono);
|
|
289
|
+
word-break:break-all}
|
|
290
|
+
|
|
291
|
+
.wiz-body .card>footer{display:flex;gap:var(--s2);justify-content:flex-end;
|
|
292
|
+
padding:0 var(--s4) var(--s4);flex-wrap:wrap}
|
|
293
|
+
.wiz-main{padding:var(--s6);overflow-y:auto;min-width:0}
|
|
294
|
+
/* The pane is the content. An inner cap left a wizard's worth of empty gutter on
|
|
295
|
+
any real monitor; the prose keeps its own measure below, which is the only part
|
|
296
|
+
that actually wants to be narrow. */
|
|
297
|
+
.wiz-body{width:100%;max-width:1320px;display:flex;flex-direction:column;gap:var(--s4)}
|
|
298
|
+
.onboard h2{font-size:22px}
|
|
299
|
+
.onboard .lede{color:var(--text-3);font-size:13px;line-height:1.65;max-width:70ch}
|
|
300
|
+
|
|
301
|
+
/* Step 1: the routes in, one card each. A radio dressed as a card — the label
|
|
302
|
+
is the hit target, so the whole card is clickable without any script. */
|
|
303
|
+
.routes{display:flex;flex-direction:column;gap:var(--s2)}
|
|
304
|
+
.route{display:flex;gap:var(--s3);align-items:flex-start;padding:var(--s4);
|
|
305
|
+
border:1px solid var(--border);border-radius:var(--r);background:var(--surface);
|
|
306
|
+
cursor:pointer;transition:border-color .14s var(--ease),background .14s var(--ease)}
|
|
307
|
+
.route:hover{border-color:var(--border-strong);background:var(--surface-2)}
|
|
308
|
+
.route:has(input:checked){border-color:var(--accent);background:var(--accent-soft)}
|
|
309
|
+
.route:has(input:focus-visible){outline:2px solid var(--accent);outline-offset:2px}
|
|
310
|
+
.route input{margin-top:2px;accent-color:var(--accent);flex:none;width:auto}
|
|
311
|
+
.route .rb{display:flex;flex-direction:column;gap:4px;min-width:0;flex:1}
|
|
312
|
+
.route .rt{display:flex;align-items:center;gap:var(--s2);flex-wrap:wrap;
|
|
313
|
+
font-size:13.5px;font-weight:600;color:var(--text)}
|
|
314
|
+
.route .rs{font-size:11.5px;color:var(--text-3);line-height:1.6}
|
|
315
|
+
|
|
316
|
+
/* Step 3's verdict, one line per thing checked. */
|
|
317
|
+
.checks{display:flex;flex-direction:column;gap:var(--s2)}
|
|
318
|
+
.check{display:flex;gap:var(--s2);align-items:flex-start;font-size:12.5px;
|
|
319
|
+
color:var(--text-2)}
|
|
320
|
+
.check i{width:16px;height:16px;flex:none;border-radius:50%;margin-top:1px;
|
|
321
|
+
display:grid;place-items:center;font-size:10px;font-style:normal;font-weight:700}
|
|
322
|
+
.check.ok i{background:var(--ready-w);color:var(--ready-t)}
|
|
323
|
+
.check.no i{background:var(--crit-w);color:var(--crit-t)}
|
|
324
|
+
.check.na i{background:var(--surface-3);color:var(--text-3)}
|
|
325
|
+
|
|
326
|
+
@media(max-width:900px){
|
|
327
|
+
.onboard{grid-template-columns:1fr}
|
|
328
|
+
.wiz-rail{flex-direction:row;align-items:center;gap:var(--s4);flex-wrap:wrap;
|
|
329
|
+
border-right:0;border-bottom:1px solid var(--border);padding:var(--s4)}
|
|
330
|
+
.wiz-steps{flex-direction:row;gap:var(--s2);flex:1 1 100%;overflow-x:auto}
|
|
331
|
+
.wiz-step{padding:8px;flex:none;width:auto}
|
|
332
|
+
.wiz-step .t{display:none}
|
|
333
|
+
.wiz-step.on .t{display:block}
|
|
334
|
+
.wiz-step.on .t span{display:none}
|
|
335
|
+
.wiz-foot{border-top:0;padding-top:0;flex-direction:row;align-items:center;
|
|
336
|
+
flex:1 1 100%}
|
|
337
|
+
.wiz-main{padding:var(--s5) var(--s4)}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
pre.code{background:var(--surface-2);border:1px solid var(--border);border-radius:var(--r-sm);
|
|
341
|
+
padding:13px 15px;font-family:var(--mono);font-size:11.5px;color:var(--text-2);
|
|
342
|
+
overflow-x:auto;margin:0;line-height:1.85;white-space:pre-wrap;word-break:break-all}
|
|
343
|
+
pre.code b{color:var(--ready-t);font-weight:400}
|
|
344
|
+
pre.code .c{color:var(--text-3)}
|
|
345
|
+
.copyrow{display:flex;gap:var(--s2);align-items:center}
|
|
346
|
+
|
|
347
|
+
.guide{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s5);max-width:820px}
|
|
348
|
+
.guide section{display:flex;flex-direction:column;gap:var(--s3)}
|
|
349
|
+
.guide h2{font-size:17px;display:flex;align-items:center;gap:10px}
|
|
350
|
+
.guide h2 .num{width:22px;height:22px;border-radius:7px;background:var(--surface-2);
|
|
351
|
+
color:var(--accent);display:grid;place-items:center;font-size:11px;font-family:var(--mono);flex:none}
|
|
352
|
+
.guide p{color:var(--text-2);font-size:13px;line-height:1.72}
|
|
353
|
+
.guide ul{margin:0;padding-left:18px;color:var(--text-2);font-size:13px;line-height:1.8}
|
|
354
|
+
.guide .card .pad{display:flex;flex-direction:column;gap:var(--s3)}
|
|
355
|
+
|
|
356
|
+
.skel{background:linear-gradient(90deg,var(--surface-2) 25%,var(--surface-3) 37%,var(--surface-2) 63%);
|
|
357
|
+
background-size:400% 100%;border-radius:var(--r-xs);height:11px}
|
|
358
|
+
@media (prefers-reduced-motion:no-preference){
|
|
359
|
+
.skel{animation:shimmer 1.4s ease infinite}
|
|
360
|
+
@keyframes shimmer{0%{background-position:100% 0}100%{background-position:0 0}}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/* ---------- capacity horizon ----------
|
|
364
|
+
Not a .chart: it is a burn-down of *future* windows, so each bar is a bucket and
|
|
365
|
+
the interesting mark is the step where a window resets, not the trend line. */
|
|
366
|
+
.hz{display:flex;align-items:flex-end;gap:3px;height:118px}
|
|
367
|
+
.hz div{flex:1;background:var(--cool);border-radius:3px 3px 0 0;min-height:4px;
|
|
368
|
+
opacity:.62;transition:opacity .14s var(--ease),height .3s var(--ease)}
|
|
369
|
+
.hz div:hover{opacity:1}
|
|
370
|
+
.hz div.step{background:var(--ready);opacity:.85}
|
|
371
|
+
.hzx{display:flex;gap:3px;margin-top:6px}
|
|
372
|
+
.hzx span{flex:1;text-align:center;font-size:9.5px;color:var(--text-3)}
|
|
373
|
+
.hznote{display:flex;gap:var(--s4);flex-wrap:wrap;margin-top:var(--s3);
|
|
374
|
+
font-size:11.5px;color:var(--text-3)}
|
|
375
|
+
.hznote b{color:var(--text);font-weight:500}
|
|
376
|
+
|
|
377
|
+
/* ---------- table cell helpers ---------- */
|
|
378
|
+
.spark{display:inline-block;vertical-align:middle}
|
|
379
|
+
.aid{display:flex;flex-direction:column;gap:2px;min-width:0}
|
|
380
|
+
.aid .nm{color:var(--text);font-weight:550;display:flex;align-items:center;gap:6px}
|
|
381
|
+
.aid .sub{font-size:10.5px;color:var(--text-3);font-family:var(--mono)}
|
|
382
|
+
.acts{display:flex;gap:5px;justify-content:flex-end;flex-wrap:nowrap}
|
|
383
|
+
.addr{font-size:11.5px;color:var(--text-3)}
|
|
384
|
+
.tog{width:34px;height:19px;border-radius:99px;background:var(--surface-3);
|
|
385
|
+
border:1px solid var(--border-strong);position:relative;flex:none;padding:0;
|
|
386
|
+
transition:background .16s var(--ease),border-color .16s var(--ease)}
|
|
387
|
+
.tog i{position:absolute;top:2px;left:2px;width:13px;height:13px;border-radius:50%;
|
|
388
|
+
background:var(--text-3);transition:transform .16s var(--ease),background .16s var(--ease)}
|
|
389
|
+
.tog.on{background:var(--ready-w);border-color:var(--ready)}
|
|
390
|
+
.tog.on i{transform:translateX(15px);background:var(--ready)}
|
|
391
|
+
|
|
392
|
+
/* ---------- form details ----------
|
|
393
|
+
label.f stacks, so anything inline becomes its own row. The caption line needs
|
|
394
|
+
to be one element or the "optional" tag lands under the label. */
|
|
395
|
+
label.f .t,.f .t{display:flex;align-items:baseline;gap:6px;flex-wrap:wrap}
|
|
396
|
+
label.f .t em{font-style:normal;text-transform:none;letter-spacing:normal;
|
|
397
|
+
font-weight:400;color:var(--text-3);font-size:10.5px}
|
|
398
|
+
.tog:disabled{opacity:.38;cursor:default}
|
|
399
|
+
/* A disabled control dispatches no pointer events, so its tooltip has to hang
|
|
400
|
+
on a wrapper with a box of its own. */
|
|
401
|
+
.held{display:inline-block;cursor:help}
|
|
402
|
+
|
|
403
|
+
/* A modal is narrower than a view, so its grids need their own breakpoints or
|
|
404
|
+
every field stacks and the dialog turns into a scroll. */
|
|
405
|
+
/* ---------- Advanced disclosure ----------
|
|
406
|
+
The add-account form is essentials above, admin fields folded away below. The
|
|
407
|
+
summary has to look like a control rather than a heading, or nobody opens it. */
|
|
408
|
+
.adv{border-top:1px solid var(--border);padding-top:var(--s3)}
|
|
409
|
+
.adv>summary{cursor:pointer;list-style:none;display:flex;align-items:center;gap:6px;
|
|
410
|
+
font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;
|
|
411
|
+
color:var(--text-3);user-select:none}
|
|
412
|
+
.adv>summary::-webkit-details-marker{display:none}
|
|
413
|
+
.adv>summary::before{content:"+";font-family:var(--mono);font-size:13px;color:var(--accent)}
|
|
414
|
+
.adv[open]>summary::before{content:"\2212"}
|
|
415
|
+
.adv>summary:hover{color:var(--text)}
|
|
416
|
+
.adv>summary:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:3px}
|
|
417
|
+
.adv[open]>summary{margin-bottom:var(--s3)}
|
|
418
|
+
|
|
419
|
+
.modal{width:min(580px,100%)}
|
|
420
|
+
.modal .grid.g2{grid-template-columns:repeat(auto-fit,minmax(200px,1fr))}
|
|
421
|
+
.modal .grid.g3{grid-template-columns:repeat(auto-fit,minmax(140px,1fr))}
|
|
422
|
+
|
|
423
|
+
/* ============================================================================
|
|
424
|
+
Categorical series palette
|
|
425
|
+
---------------------------------------------------------------------------
|
|
426
|
+
The four state colours above are RESERVED: ready/cooling/critical mean health
|
|
427
|
+
and cache means the cache, so none of them may stand in for "account number 4".
|
|
428
|
+
Series identity therefore needs its own ramp, and this is it — eight fixed
|
|
429
|
+
slots, assigned in order and never cycled.
|
|
430
|
+
|
|
431
|
+
Validated with the data-viz validator against THIS console's surfaces, not a
|
|
432
|
+
generic white/black:
|
|
433
|
+
dark (#111419): lightness, chroma, CVD, normal-vision and 3:1 contrast all pass
|
|
434
|
+
(worst adjacent ΔE 8.4 protan, 19.3 normal)
|
|
435
|
+
light (#FFFFFF): passes, with three slots under 3:1 — which is why every chart
|
|
436
|
+
on the Usage screen ships a totals table beside it. That table
|
|
437
|
+
is the relief the contrast warning obliges, not a nicety.
|
|
438
|
+
Do not re-step these by eye. Re-run the validator.
|
|
439
|
+
========================================================================= */
|
|
440
|
+
:root{
|
|
441
|
+
--series-1:#3987e5; --series-2:#d95926; --series-3:#199e70; --series-4:#c98500;
|
|
442
|
+
--series-5:#d55181; --series-6:#008300; --series-7:#9085e9; --series-8:#e66767;
|
|
443
|
+
--series-other:#616B79;
|
|
444
|
+
}
|
|
445
|
+
:root[data-theme="light"]{
|
|
446
|
+
--series-1:#2a78d6; --series-2:#eb6834; --series-3:#1baf7a; --series-4:#eda100;
|
|
447
|
+
--series-5:#e87ba4; --series-6:#008300; --series-7:#4a3aa7; --series-8:#e34948;
|
|
448
|
+
--series-other:#8892A0;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/* ---------- chart chrome ---------- */
|
|
452
|
+
.plot{position:relative}
|
|
453
|
+
.chart{width:100%;height:auto;display:block;overflow:visible}
|
|
454
|
+
.chart text{fill:var(--text-3);font-family:var(--mono);font-size:9px}
|
|
455
|
+
.chart .grid-line{stroke:var(--border);stroke-width:1;shape-rendering:crispEdges}
|
|
456
|
+
.chart .axis{stroke:var(--border-strong);stroke-width:1;shape-rendering:crispEdges}
|
|
457
|
+
/* The gap between stacked segments is surface-coloured space, never a stroke. */
|
|
458
|
+
.chart .seg{stroke:var(--surface);stroke-width:2;stroke-linejoin:miter}
|
|
459
|
+
.chart .ln{fill:none;stroke-width:2;stroke-linejoin:round;stroke-linecap:round}
|
|
460
|
+
.chart .dot{stroke:var(--surface);stroke-width:2}
|
|
461
|
+
.chart .hit{fill:transparent;cursor:crosshair}
|
|
462
|
+
.chart .cross{stroke:var(--border-strong);stroke-width:1;pointer-events:none}
|
|
463
|
+
|
|
464
|
+
.legend{display:flex;gap:var(--s4);flex-wrap:wrap;margin-top:var(--s3);font-size:11px;
|
|
465
|
+
color:var(--text-2)}
|
|
466
|
+
.legend span{display:flex;align-items:center;gap:6px}
|
|
467
|
+
.legend i{width:9px;height:9px;border-radius:3px;flex:none}
|
|
468
|
+
|
|
469
|
+
/* Tooltips enhance; they never gate a value — the totals table carries them all. */
|
|
470
|
+
.tip{position:absolute;pointer-events:none;z-index:20;background:var(--surface-2);
|
|
471
|
+
border:1px solid var(--border-strong);border-radius:var(--r-sm);padding:8px 10px;
|
|
472
|
+
font-size:11px;color:var(--text-2);white-space:nowrap;
|
|
473
|
+
box-shadow:0 6px 20px rgba(0,0,0,.28)}
|
|
474
|
+
.tip b{color:var(--text);font-weight:600;display:block;margin-bottom:4px;font-size:11.5px}
|
|
475
|
+
.tip .tr{display:flex;align-items:center;gap:7px;justify-content:space-between;gap:14px}
|
|
476
|
+
.tip .tr i{width:8px;height:8px;border-radius:2px;flex:none}
|
|
477
|
+
.tip .tr .nm{display:flex;align-items:center;gap:6px}
|
|
478
|
+
.tip .tr .v{font-family:var(--mono);color:var(--text)}
|
|
479
|
+
|
|
480
|
+
.filters{display:flex;gap:var(--s3);align-items:center;flex-wrap:wrap;
|
|
481
|
+
padding-bottom:var(--s4)}
|
|
482
|
+
.seg-ctl{display:inline-flex;border:1px solid var(--border);border-radius:var(--r-sm);
|
|
483
|
+
overflow:hidden;background:var(--surface)}
|
|
484
|
+
.seg-ctl button{padding:6px 12px;font-size:12px;color:var(--text-3);
|
|
485
|
+
border-right:1px solid var(--border);transition:background .14s var(--ease),color .14s var(--ease)}
|
|
486
|
+
.seg-ctl button:last-child{border-right:0}
|
|
487
|
+
.seg-ctl button:hover{background:var(--surface-2);color:var(--text)}
|
|
488
|
+
.seg-ctl button.on{background:var(--accent-soft);color:var(--accent);font-weight:600}
|
|
489
|
+
/* Refetch holds the previous render rather than flashing a skeleton. */
|
|
490
|
+
.stale{opacity:.45;transition:opacity .18s var(--ease)}
|
|
491
|
+
|
|
492
|
+
/* ============================================================================
|
|
493
|
+
The mark
|
|
494
|
+
---------------------------------------------------------------------------
|
|
495
|
+
A sealed, layered pot: dum biryani, and a pool of accounts, in one shape. The
|
|
496
|
+
vessel takes currentColor so it sits on any surface; the lid and the layers
|
|
497
|
+
are the accent. Drawn in strokes at a 32 viewBox because it has to survive a
|
|
498
|
+
16px favicon — an emoji did not, and neither did anything with more detail.
|
|
499
|
+
========================================================================= */
|
|
500
|
+
.mark{width:26px;height:26px;flex:none;color:var(--text-2)}
|
|
501
|
+
.brand .mark{color:var(--text-2)}
|
|
502
|
+
.mark-lockup{display:flex;align-items:center;gap:9px}
|
|
503
|
+
.mark-lockup b{font-size:15px;font-weight:650;letter-spacing:-.02em}
|
|
504
|
+
.mark-lockup .mark{width:28px;height:28px}
|
|
505
|
+
|
|
506
|
+
/* ============================================================================
|
|
507
|
+
Landing page
|
|
508
|
+
---------------------------------------------------------------------------
|
|
509
|
+
The only thing an unauthenticated visitor sees, so it has to say what this is
|
|
510
|
+
before it asks for anything. Two columns on a desktop — the pitch, and the one
|
|
511
|
+
field that gets you in — stacking on a phone with the sign-in card first,
|
|
512
|
+
because someone on a phone already knows what they came for.
|
|
513
|
+
========================================================================= */
|
|
514
|
+
.lp{min-height:100vh;display:flex;flex-direction:column;
|
|
515
|
+
background:
|
|
516
|
+
radial-gradient(900px 420px at 82% -8%, var(--accent-soft), transparent 62%),
|
|
517
|
+
radial-gradient(700px 380px at 8% 108%, var(--surface-2), transparent 60%),
|
|
518
|
+
var(--bg)}
|
|
519
|
+
.lp-top{display:flex;align-items:center;justify-content:space-between;gap:var(--s4);
|
|
520
|
+
padding:var(--s5) var(--s6);flex-wrap:wrap}
|
|
521
|
+
.lp-addr{font-size:11.5px;color:var(--text-3)}
|
|
522
|
+
.lp-addr:empty{display:none}
|
|
523
|
+
|
|
524
|
+
.lp-main{flex:1;display:grid;grid-template-columns:minmax(0,1.15fr) minmax(340px,.85fr);
|
|
525
|
+
gap:var(--s7);align-items:center;
|
|
526
|
+
padding:var(--s5) var(--s6) var(--s7);max-width:1180px;width:100%;margin:0 auto}
|
|
527
|
+
@media(max-width:940px){
|
|
528
|
+
.lp-main{grid-template-columns:minmax(0,1fr);gap:var(--s6);align-items:start}
|
|
529
|
+
.lp-auth{order:-1}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
.lp-eyebrow{display:inline-block;font-size:11px;font-weight:650;letter-spacing:.1em;
|
|
533
|
+
text-transform:uppercase;color:var(--accent);
|
|
534
|
+
background:var(--accent-soft);border-radius:99px;padding:5px 11px;margin-bottom:var(--s4)}
|
|
535
|
+
.lp-hero h1{font-size:clamp(28px,4.4vw,42px);line-height:1.12;letter-spacing:-.03em;
|
|
536
|
+
margin-bottom:var(--s4)}
|
|
537
|
+
.lp-lede{color:var(--text-2);font-size:15px;line-height:1.65;max-width:52ch}
|
|
538
|
+
|
|
539
|
+
.lp-points{list-style:none;margin:var(--s6) 0 0;padding:0;
|
|
540
|
+
display:flex;flex-direction:column;gap:var(--s4)}
|
|
541
|
+
.lp-points li{display:flex;gap:13px;align-items:flex-start}
|
|
542
|
+
.lp-points svg{width:19px;height:19px;flex:none;color:var(--accent);margin-top:2px}
|
|
543
|
+
.lp-points b{display:block;font-size:13.5px;font-weight:600;margin-bottom:3px}
|
|
544
|
+
.lp-points span{color:var(--text-3);font-size:12.5px;line-height:1.6;display:block}
|
|
545
|
+
|
|
546
|
+
.lp-auth .card{background:var(--surface)}
|
|
547
|
+
.lp-auth h2{font-size:17px}
|
|
548
|
+
.lp-sub{color:var(--text-3);font-size:12.5px;margin-top:5px;line-height:1.55}
|
|
549
|
+
.lp-go{width:100%;justify-content:center;padding:10px}
|
|
550
|
+
|
|
551
|
+
.lp-help{border-top:1px solid var(--border);padding-top:var(--s3);margin-top:var(--s1)}
|
|
552
|
+
.lp-help summary{cursor:pointer;font-size:12px;color:var(--text-3);
|
|
553
|
+
list-style:none;display:flex;align-items:center;gap:6px}
|
|
554
|
+
.lp-help summary::-webkit-details-marker{display:none}
|
|
555
|
+
.lp-help summary::before{content:"+";font-family:var(--mono);color:var(--accent)}
|
|
556
|
+
.lp-help[open] summary::before{content:"−"}
|
|
557
|
+
.lp-help summary:hover{color:var(--text)}
|
|
558
|
+
.lp-help p{color:var(--text-3);font-size:12px;line-height:1.65;margin:var(--s3) 0}
|
|
559
|
+
|
|
560
|
+
.lp-foot{display:flex;gap:10px;flex-wrap:wrap;justify-content:center;
|
|
561
|
+
padding:var(--s4) var(--s6) var(--s5);border-top:1px solid var(--border);
|
|
562
|
+
font-size:11.5px;color:var(--text-3)}
|
|
563
|
+
.lp-dot{opacity:.5}
|
|
564
|
+
@media(max-width:640px){.lp-top,.lp-main,.lp-foot{padding-left:var(--s4);padding-right:var(--s4)}}
|
|
565
|
+
.lp-endpoint{margin-top:var(--s6);max-width:52ch}
|
|
566
|
+
.lp-endpoint-label{display:block;font-size:10.5px;font-weight:650;letter-spacing:.09em;
|
|
567
|
+
text-transform:uppercase;color:var(--text-3);margin-bottom:var(--s2)}
|
|
568
|
+
.lp-endpoint .code{background:var(--surface);font-size:11.5px;line-height:1.9}
|
|
569
|
+
|
|
570
|
+
/* ---- token source picker ---------------------------------------------------
|
|
571
|
+
Three ways a subscription account gets its token. A segmented control rather
|
|
572
|
+
than a dropdown because the choice carries a trade-off per option, and a
|
|
573
|
+
trade-off you have to open a menu to read is one nobody reads. */
|
|
574
|
+
.seg{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:var(--s2)}
|
|
575
|
+
.segb{text-align:left;border:1px solid var(--border);border-radius:var(--r-sm);
|
|
576
|
+
background:var(--surface-2);color:var(--text-2);padding:9px 11px;cursor:pointer;
|
|
577
|
+
font:inherit;font-size:12px;font-weight:600;transition:border-color .12s var(--ease)}
|
|
578
|
+
.segb:hover{border-color:var(--border-strong);color:var(--text)}
|
|
579
|
+
.segb.on{border-color:var(--accent);background:var(--accent-soft);color:var(--accent)}
|
|
580
|
+
.segt{display:block;font-weight:400;font-size:10.5px;color:var(--text-3);margin-top:3px}
|
|
581
|
+
.segb.on .segt{color:var(--accent)}
|
|
582
|
+
|
|
583
|
+
/* One found credentials file. The path is the point — picking the wrong profile
|
|
584
|
+
is the failure this whole panel exists to prevent — so it leads, in mono. */
|
|
585
|
+
.pick{display:flex;gap:var(--s2);align-items:flex-start;padding:9px 11px;cursor:pointer;
|
|
586
|
+
border:1px solid var(--border);border-radius:var(--r-sm);background:var(--surface-2)}
|
|
587
|
+
.pick:hover{border-color:var(--border-strong)}
|
|
588
|
+
/* Never full width: these sit inside form blocks whose inputs stretch, and a
|
|
589
|
+
checkbox stretched to the row pushes its own label off the end. */
|
|
590
|
+
.pick input{margin-top:3px;accent-color:var(--accent);width:auto;flex:none}
|
|
591
|
+
.pickb{display:flex;flex-direction:column;gap:3px;min-width:0}
|
|
592
|
+
.pickt{display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-family:var(--mono);
|
|
593
|
+
font-size:11.5px;color:var(--text);word-break:break-all}
|
|
594
|
+
.picks{font-size:11px;color:var(--text-3);line-height:1.5}
|
|
595
|
+
|
|
596
|
+
/* ---- Efficiency ----------------------------------------------------------- */
|
|
597
|
+
/* A verdict that needs acting on. Colour only, never colour alone: every one of
|
|
598
|
+
these sits beside the sentence saying what is wrong. */
|
|
599
|
+
.warn{color:var(--crit-t)}
|
|
600
|
+
|
|
601
|
+
/* The pace track carries a second mark — where spending *should* be by now — so
|
|
602
|
+
unlike the headroom meters it has to position a child and must not clip it.
|
|
603
|
+
Scope names are account ids rather than "req"/"in"/"out", so the label column
|
|
604
|
+
is wider than the 20px those get. */
|
|
605
|
+
.pace .track{position:relative;overflow:visible}
|
|
606
|
+
.pace .k{width:auto;min-width:72px;font-size:10.5px}
|
|
607
|
+
.pace .v{width:48px}
|
|
608
|
+
/* The target mark. Taller than the track so it reads as a threshold rather than
|
|
609
|
+
as part of the fill. */
|
|
610
|
+
.pace .track b{position:absolute;top:-3px;bottom:-3px;border-radius:1px}
|