@litfamily/litmuse 0.1.1 → 0.1.2

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 (168) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +20 -0
  3. package/README_ko-KR.md +20 -0
  4. package/bin/litmuse.mjs +4 -0
  5. package/lib/stage-card.mjs +180 -5
  6. package/package.json +1 -1
  7. package/plugin/.muse-plugin/plugin.json +26 -11
  8. package/plugin/commands/lit-status.md +2 -1
  9. package/plugin/commands/lit.md +1 -1
  10. package/plugin/hooks/lib/digest.mjs +1 -1
  11. package/plugin/hooks/lib/next-step.mjs +68 -0
  12. package/plugin/hooks/lib/routes.mjs +59 -0
  13. package/plugin/hooks/lib/workflow-stack.mjs +318 -0
  14. package/plugin/hooks/session-start.mjs +29 -2
  15. package/plugin/hooks/user-prompt-submit.mjs +138 -7
  16. package/plugin/lib/capped-fetch.mjs +25 -0
  17. package/plugin/lib/pinned-fetch.mjs +79 -0
  18. package/plugin/lib/plan-task-rows.mjs +32 -0
  19. package/plugin/lib/render_pages.py +29 -1
  20. package/plugin/lib/rewind/collect.mjs +679 -0
  21. package/plugin/lib/rewind/gauge.mjs +72 -0
  22. package/plugin/lib/rewind/next-step.mjs +91 -0
  23. package/plugin/lib/rewind/redact.mjs +83 -0
  24. package/plugin/lib/rewind/transcripts.mjs +227 -0
  25. package/plugin/skills/frontend-ui-ux/SKILL.md +24 -9
  26. package/plugin/skills/frontend-ui-ux/kit/components.css +414 -0
  27. package/plugin/skills/frontend-ui-ux/kit/fonts.json +22 -0
  28. package/plugin/skills/frontend-ui-ux/kit/layouts/landing.html +164 -0
  29. package/plugin/skills/frontend-ui-ux/kit/layouts/settings.html +114 -0
  30. package/plugin/skills/frontend-ui-ux/kit/layouts/workspace.html +115 -0
  31. package/plugin/skills/frontend-ui-ux/kit/styles/computational-brutalism.json +227 -0
  32. package/plugin/skills/frontend-ui-ux/kit/styles/field-notes.json +205 -0
  33. package/plugin/skills/frontend-ui-ux/kit/styles/graphite.json +200 -0
  34. package/plugin/skills/frontend-ui-ux/kit/styles/meadow.json +199 -0
  35. package/plugin/skills/frontend-ui-ux/kit/styles/neo-brutalism.json +222 -0
  36. package/plugin/skills/frontend-ui-ux/kit/ui.js +226 -0
  37. package/plugin/skills/frontend-ui-ux/references/complete-contract.md +2 -1
  38. package/plugin/skills/frontend-ui-ux/references/craft-floor.md +9 -4
  39. package/plugin/skills/frontend-ui-ux/references/default-editorial-pixel.json +59 -7
  40. package/plugin/skills/frontend-ui-ux/references/house-styles.md +234 -0
  41. package/plugin/skills/frontend-ui-ux/references/motion-guide.md +3 -3
  42. package/plugin/skills/frontend-ui-ux/references/slop-register.md +7 -7
  43. package/plugin/skills/frontend-ui-ux/scripts/design-lock.mjs +400 -0
  44. package/plugin/skills/frontend-ui-ux/scripts/interface-probe-lock.mjs +196 -0
  45. package/plugin/skills/frontend-ui-ux/scripts/interface-probe-page.mjs +31 -10
  46. package/plugin/skills/frontend-ui-ux/scripts/interface-probe-rules.mjs +19 -7
  47. package/plugin/skills/frontend-ui-ux/scripts/interface-probe-static.mjs +4 -0
  48. package/plugin/skills/frontend-ui-ux/scripts/interface-probe.mjs +59 -7
  49. package/plugin/skills/frontend-ui-ux/scripts/pixel-art.mjs +60 -0
  50. package/plugin/skills/lit-adhd/SKILL.md +188 -0
  51. package/plugin/skills/lit-adhd/references/card-examples.md +121 -0
  52. package/plugin/skills/lit-adhd/references/status-taxonomy.md +43 -0
  53. package/plugin/skills/lit-adhd/scripts/rewind.mjs +11 -0
  54. package/plugin/skills/lit-diagram-drawer/SKILL.md +3 -3
  55. package/plugin/skills/lit-diagram-drawer/scripts/diagram-craft-rules.mjs +1 -2
  56. package/plugin/skills/lit-diagram-drawer/scripts/visual-quality.mjs +0 -1
  57. package/plugin/skills/lit-docx/SKILL.md +1 -1
  58. package/plugin/skills/lit-hijack/SKILL.md +589 -0
  59. package/plugin/skills/lit-hijack/references/capture-and-provenance.md +123 -0
  60. package/plugin/skills/lit-hijack/references/clean-room-build.md +133 -0
  61. package/plugin/skills/lit-hijack/references/distil-and-licence.md +128 -0
  62. package/plugin/skills/lit-hijack/references/gap-and-port-design.md +152 -0
  63. package/plugin/skills/lit-hijack/references/parity-and-report.md +133 -0
  64. package/plugin/skills/lit-hijack/scripts/hijack.mjs +879 -0
  65. package/plugin/skills/lit-pptx/SKILL.md +20 -9
  66. package/plugin/skills/lit-pptx/fonts/jetbrains-mono/JetBrainsMono-Bold.ttf +0 -0
  67. package/plugin/skills/lit-pptx/fonts/jetbrains-mono/JetBrainsMono-Regular.ttf +0 -0
  68. package/plugin/skills/lit-pptx/fonts/jetbrains-mono/OFL.txt +93 -0
  69. package/plugin/skills/lit-pptx/fonts/provenance.json +32 -1
  70. package/plugin/skills/lit-pptx/references/direction-step.md +19 -2
  71. package/plugin/skills/lit-pptx/references/examples/13-engineering-review-computational-brutalism-ko.md +173 -0
  72. package/plugin/skills/lit-pptx/references/examples/14-launch-pitch-neo-brutalism-ko.md +154 -0
  73. package/plugin/skills/lit-pptx/references/examples/15-design-lecture-bauhaus-ko.md +139 -0
  74. package/plugin/skills/lit-pptx/references/examples/16-community-programme-riso-ko.md +158 -0
  75. package/plugin/skills/lit-pptx/references/examples/17-public-data-briefing-newsroom-ko.md +179 -0
  76. package/plugin/skills/lit-pptx/references/tonalities/bauhaus.md +112 -0
  77. package/plugin/skills/lit-pptx/references/tonalities/computational-brutalism.md +118 -0
  78. package/plugin/skills/lit-pptx/references/tonalities/neo-brutalism.md +125 -0
  79. package/plugin/skills/lit-pptx/references/tonalities/newsroom.md +115 -0
  80. package/plugin/skills/lit-pptx/references/tonalities/riso.md +109 -0
  81. package/plugin/skills/lit-pptx/scripts/compile-deck.js +1 -1
  82. package/plugin/skills/lit-pptx/scripts/craft_extras.py +136 -92
  83. package/plugin/skills/lit-pptx/scripts/embed_fonts.py +13 -1
  84. package/plugin/skills/lit-pptx/scripts/lib/grid-resolver.js +105 -116
  85. package/plugin/skills/lit-pptx/scripts/lib/layout-resolver.js +9 -15
  86. package/plugin/skills/lit-pptx/scripts/lib/render-pack.js +368 -561
  87. package/plugin/skills/lit-pptx/scripts/lib/slide-grid.js +104 -0
  88. package/plugin/skills/lit-pptx/scripts/lib/template-registry.js +57 -6
  89. package/plugin/skills/lit-pptx/scripts/qa_deck.py +4 -4
  90. package/plugin/skills/lit-pptx/templates/tonalities/atlas/pack.yaml +1 -1
  91. package/plugin/skills/lit-pptx/templates/tonalities/bauhaus/pack.yaml +98 -0
  92. package/plugin/skills/lit-pptx/templates/tonalities/chalk/pack.yaml +1 -1
  93. package/plugin/skills/lit-pptx/templates/tonalities/computational-brutalism/pack.yaml +99 -0
  94. package/plugin/skills/lit-pptx/templates/tonalities/gazette/pack.yaml +1 -1
  95. package/plugin/skills/lit-pptx/templates/tonalities/ledger/pack.yaml +1 -1
  96. package/plugin/skills/lit-pptx/templates/tonalities/neo-brutalism/pack.yaml +107 -0
  97. package/plugin/skills/lit-pptx/templates/tonalities/newsroom/pack.yaml +99 -0
  98. package/plugin/skills/lit-pptx/templates/tonalities/night/pack.yaml +1 -1
  99. package/plugin/skills/lit-pptx/templates/tonalities/paper/pack.yaml +1 -1
  100. package/plugin/skills/lit-pptx/templates/tonalities/riso/pack.yaml +99 -0
  101. package/plugin/skills/lit-pptx/templates/tonalities/signal/pack.yaml +2 -2
  102. package/plugin/skills/lit-pptx/templates/tonalities/studio/pack.yaml +1 -1
  103. package/plugin/skills/lit-recap/SKILL.md +3 -0
  104. package/plugin/skills/lit-schematic/SKILL.md +241 -0
  105. package/plugin/skills/lit-schematic/examples/iso-heat-exchanger/build.mjs +14 -0
  106. package/plugin/skills/lit-schematic/examples/iso-heat-exchanger/scene.mjs +8 -0
  107. package/plugin/skills/lit-schematic/examples/journal-adsorption-rig/build.mjs +22 -0
  108. package/plugin/skills/lit-schematic/examples/pid-dosing-skid/build.mjs +73 -0
  109. package/plugin/skills/lit-schematic/references/cutaway-outputs.md +25 -0
  110. package/plugin/skills/lit-schematic/references/isometric-craft.md +24 -0
  111. package/plugin/skills/lit-schematic/references/journal-figures.md +19 -0
  112. package/plugin/skills/lit-schematic/references/kit-api.md +27 -0
  113. package/plugin/skills/lit-schematic/references/pfd-and-layout.md +20 -0
  114. package/plugin/skills/lit-schematic/references/pid-drafting.md +26 -0
  115. package/plugin/skills/lit-schematic/references/pid-symbols.md +27 -0
  116. package/plugin/skills/lit-schematic/references/verification.md +24 -0
  117. package/plugin/skills/lit-schematic/scripts/doctor.mjs +13 -0
  118. package/plugin/skills/lit-schematic/scripts/lib/figure.mjs +49 -0
  119. package/plugin/skills/lit-schematic/scripts/lib/isa.mjs +18 -0
  120. package/plugin/skills/lit-schematic/scripts/lib/iso.mjs +58 -0
  121. package/plugin/skills/lit-schematic/scripts/lib/legend-sheet.mjs +10 -0
  122. package/plugin/skills/lit-schematic/scripts/lib/qa-core.mjs +241 -0
  123. package/plugin/skills/lit-schematic/scripts/lib/sheet.mjs +446 -0
  124. package/plugin/skills/lit-schematic/scripts/lib/svg.mjs +39 -0
  125. package/plugin/skills/lit-schematic/scripts/lib/theme.mjs +25 -0
  126. package/plugin/skills/lit-schematic/scripts/motion.mjs +8 -0
  127. package/plugin/skills/lit-schematic/scripts/qa.mjs +21 -0
  128. package/plugin/skills/lit-schematic/scripts/render.mjs +38 -0
  129. package/plugin/skills/lit-schematic/scripts/scaffold.mjs +73 -0
  130. package/plugin/skills/lit-scientific-visualization/SKILL.md +2 -0
  131. package/plugin/skills/lit-typographic-motion/SKILL.md +12 -8
  132. package/plugin/skills/lit-typographic-motion/engine/THIRD_PARTY_NOTICES +11 -0
  133. package/plugin/skills/lit-typographic-motion/engine/blender/runner.py +218 -0
  134. package/plugin/skills/lit-typographic-motion/engine/core/presets.mjs +1 -1
  135. package/plugin/skills/lit-typographic-motion/engine/node/assets.mjs +218 -0
  136. package/plugin/skills/lit-typographic-motion/engine/node/blender.mjs +144 -0
  137. package/plugin/skills/lit-typographic-motion/engine/node/board.mjs +136 -0
  138. package/plugin/skills/lit-typographic-motion/engine/node/done.mjs +42 -3
  139. package/plugin/skills/lit-typographic-motion/engine/node/gallery.mjs +120 -0
  140. package/plugin/skills/lit-typographic-motion/engine/node/house.mjs +44 -0
  141. package/plugin/skills/lit-typographic-motion/engine/node/pipeline.mjs +18 -7
  142. package/plugin/skills/lit-typographic-motion/engine/node/runtime.mjs +6 -3
  143. package/plugin/skills/lit-typographic-motion/engine/node/scaffold.mjs +210 -0
  144. package/plugin/skills/lit-typographic-motion/engine/node/score.mjs +639 -0
  145. package/plugin/skills/lit-typographic-motion/engine/node/sound-cli.mjs +21 -4
  146. package/plugin/skills/lit-typographic-motion/engine/node/sound.mjs +126 -27
  147. package/plugin/skills/lit-typographic-motion/engine/node/stage-serve.mjs +29 -5
  148. package/plugin/skills/lit-typographic-motion/engine/node/stage.mjs +82 -14
  149. package/plugin/skills/lit-typographic-motion/engine/node/styles.mjs +214 -0
  150. package/plugin/skills/lit-typographic-motion/engine/node/treatment.mjs +311 -10
  151. package/plugin/skills/lit-typographic-motion/engine/vendor/three/LICENSE +21 -0
  152. package/plugin/skills/lit-typographic-motion/engine/vendor/three/addons/environments/RoomEnvironment.js +144 -0
  153. package/plugin/skills/lit-typographic-motion/engine/vendor/three/addons/geometries/RoundedBoxGeometry.js +155 -0
  154. package/plugin/skills/lit-typographic-motion/engine/vendor/three/three.module.js +54571 -0
  155. package/plugin/skills/lit-typographic-motion/recipes/hero-3d/expected.json +69 -0
  156. package/plugin/skills/lit-typographic-motion/recipes/hero-3d/index.html +139 -0
  157. package/plugin/skills/lit-typographic-motion/references/depth.md +122 -0
  158. package/plugin/skills/lit-typographic-motion/references/sound.md +74 -16
  159. package/plugin/skills/lit-typographic-motion/references/stage.md +29 -5
  160. package/plugin/skills/lit-typographic-motion/references/studio.md +245 -0
  161. package/plugin/skills/lit-typographic-motion/references/treatment.md +31 -8
  162. package/plugin/skills/lit-typographic-motion/scripts/motion.mjs +48 -7
  163. package/plugin/skills/lit-typographic-motion/styles/paper.json +41 -0
  164. package/plugin/skills/lit-typographic-motion/styles/swiss-signal.json +41 -0
  165. package/plugin/skills/lit-typographic-motion/styles/terminalcore.json +42 -0
  166. package/plugin/skills/lit-typographic-motion/styles/tidal.json +41 -0
  167. package/plugin/skills/readme-studio/SKILL.md +1 -1
  168. package/plugin/skills/start-work/SKILL.md +6 -0
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * slide-grid.js — where things sit on a slide, the same for every tonality.
5
+ *
6
+ * One column grid per canvas and one set of zones per slide kind. A tonality changes how a zone
7
+ * looks (face, weight, size, colour, rules, fills, ornaments), never where it is: every title,
8
+ * header, body and footer of a deck lands on the coordinates below whichever pack draws it, so two
9
+ * tonalities of one source differ in look and never in placement. The density dial sets type and
10
+ * spacing inside the zones; it no longer moves the margins. Values are points on the 960 x 540
11
+ * (16:9) or 720 x 540 (4:3) canvas; `emu()` gives the same box in EMU (12,700 per point).
12
+ *
13
+ * Content slides
14
+ * header the band over the title: grid marks, the slide index, a kicker label
15
+ * title the title frame of every treatment (top-rule, band, top-plain-large, kicker-numeral,
16
+ * side-rail, bottom-anchor); a treatment styles it and never moves it
17
+ * aside right of the title: the kicker numeral
18
+ * body everything the slide says, from the first block to the floor
19
+ * footer the sample-data tag and the source line's foot
20
+ * Display slides (statement, quote, section, closing statement)
21
+ * header the part number or kicker over the sentence
22
+ * title the sentence, centred in its frame
23
+ * support the line under it (the quote's speaker, what the part opens with)
24
+ * side the columns right of the sentence, bled to the page edge (a composition, a rail)
25
+ * Covers
26
+ * header the meta line (date, team, presenter)
27
+ * title the deck title, standing on the frame's foot
28
+ * subtitle the line under the title
29
+ * strip the headline figures or the parts, across the page above the footer
30
+ * side the columns right of the title, bled to the page edge (a field, a picture, a numeral)
31
+ */
32
+
33
+ const CANVAS = { "16:9": { W: 960, H: 540 }, "4:3": { W: 720, H: 540 } };
34
+
35
+ // Side margin, column width and gutter: 12 columns and 11 gutters that sum exactly to the width.
36
+ const COLUMNS = { "16:9": { margin: 24, col: 54, gutter: 24 }, "4:3": { margin: 24, col: 45, gutter: 12 } };
37
+
38
+ const FOOTER = { cols: [1, 12], y: 498, h: 18 };
39
+
40
+ const ZONES = {
41
+ content: {
42
+ header: { cols: [1, 12], y: 12, h: 18 },
43
+ title: { cols: [1, 10], y: 36, h: 72 },
44
+ aside: { cols: [11, 12], y: 36, h: 72 },
45
+ body: { cols: [1, 12], y: 120, h: 366 },
46
+ footer: FOOTER,
47
+ },
48
+ display: {
49
+ header: { cols: [1, 9], y: 84, h: 36 },
50
+ title: { cols: [1, 9], y: 132, h: 216 },
51
+ support: { cols: [1, 8], y: 372, h: 114 },
52
+ side: { cols: [10, 12], y: 0, h: 540, bleed: true },
53
+ footer: FOOTER,
54
+ },
55
+ cover: {
56
+ header: { cols: [1, 8], y: 36, h: 24 },
57
+ title: { cols: [1, 9], y: 144, h: 168 },
58
+ subtitle: { cols: [1, 8], y: 324, h: 72 },
59
+ strip: { cols: [1, 12], y: 414, h: 66 },
60
+ side: { cols: [10, 12], y: 0, h: 540, bleed: true },
61
+ footer: FOOTER,
62
+ },
63
+ };
64
+
65
+ function columns(canvas) {
66
+ const c = COLUMNS[canvas];
67
+ if (!c) throw new Error(`unknown canvas "${canvas}"; canvases: ${Object.keys(COLUMNS).join(", ")}`);
68
+ return c;
69
+ }
70
+
71
+ /** Columns a…b (1-based, inclusive) as x and width in points. */
72
+ function span(canvas, a, b) {
73
+ const c = columns(canvas);
74
+ const pitch = c.col + c.gutter;
75
+ return { x: c.margin + pitch * (a - 1), w: pitch * (b - a + 1) - c.gutter };
76
+ }
77
+
78
+ /**
79
+ * One zone as a box in points. A bled zone starts half a gutter left of its first column and runs
80
+ * to the page's right edge, so what fills it meets the edge and keeps the gutter from the text.
81
+ */
82
+ function zone(canvas, kind, name) {
83
+ const spec = (ZONES[kind] || {})[name];
84
+ if (!spec) throw new Error(`no ${name} zone on a ${kind} slide`);
85
+ const s = span(canvas, spec.cols[0], spec.cols[1]);
86
+ if (spec.bleed) {
87
+ const x = s.x - columns(canvas).gutter / 2;
88
+ return { x, y: spec.y, w: CANVAS[canvas].W - x, h: spec.h, a: spec.cols[0], b: spec.cols[1] };
89
+ }
90
+ return { x: s.x, y: spec.y, w: s.w, h: spec.h, a: spec.cols[0], b: spec.cols[1] };
91
+ }
92
+
93
+ /** Every zone of every slide kind on a canvas, in points. */
94
+ function zones(canvas) {
95
+ return Object.fromEntries(Object.entries(ZONES).map(([kind, set]) => [kind, Object.fromEntries(Object.keys(set).map((name) => [name, zone(canvas, kind, name)]))]));
96
+ }
97
+
98
+ /** A box in points as EMU, the unit a .pptx file stores. */
99
+ function emu(box) {
100
+ const e = (v) => Math.round(v * 12700);
101
+ return { x: e(box.x), y: e(box.y), w: e(box.w), h: e(box.h) };
102
+ }
103
+
104
+ module.exports = { CANVAS, COLUMNS, ZONES, span, zone, zones, emu };
@@ -428,19 +428,29 @@ const FACES = {
428
428
  "A2Z Medium": { font: "에이투지체 5 Medium", bold: false },
429
429
  "A2Z Bold": { font: "에이투지체 7 Bold", bold: false },
430
430
  "A2Z Black": { font: "에이투지체 9 Black", bold: false },
431
+ // A Latin-only face: Hangul and other East Asian text in its runs is set in Pretendard.
432
+ "JetBrains Mono Regular": { font: "JetBrains Mono", bold: false, ea: "Pretendard" },
433
+ "JetBrains Mono Bold": { font: "JetBrains Mono", bold: true, ea: "Pretendard" },
431
434
  };
432
- const DECORATIONS = ["accent-rule", "hairline-rule", "header-band", "rail-fill", "colour-field", "caption-band", "column-hairlines", "box-outline", "mark-underline", "numeral"];
435
+ const DECORATIONS = ["accent-rule", "hairline-rule", "header-band", "rail-fill", "colour-field", "caption-band", "column-hairlines", "box-outline", "mark-underline", "numeral", "grid-marks"];
433
436
  const SLIDE_ROLES = ["content", "data", "data-takeaway", "sequence", "image", "statement", "reference", "definition"];
434
437
  const FILL_POLICIES = ["distribute", "step-up", "anchor-visual", "change-family"];
435
438
  // How a pack carries its display slides: the device on a type-led cover, around a statement,
436
439
  // behind a big number and under a closing's next step (`display.index` adds the part index to
437
440
  // section slides).
438
441
  const DISPLAY_DEVICES = {
439
- cover: ["plain", "drench", "plate", "rail", "band", "figures", "rules", "numeral"],
440
- statement: ["open", "drench", "rules", "plate", "offset"],
442
+ cover: ["plain", "drench", "plate", "rail", "band", "figures", "rules", "numeral", "geometry"],
443
+ statement: ["open", "drench", "rules", "plate", "offset", "geometry"],
441
444
  number: ["field", "tint", "outline"],
442
445
  closing: ["band", "box", "rules"],
443
446
  };
447
+ // Allowances a pack may claim from the gate, each a bounded value (see craft_extras.py). The accent
448
+ // limits only widen: four hue families pass every slide by default, so a pack may allow five or six.
449
+ const QA_ALLOWANCES = {
450
+ "accent-families": (v) => Number.isInteger(v) && v >= 5 && v <= 6,
451
+ "display-accent-families": (v) => Number.isInteger(v) && v >= 5 && v <= 6,
452
+ "offset-shadow": (v) => typeof v === "boolean",
453
+ };
444
454
  // Contrast pairs every pack must meet (text pairs 4.5:1, large marks 3:1).
445
455
  const CONTRAST_PAIRS = [["ink", "ground", 4.5], ["ink", "surface", 4.5], ["ink-muted", "ground", 4.5], ["ink-muted", "surface", 4.5],
446
456
  ["accent", "ground", 3.0], ["accent-deep", "accent-tint", 4.5], ["ink", "accent-tint", 4.5], ["on-field", "field", 4.5],
@@ -473,9 +483,12 @@ function validatePack(pack, file) {
473
483
  for (const key of ["faces", "faces-a2z"]) {
474
484
  if (key === "faces-a2z" && pack[key] == null) continue;
475
485
  const faces = pack[key] || {};
486
+ // A Latin-only face may set the display roles; body text always stays in a Korean face.
476
487
  for (const role of FACE_KEYS) {
477
- need(FACES[faces[role]] && (key === "faces-a2z" || /^Pretendard /u.test(faces[role])), `${key}.${role} must name a bundled face${key === "faces-a2z" ? "" : " (Pretendard Regular or Bold)"}`);
488
+ const own = key === "faces-a2z" || /^Pretendard /u.test(faces[role]) || (role !== "body" && FACES[faces[role]] && FACES[faces[role]].ea);
489
+ need(FACES[faces[role]] && own, `${key}.${role} must name a bundled face${key === "faces-a2z" ? "" : role === "body" ? " (Pretendard Regular or Bold)" : " (Pretendard Regular or Bold, or JetBrains Mono Regular or Bold)"}`);
478
490
  }
491
+ need(!(FACES[faces.body] && FACES[faces.body].ea), `${key}.body must be a face that sets Hangul`);
479
492
  const distinct = new Set(FACE_KEYS.map((r) => faces[r]));
480
493
  need(distinct.size <= 3, `${key} uses ${distinct.size} faces; at most 3`);
481
494
  need(new Set([...distinct].map((f) => String(f).split(" ")[0])).size <= 2, `${key} mixes more than two families`);
@@ -497,10 +510,32 @@ function validatePack(pack, file) {
497
510
  }
498
511
  need(["presented", "reading"].includes(pack.ramp), "ramp must be presented or reading");
499
512
  need(typeof pack.hero === "boolean", "hero must be true or false");
513
+ // A report pack reads at its density step; a bold pack (a pitch, a launch, a campaign) may set presented type.
514
+ // Any pack may take the hero step on its cover and statements. Either way the words stay in the slide grid's
515
+ // zones: the register and the hero step change size, never place.
516
+ const register = pack.register == null ? "report" : pack.register;
517
+ need(["report", "bold"].includes(register), "register must be report or bold");
518
+ need(register === "bold" || pack.ramp === "reading", "presented type needs register: bold; a report pack keeps ramp: reading");
500
519
  need(Number.isInteger(pack.density) && pack.density >= 1 && pack.density <= 10, "density must be an integer 1-10");
501
520
  need(Number.isInteger(pack.variance) && pack.variance >= 4 && pack.variance <= 10, "variance must be an integer 4-10");
502
521
  need(pack.radius === 0 || pack.radius === 6, "radius must be 0 or 6");
503
522
  need(["border", "fill", "fill+border"].includes(pack.edge), "edge must be border, fill or fill+border");
523
+ // A heavier border and a hard offset shadow (zero blur) under surfaces; the shadow needs the border.
524
+ const weight = pack["edge-weight"];
525
+ need(weight == null || (typeof weight === "number" && weight >= 0.5 && weight <= 4), "edge-weight must be 0.5-4 pt");
526
+ const shadow = pack.shadow;
527
+ if (shadow != null) {
528
+ need(typeof shadow === "object" && typeof shadow.offset === "number" && shadow.offset >= 3 && shadow.offset <= 10, "shadow.offset must be 3-10 pt");
529
+ need(shadow && PALETTE_ROLES.includes(shadow.colour), "shadow.colour must name a palette role");
530
+ need(pack.edge !== "fill", "a shadow needs a bordered edge (border or fill+border)");
531
+ }
532
+ need(pack["edge-colour"] == null || PALETTE_ROLES.includes(pack["edge-colour"]), "edge-colour must name a palette role");
533
+ // What the gate allows this pack beyond the default craft floor (read by craft_extras.py).
534
+ const qa = pack.qa || {};
535
+ need(typeof qa === "object" && !Array.isArray(qa), "qa must be a map");
536
+ for (const k of Object.keys(qa)) need(QA_ALLOWANCES[k], `unknown qa allowance "${k}" (known: ${Object.keys(QA_ALLOWANCES).join(", ")})`);
537
+ for (const [k, ok] of Object.entries(QA_ALLOWANCES)) if (qa[k] != null) need(ok(qa[k]), `qa.${k} is out of range`);
538
+ need(!qa["offset-shadow"] || shadow != null, "qa.offset-shadow needs a shadow");
504
539
  const treatments = isList(pack.treatments) ? pack.treatments : [];
505
540
  need(treatments.length >= 3 && treatments.length <= 5, "treatments must list 3-5 title treatments");
506
541
  for (const t of treatments) need(grid.TREATMENTS.includes(t), `unknown treatment "${t}" (known: ${grid.TREATMENTS.join(", ")})`);
@@ -514,6 +549,10 @@ function validatePack(pack, file) {
514
549
  need(treatments.includes(t) && grid.FAMILIES[f] && grid.FAMILIES[f].allow.includes(t), `structure.${f} must be a treatment of the pack that the family allows`);
515
550
  }
516
551
  for (const d of pack.decoration || []) need(DECORATIONS.includes(d), `unknown decoration "${d}"`);
552
+ // Grid marks sit in the top margin on the ground: a band or a filled rail there would cover them.
553
+ if ((pack.decoration || []).includes("grid-marks")) {
554
+ need(!treatments.includes("band") && !(pack.decoration || []).includes("rail-fill"), "grid-marks needs the top margin on the ground (no band treatment, no rail-fill)");
555
+ }
517
556
  need(pack.rail == null || ["surface", "field"].includes(pack.rail), "rail must be surface or field");
518
557
  const display = pack.display || {};
519
558
  need(typeof display === "object" && !Array.isArray(display), "display must be a map");
@@ -634,12 +673,22 @@ function loadTonality(id, options = {}) {
634
673
  if (!pack.canvas.includes(canvas)) {
635
674
  throw new Error(`Tonality "${pack.id}" supports ${pack.canvas.join(", ")}, not canvas ${canvas}`);
636
675
  }
637
- const tok = grid.densityTokens(density, pack.ramp);
638
- const g = grid.makeGrid(canvas, tok.grid);
676
+ // Only a bold register sets presented type; a report pack reads at its density step.
677
+ const tok = grid.densityTokens(density, pack.register === "bold" ? pack.ramp : "reading");
678
+ const g = grid.makeGrid(canvas);
639
679
  // Pretendard by default; the A2Z map only when the source asks for it (`faces: a2z`).
640
680
  const wantsA2z = String(options.faces || "").toLowerCase() === "a2z" && pack["faces-a2z"];
641
681
  const faceMap = wantsA2z ? pack["faces-a2z"] : pack.faces;
642
682
  const faces = Object.fromEntries(FACE_KEYS.map((role) => [role, FACES[faceMap[role]]]));
683
+ // The deck may add decoration kinds to the pack's own list and turn the hero step off (`hero: false`,
684
+ // the calm opt-out); both arrive as front-matter strings.
685
+ const extra = String(options.decoration || "").split(",").map((d) => d.trim()).filter(Boolean);
686
+ for (const d of extra) if (!DECORATIONS.includes(d)) throw new Error(`decoration "${d}" is not a known kind (${DECORATIONS.join(", ")})`);
687
+ const decoration = [...new Set([...(pack.decoration || []), ...extra])];
688
+ if (decoration.includes("grid-marks") && (pack.treatments.includes("band") || decoration.includes("rail-fill"))) {
689
+ throw new Error(`grid-marks needs the top margin on the ground (no band treatment, no rail-fill); tonality "${pack.id}" has one`);
690
+ }
691
+ const hero = String(options.hero).toLowerCase() === "false" || options.hero === false ? false : pack.hero;
643
692
  const P = pack.palette;
644
693
  const template = {
645
694
  name: pack.name,
@@ -662,6 +711,8 @@ function loadTonality(id, options = {}) {
662
711
  capabilities: { font_roles: {} },
663
712
  pack: {
664
713
  ...pack,
714
+ decoration,
715
+ hero,
665
716
  legacy: false,
666
717
  capabilities: [],
667
718
  faces,
@@ -18,7 +18,7 @@ exits non-zero if ANY real defect is found:
18
18
  titles and the cover subtitle as labels, not sentences;
19
19
  OF-115 no empty region; OF-117 no light figure card on a
20
20
  dark ground; OF-116 with --sibling: another tonality of
21
- the same source draws another skeleton)
21
+ the same source sets titles and body in the same places)
22
22
 
23
23
  No LibreOffice / rendering engine required: everything is read from the OOXML
24
24
  via python-pptx. (A renderer is only ever needed for the *visual* pass, which
@@ -691,14 +691,14 @@ def run_validate(pptx: Path) -> dict[str, Any]:
691
691
  def qa(pptx: Path, sibling: Path | None = None) -> dict[str, Any]:
692
692
  from ooxml_integrity import check_package
693
693
  from deck_craft import analyse as analyse_craft
694
- from craft_extras import analyse as analyse_extras, compare_skeletons
694
+ from craft_extras import analyse as analyse_extras, compare_placement
695
695
 
696
696
  integrity = check_package(pptx)
697
697
  craft = analyse_craft(pptx)
698
698
  extras = analyse_extras(pptx)
699
- # Another tonality of the same source, when given: the two must not draw one skeleton (OF-116).
699
+ # Another tonality of the same source, when given: the two must place titles and body alike (OF-116).
700
700
  if sibling is not None:
701
- extras["findings"] += compare_skeletons(pptx, sibling)
701
+ extras["findings"] += compare_placement(pptx, sibling)
702
702
  validate_report = run_validate(pptx)
703
703
  inv_summary = summarize_inventory(load_inventory_issues(pptx))
704
704
  contrast = check_contrast(pptx)
@@ -37,7 +37,7 @@ palette:
37
37
  series: ["#2B4C7E", "#555B63", "#8A6A2E", "#3E7A5A"]
38
38
 
39
39
  ramp: reading
40
- hero: false
40
+ hero: true
41
41
  type-voice:
42
42
  title-tracking: 0
43
43
  display-tracking: -1
@@ -0,0 +1,98 @@
1
+ # Bauhaus: elementary geometry in primary colours, reserved for the display slides. The cover
2
+ # and the statements arrange a square, a circle, a bar and a triangle; content slides stay calm
3
+ # on off-white with black type and a single red accent. Not for data-heavy business reviews,
4
+ # finance, public-sector reporting or sombre subjects.
5
+ id: bauhaus
6
+ name: Bauhaus
7
+ version: "1.0.0"
8
+ intent: "Design, architecture and culture talks: geometry and primaries on the display slides."
9
+ canvas: ["16:9", "4:3"]
10
+
11
+ # Pretendard throughout; the A2Z set below only when the source asks (faces: a2z).
12
+ faces:
13
+ display: Pretendard Bold
14
+ title: Pretendard Bold
15
+ body: Pretendard Regular
16
+ label: Pretendard Bold
17
+ numeral: Pretendard Bold
18
+ faces-a2z:
19
+ display: A2Z Black
20
+ title: A2Z Black
21
+ body: Pretendard Regular
22
+ label: Pretendard Bold
23
+ numeral: A2Z Black
24
+
25
+ palette:
26
+ ground: "#F4F1EA"
27
+ surface: "#E8E3D6"
28
+ ink: "#141414"
29
+ ink-muted: "#4A4740"
30
+ line: "#141414"
31
+ accent: "#C8261C"
32
+ accent-deep: "#7A140E"
33
+ # The geometry's yellow: the square on display slides, the notice, a highlighted table row.
34
+ accent-tint: "#F2C230"
35
+ field: "#1F3C99"
36
+ on-field: "#FFFFFF"
37
+ positive: "#1E6B3A"
38
+ # The accent is red, so a loss stays in ink and carries its ▼ mark.
39
+ negative: "#141414"
40
+ # Red, blue, black, then an ochre: the yellow darkened so a bar keeps 3:1 on the ground.
41
+ series: ["#C8261C", "#1F3C99", "#141414", "#9E7700"]
42
+
43
+ ramp: reading
44
+ hero: true
45
+ type-voice:
46
+ title-tracking: -1
47
+ display-tracking: -2
48
+
49
+ density: 10
50
+ variance: 7
51
+ radius: 0
52
+ edge: fill
53
+
54
+ treatments: [top-rule, side-rail, kicker-numeral, statement, bottom-anchor]
55
+ role-defaults:
56
+ content: top-rule
57
+ data: top-rule
58
+ data-takeaway: side-rail
59
+ sequence: kicker-numeral
60
+ image: bottom-anchor
61
+ statement: statement
62
+ reference: top-rule
63
+ definition: side-rail
64
+
65
+ decoration: [accent-rule, numeral, colour-field]
66
+ layout-families: [asymmetric-feature, big-number, image-split, text-two-column, comparison, process, step-diagram, timeline, chart-insight, table-insight, kpi-row, quote, statement, agenda]
67
+ # Display slides: the device a type-led cover, a statement, a big number and a closing's next step
68
+ # are drawn with (see references/layout-families.md, "Display slides"). Geometry lives here only.
69
+ display:
70
+ cover: geometry
71
+ statement: geometry
72
+ number: field
73
+ closing: rules
74
+ covers: [cover-typographic, cover-split-field, cover-numeral]
75
+ sections: [section-field, section-numeral]
76
+ closings: [closing-ask, closing-summary-list, closing-statement]
77
+
78
+ table:
79
+ header: none
80
+ header-ink: ink
81
+ rules: header-rule
82
+ banding: none
83
+ totals: bold
84
+ chart:
85
+ gridlines: none
86
+ labels: direct
87
+ highlight: accent-on-muted
88
+ image:
89
+ frame: none
90
+
91
+ fill-order: [change-family, step-up, anchor-visual, distribute]
92
+ # The display composition puts red, blue and yellow side by side; content slides keep one accent.
93
+ avoid:
94
+ - geometry on content slides (it belongs to covers and statements)
95
+ - a circle, square or triangle as a bullet or icon
96
+ - more than one accent colour on a content slide
97
+ - yellow type on the off-white ground
98
+ - this look for grief, safety or financial-loss subjects
@@ -37,7 +37,7 @@ palette:
37
37
  series: ["#9A3412", "#1F3B33", "#4E5A66", "#2E6A8E"]
38
38
 
39
39
  ramp: reading
40
- hero: false
40
+ hero: true
41
41
  type-voice:
42
42
  title-tracking: -1
43
43
  display-tracking: -1
@@ -0,0 +1,99 @@
1
+ # Computational Brutalism: retro computing with the structure left visible. A monospaced Latin
2
+ # face for titles, labels and figures, the twelve-column grid marked in the top margin, ruled
3
+ # tables, a black terminal field with phosphor text and one electric-blue signal. Not for boards,
4
+ # finance or public-sector reporting, and not for long Korean reading: mono tires over paragraphs.
5
+ id: computational-brutalism
6
+ name: Computational Brutalism
7
+ version: "1.0.0"
8
+ intent: "Engineering reviews and developer launches: the grid, the numbers and the system shown."
9
+ canvas: ["16:9", "4:3"]
10
+
11
+ # JetBrains Mono sets the Latin of titles, labels and figures; Hangul in those runs is set in
12
+ # Pretendard. Body text stays Pretendard. No A2Z map: the mono face is the look.
13
+ faces:
14
+ display: JetBrains Mono Bold
15
+ title: JetBrains Mono Bold
16
+ body: Pretendard Regular
17
+ label: JetBrains Mono Regular
18
+ numeral: JetBrains Mono Bold
19
+
20
+ palette:
21
+ ground: "#EEEDE7"
22
+ surface: "#E2E1D9"
23
+ ink: "#111111"
24
+ ink-muted: "#4D4C47"
25
+ line: "#9A988F"
26
+ accent: "#1F43E0"
27
+ accent-deep: "#14267A"
28
+ accent-tint: "#D9DEF2"
29
+ # The terminal: a black field with phosphor-green text.
30
+ field: "#111111"
31
+ on-field: "#8CF5A6"
32
+ positive: "#1C6B35"
33
+ negative: "#B3261E"
34
+ series: ["#1F43E0", "#111111", "#7A786F"]
35
+
36
+ ramp: reading
37
+ hero: true
38
+ type-voice:
39
+ title-tracking: 0
40
+ display-tracking: -1
41
+
42
+ density: 10
43
+ variance: 7
44
+ radius: 0
45
+ edge: border
46
+ edge-weight: 1
47
+ edge-colour: ink
48
+
49
+ treatments: [top-rule, top-plain-large, side-rail, bottom-anchor, statement]
50
+ role-defaults:
51
+ content: top-rule
52
+ data: top-rule
53
+ data-takeaway: bottom-anchor
54
+ sequence: top-rule
55
+ image: bottom-anchor
56
+ statement: statement
57
+ reference: top-rule
58
+ definition: side-rail
59
+ # Each family's own title here: a chart under a ruled title, a table read up to its title, a
60
+ # comparison as a spec sheet beside its rail, a sequence under a large mono headline.
61
+ structure:
62
+ chart-insight: top-rule
63
+ table-insight: bottom-anchor
64
+ comparison: side-rail
65
+ step-diagram: top-plain-large
66
+ # The grid and its numbers are the decoration; nothing is filled for ornament.
67
+ decoration: [hairline-rule, column-hairlines, box-outline, grid-marks, numeral]
68
+ layout-families: [dashboard-grid, kpi-row, chart-insight, full-chart, table-insight, ledger-table, method, step-diagram, matrix-2x2, comparison, text-two-column, references-appendix, quote, statement]
69
+ # Display slides: the device a type-led cover, a statement, a big number and a closing's next step
70
+ # are drawn with (see references/layout-families.md, "Display slides").
71
+ display:
72
+ cover: drench
73
+ statement: drench
74
+ number: outline
75
+ closing: box
76
+ covers: [cover-typographic, cover-index, cover-figures]
77
+ sections: [section-numeral, section-rule]
78
+ closings: [closing-decision-box, closing-ask, closing-summary-list]
79
+
80
+ table:
81
+ header: none
82
+ header-ink: ink
83
+ rules: grid
84
+ banding: none
85
+ totals: bold
86
+ protagonist: accent-tint
87
+ chart:
88
+ gridlines: hairline
89
+ labels: direct
90
+ highlight: accent-on-muted
91
+ image:
92
+ frame: hairline
93
+
94
+ fill-order: [change-family, distribute, anchor-visual, step-up]
95
+ avoid:
96
+ - mono body paragraphs (body text stays Pretendard)
97
+ - the accent as decoration (it marks the protagonist number or series)
98
+ - rounded corners, shadows, gradients
99
+ - a green other than the phosphor text on the terminal field
@@ -38,7 +38,7 @@ palette:
38
38
  series: ["#A61B1B", "#233A4F", "#4A5568", "#7A6A2A"]
39
39
 
40
40
  ramp: reading
41
- hero: false
41
+ hero: true
42
42
  type-voice:
43
43
  title-tracking: -1
44
44
  display-tracking: -1
@@ -38,7 +38,7 @@ palette:
38
38
  series: ["#0E6B5A", "#4B5866", "#9A6A12", "#2F5F8A"]
39
39
 
40
40
  ramp: reading
41
- hero: false
41
+ hero: true
42
42
  type-voice:
43
43
  title-tracking: -1
44
44
  display-tracking: -1
@@ -0,0 +1,107 @@
1
+ # Neo-Brutalism: loud on purpose. Thick black borders, hard offset shadows with no blur, flat
2
+ # saturated fills (yellow cards, a pink accent, a blue band) and heavy type on a warm cream ground.
3
+ # Not for formal reports, academic talks, sensitive subjects, dense reviews or decks under a
4
+ # corporate brand guide.
5
+ id: neo-brutalism
6
+ name: Neo-Brutalism
7
+ version: "1.0.0"
8
+ intent: "Pitches, launches and demo days: bold fills, black borders, hard shadows."
9
+ canvas: ["16:9", "4:3"]
10
+
11
+ # Pretendard throughout; the A2Z set below only when the source asks (faces: a2z).
12
+ faces:
13
+ display: Pretendard Bold
14
+ title: Pretendard Bold
15
+ body: Pretendard Regular
16
+ label: Pretendard Bold
17
+ numeral: Pretendard Bold
18
+ faces-a2z:
19
+ display: A2Z Black
20
+ title: A2Z Black
21
+ body: Pretendard Regular
22
+ label: Pretendard Bold
23
+ numeral: A2Z Black
24
+
25
+ palette:
26
+ ground: "#FFF5E1"
27
+ surface: "#FFD43B"
28
+ ink: "#111111"
29
+ ink-muted: "#3A3A3A"
30
+ line: "#111111"
31
+ accent: "#D81B5C"
32
+ accent-deep: "#6B0F2E"
33
+ accent-tint: "#FFC9DA"
34
+ field: "#2F4BFF"
35
+ on-field: "#FFFFFF"
36
+ positive: "#137A43"
37
+ # The accent is pink-red, so a loss stays in ink and carries its ▼ mark.
38
+ negative: "#111111"
39
+ series: ["#D81B5C", "#2F4BFF", "#111111", "#137A43"]
40
+
41
+ # A bold register: presented type and the hero step, filling the same zones every pack uses.
42
+ register: bold
43
+ ramp: presented
44
+ hero: true
45
+ type-voice:
46
+ title-tracking: -2
47
+ display-tracking: -3
48
+
49
+ density: 10
50
+ variance: 7
51
+ radius: 0
52
+ # Every surface: a filled card with a 2.5 pt black border and a 6 pt black shadow, no blur.
53
+ edge: fill+border
54
+ edge-weight: 2.5
55
+ edge-colour: ink
56
+ shadow:
57
+ offset: 6
58
+ colour: ink
59
+
60
+ treatments: [top-plain-large, band, kicker-numeral, statement, bottom-anchor]
61
+ role-defaults:
62
+ content: top-plain-large
63
+ data: band
64
+ data-takeaway: top-plain-large
65
+ sequence: band
66
+ image: bottom-anchor
67
+ statement: statement
68
+ reference: band
69
+ definition: band
70
+
71
+ decoration: [colour-field, numeral]
72
+ layout-families: [big-number, kpi-row, comparison, process, step-diagram, chart-insight, table-insight, image-split, summary-box-list, agenda, quote, statement]
73
+ # Display slides: the device a type-led cover, a statement, a big number and a closing's next step
74
+ # are drawn with (see references/layout-families.md, "Display slides").
75
+ display:
76
+ cover: plate
77
+ statement: drench
78
+ number: field
79
+ closing: box
80
+ covers: [cover-typographic, cover-split-field, cover-numeral]
81
+ sections: [section-field, section-numeral]
82
+ closings: [closing-ask, closing-decision-box, closing-statement]
83
+
84
+ table:
85
+ header: surface
86
+ header-ink: ink
87
+ rules: grid
88
+ banding: none
89
+ totals: bold
90
+ protagonist: accent-tint
91
+ chart:
92
+ gridlines: none
93
+ labels: direct
94
+ highlight: series-order
95
+ image:
96
+ frame: hairline
97
+
98
+ fill-order: [step-up, change-family, anchor-visual, distribute]
99
+ # What the gate allows this pack beyond the default craft floor (see references/anti-slop-checklist.md).
100
+ qa:
101
+ offset-shadow: true
102
+ avoid:
103
+ - blurred or soft shadows (the shadow is a hard offset or nothing)
104
+ - thin hairlines beside the 2.5 pt borders
105
+ - more than three fill colours on one slide
106
+ - muted grey text on the yellow surface below 4.5:1
107
+ - this look for grief, safety, HR or financial-loss subjects