feat: 初始化画个标仓库 - AI驱动设计系统(BYOK模式) - 224源/371504图标+25海报源+130技能

This commit is contained in:
sgp
2026-10-03 21:44:18 +08:00
commit 9e222877ea
497 changed files with 43611 additions and 0 deletions
+328
View File
@@ -0,0 +1,328 @@
<!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>