diff --git a/README.md b/README.md
index 2009a40..3b6fc77 100644
--- a/README.md
+++ b/README.md
@@ -10,11 +10,13 @@
- **Today**:每日文字记录、情绪天气、1–5 能量等级,以及随机生成的“今日电影”。
- **Timeline**:按时间轴浏览、编辑和删除记忆。
- **Museum**:将重要记忆长期珍藏为馆藏。
+- **Tasks / Goals**:创建目标和待办,关联任务、设置截止日期与优先级,查看今天/即将开始/已完成事项并追踪目标进度。
- **Insights / Profile**:查看情绪、能量、连续记录与个人系统状态。
- **System**:深浅主题、自动识别浏览器语言(也可固定中文/English)、JSON 导入导出与本地存储说明。
- **Bing 每日一图**:桌面壁纸每日自动更新;由 Life OS 同源代理读取,避免浏览器跨域限制。
- **Windows 式操作**:桌面端单击选择、双击打开;右键桌面或 App 图标可使用快捷菜单。
- **快捷键与草稿恢复**:`Ctrl / ⌘ + K` 打开启动器,`Ctrl / ⌘ + F` 搜索记忆,`Ctrl / ⌘ + S` 归档 Today;当天未归档文字会自动恢复。
+- **Tasks 快捷键**:`Alt + 6` 打开目标与待办,`Alt + 7` 打开 System。
- **移动端**:App 以全屏方式打开,保留任务栏恢复入口。
- **个人云端**:使用 ChatGPT 登录后,数据按账号隔离并同步到 SQLite 兼容的 D1 数据库。
@@ -32,7 +34,7 @@
- `lifeos_settings`
- `lifeos_system`
-备份格式包含 `schemaVersion: 1`,为后续数据迁移预留兼容空间。
+备份格式包含 `schemaVersion: 1`,为后续数据迁移预留兼容空间。目标和待办保存在 `lifeos_system.tasksBoard`,随现有云端 system 字段同步,并包含在新版 JSON 备份中。
## 运行
@@ -50,7 +52,8 @@ npm run validate
## 项目结构
```text
-public/index.html # 界面、样式与本地数据逻辑
+public/index.html # 桌面框架与窗口交互
+public/tasks.js # 目标与待办 App
public/addons.js # 窗口与辅助交互
worker/index.js # ChatGPT 登录门槛、云端数据 API 与 Bing 壁纸代理
drizzle/ # SQLite / D1 数据库迁移
diff --git a/package.json b/package.json
index 1d2eea5..890db01 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "life-os",
- "version": "1.1.0",
+ "version": "1.2.0",
"private": true,
"type": "module",
"scripts": {
diff --git a/public/addons.js b/public/addons.js
index 35fd590..006cdff 100644
--- a/public/addons.js
+++ b/public/addons.js
@@ -1,8 +1,8 @@
/* Life OS reliability and productivity add-ons. */
(() => {
const DRAFT_KEY = 'lifeos_today_draft_v1';
- const APP_VERSION = '1.1.0';
- const appIds = ['today', 'timeline', 'museum', 'insights', 'profile', 'system'];
+ const APP_VERSION = '1.2.0';
+ const appIds = ['today', 'timeline', 'museum', 'insights', 'profile', 'tasks', 'system'];
const t = (key, values) => LifeI18n.t(key, values);
function readDraft() {
@@ -75,7 +75,7 @@
});
}
function exportData() {
- const data = { app: 'Life OS', version: APP_VERSION, schemaVersion: 1, exportedAt: new Date().toISOString(), profile: profile(), memories: mems(), museum: museum(), settings: settings() };
+ const data = { app: 'Life OS', version: APP_VERSION, schemaVersion: 1, exportedAt: new Date().toISOString(), profile: profile(), memories: mems(), museum: museum(), settings: settings(), system: sys() };
const link = document.createElement('a');
link.href = URL.createObjectURL(new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }));
link.download = `life-os-backup-${day()}.json`; link.click();
@@ -89,8 +89,15 @@
try { return [field, JSON.parse(localStorage.getItem(key) || JSON.stringify(cloudFallback[field]))]; } catch { return [field, cloudFallback[field]]; }
}));
}
- function hasLocalLife(state) { return state.memories.length || state.museum.length || Object.keys(state.profile).length || Object.keys(state.settings).length > 2; }
+ function hasLocalLife(state) { return state.memories.length || state.museum.length || Object.keys(state.profile).length || Object.keys(state.settings).length > 2 || state.system?.tasksBoard?.goals?.length || state.system?.tasksBoard?.tasks?.length; }
function applyCloudState(state) {
+ let localSystem = {};
+ try { localSystem = JSON.parse(localStorage.getItem('lifeos_system') || '{}'); } catch {}
+ const localBoard = localSystem.tasksBoard, cloudBoard = state.system?.tasksBoard;
+ if ((localBoard?.goals?.length || localBoard?.tasks?.length) && state.system) {
+ const mergeById = (cloudItems = [], localItems = []) => [...cloudItems, ...localItems.filter(item => !cloudItems.some(existing => existing.id === item.id))];
+ state = { ...state, system: { ...state.system, tasksBoard: { goals: mergeById(cloudBoard?.goals, localBoard.goals), tasks: mergeById(cloudBoard?.tasks, localBoard.tasks) } } };
+ }
applyingCloud = true;
Object.entries(CLOUD_KEYS).forEach(([field, key]) => localStorage.setItem(key, JSON.stringify(state[field] ?? cloudFallback[field])));
applyingCloud = false;
@@ -156,7 +163,7 @@
if (mod && e.key.toLowerCase() === 'k') { e.preventDefault(); toggleLauncher(); }
if (mod && e.key.toLowerCase() === 'f') { e.preventDefault(); openSearch(); }
if (mod && e.key.toLowerCase() === 's' && document.querySelector('#entry')) { e.preventDefault(); archiveToday(); }
- if (e.altKey && /^[1-6]$/.test(e.key)) { e.preventDefault(); openApp(appIds[Number(e.key) - 1]); }
+ if (e.altKey && /^[1-7]$/.test(e.key)) { e.preventDefault(); openApp(appIds[Number(e.key) - 1]); }
if (e.key === 'Escape') { closeSearch(); document.querySelector('#launcher')?.classList.add('hidden'); document.querySelector('#calendar')?.classList.add('hidden'); document.querySelector('#life-context')?.remove(); }
});
})();
diff --git a/public/i18n-renderers.js b/public/i18n-renderers.js
index 3a02599..73b561a 100644
--- a/public/i18n-renderers.js
+++ b/public/i18n-renderers.js
@@ -54,11 +54,29 @@
};
window.saveProfile = () => { save('lifeos_profile', { name: pname.value, birthday: pbday.value, startDate: pstart.value || day(), motto: pmotto.value }); renderAll(); toast(t('profile.saved')); };
window.systemApp = container => {
- const state = settings(), bytes = new Blob([JSON.stringify({ mems: mems(), museum: museum(), profile: profile() })]).size, oldBackup = state.lastBackup && Date.now() - new Date(state.lastBackup) > 12096e5, detected = browserLanguage() === 'zh' ? t('system.detectedZh') : t('system.detectedEn');
- container.innerHTML = `
${t('system.eyebrow')}
${t('system.title')}
${t('system.appearance')}
${['dark','light','auto'].map(item => ``).join('')}
${t('system.language')}
${detected}
${t('system.storage')}
${t('system.local')}
${lang() === 'zh' ? `${mems().length} 条记忆 · ${museum().length} 件博物馆藏品 · 约 ${Math.ceil(bytes/1024)} KB` : `${mems().length} memories · ${museum().length} museum items · Approx. ${Math.ceil(bytes/1024)} KB`}
${t('system.dataStorage')}
${t('system.stored')}
${t('system.backups')}
${t('system.lastBackup',{value:state.lastBackup ? short(state.lastBackup.slice(0,10)) : t('common.never')})}${oldBackup ? `
${t('system.recommended')}` : ''}
${t('system.version')}
Life OS
Version 1.1.0 · Schema v1
`;
+ const state = settings(), bytes = new Blob([JSON.stringify({ mems: mems(), museum: museum(), profile: profile(), system: sys() })]).size, oldBackup = state.lastBackup && Date.now() - new Date(state.lastBackup) > 12096e5, detected = browserLanguage() === 'zh' ? t('system.detectedZh') : t('system.detectedEn');
+ container.innerHTML = `${t('system.eyebrow')}
${t('system.title')}
${t('system.appearance')}
${['dark','light','auto'].map(item => ``).join('')}
${t('system.language')}
${detected}
${t('system.storage')}
${t('system.local')}
${lang() === 'zh' ? `${mems().length} 条记忆 · ${museum().length} 件博物馆藏品 · ${sys().tasksBoard?.tasks?.length || 0} 项待办 · 约 ${Math.ceil(bytes/1024)} KB` : `${mems().length} memories · ${museum().length} museum items · ${sys().tasksBoard?.tasks?.length || 0} tasks · Approx. ${Math.ceil(bytes/1024)} KB`}
${t('system.dataStorage')}
${t('system.stored')}
${t('system.backups')}
${t('system.lastBackup',{value:state.lastBackup ? short(state.lastBackup.slice(0,10)) : t('common.never')})}${oldBackup ? `
${t('system.recommended')}` : ''}
${t('system.version')}
Life OS
Version 1.2.0 · Schema v1
`;
};
window.importPreview = () => { const data = importData; rootModal().innerHTML = `${t('dialog.backup')}
${t('dialog.ready')}
${t('dialog.exported',{date:short(data.exportedAt.slice(0,10))})}
${t('dialog.memories',{count:data.memories.length})}
${t('dialog.museumItems',{count:data.museum.length})}
`; };
window.replaceConfirm = () => { rootModal().innerHTML = `${t('dialog.final')}
${t('dialog.replaceQuestion')}
${t('dialog.replaceWarning')}
`; };
+ window.applyImport = mode => {
+ const data = importData;
+ if (mode === 'replace') {
+ save('lifeos_memories', data.memories); save('lifeos_museum', data.museum); save('lifeos_profile', data.profile || {}); save('lifeos_settings', data.settings || {}); save('lifeos_system', data.system || { schemaVersion: 1 });
+ } else {
+ const memories = mems(), ids = new Set(memories.map(item => item.id)), dates = new Set(memories.map(item => item.date));
+ save('lifeos_memories', [...memories, ...data.memories.filter(item => !ids.has(item.id) && !dates.has(item.date))]);
+ const artifacts = museum(), artifactIds = new Set(artifacts.map(item => item.id));
+ save('lifeos_museum', [...artifacts, ...data.museum.filter(item => !artifactIds.has(item.id))]);
+ const current = sys(), importedBoard = data.system?.tasksBoard;
+ if (importedBoard && Array.isArray(importedBoard.goals) && Array.isArray(importedBoard.tasks)) {
+ current.tasksBoard ||= { goals: [], tasks: [] };
+ for (const kind of ['goals', 'tasks']) { const known = new Set((current.tasksBoard[kind] || []).map(item => item.id)); current.tasksBoard[kind] = [...(current.tasksBoard[kind] || []), ...importedBoard[kind].filter(item => !known.has(item.id))]; }
+ save('lifeos_system', current);
+ }
+ }
+ rootModal().innerHTML = ''; toast('feedback.restoring'); setTimeout(() => { renderAll(); toast('feedback.restored'); }, 500);
+ };
window.resetConfirm = () => { rootModal().innerHTML = `${t('system.reset')}
${t('dialog.resetQuestion')}
${t('dialog.resetWarning')}
`; };
window.toast = key => { const message = key.includes('.') ? t(key) : key; const node = document.createElement('div'); node.className = 'toast'; node.textContent = message; document.body.append(node); setTimeout(() => node.remove(), 2000); };
window.minWin = id => { const windowNode = windows[id]; if (!windowNode) return; windowNode.classList.add('min'); if (activeWindow === id) { activeWindow = Object.keys(windows).find(key => key !== id && !windows[key].classList.contains('min')) || null; if (activeWindow) focus(windows[activeWindow]); } renderTaskbar(); toast('feedback.minimized'); };
diff --git a/public/i18n.js b/public/i18n.js
index a82994a..8022ca3 100644
--- a/public/i18n.js
+++ b/public/i18n.js
@@ -1,7 +1,7 @@
/* Life OS internationalisation: one locale catalogue, no DOM text rewriting. */
(() => {
const en = {
- app: { today: 'TODAY', timeline: 'TIMELINE', museum: 'MUSEUM', insights: 'INSIGHTS', profile: 'PROFILE', system: 'SYSTEM' },
+ app: { today: 'TODAY', timeline: 'TIMELINE', museum: 'MUSEUM', insights: 'INSIGHTS', profile: 'PROFILE', tasks: 'TASKS', system: 'SYSTEM' },
common: { save: 'SAVE', cancel: 'CANCEL', edit: 'EDIT', delete: 'DELETE', close: 'Close', open: 'Open', minimize: 'Minimize', maximize: 'Maximize', restore: 'Restore window', energy: 'ENERGY', days: 'DAYS', never: 'NEVER' },
today: { prompt: 'How was your day?', placeholder: 'Write something about today...', archive: 'ARCHIVE TODAY', update: 'UPDATE MEMORY', weather: "TODAY'S WEATHER", energy: 'ENERGY LEVEL', film: "TODAY'S FILM", archiving: 'ARCHIVING MEMORY...', archived: 'MEMORY ARCHIVED' },
weather: { clear: 'Clear', light: 'Light', cloudy: 'Cloudy', rain: 'Rain', storm: 'Storm' },
@@ -12,10 +12,11 @@
system: { eyebrow: 'SYSTEM SETTINGS', title: 'System, local and yours.', appearance: 'APPEARANCE', language: 'LANGUAGE', browserLanguage: 'BROWSER LANGUAGE', detectedZh: 'Detected: Chinese', detectedEn: 'Detected: English', auto: 'AUTO', data: 'DATA', storage: 'STORAGE', dataStorage: 'DATA STORAGE', export: 'EXPORT DATA', import: 'IMPORT DATA', reset: 'RESET LIFE OS', local: 'Local Storage', stored: 'Your data is stored locally in this browser.', backups: 'Create regular backups to prevent data loss.', lastBackup: 'LAST BACKUP · {value}', recommended: 'BACKUP RECOMMENDED', version: 'VERSION', theme: { dark: 'DARK', light: 'LIGHT', auto: 'AUTO' } },
dialog: { backup: 'LIFE OS BACKUP', ready: 'Ready to restore', exported: 'Exported: {date}', memories: 'Memories: {count}', museumItems: 'Museum Items: {count}', merge: 'MERGE', replaceAll: 'REPLACE ALL DATA', final: 'FINAL CONFIRMATION', replaceQuestion: 'Replace all Life OS data?', replaceWarning: 'This will replace all current Life OS data.', replace: 'REPLACE DATA', resetQuestion: 'Reset all local data?', resetWarning: 'Export a backup first if you want to keep it.', resetData: 'RESET DATA' },
feedback: { backup: 'BACKUP CREATED', invalid: 'INVALID BACKUP FILE', restored: 'SYSTEM RESTORED', restoring: 'RESTORING SYSTEM...', minimized: 'MINIMIZED — TAP THE APP ICON TO RESTORE', languageZh: 'Language set to Chinese', languageEn: 'Language set to English', followingZh: 'Following browser language: Chinese', followingEn: 'Following browser language: English' },
- addon: { draftRestored: 'DRAFT RESTORED', searchTitle: 'SEARCH MEMORY ARCHIVE', searchPlaceholder: 'Search titles, notes, moods…', searchHint: 'ESC to close · click a result to open it', untitled: 'Untitled memory', noResults: 'No memories found.', syncPaused: 'CLOUD SYNC PAUSED — CHANGES STAY ON THIS DEVICE', syncReady: 'CLOUD SYNC READY', migrateQuestion: 'Move this browser’s Life OS to the cloud?', migrateBody: 'Your existing local records will become the first synced copy for this ChatGPT account.', migrate: 'MIGRATE & SYNC', fresh: 'START FRESH', migrated: 'LOCAL LIFE OS MIGRATED', cloudRestored: 'CLOUD LIFE OS RESTORED', cloudUnavailable: 'CLOUD STORAGE UNAVAILABLE — LOCAL MODE ACTIVE' }
+ addon: { draftRestored: 'DRAFT RESTORED', searchTitle: 'SEARCH MEMORY ARCHIVE', searchPlaceholder: 'Search titles, notes, moods…', searchHint: 'ESC to close · click a result to open it', untitled: 'Untitled memory', noResults: 'No memories found.', syncPaused: 'CLOUD SYNC PAUSED — CHANGES STAY ON THIS DEVICE', syncReady: 'CLOUD SYNC READY', migrateQuestion: 'Move this browser’s Life OS to the cloud?', migrateBody: 'Your existing local records will become the first synced copy for this ChatGPT account.', migrate: 'MIGRATE & SYNC', fresh: 'START FRESH', migrated: 'LOCAL LIFE OS MIGRATED', cloudRestored: 'CLOUD LIFE OS RESTORED', cloudUnavailable: 'CLOUD STORAGE UNAVAILABLE — LOCAL MODE ACTIVE' },
+ tasks: { eyebrow: 'GOALS & TASKS', title: 'Make room for what matters.', subtitle: 'A small, steady plan for today and the days ahead.', addGoal: 'NEW GOAL', addTask: 'ADD TASK', taskPlaceholder: 'What needs doing?', goalPlaceholder: 'What do you want to move toward?', goalDescription: 'A note about this goal (optional)', finishedGoals: 'COMPLETED GOALS', goalComplete: 'COMPLETE GOAL', goalReopen: 'REOPEN GOAL', all: 'ALL', today: 'TODAY', upcoming: 'UPCOMING', done: 'DONE', activeGoals: 'ACTIVE GOALS', noGoals: 'Start with one goal worth moving toward.', noTasks: 'Nothing on this list yet.', due: 'DUE {date}', noDate: 'NO DUE DATE', priority: { low: 'LOW', normal: 'NORMAL', high: 'HIGH' }, goal: 'GOAL', anyGoal: 'NO GOAL', save: 'SAVE', cancel: 'CANCEL', complete: 'MARK COMPLETE', reopen: 'REOPEN', delete: 'DELETE', goalTitle: 'New goal', taskTitle: 'New task', goalCreated: 'GOAL CREATED', taskAdded: 'TASK ADDED', taskRemoved: 'TASK REMOVED', goalRemoved: 'GOAL REMOVED', confirmGoalDelete: 'Delete this goal?', confirmGoalDeleteBody: 'Tasks linked to it will be kept without a goal.', target: 'TARGET DATE', completed: 'COMPLETED', progress: '{done} OF {total} DONE', todayHeading: 'Focus for today', openCount: '{count} OPEN TASKS', overdue: 'OVERDUE' }
};
const zh = {
- app: { today: '今日', timeline: '时间线', museum: '人生博物馆', insights: '洞察', profile: '个人资料', system: '系统' },
+ app: { today: '今日', timeline: '时间线', museum: '人生博物馆', insights: '洞察', profile: '个人资料', tasks: '目标与待办', system: '系统' },
common: { save: '保存', cancel: '取消', edit: '编辑', delete: '删除', close: '关闭', open: '打开', minimize: '最小化', maximize: '最大化', restore: '恢复窗口', energy: '能量', days: '天', never: '从未' },
today: { prompt: '今天过得怎么样?', placeholder: '写下今天的片段…', archive: '归档今日', update: '更新记忆', weather: '今日天气', energy: '能量等级', film: '今日电影', archiving: '正在归档…', archived: '记忆已归档' },
weather: { clear: '晴朗', light: '微光', cloudy: '多云', rain: '雨天', storm: '风暴' },
@@ -26,7 +27,8 @@
system: { eyebrow: '系统设置', title: '你的个人系统。', appearance: '外观', language: '语言', browserLanguage: '浏览器语言', detectedZh: '当前识别:中文', detectedEn: '当前识别:英语', auto: '跟随浏览器', data: '数据', storage: '存储', dataStorage: '数据存储', export: '导出数据', import: '导入数据', reset: '重置 Life OS', local: '本地存储', stored: '你的数据仅存储在此浏览器中。', backups: '定期备份,让记忆一直在。', lastBackup: '上次备份 · {value}', recommended: '建议备份', version: '版本', theme: { dark: '深色', light: '浅色', auto: '跟随系统' } },
dialog: { backup: 'Life OS 备份', ready: '准备恢复', exported: '导出时间:{date}', memories: '记忆:{count}', museumItems: '博物馆藏品:{count}', merge: '合并', replaceAll: '替换全部数据', final: '最终确认', replaceQuestion: '替换全部 Life OS 数据?', replaceWarning: '这将覆盖当前所有 Life OS 数据。', replace: '替换数据', resetQuestion: '重置所有本地数据?', resetWarning: '如需保留数据,请先导出备份。', resetData: '重置数据' },
feedback: { backup: '备份已创建', invalid: '无效备份文件', restored: '系统已恢复', restoring: '正在恢复系统…', minimized: '已最小化 — 点击任务栏图标即可恢复', languageZh: '语言已切换为中文', languageEn: 'Language set to English', followingZh: '已跟随浏览器语言:中文', followingEn: 'Following browser language: English' },
- addon: { draftRestored: '草稿已恢复', searchTitle: '搜索记忆档案', searchPlaceholder: '搜索标题、记录或天气…', searchHint: '按 Esc 关闭 · 点击结果即可打开', untitled: '未命名记忆', noResults: '没有找到记忆。', syncPaused: '云端同步暂停——更改仍保留在此设备', syncReady: '云端同步已就绪', migrateQuestion: '将此浏览器的 Life OS 迁移到云端?', migrateBody: '现有本地记录将成为这个 ChatGPT 账号的第一份云端同步数据。', migrate: '迁移并同步', fresh: '从空白开始', migrated: '本地 Life OS 已迁移', cloudRestored: '云端 Life OS 已恢复', cloudUnavailable: '云端存储暂不可用——已使用本地模式' }
+ addon: { draftRestored: '草稿已恢复', searchTitle: '搜索记忆档案', searchPlaceholder: '搜索标题、记录或天气…', searchHint: '按 Esc 关闭 · 点击结果即可打开', untitled: '未命名记忆', noResults: '没有找到记忆。', syncPaused: '云端同步暂停——更改仍保留在此设备', syncReady: '云端同步已就绪', migrateQuestion: '将此浏览器的 Life OS 迁移到云端?', migrateBody: '现有本地记录将成为这个 ChatGPT 账号的第一份云端同步数据。', migrate: '迁移并同步', fresh: '从空白开始', migrated: '本地 Life OS 已迁移', cloudRestored: '云端 Life OS 已恢复', cloudUnavailable: '云端存储暂不可用——已使用本地模式' },
+ tasks: { eyebrow: '目标与待办', title: '把心力留给重要的事。', subtitle: '为今天和接下来的日子,排一个轻量、踏实的计划。', addGoal: '新建目标', addTask: '添加待办', taskPlaceholder: '接下来要做什么?', goalPlaceholder: '你想朝什么方向前进?', goalDescription: '关于这个目标的备注(选填)', finishedGoals: '已完成的目标', goalComplete: '完成目标', goalReopen: '重新开启目标', all: '全部', today: '今天', upcoming: '即将开始', done: '已完成', activeGoals: '进行中的目标', noGoals: '从一个值得继续靠近的目标开始。', noTasks: '这里暂时没有待办。', due: '截止 {date}', noDate: '无截止日期', priority: { low: '低', normal: '普通', high: '高' }, goal: '目标', anyGoal: '不关联目标', save: '保存', cancel: '取消', goalTitle: '新建目标', taskTitle: '新建待办', goalCreated: '目标已创建', taskAdded: '待办已添加', taskRemoved: '待办已删除', goalRemoved: '目标已删除', confirmGoalDelete: '删除这个目标?', confirmGoalDeleteBody: '关联的待办会保留,但不再关联此目标。', target: '目标日期', completed: '已完成', complete: '标记完成', reopen: '重新打开', delete: '删除', progress: '已完成 {done} / {total}', todayHeading: '今天要推进的事', openCount: '还有 {count} 项待办', overdue: '已逾期' }
};
const get = (object, key) => key.split('.').reduce((value, part) => value?.[part], object);
function currentLocale() { return typeof window.lang === 'function' ? window.lang() : 'en'; }
diff --git a/public/index.html b/public/index.html
index cd157ec..16a1c6e 100644
--- a/public/index.html
+++ b/public/index.html
@@ -5,7 +5,7 @@
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font-family:Manrope,system-ui;overflow:hidden}button,input,textarea{font:inherit;color:inherit}button{cursor:pointer}.desktop{height:100vh;background:radial-gradient(ellipse at 20% 0%,#3f4a503d,transparent 40%),radial-gradient(ellipse at 93% 65%,#59604224,transparent 42%),var(--bg);position:relative}.brand{position:absolute;top:28px;left:32px;font:500 11px 'DM Mono';letter-spacing:.18em;color:var(--muted)}.desktop-icons{padding:72px 32px;display:grid;grid-template-columns:repeat(3,96px);gap:22px}.app-icon{border:0;background:transparent;color:var(--ink);display:grid;gap:9px;justify-items:center;font:500 10px 'DM Mono';letter-spacing:.12em}.glyph{width:48px;height:48px;background:var(--panel);border:1px solid var(--line);border-radius:14px;display:grid;place-items:center;box-shadow:0 8px 22px #0002;font-size:20px}.app-icon:hover .glyph,.taskapp.active{border-color:var(--accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 12%,transparent)}.taskbar{position:absolute;bottom:18px;left:50%;transform:translateX(-50%);height:54px;display:flex;align-items:center;gap:5px;padding:6px;background:color-mix(in srgb,var(--panel) 94%,transparent);border:1px solid var(--line);border-radius:17px;box-shadow:var(--shadow);z-index:1000}.life,.taskapp,.clock{height:40px;border:0;border-radius:11px;background:transparent;color:var(--ink);padding:0 12px;font:500 11px 'DM Mono';letter-spacing:.1em}.life{background:var(--accent);color:#151812;font-weight:500}.taskapp{min-width:42px;font-size:17px;padding:0 10px}.clock{border-left:1px solid var(--line);line-height:1.35;text-align:left;font-size:10px}.launcher,.calendar{position:absolute;bottom:82px;background:var(--panel);border:1px solid var(--line);box-shadow:var(--shadow);border-radius:17px;padding:19px;z-index:1200}.launcher{left:calc(50% - 310px);width:300px}.calendar{right:calc(50% - 310px);width:230px}.hidden{display:none!important}.launchhead{display:flex;justify-content:space-between;font:500 11px 'DM Mono';letter-spacing:.12em}.launchgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin:18px 0}.launchitem{border:1px solid transparent;background:var(--panel2);border-radius:10px;padding:13px 7px;color:var(--ink);font:500 9px 'DM Mono';letter-spacing:.08em}.launchitem:hover{border-color:var(--accent)}.systemline{border-top:1px solid var(--line);padding-top:12px;color:var(--muted);font:10px 'DM Mono';letter-spacing:.05em}.window{position:absolute;width:min(700px,calc(100vw - 100px));height:min(590px,calc(100vh - 130px));background:var(--panel);border:1px solid var(--line);border-radius:15px;box-shadow:var(--shadow);overflow:hidden;animation:in .18s ease;display:flex;flex-direction:column}.window.max{width:calc(100vw - 36px)!important;height:calc(100vh - 98px)!important;left:18px!important;top:18px!important}.titlebar{height:48px;display:flex;align-items:center;gap:10px;border-bottom:1px solid var(--line);padding:0 13px;cursor:grab;flex:none}.titlebar:active{cursor:grabbing}.titleicon{width:22px;height:22px;border:1px solid var(--line);border-radius:7px;display:grid;place-items:center;font-size:12px}.wtitle{font:500 11px 'DM Mono';letter-spacing:.13em;flex:1}.controls{display:flex;gap:5px}.controls button{width:25px;height:25px;border:0;background:transparent;color:var(--muted);border-radius:6px}.controls button:hover{background:var(--panel2);color:var(--ink)}.content{padding:24px;overflow:auto;height:100%}.eyebrow{font:10px 'DM Mono';letter-spacing:.16em;color:var(--muted);margin-bottom:8px}h1,h2,p{margin-top:0}h1{font-size:28px;letter-spacing:-.05em;margin-bottom:3px}h2{font-size:13px;letter-spacing:.07em}.sub{color:var(--muted);font-size:14px}.today-grid{display:grid;grid-template-columns:1.3fr .7fr;gap:24px}.entry{width:100%;height:190px;resize:none;background:var(--panel2);border:1px solid var(--line);border-radius:12px;padding:15px;outline:none;line-height:1.55}.entry:focus{border-color:var(--accent)}.choices{display:flex;gap:7px;flex-wrap:wrap}.chip{border:1px solid var(--line);border-radius:99px;background:transparent;padding:8px 10px;color:var(--muted);font:10px 'DM Mono';letter-spacing:.06em}.chip.selected{background:var(--accent);border-color:var(--accent);color:#171b12}.energy{display:flex;gap:7px}.energy button{width:31px;height:31px;border:1px solid var(--line);background:var(--panel2);color:var(--muted);border-radius:6px}.energy button.selected{background:var(--accent);color:#171b12;border-color:var(--accent)}.film{border-top:1px solid var(--line);margin-top:18px;padding-top:16px}.film-title{font-size:20px;letter-spacing:-.03em}.film-copy{color:var(--muted);font-size:13px;line-height:1.55}.primary,.secondary,.danger{border:0;padding:11px 14px;border-radius:9px;font:500 10px 'DM Mono';letter-spacing:.1em}.primary{background:var(--accent);color:#151812}.secondary{background:var(--panel2);border:1px solid var(--line)}.danger{background:#a94e4e;color:#fff}.actions{display:flex;gap:9px;margin-top:18px}.status{font:10px 'DM Mono';letter-spacing:.1em;color:var(--accent);align-self:center}.timeline{position:relative;margin:8px 0}.memory{border-left:1px solid var(--line);padding:0 0 23px 25px;position:relative}.memory:before{content:'';position:absolute;left:-4px;top:5px;width:7px;height:7px;background:var(--accent);border-radius:50%}.memory button{all:unset;cursor:pointer;width:100%}.memory .date{font:10px 'DM Mono';color:var(--muted);letter-spacing:.1em}.memory .mtitle{font-size:18px;margin:5px 0}.memory .snippet{color:var(--muted);font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.empty{display:grid;place-items:center;min-height:280px;color:var(--muted);text-align:center;font-size:14px}.museum{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.artifact{background:var(--panel2);border:1px solid var(--line);border-radius:12px;padding:17px;text-align:left;color:var(--ink)}.artifact:hover{border-color:var(--accent)}.artifact span{display:block;font:10px 'DM Mono';color:var(--muted);letter-spacing:.08em}.artifact strong{display:block;font-size:18px;margin:18px 0 7px;letter-spacing:-.04em}.insights{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}.metric{border:1px solid var(--line);border-radius:12px;padding:18px;min-height:120px}.metric .big{font-size:36px;letter-spacing:-.07em}.bars{display:flex;align-items:end;height:56px;gap:5px;margin-top:12px}.bar{background:var(--accent);opacity:.78;width:12%;border-radius:3px 3px 0 0}.profile-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin:20px 0}.stat{border-top:1px solid var(--line);padding-top:10px}.stat b{display:block;font-size:24px;letter-spacing:-.06em}.stat span{font:10px 'DM Mono';color:var(--muted);letter-spacing:.08em}.formrow{display:grid;grid-template-columns:1fr 1.5fr;gap:12px;align-items:center;margin:9px 0}.formrow input{border:1px solid var(--line);background:var(--panel2);border-radius:8px;padding:10px}.settings section{border-top:1px solid var(--line);padding:17px 0}.settings section:first-child{border-top:0}.settings p{color:var(--muted);font-size:13px;line-height:1.55}.modal{position:fixed;inset:0;background:#0008;display:grid;place-items:center;z-index:5000}.modalbox{position:relative;width:min(410px,calc(100vw - 36px));background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:23px;box-shadow:var(--shadow)}.modal-close{position:absolute;top:12px;right:12px;width:32px;height:32px;border:1px solid var(--line);border-radius:50%;background:transparent;color:var(--ink);font-size:24px;line-height:1;cursor:pointer}.modal-close:hover,.modal-close:focus-visible{background:var(--accent);color:#fff;outline:none}.toast{position:fixed;top:24px;left:50%;transform:translateX(-50%);background:var(--accent);color:#131611;padding:12px 15px;border-radius:9px;z-index:6000;font:500 10px 'DM Mono';letter-spacing:.1em;animation:in .18s ease}.boot{position:fixed;inset:0;background:var(--bg);z-index:9000;display:grid;place-content:center;text-align:center;gap:13px;font:12px 'DM Mono';letter-spacing:.16em}.boot b{font-size:28px;color:var(--ink)}@keyframes in{from{opacity:0;transform:scale(.98)}to{opacity:1;transform:scale(1)}}
@media(max-width:650px){body{overflow:auto}.desktop{min-height:100vh}.brand{left:22px;top:20px}.desktop-icons{padding:79px 20px 100px;grid-template-columns:repeat(3,1fr);gap:28px 16px}.taskbar{bottom:11px;width:calc(100% - 22px);justify-content:space-between}.taskapp{display:inline-grid;place-items:center;min-width:32px;padding:0 6px}.clock{display:none}.window{position:fixed!important;inset:0!important;width:100%!important;height:100%!important;border:0;border-radius:0;z-index:2000!important}.window.min{display:none}.titlebar{padding-top:env(safe-area-inset-top);height:53px}.content{padding:20px}.today-grid{grid-template-columns:1fr}.launcher{left:11px;bottom:76px;width:calc(100% - 22px)}.calendar{right:11px;bottom:76px}.museum,.insights,.profile-grid{grid-template-columns:1fr 1fr}.profile-grid{gap:15px}.formrow{grid-template-columns:1fr}.boot{font-size:10px}}
LIFE OSINITIALIZING PERSONAL SYSTEM...
LIFE OS / PERSONAL DESKTOP