let DATA = null; let currentFilter = 'all'; let currentSearch = ''; async function load() { const res = await fetch('data.json?_=' + Date.now()); DATA = await res.json(); renderStats(); render(); document.getElementById('gen').textContent = '生成于 ' + DATA.generated_at; } function renderStats() { const t = DATA.templates; const fig = t.filter(x => x.has_fig).length; const sk = t.filter(x => x.has_sketch).length; const xd = t.filter(x => x.has_xd).length; const psd = t.filter(x => x.has_psd).length; const imp = t.filter(x => x.figma_key).length; const el = document.getElementById('stats'); el.innerHTML = `
${t.length}套总量
${fig}Figma 原生
${sk}含 Sketch
${xd}含 XD
${psd}含 PSD
${imp}已入 Figma Drafts
`; const banner = document.getElementById('banner'); if (DATA.imported_summary) { banner.innerHTML = DATA.imported_summary; banner.classList.add('show'); } } function matchFilter(t) { if (currentFilter === 'all') return true; if (currentFilter === 'fig') return t.has_fig; if (currentFilter === 'sketch') return t.has_sketch; if (currentFilter === 'xd') return t.has_xd; if (currentFilter === 'psd') return t.has_psd; if (currentFilter === 'imported') return !!t.figma_key; return true; } function matchSearch(t) { if (!currentSearch) return true; return t.name.toLowerCase().includes(currentSearch) || t.id.toLowerCase().includes(currentSearch); } function render() { const grid = document.getElementById('grid'); const empty = document.getElementById('empty'); const list = DATA.templates.filter(t => matchFilter(t) && matchSearch(t)); if (list.length === 0) { grid.innerHTML = ''; empty.hidden = false; return; } empty.hidden = true; grid.innerHTML = list.map(t => `
${t.cover ? `${escapeHtml(t.name)}` : ''} ${t.id} ${t.figma_key ? '已入 Figma' : ''}

${escapeHtml(t.name)}

${t.has_fig ? `FIG${t.fig_count>1?'×'+t.fig_count:''}` : ''} ${t.has_sketch ? `SKETCH${t.sketch_count>1?'×'+t.sketch_count:''}` : ''} ${t.has_xd ? `XD${t.xd_count>1?'×'+t.xd_count:''}` : ''} ${t.has_psd ? `PSD` : ''}
${t.archive_size_mb} MB
`).join(''); grid.querySelectorAll('.card').forEach(c => { c.addEventListener('click', () => openModal(c.dataset.id)); }); } function openModal(id) { const t = DATA.templates.find(x => x.id === id); if (!t) return; const body = document.getElementById('modalBody'); const variants = t.figma_variants || (t.figma_key ? [{name: t.name, key: t.figma_key, url: t.figma_url}] : []); const hasMulti = variants.length > 1; const tabsHtml = hasMulti ? `
${variants.map((v, i) => ``).join('')}
` : ''; const firstVariant = variants[0]; const iframeHtml = firstVariant ? `` : ''; const openBtn = firstVariant ? `在 Figma 打开 →` : (t.has_fig ? `在 Figma Drafts 搜 "${escapeAttr(t.name.split(/[\s\-–—]/)[0])}" →` : ''); body.innerHTML = `

${escapeHtml(t.name)}${hasMulti ? ` ${variants.length} 变体` : ''}

${t.id} ${t.archive_size_mb} MB ${t.has_fig ? `FIG×${t.fig_count}` : ''} ${t.has_sketch ? `SKETCH×${t.sketch_count}` : ''} ${t.has_xd ? `XD×${t.xd_count}` : ''} ${t.has_psd ? `PSD` : ''}
${openBtn} 在 Finder 中显示源包 复制源文件路径
${tabsHtml} ${iframeHtml}
源包
source/${t.id}/${t.archive}
解压目录
extracted/${t.id}/
${firstVariant ? `
Figma Key
${firstVariant.key}
` : ''}
`; if (hasMulti) { document.getElementById('variantTabs').addEventListener('click', e => { const btn = e.target.closest('.vtab'); if (!btn) return; const idx = +btn.dataset.idx; const v = variants[idx]; document.querySelectorAll('.vtab').forEach(b => b.classList.toggle('active', b === btn)); document.getElementById('variantFrame').src = `https://embed.figma.com/design/${v.key}/?embed-host=kang&footer=false`; document.getElementById('variantOpenBtn').href = v.url; document.getElementById('variantKey').textContent = v.key; }); } document.getElementById('modal').hidden = false; document.body.style.overflow = 'hidden'; } function closeModal() { document.getElementById('modal').hidden = true; document.body.style.overflow = ''; document.getElementById('modalBody').innerHTML = ''; } function revealSource(id) { // Dev-only helper: try opening local path in Finder via file:// // Browsers block file:// from http:// — fall back to a helpful alert const path = `${location.pathname.replace(/\/[^/]*$/, '')}/../extracted/${id}`; alert(`本地路径:\n~/Projects/research/20260422-figma模板库/extracted/${id}/\n\n在终端跑:\nopen ~/Projects/research/20260422-figma模板库/extracted/${id}`); } function escapeHtml(s) { return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); } function escapeAttr(s) { return String(s).replace(/"/g, '"'); } function copyPath(id) { const t = DATA.templates.find(x => x.id === id); if (!t) return; const p = `~/Projects/research/20260422-figma模板库/extracted/${id}/`; navigator.clipboard.writeText(p).then(() => { alert('已复制:\n' + p); }); } document.getElementById('filters').addEventListener('click', e => { const btn = e.target.closest('.pill'); if (!btn) return; document.querySelectorAll('.pill').forEach(p => p.classList.toggle('active', p === btn)); currentFilter = btn.dataset.filter; render(); }); document.getElementById('search').addEventListener('input', e => { currentSearch = e.target.value.trim().toLowerCase(); render(); }); document.getElementById('modalClose').addEventListener('click', closeModal); document.querySelector('.modal-bg').addEventListener('click', closeModal); document.addEventListener('keydown', e => { if (e.key === 'Escape') closeModal(); }); load();