diff --git a/README.md b/README.md
index 3d2f66f..12899e5 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,7 @@
- **System**:深浅主题、中文/English 切换、JSON 导入导出与本地存储说明。
- **Bing 每日一图**:桌面壁纸每日自动更新;接口不可用时保留内置背景。
- **Windows 式操作**:桌面端单击选择、双击打开;右键桌面或 App 图标可使用快捷菜单。
+- **快捷键与草稿恢复**:`Ctrl / ⌘ + K` 打开启动器,`Ctrl / ⌘ + F` 搜索记忆,`Ctrl / ⌘ + S` 归档 Today;当天未归档文字会自动恢复。
- **移动端**:App 以全屏方式打开,保留任务栏恢复入口。
## 数据与备份
diff --git a/dist/addons.js b/dist/addons.js
new file mode 100644
index 0000000..cbc8ee3
--- /dev/null
+++ b/dist/addons.js
@@ -0,0 +1,76 @@
+/* Life OS reliability and productivity add-ons. */
+(() => {
+ const DRAFT_KEY = 'lifeos_today_draft_v1';
+ const appIds = ['today', 'timeline', 'museum', 'insights', 'profile', 'system'];
+ const zh = () => (typeof lang === 'function' ? lang() : 'en') === 'zh';
+ const copy = (en, cn) => (zh() ? cn : en);
+
+ function readDraft() {
+ try { return JSON.parse(localStorage.getItem(DRAFT_KEY)); } catch { return null; }
+ }
+ function saveDraft(text) {
+ localStorage.setItem(DRAFT_KEY, JSON.stringify({ date: new Date().toISOString().slice(0, 10), text, updatedAt: new Date().toISOString() }));
+ }
+ function restoreDraft(entry) {
+ const draft = readDraft();
+ const today = new Date().toISOString().slice(0, 10);
+ if (!draft || draft.date !== today || !draft.text || entry.value) return;
+ entry.value = draft.text;
+ entry.dispatchEvent(new Event('input', { bubbles: true }));
+ if (typeof toast === 'function') toast(copy('DRAFT RESTORED', '草稿已恢复'));
+ }
+ function wireToday() {
+ const entry = document.querySelector('#entry');
+ if (!entry || entry.dataset.lifeosDraft === 'ready') return;
+ entry.dataset.lifeosDraft = 'ready';
+ restoreDraft(entry);
+ entry.addEventListener('input', () => saveDraft(entry.value));
+ }
+ function clearDraftAfterSave() {
+ setTimeout(() => {
+ const today = new Date().toISOString().slice(0, 10);
+ try {
+ const saved = JSON.parse(localStorage.getItem('lifeos_memories') || '[]').some(m => m.date === today);
+ if (saved) localStorage.removeItem(DRAFT_KEY);
+ } catch {}
+ }, 700);
+ }
+
+ function closeSearch() { document.querySelector('#lifeos-search')?.remove(); }
+ function openSearch() {
+ closeSearch();
+ const box = document.createElement('div');
+ box.id = 'lifeos-search';
+ box.innerHTML = `
m.date===d.toISOString().slice(0,10))){s++;d.setDate(d.getDate()-1)}return s}function longest(a){let ds=a.map(x=>x.date).sort(),best=0,run=0,last='';ds.forEach(d=>{run=last&&((new Date(d)-new Date(last))/864e5===1)?run+1:1;best=Math.max(best,run);last=d});return best}function insights(c){let a=mems(),last=a.filter(m=>(Date.now()-new Date(m.date+'T12:00:00'))<2592e6),cnt={clear:0,light:0,cloudy:0,rain:0,storm:0};last.forEach(x=>cnt[x.mood]++);let avg=a.length?(a.reduce((s,x)=>s+x.energy,0)/a.length).toFixed(1):'—',days={};a.forEach(x=>{let d=new Date(x.date+'T12:00:00').toLocaleDateString('en-US',{weekday:'long'});days[d]=(days[d]||0)+1});let active=Object.entries(days).sort((x,y)=>y[1]-x[1])[0]?.[0]||'—';c.innerHTML=`
LAST 30 DAYS
Insights, quietly. WEATHER
${Object.entries(cnt).map(([k,v])=>`${cap(k)} ${v} DAYS
`).join('')}AVERAGE ENERGY
${avg} / 5
${a.slice(0,10).reverse().map(x=>` `).join('')||'No entries yet '}
CURRENT STREAK
${streak(a)} DAYS
Longest streak: ${longest(a)} days
`}
function profileApp(c){let p=profile(),a=mems(),common=Object.entries(a.reduce((o,m)=>(o[m.mood]=(o[m.mood]||0)+1,o),{})).sort((x,y)=>y[1]-x[1])[0]?.[0]||'—',avg=a.length?(a.reduce((x,m)=>x+m.energy,0)/a.length).toFixed(1):'—',started=Math.max(1,Math.floor((Date.now()-new Date(p.startDate))/864e5));c.innerHTML=`
SYSTEM PROFILE
${p.name?esc(p.name)+' · ':''}LIFE OS ONLINE ${started} DAYS ONLINE
${a.length} MEMORIES
${museum().length} MUSEUM ITEMS
${streak(a)} CURRENT STREAK
${longest(a)} LONGEST STREAK
${cap(common)} COMMON WEATHER
PERSONAL SYSTEM
Display Name
Birthday (optional)
Life OS Start Date
Personal Motto
SAVE PROFILE AVERAGE ENERGY ${avg} / 5
`}function saveProfile(){save('lifeos_profile',{name:pname.value,birthday:pbday.value,startDate:pstart.value||day(),motto:pmotto.value});renderAll();toast('PROFILE SAVED')}
function systemApp(c){let s=settings(),bytes=new Blob([JSON.stringify({mems:mems(),museum:museum(),profile:profile()})]).size;let rec=s.lastBackup&&(Date.now()-new Date(s.lastBackup)>12096e5);c.innerHTML=`
SYSTEM SETTINGS
System, local and yours. APPEARANCE
${['dark','light','auto'].map(x=>`${x.toUpperCase()} `).join('')}
STORAGE
Local Storage ${mems().length} memories · ${museum().length} museum items · Approx. ${Math.ceil(bytes/1024)} KB
DATA STORAGE
Your data is stored locally in this browser. Create regular backups to prevent data loss.
${s.lastBackup?'LAST BACKUP · '+fmtDate(s.lastBackup.slice(0,10)):'LAST BACKUP · NEVER'} ${rec?'BACKUP RECOMMENDED ':''}
VERSION
Life OS Version 0.1 · Schema v1
`}function theme(x){let s=settings();s.theme=x;save('lifeos_settings',s);init();renderAll()}function exportData(){let data={app:'Life OS',version:'0.1',schemaVersion:1,exportedAt:new Date().toISOString(),profile:profile(),memories:mems(),museum:museum(),settings:settings()};let a=document.createElement('a');a.href=URL.createObjectURL(new Blob([JSON.stringify(data,null,2)],{type:'application/json'}));a.download='life-os-backup-'+day()+'.json';a.click();let s=settings();s.lastBackup=new Date().toISOString();save('lifeos_settings',s);renderAll();toast('BACKUP CREATED')}function readImport(el){let f=el.files[0];if(!f)return;let r=new FileReader;r.onload=()=>{try{let d=JSON.parse(r.result);if(d.app!=='Life OS'||d.schemaVersion!==1||!Array.isArray(d.memories)||!Array.isArray(d.museum))throw 0;importData=d;importPreview()}catch{toast('INVALID BACKUP FILE')}};r.readAsText(f)}function importPreview(){let d=importData;modal.innerHTML=`
LIFE OS BACKUP
Ready to restore Exported: ${fmtDate(d.exportedAt.slice(0,10))} Memories: ${d.memories.length} Museum Items: ${d.museum.length}
MERGE REPLACE ALL DATA CANCEL
`}function replaceConfirm(){modal.innerHTML=`
FINAL CONFIRMATION
Replace all Life OS data? This will replace all current Life OS data.
REPLACE DATA CANCEL
`}function applyImport(mode){let d=importData;if(mode==='replace'){save('lifeos_memories',d.memories);save('lifeos_museum',d.museum);save('lifeos_profile',d.profile||{});save('lifeos_settings',d.settings||{})}else{let a=mems(), ids=new Set(a.map(x=>x.id)),dates=new Set(a.map(x=>x.date));save('lifeos_memories',[...a,...d.memories.filter(x=>!ids.has(x.id)&&!dates.has(x.date))]);let mu=museum(),mi=new Set(mu.map(x=>x.id));save('lifeos_museum',[...mu,...d.museum.filter(x=>!mi.has(x.id))])}modal.innerHTML='';toast('RESTORING SYSTEM...');setTimeout(()=>{renderAll();toast('SYSTEM RESTORED')},500)}function resetConfirm(){modal.innerHTML=`
RESET LIFE OS
Reset all local data? Export a backup first if you want to keep it.
RESET DATA CANCEL
`}function toast(t){let x=document.createElement('div');x.className='toast';x.textContent=t;document.body.append(x);setTimeout(()=>x.remove(),2000)}init();
-