Sync third-party and MCP marketplace plugins
Constraint: Public skills are published only by explicit administrator action unless they are tracked third-party market sources. Confidence: high Scope-risk: narrow Directive: Keep private/internal skills out of the public marketplace and preserve normal incremental market Git history. Tested: Marketplace validation passed.
This commit is contained in:
@@ -196,18 +196,6 @@
|
||||
},
|
||||
"category": "设计"
|
||||
},
|
||||
{
|
||||
"name": "shadcn",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./plugins/codex/plugins/shadcn"
|
||||
},
|
||||
"policy": {
|
||||
"installation": "AVAILABLE",
|
||||
"authentication": "ON_INSTALL"
|
||||
},
|
||||
"category": "开发工具"
|
||||
},
|
||||
{
|
||||
"name": "frontend-slides",
|
||||
"source": {
|
||||
@@ -244,18 +232,6 @@
|
||||
},
|
||||
"category": "文档处理"
|
||||
},
|
||||
{
|
||||
"name": "ppt-master",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./plugins/codex/plugins/ppt-master"
|
||||
},
|
||||
"policy": {
|
||||
"installation": "AVAILABLE",
|
||||
"authentication": "ON_INSTALL"
|
||||
},
|
||||
"category": "文档处理"
|
||||
},
|
||||
{
|
||||
"name": "next-skills",
|
||||
"source": {
|
||||
@@ -280,18 +256,6 @@
|
||||
},
|
||||
"category": "开发工具"
|
||||
},
|
||||
{
|
||||
"name": "mcp-playwright",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./plugins/codex/plugins/mcp-playwright"
|
||||
},
|
||||
"policy": {
|
||||
"installation": "AVAILABLE",
|
||||
"authentication": "ON_INSTALL"
|
||||
},
|
||||
"category": "MCP"
|
||||
},
|
||||
{
|
||||
"name": "eapil-llmapi-structured",
|
||||
"source": {
|
||||
@@ -315,6 +279,42 @@
|
||||
"authentication": "ON_INSTALL"
|
||||
},
|
||||
"category": "知识库与检索"
|
||||
},
|
||||
{
|
||||
"name": "shadcn",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./plugins/codex/plugins/shadcn"
|
||||
},
|
||||
"policy": {
|
||||
"installation": "AVAILABLE",
|
||||
"authentication": "ON_INSTALL"
|
||||
},
|
||||
"category": "开发工具"
|
||||
},
|
||||
{
|
||||
"name": "ppt-master",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./plugins/codex/plugins/ppt-master"
|
||||
},
|
||||
"policy": {
|
||||
"installation": "AVAILABLE",
|
||||
"authentication": "ON_INSTALL"
|
||||
},
|
||||
"category": "文档处理"
|
||||
},
|
||||
{
|
||||
"name": "mcp-playwright",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./plugins/codex/plugins/mcp-playwright"
|
||||
},
|
||||
"policy": {
|
||||
"installation": "AVAILABLE",
|
||||
"authentication": "ON_INSTALL"
|
||||
},
|
||||
"category": "MCP"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
"repo": "https://github.com/shadcn-ui/ui.git",
|
||||
"ref": "main",
|
||||
"adapter": "claude-skill",
|
||||
"commit": "c879483c9694d7b135ed1efe8f73233d924ae551",
|
||||
"syncedAt": "2026-06-17T07:05:29Z"
|
||||
"commit": "c2ddedf5d24e0cee05936c69e1f199a9975a07b2",
|
||||
"syncedAt": "2026-06-18T16:00:00Z"
|
||||
},
|
||||
{
|
||||
"id": "frontend-slides",
|
||||
@@ -69,8 +69,8 @@
|
||||
"repo": "https://github.com/hugohe3/ppt-master.git",
|
||||
"ref": "main",
|
||||
"adapter": "claude-skill",
|
||||
"commit": "313ee6729dba50cbdd50b67debe601c483a5bcab",
|
||||
"syncedAt": "2026-06-17T07:05:29Z"
|
||||
"commit": "7249902c363f0b33dee9a9f7112fb258ae9c3bf7",
|
||||
"syncedAt": "2026-06-18T16:00:00Z"
|
||||
},
|
||||
{
|
||||
"id": "next-skills",
|
||||
|
||||
@@ -196,18 +196,6 @@
|
||||
},
|
||||
"category": "设计"
|
||||
},
|
||||
{
|
||||
"name": "shadcn",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./plugins/shadcn"
|
||||
},
|
||||
"policy": {
|
||||
"installation": "AVAILABLE",
|
||||
"authentication": "ON_INSTALL"
|
||||
},
|
||||
"category": "开发工具"
|
||||
},
|
||||
{
|
||||
"name": "frontend-slides",
|
||||
"source": {
|
||||
@@ -244,18 +232,6 @@
|
||||
},
|
||||
"category": "文档处理"
|
||||
},
|
||||
{
|
||||
"name": "ppt-master",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./plugins/ppt-master"
|
||||
},
|
||||
"policy": {
|
||||
"installation": "AVAILABLE",
|
||||
"authentication": "ON_INSTALL"
|
||||
},
|
||||
"category": "文档处理"
|
||||
},
|
||||
{
|
||||
"name": "next-skills",
|
||||
"source": {
|
||||
@@ -280,18 +256,6 @@
|
||||
},
|
||||
"category": "开发工具"
|
||||
},
|
||||
{
|
||||
"name": "mcp-playwright",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./plugins/mcp-playwright"
|
||||
},
|
||||
"policy": {
|
||||
"installation": "AVAILABLE",
|
||||
"authentication": "ON_INSTALL"
|
||||
},
|
||||
"category": "MCP"
|
||||
},
|
||||
{
|
||||
"name": "eapil-llmapi-structured",
|
||||
"source": {
|
||||
@@ -315,6 +279,42 @@
|
||||
"authentication": "ON_INSTALL"
|
||||
},
|
||||
"category": "知识库与检索"
|
||||
},
|
||||
{
|
||||
"name": "shadcn",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./plugins/shadcn"
|
||||
},
|
||||
"policy": {
|
||||
"installation": "AVAILABLE",
|
||||
"authentication": "ON_INSTALL"
|
||||
},
|
||||
"category": "开发工具"
|
||||
},
|
||||
{
|
||||
"name": "ppt-master",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./plugins/ppt-master"
|
||||
},
|
||||
"policy": {
|
||||
"installation": "AVAILABLE",
|
||||
"authentication": "ON_INSTALL"
|
||||
},
|
||||
"category": "文档处理"
|
||||
},
|
||||
{
|
||||
"name": "mcp-playwright",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./plugins/mcp-playwright"
|
||||
},
|
||||
"policy": {
|
||||
"installation": "AVAILABLE",
|
||||
"authentication": "ON_INSTALL"
|
||||
},
|
||||
"category": "MCP"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
"name": "playwright浏览器自动化操作",
|
||||
"version": "20260605",
|
||||
"keySource": "none",
|
||||
"syncedAt": "2026-06-17T16:01:07Z"
|
||||
"syncedAt": "2026-06-18T16:01:22Z"
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"sourceId": "ppt-master",
|
||||
"repo": "https://github.com/hugohe3/ppt-master.git",
|
||||
"ref": "main",
|
||||
"commit": "313ee6729dba50cbdd50b67debe601c483a5bcab",
|
||||
"commit": "7249902c363f0b33dee9a9f7112fb258ae9c3bf7",
|
||||
"adapter": "claude-skill",
|
||||
"sourcePath": "skills/ppt-master",
|
||||
"syncedAt": "2026-06-17T07:05:29Z"
|
||||
"syncedAt": "2026-06-18T16:00:00Z"
|
||||
}
|
||||
|
||||
+2
@@ -10,6 +10,8 @@ Photo-led editorial — large full-bleed photography dominates the page, text po
|
||||
- Decoration: thin rules, section numbering, small figure notes; nothing competes with the image.
|
||||
- Whitespace: generous around text; the photo fills, the type breathes beside it. Asymmetric magazine composition.
|
||||
|
||||
> **No usable image → fall back to `editorial`.** This style's spine is the photograph; when a page has no suitable image available, render it in the `editorial` text-led layout (magazine columns) rather than a full-bleed placeholder — an empty / dashed image frame contradicts the style's whole premise. A deterministic, observable condition, not a judgment call.
|
||||
|
||||
## 2. Typography character
|
||||
|
||||
- Editorial serif / CJK title × clean sans body; magazine-column cadence; small precise captions and figure notes. Words are concise — they point, they don't fill.
|
||||
|
||||
+144
-15
@@ -64,6 +64,8 @@
|
||||
latin: "Latin",
|
||||
sample_cjk: "数字化转型战略",
|
||||
sample_latin: "Digital Transformation",
|
||||
style_preview_label: "Overall impression (color + typography)",
|
||||
style_preview_body: "· rough feel only, not the actual slide layout",
|
||||
mode_continuous_desc: "Generate the whole deck in one pass.",
|
||||
mode_split_desc: "Stop after the spec; resume SVG generation in a fresh window.",
|
||||
refine_off_desc: "Spec is written in one go; the pipeline auto-proceeds.",
|
||||
@@ -127,6 +129,8 @@
|
||||
latin: "西文",
|
||||
sample_cjk: "数字化转型战略",
|
||||
sample_latin: "Digital Transformation",
|
||||
style_preview_label: "整体形象(配色 + 字体)",
|
||||
style_preview_body: "· 仅大致形象,非实际版式",
|
||||
mode_continuous_desc: "一次性连续生成整份演示文稿。",
|
||||
mode_split_desc: "写完设计规范后停止,另开窗口继续生成页面。",
|
||||
refine_off_desc: "设计规范一次写完,流程自动继续。",
|
||||
@@ -445,6 +449,19 @@
|
||||
return spec.candidates || spec.options || [];
|
||||
}
|
||||
|
||||
function usesCustomImagePlanValue(value) {
|
||||
var ids = (CAT.image_usage || []).map(function (item) { return item.id; });
|
||||
return value && ids.indexOf(value) === -1;
|
||||
}
|
||||
|
||||
function customImagePlanHasAiSignal() {
|
||||
return imageStrategyCandidates().length > 0 || !!recId("image_ai_path");
|
||||
}
|
||||
|
||||
function needsGeneratedImagesForUsage(value) {
|
||||
return value === "ai" || (usesCustomImagePlanValue(value) && customImagePlanHasAiSignal());
|
||||
}
|
||||
|
||||
function imageStrategySelectedIndex() {
|
||||
var spec = imageStrategySpec();
|
||||
var idx = spec.selected || 0;
|
||||
@@ -495,6 +512,18 @@
|
||||
"body_text"
|
||||
];
|
||||
|
||||
function normHex(val) {
|
||||
var v = (val || "").trim();
|
||||
if (!/^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/.test(v)) return null;
|
||||
return v.charAt(0) === "#" ? v : "#" + v;
|
||||
}
|
||||
function hexOr(val, fallback) {
|
||||
return normHex(val) || fallback;
|
||||
}
|
||||
// Replaced when the combined color+typography preview mounts; the color and
|
||||
// typography sections call it after every change so the preview stays live.
|
||||
var refreshStylePreview = function () {};
|
||||
|
||||
function renderColor(host) {
|
||||
var cands = (REC.color && REC.color.candidates) || [];
|
||||
var sec = section(5, "sec_color");
|
||||
@@ -504,11 +533,6 @@
|
||||
var cardSwatchRefs = []; // idx -> {role: swatchEl}, for live override feedback
|
||||
var selectedIdx = -1;
|
||||
|
||||
function normHex(val) {
|
||||
var v = (val || "").trim();
|
||||
if (!/^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/.test(v)) return null;
|
||||
return v.charAt(0) === "#" ? v : "#" + v;
|
||||
}
|
||||
function paintSwatch(elem, val) {
|
||||
var n = normHex(val);
|
||||
elem.style.background = n || "transparent";
|
||||
@@ -526,6 +550,7 @@
|
||||
STATE.color = { name: c.name || "", palette: Object.assign({}, normPalette(c)) };
|
||||
grid.querySelectorAll(".color-card").forEach(function (card, i) { card.classList.toggle("selected", i === idx); });
|
||||
applyHexInputs(STATE.color.palette);
|
||||
refreshStylePreview();
|
||||
}
|
||||
|
||||
cands.forEach(function (c, idx) {
|
||||
@@ -571,6 +596,7 @@
|
||||
if (n && selectedIdx >= 0 && cardSwatchRefs[selectedIdx] && cardSwatchRefs[selectedIdx][role]) {
|
||||
cardSwatchRefs[selectedIdx][role].style.background = n;
|
||||
}
|
||||
refreshStylePreview();
|
||||
});
|
||||
hexInputs[role] = inp; hexSwatches[role] = sw;
|
||||
line.appendChild(sw); line.appendChild(inp);
|
||||
@@ -593,11 +619,22 @@
|
||||
host.appendChild(sec);
|
||||
}
|
||||
|
||||
function previewFontStack(primary, fallback) {
|
||||
if (!primary) return fallback || "";
|
||||
if (!fallback) return primary;
|
||||
return primary + ", " + fallback;
|
||||
}
|
||||
|
||||
function fontSample(box, slot, css) {
|
||||
var line = el("div", "font-sample-line");
|
||||
var cjk = el("span", "fs-cjk", slot.sample_cjk || t("sample_cjk"));
|
||||
var lat = el("span", "fs-latin", slot.sample_latin || t("sample_latin"));
|
||||
if (css) { cjk.style.fontFamily = css; lat.style.fontFamily = css; }
|
||||
var cjkStack = previewFontStack(slot.cjk, css);
|
||||
var latinStack = previewFontStack(slot.latin, css);
|
||||
if (cjkStack) cjk.style.fontFamily = cjkStack;
|
||||
if (latinStack) lat.style.fontFamily = latinStack;
|
||||
if (cjkStack) cjk.title = cjkStack;
|
||||
if (latinStack) lat.title = latinStack;
|
||||
line.appendChild(cjk); line.appendChild(lat); box.appendChild(line);
|
||||
}
|
||||
|
||||
@@ -622,6 +659,7 @@
|
||||
if (sizeInput) sizeInput.value = STATE.typography.body_size || "";
|
||||
customInput.style.display = "none";
|
||||
grid.querySelectorAll(".font-card").forEach(function (card, i) { card.classList.toggle("selected", i === idx); });
|
||||
refreshStylePreview();
|
||||
}
|
||||
|
||||
function selectCustomTypography() {
|
||||
@@ -636,6 +674,7 @@
|
||||
customCard.classList.add("selected");
|
||||
customInput.style.display = "block";
|
||||
customInput.focus();
|
||||
refreshStylePreview();
|
||||
}
|
||||
|
||||
cands.forEach(function (c, idx) {
|
||||
@@ -663,6 +702,7 @@
|
||||
customInput.addEventListener("input", function () {
|
||||
if (!STATE.typography || STATE.typography.name !== "custom") selectCustomTypography();
|
||||
STATE.typography.custom = customInput.value;
|
||||
refreshStylePreview();
|
||||
});
|
||||
sec.appendChild(customInput);
|
||||
|
||||
@@ -679,6 +719,7 @@
|
||||
sizeInput.addEventListener("input", function () {
|
||||
if (!STATE.typography) STATE.typography = { name: "", heading: {}, body: {} };
|
||||
STATE.typography.body_size = sizeInput.value;
|
||||
refreshStylePreview();
|
||||
});
|
||||
sizeRow.appendChild(sizeInput);
|
||||
sizeRow.appendChild(el("div", "toggle-desc", t("font_body_size_hint")));
|
||||
@@ -702,6 +743,86 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Combined color + typography preview — not a ninth confirmation, just a
|
||||
// live "overall impression" of the two style choices made above. Kept
|
||||
// deliberately abstract (a style chip, not a slide layout); page layout
|
||||
// preview is the live-preview server's job (Step 6).
|
||||
function renderStylePreview(host) {
|
||||
var wrap = el("div", "style-preview");
|
||||
var label = el("div", "style-preview-label");
|
||||
label.appendChild(el("span", "spl-title", t("style_preview_label")));
|
||||
// The "rough feel, not a slide layout" caveat sits in the label in the
|
||||
// UI font — never rendered in the candidate's body font, so it cannot
|
||||
// pose as sample content.
|
||||
label.appendChild(el("span", "spl-note", t("style_preview_body")));
|
||||
wrap.appendChild(label);
|
||||
var card = el("div", "style-preview-card");
|
||||
var textcol = el("div", "sp-textcol");
|
||||
var title = el("div", "sp-title");
|
||||
var titleCjk = el("span", "sp-title-cjk");
|
||||
var titleLat = el("span", "sp-title-lat");
|
||||
title.appendChild(titleCjk); title.appendChild(titleLat);
|
||||
var bodyRow = el("div", "sp-body");
|
||||
var accentBar = el("span", "sp-accent-bar");
|
||||
var bodyWrap = el("div", "sp-body-wrap");
|
||||
var bodyCjk = el("span", "sp-body-cjk");
|
||||
var bodyLat = el("span", "sp-body-lat");
|
||||
bodyWrap.appendChild(bodyCjk); bodyWrap.appendChild(bodyLat);
|
||||
bodyRow.appendChild(accentBar); bodyRow.appendChild(bodyWrap);
|
||||
textcol.appendChild(title); textcol.appendChild(bodyRow);
|
||||
var chip = el("div", "sp-chip");
|
||||
var chipDot = el("span", "sp-chip-dot");
|
||||
var chipLabel = el("span", "sp-chip-label");
|
||||
chip.appendChild(chipDot); chip.appendChild(chipLabel);
|
||||
card.appendChild(textcol); card.appendChild(chip);
|
||||
wrap.appendChild(card);
|
||||
host.appendChild(wrap);
|
||||
// Pin the strip just under the sticky topbar so it stays visible while
|
||||
// the user scrolls through the color / icon / typography sections.
|
||||
var topbar = document.getElementById("topbar");
|
||||
if (topbar) wrap.style.top = topbar.offsetHeight + "px";
|
||||
|
||||
function paint() {
|
||||
var pal = (STATE.color && STATE.color.palette) || {};
|
||||
var typ = STATE.typography || {};
|
||||
var head = typ.heading || {}, body = typ.body || {};
|
||||
var bg = hexOr(pal.background, "#ffffff");
|
||||
var sbg = hexOr(pal.secondary_bg, bg);
|
||||
var pri = hexOr(pal.primary, "#1a3a6b");
|
||||
var acc = hexOr(pal.accent, pri);
|
||||
var sacc = hexOr(pal.secondary_accent, acc);
|
||||
var txt = hexOr(pal.body_text, "#1d2430");
|
||||
var bodyPx = Math.max(12, Math.min(26, parseInt(typ.body_size, 10) || 18));
|
||||
var headStack = previewFontStack(head.cjk, head.css);
|
||||
var headLatStack = previewFontStack(head.latin, head.css);
|
||||
var bodyStack = previewFontStack(body.cjk, body.css);
|
||||
var bodyLatStack = previewFontStack(body.latin, body.css);
|
||||
|
||||
card.style.background = bg;
|
||||
titleCjk.textContent = head.sample_cjk || t("sample_cjk");
|
||||
titleLat.textContent = head.sample_latin || t("sample_latin");
|
||||
title.style.color = pri;
|
||||
title.style.fontSize = Math.round(bodyPx * 1.7) + "px";
|
||||
titleCjk.style.fontFamily = headStack || "";
|
||||
titleLat.style.fontFamily = headLatStack || "";
|
||||
// CJK and Latin previewed with their own stacks (mirrors the title
|
||||
// and the per-card font samples) so each script's font is visible.
|
||||
bodyCjk.textContent = body.sample_cjk || t("sample_cjk");
|
||||
bodyLat.textContent = body.sample_latin || t("sample_latin");
|
||||
bodyWrap.style.color = txt;
|
||||
bodyWrap.style.fontSize = bodyPx + "px";
|
||||
bodyCjk.style.fontFamily = bodyStack || "";
|
||||
bodyLat.style.fontFamily = bodyLatStack || "";
|
||||
accentBar.style.background = acc;
|
||||
chip.style.background = sbg;
|
||||
chipDot.style.background = sacc;
|
||||
chipLabel.textContent = t("role_secondary_bg");
|
||||
chipLabel.style.color = txt;
|
||||
}
|
||||
refreshStylePreview = paint;
|
||||
paint();
|
||||
}
|
||||
|
||||
function renderImages(host) {
|
||||
var sec = section(8, "sec_images");
|
||||
var sub = el("div", "subfield");
|
||||
@@ -711,11 +832,10 @@
|
||||
var strategyGrid = el("div", "font-grid");
|
||||
var strategyCands = imageStrategyCandidates();
|
||||
function usesCustomImagePlan() {
|
||||
var ids = (CAT.image_usage || []).map(function (item) { return item.id; });
|
||||
return STATE.image_usage && ids.indexOf(STATE.image_usage) === -1;
|
||||
return usesCustomImagePlanValue(STATE.image_usage);
|
||||
}
|
||||
function needsGeneratedImages() {
|
||||
return STATE.image_usage === "ai" || usesCustomImagePlan();
|
||||
return needsGeneratedImagesForUsage(STATE.image_usage);
|
||||
}
|
||||
function refreshAiControls() {
|
||||
var needsAiPath = needsGeneratedImages();
|
||||
@@ -806,13 +926,23 @@
|
||||
function renderAll() {
|
||||
var host = document.getElementById("sections");
|
||||
host.innerHTML = "";
|
||||
// Detach the previous preview's repaint closure before the sections
|
||||
// re-render: color/typography auto-select would otherwise call it and
|
||||
// write to now-detached nodes until renderStylePreview remounts it.
|
||||
refreshStylePreview = function () {};
|
||||
renderCanvas(host);
|
||||
renderPages(host);
|
||||
renderAudience(host);
|
||||
renderStyle(host);
|
||||
renderColor(host);
|
||||
renderIcons(host);
|
||||
renderTypography(host);
|
||||
// Group the preview with the three sections it reflects so its sticky
|
||||
// scope ends when typography scrolls past — it does not linger over the
|
||||
// image / mode / refine sections below.
|
||||
var styleGroup = el("div", "style-group");
|
||||
renderStylePreview(styleGroup);
|
||||
renderColor(styleGroup);
|
||||
renderIcons(styleGroup);
|
||||
renderTypography(styleGroup);
|
||||
host.appendChild(styleGroup);
|
||||
renderImages(host);
|
||||
renderMode(host);
|
||||
renderRefine(host);
|
||||
@@ -871,8 +1001,7 @@
|
||||
function confirm() {
|
||||
var btn = document.getElementById("btn-confirm");
|
||||
var payload = Object.assign({}, STATE);
|
||||
var imageUsageIds = (CAT.image_usage || []).map(function (item) { return item.id; });
|
||||
var customImagePlan = payload.image_usage && imageUsageIds.indexOf(payload.image_usage) === -1;
|
||||
var customImagePlan = usesCustomImagePlanValue(payload.image_usage);
|
||||
if (payload.image_usage === "custom" || (customImagePlan && !String(payload.image_usage).trim())) {
|
||||
document.getElementById("confirm-status").textContent = t("image_usage_custom_required");
|
||||
var customImageInput = document.querySelector(".image-usage-custom-input");
|
||||
@@ -880,7 +1009,7 @@
|
||||
return;
|
||||
}
|
||||
if (customImagePlan) payload.image_usage = String(payload.image_usage).trim();
|
||||
if (payload.image_usage !== "ai" && !customImagePlan) {
|
||||
if (!needsGeneratedImagesForUsage(payload.image_usage)) {
|
||||
delete payload.image_ai_path;
|
||||
delete payload.image_strategy;
|
||||
}
|
||||
|
||||
+60
@@ -298,3 +298,63 @@ body {
|
||||
}
|
||||
.cf-title { font-size: 22px; font-weight: 700; color: #067647; }
|
||||
.cf-hint { font-size: 14px; color: var(--muted); margin-top: 10px; line-height: 1.6; }
|
||||
|
||||
/* Combined color + typography "overall impression" preview — sticky strip
|
||||
that rides along the color / icon / typography picking sections. */
|
||||
.style-preview {
|
||||
position: sticky;
|
||||
z-index: 5;
|
||||
background: var(--bg);
|
||||
padding: 8px 0;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.style-preview-label {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.spl-title { font-size: 12.5px; font-weight: 600; color: var(--muted); }
|
||||
.spl-note { font-size: 11.5px; color: var(--muted); }
|
||||
.style-preview-card {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow);
|
||||
padding: 14px 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.sp-textcol { flex: 1; min-width: 0; }
|
||||
.sp-title {
|
||||
font-weight: 700;
|
||||
line-height: 1.25;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.sp-title-lat { margin-left: 10px; opacity: 0.92; }
|
||||
.sp-body { display: flex; align-items: stretch; gap: 10px; margin-top: 6px; }
|
||||
.sp-accent-bar { flex: 0 0 4px; border-radius: 2px; }
|
||||
.sp-body-wrap {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
line-height: 1.5;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.sp-body-lat { margin-left: 10px; opacity: 0.92; }
|
||||
.sp-chip {
|
||||
flex: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 7px 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
.sp-chip-dot { width: 12px; height: 12px; border-radius: 50%; }
|
||||
.sp-chip-label { font-size: 12.5px; }
|
||||
|
||||
@@ -103,9 +103,11 @@ Both files live under `<project_path>/confirm_ui/`.
|
||||
|
||||
- `recommend.*` names the recommended `id` for each enumerable field (must match a `catalogs.json` id, or be a free string for a recommended custom value). The page badges and pre-selects it. **Guarantee**: if a `recommend.*` is omitted, the page falls back to the first catalog option so every enumerable field always shows one badged recommendation — but the AI should still set them for a meaningful default. Legacy aliases are accepted for old files (`line` → `tabler-outline`, `filled` → `tabler-filled`, `monochrome` → `chunk-filled`, `search` → `web`, `default` → `auto`, `builtin` → `host-native`), but new files should write canonical ids. For `recommend.image_usage`, do not write bare `"custom"`; if several image sources are mixed, write the concrete prose plan directly, such as `"封面用 AI 生成,产品页用用户素材,行业页用网络来源"` / `"AI cover + user product assets + web industry images"`.
|
||||
- When `recommend.image_usage` is `ai` or a custom plan that includes AI, also set `recommend.image_ai_path` to one of `auto` / `api` / `host-native` / `manual`; the page presents these as explicit choices.
|
||||
- For a custom image plan, the page treats "may include AI" as true only when the recommendation includes `recommend.image_ai_path` or `image_strategy.candidates`; a custom plan without those signals is handled as non-AI and omits AI controls / fields.
|
||||
- **Color candidates carry the user-facing core `palette`**: `background`, `secondary_bg`, `primary`, `accent`, `secondary_accent`, and `body_text`. The page renders labelled swatches and offers per-role override inputs. Legacy `text` is accepted as an alias for `body_text`, but new files should write `body_text`. Strategist derives secondary text, borders, state colors, and visual-style neutral tiers later when writing `design_spec.md` / `spec_lock.md`; those are not user-facing confirmation choices.
|
||||
- **Candidate display text may be bilingual**: color / typography candidates can provide `name_zh` / `name_en` and `note_zh` / `note_en`; the page falls back to legacy `name` / `note`.
|
||||
- **Typography candidates split CJK and Latin** for both `heading` and `body`; `css` is the preview `font-family` stack. Each candidate should also include `body_size` (the body baseline in px; common values are 18 and 24, but any reasonable integer is valid). The page exposes `body_size` as an editable numeric field, and also offers a custom typography text box so the user is not limited to the proposed candidates.
|
||||
- **Typography candidates split CJK and Latin** for both `heading` and `body`; `css` is the fallback preview `font-family` stack. The page previews CJK sample text with `cjk + css` and Latin sample text with `latin + css`, so the two script choices are visible independently. Each candidate should also include `body_size` (the body baseline in px; common values are 18 and 24, but any reasonable integer is valid). The page exposes `body_size` as an editable numeric field, and also offers a custom typography text box so the user is not limited to the proposed candidates.
|
||||
- **Combined style preview** — a compact live "overall impression" strip sits just above the color section and is **sticky**: it pins under the topbar so it stays visible while the user scrolls through the color / icon / typography sections, keeping the picking controls and their combined effect on screen together. It applies the currently selected color palette **and** typography (heading sample in `primary` over `background`, body sample in `body_text`, an `accent` bar, a `secondary_bg` chip) and repaints on every color / HEX-override / font / `body_size` change. It does not replace the per-candidate swatches or font samples (those stay for picking); it is deliberately an abstract style chip, **not** a slide-layout preview — page layout preview remains the live-preview server's job (Step 6). No schema field; it derives entirely from the existing color + typography selections.
|
||||
- **Generated image style candidates** live in `image_strategy.candidates` and are shown only when `image_usage` is `ai` or a custom image plan may include generated images. Each candidate records `rendering`, `palette`, and short `visual` / `color` / `mood` lines from Strategist h.5. The chosen value is written to `result.json.image_strategy`; it is omitted when generated images are not part of the plan.
|
||||
- `recommend.generation_mode` and `refine_spec` mirror the two mandatory notes in SKILL.md Step 4. Confirmed `generation_mode: "split"` / `refine_spec: true` are explicit user choices, equivalent to opting in through chat.
|
||||
- `lang` is a soft default; an explicit user language choice in the page (persisted to `localStorage`) wins.
|
||||
|
||||
+6
-1
@@ -340,7 +340,12 @@ VECTOR_FIGURE_DPI = 180
|
||||
VECTOR_CAPTION_SEARCH_HEIGHT = 380
|
||||
VECTOR_CAPTION_HORIZONTAL_GAP = 90
|
||||
MAX_VECTOR_BACKGROUND_AREA_RATIO = 1.9
|
||||
FIGURE_CAPTION_RE = re.compile(r'^(?:Figure|Fig\.)\s*\d+\s*[:.]', re.IGNORECASE)
|
||||
# Caption delimiters: ``Figure 1:`` / ``Figure 1.`` (classic) and ``Figure 1 |``
|
||||
# (the DeepMind / Distill / Nature house style used by many ML papers, incl.
|
||||
# full-width ``|``). Without the pipe variants, captioned vector figures route
|
||||
# to the generic per-drawing fallback, which discards fine-grained line plots
|
||||
# (each plot is hundreds of tiny primitives, none large enough on its own).
|
||||
FIGURE_CAPTION_RE = re.compile(r'^(?:Figure|Fig\.?)\s*\d+\s*[:.||]', re.IGNORECASE)
|
||||
|
||||
|
||||
def should_keep_image(
|
||||
|
||||
+6
-1
@@ -229,9 +229,14 @@ html, body {
|
||||
|
||||
#svg-content svg {
|
||||
display: block;
|
||||
/* Both dimensions auto so the box hugs the slide at any aspect ratio: with
|
||||
only height:auto, portrait formats (e.g. 1242x1660) keep their attribute
|
||||
width while max-height shrinks the height, leaving large white bands
|
||||
inside the white card. width:auto lets the element scale proportionally. */
|
||||
width: auto;
|
||||
height: auto;
|
||||
max-width: 100%;
|
||||
max-height: calc(100vh - 72px);
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* ---------- SVG element interaction states ---------- */
|
||||
|
||||
@@ -17,7 +17,7 @@ if __package__ in {None, ''}:
|
||||
sys.modules.setdefault('svg_to_pptx', package)
|
||||
__package__ = 'svg_to_pptx'
|
||||
|
||||
from .pptx_dimensions import CANVAS_FORMATS, get_project_info
|
||||
from .pptx_dimensions import CANVAS_FORMATS, get_project_info, get_viewbox_dimensions
|
||||
from .pptx_discovery import find_svg_files, find_notes_files
|
||||
from .pptx_builder import create_pptx_with_native_svg
|
||||
from .pptx_narration import NARRATION_EXTENSIONS, find_narration_files, probe_audio_duration
|
||||
@@ -334,6 +334,28 @@ Recorded narration:
|
||||
|
||||
verbose = not args.quiet
|
||||
|
||||
# Honor the actual SVG pixels over a stale project-recorded format. The
|
||||
# canvas_format read from project init can disagree with what the Executor
|
||||
# actually drew — e.g. a mirror template imported at 2560×1440 while the
|
||||
# project was initialized as ppt169 (1280×720). When the first SVG's real
|
||||
# viewBox doesn't match the recorded format's dimensions, drop the format
|
||||
# so the builder sizes the slide by pixels (custom_pixels path). Standard
|
||||
# decks match exactly, so this only changes behavior on the conflict case.
|
||||
# An explicit --format always wins and is never second-guessed.
|
||||
if args.format is None and canvas_format:
|
||||
fmt_info = CANVAS_FORMATS.get(canvas_format)
|
||||
actual_dims = get_viewbox_dimensions(ref_files[0])
|
||||
if fmt_info and actual_dims:
|
||||
fmt_dims = (fmt_info.get('width'), fmt_info.get('height'))
|
||||
if fmt_dims != actual_dims:
|
||||
if verbose:
|
||||
print(
|
||||
f" Recorded format '{canvas_format}' "
|
||||
f"({fmt_dims[0]}×{fmt_dims[1]}) differs from SVG viewBox "
|
||||
f"({actual_dims[0]}×{actual_dims[1]}); exporting by SVG pixels"
|
||||
)
|
||||
canvas_format = None
|
||||
|
||||
enable_notes = not args.no_notes
|
||||
notes: dict[str, str] = {}
|
||||
if enable_notes:
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"sourceId": "shadcn",
|
||||
"repo": "https://github.com/shadcn-ui/ui.git",
|
||||
"ref": "main",
|
||||
"commit": "c879483c9694d7b135ed1efe8f73233d924ae551",
|
||||
"commit": "c2ddedf5d24e0cee05936c69e1f199a9975a07b2",
|
||||
"adapter": "claude-skill",
|
||||
"sourcePath": "skills/shadcn",
|
||||
"syncedAt": "2026-06-17T07:05:29Z"
|
||||
"syncedAt": "2026-06-18T16:00:00Z"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user