${svg(app.icon)}
${escapeHtml(app.description)}
${escapeHtml(categoryName)}${app.environment === 'test' ? '测试' : '正式'}
${escapeHtml(new URL(app.url).hostname)}${svg('arrow')}
`;
}).join('');
document.querySelector('#empty-state').hidden = matches.length > 0;
const emptyFavorites = state.category === 'favorites' && state.favorites.length === 0;
document.querySelector('#empty-title').textContent = emptyFavorites ? '把常用应用放在这里' : '没有找到匹配的应用';
document.querySelector('#empty-description').textContent = emptyFavorites ? '点击应用卡片右上角的星标,即可加入我的常用。' : '试试其他关键词,或调整筛选条件。';
}
nav.addEventListener('click', event => {
const button = event.target.closest('[data-category]');
if (!button) return;
state.category = button.dataset.category;
render();
});
filters.addEventListener('click', event => {
const button = event.target.closest('[data-environment]');
if (!button) return;
state.environment = button.dataset.environment;
render();
});
search.addEventListener('input', () => { state.query = search.value; render(); });
grid.addEventListener('click', event => {
const button = event.target.closest('[data-favorite]');
if (!button) return;
const id = button.dataset.favorite;
const adding = !state.favorites.includes(id);
state.favorites = adding ? [...state.favorites, id] : state.favorites.filter(value => value !== id);
let persisted = true;
try { localStorage.setItem(storageKey, JSON.stringify(state.favorites)); } catch { persisted = false; }
render();
grid.querySelector(`[data-favorite="${id}"]`)?.focus({ preventScroll: true });
const toast = document.querySelector('#toast');
toast.textContent = `${adding ? '已加入我的常用' : '已取消常用'}${persisted ? '' : '(当前浏览器无法保存,仅本次有效)'}`;
toast.classList.add('visible');
clearTimeout(toastTimer);
toastTimer = setTimeout(() => toast.classList.remove('visible'), 2400);
});
document.querySelector('#reset-filters').addEventListener('click', () => {
state.category = 'all'; state.environment = 'all'; state.query = ''; search.value = ''; render();
});
document.addEventListener('keydown', event => {
if (event.key === '/' && !event.ctrlKey && !event.metaKey && !event.altKey && !event.target.closest('input,textarea,[contenteditable="true"]')) {
event.preventDefault(); search.focus();
}
if (event.key === 'Escape' && document.activeElement === search) {
search.value = ''; state.query = ''; render(); search.blur();
}
});
window.addEventListener('storage', event => {
if (event.key === storageKey || event.key === null) { state.favorites = readFavorites(); render(); }
});
render();