feat(desktop): add goal countdowns and align app icons
This commit is contained in:
1 parent
88edf64e3b
commit
e87da3e985
8 files changed
+17
-14
No files matched your search
+2
-2
@@ -4,7 +4,7 @@
|
||||
: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{dispLine truncated
|
||||
@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">LIFE OS / PERSONAL DESKTOP</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" onclick="toggleLauncher()">LIFE</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>
|
||||
</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">LIFE OS / PERSONAL DESKTOP</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" onclick="toggleLauncher()">LIFE</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']];
|
||||
@@ -32,7 +32,7 @@ function applyWallpaper(w){if(!w?.url)return;desktop.style.backgroundImage=`line
|
||||
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>${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);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="Double-click to maximize"><span class="titleicon">${APPS[id][0]}</span><span class="wtitle">${appName(id)}</span><div class="controls"><button data-window-action="min" aria-label="${i18n('Minimize')}">—</button><button data-window-action="max" aria-label="${i18n('Maximize')}">□</button><button data-window-action="close" aria-label="${i18n('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();setTimeout(applyLanguage,0)}document.addEventListener('click',e=>{if(e.target.closest('#content-today .chip,#content-today .energy button'))setTimeout(applyLanguage,0)})
|
||||
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){let seed=[...`${day()}|${m}|${e}`].reduce((a,c,i)=>a+c.charCodeAt(0)*(i+1),0),n=seed%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)}
|
||||
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>'}`}
|
||||
|
||||
Reference in new issue
Block a user