/* Render-time localisation layer. User-created records are deliberately never translated. */
(() => {
const t = (key, values) => LifeI18n.t(key, values);
const locale = () => lang() === 'zh' ? 'zh-CN' : 'en-US';
const weekday = value => new Intl.DateTimeFormat(locale(), { weekday: 'long' }).format(new Date(value + 'T12:00:00'));
const uiDate = value => new Intl.DateTimeFormat(locale(), { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' }).format(new Date(value + 'T12:00:00'));
const short = value => new Intl.DateTimeFormat(locale(), { month: 'short', day: 'numeric' }).format(new Date(value + 'T12:00:00')).toUpperCase();
const weather = mood => t(`weather.${mood}`);
const rootModal = () => document.getElementById('modal');
const linkedTasks = () => sys().tasksBoard?.tasks || [];
const escAttr = value => esc(value).replace(/\"/g, '"');
const titleOf = memory => memory.title?.trim() || memory.text?.trim().split('\n')[0].slice(0, 64) || t('timeline.untitled');
const localizedFilmField = (memory, field) => { const index = LifeI18n.dictionaries.en.prompt.films.findIndex(film => film.title === memory.filmTitle); return index < 0 ? (memory[field] || '') : (LifeI18n.dictionaries[lang() === 'zh' ? 'zh' : 'en'].prompt.films[index]?.[field === 'filmTitle' ? 'title' : 'subtitle'] || memory[field] || ''); };
const tagList = value => [...new Set(String(value || '').split(/[,,]/).map(tag => tag.trim()).filter(Boolean))].slice(0, 12);
const memoryMetaFields = memory => `
`;
window.appName = id => t(`app.${id}`);
window.moodName = weather;
window.fmtDate = uiDate;
window.shortDate = short;
window.addMemoryForDate = date => { todayTargetDate = date; todayDraft = null; todayTitleDraft = null; openApp('today'); };
window.closeModal = () => { rootModal().innerHTML = ''; };
window.applyLanguage = () => { document.documentElement.lang = lang() === 'zh' ? 'zh-CN' : 'en'; document.title = t('shell.pageTitle'); const brand = document.querySelector('#desktop-brand'), launcher = document.querySelector('#life-launcher'), boot = document.querySelector('#boot span'); if (brand) brand.textContent = t('shell.brand'); if (launcher) launcher.textContent = t('shell.life'); if (boot) boot.textContent = t('shell.initializing'); if (settings().wallpaper) applyWallpaper(settings().wallpaper); };
window.applyWallpaper = wallpaper => { if (!wallpaper?.url) return; const desktop = document.querySelector('#desktop'); desktop.style.backgroundImage = `linear-gradient(120deg,color-mix(in srgb,var(--bg) 68%,transparent),color-mix(in srgb,var(--bg) 36%,transparent)),url('${wallpaper.url}')`; desktop.style.backgroundSize = 'cover'; desktop.style.backgroundPosition = 'center'; desktop.title = t('shell.wallpaperTitle', { title: wallpaper.title || t('wallpaper.dailyImage') }); };
window.today = container => {
const entryDate = todayTargetDate || day(), memory = mems().find(item => item.date === entryDate);
if (todayDraft === null) { selectedMood = memory?.mood || 'cloudy'; selectedEnergy = memory?.energy || 3; }
const film = filmFor(selectedMood, selectedEnergy), text = todayDraft === null ? (memory?.text || '') : todayDraft;
container.innerHTML = `${weekday(entryDate).toUpperCase()}
${uiDate(entryDate)}
${t('today.prompt')} ${entryDate !== day() ? `` : ''}
`;
};
window.archiveToday = () => {
const entryDate = todayTargetDate || day(), text = document.querySelector('#entry')?.value.trim() || '', old = mems().find(item => item.date === entryDate), film = filmFor(selectedMood, selectedEnergy), now = new Date().toISOString();
const memory = { ...old, id: old?.id || `memory_${crypto.randomUUID()}`, date: entryDate, text, title: document.querySelector('#memory-title')?.value.trim() || '', mood: selectedMood, energy: selectedEnergy, tags: tagList(document.querySelector('#memory-tags')?.value), location: document.querySelector('#memory-place')?.value.trim() || '', taskId: document.querySelector('#memory-task')?.value || '', filmTitle: film.filmTitle, filmSubtitle: film.filmSubtitle, filmLine: film.filmLine, createdAt: old?.createdAt || now, updatedAt: now };
const status = document.querySelector('#saveStatus'); status.textContent = t('today.archiving');
setTimeout(() => { save('lifeos_memories', [...mems().filter(item => item.date !== entryDate), memory].sort((a, b) => b.date.localeCompare(a.date))); todayDraft = null; todayTitleDraft = null; status.textContent = t('today.archived'); renderAll(); }, 360);
};
window.timeline = container => {
const all = mems(), tags = [...new Set(all.flatMap(item => item.tags || []))].sort((a,b) => a.localeCompare(b));
const query = window.timelineQuery || '', selectedTag = window.timelineTag || '';
const filtered = all.filter(memory => {
const task = linkedTasks().find(item => item.id === memory.taskId);
const haystack = [memory.title, memory.filmTitle, memory.text, memory.location, ...(memory.tags || []), task?.title].join(' ').toLocaleLowerCase();
return (!query || haystack.includes(query.toLocaleLowerCase())) && (!selectedTag || (memory.tags || []).includes(selectedTag));
});
const isSaved = memory => museum().some(item => item.memoryId === memory.id);
container.innerHTML = `${t('timeline.eyebrow')}
${t('timeline.heading',{count:filtered.length,total:all.length})}
${filtered.length ? `${filtered.map(memory => { const task = linkedTasks().find(item => item.id === memory.taskId); return `
`; }).join('')}
` : `${all.length ? t('timeline.noMatches') : `${t('timeline.emptyTitle')}
${t('timeline.emptyBody')}`}
`}`;
};
window.museumApp = container => {
const saved = museum(), memories = mems();
container.innerHTML = `${t('museum.eyebrow')}
${t('museum.title')}
${t('museum.subtitle')}
${saved.length ? `${saved.map((artifact,index) => { const memory = memories.find(item => item.id === artifact.memoryId); if (!memory) return ''; return ``; }).join('')}
` : `${t('museum.empty')}
`}`;
};
window.setTimelineQuery = value => { window.timelineQuery = value; const field = document.querySelector('#content-timeline input[type=search]'); const start = field?.selectionStart; timeline(document.querySelector('#content-timeline')); const next = document.querySelector('#content-timeline input[type=search]'); next?.focus(); if (start !== undefined) next?.setSelectionRange(start,start); };
window.setTimelineTag = value => { window.timelineTag = value; timeline(document.querySelector('#content-timeline')); };
window.detail = id => {
const memory = mems().find(item => item.id === id); if (!memory) return;
const task = linkedTasks().find(item => item.id === memory.taskId), favorite = museum().some(item => item.memoryId === memory.id);
rootModal().innerHTML = `${t('timeline.detail')}
${esc(titleOf(memory))}
${uiDate(memory.date)} · ${weather(memory.mood)} · ${t('common.energy')} ${memory.energy}
${esc(memory.text || t('timeline.noNote')).replace(/\n/g,'
')}
${memory.location ? `
⌖ ${esc(memory.location)}
` : ''}${task ? `
↗ ${esc(t('memory.relatedTask'))}:
` : ''}${(memory.tags || []).length ? `
${memory.tags.map(tag => `# ${esc(tag)}`).join('')}
` : ''}
${t('today.film')}: ${esc(localizedFilmField(memory,'filmTitle'))}
${esc(localizedFilmField(memory,'filmSubtitle'))}
`;
};
window.editMemory = id => { const memory = mems().find(item => item.id === id); if (!memory) return; rootModal().innerHTML = ``; };
window.saveMemoryEdit = (event,id) => { event.preventDefault(); const all=mems(), memory=all.find(item=>item.id===id); if(!memory)return; memory.title=document.querySelector('#memory-edit-title').value.trim(); memory.text=document.querySelector('#memory-edit-note').value.trim(); memory.tags=tagList(document.querySelector('#memory-tags').value); memory.location=document.querySelector('#memory-place').value.trim(); memory.taskId=document.querySelector('#memory-task').value||''; memory.updatedAt=new Date().toISOString(); save('lifeos_memories',all); closeModal(); renderAll(); detail(id); };
window.preserve = id => { const all = museum(), alreadyPreserved = all.some(item => item.memoryId === id); if (alreadyPreserved) { save('lifeos_museum', all.filter(item => item.memoryId !== id)); toast(t('timeline.unpreserved')); } else { save('lifeos_museum', [...all, { id: `artifact_${crypto.randomUUID()}`, memoryId: id, preservedAt: new Date().toISOString() }]); toast(t('timeline.added')); } closeModal(); renderAll(); };
window.confirmDelete = id => { rootModal().innerHTML = `${t('timeline.confirmDelete')}
${t('timeline.remove')}
${t('timeline.irreversible')}
`; };
window.deleteMemory = id => { save('lifeos_memories', mems().filter(item => item.id !== id)); save('lifeos_museum', museum().filter(item => item.memoryId !== id)); rootModal().innerHTML = ''; renderAll(); toast(t('timeline.removed')); };
let insightView = 'week', insightOffset = 0;
const reportDateKey = value => `${value.getFullYear()}-${String(value.getMonth() + 1).padStart(2, '0')}-${String(value.getDate()).padStart(2, '0')}`;
const reportDayNumber = value => Date.UTC(value.getFullYear(), value.getMonth(), value.getDate()) / 864e5;
const reportEventDate = value => { if (!value) return ''; const parsed = new Date(value); return Number.isNaN(parsed.getTime()) ? '' : reportDateKey(parsed); };
const reportPeriod = () => {
const today = new Date(), first = insightView === 'week' ? new Date(today.getFullYear(), today.getMonth(), today.getDate(), 12) : new Date(today.getFullYear(), today.getMonth() + insightOffset, 1, 12);
if (insightView === 'week') first.setDate(first.getDate() - (first.getDay() + 6) % 7 + insightOffset * 7);
const next = new Date(first);
if (insightView === 'week') next.setDate(next.getDate() + 7); else next.setMonth(next.getMonth() + 1);
const last = new Date(next); last.setDate(last.getDate() - 1);
const elapsed = Math.min(reportDayNumber(next), reportDayNumber(today) + 1) - reportDayNumber(first);
return { start: reportDateKey(first), end: reportDateKey(next), last: reportDateKey(last), today: reportDateKey(today), elapsed };
};
window.setInsightsView = view => { if (!['week', 'month'].includes(view)) return; insightView = view; insightOffset = 0; window.insights(document.querySelector('#content-insights')); };
window.shiftInsights = delta => { const next = insightOffset + delta; if (next > 0 || !Number.isInteger(next)) return; insightOffset = next; window.insights(document.querySelector('#content-insights')); };
window.insights = container => {
if (!container) return;
const period = reportPeriod(), inPeriod = key => key && key >= period.start && key < period.end && key <= period.today;
const records = mems().filter(item => inPeriod(item.date)).sort((a, b) => a.date.localeCompare(b.date));
const daysRecorded = new Set(records.map(item => item.date)).size;
const board = sys().tasksBoard || {}, tasks = Array.isArray(board.tasks) ? board.tasks : [], goals = Array.isArray(board.goals) ? board.goals : [];
const tasksCreated = tasks.filter(item => inPeriod(reportEventDate(item.createdAt))).length;
const tasksCompleted = tasks.filter(item => item.done && inPeriod(reportEventDate(item.completedAt))).length;
const goalsCompleted = goals.filter(item => item.done && inPeriod(reportEventDate(item.completedAt))).length;
const weatherCounts = { clear: 0, light: 0, cloudy: 0, rain: 0, storm: 0 };
records.forEach(item => { if (Object.hasOwn(weatherCounts, item.mood)) weatherCounts[item.mood]++; });
const commonWeather = Object.entries(weatherCounts).filter(([, count]) => count).sort((a, b) => b[1] - a[1]).slice(0, 3);
const energies = records.map(item => ({ date: item.date, value: Number(item.energy) })).filter(item => Number.isFinite(item.value) && item.value >= 1 && item.value <= 5);
const energyAverage = energies.length ? (energies.reduce((sum, item) => sum + item.value, 0) / energies.length).toFixed(1) : '—';
const favorites = new Set(museum().map(item => item.memoryId));
const hasHighlightTag = item => (Array.isArray(item.tags) ? item.tags : []).some(tag => ['高光', 'highlight'].includes(String(tag).replace(/^#/, '').trim().toLowerCase()));
const highlights = records.filter(item => favorites.has(item.id) || hasHighlightTag(item)).reverse().slice(0, 3);
const count = value => LifeI18n.number(value), range = `${LifeI18n.date(period.start, { month: 'short', day: 'numeric', year: 'numeric' })} — ${LifeI18n.date(period.last, { month: 'short', day: 'numeric', year: 'numeric' })}`;
container.innerHTML = `${t('insights.eyebrow')}
${t('insights.title')}
${t('insights.description')}
${['week', 'month'].map(view => ``).join('')}
${range}
${t('insights.records')}
${count(daysRecorded)} / ${count(period.elapsed)}
${t('insights.recordedDays', { count: count(records.length), percent: count(period.elapsed ? Math.round(daysRecorded / period.elapsed * 100) : 0) })}
${t('insights.tasks')}
${count(tasksCompleted)}
${t('insights.completedTasks')}
${t('insights.taskDetails', { created: count(tasksCreated), goals: count(goalsCompleted) })}
${t('insights.weather')}
${commonWeather.length ? `${commonWeather.map(([key, value]) => `
${weather(key)}${count(value)}
`).join('')}
` : `${t('insights.noWeather')}
`}
${t('insights.energy')}
${energyAverage} / 5
${energies.length > 1 ? t('insights.energyFromTo', { first: energies[0].value, last: energies.at(-1).value }) : energies.length ? t('insights.singleEnergy') : t('insights.noEnergy')}
${energies.length ? `${energies.map(item => ``).join('')}
` : ''}
${t('insights.highlights')}
${t('insights.highlightSource')}
${highlights.length ? `${highlights.map(item => ``).join('')}
` : `${t('insights.highlightEmpty')}
`}
${t('insights.note')}
`;
};
window.profileApp = container => {
const profileData = profile(), all = mems(), common = Object.entries(all.reduce((out, item) => (out[item.mood] = (out[item.mood] || 0) + 1, out), {})).sort((a,b) => b[1] - a[1])[0]?.[0] || '—', average = all.length ? (all.reduce((sum,item) => sum + item.energy,0) / all.length).toFixed(1) : '—', started = Math.max(1, Math.floor((Date.now() - new Date(profileData.startDate)) / 864e5));
container.innerHTML = `${t('profile.eyebrow')}
${profileData.name ? esc(profileData.name) + ' · ' : ''}${t('profile.online')}
${started}${t('profile.daysOnline')}
${all.length}${t('profile.memories')}
${museum().length}${t('profile.museumItems')}
${streak(all)}${t('profile.streak')}
${longest(all)}${t('profile.longest')}
${common === '—' ? common : weather(common)}${t('profile.commonWeather')}
${t('profile.personal')}
${t('profile.average',{value:average})}
`;
};
window.saveProfile = () => { save('lifeos_profile', { name: pname.value, birthday: pbday.value, startDate: pstart.value || day(), motto: pmotto.value }); renderAll(); toast(t('profile.saved')); };
window.systemApp = container => {
const state = settings(), preference = LifeI18n.preference(), bytes = new Blob([JSON.stringify({ mems: mems(), museum: museum(), profile: profile(), system: sys() })]).size, oldBackup = state.lastBackup && Date.now() - new Date(state.lastBackup) > 12096e5, detected = browserLanguage() === 'zh' ? t('system.detectedZh') : t('system.detectedEn');
container.innerHTML = `${t('system.eyebrow')}
${t('system.title')}
${t('system.appearance')}
${['dark','light','auto'].map(item => ``).join('')}
${t('system.language')}
${detected}
${t('system.storage')}
${t('system.local')}
${t('system.storageSummary',{memories:LifeI18n.number(mems().length),museum:LifeI18n.number(museum().length),tasks:LifeI18n.number(sys().tasksBoard?.tasks?.length||0),size:LifeI18n.number(Math.ceil(bytes/1024))})}
${t('system.dataStorage')}
${t('system.stored')}
${t('system.backups')}
${t('system.lastBackup',{value:state.lastBackup ? short(state.lastBackup.slice(0,10)) : t('common.never')})}${oldBackup ? `
${t('system.recommended')}` : ''}
${t('system.version')}
${t('system.release',{version:window.LifeOSVersion||'1.6.0'})}
`;
};
window.importPreview = () => { const data = importData, board = data.system?.tasksBoard || {}; rootModal().innerHTML = `${t('dialog.backup')}
${t('dialog.ready')}
${t('dialog.exported',{date:short(data.exportedAt.slice(0,10))})}
${t('dialog.memories',{count:data.memories.length})}
${t('dialog.museumItems',{count:data.museum.length})}
${t('dialog.goals',{count:board.goals?.length || 0})}
${t('dialog.tasks',{count:board.tasks?.length || 0})}
`; };
window.replaceConfirm = () => { rootModal().innerHTML = `${t('dialog.final')}
${t('dialog.replaceQuestion')}
${t('dialog.replaceWarning')}
`; };
window.applyImport = mode => {
const data = importData;
if (mode === 'replace') {
save('lifeos_memories', data.memories); save('lifeos_museum', data.museum); save('lifeos_profile', data.profile || {}); save('lifeos_settings', data.settings || {}); save('lifeos_system', data.system || { schemaVersion: 2 });
} else {
const memories = mems(), ids = new Set(memories.map(item => item.id)), dates = new Set(memories.map(item => item.date));
save('lifeos_memories', [...memories, ...data.memories.filter(item => !ids.has(item.id) && !dates.has(item.date))]);
const artifacts = museum(), artifactIds = new Set(artifacts.map(item => item.id));
save('lifeos_museum', [...artifacts, ...data.museum.filter(item => !artifactIds.has(item.id))]);
const current = sys(), importedBoard = data.system?.tasksBoard;
if (importedBoard && Array.isArray(importedBoard.goals) && Array.isArray(importedBoard.tasks)) {
current.tasksBoard ||= { goals: [], tasks: [] };
for (const kind of ['goals', 'tasks']) { const known = new Set((current.tasksBoard[kind] || []).map(item => item.id)); current.tasksBoard[kind] = [...(current.tasksBoard[kind] || []), ...importedBoard[kind].filter(item => !known.has(item.id))]; }
save('lifeos_system', current);
}
}
rootModal().innerHTML = ''; toast('feedback.restoring'); setTimeout(() => { renderAll(); toast('feedback.restored'); }, 500);
};
window.resetConfirm = () => { rootModal().innerHTML = `${t('system.reset')}
${t('dialog.resetQuestion')}
${t('dialog.resetWarning')}
`; };
window.toast = key => { const message = key.includes('.') ? t(key) : key; const node = document.createElement('div'); node.className = 'toast'; node.textContent = message; document.body.append(node); setTimeout(() => node.remove(), 2000); };
window.minWin = id => { const windowNode = windows[id]; if (!windowNode) return; windowNode.classList.add('min'); if (activeWindow === id) { activeWindow = Object.keys(windows).find(key => key !== id && !windows[key].classList.contains('min')) || null; if (activeWindow) focus(windows[activeWindow]); } renderTaskbar(); toast('feedback.minimized'); };
const memoryStyle = document.createElement('style');
memoryStyle.textContent = `.memory-meta-form{display:grid;gap:9px;margin-top:12px}.memory-title-field{display:grid;gap:6px;margin-top:10px;color:var(--muted);font-size:12px}.memory-title-field input,.memory-edit-form input{width:100%;min-width:0;border:1px solid var(--line);background:var(--panel2);border-radius:8px;padding:10px 11px;color:var(--ink);outline:none;font:inherit}.memory-title-field input:focus,.memory-edit-form input:focus{border-color:var(--accent)}.memory-meta-form label,.memory-edit-form>label{display:grid;gap:6px;color:var(--muted);font-size:12px}.memory-meta-form input,.memory-meta-form select,.memory-edit-form input,.memory-edit-form textarea{width:100%;min-width:0;border:1px solid var(--line);background:var(--panel2);border-radius:8px;padding:10px 11px;color:var(--ink);outline:none;font:inherit}.memory-meta-form input:focus,.memory-meta-form select:focus,.memory-edit-form input:focus,.memory-edit-form textarea:focus,.timeline-tools input:focus,.timeline-tools select:focus{border-color:var(--accent)}.memory-edit-form{display:grid;gap:12px;max-height:min(80vh,720px);overflow:auto}.memory-edit-form h2{font-size:21px;margin:0}.memory-edit-form textarea{resize:vertical;line-height:1.55}.memory-edit-form .memory-meta-form{margin:0}.memory-edit-form .actions{margin:0}.timeline-tools{display:grid;grid-template-columns:minmax(0,1fr) 190px;gap:8px;margin:17px 0 11px}.timeline-tools input,.timeline-tools select{width:100%;min-width:0;height:40px;border:1px solid var(--line);border-radius:8px;background:var(--panel2);color:var(--ink);padding:0 11px;outline:none;font:13px Manrope,system-ui}.timeline-tools input::placeholder{color:var(--muted)}.memory-open{display:block!important;text-align:left}.memory-badges{display:flex;gap:6px;flex-wrap:wrap;margin-top:9px}.memory-badges span,.memory-detail-meta{color:var(--muted);font-size:12px}.memory-badges span{border:1px solid var(--line);background:var(--panel2);border-radius:99px;padding:4px 8px}.detail-badges{margin:0 0 14px}.text-link{border:0;background:none;padding:0;color:var(--accent);text-decoration:underline}.memory-detail-meta{margin:8px 0}.memory-edit-form .sub{margin:0}@media(max-width:600px){.timeline-tools{grid-template-columns:1fr 130px}.memory-badges span{font-size:11px}.memory-meta-form label,.memory-edit-form>label,.memory-title-field{font-size:13px}}`;
document.head.append(memoryStyle);
const reportStyle = document.createElement('style');
reportStyle.textContent = `.report-description{max-width:620px;line-height:1.55}.report-controls{display:flex;align-items:center;justify-content:space-between;gap:14px;flex-wrap:wrap;margin:20px 0}.report-range{display:flex;align-items:center;gap:10px;min-width:0}.report-range strong{font-size:14px;line-height:1.4;text-align:center}.report-step{width:34px;height:34px;flex:none;border:1px solid var(--line);border-radius:8px;background:var(--panel2);color:var(--ink);cursor:pointer;font-size:22px}.report-step:disabled{opacity:.4;cursor:default}.report-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.report-card{min-width:0;padding:18px;border:1px solid var(--line);border-radius:12px;background:var(--panel2)}.report-card .eyebrow{margin-bottom:12px}.report-card p{font-size:13px;line-height:1.5;color:var(--muted);margin:8px 0 0}.report-big{font-size:32px;font-weight:700;line-height:1.2}.report-big span{font-size:15px;color:var(--muted)}.report-secondary{color:var(--muted);font-size:12px;line-height:1.5;margin-top:8px}.report-weather{display:grid;gap:8px}.report-weather>div{display:flex;justify-content:space-between;gap:10px;font-size:14px}.report-energy-bars{height:50px;display:flex;align-items:end;gap:4px;margin-top:14px}.report-energy-bars i{flex:1;min-width:3px;max-width:24px;background:var(--accent);border-radius:3px 3px 0 0}.report-highlights{margin-top:24px}.report-highlights .sub{line-height:1.5}.report-highlight-list{display:grid;gap:8px}.report-highlight{display:grid;gap:5px;width:100%;padding:14px;text-align:left;border:1px solid var(--line);border-radius:10px;background:var(--panel2);color:var(--ink);cursor:pointer}.report-highlight:hover,.report-highlight:focus-visible{border-color:var(--accent)}.report-highlight small,.report-highlight span{color:var(--muted);line-height:1.4}.report-highlight span{font-size:13px}.report-empty{padding:18px;border:1px dashed var(--line);border-radius:10px;color:var(--muted);font-size:13px}.report-note{margin-top:18px;color:var(--muted);font-size:12px;line-height:1.5}@media(max-width:650px){.report-grid{grid-template-columns:1fr}.report-controls{align-items:flex-start}.report-range{width:100%;justify-content:space-between}}`;
document.head.append(reportStyle);
// Render every surface again after the overrides are installed; no DOM walking is involved.
window.renderIcons(); window.renderAll(); window.applyLanguage();
})();