@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.
- package/README.md +30 -19
- package/bin/construct +487 -87
- package/bin/construct-postinstall.mjs +30 -2
- package/examples/distribution/README.md +42 -0
- package/examples/distribution/manifest.json +53 -0
- package/examples/distribution/sources/adr.md +84 -0
- package/examples/distribution/sources/deck-one-pager.md +65 -0
- package/examples/distribution/sources/prd-platform.md +161 -0
- package/examples/distribution/sources/research-brief.md +80 -0
- package/examples/distribution/sources/rfc-platform.md +82 -0
- package/examples/distribution/sources/runbook.md +103 -0
- package/examples/distribution/sources/strategy.md +88 -0
- package/lib/adapters-sync.mjs +67 -0
- package/lib/artifact-gate-notice.mjs +38 -0
- package/lib/artifact-manifest.mjs +258 -0
- package/lib/artifact-release-gate.mjs +171 -0
- package/lib/artifact-reviewers.mjs +68 -0
- package/lib/artifact-type-from-path.mjs +79 -0
- package/lib/artifact-workflow.mjs +252 -0
- package/lib/audit-skills.mjs +4 -0
- package/lib/audit-specialists.mjs +285 -0
- package/lib/audit-trail.mjs +1 -1
- package/lib/auto-docs.mjs +90 -19
- package/lib/beads/drift.mjs +27 -6
- package/lib/beads-client.mjs +49 -121
- package/lib/beads-optimistic.mjs +5 -12
- package/lib/brand-fonts.mjs +93 -0
- package/lib/brand-prose.mjs +214 -0
- package/lib/brand-tokens.mjs +92 -0
- package/lib/bridges/copilot-proxy.mjs +13 -26
- package/lib/capability-ledger.mjs +156 -0
- package/lib/certification/artifact-fixtures.mjs +132 -0
- package/lib/certification/artifact-gates.mjs +63 -0
- package/lib/certification/artifact-provenance.mjs +97 -0
- package/lib/certification/canonical-scenarios.mjs +78 -0
- package/lib/certification/cli.mjs +191 -0
- package/lib/certification/dashboard-api.mjs +71 -0
- package/lib/certification/demo-parity.mjs +116 -0
- package/lib/certification/document-io-fixtures.mjs +246 -0
- package/lib/certification/document-workflow.mjs +97 -0
- package/lib/certification/eval-bridge.mjs +77 -0
- package/lib/certification/model-routing.mjs +149 -0
- package/lib/certification/prompt-budget.mjs +119 -0
- package/lib/certification/rc-gate.mjs +206 -0
- package/lib/certification/real-llm-scenarios.mjs +303 -0
- package/lib/certification/role-cards.mjs +113 -0
- package/lib/certification/role-overlays.mjs +117 -0
- package/lib/certification/run.mjs +122 -0
- package/lib/certification/runner.mjs +323 -0
- package/lib/certification/scenarios.mjs +51 -0
- package/lib/certification/skill-inventory.mjs +289 -0
- package/lib/certification/skill-scenarios.mjs +147 -0
- package/lib/certification/specialist-contracts.mjs +85 -0
- package/lib/certification/specialist-scenarios.mjs +175 -0
- package/lib/certification/stale-impact.mjs +146 -0
- package/lib/certification/status.mjs +252 -0
- package/lib/certification/store.mjs +77 -0
- package/lib/chat/cli.mjs +333 -0
- package/lib/chat/command-suggest.mjs +161 -0
- package/lib/chat/commands.mjs +215 -0
- package/lib/chat/config.mjs +142 -0
- package/lib/chat/context-compactor.mjs +250 -0
- package/lib/chat/context-continuation.mjs +253 -0
- package/lib/chat/continuation-source.mjs +58 -0
- package/lib/chat/demo-guide.mjs +61 -0
- package/lib/chat/design-tokens.mjs +91 -0
- package/lib/chat/desktop-binary.mjs +79 -0
- package/lib/chat/desktop-build.mjs +130 -0
- package/lib/chat/desktop-launcher.mjs +133 -0
- package/lib/chat/evidence.mjs +145 -0
- package/lib/chat/export.mjs +74 -0
- package/lib/chat/harness/driver.mjs +91 -0
- package/lib/chat/list-picker.mjs +112 -0
- package/lib/chat/model-picker.mjs +356 -0
- package/lib/chat/openrouter-fallback.mjs +151 -0
- package/lib/chat/permission-prompt.mjs +33 -0
- package/lib/chat/picker-catalog.mjs +45 -0
- package/lib/chat/policy-telemetry.mjs +34 -0
- package/lib/chat/present.mjs +246 -0
- package/lib/chat/session-context.mjs +39 -0
- package/lib/chat/session-persist.mjs +73 -0
- package/lib/chat/session-restore.mjs +71 -0
- package/lib/chat/session-settings.mjs +53 -0
- package/lib/chat/system-prompt.mjs +52 -0
- package/lib/chat/transparency.mjs +93 -0
- package/lib/chat/tui/color-scheme.mjs +42 -0
- package/lib/chat/tui/markdown.mjs +123 -0
- package/lib/chat/tui/presentation.mjs +100 -0
- package/lib/chat/tui/render.mjs +500 -0
- package/lib/chat/tui/turn-block.mjs +284 -0
- package/lib/chat/tui/turn-present.mjs +18 -0
- package/lib/chat/tui/turn-state.mjs +88 -0
- package/lib/chat/tui/usage.mjs +122 -0
- package/lib/chat/web-commands.mjs +146 -0
- package/lib/chat/web-launcher.mjs +63 -0
- package/lib/chat/web-picker-keys.mjs +46 -0
- package/lib/chat/web-session.mjs +159 -0
- package/lib/cli-commands.mjs +146 -15
- package/lib/cli-service-inventory.mjs +42 -0
- package/lib/comment-lint.mjs +6 -4
- package/lib/config/intake-policy.mjs +209 -0
- package/lib/config/project-config.mjs +11 -2
- package/lib/config/schema.mjs +86 -6
- package/lib/config/source-targets.mjs +311 -0
- package/lib/contract-schemas/decision.json +50 -0
- package/lib/contract-schemas/implementation.json +51 -0
- package/lib/contract-schemas/review-report.json +32 -0
- package/lib/contract-schemas/test-report.json +43 -0
- package/lib/contracts/construct-handoff.mjs +60 -0
- package/lib/contracts/validate.mjs +32 -3
- package/lib/contracts/violation-log.mjs +58 -5
- package/lib/dashboard-demo.mjs +71 -0
- package/lib/dashboard-static.mjs +19 -5
- package/lib/decisions/registry.mjs +5 -3
- package/lib/deck-export-pptx.mjs +1152 -0
- package/lib/demo-recording.mjs +142 -0
- package/lib/demo-script.mjs +114 -0
- package/lib/demo-surface.mjs +249 -0
- package/lib/demo.mjs +598 -140
- package/lib/diagram-export.mjs +192 -0
- package/lib/diagram.mjs +13 -11
- package/lib/docs-verify.mjs +25 -7
- package/lib/doctor/index.mjs +3 -1
- package/lib/doctor/project-adapters.mjs +44 -0
- package/lib/doctor/source-checkout.mjs +16 -0
- package/lib/doctor/watchers/cx-budget.mjs +98 -0
- package/lib/doctor/watchers/graph-staleness.mjs +52 -0
- package/lib/doctor/watchers/process-pressure.mjs +4 -3
- package/lib/document-export.mjs +384 -38
- package/lib/document-extract.mjs +44 -4
- package/lib/document-ingest.mjs +29 -20
- package/lib/embed/auto-sources.mjs +44 -0
- package/lib/embed/cli.mjs +4 -3
- package/lib/embed/daemon.mjs +18 -46
- package/lib/embed/demand-fetch.mjs +50 -19
- package/lib/embed/inbox.mjs +6 -10
- package/lib/embed/worker.mjs +4 -3
- package/lib/embedded-contract/index.mjs +11 -0
- package/lib/embedded-contract/model-resolve.mjs +10 -9
- package/lib/env-config.mjs +26 -0
- package/lib/evals/dataset.mjs +137 -0
- package/lib/evals/gates.mjs +175 -0
- package/lib/export-branding.mjs +32 -0
- package/lib/graph/build-co-change.mjs +54 -0
- package/lib/graph/build-from-registry.mjs +136 -0
- package/lib/graph/build-import-graph.mjs +153 -0
- package/lib/graph/cli.mjs +110 -0
- package/lib/graph/impact-cli.mjs +89 -0
- package/lib/graph/impact.mjs +108 -0
- package/lib/graph/staleness.mjs +44 -0
- package/lib/graph/store.mjs +172 -0
- package/lib/health-check.mjs +90 -76
- package/lib/hooks/artifact-release-gate.mjs +43 -0
- package/lib/hooks/brand-prose-lint.mjs +38 -0
- package/lib/hooks/graph-impact-advisory.mjs +62 -0
- package/lib/hooks/session-optimize.mjs +84 -207
- package/lib/hooks/session-start.mjs +18 -16
- package/lib/host-disposition.mjs +7 -0
- package/lib/improvement/cli.mjs +189 -0
- package/lib/improvement/controller.mjs +137 -0
- package/lib/improvement/proposal.mjs +120 -0
- package/lib/improvement/specialist-loop.mjs +192 -0
- package/lib/improvement/store.mjs +89 -0
- package/lib/improvement/surface.mjs +219 -0
- package/lib/ingest-tooling.mjs +97 -0
- package/lib/init/doc-lanes.mjs +165 -0
- package/lib/init-docs.mjs +31 -178
- package/lib/init-unified.mjs +50 -119
- package/lib/init-update-guide.mjs +102 -0
- package/lib/init-update.mjs +32 -1
- package/lib/init.mjs +8 -0
- package/lib/install/desktop-binary-download.mjs +85 -0
- package/lib/install/legacy-global-cleanup.mjs +189 -0
- package/lib/intake/constants.mjs +29 -0
- package/lib/intake/daemon.mjs +2 -0
- package/lib/intake/git-queue.mjs +9 -8
- package/lib/intake/intake-config.mjs +52 -105
- package/lib/intake/legacy-paths.mjs +5 -0
- package/lib/intake/queue.mjs +2 -1
- package/lib/intake/session-prelude.mjs +90 -1
- package/lib/libreoffice-export.mjs +97 -0
- package/lib/logging/rotate.mjs +9 -0
- package/lib/maintenance/docker-reclaim.mjs +206 -0
- package/lib/mcp/external-schema-cost.mjs +74 -0
- package/lib/mcp/server.mjs +103 -10
- package/lib/mcp/stdio-mcp-probe.mjs +188 -0
- package/lib/mcp/tool-budget.mjs +55 -4
- package/lib/mcp/tools/document.mjs +18 -4
- package/lib/mcp/tools/embedded-contract.mjs +14 -0
- package/lib/mcp/tools/skills.mjs +32 -3
- package/lib/mcp/tools/workflow.mjs +13 -1
- package/lib/model-free-selector.mjs +18 -0
- package/lib/model-registry.mjs +13 -229
- package/lib/model-router.mjs +373 -92
- package/lib/models/behavior-matrix.mjs +289 -0
- package/lib/models/catalog.mjs +209 -0
- package/lib/models/execution-capability-profile.mjs +196 -0
- package/lib/models/execution-policy.mjs +307 -0
- package/lib/models/provider-poll.mjs +383 -0
- package/lib/npm-spawn-env.mjs +17 -0
- package/lib/ollama/installed-models.mjs +129 -0
- package/lib/oracle/actions.mjs +187 -0
- package/lib/oracle/artifact-gate.mjs +99 -0
- package/lib/oracle/cli.mjs +204 -0
- package/lib/oracle/daemon-entry.mjs +14 -0
- package/lib/oracle/dispatch.mjs +81 -0
- package/lib/oracle/execute.mjs +143 -0
- package/lib/oracle/gaps.mjs +76 -0
- package/lib/oracle/index.mjs +84 -0
- package/lib/oracle/issues.mjs +164 -0
- package/lib/oracle/org-graph.mjs +170 -0
- package/lib/oracle/policy.mjs +80 -0
- package/lib/oracle/read-model.mjs +398 -0
- package/lib/oracle/reconcile.mjs +191 -0
- package/lib/oracle/routing.mjs +89 -0
- package/lib/oracle/synthesize.mjs +384 -0
- package/lib/oracle/verdicts.mjs +51 -0
- package/lib/orchestration/worker.mjs +88 -27
- package/lib/orchestration-policy.mjs +50 -0
- package/lib/parity.mjs +96 -2
- package/lib/playwright-demo.mjs +254 -0
- package/lib/project-init-shared.mjs +4 -1
- package/lib/prompt-composer.js +7 -3
- package/lib/prompt-validation-contract.mjs +24 -0
- package/lib/provider-capabilities.js +57 -11
- package/lib/providers/contract/adapters/confluence/index.mjs +181 -0
- package/lib/providers/contract/adapters/git/index.mjs +115 -0
- package/lib/providers/contract/adapters/github/index.mjs +166 -0
- package/lib/providers/contract/adapters/jira/index.mjs +187 -0
- package/lib/providers/contract/adapters/slack/index.mjs +175 -0
- package/lib/providers/contract/contract-tests.mjs +57 -0
- package/lib/providers/contract/errors.mjs +48 -0
- package/lib/providers/contract/interface.mjs +50 -0
- package/lib/providers/contract/registry.mjs +102 -0
- package/lib/providers/copilot-auth.mjs +297 -0
- package/lib/providers/credential-bootstrap.mjs +178 -0
- package/lib/providers/credential-catalog.mjs +46 -0
- package/lib/providers/credential-sources.mjs +63 -0
- package/lib/providers/creds.mjs +5 -2
- package/lib/providers/op-run.mjs +59 -0
- package/lib/providers/secret-resolver.mjs +159 -0
- package/lib/publish-template.mjs +163 -0
- package/lib/publish-tooling.mjs +119 -0
- package/lib/publish.mjs +305 -0
- package/lib/registry/cli.mjs +82 -0
- package/lib/registry/consolidation.mjs +147 -0
- package/lib/registry/generate-docs.mjs +75 -0
- package/lib/registry/skill-verification.mjs +57 -0
- package/lib/registry/surface-map.mjs +76 -0
- package/lib/registry/validate.mjs +135 -0
- package/lib/resources/budget.mjs +82 -0
- package/lib/resources/process-budget.mjs +45 -0
- package/lib/rules-delivery.mjs +9 -2
- package/lib/rules-read.mjs +26 -0
- package/lib/runtime-env.mjs +23 -0
- package/lib/runtime-pressure.mjs +51 -7
- package/lib/schema-infer.mjs +13 -2
- package/lib/server/chat-loop.mjs +622 -0
- package/lib/server/demo-preview.mjs +63 -0
- package/lib/server/index.mjs +259 -9
- package/lib/server/rate-limit.mjs +5 -3
- package/lib/server/webhook.mjs +3 -2
- package/lib/service-manager.mjs +60 -8
- package/lib/setup.mjs +70 -7
- package/lib/specialists/prompt-schema.mjs +19 -10
- package/lib/specialists/roster.mjs +43 -0
- package/lib/specialists/scaffold.mjs +56 -0
- package/lib/storage/backend.mjs +6 -6
- package/lib/storage/hybrid-query.mjs +7 -4
- package/lib/storage/state-source.mjs +5 -5
- package/lib/storage/sync.mjs +2 -3
- package/lib/telemetry/rule-calls.mjs +52 -0
- package/lib/template-registry.mjs +2 -2
- package/lib/templates/visual-requirements.mjs +27 -51
- package/lib/test-corpus-inventory.mjs +313 -0
- package/lib/uninstall/uninstall.mjs +19 -7
- package/lib/update.mjs +12 -0
- package/lib/upgrade.mjs +14 -0
- package/lib/wireframe.mjs +20 -14
- package/lib/worker/run.mjs +17 -4
- package/lib/worker/trace.mjs +11 -3
- package/package.json +28 -14
- package/personas/construct.md +2 -2
- package/platforms/claude/settings.template.json +36 -0
- package/rules/common/patterns.md +1 -1
- package/rules/common/release-gates.md +4 -3
- package/scripts/sync-specialists.mjs +41 -28
- package/skills/devops/data-engineering.md +1 -1
- package/skills/docs/adr-workflow.md +1 -0
- package/skills/docs/backlog-proposal-workflow.md +1 -0
- package/skills/docs/codebase-research-workflow.md +40 -0
- package/skills/docs/customer-profile-workflow.md +1 -0
- package/skills/docs/document-ingest-workflow.md +1 -0
- package/skills/docs/evidence-ingest-workflow.md +1 -0
- package/skills/docs/init-docs.md +2 -2
- package/skills/docs/init-project.md +7 -2
- package/skills/docs/prd-workflow.md +24 -1
- package/skills/docs/prfaq-workflow.md +1 -0
- package/skills/docs/product-intelligence-workflow.md +1 -0
- package/skills/docs/product-signal-workflow.md +1 -0
- package/skills/docs/research-workflow.md +54 -37
- package/skills/docs/runbook-workflow.md +1 -0
- package/skills/docs/strategy-workflow.md +1 -0
- package/skills/docs/user-research-workflow.md +40 -0
- package/skills/operating/orchestration-reference.md +1 -1
- package/skills/roles/architect.md +5 -0
- package/skills/roles/operator.docs.md +4 -0
- package/skills/routing.md +4 -2
- package/specialists/artifact-manifest.json +489 -0
- package/specialists/artifact-manifest.schema.json +83 -0
- package/specialists/audit-enrichments.json +454 -0
- package/specialists/contracts.json +37 -25
- package/specialists/prompts/_shared/validation-contract.md +26 -0
- package/specialists/prompts/cx-accessibility.md +21 -2
- package/specialists/prompts/cx-ai-engineer.md +24 -1
- package/specialists/prompts/cx-architect.md +4 -1
- package/specialists/prompts/cx-business-strategist.md +23 -2
- package/specialists/prompts/cx-data-analyst.md +22 -1
- package/specialists/prompts/cx-data-engineer.md +23 -2
- package/specialists/prompts/cx-debugger.md +21 -2
- package/specialists/prompts/cx-designer.md +26 -3
- package/specialists/prompts/cx-devil-advocate.md +19 -2
- package/specialists/prompts/cx-docs-keeper.md +28 -1
- package/specialists/prompts/cx-engineer.md +22 -1
- package/specialists/prompts/cx-evaluator.md +18 -1
- package/specialists/prompts/cx-explorer.md +21 -2
- package/specialists/prompts/cx-legal-compliance.md +21 -2
- package/specialists/prompts/cx-operations.md +21 -2
- package/specialists/prompts/cx-oracle.md +94 -0
- package/specialists/prompts/cx-orchestrator.md +20 -1
- package/specialists/prompts/cx-platform-engineer.md +25 -2
- package/specialists/prompts/cx-product-manager.md +32 -1
- package/specialists/prompts/cx-qa.md +24 -2
- package/specialists/prompts/cx-rd-lead.md +23 -2
- package/specialists/prompts/cx-release-manager.md +23 -2
- package/specialists/prompts/cx-researcher.md +22 -2
- package/specialists/prompts/cx-reviewer.md +18 -1
- package/specialists/prompts/cx-security.md +22 -2
- package/specialists/prompts/cx-sre.md +30 -3
- package/specialists/prompts/cx-test-automation.md +7 -1
- package/specialists/prompts/cx-trace-reviewer.md +22 -3
- package/specialists/prompts/cx-ux-researcher.md +21 -2
- package/specialists/registry.json +52 -173
- package/specialists/tone-profiles.json +42 -0
- package/templates/demos/playwright/demo-recording.config.mjs +47 -0
- package/templates/demos/recordings/agentic-platforms-prd.json +19 -0
- package/templates/demos/scripts/agentic-platforms-prd.json +44 -0
- package/templates/demos/specs/_helpers/scroll-artifact.ts +89 -0
- package/templates/demos/tapes/agentic-platforms-prd.tape +49 -0
- package/templates/demos/tapes/resource-guard-rails.tape +49 -0
- package/templates/demos/vhs/construct-cockpit.json +24 -0
- package/templates/distribution/construct-brand.typ +446 -0
- package/templates/distribution/construct-decision.typ +38 -0
- package/templates/distribution/construct-deck.html +95 -0
- package/templates/distribution/construct-pdf.typ +38 -0
- package/templates/distribution/construct-prd.typ +38 -0
- package/templates/distribution/construct-reference.docx +0 -0
- package/templates/distribution/construct-research.typ +38 -0
- package/templates/distribution/construct-web.html +92 -0
- package/templates/distribution/fonts/Geist-Bold.ttf +0 -0
- package/templates/distribution/fonts/Geist-Medium.ttf +0 -0
- package/templates/distribution/fonts/Geist-Regular.ttf +0 -0
- package/templates/distribution/fonts/Geist-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-Medium.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-Regular.ttf +0 -0
- package/templates/distribution/fonts/GeistMono-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/IBMPlexMono-Regular.otf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-Medium.ttf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-Regular.ttf +0 -0
- package/templates/distribution/fonts/JetBrainsMono-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Bold.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Medium.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-Regular.ttf +0 -0
- package/templates/distribution/fonts/PlusJakartaSans-SemiBold.ttf +0 -0
- package/templates/distribution/fonts/README.md +36 -0
- package/templates/distribution/fonts/SpaceGrotesk-Variable.ttf +0 -0
- package/templates/distribution/fonts/handwritten/Caveat.ttf +0 -0
- package/templates/distribution/fonts/legacy/IBMPlexMono-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-Medium.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/Inter-SemiBold.otf +0 -0
- package/templates/distribution/fonts/legacy/InterDisplay-SemiBold.otf +0 -0
- package/templates/distribution/fonts/legacy/SourceSerif4-Regular.otf +0 -0
- package/templates/distribution/fonts/legacy/SourceSerif4-Semibold.otf +0 -0
- package/templates/distribution/icons/activity.svg +15 -0
- package/templates/distribution/icons/alert-triangle.svg +17 -0
- package/templates/distribution/icons/book-open.svg +16 -0
- package/templates/distribution/icons/bot.svg +20 -0
- package/templates/distribution/icons/brain.svg +22 -0
- package/templates/distribution/icons/circle-check.svg +16 -0
- package/templates/distribution/icons/clipboard-check.svg +17 -0
- package/templates/distribution/icons/cpu.svg +28 -0
- package/templates/distribution/icons/database.svg +17 -0
- package/templates/distribution/icons/eye.svg +16 -0
- package/templates/distribution/icons/file-text.svg +19 -0
- package/templates/distribution/icons/gauge.svg +16 -0
- package/templates/distribution/icons/git-branch.svg +17 -0
- package/templates/distribution/icons/key.svg +17 -0
- package/templates/distribution/icons/layers.svg +17 -0
- package/templates/distribution/icons/list-checks.svg +19 -0
- package/templates/distribution/icons/lock.svg +16 -0
- package/templates/distribution/icons/message-square.svg +15 -0
- package/templates/distribution/icons/network.svg +19 -0
- package/templates/distribution/icons/route.svg +17 -0
- package/templates/distribution/icons/scale.svg +19 -0
- package/templates/distribution/icons/search.svg +16 -0
- package/templates/distribution/icons/send.svg +16 -0
- package/templates/distribution/icons/server.svg +18 -0
- package/templates/distribution/icons/shield-check.svg +16 -0
- package/templates/distribution/icons/users.svg +18 -0
- package/templates/distribution/icons/webhook.svg +17 -0
- package/templates/distribution/icons/workflow.svg +17 -0
- package/templates/distribution/icons/wrench.svg +15 -0
- package/templates/distribution/run.mjs +18 -1
- package/templates/docs/adr.md +7 -0
- package/templates/docs/construct_guide.md +8 -8
- package/templates/docs/customer-profile.md +4 -0
- package/templates/docs/one-pager.md +4 -0
- package/templates/docs/postmortem.md +31 -0
- package/templates/docs/prd-platform.md +19 -0
- package/templates/docs/prd.md +15 -0
- package/templates/docs/prfaq.md +7 -0
- package/templates/docs/qa-strategy.md +103 -0
- package/templates/docs/research-brief.md +8 -0
- package/templates/docs/rfc-platform.md +12 -0
- 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">·</span> $docId$$endif$$if(version)$ <span class="sep">·</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">·</span> $date$$endif$$if(status)$ <span class="sep">·</span> $status$$endif$</p>$endif$
|
|
89
|
+
<div class="slide-foot"><span><b>Construct</b>$if(classification)$ · $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$
|
|
Binary file
|
|
@@ -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$
|