dsh-offwork 1.1.1 → 1.1.2

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.
@@ -0,0 +1,123 @@
1
+ import { open } from 'node:fs/promises';
2
+ import { constants } from 'node:fs';
3
+ import { createHash } from 'node:crypto';
4
+ import { MEDIA_ROUTE, MEDIA_ASSETS } from './media-manifest.js';
5
+
6
+ export function mediaRange(value, size) {
7
+ if (!value) return { start: 0, end: size - 1, status: 200 };
8
+ if (typeof value !== 'string') return null;
9
+ const match = /^bytes=(\d*)-(\d*)$/i.exec(value.trim());
10
+ if (!match || (!match[1] && !match[2])) return null;
11
+ const first = match[1] ? Number(match[1]) : null;
12
+ const last = match[2] ? Number(match[2]) : null;
13
+ if (
14
+ (first !== null && !Number.isSafeInteger(first)) ||
15
+ (last !== null && !Number.isSafeInteger(last))
16
+ )
17
+ return null;
18
+ if (first === null) {
19
+ if (last <= 0) return null;
20
+ return { start: Math.max(0, size - last), end: size - 1, status: 206 };
21
+ }
22
+ if (first >= size || (last !== null && last < first)) return null;
23
+ return { start: first, end: Math.min(last ?? size - 1, size - 1), status: 206 };
24
+ }
25
+
26
+ // Only the generated allowlist can select a file. No request path is used as a disk path.
27
+ export function createMediaHandler({
28
+ assets = MEDIA_ASSETS,
29
+ root = new URL('./', import.meta.url),
30
+ } = {}) {
31
+ const cache = new Map();
32
+ async function bytesFor(key, asset) {
33
+ if (!cache.has(key)) {
34
+ const pending = (async () => {
35
+ const file = await open(
36
+ new URL(asset.file, root),
37
+ constants.O_RDONLY | (constants.O_NOFOLLOW || 0),
38
+ );
39
+ try {
40
+ const stats = await file.stat();
41
+ if (!stats.isFile() || stats.size !== asset.size || stats.size > 2000000)
42
+ throw new Error('Invalid media size');
43
+ const bytes = Buffer.alloc(asset.size);
44
+ let offset = 0;
45
+ while (offset < bytes.length) {
46
+ const result = await file.read(bytes, offset, bytes.length - offset, offset);
47
+ if (!result.bytesRead) throw new Error('Incomplete media');
48
+ offset += result.bytesRead;
49
+ }
50
+ if (createHash('sha256').update(bytes).digest('hex') !== asset.sha256)
51
+ throw new Error('Media integrity mismatch');
52
+ return bytes;
53
+ } finally {
54
+ await file.close();
55
+ }
56
+ })();
57
+ cache.set(key, pending);
58
+ pending.catch(() => {
59
+ if (cache.get(key) === pending) cache.delete(key);
60
+ });
61
+ }
62
+ return cache.get(key);
63
+ }
64
+ return async (req, res) => {
65
+ const empty = (status, headers = {}) => {
66
+ if (res.destroyed || res.writableEnded) return;
67
+ res.writeHead(status, { 'Cache-Control': 'no-store', 'Content-Length': '0', ...headers });
68
+ res.end();
69
+ };
70
+ if (req.method !== 'GET' && req.method !== 'HEAD') return empty(405, { Allow: 'GET, HEAD' });
71
+ let path;
72
+ try {
73
+ path = new URL(req.url, 'http://localhost').pathname;
74
+ } catch {
75
+ return empty(404);
76
+ }
77
+ if (!path.startsWith(MEDIA_ROUTE + '/')) return empty(404);
78
+ const key = path.slice(MEDIA_ROUTE.length + 1);
79
+ if (!Object.hasOwn(assets, key)) return empty(404);
80
+ const asset = assets[key],
81
+ etag = '"' + asset.sha256 + '"';
82
+ const rangeHeader =
83
+ req.method === 'GET' && (!req.headers['if-range'] || req.headers['if-range'] === etag)
84
+ ? req.headers.range
85
+ : null;
86
+ const range = mediaRange(rangeHeader, asset.size);
87
+ const matches = (req.headers['if-none-match'] || '')
88
+ .split(',')
89
+ .some((value) => value.trim() === '*' || value.trim().replace(/^W\//, '') === etag);
90
+ if (!range && !matches) return empty(416, { 'Content-Range': `bytes */${asset.size}` });
91
+ try {
92
+ const bytes = await bytesFor(key, asset);
93
+ if (res.destroyed || res.writableEnded) return;
94
+ const headers = {
95
+ 'Content-Type': 'video/webm',
96
+ 'Accept-Ranges': 'bytes',
97
+ 'Cache-Control': 'public, max-age=31536000, immutable',
98
+ ETag: etag,
99
+ 'X-Content-Type-Options': 'nosniff',
100
+ 'Cross-Origin-Resource-Policy': 'same-origin',
101
+ };
102
+ if (matches) {
103
+ res.writeHead(304, headers);
104
+ res.end();
105
+ return;
106
+ }
107
+ headers['Content-Length'] = String(range.end - range.start + 1);
108
+ if (range.status === 206)
109
+ headers['Content-Range'] = `bytes ${range.start}-${range.end}/${asset.size}`;
110
+ res.writeHead(range.status, headers);
111
+ res.end(req.method === 'HEAD' ? undefined : bytes.subarray(range.start, range.end + 1));
112
+ } catch {
113
+ empty(500);
114
+ }
115
+ };
116
+ }
117
+
118
+ export function registerMediaRoute(ctx) {
119
+ ctx.inject?.(['webServer'], (webCtx) => {
120
+ const handler = createMediaHandler();
121
+ webCtx.effect(() => webCtx.webServer.register({ kind: 'prefix', path: MEDIA_ROUTE, handler }));
122
+ });
123
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-offwork",
3
- "version": "1.1.1",
4
- "description": "Harness 页面陪伴角色、下班倒计时、实时收入与喝水提醒。Character companion, clock-out countdown, earnings and hydration reminders for DeepSeek Harness.",
3
+ "version": "1.1.2",
4
+ "description": "Harness 页面角色与小卡片、下班倒计时、全天喝水提醒、独立抽烟计时和收入展示。A Harness companion with clock-out, all-day hydration and smoking timers, earnings and action previews.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "now",
@@ -34,6 +34,8 @@
34
34
  },
35
35
  "files": [
36
36
  "index.js",
37
+ "media-server.js",
38
+ "media-manifest.js",
37
39
  "client.js",
38
40
  "client.source.js",
39
41
  "clock.js",
@@ -42,10 +44,12 @@
42
44
  "pet-view.js",
43
45
  "pet-motion.js",
44
46
  "pet-video.js",
47
+ "pet-reminder.js",
45
48
  "pet-interactions.js",
46
49
  "pet-surfaces.js",
47
50
  "hydration.js",
48
51
  "hydration-store.js",
52
+ "countdown.js",
49
53
  "cordis.patch.yml",
50
54
  "locale/*.json",
51
55
  "icon.svg",
@@ -10,7 +10,17 @@ export function petUIReducer(state, action) {
10
10
  case 'stay':
11
11
  return { ...state, pending: null };
12
12
  case 'navigate': {
13
- if (![null, 'home', 'income', 'earnings', ...PET_EDITOR_SCREENS].includes(action.screen))
13
+ if (
14
+ ![
15
+ null,
16
+ 'home',
17
+ 'income',
18
+ 'earnings',
19
+ 'water-countdown',
20
+ 'smoking',
21
+ ...PET_EDITOR_SCREENS,
22
+ ].includes(action.screen)
23
+ )
14
24
  return state;
15
25
  if (state.screen === action.screen)
16
26
  return { ...state, back: action.root ? null : state.back, menu: null, pending: null };
@@ -46,10 +56,39 @@ export const PET_INTERACTION_MESSAGES = {
46
56
  zh: {
47
57
  petHome: '喝水',
48
58
  petWaterCard: '喝水',
59
+ waterCountdown: '喝水倒计时',
60
+ timerWaterDetails: '查看喝水卡片',
61
+ smokingCountdown: '抽烟倒计时',
62
+ waterTimerShort: '喝水',
63
+ smokingTimerShort: '抽烟',
64
+ timerIdle: '未开始',
65
+ timerRunning: '计时中',
66
+ timerPaused: '已暂停',
67
+ timerDue: '已到时',
68
+ timerStart: '开始计时',
69
+ timerPause: '暂停计时',
70
+ timerResume: '继续计时',
71
+ timerRestart: '重新计时',
72
+ timerStop: '停止计时',
73
+ timerInterval: '间隔(分钟)',
74
+ timerHint:
75
+ '按自然时间计时,后台和刷新后继续。间隔自动保存,下次开始或重新计时时生效。到点后不自动开始下一轮。',
76
+ timerStorageError: '计时数据暂时无法读取或保存,请重试。',
77
+ timerChanged: '计时已在其他标签页更新,请按最新状态操作。',
78
+ timerRetry: '重试读取计时',
79
+ timerLoading: '读取中',
80
+ waterTimerHint: '按自然时间计时,午休、下班和休息日也可提醒;可随时打卡。',
81
+ timerQuiet: '免打扰',
82
+ timerOff: '未开启',
83
+ timerWaiting: '等待开启',
84
+ timerBreak: '已暂停',
85
+ timerFinished: '已结束',
86
+ timerUnavailable: '暂不可用',
49
87
  petCountdownCard: '下班倒计时',
88
+ petCountdownGroup: '计时概览',
50
89
  petEarningsCard: '收入',
51
- petWaterAlert: '喝水提醒已到,可从右键菜单打开喝水卡片',
52
- petPausedHint: '本班已暂停,可手动恢复。',
90
+ petWaterAlert: '查看喝水提醒',
91
+ petPausedHint: '提醒已暂停,手动恢复后重新计时。',
53
92
  petMenu: '宠物菜单',
54
93
  petBack: '返回',
55
94
  petCloseAll: '关闭面板',
@@ -69,16 +108,46 @@ export const PET_INTERACTION_MESSAGES = {
69
108
  petQuietActive: '喝水提醒已静音',
70
109
  petHomeHint: '喝水、歇一会儿,都在这里。',
71
110
  petQuietHint: '按自然时间,到期自动恢复。',
72
- petResume: '恢复本班提醒',
73
- petDismissReminder: '稍后处理',
111
+ petResume: '恢复提醒',
112
+ petDismissReminder: '收起提醒',
74
113
  },
75
114
  en: {
76
115
  petHome: 'Water',
77
116
  petWaterCard: 'Water',
117
+ waterCountdown: 'Water countdown',
118
+ timerWaterDetails: 'Water controls',
119
+ smokingCountdown: 'Smoking countdown',
120
+ waterTimerShort: 'Water',
121
+ smokingTimerShort: 'Smoking',
122
+ timerIdle: 'Not started',
123
+ timerRunning: 'Running',
124
+ timerPaused: 'Paused',
125
+ timerDue: 'Time is up',
126
+ timerStart: 'Start timer',
127
+ timerPause: 'Pause timer',
128
+ timerResume: 'Resume timer',
129
+ timerRestart: 'Restart timer',
130
+ timerStop: 'Stop timer',
131
+ timerInterval: 'Interval (minutes)',
132
+ timerHint:
133
+ 'Uses clock time and continues in the background and after reload. Interval changes save automatically and apply on the next start or restart. Expiry does not start another cycle.',
134
+ timerStorageError: 'Timer data could not be loaded or saved. Please retry.',
135
+ timerChanged: 'The timer changed in another tab. Use the updated state.',
136
+ timerRetry: 'Retry loading timer',
137
+ timerLoading: 'Loading',
138
+ waterTimerHint:
139
+ 'Uses clock time, including lunch, off-hours and days off. You can record a drink at any time.',
140
+ timerQuiet: 'Quiet time',
141
+ timerOff: 'Disabled',
142
+ timerWaiting: 'Waiting',
143
+ timerBreak: 'Paused',
144
+ timerFinished: 'Finished',
145
+ timerUnavailable: 'Unavailable',
78
146
  petCountdownCard: 'Shift countdown',
147
+ petCountdownGroup: 'Timer overview',
79
148
  petEarningsCard: 'Earnings',
80
- petWaterAlert: 'Water is due; open Water from the context menu',
81
- petPausedHint: 'Paused for this shift. Resume when ready.',
149
+ petWaterAlert: 'View water reminder',
150
+ petPausedHint: 'Reminders paused. Resume manually to start a fresh interval.',
82
151
  petMenu: 'Pet menu',
83
152
  petBack: 'Back',
84
153
  petCloseAll: 'Close panel',
@@ -100,13 +169,15 @@ export const PET_INTERACTION_MESSAGES = {
100
169
  petHomeHint: 'A drink or a little break, right here.',
101
170
  petQuietHint: 'Uses clock time and resumes automatically.',
102
171
  petResume: 'Resume reminders',
103
- petDismissReminder: 'Handle later',
172
+ petDismissReminder: 'Dismiss reminder',
104
173
  },
105
174
  };
106
175
  export const PET_INTERACTION_STYLE = `
107
176
  .owc-pet-workspace .owc-glass{opacity:var(--owc-alpha)}
108
177
  .owc-pet-water-card.owc-water-due{border-radius:14px}
109
- .owc-pet-water-alert{display:flex;align-items:center;justify-content:center;gap:6px;padding:8px 12px;border-radius:12px;cursor:default;position:fixed!important;width:140px;box-sizing:border-box;min-height:30px!important;border:1px solid var(--owc-accent)!important;background:var(--dsw-alias-bg-overlay)!important;box-shadow:0 4px 12px #18354b18;font-size:11px!important;z-index:2}
178
+ .owc-pet-water-label{min-width:0;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
179
+ .owc-pet-water-alert svg{flex:none}
180
+ .owc-pet-water-alert{display:flex;align-items:center;justify-content:center;gap:6px;padding:8px 12px;border-radius:12px;cursor:pointer;position:fixed!important;width:140px;box-sizing:border-box;min-height:44px!important;border:1px solid var(--owc-accent)!important;background:var(--dsw-alias-bg-overlay)!important;box-shadow:0 4px 12px #18354b18;font-size:11px!important;z-index:2}
110
181
  .owc-pet-panel.owc-pet-workspace{display:flex;flex-direction:column;overflow:hidden}
111
182
  .owc-pet-workspace>.owc-head{position:relative;flex:none;min-height:46px;padding:8px 12px;box-sizing:border-box}
112
183
  .owc-pet-workspace>.owc-pet-scroll{position:relative;min-height:0;flex:1;overflow:auto;overscroll-behavior:contain;scrollbar-width:none}
package/pet-motion.js CHANGED
@@ -24,6 +24,7 @@ const PET_ACTIONS = {
24
24
  export function createPetMotion(element, host, options = {}) {
25
25
  const document = host.document;
26
26
  const reduced = host.matchMedia?.('(prefers-reduced-motion: reduce)');
27
+ const ambient = options.ambient !== false;
27
28
  let enabled = options.enabled ?? true;
28
29
  let resting = options.resting ?? false;
29
30
  let animation = null;
@@ -38,7 +39,7 @@ export function createPetMotion(element, host, options = {}) {
38
39
  }
39
40
  function sync() {
40
41
  if (!allowed()) stop();
41
- element.dataset.motion = allowed() && !resting ? 'idle' : 'off';
42
+ element.dataset.motion = allowed() && !resting && ambient ? 'idle' : 'off';
42
43
  }
43
44
  function play(action) {
44
45
  if (!allowed() || !Object.hasOwn(PET_ACTIONS, action) || !element.animate) return false;
@@ -0,0 +1,20 @@
1
+ // Page-local presentation state. Never changes reminder deadlines or records.
2
+ export const initialPetReminder = Object.freeze({ eventId: null, armed: false, open: false });
3
+
4
+ export function nextPetReminder(state, { ready, eventId, phase, visible, available, ownsPrompt }) {
5
+ let next;
6
+ if (!ready || !visible || !eventId) next = initialPetReminder;
7
+ else if (['countdown', 'break', 'quiet'].includes(phase))
8
+ next = { eventId, armed: true, open: false };
9
+ else if (phase !== 'due' || !available || state.eventId !== eventId)
10
+ next = { eventId, armed: false, open: false };
11
+ else if (state.open) next = { eventId, armed: false, open: !!ownsPrompt };
12
+ else next = { eventId, armed: state.armed && !ownsPrompt, open: state.armed && !!ownsPrompt };
13
+ return state.eventId === next.eventId && state.armed === next.armed && state.open === next.open
14
+ ? state
15
+ : next;
16
+ }
17
+
18
+ export function dismissPetReminder(state) {
19
+ return state.open || state.armed ? { ...state, armed: false, open: false } : state;
20
+ }
package/pet-surfaces.js CHANGED
@@ -13,10 +13,12 @@ export function petSurfaceWidth(screen, viewport) {
13
13
  {
14
14
  income: 320,
15
15
  earnings: 320,
16
- home: 336,
16
+ home: 320,
17
+ 'water-countdown': 320,
18
+ smoking: 320,
17
19
  water: 360,
18
- appearance: 360,
19
- schedule: 400,
20
+ appearance: 340,
21
+ schedule: 360,
20
22
  menu: 224,
21
23
  reminder: 304,
22
24
  }[screen] ?? 336;
@@ -176,19 +178,141 @@ export const PET_SURFACE_STYLE = `
176
178
  .owc-v2 .owc-appearance-preview{height:260px;display:flex;align-items:center;justify-content:center;border-radius:12px;background:var(--owc-subtle);margin-bottom:16px;overflow:hidden}
177
179
  .owc-v2 .owc-appearance-preview .owc-pet-art{flex:none;animation:none}
178
180
  .owc-v2 .owc-appearance-preview .owc-pet-art *{animation:none!important}
181
+ .owc-v2 .owc-preview-actions{display:flex;gap:6px;flex-wrap:wrap}
182
+ .owc-v2 .owc-preview-actions button{min-width:52px}
183
+ .owc-v2 .owc-preview-actions button[aria-pressed=true]{color:var(--owc-accent);background:color-mix(in srgb,var(--owc-accent) 10%,transparent)}
184
+ .owc-v2 .owc-preview-status{min-height:36px;margin:8px 0 12px}
179
185
  .owc-v2 .owc-appearance-preview p{font-size:13px;color:var(--owc-muted);padding:16px;text-align:center}
180
186
  .owc-v2 .owc-pet-context{padding:0;background:var(--owc-surface);border:1px solid var(--owc-border);border-radius:var(--owc-radius);box-shadow:var(--owc-shadow);scrollbar-width:none}
181
187
  .owc-v2 .owc-pet-context::-webkit-scrollbar{display:none}
182
188
  .owc-v2 .owc-menu-natural{padding:6px;display:flow-root;box-sizing:border-box;min-width:0}
183
189
  .owc-v2 .owc-pet-context button{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;justify-content:flex-start;font-size:13px}
184
190
  .owc-v2 .owc-pet-context hr{border-color:var(--owc-border);margin:6px 4px}
185
- .owc-v2 .owc-pet-toast,.owc-v2 .owc-pet-clock,.owc-v2 .owc-pet-water-alert{background:var(--owc-surface)!important;color:var(--owc-text);border:1px solid var(--owc-border)!important;box-shadow:var(--owc-shadow)}
191
+ .owc-v2.owc-classic{border-radius:16px;touch-action:manipulation}
192
+ .owc-classic .owc-content{max-height:calc(var(--owc-viewport-height) - 2px);overflow:auto}
193
+ .owc-v2 .owc-classic-head{position:sticky;top:0;z-index:2;min-height:46px;padding:8px 12px;background:var(--owc-surface);border-bottom:1px solid var(--owc-border);gap:6px}
194
+ .owc-classic-head .owc-title{font-size:12px;min-width:0;overflow:hidden;text-overflow:ellipsis;touch-action:none}
195
+ .owc-v2.owc-classic.owc-compact{border-radius:14px;border-color:color-mix(in srgb,var(--owc-text) 10%,transparent);box-shadow:0 3px 12px #0a101a1a}
196
+ .owc-classic.owc-compact>.owc-glass{background:var(--owc-surface);background:oklch(from var(--owc-surface) clamp(0,calc(l + .02),1) calc(c * .55) h)}
197
+ .owc-classic-mini{padding:14px;font:400 12px/18px system-ui,sans-serif}
198
+ .owc-classic-mini-top{display:flex;align-items:center;gap:6px;height:32px}
199
+ .owc-classic-mini .owc-grip{flex:1;display:flex;flex-direction:row;align-items:center;gap:8px;min-width:0;touch-action:none;cursor:grab}
200
+ .owc-classic-mini-label{flex:0 0 30px;font:500 10px/14px system-ui,sans-serif;color:var(--owc-muted);text-wrap:balance}
201
+ .owc-classic-mini-time{flex:1;min-width:0;font:500 26px/32px system-ui,sans-serif;letter-spacing:-.3px;font-variant-numeric:tabular-nums;white-space:nowrap}
202
+ .owc-v2 .owc-classic-mini-state{flex:1;min-width:0;font:500 16px/20px system-ui,sans-serif;margin:0;padding:0;min-height:0;text-align:left;overflow-wrap:anywhere}
203
+ .owc-classic-mini[data-clock-phase=done] .owc-classic-mini-state{color:color-mix(in srgb,#43896d 65%,var(--owc-text))}
204
+ .owc-v2 .owc-classic-mini .owc-icon{padding:0;min-width:24px;width:24px;height:24px;min-height:24px;flex-shrink:0;border:0;border-radius:5px;background:transparent;color:var(--owc-muted)}
205
+ .owc-classic-mini-timers{display:flex;align-items:center;gap:10px;height:22px;margin-top:12px}
206
+ .owc-v2 .owc-classic-mini-timer{display:flex;align-items:center;justify-content:flex-start;gap:4px;flex-shrink:0;min-width:0;min-height:24px;padding:0;border:0;border-radius:5px;background:transparent;font:400 12px/18px system-ui,sans-serif;color:var(--owc-muted)}
207
+ .owc-classic-mini-timer svg{flex-shrink:0}
208
+ .owc-classic-mini-timer strong{font:inherit;font-variant-numeric:tabular-nums;color:var(--owc-text);white-space:nowrap}
209
+ .owc-v2 .owc-classic-mini-timer[data-countdown-due=true],.owc-classic-mini-timer[data-countdown-due=true] strong{color:color-mix(in srgb,var(--owc-accent) 55%,var(--owc-text));background:transparent}
210
+ .owc-classic-mini-income{display:flex;align-items:center;gap:3px;margin-left:auto;min-width:24px;flex-shrink:1}
211
+ .owc-v2 .owc-classic-mini-amount{display:block;min-width:0;max-width:45px;min-height:24px;padding:0;border:0;font:400 11px/24px system-ui,sans-serif;color:var(--owc-muted);font-variant-numeric:tabular-nums;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
212
+ .owc-v2 .owc-classic-mini button:hover{background:color-mix(in srgb,var(--owc-text) 6%,transparent)}
213
+ .owc-classic-mini button:focus-visible{outline:2px solid var(--owc-accent);outline-offset:3px}
214
+ .owc-classic-overview{padding:16px;display:flex;flex-direction:column;gap:14px}
215
+ .owc-classic-kicker{display:flex;align-items:center;gap:6px;color:var(--owc-muted);font-size:11px;margin-bottom:7px}
216
+ .owc-classic-time{font-size:34px;font-weight:650;line-height:1.2;letter-spacing:.5px;font-variant-numeric:tabular-nums}
217
+ .owc-classic-state{font-size:22px;line-height:1.3;margin:0;color:var(--owc-text)}
218
+ .owc-v2 .owc-classic-hint{font-size:11px;color:var(--owc-muted);line-height:1.6;margin:7px 0 0}
219
+ .owc-classic-hero>button{margin-top:12px;min-height:38px}
220
+ .owc-classic-schedule{display:flex;justify-content:space-between;flex-wrap:wrap;gap:4px;margin-top:10px;color:var(--owc-muted);font-size:10px;font-variant-numeric:tabular-nums}
221
+ .owc-classic-timers{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:10px}
222
+ .owc-v2 .owc-classic-tile{display:flex;flex-direction:column;align-items:flex-start;gap:7px;padding:11px;min-width:0;border:1px solid var(--owc-border);border-radius:12px;background:var(--owc-subtle);text-align:left}
223
+ .owc-classic-tile-title{display:flex;align-items:center;gap:6px;font-size:11px;color:var(--owc-muted)}
224
+ .owc-classic-tile strong{max-width:100%;font-size:17px;line-height:1.2;font-weight:600;font-variant-numeric:tabular-nums;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
225
+ .owc-classic-tile-hint{display:block;max-width:100%;font-size:10px;color:var(--owc-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
226
+ .owc-classic-income{display:flex;align-items:center;gap:4px;padding-top:10px;border-top:1px solid var(--owc-border)}
227
+ .owc-v2 .owc-classic-income-link{display:flex;align-items:center;justify-content:space-between;flex:1;min-width:0;gap:8px;padding:4px 0;font-size:11px;color:var(--owc-muted);min-height:32px}
228
+ .owc-classic-income-link strong{font-size:12px;font-weight:500;color:var(--owc-text);font-variant-numeric:tabular-nums}
229
+ .owc-classic-income .owc-text{font-size:10px;gap:4px;flex-shrink:0}
230
+ .owc-classic-details{border-top:1px solid var(--owc-border);padding-top:12px;font-size:11px;color:var(--owc-muted)}
231
+ .owc-classic-detail-title{display:flex;justify-content:space-between;margin-bottom:8px}
232
+ .owc-classic-details p{font-size:11px;line-height:1.5;margin:8px 0}
233
+ .owc-classic-feedback{margin:0;font-size:11px;color:var(--owc-accent)}
234
+ .owc-classic>.owc-content>.owc-note,.owc-classic>.owc-content>.owc-error{margin:8px 16px}
235
+ .owc-v2 .owc-pet-toast,.owc-v2 .owc-pet-water-alert{background:var(--owc-surface)!important;color:var(--owc-text);border:1px solid var(--owc-border)!important;box-shadow:var(--owc-shadow)}
186
236
  .owc-v2 .owc-pet-toast{border-radius:12px;font-size:12px}
187
237
  .owc-v2 .owc-pet-unsaved{padding:20px 16px}
188
238
  .owc-v2 .owc-pet-unsaved p{font-size:13px;overflow-wrap:anywhere}
239
+ .owc-detail-page{--owc-detail-surface:var(--owc-surface);--owc-control-height:38px;border-radius:14px}
240
+ @supports(color:oklch(from white l c h)){.owc-detail-page{--owc-detail-surface:oklch(from var(--owc-surface) clamp(0,calc(l + .02),1) calc(c * .55) h)}}
241
+ .owc-detail-page>.owc-glass{background:var(--owc-detail-surface);border-color:var(--owc-border)}
242
+ .owc-pet-workspace.owc-detail-page>.owc-head,.owc-classic.owc-detail-page .owc-classic-head{min-height:44px;padding:8px 16px;gap:8px;background:var(--owc-detail-surface);border-bottom:1px solid var(--owc-border)}
243
+ .owc-detail-page .owc-head .owc-icon{width:28px;height:28px;min-width:28px;min-height:28px;padding:4px}
244
+ .owc-detail-page [data-pet-page-title],.owc-classic.owc-detail-page .owc-classic-head .owc-title{font-size:14px;font-weight:550}
245
+ .owc-detail-page .owc-form,.owc-detail-page .owc-water-form,.owc-detail-page .owc-main,.owc-detail-page .owc-pet-home{padding:16px}
246
+ .owc-v2 .owc-pet-natural>.owc-form:last-child,.owc-v2 .owc-pet-natural>.owc-water-form:last-child{padding-bottom:16px}
247
+ .owc-detail-page fieldset.owc-setting-group{border:0;border-radius:0;background:transparent;padding:0;margin:0 0 18px;min-width:0}
248
+ .owc-detail-page fieldset.owc-setting-group legend{padding:0;margin-bottom:10px;font-size:12px;font-weight:550;color:var(--owc-text)}
249
+ .owc-detail-page .owc-appearance legend{display:none}
250
+ .owc-detail-page .owc-salary-legend{display:flex;align-items:center;justify-content:space-between;width:100%}
251
+ .owc-detail-page .owc-salary-legend .owc-icon{min-width:24px;min-height:24px;padding:3px}
252
+ .owc-detail-page .owc-field{position:relative;margin:0 0 12px}
253
+ .owc-detail-page .owc-field>span{font-size:12px;margin-bottom:6px}
254
+ .owc-detail-page .owc-row2{gap:12px}
255
+ .owc-detail-page :is(input[type=text],input[type=number],input[type=time],select){min-height:38px;border-radius:8px;background:color-mix(in srgb,var(--owc-text) 3%,var(--owc-detail-surface));border-color:var(--owc-border);font-size:13px}
256
+ .owc-detail-page .owc-private{box-sizing:border-box;min-height:38px;border:1px solid var(--owc-border);border-radius:8px;background:color-mix(in srgb,var(--owc-text) 3%,var(--owc-detail-surface));padding:8px 10px;font-size:14px}
257
+ .owc-detail-page .owc-seg{padding:2px;border-radius:8px;background:var(--owc-subtle)}
258
+ .owc-detail-page .owc-seg button{min-height:32px;font-size:12px;border-radius:6px}
259
+ .owc-detail-page button.owc-primary{min-height:38px;border-radius:8px;font-size:13px;font-weight:550;box-shadow:none}
260
+ .owc-v2 .owc-detail-page .owc-save,.owc-v2.owc-detail-page .owc-save{position:sticky;bottom:0;z-index:2;display:flex;justify-content:flex-end;gap:8px;padding:12px 0 0;margin:14px 0 0;border-top:1px solid var(--owc-border);background:var(--owc-detail-surface)}
261
+ .owc-detail-page .owc-save button{min-height:38px;font-size:13px;border-radius:8px;padding:8px 16px}
262
+ .owc-detail-page .owc-save button:not(.owc-primary){background:transparent;border-color:transparent;color:var(--owc-muted)}
263
+ .owc-detail-page .owc-detail-hero{margin:0 0 16px}
264
+ .owc-detail-page p.owc-detail-kicker{margin:0 0 6px;font-size:12px;color:var(--owc-muted)}
265
+ .owc-detail-value,.owc-detail-page .owc-countdown-value,.owc-detail-page .owc-time,.owc-detail-page .owc-money{font-size:28px;line-height:1.25;font-weight:500;letter-spacing:-.3px;font-variant-numeric:tabular-nums}
266
+ .owc-detail-page .owc-detail-state{font-size:20px;letter-spacing:0}
267
+ .owc-detail-meta{display:flex;align-items:center;justify-content:space-between;gap:10px;min-height:18px;margin-top:8px;font-size:11px;color:var(--owc-muted)}
268
+ .owc-detail-feedback{color:var(--owc-accent);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
269
+ .owc-detail-page .owc-detail-primary-actions{display:flex;align-items:center;gap:8px}
270
+ .owc-detail-primary-actions>.owc-primary{flex:1}
271
+ .owc-detail-page .owc-detail-secondary{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:10px}
272
+ .owc-detail-page .owc-detail-secondary button{background:transparent;border:0;padding:6px 0;font-size:12px;color:var(--owc-muted);min-height:30px}
273
+ .owc-detail-page details.owc-detail-more,.owc-detail-page .owc-water-form>details,.owc-detail-page details.owc-advanced{margin:12px 0 0;padding:0;border:0;border-radius:0;background:transparent;font-size:12px}
274
+ .owc-detail-page details>summary{font-size:12px;color:var(--owc-muted);padding:4px 0;cursor:pointer}
275
+ .owc-detail-page details p{font-size:11px;line-height:1.6;color:var(--owc-muted)}
276
+ .owc-detail-options{display:flex;gap:8px;margin-top:10px}
277
+ .owc-detail-options button{flex:1;min-height:34px;border:1px solid var(--owc-border);border-radius:8px;font-size:12px}
278
+ .owc-detail-page .owc-pet-home.owc-water-due{background:transparent;border-radius:0}
279
+ .owc-detail-page .owc-countdown-card{gap:12px}
280
+ .owc-detail-page .owc-countdown-card .owc-detail-hero{margin-bottom:0}
281
+ .owc-detail-page .owc-countdown-actions{gap:8px}
282
+ .owc-detail-page .owc-countdown-actions>.owc-primary{flex:1 1 100%}
283
+ .owc-detail-page .owc-countdown-actions>button:not(.owc-primary){min-height:30px;background:transparent;border:0;font-size:12px;color:var(--owc-muted)}
284
+ .owc-detail-page .owc-water-toggle,.owc-detail-page .owc-appearance .owc-check{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:38px;margin:0 0 10px;padding:0;font-size:13px;font-weight:400}
285
+ .owc-detail-page .owc-appearance .owc-check input{order:2;margin-left:auto}
286
+ .owc-detail-page .owc-water-toggle input,.owc-detail-page .owc-appearance .owc-check input{appearance:none;flex:none;width:32px;height:18px;min-height:18px;padding:0;border:0;border-radius:20px;background:color-mix(in srgb,var(--owc-text) 22%,var(--owc-detail-surface));position:relative;cursor:pointer}
287
+ .owc-detail-page .owc-water-toggle input:before,.owc-detail-page .owc-appearance .owc-check input:before{content:'';position:absolute;width:14px;height:14px;top:2px;left:2px;border-radius:50%;background:#fff;box-shadow:0 1px 3px #0002}
288
+ .owc-detail-page .owc-water-toggle input:checked,.owc-detail-page .owc-appearance .owc-check input:checked{background:var(--owc-accent)}
289
+ .owc-detail-page .owc-water-toggle input:checked:before,.owc-detail-page .owc-appearance .owc-check input:checked:before{left:16px}
290
+ .owc-detail-page .owc-water-interval{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:12px;margin:6px 0 10px}
291
+ .owc-detail-page .owc-water-interval input{width:92px;max-width:92px;text-align:center}
292
+ .owc-detail-page .owc-water-presets{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:5px;margin:0 0 12px}
293
+ .owc-detail-page .owc-water-presets button{font-size:12px;font-weight:400;min-height:32px;padding:4px;border-radius:7px;background:transparent}
294
+ .owc-detail-page .owc-water-presets button[aria-pressed=true]{background:color-mix(in srgb,var(--owc-accent) 9%,var(--owc-detail-surface));border-color:color-mix(in srgb,var(--owc-accent) 35%,transparent);color:var(--owc-accent)}
295
+ .owc-detail-page .owc-water-form>p{font-size:11px;line-height:1.6;margin:8px 0;color:var(--owc-muted)}
296
+ .owc-detail-page p.owc-error{color:color-mix(in srgb,#dc4c64 70%,var(--owc-text))}
297
+ .owc-detail-page .owc-stats{border:0;background:transparent;padding:0;gap:20px;margin:16px 0}
298
+ .owc-detail-page .owc-stats>div{padding:0;border:0;border-radius:0;background:transparent;min-width:0}
299
+ .owc-detail-page .owc-stats strong{font-size:15px;font-weight:500}
300
+ .owc-detail-page .owc-meta{border:0;margin:8px 0 0;padding:0;font-size:11px}
301
+ .owc-detail-page .owc-row-actions{margin-top:14px;gap:12px}
302
+ .owc-detail-page .owc-row-actions button{font-size:11px;min-height:30px;padding:3px 0}
303
+ .owc-detail-page .owc-progress{height:3px;margin:14px 0 16px}
304
+ .owc-detail-page .owc-preview{background:transparent;border:0;padding:0;margin:10px 0;font-size:11px;color:var(--owc-muted)}
305
+ .owc-v2 .owc-appearance-preview{height:180px;border-radius:10px;background:color-mix(in srgb,var(--owc-text) 3%,var(--owc-detail-surface,var(--owc-surface)))}
306
+ .owc-v2 .owc-appearance-preview>.owc-pet-art{transform:scale(.7);transform-origin:50% 50%}
307
+ .owc-detail-page .owc-appearance input[type=range]{width:100%}
308
+ .owc-detail-page .owc-range-value{position:absolute;right:0;top:0;font-size:11px;color:var(--owc-muted)}
309
+ .owc-detail-page .owc-preview-tools{margin-bottom:14px}
310
+ .owc-detail-page .owc-preview-actions button{min-height:32px;font-size:12px;border-radius:7px}
311
+ .owc-detail-page .owc-preview-status{font-size:11px;line-height:1.5}
312
+ @media(pointer:coarse){.owc-detail-page{--owc-control-height:44px}.owc-detail-page .owc-head .owc-icon{width:44px;height:44px;min-width:44px;min-height:44px}.owc-detail-page button.owc-primary,.owc-detail-page .owc-save button{min-height:44px}.owc-detail-page :is(input[type=text],input[type=number],input[type=time],select){font-size:16px;min-height:44px}}
189
313
  @container (max-width:310px){.owc-v2 .owc-row2{grid-template-columns:1fr}.owc-v2 .owc-stats{gap:8px}.owc-v2 .owc-days{gap:2px}.owc-v2 .owc-days button{font-size:11px}}
190
314
  @media (pointer:coarse){.owc-v2{--owc-control-height:44px}.owc-v2 .owc-icon{min-width:44px}.owc-v2 button.owc-primary{min-height:44px}.owc-v2 :is(input[type=text],input[type=time],input[type=number],select){font-size:16px}.owc-v2 input[type=range]{min-height:36px}.owc-v2 .owc-check{min-height:44px}}
191
- @media (max-height:600px){.owc-v2 .owc-appearance-preview{height:120px}.owc-v2 .owc-appearance-preview svg{transform:scale(.55)}.owc-v2 .owc-appearance-preview img{transform:scale(.45)}}
315
+ @media (max-height:600px){.owc-v2 .owc-appearance-preview{height:120px}.owc-v2 .owc-appearance-preview>.owc-pet-art{transform:scale(.45);transform-origin:50% 50%}.owc-v2 .owc-appearance-preview>svg.owc-pet-art{transform:scale(.55)}}
192
316
  @media (prefers-contrast:more){.owc-v2{--owc-border:var(--owc-text)}.owc-v2 .owc-glass{opacity:1!important}}
193
317
  @media (prefers-reduced-transparency:reduce){.owc-v2 .owc-glass{opacity:1!important;backdrop-filter:none;-webkit-backdrop-filter:none}}
194
318
  `;