Files

329 lines
15 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/svg+xml" href="logo/favicon.svg">
<title>素材中心 · 画个标</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: "PingFang SC", "Microsoft YaHei", "Segoe UI", sans-serif;
font-size: 14px; color: #1f2937; background: #f3f4f6; min-height: 100vh;
}
a { color: #4f46e5; text-decoration: none; }
a:hover { text-decoration: underline; }
.hidden { display: none !important; }
/* ===== 顶栏 ===== */
.topbar {
display: flex; align-items: center; justify-content: space-between;
padding: 0 20px; height: 54px;
background: linear-gradient(90deg, #1e1b4b, #312e81);
color: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.15);
position: sticky; top: 0; z-index: 20;
}
.topbar .brand { display: flex; align-items: center; gap: 10px; }
.topbar .brand .icon { font-size: 20px; }
.topbar .brand .name { font-size: 17px; font-weight: 700; letter-spacing: 1px; }
.topbar .brand .tag {
font-size: 11px; color: #c7d2fe; background: rgba(255,255,255,.12);
padding: 2px 8px; border-radius: 20px;
}
.btn-back {
border: 1px solid rgba(255,255,255,.35); border-radius: 6px;
background: rgba(255,255,255,.1); color: #fff; padding: 6px 14px;
font-size: 13px; cursor: pointer; transition: background .15s;
}
.btn-back:hover { background: rgba(255,255,255,.22); }
.wrap { max-width: 1080px; margin: 22px auto 60px; padding: 0 16px; }
/* ===== 概览统计 ===== */
.stats {
display: flex; gap: 14px; flex-wrap: wrap; margin-bottom: 18px;
}
.stat-card {
flex: 1; min-width: 150px; background: #fff; border: 1px solid #e5e7eb;
border-radius: 10px; padding: 14px 18px; box-shadow: 0 1px 3px rgba(0,0,0,.06);
}
.stat-card .num { font-size: 26px; font-weight: 800; color: #4f46e5; }
.stat-card .lab { font-size: 12px; color: #6b7280; margin-top: 2px; }
/* ===== 指引 ===== */
.guide {
background: #eef2ff; border: 1px solid #c7d2fe; border-radius: 10px;
padding: 14px 18px; margin-bottom: 20px; font-size: 13px; line-height: 1.8;
}
.guide b { color: #4338ca; }
.guide code {
background: #e0e7ff; border-radius: 4px; padding: 1px 6px;
font-size: 12px; color: #3730a3;
}
.guide ol { margin: 6px 0 0 20px; }
/* ===== 工具栏(分类 Tab + 搜索) ===== */
.toolbar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 16px; }
.filters { display: flex; gap: 6px; flex-wrap: wrap; }
.fchip {
border: 1px solid #e5e7eb; background: #fff; color: #4b5563;
padding: 6px 14px; border-radius: 20px; font-size: 13px; cursor: pointer;
transition: all .15s;
}
.fchip:hover { border-color: #c7d2fe; color: #4338ca; }
.fchip.active { background: #4f46e5; border-color: #4f46e5; color: #fff; }
.search {
flex: 1; min-width: 200px; border: 1px solid #e5e7eb; border-radius: 8px;
padding: 8px 12px; font-size: 13px; color: #1f2937; outline: none;
}
.search:focus { border-color: #4f46e5; }
/* ===== 来源卡片 ===== */
.grid {
display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 14px;
}
.card {
background: #fff; border: 1px solid #e5e7eb; border-radius: 12px;
padding: 16px; box-shadow: 0 1px 3px rgba(0,0,0,.06);
display: flex; flex-direction: column; gap: 10px;
transition: box-shadow .15s, transform .15s;
}
.card:hover { box-shadow: 0 4px 14px rgba(79,70,229,.12); transform: translateY(-1px); }
.card-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.card-title { font-size: 15px; font-weight: 700; }
.card-en { font-size: 11px; color: #6b7280; }
.tag {
font-size: 11px; padding: 2px 8px; border-radius: 20px; white-space: nowrap;
}
.tag.line { background: #dbeafe; color: #1d4ed8; }
.tag.fill { background: #fee2e2; color: #b91c1c; }
.tag.mix { background: #fef3c7; color: #b45309; }
.tag.brand{ background: #e0e7ff; color: #4338ca; }
.card-desc { font-size: 12px; color: #4b5563; line-height: 1.6; min-height: 34px; }
.samples {
display: flex; gap: 6px; background: #f9fafb; border: 1px solid #f3f4f6;
border-radius: 8px; padding: 8px 10px; min-height: 44px; align-items: center;
}
.samples svg { width: 24px; height: 24px; flex-shrink: 0; }
.card-meta { display: grid; grid-template-columns: 64px 1fr; gap: 4px 8px; font-size: 12px; }
.card-meta dt { color: #6b7280; }
.card-meta dd { color: #1f2937; word-break: break-all; }
.license-cc { color: #b45309; font-weight: 600; }
.card-foot {
margin-top: auto; padding-top: 8px; border-top: 1px dashed #e5e7eb;
display: flex; justify-content: space-between; align-items: center; font-size: 11px; color: #9ca3af;
}
.foot-links { display: flex; gap: 10px; }
.foot-links a { color: #4f46e5; }
.empty { text-align: center; color: #6b7280; padding: 60px 0; }
</style>
</head>
<body>
<div class="topbar">
<div class="brand">
<img class="icon" src="logo/logo-mark.svg" alt="画个标" width="20" height="20">
<span class="name">画个标</span>
<span class="tag">素材中心</span>
</div>
<button class="btn-back" onclick="location.href='materials.html'">🗃 素材库</button>
<button class="btn-back" onclick="location.href='index.html'">← 返回设计工作台</button>
</div>
<div class="wrap">
<div class="stats">
<div class="stat-card"><div class="num" id="st-sources">-</div><div class="lab">素材来源(个)</div></div>
<div class="stat-card"><div class="num" id="st-icons">-</div><div class="lab">内置图标总数(个)</div></div>
<div class="stat-card"><div class="num" id="st-cats">-</div><div class="lab">素材分类(类)</div></div>
<div class="stat-card"><div class="num" id="st-time">-</div><div class="lab">最后构建时间</div></div>
</div>
<div class="guide">
<b>📦 素材中心说明</b>
<div>所有图标 / 表情 / 品牌 Logo 素材均已<b>离线内置</b>(构建时一次性下载),页面运行无需联网;按<b>单色图标 · 多色表情 · 品牌标识</b>三大类分类管理,可按关键词检索<b>中文名 / 英文名 / 描述</b>,来源、协议与构建信息清晰可查。</div>
<div style="margin-top:6px; color:#3730a3;">协议以各来源官方为准,商用前请核对(CC BY 4.0 需署名)。如何补充新源见下方原指引。</div>
</div>
<div class="toolbar">
<div class="filters" id="filters"></div>
<input class="search" id="search" placeholder="🔍 搜索素材(中文名 / 英文名 / 描述)">
</div>
<div class="grid" id="grid"></div>
<div class="empty hidden" id="empty">加载图标来源数据失败,请确认 js/data/icon-sources.js 已加载。</div>
</div>
<script src="js/data/icon-sources.js?v=20260823g"></script>
<script src="js/data/builtin-icons.js?v=20260823g"></script>
<script>
(function () {
'use strict';
// 优先读机器可读 manifest(js/data/manifest/icons.json,构建时生成);失败回退内置配置
var SOURCES = [];
var BUILD_AT = null;
var FALLBACK = window.HGB_IconSources || [];
// builtin-icons.js 用 const 声明(全局词法绑定,非 window 属性),用 typeof 探测
var BUILTIN = (typeof HGB_BuiltinIcons !== 'undefined') ? HGB_BuiltinIcons : null;
function resolveSources(cb) {
fetch('js/data/manifest/icons.json?v=20260823g')
.then(function (r) { return r.json(); })
.then(function (m) {
SOURCES = m.sources || [];
BUILD_AT = m.builtAt || null;
cb();
})
.catch(function () { SOURCES = FALLBACK; cb(); });
}
/* 描边型判断(与 panels.js isStrokeIcon 一致) */
var STROKE_SOURCES = new Set(['feather', 'lucide', 'tabler', 'iconoir']);
function isStroke(ic, sid) {
return STROKE_SOURCES.has(sid) ||
/stroke\s*=|\bstroke-linecap\s*=|\bstroke-linejoin\s*=/.test(ic && ic.svg || '');
}
function iconAttrs(ic, sid) {
// 多色素材(emoji/品牌)保留原色,不强制单色
if (ic.type && ic.type !== 'icon') return 'fill="inherit" stroke="inherit"';
return isStroke(ic, sid)
? 'fill="none" stroke="#4f46e5" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"'
: 'fill="#4f46e5" stroke="none"';
}
function bodyOf(ic) {
return ic.svg
.replace(/fill="currentColor"/g, 'fill="inherit"')
.replace(/stroke="currentColor"/g, 'stroke="inherit"');
}
function esc(s) {
return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;')
.replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
var STYLE_TAG = { '线条': 'line', '填充': 'fill', '混合': 'mix', '品牌': 'brand', '多色': 'mix' };
var TYPE_CAT = { icon: '单色图标', emoji: '多色表情', brand: '品牌标识' };
var TYPE_EMOJI = { icon: '🔹', emoji: '🌈', brand: '🏷️' };
var filter = 'all';
var keyword = '';
function renderStats() {
document.getElementById('st-sources').textContent = SOURCES.length;
document.getElementById('st-cats').textContent = Object.keys(TYPE_CAT).length;
var total = 0;
SOURCES.forEach(function (s) {
var count = (typeof s.count === 'number') ? s.count
: (BUILTIN && BUILTIN.sources && BUILTIN.sources[s.id]) ? BUILTIN.sources[s.id].count : 0;
total += (typeof count === 'number' ? count : 0);
});
document.getElementById('st-icons').textContent = total.toLocaleString();
var meta = BUILTIN && BUILTIN.meta;
var ts = BUILD_AT || (meta && meta.generatedAt);
if (ts) {
var d = new Date(ts);
document.getElementById('st-time').textContent =
d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' +
String(d.getDate()).padStart(2, '0') + ' ' + String(d.getHours()).padStart(2, '0') + ':' +
String(d.getMinutes()).padStart(2, '0');
} else {
document.getElementById('st-time').textContent = '—';
}
}
function renderFilters() {
var countByType = {};
SOURCES.forEach(function (s) { countByType[s.type] = (countByType[s.type] || 0) + 1; });
var cats = [{ id: 'all', name: '全部 ' + SOURCES.length }]
.concat(Object.keys(TYPE_CAT).map(function (t) {
return { id: t, name: (TYPE_EMOJI[t] || '') + ' ' + TYPE_CAT[t] + ' ' + (countByType[t] || 0) };
}));
document.getElementById('filters').innerHTML = cats.map(function (c) {
return '<button class="fchip ' + (filter === c.id ? 'active' : '') + '" data-f="' + esc(c.id) + '">' + esc(c.name) + '</button>';
}).join('');
document.querySelectorAll('#filters [data-f]').forEach(function (b) {
b.addEventListener('click', function () { filter = b.dataset.f; renderFilters(); render(); });
});
}
function render() {
if (!SOURCES.length) {
document.getElementById('empty').classList.remove('hidden');
return;
}
var kw = keyword.trim().toLowerCase();
var list = SOURCES.filter(function (s) {
if (filter !== 'all' && s.type !== filter) return false;
if (!kw) return true;
return (s.nameCn || '').toLowerCase().includes(kw) ||
(s.name || '').toLowerCase().includes(kw) ||
(s.desc || '').toLowerCase().includes(kw) ||
(s.id || '').toLowerCase().includes(kw);
});
var grid = document.getElementById('grid');
if (!list.length) {
grid.innerHTML = '';
document.getElementById('empty').classList.remove('hidden');
document.getElementById('empty').textContent = '未找到匹配素材,请调整分类或搜索词。';
return;
}
document.getElementById('empty').classList.add('hidden');
grid.innerHTML = list.map(function (s) {
var srcData = BUILTIN && BUILTIN.sources && BUILTIN.sources[s.id];
var count = (typeof s.count === 'number') ? s.count : (srcData ? srcData.count : '—');
var tagCls = STYLE_TAG[s.style] || 'line';
// 取前 4 个图标做示例(懒加载拆分后索引只存 samples body,包装为 {svg,type} 复用渲染逻辑)
var samples = (srcData && srcData.samples
? srcData.samples.map(function (b) { return { svg: b, type: srcData.type }; })
: []);
var samplesHtml = samples.length
? samples.map(function (ic) {
return '<svg viewBox="0 0 24 24" ' + iconAttrs(ic, s.id) + '>' + bodyOf(ic) + '</svg>';
}).join('')
: '<span style="font-size:11px;color:#9ca3af">构建后显示示例</span>';
var npm = s.npm || '—';
var licenseCls = /CC BY/i.test(s.license) ? 'license-cc' : '';
return '' +
'<div class="card">' +
'<div class="card-head">' +
'<span class="card-title">' + esc(s.nameCn || s.name) + '</span>' +
'<span class="card-en">' + esc(s.name) + '</span>' +
'<span class="tag ' + tagCls + '">' + esc(TYPE_CAT[s.type] || s.style || '') + '</span>' +
'</div>' +
'<div class="card-desc">' + esc(s.desc || '') + '</div>' +
'<div class="samples">' + samplesHtml + '</div>' +
'<dl class="card-meta">' +
'<dt>协议</dt><dd><span class="' + licenseCls + '">' + esc(s.license || '—') + '</span> ' + esc(s.licenseNote || '') + '</dd>' +
'<dt>图标数</dt><dd>' + count + '</dd>' +
'<dt>坐标系</dt><dd>' + esc(s.coordText || '—') + '</dd>' +
'<dt>变体</dt><dd>' + esc(s.variants || '—') + '</dd>' +
'<dt>npm 包</dt><dd>' + esc(npm) + '</dd>' +
'<dt>构建方式</dt><dd>' + esc(s.format || '—') + '</dd>' +
'</dl>' +
'<div class="card-foot">' +
'<span>收录于 ' + esc(s.added || '—') + '</span>' +
'<span class="foot-links">' +
(s.home ? '<a href="' + esc(s.home) + '" target="_blank" rel="noopener">官网</a>' : '') +
(s.github ? '<a href="' + esc(s.github) + '" target="_blank" rel="noopener">GitHub</a>' : '') +
'</span>' +
'</div>' +
'</div>';
}).join('');
}
function init() {
document.getElementById('search').addEventListener('input', function (e) {
keyword = e.target.value;
render();
});
renderStats();
renderFilters();
render();
}
// manifest 异步读取(fetch 失败自动回退内置配置),就绪后再渲染
resolveSources(init);
})();
</script>
</body>
</html>