feat(memory): add tags, context, favorites, and task links

This commit is contained in:
Codex committed 2026-09-27 13:08:22 +00:00
1 parent f5e689d68b
commit 5fd997be3c
7 files changed
+35 -16

No files matched your search

+24 -7
View File
@@ -7,6 +7,9 @@
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 tagList = value => [...new Set(String(value || '').split(/[,,]/).map(tag => tag.trim()).filter(Boolean))].slice(0, 12);
const memoryMetaFields = memory => `<div class="memory-meta-form"><label>${t('memory.tags')}<input id="memory-tags" maxlength="180" value="${esc((memory?.tags || []).join(', '))}" placeholder="${t('memory.tagsPlaceholder')}"></label><label>${t('memory.place')}<input id="memory-place" maxlength="100" value="${esc(memory?.location || '')}" placeholder="${t('memory.placePlaceholder')}"></label><label>${t('memory.linkTask')}<select id="memory-task"><option value="">${t('memory.noTask')}</option>${linkedTasks().map(task => `<option value="${esc(task.id)}" ${task.id === memory?.taskId ? 'selected' : ''}>${esc(task.title)}</option>`).join('')}</select></label></div>`;
window.appName = id => t(`app.${id}`);
window.moodName = weather;
@@ -23,23 +26,34 @@
const memory = mems().find(item => item.date === day());
if (todayDraft === null) { selectedMood = memory?.mood || 'cloudy'; selectedEnergy = memory?.energy || 3; }
const film = memory || filmFor(selectedMood, selectedEnergy), text = todayDraft === null ? (memory?.text || '') : todayDraft;
container.innerHTML = `<div class="eyebrow">${weekday(day()).toUpperCase()}</div><h1>${uiDate(day())}</h1><p class="sub">${t('today.prompt')}</p><div class="today-grid"><div><textarea id="entry" class="entry" placeholder="${t('today.placeholder')}" oninput="todayDraft=this.value">${esc(text)}</textarea><div class="actions"><button class="primary" onclick="archiveToday()">${t(memory ? 'today.update' : 'today.archive')}</button><span id="saveStatus" class="status"></span></div></div><aside><div class="eyebrow">${t('today.weather')}</div><div class="choices">${['clear','light','cloudy','rain','storm'].map(item => `<button class="chip ${item === selectedMood ? 'selected' : ''}" onclick="setMood('${item}')">${weather(item)}</button>`).join('')}</div><div class="eyebrow" style="margin-top:22px">${t('today.energy')}</div><div class="energy">${[1,2,3,4,5].map(item => `<button class="${item === selectedEnergy ? 'selected' : ''}" onclick="setEnergy(${item})">${item}</button>`).join('')}</div><div class="film"><div class="eyebrow">${t('today.film')}</div><div class="film-title">${film.filmTitle}</div><div class="sub">${film.filmSubtitle}</div><p class="film-copy">“${film.filmLine}”</p></div></aside></div>`;
container.innerHTML = `<div class="eyebrow">${weekday(day()).toUpperCase()}</div><h1>${uiDate(day())}</h1><p class="sub">${t('today.prompt')}</p><div class="today-grid"><div><textarea id="entry" class="entry" placeholder="${t('today.placeholder')}" oninput="todayDraft=this.value">${esc(text)}</textarea>${memoryMetaFields(memory)}<div class="actions"><button class="primary" onclick="archiveToday()">${t(memory ? 'today.update' : 'today.archive')}</button><span id="saveStatus" class="status"></span></div></div><aside><div class="eyebrow">${t('today.weather')}</div><div class="choices">${['clear','light','cloudy','rain','storm'].map(item => `<button class="chip ${item === selectedMood ? 'selected' : ''}" onclick="setMood('${item}')">${weather(item)}</button>`).join('')}</div><div class="eyebrow" style="margin-top:22px">${t('today.energy')}</div><div class="energy">${[1,2,3,4,5].map(item => `<button class="${item === selectedEnergy ? 'selected' : ''}" onclick="setEnergy(${item})">${item}</button>`).join('')}</div><div class="film"><div class="eyebrow">${t('today.film')}</div><div class="film-title">${film.filmTitle}</div><div class="sub">${film.filmSubtitle}</div><p class="film-copy">“${film.filmLine}”</p></div></aside></div>`;
};
window.archiveToday = () => {
const text = document.querySelector('#entry')?.value.trim() || '', old = mems().find(item => item.date === day()), film = old || filmFor(selectedMood, selectedEnergy), now = new Date().toISOString();
const memory = { id: old?.id || `memory_${crypto.randomUUID()}`, date: day(), text, mood: selectedMood, energy: selectedEnergy, filmTitle: film.filmTitle, filmSubtitle: film.filmSubtitle, filmLine: film.filmLine, createdAt: old?.createdAt || now, updatedAt: now };
const memory = { ...old, id: old?.id || `memory_${crypto.randomUUID()}`, date: day(), text, 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 !== day()), memory].sort((a, b) => b.date.localeCompare(a.date))); todayDraft = null; status.textContent = t('today.archived'); renderAll(); }, 360);
};
window.timeline = container => {
const all = mems();
container.innerHTML = `<div class="eyebrow">${t('timeline.eyebrow')}</div><h1>${lang() === 'zh' ? `${all.length} 条记忆已归档` : `${all.length} ${all.length === 1 ? 'MEMORY' : 'MEMORIES'} ARCHIVED`}</h1>${all.length ? `<div class="timeline">${all.map(memory => `<article class="memory"><button onclick="detail('${memory.id}')"><div class="date">${short(memory.date)} · ${weather(memory.mood).toUpperCase()} · ${t('common.energy')} ${memory.energy}</div><div class="mtitle">${esc(memory.filmTitle)}</div><div class="snippet">${esc(memory.text || t('timeline.noNote'))}</div></button></article>`).join('')}</div>` : `<div class="empty">${t('timeline.emptyTitle')}<br>${t('timeline.emptyBody')}</div>`}`;
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.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 = `<div class="eyebrow">${t('timeline.eyebrow')}</div><h1>${t('timeline.heading',{count:filtered.length,total:all.length})}</h1><div class="timeline-tools"><label class="timeline-search"><span class="sr-only">${t('timeline.search')}</span><input type="search" value="${esc(query)}" placeholder="${t('timeline.searchPlaceholder')}" oninput="setTimelineQuery(this.value)"></label><label class="timeline-tag-filter"><span class="sr-only">${t('timeline.filterTag')}</span><select onchange="setTimelineTag(this.value)"><option value="">${t('timeline.allTags')}</option>${tags.map(tag => `<option value="${esc(tag)}" ${tag === selectedTag ? 'selected' : ''}>${esc(tag)}</option>`).join('')}</select></label></div>${filtered.length ? `<div class="timeline">${filtered.map(memory => { const task = linkedTasks().find(item => item.id === memory.taskId); return `<article class="memory"><button class="memory-open" onclick="detail('${esc(memory.id)}')"><div class="date">${short(memory.date)} · ${weather(memory.mood).toUpperCase()} · ${t('common.energy')} ${memory.energy}${isSaved(memory) ? ` · ★ ${t('timeline.favorite')}` : ''}</div><div class="mtitle">${esc(memory.filmTitle)}</div><div class="snippet">${esc(memory.text || t('timeline.noNote'))}</div><div class="memory-badges">${memory.location ? `<span>⌖ ${esc(memory.location)}</span>` : ''}${task ? `<span>↗ ${esc(task.title)}</span>` : ''}${(memory.tags || []).map(tag => `<span># ${esc(tag)}</span>`).join('')}</div></button></article>`; }).join('')}</div>` : `<div class="empty">${all.length ? t('timeline.noMatches') : `${t('timeline.emptyTitle')}<br>${t('timeline.emptyBody')}`}</div>`}`;
};
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;
rootModal().innerHTML = `<div class="modal" onclick="if(event.target === this) closeModal()"><div class="modalbox" role="dialog" aria-modal="true" aria-labelledby="memory-detail-title"><button class="modal-close" type="button" onclick="closeModal()" aria-label="${t('common.close')}">×</button><div class="eyebrow">${t('timeline.detail')}</div><h1 id="memory-detail-title">${esc(memory.filmTitle)}</h1><p class="sub">${uiDate(memory.date)} · ${weather(memory.mood)} · ${t('common.energy')} ${memory.energy}</p><p style="line-height:1.65">${esc(memory.text || t('timeline.noNote')).replace(/\n/g,'<br>')}</p><p class="film-copy">${memory.filmSubtitle}</p><div class="actions"><button class="primary" onclick="editMemory('${memory.id}')">${t('common.edit')}</button><button class="secondary" onclick="preserve('${memory.id}')">${museum().some(item => item.memoryId === memory.id) ? t('timeline.preserved') : t('timeline.addMuseum')}</button><button class="danger" onclick="confirmDelete('${memory.id}')">${t('common.delete')}</button></div></div></div>`;
const task = linkedTasks().find(item => item.id === memory.taskId), favorite = museum().some(item => item.memoryId === memory.id);
rootModal().innerHTML = `<div class="modal" onclick="if(event.target === this) closeModal()"><div class="modalbox" role="dialog" aria-modal="true" aria-labelledby="memory-detail-title"><button class="modal-close" type="button" onclick="closeModal()" aria-label="${t('common.close')}">×</button><div class="eyebrow">${t('timeline.detail')}</div><h1 id="memory-detail-title">${esc(memory.filmTitle)}</h1><p class="sub">${uiDate(memory.date)} · ${weather(memory.mood)} · ${t('common.energy')} ${memory.energy}</p><p style="line-height:1.65">${esc(memory.text || t('timeline.noNote')).replace(/\n/g,'<br>')}</p>${memory.location ? `<p class="memory-detail-meta">⌖ ${esc(memory.location)}</p>` : ''}${task ? `<p class="memory-detail-meta">↗ ${esc(t('memory.relatedTask'))}: <button class="text-link" onclick="closeModal();openApp('tasks')">${esc(task.title)}</button></p>` : ''}${(memory.tags || []).length ? `<div class="memory-badges detail-badges">${memory.tags.map(tag => `<span># ${esc(tag)}</span>`).join('')}</div>` : ''}<p class="film-copy">${esc(memory.filmSubtitle || '')}</p><div class="actions"><button class="primary" onclick="editMemory('${esc(memory.id)}')">${t('common.edit')}</button><button class="secondary" onclick="preserve('${esc(memory.id)}')">${favorite ? t('timeline.preserved') : t('memory.favorite')}</button><button class="danger" onclick="confirmDelete('${esc(memory.id)}')">${t('common.delete')}</button></div></div></div>`;
};
window.editMemory = id => { const memory = mems().find(item => item.id === id); rootModal().innerHTML = ''; openApp('today'); selectedMood = memory.mood; selectedEnergy = memory.energy; const all = mems(), index = all.findIndex(item => item.id === id); all[index].date = day(); save('lifeos_memories', all); today(document.querySelector('#content-today')); document.querySelector('#entry').value = memory.text; toast(t('timeline.editing')); };
window.editMemory = id => { const memory = mems().find(item => item.id === id); if (!memory) return; rootModal().innerHTML = `<div class="modal" onclick="if(event.target===this) closeModal()"><form class="modalbox memory-edit-form" onsubmit="saveMemoryEdit(event,'${esc(memory.id)}')" role="dialog" aria-modal="true" aria-labelledby="memory-edit-title"><button type="button" class="modal-close" onclick="closeModal()" aria-label="${t('common.close')}">×</button><div class="eyebrow">${t('timeline.detail')}</div><h2 id="memory-edit-title">${t('memory.edit')}</h2><p class="sub">${uiDate(memory.date)}</p><label>${t('memory.note')}<textarea id="memory-edit-note" rows="5">${esc(memory.text || '')}</textarea></label>${memoryMetaFields(memory)}<div class="actions"><button class="primary" type="submit">${t('common.save')}</button><button class="secondary" type="button" onclick="closeModal()">${t('common.cancel')}</button></div></form></div>`; };
window.saveMemoryEdit = (event,id) => { event.preventDefault(); const all=mems(), memory=all.find(item=>item.id===id); if(!memory)return; 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 = `<div class="modal"><div class="modalbox"><div class="eyebrow">${t('timeline.confirmDelete')}</div><h2>${t('timeline.remove')}</h2><p class="sub">${t('timeline.irreversible')}</p><div class="actions"><button class="danger" onclick="deleteMemory('${id}')">${t('timeline.deleteMemory')}</button><button class="secondary" onclick="modal.innerHTML=''">${t('common.cancel')}</button></div></div></div>`; };
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')); };
@@ -55,7 +69,7 @@
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(), 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 = `<div class="eyebrow">${t('system.eyebrow')}</div><h1>${t('system.title')}</h1><div class="settings"><section><div class="eyebrow">${t('system.appearance')}</div><div class="choices">${['dark','light','auto'].map(item => `<button class="chip ${state.theme === item ? 'selected' : ''}" onclick="theme('${item}')">${t(`system.theme.${item}`)}</button>`).join('')}</div></section><section><div class="eyebrow">${t('system.language')}</div><p class="sub">${detected}</p><div class="choices"><button class="chip ${state.languageMode !== 'manual' ? 'selected' : ''}" onclick="setLanguage('auto')">${t('system.auto')}</button><button class="chip ${state.languageMode === 'manual' && state.language === 'en' ? 'selected' : ''}" onclick="setLanguage('en')">ENGLISH</button><button class="chip ${state.languageMode === 'manual' && state.language === 'zh' ? 'selected' : ''}" onclick="setLanguage('zh')">中文</button></div></section><section><div class="eyebrow">${t('system.data')}</div><div class="actions"><button class="primary" onclick="exportData()">${t('system.export')}</button><button class="secondary" onclick="document.querySelector('#importer').click()">${t('system.import')}</button><button class="danger" onclick="resetConfirm()">${t('system.reset')}</button><input id="importer" class="hidden" type="file" accept=".json" onchange="readImport(this)"></div></section><section><div class="eyebrow">${t('system.storage')}</div><p>${t('system.local')}<br>${lang() === 'zh' ? `${mems().length} 条记忆 · ${museum().length} 件博物馆藏品 · ${sys().tasksBoard?.tasks?.length || 0} 项待办 · 约 ${Math.ceil(bytes/1024)} KB` : `${mems().length} memories · ${museum().length} museum items · ${sys().tasksBoard?.tasks?.length || 0} tasks · Approx. ${Math.ceil(bytes/1024)} KB`}</p></section><section><div class="eyebrow">${t('system.dataStorage')}</div><p>${t('system.stored')}<br>${t('system.backups')}</p><p>${t('system.lastBackup',{value:state.lastBackup ? short(state.lastBackup.slice(0,10)) : t('common.never')})}${oldBackup ? `<br><b style="color:var(--accent)">${t('system.recommended')}</b>` : ''}</p></section><section><div class="eyebrow">${t('system.version')}</div><p>Life OS<br>Version 1.3.0 · Schema v1</p></section></div>`;
container.innerHTML = `<div class="eyebrow">${t('system.eyebrow')}</div><h1>${t('system.title')}</h1><div class="settings"><section><div class="eyebrow">${t('system.appearance')}</div><div class="choices">${['dark','light','auto'].map(item => `<button class="chip ${state.theme === item ? 'selected' : ''}" onclick="theme('${item}')">${t(`system.theme.${item}`)}</button>`).join('')}</div></section><section><div class="eyebrow">${t('system.language')}</div><p class="sub">${detected}</p><div class="choices"><button class="chip ${state.languageMode !== 'manual' ? 'selected' : ''}" onclick="setLanguage('auto')">${t('system.auto')}</button><button class="chip ${state.languageMode === 'manual' && state.language === 'en' ? 'selected' : ''}" onclick="setLanguage('en')">ENGLISH</button><button class="chip ${state.languageMode === 'manual' && state.language === 'zh' ? 'selected' : ''}" onclick="setLanguage('zh')">中文</button></div></section><section><div class="eyebrow">${t('system.data')}</div><div class="actions"><button class="primary" onclick="exportData()">${t('system.export')}</button><button class="secondary" onclick="document.querySelector('#importer').click()">${t('system.import')}</button><button class="danger" onclick="resetConfirm()">${t('system.reset')}</button><input id="importer" class="hidden" type="file" accept=".json" onchange="readImport(this)"></div></section><section><div class="eyebrow">${t('system.storage')}</div><p>${t('system.local')}<br>${lang() === 'zh' ? `${mems().length} 条记忆 · ${museum().length} 件博物馆藏品 · ${sys().tasksBoard?.tasks?.length || 0} 项待办 · 约 ${Math.ceil(bytes/1024)} KB` : `${mems().length} memories · ${museum().length} museum items · ${sys().tasksBoard?.tasks?.length || 0} tasks · Approx. ${Math.ceil(bytes/1024)} KB`}</p></section><section><div class="eyebrow">${t('system.dataStorage')}</div><p>${t('system.stored')}<br>${t('system.backups')}</p><p>${t('system.lastBackup',{value:state.lastBackup ? short(state.lastBackup.slice(0,10)) : t('common.never')})}${oldBackup ? `<br><b style="color:var(--accent)">${t('system.recommended')}</b>` : ''}</p></section><section><div class="eyebrow">${t('system.version')}</div><p>Life OS<br>Version 1.4.0 · Schema v1</p></section></div>`;
};
window.importPreview = () => { const data = importData; rootModal().innerHTML = `<div class="modal"><div class="modalbox"><div class="eyebrow">${t('dialog.backup')}</div><h2>${t('dialog.ready')}</h2><p class="sub">${t('dialog.exported',{date:short(data.exportedAt.slice(0,10))})}<br>${t('dialog.memories',{count:data.memories.length})}<br>${t('dialog.museumItems',{count:data.museum.length})}</p><div class="actions"><button class="primary" onclick="applyImport('merge')">${t('dialog.merge')}</button><button class="danger" onclick="replaceConfirm()">${t('dialog.replaceAll')}</button><button class="secondary" onclick="modal.innerHTML=''">${t('common.cancel')}</button></div></div></div>`; };
window.replaceConfirm = () => { rootModal().innerHTML = `<div class="modal"><div class="modalbox"><div class="eyebrow">${t('dialog.final')}</div><h2>${t('dialog.replaceQuestion')}</h2><p class="sub">${t('dialog.replaceWarning')}</p><div class="actions"><button class="danger" onclick="applyImport('replace')">${t('dialog.replace')}</button><button class="secondary" onclick="importPreview()">${t('common.cancel')}</button></div></div></div>`; };
@@ -80,6 +94,9 @@
window.resetConfirm = () => { rootModal().innerHTML = `<div class="modal"><div class="modalbox"><div class="eyebrow">${t('system.reset')}</div><h2>${t('dialog.resetQuestion')}</h2><p class="sub">${t('dialog.resetWarning')}</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()">${t('dialog.resetData')}</button><button class="secondary" onclick="modal.innerHTML=''">${t('common.cancel')}</button></div></div></div>`; };
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-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 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 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{font-size:13px}}`;
document.head.append(memoryStyle);
// Render every surface again after the overrides are installed; no DOM walking is involved.
renderIcons(); renderAll(); applyLanguage();
})();