@taloswork/ose-shared 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/package.json +3 -3
  2. package/skills/document/SKILL.md +32 -17
  3. package/skills/document/art-directions/base.css +23 -0
  4. package/skills/document/art-directions/cinematic-depth.css +6 -0
  5. package/skills/document/art-directions/collage-paper.css +8 -0
  6. package/skills/document/art-directions/editorial-cut.css +8 -0
  7. package/skills/document/art-directions/geometric-pop.css +9 -0
  8. package/skills/document/art-directions/gradient-mesh.css +6 -0
  9. package/skills/document/art-directions/illustrated-doodle.css +8 -0
  10. package/skills/document/art-directions/luxury-ornament.css +6 -0
  11. package/skills/document/art-directions/maximalist-blocks.css +9 -0
  12. package/skills/document/art-directions/technical-grid.css +6 -0
  13. package/skills/document/assets/fonts/fonts.json +2 -2
  14. package/skills/document/bin/deck-build.cjs +266 -10
  15. package/skills/document/bin/doc-convert.cjs +66 -28
  16. package/skills/document/bin/doc-gen.cjs +8 -9
  17. package/skills/document/bin/html2pptx.cjs +243 -24
  18. package/skills/document/bin/pptx-clone.cjs +41 -30
  19. package/skills/document/bin/pptx-embed-fonts.cjs +6 -2
  20. package/skills/document/bin/pptx-slide.cjs +46 -50
  21. package/skills/document/catalog.json +11 -0
  22. package/skills/document/components/base.css +45 -0
  23. package/skills/document/components/profiles/clean.css +2 -0
  24. package/skills/document/components/profiles/editorial.css +3 -0
  25. package/skills/document/components/profiles/glass.css +2 -0
  26. package/skills/document/components/profiles/luxury.css +3 -0
  27. package/skills/document/components/profiles/organic.css +2 -0
  28. package/skills/document/components/profiles/poster.css +3 -0
  29. package/skills/document/content-pipeline.md +110 -54
  30. package/skills/document/deck-design.md +24 -15
  31. package/skills/document/layouts/README.md +41 -12
  32. package/skills/document/layouts/blocks-2x2.tpl +19 -9
  33. package/skills/document/layouts/business-model.tpl +6 -0
  34. package/skills/document/layouts/cards-3.tpl +1 -1
  35. package/skills/document/layouts/customer-proof.tpl +6 -0
  36. package/skills/document/layouts/funding-ask.tpl +5 -0
  37. package/skills/document/layouts/market-rings.tpl +5 -0
  38. package/skills/document/layouts/org-tree.tpl +1 -1
  39. package/skills/document/layouts/pitch-problem.tpl +6 -0
  40. package/skills/document/layouts/pitch-solution.tpl +5 -0
  41. package/skills/document/layouts/pricing-tiers.tpl +5 -0
  42. package/skills/document/layouts/product-device.tpl +6 -0
  43. package/skills/document/layouts/profiles.json +24 -0
  44. package/skills/document/layouts/slots.json +1 -1
  45. package/skills/document/layouts/steps-vertical.tpl +1 -1
  46. package/skills/document/layouts/team-founders.tpl +5 -0
  47. package/skills/document/layouts/traction-milestones.tpl +6 -0
  48. package/skills/document/media-styles/cinematic.css +2 -0
  49. package/skills/document/media-styles/clean-product.css +2 -0
  50. package/skills/document/media-styles/cutout-collage.css +3 -0
  51. package/skills/document/media-styles/device-stage.css +3 -0
  52. package/skills/document/media-styles/editorial-photo.css +3 -0
  53. package/skills/document/media-styles/illustrated.css +2 -0
  54. package/skills/document/media-styles/spatial-3d.css +3 -0
  55. package/skills/document/motion/mckinsey-photo.json +22 -0
  56. package/skills/document/motion/prismatic-3d.json +31 -0
  57. package/skills/document/packs/README.md +7 -3
  58. package/skills/document/packs/mckinsey-photo/backdrop.html +6 -0
  59. package/skills/document/packs/mckinsey-photo/layouts/cover-image.tpl +5 -0
  60. package/skills/document/packs/mckinsey-photo/layouts/cover.tpl +5 -0
  61. package/skills/document/packs/mckinsey-photo/layouts/image-left.tpl +5 -0
  62. package/skills/document/packs/mckinsey-photo/layouts/numbered-brief.tpl +5 -0
  63. package/skills/document/packs/mckinsey-photo/pack.css +27 -0
  64. package/skills/document/packs/prismatic-3d/backdrop.html +4 -0
  65. package/skills/document/packs/prismatic-3d/layouts/big-stat.tpl +6 -0
  66. package/skills/document/packs/prismatic-3d/layouts/cover.tpl +9 -0
  67. package/skills/document/packs/prismatic-3d/layouts/section-image.tpl +6 -0
  68. package/skills/document/packs/prismatic-3d/pack.css +28 -0
  69. package/skills/document/recipes/agency-pitch.json +13 -0
  70. package/skills/document/recipes/product-launch.json +13 -0
  71. package/skills/document/recipes/startup-fundraising.json +19 -0
  72. package/skills/document/styles/README.md +13 -6
  73. package/skills/document/styles/_contract-defaults.css +1 -1
  74. package/skills/document/styles/_samples/_groups/biz-premium.webp +0 -0
  75. package/skills/document/styles/_samples/_groups/creative.webp +0 -0
  76. package/skills/document/styles/_samples/_groups/editorial.webp +0 -0
  77. package/skills/document/styles/_samples/_groups/minimal.webp +0 -0
  78. package/skills/document/styles/_samples/_groups/natural.webp +0 -0
  79. package/skills/document/styles/_samples/_groups/tech-future.webp +0 -0
  80. package/skills/document/styles/_samples/bistro-warm.webp +0 -0
  81. package/skills/document/styles/_samples/board-brief.webp +0 -0
  82. package/skills/document/styles/_samples/bold-clash.webp +0 -0
  83. package/skills/document/styles/_samples/campus-teal.webp +0 -0
  84. package/skills/document/styles/_samples/clinic-care.webp +0 -0
  85. package/skills/document/styles/_samples/dark-glass.webp +0 -0
  86. package/skills/document/styles/_samples/gov-brief.webp +0 -0
  87. package/skills/document/styles/_samples/grid-tech.webp +0 -0
  88. package/skills/document/styles/_samples/legal-slate.webp +0 -0
  89. package/skills/document/styles/_samples/mckinsey-photo.webp +0 -0
  90. package/skills/document/styles/_samples/memphis.webp +0 -0
  91. package/skills/document/styles/_samples/neon-cyber.webp +0 -0
  92. package/skills/document/styles/_samples/organic-earth.webp +0 -0
  93. package/skills/document/styles/_samples/pastel-saas.webp +0 -0
  94. package/skills/document/styles/_samples/portfolio-noir.webp +0 -0
  95. package/skills/document/styles/_samples/press-room.webp +0 -0
  96. package/skills/document/styles/_samples/prismatic-3d.webp +0 -0
  97. package/skills/document/styles/_samples/retro-press.webp +0 -0
  98. package/skills/document/styles/_samples/sport-arena.webp +0 -0
  99. package/skills/document/styles/_samples/wedding-blush.webp +0 -0
  100. package/skills/document/styles/dark-glass/backdrop.html +7 -3
  101. package/skills/document/styles/dark-glass/skin.css +1 -0
  102. package/skills/document/styles/portfolio-noir/skin.css +3 -0
  103. package/skills/document/theme-system.json +68 -0
  104. package/skills/document/theme-system.md +101 -0
@@ -9820,11 +9820,40 @@ var import_promises = require("node:fs/promises");
9820
9820
 
9821
9821
  // packages/core/dist/document/pptx-slide.js
9822
9822
  var import_jszip = __toESM(require_lib3(), 1);
9823
+
9824
+ // packages/core/dist/document/pptx-register-slide.js
9823
9825
  var SLIDE_CT = "application/vnd.openxmlformats-officedocument.presentationml.slide+xml";
9824
9826
  var SLIDE_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide";
9827
+ async function text(zip, path) {
9828
+ const f = zip.file(path);
9829
+ if (!f)
9830
+ throw new Error(`not a PowerPoint file (${path} missing)`);
9831
+ return f.async("string");
9832
+ }
9833
+ async function registerSlidePart(zip, newNum, newXmlPath) {
9834
+ const ctPath = "[Content_Types].xml";
9835
+ const ct = await text(zip, ctPath);
9836
+ if (!ct.includes(`PartName="/${newXmlPath}"`)) {
9837
+ zip.file(ctPath, ct.replace("</Types>", `<Override PartName="/${newXmlPath}" ContentType="${SLIDE_CT}"/></Types>`));
9838
+ }
9839
+ const presRelsPath = "ppt/_rels/presentation.xml.rels";
9840
+ const presRels = await text(zip, presRelsPath);
9841
+ const maxRid = Math.max(0, ...[...presRels.matchAll(/Id="rId(\d+)"/g)].map((m) => Number(m[1])));
9842
+ const newRid = `rId${maxRid + 1}`;
9843
+ zip.file(presRelsPath, presRels.replace("</Relationships>", `<Relationship Id="${newRid}" Type="${SLIDE_REL_TYPE}" Target="slides/slide${newNum}.xml"/></Relationships>`));
9844
+ const presPath = "ppt/presentation.xml";
9845
+ const pres = await text(zip, presPath);
9846
+ const maxSldId = Math.max(255, ...[...pres.matchAll(/<p:sldId id="(\d+)"/g)].map((m) => Number(m[1])));
9847
+ const updated = pres.replace("</p:sldIdLst>", `<p:sldId id="${maxSldId + 1}" r:id="${newRid}"/></p:sldIdLst>`);
9848
+ if (updated === pres)
9849
+ throw new Error("could not locate the slide list in presentation.xml");
9850
+ zip.file(presPath, updated);
9851
+ }
9852
+
9853
+ // packages/core/dist/document/pptx-slide.js
9825
9854
  var LAYOUT_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout";
9826
9855
  var BLANK_SLIDE_XML = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"><p:cSld><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr/></p:spTree></p:cSld><p:clrMapOvr><a:masterClrMapping/></p:clrMapOvr></p:sld>';
9827
- async function text(zip, path) {
9856
+ async function text2(zip, path) {
9828
9857
  const f = zip.file(path);
9829
9858
  if (!f)
9830
9859
  throw new Error(`not a PowerPoint file (${path} missing)`);
@@ -9872,29 +9901,14 @@ async function addBlankSlide(bytes, opts = {}) {
9872
9901
  zip.file(newXmlPath, BLANK_SLIDE_XML);
9873
9902
  zip.file(`ppt/slides/_rels/slide${newNum}.xml.rels`, `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
9874
9903
  <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="${LAYOUT_REL_TYPE}" Target="../slideLayouts/slideLayout${layout}.xml"/></Relationships>`);
9875
- const ct = await text(zip, "[Content_Types].xml");
9876
- if (!ct.includes(`PartName="/${newXmlPath}"`)) {
9877
- zip.file("[Content_Types].xml", ct.replace("</Types>", `<Override PartName="/${newXmlPath}" ContentType="${SLIDE_CT}"/></Types>`));
9878
- }
9879
- const presRelsPath = "ppt/_rels/presentation.xml.rels";
9880
- const presRels = await text(zip, presRelsPath);
9881
- const maxRid = Math.max(0, ...[...presRels.matchAll(/Id="rId(\d+)"/g)].map((m) => Number(m[1])));
9882
- const newRid = `rId${maxRid + 1}`;
9883
- zip.file(presRelsPath, presRels.replace("</Relationships>", `<Relationship Id="${newRid}" Type="${SLIDE_REL_TYPE}" Target="slides/slide${newNum}.xml"/></Relationships>`));
9884
- const presPath = "ppt/presentation.xml";
9885
- const pres = await text(zip, presPath);
9886
- const maxSldId = Math.max(255, ...[...pres.matchAll(/<p:sldId id="(\d+)"/g)].map((m) => Number(m[1])));
9887
- const updated = pres.replace("</p:sldIdLst>", `<p:sldId id="${maxSldId + 1}" r:id="${newRid}"/></p:sldIdLst>`);
9888
- if (updated === pres)
9889
- throw new Error("could not locate the slide list in presentation.xml");
9890
- zip.file(presPath, updated);
9904
+ await registerSlidePart(zip, newNum, newXmlPath);
9891
9905
  const out2 = await zip.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
9892
9906
  return { bytes: new Uint8Array(out2), newSlideNumber: newNum, layout };
9893
9907
  }
9894
9908
  async function reorderPptxSlides(bytes, order) {
9895
9909
  const zip = await load(bytes);
9896
9910
  const presPath = "ppt/presentation.xml";
9897
- const pres = await text(zip, presPath);
9911
+ const pres = await text2(zip, presPath);
9898
9912
  const listMatch = /(<p:sldIdLst\b[^>]*>)([\s\S]*?)(<\/p:sldIdLst>)/.exec(pres);
9899
9913
  if (!listMatch)
9900
9914
  throw new Error("no <p:sldIdLst> in presentation.xml (corrupt deck?)");
@@ -9965,9 +9979,9 @@ function unescXml(s) {
9965
9979
  }
9966
9980
  });
9967
9981
  }
9968
- function visualWidth(text3) {
9982
+ function visualWidth(text4) {
9969
9983
  let w = 0;
9970
- for (const ch of text3) {
9984
+ for (const ch of text4) {
9971
9985
  const cp = ch.codePointAt(0);
9972
9986
  if (ch === " " || ch === " ")
9973
9987
  w += 0.35;
@@ -9996,9 +10010,9 @@ function replaceTextAcrossRuns(xml, find, replacement, opts) {
9996
10010
  const segs = [];
9997
10011
  let flat = "";
9998
10012
  for (const m of para.matchAll(textRe)) {
9999
- const text3 = unescXml(m[2]);
10000
- segs.push({ el: m[0], attrs: m[1] ?? "", text: text3, start: flat.length });
10001
- flat += text3;
10013
+ const text4 = unescXml(m[2]);
10014
+ segs.push({ el: m[0], attrs: m[1] ?? "", text: text4, start: flat.length });
10015
+ flat += text4;
10002
10016
  }
10003
10017
  if (!flat.includes(find))
10004
10018
  return para;
@@ -10042,8 +10056,6 @@ function replaceTextAcrossRuns(xml, find, replacement, opts) {
10042
10056
  }
10043
10057
 
10044
10058
  // packages/core/dist/document/pptx-clone.js
10045
- var SLIDE_CT2 = "application/vnd.openxmlformats-officedocument.presentationml.slide+xml";
10046
- var SLIDE_REL_TYPE2 = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide";
10047
10059
  var EMU_PER_PT = 12700;
10048
10060
  function capacityWarning(slideXml, token, value) {
10049
10061
  const idx = slideXml.indexOf(token);
@@ -10076,7 +10088,7 @@ function capacityWarning(slideXml, token, value) {
10076
10088
  return void 0;
10077
10089
  return `${token}: replacement is ~${Math.ceil(width)} width-units, slot fits ~${Math.floor(capacity)} (${Math.round(widthPt)}\xD7${Math.round(heightPt)}pt box @ ${fontPt}pt) \u2014 likely overflow. Shorten the wording; do NOT shrink the font to make it fit.`;
10078
10090
  }
10079
- async function text2(zip, path) {
10091
+ async function text3(zip, path) {
10080
10092
  const f = zip.file(path);
10081
10093
  if (!f)
10082
10094
  throw new Error(`not a PowerPoint file (${path} missing)`);
@@ -10101,7 +10113,7 @@ async function clonePptxSlide(bytes, slideNumber, replacements = {}) {
10101
10113
  const newNum = Math.max(...slideNums) + 1;
10102
10114
  const srcXmlPath = `ppt/slides/slide${slideNumber}.xml`;
10103
10115
  const newXmlPath = `ppt/slides/slide${newNum}.xml`;
10104
- let slideXml = await text2(zip, srcXmlPath);
10116
+ let slideXml = await text3(zip, srcXmlPath);
10105
10117
  const replaced = [];
10106
10118
  const missing = [];
10107
10119
  const warnings = [];
@@ -10129,23 +10141,7 @@ async function clonePptxSlide(bytes, slideNumber, replacements = {}) {
10129
10141
  const rels = removeElement(await srcRels.async("string"), "Relationship", (el) => /Type="[^"]*\/notesSlide"/.test(el)).xml;
10130
10142
  zip.file(`ppt/slides/_rels/slide${newNum}.xml.rels`, rels);
10131
10143
  }
10132
- const ctPath = "[Content_Types].xml";
10133
- const ct = await text2(zip, ctPath);
10134
- if (!ct.includes(`PartName="/${newXmlPath}"`)) {
10135
- zip.file(ctPath, ct.replace("</Types>", `<Override PartName="/${newXmlPath}" ContentType="${SLIDE_CT2}"/></Types>`));
10136
- }
10137
- const presRelsPath = "ppt/_rels/presentation.xml.rels";
10138
- const presRels = await text2(zip, presRelsPath);
10139
- const maxRid = Math.max(0, ...[...presRels.matchAll(/Id="rId(\d+)"/g)].map((m) => Number(m[1])));
10140
- const newRid = `rId${maxRid + 1}`;
10141
- zip.file(presRelsPath, presRels.replace("</Relationships>", `<Relationship Id="${newRid}" Type="${SLIDE_REL_TYPE2}" Target="slides/slide${newNum}.xml"/></Relationships>`));
10142
- const presPath = "ppt/presentation.xml";
10143
- const pres = await text2(zip, presPath);
10144
- const maxSldId = Math.max(255, ...[...pres.matchAll(/<p:sldId id="(\d+)"/g)].map((m) => Number(m[1])));
10145
- const updated = pres.replace("</p:sldIdLst>", `<p:sldId id="${maxSldId + 1}" r:id="${newRid}"/></p:sldIdLst>`);
10146
- if (updated === pres)
10147
- throw new Error("could not locate the slide list in presentation.xml");
10148
- zip.file(presPath, updated);
10144
+ await registerSlidePart(zip, newNum, newXmlPath);
10149
10145
  const out2 = await zip.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
10150
10146
  return { bytes: new Uint8Array(out2), newSlideNumber: newNum, replaced, missing, warnings };
10151
10147
  }
@@ -10170,9 +10166,9 @@ async function removePptxSlide(bytes, slideNumber) {
10170
10166
  const xmlPath = `ppt/slides/slide${slideNumber}.xml`;
10171
10167
  const relsPath = `ppt/slides/_rels/slide${slideNumber}.xml.rels`;
10172
10168
  const presRelsPath = "ppt/_rels/presentation.xml.rels";
10173
- const presRels = await text2(zip, presRelsPath);
10169
+ const presRels = await text3(zip, presRelsPath);
10174
10170
  const targetRe = new RegExp(`Target="(/ppt/)?slides/slide${slideNumber}\\.xml"`);
10175
- const relMatch = findElement(presRels, "Relationship", (r) => r.includes(SLIDE_REL_TYPE2) && targetRe.test(r));
10171
+ const relMatch = findElement(presRels, "Relationship", (r) => r.includes(SLIDE_REL_TYPE) && targetRe.test(r));
10176
10172
  if (!relMatch)
10177
10173
  throw new Error(`slide ${slideNumber} is not wired into presentation.xml.rels (corrupt deck?)`);
10178
10174
  const rId = /Id="(rId\d+)"/.exec(relMatch)?.[1];
@@ -10180,19 +10176,19 @@ async function removePptxSlide(bytes, slideNumber) {
10180
10176
  throw new Error(`slide ${slideNumber}'s relationship carries no Id (corrupt deck?)`);
10181
10177
  zip.file(presRelsPath, presRels.replace(relMatch, ""));
10182
10178
  const presPath = "ppt/presentation.xml";
10183
- const pres = await text2(zip, presPath);
10179
+ const pres = await text3(zip, presPath);
10184
10180
  const sld = removeElement(pres, "p:sldId", (el) => el.includes(`r:id="${rId}"`));
10185
10181
  if (!sld.removed)
10186
10182
  throw new Error(`slide ${slideNumber} (${rId}) not found in the slide list (corrupt deck?)`);
10187
10183
  zip.file(presPath, sld.xml);
10188
- const slideRels = zip.file(relsPath) ? await text2(zip, relsPath) : "";
10184
+ const slideRels = zip.file(relsPath) ? await text3(zip, relsPath) : "";
10189
10185
  const notesTarget = /<Relationship\b[^>]*Type="[^"]*\/notesSlide"[^>]*Target="\.\.\/(notesSlides\/notesSlide\d+\.xml)"/.exec(slideRels)?.[1];
10190
10186
  const removedParts = [xmlPath, relsPath];
10191
10187
  if (notesTarget) {
10192
10188
  const otherSlideRels = Object.keys(zip.files).filter((p) => /^ppt\/slides\/_rels\/slide\d+\.xml\.rels$/.test(p) && p !== relsPath);
10193
10189
  let shared = false;
10194
10190
  for (const p of otherSlideRels) {
10195
- if ((await text2(zip, p)).includes(notesTarget)) {
10191
+ if ((await text3(zip, p)).includes(notesTarget)) {
10196
10192
  shared = true;
10197
10193
  break;
10198
10194
  }
@@ -10200,7 +10196,7 @@ async function removePptxSlide(bytes, slideNumber) {
10200
10196
  if (!shared)
10201
10197
  removedParts.push(`ppt/${notesTarget}`, `ppt/notesSlides/_rels/${notesTarget.split("/")[1]}.rels`);
10202
10198
  }
10203
- let ct = await text2(zip, "[Content_Types].xml");
10199
+ let ct = await text3(zip, "[Content_Types].xml");
10204
10200
  for (const part of removedParts) {
10205
10201
  zip.remove(part);
10206
10202
  ct = removeElement(ct, "Override", (el) => el.includes(`PartName="/${part}"`)).xml;
@@ -7,13 +7,24 @@
7
7
  {"id":"creative","name_cn":"创意手作","sample":"styles/_samples/_groups/creative.webp","scenarios":["创意","教学","工作坊","头脑风暴"]},
8
8
  {"id":"natural","name_cn":"自然温和","sample":"styles/_samples/_groups/natural.webp","scenarios":["品牌手册","可持续","美妆","零售"]}
9
9
  ],
10
+ "collections": [
11
+ {
12
+ "id": "original-13",
13
+ "name_cn": "初代经典",
14
+ "renderMode": "classic-v1",
15
+ "sourceCommit": "7306d8e5",
16
+ "entryIds": ["cool", "swiss-grid", "black-gold", "olive-gold", "mckinsey", "apple", "mono", "ivory-gold", "magazine-ink", "anthropic", "hand-drawn", "cinereel", "branding"]
17
+ }
18
+ ],
10
19
  "entries": [
11
20
  {"id":"cool","kind":"skin","group":"tech-future","name_cn":"酷炫深色","oneliner":"深蓝辉光 · 科技发布","scenarios":["科技","发布","pitch","主题演讲"],"mode":"dark","sample":"styles/_samples/cool.webp","motion":"motion/cool.json"},
12
21
  {"id":"neon-cyber","kind":"skin","group":"tech-future","name_cn":"霓虹赛博","oneliner":"霓虹品红 · 赛博夜场","scenarios":["科技","游戏","Web3","夜场发布"],"mode":"dark","sample":"styles/_samples/neon-cyber.webp","motion":"motion/neon-cyber.json"},
22
+ {"id":"prismatic-3d","kind":"pack","group":"tech-future","name_cn":"棱镜空间","oneliner":"紫黑棱镜 · 立体发布","scenarios":["科技发布","AI 产品","创意提案","未来趋势"],"mode":"dark","sample":"styles/_samples/prismatic-3d.webp","motion":"motion/prismatic-3d.json"},
13
23
  {"id":"swiss-grid","kind":"skin","group":"tech-future","name_cn":"瑞士国际","oneliner":"克莱因蓝白 · 瑞士国际","scenarios":["科技产品","数据汇报","设计","年度总结"],"mode":"light","sample":"styles/_samples/swiss-grid.webp","motion":"motion/swiss-grid.json"},
14
24
  {"id":"black-gold","kind":"skin","group":"biz-premium","name_cn":"黑金","oneliner":"纯黑金辉 · 高端商务","scenarios":["金融","年报","路演","奢侈品"],"mode":"dark","sample":"styles/_samples/black-gold.webp","motion":"motion/black-gold.json"},
15
25
  {"id":"olive-gold","kind":"skin","group":"biz-premium","name_cn":"橄榄金","oneliner":"橄榄亮金 · 高端招商","scenarios":["高端","证书","招商","品牌"],"mode":"dark","sample":"styles/_samples/olive-gold.webp","motion":"motion/olive-gold.json"},
16
26
  {"id":"mckinsey","kind":"skin","group":"biz-premium","name_cn":"白底咨询","oneliner":"白底藏青 · 咨询评审","scenarios":["咨询","战略汇报","评审","董事会"],"mode":"light","sample":"styles/_samples/mckinsey.webp","motion":"motion/mckinsey.json"},
27
+ {"id":"mckinsey-photo","kind":"pack","group":"biz-premium","name_cn":"咨询影像","oneliner":"藏青影像 · 企业叙事","scenarios":["咨询","企业战略","案例研究","年度回顾"],"mode":"light","sample":"styles/_samples/mckinsey-photo.webp","motion":"motion/mckinsey-photo.json"},
17
28
  {"id":"apple","kind":"skin","group":"minimal","name_cn":"纯黑极简","oneliner":"纯黑极简 · 产品发布","scenarios":["产品发布","主题演讲","极简演示"],"mode":"dark","sample":"styles/_samples/apple.webp","motion":"motion/apple.json"},
18
29
  {"id":"mono","kind":"skin","group":"minimal","name_cn":"纯白黑灰","oneliner":"纯白黑灰 · 极简设计","scenarios":["极简","高端","设计","单色报告"],"mode":"light","sample":"styles/_samples/mono.webp","motion":"motion/mono.json"},
19
30
  {"id":"ivory-gold","kind":"skin","group":"minimal","name_cn":"奶油青铜","oneliner":"奶油青铜 · 奢华极简","scenarios":["奢华极简","品牌手册","路演","高端零售"],"mode":"light","sample":"styles/_samples/ivory-gold.webp","motion":"motion/ivory-gold.json"},
@@ -0,0 +1,45 @@
1
+ /* Theme V2 semantic component contract. Enabled only by --theme-v2. */
2
+ :root{
3
+ --cmp-radius:var(--card-radius,4px);--cmp-border:var(--hair);--cmp-surface:transparent;
4
+ --cmp-surface-strong:var(--title);--cmp-on-strong:var(--slide-bg);--cmp-shadow:none;
5
+ --cmp-pad:24px;--cmp-gap:18px;--cmp-line:1px;--cmp-media-radius:var(--cmp-radius);
6
+ --cmp-media-fit:cover;--cmp-media-filter:none;--cmp-media-shadow:none;
7
+ }
8
+ .component-card,.metric-card,.proof-card,.price-card,.team-card,.feature-card,.fund-card{
9
+ position:relative;background:var(--cmp-surface);border:var(--cmp-line) solid var(--cmp-border);
10
+ border-radius:var(--cmp-radius);box-shadow:var(--cmp-shadow);padding:var(--cmp-pad);overflow:hidden;
11
+ }
12
+ .component-card--strong,.metric-card--hero,.price-card--featured{
13
+ background:var(--cmp-surface-strong);border-color:var(--cmp-surface-strong);color:var(--cmp-on-strong);
14
+ }
15
+ .component-card--strong :is(h2,h3,p,span),.metric-card--hero :is(h2,h3,p,span),.price-card--featured :is(h2,h3,p,span){color:inherit}
16
+ .metric-value{font-family:var(--font-head);font-size:calc(44px * var(--type-scale,1));font-weight:800;letter-spacing:-.04em;line-height:1}
17
+ .metric-label,.component-label,.media-caption{color:var(--muted);font-size:calc(13px * var(--type-scale,1));letter-spacing:.08em;text-transform:uppercase}
18
+ .component-title{font-family:var(--font-head);color:var(--title);font-weight:750;line-height:1.15}
19
+ .component-copy{color:var(--body);line-height:1.55}
20
+ .badge,.tag,.pill{display:inline-flex;align-items:center;width:max-content;border:var(--cmp-line) solid var(--cmp-border);border-radius:999px;padding:5px 11px;font-size:calc(12px * var(--type-scale,1));font-weight:700}
21
+ .badge--accent,.tag--accent{background:var(--accent);border-color:var(--accent);color:var(--slide-bg)}
22
+ .media-frame,.media-cutout,.device-frame{position:relative;overflow:hidden;border:var(--cmp-line) solid var(--cmp-border);border-radius:var(--cmp-media-radius);box-shadow:var(--cmp-media-shadow);background:var(--card-bg,transparent)}
23
+ .media-frame>img,.media-cutout>img,.device-frame>img{width:100%;height:100%;object-fit:var(--cmp-media-fit);filter:var(--cmp-media-filter)}
24
+ .quote-mark{font-family:var(--font-head);color:var(--accent);font-size:calc(64px * var(--type-scale,1));line-height:.6}
25
+ .quote-copy{font-family:var(--font-head);font-size:calc(26px * var(--type-scale,1));line-height:1.32;color:var(--title)}
26
+ .logo-tile{display:flex;align-items:center;justify-content:center;min-height:62px;border:var(--cmp-line) solid var(--cmp-border);border-radius:var(--cmp-radius);color:var(--muted);font-weight:700}
27
+ .progress-track,.fund-track{height:10px;background:var(--hair);border-radius:999px;overflow:hidden}
28
+ .progress-fill,.fund-fill{height:100%;background:var(--accent);border-radius:inherit}
29
+ .timeline-node{width:14px;height:14px;border-radius:50%;background:var(--accent);box-shadow:0 0 0 5px var(--cmp-surface)}
30
+ .avatar-frame{overflow:hidden;border-radius:50%;border:var(--cmp-line) solid var(--cmp-border)}
31
+ .avatar-frame>img{width:100%;height:100%;object-fit:cover}
32
+ .data-chip{font-family:var(--font-head);font-variant-numeric:tabular-nums;color:var(--accent);font-weight:800}
33
+ .component-grid{display:grid;gap:var(--cmp-gap)}
34
+ .chart-shell,.table-shell,.callout,.workflow-node{background:var(--cmp-surface);border:var(--cmp-line) solid var(--cmp-border);border-radius:var(--cmp-radius);box-shadow:var(--cmp-shadow)}
35
+ .chart-shell,.table-shell{padding:var(--cmp-pad)}
36
+ .chart-shell .chart{background:transparent}
37
+ .legend-item{display:flex;align-items:center;gap:8px;color:var(--muted);font-size:calc(13px * var(--type-scale,1))}
38
+ .legend-item:before{content:"";width:9px;height:9px;border-radius:50%;background:var(--accent)}
39
+ .table-shell .dt-head{background:color-mix(in srgb,var(--accent) 10%,transparent);border-bottom:var(--cmp-line) solid var(--cmp-border)}
40
+ .table-shell .dt-row{border-bottom:var(--cmp-line) solid var(--hair)}
41
+ .callout{padding:16px 18px;border-left:4px solid var(--accent)}
42
+ .workflow-node{padding:18px;text-align:center}
43
+ .workflow-connector{height:var(--cmp-line);background:var(--cmp-border)}
44
+ .cta-block{background:var(--accent);color:var(--slide-bg);border-radius:var(--cmp-radius);padding:var(--cmp-pad)}
45
+ .cta-block :is(h2,h3,p,span){color:inherit}
@@ -0,0 +1,2 @@
1
+ :root{--cmp-radius:6px;--cmp-line:1px;--cmp-pad:24px;--cmp-gap:16px;--cmp-surface:var(--card-bg,transparent);--cmp-border:var(--hair);--cmp-shadow:none}
2
+ .component-card:before,.metric-card:before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--accent)}
@@ -0,0 +1,3 @@
1
+ :root{--cmp-radius:0px;--cmp-line:1px;--cmp-pad:22px;--cmp-gap:14px;--cmp-surface:transparent;--cmp-border:var(--title);--cmp-shadow:none}
2
+ .component-card,.metric-card,.proof-card,.price-card,.team-card{border-left-width:5px;border-top:0;border-right:0;border-bottom:0}
3
+ .component-label{font-family:var(--font-head);letter-spacing:.16em}
@@ -0,0 +1,2 @@
1
+ :root{--cmp-radius:18px;--cmp-line:1px;--cmp-pad:26px;--cmp-gap:18px;--cmp-surface:color-mix(in srgb,var(--slide-bg) 58%,transparent);--cmp-border:color-mix(in srgb,var(--accent) 28%,var(--hair));--cmp-shadow:0 18px 44px rgba(0,0,0,.18)}
2
+ .component-card,.metric-card,.proof-card,.price-card,.team-card{backdrop-filter:blur(16px)}
@@ -0,0 +1,3 @@
1
+ :root{--cmp-radius:0px;--cmp-line:1px;--cmp-pad:28px;--cmp-gap:20px;--cmp-surface:transparent;--cmp-border:var(--accent);--cmp-shadow:none}
2
+ .component-card,.metric-card,.proof-card,.price-card,.team-card{border-top:0;border-left:0;border-right:0}
3
+ .component-label,.metric-label{font-family:var(--font-head);letter-spacing:.2em}
@@ -0,0 +1,2 @@
1
+ :root{--cmp-radius:24px;--cmp-line:1px;--cmp-pad:26px;--cmp-gap:20px;--cmp-surface:var(--card-bg,transparent);--cmp-border:var(--hair);--cmp-shadow:0 14px 34px rgba(0,0,0,.08)}
2
+ .component-card:nth-child(even),.feature-card:nth-child(even){border-radius:40px 16px 40px 16px}
@@ -0,0 +1,3 @@
1
+ :root{--cmp-radius:2px;--cmp-line:2px;--cmp-pad:24px;--cmp-gap:18px;--cmp-surface:var(--card-bg,transparent);--cmp-border:var(--title);--cmp-shadow:8px 8px 0 var(--title)}
2
+ .component-card--strong,.metric-card--hero,.price-card--featured{transform:rotate(-1deg)}
3
+ .badge,.tag,.pill{border-radius:2px;text-transform:uppercase}
@@ -14,8 +14,10 @@ a blank slide** — authoring every slide from zero is the #1 "AI-generated" tel
14
14
 
15
15
  ## Execution discipline (whole pipeline)
16
16
 
17
- 1. **Sequential**: phases run in order; the two gated artifacts (`deck-brief.md`,
18
- `slide-outline.md`) MUST be user-approved before the phase after them.
17
+ 1. **Sequential**: phases run in order. `deck-brief.md` is a persisted echo of
18
+ already-confirmed inputs, not another approval round. `slide-outline.md` is the
19
+ single substantive approval gate before authoring. Only pause on the brief when
20
+ it introduces a material assumption the user has not already approved.
19
21
  2. **State echo**: each phase start, one line — `[Phase N] <name> — inputs: OK/missing`.
20
22
  3. **No fabrication**: never invent a value that must come from a tool (a stat, a
21
23
  citation, a palette). Missing → halt and get it.
@@ -31,15 +33,13 @@ Two entry modes:
31
33
 
32
34
  ## Phase 0 — Clarify & Brief (MANDATORY gate)
33
35
 
34
- Before any research or generation, confirm requirements and persist a brief.
35
- **Use the AskUserQuestion tool** (not plain-text questions) — no default fallbacks
36
- unless the user says "你决定/you decide".
37
-
38
- **ONE AskUserQuestion call, FOUR questions** (the tool allows up to 4 per call — use
39
- all four; do NOT split into a second call, do NOT drop any). This is a HARD GATE: ask
40
- and receive answers BEFORE any research / outline / brief-confirm / generation. Do NOT
41
- jump from this call straight to an outline or a "确认生成" gate. Even for a small/quick
42
- deck, even if the style seems "obvious", all four are asked (only "你决定" bypasses).
36
+ Before any research or generation, confirm requirements and persist a brief. Follow
37
+ the canonical interaction contract in `SKILL.md`: ask only for missing values among
38
+ content source, scenario, and slide count in **ONE AskUserQuestion call with at most
39
+ THREE questions**. Do not re-ask values already supplied. The scenario-ranked style
40
+ picker is a separate single-question image-card call after the scenario is known,
41
+ unless the user already selected a catalog entry, supplied a style template, or
42
+ explicitly authorized automatic selection.
43
43
 
44
44
  1. **Content source** — 提供材料 / 联网调研 / 自由创作. (Provided → request the materials now.
45
45
  When you ask the user for a FILE — a template/materials .pptx/.docx/.pdf — set
@@ -49,72 +49,86 @@ deck, even if the style seems "obvious", all four are asked (only "你决定" by
49
49
  in the chosen skin; NEVER reuse its look.** In particular a **`.pptx` handed in as
50
50
  content** → read its text (the `.txt` sidecar or `bin/doc-text`) and treat it like
51
51
  any other source; do **NOT** `pptx-tokenize`/`pptx-theme` it (those reuse the deck's
52
- STYLE — a Q3 decision, see below). Reusing an uploaded deck's look is ONLY triggered
53
- when the user picks that as their **style** (Q3 → "复用这份 PPT 的风格"), never merely
52
+ STYLE — a separate style-picker decision, see below). Reusing an uploaded deck's look is ONLY triggered
53
+ when the user picks that as their **style** ("复用这份 PPT 的风格"), never merely
54
54
  because the content happened to arrive as a `.pptx`.
55
55
  2. **Scenario** — the deck's use case (pitch / 内审 / 销售 / 讲座 / 发布会 / 培训 / 大会 /
56
56
  工作汇报 / 述职 / 项目评审 / 年度总结 / 商业计划书 / 投标 / 政务汇报 / 公司产品介绍 …
57
57
  — the full 15 with their logic live in the **Scenario matrix** below). AskUserQuestion
58
58
  caps at 4 **TEXT** options, so **for THIS scenario question** present the **3–4 best-fit
59
- for the topic** + let the user type Other (any scenario, listed or not). **This 4-cap is
60
- TEXT-only — it does NOT apply to Q3 (style), which is an IMAGE-CARD pick capped at 16:
61
- Q3 shows ALL skins, never a shortlist.** Then look the choice up in the matrix (unlisted →
59
+ for the topic** + let the user type Other (any scenario, listed or not). This text-option
60
+ cap does not apply to the separate image-card style picker; follow `SKILL.md` for its
61
+ current catalog-backed recommendation and browse-all contract. Then look the choice up in the matrix (unlisted →
62
62
  map to the nearest row). Record as a short phrase. Scenario is NOT just wording — it
63
63
  locks **five** things at once (tone · density · **required roles** · narrative shape ·
64
64
  page range). Carry them through: required roles = a checklist Phase 2 MUST satisfy
65
- (MECE), density feeds Phase 4 §4.2 P2, narrative picks the Phase 2 pyramid, pages seed Q4.
66
- 3. **Style — pick ONE skin.** By DEFAULT show **EVERY skin as image-card options** —
67
- **all 13 (anthropic · apple · black-gold · branding · cinereel · cool · hand-drawn ·
68
- ivory-gold · magazine-ink · mckinsey · mono · olive-gold · swiss-grid), NOT a 3–5
69
- shortlist.** Image-card picks are capped at **16**, so the
70
- 4-option TEXT cap does NOT apply here — emit one card per skin every time —
71
- one option per skin `{ label: skin 名, description: <the skin's **picker one-liner**
72
- from styles/README.md, VERBATIM — one short line, no 首选/风/keynote padding>, imagePath: <absolute
73
- path to styles/_samples/<skin>.webp under the skill dir> }`. The sample images are
74
- **PRE-RENDERED** WebP (a fixed cover + content page stacked per skin, all the same size)
75
- and shipped with the skill — use them directly, don't render anything at question
76
- time. (The picker UI lays the cards out itself; you only supply the options + images.)
77
- `styles/README.md` gives each skin's 适用场景 + light/dark; order the cards
78
- best-fit-for-scenario first but show them all. **When the user says 你决定 → `cool`**
79
- (酷炫深色); `mckinsey`/`mono` for clean/formal/light; match the rest by 适用场景.
80
- After confirmation, save the deck-skin preference to memory.
81
- **Custom style (其他/自定义)** — prefer **B (derive a bespoke look)**: from an existing
82
- deck → `pptx-tokenize` (reuse its layouts/master) or `pptx-theme` (its palette → our
83
- layouts); from a reference image → build a `styles/<name>/skin.css`. (Fallbacks: map to
84
- the nearest of the 9, or tweak one.) A deck given HERE is used for its STYLE — distinct
85
- from a deck given as **content** (Q1), which is text-only. Full routing in SKILL.md.
86
- 4. **Slide count** — default = the chosen scenario's **默认页数** column in the Scenario
65
+ (MECE), density feeds Phase 4 §4.2 P2, narrative picks the Phase 2 pyramid, pages seed the slide-count choice.
66
+ 3. **Slide count** — default = the chosen scenario's **默认页数** column in the Scenario
87
67
  matrix (e.g. pitch 10–12 · 内审 6–8 · keynote 15–20 · 工作汇报 5–10 · BP 15–25 · 投标
88
68
  15–30); clamp 3–30, double-check outside.
89
69
 
70
+ **Separate style picker — pick ONE current catalog entry after scenario is known.**
71
+ Read `catalog.json` immediately before asking and use the scenario-ranked picker
72
+ contract in `SKILL.md`: show at most eight recommended final entries, then
73
+ “查看全部风格” and the custom-template card. If the user expands, show every current
74
+ `catalog.entries` item in catalog order. Never use a hard-coded skin count or stale
75
+ id list. Samples are pre-rendered WebP assets — use `entry.sample` directly and do
76
+ not render anything during the question. Rank by catalog metadata and the confirmed
77
+ scenario; do not hardcode a default family. After confirmation, save the deck-skin
78
+ preference to memory.
79
+
80
+ **Custom style (其他/自定义)** — prefer **B (derive a bespoke look)**: from an existing
81
+ deck → `pptx-tokenize` (reuse its layouts/master) or `pptx-theme` (its palette → our
82
+ layouts); from a reference image → build a `styles/<name>/skin.css`. Fallbacks: map
83
+ to the nearest catalog family or tweak one. A deck given here is used for its STYLE —
84
+ distinct from a deck given as **content**, which is text-only. Full routing in `SKILL.md`.
85
+
90
86
  **Scenario matrix** (each row locks tone · density · required roles · narrative ·
91
87
  pages). The required roles are a HARD checklist — Phase 2 must include them all:
92
88
 
93
89
  | 场景 | 语气 | 密度 | 必备章节(required roles — all MUST appear) | 叙事(Phase 2) | 默认页数 |
94
90
  |---|---|---|---|---|---|
95
- | **pitch** 融资/路演 | 自信 · 有张力 · 故事驱动 | 稀疏 · 大数字 | Problem · Solution · Market · Product/Traction · Team · **Ask** | SCQA / 问题-方案 | 10–12 |
91
+ | **pitch** 融资/路演 | 自信 · 有张力 · 故事驱动 | 稀疏 · 大数字 | Problem/问题 · Solution/解决方案 · Market/市场 · Product/Traction/产品/进展 · Team/团队 · **Ask/融资需求** | SCQA / 问题-方案 | 10–12 |
96
92
  | **内审** | 客观 · 直接 · 结论先行 | 略密(容明细/数据) | 结论/进展 · 关键指标 · 问题与风险 · 下一步/决策项 | 结论先行 | 6–8 |
97
- | **销售** | 利益驱动 · 客户中心 · 说服 | 中 · 重价值与佐证 | 痛点 · 方案/价值主张 · 差异化 · 客户案例 · ROI/定价 · **CTA** | 问题-方案 | 8–12 |
93
+ | **销售** | 利益驱动 · 客户中心 · 说服 | 中 · 重价值与佐证 | 痛点 · 方案/价值主张 · 差异化 · 客户案例 · ROI/定价 · **CTA/行动号召** | 问题-方案 | 8–12 |
98
94
  | **讲座** 学术 | 讲解 · 循序渐进 | 中 · 概念+例子 | 学习目标 · 背景/基础 · 核心概念 · 案例/推导 · 小结 | 由浅入深 | 10–15 |
99
- | **发布会** keynote | 情绪化 · 戏剧性 · 揭晓感 | **极稀疏 · 一页一信息 · 超大视觉** | 钩子 · 背景/痛点 · 产品揭晓 · 亮点特性 · Demo · 价格上市 · **行动号召** | 情绪线 | 15–20 |
100
- | **培训** | 教学 · 清晰 · 可操作 | 中 · 分模块 | Objectives · Modules · 练习/示例 · Quiz · Summary | 由浅入深 | 8–15 |
95
+ | **发布会** keynote | 情绪化 · 戏剧性 · 揭晓感 | **极稀疏 · 一页一信息 · 超大视觉** | 钩子 · 背景/痛点 · 产品揭晓 · 亮点特性 · Demo/演示 · 价格上市 · **行动号召** | 情绪线 | 15–20 |
96
+ | **培训** | 教学 · 清晰 · 可操作 | 中 · 分模块 | Objectives/学习目标 · Module/课程模块 · 练习/示例/Exercise · Quiz/随堂测验 · Summary/小结 | 由浅入深 | 8–15 |
101
97
  | **大会** talk | 观点鲜明 · 启发 · 记忆点 | 稀疏 · 一图一句 | 开场钩子 · 核心论点 · 论据/案例 · 反直觉洞察 · 收束记忆点 | 情绪线 / SCQA | 15–20 |
102
98
  | **工作汇报** 周月季报 | 客观 · 简洁 · 结论先行 | 中 | 概要 · 进展/成果 · 数据指标 · 问题 · 下一步计划 | 结论先行 | 5–10 |
103
- | **述职/晋升** | 自信 · 有据 · 个人视角 | 中 | 职责概述 · 关键成果(STAR)· 数据佐证 · 能力成长 · 规划 | 结论先行 | 8–12 |
99
+ | **述职/晋升** | 自信 · 有据 · 个人视角 | 中 | 职责概述 · 关键成果/STAR · 数据佐证 · 能力成长 · 规划 | 结论先行 | 8–12 |
104
100
  | **项目立项/评审** | 严谨 · 说服 | 中偏密 | 背景/痛点 · 目标 · 方案 · 计划/里程碑 · 资源/预算 · 风险 | 问题-方案 | 8–14 |
105
101
  | **年度总结/年会** | 振奋 · 仪式感 | 稀疏 · 大视觉 | 回顾/高光 · 关键数据 · 里程碑 · 团队 · 来年展望 | 情绪线 | 12–20 |
106
102
  | **商业计划书** BP | 自信 · 全面 · 数据支撑 | 中偏密 | 问题 · 方案 · 市场 · 产品 · 商业模式 · 竞争 · 财务预测 · 团队 · **融资** | SCQA | 15–25 |
107
103
  | **投标** 招投标 | 正式 · 要素齐全 · 得分导向 | 密 | 需求理解 · 方案 · 团队/资质 · 案例业绩 · 实施计划 · **报价** · 售后 | 结论先行 | 15–30 |
108
104
  | **政务/党政汇报** | 严肃 · 规范 | 中偏密 | 背景/依据 · 工作举措 · 成效数据 · 存在问题 · 下步部署 | 结论先行 | 8–15 |
109
105
  | **公司/产品介绍** | 品牌 · 自信 · 利益导向 | 中 · 重视觉 | 公司/愿景 · 核心能力 · 产品/方案 · 标杆案例 · 数据 · 合作邀请 | 问题-方案 | 8–15 |
110
-
111
- Tone also biases the skin pick (Q3): 张力/情绪(pitch·发布会·大会)→ 深色酷炫(cool/apple/
106
+ | **产品调研** 竞品分析 市场研究 | 客观 · 有据 · 不替结论下判断 | 中 | 市场规模 · 竞品格局 · 用户需求 · 差距与机会 · 结论建议 | 先摆事实,再给判断 | 10–15 |
107
+ | **会议纪要** 会后同步 | 简洁 · 可执行 · 不复述过程 | 低 | 议题 · 结论 · 待办 · 待决事项 | 结论先行 | 4–8 |
108
+ | **数据分析** 数据解读 指标复盘 | 结论先行 · 可追溯 | 中 | 结论 · 口径与数据源 · 维度拆解 · 归因 · 建议 | 结论先行 | 6–10 |
109
+
110
+ **必备章节要中英双写(`English/中文`)。** 这张 checklist 的用法是拿每个词去**生成
111
+ 出来的大纲标题**里找子串(斜杠分段,命中任意一段就算有)。写成纯英文时,一份写得
112
+ 好好的中文稿一个都命中不了 —— 于是每次都判缺、每次都白花一轮模型回喂(10–30 秒
113
+ +一次调用的钱)。反过来整行换成中文也不对:中文路演稿**真的**会写 Problem / Ask /
114
+ Demo,换掉只是把漏判翻到另一边。中文那半边要挑够窄的词:写「融资」的话,一张叫
115
+ 「我们的融资故事」的封面就能把 Ask 顶掉,而那一节缺了正是这张表存在的理由。
116
+ 括号写法(`关键成果(STAR)`)同样不行 —— 它一个斜杠都没有,整串当一个词去找,
117
+ 大纲里必须原样出现那七个字符才算命中。
118
+
119
+ 还有两个容易漏的:**用单数**(大纲会写 `Module 1`,`Modules` 匹配不上,而 `Module`
120
+ 两种都命中);**本来就是中文的那几段也要补英文**(`练习/示例` 漏了 `Exercise`,
121
+ 一份英文培训大纲就在这一项上判缺)。
122
+
123
+ Tone also biases the catalog style ranking: 张力/情绪(pitch·发布会·大会)→ 深色酷炫(cool/apple/
112
124
  black-gold);务实(内审·销售)→ 白底咨询(mckinsey);清晰(讲座·培训)→ 中性/教育风.
113
125
  If the user's scenario isn't listed, map it to the nearest row's tone/density/narrative.
114
126
 
115
127
  **Persist `deck-brief.md`**: Topic · Scenario · Source · Style(name + 2–3 sentence
116
128
  palette/type/motif) · Theme file · Template style · Slide Count · (assumptions).
117
- Then **GATE**: show it, AskUserQuestion approve/revise, advance only on approval.
129
+ Persist it and continue. Do not ask the user to approve information they just supplied.
130
+ If the brief adds a material assumption that changes scope, show only that assumption
131
+ and pause for approval before continuing.
118
132
  (Offline / no web tool → say so; source can only be 提供材料 or 自由创作.)
119
133
 
120
134
  ---
@@ -133,6 +147,39 @@ Search 8–12 queries across layers, then organize into 6 dimensions:
133
147
 
134
148
  Dimensions: 概述 / 背景 / 关键信息 / 证据(案例·数据) / 分析 / 展望.
135
149
 
150
+ **层次是兜底,场景才是方向。** 上面那五层是**开放式议题**的默认铺法(用户只给了
151
+ 一个主题、没有材料)。一旦 Phase 0 选定了场景,就照下表按**这份稿子真正需要的东西**
152
+ 去查 —— 每条焦点出 1–2 条查询词,再从上面五层补 2–4 条防止十条围着一个角度。
153
+ 用户在 Phase 0 自己交代的那句话优先级最高,先查他明说要的。
154
+
155
+ 焦点直接对着上面场景矩阵的 `必备章节` —— 查回来的东西要能填进那些章节,
156
+ 否则这一轮检索就是白花的(按次计费)。
157
+
158
+ | 场景 | 检索焦点 |
159
+ |---|---|
160
+ | **pitch** 融资/路演 | 市场规模与增速(TAM/SAM) · 可比公司融资与估值 · 同类产品与差异 · 目标客群痛点证据 |
161
+ | **内审** | 行业对标指标 · 口径定义 · 外部风险事件 |
162
+ | **销售** | 客户所在行业痛点 · 竞品方案与差异 · 同行落地案例与 ROI · 公开定价 |
163
+ | **讲座** 学术 | 概念定义与权威教材 · 关键论文与综述 · 经典案例/推导 · 常见误解 |
164
+ | **发布会** keynote | 同类产品痛点与差评 · 竞品参数对比 · 竞品定价与上市节奏 · 可引用的用户场景 |
165
+ | **培训** | 权威教程与标准 · 分步操作示例 · 常见错误与排错 · 练习题与考点 |
166
+ | **大会** talk | 反直觉数据与研究 · 争议观点与反例 · 一手案例/失败教训 · 可引用金句 |
167
+ | **工作汇报** 周月季报 | 行业基准与同行数据 · 指标口径定义 · 外部环境变化 |
168
+ | **述职/晋升** | 同岗位能力模型 · 行业对标数据 · 该领域公认的成果衡量口径 |
169
+ | **项目立项/评审** | 同类项目投入产出 · 技术方案与选型对比 · 合规与风险案例 · 人力/预算基准 |
170
+ | **年度总结/年会** | 行业年度大事 · 同行里程碑对照 · 来年趋势预测 |
171
+ | **商业计划书** BP | 市场规模与分层 · 竞争格局与份额 · 商业模式与单位经济 · 可比公司财务与融资 |
172
+ | **投标** 招投标 | 招标方背景与既往采购 · 评分办法与资质要求 · 同类中标公告与报价区间 · 标杆案例 |
173
+ | **政务/党政汇报** | 政策文件与依据原文 · 上级部署口径 · 同级地区做法 · 公开统计数据 |
174
+ | **公司/产品介绍** | 行业地位与第三方报告 · 竞品对比 · 可公开引用的客户案例 · 行业趋势 |
175
+ | **产品调研** 竞品分析 市场研究 | 市场规模与增速 · 竞品功能与定价对比 · 用户评价与差评 · 渠道与份额数据 |
176
+ | **会议纪要** 会后同步 | 议题涉及的外部事实 · 相关口径与定义 · 同类决策的公开先例 |
177
+ | **数据分析** 数据解读 指标复盘 | 指标口径定义 · 行业基准值 · 同类归因案例与方法 |
178
+
179
+ 内部数据型的几档(内审 · 述职 · 工作汇报 · 年度总结)焦点**刻意收窄到对标与口径**:
180
+ 它们的主体材料在用户自己手里,联网能补的只有"外面是什么水位"。照五层去查行业定义
181
+ 和历史,查回来的东西一条都填不进它们的必备章节。
182
+
136
183
  **Fan out, then cite:** run layers in PARALLEL (many searches at once, or a
137
184
  sub-agent per dimension) — breadth beats depth-first. **Every data point
138
185
  (number/percent/year/quote) carries its source** — keep a `{claim → URL}` list; a
@@ -291,12 +338,21 @@ this **priority order** — P1 outranks P2 outranks P3; never trade layout-fit f
291
338
 
292
339
  ---
293
340
 
294
- ## Phase 5 — Render + adversarial visual QA (both traditions)
341
+ ## Phase 5 — Fast preview + final adversarial QA
342
+
343
+ Use a two-stage loop; do not pay the Office cold-start cost on every visual edit:
344
+
345
+ 1. **Authoring loop (HTML, fast):** render `deck.html` directly with
346
+ `bin/html2pptx.cjs deck.html --preview-dir <dir> [--preview-pages <spec>]` and inspect every page on
347
+ the first pass. Fix HTML/CSS, then preview only changed pages. Repeat until the HTML
348
+ composition is clean.
349
+ 2. **Delivery loop (PPTX, once after HTML is clean):** generate the PPTX, run
350
+ `deck-qa --json` and `doc-validate --strict`, fixing objective findings before
351
+ visual review. Then perform one faithful `doc-convert → pdf-render all` pass to
352
+ catch PPTX/Office-only drift. If that final pass finds drift, fix and rerun only as
353
+ required; never use PPTX→PDF as the routine authoring preview.
295
354
 
296
- Render, then run the **mandatory hostile-review loop** (deck-design.md "Verify before
297
- delivering"): render every slide to an image (`doc-convert → pdf-render all`),
298
- **assume there ARE defects and LOOK at every page** — text overflow/clipping,
299
- collisions, tofu/blank CJK, cramped gaps, low contrast, leftover placeholders,
300
- cross-slide inconsistency (palette / dark positions / type hierarchy). Fix →
301
- re-render the affected slides → repeat until a clean pass. Do NOT declare done before
302
- one full fix-and-verify cycle. Finish with `doc-validate --strict`.
355
+ Assume there are defects and inspect text overflow/clipping, collisions, tofu/blank
356
+ CJK, cramped gaps, low contrast, leftover placeholders, and cross-slide consistency.
357
+ Finish only after the generated PPTX has one clean structural, mechanical, and faithful
358
+ visual pass.
@@ -447,8 +447,8 @@ Two rules:
447
447
  ## Fonts — bundled faces reach the render, not the native text layer
448
448
 
449
449
  `assets/fonts/fonts.json` is the single source of truth for the skill's font set.
450
- `html2pptx` base64-injects every face from it at render; `scripts/deck-skin-samples.mjs`
451
- and `scripts/deck-fidelity.mjs` serve the same files from disk. **Add a face there and
450
+ `html2pptx` base64-injects every face from it at render; `scripts/deck/deck-skin-samples.mjs`
451
+ and `scripts/deck/deck-fidelity.mjs` serve the same files from disk. **Add a face there and
452
452
  drop the `.woff2` beside it — never inline a base64 `@font-face` into a `skin.css`.**
453
453
 
454
454
  The part that trips people up: an injected `@font-face` styles the **browser** render —
@@ -694,11 +694,10 @@ p.writeFile({ fileName: "deck.pptx" }).then(() => console.log("ok"));
694
694
 
695
695
  ## Verify before delivering (MANDATORY automated visual-QA loop)
696
696
 
697
- **Assume there are problems — your job is to find them.** The first render is
698
- almost never right; treat QA as a bug hunt, not a confirmation step. This is a
699
- **closed loop with per-slide regeneration**, required for ANY deck (archetype,
700
- freeform, or html2pptx). Run it as **two gates (cheap→expensive) then a bounded
701
- fix loop** — never spend vision on defects a machine already caught.
697
+ **Assume there are problems — your job is to find them.** Treat QA as a bug hunt,
698
+ not a confirmation step. Use the cheapest faithful surface for each stage: direct
699
+ HTML screenshots while authoring, then PPTX checks and one Office-rendered final pass.
700
+ Do not launch LibreOffice after every CSS/content edit.
702
701
 
703
702
  **Gate 1 — structural + schema** (blocking): `doc-validate.cjs deck.pptx
704
703
  --strict` — exit 1 = fix the reported part, never deliver (`--strict` = full
@@ -716,20 +715,30 @@ are objective bugs and free to detect, so they must never reach the vision pass
716
715
  delivery. (This is the same lint as `doc-validate --design-lint`, JSON-shaped for
717
716
  the loop.)
718
717
 
719
- **The loop** (≤2 rounds, then report residuals — never silently ship):
720
-
721
- 1. **Render per slide**: `doc-convert.cjs deck.pptx /tmp/deck.pdf` then
722
- `pdf-render.cjs /tmp/deck.pdf all 1` (in-app Chromium) → one PNG per slide.
718
+ **The loop** (≤2 delivery rounds, then report residuals — never silently ship):
719
+
720
+ 1. **Fast authoring preview:** before generating the delivery PPTX, run
721
+ `html2pptx.cjs deck.html --preview-dir /tmp/deck-preview --style <id> ...`.
722
+ The first pass renders every slide; after a fix add `--preview-pages 3,7-8` to
723
+ capture only changed slides. Inspect and iterate on these HTML-faithful images.
724
+ 2. **Build + machine gates:** generate `deck.pptx`, then run Gate 1 and Gate 2 above.
725
+ Fix every objective finding before the expensive final render.
726
+ 3. **Final Office render:** once HTML preview and machine gates are clean, run
727
+ `doc-convert.cjs deck.pptx /tmp/deck.pdf --cache-dir /tmp/talos-office-cache` then
728
+ `pdf-render.cjs /tmp/deck.pdf all 1` → one PNG per slide. This happens once in
729
+ the normal path and verifies PPTX/Office-only drift; rerun it only after fixing
730
+ drift found in this final pass.
723
731
  **Reading the previews is the single most token-expensive step in the whole
724
732
  deck flow** (an image costs ≈ w×h∕750 tokens; a full-res 1280×720 slide ≈ 1.2k
725
733
  each, ~15k for a 12-page deck — and you re-read every round). The trailing `1`
726
734
  renders at ≈72dpi (¼ the pixels of the default `2`) — **≈¼ the vision tokens**,
727
735
  and slide composition/color/hierarchy read fine at that size. Two ways to spend
728
736
  even less: (a) Gates 1–2 are token-FREE — never send a slide to vision that a
729
- machine already flagged; (b) on re-runs render + read **only the slides you
730
- regenerated**, not the whole deck (`pdf-render … 3-3 1` etc.). Bump back to `2`
731
- only for a slide whose fine print you genuinely need to read.
732
- 2. **Vision critic — the part only a model can do.** LOOK at every slide image
737
+ machine already flagged; (b) use `--preview-pages` during HTML iteration and
738
+ read only changed slides. `pdf-render 3-3` saves PNG work but does **not** avoid
739
+ the whole-deck Office conversion, so reserve it for a genuine final-PPTX rerun.
740
+ Bump scale back to `2` only when fine print genuinely needs it.
741
+ 4. **Vision critic — the part only a model can do.** LOOK at every slide image
733
742
  (not "glance"); **if sub-agent dispatch is available, critique slides in
734
743
  parallel — one fresh-eyes agent per slide** (they catch what you, having
735
744
  authored it, will not). Pass each critic the slide's `deck-qa` findings so it