feat(i18n): localize dynamic Life OS interface
This commit is contained in:
1 parent
765a7de7b5
commit
5b7c57c4f5
1 file changed
+7
-4
+7
-4
@@ -10,11 +10,14 @@ const films=[['A Quiet Tuesday','Drama · Slice of Life'],['The Afternoon That S
|
||||
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(){return load('lifeos_system',{schemaVersion:1})}
|
||||
const ZH={'LIFE OS / PERSONAL DESKTOP':'LIFE OS / 人生操作系统','TODAY':'今日','TIMELINE':'时间线','MUSEUM':'人生博物馆','INSIGHTS':'洞察','PROFILE':'个人资料','SYSTEM':'系统','SYSTEM ONLINE · LIFE OS v0.1':'系统在线 · LIFE OS v0.1','SYSTEM DATE':'系统日期','MEMORY ARCHIVE':'记忆档案','MEMORIES ARCHIVED':'条记忆已归档','MEMORY ARCHIVED':'记忆已归档','How was your day?':'今天过得怎么样?','Write something about today...':'写下今天的片段…',"TODAY'S WEATHER":'今日天气','ENERGY LEVEL':'能量等级',"TODAY'S FILM":'今日电影','ARCHIVE TODAY':'归档今日','UPDATE MEMORY':'更新记忆','ARCHIVING MEMORY...':'正在归档…','MUSEUM':'人生博物馆','Preserved moments':'被珍藏的瞬间','Worth keeping for the long view.':'值得留给漫长回望的片段。','A museum begins with one preserved moment.':'从一份被珍藏的记忆开始。','STATUS: PRESERVED':'状态:已珍藏','LAST 30 DAYS':'最近 30 天','Insights, quietly.':'安静地看见时间。','WEATHER':'天气','AVERAGE ENERGY':'平均能量','MOST ACTIVE DAY':'最活跃日','CURRENT STREAK':'当前连续记录','LONGEST STREAK':'最长连续记录','SYSTEM PROFILE':'系统档案','LIFE OS ONLINE':'LIFE OS 在线','DAYS ONLINE':'系统运行天数','MEMORIES':'记忆','MUSEUM ITEMS':'博物馆藏品','COMMON WEATHER':'常见天气','PERSONAL SYSTEM':'个人系统','Display Name':'显示名称','Birthday (optional)':'生日(选填)','Life OS Start Date':'Life OS 启动日期','Personal Motto':'个人格言','SAVE PROFILE':'保存资料','PROFILE SAVED':'资料已保存','SYSTEM SETTINGS':'系统设置','System, local and yours.':'你的本地个人系统。','APPEARANCE':'外观','DATA':'数据','STORAGE':'存储','DATA STORAGE':'数据存储','Your data is stored locally in this browser.':'你的数据仅存储在此浏览器中。','Create regular backups to prevent data loss.':'定期备份,让记忆一直在。','LAST BACKUP · NEVER':'上次备份 · 从未','BACKUP RECOMMENDED':'建议备份','VERSION':'版本','EXPORT DATA':'导出数据','IMPORT DATA':'导入数据','RESET LIFE OS':'重置 Life OS','LANGUAGE':'语言','ENGLISH':'English','CHINESE':'中文','Dark':'深色','Light':'浅色','Auto':'自动','No memories yet.':'还没有记忆。','Open Today when you are ready to keep one.':'准备好记录时,打开“今日”。','MEMORY DETAIL':'记忆详情','EDIT':'编辑','ADD TO MUSEUM':'加入博物馆','PRESERVED':'已珍藏','DELETE':'删除','CONFIRM DELETE':'确认删除','Remove this memory?':'删除这条记忆?','This cannot be undone.':'此操作无法撤销。','DELETE MEMORY':'删除记忆','CANCEL':'取消','BACKUP CREATED':'备份已创建','INVALID BACKUP FILE':'无效备份文件','SYSTEM RESTORED':'系统已恢复','RESTORING SYSTEM...':'正在恢复系统…','MINIMIZED — TAP THE APP ICON TO RESTORE':'已最小化 — 点击任务栏图标即可恢复'};
|
||||
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 applyLanguage(){let zh=lang()==='zh';document.title=zh?'Life OS / 人生操作系统':'Life OS';let map=zh?ZH:Object.fromEntries(Object.entries(ZH).map(([a,b])=>[b,a]));let walker=document.createTreeWalker(document.body,NodeFilter.SHOW_TEXT);let n=[];while(walker.nextNode())n.push(walker.currentNode);n.forEach(x=>{let v=x.nodeValue.trim();if(map[v])x.nodeValue=x.nodeValue.replace(v,map[v])});document.querySelectorAll('[placeholder]').forEach(x=>{if(zh&&ZH[x.placeholder])x.placeholder=ZH[x.placeholder];if(!zh){let k=Object.entries(ZH).find(([,v])=>v===x.placeholder)?.[0];if(k)x.placeholder=k}})}function setLanguage(v){let s=settings();s.language=v;s.languageMode=v==='auto'?'auto':'manual';save('lifeos_settings',s);renderAll();applyLanguage();toast(v==='auto'?(lang()==='zh'?'已跟随浏览器语言:中文':'Following browser language: English'):v==='zh'?'语言已切换为中文':'Language set to English')}
|
||||
function day(){return new Date().toISOString().slice(0,10)}function fmtDate(d){return new Date(d+'T12:00:00').toLocaleDateString('en-US',{weekday:'long',month:'long',day:'numeric',year:'numeric'})}function shortDate(d){return new Date(d+'T12:00:00').toLocaleDateString('en-US',{month:'short',day:'numeric'}).toUpperCase()}function cap(x){return x[0].toUpperCase()+x.slice(1)}function esc(s){return (s||'').replace(/[&<>]/g,x=>({'&':'&','<':'<','>':'>'}[x]))}
|
||||
Object.assign(ZH,{
|
||||
'INITIALIZING PERSONAL SYSTEM...':'正在初始化个人系统…','GOOD MORNING':'早上好','GOOD AFTERNOON':'下午好','GOOD EVENING':'晚上好','Open':'打开','Minimize':'最小化','Maximize':'最大化','Close':'关闭','Refresh desktop':'刷新桌面','New today entry':'新建今日记录','System settings':'系统设置','CLEAR':'晴朗','LIGHT':'微光','CLOUDY':'多云','RAIN':'雨天','STORM':'风暴','Clear':'晴朗','Light':'微光','Cloudy':'多云','Rain':'雨天','Storm':'风暴','MONDAY':'星期一','TUESDAY':'星期二','WEDNESDAY':'星期三','THURSDAY':'星期四','FRIDAY':'星期五','SATURDAY':'星期六','SUNDAY':'星期日','Monday':'星期一','Tuesday':'星期二','Wednesday':'星期三','Thursday':'星期四','Friday':'星期五','Saturday':'星期六','Sunday':'星期日','No written note.':'尚未写下记录。','EDITING AS TODAY — SAVE TO UPDATE':'正在以今日记录编辑 — 保存即可更新','MEMORY PRESERVED':'记忆已珍藏','MEMORY REMOVED':'记忆已删除','No entries yet':'暂无记录','Longest streak:':'最长连续记录:','days':'天','DAYS':'天','ENERGY':'能量','Local Storage':'本地存储','memories':'条记忆','museum items':'件博物馆藏品','Approx.':'约','KB':'KB','Life OS Backup':'Life OS 备份','Ready to restore':'准备恢复','Exported:':'导出时间:','Memories:':'记忆:','Museum Items:':'博物馆藏品:','MERGE':'合并','REPLACE ALL DATA':'替换全部数据','FINAL CONFIRMATION':'最终确认','Replace all Life OS data?':'替换全部 Life OS 数据?','This will replace all current Life OS data.':'这将覆盖当前所有 Life OS 数据。','REPLACE DATA':'替换数据','Reset all local data?':'重置所有本地数据?','Export a backup first if you want to keep it.':'如需保留数据,请先导出备份。','RESET DATA':'重置数据','Following browser language: English':'已跟随浏览器语言:English'
|
||||
});
|
||||
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 localizeDynamicText(zh){if(!zh)return;let walker=document.createTreeWalker(document.body,NodeFilter.SHOW_TEXT),nodes=[];while(walker.nextNode())nodes.push(walker.currentNode);nodes.forEach(node=>{let v=node.nodeValue.trim(),r;if(r=v.match(/^(\d+) (?:MEMORY|MEMORIES) ARCHIVED$/))node.nodeValue=node.nodeValue.replace(v,`${r[1]} 条记忆已归档`);else if(r=v.match(/^(\d+) DAYS$/))node.nodeValue=node.nodeValue.replace(v,`${r[1]} 天`);else if(r=v.match(/^Longest streak: (\d+) days$/))node.nodeValue=node.nodeValue.replace(v,`最长连续记录:${r[1]} 天`);else if(r=v.match(/^(\d+) memories · (\d+) preserved$/))node.nodeValue=node.nodeValue.replace(v,`${r[1]} 条记忆 · ${r[2]} 件已珍藏`);else if(r=v.match(/^Exported: (.+)$/))node.nodeValue=node.nodeValue.replace(v,`导出时间:${r[1]}`);else if(r=v.match(/^Memories: (\d+)$/))node.nodeValue=node.nodeValue.replace(v,`记忆:${r[1]}`);else if(r=v.match(/^Museum Items: (\d+)$/))node.nodeValue=node.nodeValue.replace(v,`博物馆藏品:${r[1]}`);else if(r=v.match(/^LAST BACKUP · (.+)$/))node.nodeValue=node.nodeValue.replace(v,`上次备份 · ${r[1]}`);else if(r=v.match(/^(\d+) memories · (\d+) museum items · Approx\. (\d+) KB$/))node.nodeValue=node.nodeValue.replace(v,`${r[1]} 条记忆 · ${r[2]} 件博物馆藏品 · 约 ${r[3]} KB`)})}function applyLanguage(){let zh=lang()==='zh';document.documentElement.lang=zh?'zh-CN':'en';document.title=zh?'Life OS / 人生操作系统':'Life OS';let map=zh?ZH:Object.fromEntries(Object.entries(ZH).map(([a,b])=>[b,a]));let walker=document.createTreeWalker(document.body,NodeFilter.SHOW_TEXT);let n=[];while(walker.nextNode())n.push(walker.currentNode);n.forEach(x=>{let v=x.nodeValue.trim();if(map[v])x.nodeValue=x.nodeValue.replace(v,map[v])});document.querySelectorAll('[placeholder]').forEach(x=>{if(zh&&ZH[x.placeholder])x.placeholder=ZH[x.placeholder];if(!zh){let k=Object.entries(ZH).find(([,v])=>v===x.placeholder)?.[0];if(k)x.placeholder=k}});localizeDynamicText(zh)}function setLanguage(v){let s=settings();s.language=v;s.languageMode=v==='auto'?'auto':'manual';save('lifeos_settings',s);renderIcons();renderAll();applyLanguage();toast(v==='auto'?(lang()==='zh'?'已跟随浏览器语言:中文':'Following browser language: English'):v==='zh'?'语言已切换为中文':'Language set to English')}
|
||||
function i18n(key,fallback=key){return lang()==='zh'?(ZH[key]||key):fallback}function appName(id){return i18n(APPS[id][1])}function moodName(m){return i18n(m.toUpperCase(),cap(m))}function day(){return new Date().toISOString().slice(0,10)}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=`Bing daily image: ${w.title||'Daily image'}`}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.querySelector('.glyph').style.outline='none');e.currentTarget.querySelector('.glyph').style.outline='2px solid var(--accent)'}function renderIcons(){icons.innerHTML=Object.entries(APPS).map(([id,[g,n]])=>`<button class="app-icon" onclick="desktopIconClick('${id}',event)" ondblclick="openApp('${id}')" oncontextmenu="showContext(event,'${id}')"><span class="glyph">${g}</span><span>${n}</span></button>`).join('')}function renderTaskbar(){let ids=innerWidth<650?Object.keys(windows):['today','timeline','museum','insights'];tasks.innerHTML=ids.map(id=>`<button class="taskapp ${windows[id]?'active':''}" title="${APPS[id][1]}" aria-label="Open ${APPS[id][1]}" onclick="openApp('${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('en-US',{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>GOOD ${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>${a[1]}</button>`).join('')}</div><div class="systemline">SYSTEM ONLINE · LIFE OS v0.1</div>`}function toggleCalendar(){calendar.classList.toggle('hidden');launcher.classList.add('hidden');calendar.innerHTML=`<div class="eyebrow">SYSTEM DATE</div><h2>${fmtDate(day())}</h2><p class="sub">${mems().length} memories · ${museum().length} preserved</p>`}function hideContext(){document.querySelector('#life-context')?.remove()}function showContext(e,id){e.preventDefault();e.stopPropagation();hideContext();let zh=lang()==='zh',labels=zh?{open:'打开',min:'最小化',close:'关闭',refresh:'刷新桌面',new:'新建今日记录',system:'系统设置'}:{open:'Open',min:'Minimize',close:'Close',refresh:'Refresh desktop',new:'New today entry',system:'System settings'},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 desktopIconClick(id,e){if(innerWidth<650){openApp(id);return}selectedIcon=id;document.querySelectorAll('.app-icon').forEach(x=>x.querySelector('.glyph').style.outline='none');e.currentTarget.querySelector('.glyph').style.outline='2px solid var(--accent)'}function renderIcons(){icons.innerHTML=Object.entries(APPS).map(([id,[g,n]])=>`<button class="app-icon" onclick="desktopIconClick('${id}',event)" ondblclick="openApp('${id}')" oncontextmenu="showContext(event,'${id}')"><span class="glyph">${g}</span><span>${appName(id)}</span></button>`).join('')}function renderTaskbar(){let ids=innerWidth<650?Object.keys(windows):['today','timeline','museum','insights'];tasks.innerHTML=ids.map(id=>`<button class="taskapp ${windows[id]?'active':''}" title="${appName(id)}" aria-label="${i18n('Open')} ${appName(id)}" onclick="openApp('${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>${i18n(`GOOD ${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">${i18n('SYSTEM ONLINE · LIFE OS v0.1')}</div>`}function toggleCalendar(){calendar.classList.toggle('hidden');launcher.classList.add('hidden');calendar.innerHTML=`<div class="eyebrow">${i18n('SYSTEM DATE')}</div><h2>${fmtDate(day())}</h2><p class="sub">${lang()==='zh'?`${mems().length} 条记忆 · ${museum().length} 件已珍藏`:`${mems().length} memories · ${museum().length} preserved`}</p>`}function hideContext(){document.querySelector('#life-context')?.remove()}function showContext(e,id){e.preventDefault();e.stopPropagation();hideContext();let zh=lang()==='zh',labels=zh?{open:'打开',min:'最小化',close:'关闭',refresh:'刷新桌面',new:'新建今日记录',system:'系统设置'}:{open:'Open',min:'Minimize',close:'Close',refresh:'Refresh desktop',new:'New today entry',system:'System settings'},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);renderTaskbar();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"><span class="titleicon">${APPS[id][0]}</span><span class="wtitle">${APPS[id][1]}</span><div class="controls"><button data-window-action="min" aria-label="Minimize">—</button><button data-window-action="max" aria-label="Maximize">□</button><button data-window-action="close" aria-label="Close">×</button></div></div><div class="content" id="content-${id}"></div>`;windows[id]=w;document.querySelector('#windows').append(w);let controls=w.querySelector('.controls');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)});w.addEventListener('mousedown',()=>focus(w));drag(w);renderApp(id);focus(w);renderTaskbar()}function focus(w){w.style.zIndex=++z}function closeWin(id){windows[id]?.remove();delete windows[id];renderTaskbar()}function minWin(id){let w=windows[id];if(!w)return;w.classList.add('min');renderTaskbar();toast('MINIMIZED — TAP THE APP ICON TO RESTORE')}function maxWin(id){windows[id].classList.toggle('max');focus(windows[id])}function drag(w){let bar=w.querySelector('.titlebar'),x,y,l,t;bar.onpointerdown=e=>{if(e.target.closest('.controls')||innerWidth<650)return;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}}
|
||||
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==='system')systemApp(c);setTimeout(applyLanguage,0)}function renderAll(){Object.keys(windows).forEach(renderApp);renderTaskbar();setTimeout(applyLanguage,0)}
|
||||
let todayDraft=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){let n=Math.abs([...m].reduce((a,c)=>a+c.charCodeAt(0),e*7)+new Date().getDate())%films.length;return{filmTitle:films[n][0],filmSubtitle:films[n][1],filmLine:lines[(n*3+e)%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)}
|
||||
|
||||
Reference in new issue
Block a user