@litfamily/litopencode 1.0.31 → 1.0.33

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 (282) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README-Ko-KR.md +6 -6
  3. package/README.md +6 -6
  4. package/TRADEMARK.md +12 -0
  5. package/dist/activation-primary-prompts.js +5 -1
  6. package/dist/activation-probe.js +1 -0
  7. package/dist/activation-prompt-utils.d.ts +1 -1
  8. package/dist/activation-reentry.d.ts +20 -0
  9. package/dist/activation-reentry.js +110 -1
  10. package/dist/activation-routing.d.ts +2 -1
  11. package/dist/activation-routing.js +100 -11
  12. package/dist/activation-workflow-prompts.d.ts +4 -0
  13. package/dist/activation-workflow-prompts.js +30 -0
  14. package/dist/activation.d.ts +1 -1
  15. package/dist/activation.js +44 -5
  16. package/dist/adhd-next.d.ts +51 -0
  17. package/dist/adhd-next.js +150 -0
  18. package/dist/agents/defaults.js +1 -1
  19. package/dist/cli/managed-skill-assets.d.ts +1 -1
  20. package/dist/cli/managed-skill-assets.js +7 -1
  21. package/dist/cli/native-skill-integrity.js +1 -1
  22. package/dist/cli.js +7 -1
  23. package/dist/commands.d.ts +28 -1
  24. package/dist/commands.js +58 -5
  25. package/dist/durable-plan.d.ts +5 -1
  26. package/dist/durable-plan.js +14 -2
  27. package/dist/features.d.ts +80 -1
  28. package/dist/features.js +105 -1
  29. package/dist/hooks.js +3 -0
  30. package/dist/ledger.js +12 -1
  31. package/dist/plan-scope-roots.d.ts +23 -0
  32. package/dist/plan-scope-roots.js +116 -0
  33. package/dist/restart-notice.d.ts +7 -0
  34. package/dist/restart-notice.js +40 -0
  35. package/dist/secret-shapes.js +5 -5
  36. package/dist/server.js +4 -0
  37. package/dist/session-liveness.d.ts +20 -0
  38. package/dist/session-liveness.js +99 -0
  39. package/dist/skills.d.ts +22 -1
  40. package/dist/skills.js +36 -0
  41. package/dist/start-work-typed.d.ts +34 -0
  42. package/dist/start-work-typed.js +167 -0
  43. package/dist/state.d.ts +3 -0
  44. package/dist/state.js +3 -0
  45. package/dist/tools.js +22 -2
  46. package/dist/tui/mission-band.d.ts +22 -1
  47. package/dist/tui/mission-band.js +75 -16
  48. package/dist/tui/mission-looks.d.ts +38 -0
  49. package/dist/tui/mission-looks.js +73 -0
  50. package/dist/tui/mission-model.d.ts +2 -0
  51. package/dist/tui/mission-model.js +3 -1
  52. package/dist/tui/mission-rainbow.d.ts +33 -0
  53. package/dist/tui/mission-rainbow.js +90 -0
  54. package/dist/tui/mission-source.d.ts +49 -0
  55. package/dist/tui/mission-source.js +217 -2
  56. package/dist/tui/mission-text.d.ts +2 -0
  57. package/dist/tui/mission-text.js +9 -1
  58. package/dist/tui/mission-threads.d.ts +58 -0
  59. package/dist/tui/mission-threads.js +128 -0
  60. package/dist/tui.js +112 -7
  61. package/dist/uiux-visual-catalog.js +8 -1
  62. package/dist/v2/events.js +3 -0
  63. package/dist/v2/session-hooks.js +38 -5
  64. package/dist/workflow-stack.d.ts +53 -0
  65. package/dist/workflow-stack.js +196 -0
  66. package/docs/assets/readme/badge-version.svg +1 -1
  67. package/docs/assets/skills/lit-schematic.webp +0 -0
  68. package/docs/reference-Ko-KR.md +48 -3
  69. package/docs/reference.md +54 -3
  70. package/package.json +1 -1
  71. package/skills/frontend-ui-ux/SKILL.md +3 -2
  72. package/skills/frontend-ui-ux/kit/components.css +414 -0
  73. package/skills/frontend-ui-ux/kit/fonts.json +22 -0
  74. package/skills/frontend-ui-ux/kit/layouts/landing.html +164 -0
  75. package/skills/frontend-ui-ux/kit/layouts/settings.html +114 -0
  76. package/skills/frontend-ui-ux/kit/layouts/workspace.html +115 -0
  77. package/skills/frontend-ui-ux/kit/styles/computational-brutalism.json +227 -0
  78. package/skills/frontend-ui-ux/kit/styles/field-notes.json +205 -0
  79. package/skills/frontend-ui-ux/kit/styles/graphite.json +200 -0
  80. package/skills/frontend-ui-ux/kit/styles/meadow.json +199 -0
  81. package/skills/frontend-ui-ux/kit/styles/neo-brutalism.json +222 -0
  82. package/skills/frontend-ui-ux/kit/ui.js +226 -0
  83. package/skills/frontend-ui-ux/references/brand-and-imagery.md +2 -2
  84. package/skills/frontend-ui-ux/references/complete-contract.md +2 -1
  85. package/skills/frontend-ui-ux/references/craft-floor.md +6 -2
  86. package/skills/frontend-ui-ux/references/default-editorial-pixel.json +3 -0
  87. package/skills/frontend-ui-ux/references/house-styles.md +236 -0
  88. package/skills/frontend-ui-ux/references/motion-guide.md +2 -2
  89. package/skills/frontend-ui-ux/references/production.md +3 -1
  90. package/skills/frontend-ui-ux/references/slop-register.md +4 -4
  91. package/skills/frontend-ui-ux/scripts/design-lock.mjs +400 -0
  92. package/skills/frontend-ui-ux/scripts/pixel-art.mjs +60 -0
  93. package/skills/frontend-ui-ux/scripts/probe-lock.mjs +235 -0
  94. package/skills/frontend-ui-ux/scripts/probe-page.mjs +34 -12
  95. package/skills/frontend-ui-ux/scripts/probe-static.mjs +16 -3
  96. package/skills/frontend-ui-ux/scripts/probe-thresholds.json +8 -2
  97. package/skills/frontend-ui-ux/scripts/probe.mjs +35 -7
  98. package/skills/lit-adhd/SKILL.md +209 -0
  99. package/skills/lit-adhd/references/brief-format.md +46 -0
  100. package/skills/lit-adhd/references/card-examples.md +103 -0
  101. package/skills/lit-adhd/references/status-taxonomy.md +34 -0
  102. package/skills/lit-adhd/scripts/rewind.mjs +933 -0
  103. package/skills/lit-diagram-drawer/SKILL.md +4 -3
  104. package/skills/lit-diagram-drawer/references/style-guide.md +20 -11
  105. package/skills/lit-diagram-drawer/references/type-architecture.md +3 -3
  106. package/skills/lit-diagram-drawer/references/type-dependency.md +1 -1
  107. package/skills/lit-diagram-drawer/references/type-tree.md +3 -3
  108. package/skills/lit-diagram-drawer/references/type-wardley.md +1 -1
  109. package/skills/lit-diagram-drawer/references/verifier-guide.md +1 -0
  110. package/skills/lit-diagram-drawer/scripts/craft-thresholds.json +1 -2
  111. package/skills/lit-diagram-drawer/scripts/visual-quality.mjs +1 -2
  112. package/skills/lit-docx/SKILL.md +2 -1
  113. package/skills/lit-handoff/SKILL.md +2 -0
  114. package/skills/lit-hijack/SKILL.md +422 -0
  115. package/skills/lit-hijack/references/capture-and-provenance.md +87 -0
  116. package/skills/lit-hijack/references/clean-room-build.md +111 -0
  117. package/skills/lit-hijack/references/distil-and-licence.md +100 -0
  118. package/skills/lit-hijack/references/gap-and-port-design.md +126 -0
  119. package/skills/lit-hijack/references/parity-and-report.md +110 -0
  120. package/skills/lit-hijack/scripts/hijack.mjs +993 -0
  121. package/skills/lit-plan/SKILL.md +8 -0
  122. package/skills/lit-pptx/SKILL.md +27 -11
  123. package/skills/lit-pptx/fonts/jetbrains-mono/JetBrainsMono-Bold.ttf +0 -0
  124. package/skills/lit-pptx/fonts/jetbrains-mono/JetBrainsMono-Regular.ttf +0 -0
  125. package/skills/lit-pptx/fonts/jetbrains-mono/OFL.txt +93 -0
  126. package/skills/lit-pptx/fonts/jetbrains-mono/SOURCE.md +17 -0
  127. package/skills/lit-pptx/references/density-and-fill.md +9 -3
  128. package/skills/lit-pptx/references/direction-step.md +21 -6
  129. package/skills/lit-pptx/references/examples/04-lecture-chalk-ko.md +3 -6
  130. package/skills/lit-pptx/references/examples/05-research-talk-paper-en.md +1 -5
  131. package/skills/lit-pptx/references/examples/08-reliability-keynote-night-en.md +0 -4
  132. package/skills/lit-pptx/references/examples/09-status-update-ledger-en.md +2 -2
  133. package/skills/lit-pptx/references/examples/11-forecast-review-night-ko.md +4 -7
  134. package/skills/lit-pptx/references/examples/13-engineering-review-computational-brutalism-ko.md +173 -0
  135. package/skills/lit-pptx/references/examples/14-launch-pitch-neo-brutalism-ko.md +154 -0
  136. package/skills/lit-pptx/references/examples/15-design-lecture-bauhaus-ko.md +139 -0
  137. package/skills/lit-pptx/references/examples/16-community-programme-riso-ko.md +158 -0
  138. package/skills/lit-pptx/references/examples/17-public-data-briefing-newsroom-ko.md +179 -0
  139. package/skills/lit-pptx/references/layout-families.md +65 -47
  140. package/skills/lit-pptx/references/title-treatments.md +83 -134
  141. package/skills/lit-pptx/references/tonalities/atlas.md +3 -3
  142. package/skills/lit-pptx/references/tonalities/bauhaus.md +140 -0
  143. package/skills/lit-pptx/references/tonalities/chalk.md +7 -6
  144. package/skills/lit-pptx/references/tonalities/computational-brutalism.md +150 -0
  145. package/skills/lit-pptx/references/tonalities/gazette.md +1 -1
  146. package/skills/lit-pptx/references/tonalities/ledger.md +1 -1
  147. package/skills/lit-pptx/references/tonalities/neo-brutalism.md +161 -0
  148. package/skills/lit-pptx/references/tonalities/newsroom.md +144 -0
  149. package/skills/lit-pptx/references/tonalities/night.md +1 -1
  150. package/skills/lit-pptx/references/tonalities/paper.md +2 -2
  151. package/skills/lit-pptx/references/tonalities/riso.md +137 -0
  152. package/skills/lit-pptx/references/tonalities/signal.md +3 -3
  153. package/skills/lit-pptx/references/tonalities/studio.md +7 -6
  154. package/skills/lit-pptx/run.mjs +2 -1
  155. package/skills/lit-pptx/scripts/compile-deck.js +1 -1
  156. package/skills/lit-pptx/scripts/craft-thresholds.json +4 -8
  157. package/skills/lit-pptx/scripts/craft_extras.py +56 -3
  158. package/skills/lit-pptx/scripts/deck_output.py +75 -90
  159. package/skills/lit-pptx/scripts/embed_fonts.py +13 -1
  160. package/skills/lit-pptx/scripts/layout_inventory.py +12 -5
  161. package/skills/lit-pptx/scripts/lib/grid-resolver.js +101 -110
  162. package/skills/lit-pptx/scripts/lib/layout-resolver.js +9 -15
  163. package/skills/lit-pptx/scripts/lib/render-pack.js +373 -566
  164. package/skills/lit-pptx/scripts/lib/slide-grid.js +104 -0
  165. package/skills/lit-pptx/scripts/lib/template-registry.js +57 -6
  166. package/skills/lit-pptx/scripts/qa_deck.py +10 -11
  167. package/skills/lit-pptx/scripts/render_pages.py +118 -0
  168. package/skills/lit-pptx/templates/tonalities/atlas/pack.yaml +1 -1
  169. package/skills/lit-pptx/templates/tonalities/bauhaus/pack.yaml +98 -0
  170. package/skills/lit-pptx/templates/tonalities/chalk/pack.yaml +1 -1
  171. package/skills/lit-pptx/templates/tonalities/computational-brutalism/pack.yaml +99 -0
  172. package/skills/lit-pptx/templates/tonalities/gazette/pack.yaml +1 -1
  173. package/skills/lit-pptx/templates/tonalities/ledger/pack.yaml +1 -1
  174. package/skills/lit-pptx/templates/tonalities/neo-brutalism/pack.yaml +107 -0
  175. package/skills/lit-pptx/templates/tonalities/newsroom/pack.yaml +99 -0
  176. package/skills/lit-pptx/templates/tonalities/night/pack.yaml +1 -1
  177. package/skills/lit-pptx/templates/tonalities/paper/pack.yaml +1 -1
  178. package/skills/lit-pptx/templates/tonalities/riso/pack.yaml +99 -0
  179. package/skills/lit-pptx/templates/tonalities/signal/pack.yaml +2 -2
  180. package/skills/lit-pptx/templates/tonalities/studio/pack.yaml +1 -1
  181. package/skills/lit-recap/SKILL.md +4 -0
  182. package/skills/lit-schematic/SKILL.md +118 -0
  183. package/skills/lit-schematic/examples/iso-heat-exchanger/build.mjs +38 -0
  184. package/skills/lit-schematic/examples/iso-heat-exchanger/scene.mjs +133 -0
  185. package/skills/lit-schematic/examples/journal-adsorption-rig/build.mjs +87 -0
  186. package/skills/lit-schematic/examples/pid-dosing-skid/build.mjs +85 -0
  187. package/skills/lit-schematic/references/cutaway-outputs.md +70 -0
  188. package/skills/lit-schematic/references/isometric-craft.md +101 -0
  189. package/skills/lit-schematic/references/journal-figures.md +72 -0
  190. package/skills/lit-schematic/references/kit-api.md +134 -0
  191. package/skills/lit-schematic/references/pfd-and-layout.md +53 -0
  192. package/skills/lit-schematic/references/pid-drafting.md +103 -0
  193. package/skills/lit-schematic/references/pid-symbols.md +163 -0
  194. package/skills/lit-schematic/references/verification.md +100 -0
  195. package/skills/lit-schematic/scripts/doctor.mjs +57 -0
  196. package/skills/lit-schematic/scripts/lib/figure.mjs +81 -0
  197. package/skills/lit-schematic/scripts/lib/fonts.mjs +25 -0
  198. package/skills/lit-schematic/scripts/lib/geom.mjs +113 -0
  199. package/skills/lit-schematic/scripts/lib/interactive.mjs +292 -0
  200. package/skills/lit-schematic/scripts/lib/isa.mjs +115 -0
  201. package/skills/lit-schematic/scripts/lib/iso.mjs +719 -0
  202. package/skills/lit-schematic/scripts/lib/legend-sheet.mjs +162 -0
  203. package/skills/lit-schematic/scripts/lib/pid.mjs +937 -0
  204. package/skills/lit-schematic/scripts/lib/png.mjs +132 -0
  205. package/skills/lit-schematic/scripts/lib/qa-core.mjs +658 -0
  206. package/skills/lit-schematic/scripts/lib/route.mjs +218 -0
  207. package/skills/lit-schematic/scripts/lib/sheet.mjs +890 -0
  208. package/skills/lit-schematic/scripts/lib/svg.mjs +82 -0
  209. package/skills/lit-schematic/scripts/lib/theme.mjs +115 -0
  210. package/skills/lit-schematic/scripts/motion.mjs +129 -0
  211. package/skills/lit-schematic/scripts/qa.mjs +92 -0
  212. package/skills/lit-schematic/scripts/render.mjs +233 -0
  213. package/skills/lit-schematic/scripts/scaffold.mjs +60 -0
  214. package/skills/lit-scientific-visualization/SKILL.md +27 -4
  215. package/skills/lit-typographic-motion/SKILL.md +19 -17
  216. package/skills/lit-typographic-motion/THIRD_PARTY_NOTICES +9 -0
  217. package/skills/lit-typographic-motion/engine/accents.mjs +6 -0
  218. package/skills/lit-typographic-motion/engine/assets.mjs +282 -0
  219. package/skills/lit-typographic-motion/engine/blender/runner.py +228 -0
  220. package/skills/lit-typographic-motion/engine/blender.mjs +126 -0
  221. package/skills/lit-typographic-motion/engine/board.mjs +138 -0
  222. package/skills/lit-typographic-motion/engine/cover/assets.mjs +11 -8
  223. package/skills/lit-typographic-motion/engine/cover/capture.mjs +6 -6
  224. package/skills/lit-typographic-motion/engine/cover/render.mjs +20 -7
  225. package/skills/lit-typographic-motion/engine/cover/treatment.mjs +30 -3
  226. package/skills/lit-typographic-motion/engine/fill.mjs +54 -0
  227. package/skills/lit-typographic-motion/engine/gallery.mjs +118 -0
  228. package/skills/lit-typographic-motion/engine/house.mjs +42 -0
  229. package/skills/lit-typographic-motion/engine/look.mjs +65 -8
  230. package/skills/lit-typographic-motion/engine/loudness.mjs +101 -0
  231. package/skills/lit-typographic-motion/engine/scaffold.mjs +219 -0
  232. package/skills/lit-typographic-motion/engine/score.mjs +594 -0
  233. package/skills/lit-typographic-motion/engine/sound.mjs +9 -57
  234. package/skills/lit-typographic-motion/engine/stage/capture.mjs +23 -9
  235. package/skills/lit-typographic-motion/engine/stage/clock.js +8 -2
  236. package/skills/lit-typographic-motion/engine/stage/cover-init.js +12 -1
  237. package/skills/lit-typographic-motion/engine/stage/frame-worker.mjs +15 -7
  238. package/skills/lit-typographic-motion/engine/stage/gate.mjs +48 -4
  239. package/skills/lit-typographic-motion/engine/stage/qa-page.js +16 -5
  240. package/skills/lit-typographic-motion/engine/stage/qa.mjs +148 -7
  241. package/skills/lit-typographic-motion/engine/stage/render.mjs +69 -7
  242. package/skills/lit-typographic-motion/engine/stage/scan.mjs +20 -1
  243. package/skills/lit-typographic-motion/engine/stage/stage-kit.js +85 -4
  244. package/skills/lit-typographic-motion/engine/stills.mjs +1 -1
  245. package/skills/lit-typographic-motion/engine/styles.mjs +221 -0
  246. package/skills/lit-typographic-motion/engine/text.mjs +1 -1
  247. package/skills/lit-typographic-motion/engine/treatment.mjs +190 -6
  248. package/skills/lit-typographic-motion/engine/vendor/three/LICENSE +21 -0
  249. package/skills/lit-typographic-motion/engine/vendor/three/addons/environments/RoomEnvironment.js +144 -0
  250. package/skills/lit-typographic-motion/engine/vendor/three/addons/geometries/RoundedBoxGeometry.js +155 -0
  251. package/skills/lit-typographic-motion/engine/vendor/three/three.module.js +54571 -0
  252. package/skills/lit-typographic-motion/gate.mjs +18 -2
  253. package/skills/lit-typographic-motion/probe.mjs +13 -1
  254. package/skills/lit-typographic-motion/recipes/hero-3d/expected.json +64 -0
  255. package/skills/lit-typographic-motion/recipes/hero-3d/index.html +132 -0
  256. package/skills/lit-typographic-motion/recipes/orbit-loop/expected.json +2 -0
  257. package/skills/lit-typographic-motion/recipes/painted-light/expected.json +2 -0
  258. package/skills/lit-typographic-motion/recipes/weight-tide/expected.json +2 -0
  259. package/skills/lit-typographic-motion/references/cover.md +56 -13
  260. package/skills/lit-typographic-motion/references/craft-loop.md +20 -7
  261. package/skills/lit-typographic-motion/references/depth.md +112 -0
  262. package/skills/lit-typographic-motion/references/runtime.md +21 -6
  263. package/skills/lit-typographic-motion/references/sound.md +76 -0
  264. package/skills/lit-typographic-motion/references/stage.md +50 -7
  265. package/skills/lit-typographic-motion/references/studio.md +162 -0
  266. package/skills/lit-typographic-motion/references/style-bibles.md +18 -0
  267. package/skills/lit-typographic-motion/references/treatment.md +108 -9
  268. package/skills/lit-typographic-motion/render.mjs +60 -3
  269. package/skills/lit-typographic-motion/styles/paper.json +185 -0
  270. package/skills/lit-typographic-motion/styles/swiss-signal.json +188 -0
  271. package/skills/lit-typographic-motion/styles/terminalcore.json +182 -0
  272. package/skills/lit-typographic-motion/styles/tidal.json +187 -0
  273. package/skills/litresearch/SKILL.md +2 -0
  274. package/skills/managed-skill-manifest.json +517 -88
  275. package/skills/readme-studio/SKILL.md +1 -1
  276. package/skills/review-work/SKILL.md +2 -0
  277. package/skills/start-work/SKILL.md +8 -0
  278. package/skills/workflow-loop/SKILL.md +4 -0
  279. package/tools/check-pack-payload.mjs +5 -2
  280. package/tools/payload-substance-parity.json +4 -3
  281. package/tools/qa-cover-recipes.mjs +4 -3
  282. package/tools/version-manifests.json +2 -2
@@ -10,14 +10,9 @@
10
10
  * out in points on the 960 x 540 (16:9) or 720 x 540 (4:3) canvas; the renderer converts to inches.
11
11
  */
12
12
 
13
- const CANVAS = { "16:9": { W: 960, H: 540 }, "4:3": { W: 720, H: 540 } };
13
+ const SG = require("./slide-grid");
14
14
 
15
- // Side margin, column width and gutter of the airy, standard, dense and compact grids. Each sums
16
- // exactly to the canvas width over 12 columns and 11 gutters.
17
- const GRIDS = {
18
- "16:9": { airy: { margin: 60, col: 48, gutter: 24 }, standard: { margin: 48, col: 50, gutter: 24 }, dense: { margin: 36, col: 52, gutter: 24 }, compact: { margin: 24, col: 54, gutter: 24 } },
19
- "4:3": { airy: { margin: 54, col: 40, gutter: 12 }, standard: { margin: 42, col: 42, gutter: 12 }, dense: { margin: 30, col: 44, gutter: 12 }, compact: { margin: 24, col: 45, gutter: 12 } },
20
- };
15
+ const { CANVAS } = SG;
21
16
 
22
17
  const RAMPS = {
23
18
  presented: { source: 12, label: 14, body: 18, lead: 24, title: 32, display: 44, cover: 56, hero: 72 },
@@ -29,19 +24,22 @@ const RAMPS = {
29
24
  const RAMP_ORDER = ["source", "label", "body", "lead", "title", "display", "cover", "hero"];
30
25
  const SCALE = [6, 12, 18, 24, 36, 48, 72];
31
26
 
32
- // Vertical zones shared by every canvas (all are 540 pt tall).
33
- const ZONE = { top: 36, body: 120, floor: 486, footer: 498, footerH: 18 };
27
+ // The content zones of the shared slide grid (slide-grid.js), the same on both canvases.
28
+ const ZONE = (() => {
29
+ const z = SG.ZONES.content;
30
+ return { top: z.title.y, body: z.body.y, floor: z.body.y + z.body.h, footer: z.footer.y, footerH: z.footer.h };
31
+ })();
34
32
 
35
- // What the density dial sets: grid, ramp, gaps, padding, table rows and the empty band a content
36
- // slide may leave under its last line. From density 5 a deck reads on the reading ramp (body 14 pt);
37
- // density 10 is the compact step: 24 pt margins, body 13 pt, titles that sit close over the body.
33
+ // What the density dial sets: ramp, gaps, padding, table rows and the empty band a content slide may
34
+ // leave under its last line; never where the zones are. From density 5 a deck reads on the reading ramp
35
+ // (body 14 pt); density 10 is the compact step: body 13 pt and the tightest gaps.
38
36
  const DENSITY_BANDS = [
39
- { upTo: 2, grid: "airy", ramp: "presented", item: 18, group: 36, block: 48, pad: 24, rowMin: 36, maxLines: 6, cap: 0.28, stepUp: true },
40
- { upTo: 4, grid: "standard", ramp: "presented", item: 12, group: 24, block: 36, pad: 24, rowMin: 32, maxLines: 8, cap: 0.24, stepUp: true },
41
- { upTo: 6, grid: "standard", ramp: "reading", item: 6, group: 18, block: 24, pad: 18, rowMin: 24, maxLines: 12, cap: 0.18, stepUp: true },
42
- { upTo: 8, grid: "dense", ramp: "reading", item: 6, group: 12, block: 24, pad: 12, rowMin: 22, maxLines: 16, cap: 0.14, stepUp: true },
43
- { upTo: 9, grid: "dense", ramp: "reading", item: 6, group: 12, block: 24, pad: 12, rowMin: 20, maxLines: 18, cap: 0.12, stepUp: false },
44
- { upTo: 10, grid: "compact", ramp: "compact", item: 6, group: 12, block: 18, pad: 12, rowMin: 18, maxLines: 22, cap: 0.12, stepUp: false, tight: true },
37
+ { upTo: 2, ramp: "presented", item: 18, group: 36, block: 48, pad: 24, rowMin: 36, maxLines: 6, cap: 0.28, stepUp: true },
38
+ { upTo: 4, ramp: "presented", item: 12, group: 24, block: 36, pad: 24, rowMin: 32, maxLines: 8, cap: 0.24, stepUp: true },
39
+ { upTo: 6, ramp: "reading", item: 6, group: 18, block: 24, pad: 18, rowMin: 24, maxLines: 12, cap: 0.18, stepUp: true },
40
+ { upTo: 8, ramp: "reading", item: 6, group: 12, block: 24, pad: 12, rowMin: 22, maxLines: 16, cap: 0.14, stepUp: true },
41
+ { upTo: 9, ramp: "reading", item: 6, group: 12, block: 24, pad: 12, rowMin: 20, maxLines: 18, cap: 0.12, stepUp: false },
42
+ { upTo: 10, ramp: "compact", item: 6, group: 12, block: 18, pad: 12, rowMin: 18, maxLines: 22, cap: 0.12, stepUp: false, tight: true },
45
43
  ];
46
44
 
47
45
  const TREATMENTS = ["top-rule", "top-plain-large", "side-rail", "band", "statement", "overlay", "kicker-numeral", "bottom-anchor"];
@@ -95,10 +93,15 @@ function clampDial(value, fallback) {
95
93
  return Number.isFinite(n) ? Math.max(1, Math.min(10, n)) : fallback;
96
94
  }
97
95
 
96
+ /**
97
+ * The density band's tokens. A bold pack (`ramp: presented`, allowed only with `register: bold`)
98
+ * sets its type on the presented ramp at any density, so its titles and figures fill the same zones
99
+ * at presentation size; every other pack takes the band's ramp.
100
+ */
98
101
  function densityTokens(density, packRamp) {
99
102
  const d = clampDial(density, 5);
100
103
  const band = DENSITY_BANDS.find((b) => d <= b.upTo);
101
- const ramp = band.ramp || packRamp || "presented";
104
+ const ramp = packRamp === "presented" ? "presented" : band.ramp;
102
105
  return { density: d, ...band, ramp, sizes: RAMPS[ramp] };
103
106
  }
104
107
 
@@ -109,12 +112,13 @@ function varianceMax(variance, treatmentCount) {
109
112
  return Math.min(max, treatmentCount);
110
113
  }
111
114
 
112
- function makeGrid(canvas, name) {
115
+ /** The shared slide grid's columns on a canvas (the same for every pack and density). */
116
+ function makeGrid(canvas) {
113
117
  const { W, H } = CANVAS[canvas];
114
- const g = GRIDS[canvas][name];
118
+ const g = SG.COLUMNS[canvas];
115
119
  const pitch = g.col + g.gutter;
116
120
  return {
117
- canvas, name, W, H, ...g, pitch,
121
+ canvas, name: "compact", W, H, ...g, pitch,
118
122
  /** Columns a…b (1-based, inclusive) as x and width in points. */
119
123
  span(a, b) { return { x: g.margin + pitch * (a - 1), w: pitch * (b - a + 1) - g.gutter }; },
120
124
  colX(a) { return g.margin + pitch * (a - 1); },
@@ -279,7 +283,16 @@ function leading(text) {
279
283
  * How much wider than the estimate a face sets: heavier weights of the bundled faces run wider,
280
284
  * so a frame measured at the regular width would lose its last line.
281
285
  */
282
- function faceWidth(face) {
286
+ function faceWidth(face, text) {
287
+ // A monospaced Latin face sets every Latin character and space at 0.6 em, and its Hangul in the
288
+ // East Asian face; the factor is that advance over the estimate, or the Latin worst case unread.
289
+ if (face && face.ea) {
290
+ const est = textEms(text);
291
+ if (text == null || !est) return 1.26;
292
+ let mono = 0;
293
+ for (const ch of String(text)) mono += /[ᄀ-ᇿ㄰-㆏가-힣一-鿿]/u.test(ch) ? 0.94 * (face.bold ? 1.08 : 1.0) : 0.6;
294
+ return Math.max(1, mono / est);
295
+ }
283
296
  const font = (face && face.font) || "";
284
297
  return /9 Black/u.test(font) ? 1.14 : /7 Bold/u.test(font) ? 1.08 : /5 Medium/u.test(font) ? 1.05 : face && face.bold ? 1.04 : 1.0;
285
298
  }
@@ -375,86 +388,90 @@ function pickTreatment({ pack, family, role, override, slideNumber, used, max, a
375
388
  /**
376
389
  * Title frame, companion decoration and body zone of one treatment, in points.
377
390
  *
391
+ * Every treatment sets its title in the shared title zone (slide-grid.js) and leaves the body zone
392
+ * as it is: a treatment chooses the title's face, size and colour and what is drawn with it, never
393
+ * where the title or the body stands. A statement sets its sentence in the display title zone.
394
+ *
378
395
  * ctx: { grid, sizes, title, hero, decoration (Set), numeral, rail ("surface" or "field") }
379
- * Returns { title, decor[], numeral?, body: {a, b, y, bottom}, side?, lines } where body columns
380
- * a…b are on the deck grid. `fits` is false when the title does not fit the treatment's budget.
396
+ * Returns { title, decor[], numeral?, body: {a, b, y, bottom}, support?, lines } where body
397
+ * columns a…b are on the deck grid. `fits` is false when the title does not fit its zone.
381
398
  */
382
399
  function treatmentGeometry(id, ctx) {
383
400
  const { grid, sizes } = ctx;
384
401
  // Lines of the title at a size, in the face that sets it (ctx.wide: face role → width factor).
385
- const fit = (t, w, size, face = "title") => lineCount(t, w / ((ctx.wide && ctx.wide[face]) || 1), size);
402
+ const fit = (t, w, size, face = "title") => {
403
+ const k = ctx.wide && ctx.wide[face];
404
+ return lineCount(t, w / ((typeof k === "function" ? k(t) : k) || 1), size);
405
+ };
386
406
  const text = ctx.title || "";
387
407
  const decor = [];
388
408
  const full = grid.span(1, 12);
389
409
  const out = { id, decor, fits: true };
390
- const box = (cols, y, size, lines, face, colour) => {
391
- const s = grid.span(cols[0], cols[1]);
392
- return { x: s.x, y, w: s.w, h: Math.ceil(lines * size * 1.15), size, face, colour, lines };
410
+ const T = SG.zone(grid.canvas, "content", "title");
411
+ const B = SG.zone(grid.canvas, "content", "body");
412
+ const body = { a: B.a, b: B.b, y: B.y, bottom: B.y + B.h };
413
+ // The title frame is the zone itself. Two lines at the title step take 1.15 leading, or 1.1 when
414
+ // that is what keeps them inside the zone (the presented title step); display type sets solid (1.0)
415
+ // when that is what keeps two lines of it in the zone.
416
+ const frame = (size, face, colour) => {
417
+ const lines = fit(text, T.w, size, face);
418
+ const lineSpacing = [1.15, 1.1, ...(face === "display" ? [1.0] : [])].find((k) => lines * size * k <= T.h + 0.5) || 1.1;
419
+ return { x: T.x, y: T.y, w: T.w, h: T.h, size, face, colour, lines, lineSpacing, fits: lines * size * lineSpacing <= T.h + 0.5 };
393
420
  };
394
-
395
- // On the compact step (ctx.tight) a top title frame is as tall as its lines, the rule sits 6 pt
396
- // under it and the body 18 pt under the rule; a band keeps its height and the body follows it at 12.
397
- if (id === "top-rule" || id === "band") {
398
- const cols = id === "band" ? [1, 11] : [1, 12];
399
- const w = grid.span(cols[0], cols[1]).w;
400
- const lines = fit(text, w, sizes.title);
401
- out.fits = lines <= 1 || (lines <= 2 && sizes.title * 1.15 * 2 <= 60.5);
402
- const h = ctx.tight && id === "top-rule" ? Math.ceil(Math.max(1, lines) * sizes.title * 1.15) + 2 : 60;
403
- out.title = { ...box(cols, 36, sizes.title, lines, "title", id === "band" ? "on-field" : "ink"), h };
404
- if (id === "band") {
405
- decor.push({ x: 0, y: 0, w: grid.W, h: 108, role: "field", kind: "band" });
406
- out.body = { a: 1, b: 12, y: ctx.tight ? 120 : 132, bottom: ZONE.floor };
407
- } else {
408
- const ruleY = 36 + h + 6;
409
- if (ctx.decoration.has("accent-rule")) decor.push({ x: out.title.x, y: ruleY, w: grid.col, h: 4, role: "accent", kind: "rule" });
421
+ // Where the title's last line ends inside the frame (a rule under it follows the text, not the zone).
422
+ const textBottom = (f) => T.y + Math.ceil(Math.max(1, f.lines) * f.size * f.lineSpacing) + 2;
423
+
424
+ if (id === "top-rule" || id === "band" || id === "bottom-anchor" || id === "side-rail" || id === "kicker-numeral") {
425
+ const f = frame(sizes.title, "title", id === "band" ? "on-field" : "ink");
426
+ out.fits = f.fits && f.lines <= 2 && (id !== "kicker-numeral" || ctx.numeral != null);
427
+ out.title = f;
428
+ out.body = body;
429
+ if (id === "top-rule") {
430
+ const ruleY = textBottom(f) + 4;
431
+ if (ctx.decoration.has("accent-rule")) decor.push({ x: T.x, y: ruleY, w: grid.col, h: 4, role: "accent", kind: "rule" });
410
432
  else decor.push({ x: full.x, y: ruleY, w: full.w, h: 0.75, role: "line", kind: "rule" });
411
- out.body = { a: 1, b: 12, y: ctx.tight ? ruleY + 18 : ZONE.body, bottom: ZONE.floor };
433
+ } else if (id === "band") {
434
+ // The field runs from the page's top edge to the foot of the title zone.
435
+ decor.push({ x: 0, y: 0, w: grid.W, h: T.y + T.h, role: "field", kind: "band" });
436
+ } else if (id === "bottom-anchor") {
437
+ // The title zone closes on a heavy rule in the ink across the page's columns.
438
+ decor.push({ x: full.x, y: T.y + T.h + 2, w: full.w, h: 3, role: "ink", kind: "rule" });
439
+ } else if (id === "side-rail") {
440
+ // A bar in the left margin: the full page height in the rail colour where the pack fills its
441
+ // rail, else as tall as the title zone in the accent.
442
+ const filled = ctx.decoration.has("rail-fill");
443
+ const w = Math.round(grid.margin / 2);
444
+ decor.push(filled ? { x: 0, y: 0, w, h: grid.H, role: ctx.rail === "field" ? "field" : "accent", kind: "rail" }
445
+ : { x: 0, y: T.y, w, h: T.h, role: "accent", kind: "rail" });
446
+ } else {
447
+ const A = SG.zone(grid.canvas, "content", "aside");
448
+ out.numeral = { x: A.x, y: A.y, w: A.w, h: A.h, size: sizes.cover, text: String(ctx.numeral || ""), align: "right" };
412
449
  }
413
450
  return out;
414
451
  }
415
452
 
416
453
  if (id === "top-plain-large") {
417
- const w = grid.span(1, 10).w;
418
- const lines = fit(text, w, sizes.display, "display");
419
- out.fits = lines <= 2;
420
- // The frame reaches 8 pt into the gap below it: heavy display faces hang their descenders lower.
421
- const h = lines <= 1 ? 54 : 102;
422
- out.title = { ...box([1, 10], 36, sizes.display, lines, "display", "ink"), h: h + 8 };
423
- out.body = { a: 1, b: 12, y: 36 + h + (ctx.tight ? 18 : 24), bottom: ZONE.floor };
424
- return out;
425
- }
426
-
427
- if (id === "side-rail") {
428
- const s = grid.span(1, 4);
429
- const lines = fit(text, s.w, sizes.title);
430
- out.fits = lines <= 4;
431
- const filled = ctx.decoration.has("rail-fill");
432
- const rail = filled && ctx.rail === "field" ? "field" : "surface";
433
- out.title = box([1, 4], 36, sizes.title, lines, "title", rail === "field" ? "on-field" : "ink");
434
- // In a rail a few words fill a line, so one word moving down can add a line the estimate does
435
- // not see; the frame keeps a spare line (the text is top-aligned, so nothing moves).
436
- out.title.h = Math.ceil((lines + 1) * sizes.title * 1.15);
437
- const railRight = s.x + s.w + grid.gutter / 2;
438
- if (filled) decor.push({ x: 0, y: 0, w: railRight, h: grid.H, role: rail, kind: "rail" });
439
- else if (ctx.decoration.has("hairline-rule")) decor.push({ x: railRight, y: 36, w: 0.75, h: 450, role: "line", kind: "rule" });
440
- out.body = { a: 5, b: 12, y: 36, bottom: ZONE.floor };
454
+ const f = frame(sizes.display, "display", "ink");
455
+ out.fits = f.fits && f.lines <= 2;
456
+ out.title = f;
457
+ out.body = body;
441
458
  return out;
442
459
  }
443
460
 
444
461
  if (id === "statement") {
445
- // A pack may set the sentence beside a rail (`statementCols`); a hero pack sets it at the hero
446
- // step while it breaks into at most three balanced lines.
447
- const cols = ctx.statementCols || [1, 10];
448
- const w = grid.span(cols[0], cols[1]).w;
449
- const heroLines = fit(text, w, sizes.hero, "display");
450
- const useHero = ctx.hero && heroLines <= 3;
462
+ // The sentence is centred in the display title zone, at the hero step in a hero pack while it
463
+ // keeps to three lines there, else at the display step.
464
+ const D = SG.zone(grid.canvas, "display", "title");
465
+ const tall = (size, lines) => lines * size * 1.15 <= D.h + 0.5;
466
+ const heroLines = fit(text, D.w, sizes.hero, "display");
467
+ const useHero = ctx.hero && heroLines <= 3 && tall(sizes.hero, heroLines);
451
468
  const size = useHero ? sizes.hero : sizes.display;
452
- const lines = useHero ? heroLines : fit(text, w, sizes.display, "display");
453
- out.fits = lines <= 3;
454
- const h = Math.ceil(lines * size * 1.15);
455
- const y = Math.round((303 - h / 2) / 6) * 6;
456
- out.title = { ...box(cols, y, size, lines, "display", "ink"), h };
457
- out.support = { ...grid.span(cols[0], Math.min(12, cols[0] + 7)), y: y + h + 24 };
469
+ const lines = useHero ? heroLines : fit(text, D.w, sizes.display, "display");
470
+ out.fits = lines <= 3 && tall(size, lines);
471
+ out.title = { x: D.x, y: D.y, w: D.w, h: D.h, size, face: "display", colour: "ink", lines, lineSpacing: 1.15, valign: "middle",
472
+ textH: Math.ceil(lines * size * 1.15) };
473
+ const P = SG.zone(grid.canvas, "display", "support");
474
+ out.support = { x: P.x, y: P.y, w: P.w, bottom: P.y + P.h };
458
475
  out.body = null;
459
476
  return out;
460
477
  }
@@ -466,37 +483,11 @@ function treatmentGeometry(id, ctx) {
466
483
  // The panel is as tall as the title and its caption line (ending 24 pt under the caption at 432), so its
467
484
  // lower part never stands empty; the sample tag in the footer zone sits on the picture, clear of its edge.
468
485
  decor.push({ x: 0, y: 324, w: s.x + s.w + 24, h: 146, role: "field", kind: "panel", alpha: 12 });
469
- out.title = { ...box([1, 7], 348, sizes.title, lines, "title", "on-field"), h: 72 };
486
+ out.title = { x: s.x, y: 348, w: s.w, h: 72, size: sizes.title, face: "title", colour: "on-field", lines };
470
487
  out.image = { x: 0, y: 0, w: grid.W, h: grid.H };
471
488
  out.body = null;
472
489
  return out;
473
490
  }
474
-
475
- if (id === "kicker-numeral") {
476
- const w = grid.span(3, 12).w;
477
- const lines = fit(text, w, sizes.title);
478
- out.fits = lines <= 1 && ctx.numeral != null;
479
- const n = grid.span(1, 2);
480
- out.numeral = { x: n.x, y: 36, w: n.w, h: 72, size: sizes.cover, text: String(ctx.numeral || "") };
481
- out.title = { ...box([3, 12], 48, sizes.title, lines, "title", "ink"), h: 60 };
482
- out.body = { a: 1, b: 12, y: ctx.tight ? 120 : 132, bottom: ZONE.floor };
483
- return out;
484
- }
485
-
486
- if (id === "bottom-anchor") {
487
- // The title's last line ends on the body floor, so a one-line title leaves no band under itself;
488
- // the hairline sits 12 pt above the title and the visual's zone ends 24 pt above it.
489
- const s = grid.span(1, 9);
490
- const lines = fit(text, s.w, sizes.title);
491
- out.fits = lines <= 2;
492
- const h = Math.ceil(Math.max(1, lines) * sizes.title * 1.15) + 2;
493
- const y = ZONE.floor - h;
494
- out.title = { ...box([1, 9], y, sizes.title, lines, "title", "ink"), h };
495
- if (ctx.decoration.has("hairline-rule")) decor.push({ x: full.x, y: y - 12, w: full.w, h: 0.75, role: "line", kind: "rule" });
496
- out.side = { ...grid.span(10, 12), y, bottom: ZONE.floor };
497
- out.body = { a: 1, b: 12, y: 36, bottom: y - 24 };
498
- return out;
499
- }
500
491
  throw new Error(`unknown title treatment "${id}"`);
501
492
  }
502
493
 
@@ -514,7 +505,7 @@ function treatmentApplies(id, facts, ctx) {
514
505
  }
515
506
 
516
507
  module.exports = {
517
- CANVAS, GRIDS, RAMPS, SCALE, ZONE, TREATMENTS, FAMILIES, VARIANTS, LEGACY_LAYOUTS,
508
+ CANVAS, RAMPS, SCALE, ZONE, TREATMENTS, FAMILIES, VARIANTS, LEGACY_LAYOUTS,
518
509
  densityTokens, varianceMax, makeGrid, stepGap, stepRole, clampDial,
519
510
  textEms, lineCount, leading, textHeight, isKorean, faceWidth, balanceLines, keepLines,
520
511
  familyIds, resolveFamily, familyAllows, pickTreatment, treatmentGeometry, treatmentApplies,
@@ -831,6 +831,7 @@ function comparisonRole(blocks) {
831
831
 
832
832
  /** The ordered content of a pack slide, captions bound to the visual before them. */
833
833
  // Families whose geometry is a chart: a table of numbers there is drawn as one.
834
+ const DISPLAY_FAMILIES = new Set(["statement", "quote", "closing-statement"]);
834
835
  const CHART_FAMILIES = new Set(["chart-insight", "full-chart", "kpi-over-chart", "dashboard-grid"]);
835
836
  const NUMBER_CELL = /^[+\-−±▲▼△▽]?\s*[\d.,]+\s*(?:%p?|배|x|pt|bp|[가-힣]{1,3}|[A-Za-z]{1,3})?$/u;
836
837
 
@@ -998,7 +999,7 @@ function resolvePack(ast, templateObj) {
998
999
  const numbering = { figure: 0, table: 0, FIGURE, TABLE };
999
1000
  const facts = deckFacts(ast);
1000
1001
  const families = pack["layout-families"] || [];
1001
- const wide = { title: grid.faceWidth(pack.faces.title), display: grid.faceWidth(pack.faces.display) };
1002
+ const wide = { title: (t) => grid.faceWidth(pack.faces.title, t), display: (t) => grid.faceWidth(pack.faces.display, t) };
1002
1003
  let part = 0;
1003
1004
 
1004
1005
  const slides = ast.slides.map((slide) => {
@@ -1046,23 +1047,13 @@ function resolvePack(ast, templateObj) {
1046
1047
  } else if (!families.includes(family)) {
1047
1048
  notes.push(`slide ${n}: ${family} is not a ${pack.id} layout family (${families.join(", ")}); drawn with its own geometry`);
1048
1049
  }
1049
- let titleOnly = null;
1050
- if (family === "statement" && !pack.treatments.includes("statement")) {
1051
- // A pack without a statement title sets the sentence as a top title, the largest it has.
1052
- titleOnly = ["top-plain-large", "band", "top-rule"].find((t) => pack.treatments.includes(t));
1053
- if (!titleOnly) throw new Error(`Slide ${n}: ${pack.id} has no statement or top title to set a statement slide with; choose another tonality`);
1054
- notes.push(`slide ${n}: ${pack.id} has no statement title, so the sentence is drawn as a ${titleOnly} title`);
1055
- family = "text-column";
1056
- role = "content";
1057
- }
1058
1050
  const items = packItems(slide.blocks, numbering, CHART_FAMILIES.has(family));
1059
1051
  const paras = slide.blocks.filter((b) => b.type === "body").flatMap((b) => b.items || []);
1060
1052
  // A closing is not part of a section, so it never carries the part number.
1061
1053
  const closing = grid.VARIANTS.closing.includes(family);
1062
1054
  // An agenda numbers its own rows: a count beside its title reads as a stray section number.
1063
1055
  const numeral = family === "agenda" ? null : part > 0 && !closing ? part : null;
1064
- const statementCols = (pack.display || {}).statement === "offset" ? [5, 12] : null;
1065
- const ctx = { grid: pack.grid, sizes, title: plain(title), hero: pack.hero, decoration, numeral, rail: pack.rail, wide, statementCols, tight: Boolean((pack.tok || {}).tight) };
1056
+ const ctx = { grid: pack.grid, sizes, title: plain(title), hero: pack.hero, decoration, numeral, rail: pack.rail, wide, tight: Boolean((pack.tok || {}).tight) };
1066
1057
  const facts1 = slideFacts(slide.blocks);
1067
1058
  // A quote under a statement title says the quote; the source line becomes the support line.
1068
1059
  let statementText = null;
@@ -1080,8 +1071,11 @@ function resolvePack(ast, templateObj) {
1080
1071
  };
1081
1072
  let allow = grid.familyAllows(family);
1082
1073
  let treatment;
1083
- if (titleOnly) {
1084
- treatment = titleOnly;
1074
+ // A statement, a quote and a closing statement sit in the display zone in every pack (the shared
1075
+ // slide grid): the statement title is theirs whether or not the pack lists it among its treatments.
1076
+ const display = DISPLAY_FAMILIES.has(family) && !meta.title && applicable("statement") === true;
1077
+ if (display) {
1078
+ treatment = "statement";
1085
1079
  } else if (!families.includes(family) && !meta.title && !pack.treatments.some((t) => allow.includes(t) && applicable(t) === true)) {
1086
1080
  // A family from outside the pack that takes none of its titles gets the pack's title for its role.
1087
1081
  allow = [pack["role-defaults"][role] || pack.treatments[0], ...grid.familyAllows("text-column")];
@@ -1093,7 +1087,7 @@ function resolvePack(ast, templateObj) {
1093
1087
  applicable, onNote: (note) => notes.push(note),
1094
1088
  });
1095
1089
  }
1096
- const candidates = meta.title || titleOnly ? [treatment] : [treatment, ...pack.treatments.filter((t) =>
1090
+ const candidates = meta.title || display ? [treatment] : [treatment, ...pack.treatments.filter((t) =>
1097
1091
  t !== treatment && allow.includes(t) && !["statement", "overlay"].includes(t) && applicable(t) === true)];
1098
1092
  return {
1099
1093
  ...base,