clauddy 1.14.0 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/auth.js +27 -2
- package/main.js +15 -4
- package/package.json +1 -1
- package/renderer/index.html +30 -66
- package/renderer/pet.js +69 -5
- package/renderer/style.css +194 -61
package/auth.js
CHANGED
|
@@ -152,6 +152,32 @@ function win(o) {
|
|
|
152
152
|
: null
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
// Weekly limits scoped to one model (e.g. "Fable" on Max) live in the `limits`
|
|
156
|
+
// array. The older seven_day_<model> windows are kept as a fallback for plans
|
|
157
|
+
// that still report them that way.
|
|
158
|
+
function scopedWeeks(j) {
|
|
159
|
+
const out = []
|
|
160
|
+
for (const l of Array.isArray(j.limits) ? j.limits : []) {
|
|
161
|
+
if (l?.kind !== 'weekly_scoped' || typeof l.percent !== 'number') continue
|
|
162
|
+
const label = l.scope?.model?.display_name
|
|
163
|
+
if (!label) continue
|
|
164
|
+
out.push({
|
|
165
|
+
label,
|
|
166
|
+
pct: l.percent,
|
|
167
|
+
resetMs: l.resets_at ? Date.parse(l.resets_at) - Date.now() : null,
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
if (out.length) return out
|
|
171
|
+
for (const [key, label] of [
|
|
172
|
+
['seven_day_opus', 'Opus'],
|
|
173
|
+
['seven_day_sonnet', 'Sonnet'],
|
|
174
|
+
]) {
|
|
175
|
+
const w = win(j[key])
|
|
176
|
+
if (w) out.push({ label, ...w })
|
|
177
|
+
}
|
|
178
|
+
return out
|
|
179
|
+
}
|
|
180
|
+
|
|
155
181
|
// Step 3: fetch the authoritative usage
|
|
156
182
|
async function fetchUsage() {
|
|
157
183
|
const token = await validToken()
|
|
@@ -173,8 +199,7 @@ async function fetchUsage() {
|
|
|
173
199
|
return {
|
|
174
200
|
session: win(j.five_hour) || { pct: 0, resetMs: null },
|
|
175
201
|
week: win(j.seven_day) || { pct: 0, resetMs: null },
|
|
176
|
-
|
|
177
|
-
opus: win(j.seven_day_opus),
|
|
202
|
+
scoped: scopedWeeks(j),
|
|
178
203
|
}
|
|
179
204
|
}
|
|
180
205
|
|
package/main.js
CHANGED
|
@@ -95,12 +95,22 @@ function loadConfig() {
|
|
|
95
95
|
// native notification when usage crosses a threshold
|
|
96
96
|
const armed = new Set()
|
|
97
97
|
function checkAlerts(config, d) {
|
|
98
|
-
|
|
99
|
-
const ths = config.alertThresholds || [80, 95]
|
|
100
|
-
const scopes = [
|
|
98
|
+
alertScopes(config, [
|
|
101
99
|
['session', d.session.pct],
|
|
102
100
|
['weekly usage', d.week.pct],
|
|
103
|
-
]
|
|
101
|
+
])
|
|
102
|
+
}
|
|
103
|
+
// per-model weekly limits only exist on the account side, so they're checked
|
|
104
|
+
// off the OAuth poll rather than the local tick
|
|
105
|
+
function checkScopedAlerts(config, u) {
|
|
106
|
+
alertScopes(
|
|
107
|
+
config,
|
|
108
|
+
(u?.scoped || []).map((s) => [`${s.label} weekly usage`, s.pct]),
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
function alertScopes(config, scopes) {
|
|
112
|
+
if (!config.alerts || !Notification.isSupported()) return
|
|
113
|
+
const ths = config.alertThresholds || [80, 95]
|
|
104
114
|
for (const [name, pct] of scopes) {
|
|
105
115
|
for (const t of ths) {
|
|
106
116
|
const key = `${name}:${t}`
|
|
@@ -361,6 +371,7 @@ function updateTray() {
|
|
|
361
371
|
function pushRealUsage(u) {
|
|
362
372
|
sessionPct = u?.session ? u.session.pct : null
|
|
363
373
|
updateTray()
|
|
374
|
+
checkScopedAlerts(config, u)
|
|
364
375
|
if (win && !win.isDestroyed()) win.webContents.send('real-usage', u)
|
|
365
376
|
}
|
|
366
377
|
|
package/package.json
CHANGED
package/renderer/index.html
CHANGED
|
@@ -240,6 +240,7 @@
|
|
|
240
240
|
<div class="divider"></div>
|
|
241
241
|
|
|
242
242
|
<!-- session + weekly (real usage; needs login) -->
|
|
243
|
+
<div id="home-success" hidden>✓ Logged in — your real usage is live</div>
|
|
243
244
|
<div id="limits">
|
|
244
245
|
<div id="limits-meters">
|
|
245
246
|
<div class="meter">
|
|
@@ -253,6 +254,8 @@
|
|
|
253
254
|
<div class="track"><div class="fill week" id="week-fill"></div></div>
|
|
254
255
|
<div class="sub" id="week-sub">—</div>
|
|
255
256
|
</div>
|
|
257
|
+
<!-- per-model weekly limits (e.g. Fable), one meter each -->
|
|
258
|
+
<div id="scoped-meters"></div>
|
|
256
259
|
</div>
|
|
257
260
|
<button id="limits-connect">
|
|
258
261
|
<svg class="lc-ico" viewBox="0 0 24 24" aria-hidden="true">
|
|
@@ -301,91 +304,52 @@
|
|
|
301
304
|
|
|
302
305
|
<!-- settings panel -->
|
|
303
306
|
<div id="settings">
|
|
304
|
-
<div class="set-title">Settings</div>
|
|
305
|
-
|
|
306
307
|
<div id="account">
|
|
307
308
|
<div id="acc-disconnected">
|
|
308
|
-
<div class="set-hint">Connect your account to show your real usage
|
|
309
|
+
<div class="set-hint set-hint-left">Connect your Claude account to show your real usage.</div>
|
|
309
310
|
<button id="acc-connect" class="acc-btn">Log in with browser</button>
|
|
310
311
|
<div id="acc-paste">
|
|
311
|
-
<div class="set-hint">
|
|
312
|
+
<div class="set-hint set-hint-left">Paste the code from the browser page:</div>
|
|
312
313
|
<input id="acc-code" type="text" placeholder="authentication code" />
|
|
313
314
|
<button id="acc-confirm" class="acc-btn">Connect</button>
|
|
314
315
|
<div id="acc-msg"></div>
|
|
315
316
|
</div>
|
|
316
317
|
</div>
|
|
317
318
|
<div id="acc-connected">
|
|
318
|
-
<span class="acc-ok">● Connected
|
|
319
|
+
<span class="acc-ok" id="acc-ok">● Connected</span>
|
|
319
320
|
<button id="acc-logout" class="acc-link">Disconnect</button>
|
|
320
321
|
</div>
|
|
321
322
|
</div>
|
|
322
323
|
|
|
323
|
-
<div
|
|
324
|
-
<div class="set-
|
|
325
|
-
|
|
326
|
-
<
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
</div>
|
|
330
|
-
|
|
331
|
-
<div class="set-section">
|
|
332
|
-
<div class="set-sec-title">Zoom</div>
|
|
333
|
-
<div class="set-thresholds">
|
|
334
|
-
<span class="thr">
|
|
335
|
-
<span class="thr-cap">size</span>
|
|
336
|
-
<span class="num">
|
|
337
|
-
<input id="set-zoom" type="number" min="100" max="200" step="25" />
|
|
338
|
-
<span class="num-spin">
|
|
339
|
-
<button type="button" class="num-btn" data-for="set-zoom" data-step="25">▲</button>
|
|
340
|
-
<button type="button" class="num-btn" data-for="set-zoom" data-step="-25">▼</button>
|
|
341
|
-
</span>
|
|
342
|
-
</span>
|
|
324
|
+
<div id="set-rows">
|
|
325
|
+
<div class="set-row">
|
|
326
|
+
<span class="set-row-label">Display</span>
|
|
327
|
+
<span class="seg" id="set-mode">
|
|
328
|
+
<button type="button" class="seg-btn" data-mode="floating">Floating pet</button>
|
|
329
|
+
<button type="button" class="seg-btn" data-mode="menubar">Menu bar</button>
|
|
343
330
|
</span>
|
|
344
331
|
</div>
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
<div class="set-section">
|
|
348
|
-
<div class="set-sec-title">
|
|
349
|
-
Alerts
|
|
332
|
+
<div class="set-row">
|
|
333
|
+
<span class="set-row-label">Alerts</span>
|
|
350
334
|
<input id="set-alerts" class="set-toggle" type="checkbox" aria-label="Enable alerts" />
|
|
351
335
|
</div>
|
|
352
|
-
<div class="set-
|
|
353
|
-
<
|
|
354
|
-
<span class="
|
|
355
|
-
<span class="num">
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
</span>
|
|
362
|
-
</span>
|
|
363
|
-
<span class="thr">
|
|
364
|
-
<span class="thr-cap">near limit</span>
|
|
365
|
-
<span class="num">
|
|
366
|
-
<input id="set-t2" type="number" min="1" max="100" />
|
|
367
|
-
<span class="num-spin">
|
|
368
|
-
<button type="button" class="num-btn" data-for="set-t2" data-step="1">▲</button>
|
|
369
|
-
<button type="button" class="num-btn" data-for="set-t2" data-step="-1">▼</button>
|
|
370
|
-
</span>
|
|
371
|
-
</span>
|
|
372
|
-
</span>
|
|
336
|
+
<div class="set-subgroup">
|
|
337
|
+
<div class="set-row set-sub">
|
|
338
|
+
<span class="set-row-label sub-label">heads-up</span>
|
|
339
|
+
<span class="num"><button type="button" class="num-btn" data-for="set-t1" data-step="-1">−</button><span class="num-val"><input id="set-t1" type="number" min="1" max="100" step="1" /><i>%</i></span><button type="button" class="num-btn" data-for="set-t1" data-step="1">+</button></span>
|
|
340
|
+
</div>
|
|
341
|
+
<div class="set-row set-sub">
|
|
342
|
+
<span class="set-row-label sub-label">near limit</span>
|
|
343
|
+
<span class="num"><button type="button" class="num-btn" data-for="set-t2" data-step="-1">−</button><span class="num-val"><input id="set-t2" type="number" min="1" max="100" step="1" /><i>%</i></span><button type="button" class="num-btn" data-for="set-t2" data-step="1">+</button></span>
|
|
344
|
+
</div>
|
|
373
345
|
</div>
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
<div class="set-
|
|
379
|
-
<span class="
|
|
380
|
-
|
|
381
|
-
<span class="num">
|
|
382
|
-
<input id="set-fire" type="number" min="1" max="99" />
|
|
383
|
-
<span class="num-spin">
|
|
384
|
-
<button type="button" class="num-btn" data-for="set-fire" data-step="1">▲</button>
|
|
385
|
-
<button type="button" class="num-btn" data-for="set-fire" data-step="-1">▼</button>
|
|
386
|
-
</span>
|
|
387
|
-
</span>
|
|
388
|
-
</span>
|
|
346
|
+
<div class="set-row">
|
|
347
|
+
<span class="set-row-label">Catch fire<i class="set-row-hint">at this session %</i></span>
|
|
348
|
+
<span class="num"><button type="button" class="num-btn" data-for="set-fire" data-step="-1">−</button><span class="num-val"><input id="set-fire" type="number" min="1" max="99" step="1" /><i>%</i></span><button type="button" class="num-btn" data-for="set-fire" data-step="1">+</button></span>
|
|
349
|
+
</div>
|
|
350
|
+
<div class="set-row">
|
|
351
|
+
<span class="set-row-label">Zoom</span>
|
|
352
|
+
<span class="num"><button type="button" class="num-btn" data-for="set-zoom" data-step="-25">−</button><span class="num-val"><input id="set-zoom" type="number" min="100" max="200" step="25" /><i>%</i></span><button type="button" class="num-btn" data-for="set-zoom" data-step="25">+</button></span>
|
|
389
353
|
</div>
|
|
390
354
|
</div>
|
|
391
355
|
|
package/renderer/pet.js
CHANGED
|
@@ -89,9 +89,9 @@ const SPRITE = [
|
|
|
89
89
|
}
|
|
90
90
|
// clouds (blocky, dark gray)
|
|
91
91
|
const cloud = (x, y, b, m, t) => {
|
|
92
|
-
add('rect', { x: x + 12, y: y, width: t, height: 9, fill: '#3a3a3a' })
|
|
93
|
-
add('rect', { x: x + 5, y: y + 7, width: m, height: 9, fill: '#3a3a3a' })
|
|
94
|
-
add('rect', { x: x, y: y + 14, width: b, height: 10, fill: '#3a3a3a' })
|
|
92
|
+
add('rect', { x: x + 12, y: y, width: t, height: 9, fill: '#3a3a3a', class: 'cloud' })
|
|
93
|
+
add('rect', { x: x + 5, y: y + 7, width: m, height: 9, fill: '#3a3a3a', class: 'cloud' })
|
|
94
|
+
add('rect', { x: x, y: y + 14, width: b, height: 10, fill: '#3a3a3a', class: 'cloud' })
|
|
95
95
|
}
|
|
96
96
|
cloud(16, 10, 58, 42, 22)
|
|
97
97
|
cloud(120, 50, 40, 28, 15)
|
|
@@ -404,6 +404,27 @@ function fitNames(box) {
|
|
|
404
404
|
box.style.setProperty('--name-w', `${w}px`)
|
|
405
405
|
}
|
|
406
406
|
|
|
407
|
+
// per-model weekly limits (e.g. "Fable" on Max) — one meter each, in the same
|
|
408
|
+
// shape as the all-models one. The token count pairs the limit with the local
|
|
409
|
+
// log totals for that model family (a "Fable" limit covers every Fable row).
|
|
410
|
+
function renderScoped(list, byModel) {
|
|
411
|
+
const box = el('scoped-meters')
|
|
412
|
+
box.innerHTML = ''
|
|
413
|
+
for (const s of list) {
|
|
414
|
+
const key = s.label.toLowerCase()
|
|
415
|
+
const tokens = byModel
|
|
416
|
+
.filter((m) => m.label.toLowerCase().startsWith(key))
|
|
417
|
+
.reduce((n, m) => n + m.tokens, 0)
|
|
418
|
+
const m = document.createElement('div')
|
|
419
|
+
m.className = 'meter'
|
|
420
|
+
m.innerHTML =
|
|
421
|
+
`<div class="meter-top"><span>weekly · ${esc(key)}</span><span>${Math.round(s.pct)}%</span></div>` +
|
|
422
|
+
`<div class="track"><div class="fill week${s.pct >= 80 ? ' high' : ''}" style="width:${s.pct}%"></div></div>` +
|
|
423
|
+
`<div class="sub">${s.resetMs != null ? `resets in ${fmtReset(s.resetMs)} · ` : ''}${fmtTokens(tokens)} tokens</div>`
|
|
424
|
+
box.appendChild(m)
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
407
428
|
// by model (7 days)
|
|
408
429
|
function renderModels(list) {
|
|
409
430
|
renderBars('bymodel-list', list, 4)
|
|
@@ -571,6 +592,7 @@ function render(d) {
|
|
|
571
592
|
? `resets in ${fmtReset(wkReset)} · ${fmtTokens(d.week.tokens)} tokens`
|
|
572
593
|
: `${fmtTokens(d.week.tokens)} tokens · last 7 days`
|
|
573
594
|
|
|
595
|
+
renderScoped(liveOn ? realUsage.scoped || [] : [], d.byModel || [])
|
|
574
596
|
renderModels(d.byModel || [])
|
|
575
597
|
renderProjects(d.byProject || [])
|
|
576
598
|
renderHeat(d.days30 || [])
|
|
@@ -655,6 +677,10 @@ window.api.onAuthState((s) => {
|
|
|
655
677
|
|
|
656
678
|
// logged-in account chip (email + plan) shown top-left when connected
|
|
657
679
|
function showProfile(p) {
|
|
680
|
+
// the settings line says who is connected, not just that someone is
|
|
681
|
+
el('acc-ok').textContent = p?.email
|
|
682
|
+
? `● ${p.email}${p.plan ? ` · ${p.plan}` : ''}`
|
|
683
|
+
: '● Connected'
|
|
658
684
|
const chip = el('account-chip')
|
|
659
685
|
const mini = el('mini-acct')
|
|
660
686
|
if (!p?.email) {
|
|
@@ -681,11 +707,23 @@ function setPlan(node, plan) {
|
|
|
681
707
|
}
|
|
682
708
|
}
|
|
683
709
|
window.api.onProfile(showProfile)
|
|
710
|
+
let successTimer = null
|
|
684
711
|
window.api.onAuthResult((r) => {
|
|
685
712
|
if (r?.ok) {
|
|
686
713
|
el('acc-msg').textContent = ''
|
|
687
714
|
el('acc-code').value = ''
|
|
688
715
|
el('acc-paste').classList.remove('show')
|
|
716
|
+
// logging in is done: land back on the home panel, where the fresh live
|
|
717
|
+
// meters show up under a short-lived cheer — and the pet throws confetti
|
|
718
|
+
document.body.classList.remove('settings-open')
|
|
719
|
+
clearSaveDirty()
|
|
720
|
+
el('home-success').hidden = false
|
|
721
|
+
clearTimeout(successTimer)
|
|
722
|
+
successTimer = setTimeout(() => {
|
|
723
|
+
el('home-success').hidden = true
|
|
724
|
+
fitSize()
|
|
725
|
+
}, 6000)
|
|
726
|
+
celebrate()
|
|
689
727
|
} else {
|
|
690
728
|
const e = r?.error || ''
|
|
691
729
|
el('acc-msg').textContent = /429|rate_limit/i.test(e)
|
|
@@ -748,6 +786,10 @@ el('acc-connect').addEventListener('click', () => {
|
|
|
748
786
|
el('acc-paste').classList.add('show') // reveal the code field
|
|
749
787
|
fitSize()
|
|
750
788
|
})
|
|
789
|
+
// the Connect button only lights up once there's a code to submit
|
|
790
|
+
el('acc-code').addEventListener('input', () => {
|
|
791
|
+
el('acc-confirm').classList.toggle('ready', !!el('acc-code').value.trim())
|
|
792
|
+
})
|
|
751
793
|
el('acc-confirm').addEventListener('click', () => {
|
|
752
794
|
const code = el('acc-code').value.trim()
|
|
753
795
|
if (!code) return
|
|
@@ -826,16 +868,34 @@ function snapshotSettings() {
|
|
|
826
868
|
}
|
|
827
869
|
function refreshSaveDirty() {
|
|
828
870
|
if (settingsBaseline == null) return
|
|
829
|
-
|
|
871
|
+
const dirty = snapshotSettings() !== settingsBaseline
|
|
872
|
+
const b = el('set-save')
|
|
873
|
+
b.classList.toggle('dirty', dirty)
|
|
874
|
+
b.disabled = !dirty // nothing to save, nothing to click
|
|
830
875
|
}
|
|
831
876
|
function clearSaveDirty() {
|
|
832
877
|
settingsBaseline = snapshotSettings()
|
|
833
|
-
el('set-save')
|
|
878
|
+
const b = el('set-save')
|
|
879
|
+
b.classList.remove('dirty')
|
|
880
|
+
b.disabled = true
|
|
881
|
+
}
|
|
882
|
+
function reflectAlertsOn() {
|
|
883
|
+
el('set-rows').classList.toggle('alerts-off', !el('set-alerts').checked)
|
|
884
|
+
}
|
|
885
|
+
// zoom applies as you step it, so the widget shows the size right away;
|
|
886
|
+
// Cancel puts the saved value back
|
|
887
|
+
function previewZoom() {
|
|
888
|
+
const v = Number.parseFloat(el('set-zoom').value)
|
|
889
|
+
if (v >= 100 && v <= 200) {
|
|
890
|
+
applyZoom(v)
|
|
891
|
+
fitSize()
|
|
892
|
+
}
|
|
834
893
|
}
|
|
835
894
|
function populateSettings() {
|
|
836
895
|
const c = currentConfig || {}
|
|
837
896
|
setModeUI(c.mode)
|
|
838
897
|
el('set-alerts').checked = c.alerts !== false
|
|
898
|
+
reflectAlertsOn()
|
|
839
899
|
const th = c.alertThresholds || [80, 95]
|
|
840
900
|
el('set-t1').value = th[0] != null ? th[0] : 80
|
|
841
901
|
el('set-t2').value = th[1] != null ? th[1] : 95
|
|
@@ -861,6 +921,7 @@ for (const b of document.querySelectorAll('.num-btn')) {
|
|
|
861
921
|
const next = (Number.parseInt(input.value, 10) || 0) + Number(b.dataset.step)
|
|
862
922
|
input.value = Math.min(max, Math.max(min, next))
|
|
863
923
|
refreshSaveDirty() // steppers change the value without an 'input' event
|
|
924
|
+
if (input.id === 'set-zoom') previewZoom()
|
|
864
925
|
})
|
|
865
926
|
}
|
|
866
927
|
// light up Save whenever an editable field changes
|
|
@@ -868,6 +929,8 @@ for (const id of ['set-alerts', 'set-t1', 'set-t2', 'set-fire', 'set-zoom']) {
|
|
|
868
929
|
el(id).addEventListener('input', refreshSaveDirty)
|
|
869
930
|
el(id).addEventListener('change', refreshSaveDirty)
|
|
870
931
|
}
|
|
932
|
+
el('set-alerts').addEventListener('change', reflectAlertsOn)
|
|
933
|
+
el('set-zoom').addEventListener('input', previewZoom)
|
|
871
934
|
// display-mode segmented control (floating pet | menu bar)
|
|
872
935
|
for (const b of document.querySelectorAll('#set-mode .seg-btn')) {
|
|
873
936
|
b.addEventListener('click', () => {
|
|
@@ -878,6 +941,7 @@ for (const b of document.querySelectorAll('#set-mode .seg-btn')) {
|
|
|
878
941
|
el('set-cancel').addEventListener('click', () => {
|
|
879
942
|
document.body.classList.remove('settings-open')
|
|
880
943
|
clearSaveDirty()
|
|
944
|
+
applyZoom(currentConfig?.zoom != null ? currentConfig.zoom : 100) // undo the preview
|
|
881
945
|
fitSize()
|
|
882
946
|
})
|
|
883
947
|
el('set-save').addEventListener('click', () => {
|
package/renderer/style.css
CHANGED
|
@@ -221,13 +221,17 @@ body.settings-open #mini {
|
|
|
221
221
|
}
|
|
222
222
|
body.settings-open #settings {
|
|
223
223
|
display: block;
|
|
224
|
+
animation: set-in 0.18s ease;
|
|
224
225
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
226
|
+
@keyframes set-in {
|
|
227
|
+
from {
|
|
228
|
+
opacity: 0;
|
|
229
|
+
transform: translateY(4px);
|
|
230
|
+
}
|
|
231
|
+
to {
|
|
232
|
+
opacity: 1;
|
|
233
|
+
transform: none;
|
|
234
|
+
}
|
|
231
235
|
}
|
|
232
236
|
.set-hint {
|
|
233
237
|
font-size: 9px;
|
|
@@ -302,94 +306,168 @@ input[type="number"]::-webkit-outer-spin-button {
|
|
|
302
306
|
body.is-menubar #min {
|
|
303
307
|
display: none;
|
|
304
308
|
}
|
|
305
|
-
/* settings
|
|
306
|
-
|
|
307
|
-
.
|
|
308
|
-
|
|
309
|
+
/* settings rows: label on the left, control on the right — one setting per
|
|
310
|
+
line so the screen stays short. Alert thresholds indent under their toggle
|
|
311
|
+
and dim while alerts are off. */
|
|
312
|
+
#set-rows {
|
|
313
|
+
margin-bottom: 10px; /* #account above already draws the divider */
|
|
309
314
|
}
|
|
310
|
-
.set-
|
|
311
|
-
border-top: 0.5px solid var(--hair);
|
|
312
|
-
padding-top: 10px;
|
|
313
|
-
}
|
|
314
|
-
.set-sec-title {
|
|
315
|
+
.set-row {
|
|
315
316
|
display: flex;
|
|
316
317
|
align-items: center;
|
|
317
318
|
justify-content: space-between;
|
|
319
|
+
gap: 8px;
|
|
320
|
+
min-height: 34px;
|
|
321
|
+
border-bottom: 0.5px solid var(--hair);
|
|
322
|
+
}
|
|
323
|
+
/* the threshold pair lives in an inset card under its toggle row */
|
|
324
|
+
.set-row:has(+ .set-subgroup) {
|
|
325
|
+
border-bottom: none;
|
|
326
|
+
}
|
|
327
|
+
/* flat, but visibly nested: a soft coral guide bar ties the pair to the
|
|
328
|
+
toggle above, and each label hugs its own stepper */
|
|
329
|
+
.set-subgroup {
|
|
330
|
+
position: relative;
|
|
331
|
+
padding-left: 14px;
|
|
332
|
+
border-bottom: 0.5px solid var(--hair);
|
|
333
|
+
padding-bottom: 3px;
|
|
334
|
+
transition: opacity 0.15s;
|
|
335
|
+
}
|
|
336
|
+
.set-subgroup::before {
|
|
337
|
+
content: "";
|
|
338
|
+
position: absolute;
|
|
339
|
+
left: 3px;
|
|
340
|
+
top: 5px;
|
|
341
|
+
bottom: 10px;
|
|
342
|
+
width: 2px;
|
|
343
|
+
border-radius: 1px;
|
|
344
|
+
background: var(--coral);
|
|
345
|
+
opacity: 0.35;
|
|
346
|
+
}
|
|
347
|
+
.set-subgroup .set-row {
|
|
348
|
+
border-bottom: none;
|
|
349
|
+
justify-content: flex-end;
|
|
350
|
+
gap: 10px;
|
|
351
|
+
}
|
|
352
|
+
.set-row-label {
|
|
318
353
|
font-size: 11px;
|
|
319
354
|
font-weight: 600;
|
|
320
355
|
color: var(--text);
|
|
321
|
-
margin-bottom: 8px;
|
|
322
356
|
}
|
|
323
|
-
|
|
357
|
+
.set-row-hint {
|
|
358
|
+
display: block;
|
|
359
|
+
white-space: nowrap;
|
|
360
|
+
font-style: normal;
|
|
361
|
+
font-weight: 400;
|
|
362
|
+
font-size: 9px;
|
|
363
|
+
color: var(--muted);
|
|
364
|
+
margin-top: 2px;
|
|
365
|
+
}
|
|
366
|
+
.set-row.set-sub {
|
|
367
|
+
min-height: 31px;
|
|
368
|
+
}
|
|
369
|
+
.set-row.set-sub .set-row-label {
|
|
370
|
+
font-weight: 400;
|
|
371
|
+
font-size: 10.5px;
|
|
372
|
+
color: var(--muted);
|
|
373
|
+
}
|
|
374
|
+
#set-rows.alerts-off .set-subgroup {
|
|
375
|
+
opacity: 0.35;
|
|
376
|
+
pointer-events: none;
|
|
377
|
+
}
|
|
378
|
+
.set-row .seg {
|
|
379
|
+
margin-top: 0;
|
|
380
|
+
flex: none;
|
|
381
|
+
}
|
|
382
|
+
.set-row .seg-btn {
|
|
383
|
+
flex: none;
|
|
384
|
+
padding: 4px 9px;
|
|
385
|
+
}
|
|
386
|
+
/* alerts on/off pill switch (still an <input type=checkbox> underneath) */
|
|
324
387
|
.set-toggle {
|
|
388
|
+
appearance: none;
|
|
389
|
+
-webkit-appearance: none;
|
|
325
390
|
margin: 0;
|
|
326
|
-
width:
|
|
327
|
-
height:
|
|
328
|
-
|
|
391
|
+
width: 30px;
|
|
392
|
+
height: 18px;
|
|
393
|
+
border-radius: 9px;
|
|
394
|
+
background: rgba(255, 255, 255, 0.14);
|
|
395
|
+
position: relative;
|
|
329
396
|
cursor: pointer;
|
|
397
|
+
-webkit-app-region: no-drag;
|
|
398
|
+
transition: background 0.18s;
|
|
330
399
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
400
|
+
.set-toggle::after {
|
|
401
|
+
content: "";
|
|
402
|
+
position: absolute;
|
|
403
|
+
top: 2px;
|
|
404
|
+
left: 2px;
|
|
405
|
+
width: 14px;
|
|
406
|
+
height: 14px;
|
|
407
|
+
border-radius: 50%;
|
|
408
|
+
background: #fff;
|
|
409
|
+
opacity: 0.85;
|
|
410
|
+
transition:
|
|
411
|
+
left 0.18s,
|
|
412
|
+
opacity 0.18s;
|
|
336
413
|
}
|
|
337
|
-
.
|
|
338
|
-
|
|
339
|
-
flex-direction: column;
|
|
340
|
-
gap: 4px;
|
|
414
|
+
.set-toggle:checked {
|
|
415
|
+
background: var(--coral);
|
|
341
416
|
}
|
|
342
|
-
.
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
height: 12px;
|
|
346
|
-
line-height: 12px;
|
|
417
|
+
.set-toggle:checked::after {
|
|
418
|
+
left: 14px;
|
|
419
|
+
opacity: 1;
|
|
347
420
|
}
|
|
348
|
-
/* number field
|
|
421
|
+
/* number field: minus / value% / plus */
|
|
349
422
|
.num {
|
|
350
|
-
|
|
423
|
+
display: flex;
|
|
424
|
+
align-items: center;
|
|
425
|
+
gap: 4px;
|
|
351
426
|
flex: none;
|
|
352
|
-
width: 66px;
|
|
353
427
|
}
|
|
354
|
-
.num
|
|
428
|
+
.num-val {
|
|
429
|
+
position: relative;
|
|
430
|
+
width: 54px;
|
|
431
|
+
}
|
|
432
|
+
.num-val input {
|
|
355
433
|
width: 100%;
|
|
356
434
|
background: rgba(255, 255, 255, 0.06);
|
|
357
435
|
border: 0.5px solid var(--hair);
|
|
358
|
-
border-radius:
|
|
436
|
+
border-radius: 7px;
|
|
359
437
|
color: var(--text);
|
|
360
|
-
font-size:
|
|
438
|
+
font-size: 12px;
|
|
361
439
|
font-weight: 600;
|
|
362
440
|
font-variant-numeric: tabular-nums;
|
|
363
|
-
|
|
441
|
+
text-align: right;
|
|
442
|
+
padding: 4px 20px 4px 6px;
|
|
364
443
|
outline: none;
|
|
365
444
|
transition:
|
|
366
445
|
border-color 0.15s,
|
|
367
446
|
background 0.15s;
|
|
368
447
|
}
|
|
369
|
-
.num input:focus {
|
|
448
|
+
.num-val input:focus {
|
|
370
449
|
border-color: var(--coral);
|
|
371
450
|
background: rgba(255, 255, 255, 0.09);
|
|
372
451
|
}
|
|
373
|
-
.num-
|
|
452
|
+
.num-val i {
|
|
374
453
|
position: absolute;
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
gap: 2px;
|
|
454
|
+
right: 7px;
|
|
455
|
+
top: 50%;
|
|
456
|
+
transform: translateY(-50%);
|
|
457
|
+
font-style: normal;
|
|
458
|
+
font-size: 10px;
|
|
459
|
+
color: var(--muted);
|
|
460
|
+
pointer-events: none;
|
|
383
461
|
}
|
|
384
462
|
.num-btn {
|
|
385
463
|
-webkit-app-region: no-drag;
|
|
386
464
|
border: none;
|
|
387
465
|
background: rgba(255, 255, 255, 0.1);
|
|
388
466
|
color: var(--muted);
|
|
389
|
-
width:
|
|
390
|
-
height:
|
|
391
|
-
border-radius:
|
|
392
|
-
font-size:
|
|
467
|
+
width: 20px;
|
|
468
|
+
height: 20px;
|
|
469
|
+
border-radius: 6px;
|
|
470
|
+
font-size: 12px;
|
|
393
471
|
line-height: 1;
|
|
394
472
|
display: flex;
|
|
395
473
|
align-items: center;
|
|
@@ -432,7 +510,7 @@ body.is-menubar #min {
|
|
|
432
510
|
background 0.18s,
|
|
433
511
|
transform 0.1s;
|
|
434
512
|
}
|
|
435
|
-
.set-actions button:active {
|
|
513
|
+
.set-actions button:active:not(:disabled) {
|
|
436
514
|
transform: scale(0.97);
|
|
437
515
|
}
|
|
438
516
|
#set-cancel {
|
|
@@ -443,10 +521,18 @@ body.is-menubar #min {
|
|
|
443
521
|
background: rgba(255, 255, 255, 0.2);
|
|
444
522
|
}
|
|
445
523
|
#set-save {
|
|
446
|
-
background: rgba(
|
|
524
|
+
background: rgba(255, 255, 255, 0.12); /* rests like Cancel; dirty lights it */
|
|
525
|
+
color: var(--text);
|
|
526
|
+
}
|
|
527
|
+
#set-save:disabled {
|
|
528
|
+
opacity: 0.45;
|
|
529
|
+
cursor: default;
|
|
530
|
+
}
|
|
531
|
+
#set-save.dirty,
|
|
532
|
+
#set-save.dirty:hover {
|
|
447
533
|
color: #fff;
|
|
448
534
|
}
|
|
449
|
-
#set-save:hover {
|
|
535
|
+
#set-save.dirty:hover {
|
|
450
536
|
background: #e2876a;
|
|
451
537
|
}
|
|
452
538
|
/* unsaved changes: light up and pulse so you don't forget to save */
|
|
@@ -562,6 +648,10 @@ body.live .live-only {
|
|
|
562
648
|
opacity: 0.5;
|
|
563
649
|
cursor: default;
|
|
564
650
|
}
|
|
651
|
+
#acc-confirm:not(.ready) {
|
|
652
|
+
background: rgba(255, 255, 255, 0.12);
|
|
653
|
+
color: var(--muted);
|
|
654
|
+
}
|
|
565
655
|
.acc-link {
|
|
566
656
|
border: none;
|
|
567
657
|
background: none;
|
|
@@ -580,11 +670,18 @@ body.live .live-only {
|
|
|
580
670
|
color: var(--green);
|
|
581
671
|
font-size: 11px;
|
|
582
672
|
font-weight: 600;
|
|
673
|
+
flex: 1;
|
|
674
|
+
min-width: 0;
|
|
675
|
+
overflow: hidden;
|
|
676
|
+
text-overflow: ellipsis;
|
|
677
|
+
white-space: nowrap;
|
|
583
678
|
}
|
|
584
679
|
#acc-connected {
|
|
585
680
|
display: none;
|
|
586
681
|
align-items: center;
|
|
587
682
|
justify-content: space-between;
|
|
683
|
+
gap: 10px; /* the email ellipsizes instead of touching Disconnect */
|
|
684
|
+
flex-wrap: wrap; /* the success line drops to its own row */
|
|
588
685
|
}
|
|
589
686
|
#acc-paste {
|
|
590
687
|
display: none;
|
|
@@ -616,6 +713,16 @@ body.auth-on #acc-connected {
|
|
|
616
713
|
display: flex;
|
|
617
714
|
}
|
|
618
715
|
|
|
716
|
+
/* just logged in: a short-lived green cheer at the top of the home panel */
|
|
717
|
+
#home-success {
|
|
718
|
+
font-size: 10px;
|
|
719
|
+
font-weight: 600;
|
|
720
|
+
color: var(--green);
|
|
721
|
+
text-align: center;
|
|
722
|
+
margin-bottom: 8px;
|
|
723
|
+
animation: set-in 0.18s ease;
|
|
724
|
+
}
|
|
725
|
+
|
|
619
726
|
/* limits: real meters when connected, a "connect" prompt otherwise */
|
|
620
727
|
#limits-connect {
|
|
621
728
|
display: flex;
|
|
@@ -775,6 +882,24 @@ body.settings-open #min {
|
|
|
775
882
|
|
|
776
883
|
/* pet */
|
|
777
884
|
/* expanded: a little night scene */
|
|
885
|
+
body.settings-open #stage {
|
|
886
|
+
height: 100px;
|
|
887
|
+
}
|
|
888
|
+
/* the ground layers stretch to fill the stage; while it's shortened, pin them
|
|
889
|
+
at their natural height so the gears stay round — the top just clips away.
|
|
890
|
+
#scene keeps stretching (dots and stars tolerate it) so its frame rows stay
|
|
891
|
+
at the edges; the blocky clouds don't, so they sit this screen out. */
|
|
892
|
+
body.settings-open #gears {
|
|
893
|
+
top: auto;
|
|
894
|
+
bottom: 0;
|
|
895
|
+
height: 132px;
|
|
896
|
+
}
|
|
897
|
+
body.settings-open .cloud {
|
|
898
|
+
display: none;
|
|
899
|
+
}
|
|
900
|
+
body.settings-open #pet {
|
|
901
|
+
zoom: 0.65;
|
|
902
|
+
}
|
|
778
903
|
#stage {
|
|
779
904
|
position: relative;
|
|
780
905
|
height: 132px;
|
|
@@ -908,10 +1033,17 @@ body.settings-open #robot,
|
|
|
908
1033
|
body.settings-open #gears {
|
|
909
1034
|
opacity: 1;
|
|
910
1035
|
}
|
|
911
|
-
/* declutter the scene while configuring
|
|
1036
|
+
/* declutter the scene while configuring: mood marks AND activity props —
|
|
1037
|
+
the shortened stage only fits the pet and its gears */
|
|
912
1038
|
body.settings-open #zzz,
|
|
913
1039
|
body.settings-open #flames,
|
|
914
|
-
body.settings-open #sweat
|
|
1040
|
+
body.settings-open #sweat,
|
|
1041
|
+
body.settings-open #terminal,
|
|
1042
|
+
body.settings-open #reading,
|
|
1043
|
+
body.settings-open #mug,
|
|
1044
|
+
body.settings-open #desk,
|
|
1045
|
+
body.settings-open #plant,
|
|
1046
|
+
body.settings-open #glasses {
|
|
915
1047
|
display: none;
|
|
916
1048
|
}
|
|
917
1049
|
/* reading mode: an open book with a magnifying glass scanning the lines */
|
|
@@ -1679,7 +1811,8 @@ body.state-tired #status-text {
|
|
|
1679
1811
|
}
|
|
1680
1812
|
|
|
1681
1813
|
/* meters */
|
|
1682
|
-
.meter + .meter
|
|
1814
|
+
.meter + .meter,
|
|
1815
|
+
#scoped-meters .meter {
|
|
1683
1816
|
margin-top: 9px;
|
|
1684
1817
|
}
|
|
1685
1818
|
.meter-top {
|