feat(insights): add factual weekly and monthly reports
This commit is contained in:
1 parent
0d673a3b5a
commit
2513f7fb48
9 files changed
+138
-29
No files matched your search
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "life-os",
|
"name": "life-os",
|
||||||
"version": "1.5.8",
|
"version": "1.6.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
const DRAFT_KEY = 'lifeos_today_draft_v1';
|
const DRAFT_KEY = 'lifeos_today_draft_v1';
|
||||||
const localDay = () => { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; };
|
const localDay = () => { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; };
|
||||||
const escapeHtml = value => String(value || '').replace(/[&<>]/g, char => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[char]));
|
const escapeHtml = value => String(value || '').replace(/[&<>]/g, char => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[char]));
|
||||||
const APP_VERSION = '1.5.8';
|
const APP_VERSION = '1.6.0';
|
||||||
window.LifeOSVersion = APP_VERSION;
|
window.LifeOSVersion = APP_VERSION;
|
||||||
const appIds = ['today', 'timeline', 'museum', 'insights', 'profile', 'tasks', 'calendar', 'system'];
|
const appIds = ['today', 'timeline', 'museum', 'insights', 'profile', 'tasks', 'calendar', 'system'];
|
||||||
const t = (key, values) => LifeI18n.t(key, values);
|
const t = (key, values) => LifeI18n.t(key, values);
|
||||||
|
|||||||
@@ -61,10 +61,47 @@
|
|||||||
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.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.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')); };
|
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 => {
|
window.insights = container => {
|
||||||
const all = mems(), recent = all.filter(item => Date.now() - new Date(item.date + 'T12:00:00') < 2592e6), counts = { clear: 0, light: 0, cloudy: 0, rain: 0, storm: 0 }; recent.forEach(item => counts[item.mood]++);
|
if (!container) return;
|
||||||
const average = all.length ? (all.reduce((sum, item) => sum + item.energy, 0) / all.length).toFixed(1) : '—', dayCounts = {}; all.forEach(item => { const name = weekday(item.date); dayCounts[name] = (dayCounts[name] || 0) + 1; }); const active = Object.entries(dayCounts).sort((a,b) => b[1] - a[1])[0]?.[0] || '—';
|
const period = reportPeriod(), inPeriod = key => key && key >= period.start && key < period.end && key <= period.today;
|
||||||
container.innerHTML = `<div class="eyebrow">${t('insights.eyebrow')}</div><h1>${t('insights.title')}</h1><div class="insights"><section class="metric"><div class="eyebrow">${t('insights.weather')}</div>${Object.entries(counts).map(([key, value]) => `<div class="sub">${weather(key)} <b style="float:right;color:var(--ink)">${value} ${t('common.days')}</b></div>`).join('')}</section><section class="metric"><div class="eyebrow">${t('insights.average')}</div><div class="big">${average}<span style="font-size:16px"> / 5</span></div><div class="bars">${all.slice(0,10).reverse().map(item => `<i class="bar" style="height:${item.energy * 18}%"></i>`).join('') || `<span class="sub">${t('insights.noEntries')}</span>`}</div></section><section class="metric"><div class="eyebrow">${t('insights.active')}</div><div class="big" style="font-size:26px">${active}</div></section><section class="metric"><div class="eyebrow">${t('insights.current')}</div><div class="big">${streak(all)}<span style="font-size:16px"> ${t('common.days')}</span></div><div class="sub">${t('insights.longest',{count:longest(all)})}</div></section></div>`;
|
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 = `<div class="eyebrow">${t('insights.eyebrow')}</div><h1>${t('insights.title')}</h1><p class="sub report-description">${t('insights.description')}</p>
|
||||||
|
<div class="report-controls"><div class="choices" role="group" aria-label="${t('insights.period')}">${['week', 'month'].map(view => `<button class="chip ${view === insightView ? 'selected' : ''}" aria-pressed="${view === insightView}" onclick="setInsightsView('${view}')">${t(`insights.${view}`)}</button>`).join('')}</div><div class="report-range"><button class="report-step" aria-label="${t('insights.previous')}" onclick="shiftInsights(-1)">‹</button><strong>${range}</strong><button class="report-step" aria-label="${t('insights.next')}" onclick="shiftInsights(1)" ${insightOffset === 0 ? 'disabled' : ''}>›</button></div></div>
|
||||||
|
<div class="report-grid"><section class="report-card"><div class="eyebrow">${t('insights.records')}</div><div class="report-big">${count(daysRecorded)} <span>/ ${count(period.elapsed)}</span></div><p>${t('insights.recordedDays', { count: count(records.length), percent: count(period.elapsed ? Math.round(daysRecorded / period.elapsed * 100) : 0) })}</p></section>
|
||||||
|
<section class="report-card"><div class="eyebrow">${t('insights.tasks')}</div><div class="report-big">${count(tasksCompleted)}</div><p>${t('insights.completedTasks')}</p><div class="report-secondary">${t('insights.taskDetails', { created: count(tasksCreated), goals: count(goalsCompleted) })}</div></section>
|
||||||
|
<section class="report-card"><div class="eyebrow">${t('insights.weather')}</div>${commonWeather.length ? `<div class="report-weather">${commonWeather.map(([key, value]) => `<div><span>${weather(key)}</span><strong>${count(value)}</strong></div>`).join('')}</div>` : `<p>${t('insights.noWeather')}</p>`}</section>
|
||||||
|
<section class="report-card"><div class="eyebrow">${t('insights.energy')}</div><div class="report-big">${energyAverage} <span>/ 5</span></div><p>${energies.length > 1 ? t('insights.energyFromTo', { first: energies[0].value, last: energies.at(-1).value }) : energies.length ? t('insights.singleEnergy') : t('insights.noEnergy')}</p>${energies.length ? `<div class="report-energy-bars" aria-label="${t('insights.energy')}">${energies.map(item => `<i style="height:${item.value * 20}%" title="${esc(item.date)} · ${item.value}/5"></i>`).join('')}</div>` : ''}</section></div>
|
||||||
|
<section class="report-highlights"><div class="eyebrow">${t('insights.highlights')}</div><p class="sub">${t('insights.highlightSource')}</p>${highlights.length ? `<div class="report-highlight-list">${highlights.map(item => `<button class="report-highlight" data-id="${escAttr(String(item.id))}" onclick="detail(this.dataset.id)"><small>${short(item.date)} · ${favorites.has(item.id) ? t('insights.highlightFavorite') : t('insights.highlightTag')}</small><strong>${esc(titleOf(item))}</strong>${item.text ? `<span>${esc(item.text.trim().slice(0, 140))}</span>` : ''}</button>`).join('')}</div>` : `<div class="report-empty">${t('insights.highlightEmpty')}</div>`}</section>
|
||||||
|
<p class="report-note">${t('insights.note')}</p>`;
|
||||||
};
|
};
|
||||||
window.profileApp = container => {
|
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));
|
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));
|
||||||
@@ -73,7 +110,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.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 => {
|
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');
|
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 = `<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 ${preference === 'auto' ? 'selected' : ''}" onclick="window.setLanguage('auto')">${t('system.auto')}</button><button class="chip ${preference === 'en' ? 'selected' : ''}" onclick="window.setLanguage('en')">${t('system.languageEnglish')}</button><button class="chip ${preference === 'zh' ? 'selected' : ''}" onclick="window.setLanguage('zh')">${t('system.languageChinese')}</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>${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))})}</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>${t('system.release',{version:window.LifeOSVersion||'1.5.8'})}</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 ${preference === 'auto' ? 'selected' : ''}" onclick="window.setLanguage('auto')">${t('system.auto')}</button><button class="chip ${preference === 'en' ? 'selected' : ''}" onclick="window.setLanguage('en')">${t('system.languageEnglish')}</button><button class="chip ${preference === 'zh' ? 'selected' : ''}" onclick="window.setLanguage('zh')">${t('system.languageChinese')}</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>${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))})}</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>${t('system.release',{version:window.LifeOSVersion||'1.6.0'})}</p></section></div>`;
|
||||||
};
|
};
|
||||||
window.importPreview = () => { const data = importData, board = data.system?.tasksBoard || {}; 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})}<br>${t('dialog.goals',{count:board.goals?.length || 0})}<br>${t('dialog.tasks',{count:board.tasks?.length || 0})}</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.importPreview = () => { const data = importData, board = data.system?.tasksBoard || {}; 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})}<br>${t('dialog.goals',{count:board.goals?.length || 0})}<br>${t('dialog.tasks',{count:board.tasks?.length || 0})}</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>`; };
|
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>`; };
|
||||||
@@ -101,6 +138,9 @@
|
|||||||
const memoryStyle = document.createElement('style');
|
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}}`;
|
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);
|
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.
|
// Render every surface again after the overrides are installed; no DOM walking is involved.
|
||||||
window.renderIcons(); window.renderAll(); window.applyLanguage();
|
window.renderIcons(); window.renderAll(); window.applyLanguage();
|
||||||
})();
|
})();
|
||||||
Loaded 3 of 9 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user