@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
@@ -159,12 +159,61 @@ function derivePins(rows, warnings) {
159
159
  });
160
160
  }
161
161
 
162
+ // packages/core/dist/document/html-escape.js
163
+ function escapeHtml(value) {
164
+ return String(value).replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
165
+ }
166
+
162
167
  // packages/core/dist/document/deck-build.js
168
+ var SCENES = /* @__PURE__ */ new Set(["hero", "content", "data", "media", "closing"]);
169
+ var DECOR_DENSITIES = /* @__PURE__ */ new Set(["low", "medium", "high"]);
170
+ var SCENE_VARIANTS = ["a", "b", "c"];
171
+ var DEFAULT_SCENE_DENSITY = {
172
+ hero: "high",
173
+ content: "low",
174
+ data: "medium",
175
+ media: "medium",
176
+ closing: "high"
177
+ };
178
+ function planDeckVisualRhythm(slides, profiles = {}, rhythm = "auto") {
179
+ const errors = [];
180
+ const sceneCounts = /* @__PURE__ */ new Map();
181
+ const planned = [];
182
+ const densities = [];
183
+ slides.forEach((slide, index) => {
184
+ const profile = slide.intent ? profiles[slide.intent] : void 0;
185
+ const scene = slide.scene ?? profile?.scene ?? "content";
186
+ if (!SCENES.has(scene)) {
187
+ errors.push(`slide ${index + 1}: invalid scene "${scene}"`);
188
+ planned.push(slide);
189
+ return;
190
+ }
191
+ let density = slide.decorDensity ?? profile?.decorDensity ?? DEFAULT_SCENE_DENSITY[scene];
192
+ if (!DECOR_DENSITIES.has(density)) {
193
+ errors.push(`slide ${index + 1}: invalid decorDensity "${density}"`);
194
+ planned.push(slide);
195
+ return;
196
+ }
197
+ if (rhythm === "auto" && slide.decorDensity === void 0) {
198
+ const prev = densities.at(-1);
199
+ const prev2 = densities.at(-2);
200
+ if (density === "high" && prev === "high" && scene !== "closing")
201
+ density = "medium";
202
+ else if (density === "medium" && prev === "medium" && prev2 === "medium")
203
+ density = "low";
204
+ else if (density === "low" && prev === "low")
205
+ density = "medium";
206
+ }
207
+ const occurrence = sceneCounts.get(scene) ?? 0;
208
+ sceneCounts.set(scene, occurrence + 1);
209
+ const sceneVariant = slide.sceneVariant ?? SCENE_VARIANTS[occurrence % SCENE_VARIANTS.length];
210
+ densities.push(density);
211
+ planned.push({ ...slide, scene, decorDensity: density, sceneVariant });
212
+ });
213
+ return { slides: planned, errors };
214
+ }
163
215
  var SLOT_RE = /\{\{(?![#/])([^{}]*)\}\}/g;
164
216
  var BLOCK_RE = /\{\{#([A-Za-z_][\w-]*)\}\}([\s\S]*?)\{\{\/\1\}\}/g;
165
- function escapeHtml(s) {
166
- return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
167
- }
168
217
  var slotName = (inner) => {
169
218
  const bar = inner.indexOf("|");
170
219
  return (bar >= 0 ? inner.slice(0, bar) : inner).trim();
@@ -247,6 +296,11 @@ function injectCharts(html, chart, layout, problems) {
247
296
  function stripLeadComment(tpl) {
248
297
  return tpl.replace(/^\s*<!--[\s\S]*?-->\s*/, "");
249
298
  }
299
+ function injectVisualScene(html, slide) {
300
+ if (!slide.scene || !slide.decorDensity || !slide.sceneVariant)
301
+ return html;
302
+ return html.replace(/<section\b/, `<section data-scene="${slide.scene}" data-decor-density="${slide.decorDensity}" data-scene-variant="${slide.sceneVariant}"`);
303
+ }
250
304
  function assembleDeck(spec, readLayout) {
251
305
  const problems = { warnings: [], errors: [] };
252
306
  const sections = [];
@@ -279,6 +333,8 @@ function assembleDeck(spec, readLayout) {
279
333
  const chart = figures.chart ?? slide.chart;
280
334
  if (chart !== void 0 && !local.errors.length)
281
335
  html = injectCharts(html, chart, where, local);
336
+ if (!local.errors.length)
337
+ html = injectVisualScene(html, slide);
282
338
  problems.warnings.push(...local.warnings);
283
339
  problems.errors.push(...local.errors);
284
340
  if (!local.errors.length)
@@ -290,7 +346,9 @@ function assembleDeck(spec, readLayout) {
290
346
  // packages/core/dist/document/deck-catalog.js
291
347
  var KINDS = ["skin", "pack"];
292
348
  var MODES = ["light", "dark"];
349
+ var RENDER_MODES = ["classic-v1", "theme-v2"];
293
350
  var ID_RE = /^[a-z0-9][a-z0-9-]*$/;
351
+ var COMMIT_RE = /^[0-9a-f]{7,40}$/;
294
352
  function isStr(v) {
295
353
  return typeof v === "string" && v.length > 0;
296
354
  }
@@ -320,6 +378,35 @@ function parseCatalog(raw) {
320
378
  groups.push({ id: g.id, name_cn: g.name_cn, sample: g.sample, scenarios: g.scenarios });
321
379
  }
322
380
  });
381
+ const collections = [];
382
+ const rawCollections = o.collections === void 0 ? [] : Array.isArray(o.collections) ? o.collections : [];
383
+ if (o.collections !== void 0 && !Array.isArray(o.collections))
384
+ errors.push("catalog.collections must be an array when present");
385
+ rawCollections.forEach((c, i) => {
386
+ const at = `collection[${i}]`;
387
+ const before = errors.length;
388
+ if (!isStr(c?.id) || !ID_RE.test(c.id))
389
+ errors.push(`${at}: bad id`);
390
+ if (!isStr(c?.name_cn))
391
+ errors.push(`${at}: missing name_cn`);
392
+ if (!isStr(c?.renderMode) || !RENDER_MODES.includes(c.renderMode))
393
+ errors.push(`${at}: bad renderMode`);
394
+ if (!isStrArr(c?.entryIds) || c.entryIds.length === 0)
395
+ errors.push(`${at}: entryIds must be a non-empty string array`);
396
+ if (c?.sourceCommit !== void 0 && (!isStr(c.sourceCommit) || !COMMIT_RE.test(c.sourceCommit)))
397
+ errors.push(`${at}: bad sourceCommit`);
398
+ if (c?.renderMode === "classic-v1" && c?.sourceCommit === void 0)
399
+ errors.push(`${at}: classic-v1 requires sourceCommit`);
400
+ if (errors.length === before) {
401
+ collections.push({
402
+ id: c.id,
403
+ name_cn: c.name_cn,
404
+ renderMode: c.renderMode,
405
+ entryIds: c.entryIds,
406
+ ...c.sourceCommit !== void 0 ? { sourceCommit: c.sourceCommit } : {}
407
+ });
408
+ }
409
+ });
323
410
  const entries = [];
324
411
  const rawEntries = Array.isArray(o.entries) ? o.entries : [];
325
412
  if (!Array.isArray(o.entries))
@@ -359,11 +446,116 @@ function parseCatalog(raw) {
359
446
  });
360
447
  }
361
448
  });
362
- return { catalog: errors.length ? null : { groups, entries }, errors };
449
+ return { catalog: errors.length ? null : { groups, ...collections.length ? { collections } : {}, entries }, errors };
363
450
  }
364
451
  function resolveEntry(catalog, id) {
365
452
  return catalog.entries.find((e) => e.id === id) ?? null;
366
453
  }
454
+ function renderModeOf(catalog, id) {
455
+ return catalog.collections?.find((c) => c.entryIds.includes(id))?.renderMode ?? "theme-v2";
456
+ }
457
+
458
+ // packages/core/dist/shared/guards.js
459
+ function isRecord(value) {
460
+ return value !== null && typeof value === "object" && !Array.isArray(value);
461
+ }
462
+
463
+ // packages/core/dist/document/deck-theme-system.js
464
+ var ID_RE2 = /^[a-z0-9][a-z0-9-]*$/;
465
+ var PATH_RE = /^[a-z0-9_./-]+\.css$/i;
466
+ function isSafeCssPath(v) {
467
+ return typeof v === "string" && PATH_RE.test(v) && !v.startsWith("/") && !v.includes("..");
468
+ }
469
+ function parsePathMap(value, at, errors) {
470
+ if (!isRecord(value)) {
471
+ errors.push(`${at} must be an object`);
472
+ return {};
473
+ }
474
+ const out = {};
475
+ for (const [id, path] of Object.entries(value)) {
476
+ if (!ID_RE2.test(id))
477
+ errors.push(`${at}.${id}: bad id`);
478
+ if (!isSafeCssPath(path)) {
479
+ errors.push(`${at}.${id}: must be a safe relative .css path`);
480
+ continue;
481
+ }
482
+ out[id] = path;
483
+ }
484
+ return out;
485
+ }
486
+ function parseThemeSystem(raw) {
487
+ const errors = [];
488
+ if (!isRecord(raw))
489
+ return { system: null, errors: ["theme system is not an object"] };
490
+ if (raw.version !== 2)
491
+ errors.push("theme system version must be 2");
492
+ if (!isSafeCssPath(raw.componentBase)) {
493
+ errors.push("componentBase must be a safe relative .css path");
494
+ }
495
+ if (!isSafeCssPath(raw.artBase)) {
496
+ errors.push("artBase must be a safe relative .css path");
497
+ }
498
+ const componentProfiles = parsePathMap(raw.componentProfiles, "componentProfiles", errors);
499
+ const mediaStyles = parsePathMap(raw.mediaStyles, "mediaStyles", errors);
500
+ const artDirections = parsePathMap(raw.artDirections, "artDirections", errors);
501
+ const bindings = {};
502
+ if (!isRecord(raw.bindings))
503
+ errors.push("bindings must be an object");
504
+ else {
505
+ for (const [id, value] of Object.entries(raw.bindings)) {
506
+ const at = `bindings.${id}`;
507
+ if (!ID_RE2.test(id))
508
+ errors.push(`${at}: bad id`);
509
+ if (!isRecord(value)) {
510
+ errors.push(`${at} must be an object`);
511
+ continue;
512
+ }
513
+ const componentProfile = value.componentProfile;
514
+ const mediaStyle = value.mediaStyle;
515
+ const artDirection = value.artDirection;
516
+ if (typeof componentProfile !== "string" || !ID_RE2.test(componentProfile))
517
+ errors.push(`${at}: bad componentProfile`);
518
+ if (typeof mediaStyle !== "string" || !ID_RE2.test(mediaStyle))
519
+ errors.push(`${at}: bad mediaStyle`);
520
+ if (typeof artDirection !== "string" || !ID_RE2.test(artDirection))
521
+ errors.push(`${at}: bad artDirection`);
522
+ if (typeof componentProfile === "string" && typeof mediaStyle === "string" && typeof artDirection === "string") {
523
+ bindings[id] = { componentProfile, mediaStyle, artDirection };
524
+ }
525
+ }
526
+ }
527
+ if (errors.length)
528
+ return { system: null, errors };
529
+ return {
530
+ system: {
531
+ version: 2,
532
+ componentBase: raw.componentBase,
533
+ artBase: raw.artBase,
534
+ componentProfiles,
535
+ mediaStyles,
536
+ artDirections,
537
+ bindings
538
+ },
539
+ errors
540
+ };
541
+ }
542
+ function resolveThemeLayers(system, styleId, mediaOverride) {
543
+ const binding = system.bindings[styleId];
544
+ if (!binding)
545
+ return null;
546
+ const mediaStyle = mediaOverride ?? binding.mediaStyle;
547
+ const componentPath = system.componentProfiles[binding.componentProfile];
548
+ const mediaPath = system.mediaStyles[mediaStyle];
549
+ const artPath = system.artDirections[binding.artDirection];
550
+ if (!componentPath || !mediaPath || !artPath)
551
+ return null;
552
+ return {
553
+ paths: [system.componentBase, componentPath, mediaPath, system.artBase, artPath],
554
+ componentProfile: binding.componentProfile,
555
+ mediaStyle,
556
+ artDirection: binding.artDirection
557
+ };
558
+ }
367
559
 
368
560
  // packages/core/dist/document/deck-motion.js
369
561
  var ANIM_EFFECTS = [
@@ -513,20 +705,61 @@ async function main() {
513
705
  }
514
706
  const skillDir = (0, import_node_path.dirname)((0, import_node_path.dirname)(process.argv[1] || ""));
515
707
  const layoutsDir = (0, import_node_path.join)(skillDir, "layouts");
708
+ if (spec?.themeVersion !== void 0 && spec.themeVersion !== 2) die("themeVersion must be 2 when present", 64);
709
+ if (spec?.themeVersion === 2 && typeof spec?.style !== "string") die("themeVersion: 2 requires a catalog style id", 64);
710
+ if (spec?.mediaStyle !== void 0 && (spec.themeVersion !== 2 || typeof spec.mediaStyle !== "string")) {
711
+ die("mediaStyle must be a string and requires themeVersion: 2", 64);
712
+ }
713
+ if (spec?.layoutPack !== void 0 && typeof spec.layoutPack !== "string") die('layoutPack must be a pack id or "none"', 64);
714
+ if (spec?.decorRhythm !== void 0 && (spec.themeVersion !== 2 || !["auto", "steady"].includes(spec.decorRhythm))) {
715
+ die("decorRhythm must be auto|steady and requires themeVersion: 2", 64);
716
+ }
717
+ if (spec?.themeVersion !== 2 && Array.isArray(spec?.slides) && spec.slides.some((s) => s?.scene !== void 0 || s?.decorDensity !== void 0)) {
718
+ die("slide scene/decorDensity requires themeVersion: 2", 64);
719
+ }
516
720
  let catalogEntry = null;
517
721
  let packLayoutsDir = null;
518
722
  const styleId = typeof spec?.style === "string" ? spec.style : null;
519
- if (styleId) {
723
+ let catalog = null;
724
+ if (styleId || typeof spec?.layoutPack === "string" && spec.layoutPack !== "none") {
520
725
  const catalogPath = (0, import_node_path.join)(skillDir, "catalog.json");
521
726
  if ((0, import_node_fs.existsSync)(catalogPath)) {
522
727
  try {
523
- const { catalog } = parseCatalog(JSON.parse((0, import_node_fs.readFileSync)(catalogPath, "utf8")));
728
+ ({ catalog } = parseCatalog(JSON.parse((0, import_node_fs.readFileSync)(catalogPath, "utf8"))));
524
729
  catalogEntry = catalog ? resolveEntry(catalog, styleId) : null;
525
- if (catalogEntry?.kind === "pack") packLayoutsDir = (0, import_node_path.join)(skillDir, "packs", catalogEntry.id, "layouts");
526
730
  } catch {
527
731
  }
528
732
  }
529
733
  }
734
+ let layoutPackEntry = null;
735
+ if (spec?.layoutPack === "none") {
736
+ layoutPackEntry = null;
737
+ } else if (typeof spec?.layoutPack === "string") {
738
+ layoutPackEntry = catalog ? resolveEntry(catalog, spec.layoutPack) : null;
739
+ if (!layoutPackEntry || layoutPackEntry.kind !== "pack") die(`layoutPack "${spec.layoutPack}" is not a registered pack`, 64);
740
+ } else if (catalogEntry?.kind === "pack") {
741
+ layoutPackEntry = catalogEntry;
742
+ process.stderr.write(`deck-build: legacy compatibility \u2014 style "${styleId}" implicitly enables layoutPack "${styleId}"; set layoutPack:"none" to use shared layouts only
743
+ `);
744
+ }
745
+ if (layoutPackEntry) packLayoutsDir = (0, import_node_path.join)(skillDir, "packs", layoutPackEntry.id, "layouts");
746
+ if (spec?.themeVersion === 2) {
747
+ if (!catalogEntry) die(`Theme V2 style "${styleId}" is not registered in catalog.json`, 64);
748
+ if (catalog && renderModeOf(catalog, catalogEntry.id) === "classic-v1") {
749
+ die(`style "${catalogEntry.id}" is frozen in collection "original-13" as classic-v1; omit themeVersion/mediaStyle/decorRhythm and use a V1 layout`, 64);
750
+ }
751
+ const registryPath = (0, import_node_path.join)(skillDir, "theme-system.json");
752
+ let parsed;
753
+ try {
754
+ parsed = parseThemeSystem(JSON.parse((0, import_node_fs.readFileSync)(registryPath, "utf8")));
755
+ } catch (e) {
756
+ die(`cannot read Theme V2 registry ${registryPath}: ${e.message}`, 66);
757
+ }
758
+ if (!parsed.system) die(`invalid Theme V2 registry: ${parsed.errors.join("; ")}`, 66);
759
+ if (!resolveThemeLayers(parsed.system, catalogEntry.id, spec.mediaStyle)) {
760
+ die(`Theme V2 cannot resolve style "${catalogEntry.id}"${spec.mediaStyle ? ` with media style "${spec.mediaStyle}"` : ""}`, 64);
761
+ }
762
+ }
530
763
  let fromPack = 0, fromShared = 0;
531
764
  const readLayout = (id) => {
532
765
  if (!/^[a-z0-9-]+$/i.test(id)) return null;
@@ -542,7 +775,30 @@ async function main() {
542
775
  if (packLayoutsDir) fromShared++;
543
776
  return (0, import_node_fs.readFileSync)(p, "utf8");
544
777
  };
545
- const { html, warnings, errors } = assembleDeck(spec, readLayout);
778
+ const profilesPath = (0, import_node_path.join)(layoutsDir, "profiles.json");
779
+ const profiles = (0, import_node_fs.existsSync)(profilesPath) ? JSON.parse((0, import_node_fs.readFileSync)(profilesPath, "utf8")) : null;
780
+ let buildSpec = spec;
781
+ let rhythmErrors = [];
782
+ if (spec?.themeVersion === 2 && Array.isArray(spec?.slides)) {
783
+ const planned = planDeckVisualRhythm(spec.slides, profiles?.intents ?? {}, spec.decorRhythm ?? "auto");
784
+ buildSpec = { ...spec, slides: planned.slides };
785
+ rhythmErrors = planned.errors;
786
+ }
787
+ const { html, warnings, errors } = assembleDeck(buildSpec, readLayout);
788
+ errors.push(...rhythmErrors);
789
+ if (profiles) {
790
+ for (const [i, slide] of (Array.isArray(spec?.slides) ? spec.slides : []).entries()) {
791
+ if (profiles.themeV2Layouts?.includes(slide?.layout) && spec?.themeVersion !== 2) {
792
+ errors.push(`slide ${i + 1} (${slide.layout}): this layout requires themeVersion: 2`);
793
+ }
794
+ if (slide?.intent === void 0) continue;
795
+ const profile = profiles?.intents?.[slide.intent];
796
+ if (!profile) errors.push(`slide ${i + 1}: unknown intent "${slide.intent}"`);
797
+ else if (!profile.candidates?.includes(slide.layout)) {
798
+ warnings.push(`slide ${i + 1} (${slide.layout}): intent "${slide.intent}" recommends ${profile.candidates.join(", ")}`);
799
+ }
800
+ }
801
+ }
546
802
  for (const w of warnings) process.stderr.write(`deck-build: ${w}
547
803
  `);
548
804
  if (errors.length) {
@@ -555,7 +811,7 @@ async function main() {
555
811
  const ratio = fromShared / resolved;
556
812
  if (ratio > 0.4) {
557
813
  process.stderr.write(
558
- `deck-build: pack-dilution \u2014 ${fromShared}/${resolved} \u9875\uFF08${Math.round(ratio * 100)}%\uFF09\u56DE\u843D\u5230\u5171\u4EAB\u7248\u5F0F\uFF0C\u8D85\u8FC7 40% \u9608\u503C\u3002\u5305 "${styleId}" \u7684\u9875\u9762\u6E05\u5355\u76F8\u5BF9\u672C\u6B21\u5185\u5BB9\u91CF\u504F\u8584\uFF0C\u8003\u8651\u6269\u5305\u6216\u6539\u7528 skin\u3002
814
+ `deck-build: pack-dilution \u2014 ${fromShared}/${resolved} \u9875\uFF08${Math.round(ratio * 100)}%\uFF09\u56DE\u843D\u5230\u5171\u4EAB\u7248\u5F0F\uFF0C\u8D85\u8FC7 40% \u9608\u503C\u3002\u7248\u5F0F\u5305 "${layoutPackEntry?.id}" \u7684\u9875\u9762\u6E05\u5355\u76F8\u5BF9\u672C\u6B21\u5185\u5BB9\u91CF\u504F\u8584\uFF0C\u8003\u8651\u6269\u5305\u6216\u4F7F\u7528\u5171\u4EAB\u7248\u5F0F\u3002
559
815
  `
560
816
  );
561
817
  }
@@ -592,7 +848,7 @@ async function main() {
592
848
  out: outPath,
593
849
  slides: Array.isArray(spec?.slides) ? spec.slides.length : 0,
594
850
  warnings,
595
- next: `html2pptx ${outPath} out.pptx${styleId ? ` --style ${styleId}` : " --style <skin>"}`
851
+ next: `html2pptx ${outPath} out.pptx${styleId ? ` --style ${styleId}` : " --style <skin>"}${spec?.themeVersion === 2 ? " --theme-v2" : ""}${spec?.mediaStyle ? ` --media-style ${spec.mediaStyle}` : ""}`
596
852
  }, null, 1) + "\n");
597
853
  }
598
854
  main().catch((e) => die(e.message, 1));
@@ -315,7 +315,7 @@ var require_BufferList = __commonJS({
315
315
  this.head = this.tail = null;
316
316
  this.length = 0;
317
317
  };
318
- BufferList.prototype.join = function join2(s) {
318
+ BufferList.prototype.join = function join3(s) {
319
319
  if (this.length === 0) return "";
320
320
  var p = this.head;
321
321
  var ret2 = "" + p.data;
@@ -35136,7 +35136,7 @@ var require_buffer_list2 = __commonJS({
35136
35136
  }
35137
35137
  }, {
35138
35138
  key: "join",
35139
- value: function join2(s) {
35139
+ value: function join3(s) {
35140
35140
  if (this.length === 0) return "";
35141
35141
  var p = this.head;
35142
35142
  var ret2 = "" + p.data;
@@ -57654,7 +57654,7 @@ var require_polyfills = __commonJS({
57654
57654
  }
57655
57655
  if (platform === "win32") {
57656
57656
  fs.rename = typeof fs.rename !== "function" ? fs.rename : (function(fs$rename) {
57657
- function rename(from, to, cb2) {
57657
+ function rename2(from, to, cb2) {
57658
57658
  var start = Date.now();
57659
57659
  var backoff = 0;
57660
57660
  fs$rename(from, to, function CB(er) {
@@ -57674,8 +57674,8 @@ var require_polyfills = __commonJS({
57674
57674
  if (cb2) cb2(er);
57675
57675
  });
57676
57676
  }
57677
- if (Object.setPrototypeOf) Object.setPrototypeOf(rename, fs$rename);
57678
- return rename;
57677
+ if (Object.setPrototypeOf) Object.setPrototypeOf(rename2, fs$rename);
57678
+ return rename2;
57679
57679
  })(fs.rename);
57680
57680
  }
57681
57681
  fs.read = typeof fs.read !== "function" ? fs.read : (function(fs$read) {
@@ -62361,7 +62361,7 @@ var require_buffer_list3 = __commonJS({
62361
62361
  }
62362
62362
  }, {
62363
62363
  key: "join",
62364
- value: function join2(s) {
62364
+ value: function join3(s) {
62365
62365
  if (this.length === 0) return "";
62366
62366
  var p = this.head;
62367
62367
  var ret2 = "" + p.data;
@@ -65727,7 +65727,7 @@ var require_buffer_list4 = __commonJS({
65727
65727
  }
65728
65728
  }, {
65729
65729
  key: "join",
65730
- value: function join2(s) {
65730
+ value: function join3(s) {
65731
65731
  if (this.length === 0) return "";
65732
65732
  var p = this.head;
65733
65733
  var ret2 = "" + p.data;
@@ -68523,7 +68523,7 @@ var require_buffer_list5 = __commonJS({
68523
68523
  }
68524
68524
  }, {
68525
68525
  key: "join",
68526
- value: function join2(s) {
68526
+ value: function join3(s) {
68527
68527
  if (this.length === 0) return "";
68528
68528
  var p = this.head;
68529
68529
  var ret2 = "" + p.data;
@@ -71393,7 +71393,7 @@ var require_buffer_list6 = __commonJS({
71393
71393
  }
71394
71394
  }, {
71395
71395
  key: "join",
71396
- value: function join2(s) {
71396
+ value: function join3(s) {
71397
71397
  if (this.length === 0) return "";
71398
71398
  var p = this.head;
71399
71399
  var ret2 = "" + p.data;
@@ -74182,7 +74182,7 @@ var require_buffer_list7 = __commonJS({
74182
74182
  }
74183
74183
  }, {
74184
74184
  key: "join",
74185
- value: function join2(s) {
74185
+ value: function join3(s) {
74186
74186
  if (this.length === 0) return "";
74187
74187
  var p = this.head;
74188
74188
  var ret2 = "" + p.data;
@@ -77172,7 +77172,7 @@ var require_buffer_list8 = __commonJS({
77172
77172
  }
77173
77173
  }, {
77174
77174
  key: "join",
77175
- value: function join2(s) {
77175
+ value: function join3(s) {
77176
77176
  if (this.length === 0) return "";
77177
77177
  var p = this.head;
77178
77178
  var ret2 = "" + p.data;
@@ -82364,7 +82364,7 @@ var require_BufferList3 = __commonJS({
82364
82364
  this.head = this.tail = null;
82365
82365
  this.length = 0;
82366
82366
  };
82367
- BufferList.prototype.join = function join2(s) {
82367
+ BufferList.prototype.join = function join3(s) {
82368
82368
  if (this.length === 0) return "";
82369
82369
  var p = this.head;
82370
82370
  var ret2 = "" + p.data;
@@ -85503,7 +85503,7 @@ var require_dir_writer = __commonJS({
85503
85503
  module2.exports = DirWriter;
85504
85504
  var Writer = require_writer2();
85505
85505
  var inherits2 = require_inherits();
85506
- var mkdir = require_mkdirp();
85506
+ var mkdir2 = require_mkdirp();
85507
85507
  var path = require("path");
85508
85508
  var collect = require_collect();
85509
85509
  inherits2(DirWriter, Writer);
@@ -85519,7 +85519,7 @@ var require_dir_writer = __commonJS({
85519
85519
  }
85520
85520
  DirWriter.prototype._create = function() {
85521
85521
  var self2 = this;
85522
- mkdir(self2._path, Writer.dirmode, function(er) {
85522
+ mkdir2(self2._path, Writer.dirmode, function(er) {
85523
85523
  if (er) return self2.error(er);
85524
85524
  self2.ready = true;
85525
85525
  self2.emit("ready");
@@ -85864,7 +85864,7 @@ var require_writer2 = __commonJS({
85864
85864
  var fs = require_graceful_fs();
85865
85865
  var inherits2 = require_inherits();
85866
85866
  var rimraf = require_rimraf();
85867
- var mkdir = require_mkdirp();
85867
+ var mkdir2 = require_mkdirp();
85868
85868
  var path = require("path");
85869
85869
  var umask = process.platform === "win32" ? 0 : process.umask();
85870
85870
  var getType = require_get_type();
@@ -85970,7 +85970,7 @@ var require_writer2 = __commonJS({
85970
85970
  }
85971
85971
  };
85972
85972
  function create2(self2) {
85973
- mkdir(path.dirname(self2._path), Writer.dirmode, function(er, made) {
85973
+ mkdir2(path.dirname(self2._path), Writer.dirmode, function(er, made) {
85974
85974
  if (er) return self2.error(er);
85975
85975
  self2._madeDir = made;
85976
85976
  return self2._create();
@@ -148973,6 +148973,7 @@ var require_fontkit_umd = __commonJS({
148973
148973
  });
148974
148974
 
148975
148975
  // apps/admin/data/skills.lib/document/bin-src/doc-convert.mjs
148976
+ var import_node_crypto = require("node:crypto");
148976
148977
  var import_node_path2 = require("node:path");
148977
148978
  var import_promises2 = require("node:fs/promises");
148978
148979
 
@@ -152673,7 +152674,7 @@ var require_buffer_list = /* @__PURE__ */ __commonJSMin(((exports2, module2) =>
152673
152674
  },
152674
152675
  {
152675
152676
  key: "join",
152676
- value: function join2(s) {
152677
+ value: function join3(s) {
152677
152678
  if (this.length === 0) return "";
152678
152679
  var p = this.head;
152679
152680
  var ret2 = "" + p.data;
@@ -170152,6 +170153,11 @@ async function generateDocument(kind, spec, theme) {
170152
170153
  }
170153
170154
  }
170154
170155
 
170156
+ // packages/core/dist/document/html-escape.js
170157
+ function escapeHtml(value) {
170158
+ return String(value).replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
170159
+ }
170160
+
170155
170161
  // packages/core/dist/document/doc-html.js
170156
170162
  var FONT_STACK = 'TalosCJK, "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", "Source Han Sans SC", "WenQuanYi Zen Hei", sans-serif';
170157
170163
 
@@ -170180,9 +170186,6 @@ function htmlShell(bodyHtml, title) {
170180
170186
  const head = `<meta charset="utf-8">` + (title ? `<title>${escapeHtml(title)}</title>` : "") + `<style>${PRINT_STYLE}</style>`;
170181
170187
  return `<!doctype html><html lang="zh"><head>${head}</head><body>${bodyHtml}</body></html>`;
170182
170188
  }
170183
- function escapeHtml(s) {
170184
- return s.replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
170185
- }
170186
170189
  function titleFromFilename(filename) {
170187
170190
  const base = filename.split(/[\\/]/).pop() ?? filename;
170188
170191
  const stem = base.replace(/\.[^.]+$/, "").trim();
@@ -170468,10 +170471,39 @@ async function renderHtmlViaService(html) {
170468
170471
  return null;
170469
170472
  }
170470
170473
  }
170471
- async function renderViaSoffice(input) {
170474
+ async function faithfulCachePath(cacheDir, input, raw) {
170475
+ if (!cacheDir) return null;
170476
+ await (0, import_promises2.mkdir)(cacheDir, { recursive: true, mode: 448 });
170477
+ const key = (0, import_node_crypto.createHash)("sha256").update("talos-doc-convert-pdf-v1\0").update((0, import_node_path2.extname)(input).toLowerCase()).update("\0").update(raw).digest("hex");
170478
+ return (0, import_node_path2.join)(cacheDir, `${key}.pdf`);
170479
+ }
170480
+ async function readCachedPdf(path) {
170481
+ if (!path) return null;
170482
+ const bytes = await (0, import_promises2.readFile)(path).catch(() => null);
170483
+ return bytes && bytes.subarray(0, 5).toString("latin1") === "%PDF-" ? bytes : null;
170484
+ }
170485
+ async function writeCachedPdf(path, bytes) {
170486
+ if (!path) return;
170487
+ const tmp = `${path}.${process.pid}.tmp`;
170488
+ try {
170489
+ await (0, import_promises2.writeFile)(tmp, bytes, { mode: 384 });
170490
+ await (0, import_promises2.rename)(tmp, path);
170491
+ } finally {
170492
+ await (0, import_promises2.rm)(tmp, { force: true }).catch(() => {
170493
+ });
170494
+ }
170495
+ }
170496
+ async function renderViaSoffice(input, cachePath) {
170472
170497
  if (!resolveSoffice()) return null;
170498
+ const cached = await readCachedPdf(cachePath);
170499
+ if (cached) return { bytes: cached, cached: true };
170473
170500
  try {
170474
- return await convertToPdfViaSoffice(input);
170501
+ const bytes = await convertToPdfViaSoffice(input);
170502
+ await writeCachedPdf(cachePath, bytes).catch((e) => {
170503
+ process.stderr.write(`doc-convert: cache write skipped (${e.message})
170504
+ `);
170505
+ });
170506
+ return { bytes, cached: false };
170475
170507
  } catch (e) {
170476
170508
  process.stderr.write(`doc-convert: LibreOffice failed (${e.message}); falling back to HTML render
170477
170509
  `);
@@ -170493,8 +170525,13 @@ async function renderViaOfficeService(input, raw) {
170493
170525
  }
170494
170526
  }
170495
170527
  async function main() {
170496
- const [input, out] = process.argv.slice(2);
170497
- if (!input || !out) die("usage: doc-convert <in.docx|pptx|xlsx> <out.pdf>", 64);
170528
+ const args = process.argv.slice(2);
170529
+ const cacheIdx = args.indexOf("--cache-dir");
170530
+ const cacheDir = cacheIdx >= 0 ? args[cacheIdx + 1] : process.env.TALOS_OFFICE_CACHE_DIR;
170531
+ const consumed = /* @__PURE__ */ new Set([cacheIdx, cacheIdx >= 0 ? cacheIdx + 1 : -1]);
170532
+ const [input, out] = args.filter((_4, i) => !consumed.has(i));
170533
+ if (!input || !out) die("usage: doc-convert <in.docx|pptx|xlsx> <out.pdf> [--cache-dir <dir>]", 64);
170534
+ if (cacheIdx >= 0 && !cacheDir) die("--cache-dir requires a directory", 64);
170498
170535
  if (!/\.pdf$/i.test(out)) die("output must be a .pdf path", 64);
170499
170536
  let raw;
170500
170537
  try {
@@ -170503,10 +170540,11 @@ async function main() {
170503
170540
  die(`cannot read ${input}: ${e.message}`, 66);
170504
170541
  }
170505
170542
  const buffer = raw.buffer.slice(raw.byteOffset, raw.byteOffset + raw.byteLength);
170506
- const loBytes = await renderViaSoffice(input);
170507
- if (loBytes) {
170508
- await (0, import_promises2.writeFile)(out, Buffer.from(loBytes));
170509
- process.stdout.write(`wrote ${out} [pdf (libreoffice)]
170543
+ const cachePath = await faithfulCachePath(cacheDir, input, raw).catch((e) => die(`cannot prepare cache: ${e.message}`, 73));
170544
+ const loResult = await renderViaSoffice(input, cachePath);
170545
+ if (loResult) {
170546
+ await (0, import_promises2.writeFile)(out, Buffer.from(loResult.bytes));
170547
+ process.stdout.write(`wrote ${out} [pdf (libreoffice${loResult.cached ? ", cache hit" : ""})]
170510
170548
  `);
170511
170549
  return;
170512
170550
  }
@@ -152164,7 +152164,7 @@ function themeCssVars(theme) {
152164
152164
  `--secondary:${c.secondary}`,
152165
152165
  `--text:${c.text}`,
152166
152166
  `--muted:${c.muted}`,
152167
- `--card-bg:${c.tableHeaderBg}`,
152167
+ `--surface-card:${c.tableHeaderBg}`,
152168
152168
  `--border:${c.tableBorder}`,
152169
152169
  `--slide-bg:${theme.pptx.background}`,
152170
152170
  `--title-color:${theme.pptx.titleColor}`,
@@ -154029,6 +154029,11 @@ async function generateDocument(kind, spec, theme) {
154029
154029
  }
154030
154030
  }
154031
154031
 
154032
+ // packages/core/dist/document/html-escape.js
154033
+ function escapeHtml(value) {
154034
+ return String(value).replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
154035
+ }
154036
+
154032
154037
  // packages/core/dist/document/doc-html.js
154033
154038
  var FONT_STACK = 'TalosCJK, "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", "Source Han Sans SC", "WenQuanYi Zen Hei", sans-serif';
154034
154039
  function stackWith(font) {
@@ -154060,11 +154065,8 @@ img { max-width: 100%; margin: 8pt 0; }
154060
154065
  .cover img.logo { max-height: 60pt; margin-bottom: 24pt; }
154061
154066
  `.trim();
154062
154067
  }
154063
- function foldFullwidthAlnum2(s) {
154064
- return s.replace(/[0-9A-Za-z]/g, (c) => String.fromCharCode(c.charCodeAt(0) - 65248)).replace(/ /g, " ");
154065
- }
154066
154068
  function norm(s) {
154067
- return foldFullwidthAlnum2(String(s)).replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
154069
+ return escapeHtml(foldFullwidthAlnum(String(s)));
154068
154070
  }
154069
154071
  function paragraphHtml(text) {
154070
154072
  return `<p>${norm(text).replace(/\r?\n/g, "<br>")}</p>`;
@@ -154191,9 +154193,6 @@ function parseTemplate(src) {
154191
154193
  }
154192
154194
  return walk();
154193
154195
  }
154194
- function escapeHtml(s) {
154195
- return s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
154196
- }
154197
154196
  function resolve(path, scope) {
154198
154197
  if (path === "." || path === "this")
154199
154198
  return scope.ctx;
@@ -154252,7 +154251,7 @@ function interpolate(src, data) {
154252
154251
  }
154253
154252
 
154254
154253
  // packages/core/dist/document/html-templates.js
154255
- var CARD = "background:var(--card-bg);border:1px solid var(--border);border-radius:16px";
154254
+ var CARD = "background:var(--surface-card);border:1px solid var(--border);border-radius:16px";
154256
154255
  var TEMPLATES = {
154257
154256
  cover: {
154258
154257
  html: `<section class="slide" style="padding:0;display:flex;flex-direction:column;justify-content:center;padding-left:88px">