Update Site source

This commit is contained in:
Codex committed 2026-09-22 07:27:15 -04:00
commit 006791dbd5
2 files changed
+30

No files matched your search

+24
View File
@@ -0,0 +1,24 @@
<!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"/><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{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: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>
const APPS={today:['◉','TODAY'],timeline:['⌁','TIMELINE'],museum:['◇','MUSEUM'],insights:['⌇','INSIGHTS'],profile:['⊙','PROFILE'],system:['⚙','SYSTEM']};let windows={},z=20,selectedMood='cloudy',selectedEnergy=3,importData=null;
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 load('lifeos_settings',{theme:'dark',lastBackup:null})}function sys(){return load('lifeos_system',{schemaVersion:1})}
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=>({'&':'&amp;','<':'&lt;','>':'&gt;'}[x]))}
function init(){let s=settings();document.documentElement.dataset.theme=s.theme==='auto'?(matchMedia('(prefers-color-scheme:light)').matches?'light':'dark'):s.theme;renderIcons();renderTaskbar();setInterval(clock,1000);clock();setTimeout(()=>boot.remove(),650)}
function renderIcons(){icons.innerHTML=Object.entries(APPS).map(([id,[g,n]])=>`<button class="app-icon" onclick="openApp('${id}')"><span class="glyph">${g}</span><span>${n}</span></button>`).join('')}function renderTaskbar(){tasks.innerHTML=['today','timeline','museum','insights'].map(id=>`<button class="taskapp ${windows[id]?'active':''}" title="${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 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"><span class="titleicon">${APPS[id][0]}</span><span class="wtitle">${APPS[id][1]}</span><div class="controls"><button onclick="minWin('${id}')">—</button><button onclick="maxWin('${id}')">□</button><button onclick="closeWin('${id}')">×</button></div></div><div class="content" id="content-${id}"></div>`;windows[id]=w;document.querySelector('#windows').append(w);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){windows[id].classList.add('min');renderTaskbar()}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(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)}function renderAll(){Object.keys(windows).forEach(renderApp);renderTaskbar()}
function today(c){let m=mems().find(x=>x.date===day());selectedMood=m?.mood||'cloudy';selectedEnergy=m?.energy||3;let f=m||filmFor(selectedMood,selectedEnergy);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...">${esc(m?.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 setMood(x){selectedMood=x;today(document.querySelector('#content-today'))}function setEnergy(x){selectedEnergy=x;today(document.querySelector('#content-today'))}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)));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.filmTitle)}</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 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">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'||d.schemaVersion!==1||!Array.isArray(d.memories)||!Array.isArray(d.museum))throw 0;importData=d;importPreview()}catch{toast('INVALID BACKUP FILE')}};r.readAsText(f)}function importPreview(){let d=importData;modal.innerHTML=`<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)}init();
</script></body></html>