28 lines
44 KiB
HTML
28 lines
44 KiB
HTML
<!doctype html>
|
||
<html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#111317"/><meta name="mobile-web-app-capable" content="yes"/><meta name="apple-mobile-web-app-capable" content="yes"/><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/><meta name="apple-mobile-web-app-title" content="Life OS"/><link rel="manifest" href="/manifest.webmanifest"/><link rel="icon" href="/life-os-icon.svg" type="image/svg+xml"/><link rel="apple-touch-icon" href="/life-os-icon.svg"/><title>Life OS</title><style>
|
||
@import url('https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Manrope:wght@400;500;600;700&display=swap');
|
||
:root{--bg:#101216;--panel:#191c21;--panel2:#20242a;--ink:#f0f2f5;--muted:#939aa5;--line:#30353d;--accent:#c8ff62;--shadow:0 22px 65px #0008} [data-theme="light"]{--bg:#e9ecee;--panel:#f7f8f7;--panel2:#eef0ee;--ink:#14171a;--muted:#66707a;--line:#d5d9da;--accent:#506c00;--shadow:0 22px 65px #7b828733}
|
||
*{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}}
|
||
</style></head><body><div id="boot" class="boot"><b>LIFE OS</b><span>INITIALIZING PERSONAL SYSTEM...</span></div><main class="desktop" id="desktop"><div class="brand" id="desktop-brand"></div><div id="desktop-goal-countdowns" class="desktop-goal-countdowns" aria-live="polite"></div><div id="icons" class="desktop-icons"></div><div id="windows"></div><div id="launcher" class="launcher hidden"></div><div id="calendar" class="calendar hidden"></div><nav class="taskbar"><button class="life" id="life-launcher" onclick="toggleLauncher()"></button><div id="tasks"></div><button class="clock" onclick="toggleCalendar()"><span id="clock"></span><br><span id="date"></span></button></nav></main><div id="modal"></div><script>
|
||
const ICONS={today:`<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3.5" y="5" width="17" height="15" rx="3"/><path d="M7.5 3.5v3M16.5 3.5v3M3.5 9h17M8 13h3M8 16h6"/></svg>`,timeline:`<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="8.5"/><path d="M12 7v5l3.5 2.5M4 12h1.5M18.5 12H20M12 4v1.5M12 18.5V20"/></svg>`,museum:`<svg viewBox="0 0 24 24" aria-hidden="true"><path d="m4 10 8-5 8 5M5.5 10v8M9.5 10v8M14.5 10v8M18.5 10v8M3.5 19.5h17"/></svg>`,insights:`<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 19.5V5M4 19.5h16M7.5 16v-4M12 16V8M16.5 16v-7"/></svg>`,profile:`<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="8" r="3.5"/><path d="M5 20c.8-4 3.1-6 7-6s6.2 2 7 6"/></svg>`,system:`<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M12 3.5v2M12 18.5v2M20.5 12h-2M5.5 12h-2M18 6l-1.4 1.4M7.4 16.6 6 18M18 18l-1.4-1.4M7.4 7.4 6 6"/></svg>`,tasks:`<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="4" y="4" width="16" height="17" rx="3"/><path d="M8 9h8M8 13h8M8 17h4M9 2.8v2.5M15 2.8v2.5"/></svg>`,calendar:`<svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3.5" y="5" width="17" height="16" rx="3"/><path d="M7.5 3v4M16.5 3v4M3.5 10h17M8 14h2M14 14h2M8 17h2"/></svg>`};const APPS={today:[ICONS.today,'TODAY'],timeline:[ICONS.timeline,'TIMELINE'],museum:[ICONS.museum,'MUSEUM'],insights:[ICONS.insights,'INSIGHTS'],profile:[ICONS.profile,'PROFILE'],tasks:[ICONS.tasks,'TASKS'],calendar:[ICONS.calendar,'CALENDAR'],system:[ICONS.system,'SYSTEM']};let windows={},z=20,selectedMood='cloudy',selectedEnergy=3,importData=null,selectedIcon=null,activeWindow=null;
|
||
document.head.insertAdjacentHTML('beforeend','<style>.window{resize:both;min-width:360px;min-height:280px}.window.min{display:none!important}.window.active-window{border-color:color-mix(in srgb,var(--accent) 52%,var(--line))}.taskapp.minimized{opacity:.58;border:1px dashed var(--accent)}.taskapp.running{position:relative}.taskapp.running:after{content:"";position:absolute;bottom:3px;left:50%;width:4px;height:4px;transform:translateX(-50%);border-radius:50%;background:var(--accent)}.taskapp.foreground:after{width:16px;border-radius:3px}.glyph{background:transparent;border:0;box-shadow:none;border-radius:0}.glyph svg{width:33px;height:33px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}.app-icon:hover .glyph{box-shadow:none}.app-icon:focus-visible .glyph{outline:2px solid var(--accent);outline-offset:5px}.taskapp svg{width:21px;height:21px;display:block;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}.titleicon{border:0;background:transparent}.titleicon svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}@media(max-width:650px){.window{resize:none;min-width:0;min-height:0}}</style>');
|
||
const films=[['A Quiet Tuesday','Drama · Slice of Life'],['The Afternoon That Stayed','Independent · Warm'],['Small Light, Long Hallway','Contemplative · Drama'],['Before the Notifications','Minimal · Coming of Age'],['Room With Open Curtains','Slice of Life · Calm'],['The Usual Way Home','Drama · Familiar'],['Notes Left Unfinished','Reflective · Short Film'],['A Weather of Thoughts','Quiet · Personal'],['The Day Between Things','Indie · Observational'],['Slow Hours, Soft Edges','Slice of Life · Dream'],['A Window Still Lit','Drama · Evening'],['Ordinary, Carefully Held','Human · Gentle'],['One More Page Tonight','Study · Slow Burn'],['The Space After Lunch','Memory · Calm'],['Late Bus, Clear Mind','Urban · Reflective'],['Little Things in Focus','Documentary · Life'],['What the Room Remembered','Poetic · Still'],['Blue Hour, No Rush','Evening · Quiet'],['The Map of a Small Day','Journey · Personal'],['Somewhere After Four','Indie · Warm'],['A Day With No Headline','Slice of Life · Soft'],['The Sound of Taking Time','Slow Cinema · Personal'],['Things That Did Not Need Fixing','Drama · Gentle'],['Halfway Through September','Memory · Youth'],['A Small Archive of Us','Personal · Quiet'],['The Night Kept Its Shape','Noir · Calm'],['Unscheduled Sunshine','Light · Personal'],['The Last Open Tab','Modern · Reflective'],['A Place for Today','Drama · Human'],['Nothing Lost to the Day','Slice of Life · Hopeful']];
|
||
const lines=['Nothing remarkable happened. Maybe that was the point.','A small day, held carefully enough to remember.','The ordinary parts stayed longer than expected.','Not every chapter needs a dramatic ending.','Some thoughts arrived; some were allowed to pass.','A quiet record of being here.','The day moved at its own reasonable pace.','There was room for a little more softness.','Even the unfinished things belonged here.','The light changed, and so did the mood.','A scene worth keeping, without needing an explanation.','It was enough to notice the day as it was.','Time did not rush; neither did this memory.','The details were small, but they were real.','A pause became part of the story.','Somewhere, this became a day to keep.','The familiar became briefly luminous.','Nothing needed to be solved before nightfall.','A few moments stayed in focus.','The story was quiet, but it was still yours.','Today left a small mark.','A gentle frame around an ordinary hour.','The day asked for attention, not perfection.','This memory can rest here for now.','The world was loud; this page was not.','A few loose threads, honestly kept.','The simplest version of today was enough.','The day settled before it ended.','There was meaning in the in-between.','A private moment, safely archived.'];
|
||
function load(k,d){try{return JSON.parse(localStorage.getItem(k))||d}catch{return d}}function save(k,v){localStorage.setItem(k,JSON.stringify(v))}function mems(){return load('lifeos_memories',[])}function museum(){return load('lifeos_museum',[])}function profile(){return load('lifeos_profile',{name:'',birthday:'',startDate:new Date().toISOString().slice(0,10),motto:''})}function settings(){return{theme:'dark',lastBackup:null,language:'auto',languageMode:'auto',...load('lifeos_settings',{})}}function sys(){let value=load('lifeos_system',{schemaVersion:2});if((Number(value.schemaVersion)||1)<2)value.schemaVersion=2;return value}
|
||
function browserLanguage(){let ls=navigator.languages?.length?navigator.languages:[navigator.language||'en'];return ls.some(x=>String(x).toLowerCase().startsWith('zh'))?'zh':'en'}function lang(){let s=settings();return s.languageMode==='manual'&&['en','zh'].includes(s.language)?s.language:browserLanguage()}function i18n(key,fallback=key){return fallback}
|
||
function appName(id){return i18n(APPS[id][1])}function moodName(m){return i18n(m.toUpperCase(),cap(m))}function day(){const d=new Date();return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`}function fmtDate(d){return new Date(d+'T12:00:00').toLocaleDateString(lang()==='zh'?'zh-CN':'en-US',{weekday:'long',month:'long',day:'numeric',year:'numeric'})}function shortDate(d){return new Date(d+'T12:00:00').toLocaleDateString(lang()==='zh'?'zh-CN':'en-US',lang()==='zh'?{month:'numeric',day:'numeric'}:{month:'short',day:'numeric'}).toUpperCase()}function cap(x){return x[0].toUpperCase()+x.slice(1)}function esc(s){return (s||'').replace(/[&<>]/g,x=>({'&':'&','<':'<','>':'>'}[x]))}
|
||
function applyWallpaper(w){if(!w?.url)return;desktop.style.backgroundImage=`linear-gradient(120deg,color-mix(in srgb,var(--bg) 68%,transparent),color-mix(in srgb,var(--bg) 36%,transparent)),url('${w.url}')`;desktop.style.backgroundSize='cover';desktop.style.backgroundPosition='center';desktop.title=LifeI18n.t('shell.wallpaperTitle',{title:w.title||LifeI18n.t('wallpaper.dailyImage')})}async function loadBingWallpaper(){let s=settings();if(s.wallpaper?.date===day()&&s.wallpaper?.source==='lifeos-proxy'){applyWallpaper(s.wallpaper);return}try{let r=await fetch('/api/bing-wallpaper'),d=await r.json();if(!r.ok||!d.url)throw Error(d.error||'No Bing image');s.wallpaper={date:day(),url:'/api/bing-wallpaper-image?date='+day(),title:d.title||'Bing daily image',source:'lifeos-proxy'};save('lifeos_settings',s);applyWallpaper(s.wallpaper)}catch(e){console.info('Bing wallpaper unavailable; using the calm fallback.',e)}}function init(){let s=settings();document.documentElement.dataset.theme=s.theme==='auto'?(matchMedia('(prefers-color-scheme:light)').matches?'light':'dark'):s.theme;renderIcons();renderTaskbar();loadBingWallpaper();setInterval(clock,1000);clock();setTimeout(applyLanguage,0);setTimeout(()=>boot.remove(),650)}
|
||
function desktopIconClick(id,e){if(innerWidth<650){openApp(id);return}selectedIcon=id;document.querySelectorAll('.app-icon').forEach(x=>x.classList.remove('selected'));e.currentTarget.classList.add('selected')}function desktopIconKey(id,e){if(e.key==='Enter'||e.key===' '){e.preventDefault();openApp(id)}}function renderIcons(){icons.innerHTML=Object.entries(APPS).map(([id,[g,n]])=>`<button class="app-icon ${selectedIcon===id?'selected':''}" onclick="desktopIconClick('${id}',event)" ondblclick="openApp('${id}')" onkeydown="desktopIconKey('${id}',event)" oncontextmenu="showContext(event,'${id}')" title="${appName(id)}"><span class="glyph">${g}</span><span>${appName(id)}</span></button>`).join('')}function taskClick(id){let w=windows[id];if(!w){openApp(id);return}if(w.classList.contains('min')){openApp(id);return}if(activeWindow===id){minWin(id);return}focus(w)}function renderTaskbar(){let ids=innerWidth<650?Object.keys(windows):['today','timeline','museum','insights','tasks','calendar'];tasks.innerHTML=ids.map(id=>{let w=windows[id],min=w?.classList.contains('min'),fg=activeWindow===id&&!min;return`<button class="taskapp ${w?'active running':''} ${min?'minimized':''} ${fg?'foreground':''}" title="${min?i18n('Restore window','Restore window'):appName(id)}" aria-label="${min?i18n('Restore window','Restore window'):i18n('Open')} ${appName(id)}" onclick="taskClick('${id}')">${APPS[id][0]}</button>`}).join('')}
|
||
function clock(){let d=new Date();document.querySelector('#clock').textContent=d.toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'});document.querySelector('#date').textContent=d.toLocaleDateString(lang()==='zh'?'zh-CN':'en-US',lang()==='zh'?{month:'numeric',day:'numeric'}:{month:'short',day:'numeric'}).toUpperCase()}function toggleLauncher(){launcher.classList.toggle('hidden');calendar.classList.add('hidden');launcher.innerHTML=`<div class="launchhead"><span>LIFE OS</span><span>${LifeI18n.t(`shell.${new Date().getHours()<12?'morning':new Date().getHours()<18?'afternoon':'evening'}`)}</span></div><div class="launchgrid">${Object.entries(APPS).map(([id,a])=>`<button class="launchitem" onclick="openApp('${id}');toggleLauncher()">${a[0]}<br><br>${appName(id)}</button>`).join('')}</div><div class="systemline">${LifeI18n.t('shell.systemOnline',{version:LifeOSVersion})}</div>`}function toggleCalendar(){calendar.classList.toggle('hidden');launcher.classList.add('hidden');calendar.innerHTML=`<div class="eyebrow">${LifeI18n.t('shell.systemDate')}</div><h2>${fmtDate(day())}</h2><p class="sub">${LifeI18n.t('shell.todaySummary',{memories:mems().length,preserved:museum().length})}</p>`}function hideContext(){document.querySelector('#life-context')?.remove()}function showContext(e,id){e.preventDefault();e.stopPropagation();hideContext();let labels={open:LifeI18n.t('shell.open'),min:LifeI18n.t('shell.minimize'),close:LifeI18n.t('shell.menuCloseWindow'),refresh:LifeI18n.t('shell.refresh'),new:LifeI18n.t('shell.newToday'),system:LifeI18n.t('shell.systemSettings')},actions=id?`<button onclick="openApp('${id}');hideContext()">${labels.open}</button>${windows[id]?`<button onclick="minWin('${id}');hideContext()">${labels.min}</button><button onclick="closeWin('${id}');hideContext()">${labels.close}</button>`:''}`:`<button onclick="renderIcons();hideContext()">${labels.refresh}</button><button onclick="openApp('today');hideContext()">${labels.new}</button><hr><button onclick="openApp('system');hideContext()">${labels.system}</button>`;let m=document.createElement('div');m.id='life-context';m.style.cssText=`position:fixed;z-index:7000;left:${Math.min(e.clientX,innerWidth-190)}px;top:${Math.min(e.clientY,innerHeight-150)}px;width:180px;padding:6px;background:var(--panel);border:1px solid var(--line);border-radius:10px;box-shadow:var(--shadow)`;m.innerHTML=actions;m.querySelectorAll('button').forEach(b=>b.style.cssText='display:block;width:100%;border:0;background:transparent;color:var(--ink);text-align:left;border-radius:6px;padding:9px 10px;font:12px Manrope');m.querySelectorAll('button').forEach(b=>b.onmouseenter=()=>b.style.background='var(--panel2)');document.body.append(m)}document.addEventListener('contextmenu',e=>{if(e.target.closest('.desktop')&&!e.target.closest('.app-icon')&&!e.target.closest('.window'))showContext(e,null)});document.addEventListener('click',hideContext)
|
||
function openApp(id){launcher.classList.add('hidden');let w=windows[id];if(w){w.classList.remove('min');focus(w);return}w=document.createElement('section');w.className='window';w.dataset.id=id;w.style.left=(innerWidth<650?0:90+Object.keys(windows).length*36)+'px';w.style.top=(innerWidth<650?0:72+Object.keys(windows).length*28)+'px';w.innerHTML=`<div class="titlebar" title="${LifeI18n.t('shell.doubleClickMaximize')}"><span class="titleicon">${APPS[id][0]}</span><span class="wtitle">${appName(id)}</span><div class="controls"><button data-window-action="min" aria-label="${LifeI18n.t('shell.minimize')}">—</button><button data-window-action="max" aria-label="${LifeI18n.t('shell.maximize')}">□</button><button data-window-action="close" aria-label="${LifeI18n.t('shell.close')}">×</button></div></div><div class="content" id="content-${id}"></div>`;windows[id]=w;document.querySelector('#windows').append(w);let controls=w.querySelector('.controls'),bar=w.querySelector('.titlebar');controls.addEventListener('pointerdown',e=>{e.preventDefault();e.stopPropagation()});controls.addEventListener('click',e=>{let action=e.target.closest('button')?.dataset.windowAction;if(!action)return;e.preventDefault();e.stopPropagation();if(action==='min')minWin(id);if(action==='max')maxWin(id);if(action==='close')closeWin(id)});bar.addEventListener('dblclick',e=>{if(!e.target.closest('.controls'))maxWin(id)});w.addEventListener('pointerdown',()=>focus(w));drag(w);renderApp(id);focus(w)}function focus(w){if(!w)return;activeWindow=w.dataset.id;w.style.zIndex=++z;document.querySelectorAll('.window').forEach(x=>x.classList.toggle('active-window',x===w));renderTaskbar()}function closeWin(id){windows[id]?.remove();delete windows[id];if(activeWindow===id){activeWindow=Object.keys(windows).find(k=>!windows[k].classList.contains('min'))||null;if(activeWindow)focus(windows[activeWindow])}renderTaskbar()}function minWin(id){let w=windows[id];if(!w)return;w.classList.add('min');if(activeWindow===id){activeWindow=Object.keys(windows).find(k=>k!==id&&!windows[k].classList.contains('min'))||null;if(activeWindow)focus(windows[activeWindow])}renderTaskbar();toast(i18n('MINIMIZED — TAP THE APP ICON TO RESTORE'))}function maxWin(id){let w=windows[id];if(!w)return;w.classList.toggle('max');focus(w)}function snapWindow(w){if(innerWidth<650||w.classList.contains('max'))return;let r=w.getBoundingClientRect(),edge=22;if(r.top<edge){w.classList.add('max');return}if(r.left<edge){w.style.left='18px';w.style.top='18px';w.style.width='calc(50vw - 27px)';w.style.height='calc(100vh - 98px)'}else if(r.right>innerWidth-edge){w.style.left='calc(50vw + 9px)';w.style.top='18px';w.style.width='calc(50vw - 27px)';w.style.height='calc(100vh - 98px)'}}function drag(w){let bar=w.querySelector('.titlebar'),x,y,l,t;bar.onpointerdown=e=>{if(e.target.closest('.controls')||innerWidth<650)return;if(w.classList.contains('max'))w.classList.remove('max');x=e.clientX;y=e.clientY;l=w.offsetLeft;t=w.offsetTop;bar.setPointerCapture(e.pointerId);bar.onpointermove=e=>{w.style.left=Math.max(0,l+e.clientX-x)+'px';w.style.top=Math.max(0,t+e.clientY-y)+'px'};bar.onpointerup=()=>{bar.onpointermove=null;snapWindow(w)}}}document.addEventListener('keydown',e=>{if(e.altKey&&e.key==='F4'&&activeWindow){e.preventDefault();closeWin(activeWindow)}if(e.key==='Escape'&&activeWindow&&!document.querySelector('.modal')&&!document.querySelector('#lifeos-search')){let w=windows[activeWindow];if(w?.classList.contains('max')){e.preventDefault();maxWin(activeWindow)}}})
|
||
function renderApp(id){let c=document.querySelector('#content-'+id);if(!c)return;if(id==='today')today(c);if(id==='timeline')timeline(c);if(id==='museum')museumApp(c);if(id==='insights')insights(c);if(id==='profile')profileApp(c);if(id==='tasks')tasksApp(c);if(id==='calendar')calendarApp(c);if(id==='system')systemApp(c);setTimeout(applyLanguage,0)}function renderAll(){Object.keys(windows).forEach(renderApp);renderTaskbar();window.renderDesktopGoalCountdowns?.();setTimeout(applyLanguage,0)}document.addEventListener('click',e=>{if(e.target.closest('#content-today .chip,#content-today .energy button'))setTimeout(applyLanguage,0)})
|
||
let todayDraft=null,todayTitleDraft=null,todayTargetDate=null;function today(c){let m=mems().find(x=>x.date===day());if(todayDraft===null){selectedMood=m?.mood||'cloudy';selectedEnergy=m?.energy||3}let f=m||filmFor(selectedMood,selectedEnergy),text=todayDraft===null?(m?.text||''):todayDraft;c.innerHTML=`<div class="eyebrow">${new Date().toLocaleDateString('en-US',{weekday:'long'}).toUpperCase()}</div><h1>${fmtDate(day()).replace(/^[^,]+, /,'')}</h1><p class="sub">How was your day?</p><div class="today-grid"><div><textarea id="entry" class="entry" placeholder="Write something about today..." oninput="todayDraft=this.value">${esc(text)}</textarea><div class="actions"><button class="primary" onclick="archiveToday()">${m?'UPDATE MEMORY':'ARCHIVE TODAY'}</button><span id="saveStatus" class="status"></span></div></div><aside><div class="eyebrow">TODAY'S WEATHER</div><div class="choices">${['clear','light','cloudy','rain','storm'].map(x=>`<button class="chip ${x===selectedMood?'selected':''}" onclick="setMood('${x}')">${x.toUpperCase()}</button>`).join('')}</div><div class="eyebrow" style="margin-top:22px">ENERGY LEVEL</div><div class="energy">${[1,2,3,4,5].map(x=>`<button class="${x===selectedEnergy?'selected':''}" onclick="setEnergy(${x})">${x}</button>`).join('')}</div><div class="film"><div class="eyebrow">TODAY'S FILM</div><div class="film-title">${f.filmTitle}</div><div class="sub">${f.filmSubtitle}</div><p class="film-copy">“${f.filmLine}”</p></div></aside></div>`}function filmFor(m,e){const dict=LifeI18n.dictionaries[lang()==='zh'?'zh':'en'],seed=[...`${day()}|${m}|${e}`].reduce((sum,char,index)=>sum+char.charCodeAt(0)*(index+1),0),n=seed%dict.prompt.films.length;return{filmTitle:dict.prompt.films[n].title,filmSubtitle:dict.prompt.films[n].subtitle,filmLine:dict.prompt.lines[(n*3+e)%dict.prompt.lines.length]}}function rerenderToday(){let old=document.querySelector('#entry');todayDraft=old?old.value:todayDraft||'';today(document.querySelector('#content-today'));let entry=document.querySelector('#entry');entry?.focus();entry?.setSelectionRange(entry.value.length,entry.value.length)}function setMood(x){selectedMood=x;rerenderToday()}function setEnergy(x){selectedEnergy=x;rerenderToday()}function archiveToday(){let text=document.querySelector('#entry').value.trim(),a=mems(),old=a.find(x=>x.date===day()),f=old||filmFor(selectedMood,selectedEnergy),now=new Date().toISOString(),m={id:old?.id||'memory_'+crypto.randomUUID(),date:day(),text,mood:selectedMood,energy:selectedEnergy,filmTitle:f.filmTitle,filmSubtitle:f.filmSubtitle,filmLine:f.filmLine,createdAt:old?.createdAt||now,updatedAt:now};let status=document.querySelector('#saveStatus');status.textContent='ARCHIVING MEMORY...';setTimeout(()=>{save('lifeos_memories',[...a.filter(x=>x.date!==day()),m].sort((x,y)=>y.date.localeCompare(x.date)));todayDraft=null;status.textContent='MEMORY ARCHIVED';renderAll()},360)}
|
||
function timeline(c){let a=mems();c.innerHTML=`<div class="eyebrow">MEMORY ARCHIVE</div><h1>${a.length} ${a.length===1?'MEMORY':'MEMORIES'} ARCHIVED</h1>${a.length?`<div class="timeline">${a.map(m=>`<article class="memory"><button onclick="detail('${m.id}')"><div class="date">${shortDate(m.date)} · ${m.mood.toUpperCase()} · ENERGY ${m.energy}</div><div class="mtitle">${esc(m.filmTitle)}</div><div class="snippet">${esc(m.text||'No written note.')}</div></button></article>`).join('')}</div>`:'<div class="empty">No memories yet.<br>Open Today when you are ready to keep one.</div>'}`}function detail(id){let m=mems().find(x=>x.id===id);if(!m)return;modal.innerHTML=`<div class="modal"><div class="modalbox"><div class="eyebrow">MEMORY DETAIL</div><h1>${esc(m.filmTitle)}</h1><p class="sub">${fmtDate(m.date)} · ${m.mood.toUpperCase()} · ENERGY ${m.energy}</p><p style="line-height:1.65">${esc(m.text||'No written note.').replace(/\n/g,'<br>')}</p><p class="film-copy">${m.filmSubtitle}</p><div class="actions"><button class="primary" onclick="editMemory('${m.id}')">EDIT</button><button class="secondary" onclick="preserve('${m.id}')">${museum().some(x=>x.memoryId===m.id)?'PRESERVED':'ADD TO MUSEUM'}</button><button class="danger" onclick="confirmDelete('${m.id}')">DELETE</button></div></div></div>`}function editMemory(id){let m=mems().find(x=>x.id===id);modal.innerHTML='';openApp('today');selectedMood=m.mood;selectedEnergy=m.energy;let cur=mems(),ix=cur.findIndex(x=>x.id===id);cur[ix].date=day();save('lifeos_memories',cur);today(document.querySelector('#content-today'));document.querySelector('#entry').value=m.text;toast('EDITING AS TODAY — SAVE TO UPDATE')}function preserve(id){let m=mems().find(x=>x.id===id),a=museum();if(!a.some(x=>x.memoryId===id)){save('lifeos_museum',[...a,{id:'artifact_'+crypto.randomUUID(),memoryId:id,preservedAt:new Date().toISOString()}]);toast('MEMORY PRESERVED')}modal.innerHTML='';renderAll()}function confirmDelete(id){modal.innerHTML=`<div class="modal"><div class="modalbox"><div class="eyebrow">CONFIRM DELETE</div><h2>Remove this memory?</h2><p class="sub">This cannot be undone.</p><div class="actions"><button class="danger" onclick="deleteMemory('${id}')">DELETE MEMORY</button><button class="secondary" onclick="modal.innerHTML=''">CANCEL</button></div></div></div>`}function deleteMemory(id){save('lifeos_memories',mems().filter(x=>x.id!==id));save('lifeos_museum',museum().filter(x=>x.memoryId!==id));modal.innerHTML='';renderAll();toast('MEMORY REMOVED')}
|
||
function museumApp(c){let a=museum(),ms=mems();c.innerHTML=`<div class="eyebrow">MUSEUM</div><h1>Preserved moments</h1><p class="sub">Worth keeping for the long view.</p>${a.length?`<div class="museum">${a.map(x=>{let m=ms.find(q=>q.id===x.memoryId);return m?`<button class="artifact" onclick="detail('${m.id}')"><span>COLLECTION #${x.id.slice(-4).toUpperCase()}</span><strong>${esc(m.title||m.text?.trim().split('\n')[0].slice(0,64)||i18n('timeline.untitled'))}</strong><span>${fmtDate(m.date)}<br><br>STATUS: PRESERVED</span></button>`:''}).join('')}</div>`:'<div class="empty">A museum begins with one preserved moment.</div>'}`}
|
||
function streak(a){let s=0,d=new Date();while(a.some(m=>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=`<div class="eyebrow">LAST 30 DAYS</div><h1>Insights, quietly.</h1><div class="insights"><section class="metric"><div class="eyebrow">WEATHER</div>${Object.entries(cnt).map(([k,v])=>`<div class="sub">${cap(k)} <b style="float:right;color:var(--ink)">${v} DAYS</b></div>`).join('')}</section><section class="metric"><div class="eyebrow">AVERAGE ENERGY</div><div class="big">${avg}<span style="font-size:16px"> / 5</span></div><div class="bars">${a.slice(0,10).reverse().map(x=>`<i class="bar" style="height:${x.energy*18}%"></i>`).join('')||'<span class="sub">No entries yet</span>'}</div></section><section class="metric"><div class="eyebrow">MOST ACTIVE DAY</div><div class="big" style="font-size:26px">${active}</div></section><section class="metric"><div class="eyebrow">CURRENT STREAK</div><div class="big">${streak(a)}<span style="font-size:16px"> DAYS</span></div><div class="sub">Longest streak: ${longest(a)} days</div></section></div>`}
|
||
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=`<div class="eyebrow">SYSTEM PROFILE</div><h1>${p.name?esc(p.name)+' · ':''}LIFE OS ONLINE</h1><div class="profile-grid"><div class="stat"><b>${started}</b><span>DAYS ONLINE</span></div><div class="stat"><b>${a.length}</b><span>MEMORIES</span></div><div class="stat"><b>${museum().length}</b><span>MUSEUM ITEMS</span></div><div class="stat"><b>${streak(a)}</b><span>CURRENT STREAK</span></div><div class="stat"><b>${longest(a)}</b><span>LONGEST STREAK</span></div><div class="stat"><b>${cap(common)}</b><span>COMMON WEATHER</span></div></div><div class="eyebrow">PERSONAL SYSTEM</div><div class="formrow"><label>Display Name</label><input id="pname" value="${esc(p.name)}"></div><div class="formrow"><label>Birthday (optional)</label><input id="pbday" type="date" value="${esc(p.birthday)}"></div><div class="formrow"><label>Life OS Start Date</label><input id="pstart" type="date" value="${esc(p.startDate)}"></div><div class="formrow"><label>Personal Motto</label><input id="pmotto" value="${esc(p.motto)}"></div><div class="actions"><button class="primary" onclick="saveProfile()">SAVE PROFILE</button><span class="status">AVERAGE ENERGY ${avg} / 5</span></div>`}function saveProfile(){save('lifeos_profile',{name:pname.value,birthday:pbday.value,startDate:pstart.value||day(),motto:pmotto.value});renderAll();toast('PROFILE SAVED')}
|
||
function today(c){let m=mems().find(x=>x.date===day());if(todayDraft===null){selectedMood=m?.mood||'cloudy';selectedEnergy=m?.energy||3}let f=m||filmFor(selectedMood,selectedEnergy),text=todayDraft===null?(m?.text||''):todayDraft,cWeek=new Date().toLocaleDateString(lang()==='zh'?'zh-CN':'en-US',{weekday:'long'}).toUpperCase();c.innerHTML=`<div class="eyebrow">${cWeek}</div><h1>${fmtDate(day()).replace(/^[^,]+, /,'')}</h1><p class="sub">${i18n('How was your day?')}</p><div class="today-grid"><div><textarea id="entry" class="entry" placeholder="${i18n('Write something about today...')}" oninput="todayDraft=this.value">${esc(text)}</textarea><div class="actions"><button class="primary" onclick="archiveToday()">${i18n(m?'UPDATE MEMORY':'ARCHIVE TODAY')}</button><span id="saveStatus" class="status"></span></div></div><aside><div class="eyebrow">${i18n("TODAY'S WEATHER")}</div><div class="choices">${['clear','light','cloudy','rain','storm'].map(x=>`<button class="chip ${x===selectedMood?'selected':''}" onclick="setMood('${x}')">${moodName(x)}</button>`).join('')}</div><div class="eyebrow" style="margin-top:22px">${i18n('ENERGY LEVEL')}</div><div class="energy">${[1,2,3,4,5].map(x=>`<button class="${x===selectedEnergy?'selected':''}" onclick="setEnergy(${x})">${x}</button>`).join('')}</div><div class="film"><div class="eyebrow">${i18n("TODAY'S FILM")}</div><div class="film-title">${f.filmTitle}</div><div class="sub">${f.filmSubtitle}</div><p class="film-copy">“${f.filmLine}”</p></div></aside></div>`}function rerenderToday(){let old=document.querySelector('#entry');todayDraft=old?old.value:todayDraft||'';today(document.querySelector('#content-today'));let entry=document.querySelector('#entry');entry?.focus();entry?.setSelectionRange(entry.value.length,entry.value.length)}
|
||
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=`<div class="eyebrow">SYSTEM SETTINGS</div><h1>System, local and yours.</h1><div class="settings"><section><div class="eyebrow">APPEARANCE</div><div class="choices">${['dark','light','auto'].map(x=>`<button class="chip ${s.theme===x?'selected':''}" onclick="theme('${x}')">${x.toUpperCase()}</button>`).join('')}</div></section><section><div class="eyebrow">LANGUAGE</div><div class="choices"><button class="chip ${s.language!=='zh'?'selected':''}" onclick="setLanguage('en')">ENGLISH</button><button class="chip ${s.language==='zh'?'selected':''}" onclick="setLanguage('zh')">CHINESE</button></div></section><section><div class="eyebrow">DATA</div><div class="actions"><button class="primary" onclick="exportData()">EXPORT DATA</button><button class="secondary" onclick="document.querySelector('#importer').click()">IMPORT DATA</button><button class="danger" onclick="resetConfirm()">RESET LIFE OS</button><input id="importer" class="hidden" type="file" accept=".json" onchange="readImport(this)"></div></section><section><div class="eyebrow">STORAGE</div><p>Local Storage<br>${mems().length} memories · ${museum().length} museum items · Approx. ${Math.ceil(bytes/1024)} KB</p></section><section><div class="eyebrow">DATA STORAGE</div><p>Your data is stored locally in this browser.<br>Create regular backups to prevent data loss.</p><p>${s.lastBackup?'LAST BACKUP · '+fmtDate(s.lastBackup.slice(0,10)):'LAST BACKUP · NEVER'} ${rec?'<br><b style="color:var(--accent)">BACKUP RECOMMENDED</b>':''}</p></section><section><div class="eyebrow">VERSION</div><p>Life OS<br>Version 0.1 · Schema v1</p></section></div>`}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'||![1,2].includes(d.schemaVersion)||!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=`<div class="modal"><div class="modalbox"><div class="eyebrow">LIFE OS BACKUP</div><h2>Ready to restore</h2><p class="sub">Exported: ${fmtDate(d.exportedAt.slice(0,10))}<br>Memories: ${d.memories.length}<br>Museum Items: ${d.museum.length}</p><div class="actions"><button class="primary" onclick="applyImport('merge')">MERGE</button><button class="danger" onclick="replaceConfirm()">REPLACE ALL DATA</button><button class="secondary" onclick="modal.innerHTML=''">CANCEL</button></div></div></div>`}function replaceConfirm(){modal.innerHTML=`<div class="modal"><div class="modalbox"><div class="eyebrow">FINAL CONFIRMATION</div><h2>Replace all Life OS data?</h2><p class="sub">This will replace all current Life OS data.</p><div class="actions"><button class="danger" onclick="applyImport('replace')">REPLACE DATA</button><button class="secondary" onclick="importPreview()">CANCEL</button></div></div></div>`}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=`<div class="modal"><div class="modalbox"><div class="eyebrow">RESET LIFE OS</div><h2>Reset all local data?</h2><p class="sub">Export a backup first if you want to keep it.</p><div class="actions"><button class="danger" onclick="['lifeos_memories','lifeos_museum','lifeos_profile','lifeos_settings','lifeos_system'].forEach(k=>localStorage.removeItem(k));location.reload()">RESET DATA</button><button class="secondary" onclick="modal.innerHTML=''">CANCEL</button></div></div></div>`}function toast(t){let x=document.createElement('div');x.className='toast';x.textContent=t;document.body.append(x);setTimeout(()=>x.remove(),2000)}
|
||
</script><script src="boot-guard.js"></script><script src="locales/en.js"></script><script src="locales/zh-CN.js"></script><script src="i18n.js"></script><script src="tasks.js"></script><script src="calendar.js"></script><script src="addons.js"></script><script src="i18n-renderers.js"></script><script>init();if('serviceWorker' in navigator){window.addEventListener('load',()=>navigator.serviceWorker.register('/service-worker.js').catch(()=>{}))}</script></body></html>
|