@geraldmaron/construct 1.1.1 → 1.2.1

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 (426) hide show
  1. package/README.md +30 -19
  2. package/bin/construct +487 -87
  3. package/bin/construct-postinstall.mjs +30 -2
  4. package/examples/distribution/README.md +42 -0
  5. package/examples/distribution/manifest.json +53 -0
  6. package/examples/distribution/sources/adr.md +84 -0
  7. package/examples/distribution/sources/deck-one-pager.md +65 -0
  8. package/examples/distribution/sources/prd-platform.md +161 -0
  9. package/examples/distribution/sources/research-brief.md +80 -0
  10. package/examples/distribution/sources/rfc-platform.md +82 -0
  11. package/examples/distribution/sources/runbook.md +103 -0
  12. package/examples/distribution/sources/strategy.md +88 -0
  13. package/lib/adapters-sync.mjs +67 -0
  14. package/lib/artifact-gate-notice.mjs +38 -0
  15. package/lib/artifact-manifest.mjs +258 -0
  16. package/lib/artifact-release-gate.mjs +171 -0
  17. package/lib/artifact-reviewers.mjs +68 -0
  18. package/lib/artifact-type-from-path.mjs +79 -0
  19. package/lib/artifact-workflow.mjs +252 -0
  20. package/lib/audit-skills.mjs +4 -0
  21. package/lib/audit-specialists.mjs +285 -0
  22. package/lib/audit-trail.mjs +1 -1
  23. package/lib/auto-docs.mjs +90 -19
  24. package/lib/beads/drift.mjs +27 -6
  25. package/lib/beads-client.mjs +49 -121
  26. package/lib/beads-optimistic.mjs +5 -12
  27. package/lib/brand-fonts.mjs +93 -0
  28. package/lib/brand-prose.mjs +214 -0
  29. package/lib/brand-tokens.mjs +92 -0
  30. package/lib/bridges/copilot-proxy.mjs +13 -26
  31. package/lib/capability-ledger.mjs +156 -0
  32. package/lib/certification/artifact-fixtures.mjs +132 -0
  33. package/lib/certification/artifact-gates.mjs +63 -0
  34. package/lib/certification/artifact-provenance.mjs +97 -0
  35. package/lib/certification/canonical-scenarios.mjs +78 -0
  36. package/lib/certification/cli.mjs +191 -0
  37. package/lib/certification/dashboard-api.mjs +71 -0
  38. package/lib/certification/demo-parity.mjs +116 -0
  39. package/lib/certification/document-io-fixtures.mjs +246 -0
  40. package/lib/certification/document-workflow.mjs +97 -0
  41. package/lib/certification/eval-bridge.mjs +77 -0
  42. package/lib/certification/model-routing.mjs +149 -0
  43. package/lib/certification/prompt-budget.mjs +119 -0
  44. package/lib/certification/rc-gate.mjs +206 -0
  45. package/lib/certification/real-llm-scenarios.mjs +303 -0
  46. package/lib/certification/role-cards.mjs +113 -0
  47. package/lib/certification/role-overlays.mjs +117 -0
  48. package/lib/certification/run.mjs +122 -0
  49. package/lib/certification/runner.mjs +323 -0
  50. package/lib/certification/scenarios.mjs +51 -0
  51. package/lib/certification/skill-inventory.mjs +289 -0
  52. package/lib/certification/skill-scenarios.mjs +147 -0
  53. package/lib/certification/specialist-contracts.mjs +85 -0
  54. package/lib/certification/specialist-scenarios.mjs +175 -0
  55. package/lib/certification/stale-impact.mjs +146 -0
  56. package/lib/certification/status.mjs +252 -0
  57. package/lib/certification/store.mjs +77 -0
  58. package/lib/chat/cli.mjs +333 -0
  59. package/lib/chat/command-suggest.mjs +161 -0
  60. package/lib/chat/commands.mjs +215 -0
  61. package/lib/chat/config.mjs +142 -0
  62. package/lib/chat/context-compactor.mjs +250 -0
  63. package/lib/chat/context-continuation.mjs +253 -0
  64. package/lib/chat/continuation-source.mjs +58 -0
  65. package/lib/chat/demo-guide.mjs +61 -0
  66. package/lib/chat/design-tokens.mjs +91 -0
  67. package/lib/chat/desktop-binary.mjs +79 -0
  68. package/lib/chat/desktop-build.mjs +130 -0
  69. package/lib/chat/desktop-launcher.mjs +133 -0
  70. package/lib/chat/evidence.mjs +145 -0
  71. package/lib/chat/export.mjs +74 -0
  72. package/lib/chat/harness/driver.mjs +91 -0
  73. package/lib/chat/list-picker.mjs +112 -0
  74. package/lib/chat/model-picker.mjs +356 -0
  75. package/lib/chat/openrouter-fallback.mjs +151 -0
  76. package/lib/chat/permission-prompt.mjs +33 -0
  77. package/lib/chat/picker-catalog.mjs +45 -0
  78. package/lib/chat/policy-telemetry.mjs +34 -0
  79. package/lib/chat/present.mjs +246 -0
  80. package/lib/chat/session-context.mjs +39 -0
  81. package/lib/chat/session-persist.mjs +73 -0
  82. package/lib/chat/session-restore.mjs +71 -0
  83. package/lib/chat/session-settings.mjs +53 -0
  84. package/lib/chat/system-prompt.mjs +52 -0
  85. package/lib/chat/transparency.mjs +93 -0
  86. package/lib/chat/tui/color-scheme.mjs +42 -0
  87. package/lib/chat/tui/markdown.mjs +123 -0
  88. package/lib/chat/tui/presentation.mjs +100 -0
  89. package/lib/chat/tui/render.mjs +500 -0
  90. package/lib/chat/tui/turn-block.mjs +284 -0
  91. package/lib/chat/tui/turn-present.mjs +18 -0
  92. package/lib/chat/tui/turn-state.mjs +88 -0
  93. package/lib/chat/tui/usage.mjs +122 -0
  94. package/lib/chat/web-commands.mjs +146 -0
  95. package/lib/chat/web-launcher.mjs +63 -0
  96. package/lib/chat/web-picker-keys.mjs +46 -0
  97. package/lib/chat/web-session.mjs +159 -0
  98. package/lib/cli-commands.mjs +146 -15
  99. package/lib/cli-service-inventory.mjs +42 -0
  100. package/lib/comment-lint.mjs +6 -4
  101. package/lib/config/intake-policy.mjs +209 -0
  102. package/lib/config/project-config.mjs +11 -2
  103. package/lib/config/schema.mjs +86 -6
  104. package/lib/config/source-targets.mjs +311 -0
  105. package/lib/contract-schemas/decision.json +50 -0
  106. package/lib/contract-schemas/implementation.json +51 -0
  107. package/lib/contract-schemas/review-report.json +32 -0
  108. package/lib/contract-schemas/test-report.json +43 -0
  109. package/lib/contracts/construct-handoff.mjs +60 -0
  110. package/lib/contracts/validate.mjs +32 -3
  111. package/lib/contracts/violation-log.mjs +58 -5
  112. package/lib/dashboard-demo.mjs +71 -0
  113. package/lib/dashboard-static.mjs +19 -5
  114. package/lib/decisions/registry.mjs +5 -3
  115. package/lib/deck-export-pptx.mjs +1152 -0
  116. package/lib/demo-recording.mjs +142 -0
  117. package/lib/demo-script.mjs +114 -0
  118. package/lib/demo-surface.mjs +249 -0
  119. package/lib/demo.mjs +598 -140
  120. package/lib/diagram-export.mjs +192 -0
  121. package/lib/diagram.mjs +13 -11
  122. package/lib/docs-verify.mjs +25 -7
  123. package/lib/doctor/index.mjs +3 -1
  124. package/lib/doctor/project-adapters.mjs +44 -0
  125. package/lib/doctor/source-checkout.mjs +16 -0
  126. package/lib/doctor/watchers/cx-budget.mjs +98 -0
  127. package/lib/doctor/watchers/graph-staleness.mjs +52 -0
  128. package/lib/doctor/watchers/process-pressure.mjs +4 -3
  129. package/lib/document-export.mjs +384 -38
  130. package/lib/document-extract.mjs +44 -4
  131. package/lib/document-ingest.mjs +29 -20
  132. package/lib/embed/auto-sources.mjs +44 -0
  133. package/lib/embed/cli.mjs +4 -3
  134. package/lib/embed/daemon.mjs +18 -46
  135. package/lib/embed/demand-fetch.mjs +50 -19
  136. package/lib/embed/inbox.mjs +6 -10
  137. package/lib/embed/worker.mjs +4 -3
  138. package/lib/embedded-contract/index.mjs +11 -0
  139. package/lib/embedded-contract/model-resolve.mjs +10 -9
  140. package/lib/env-config.mjs +26 -0
  141. package/lib/evals/dataset.mjs +137 -0
  142. package/lib/evals/gates.mjs +175 -0
  143. package/lib/export-branding.mjs +32 -0
  144. package/lib/graph/build-co-change.mjs +54 -0
  145. package/lib/graph/build-from-registry.mjs +136 -0
  146. package/lib/graph/build-import-graph.mjs +153 -0
  147. package/lib/graph/cli.mjs +110 -0
  148. package/lib/graph/impact-cli.mjs +89 -0
  149. package/lib/graph/impact.mjs +108 -0
  150. package/lib/graph/staleness.mjs +44 -0
  151. package/lib/graph/store.mjs +172 -0
  152. package/lib/health-check.mjs +90 -76
  153. package/lib/hooks/artifact-release-gate.mjs +43 -0
  154. package/lib/hooks/brand-prose-lint.mjs +38 -0
  155. package/lib/hooks/graph-impact-advisory.mjs +62 -0
  156. package/lib/hooks/session-optimize.mjs +84 -207
  157. package/lib/hooks/session-start.mjs +18 -16
  158. package/lib/host-disposition.mjs +7 -0
  159. package/lib/improvement/cli.mjs +189 -0
  160. package/lib/improvement/controller.mjs +137 -0
  161. package/lib/improvement/proposal.mjs +120 -0
  162. package/lib/improvement/specialist-loop.mjs +192 -0
  163. package/lib/improvement/store.mjs +89 -0
  164. package/lib/improvement/surface.mjs +219 -0
  165. package/lib/ingest-tooling.mjs +97 -0
  166. package/lib/init/doc-lanes.mjs +165 -0
  167. package/lib/init-docs.mjs +31 -178
  168. package/lib/init-unified.mjs +50 -119
  169. package/lib/init-update-guide.mjs +102 -0
  170. package/lib/init-update.mjs +32 -1
  171. package/lib/init.mjs +8 -0
  172. package/lib/install/desktop-binary-download.mjs +85 -0
  173. package/lib/install/legacy-global-cleanup.mjs +189 -0
  174. package/lib/intake/constants.mjs +29 -0
  175. package/lib/intake/daemon.mjs +2 -0
  176. package/lib/intake/git-queue.mjs +9 -8
  177. package/lib/intake/intake-config.mjs +52 -105
  178. package/lib/intake/legacy-paths.mjs +5 -0
  179. package/lib/intake/queue.mjs +2 -1
  180. package/lib/intake/session-prelude.mjs +90 -1
  181. package/lib/libreoffice-export.mjs +97 -0
  182. package/lib/logging/rotate.mjs +9 -0
  183. package/lib/maintenance/docker-reclaim.mjs +206 -0
  184. package/lib/mcp/external-schema-cost.mjs +74 -0
  185. package/lib/mcp/server.mjs +103 -10
  186. package/lib/mcp/stdio-mcp-probe.mjs +188 -0
  187. package/lib/mcp/tool-budget.mjs +55 -4
  188. package/lib/mcp/tools/document.mjs +18 -4
  189. package/lib/mcp/tools/embedded-contract.mjs +14 -0
  190. package/lib/mcp/tools/skills.mjs +32 -3
  191. package/lib/mcp/tools/workflow.mjs +13 -1
  192. package/lib/model-free-selector.mjs +18 -0
  193. package/lib/model-registry.mjs +13 -229
  194. package/lib/model-router.mjs +373 -92
  195. package/lib/models/behavior-matrix.mjs +289 -0
  196. package/lib/models/catalog.mjs +209 -0
  197. package/lib/models/execution-capability-profile.mjs +196 -0
  198. package/lib/models/execution-policy.mjs +307 -0
  199. package/lib/models/provider-poll.mjs +383 -0
  200. package/lib/npm-spawn-env.mjs +17 -0
  201. package/lib/ollama/installed-models.mjs +129 -0
  202. package/lib/oracle/actions.mjs +187 -0
  203. package/lib/oracle/artifact-gate.mjs +99 -0
  204. package/lib/oracle/cli.mjs +204 -0
  205. package/lib/oracle/daemon-entry.mjs +14 -0
  206. package/lib/oracle/dispatch.mjs +81 -0
  207. package/lib/oracle/execute.mjs +143 -0
  208. package/lib/oracle/gaps.mjs +76 -0
  209. package/lib/oracle/index.mjs +84 -0
  210. package/lib/oracle/issues.mjs +164 -0
  211. package/lib/oracle/org-graph.mjs +170 -0
  212. package/lib/oracle/policy.mjs +80 -0
  213. package/lib/oracle/read-model.mjs +398 -0
  214. package/lib/oracle/reconcile.mjs +191 -0
  215. package/lib/oracle/routing.mjs +89 -0
  216. package/lib/oracle/synthesize.mjs +384 -0
  217. package/lib/oracle/verdicts.mjs +51 -0
  218. package/lib/orchestration/worker.mjs +88 -27
  219. package/lib/orchestration-policy.mjs +50 -0
  220. package/lib/parity.mjs +96 -2
  221. package/lib/playwright-demo.mjs +254 -0
  222. package/lib/project-init-shared.mjs +4 -1
  223. package/lib/prompt-composer.js +7 -3
  224. package/lib/prompt-validation-contract.mjs +24 -0
  225. package/lib/provider-capabilities.js +57 -11
  226. package/lib/providers/contract/adapters/confluence/index.mjs +181 -0
  227. package/lib/providers/contract/adapters/git/index.mjs +115 -0
  228. package/lib/providers/contract/adapters/github/index.mjs +166 -0
  229. package/lib/providers/contract/adapters/jira/index.mjs +187 -0
  230. package/lib/providers/contract/adapters/slack/index.mjs +175 -0
  231. package/lib/providers/contract/contract-tests.mjs +57 -0
  232. package/lib/providers/contract/errors.mjs +48 -0
  233. package/lib/providers/contract/interface.mjs +50 -0
  234. package/lib/providers/contract/registry.mjs +102 -0
  235. package/lib/providers/copilot-auth.mjs +297 -0
  236. package/lib/providers/credential-bootstrap.mjs +178 -0
  237. package/lib/providers/credential-catalog.mjs +46 -0
  238. package/lib/providers/credential-sources.mjs +63 -0
  239. package/lib/providers/creds.mjs +5 -2
  240. package/lib/providers/op-run.mjs +59 -0
  241. package/lib/providers/secret-resolver.mjs +159 -0
  242. package/lib/publish-template.mjs +163 -0
  243. package/lib/publish-tooling.mjs +119 -0
  244. package/lib/publish.mjs +305 -0
  245. package/lib/registry/cli.mjs +82 -0
  246. package/lib/registry/consolidation.mjs +147 -0
  247. package/lib/registry/generate-docs.mjs +75 -0
  248. package/lib/registry/skill-verification.mjs +57 -0
  249. package/lib/registry/surface-map.mjs +76 -0
  250. package/lib/registry/validate.mjs +135 -0
  251. package/lib/resources/budget.mjs +82 -0
  252. package/lib/resources/process-budget.mjs +45 -0
  253. package/lib/rules-delivery.mjs +9 -2
  254. package/lib/rules-read.mjs +26 -0
  255. package/lib/runtime-env.mjs +23 -0
  256. package/lib/runtime-pressure.mjs +51 -7
  257. package/lib/schema-infer.mjs +13 -2
  258. package/lib/server/chat-loop.mjs +622 -0
  259. package/lib/server/demo-preview.mjs +63 -0
  260. package/lib/server/index.mjs +259 -9
  261. package/lib/server/rate-limit.mjs +5 -3
  262. package/lib/server/webhook.mjs +3 -2
  263. package/lib/service-manager.mjs +60 -8
  264. package/lib/setup.mjs +70 -7
  265. package/lib/specialists/prompt-schema.mjs +19 -10
  266. package/lib/specialists/roster.mjs +43 -0
  267. package/lib/specialists/scaffold.mjs +56 -0
  268. package/lib/storage/backend.mjs +6 -6
  269. package/lib/storage/hybrid-query.mjs +7 -4
  270. package/lib/storage/state-source.mjs +5 -5
  271. package/lib/storage/sync.mjs +2 -3
  272. package/lib/telemetry/rule-calls.mjs +52 -0
  273. package/lib/template-registry.mjs +2 -2
  274. package/lib/templates/visual-requirements.mjs +27 -51
  275. package/lib/test-corpus-inventory.mjs +313 -0
  276. package/lib/uninstall/uninstall.mjs +19 -7
  277. package/lib/update.mjs +12 -0
  278. package/lib/upgrade.mjs +14 -0
  279. package/lib/wireframe.mjs +20 -14
  280. package/lib/worker/run.mjs +17 -4
  281. package/lib/worker/trace.mjs +11 -3
  282. package/package.json +28 -14
  283. package/personas/construct.md +2 -2
  284. package/platforms/claude/settings.template.json +36 -0
  285. package/rules/common/patterns.md +1 -1
  286. package/rules/common/release-gates.md +4 -3
  287. package/scripts/sync-specialists.mjs +41 -28
  288. package/skills/devops/data-engineering.md +1 -1
  289. package/skills/docs/adr-workflow.md +1 -0
  290. package/skills/docs/backlog-proposal-workflow.md +1 -0
  291. package/skills/docs/codebase-research-workflow.md +40 -0
  292. package/skills/docs/customer-profile-workflow.md +1 -0
  293. package/skills/docs/document-ingest-workflow.md +1 -0
  294. package/skills/docs/evidence-ingest-workflow.md +1 -0
  295. package/skills/docs/init-docs.md +2 -2
  296. package/skills/docs/init-project.md +7 -2
  297. package/skills/docs/prd-workflow.md +24 -1
  298. package/skills/docs/prfaq-workflow.md +1 -0
  299. package/skills/docs/product-intelligence-workflow.md +1 -0
  300. package/skills/docs/product-signal-workflow.md +1 -0
  301. package/skills/docs/research-workflow.md +54 -37
  302. package/skills/docs/runbook-workflow.md +1 -0
  303. package/skills/docs/strategy-workflow.md +1 -0
  304. package/skills/docs/user-research-workflow.md +40 -0
  305. package/skills/operating/orchestration-reference.md +1 -1
  306. package/skills/roles/architect.md +5 -0
  307. package/skills/roles/operator.docs.md +4 -0
  308. package/skills/routing.md +4 -2
  309. package/specialists/artifact-manifest.json +489 -0
  310. package/specialists/artifact-manifest.schema.json +83 -0
  311. package/specialists/audit-enrichments.json +454 -0
  312. package/specialists/contracts.json +37 -25
  313. package/specialists/prompts/_shared/validation-contract.md +26 -0
  314. package/specialists/prompts/cx-accessibility.md +21 -2
  315. package/specialists/prompts/cx-ai-engineer.md +24 -1
  316. package/specialists/prompts/cx-architect.md +4 -1
  317. package/specialists/prompts/cx-business-strategist.md +23 -2
  318. package/specialists/prompts/cx-data-analyst.md +22 -1
  319. package/specialists/prompts/cx-data-engineer.md +23 -2
  320. package/specialists/prompts/cx-debugger.md +21 -2
  321. package/specialists/prompts/cx-designer.md +26 -3
  322. package/specialists/prompts/cx-devil-advocate.md +19 -2
  323. package/specialists/prompts/cx-docs-keeper.md +28 -1
  324. package/specialists/prompts/cx-engineer.md +22 -1
  325. package/specialists/prompts/cx-evaluator.md +18 -1
  326. package/specialists/prompts/cx-explorer.md +21 -2
  327. package/specialists/prompts/cx-legal-compliance.md +21 -2
  328. package/specialists/prompts/cx-operations.md +21 -2
  329. package/specialists/prompts/cx-oracle.md +94 -0
  330. package/specialists/prompts/cx-orchestrator.md +20 -1
  331. package/specialists/prompts/cx-platform-engineer.md +25 -2
  332. package/specialists/prompts/cx-product-manager.md +32 -1
  333. package/specialists/prompts/cx-qa.md +24 -2
  334. package/specialists/prompts/cx-rd-lead.md +23 -2
  335. package/specialists/prompts/cx-release-manager.md +23 -2
  336. package/specialists/prompts/cx-researcher.md +22 -2
  337. package/specialists/prompts/cx-reviewer.md +18 -1
  338. package/specialists/prompts/cx-security.md +22 -2
  339. package/specialists/prompts/cx-sre.md +30 -3
  340. package/specialists/prompts/cx-test-automation.md +7 -1
  341. package/specialists/prompts/cx-trace-reviewer.md +22 -3
  342. package/specialists/prompts/cx-ux-researcher.md +21 -2
  343. package/specialists/registry.json +52 -173
  344. package/specialists/tone-profiles.json +42 -0
  345. package/templates/demos/playwright/demo-recording.config.mjs +47 -0
  346. package/templates/demos/recordings/agentic-platforms-prd.json +19 -0
  347. package/templates/demos/scripts/agentic-platforms-prd.json +44 -0
  348. package/templates/demos/specs/_helpers/scroll-artifact.ts +89 -0
  349. package/templates/demos/tapes/agentic-platforms-prd.tape +49 -0
  350. package/templates/demos/tapes/resource-guard-rails.tape +49 -0
  351. package/templates/demos/vhs/construct-cockpit.json +24 -0
  352. package/templates/distribution/construct-brand.typ +446 -0
  353. package/templates/distribution/construct-decision.typ +38 -0
  354. package/templates/distribution/construct-deck.html +95 -0
  355. package/templates/distribution/construct-pdf.typ +38 -0
  356. package/templates/distribution/construct-prd.typ +38 -0
  357. package/templates/distribution/construct-reference.docx +0 -0
  358. package/templates/distribution/construct-research.typ +38 -0
  359. package/templates/distribution/construct-web.html +92 -0
  360. package/templates/distribution/fonts/Geist-Bold.ttf +0 -0
  361. package/templates/distribution/fonts/Geist-Medium.ttf +0 -0
  362. package/templates/distribution/fonts/Geist-Regular.ttf +0 -0
  363. package/templates/distribution/fonts/Geist-SemiBold.ttf +0 -0
  364. package/templates/distribution/fonts/GeistMono-Medium.ttf +0 -0
  365. package/templates/distribution/fonts/GeistMono-Regular.ttf +0 -0
  366. package/templates/distribution/fonts/GeistMono-SemiBold.ttf +0 -0
  367. package/templates/distribution/fonts/IBMPlexMono-Regular.otf +0 -0
  368. package/templates/distribution/fonts/JetBrainsMono-Medium.ttf +0 -0
  369. package/templates/distribution/fonts/JetBrainsMono-Regular.ttf +0 -0
  370. package/templates/distribution/fonts/JetBrainsMono-SemiBold.ttf +0 -0
  371. package/templates/distribution/fonts/PlusJakartaSans-Bold.ttf +0 -0
  372. package/templates/distribution/fonts/PlusJakartaSans-Medium.ttf +0 -0
  373. package/templates/distribution/fonts/PlusJakartaSans-Regular.ttf +0 -0
  374. package/templates/distribution/fonts/PlusJakartaSans-SemiBold.ttf +0 -0
  375. package/templates/distribution/fonts/README.md +36 -0
  376. package/templates/distribution/fonts/SpaceGrotesk-Variable.ttf +0 -0
  377. package/templates/distribution/fonts/handwritten/Caveat.ttf +0 -0
  378. package/templates/distribution/fonts/legacy/IBMPlexMono-Regular.otf +0 -0
  379. package/templates/distribution/fonts/legacy/Inter-Medium.otf +0 -0
  380. package/templates/distribution/fonts/legacy/Inter-Regular.otf +0 -0
  381. package/templates/distribution/fonts/legacy/Inter-SemiBold.otf +0 -0
  382. package/templates/distribution/fonts/legacy/InterDisplay-SemiBold.otf +0 -0
  383. package/templates/distribution/fonts/legacy/SourceSerif4-Regular.otf +0 -0
  384. package/templates/distribution/fonts/legacy/SourceSerif4-Semibold.otf +0 -0
  385. package/templates/distribution/icons/activity.svg +15 -0
  386. package/templates/distribution/icons/alert-triangle.svg +17 -0
  387. package/templates/distribution/icons/book-open.svg +16 -0
  388. package/templates/distribution/icons/bot.svg +20 -0
  389. package/templates/distribution/icons/brain.svg +22 -0
  390. package/templates/distribution/icons/circle-check.svg +16 -0
  391. package/templates/distribution/icons/clipboard-check.svg +17 -0
  392. package/templates/distribution/icons/cpu.svg +28 -0
  393. package/templates/distribution/icons/database.svg +17 -0
  394. package/templates/distribution/icons/eye.svg +16 -0
  395. package/templates/distribution/icons/file-text.svg +19 -0
  396. package/templates/distribution/icons/gauge.svg +16 -0
  397. package/templates/distribution/icons/git-branch.svg +17 -0
  398. package/templates/distribution/icons/key.svg +17 -0
  399. package/templates/distribution/icons/layers.svg +17 -0
  400. package/templates/distribution/icons/list-checks.svg +19 -0
  401. package/templates/distribution/icons/lock.svg +16 -0
  402. package/templates/distribution/icons/message-square.svg +15 -0
  403. package/templates/distribution/icons/network.svg +19 -0
  404. package/templates/distribution/icons/route.svg +17 -0
  405. package/templates/distribution/icons/scale.svg +19 -0
  406. package/templates/distribution/icons/search.svg +16 -0
  407. package/templates/distribution/icons/send.svg +16 -0
  408. package/templates/distribution/icons/server.svg +18 -0
  409. package/templates/distribution/icons/shield-check.svg +16 -0
  410. package/templates/distribution/icons/users.svg +18 -0
  411. package/templates/distribution/icons/webhook.svg +17 -0
  412. package/templates/distribution/icons/workflow.svg +17 -0
  413. package/templates/distribution/icons/wrench.svg +15 -0
  414. package/templates/distribution/run.mjs +18 -1
  415. package/templates/docs/adr.md +7 -0
  416. package/templates/docs/construct_guide.md +8 -8
  417. package/templates/docs/customer-profile.md +4 -0
  418. package/templates/docs/one-pager.md +4 -0
  419. package/templates/docs/postmortem.md +31 -0
  420. package/templates/docs/prd-platform.md +19 -0
  421. package/templates/docs/prd.md +15 -0
  422. package/templates/docs/prfaq.md +7 -0
  423. package/templates/docs/qa-strategy.md +103 -0
  424. package/templates/docs/research-brief.md +8 -0
  425. package/templates/docs/rfc-platform.md +12 -0
  426. package/templates/docs/test-plan.md +7 -0
@@ -0,0 +1,446 @@
1
+ /**
2
+ * templates/distribution/construct-brand.typ — Construct distribution brand system.
3
+ *
4
+ * Single source of typographic, spacing, and color tokens for every bundled PDF
5
+ * layout (prd, research, decision, fallback). The system is monochrome: black,
6
+ * white, and a grey ink ramp carry all document chrome and accents, so printed
7
+ * artifacts read as one consistent family regardless of type. Color belongs to
8
+ * diagrams, not to the page furniture.
9
+ *
10
+ * Space Grotesk is referenced by explicit numeric weight (400/500/600/700) and ships
11
+ * as a single weight-axis variable TTF registered under the family name "Space
12
+ * Grotesk"; named weights like "Space Grotesk SemiBold" must never be used — they
13
+ * fall back to a serif. Document-wide #set/#show rules sit at module scope so the Pandoc $body$
14
+ * inherits them; exported helpers keep stable signatures for the layout templates.
15
+ */
16
+
17
+ // Monochrome ink ramp. `ink` is the single strong accent (rules, ticks, header
18
+ // underlines, emphasis); the greys below it carry hierarchy; hairlines and
19
+ // surfaces stay near-white so panels read as paper, not color.
20
+
21
+ #let ink = rgb("#0a0c10")
22
+ #let ink-strong = rgb("#16191f")
23
+ #let ink-body = rgb("#23272e")
24
+ #let ink-muted = rgb("#565c66")
25
+ #let ink-faint = rgb("#9499a2")
26
+ #let hairline = rgb("#e3e4e8")
27
+ #let hairline-strong = rgb("#cdd0d6")
28
+ #let surface = rgb("#fafafa")
29
+ #let surface-alt = rgb("#f3f4f6")
30
+ #let paper = rgb("#ffffff")
31
+
32
+ // Back-compatible token names kept for any external reference, remapped onto the
33
+ // monochrome ramp so legacy callers inherit the new palette automatically.
34
+
35
+ #let brand-accent = ink
36
+ #let brand-accent-deep = ink
37
+ #let brand-violet-soft = ink-muted
38
+ #let brand-warm = ink
39
+ #let brand-warm-deep = ink-strong
40
+ #let brand-info = ink-muted
41
+ #let brand-success = ink
42
+ #let brand-navy = rgb("#0c1018")
43
+ #let brand-ink = ink-strong
44
+ #let brand-ink-soft = ink-body
45
+ #let brand-muted = ink-muted
46
+ #let brand-faint = ink-faint
47
+ #let brand-surface = surface
48
+ #let brand-surface-alt = surface-alt
49
+ #let brand-surface-warm = surface
50
+ #let brand-surface-info = surface-alt
51
+ #let brand-hairline = hairline
52
+ #let brand-rule = hairline-strong
53
+
54
+ #let construct-font-sans = ("Space Grotesk",)
55
+ #let construct-font-display = ("Space Grotesk",)
56
+ #let construct-font-mono = ("JetBrains Mono",)
57
+
58
+ // Type scale. One modular ramp shared by every document type so sizing never
59
+ // drifts between a PRD, a research brief, and a decision record.
60
+
61
+ #let fs-micro = 8pt
62
+ #let fs-small = 8.5pt
63
+ #let fs-meta = 9pt
64
+ #let fs-body = 10pt
65
+ #let fs-h4 = 8.5pt
66
+ #let fs-h3 = 11pt
67
+ #let fs-h2 = 13pt
68
+ #let fs-h1 = 17pt
69
+ #let fs-subtitle = 11.5pt
70
+ #let fs-title = 24pt
71
+
72
+ // Weight scale. Numeric only; these resolve to the bundled Space Grotesk variable face.
73
+
74
+ #let wt-regular = 400
75
+ #let wt-medium = 500
76
+ #let wt-semibold = 600
77
+ #let wt-bold = 700
78
+
79
+ #let construct-figure-max-width = 58%
80
+ #let construct-figure-max-height = 2.35in
81
+
82
+ // A document-type word maps to a compact badge so the masthead reads as a
83
+ // labelled artifact rather than a bare title.
84
+
85
+ #let construct-badge-label(artifact-type) = {
86
+ if artifact-type == "" [Document]
87
+ else {
88
+ let lower = lower(artifact-type)
89
+ if lower.contains("prd") [PRD]
90
+ else if lower.contains("research") or lower.contains("brief") or lower.contains("finding") [Research]
91
+ else if lower.contains("adr") [ADR]
92
+ else if lower.contains("rfc") [RFC]
93
+ else if lower.contains("strategy") [Strategy]
94
+ else if lower.contains("runbook") [Runbook]
95
+ else [#upper(artifact-type)]
96
+ }
97
+ }
98
+
99
+ // Status pill, monochrome. Settled states (approved/shipped) read as a solid
100
+ // black chip; active states as a black outline; everything else as a faint
101
+ // outline. Color is never used to encode status.
102
+
103
+ #let construct-status-pill(status) = {
104
+ let s = lower(status).trim()
105
+ let settled = s == "approved" or s == "shipped"
106
+ let active = s.contains("review") or s.contains("progress")
107
+ let fill-c = if settled { ink } else { paper }
108
+ let stroke-c = if settled { ink } else if active { ink } else { hairline-strong }
109
+ let text-c = if settled { paper } else if active { ink } else { ink-muted }
110
+ box(
111
+ fill: fill-c,
112
+ inset: (x: 6pt, y: 2.5pt),
113
+ radius: 3pt,
114
+ stroke: 0.6pt + stroke-c,
115
+ baseline: 0.16em,
116
+ )[
117
+ #text(font: construct-font-sans, size: 7pt, weight: wt-bold, fill: text-c, tracking: 0.05em)[#upper(status)]
118
+ ]
119
+ }
120
+
121
+ // Kicker chips above the title: artifact badge, doc id, version, classification,
122
+ // separated by a faint dot.
123
+
124
+ #let construct-meta-chips(
125
+ artifact-type,
126
+ doc-id: "",
127
+ version: "",
128
+ classification: "",
129
+ ) = {
130
+ let chips = ()
131
+ if artifact-type != "" { chips.push(construct-badge-label(artifact-type)) }
132
+ if doc-id != "" { chips.push(doc-id) }
133
+ if version != "" { chips.push("v" + version) }
134
+ if classification != "" { chips.push(upper(classification)) }
135
+ if chips.len() == 0 { none }
136
+ else {
137
+ set text(font: construct-font-sans, size: fs-micro, weight: wt-bold, fill: ink, tracking: 0.08em)
138
+ chips.join([#text(fill: ink-faint, weight: wt-regular)[ #sym.dot.c ]])
139
+ }
140
+ }
141
+
142
+ // Byline pairs the status pill with owner and date in muted text.
143
+
144
+ #let construct-editorial-line(status, owner, date) = {
145
+ let parts = ()
146
+ if owner != "" { parts.push(owner) }
147
+ if date != "" { parts.push(date) }
148
+ let tail = if parts.len() == 0 { none } else {
149
+ set text(font: construct-font-sans, size: fs-small, fill: ink-muted)
150
+ parts.join([#text(fill: ink-faint)[ #sym.dot.c ]])
151
+ }
152
+ if status == "" and tail == none { none }
153
+ else {
154
+ box[
155
+ #if status != "" [#construct-status-pill(status)#h(7pt)]
156
+ #if tail != none [#tail]
157
+ ]
158
+ }
159
+ }
160
+
161
+ // Masthead: a cover-grade title block. Kicker chips, large display title, an
162
+ // optional subtitle, a byline, and a full hairline pinned by a short black tick
163
+ // at the left margin.
164
+
165
+ #let construct-masthead(
166
+ title,
167
+ subtitle,
168
+ status,
169
+ owner,
170
+ date,
171
+ artifact-type,
172
+ version: "",
173
+ doc-id: "",
174
+ classification: "",
175
+ ) = {
176
+ let chips = construct-meta-chips(
177
+ artifact-type,
178
+ doc-id: doc-id,
179
+ version: version,
180
+ classification: classification,
181
+ )
182
+ if chips != none {
183
+ chips
184
+ v(0.62em)
185
+ }
186
+ text(font: construct-font-display, size: fs-title, weight: wt-semibold, fill: ink, tracking: -0.02em)[#title]
187
+ if subtitle != "" {
188
+ v(0.45em)
189
+ text(font: construct-font-sans, size: fs-subtitle, fill: ink-muted, tracking: -0.005em)[#subtitle]
190
+ }
191
+ let byline = construct-editorial-line(status, owner, date)
192
+ if byline != none {
193
+ v(0.78em)
194
+ byline
195
+ }
196
+ v(0.95em)
197
+ block(width: 100%, height: 2pt, {
198
+ place(left + horizon, line(length: 100%, stroke: 0.5pt + hairline))
199
+ place(left + horizon, line(length: 46pt, stroke: 2pt + ink))
200
+ })
201
+ v(1.05em)
202
+ }
203
+
204
+ // Shared callout shell: a grey panel with a black left bar and a small bold
205
+ // uppercase label. Variants differ only by label and tint, never by color, so
206
+ // the differentiator is the word, not a hue.
207
+
208
+ #let construct-callout(label, body, tint: surface, bar: 2.5pt) = {
209
+ block(
210
+ fill: tint,
211
+ stroke: (left: bar + ink, rest: none),
212
+ inset: (left: 14pt, top: 11pt, bottom: 11pt, right: 13pt),
213
+ width: 100%,
214
+ radius: (right: 3pt),
215
+ )[
216
+ #set par(leading: 0.66em, spacing: 0.72em)
217
+ #set text(font: construct-font-sans, size: fs-body, fill: ink-body)
218
+ #text(size: 7.5pt, weight: wt-bold, fill: ink, tracking: 0.09em)[#upper(label)]
219
+ #v(0.42em)
220
+ #body
221
+ ]
222
+ v(0.85em)
223
+ }
224
+
225
+ #let construct-at-a-glance(body) = construct-callout("At a glance", body, tint: surface-alt)
226
+
227
+ #let construct-note(body) = construct-callout("Note", body, tint: surface)
228
+
229
+ #let construct-decision-callout(body) = construct-callout("Decision", body, tint: surface-alt, bar: 3.5pt)
230
+
231
+ // Key metrics reads as a measured card: clean surface, a top black rule, a bold
232
+ // label, then the metrics table.
233
+
234
+ #let construct-key-metrics(body) = {
235
+ block(
236
+ fill: surface,
237
+ stroke: (top: 1.5pt + ink, rest: 0.5pt + hairline),
238
+ inset: 13pt,
239
+ width: 100%,
240
+ radius: (bottom: 3pt),
241
+ )[
242
+ #text(font: construct-font-sans, size: 7.5pt, weight: wt-bold, fill: ink, tracking: 0.09em)[KEY METRICS]
243
+ #v(0.48em)
244
+ #body
245
+ ]
246
+ v(0.85em)
247
+ }
248
+
249
+ // Running header appears from page two: short title at left, doc id and page at
250
+ // right, closed by a hairline.
251
+
252
+ #let construct-running-header(title, doc-id: "", version: "") = context {
253
+ if counter(page).get().first() > 1 [
254
+ #set text(font: construct-font-sans, size: fs-micro, fill: ink-muted)
255
+ #let short-title = if title.len() > 52 { title.slice(0, 49) + "…" } else { title }
256
+ #text(fill: ink-muted)[#short-title]
257
+ #h(1fr)
258
+ #if doc-id != "" [
259
+ #text(fill: ink, weight: wt-medium)[#doc-id]
260
+ #text(fill: ink-faint)[ #sym.dot.c ]
261
+ ]
262
+ #if version != "" [
263
+ #text(fill: ink-faint)[v#version]
264
+ #text(fill: ink-faint)[ #sym.dot.c ]
265
+ ]
266
+ #text(fill: ink-muted)[#counter(page).display()]
267
+ #v(0.3em)
268
+ #line(length: 100%, stroke: 0.4pt + hairline)
269
+ ]
270
+ }
271
+
272
+ #let construct-running-footer(footer-label, classification: "") = context {
273
+ if counter(page).get().first() > 1 [
274
+ #v(0.3em)
275
+ #line(length: 100%, stroke: 0.4pt + hairline)
276
+ #v(0.28em)
277
+ #set text(font: construct-font-sans, size: fs-micro, fill: ink-faint)
278
+ #align(center)[
279
+ #text(weight: wt-bold, fill: ink, tracking: 0.04em)[Construct]
280
+ #text[ #sym.dot.c #footer-label]
281
+ #if classification != "" [ #sym.dot.c #upper(classification)]
282
+ ]
283
+ ]
284
+ }
285
+
286
+ // The theme function carries every body-level #set/#show rule. Module-scope set
287
+ // rules do NOT propagate through `#import`, so each layout template applies this
288
+ // with `#show: construct-theme` to wrap the Pandoc $body$ in the styled scope.
289
+ // Space Grotesk is referenced by numeric weight throughout to avoid serif fallback.
290
+
291
+ #let construct-theme(body) = {
292
+ set text(font: construct-font-sans, size: fs-body, fill: ink-body, lang: "en", tracking: 0.002em)
293
+ set par(justify: false, leading: 0.82em, spacing: 1.15em)
294
+ set heading(numbering: none, outlined: true)
295
+
296
+ show strong: set text(font: construct-font-sans, weight: wt-semibold, fill: ink)
297
+ show emph: set text(style: "italic")
298
+
299
+ // Lists inherit the open body leading; a per-item block gap keeps a wrapped
300
+ // bullet from reading as two bullets, with the gap clearly wider than the
301
+ // line leading within an item.
302
+
303
+ show list.item: it => block(below: 0.6em, it)
304
+ show enum.item: it => block(below: 0.6em, it)
305
+ set list(marker: (text(fill: ink)[•], text(fill: ink-muted)[‣], text(fill: ink-faint)[–]), indent: 2pt, body-indent: 8pt, spacing: 0.9em)
306
+ set enum(indent: 2pt, body-indent: 8pt, spacing: 0.9em)
307
+
308
+ // Links carry ink with a hairline underline so citations stay legible without
309
+ // introducing color.
310
+
311
+ show link: it => {
312
+ set text(fill: ink)
313
+ underline(offset: 1.5pt, stroke: 0.5pt + hairline-strong, it)
314
+ }
315
+
316
+ // Headings. Level 2 is the working section header (the H1 title is rendered by
317
+ // the masthead and stripped from the body), so it carries a short black rule.
318
+
319
+ show heading.where(level: 1): it => {
320
+ v(1.2em, weak: true)
321
+ block(sticky: true, {
322
+ text(font: construct-font-display, size: fs-h1, weight: wt-semibold, fill: ink, tracking: -0.015em)[#it.body]
323
+ v(0.22em)
324
+ line(length: 100%, stroke: 0.5pt + hairline)
325
+ v(0.55em, weak: true)
326
+ })
327
+ }
328
+ show heading.where(level: 2): it => {
329
+ v(1.65em, weak: true)
330
+ block(sticky: true, {
331
+ text(font: construct-font-display, size: fs-h2, weight: wt-semibold, fill: ink, tracking: -0.01em)[#it.body]
332
+ v(0.4em)
333
+ line(length: 26pt, stroke: 1.5pt + ink)
334
+ v(0.62em, weak: true)
335
+ })
336
+ }
337
+ show heading.where(level: 3): it => {
338
+ v(1.15em, weak: true)
339
+ block(sticky: true, {
340
+ text(font: construct-font-sans, size: fs-h3, weight: wt-semibold, fill: ink-strong)[#it.body]
341
+ v(0.38em, weak: true)
342
+ })
343
+ }
344
+ show heading.where(level: 4): it => {
345
+ v(0.75em, weak: true)
346
+ block(sticky: true, {
347
+ text(font: construct-font-sans, size: fs-h4, weight: wt-bold, fill: ink-muted, tracking: 0.07em)[#upper(it.body)]
348
+ v(0.22em, weak: true)
349
+ })
350
+ }
351
+
352
+ // Inline code sits in a faint grey chip; block code in a bordered panel.
353
+
354
+ show raw.where(block: false): it => box(
355
+ fill: surface-alt,
356
+ inset: (x: 3.5pt, y: 0pt),
357
+ outset: (y: 2.5pt),
358
+ radius: 2.5pt,
359
+ )[#text(font: construct-font-mono, size: 8.8pt, fill: ink-strong)[#it]]
360
+
361
+ show raw.where(block: true): it => block(
362
+ fill: surface,
363
+ stroke: 0.5pt + hairline,
364
+ inset: 11pt,
365
+ radius: 4pt,
366
+ width: 100%,
367
+ )[#text(font: construct-font-mono, size: fs-meta, fill: ink-body)[#it]]
368
+
369
+ // Tables use a horizontal-only rule system: a strong black line under the bold
370
+ // header, light hairlines between rows, no vertical chrome. The first column
371
+ // carries semibold weight for scanning.
372
+
373
+ set table(
374
+ stroke: (x, y) => (
375
+ bottom: if y == 0 { 1pt + ink } else { 0.5pt + hairline },
376
+ top: none, left: none, right: none,
377
+ ),
378
+ inset: (x: 9pt, y: 7.5pt),
379
+ fill: none,
380
+ )
381
+ show table.cell: it => {
382
+ if it.y == 0 {
383
+ set text(font: construct-font-sans, weight: wt-bold, fill: ink, size: fs-small)
384
+ it
385
+ } else {
386
+ set text(
387
+ font: construct-font-sans,
388
+ size: fs-meta,
389
+ fill: if it.x == 0 { ink-strong } else { ink-body },
390
+ weight: if it.x == 0 { wt-semibold } else { wt-regular },
391
+ )
392
+ it
393
+ }
394
+ }
395
+
396
+ // Pandoc wraps markdown tables in a centered figure; lift the table out and
397
+ // left-align it so it reads as a clean data block aligned to the text column.
398
+
399
+ show figure.where(kind: table): it => {
400
+ if it.body.has("body") { align(left, it.body.body) } else { align(left, it.body) }
401
+ }
402
+
403
+ // Figures: a light bordered frame, a constrained width, and a caption that
404
+ // leads with a bold figure number.
405
+
406
+ set figure(supplement: "Figure")
407
+ show figure.where(kind: image): it => {
408
+ v(0.95em, weak: true)
409
+ block(
410
+ stroke: 0.5pt + hairline,
411
+ fill: white,
412
+ inset: 9pt,
413
+ width: 100%,
414
+ radius: 4pt,
415
+ )[
416
+ #align(center)[
417
+ #box(width: construct-figure-max-width)[
418
+ #show image: img => image(img, width: 100%, height: construct-figure-max-height, fit: "contain")
419
+ #it.body
420
+ ]
421
+ ]
422
+ ]
423
+ if it.caption != none {
424
+ v(0.35em)
425
+ align(center)[
426
+ #text(font: construct-font-sans, size: fs-micro, weight: wt-bold, fill: ink, tracking: 0.03em)[#it.supplement #it.counter.display()]
427
+ #text(font: construct-font-sans, size: fs-micro, fill: ink-muted)[ — #it.caption.body]
428
+ ]
429
+ }
430
+ v(0.95em, weak: true)
431
+ }
432
+
433
+ // Blockquotes route to callouts: a quote carrying a Metric/Baseline table
434
+ // becomes the key-metrics card, anything else becomes the at-a-glance aside.
435
+
436
+ show quote: it => {
437
+ let r = repr(it.body)
438
+ if r.contains("Metric") and r.contains("Baseline") {
439
+ construct-key-metrics(it.body)
440
+ } else {
441
+ construct-at-a-glance(it.body)
442
+ }
443
+ }
444
+
445
+ body
446
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * templates/distribution/construct-decision.typ — Construct distribution PDF.
3
+ */
4
+
5
+ #import "construct-brand.typ": *
6
+
7
+ #show: construct-theme
8
+
9
+ #set page(
10
+ paper: "a4",
11
+ margin: (x: 2cm, top: 1.8cm, bottom: 2.2cm),
12
+ numbering: "1",
13
+ header: construct-running-header(
14
+ "$if(title)$$title$$endif$",
15
+ doc-id: "$if(docId)$$docId$$endif$",
16
+ version: "$if(version)$$version$$endif$",
17
+ ),
18
+ footer: construct-running-footer(
19
+ "Decision record",
20
+ classification: "$if(classification)$$classification$$endif$",
21
+ ),
22
+ )
23
+
24
+ $if(title)$
25
+ #construct-masthead(
26
+ "$title$",
27
+ "$if(subtitle)$$subtitle$$endif$",
28
+ "$if(status)$$status$$endif$",
29
+ "$if(owner)$$owner$$endif$",
30
+ "$if(date)$$date$$endif$",
31
+ "$if(artifactType)$$artifactType$$endif$",
32
+ version: "$if(version)$$version$$endif$",
33
+ doc-id: "$if(docId)$$docId$$endif$",
34
+ classification: "$if(classification)$$classification$$endif$",
35
+ )
36
+ $endif$
37
+
38
+ $body$
@@ -0,0 +1,95 @@
1
+ <!--
2
+ templates/distribution/construct-deck.html — Construct branded 16:9 slide deck
3
+ HTML for `construct export --to=deck`. Pandoc emits section divs (--section-divs)
4
+ from markdown slides separated by ---; Space Grotesk via Google Fonts; monochrome ink
5
+ tokens mirror construct-brand.typ. Metadata variables fill the title slide.
6
+ -->
7
+ <!DOCTYPE html>
8
+ <html lang="en">
9
+ <head>
10
+ <meta charset="utf-8">
11
+ <meta name="viewport" content="width=device-width, initial-scale=1">
12
+ <title>$if(title)$$title$$else$Construct deck$endif$</title>
13
+ <link rel="preconnect" href="https://fonts.googleapis.com">
14
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
15
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">
16
+ <style>
17
+ :root {
18
+ --ink:#0a0c10; --ink-strong:#16191f; --ink-body:#23272e; --muted:#565c66; --faint:#9499a2;
19
+ --hairline:#e3e4e8; --hairline-strong:#cdd0d6; --surface:#fafafa; --surface-alt:#f3f4f6; --paper:#ffffff;
20
+ --sans:'Space Grotesk',system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
21
+ --mono:'JetBrains Mono',ui-monospace,'SF Mono',Menlo,monospace;
22
+ --slide-w:1280px;
23
+ --slide-h:720px;
24
+ }
25
+ * { box-sizing:border-box; }
26
+ html { scroll-behavior:smooth; -webkit-text-size-adjust:100%; }
27
+ body { margin:0; background:#e8e9ec; color:var(--ink-body); font-family:var(--sans); font-size:22px; line-height:1.45; -webkit-font-smoothing:antialiased; }
28
+ .deck { display:flex; flex-direction:column; align-items:center; gap:32px; padding:40px 24px 80px; }
29
+ section.slide {
30
+ width:min(100%, var(--slide-w));
31
+ aspect-ratio:16/9;
32
+ background:var(--paper);
33
+ border:1px solid var(--hairline);
34
+ border-radius:8px;
35
+ box-shadow:0 18px 48px rgba(10,12,16,.08);
36
+ padding:56px 64px 48px;
37
+ overflow:hidden;
38
+ display:flex;
39
+ flex-direction:column;
40
+ position:relative;
41
+ }
42
+ section.slide::after {
43
+ content:"";
44
+ position:absolute;
45
+ left:0; right:0; bottom:0;
46
+ height:4px;
47
+ background:linear-gradient(90deg, var(--ink) 46px, var(--hairline) 46px);
48
+ }
49
+ section.slide.title-slide { justify-content:center; }
50
+ section.slide.title-slide h1 { font-size:52px; line-height:1.06; letter-spacing:-.024em; margin:0 0 16px; color:var(--ink); font-weight:600; }
51
+ section.slide.title-slide .subtitle { font-size:26px; color:var(--muted); margin:0 0 28px; letter-spacing:-.005em; }
52
+ section.slide.title-slide .meta { font-size:15px; color:var(--muted); letter-spacing:.04em; text-transform:uppercase; font-weight:600; }
53
+ section.slide.title-slide .meta .sep { color:var(--faint); font-weight:400; }
54
+ section.slide h1 { font-size:40px; line-height:1.08; letter-spacing:-.02em; margin:0 0 20px; color:var(--ink); font-weight:600; }
55
+ section.slide h1::after { content:""; display:block; width:36px; height:3px; background:var(--ink); margin-top:14px; }
56
+ section.slide h2 { font-size:28px; font-weight:600; color:var(--ink-strong); margin:28px 0 12px; letter-spacing:-.01em; }
57
+ section.slide h3 { font-size:22px; font-weight:600; color:var(--ink-strong); margin:20px 0 8px; }
58
+ section.slide p { margin:0 0 14px; max-width:52em; }
59
+ section.slide ul, section.slide ol { margin:0 0 16px; padding-left:1.2em; }
60
+ section.slide li { margin:0 0 10px; }
61
+ section.slide li::marker { color:var(--ink); }
62
+ section.slide strong { color:var(--ink); font-weight:600; }
63
+ section.slide code { font-family:var(--mono); font-size:.84em; background:var(--surface-alt); padding:1px 6px; border-radius:4px; }
64
+ section.slide pre { background:var(--surface); border:1px solid var(--hairline); border-radius:8px; padding:16px 18px; overflow:auto; font-size:17px; }
65
+ section.slide pre code { background:none; padding:0; }
66
+ section.slide blockquote { margin:16px 0; padding:14px 18px; background:var(--surface-alt); border-left:3px solid var(--ink); border-radius:0 4px 4px 0; }
67
+ section.slide table { width:100%; border-collapse:collapse; font-size:18px; margin:12px 0; }
68
+ section.slide th, section.slide td { text-align:left; padding:8px 12px; border-bottom:1px solid var(--hairline); }
69
+ section.slide thead th { font-weight:700; color:var(--ink); border-bottom:2px solid var(--ink); }
70
+ section.slide figure { margin:16px 0; text-align:center; }
71
+ section.slide figure img { max-width:58%; max-height:42vh; width:auto; height:auto; object-fit:contain; border:1px solid var(--hairline); border-radius:6px; padding:8px; }
72
+ .slide-foot { margin-top:auto; padding-top:16px; font-size:13px; color:var(--faint); display:flex; justify-content:space-between; align-items:center; }
73
+ .slide-foot b { color:var(--ink); font-weight:600; }
74
+ @media print {
75
+ body { background:var(--paper); }
76
+ .deck { gap:0; padding:0; }
77
+ section.slide { box-shadow:none; border:none; border-radius:0; page-break-after:always; width:100%; height:100vh; }
78
+ }
79
+ </style>
80
+ </head>
81
+ <body>
82
+ <div class="deck">
83
+ $if(title)$
84
+ <section class="slide title-slide">
85
+ $if(artifactType)$<div class="meta">$artifactType$$if(docId)$ <span class="sep">&middot;</span> $docId$$endif$$if(version)$ <span class="sep">&middot;</span> v$version$$endif$</div>$endif$
86
+ <h1>$title$</h1>
87
+ $if(subtitle)$<p class="subtitle">$subtitle$</p>$endif$
88
+ $if(owner)$<p class="meta">$owner$$if(date)$ <span class="sep">&middot;</span> $date$$endif$$if(status)$ <span class="sep">&middot;</span> $status$$endif$</p>$endif$
89
+ <div class="slide-foot"><span><b>Construct</b>$if(classification)$ &middot; $classification$$endif$</span></div>
90
+ </section>
91
+ $endif$
92
+ $body$
93
+ </div>
94
+ </body>
95
+ </html>
@@ -0,0 +1,38 @@
1
+ /**
2
+ * templates/distribution/construct-pdf.typ — Construct distribution PDF.
3
+ */
4
+
5
+ #import "construct-brand.typ": *
6
+
7
+ #show: construct-theme
8
+
9
+ #set page(
10
+ paper: "a4",
11
+ margin: (x: 2cm, top: 1.8cm, bottom: 2.2cm),
12
+ numbering: "1",
13
+ header: construct-running-header(
14
+ "$if(title)$$title$$endif$",
15
+ doc-id: "$if(docId)$$docId$$endif$",
16
+ version: "$if(version)$$version$$endif$",
17
+ ),
18
+ footer: construct-running-footer(
19
+ "Construct brief",
20
+ classification: "$if(classification)$$classification$$endif$",
21
+ ),
22
+ )
23
+
24
+ $if(title)$
25
+ #construct-masthead(
26
+ "$title$",
27
+ "$if(subtitle)$$subtitle$$endif$",
28
+ "$if(status)$$status$$endif$",
29
+ "$if(owner)$$owner$$endif$",
30
+ "$if(date)$$date$$endif$",
31
+ "$if(artifactType)$$artifactType$$endif$",
32
+ version: "$if(version)$$version$$endif$",
33
+ doc-id: "$if(docId)$$docId$$endif$",
34
+ classification: "$if(classification)$$classification$$endif$",
35
+ )
36
+ $endif$
37
+
38
+ $body$
@@ -0,0 +1,38 @@
1
+ /**
2
+ * templates/distribution/construct-prd.typ — Construct distribution PDF.
3
+ */
4
+
5
+ #import "construct-brand.typ": *
6
+
7
+ #show: construct-theme
8
+
9
+ #set page(
10
+ paper: "a4",
11
+ margin: (x: 2cm, top: 1.8cm, bottom: 2.2cm),
12
+ numbering: "1",
13
+ header: construct-running-header(
14
+ "$if(title)$$title$$endif$",
15
+ doc-id: "$if(docId)$$docId$$endif$",
16
+ version: "$if(version)$$version$$endif$",
17
+ ),
18
+ footer: construct-running-footer(
19
+ "Editorial brief",
20
+ classification: "$if(classification)$$classification$$endif$",
21
+ ),
22
+ )
23
+
24
+ $if(title)$
25
+ #construct-masthead(
26
+ "$title$",
27
+ "$if(subtitle)$$subtitle$$endif$",
28
+ "$if(status)$$status$$endif$",
29
+ "$if(owner)$$owner$$endif$",
30
+ "$if(date)$$date$$endif$",
31
+ "$if(artifactType)$$artifactType$$endif$",
32
+ version: "$if(version)$$version$$endif$",
33
+ doc-id: "$if(docId)$$docId$$endif$",
34
+ classification: "$if(classification)$$classification$$endif$",
35
+ )
36
+ $endif$
37
+
38
+ $body$
@@ -0,0 +1,38 @@
1
+ /**
2
+ * templates/distribution/construct-research.typ — Construct distribution PDF.
3
+ */
4
+
5
+ #import "construct-brand.typ": *
6
+
7
+ #show: construct-theme
8
+
9
+ #set page(
10
+ paper: "a4",
11
+ margin: (x: 1.85cm, top: 1.75cm, bottom: 2.1cm),
12
+ numbering: "1",
13
+ header: construct-running-header(
14
+ "$if(title)$$title$$endif$",
15
+ doc-id: "$if(docId)$$docId$$endif$",
16
+ version: "$if(version)$$version$$endif$",
17
+ ),
18
+ footer: construct-running-footer(
19
+ "Research analytics",
20
+ classification: "$if(classification)$$classification$$endif$",
21
+ ),
22
+ )
23
+
24
+ $if(title)$
25
+ #construct-masthead(
26
+ "$title$",
27
+ "$if(subtitle)$$subtitle$$endif$",
28
+ "$if(status)$$status$$endif$",
29
+ "$if(owner)$$owner$$endif$",
30
+ "$if(date)$$date$$endif$",
31
+ "$if(artifactType)$$artifactType$$endif$",
32
+ version: "$if(version)$$version$$endif$",
33
+ doc-id: "$if(docId)$$docId$$endif$",
34
+ classification: "$if(classification)$$classification$$endif$",
35
+ )
36
+ $endif$
37
+
38
+ $body$