clauddy 1.28.3 → 1.29.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.1",
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>