feat: 发布热更 1.5.94,修复中转历史会话并支持一键清空
This commit is contained in:
+137
-38
@@ -561,8 +561,8 @@ async function voiceLoad() {
|
||||
let skillCat = null; // 完整 catalog {total, categories, skills}
|
||||
let storeCat = "__all__"; // 当前分类(__all__ / __hot__ / 中文分类名)
|
||||
let storeSort = "hot"; // hot / downloads / stars
|
||||
let storeShown = 0; // 已渲染数量(分页)
|
||||
const STORE_PAGE = 60;
|
||||
let storePage = 0;
|
||||
const STORE_PAGE = 24;
|
||||
const installed = new Set(JSON.parse(localStorage.getItem("installedSkills") || "[]"));
|
||||
let installedSkills = []; // 磁盘实况(~/.codex/skills 扫描结果)
|
||||
let installedDirs = new Set(); // 已安装技能的目录名,用于精确标注“已安装”
|
||||
@@ -668,10 +668,11 @@ function skillCardHTML(s) {
|
||||
<div class="sk-subtitle">${esc(s.subtitle || s.name)}</div>
|
||||
<div class="sk-brief">${esc(s.brief || "")}</div>
|
||||
${agentBadges(s)}
|
||||
<div class="sk-meta">${meta}</div>
|
||||
<div class="sk-foot">
|
||||
<button class="btn primary small" data-install="${esc(String(s.id))}">${isIn ? "重新安装" : "安装"}</button>
|
||||
<button class="btn ghost small" data-detail="${esc(String(s.id))}">详情</button>
|
||||
<div class="sk-meta">${meta}</div>
|
||||
<div class="sk-card-actions">
|
||||
<button class="btn ghost small install" data-install="${esc(String(s.id))}" title="${isIn ? "重新安装技能" : "安装技能"}">${isIn ? "重新安装" : "安装"}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
@@ -682,7 +683,7 @@ function renderStore(reset = true) {
|
||||
const more = $("#store-more");
|
||||
if (storeCat === "__installed__") { renderInstalledSkills(); return; }
|
||||
const list = storeFiltered();
|
||||
if (reset) { storeShown = 0; grid.innerHTML = ""; }
|
||||
if (reset) storePage = 0;
|
||||
if (!list.length) {
|
||||
grid.innerHTML = "";
|
||||
empty.classList.remove("hidden");
|
||||
@@ -691,35 +692,75 @@ function renderStore(reset = true) {
|
||||
return;
|
||||
}
|
||||
empty.classList.add("hidden");
|
||||
const next = list.slice(storeShown, storeShown + STORE_PAGE);
|
||||
grid.insertAdjacentHTML("beforeend", next.map(skillCardHTML).join(""));
|
||||
storeShown += next.length;
|
||||
// 副标题
|
||||
const pageCount = Math.ceil(list.length / STORE_PAGE);
|
||||
storePage = Math.min(storePage, pageCount - 1);
|
||||
const first = storePage * STORE_PAGE;
|
||||
const next = list.slice(first, first + STORE_PAGE);
|
||||
grid.innerHTML = next.map(skillCardHTML).join("");
|
||||
|
||||
$("#store-cat-title").textContent =
|
||||
storeCat === "__all__" ? "全部技能" : storeCat === "__hot__" ? "热门技能" : storeCat;
|
||||
$("#store-count").textContent = `${list.length.toLocaleString()} 个`;
|
||||
$("#store-cat-sub").textContent =
|
||||
`${list.length} 个技能 · 通用 SKILL.md 标准,适配 Claude · Codex · OpenClaw`;
|
||||
// 加载更多
|
||||
if (storeShown < list.length) {
|
||||
more.classList.remove("hidden");
|
||||
more.innerHTML = `已显示 ${storeShown} / ${list.length} · <button class="btn ghost small" id="store-more-btn">加载更多</button>`;
|
||||
$("#store-more-btn").addEventListener("click", () => renderStore(false));
|
||||
} else {
|
||||
more.classList.remove("hidden");
|
||||
more.innerHTML = `<span class="muted small">已全部显示 · 共 ${list.length} 个</span>`;
|
||||
}
|
||||
`通用 SKILL.md 标准,适配 Claude · Codex · OpenClaw`;
|
||||
renderStorePager(more, pageCount, list.length);
|
||||
bindStoreCards();
|
||||
}
|
||||
|
||||
function renderStorePager(more, pageCount, total) {
|
||||
more.classList.remove("hidden");
|
||||
if (pageCount <= 1) {
|
||||
more.innerHTML = `<span class="muted small">共 ${total.toLocaleString()} 个技能</span>`;
|
||||
return;
|
||||
}
|
||||
const pages = new Set([0, pageCount - 1, storePage - 1, storePage, storePage + 1]);
|
||||
const orderedPages = [...pages].filter((p) => p >= 0 && p < pageCount).sort((a, b) => a - b);
|
||||
let last = -1;
|
||||
const pageButtons = orderedPages.map((p) => {
|
||||
const gap = p - last > 1 ? `<span class="store-page-gap">…</span>` : "";
|
||||
last = p;
|
||||
return `${gap}<button class="store-page-btn${p === storePage ? " active" : ""}" data-store-page="${p}" aria-label="第 ${p + 1} 页" aria-current="${p === storePage ? "page" : "false"}">${p + 1}</button>`;
|
||||
}).join("");
|
||||
more.innerHTML = `
|
||||
<span class="store-page-summary">${storePage * STORE_PAGE + 1}-${Math.min((storePage + 1) * STORE_PAGE, total)} / ${total.toLocaleString()}</span>
|
||||
<div class="store-pagination" aria-label="技能分页">
|
||||
<button class="store-page-btn" data-store-page="${storePage - 1}" title="上一页" aria-label="上一页" ${storePage === 0 ? "disabled" : ""}>‹</button>
|
||||
${pageButtons}
|
||||
<button class="store-page-btn" data-store-page="${storePage + 1}" title="下一页" aria-label="下一页" ${storePage === pageCount - 1 ? "disabled" : ""}>›</button>
|
||||
</div>`;
|
||||
more.querySelectorAll("[data-store-page]").forEach((btn) => btn.addEventListener("click", () => {
|
||||
const nextPage = Number(btn.dataset.storePage);
|
||||
if (Number.isNaN(nextPage) || nextPage < 0 || nextPage >= pageCount || nextPage === storePage) return;
|
||||
storePage = nextPage;
|
||||
renderStore(false);
|
||||
$("#market-grid").scrollIntoView({ block: "start", behavior: "smooth" });
|
||||
}));
|
||||
}
|
||||
|
||||
function bindStoreCards() {
|
||||
const grid = $("#market-grid");
|
||||
grid.querySelectorAll(".skill-card[data-id]").forEach((card) => {
|
||||
if (card._bound) return; card._bound = true;
|
||||
card.tabIndex = 0;
|
||||
card.setAttribute("role", "button");
|
||||
card.setAttribute("aria-label", `查看 ${card.querySelector(".sk-name")?.textContent || "技能"} 详情`);
|
||||
const openDetail = () => openSkillDetail(card.dataset.id);
|
||||
card.addEventListener("click", (ev) => {
|
||||
if (ev.target.closest("[data-install]")) return;
|
||||
openDetail();
|
||||
});
|
||||
card.addEventListener("keydown", (ev) => {
|
||||
if (ev.key !== "Enter" && ev.key !== " ") return;
|
||||
ev.preventDefault();
|
||||
openDetail();
|
||||
});
|
||||
});
|
||||
grid.querySelectorAll("[data-install]").forEach((btn) => {
|
||||
if (btn._bound) return; btn._bound = true;
|
||||
btn.addEventListener("click", () => installSkill(btn.dataset.install, btn));
|
||||
});
|
||||
grid.querySelectorAll("[data-detail]").forEach((btn) => {
|
||||
if (btn._bound) return; btn._bound = true;
|
||||
btn.addEventListener("click", () => openSkillDetail(btn.dataset.detail));
|
||||
btn.addEventListener("click", (ev) => {
|
||||
ev.stopPropagation();
|
||||
installSkill(btn.dataset.install, btn);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -731,8 +772,9 @@ function renderInstalledSkills() {
|
||||
more.classList.add("hidden");
|
||||
more.innerHTML = "";
|
||||
$("#store-cat-title").textContent = "我已安装";
|
||||
$("#store-count").textContent = `${installedSkills.length.toLocaleString()} 个`;
|
||||
$("#store-cat-sub").textContent =
|
||||
`${installedSkills.length} 个已安装技能 · Codex 侧位于 ~/.codex/skills · 卸载会同时删除工具箱仓库实体`;
|
||||
`Codex 侧位于 ~/.codex/skills · 卸载会同时删除工具箱仓库实体`;
|
||||
if (!installedSkills.length) {
|
||||
grid.innerHTML = "";
|
||||
empty.classList.remove("hidden");
|
||||
@@ -784,9 +826,9 @@ function renderStoreNav() {
|
||||
const item = (key, name, count) => {
|
||||
const active = storeCat === key ? " active" : "";
|
||||
const c = count != null ? `<span class="nav-count">${count.toLocaleString()}</span>` : "";
|
||||
return `<div class="nav-item${active}" data-cat="${esc(key)}">
|
||||
return `<button type="button" class="nav-item${active}" data-cat="${esc(key)}" aria-pressed="${storeCat === key}">
|
||||
<span class="nav-ico">${CAT_ICON[name] || "•"}</span>
|
||||
<span class="nav-name">${esc(name)}</span>${c}</div>`;
|
||||
<span class="nav-name">${esc(name)}</span>${c}</button>`;
|
||||
};
|
||||
let html = "";
|
||||
html += `<div class="store-nav-group">精选</div>`;
|
||||
@@ -797,15 +839,32 @@ function renderStoreNav() {
|
||||
for (const c of skillCat.categories) html += item(c.name, c.name, c.count);
|
||||
nav.innerHTML = html;
|
||||
nav.querySelectorAll(".nav-item").forEach((el) =>
|
||||
el.addEventListener("click", async () => {
|
||||
storeCat = el.dataset.cat;
|
||||
nav.querySelectorAll(".nav-item").forEach((x) => x.classList.remove("active"));
|
||||
el.classList.add("active");
|
||||
$("#market-grid").scrollTop = 0;
|
||||
if (storeCat === "__installed__") await loadInstalledSkills({ includeSize: true, includeMeta: true });
|
||||
renderStore(true);
|
||||
})
|
||||
el.addEventListener("click", () => selectStoreCategory(el.dataset.cat))
|
||||
);
|
||||
renderStoreCategoryChips();
|
||||
}
|
||||
|
||||
function renderStoreCategoryChips() {
|
||||
const chips = $("#store-category-chips");
|
||||
if (!skillCat || !chips) return;
|
||||
const quickCategories = skillCat.categories.slice(0, 9);
|
||||
const item = (key, name) => `<button type="button" class="store-filter-chip${storeCat === key ? " active" : ""}" data-store-filter="${esc(key)}" aria-pressed="${storeCat === key}">${esc(name)}</button>`;
|
||||
chips.innerHTML = [
|
||||
item("__all__", "全部"),
|
||||
item("__hot__", "热门"),
|
||||
...quickCategories.map((c) => item(c.name, c.name)),
|
||||
].join("");
|
||||
chips.querySelectorAll("[data-store-filter]").forEach((el) =>
|
||||
el.addEventListener("click", () => selectStoreCategory(el.dataset.storeFilter))
|
||||
);
|
||||
}
|
||||
|
||||
async function selectStoreCategory(category) {
|
||||
storeCat = category;
|
||||
$("#market-grid").scrollTop = 0;
|
||||
if (storeCat === "__installed__") await loadInstalledSkills({ includeSize: true, includeMeta: true });
|
||||
renderStoreNav();
|
||||
renderStore(true);
|
||||
}
|
||||
|
||||
async function installSkill(id, btn) {
|
||||
@@ -1172,6 +1231,8 @@ let sessData = [];
|
||||
let sessCurrent = null;
|
||||
let sessLoaded = false;
|
||||
let sessAllMode = false;
|
||||
const SESS_DEFAULT_LIMIT = 500;
|
||||
const SESS_ALL_LIMIT = 5000;
|
||||
|
||||
function renderSessionDataDir(info) {
|
||||
if (!info || !info.path) return;
|
||||
@@ -1290,8 +1351,8 @@ async function sessLoad(opts = {}) {
|
||||
try {
|
||||
sessData = await apic.sessionsList(
|
||||
sessAllMode
|
||||
? { includeSubagent: true, includeArchived: true }
|
||||
: { includeSubagent: false, includeArchived: false }
|
||||
? { includeSubagent: true, includeArchived: true, limit: SESS_ALL_LIMIT }
|
||||
: { includeSubagent: false, includeArchived: false, limit: SESS_DEFAULT_LIMIT }
|
||||
);
|
||||
sessLoaded = true;
|
||||
sessCurrent = null;
|
||||
@@ -1329,6 +1390,33 @@ $("#sess-delete").addEventListener("click", async (ev) => {
|
||||
});
|
||||
|
||||
$("#sess-search").addEventListener("input", sessRender);
|
||||
$("#sess-clear-all").addEventListener("click", async (ev) => {
|
||||
const status = await apic.providerSyncStatus();
|
||||
if (status.running) {
|
||||
toast("请先完全退出 Codex(Cmd+Q)再清除会话记录", "err");
|
||||
return;
|
||||
}
|
||||
const confirmed = confirm(
|
||||
"确定清除全部会话记录?\n\n将删除 sessions、archived_sessions、Codex 会话索引、状态库线程、侧栏目录和工具箱会话记忆中的全部对话和项目摘要(已固定的长期偏好会保留)。\n不会删除中转配置、登录凭证、项目源码、插件和自动化任务。\n\n执行前会自动备份,操作不可直接撤销。"
|
||||
);
|
||||
if (!confirmed) return;
|
||||
const btn = ev.currentTarget;
|
||||
busy(btn, true, "清除中…");
|
||||
try {
|
||||
const r = await apic.sessionsClearAll();
|
||||
sessData = [];
|
||||
sessCurrent = null;
|
||||
$("#sess-empty").classList.remove("hidden");
|
||||
$("#sess-conv").classList.add("hidden");
|
||||
sessRender();
|
||||
if (r.cleanupWarning) toast(`会话已清除,但会话记忆清理有警告:${r.cleanupWarning}`, "err");
|
||||
else toast(`已清除 ${r.filesRemoved || 0} 个会话文件和 ${r.memoryRemoved || 0} 条会话记忆;执行前备份在 ${r.backup}`, "ok");
|
||||
} catch (e) {
|
||||
toast(`清除会话失败:${e.message || e}`, "err");
|
||||
} finally {
|
||||
busy(btn, false);
|
||||
}
|
||||
});
|
||||
$("#sess-refresh").addEventListener("click", sessLoad);
|
||||
$("#session-data-dir-select").addEventListener("click", (ev) => changeSessionDataDir(false, ev.currentTarget));
|
||||
$("#session-data-dir-reset").addEventListener("click", (ev) => changeSessionDataDir(true, ev.currentTarget));
|
||||
@@ -2268,6 +2356,17 @@ $("#btn-check-update").addEventListener("click", async (ev) => {
|
||||
showUpdateModal({ version: h.contentVersion, notes: h.notes, mandatory: h.mandatory, kind: "hot" });
|
||||
return;
|
||||
}
|
||||
if (h && (h.needFull || h.needFullAfterHot)) {
|
||||
info.textContent = `需要安装完整包 v${h.fullAppVersion || h.contentVersion}`;
|
||||
showUpdateModal({
|
||||
version: h.fullAppVersion || h.contentVersion,
|
||||
notes: `${h.notes || ""}${h.needFullAfterHot ? "\n\n本次更新还需要安装完整包,以注册 TD 工具箱深链协议。" : ""}`.trim(),
|
||||
mandatory: h.mandatory,
|
||||
url: RELEASE_FALLBACK,
|
||||
kind: "full",
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 否则查完整安装包
|
||||
const r = await apic.checkUpdate();
|
||||
if (r.dev) info.textContent = "(开发版不检查更新)";
|
||||
|
||||
+14
-7
@@ -24,7 +24,7 @@
|
||||
|
||||
<a class="promo" href="#" data-open="https://api.tokendun.cc" title="官方推荐中转 · api.tokendun.cc">
|
||||
<span class="promo-badge">官方推荐</span>
|
||||
<span class="promo-text">推荐中转 · <b>安全 · 稳定 · 可靠</b></span>
|
||||
<span class="promo-text">推荐中转 · <b>安全 · 稳定 · 可靠</b>(自备魔法访问)</span>
|
||||
<span class="promo-arrow">↗</span>
|
||||
</a>
|
||||
|
||||
@@ -53,21 +53,27 @@
|
||||
<!-- 右侧内容 -->
|
||||
<div class="store-main">
|
||||
<div class="store-head">
|
||||
<div class="store-crumb">
|
||||
<span class="muted">技能商店</span>
|
||||
<span class="store-crumb-sep">/</span>
|
||||
<span id="store-cat-title">全部技能</span>
|
||||
<div class="store-heading">
|
||||
<div class="store-title-row">
|
||||
<h2 id="store-cat-title">全部技能</h2>
|
||||
<span class="store-crumb-sep">/</span>
|
||||
<span class="store-count" id="store-count">—</span>
|
||||
</div>
|
||||
<p class="muted store-sub" id="store-cat-sub">通用 SKILL.md 标准,适配 Claude · Codex · OpenClaw</p>
|
||||
</div>
|
||||
<div class="store-tools">
|
||||
<input id="market-search" class="search" type="search" placeholder="搜索技能 / 作者 / 功能…" />
|
||||
<div class="segmented" id="store-sort">
|
||||
<div class="segmented" id="store-sort" aria-label="技能排序方式">
|
||||
<button class="seg active" data-sort="hot">热门</button>
|
||||
<button class="seg" data-sort="downloads">下载量</button>
|
||||
<button class="seg" data-sort="stars">星标</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="muted store-sub" id="store-cat-sub">全部技能 · 通用 SKILL.md 标准,适配 Claude · Codex · OpenClaw</p>
|
||||
<div class="store-filter-row" aria-label="快捷分类">
|
||||
<div class="store-filter-label">分类</div>
|
||||
<div class="store-category-chips" id="store-category-chips"></div>
|
||||
</div>
|
||||
<div id="market-grid" class="grid store-grid"></div>
|
||||
<div id="store-more" class="store-more hidden"></div>
|
||||
<div id="market-empty" class="empty hidden"></div>
|
||||
@@ -482,6 +488,7 @@
|
||||
<span id="sess-scope" class="muted small">当前会话</span>
|
||||
<button id="sess-refresh" class="btn ghost">刷新</button>
|
||||
<button id="sess-fetch-all" class="btn ghost">获取全部记录</button>
|
||||
<button id="sess-clear-all" class="btn danger">一键清除</button>
|
||||
<button id="sess-import" class="btn ghost">导入备份…</button>
|
||||
<button id="sess-backup" class="btn">备份全部…</button>
|
||||
</div>
|
||||
|
||||
+68
-42
@@ -377,31 +377,31 @@ h3 { margin: 0; font-size: 16px; }
|
||||
.mem-line b { color: var(--text); margin-right: 8px; font-weight: 600; }
|
||||
|
||||
/* ===========================================================================
|
||||
技能商店(cocoloop 镜像)—— 侧栏分类 + 精致卡片
|
||||
技能商店(cocoloop 镜像)—— 高密度目录浏览
|
||||
=========================================================================== */
|
||||
.store { display: grid; grid-template-columns: 236px 1fr; gap: 18px;
|
||||
.store { display: grid; grid-template-columns: 218px 1fr; gap: 14px;
|
||||
height: calc(100vh - 150px); min-height: 420px; }
|
||||
|
||||
/* 左侧分类导航 */
|
||||
.store-nav { display: flex; flex-direction: column; background: var(--bg-2);
|
||||
border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
|
||||
border-right: 1px solid var(--border); overflow: hidden; }
|
||||
.store-nav-head { display: flex; align-items: baseline; justify-content: space-between;
|
||||
padding: 13px 15px 9px; border-bottom: 1px solid var(--border); }
|
||||
padding: 9px 10px 8px; border-bottom: 1px solid var(--border); }
|
||||
.store-nav-title { font-size: 12px; font-weight: 700; color: var(--muted);
|
||||
letter-spacing: .5px; }
|
||||
.store-nav-total { font-size: 11px; color: var(--accent); font-weight: 700; }
|
||||
.store-nav-list { flex: 1; overflow-y: auto; padding: 8px; display: flex;
|
||||
.store-nav-list { flex: 1; overflow-y: auto; padding: 6px; display: flex;
|
||||
flex-direction: column; gap: 2px; }
|
||||
.store-nav-list::-webkit-scrollbar { width: 8px; }
|
||||
.store-nav-list::-webkit-scrollbar-thumb { background: var(--border);
|
||||
border-radius: 8px; }
|
||||
.store-nav-group { font-size: 10px; font-weight: 700; color: var(--muted);
|
||||
text-transform: uppercase; letter-spacing: .6px; padding: 10px 10px 4px; opacity: .7; }
|
||||
text-transform: uppercase; letter-spacing: .6px; padding: 9px 8px 3px; opacity: .7; }
|
||||
.nav-item { display: flex; align-items: center; gap: 9px; padding: 8px 10px;
|
||||
border-radius: 9px; cursor: pointer; border: 1px solid transparent;
|
||||
transition: .12s; color: var(--text); }
|
||||
border-radius: 7px; cursor: pointer; border: 1px solid transparent; width: 100%;
|
||||
text-align: left; font-family: inherit; background: transparent; transition: .12s; color: var(--text); }
|
||||
.nav-item:hover { background: var(--card); }
|
||||
.nav-item.active { background: var(--grad); color: #fff; box-shadow: var(--shadow); }
|
||||
.nav-item.active { background: var(--grad); color: #fff; box-shadow: 0 4px 14px rgba(91,108,255,.24); }
|
||||
.nav-item.active .nav-count { background: rgba(255,255,255,.22); color: #fff; }
|
||||
.nav-ico { width: 18px; text-align: center; font-size: 14px; flex-shrink: 0; }
|
||||
.nav-name { flex: 1; font-size: 13px; font-weight: 600; white-space: nowrap;
|
||||
@@ -410,67 +410,88 @@ h3 { margin: 0; font-size: 16px; }
|
||||
background: var(--card-2); padding: 1px 8px; border-radius: 999px; flex-shrink: 0; }
|
||||
|
||||
/* 右侧主区 */
|
||||
.store-main { display: flex; flex-direction: column; min-width: 0; }
|
||||
.store-main { display: flex; flex-direction: column; min-width: 0; min-height: 0; }
|
||||
.store-head { display: flex; align-items: center; justify-content: space-between;
|
||||
gap: 16px; flex-wrap: wrap; }
|
||||
.store-crumb { display: flex; align-items: baseline; gap: 8px; font-size: 20px;
|
||||
font-weight: 700; }
|
||||
.store-crumb .muted { font-size: 13px; font-weight: 600; }
|
||||
gap: 16px; min-height: 42px; }
|
||||
.store-heading { min-width: 0; }
|
||||
.store-title-row { display: flex; align-items: baseline; gap: 8px; }
|
||||
.store-title-row h2 { font-size: 22px; line-height: 1.15; }
|
||||
.store-crumb-sep { color: var(--muted); font-weight: 400; }
|
||||
.store-tools { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
|
||||
.store-sub { margin: 6px 0 14px; font-size: 12.5px; }
|
||||
.store-count { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; }
|
||||
.store-tools { display: flex; gap: 8px; align-items: center; flex: 0 0 auto; }
|
||||
.store-tools .search { width: min(240px, 25vw); padding: 8px 10px; border-radius: 7px; }
|
||||
.store-tools .segmented { border-radius: 7px; padding: 2px; flex-shrink: 0; }
|
||||
.store-tools .seg { padding: 5px 9px; border-radius: 5px; white-space: nowrap; }
|
||||
.store-sub { margin: 3px 0 0; font-size: 12px; }
|
||||
.store-filter-row { display: flex; align-items: flex-start; gap: 9px; margin: 13px 0 12px; }
|
||||
.store-filter-label { color: var(--muted); font-size: 12px; line-height: 28px; flex: 0 0 auto; }
|
||||
.store-category-chips { display: flex; flex-wrap: wrap; gap: 6px; }
|
||||
.store-filter-chip { height: 28px; padding: 0 10px; border: 1px solid var(--border); border-radius: 6px;
|
||||
color: var(--muted); background: var(--card); font: 600 12px/1 inherit; cursor: pointer; }
|
||||
.store-filter-chip:hover { color: var(--text); border-color: var(--accent); }
|
||||
.store-filter-chip.active { border-color: transparent; color: #fff; background: var(--grad); }
|
||||
.store-grid { overflow-y: auto; align-content: start; padding: 2px 4px 4px 2px;
|
||||
flex: 1; grid-template-columns: repeat(auto-fill, minmax(310px, 1fr)); }
|
||||
flex: 1; min-height: 0; grid-template-columns: repeat(auto-fill, minmax(205px, 1fr)); gap: 10px; }
|
||||
.store-grid::-webkit-scrollbar { width: 9px; }
|
||||
.store-grid::-webkit-scrollbar-thumb { background: var(--border); border-radius: 9px; }
|
||||
|
||||
/* 技能卡片 */
|
||||
.skill-card { background: var(--card); border: 1px solid var(--border);
|
||||
border-radius: var(--radius); padding: 15px; display: flex; flex-direction: column;
|
||||
gap: 11px; transition: .15s; position: relative; overflow: hidden; }
|
||||
border-radius: 8px; min-height: 188px; padding: 12px; display: flex; flex-direction: column;
|
||||
gap: 8px; transition: .15s; position: relative; overflow: hidden; cursor: pointer; }
|
||||
.skill-card::before { content: ""; position: absolute; inset: 0 0 auto 0; height: 3px;
|
||||
background: var(--grad); opacity: 0; transition: .15s; }
|
||||
.skill-card:hover { transform: translateY(-3px); box-shadow: var(--shadow);
|
||||
.skill-card:hover { transform: translateY(-2px); box-shadow: var(--shadow);
|
||||
border-color: var(--accent); }
|
||||
.skill-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
||||
.skill-card:hover::before { opacity: 1; }
|
||||
.sk-top { display: flex; align-items: flex-start; gap: 11px; }
|
||||
.sk-ico { width: 42px; height: 42px; flex-shrink: 0; display: grid; place-items: center;
|
||||
font-size: 22px; background: var(--card-2); border: 1px solid var(--border);
|
||||
border-radius: 11px; }
|
||||
.sk-top { display: flex; align-items: flex-start; gap: 9px; }
|
||||
.sk-ico { width: 36px; height: 36px; flex-shrink: 0; display: grid; place-items: center;
|
||||
font-size: 19px; background: var(--card-2); border: 1px solid var(--border); border-radius: 7px; }
|
||||
.sk-headings { min-width: 0; flex: 1; }
|
||||
.sk-name { font-weight: 700; font-size: 15px; line-height: 1.25; word-break: break-word; }
|
||||
.sk-name { font-weight: 700; font-size: 14px; line-height: 1.28; word-break: break-word;
|
||||
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
||||
.sk-author { color: var(--muted); font-size: 12px; margin-top: 2px; }
|
||||
.sk-sec { flex-shrink: 0; width: 24px; height: 24px; display: grid; place-items: center;
|
||||
border-radius: 7px; font-size: 11px; font-weight: 800; }
|
||||
.sk-sec { flex-shrink: 0; width: 21px; height: 21px; display: grid; place-items: center;
|
||||
border-radius: 5px; font-size: 10px; font-weight: 800; }
|
||||
.sk-sec.S { background: linear-gradient(135deg,#f59e0b,#f97316); color: #fff; }
|
||||
.sk-sec.A { background: rgba(74,222,128,.16); color: var(--ok); }
|
||||
.sk-sec.B { background: var(--card-2); color: var(--muted); }
|
||||
.sk-subtitle { font-weight: 600; font-size: 13.5px; line-height: 1.4; color: var(--text); }
|
||||
.sk-brief { color: var(--muted); font-size: 12.5px; line-height: 1.55; flex: 1;
|
||||
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
|
||||
overflow: hidden; }
|
||||
.sk-agents { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
|
||||
.sk-agents .lab { font-size: 11px; color: var(--muted); margin-right: 2px; }
|
||||
.agent-badge { font-size: 11px; font-weight: 700; padding: 2px 9px; border-radius: 999px;
|
||||
.sk-subtitle { font-weight: 600; font-size: 12.5px; line-height: 1.35; color: var(--text);
|
||||
display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden; }
|
||||
.sk-brief { color: var(--muted); font-size: 12px; line-height: 1.45; flex: 1;
|
||||
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
||||
.sk-agents { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
|
||||
.store-grid .sk-agents .lab { display: none; }
|
||||
.agent-badge { font-size: 10px; font-weight: 700; padding: 2px 7px; border-radius: 999px;
|
||||
display: inline-flex; align-items: center; gap: 5px; background: var(--card-2);
|
||||
border: 1px solid var(--border); }
|
||||
.agent-badge::before { content: ""; width: 6px; height: 6px; border-radius: 50%; }
|
||||
.agent-badge::before { content: ""; width: 5px; height: 5px; border-radius: 50%; }
|
||||
.agent-badge.claude::before { background: #d97757; }
|
||||
.agent-badge.codex::before { background: #5b6cff; }
|
||||
.agent-badge.openclaw::before { background: #a855f7; }
|
||||
.agent-badge.mm { background: rgba(245,158,11,.14); border-color: transparent; color: #f59e0b; }
|
||||
.agent-badge.mm::before { display: none; }
|
||||
.sk-meta { display: flex; align-items: center; gap: 13px; padding-top: 9px;
|
||||
border-top: 1px solid var(--border); color: var(--muted); font-size: 12px;
|
||||
flex-wrap: wrap; }
|
||||
.sk-meta { display: flex; align-items: center; gap: 9px; color: var(--muted); font-size: 11px;
|
||||
min-width: 0; flex-wrap: nowrap; overflow: hidden; }
|
||||
.sk-meta > span { white-space: nowrap; }
|
||||
.sk-meta .cred { margin-left: auto; font-size: 10px; font-weight: 700; padding: 1px 7px;
|
||||
border: 1px solid var(--border); border-radius: 6px; letter-spacing: .3px; }
|
||||
.sk-foot { display: flex; align-items: center; gap: 9px; }
|
||||
.sk-foot .btn.primary { flex: 1; }
|
||||
.sk-foot { display: flex; align-items: center; gap: 8px; padding-top: 8px; border-top: 1px solid var(--border); }
|
||||
.sk-card-actions { display: flex; gap: 4px; margin-left: auto; }
|
||||
.sk-card-actions .install { min-width: 58px; }
|
||||
|
||||
/* 加载更多 */
|
||||
/* 分页 */
|
||||
.store-more { display: flex; align-items: center; justify-content: center; gap: 12px;
|
||||
padding: 14px; color: var(--muted); font-size: 13px; }
|
||||
padding: 12px 0 2px; color: var(--muted); font-size: 12px; }
|
||||
.store-pagination { display: flex; align-items: center; gap: 4px; }
|
||||
.store-page-btn { width: 28px; height: 28px; display: grid; place-items: center; padding: 0;
|
||||
border: 1px solid var(--border); border-radius: 6px; background: var(--card); color: var(--muted);
|
||||
font: 600 12px/1 inherit; cursor: pointer; }
|
||||
.store-page-btn:hover:not(:disabled) { color: var(--text); border-color: var(--accent); }
|
||||
.store-page-btn.active { background: var(--grad); border-color: transparent; color: #fff; }
|
||||
.store-page-btn:disabled { opacity: .4; cursor: not-allowed; }
|
||||
.store-page-gap { padding: 0 2px; color: var(--muted); }
|
||||
|
||||
/* 技能详情弹窗内的排版 */
|
||||
.sd-hero { display: flex; align-items: flex-start; gap: 14px; margin-bottom: 4px; }
|
||||
@@ -486,6 +507,11 @@ h3 { margin: 0; font-size: 16px; }
|
||||
@media (max-width: 820px) {
|
||||
.store { grid-template-columns: 1fr; height: auto; }
|
||||
.store-nav { max-height: 200px; }
|
||||
.store-head { align-items: flex-start; flex-direction: column; }
|
||||
.store-tools { width: 100%; }
|
||||
.store-tools .search { flex: 1; width: auto; }
|
||||
.store-grid { max-height: none; overflow: visible; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
|
||||
.store-filter-row { align-items: flex-start; }
|
||||
}
|
||||
|
||||
/* 品牌 logo 图片 */
|
||||
|
||||
Reference in New Issue
Block a user