clauddy 1.28.3 → 1.29.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/README.md CHANGED
@@ -24,7 +24,7 @@ The **percentages are real**, pulled from your account (you log in once — see
24
24
 
25
25
  The session/weekly **%** comes straight from your Anthropic account, so it matches the official panel exactly. You connect once via a browser login:
26
26
 
27
- 1. Open **⚙ Settings → Connections → Claude → Connect** — your browser opens an Anthropic auth page.
27
+ 1. Open **⚙ Settings → Accounts → Claude → Connect** — your browser opens an Anthropic auth page.
28
28
  2. Log in, copy the **authentication code** shown, and paste it back into the app → **Connect**.
29
29
 
30
30
  The token is saved locally (see [Data & privacy](#data--privacy)) and refreshed automatically. **Until you connect**, the limits area shows a _"Connect your account"_ prompt instead of percentages.
@@ -41,7 +41,7 @@ Removing an account (the **×** on its row) deletes its token from disk. The one
41
41
 
42
42
  ## Codex
43
43
 
44
- Clauddy can follow [Codex](https://openai.com/codex) (the CLI and the desktop app) next to Claude. Open **⚙ Settings → Connections → Codex → Connect**: it looks for a Codex session on this computer and starts monitoring if it finds one. There's nothing to log into — Codex writes its token counts and rate limits to `~/.codex/sessions` (or `$CODEX_HOME`), and Clauddy only reads those files.
44
+ Clauddy can follow [Codex](https://openai.com/codex) (the CLI and the desktop app) next to Claude. Open **⚙ Settings → Accounts → Codex → Connect**: it looks for a Codex session on this computer and starts monitoring if it finds one. There's nothing to log into — Codex writes its token counts and rate limits to `~/.codex/sessions` (or `$CODEX_HOME`), and Clauddy only reads those files.
45
45
 
46
46
  With both connected, **tabs under the pet** pick which service the whole panel shows — account chip, pet mood, session and weekly %, the breakdowns, the 30-day map and the **↗** Usage link. The other tab keeps its % in view and gets a **!** when it crosses an alert threshold. Minimized, the ring becomes one reading per service; click one to switch.
47
47
 
@@ -55,7 +55,7 @@ Disconnecting (the **×** on the Codex card) only stops monitoring in Clauddy; y
55
55
 
56
56
  ## Cursor
57
57
 
58
- Clauddy can follow [Cursor](https://cursor.com) too. Open **⚙ Settings → Connections → Cursor → Connect**: it needs the Cursor app signed in on this computer, and starts monitoring once it finds that login. Clauddy reads the app's session token from its local storage and asks Cursor's own dashboard endpoints for your usage, the same numbers as the Usage page on cursor.com.
58
+ Clauddy can follow [Cursor](https://cursor.com) too. Open **⚙ Settings → Accounts → Cursor → Connect**: it needs the Cursor app signed in on this computer, and starts monitoring once it finds that login. Clauddy reads the app's session token from its local storage and asks Cursor's own dashboard endpoints for your usage, the same numbers as the Usage page on cursor.com.
59
59
 
60
60
  With three services connected, the tabs under the pet show each logo and %, and the minimized dock gets a third line.
61
61
 
@@ -103,6 +103,12 @@ It only appears once there's enough to say honestly — roughly 5 minutes into a
103
103
 
104
104
  Plus a welcome **wave** on launch. You can [poke the pet from the terminal](#play-with-the-pet) too.
105
105
 
106
+ ### Pick your pet
107
+
108
+ Clauddy is the default, but **⚙ Settings → Appearance → pet** has more: a Wizard, a Halloween pack (Pumpkin, Skull, Ghost, Bat, Black cat) and a Christmas pack (Snowman, Santa, Reindeer, Gingerbread). Click one to preview it and **Save** to keep it. Each pet stays still and blinks when nothing is running, and has its own way of moving while a provider works: the Pumpkin rocks, the Bat flaps, the Wizard levitates while an owl flies by.
109
+
110
+ On holidays a pet from the season's pack takes over for the day, whichever pet you picked: one Halloween pet on October 31, one Christmas pet on December 24 and 25. The pick changes every year and holds all day. Your own pet comes back the next day.
111
+
106
112
  ### What Claude's up to
107
113
 
108
114
  While Claude Code is actively working, the pet sets up a little desk scene that
@@ -148,7 +154,7 @@ Optionally the pet also **blips** as it talks: short chiptune notes, one per let
148
154
 
149
155
  Audio should never catch you off guard in a meeting:
150
156
 
151
- - **Off by default.** Turn it on under **⚙ Settings → voice → Chiptune voice** (you'll hear a sample).
157
+ - **Off by default.** Turn it on under **⚙ Settings → Appearance → Chiptune voice** (you'll hear a sample).
152
158
  - **One-click mute.** While the voice is on, a 🔊 button sits in the title bar. Click it to mute for an hour, and again to unmute.
153
159
  - **Always silent** in menu-bar mode and while the pet is minimized.
154
160
 
@@ -231,14 +237,14 @@ update-desktop-database ~/.local/share/applications 2>/dev/null
231
237
 
232
238
  - **Drag** the widget anywhere on screen
233
239
  - **–** minimizes to just the pet, ringed by the live session % (the number sits inside the ring) — or, with more than one service connected, one reading per service; the **⤢** button or a double-click on the pet expands it back
234
- - **Pet icon** in the compact header switches to **Just the pet** (floating mode). Drag the pet itself to move it. Hovering over the pet (or focusing it) reveals the usage glance; the small expand button in it returns to compact. Keyboard users can focus the pet and press Enter. The chosen size is remembered. In compact and pet-only modes, the pet follows whoever is working, with provider logos identifying Claude, Codex, Cursor, or several at once. Usage selection stays manual. When neither is active, the hottest session still sets the mood: the pet catches fire (or maxes out) with that provider's logo beside it, and otherwise rests.
240
+ - **Pet icon** in the compact header switches to **Just the pet** (floating mode). Drag the pet itself to move it. Hovering over the pet (or focusing it) pops up the usage glance as the pet's own speech bubble, one reading and a usage bar per service; the small expand button in it returns to compact. Keyboard users can focus the pet and press Enter. The chosen size is remembered. In compact and pet-only modes, the pet follows whoever is working, with provider logos identifying Claude, Codex, Cursor, or several at once. Usage selection stays manual. When neither is active, the hottest session still sets the mood: the pet catches fire (or maxes out) with that provider's logo beside it, and otherwise rests.
235
241
  - **⚙** opens settings (log in, toggle alerts, set thresholds, pick the display mode)
236
242
  - **↗** opens the official Usage page of the service on screen
237
243
  - **×** quits
238
244
 
239
245
  ### Floating or menu bar
240
246
 
241
- Under **⚙ Settings → Preferences** you can pick where Clauddy lives:
247
+ Under **⚙ Settings → Appearance** you can pick where Clauddy lives:
242
248
 
243
249
  - **Floating pet** — the always-on widget in the corner (default).
244
250
  - **Menu bar** — a small pet icon in the macOS menu bar showing your live session **%** (it turns 🔥 near your limit; with Codex or Cursor too it reads `C 64% · X 28% · Cu 5%`). Click it to pop open the full pet + usage panel; click away to dismiss. Right-click for a quick menu.
@@ -251,7 +257,7 @@ When a provider's activity changes, the pet briefly glances toward it, with no c
251
257
 
252
258
  ## Alerts
253
259
 
254
- Optional **macOS notifications**, toggled (with their thresholds) in **⚙ Settings**:
260
+ Optional **macOS notifications**, toggled (with their thresholds) in **⚙ Settings → Alerts**:
255
261
 
256
262
  | Notification | When |
257
263
  | --- | --- |
package/main.js CHANGED
@@ -89,6 +89,7 @@ function publicConfig(c) {
89
89
  talk: c.talk,
90
90
  sound: c.sound,
91
91
  soundMutedUntil: c.soundMutedUntil,
92
+ pet: c.pet,
92
93
  }
93
94
  }
94
95
 
@@ -108,6 +109,7 @@ function loadConfig() {
108
109
  talk: true, // speech bubbles on transitions
109
110
  sound: false, // chiptune blips: opt-in, audio must never surprise anyone
110
111
  soundMutedUntil: 0, // the one-click "mute for 1 hour", as an epoch ms
112
+ pet: 'clauddy', // renderer/pets.js; holidays override it on their own days
111
113
  pollIntervalMs: 4000,
112
114
  activeThresholdMs: 20000,
113
115
  sleepThresholdMs: 300000,
@@ -527,6 +529,9 @@ function createWindow() {
527
529
  resizable: false,
528
530
  show: false, // applyMode() reveals it (floating) or keeps it a hidden popover (menubar)
529
531
  alwaysOnTop: true,
532
+ // macOS: the widget is rarely the focused window, so without this the first
533
+ // click only focuses it and a button needs a second one
534
+ acceptFirstMouse: true,
530
535
  skipTaskbar: true,
531
536
  hasShadow: false,
532
537
  fullscreenable: false,
@@ -781,6 +786,10 @@ function positionUnderTray() {
781
786
  }
782
787
  x = Math.max(8, Math.min(x, workAreaSize.width - b.width - 8))
783
788
  moveWindow(x, y)
789
+ // the popover's nib points at the icon, even when the screen edge pushed the
790
+ // window off-centre
791
+ const iconX = trayBounds?.width ? trayBounds.x + trayBounds.width / 2 : x + b.width / 2
792
+ win.webContents.send('popover-anchor', Math.round(iconX - x))
784
793
  }
785
794
 
786
795
  function positionFloating() {
@@ -866,10 +875,38 @@ ipcMain.on('pet-pointer-watch', (_e, enabled) => {
866
875
  })
867
876
 
868
877
  // resize the window to fit the content
869
- ipcMain.on('resize', (_e, w, h) => {
878
+ // Opening and closing Settings eases the window between sizes (an ease-out over
879
+ // RESIZE_MS) instead of jumping; any other resize is immediate and cancels a
880
+ // tween still running, so the latest size always wins.
881
+ const RESIZE_MS = 240
882
+ let resizeTween = null
883
+ ipcMain.on('resize', (_e, w, h, animate) => {
870
884
  if (!win || win.isDestroyed()) return
871
885
  const width = Math.max(100, Math.round(w))
872
886
  const height = Math.max(110, Math.round(h))
887
+ clearInterval(resizeTween)
888
+ resizeTween = null
889
+ if (!animate) return setWindowSize(width, height)
890
+ const from = win.getBounds()
891
+ const start = Date.now()
892
+ const step = () => {
893
+ if (!win || win.isDestroyed()) return clearInterval(resizeTween)
894
+ const t = Math.min(1, (Date.now() - start) / RESIZE_MS)
895
+ const k = 1 - (1 - t) ** 3
896
+ setWindowSize(
897
+ Math.round(from.width + (width - from.width) * k),
898
+ Math.round(from.height + (height - from.height) * k),
899
+ )
900
+ if (t === 1) {
901
+ clearInterval(resizeTween)
902
+ resizeTween = null
903
+ }
904
+ }
905
+ resizeTween = setInterval(step, 16)
906
+ step()
907
+ })
908
+
909
+ function setWindowSize(width, height) {
873
910
  if (currentMode === 'menubar') {
874
911
  win.setContentSize(width, height)
875
912
  if (win.isVisible()) positionUnderTray() // keep it anchored under the tray
@@ -885,7 +922,7 @@ ipcMain.on('resize', (_e, w, h) => {
885
922
  const after = win.getBounds()
886
923
  moveWindow(right - after.width, bottom - after.height)
887
924
  }
888
- })
925
+ }
889
926
 
890
927
  // the arrow opens the page of whichever service the panel is showing
891
928
  ipcMain.on('open-usage', (_e, provider) =>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "clauddy",
3
3
  "desktopName": "clauddy.desktop",
4
- "version": "1.28.3",
4
+ "version": "1.29.0",
5
5
  "description": "A cute desktop pet that tracks your Claude Code usage",
6
6
  "main": "main.js",
7
7
  "bin": {
package/preload.js CHANGED
@@ -10,7 +10,7 @@ contextBridge.exposeInMainWorld('api', {
10
10
  onError: (cb) => ipcRenderer.on('usage-error', (_e, msg) => cb(msg)),
11
11
  onConfig: (cb) => ipcRenderer.on('config', (_e, cfg) => cb(cfg)),
12
12
  saveConfig: (patch) => ipcRenderer.send('save-config', patch),
13
- resize: (w, h) => ipcRenderer.send('resize', w, h),
13
+ resize: (w, h, animate) => ipcRenderer.send('resize', w, h, animate),
14
14
  openUsage: (provider) => ipcRenderer.send('open-usage', provider),
15
15
  codexDetect: () => ipcRenderer.send('codex-detect'),
16
16
  onCodexDetected: (cb) => ipcRenderer.on('codex-detected', (_e, r) => cb(r)),
@@ -36,6 +36,7 @@ contextBridge.exposeInMainWorld('api', {
36
36
  onDebugState: (cb) => ipcRenderer.on('debug-state', (_e, s) => cb(s)),
37
37
  onVersion: (cb) => ipcRenderer.on('version', (_e, v) => cb(v)),
38
38
  onUpdateStatus: (cb) => ipcRenderer.on('update-status', (_e, s) => cb(s)),
39
+ onPopoverAnchor: (cb) => ipcRenderer.on('popover-anchor', (_e, x) => cb(x)),
39
40
  checkUpdates: () => ipcRenderer.send('check-updates'),
40
41
  doUpdate: () => ipcRenderer.send('do-update'),
41
42
  quit: () => ipcRenderer.send('quit'),
@@ -6,6 +6,7 @@
6
6
  <title>Clauddy</title>
7
7
  <link rel="stylesheet" href="style.css" />
8
8
  <link rel="stylesheet" href="motion.css" />
9
+ <link rel="stylesheet" href="pets.css" />
9
10
  </head>
10
11
  <body class="state-idle one-account">
11
12
  <!-- brand marks from simple-icons (CC0); the trademarks are Anthropic's and OpenAI's -->
@@ -226,10 +227,11 @@
226
227
 
227
228
  <div id="pet-activity" class="activity-logos" role="img" hidden><span data-provider="claude"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="#logo-claude" /></svg></span><span data-provider="codex"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="#logo-openai" /></svg></span><span data-provider="cursor"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="#logo-cursor" /></svg></span></div>
228
229
  <section id="pet-glance" aria-label="Session usage">
229
- <button type="button" data-provider="claude" id="glance-claude"><svg class="mini-logo" viewBox="0 0 24 24" aria-hidden="true"><use href="#logo-claude" /></svg><span>Claude</span><b id="glance-claude-value">—</b></button>
230
- <button type="button" data-provider="codex" id="glance-codex"><svg class="mini-logo" viewBox="0 0 24 24" aria-hidden="true"><use href="#logo-openai" /></svg><span>Codex</span><b id="glance-codex-value">—</b></button>
231
- <button type="button" data-provider="cursor" id="glance-cursor"><svg class="mini-logo" viewBox="0 0 24 24" aria-hidden="true"><use href="#logo-cursor" /></svg><span>Cursor</span><b id="glance-cursor-value">—</b></button>
230
+ <button type="button" data-provider="claude" id="glance-claude"><svg class="mini-logo" viewBox="0 0 24 24" aria-hidden="true"><use href="#logo-claude" /></svg><span>Claude</span><b id="glance-claude-value">—</b><i class="glance-bar" aria-hidden="true"></i></button>
231
+ <button type="button" data-provider="codex" id="glance-codex"><svg class="mini-logo" viewBox="0 0 24 24" aria-hidden="true"><use href="#logo-openai" /></svg><span>Codex</span><b id="glance-codex-value">—</b><i class="glance-bar" aria-hidden="true"></i></button>
232
+ <button type="button" data-provider="cursor" id="glance-cursor"><svg class="mini-logo" viewBox="0 0 24 24" aria-hidden="true"><use href="#logo-cursor" /></svg><span>Cursor</span><b id="glance-cursor-value">—</b><i class="glance-bar" aria-hidden="true"></i></button>
232
233
  <span id="glance-empty" hidden>Connect an account in Settings.</span>
234
+ <i class="glance-sep" aria-hidden="true"></i>
233
235
  <button type="button" id="pet-restore" aria-label="Open compact monitor" title="Open compact monitor"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 4h6v6M20 4l-7 7M10 20H4v-6M4 20l7-7" /></svg></button>
234
236
  </section>
235
237
  <div id="companion-notice" role="status" hidden></div>
@@ -318,148 +320,176 @@
318
320
 
319
321
  <!-- settings panel -->
320
322
  <div id="settings">
321
- <div class="settings-heading">
322
- <div>
323
- <h3>Make yourself at home.</h3>
324
- <p>Your accounts, your little companion.</p>
325
- </div>
326
- </div>
327
323
 
328
- <div class="settings-label connections-label">Connections</div>
329
- <!-- one tile per service; a click opens its card below -->
330
- <div id="conn-tiles">
331
- <button type="button" class="conn-tile" id="conn-claude" data-conn="claude" aria-expanded="false" aria-controls="account">
332
- <span class="connection-avatar claude-avatar"><svg class="connection-logo" viewBox="0 0 24 24" aria-hidden="true"><use href="#logo-claude" /></svg><i class="conn-check" hidden aria-hidden="true">✓</i></span>
333
- <span class="conn-name">Claude</span>
334
- <span class="conn-state" id="conn-claude-state">Connect</span>
335
- </button>
336
- <button type="button" class="conn-tile" id="conn-codex" data-conn="codex" aria-expanded="false" aria-controls="codex-settings">
337
- <span class="connection-avatar codex-avatar"><svg class="connection-logo" viewBox="0 0 24 24" aria-hidden="true"><use href="#logo-openai" /></svg><i class="conn-check" hidden aria-hidden="true">✓</i></span>
338
- <span class="conn-name">Codex</span>
339
- <span class="conn-state" id="conn-codex-state">Connect</span>
340
- </button>
341
- <button type="button" class="conn-tile" id="conn-cursor" data-conn="cursor" aria-expanded="false" aria-controls="cursor-settings">
342
- <span class="connection-avatar cursor-avatar"><svg class="connection-logo" viewBox="0 0 24 24" aria-hidden="true"><use href="#logo-cursor" /></svg><i class="conn-check" hidden aria-hidden="true">✓</i></span>
343
- <span class="conn-name">Cursor</span>
344
- <span class="conn-state" id="conn-cursor-state">Connect</span>
345
- </button>
324
+ <div id="set-tabs" role="tablist" aria-label="Settings">
325
+ <button type="button" role="tab" class="set-tab" id="tab-accounts" data-tab="accounts" aria-controls="pane-accounts">Accounts</button>
326
+ <button type="button" role="tab" class="set-tab" id="tab-alerts" data-tab="alerts" aria-controls="pane-alerts">Alerts</button>
327
+ <button type="button" role="tab" class="set-tab" id="tab-appearance" data-tab="appearance" aria-controls="pane-appearance">Appearance</button>
346
328
  </div>
347
- <div id="connections">
348
- <div id="account" class="conn-panel">
349
- <!-- who you are comes first: the list, then the login for whichever
350
- account is active — so "add" and the code field sit together -->
351
- <div id="acc-connected" class="connect-block connection-card">
352
- <span class="connection-avatar claude-avatar" id="acc-avatar"><svg class="connection-logo" viewBox="0 0 24 24" aria-hidden="true"><use href="#logo-claude" /></svg></span>
353
- <div class="connection-copy">
354
- <div class="connection-heading"><b>Claude</b><span id="acc-sub" class="plan-badge" hidden></span></div>
355
- <span class="connection-caption acc-ok" id="acc-ok">Connected</span>
356
- </div>
357
- <button id="acc-logout" class="cb-out connection-remove" title="Disconnect Claude" aria-label="Disconnect Claude">
358
- <svg class="ic" viewBox="0 0 24 24" aria-hidden="true">
359
- <path d="M18 6 6 18M6 6l12 12" />
360
- </svg>
329
+ <!-- one pane at a time; the window eases to each pane's own height -->
330
+ <div id="set-rows">
331
+ <section class="set-pane" id="pane-accounts" role="tabpanel" aria-labelledby="tab-accounts">
332
+ <!-- one tile per service; a click opens its card below -->
333
+ <div id="conn-tiles">
334
+ <button type="button" class="conn-tile" id="conn-claude" data-conn="claude" aria-expanded="false" aria-controls="account">
335
+ <span class="connection-avatar claude-avatar"><svg class="connection-logo" viewBox="0 0 24 24" aria-hidden="true"><use href="#logo-claude" /></svg><i class="conn-check" hidden aria-hidden="true">✓</i></span>
336
+ <span class="conn-name">Claude</span>
337
+ <span class="conn-state" id="conn-claude-state">Connect</span>
338
+ </button>
339
+ <button type="button" class="conn-tile" id="conn-codex" data-conn="codex" aria-expanded="false" aria-controls="codex-settings">
340
+ <span class="connection-avatar codex-avatar"><svg class="connection-logo" viewBox="0 0 24 24" aria-hidden="true"><use href="#logo-openai" /></svg><i class="conn-check" hidden aria-hidden="true">✓</i></span>
341
+ <span class="conn-name">Codex</span>
342
+ <span class="conn-state" id="conn-codex-state">Connect</span>
343
+ </button>
344
+ <button type="button" class="conn-tile" id="conn-cursor" data-conn="cursor" aria-expanded="false" aria-controls="cursor-settings">
345
+ <span class="connection-avatar cursor-avatar"><svg class="connection-logo" viewBox="0 0 24 24" aria-hidden="true"><use href="#logo-cursor" /></svg><i class="conn-check" hidden aria-hidden="true">✓</i></span>
346
+ <span class="conn-name">Cursor</span>
347
+ <span class="conn-state" id="conn-cursor-state">Connect</span>
361
348
  </button>
362
349
  </div>
363
- <div id="acc-disconnected">
364
- <div id="acc-intro" class="connection-card">
365
- <span class="connection-avatar claude-avatar"><svg class="connection-logo" viewBox="0 0 24 24" aria-hidden="true"><use href="#logo-claude" /></svg></span>
350
+ <div id="connections">
351
+ <div id="account" class="conn-panel">
352
+ <!-- who you are comes first: the list, then the login for whichever
353
+ account is active — so "add" and the code field sit together -->
354
+ <div id="acc-connected" class="connect-block connection-card">
355
+ <span class="connection-avatar claude-avatar" id="acc-avatar"><svg class="connection-logo" viewBox="0 0 24 24" aria-hidden="true"><use href="#logo-claude" /></svg></span>
366
356
  <div class="connection-copy">
367
- <div class="connection-heading"><b>Claude</b></div>
368
- <span class="connection-hint">Connect via browser to track your usage.</span>
357
+ <div class="connection-heading"><b>Claude</b><span id="acc-sub" class="plan-badge" hidden></span></div>
358
+ <span class="connection-caption acc-ok" id="acc-ok">Connected</span>
369
359
  </div>
370
- <button id="acc-connect" class="connection-btn claude-connect-btn" aria-label="Connect Claude" title="Log in with browser"><svg class="connection-logo" viewBox="0 0 24 24" aria-hidden="true"><use href="#logo-claude" /></svg><span>Connect</span></button>
360
+ <button id="acc-logout" class="cb-out connection-remove" title="Disconnect Claude" aria-label="Disconnect Claude">
361
+ <svg class="ic" viewBox="0 0 24 24" aria-hidden="true">
362
+ <path d="M18 6 6 18M6 6l12 12" />
363
+ </svg>
364
+ </button>
371
365
  </div>
372
- <div id="acc-paste">
373
- <div class="set-hint set-hint-left">Paste the code from the browser page:</div>
374
- <input id="acc-code" type="text" placeholder="authentication code" />
375
- <button id="acc-confirm" class="acc-btn">Connect</button>
376
- <div id="acc-msg"></div>
366
+ <div id="acc-disconnected">
367
+ <div id="acc-intro" class="connection-card">
368
+ <span class="connection-avatar claude-avatar"><svg class="connection-logo" viewBox="0 0 24 24" aria-hidden="true"><use href="#logo-claude" /></svg></span>
369
+ <div class="connection-copy">
370
+ <div class="connection-heading"><b>Claude</b></div>
371
+ <span class="connection-hint">Connect via browser to track your usage.</span>
372
+ </div>
373
+ <button id="acc-connect" class="connection-btn claude-connect-btn" aria-label="Connect Claude" title="Log in with browser"><svg class="connection-logo" viewBox="0 0 24 24" aria-hidden="true"><use href="#logo-claude" /></svg><span>Connect</span></button>
374
+ </div>
375
+ <div id="acc-paste">
376
+ <div class="set-hint set-hint-left">Paste the code from the browser page:</div>
377
+ <input id="acc-code" type="text" placeholder="authentication code" />
378
+ <button id="acc-confirm" class="acc-btn">Connect</button>
379
+ <div id="acc-msg"></div>
380
+ </div>
377
381
  </div>
378
382
  </div>
379
- </div>
380
383
 
381
- <!-- Codex: opt-in, read from its local logs — nothing to log into here -->
382
- <div id="codex-settings" class="connection-card conn-panel">
383
- <span class="codex-avatar connection-avatar"><svg class="codex-logo" viewBox="0 0 24 24" aria-hidden="true"><use href="#logo-openai" /></svg></span>
384
- <div class="connection-copy">
385
- <div class="connection-heading"><b>Codex</b><span id="codex-plan" class="plan-badge" hidden></span></div>
386
- <span class="connection-caption" id="codex-connected-caption">Connected on this device</span>
387
- <span id="codex-hint" class="connection-hint">Track Codex alongside Claude, in this pet.</span>
388
- </div>
389
- <div class="codex-actions">
390
- <button id="codex-connect" class="connection-btn" aria-label="Connect Codex"><svg class="codex-logo" viewBox="0 0 24 24" aria-hidden="true"><use href="#logo-openai" /></svg><span>Connect</span></button>
391
- <span id="codex-setup" hidden>
392
- <button id="codex-retry" class="connection-btn">Retry</button>
393
- <button id="codex-setup-cancel" class="codex-x" title="Cancel" aria-label="Cancel">
384
+ <!-- Codex: opt-in, read from its local logs — nothing to log into here -->
385
+ <div id="codex-settings" class="connection-card conn-panel">
386
+ <span class="codex-avatar connection-avatar"><svg class="codex-logo" viewBox="0 0 24 24" aria-hidden="true"><use href="#logo-openai" /></svg></span>
387
+ <div class="connection-copy">
388
+ <div class="connection-heading"><b>Codex</b><span id="codex-plan" class="plan-badge" hidden></span></div>
389
+ <span class="connection-caption" id="codex-connected-caption">Connected on this device</span>
390
+ <span id="codex-hint" class="connection-hint">Track Codex alongside Claude, in this pet.</span>
391
+ </div>
392
+ <div class="codex-actions">
393
+ <button id="codex-connect" class="connection-btn" aria-label="Connect Codex"><svg class="codex-logo" viewBox="0 0 24 24" aria-hidden="true"><use href="#logo-openai" /></svg><span>Connect</span></button>
394
+ <span id="codex-setup" hidden>
395
+ <button id="codex-retry" class="connection-btn">Retry</button>
396
+ <button id="codex-setup-cancel" class="codex-x" title="Cancel" aria-label="Cancel">
397
+ <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12" /></svg>
398
+ </button>
399
+ </span>
400
+ <button id="codex-disconnect" class="codex-x connection-remove" title="Disconnect Codex" aria-label="Disconnect Codex">
394
401
  <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12" /></svg>
395
402
  </button>
396
- </span>
397
- <button id="codex-disconnect" class="codex-x connection-remove" title="Disconnect Codex" aria-label="Disconnect Codex">
398
- <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12" /></svg>
399
- </button>
403
+ </div>
400
404
  </div>
401
- </div>
402
405
 
403
- <!-- Cursor: opt-in, through the Cursor app's own login on this machine -->
404
- <div id="cursor-settings" class="connection-card conn-panel">
405
- <span class="cursor-avatar connection-avatar"><svg class="cursor-logo" viewBox="0 0 24 24" aria-hidden="true"><use href="#logo-cursor" /></svg></span>
406
- <div class="connection-copy">
407
- <div class="connection-heading"><b>Cursor</b><span id="cursor-plan" class="plan-badge" hidden></span></div>
408
- <span class="connection-caption" id="cursor-connected-caption">Connected on this device</span>
409
- <span id="cursor-hint" class="connection-hint">Track Cursor alongside Claude, in this pet.</span>
410
- </div>
411
- <div class="codex-actions">
412
- <button id="cursor-connect" class="connection-btn" aria-label="Connect Cursor"><svg class="cursor-logo" viewBox="0 0 24 24" aria-hidden="true"><use href="#logo-cursor" /></svg><span>Connect</span></button>
413
- <span id="cursor-setup" hidden>
414
- <button id="cursor-retry" class="connection-btn">Retry</button>
415
- <button id="cursor-setup-cancel" class="codex-x" title="Cancel" aria-label="Cancel">
406
+ <!-- Cursor: opt-in, through the Cursor app's own login on this machine -->
407
+ <div id="cursor-settings" class="connection-card conn-panel">
408
+ <span class="cursor-avatar connection-avatar"><svg class="cursor-logo" viewBox="0 0 24 24" aria-hidden="true"><use href="#logo-cursor" /></svg></span>
409
+ <div class="connection-copy">
410
+ <div class="connection-heading"><b>Cursor</b><span id="cursor-plan" class="plan-badge" hidden></span></div>
411
+ <span class="connection-caption" id="cursor-connected-caption">Connected on this device</span>
412
+ <span id="cursor-hint" class="connection-hint">Track Cursor alongside Claude, in this pet.</span>
413
+ </div>
414
+ <div class="codex-actions">
415
+ <button id="cursor-connect" class="connection-btn" aria-label="Connect Cursor"><svg class="cursor-logo" viewBox="0 0 24 24" aria-hidden="true"><use href="#logo-cursor" /></svg><span>Connect</span></button>
416
+ <span id="cursor-setup" hidden>
417
+ <button id="cursor-retry" class="connection-btn">Retry</button>
418
+ <button id="cursor-setup-cancel" class="codex-x" title="Cancel" aria-label="Cancel">
419
+ <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12" /></svg>
420
+ </button>
421
+ </span>
422
+ <button id="cursor-disconnect" class="codex-x connection-remove" title="Disconnect Cursor" aria-label="Disconnect Cursor">
416
423
  <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12" /></svg>
417
424
  </button>
418
- </span>
419
- <button id="cursor-disconnect" class="codex-x connection-remove" title="Disconnect Cursor" aria-label="Disconnect Cursor">
420
- <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12" /></svg>
421
- </button>
422
- </div>
423
- </div>
424
- </div>
425
-
426
- <div class="settings-label">Preferences</div>
427
- <div id="set-rows">
428
- <span class="seg settings-segment" id="set-mode">
429
- <button type="button" class="seg-btn" data-mode="floating">Floating pet</button>
430
- <button type="button" class="seg-btn" data-mode="menubar">Menu bar</button>
431
- </span>
432
- <div class="set-row">
433
- <span class="set-row-label">Gentle notifications</span>
434
- <input id="set-alerts" class="set-toggle" type="checkbox" aria-label="Enable alerts" />
435
- </div>
436
- <div class="settings-label threshold-label">alert thresholds</div>
437
- <div class="set-row set-sub">
438
- <span class="set-row-label">Heads-up</span>
439
- <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>
440
- </div>
441
- <div class="set-row set-sub">
442
- <span class="set-row-label">Near limit</span>
443
- <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>
444
- </div>
445
- <div class="settings-label threshold-label">mood thresholds</div>
446
- <div class="set-row">
447
- <span class="set-row-label">Catch fire</span>
448
- <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>
449
- </div>
450
- <div class="set-row">
451
- <span class="set-row-label">Zoom</span>
452
- <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>
453
- </div>
454
- <div class="settings-label threshold-label">voice</div>
455
- <div class="set-row">
456
- <span class="set-row-label">Speech bubbles</span>
457
- <input id="set-talk" class="set-toggle" type="checkbox" aria-label="Let the pet talk" />
425
+ </div>
458
426
  </div>
459
- <div class="set-row voice-sub">
460
- <span class="set-row-label">Chiptune voice</span>
461
- <input id="set-sound" class="set-toggle" type="checkbox" aria-label="Play blips when the pet talks" />
462
427
  </div>
428
+ </section>
429
+ <!-- every pane is the same pattern: a muted label over a rounded group of
430
+ equal-height rows, label left and a compact control right -->
431
+ <section class="set-pane" id="pane-alerts" role="tabpanel" aria-labelledby="tab-alerts">
432
+ <div class="settings-label">notifications</div>
433
+ <div class="set-group">
434
+ <div class="set-row">
435
+ <span class="set-row-label">Gentle notifications</span>
436
+ <input id="set-alerts" class="set-toggle" type="checkbox" aria-label="Enable alerts" />
437
+ </div>
438
+ <div class="set-row set-sub">
439
+ <span class="set-row-label">Heads-up at</span>
440
+ <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>
441
+ </div>
442
+ <div class="set-row set-sub">
443
+ <span class="set-row-label">Near limit at</span>
444
+ <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>
445
+ </div>
446
+ </div>
447
+ <div class="settings-label">mood</div>
448
+ <div class="set-group">
449
+ <div class="set-row">
450
+ <span class="set-row-label">Catch fire at</span>
451
+ <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>
452
+ </div>
453
+ </div>
454
+ </section>
455
+ <section class="set-pane" id="pane-appearance" role="tabpanel" aria-labelledby="tab-appearance">
456
+ <div class="settings-label">pet</div>
457
+ <div class="set-group">
458
+ <div class="set-row">
459
+ <span class="pet-current">
460
+ <span id="pet-current-thumb" class="pet-thumb" aria-hidden="true"></span>
461
+ <span id="pet-current-name" class="set-row-label"></span>
462
+ </span>
463
+ <button type="button" id="pet-edit" class="set-link" aria-label="Change pet" aria-haspopup="dialog">Change</button>
464
+ </div>
465
+ </div>
466
+ <div id="pet-season" hidden></div>
467
+ <div class="settings-label">display</div>
468
+ <div class="set-group">
469
+ <div class="set-row">
470
+ <span class="set-row-label">Lives in</span>
471
+ <span class="seg settings-segment" id="set-mode">
472
+ <button type="button" class="seg-btn" data-mode="floating">Floating</button>
473
+ <button type="button" class="seg-btn" data-mode="menubar">Menu bar</button>
474
+ </span>
475
+ </div>
476
+ <div class="set-row">
477
+ <span class="set-row-label">Zoom</span>
478
+ <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>
479
+ </div>
480
+ </div>
481
+ <div class="settings-label">voice</div>
482
+ <div class="set-group">
483
+ <div class="set-row">
484
+ <span class="set-row-label">Speech bubbles</span>
485
+ <input id="set-talk" class="set-toggle" type="checkbox" aria-label="Let the pet talk" />
486
+ </div>
487
+ <div class="set-row voice-sub">
488
+ <span class="set-row-label">Chiptune voice</span>
489
+ <input id="set-sound" class="set-toggle" type="checkbox" aria-label="Play blips when the pet talks" />
490
+ </div>
491
+ </div>
492
+ </section>
463
493
  </div>
464
494
 
465
495
  <div class="set-actions">
@@ -476,10 +506,22 @@
476
506
  </span>
477
507
  </div>
478
508
  </div>
509
+ <div id="pet-modal" role="dialog" aria-modal="true" aria-labelledby="pet-modal-title" hidden>
510
+ <div class="pet-modal-card">
511
+ <div class="pet-modal-head">
512
+ <span id="pet-modal-title">Choose a pet</span>
513
+ <button type="button" id="pet-modal-close" class="pet-edit" aria-label="Close">
514
+ <svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6 6 18" /></svg>
515
+ </button>
516
+ </div>
517
+ <div id="set-pet" role="radiogroup" aria-label="Pet"></div>
518
+ </div>
519
+ </div>
479
520
  </div>
480
521
  <script src="burn.js"></script>
481
522
  <script src="voice.js"></script>
482
523
  <script src="companion.js"></script>
524
+ <script src="pets.js"></script>
483
525
  <script src="pet.js"></script>
484
526
  </body>
485
527
  </html>