// 首页三栏卡片:拉取目录 -> cards.layout 分配三栏 -> 渲染(time 卡大字 + 其他占位)
// Home three-column cards: fetch catalog -> cards.layout assigns 3 columns -> render
// (big time card + generic placeholders for the rest)
window.updateHomeCards = async function () {
const page = document.querySelector('[data-page="home"]');
page.innerHTML = `
`;
let catalog = [];
try {
catalog = JSON.parse(await bridge.call('fetchCards'));
} catch (e) {
// 桥接不可用(如未接线)时按空目录处理
// Treat bridge failure as an empty catalog
catalog = [];
}
if (!Array.isArray(catalog)) catalog = [];
// 兜底:无目录或缺失 time 卡时补内置大字时间卡,保证首页始终有时间卡
// Fallback: prepend builtin big time card when the catalog is empty or missing the time card
if (!catalog.some((c) => c.id === 'time')) {
catalog.push({ id: 'time', name: '大字时间', priority: 0 });
}
const cols = cards.layout(catalog);
cols.forEach((ids, i) => {
const col = document.getElementById(`col-${i}`);
ids.forEach((id) => {
if (id === 'time') col.appendChild(renderTimeCard());
else col.appendChild(renderGenericCard(id));
});
});
startTimeCardTicker();
};
// 内置大字时间卡:当前时间(时:分 + 小号秒)+ 日期
// Builtin big time card: current time (HH:MM + small seconds) + date
function renderTimeCard() {
const el = document.createElement('div');
el.className = 'card time-card';
const now = new Date();
el.innerHTML = `${fmtTime(now)}${fmtSec(now)}
${fmtDate(now)}
`;
return el;
}
// 其他卡片占位:仅显示 id,后续 Task 接具体卡片渲染
// Other cards placeholder: show id only; concrete renderers land in later tasks
function renderGenericCard(id) {
const el = document.createElement('div');
el.className = 'card';
el.textContent = id;
return el;
}
function fmtTime(d) {
const hh = String(d.getHours()).padStart(2, '0');
const mm = String(d.getMinutes()).padStart(2, '0');
return `${hh}:${mm}`;
}
function fmtSec(d) {
return `:${String(d.getSeconds()).padStart(2, '0')}`;
}
function fmtDate(d) {
const days = ['日', '一', '二', '三', '四', '五', '六'];
return `${d.getMonth() + 1}月${d.getDate()}日 周${days[d.getDay()]}`;
}
// 每秒刷新一次大字时间卡(含秒显)
// Refresh the big time card every second (including the seconds display)
let timeTicker = null;
function startTimeCardTicker() {
if (timeTicker) return;
timeTicker = setInterval(() => {
const card = document.querySelector('.time-card');
if (!card) return;
const now = new Date();
card.querySelector('.hm').textContent = fmtTime(now);
card.querySelector('.sec').textContent = fmtSec(now);
card.querySelector('.sub').textContent = fmtDate(now);
}, 1000);
}
// 原生媒体会话事件:有媒体时在中间栏渲染媒体卡,无媒体时降级重新布局
// Native media session event: render media card in middle column when present,
// otherwise fall back to re-layout (calendar/weather fill the media card slot)
window.HearthEvents = window.HearthEvents || {};
window.HearthEvents.mediaSessionChanged = function (info) {
const mediaCol = document.getElementById('col-1');
if (!mediaCol) return;
if (info) {
mediaCol.innerHTML = '';
mediaCol.appendChild(renderMediaCard(info));
} else {
// 无媒体:降级到日历/天气(由 cards.layout 重新计算)
// No media: fall back to calendar/weather (recomputed by cards.layout)
window.updateHomeCards();
}
};
// 媒体卡:正在播放标签 + 标题/艺术家 + 进度条
// Media card: playing caption + title/artist + progress bar
function renderMediaCard(info) {
const el = document.createElement('div');
el.className = 'card media-card';
const pct = info.duration ? (info.position / info.duration) * 100 : 0;
// title/artist 来自任意应用元数据,转义后插入,防 XSS
// title/artist come from arbitrary app metadata; escape before insert (XSS)
el.innerHTML = `正在播放
${escapeHtml(info.title)}
${escapeHtml(info.artist)}
`;
return el;
}
// 转义 HTML 特殊字符,避免媒体元数据注入标签
// Escape HTML special chars to prevent metadata injection
function escapeHtml(s) {
return String(s == null ? '' : s)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}