@litfamily/litgrok 1.0.34 → 1.0.35
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/.grok/hooks/lit-status-line.mjs +59 -1
- package/.grok/rules/00-litgrok.md +4 -6
- package/.grok/skills/browser-drive/scripts/capability-probe.mjs +1 -1
- package/.grok/skills/frontend-ui-ux/SKILL.md +47 -39
- package/.grok/skills/lit-diagram-drawer/SKILL.md +32 -93
- package/.grok/skills/lit-docx/SKILL.md +31 -77
- package/.grok/skills/lit-humanizer/scripts/extract.mjs +1 -1
- package/.grok/skills/lit-humanizer/scripts/test-fixtures.mjs +1 -1
- package/.grok/skills/lit-pptx/SKILL.md +30 -90
- package/.grok/skills/lit-schematic/SKILL.md +30 -115
- package/.grok/skills/lit-scientific-visualization/SKILL.md +36 -221
- package/.grok/skills/lit-typographic-motion/SKILL.md +40 -29
- package/.grok/skills/litgrok/SKILL.md +6 -3
- package/.grok/skills/litwork/SKILL.md +1 -1
- package/.grok/skills/lsp-setup/scripts/lsp-server-table.mjs +1 -1
- package/.grok/skills/readme-studio/SKILL.md +40 -24
- package/.grok/skills/visual-qa/SKILL.md +33 -28
- package/.grok-plugin/plugin.json +1 -1
- package/CHANGELOG.md +12 -1
- package/CONTRIBUTING.md +0 -1
- package/README.md +9 -9
- package/README_ko-KR.md +9 -9
- package/bin/litgrok.mjs +351 -30
- package/docs/assets/readme/README.md +7 -0
- package/docs/assets/readme/badge-version.svg +1 -1
- package/docs/reference.md +11 -5
- package/docs/reference_ko-KR.md +10 -5
- package/package.json +4 -6
- package/plugin.json +1 -1
- package/.grok/skills/frontend-ui-ux/ATTRIBUTION.md +0 -23
- package/.grok/skills/frontend-ui-ux/LICENSE +0 -21
- package/.grok/skills/frontend-ui-ux/PROVENANCE.json +0 -38
- package/.grok/skills/frontend-ui-ux/SOURCE-MANIFEST.json +0 -1060
- package/.grok/skills/frontend-ui-ux/THIRD-PARTY-NOTICE.txt +0 -24
- package/.grok/skills/frontend-ui-ux/data/design-intelligence.json +0 -1
- package/.grok/skills/frontend-ui-ux/examples/phase-3-readme-ab/README.md +0 -9
- package/.grok/skills/frontend-ui-ux/examples/phase-3-readme-motion-ab.md +0 -24
- package/.grok/skills/frontend-ui-ux/kit/components.css +0 -414
- package/.grok/skills/frontend-ui-ux/kit/fonts.json +0 -22
- package/.grok/skills/frontend-ui-ux/kit/layouts/landing.html +0 -164
- package/.grok/skills/frontend-ui-ux/kit/layouts/settings.html +0 -114
- package/.grok/skills/frontend-ui-ux/kit/layouts/workspace.html +0 -115
- package/.grok/skills/frontend-ui-ux/kit/styles/computational-brutalism.json +0 -227
- package/.grok/skills/frontend-ui-ux/kit/styles/field-notes.json +0 -205
- package/.grok/skills/frontend-ui-ux/kit/styles/graphite.json +0 -200
- package/.grok/skills/frontend-ui-ux/kit/styles/meadow.json +0 -199
- package/.grok/skills/frontend-ui-ux/kit/styles/neo-brutalism.json +0 -222
- package/.grok/skills/frontend-ui-ux/kit/ui.js +0 -226
- package/.grok/skills/frontend-ui-ux/references/_canonical-corpus/legal/frontend-ATTRIBUTION.md +0 -217
- package/.grok/skills/frontend-ui-ux/references/_canonical-corpus/legal/frontend-LICENSE-Apache-2.0.txt +0 -201
- package/.grok/skills/frontend-ui-ux/references/_canonical-corpus/legal/root-LICENSE +0 -21
- package/.grok/skills/frontend-ui-ux/references/_canonical-corpus/manifest.json +0 -873
- package/.grok/skills/frontend-ui-ux/references/adaptive-layout.md +0 -88
- package/.grok/skills/frontend-ui-ux/references/brand-and-imagery.md +0 -87
- package/.grok/skills/frontend-ui-ux/references/complete-contract.md +0 -572
- package/.grok/skills/frontend-ui-ux/references/composition.md +0 -79
- package/.grok/skills/frontend-ui-ux/references/craft-floor.md +0 -262
- package/.grok/skills/frontend-ui-ux/references/creative-directions.md +0 -74
- package/.grok/skills/frontend-ui-ux/references/default-editorial-pixel.json +0 -28
- package/.grok/skills/frontend-ui-ux/references/design/README.md +0 -248
- package/.grok/skills/frontend-ui-ux/references/design/_INDEX.md +0 -191
- package/.grok/skills/frontend-ui-ux/references/design/airbnb.md +0 -393
- package/.grok/skills/frontend-ui-ux/references/design/airtable.md +0 -92
- package/.grok/skills/frontend-ui-ux/references/design/apple.md +0 -250
- package/.grok/skills/frontend-ui-ux/references/design/aside.md +0 -209
- package/.grok/skills/frontend-ui-ux/references/design/binance.md +0 -348
- package/.grok/skills/frontend-ui-ux/references/design/bmw.md +0 -183
- package/.grok/skills/frontend-ui-ux/references/design/brutalist-skill.md +0 -92
- package/.grok/skills/frontend-ui-ux/references/design/bugatti.md +0 -271
- package/.grok/skills/frontend-ui-ux/references/design/cal.md +0 -262
- package/.grok/skills/frontend-ui-ux/references/design/claude.md +0 -315
- package/.grok/skills/frontend-ui-ux/references/design/clay.md +0 -307
- package/.grok/skills/frontend-ui-ux/references/design/clickhouse.md +0 -284
- package/.grok/skills/frontend-ui-ux/references/design/clone-from-url.md +0 -65
- package/.grok/skills/frontend-ui-ux/references/design/cohere.md +0 -269
- package/.grok/skills/frontend-ui-ux/references/design/coinbase.md +0 -132
- package/.grok/skills/frontend-ui-ux/references/design/composio.md +0 -310
- package/.grok/skills/frontend-ui-ux/references/design/cursor.md +0 -312
- package/.grok/skills/frontend-ui-ux/references/design/design-system-architecture.md +0 -244
- package/.grok/skills/frontend-ui-ux/references/design/elevenlabs.md +0 -268
- package/.grok/skills/frontend-ui-ux/references/design/expo.md +0 -284
- package/.grok/skills/frontend-ui-ux/references/design/ferrari.md +0 -317
- package/.grok/skills/frontend-ui-ux/references/design/figma.md +0 -223
- package/.grok/skills/frontend-ui-ux/references/design/framer.md +0 -249
- package/.grok/skills/frontend-ui-ux/references/design/gpt-tasteskill.md +0 -74
- package/.grok/skills/frontend-ui-ux/references/design/hashicorp.md +0 -281
- package/.grok/skills/frontend-ui-ux/references/design/ibm.md +0 -335
- package/.grok/skills/frontend-ui-ux/references/design/image-to-code-skill.md +0 -1228
- package/.grok/skills/frontend-ui-ux/references/design/imagegen-brandkit.md +0 -798
- package/.grok/skills/frontend-ui-ux/references/design/imagegen-frontend-mobile.md +0 -1465
- package/.grok/skills/frontend-ui-ux/references/design/imagegen-frontend-web.md +0 -987
- package/.grok/skills/frontend-ui-ux/references/design/intercom.md +0 -149
- package/.grok/skills/frontend-ui-ux/references/design/kraken.md +0 -128
- package/.grok/skills/frontend-ui-ux/references/design/lamborghini.md +0 -291
- package/.grok/skills/frontend-ui-ux/references/design/layout-skill.md +0 -107
- package/.grok/skills/frontend-ui-ux/references/design/lazyweb.md +0 -77
- package/.grok/skills/frontend-ui-ux/references/design/linear.app.md +0 -370
- package/.grok/skills/frontend-ui-ux/references/design/lovable.md +0 -301
- package/.grok/skills/frontend-ui-ux/references/design/mastercard.md +0 -368
- package/.grok/skills/frontend-ui-ux/references/design/meta.md +0 -369
- package/.grok/skills/frontend-ui-ux/references/design/minimalist-skill.md +0 -85
- package/.grok/skills/frontend-ui-ux/references/design/minimax.md +0 -260
- package/.grok/skills/frontend-ui-ux/references/design/mintlify.md +0 -329
- package/.grok/skills/frontend-ui-ux/references/design/miro.md +0 -111
- package/.grok/skills/frontend-ui-ux/references/design/mistral.ai.md +0 -264
- package/.grok/skills/frontend-ui-ux/references/design/mongodb.md +0 -269
- package/.grok/skills/frontend-ui-ux/references/design/nike.md +0 -366
- package/.grok/skills/frontend-ui-ux/references/design/notion.md +0 -312
- package/.grok/skills/frontend-ui-ux/references/design/nvidia.md +0 -296
- package/.grok/skills/frontend-ui-ux/references/design/ollama.md +0 -270
- package/.grok/skills/frontend-ui-ux/references/design/opencode.ai.md +0 -284
- package/.grok/skills/frontend-ui-ux/references/design/output-skill.md +0 -49
- package/.grok/skills/frontend-ui-ux/references/design/pinterest.md +0 -233
- package/.grok/skills/frontend-ui-ux/references/design/playstation.md +0 -367
- package/.grok/skills/frontend-ui-ux/references/design/posthog.md +0 -259
- package/.grok/skills/frontend-ui-ux/references/design/raycast.md +0 -271
- package/.grok/skills/frontend-ui-ux/references/design/react-dev-tooling-skill.md +0 -230
- package/.grok/skills/frontend-ui-ux/references/design/redesign-skill.md +0 -178
- package/.grok/skills/frontend-ui-ux/references/design/renault.md +0 -314
- package/.grok/skills/frontend-ui-ux/references/design/replicate.md +0 -264
- package/.grok/skills/frontend-ui-ux/references/design/resend.md +0 -306
- package/.grok/skills/frontend-ui-ux/references/design/revolut.md +0 -188
- package/.grok/skills/frontend-ui-ux/references/design/runwayml.md +0 -247
- package/.grok/skills/frontend-ui-ux/references/design/sanity.md +0 -360
- package/.grok/skills/frontend-ui-ux/references/design/sentry.md +0 -265
- package/.grok/skills/frontend-ui-ux/references/design/shopify.md +0 -353
- package/.grok/skills/frontend-ui-ux/references/design/soft-skill.md +0 -98
- package/.grok/skills/frontend-ui-ux/references/design/spacex.md +0 -197
- package/.grok/skills/frontend-ui-ux/references/design/spotify.md +0 -249
- package/.grok/skills/frontend-ui-ux/references/design/starbucks.md +0 -583
- package/.grok/skills/frontend-ui-ux/references/design/stitch-design-example.md +0 -121
- package/.grok/skills/frontend-ui-ux/references/design/stitch-skill.md +0 -184
- package/.grok/skills/frontend-ui-ux/references/design/stripe.md +0 -325
- package/.grok/skills/frontend-ui-ux/references/design/supabase.md +0 -258
- package/.grok/skills/frontend-ui-ux/references/design/superhuman.md +0 -255
- package/.grok/skills/frontend-ui-ux/references/design/taste-skill.md +0 -1206
- package/.grok/skills/frontend-ui-ux/references/design/tesla.md +0 -289
- package/.grok/skills/frontend-ui-ux/references/design/theverge.md +0 -342
- package/.grok/skills/frontend-ui-ux/references/design/together.ai.md +0 -266
- package/.grok/skills/frontend-ui-ux/references/design/uber.md +0 -298
- package/.grok/skills/frontend-ui-ux/references/design/vercel.md +0 -313
- package/.grok/skills/frontend-ui-ux/references/design/vodafone.md +0 -426
- package/.grok/skills/frontend-ui-ux/references/design/voltagent.md +0 -326
- package/.grok/skills/frontend-ui-ux/references/design/warp.md +0 -256
- package/.grok/skills/frontend-ui-ux/references/design/webflow.md +0 -95
- package/.grok/skills/frontend-ui-ux/references/design/wired.md +0 -281
- package/.grok/skills/frontend-ui-ux/references/design/wise.md +0 -176
- package/.grok/skills/frontend-ui-ux/references/design/x.ai.md +0 -260
- package/.grok/skills/frontend-ui-ux/references/design/zapier.md +0 -331
- package/.grok/skills/frontend-ui-ux/references/designpowers/EVIDENCE.md +0 -97
- package/.grok/skills/frontend-ui-ux/references/designpowers/README.md +0 -48
- package/.grok/skills/frontend-ui-ux/references/designpowers/UPSTREAM.md +0 -80
- package/.grok/skills/frontend-ui-ux/references/designpowers/lane-a-direction.md +0 -64
- package/.grok/skills/frontend-ui-ux/references/designpowers/lane-b-execution.md +0 -65
- package/.grok/skills/frontend-ui-ux/references/designpowers/lane-c-review.md +0 -65
- package/.grok/skills/frontend-ui-ux/references/designpowers/lane-d-memory.md +0 -83
- package/.grok/skills/frontend-ui-ux/references/designpowers/orchestration.md +0 -80
- package/.grok/skills/frontend-ui-ux/references/designpowers/routing.md +0 -79
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/LICENSE +0 -21
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/agents/accessibility-reviewer.md +0 -83
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/agents/content-writer.md +0 -132
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/agents/design-builder.md +0 -109
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/agents/design-critic.md +0 -89
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/agents/design-lead.md +0 -113
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/agents/design-scout.md +0 -78
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/agents/design-strategist.md +0 -121
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/agents/heuristic-evaluator.md +0 -268
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/agents/inspiration-scout.md +0 -107
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/agents/motion-designer.md +0 -120
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/skills/accessible-content/reference.md +0 -101
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/skills/adaptive-interfaces/reference.md +0 -109
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/skills/cognitive-accessibility/reference.md +0 -107
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/skills/design-debate/reference.md +0 -199
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/skills/design-debt-tracker/reference.md +0 -174
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/skills/design-handoff/reference.md +0 -125
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/skills/design-md/reference.md +0 -106
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/skills/design-retrospective/reference.md +0 -266
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/skills/design-review/reference.md +0 -123
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/skills/design-system-alignment/reference.md +0 -120
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/skills/designpowers-critique/reference.md +0 -164
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/skills/heuristic-evaluation/reference.md +0 -85
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/skills/inclusive-personas/reference.md +0 -98
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/skills/inspiration-scouting/reference.md +0 -165
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/skills/interaction-design/reference.md +0 -122
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/skills/motion-choreography/reference.md +0 -81
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/skills/research-planning/reference.md +0 -96
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/skills/responsive-patterns/reference.md +0 -77
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/skills/synthetic-user-testing/reference.md +0 -192
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/skills/taste-feedback/reference.md +0 -165
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/skills/taste-report/reference.md +0 -78
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/skills/token-architecture/reference.md +0 -75
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/skills/ui-composition/reference.md +0 -117
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/skills/usability-testing/reference.md +0 -78
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/skills/verification-before-shipping/reference.md +0 -125
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/skills/voice-and-tone/reference.md +0 -79
- package/.grok/skills/frontend-ui-ux/references/designpowers/vendor/skills/writing-design-plans/reference.md +0 -119
- package/.grok/skills/frontend-ui-ux/references/evidence-review.md +0 -126
- package/.grok/skills/frontend-ui-ux/references/house-styles.md +0 -234
- package/.grok/skills/frontend-ui-ux/references/implementation-platforms.md +0 -103
- package/.grok/skills/frontend-ui-ux/references/inclusive-interface.md +0 -88
- package/.grok/skills/frontend-ui-ux/references/interaction-motion.md +0 -47
- package/.grok/skills/frontend-ui-ux/references/motion-guide.md +0 -130
- package/.grok/skills/frontend-ui-ux/references/operating-lanes.md +0 -92
- package/.grok/skills/frontend-ui-ux/references/perfection/README.md +0 -160
- package/.grok/skills/frontend-ui-ux/references/perfection/react-perf-tooling.md +0 -127
- package/.grok/skills/frontend-ui-ux/references/performance-delivery.md +0 -85
- package/.grok/skills/frontend-ui-ux/references/probe-loop.md +0 -27
- package/.grok/skills/frontend-ui-ux/references/product-direction.md +0 -80
- package/.grok/skills/frontend-ui-ux/references/production-interview.md +0 -17
- package/.grok/skills/frontend-ui-ux/references/production.md +0 -27
- package/.grok/skills/frontend-ui-ux/references/redesign-playbook.md +0 -91
- package/.grok/skills/frontend-ui-ux/references/slop-register.md +0 -150
- package/.grok/skills/frontend-ui-ux/references/system-foundations.md +0 -80
- package/.grok/skills/frontend-ui-ux/references/taste-direction.md +0 -86
- package/.grok/skills/frontend-ui-ux/references/ui-ux-db/README.md +0 -659
- package/.grok/skills/frontend-ui-ux/references/ui-ux-db/data/charts.csv +0 -26
- package/.grok/skills/frontend-ui-ux/references/ui-ux-db/data/colors.csv +0 -162
- package/.grok/skills/frontend-ui-ux/references/ui-ux-db/data/icons.csv +0 -106
- package/.grok/skills/frontend-ui-ux/references/ui-ux-db/data/landing.csv +0 -35
- package/.grok/skills/frontend-ui-ux/references/ui-ux-db/data/products.csv +0 -162
- package/.grok/skills/frontend-ui-ux/references/ui-ux-db/data/react-performance.csv +0 -45
- package/.grok/skills/frontend-ui-ux/references/ui-ux-db/data/stacks/astro.csv +0 -54
- package/.grok/skills/frontend-ui-ux/references/ui-ux-db/data/stacks/flutter.csv +0 -53
- package/.grok/skills/frontend-ui-ux/references/ui-ux-db/data/stacks/html-tailwind.csv +0 -56
- package/.grok/skills/frontend-ui-ux/references/ui-ux-db/data/stacks/jetpack-compose.csv +0 -53
- package/.grok/skills/frontend-ui-ux/references/ui-ux-db/data/stacks/nextjs.csv +0 -53
- package/.grok/skills/frontend-ui-ux/references/ui-ux-db/data/stacks/nuxt-ui.csv +0 -51
- package/.grok/skills/frontend-ui-ux/references/ui-ux-db/data/stacks/nuxtjs.csv +0 -59
- package/.grok/skills/frontend-ui-ux/references/ui-ux-db/data/stacks/react-native.csv +0 -52
- package/.grok/skills/frontend-ui-ux/references/ui-ux-db/data/stacks/react.csv +0 -54
- package/.grok/skills/frontend-ui-ux/references/ui-ux-db/data/stacks/shadcn.csv +0 -61
- package/.grok/skills/frontend-ui-ux/references/ui-ux-db/data/stacks/svelte.csv +0 -54
- package/.grok/skills/frontend-ui-ux/references/ui-ux-db/data/stacks/swiftui.csv +0 -51
- package/.grok/skills/frontend-ui-ux/references/ui-ux-db/data/stacks/vue.csv +0 -50
- package/.grok/skills/frontend-ui-ux/references/ui-ux-db/data/styles.csv +0 -85
- package/.grok/skills/frontend-ui-ux/references/ui-ux-db/data/typography.csv +0 -74
- package/.grok/skills/frontend-ui-ux/references/ui-ux-db/data/ui-reasoning.csv +0 -162
- package/.grok/skills/frontend-ui-ux/references/ui-ux-db/data/ux-guidelines.csv +0 -100
- package/.grok/skills/frontend-ui-ux/references/ui-ux-db/data/web-interface.csv +0 -31
- package/.grok/skills/frontend-ui-ux/references/ui-ux-db/scripts/core.py +0 -262
- package/.grok/skills/frontend-ui-ux/references/ui-ux-db/scripts/design_system.py +0 -1148
- package/.grok/skills/frontend-ui-ux/references/ui-ux-db/scripts/search.py +0 -114
- package/.grok/skills/frontend-ui-ux/references/visual-language.md +0 -80
- package/.grok/skills/frontend-ui-ux/references/visual-reconstruction.md +0 -51
- package/.grok/skills/frontend-ui-ux/schemas/design-contract-v1alpha1.schema.json +0 -306
- package/.grok/skills/frontend-ui-ux/schemas/design-contract-v1beta1.schema.json +0 -291
- package/.grok/skills/frontend-ui-ux/schemas/design-contract-v1beta2.schema.json +0 -311
- package/.grok/skills/frontend-ui-ux/scripts/design-contract-format.mjs +0 -286
- package/.grok/skills/frontend-ui-ux/scripts/design-contract-inventory-rules.mjs +0 -354
- package/.grok/skills/frontend-ui-ux/scripts/design-contract-rules.mjs +0 -361
- package/.grok/skills/frontend-ui-ux/scripts/design-contract-surface-rules.mjs +0 -313
- package/.grok/skills/frontend-ui-ux/scripts/design-data.mjs +0 -68
- package/.grok/skills/frontend-ui-ux/scripts/design-lock.mjs +0 -400
- package/.grok/skills/frontend-ui-ux/scripts/errors.mjs +0 -12
- package/.grok/skills/frontend-ui-ux/scripts/import-design-intelligence.mjs +0 -124
- package/.grok/skills/frontend-ui-ux/scripts/interface-probe-driver.mjs +0 -33
- package/.grok/skills/frontend-ui-ux/scripts/interface-probe-lock.mjs +0 -196
- package/.grok/skills/frontend-ui-ux/scripts/interface-probe-page.mjs +0 -1555
- package/.grok/skills/frontend-ui-ux/scripts/interface-probe-rules.mjs +0 -153
- package/.grok/skills/frontend-ui-ux/scripts/interface-probe-server.mjs +0 -57
- package/.grok/skills/frontend-ui-ux/scripts/interface-probe-static.mjs +0 -139
- package/.grok/skills/frontend-ui-ux/scripts/interface-probe.mjs +0 -363
- package/.grok/skills/frontend-ui-ux/scripts/json-boundary.mjs +0 -63
- package/.grok/skills/frontend-ui-ux/scripts/pixel-art.mjs +0 -60
- package/.grok/skills/frontend-ui-ux/scripts/query-design-intelligence.mjs +0 -106
- package/.grok/skills/frontend-ui-ux/scripts/search.mjs +0 -43
- package/.grok/skills/frontend-ui-ux/scripts/source-replay.mjs +0 -153
- package/.grok/skills/frontend-ui-ux/scripts/strict-json.mjs +0 -143
- package/.grok/skills/frontend-ui-ux/scripts/validate-design-contract.mjs +0 -52
- package/.grok/skills/frontend-ui-ux/scripts/verify-canonical-corpus.mjs +0 -214
- package/.grok/skills/lit-diagram-drawer/NOTICE +0 -33
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-architecture-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-architecture-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-architecture-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-bar-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-bar-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-bar-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-beeswarm-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-beeswarm-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-beeswarm-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-bubble-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-bubble-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-bubble-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-bump-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-bump-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-bump-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-data-flow-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-data-flow-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-data-flow-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-datalake-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-datalake-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-datalake-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-db-schema-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-db-schema-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-db-schema-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-dependency-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-dependency-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-dependency-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-deployment-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-deployment-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-deployment-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-dp-integration-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-dp-integration-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-dp-integration-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-dp-security-matrix-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-dp-security-matrix-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-dp-security-matrix-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-er-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-er-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-er-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-fishbone-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-fishbone-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-fishbone-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-flowchart-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-flowchart-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-flowchart-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-gantt-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-gantt-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-gantt-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-heatmap-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-heatmap-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-heatmap-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-high-level-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-high-level-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-high-level-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-high-level-vertical-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-high-level-vertical-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-high-level-vertical-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-import-drawio-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-import-drawio-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-import-drawio-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-import-excalidraw-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-import-excalidraw-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-import-excalidraw-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-import-mermaid-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-import-mermaid-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-import-mermaid-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-it-state-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-it-state-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-it-state-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-journey-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-journey-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-journey-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-kanban-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-kanban-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-kanban-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-layers-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-layers-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-layers-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-line-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-line-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-line-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-loop-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-loop-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-loop-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-loop-terminal-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-loop-terminal-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-loop-terminal-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-marimekko-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-marimekko-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-marimekko-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-medallion-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-medallion-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-medallion-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-nested-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-nested-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-nested-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-org-chart-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-org-chart-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-org-chart-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-paved-road-animated-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-paved-road-animated-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-paved-road-animated-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-polar-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-polar-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-polar-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-policy-trace-animated-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-policy-trace-animated-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-policy-trace-animated-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-process-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-process-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-process-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-pyramid-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-pyramid-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-pyramid-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-quadrant-consultant-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-quadrant-consultant-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-quadrant-consultant-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-quadrant-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-quadrant-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-quadrant-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-queue-animated-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-queue-animated-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-queue-animated-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-radar-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-radar-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-radar-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-ridgeline-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-ridgeline-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-ridgeline-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-sankey-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-sankey-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-sankey-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-scatter-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-scatter-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-scatter-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-sequence-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-sequence-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-sequence-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-sequence-oauth-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-sequence-oauth-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-sequence-oauth-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-slopegraph-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-slopegraph-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-slopegraph-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-state-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-state-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-state-lifecycle-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-state-lifecycle-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-state-lifecycle-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-state-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-story-map-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-story-map-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-story-map-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-streamgraph-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-streamgraph-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-streamgraph-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-swimlane-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-swimlane-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-swimlane-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-timeline-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-timeline-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-timeline-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-tree-block-decomposition-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-tree-block-decomposition-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-tree-block-decomposition-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-tree-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-tree-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-tree-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-treemap-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-treemap-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-treemap-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-uml-class-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-uml-class-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-uml-class-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-venn-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-venn-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-venn-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-wardley-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-wardley-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-wardley-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-waterfall-dark.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-waterfall-full.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/examples/type-waterfall-light.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/fonts/OFL.txt +0 -94
- package/.grok/skills/lit-diagram-drawer/assets/fonts/PretendardVariable.woff2 +0 -0
- package/.grok/skills/lit-diagram-drawer/assets/fonts/provenance.json +0 -22
- package/.grok/skills/lit-diagram-drawer/assets/icons.html +0 -231
- package/.grok/skills/lit-diagram-drawer/assets/index.html +0 -1
- package/.grok/skills/lit-diagram-drawer/assets/licenses/devicon-MIT.txt +0 -20
- package/.grok/skills/lit-diagram-drawer/assets/licenses/diagram-design-MIT.txt +0 -21
- package/.grok/skills/lit-diagram-drawer/assets/licenses/log-z-MIT.txt +0 -21
- package/.grok/skills/lit-diagram-drawer/assets/licenses/simple-icons-CC0.md +0 -30
- package/.grok/skills/lit-diagram-drawer/assets/licenses/tabler-MIT.txt +0 -21
- package/.grok/skills/lit-diagram-drawer/examples/01-service-architecture/after.html +0 -1
- package/.grok/skills/lit-diagram-drawer/examples/01-service-architecture/brief.md +0 -33
- package/.grok/skills/lit-diagram-drawer/examples/01-service-architecture/constructed-naive-foil.html +0 -1
- package/.grok/skills/lit-diagram-drawer/examples/02-release-decision/after.html +0 -1
- package/.grok/skills/lit-diagram-drawer/examples/02-release-decision/brief.md +0 -18
- package/.grok/skills/lit-diagram-drawer/examples/02-release-decision/constructed-naive-foil.html +0 -1
- package/.grok/skills/lit-diagram-drawer/examples/03-data-retention/after.html +0 -1
- package/.grok/skills/lit-diagram-drawer/examples/03-data-retention/brief.md +0 -19
- package/.grok/skills/lit-diagram-drawer/examples/03-data-retention/constructed-naive-foil.html +0 -1
- package/.grok/skills/lit-diagram-drawer/examples/04-oauth-sequence/after.html +0 -1
- package/.grok/skills/lit-diagram-drawer/examples/04-oauth-sequence/brief.md +0 -17
- package/.grok/skills/lit-diagram-drawer/examples/04-oauth-sequence/constructed-naive-foil.html +0 -1
- package/.grok/skills/lit-diagram-drawer/examples/05-review-lanes/after.html +0 -1
- package/.grok/skills/lit-diagram-drawer/examples/05-review-lanes/brief.md +0 -17
- package/.grok/skills/lit-diagram-drawer/examples/05-review-lanes/constructed-naive-foil.html +0 -1
- package/.grok/skills/lit-diagram-drawer/examples/06-rollout-gates/after.html +0 -1
- package/.grok/skills/lit-diagram-drawer/examples/06-rollout-gates/brief.md +0 -17
- package/.grok/skills/lit-diagram-drawer/examples/06-rollout-gates/constructed-naive-foil.html +0 -1
- package/.grok/skills/lit-diagram-drawer/examples/07-deployment-boundary/after.html +0 -1
- package/.grok/skills/lit-diagram-drawer/examples/07-deployment-boundary/brief.md +0 -19
- package/.grok/skills/lit-diagram-drawer/examples/07-deployment-boundary/constructed-naive-foil.html +0 -1
- package/.grok/skills/lit-diagram-drawer/examples/08-policy-traces/after.html +0 -1
- package/.grok/skills/lit-diagram-drawer/examples/08-policy-traces/brief.md +0 -20
- package/.grok/skills/lit-diagram-drawer/examples/08-policy-traces/constructed-naive-foil.html +0 -1
- package/.grok/skills/lit-diagram-drawer/references/accessibility.md +0 -37
- package/.grok/skills/lit-diagram-drawer/references/export.md +0 -29
- package/.grok/skills/lit-diagram-drawer/references/import-drawio.md +0 -21
- package/.grok/skills/lit-diagram-drawer/references/import-excalidraw.md +0 -14
- package/.grok/skills/lit-diagram-drawer/references/import-mermaid.md +0 -18
- package/.grok/skills/lit-diagram-drawer/references/import-schema.md +0 -28
- package/.grok/skills/lit-diagram-drawer/references/korean-typography.md +0 -34
- package/.grok/skills/lit-diagram-drawer/references/motion.md +0 -29
- package/.grok/skills/lit-diagram-drawer/references/office-pptx-docx.md +0 -42
- package/.grok/skills/lit-diagram-drawer/references/output-spec.md +0 -48
- package/.grok/skills/lit-diagram-drawer/references/primitive-annotation.md +0 -12
- package/.grok/skills/lit-diagram-drawer/references/primitive-icons.md +0 -21
- package/.grok/skills/lit-diagram-drawer/references/primitive-sketchy.md +0 -11
- package/.grok/skills/lit-diagram-drawer/references/primitive-terminal.md +0 -11
- package/.grok/skills/lit-diagram-drawer/references/profiles.md +0 -24
- package/.grok/skills/lit-diagram-drawer/references/semantic-patterns.md +0 -27
- package/.grok/skills/lit-diagram-drawer/references/style-guide.md +0 -76
- package/.grok/skills/lit-diagram-drawer/references/type-architecture.md +0 -51
- package/.grok/skills/lit-diagram-drawer/references/type-bar.md +0 -51
- package/.grok/skills/lit-diagram-drawer/references/type-beeswarm.md +0 -49
- package/.grok/skills/lit-diagram-drawer/references/type-bubble.md +0 -49
- package/.grok/skills/lit-diagram-drawer/references/type-bump.md +0 -49
- package/.grok/skills/lit-diagram-drawer/references/type-catalog.json +0 -1433
- package/.grok/skills/lit-diagram-drawer/references/type-data-flow.md +0 -51
- package/.grok/skills/lit-diagram-drawer/references/type-datalake.md +0 -49
- package/.grok/skills/lit-diagram-drawer/references/type-db-schema.md +0 -49
- package/.grok/skills/lit-diagram-drawer/references/type-dependency.md +0 -49
- package/.grok/skills/lit-diagram-drawer/references/type-deployment.md +0 -49
- package/.grok/skills/lit-diagram-drawer/references/type-dp-integration.md +0 -49
- package/.grok/skills/lit-diagram-drawer/references/type-dp-security-matrix.md +0 -49
- package/.grok/skills/lit-diagram-drawer/references/type-er.md +0 -49
- package/.grok/skills/lit-diagram-drawer/references/type-fishbone.md +0 -49
- package/.grok/skills/lit-diagram-drawer/references/type-flowchart.md +0 -49
- package/.grok/skills/lit-diagram-drawer/references/type-gantt.md +0 -49
- package/.grok/skills/lit-diagram-drawer/references/type-heatmap.md +0 -53
- package/.grok/skills/lit-diagram-drawer/references/type-high-level-vertical.md +0 -53
- package/.grok/skills/lit-diagram-drawer/references/type-high-level.md +0 -53
- package/.grok/skills/lit-diagram-drawer/references/type-import-drawio.md +0 -53
- package/.grok/skills/lit-diagram-drawer/references/type-import-excalidraw.md +0 -53
- package/.grok/skills/lit-diagram-drawer/references/type-import-mermaid.md +0 -53
- package/.grok/skills/lit-diagram-drawer/references/type-it-state.md +0 -54
- package/.grok/skills/lit-diagram-drawer/references/type-journey.md +0 -54
- package/.grok/skills/lit-diagram-drawer/references/type-kanban.md +0 -54
- package/.grok/skills/lit-diagram-drawer/references/type-layers.md +0 -54
- package/.grok/skills/lit-diagram-drawer/references/type-line.md +0 -54
- package/.grok/skills/lit-diagram-drawer/references/type-loop-terminal.md +0 -53
- package/.grok/skills/lit-diagram-drawer/references/type-loop.md +0 -54
- package/.grok/skills/lit-diagram-drawer/references/type-marimekko.md +0 -54
- package/.grok/skills/lit-diagram-drawer/references/type-medallion.md +0 -54
- package/.grok/skills/lit-diagram-drawer/references/type-nested.md +0 -59
- package/.grok/skills/lit-diagram-drawer/references/type-org-chart.md +0 -58
- package/.grok/skills/lit-diagram-drawer/references/type-paved-road-animated.md +0 -72
- package/.grok/skills/lit-diagram-drawer/references/type-polar.md +0 -67
- package/.grok/skills/lit-diagram-drawer/references/type-policy-trace-animated.md +0 -69
- package/.grok/skills/lit-diagram-drawer/references/type-process.md +0 -58
- package/.grok/skills/lit-diagram-drawer/references/type-pyramid.md +0 -59
- package/.grok/skills/lit-diagram-drawer/references/type-quadrant-consultant.md +0 -60
- package/.grok/skills/lit-diagram-drawer/references/type-quadrant.md +0 -57
- package/.grok/skills/lit-diagram-drawer/references/type-queue-animated.md +0 -63
- package/.grok/skills/lit-diagram-drawer/references/type-radar.md +0 -57
- package/.grok/skills/lit-diagram-drawer/references/type-ridgeline.md +0 -63
- package/.grok/skills/lit-diagram-drawer/references/type-sankey.md +0 -68
- package/.grok/skills/lit-diagram-drawer/references/type-scatter.md +0 -60
- package/.grok/skills/lit-diagram-drawer/references/type-sequence-oauth.md +0 -66
- package/.grok/skills/lit-diagram-drawer/references/type-sequence.md +0 -63
- package/.grok/skills/lit-diagram-drawer/references/type-slopegraph.md +0 -65
- package/.grok/skills/lit-diagram-drawer/references/type-state-lifecycle.md +0 -53
- package/.grok/skills/lit-diagram-drawer/references/type-state.md +0 -59
- package/.grok/skills/lit-diagram-drawer/references/type-story-map.md +0 -52
- package/.grok/skills/lit-diagram-drawer/references/type-streamgraph.md +0 -53
- package/.grok/skills/lit-diagram-drawer/references/type-swimlane.md +0 -52
- package/.grok/skills/lit-diagram-drawer/references/type-timeline.md +0 -52
- package/.grok/skills/lit-diagram-drawer/references/type-tree-block-decomposition.md +0 -57
- package/.grok/skills/lit-diagram-drawer/references/type-tree.md +0 -54
- package/.grok/skills/lit-diagram-drawer/references/type-treemap.md +0 -54
- package/.grok/skills/lit-diagram-drawer/references/type-uml-class.md +0 -52
- package/.grok/skills/lit-diagram-drawer/references/type-venn.md +0 -51
- package/.grok/skills/lit-diagram-drawer/references/type-wardley.md +0 -52
- package/.grok/skills/lit-diagram-drawer/references/type-waterfall.md +0 -54
- package/.grok/skills/lit-diagram-drawer/references/verifier-guide.md +0 -64
- package/.grok/skills/lit-diagram-drawer/scripts/block-registry.mjs +0 -43
- package/.grok/skills/lit-diagram-drawer/scripts/brief-contract.mjs +0 -94
- package/.grok/skills/lit-diagram-drawer/scripts/check-visible-text.mjs +0 -15
- package/.grok/skills/lit-diagram-drawer/scripts/diagram-metrics.mjs +0 -219
- package/.grok/skills/lit-diagram-drawer/scripts/doctor.mjs +0 -22
- package/.grok/skills/lit-diagram-drawer/scripts/drawio-extract.mjs +0 -359
- package/.grok/skills/lit-diagram-drawer/scripts/excalidraw-extract.mjs +0 -196
- package/.grok/skills/lit-diagram-drawer/scripts/export.mjs +0 -117
- package/.grok/skills/lit-diagram-drawer/scripts/import-common.mjs +0 -68
- package/.grok/skills/lit-diagram-drawer/scripts/mermaid-extract.mjs +0 -232
- package/.grok/skills/lit-diagram-drawer/scripts/office-safety.mjs +0 -54
- package/.grok/skills/lit-diagram-drawer/scripts/renderer-status.mjs +0 -43
- package/.grok/skills/lit-diagram-drawer/scripts/verify-all.mjs +0 -73
- package/.grok/skills/lit-diagram-drawer/scripts/verify-brief.mjs +0 -16
- package/.grok/skills/lit-diagram-drawer/scripts/verify-coverage.mjs +0 -56
- package/.grok/skills/lit-diagram-drawer/scripts/verify-diagram.mjs +0 -22
- package/.grok/skills/lit-diagram-drawer/scripts/verify-motion.mjs +0 -12
- package/.grok/skills/lit-diagram-drawer/scripts/verify-type.mjs +0 -48
- package/.grok/skills/lit-diagram-drawer/scripts/visual-quality.mjs +0 -344
- package/.grok/skills/lit-docx/LICENSE +0 -21
- package/.grok/skills/lit-docx/NOTICE.md +0 -1
- package/.grok/skills/lit-docx/examples/01-report-ko.md +0 -137
- package/.grok/skills/lit-docx/examples/02-brief-ko.md +0 -82
- package/.grok/skills/lit-docx/examples/03-manual-ko.md +0 -118
- package/.grok/skills/lit-docx/examples/04-proposal-ko.md +0 -92
- package/.grok/skills/lit-docx/examples/05-memo-en.md +0 -42
- package/.grok/skills/lit-docx/examples/06-journal-en.md +0 -90
- package/.grok/skills/lit-docx/examples/07-report-en.md +0 -103
- package/.grok/skills/lit-docx/examples/08-proposal-en.md +0 -90
- package/.grok/skills/lit-docx/examples/09-memo-ko.md +0 -41
- package/.grok/skills/lit-docx/references/components.md +0 -151
- package/.grok/skills/lit-docx/references/direction-step.md +0 -129
- package/.grok/skills/lit-docx/references/frontmatter_schema.md +0 -42
- package/.grok/skills/lit-docx/references/journal_style_spec.md +0 -17
- package/.grok/skills/lit-docx/references/markdown_quality_checklist.md +0 -37
- package/.grok/skills/lit-docx/references/page-composition.md +0 -129
- package/.grok/skills/lit-docx/references/slop_phrase_list.yaml +0 -58
- package/.grok/skills/lit-docx/references/slop_rules.md +0 -35
- package/.grok/skills/lit-docx/references/tonalities/brief.md +0 -98
- package/.grok/skills/lit-docx/references/tonalities/journal.md +0 -100
- package/.grok/skills/lit-docx/references/tonalities/manual.md +0 -107
- package/.grok/skills/lit-docx/references/tonalities/memo.md +0 -95
- package/.grok/skills/lit-docx/references/tonalities/proposal.md +0 -104
- package/.grok/skills/lit-docx/references/tonalities/report.md +0 -114
- package/.grok/skills/lit-docx/requirements.lock +0 -12
- package/.grok/skills/lit-docx/scripts/clean_markdown.py +0 -95
- package/.grok/skills/lit-docx/scripts/convert_md_to_docx.py +0 -1377
- package/.grok/skills/lit-docx/scripts/convert_md_to_pdf.py +0 -284
- package/.grok/skills/lit-docx/scripts/convert_pdf.py +0 -130
- package/.grok/skills/lit-docx/scripts/docx_design.py +0 -2446
- package/.grok/skills/lit-docx/scripts/docx_gate.py +0 -206
- package/.grok/skills/lit-docx/scripts/docx_layout.py +0 -959
- package/.grok/skills/lit-docx/scripts/edit_docx.py +0 -297
- package/.grok/skills/lit-docx/scripts/embed_images.py +0 -78
- package/.grok/skills/lit-docx/scripts/generate_docx_templates.py +0 -491
- package/.grok/skills/lit-docx/scripts/pdf_fonts.py +0 -63
- package/.grok/skills/lit-docx/scripts/run.mjs +0 -23
- package/.grok/skills/lit-docx/scripts/slop_lint.py +0 -563
- package/.grok/skills/lit-docx/scripts/visual_audit.py +0 -41
- package/.grok/skills/lit-docx/templates/docx/acs.docx +0 -0
- package/.grok/skills/lit-docx/templates/docx/elsevier.docx +0 -0
- package/.grok/skills/lit-docx/templates/docx/ieee.docx +0 -0
- package/.grok/skills/lit-docx/templates/docx/korean-generic.docx +0 -0
- package/.grok/skills/lit-docx/templates/docx/nature.docx +0 -0
- package/.grok/skills/lit-docx/templates/latex/acs/README.md +0 -5
- package/.grok/skills/lit-docx/templates/latex/acs/template.tex +0 -42
- package/.grok/skills/lit-docx/templates/latex/elsevier/README.md +0 -5
- package/.grok/skills/lit-docx/templates/latex/elsevier/template.tex +0 -43
- package/.grok/skills/lit-docx/templates/latex/ieee/README.md +0 -5
- package/.grok/skills/lit-docx/templates/latex/ieee/template.tex +0 -42
- package/.grok/skills/lit-docx/templates/latex/korean-generic/README.md +0 -5
- package/.grok/skills/lit-docx/templates/latex/korean-generic/template.tex +0 -42
- package/.grok/skills/lit-docx/templates/latex/nature/README.md +0 -5
- package/.grok/skills/lit-docx/templates/latex/nature/template.tex +0 -42
- package/.grok/skills/lit-docx/templates/registry.yaml +0 -1366
- package/.grok/skills/lit-docx/templates/tonalities/brief.yaml +0 -28
- package/.grok/skills/lit-docx/templates/tonalities/journal.yaml +0 -25
- package/.grok/skills/lit-docx/templates/tonalities/manual.yaml +0 -26
- package/.grok/skills/lit-docx/templates/tonalities/memo.yaml +0 -26
- package/.grok/skills/lit-docx/templates/tonalities/proposal.yaml +0 -26
- package/.grok/skills/lit-docx/templates/tonalities/report.yaml +0 -26
- package/.grok/skills/lit-pptx/FORBIDDEN_TERMS.json +0 -55
- package/.grok/skills/lit-pptx/NOTICE.md +0 -1
- package/.grok/skills/lit-pptx/agents/analyst.md +0 -49
- package/.grok/skills/lit-pptx/agents/antithesis.md +0 -59
- package/.grok/skills/lit-pptx/agents/architect.md +0 -44
- package/.grok/skills/lit-pptx/agents/critic.md +0 -42
- package/.grok/skills/lit-pptx/agents/synthesizer.md +0 -47
- package/.grok/skills/lit-pptx/agents/thesis.md +0 -33
- package/.grok/skills/lit-pptx/assets/azure/circle-lg.png +0 -0
- package/.grok/skills/lit-pptx/assets/azure/circle-md.png +0 -0
- package/.grok/skills/lit-pptx/assets/azure/circle-sm.png +0 -0
- package/.grok/skills/lit-pptx/examples/01-pitch-signal-ko.md +0 -175
- package/.grok/skills/lit-pptx/examples/02-business-review-ledger-ko.md +0 -199
- package/.grok/skills/lit-pptx/examples/03-research-talk-paper-en.md +0 -177
- package/.grok/skills/lit-pptx/examples/04-lecture-chalk-ko.md +0 -176
- package/.grok/skills/lit-pptx/examples/05-data-review-night-en.md +0 -221
- package/.grok/skills/lit-pptx/examples/06-image-led-atlas-en.md +0 -143
- package/.grok/skills/lit-pptx/examples/07-briefing-gazette-ko.md +0 -192
- package/.grok/skills/lit-pptx/examples/08-status-update-ledger-en.md +0 -168
- package/.grok/skills/lit-pptx/examples/09-pitch-studio-en.md +0 -157
- package/.grok/skills/lit-pptx/examples/10-image-led-studio-ko.md +0 -175
- package/.grok/skills/lit-pptx/examples/11-business-review-night-en.md +0 -181
- package/.grok/skills/lit-pptx/examples/12-status-update-gazette-ko.md +0 -199
- package/.grok/skills/lit-pptx/examples/13-engineering-review-computational-brutalism-ko.md +0 -173
- package/.grok/skills/lit-pptx/examples/14-launch-pitch-neo-brutalism-ko.md +0 -154
- package/.grok/skills/lit-pptx/examples/15-design-lecture-bauhaus-ko.md +0 -139
- package/.grok/skills/lit-pptx/examples/16-community-programme-riso-ko.md +0 -158
- package/.grok/skills/lit-pptx/examples/17-public-data-briefing-newsroom-ko.md +0 -179
- package/.grok/skills/lit-pptx/fonts/jetbrains-mono/JetBrainsMono-Bold.ttf +0 -0
- package/.grok/skills/lit-pptx/fonts/jetbrains-mono/JetBrainsMono-Regular.ttf +0 -0
- package/.grok/skills/lit-pptx/fonts/jetbrains-mono/OFL.txt +0 -93
- package/.grok/skills/lit-pptx/fonts/jetbrains-mono/provenance.json +0 -1
- package/.grok/skills/lit-pptx/package-lock.json +0 -795
- package/.grok/skills/lit-pptx/package.json +0 -13
- package/.grok/skills/lit-pptx/pretendard-font/OFL.txt +0 -94
- package/.grok/skills/lit-pptx/pretendard-font/public/static/Pretendard-Bold.otf +0 -0
- package/.grok/skills/lit-pptx/pretendard-font/public/static/Pretendard-Regular.otf +0 -0
- package/.grok/skills/lit-pptx/references/density-and-fill.md +0 -155
- package/.grok/skills/lit-pptx/references/direction-step.md +0 -237
- package/.grok/skills/lit-pptx/references/layout-families.md +0 -193
- package/.grok/skills/lit-pptx/references/title-treatments.md +0 -134
- package/.grok/skills/lit-pptx/references/tonalities/atlas.md +0 -121
- package/.grok/skills/lit-pptx/references/tonalities/bauhaus.md +0 -140
- package/.grok/skills/lit-pptx/references/tonalities/chalk.md +0 -133
- package/.grok/skills/lit-pptx/references/tonalities/computational-brutalism.md +0 -150
- package/.grok/skills/lit-pptx/references/tonalities/gazette.md +0 -125
- package/.grok/skills/lit-pptx/references/tonalities/ledger.md +0 -132
- package/.grok/skills/lit-pptx/references/tonalities/neo-brutalism.md +0 -161
- package/.grok/skills/lit-pptx/references/tonalities/newsroom.md +0 -144
- package/.grok/skills/lit-pptx/references/tonalities/night.md +0 -129
- package/.grok/skills/lit-pptx/references/tonalities/paper.md +0 -125
- package/.grok/skills/lit-pptx/references/tonalities/riso.md +0 -137
- package/.grok/skills/lit-pptx/references/tonalities/signal.md +0 -116
- package/.grok/skills/lit-pptx/references/tonalities/studio.md +0 -121
- package/.grok/skills/lit-pptx/requirements.lock +0 -12
- package/.grok/skills/lit-pptx/scripts/compile-deck.js +0 -451
- package/.grok/skills/lit-pptx/scripts/craft_extras.py +0 -307
- package/.grok/skills/lit-pptx/scripts/deck_output.py +0 -612
- package/.grok/skills/lit-pptx/scripts/deps.cjs +0 -83
- package/.grok/skills/lit-pptx/scripts/embed_fonts.py +0 -213
- package/.grok/skills/lit-pptx/scripts/inventory.py +0 -74
- package/.grok/skills/lit-pptx/scripts/learn_template.py +0 -433
- package/.grok/skills/lit-pptx/scripts/lib/derive-palette.js +0 -176
- package/.grok/skills/lit-pptx/scripts/lib/directive-parser.js +0 -244
- package/.grok/skills/lit-pptx/scripts/lib/font-map.js +0 -79
- package/.grok/skills/lit-pptx/scripts/lib/gen-bg.js +0 -85
- package/.grok/skills/lit-pptx/scripts/lib/gen-gradient-assets.js +0 -36
- package/.grok/skills/lit-pptx/scripts/lib/grid-resolver.js +0 -512
- package/.grok/skills/lit-pptx/scripts/lib/layout-resolver.js +0 -1113
- package/.grok/skills/lit-pptx/scripts/lib/markdown-loader.js +0 -138
- package/.grok/skills/lit-pptx/scripts/lib/render-adapter-html.js +0 -356
- package/.grok/skills/lit-pptx/scripts/lib/render-adapter-pptx.js +0 -2236
- package/.grok/skills/lit-pptx/scripts/lib/render-pack.js +0 -3365
- package/.grok/skills/lit-pptx/scripts/lib/slide-ast.js +0 -565
- package/.grok/skills/lit-pptx/scripts/lib/slide-grid.js +0 -104
- package/.grok/skills/lit-pptx/scripts/lib/spec-validator.js +0 -94
- package/.grok/skills/lit-pptx/scripts/lib/template-registry.js +0 -730
- package/.grok/skills/lit-pptx/scripts/ooxml_integrity.py +0 -37
- package/.grok/skills/lit-pptx/scripts/qa_deck.py +0 -809
- package/.grok/skills/lit-pptx/scripts/run.mjs +0 -25
- package/.grok/skills/lit-pptx/scripts/validate_pptx.py +0 -223
- package/.grok/skills/lit-pptx/specs/markdown-slide-spec-v1.md +0 -251
- package/.grok/skills/lit-pptx/specs/markdown-slide-spec-v2.md +0 -207
- package/.grok/skills/lit-pptx/specs/slide-ast-v1.schema.json +0 -229
- package/.grok/skills/lit-pptx/specs/slide-ast-v2.schema.json +0 -144
- package/.grok/skills/lit-pptx/specs/template-enrollment-contract.md +0 -379
- package/.grok/skills/lit-pptx/templates/bauhaus_light.css +0 -4
- package/.grok/skills/lit-pptx/templates/boilerplate_professional.css +0 -508
- package/.grok/skills/lit-pptx/templates/enrolled/AZURE-A2Z/capabilities.yaml +0 -146
- package/.grok/skills/lit-pptx/templates/enrolled/AZURE-A2Z/layout-mapping.yaml +0 -454
- package/.grok/skills/lit-pptx/templates/enrolled/AZURE-A2Z/template.yaml +0 -40
- package/.grok/skills/lit-pptx/templates/enrolled/AZURE-PRO/capabilities.yaml +0 -154
- package/.grok/skills/lit-pptx/templates/enrolled/AZURE-PRO/layout-mapping.yaml +0 -435
- package/.grok/skills/lit-pptx/templates/enrolled/AZURE-PRO/template.yaml +0 -40
- package/.grok/skills/lit-pptx/templates/enrolled/BOILERPLATE-A2Z/capabilities.yaml +0 -179
- package/.grok/skills/lit-pptx/templates/enrolled/BOILERPLATE-A2Z/layout-mapping.yaml +0 -299
- package/.grok/skills/lit-pptx/templates/enrolled/BOILERPLATE-A2Z/template.yaml +0 -45
- package/.grok/skills/lit-pptx/templates/enrolled/BOILERPLATE-PRETENDARD/capabilities.yaml +0 -181
- package/.grok/skills/lit-pptx/templates/enrolled/BOILERPLATE-PRETENDARD/layout-mapping.yaml +0 -299
- package/.grok/skills/lit-pptx/templates/enrolled/BOILERPLATE-PRETENDARD/template.yaml +0 -44
- package/.grok/skills/lit-pptx/templates/tonalities/atlas/pack.yaml +0 -81
- package/.grok/skills/lit-pptx/templates/tonalities/bauhaus/pack.yaml +0 -98
- package/.grok/skills/lit-pptx/templates/tonalities/chalk/pack.yaml +0 -87
- package/.grok/skills/lit-pptx/templates/tonalities/computational-brutalism/pack.yaml +0 -99
- package/.grok/skills/lit-pptx/templates/tonalities/gazette/pack.yaml +0 -81
- package/.grok/skills/lit-pptx/templates/tonalities/ledger/pack.yaml +0 -82
- package/.grok/skills/lit-pptx/templates/tonalities/neo-brutalism/pack.yaml +0 -107
- package/.grok/skills/lit-pptx/templates/tonalities/newsroom/pack.yaml +0 -99
- package/.grok/skills/lit-pptx/templates/tonalities/night/pack.yaml +0 -82
- package/.grok/skills/lit-pptx/templates/tonalities/paper/pack.yaml +0 -87
- package/.grok/skills/lit-pptx/templates/tonalities/riso/pack.yaml +0 -99
- package/.grok/skills/lit-pptx/templates/tonalities/signal/pack.yaml +0 -83
- package/.grok/skills/lit-pptx/templates/tonalities/studio/pack.yaml +0 -81
- package/.grok/skills/lit-schematic/examples/iso-heat-exchanger/build.mjs +0 -38
- package/.grok/skills/lit-schematic/examples/iso-heat-exchanger/scene.mjs +0 -133
- package/.grok/skills/lit-schematic/examples/journal-adsorption-rig/build.mjs +0 -87
- package/.grok/skills/lit-schematic/examples/pid-dosing-skid/build.mjs +0 -85
- package/.grok/skills/lit-schematic/references/cutaway-outputs.md +0 -70
- package/.grok/skills/lit-schematic/references/isometric-craft.md +0 -101
- package/.grok/skills/lit-schematic/references/journal-figures.md +0 -72
- package/.grok/skills/lit-schematic/references/kit-api.md +0 -134
- package/.grok/skills/lit-schematic/references/pfd-and-layout.md +0 -53
- package/.grok/skills/lit-schematic/references/pid-drafting.md +0 -103
- package/.grok/skills/lit-schematic/references/pid-symbols.md +0 -163
- package/.grok/skills/lit-schematic/references/verification.md +0 -100
- package/.grok/skills/lit-schematic/scripts/doctor.mjs +0 -57
- package/.grok/skills/lit-schematic/scripts/lib/figure.mjs +0 -81
- package/.grok/skills/lit-schematic/scripts/lib/fonts.mjs +0 -27
- package/.grok/skills/lit-schematic/scripts/lib/geom.mjs +0 -113
- package/.grok/skills/lit-schematic/scripts/lib/interactive.mjs +0 -292
- package/.grok/skills/lit-schematic/scripts/lib/isa.mjs +0 -115
- package/.grok/skills/lit-schematic/scripts/lib/iso.mjs +0 -719
- package/.grok/skills/lit-schematic/scripts/lib/legend-sheet.mjs +0 -162
- package/.grok/skills/lit-schematic/scripts/lib/pid.mjs +0 -937
- package/.grok/skills/lit-schematic/scripts/lib/png.mjs +0 -132
- package/.grok/skills/lit-schematic/scripts/lib/qa-core.mjs +0 -658
- package/.grok/skills/lit-schematic/scripts/lib/route.mjs +0 -218
- package/.grok/skills/lit-schematic/scripts/lib/sheet.mjs +0 -890
- package/.grok/skills/lit-schematic/scripts/lib/svg.mjs +0 -82
- package/.grok/skills/lit-schematic/scripts/lib/theme.mjs +0 -115
- package/.grok/skills/lit-schematic/scripts/motion.mjs +0 -129
- package/.grok/skills/lit-schematic/scripts/qa.mjs +0 -92
- package/.grok/skills/lit-schematic/scripts/render.mjs +0 -233
- package/.grok/skills/lit-schematic/scripts/scaffold.mjs +0 -60
- package/.grok/skills/lit-scientific-visualization/scripts/verify-canonical-corpus.mjs +0 -181
- package/.grok/skills/lit-typographic-motion/NOTICE +0 -23
- package/.grok/skills/lit-typographic-motion/THIRD_PARTY_NOTICES +0 -56
- package/.grok/skills/lit-typographic-motion/fonts/Archivo-OFL.txt +0 -93
- package/.grok/skills/lit-typographic-motion/fonts/Archivo-w100-wt400.ttf +0 -0
- package/.grok/skills/lit-typographic-motion/fonts/Archivo-w100-wt700.ttf +0 -0
- package/.grok/skills/lit-typographic-motion/fonts/Archivo-w100-wt900.ttf +0 -0
- package/.grok/skills/lit-typographic-motion/fonts/Archivo-w125-wt400.ttf +0 -0
- package/.grok/skills/lit-typographic-motion/fonts/Archivo-w125-wt700.ttf +0 -0
- package/.grok/skills/lit-typographic-motion/fonts/Archivo-w125-wt900.ttf +0 -0
- package/.grok/skills/lit-typographic-motion/fonts/Archivo-w75-wt400.ttf +0 -0
- package/.grok/skills/lit-typographic-motion/fonts/Archivo-w75-wt700.ttf +0 -0
- package/.grok/skills/lit-typographic-motion/fonts/Archivo-w75-wt900.ttf +0 -0
- package/.grok/skills/lit-typographic-motion/fonts/JetBrainsMono-OFL.txt +0 -93
- package/.grok/skills/lit-typographic-motion/fonts/JetBrainsMono-wght-latin.woff2 +0 -0
- package/.grok/skills/lit-typographic-motion/fonts/Silkscreen-Bold.ttf +0 -0
- package/.grok/skills/lit-typographic-motion/fonts/Silkscreen-OFL.txt +0 -93
- package/.grok/skills/lit-typographic-motion/fonts/Silkscreen-Regular.ttf +0 -0
- package/.grok/skills/lit-typographic-motion/fonts/VT323-OFL.txt +0 -93
- package/.grok/skills/lit-typographic-motion/fonts/VT323-Regular.ttf +0 -0
- package/.grok/skills/lit-typographic-motion/fonts/pins.json +0 -386
- package/.grok/skills/lit-typographic-motion/fonts/stroke/CREDITS +0 -41
- package/.grok/skills/lit-typographic-motion/fonts/stroke/EMSAllure.svg +0 -216
- package/.grok/skills/lit-typographic-motion/fonts/stroke/EMSFelix.svg +0 -216
- package/.grok/skills/lit-typographic-motion/fonts/stroke/EMSOsmotron.svg +0 -216
- package/.grok/skills/lit-typographic-motion/fonts/stroke/EMSReadability.svg +0 -216
- package/.grok/skills/lit-typographic-motion/fonts/stroke/EMSTech.svg +0 -217
- package/.grok/skills/lit-typographic-motion/fonts/stroke/OFL-1.1.txt +0 -91
- package/.grok/skills/lit-typographic-motion/package-lock.json +0 -63
- package/.grok/skills/lit-typographic-motion/package.json +0 -10
- package/.grok/skills/lit-typographic-motion/recipes/hero-3d/expected.json +0 -62
- package/.grok/skills/lit-typographic-motion/recipes/hero-3d/index.html +0 -82
- package/.grok/skills/lit-typographic-motion/recipes/negatives/card-scenes/expected.json +0 -16
- package/.grok/skills/lit-typographic-motion/recipes/negatives/card-scenes/index.html +0 -36
- package/.grok/skills/lit-typographic-motion/recipes/negatives/reset-sweep/expected.json +0 -16
- package/.grok/skills/lit-typographic-motion/recipes/negatives/reset-sweep/index.html +0 -24
- package/.grok/skills/lit-typographic-motion/recipes/negatives/still-glow/expected.json +0 -17
- package/.grok/skills/lit-typographic-motion/recipes/negatives/still-glow/index.html +0 -25
- package/.grok/skills/lit-typographic-motion/recipes/negatives/title-slam/expected.json +0 -17
- package/.grok/skills/lit-typographic-motion/recipes/negatives/title-slam/index.html +0 -37
- package/.grok/skills/lit-typographic-motion/recipes/orbit-loop/expected.json +0 -67
- package/.grok/skills/lit-typographic-motion/recipes/orbit-loop/index.html +0 -119
- package/.grok/skills/lit-typographic-motion/recipes/painted-light/expected.json +0 -95
- package/.grok/skills/lit-typographic-motion/recipes/painted-light/index.html +0 -192
- package/.grok/skills/lit-typographic-motion/recipes/weight-tide/expected.json +0 -67
- package/.grok/skills/lit-typographic-motion/recipes/weight-tide/index.html +0 -85
- package/.grok/skills/lit-typographic-motion/references/choice-first.md +0 -30
- package/.grok/skills/lit-typographic-motion/references/complete-contract.md +0 -124
- package/.grok/skills/lit-typographic-motion/references/cover.md +0 -389
- package/.grok/skills/lit-typographic-motion/references/craft-loop.md +0 -112
- package/.grok/skills/lit-typographic-motion/references/depth.md +0 -119
- package/.grok/skills/lit-typographic-motion/references/runtime.md +0 -138
- package/.grok/skills/lit-typographic-motion/references/scene-contract.md +0 -98
- package/.grok/skills/lit-typographic-motion/references/stage.md +0 -194
- package/.grok/skills/lit-typographic-motion/references/style-bible.md +0 -102
- package/.grok/skills/lit-typographic-motion/references/treatment.md +0 -153
- package/.grok/skills/lit-typographic-motion/references/type-craft.md +0 -94
- package/.grok/skills/lit-typographic-motion/requirements-audio.txt +0 -794
- package/.grok/skills/lit-typographic-motion/scripts/beat_grid.py +0 -29
- package/.grok/skills/lit-typographic-motion/scripts/blender/runner.py +0 -218
- package/.grok/skills/lit-typographic-motion/scripts/lib/assets.mjs +0 -230
- package/.grok/skills/lit-typographic-motion/scripts/lib/blender.mjs +0 -136
- package/.grok/skills/lit-typographic-motion/scripts/lib/board.mjs +0 -153
- package/.grok/skills/lit-typographic-motion/scripts/lib/bundle.mjs +0 -23
- package/.grok/skills/lit-typographic-motion/scripts/lib/cache.mjs +0 -107
- package/.grok/skills/lit-typographic-motion/scripts/lib/chrome.mjs +0 -276
- package/.grok/skills/lit-typographic-motion/scripts/lib/clips.mjs +0 -119
- package/.grok/skills/lit-typographic-motion/scripts/lib/completion.mjs +0 -43
- package/.grok/skills/lit-typographic-motion/scripts/lib/constants.mjs +0 -244
- package/.grok/skills/lit-typographic-motion/scripts/lib/cover-assets.mjs +0 -88
- package/.grok/skills/lit-typographic-motion/scripts/lib/cover-capture.mjs +0 -153
- package/.grok/skills/lit-typographic-motion/scripts/lib/cover-gate.mjs +0 -312
- package/.grok/skills/lit-typographic-motion/scripts/lib/cover-image.mjs +0 -111
- package/.grok/skills/lit-typographic-motion/scripts/lib/cover-pool.mjs +0 -62
- package/.grok/skills/lit-typographic-motion/scripts/lib/cover-sheet.mjs +0 -73
- package/.grok/skills/lit-typographic-motion/scripts/lib/cover-treatment.mjs +0 -128
- package/.grok/skills/lit-typographic-motion/scripts/lib/cover-webp.mjs +0 -134
- package/.grok/skills/lit-typographic-motion/scripts/lib/cover.mjs +0 -354
- package/.grok/skills/lit-typographic-motion/scripts/lib/encode.mjs +0 -33
- package/.grok/skills/lit-typographic-motion/scripts/lib/flash.mjs +0 -170
- package/.grok/skills/lit-typographic-motion/scripts/lib/fonts.mjs +0 -150
- package/.grok/skills/lit-typographic-motion/scripts/lib/frame-pool.mjs +0 -162
- package/.grok/skills/lit-typographic-motion/scripts/lib/gallery.mjs +0 -160
- package/.grok/skills/lit-typographic-motion/scripts/lib/gate.mjs +0 -586
- package/.grok/skills/lit-typographic-motion/scripts/lib/house.mjs +0 -43
- package/.grok/skills/lit-typographic-motion/scripts/lib/look.mjs +0 -289
- package/.grok/skills/lit-typographic-motion/scripts/lib/png.mjs +0 -95
- package/.grok/skills/lit-typographic-motion/scripts/lib/presets.mjs +0 -161
- package/.grok/skills/lit-typographic-motion/scripts/lib/render.mjs +0 -563
- package/.grok/skills/lit-typographic-motion/scripts/lib/report.mjs +0 -65
- package/.grok/skills/lit-typographic-motion/scripts/lib/scaffold.mjs +0 -174
- package/.grok/skills/lit-typographic-motion/scripts/lib/score.mjs +0 -640
- package/.grok/skills/lit-typographic-motion/scripts/lib/seed.mjs +0 -31
- package/.grok/skills/lit-typographic-motion/scripts/lib/session.mjs +0 -102
- package/.grok/skills/lit-typographic-motion/scripts/lib/sound.mjs +0 -517
- package/.grok/skills/lit-typographic-motion/scripts/lib/soundtrack.mjs +0 -46
- package/.grok/skills/lit-typographic-motion/scripts/lib/stage-gate.mjs +0 -139
- package/.grok/skills/lit-typographic-motion/scripts/lib/stage-qa.mjs +0 -400
- package/.grok/skills/lit-typographic-motion/scripts/lib/stage-session.mjs +0 -166
- package/.grok/skills/lit-typographic-motion/scripts/lib/stage.mjs +0 -504
- package/.grok/skills/lit-typographic-motion/scripts/lib/stills.mjs +0 -90
- package/.grok/skills/lit-typographic-motion/scripts/lib/styles.mjs +0 -224
- package/.grok/skills/lit-typographic-motion/scripts/lib/text.mjs +0 -114
- package/.grok/skills/lit-typographic-motion/scripts/lib/timeline.mjs +0 -223
- package/.grok/skills/lit-typographic-motion/scripts/lib/treatment.mjs +0 -446
- package/.grok/skills/lit-typographic-motion/scripts/lib/vendor/three/LICENSE +0 -21
- package/.grok/skills/lit-typographic-motion/scripts/lib/vendor/three/addons/environments/RoomEnvironment.js +0 -144
- package/.grok/skills/lit-typographic-motion/scripts/lib/vendor/three/addons/geometries/RoundedBoxGeometry.js +0 -155
- package/.grok/skills/lit-typographic-motion/scripts/lib/vendor/three/three.module.js +0 -54571
- package/.grok/skills/lit-typographic-motion/scripts/motion.mjs +0 -225
- package/.grok/skills/lit-typographic-motion/scripts/page/engine.js +0 -344
- package/.grok/skills/lit-typographic-motion/scripts/page/gl.js +0 -365
- package/.grok/skills/lit-typographic-motion/scripts/page/passes.js +0 -211
- package/.grok/skills/lit-typographic-motion/scripts/page/post.js +0 -127
- package/.grok/skills/lit-typographic-motion/scripts/page/scenes.js +0 -359
- package/.grok/skills/lit-typographic-motion/scripts/page/stroke.js +0 -154
- package/.grok/skills/lit-typographic-motion/scripts/page/type.js +0 -142
- package/.grok/skills/lit-typographic-motion/scripts/page/util.js +0 -68
- package/.grok/skills/lit-typographic-motion/scripts/prewarm.mjs +0 -216
- package/.grok/skills/lit-typographic-motion/scripts/stage/cover-init.js +0 -56
- package/.grok/skills/lit-typographic-motion/scripts/stage/host.js +0 -200
- package/.grok/skills/lit-typographic-motion/scripts/stage/loop-kit.js +0 -96
- package/.grok/skills/lit-typographic-motion/scripts/stage/qa.js +0 -137
- package/.grok/skills/lit-typographic-motion/scripts/stage/stage-kit.js +0 -523
- package/.grok/skills/lit-typographic-motion/styles/paper.json +0 -1
- package/.grok/skills/lit-typographic-motion/styles/swiss-signal.json +0 -1
- package/.grok/skills/lit-typographic-motion/styles/terminalcore.json +0 -1
- package/.grok/skills/lit-typographic-motion/styles/tidal.json +0 -1
- package/.grok/skills/readme-studio/references/choice-first.md +0 -24
- package/.grok/skills/readme-studio/references/complete-contract.md +0 -26
- package/.grok/skills/readme-studio/references/decoration-patterns.md +0 -29
- package/.grok/skills/readme-studio/references/facts.md +0 -15
- package/.grok/skills/readme-studio/references/motion.md +0 -42
- package/.grok/skills/readme-studio/references/production.md +0 -9
- package/.grok/skills/readme-studio/references/typography.md +0 -16
- package/.grok/skills/readme-studio/scripts/validate-facts.mjs +0 -52
- package/.grok/skills/readme-studio/templates/cover-source.json +0 -27
- package/.grok/skills/readme-studio/templates/facts.json +0 -13
- package/.grok/skills/readme-studio/templates/hyperframes/index.html +0 -41
- package/.grok/skills/readme-studio/templates/hyperframes/index.motion.json +0 -4
- package/.grok/skills/readme-studio/templates/hyperframes/package-lock.json +0 -2016
- package/.grok/skills/readme-studio/templates/hyperframes/package.json +0 -12
- package/.grok/skills/readme-studio/templates/picture.md +0 -61
- package/.grok/skills/readme-studio/templates/remotion/package-lock.json +0 -3785
- package/.grok/skills/readme-studio/templates/remotion/package.json +0 -19
- package/.grok/skills/readme-studio/templates/remotion/src/index.jsx +0 -31
- package/.grok/skills/readme-studio/templates/typography/outline.mjs +0 -62
- package/.grok/skills/readme-studio/templates/typography/package-lock.json +0 -135
- package/.grok/skills/readme-studio/templates/typography/package.json +0 -10
- package/.grok/skills/visual-qa/references/capture-playbook.md +0 -47
- package/.grok/skills/visual-qa/references/complete-contract.md +0 -721
- package/.grok/skills/visual-qa/references/verdict-taxonomy.md +0 -34
- package/.grok/skills/visual-qa/scripts/verify-evidence-manifest.mjs +0 -91
- package/.grok/vendor/NOTICE.md +0 -16
- package/.grok/vendor/licenses/045_scientific-visualization-MIT.txt +0 -21
- package/.grok/vendor/provenance/045_scientific-visualization.md +0 -37
- package/.grok/vendor/scientific-visualization/assets/color_palettes.py +0 -197
- package/.grok/vendor/scientific-visualization/assets/nature.mplstyle +0 -75
- package/.grok/vendor/scientific-visualization/assets/presentation.mplstyle +0 -74
- package/.grok/vendor/scientific-visualization/assets/publication.mplstyle +0 -78
- package/.grok/vendor/scientific-visualization/evals/evals.json +0 -158
- package/.grok/vendor/scientific-visualization/references/_canonical-corpus/manifest.json +0 -38
- package/.grok/vendor/scientific-visualization/references/color_palettes.md +0 -380
- package/.grok/vendor/scientific-visualization/references/journal_requirements.md +0 -359
- package/.grok/vendor/scientific-visualization/references/matplotlib_examples.md +0 -608
- package/.grok/vendor/scientific-visualization/references/mdanalysis_martini_visualization.md +0 -85
- package/.grok/vendor/scientific-visualization/references/publication_guidelines.md +0 -217
- package/.grok/vendor/scientific-visualization/references/seaborn_for_publications.md +0 -293
- package/.grok/vendor/scientific-visualization/scripts/figure_export.py +0 -238
- package/.grok/vendor/scientific-visualization/scripts/style_presets.py +0 -467
- package/.grok/vendor/scientific-visualization/tests/test_figure_export.py +0 -51
- package/.grok/vendor/scientific-visualization/tests/test_style_presets.py +0 -114
- /package/.grok/skills/{lit-pptx → litgrok}/scripts/proc.cjs +0 -0
|
@@ -1,2236 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* render-adapter-pptx.js — Render a resolved slide spec to a PPTX file.
|
|
5
|
-
*
|
|
6
|
-
* Architecture: Each text line is its own separate shape (individual addText()
|
|
7
|
-
* call). Bullet items use manual "• " prefix in green (#00AE41) Bold 16pt.
|
|
8
|
-
* Section headers are separate shapes in Bold 18pt black.
|
|
9
|
-
*
|
|
10
|
-
* Input: resolved spec from layout-resolver, templateObj from registry, output path.
|
|
11
|
-
* Output: writes a .pptx file via pptxgenjs.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const fs = require("fs");
|
|
15
|
-
const os = require("os");
|
|
16
|
-
const path = require("path");
|
|
17
|
-
const { extractContent, looksLikeKpi } = require("./layout-resolver");
|
|
18
|
-
const { LEGACY_TOKENS } = require("./template-registry");
|
|
19
|
-
|
|
20
|
-
// Narrowest a KPI badge can be and still read as one glance (template token kpi.minCardWidth).
|
|
21
|
-
let KPI_MIN_CARD_WIDTH_IN = LEGACY_TOKENS.kpi.minCardWidth;
|
|
22
|
-
let KPI_GAP = LEGACY_TOKENS.kpi.gap;
|
|
23
|
-
|
|
24
|
-
const TOOLKIT_ROOT = path.resolve(__dirname, "../..");
|
|
25
|
-
const ASSETS_AZURE = path.join(TOOLKIT_ROOT, "assets", "azure");
|
|
26
|
-
|
|
27
|
-
/** Resolve a decoration asset path: explicit absolute override (custom-accent
|
|
28
|
-
* regenerated gradients), otherwise only the enrolled Azure assets. */
|
|
29
|
-
function decorAssetPath(d) {
|
|
30
|
-
if (d.assetPath) return d.assetPath;
|
|
31
|
-
if (d.dir === "azure") return path.join(ASSETS_AZURE, d.asset);
|
|
32
|
-
throw new Error(`Decoration asset is not enrolled: ${d.asset}`);
|
|
33
|
-
}
|
|
34
|
-
const hex = (c) => (c || "#000000").replace("#", "");
|
|
35
|
-
|
|
36
|
-
// A template may set its own disclaimer (`text:` on the decoration); the default
|
|
37
|
-
// names no organisation and claims no secrecy.
|
|
38
|
-
const DEFAULT_DISCLAIMER = "※ 이 문서의 사용 범위는 작성자가 지정한 권한을 따릅니다.";
|
|
39
|
-
|
|
40
|
-
function linkedTableCell(text, options) {
|
|
41
|
-
const original = cleanMd(text);
|
|
42
|
-
let display = original;
|
|
43
|
-
let url = null;
|
|
44
|
-
const markdown = original.match(/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/i);
|
|
45
|
-
if (markdown) {
|
|
46
|
-
display = original.replace(markdown[0], markdown[1]);
|
|
47
|
-
url = markdown[2];
|
|
48
|
-
} else {
|
|
49
|
-
const rawUrl = original.match(/https?:\/\/[^\s<>\])}]+/i);
|
|
50
|
-
const doi = original.match(/\b10\.\d{4,9}\/[^\s<>\])}]+/i);
|
|
51
|
-
if (rawUrl) url = rawUrl[0].replace(/[.,;:]+$/, "");
|
|
52
|
-
else if (doi) url = `https://doi.org/${doi[0].replace(/[.,;:]+$/, "")}`;
|
|
53
|
-
}
|
|
54
|
-
return {
|
|
55
|
-
text: display,
|
|
56
|
-
options: {
|
|
57
|
-
...options,
|
|
58
|
-
...(url ? { hyperlink: { url } } : {}),
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// ── Text style constants (defaults; every template reassigns them in render()).
|
|
64
|
-
// These are reassigned from the template at the start of render() so the same
|
|
65
|
-
// adapter serves any enrolled template (e.g. BOILERPLATE-PRETENDARD).
|
|
66
|
-
let FONT_BOLD = "Pretendard";
|
|
67
|
-
let FONT_MEDIUM = "Pretendard";
|
|
68
|
-
let FONT_LIGHT = "Pretendard";
|
|
69
|
-
let CHAR_SPACING = -0.7;
|
|
70
|
-
// Chart colours and faces for templates without the rich palette; set per render().
|
|
71
|
-
let PLAIN_CHART_STYLE = { series: ["3C3836", "689D6A", "D79921", "B16286"], ink: "3C3836", ink_muted: "6E6256", line: "CBC2B5", titleFont: "Pretendard", bodyFont: "Pretendard" };
|
|
72
|
-
|
|
73
|
-
function expandHome(p) {
|
|
74
|
-
if (p === "~") return os.homedir();
|
|
75
|
-
if (p && p.startsWith(`~${path.sep}`)) return path.join(os.homedir(), p.slice(2));
|
|
76
|
-
return p;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function resolveImagePath(src, sourceDir) {
|
|
80
|
-
if (!src) throw new Error("Image source is empty");
|
|
81
|
-
const expanded = expandHome(src);
|
|
82
|
-
const candidates = [];
|
|
83
|
-
if (path.isAbsolute(expanded)) candidates.push(expanded);
|
|
84
|
-
else {
|
|
85
|
-
if (sourceDir) candidates.push(path.resolve(sourceDir, expanded));
|
|
86
|
-
candidates.push(path.resolve(process.cwd(), expanded));
|
|
87
|
-
candidates.push(path.resolve(TOOLKIT_ROOT, expanded));
|
|
88
|
-
}
|
|
89
|
-
const found = candidates.find((candidate) => fs.existsSync(candidate));
|
|
90
|
-
if (!found) {
|
|
91
|
-
throw new Error(`Image not found: ${src}. Checked: ${candidates.join(", ")}`);
|
|
92
|
-
}
|
|
93
|
-
return found;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function readImageSize(filePath) {
|
|
97
|
-
const buf = fs.readFileSync(filePath);
|
|
98
|
-
if (buf.length >= 24 && buf.toString("ascii", 1, 4) === "PNG") {
|
|
99
|
-
return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20) };
|
|
100
|
-
}
|
|
101
|
-
if (buf.length >= 4 && buf[0] === 0xff && buf[1] === 0xd8) {
|
|
102
|
-
let offset = 2;
|
|
103
|
-
while (offset + 9 < buf.length) {
|
|
104
|
-
if (buf[offset] !== 0xff) { offset++; continue; }
|
|
105
|
-
const marker = buf[offset + 1];
|
|
106
|
-
const length = buf.readUInt16BE(offset + 2);
|
|
107
|
-
if (length < 2) break;
|
|
108
|
-
if ((marker >= 0xc0 && marker <= 0xc3) || (marker >= 0xc5 && marker <= 0xc7) ||
|
|
109
|
-
(marker >= 0xc9 && marker <= 0xcb) || (marker >= 0xcd && marker <= 0xcf)) {
|
|
110
|
-
return { width: buf.readUInt16BE(offset + 7), height: buf.readUInt16BE(offset + 5) };
|
|
111
|
-
}
|
|
112
|
-
offset += 2 + length;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
return null;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function containBox(x, y, w, h, imageSize) {
|
|
119
|
-
if (!imageSize || !imageSize.width || !imageSize.height) return { x, y, w, h };
|
|
120
|
-
const boxRatio = w / h;
|
|
121
|
-
const imageRatio = imageSize.width / imageSize.height;
|
|
122
|
-
if (imageRatio > boxRatio) {
|
|
123
|
-
const fittedH = w / imageRatio;
|
|
124
|
-
return { x, y: y + (h - fittedH) / 2, w, h: fittedH };
|
|
125
|
-
}
|
|
126
|
-
const fittedW = h * imageRatio;
|
|
127
|
-
return { x: x + (w - fittedW) / 2, y, w: fittedW, h };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Bullet items. Geometry comes from the template's tokens (bullet.*, secHeader.*, summary.*,
|
|
131
|
-
// toc.*), reassigned in render(); these defaults are the legacy values.
|
|
132
|
-
let BULLET_COLOR = "1D4ED8"; // bullet color (reassigned per template)
|
|
133
|
-
let SECTION_ACCENT = "1D4ED8"; // section-card / accent color (reassigned per template)
|
|
134
|
-
const BULLET_PREFIX = "\u2022 "; // "• " (bullet + two spaces)
|
|
135
|
-
let BULLET_X = LEGACY_TOKENS.bullet.x;
|
|
136
|
-
let BULLET_W = LEGACY_TOKENS.bullet.w;
|
|
137
|
-
|
|
138
|
-
// Section headers
|
|
139
|
-
let SEC_HEADER_FONT = FONT_BOLD;
|
|
140
|
-
let SEC_HEADER_SIZE = LEGACY_TOKENS.secHeader.size;
|
|
141
|
-
const SEC_HEADER_COLOR = "000000";
|
|
142
|
-
let SEC_HEADER_X = LEGACY_TOKENS.secHeader.x;
|
|
143
|
-
let SEC_HEADER_W = LEGACY_TOKENS.secHeader.w;
|
|
144
|
-
let SEC_HEADER_H = LEGACY_TOKENS.secHeader.h;
|
|
145
|
-
|
|
146
|
-
// Summary (narrow width)
|
|
147
|
-
let SUMMARY_HEADER_W = LEGACY_TOKENS.summary.headerW;
|
|
148
|
-
let SUMMARY_BULLET_W = LEGACY_TOKENS.summary.bulletW;
|
|
149
|
-
let SUMMARY_BULLET_SIZE = LEGACY_TOKENS.summary.bulletSize;
|
|
150
|
-
let SUMMARY_BULLET_H = LEGACY_TOKENS.summary.bulletH;
|
|
151
|
-
let SUMMARY_BULLET_Y_SPACING = LEGACY_TOKENS.summary.ySpacing;
|
|
152
|
-
let SUMMARY_BULLET_LINE_SPACING = LEGACY_TOKENS.summary.lineSpacing;
|
|
153
|
-
|
|
154
|
-
// TOC items (separate shapes)
|
|
155
|
-
let TOC_FONT = FONT_BOLD;
|
|
156
|
-
let TOC_SIZE = LEGACY_TOKENS.toc.size;
|
|
157
|
-
const TOC_COLOR = "000000";
|
|
158
|
-
let TOC_X = LEGACY_TOKENS.toc.x;
|
|
159
|
-
let TOC_W = LEGACY_TOKENS.toc.w;
|
|
160
|
-
let TOC_H = LEGACY_TOKENS.toc.h;
|
|
161
|
-
let TOC_Y_START = LEGACY_TOKENS.toc.yStart;
|
|
162
|
-
let TOC_Y_SPACING = LEGACY_TOKENS.toc.ySpacing;
|
|
163
|
-
|
|
164
|
-
/** Reassign the token-driven geometry from the template's pack (legacy values when absent). */
|
|
165
|
-
function applyTokens(tokens) {
|
|
166
|
-
const t = tokens || LEGACY_TOKENS;
|
|
167
|
-
({ x: BULLET_X, w: BULLET_W } = t.bullet);
|
|
168
|
-
({ size: SEC_HEADER_SIZE, x: SEC_HEADER_X, w: SEC_HEADER_W, h: SEC_HEADER_H } = t.secHeader);
|
|
169
|
-
({ headerW: SUMMARY_HEADER_W, bulletW: SUMMARY_BULLET_W, bulletSize: SUMMARY_BULLET_SIZE, bulletH: SUMMARY_BULLET_H,
|
|
170
|
-
ySpacing: SUMMARY_BULLET_Y_SPACING, lineSpacing: SUMMARY_BULLET_LINE_SPACING } = t.summary);
|
|
171
|
-
({ size: TOC_SIZE, x: TOC_X, w: TOC_W, h: TOC_H, yStart: TOC_Y_START, ySpacing: TOC_Y_SPACING } = t.toc);
|
|
172
|
-
({ pad: CARD_PAD, chip: CARD_CHIP, minH: CARD_MIN_H, gap: CARD_GAP, radius: CARD_RADIUS } = t.card);
|
|
173
|
-
({ minCardWidth: KPI_MIN_CARD_WIDTH_IN, gap: KPI_GAP } = t.kpi);
|
|
174
|
-
NOTICE = { ...t.notice };
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function weightedTextLength(text) {
|
|
178
|
-
let total = 0;
|
|
179
|
-
for (const ch of String(text || "")) {
|
|
180
|
-
if (/\s/.test(ch)) total += 0.35;
|
|
181
|
-
else if (/^[\x00-\x7F]$/.test(ch)) total += 0.58;
|
|
182
|
-
else total += 1.0;
|
|
183
|
-
}
|
|
184
|
-
return total;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function estimateWrappedLines(text, widthIn, fontSize) {
|
|
188
|
-
const usablePt = Math.max(12, (widthIn || 4) * 72 - 10);
|
|
189
|
-
// Conservative on purpose: PowerPoint wraps dense mixed
|
|
190
|
-
// Korean-English strings earlier than a naive ASCII width estimate.
|
|
191
|
-
const unitsPerLine = Math.max(4, usablePt / Math.max(4, fontSize * 0.95));
|
|
192
|
-
return Math.max(1, Math.ceil(weightedTextLength(text) / unitsPerLine));
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function estimateTextHeight(text, widthIn, fontSize, lineSpacingMultiple) {
|
|
196
|
-
const lines = estimateWrappedLines(text, widthIn, fontSize);
|
|
197
|
-
return Math.max(0.14, ((lines * fontSize * lineSpacingMultiple) / 72) * 1.4 + 0.08);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function bodyElements(items) {
|
|
201
|
-
const elements = [];
|
|
202
|
-
let numberedCounter = 0;
|
|
203
|
-
for (const item of items) {
|
|
204
|
-
if (item.type === "section") {
|
|
205
|
-
elements.push({ kind: "section", text: item.heading || "" });
|
|
206
|
-
numberedCounter = 0;
|
|
207
|
-
for (const child of item.children || []) {
|
|
208
|
-
numberedCounter++;
|
|
209
|
-
elements.push({ kind: "numbered", text: `(${numberedCounter}) ${child.text || ""}` });
|
|
210
|
-
}
|
|
211
|
-
} else if (item.type === "bullet") {
|
|
212
|
-
elements.push({ kind: "bullet", text: item.text || "" });
|
|
213
|
-
numberedCounter = 0;
|
|
214
|
-
for (const child of item.children || []) {
|
|
215
|
-
numberedCounter++;
|
|
216
|
-
elements.push({ kind: "numbered", text: `(${numberedCounter}) ${child.text || ""}` });
|
|
217
|
-
}
|
|
218
|
-
} else if (item.type === "numbered") {
|
|
219
|
-
numberedCounter++;
|
|
220
|
-
elements.push({ kind: "numbered", text: `(${numberedCounter}) ${item.text || ""}` });
|
|
221
|
-
} else if (item.type === "text") {
|
|
222
|
-
elements.push({ kind: "text", text: item.text || "" });
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
return elements;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function chooseBodyStyle(items, widthIn, startY, bottomY) {
|
|
229
|
-
const available = Math.max(0.5, (bottomY || 7.0) - startY);
|
|
230
|
-
const sizes = [16, 15, 14, 13];
|
|
231
|
-
for (const bodySize of sizes) {
|
|
232
|
-
const sectionSize = Math.min(18, bodySize + 2);
|
|
233
|
-
const lineSpacing = bodySize <= 10 ? 1.05 : 1.18;
|
|
234
|
-
const gap = bodySize <= 10 ? 0.035 : 0.05;
|
|
235
|
-
const total = bodyElements(items).reduce((sum, el) => {
|
|
236
|
-
const size = el.kind === "section" ? sectionSize : bodySize;
|
|
237
|
-
const w = el.kind === "section" ? Math.min(widthIn, SEC_HEADER_W) : widthIn;
|
|
238
|
-
return sum + estimateTextHeight(el.text, w, size, lineSpacing) + gap;
|
|
239
|
-
}, 0);
|
|
240
|
-
if (total <= available) return { bodySize, sectionSize, lineSpacing, gap };
|
|
241
|
-
}
|
|
242
|
-
return { bodySize: 13, sectionSize: 15, lineSpacing: 1.08, gap: 0.04 };
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function estimateSectionHeight(item, secW, bulletW, style) {
|
|
246
|
-
let total = estimateTextHeight(item.heading || "", secW, style.sectionSize, style.lineSpacing) + style.gap;
|
|
247
|
-
let numberedCounter = 0;
|
|
248
|
-
for (const child of item.children || []) {
|
|
249
|
-
numberedCounter++;
|
|
250
|
-
total += estimateTextHeight(`(${numberedCounter}) ${child.text || ""}`, bulletW, style.bodySize, style.lineSpacing) + style.gap;
|
|
251
|
-
}
|
|
252
|
-
return total + 0.12;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function addSectionCard(slide, x, y, w, h) {
|
|
256
|
-
slide.addShape("rect", {
|
|
257
|
-
x,
|
|
258
|
-
y: y - 0.035,
|
|
259
|
-
w,
|
|
260
|
-
h: Math.max(0.28, h),
|
|
261
|
-
fill: { color: "F7F9F8", transparency: 12 },
|
|
262
|
-
line: { color: "D8E2DD", width: 0.5, transparency: 20 },
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function bodyItemsToDiagnosticLines(items) {
|
|
267
|
-
const lines = [];
|
|
268
|
-
let numberedCounter = 0;
|
|
269
|
-
for (const item of items || []) {
|
|
270
|
-
if (item.type === "section") {
|
|
271
|
-
lines.push(`■ ${item.heading || ""}`);
|
|
272
|
-
numberedCounter = 0;
|
|
273
|
-
for (const child of item.children || []) {
|
|
274
|
-
numberedCounter++;
|
|
275
|
-
lines.push(`(${numberedCounter}) ${child.text || ""}`);
|
|
276
|
-
}
|
|
277
|
-
} else if (item.type === "bullet") {
|
|
278
|
-
lines.push(`• ${item.text || ""}`);
|
|
279
|
-
numberedCounter = 0;
|
|
280
|
-
for (const child of item.children || []) {
|
|
281
|
-
numberedCounter++;
|
|
282
|
-
lines.push(`(${numberedCounter}) ${child.text || ""}`);
|
|
283
|
-
}
|
|
284
|
-
} else if (item.type === "numbered") {
|
|
285
|
-
numberedCounter++;
|
|
286
|
-
lines.push(`(${numberedCounter}) ${item.text || ""}`);
|
|
287
|
-
} else if (item.type === "text") {
|
|
288
|
-
lines.push(item.text || "");
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
return lines.filter(Boolean);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
function bodyItemsToDiagnosticCards(items) {
|
|
295
|
-
const cards = [];
|
|
296
|
-
let numberedCounter = 0;
|
|
297
|
-
for (const item of items || []) {
|
|
298
|
-
if (item.type === "section" || item.type === "bullet") {
|
|
299
|
-
const title = item.heading || item.text || "";
|
|
300
|
-
const body = [];
|
|
301
|
-
numberedCounter = 0;
|
|
302
|
-
for (const child of item.children || []) {
|
|
303
|
-
numberedCounter++;
|
|
304
|
-
body.push(`(${numberedCounter}) ${child.text || ""}`);
|
|
305
|
-
}
|
|
306
|
-
if (title || body.length) cards.push({ title, body });
|
|
307
|
-
} else if (item.type === "numbered") {
|
|
308
|
-
numberedCounter++;
|
|
309
|
-
cards.push({ title: `(${numberedCounter})`, body: [item.text || ""] });
|
|
310
|
-
} else if (item.type === "text" && item.text) {
|
|
311
|
-
cards.push({ title: "", body: [item.text] });
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
return cards;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
function splitLinesByWeight(lines, columns) {
|
|
318
|
-
const groups = Array.from({ length: columns }, () => []);
|
|
319
|
-
const weights = Array(columns).fill(0);
|
|
320
|
-
for (const line of lines) {
|
|
321
|
-
let target = 0;
|
|
322
|
-
for (let i = 1; i < columns; i++) {
|
|
323
|
-
if (weights[i] < weights[target]) target = i;
|
|
324
|
-
}
|
|
325
|
-
groups[target].push(line);
|
|
326
|
-
weights[target] += Math.max(20, weightedTextLength(line));
|
|
327
|
-
}
|
|
328
|
-
return groups;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
function splitCardsByWeight(cards, columns) {
|
|
332
|
-
const groups = Array.from({ length: columns }, () => []);
|
|
333
|
-
const weights = Array(columns).fill(0);
|
|
334
|
-
for (const card of cards) {
|
|
335
|
-
let target = 0;
|
|
336
|
-
for (let i = 1; i < columns; i++) {
|
|
337
|
-
if (weights[i] < weights[target]) target = i;
|
|
338
|
-
}
|
|
339
|
-
groups[target].push(card);
|
|
340
|
-
const cardWeight = weightedTextLength(card.title || "") * 1.4
|
|
341
|
-
+ (card.body || []).reduce((sum, line) => sum + weightedTextLength(line), 0);
|
|
342
|
-
weights[target] += Math.max(80, cardWeight);
|
|
343
|
-
}
|
|
344
|
-
return groups;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
function renderDiagnosticCard(slide, card, x, y, w, h, idx) {
|
|
348
|
-
slide.addShape("rect", {
|
|
349
|
-
x,
|
|
350
|
-
y,
|
|
351
|
-
w,
|
|
352
|
-
h,
|
|
353
|
-
fill: { color: idx % 2 ? "F8FAF9" : "F3F6F4", transparency: 2 },
|
|
354
|
-
line: { color: "D6E2DB", width: 0.35, transparency: 12 },
|
|
355
|
-
});
|
|
356
|
-
slide.addShape("rect", {
|
|
357
|
-
x,
|
|
358
|
-
y,
|
|
359
|
-
w: 0.028,
|
|
360
|
-
h,
|
|
361
|
-
fill: { color: SECTION_ACCENT, transparency: 0 },
|
|
362
|
-
line: { color: SECTION_ACCENT, transparency: 100 },
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
const padX = 0.055;
|
|
366
|
-
const padY = 0.05;
|
|
367
|
-
const titleH = card.title ? Math.min(0.28, Math.max(0.18, h * 0.16)) : 0;
|
|
368
|
-
if (card.title) {
|
|
369
|
-
slide.addText(card.title, {
|
|
370
|
-
x: x + padX,
|
|
371
|
-
y: y + padY,
|
|
372
|
-
w: w - padX * 1.45,
|
|
373
|
-
h: titleH,
|
|
374
|
-
fontFace: FONT_BOLD,
|
|
375
|
-
fontSize: 7.4,
|
|
376
|
-
color: "002554",
|
|
377
|
-
charSpacing: -0.25,
|
|
378
|
-
fit: "shrink",
|
|
379
|
-
margin: 0.01,
|
|
380
|
-
breakLine: false,
|
|
381
|
-
valign: "top",
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
const bodyText = (card.body || []).join("\n");
|
|
386
|
-
if (bodyText) {
|
|
387
|
-
slide.addText(bodyText, {
|
|
388
|
-
x: x + padX,
|
|
389
|
-
y: y + padY + titleH + 0.015,
|
|
390
|
-
w: w - padX * 1.45,
|
|
391
|
-
h: Math.max(0.16, h - titleH - padY * 1.6),
|
|
392
|
-
fontFace: FONT_MEDIUM,
|
|
393
|
-
fontSize: 5.85,
|
|
394
|
-
color: "1F1F1F",
|
|
395
|
-
charSpacing: -0.18,
|
|
396
|
-
fit: "shrink",
|
|
397
|
-
margin: 0.008,
|
|
398
|
-
paraSpaceAfterPt: 0,
|
|
399
|
-
lineSpacingMultiple: 0.96,
|
|
400
|
-
breakLine: false,
|
|
401
|
-
valign: "top",
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
function estimateDiagnosticCardHeight(card, w) {
|
|
407
|
-
const padY = 0.05;
|
|
408
|
-
const titleH = card.title ? 0.24 : 0;
|
|
409
|
-
const bodyText = (card.body || []).join("\n");
|
|
410
|
-
const bodyH = bodyText ? estimateTextHeight(bodyText, Math.max(0.8, w - 0.12), 5.85, 1.02) : 0;
|
|
411
|
-
return Math.max(0.56, Math.min(2.35, padY * 2 + titleH + bodyH));
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
function renderCramStressBody(slide, items, pageH) {
|
|
415
|
-
const cards = bodyItemsToDiagnosticCards(items);
|
|
416
|
-
const columns = 3;
|
|
417
|
-
const groups = cards.length ? splitCardsByWeight(cards, columns) : splitLinesByWeight(bodyItemsToDiagnosticLines(items), columns).map((group) => [{ title: "", body: group }]);
|
|
418
|
-
const x = 0.28;
|
|
419
|
-
const y = 0.98;
|
|
420
|
-
const w = 7.78;
|
|
421
|
-
const h = pageH - y - 0.34;
|
|
422
|
-
const gap = 0.105;
|
|
423
|
-
const colW = (w - gap * (columns - 1)) / columns;
|
|
424
|
-
groups.forEach((group, idx) => {
|
|
425
|
-
const colX = x + idx * (colW + gap);
|
|
426
|
-
const cardGap = 0.075;
|
|
427
|
-
const availableH = h - cardGap * Math.max(0, group.length - 1);
|
|
428
|
-
const desiredHeights = group.map((card) => estimateDiagnosticCardHeight(card, colW));
|
|
429
|
-
const desiredTotal = desiredHeights.reduce((sum, value) => sum + value, 0) || 1;
|
|
430
|
-
const scale = desiredTotal > availableH ? availableH / desiredTotal : 1;
|
|
431
|
-
let cursorY = y;
|
|
432
|
-
group.forEach((card, cardIdx) => {
|
|
433
|
-
const remainingCards = group.length - cardIdx - 1;
|
|
434
|
-
const rawH = desiredHeights[cardIdx] * scale;
|
|
435
|
-
const cardH = Math.max(0.62, Math.min(rawH, h - (cursorY - y) - remainingCards * (0.62 + cardGap)));
|
|
436
|
-
renderDiagnosticCard(slide, card, colX, cursorY, colW, cardH, idx + cardIdx);
|
|
437
|
-
cursorY += cardH + cardGap;
|
|
438
|
-
});
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
function renderCramStressImageSheet(slide, content, sourceDir, pageW, pageH) {
|
|
443
|
-
if (!content) return;
|
|
444
|
-
const images = content.type === "image-grid"
|
|
445
|
-
? (Array.isArray(content.images) ? content.images : [])
|
|
446
|
-
: content.type === "image" ? [content] : [];
|
|
447
|
-
if (images.length === 0) return;
|
|
448
|
-
const x = 8.24;
|
|
449
|
-
const y = 0.98;
|
|
450
|
-
const w = pageW - x - 0.25;
|
|
451
|
-
const h = pageH - y - 0.34;
|
|
452
|
-
const cols = images.length >= 8 ? 2 : 1;
|
|
453
|
-
const rows = Math.ceil(images.length / cols);
|
|
454
|
-
const gap = 0.04;
|
|
455
|
-
const cellW = (w - gap * (cols - 1)) / cols;
|
|
456
|
-
const headerH = 0.28;
|
|
457
|
-
const sheetH = Math.min(h - 0.9, rows * 0.58 + gap * (rows - 1));
|
|
458
|
-
const cellH = (sheetH - gap * (rows - 1)) / rows;
|
|
459
|
-
const noteY = y + headerH + sheetH + 0.16;
|
|
460
|
-
const noteH = Math.min(1.0, h - (noteY - y) - 0.08);
|
|
461
|
-
const panelH = noteY - y + noteH;
|
|
462
|
-
slide.addShape("rect", {
|
|
463
|
-
x: x - 0.04,
|
|
464
|
-
y: y - 0.04,
|
|
465
|
-
w: w + 0.08,
|
|
466
|
-
h: panelH + 0.08,
|
|
467
|
-
fill: { color: "F5F8F6", transparency: 0 },
|
|
468
|
-
line: { color: "CAD4CE", width: 0.5 },
|
|
469
|
-
});
|
|
470
|
-
slide.addText("RAW EVIDENCE CONTACT SHEET", {
|
|
471
|
-
x,
|
|
472
|
-
y: y + 0.02,
|
|
473
|
-
w,
|
|
474
|
-
h: 0.16,
|
|
475
|
-
fontFace: FONT_BOLD,
|
|
476
|
-
fontSize: 5.8,
|
|
477
|
-
color: "002554",
|
|
478
|
-
charSpacing: -0.15,
|
|
479
|
-
fit: "shrink",
|
|
480
|
-
margin: 0,
|
|
481
|
-
});
|
|
482
|
-
slide.addShape("rect", {
|
|
483
|
-
x,
|
|
484
|
-
y: y + 0.22,
|
|
485
|
-
w,
|
|
486
|
-
h: 0,
|
|
487
|
-
line: { color: SECTION_ACCENT, width: 0.45 },
|
|
488
|
-
});
|
|
489
|
-
images.forEach((image, idx) => {
|
|
490
|
-
const imgPath = resolveImagePath(image.src, sourceDir);
|
|
491
|
-
const col = idx % cols;
|
|
492
|
-
const row = Math.floor(idx / cols);
|
|
493
|
-
const fit = containBox(
|
|
494
|
-
x + col * (cellW + gap),
|
|
495
|
-
y + headerH + row * (cellH + gap),
|
|
496
|
-
cellW,
|
|
497
|
-
cellH,
|
|
498
|
-
readImageSize(imgPath),
|
|
499
|
-
);
|
|
500
|
-
slide.addImage({
|
|
501
|
-
path: imgPath,
|
|
502
|
-
...fit,
|
|
503
|
-
altText: image.caption || path.basename(image.src || imgPath),
|
|
504
|
-
});
|
|
505
|
-
});
|
|
506
|
-
slide.addShape("rect", {
|
|
507
|
-
x,
|
|
508
|
-
y: noteY,
|
|
509
|
-
w,
|
|
510
|
-
h: noteH,
|
|
511
|
-
fill: { color: "FFFFFF", transparency: 0 },
|
|
512
|
-
line: { color: "D6E2DB", width: 0.35, transparency: 20 },
|
|
513
|
-
});
|
|
514
|
-
slide.addText("diagnostic cram-stress\nmicrotext is intentional\nproduction decks must synthesize\noverflow / marker / missing media = fail", {
|
|
515
|
-
x: x + 0.06,
|
|
516
|
-
y: noteY + 0.07,
|
|
517
|
-
w: w - 0.12,
|
|
518
|
-
h: Math.max(0.24, noteH - 0.14),
|
|
519
|
-
fontFace: FONT_MEDIUM,
|
|
520
|
-
fontSize: 5.3,
|
|
521
|
-
color: "333333",
|
|
522
|
-
charSpacing: -0.15,
|
|
523
|
-
fit: "shrink",
|
|
524
|
-
margin: 0.01,
|
|
525
|
-
breakLine: false,
|
|
526
|
-
lineSpacingMultiple: 1.02,
|
|
527
|
-
valign: "mid",
|
|
528
|
-
});
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
function isCramStressDeck(resolved) {
|
|
532
|
-
const meta = (resolved && resolved.deck && resolved.deck.metadata) || {};
|
|
533
|
-
return String(meta.render_mode || meta.renderMode || "").toLowerCase() === "cram-stress";
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
function renderCramStressSlide(slide, slideSpec, pageW, pageH, sourceDir) {
|
|
537
|
-
const regions = slideSpec.regions || {};
|
|
538
|
-
const title = regions.title && regions.title.content;
|
|
539
|
-
if (title) {
|
|
540
|
-
slide.addText(String(title), {
|
|
541
|
-
x: 0.24,
|
|
542
|
-
y: 0.24,
|
|
543
|
-
w: pageW - 1.72,
|
|
544
|
-
h: 0.46,
|
|
545
|
-
fontFace: FONT_BOLD,
|
|
546
|
-
fontSize: 13.5,
|
|
547
|
-
color: "000000",
|
|
548
|
-
charSpacing: CHAR_SPACING,
|
|
549
|
-
fit: "shrink",
|
|
550
|
-
});
|
|
551
|
-
slide.addShape("rect", {
|
|
552
|
-
x: 0.24,
|
|
553
|
-
y: 0.76,
|
|
554
|
-
w: pageW - 0.48,
|
|
555
|
-
h: 0,
|
|
556
|
-
line: { color: SECTION_ACCENT, width: 1.2 },
|
|
557
|
-
});
|
|
558
|
-
}
|
|
559
|
-
if (regions.body && regions.body.content && regions.body.content.type === "body") {
|
|
560
|
-
renderCramStressBody(slide, regions.body.content.items || [], pageH);
|
|
561
|
-
}
|
|
562
|
-
if (regions.image && regions.image.content) {
|
|
563
|
-
renderCramStressImageSheet(slide, regions.image.content, sourceDir, pageW, pageH);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
function shouldUseDenseBodyFlow(items, widthIn, startY, bottomY) {
|
|
568
|
-
const elements = bodyElements(items);
|
|
569
|
-
const charCount = elements.reduce((sum, el) => sum + String(el.text || "").length, 0);
|
|
570
|
-
const available = Math.max(0.5, (bottomY || 7.0) - startY);
|
|
571
|
-
const estimatedAtMin = elements.reduce((sum, el) => {
|
|
572
|
-
const size = el.kind === "section" ? 12.8 : 11.5;
|
|
573
|
-
return sum + estimateTextHeight(el.text, Math.max(1.8, widthIn / 2), size, 1.02) + 0.025;
|
|
574
|
-
}, 0);
|
|
575
|
-
return widthIn >= 4.4 && (elements.length >= 18 || charCount >= 2400 || estimatedAtMin > available * 1.35);
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
function chooseDenseBodyStyle() {
|
|
579
|
-
return { bodySize: 11.5, sectionSize: 12.8, lineSpacing: 1.02, gap: 0.025 };
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
// ── Font role helpers ───────────────────────────────────────────────────────
|
|
583
|
-
|
|
584
|
-
/**
|
|
585
|
-
* Build font role map from template capabilities.
|
|
586
|
-
* Maps YAML field names to pptxgenjs option names.
|
|
587
|
-
*/
|
|
588
|
-
function buildFontRoles(templateObj) {
|
|
589
|
-
if (!templateObj || !templateObj.capabilities || !templateObj.capabilities.font_roles) {
|
|
590
|
-
return {};
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
const roles = {};
|
|
594
|
-
for (const [name, def] of Object.entries(templateObj.capabilities.font_roles)) {
|
|
595
|
-
roles[name] = {
|
|
596
|
-
font: def.font,
|
|
597
|
-
size: def.size,
|
|
598
|
-
color: (def.color || "#000000").replace("#", ""),
|
|
599
|
-
align: def.align || "left",
|
|
600
|
-
charSpacing: def.char_spacing,
|
|
601
|
-
lineSpacing: def.line_spacing,
|
|
602
|
-
bold: def.bold,
|
|
603
|
-
spaceAfter: def.space_after,
|
|
604
|
-
bulletType: def.bullet_type,
|
|
605
|
-
bulletChar: def.bullet_char,
|
|
606
|
-
bulletFont: def.bullet_font,
|
|
607
|
-
numberType: def.number_type,
|
|
608
|
-
marL: def.marL,
|
|
609
|
-
indent: def.indent,
|
|
610
|
-
fill: def.fill ? def.fill.replace("#", "") : undefined,
|
|
611
|
-
borderColor: def.border_color ? def.border_color.replace("#", "") : undefined,
|
|
612
|
-
borderWidth: def.border_width,
|
|
613
|
-
};
|
|
614
|
-
}
|
|
615
|
-
return roles;
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
// ── Decoration rendering ────────────────────────────────────────────────────
|
|
619
|
-
|
|
620
|
-
/**
|
|
621
|
-
* Add decoration elements to a pptxgenjs slide.
|
|
622
|
-
* Note: slide_number is intentionally omitted — the reference template has none.
|
|
623
|
-
*/
|
|
624
|
-
function addDecorations(slide, decorations, fontRoles) {
|
|
625
|
-
if (!decorations || !Array.isArray(decorations)) return;
|
|
626
|
-
|
|
627
|
-
for (const d of decorations) {
|
|
628
|
-
if (d.type === "image") {
|
|
629
|
-
slide.addImage({
|
|
630
|
-
path: decorAssetPath(d),
|
|
631
|
-
x: d.x || 0,
|
|
632
|
-
y: d.y || 0,
|
|
633
|
-
w: d.w || 1,
|
|
634
|
-
h: d.h || 1,
|
|
635
|
-
rotate: d.rotate || undefined,
|
|
636
|
-
altText: d.asset || "template-image",
|
|
637
|
-
});
|
|
638
|
-
|
|
639
|
-
} else if (d.type === "rect" || d.type === "roundRect" || d.type === "pill") {
|
|
640
|
-
// Solid color block / rounded card / full-radius pill. Optional text label.
|
|
641
|
-
const shape = d.type === "rect" ? "rect" : "roundRect";
|
|
642
|
-
const opts = {
|
|
643
|
-
x: d.x || 0, y: d.y || 0, w: d.w || 1, h: d.h || 0.5,
|
|
644
|
-
};
|
|
645
|
-
if (d.fill) opts.fill = { color: hex(d.fill), transparency: d.alpha || 0 };
|
|
646
|
-
else opts.fill = { type: "none" };
|
|
647
|
-
if (d.line) opts.line = { color: hex(d.line), width: d.line_width || 1 };
|
|
648
|
-
else opts.line = { type: "none" };
|
|
649
|
-
if (shape === "roundRect") opts.rectRadius = d.type === "pill" ? (d.h || 0.5) / 2 : (d.radius != null ? d.radius : 0.12);
|
|
650
|
-
slide.addShape(shape, opts);
|
|
651
|
-
if (d.text) {
|
|
652
|
-
slide.addText(d.text, {
|
|
653
|
-
x: d.x || 0, y: d.y || 0, w: d.w || 1, h: d.h || 0.5,
|
|
654
|
-
fontFace: d.font || FONT_BOLD, fontSize: d.size || 12,
|
|
655
|
-
color: hex(d.text_color || "#FFFFFF"), bold: !!d.bold,
|
|
656
|
-
align: d.align || "center", valign: "middle",
|
|
657
|
-
charSpacing: d.char_spacing != null ? d.char_spacing : CHAR_SPACING,
|
|
658
|
-
});
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
} else if (d.type === "ellipse" || d.type === "ring") {
|
|
662
|
-
const opts = { x: d.x || 0, y: d.y || 0, w: d.w || 1, h: d.h || (d.w || 1) };
|
|
663
|
-
if (d.type === "ring" || !d.fill) {
|
|
664
|
-
opts.fill = { type: "none" };
|
|
665
|
-
opts.line = { color: hex(d.line || d.color || "#FFFFFF"), width: d.line_width || 2 };
|
|
666
|
-
} else {
|
|
667
|
-
opts.fill = { color: hex(d.fill), transparency: d.alpha || 0 };
|
|
668
|
-
opts.line = { type: "none" };
|
|
669
|
-
}
|
|
670
|
-
slide.addShape("ellipse", opts);
|
|
671
|
-
|
|
672
|
-
} else if (d.type === "text") {
|
|
673
|
-
slide.addText(d.text || "", {
|
|
674
|
-
x: d.x || 0, y: d.y || 0, w: d.w || 4, h: d.h || 1,
|
|
675
|
-
fontFace: d.font || FONT_BOLD, fontSize: d.size || 14,
|
|
676
|
-
color: hex(d.color || "#000000"), bold: !!d.bold,
|
|
677
|
-
align: d.align || "left", valign: d.valign || "top",
|
|
678
|
-
charSpacing: d.char_spacing != null ? d.char_spacing : CHAR_SPACING,
|
|
679
|
-
transparency: d.transparency != null ? d.transparency : undefined,
|
|
680
|
-
});
|
|
681
|
-
|
|
682
|
-
} else if (d.type === "line") {
|
|
683
|
-
slide.addShape("rect", {
|
|
684
|
-
x: d.x || 0,
|
|
685
|
-
y: d.y || 0,
|
|
686
|
-
w: d.w || 1,
|
|
687
|
-
h: 0,
|
|
688
|
-
line: { color: (d.color || "#000000").replace("#", ""), width: d.width || 1 },
|
|
689
|
-
});
|
|
690
|
-
|
|
691
|
-
} else if (d.type === "confidential_mark") {
|
|
692
|
-
const role = fontRoles.confidential_mark || {};
|
|
693
|
-
const mX = d.x || 0, mY = d.y || 0, mW = d.w || 1, mH = d.h || 0.3;
|
|
694
|
-
const bColor = role.borderColor || "C00000";
|
|
695
|
-
const bPt = role.borderWidth || 1;
|
|
696
|
-
slide.addShape("rect", {
|
|
697
|
-
x: mX, y: mY, w: mW, h: mH,
|
|
698
|
-
line: { color: bColor, width: bPt },
|
|
699
|
-
});
|
|
700
|
-
slide.addText(d.text || "내부 검토", {
|
|
701
|
-
x: mX, y: mY, w: mW, h: mH,
|
|
702
|
-
fontFace: role.font || FONT_MEDIUM,
|
|
703
|
-
fontSize: role.size || 14,
|
|
704
|
-
color: role.color || "C00000",
|
|
705
|
-
align: role.align || "center",
|
|
706
|
-
valign: "middle",
|
|
707
|
-
});
|
|
708
|
-
|
|
709
|
-
} else if (d.type === "disclaimer") {
|
|
710
|
-
const role = fontRoles.disclaimer || {};
|
|
711
|
-
slide.addText(
|
|
712
|
-
d.text || DEFAULT_DISCLAIMER,
|
|
713
|
-
{
|
|
714
|
-
x: d.x || 0,
|
|
715
|
-
y: d.y || 0,
|
|
716
|
-
w: d.w || 1,
|
|
717
|
-
h: d.h || 0.2,
|
|
718
|
-
fontFace: role.font || FONT_LIGHT,
|
|
719
|
-
fontSize: role.size || 6,
|
|
720
|
-
color: role.color || "666666",
|
|
721
|
-
align: role.align || "left",
|
|
722
|
-
}
|
|
723
|
-
);
|
|
724
|
-
}
|
|
725
|
-
// slide_number intentionally removed — reference template has none
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
// ── Body shape rendering (individual shapes per line) ────────────────────
|
|
730
|
-
|
|
731
|
-
/**
|
|
732
|
-
* Add body items as individual shapes to a slide.
|
|
733
|
-
* Each section header and bullet item is a separate positioned shape.
|
|
734
|
-
*
|
|
735
|
-
* @param {object} slide - pptxgenjs slide
|
|
736
|
-
* @param {Array} items - AST items (section, bullet, numbered, text)
|
|
737
|
-
* @param {number} startY - Starting y position
|
|
738
|
-
* @param {number} maxX - Max width for positioning
|
|
739
|
-
* @returns {number} The y position after the last item
|
|
740
|
-
*/
|
|
741
|
-
function addBodyShapes(slide, items, startY, maxX, bottomY, originX) {
|
|
742
|
-
const denseFlow = shouldUseDenseBodyFlow(items, maxX, startY, bottomY || 7.0);
|
|
743
|
-
const columns = denseFlow ? 2 : 1;
|
|
744
|
-
const colGap = denseFlow ? 0.16 : 0;
|
|
745
|
-
const colW = (maxX - colGap * (columns - 1)) / columns;
|
|
746
|
-
let col = 0;
|
|
747
|
-
let y = startY;
|
|
748
|
-
// Template regions have always drawn body text at the template's own left
|
|
749
|
-
// margin. A placed box carries its own, and passes it here.
|
|
750
|
-
const baseX = originX != null ? originX : SEC_HEADER_X;
|
|
751
|
-
const bodyIndent = denseFlow ? 0.13 : (BULLET_X - SEC_HEADER_X);
|
|
752
|
-
const BODY_FULL_W = 10.188;
|
|
753
|
-
const scale = colW / BODY_FULL_W;
|
|
754
|
-
const secW = denseFlow ? Math.max(1.4, colW - 0.04) : SEC_HEADER_W * scale;
|
|
755
|
-
const bulletW = denseFlow ? Math.max(1.2, colW - bodyIndent - 0.02) : BULLET_W * scale;
|
|
756
|
-
let numberedCounter = 0;
|
|
757
|
-
const style = denseFlow ? chooseDenseBodyStyle() : chooseBodyStyle(items, bulletW, startY, bottomY || 7.0);
|
|
758
|
-
|
|
759
|
-
function colX() { return baseX + col * (colW + colGap); }
|
|
760
|
-
function secX() { return colX(); }
|
|
761
|
-
function bulletX() { return colX() + bodyIndent; }
|
|
762
|
-
function maybeAdvanceColumn(nextHeight) {
|
|
763
|
-
if (!denseFlow || col >= columns - 1) return;
|
|
764
|
-
if (y + nextHeight > (bottomY || 7.0)) {
|
|
765
|
-
col++;
|
|
766
|
-
y = startY;
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
for (const item of items) {
|
|
771
|
-
if (item.type === "section") {
|
|
772
|
-
const sectionH = estimateTextHeight(item.heading, secW, style.sectionSize, style.lineSpacing);
|
|
773
|
-
const cardH = estimateSectionHeight(item, secW, bulletW, style);
|
|
774
|
-
maybeAdvanceColumn(Math.min(cardH, sectionH + 0.2));
|
|
775
|
-
addSectionCard(slide, secX() - 0.035, y - 0.03, Math.max(secW, bulletW + bodyIndent) + 0.07, cardH);
|
|
776
|
-
// Section header — separate shape, black, 1.5 line spacing
|
|
777
|
-
slide.addText(mdRuns(item.heading, {
|
|
778
|
-
fontFace: SEC_HEADER_FONT,
|
|
779
|
-
fontSize: style.sectionSize,
|
|
780
|
-
color: SEC_HEADER_COLOR,
|
|
781
|
-
charSpacing: CHAR_SPACING,
|
|
782
|
-
}), {
|
|
783
|
-
x: secX(),
|
|
784
|
-
y: y,
|
|
785
|
-
w: secW,
|
|
786
|
-
h: sectionH,
|
|
787
|
-
lineSpacingMultiple: style.lineSpacing,
|
|
788
|
-
});
|
|
789
|
-
y += sectionH + style.gap;
|
|
790
|
-
|
|
791
|
-
// Numbered children with (1)(2)(3) format
|
|
792
|
-
if (item.children) {
|
|
793
|
-
numberedCounter = 0;
|
|
794
|
-
for (const child of item.children) {
|
|
795
|
-
numberedCounter++;
|
|
796
|
-
const numPrefix = `(${numberedCounter}) `;
|
|
797
|
-
const text = numPrefix + cleanMd(child.text);
|
|
798
|
-
const h = estimateTextHeight(text, bulletW, style.bodySize, style.lineSpacing);
|
|
799
|
-
maybeAdvanceColumn(h);
|
|
800
|
-
slide.addText([
|
|
801
|
-
{ text: numPrefix, options: { fontFace: FONT_MEDIUM, fontSize: style.bodySize, color: "000000", charSpacing: CHAR_SPACING } },
|
|
802
|
-
...mdRuns(child.text, { fontFace: FONT_MEDIUM, fontSize: style.bodySize, color: "000000", charSpacing: CHAR_SPACING }),
|
|
803
|
-
], {
|
|
804
|
-
x: bulletX(),
|
|
805
|
-
y: y,
|
|
806
|
-
w: bulletW,
|
|
807
|
-
h,
|
|
808
|
-
lineSpacingMultiple: style.lineSpacing,
|
|
809
|
-
});
|
|
810
|
-
y += h + style.gap;
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
y += 0.08;
|
|
814
|
-
|
|
815
|
-
} else if (item.type === "bullet") {
|
|
816
|
-
const h = estimateTextHeight(item.text, bulletW, style.bodySize, style.lineSpacing);
|
|
817
|
-
maybeAdvanceColumn(h);
|
|
818
|
-
// Bullet item — green prefix + black text
|
|
819
|
-
slide.addText([
|
|
820
|
-
{ text: BULLET_PREFIX, options: { fontFace: FONT_BOLD, fontSize: style.bodySize, color: BULLET_COLOR, charSpacing: CHAR_SPACING } },
|
|
821
|
-
...mdRuns(item.text, { fontFace: FONT_MEDIUM, fontSize: style.bodySize, color: "000000", charSpacing: CHAR_SPACING }),
|
|
822
|
-
], {
|
|
823
|
-
x: bulletX(),
|
|
824
|
-
y: y,
|
|
825
|
-
w: bulletW,
|
|
826
|
-
h,
|
|
827
|
-
lineSpacingMultiple: style.lineSpacing,
|
|
828
|
-
});
|
|
829
|
-
y += h + style.gap;
|
|
830
|
-
|
|
831
|
-
// Children (numbered sub-items)
|
|
832
|
-
if (item.children) {
|
|
833
|
-
numberedCounter = 0;
|
|
834
|
-
for (const child of item.children) {
|
|
835
|
-
numberedCounter++;
|
|
836
|
-
const numPrefix = `(${numberedCounter}) `;
|
|
837
|
-
const text = numPrefix + cleanMd(child.text);
|
|
838
|
-
const childH = estimateTextHeight(text, bulletW, style.bodySize, style.lineSpacing);
|
|
839
|
-
maybeAdvanceColumn(childH);
|
|
840
|
-
slide.addText([
|
|
841
|
-
{ text: numPrefix, options: { fontFace: FONT_MEDIUM, fontSize: style.bodySize, color: "000000", charSpacing: CHAR_SPACING } },
|
|
842
|
-
...mdRuns(child.text, { fontFace: FONT_MEDIUM, fontSize: style.bodySize, color: "000000", charSpacing: CHAR_SPACING }),
|
|
843
|
-
], {
|
|
844
|
-
x: bulletX(),
|
|
845
|
-
y: y,
|
|
846
|
-
w: bulletW,
|
|
847
|
-
h: childH,
|
|
848
|
-
lineSpacingMultiple: style.lineSpacing,
|
|
849
|
-
});
|
|
850
|
-
y += childH + style.gap;
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
} else if (item.type === "numbered") {
|
|
855
|
-
numberedCounter++;
|
|
856
|
-
const numPrefix = `(${numberedCounter}) `;
|
|
857
|
-
const text = numPrefix + cleanMd(item.text);
|
|
858
|
-
const h = estimateTextHeight(text, bulletW, style.bodySize, style.lineSpacing);
|
|
859
|
-
maybeAdvanceColumn(h);
|
|
860
|
-
slide.addText([
|
|
861
|
-
{ text: numPrefix, options: { fontFace: FONT_MEDIUM, fontSize: style.bodySize, color: "000000", charSpacing: CHAR_SPACING } },
|
|
862
|
-
...mdRuns(item.text, { fontFace: FONT_MEDIUM, fontSize: style.bodySize, color: "000000", charSpacing: CHAR_SPACING }),
|
|
863
|
-
], {
|
|
864
|
-
x: bulletX(),
|
|
865
|
-
y: y,
|
|
866
|
-
w: bulletW,
|
|
867
|
-
h,
|
|
868
|
-
lineSpacingMultiple: style.lineSpacing,
|
|
869
|
-
});
|
|
870
|
-
y += h + style.gap;
|
|
871
|
-
|
|
872
|
-
} else if (item.type === "text") {
|
|
873
|
-
const h = estimateTextHeight(item.text, bulletW, style.bodySize, style.lineSpacing);
|
|
874
|
-
maybeAdvanceColumn(h);
|
|
875
|
-
slide.addText(mdRuns(item.text, {
|
|
876
|
-
fontFace: FONT_MEDIUM,
|
|
877
|
-
fontSize: style.bodySize,
|
|
878
|
-
color: "000000",
|
|
879
|
-
charSpacing: CHAR_SPACING,
|
|
880
|
-
}), {
|
|
881
|
-
x: bulletX(),
|
|
882
|
-
y: y,
|
|
883
|
-
w: bulletW,
|
|
884
|
-
h,
|
|
885
|
-
lineSpacingMultiple: style.lineSpacing,
|
|
886
|
-
});
|
|
887
|
-
y += h + style.gap;
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
return y;
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
/**
|
|
895
|
-
* Add summary group content as individual shapes.
|
|
896
|
-
* Uses narrow width (~4.3" for headers, ~4.1" for bullets).
|
|
897
|
-
*/
|
|
898
|
-
function addSummaryShapes(slide, content, region) {
|
|
899
|
-
let y = region.y || 1.04;
|
|
900
|
-
|
|
901
|
-
// Group heading
|
|
902
|
-
if (content.heading) {
|
|
903
|
-
slide.addText(mdRuns(content.heading, {
|
|
904
|
-
fontFace: SEC_HEADER_FONT,
|
|
905
|
-
fontSize: SEC_HEADER_SIZE,
|
|
906
|
-
color: SEC_HEADER_COLOR,
|
|
907
|
-
charSpacing: CHAR_SPACING,
|
|
908
|
-
}), {
|
|
909
|
-
x: region.x || 0.39,
|
|
910
|
-
y: y,
|
|
911
|
-
w: SUMMARY_HEADER_W,
|
|
912
|
-
h: SEC_HEADER_H,
|
|
913
|
-
});
|
|
914
|
-
y += SEC_HEADER_H + 0.08;
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
// Items
|
|
918
|
-
if (content.items) {
|
|
919
|
-
for (const item of content.items) {
|
|
920
|
-
if (item.type === "section") {
|
|
921
|
-
// Section header
|
|
922
|
-
slide.addText(mdRuns(item.heading, {
|
|
923
|
-
fontFace: SEC_HEADER_FONT,
|
|
924
|
-
fontSize: SEC_HEADER_SIZE,
|
|
925
|
-
color: SEC_HEADER_COLOR,
|
|
926
|
-
charSpacing: CHAR_SPACING,
|
|
927
|
-
}), {
|
|
928
|
-
x: region.x || 0.39,
|
|
929
|
-
y: y,
|
|
930
|
-
w: SUMMARY_HEADER_W,
|
|
931
|
-
h: SEC_HEADER_H,
|
|
932
|
-
});
|
|
933
|
-
y += SEC_HEADER_H + 0.05;
|
|
934
|
-
|
|
935
|
-
// Children as bullet items
|
|
936
|
-
if (item.children) {
|
|
937
|
-
for (const child of item.children) {
|
|
938
|
-
slide.addText([
|
|
939
|
-
{ text: BULLET_PREFIX, options: { fontFace: FONT_BOLD, fontSize: SUMMARY_BULLET_SIZE, color: BULLET_COLOR, charSpacing: CHAR_SPACING } },
|
|
940
|
-
...mdRuns(child.text, { fontFace: FONT_MEDIUM, fontSize: SUMMARY_BULLET_SIZE, color: "000000", charSpacing: CHAR_SPACING }),
|
|
941
|
-
], {
|
|
942
|
-
x: 0.59,
|
|
943
|
-
y: y,
|
|
944
|
-
w: SUMMARY_BULLET_W,
|
|
945
|
-
h: SUMMARY_BULLET_H,
|
|
946
|
-
lineSpacingMultiple: SUMMARY_BULLET_LINE_SPACING,
|
|
947
|
-
});
|
|
948
|
-
y += SUMMARY_BULLET_Y_SPACING;
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
} else if (item.type === "bullet") {
|
|
953
|
-
slide.addText([
|
|
954
|
-
{ text: BULLET_PREFIX, options: { fontFace: FONT_BOLD, fontSize: SUMMARY_BULLET_SIZE, color: BULLET_COLOR, charSpacing: CHAR_SPACING } },
|
|
955
|
-
...mdRuns(item.text, { fontFace: FONT_MEDIUM, fontSize: SUMMARY_BULLET_SIZE, color: "000000", charSpacing: CHAR_SPACING }),
|
|
956
|
-
], {
|
|
957
|
-
x: 0.59,
|
|
958
|
-
y: y,
|
|
959
|
-
w: SUMMARY_BULLET_W,
|
|
960
|
-
h: SUMMARY_BULLET_H,
|
|
961
|
-
lineSpacingMultiple: SUMMARY_BULLET_LINE_SPACING,
|
|
962
|
-
});
|
|
963
|
-
y += SUMMARY_BULLET_Y_SPACING;
|
|
964
|
-
|
|
965
|
-
if (item.children) {
|
|
966
|
-
for (const child of item.children) {
|
|
967
|
-
slide.addText([
|
|
968
|
-
{ text: BULLET_PREFIX, options: { fontFace: FONT_BOLD, fontSize: SUMMARY_BULLET_SIZE, color: BULLET_COLOR, charSpacing: CHAR_SPACING } },
|
|
969
|
-
...mdRuns(child.text, { fontFace: FONT_MEDIUM, fontSize: SUMMARY_BULLET_SIZE, color: "000000", charSpacing: CHAR_SPACING }),
|
|
970
|
-
], {
|
|
971
|
-
x: 0.59,
|
|
972
|
-
y: y,
|
|
973
|
-
w: SUMMARY_BULLET_W,
|
|
974
|
-
h: SUMMARY_BULLET_H,
|
|
975
|
-
lineSpacingMultiple: SUMMARY_BULLET_LINE_SPACING,
|
|
976
|
-
});
|
|
977
|
-
y += SUMMARY_BULLET_Y_SPACING;
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
} else if (item.type === "numbered") {
|
|
982
|
-
slide.addText([
|
|
983
|
-
{ text: BULLET_PREFIX, options: { fontFace: FONT_BOLD, fontSize: SUMMARY_BULLET_SIZE, color: BULLET_COLOR, charSpacing: CHAR_SPACING } },
|
|
984
|
-
...mdRuns(item.text, { fontFace: FONT_MEDIUM, fontSize: SUMMARY_BULLET_SIZE, color: "000000", charSpacing: CHAR_SPACING }),
|
|
985
|
-
], {
|
|
986
|
-
x: 0.59,
|
|
987
|
-
y: y,
|
|
988
|
-
w: SUMMARY_BULLET_W,
|
|
989
|
-
h: SUMMARY_BULLET_H,
|
|
990
|
-
lineSpacingMultiple: SUMMARY_BULLET_LINE_SPACING,
|
|
991
|
-
});
|
|
992
|
-
y += SUMMARY_BULLET_Y_SPACING;
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
// ── TOC rendering (separate shapes per item) ─────────────────────────────
|
|
999
|
-
|
|
1000
|
-
/**
|
|
1001
|
-
* Add TOC items as separate text shapes, matching TEMPLATE-PPTX.pptx exactly.
|
|
1002
|
-
* Each item is: "N. text" in the bold face at 18pt, w=7.5, h=0.55, spaced 0.65" apart.
|
|
1003
|
-
*/
|
|
1004
|
-
function addTocShapes(slide, items) {
|
|
1005
|
-
let y = TOC_Y_START;
|
|
1006
|
-
let num = 1;
|
|
1007
|
-
|
|
1008
|
-
for (const item of items) {
|
|
1009
|
-
const text = `${num}. ${cleanMd(item.text || item.heading || "")}`;
|
|
1010
|
-
slide.addText(text, {
|
|
1011
|
-
x: TOC_X,
|
|
1012
|
-
y: y,
|
|
1013
|
-
w: TOC_W,
|
|
1014
|
-
h: TOC_H,
|
|
1015
|
-
fontFace: TOC_FONT,
|
|
1016
|
-
fontSize: TOC_SIZE,
|
|
1017
|
-
color: TOC_COLOR,
|
|
1018
|
-
charSpacing: CHAR_SPACING,
|
|
1019
|
-
});
|
|
1020
|
-
y += TOC_Y_SPACING;
|
|
1021
|
-
num++;
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
/**
|
|
1026
|
-
* Check if a slide is a TOC slide by inspecting the title region content.
|
|
1027
|
-
*/
|
|
1028
|
-
function isTocSlide(regions) {
|
|
1029
|
-
const title = regions.title;
|
|
1030
|
-
if (!title || typeof title.content !== "string") return false;
|
|
1031
|
-
return title.content.includes("목차");
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
|
-
function adjustedRegionsForImageColumn(regions) {
|
|
1035
|
-
const body = regions.body;
|
|
1036
|
-
const image = regions.image;
|
|
1037
|
-
if (!body || !image || !body.content || !image.content) return regions;
|
|
1038
|
-
if (body.content.type !== "body" || !["image", "image-grid"].includes(image.content.type)) return regions;
|
|
1039
|
-
|
|
1040
|
-
const bodyX = body.x || 0;
|
|
1041
|
-
const bodyW = body.w || 10.188;
|
|
1042
|
-
const imageX = image.x || 0;
|
|
1043
|
-
const gap = 0.25;
|
|
1044
|
-
if (imageX <= bodyX || imageX >= bodyX + bodyW) return regions;
|
|
1045
|
-
|
|
1046
|
-
const denseImageGrid = image.content.type === "image-grid" && Array.isArray(image.content.images) && image.content.images.length >= 6;
|
|
1047
|
-
return {
|
|
1048
|
-
...regions,
|
|
1049
|
-
body: {
|
|
1050
|
-
...body,
|
|
1051
|
-
w: Math.max(0.5, imageX - bodyX - gap),
|
|
1052
|
-
},
|
|
1053
|
-
image: denseImageGrid ? {
|
|
1054
|
-
...image,
|
|
1055
|
-
y: Math.min(image.y || 1.5, 1.08),
|
|
1056
|
-
h: Math.max(image.h || 3.0, 5.85),
|
|
1057
|
-
} : image,
|
|
1058
|
-
figure_caption: denseImageGrid && regions.figure_caption ? {
|
|
1059
|
-
...regions.figure_caption,
|
|
1060
|
-
y: Math.max(regions.figure_caption.y || 4.55, 7.02),
|
|
1061
|
-
} : regions.figure_caption,
|
|
1062
|
-
};
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
// ── Region rendering ────────────────────────────────────────────────────────
|
|
1066
|
-
|
|
1067
|
-
/**
|
|
1068
|
-
* Add a region as a positioned element on the slide.
|
|
1069
|
-
*
|
|
1070
|
-
* Content types:
|
|
1071
|
-
* - string: title, main-box, notes
|
|
1072
|
-
* - { type: "body" } bullet/section/numbered items → individual shapes
|
|
1073
|
-
* - { type: "kpi-table" } table with headers and rows
|
|
1074
|
-
* - { type: "image" } image element
|
|
1075
|
-
* - { type: "figure-caption" } figure caption (auto-numbered "도 N.")
|
|
1076
|
-
* - { type: "table-caption" } table caption (auto-numbered "표 N.")
|
|
1077
|
-
* - { type: "summary-group" } heading + items → individual shapes
|
|
1078
|
-
*/
|
|
1079
|
-
function addRegion(slide, name, region, fontRoles, pageH, regionBottom, isToc, sourceDir) {
|
|
1080
|
-
if (!region) return;
|
|
1081
|
-
|
|
1082
|
-
const content = region.content;
|
|
1083
|
-
if (content === undefined || content === null) return;
|
|
1084
|
-
if (typeof content === "object" && !content.type) return;
|
|
1085
|
-
|
|
1086
|
-
const roleName = region.font_role || region.heading_role || region.sub_item_role;
|
|
1087
|
-
const role = fontRoles[roleName] || {};
|
|
1088
|
-
|
|
1089
|
-
// ── String content (title, main-box, notes) ────────────────────────
|
|
1090
|
-
if (typeof content === "string") {
|
|
1091
|
-
const opts = {
|
|
1092
|
-
x: region.x || 0,
|
|
1093
|
-
y: region.y || 0,
|
|
1094
|
-
w: region.w || 4,
|
|
1095
|
-
h: region.h || ((regionBottom || (pageH - 0.5)) - (region.y || 0)),
|
|
1096
|
-
fontFace: role.font || FONT_BOLD,
|
|
1097
|
-
fontSize: role.size || 16,
|
|
1098
|
-
color: (role.color || "000000").replace("#", ""),
|
|
1099
|
-
align: region.align || role.align || "left",
|
|
1100
|
-
valign: region.valign || "top",
|
|
1101
|
-
};
|
|
1102
|
-
|
|
1103
|
-
if (role.charSpacing != null) opts.charSpacing = role.charSpacing;
|
|
1104
|
-
if (role.lineSpacing) opts.lineSpacingMultiple = role.lineSpacing;
|
|
1105
|
-
|
|
1106
|
-
// Border (main_box)
|
|
1107
|
-
if (region.border) {
|
|
1108
|
-
opts.line = {
|
|
1109
|
-
color: (region.border.color || "#000000").replace("#", ""),
|
|
1110
|
-
width: region.border.width || 1,
|
|
1111
|
-
};
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
// Autofit
|
|
1115
|
-
if (region.autofit === "resize") opts.autoFit = true;
|
|
1116
|
-
else if (region.autofit === "shrink") opts.shrinkText = true;
|
|
1117
|
-
|
|
1118
|
-
slide.addText(mdRuns(content, {
|
|
1119
|
-
fontFace: opts.fontFace,
|
|
1120
|
-
fontSize: opts.fontSize,
|
|
1121
|
-
color: opts.color,
|
|
1122
|
-
...(opts.charSpacing != null ? { charSpacing: opts.charSpacing } : {}),
|
|
1123
|
-
}), name === "title" ? { ...opts, objectName: "title@legacy" } : opts);
|
|
1124
|
-
return;
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
// ── Body content → TOC shapes or individual shapes ──────────────────
|
|
1128
|
-
if (content.type === "body") {
|
|
1129
|
-
if (isToc) {
|
|
1130
|
-
addTocShapes(slide, content.items || []);
|
|
1131
|
-
} else {
|
|
1132
|
-
const startY = region.y || 1.044;
|
|
1133
|
-
addBodyShapes(slide, content.items || [], startY, region.w || 10.188, regionBottom || pageH - 0.5, region.originX);
|
|
1134
|
-
}
|
|
1135
|
-
return;
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
// ── KPI Table ──────────────────────────────────────────────────────
|
|
1139
|
-
if (content.type === "kpi-table" && content.chart) {
|
|
1140
|
-
renderChart(slide, content, region, PLAIN_CHART_STYLE, regionBottom);
|
|
1141
|
-
return;
|
|
1142
|
-
}
|
|
1143
|
-
if (content.type === "kpi-table") {
|
|
1144
|
-
const numeric = numericColumns(content.headers || [], content.rows || []);
|
|
1145
|
-
const metrics = tableMetrics((content.rows || []).length + 1, region.y || 0, regionBottom,
|
|
1146
|
-
{ rowH: 0.35, maxRowH: 0.55, headerSize: 14, dataSize: 14 });
|
|
1147
|
-
const headerRole = fontRoles[region.font_role_header] || fontRoles.table_header;
|
|
1148
|
-
const dataRole = fontRoles[region.font_role_data] || fontRoles.table_data;
|
|
1149
|
-
|
|
1150
|
-
const tableData = [];
|
|
1151
|
-
|
|
1152
|
-
if (content.headers && content.headers.length > 0) {
|
|
1153
|
-
tableData.push(
|
|
1154
|
-
content.headers.map((h) => ({
|
|
1155
|
-
text: cleanMd(h),
|
|
1156
|
-
options: {
|
|
1157
|
-
bold: false,
|
|
1158
|
-
fontSize: headerRole ? headerRole.size : 14,
|
|
1159
|
-
fontFace: headerRole ? headerRole.font : FONT_BOLD,
|
|
1160
|
-
color: headerRole ? headerRole.color : "000000",
|
|
1161
|
-
align: numeric[content.headers.indexOf(h)] ? "right" : (headerRole ? headerRole.align : "left"),
|
|
1162
|
-
valign: "middle",
|
|
1163
|
-
...(headerRole && headerRole.charSpacing != null
|
|
1164
|
-
? { charSpacing: headerRole.charSpacing }
|
|
1165
|
-
: {}),
|
|
1166
|
-
...(headerRole && headerRole.fill
|
|
1167
|
-
? { fill: { color: headerRole.fill.replace("#", "") } }
|
|
1168
|
-
: {}),
|
|
1169
|
-
},
|
|
1170
|
-
}))
|
|
1171
|
-
);
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
if (content.rows) {
|
|
1175
|
-
for (const row of content.rows) {
|
|
1176
|
-
tableData.push(
|
|
1177
|
-
row.map((cell, ci) => linkedTableCell(cell, {
|
|
1178
|
-
fontSize: dataRole ? dataRole.size : 14,
|
|
1179
|
-
fontFace: dataRole ? dataRole.font : FONT_MEDIUM,
|
|
1180
|
-
color: dataRole ? dataRole.color : "000000",
|
|
1181
|
-
align: numeric[ci] ? "right" : (dataRole ? dataRole.align : "left"),
|
|
1182
|
-
valign: "middle",
|
|
1183
|
-
...(dataRole && dataRole.charSpacing != null
|
|
1184
|
-
? { charSpacing: dataRole.charSpacing }
|
|
1185
|
-
: {}),
|
|
1186
|
-
}))
|
|
1187
|
-
);
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
const numCols = (content.headers && content.headers.length) || 1;
|
|
1192
|
-
const tblX = region.x || 0;
|
|
1193
|
-
const tblY = region.y || 0;
|
|
1194
|
-
const tblW = region.w || 4;
|
|
1195
|
-
slide.addTable(tableData, {
|
|
1196
|
-
x: tblX,
|
|
1197
|
-
y: tblY,
|
|
1198
|
-
w: tblW,
|
|
1199
|
-
border: { pt: 1.5, color: "000000" },
|
|
1200
|
-
colW: numCols > 0 && content.headers ? proportionalWidths(content.headers, content.rows || [], tblW) : Array(numCols).fill(tblW / numCols),
|
|
1201
|
-
margin: [3.6, 7.2, 3.6, 7.2],
|
|
1202
|
-
rowH: metrics.rowH,
|
|
1203
|
-
});
|
|
1204
|
-
return;
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
// ── Image ──────────────────────────────────────────────────────────
|
|
1208
|
-
if (content.type === "image") {
|
|
1209
|
-
const imgPath = resolveImagePath(content.src, sourceDir);
|
|
1210
|
-
const fit = containBox(
|
|
1211
|
-
region.x || 0,
|
|
1212
|
-
region.y || 0,
|
|
1213
|
-
region.w || 2,
|
|
1214
|
-
region.h || 1,
|
|
1215
|
-
readImageSize(imgPath),
|
|
1216
|
-
);
|
|
1217
|
-
slide.addImage({
|
|
1218
|
-
path: imgPath,
|
|
1219
|
-
...fit,
|
|
1220
|
-
altText: content.caption || path.basename(content.src || imgPath),
|
|
1221
|
-
});
|
|
1222
|
-
return;
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
if (content.type === "image-grid") {
|
|
1226
|
-
const images = Array.isArray(content.images) ? content.images : [];
|
|
1227
|
-
const count = images.length;
|
|
1228
|
-
if (count === 0) return;
|
|
1229
|
-
const cols = count <= 2 ? count : 2;
|
|
1230
|
-
const rows = Math.ceil(count / cols);
|
|
1231
|
-
const gap = 0.06;
|
|
1232
|
-
const baseX = region.x || 0;
|
|
1233
|
-
const baseY = region.y || 0;
|
|
1234
|
-
const baseW = region.w || 4;
|
|
1235
|
-
const baseH = region.h || 3;
|
|
1236
|
-
const cellW = (baseW - gap * (cols - 1)) / cols;
|
|
1237
|
-
const cellH = (baseH - gap * (rows - 1)) / rows;
|
|
1238
|
-
images.forEach((image, idx) => {
|
|
1239
|
-
const imgPath = resolveImagePath(image.src, sourceDir);
|
|
1240
|
-
const col = idx % cols;
|
|
1241
|
-
const row = Math.floor(idx / cols);
|
|
1242
|
-
const fit = containBox(
|
|
1243
|
-
baseX + col * (cellW + gap),
|
|
1244
|
-
baseY + row * (cellH + gap),
|
|
1245
|
-
cellW,
|
|
1246
|
-
cellH,
|
|
1247
|
-
readImageSize(imgPath),
|
|
1248
|
-
);
|
|
1249
|
-
slide.addImage({
|
|
1250
|
-
path: imgPath,
|
|
1251
|
-
...fit,
|
|
1252
|
-
altText: image.caption || path.basename(image.src || imgPath),
|
|
1253
|
-
});
|
|
1254
|
-
});
|
|
1255
|
-
return;
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
// ── Figure Caption ────────────────────────────────────────────────
|
|
1259
|
-
if (content.type === "figure-caption") {
|
|
1260
|
-
const num = content.number || 1;
|
|
1261
|
-
const prefix = content.prefix || "도";
|
|
1262
|
-
const captionText = content.caption ? `${prefix} ${num}. ${cleanMd(content.caption)}` : `${prefix} ${num}.`;
|
|
1263
|
-
const captionRole = fontRoles.figure_caption || {};
|
|
1264
|
-
const captionW = region.w || 4;
|
|
1265
|
-
const captionSize = captionRole.size || 16;
|
|
1266
|
-
const captionSpacing = captionRole.lineSpacing || 1.3;
|
|
1267
|
-
slide.addText(captionText, {
|
|
1268
|
-
x: region.x || 0,
|
|
1269
|
-
y: region.y || 0,
|
|
1270
|
-
w: captionW,
|
|
1271
|
-
// A caption that wraps needs a box tall enough to hold every line, or it
|
|
1272
|
-
// overflows its frame. The declared region height is the floor, not the cap.
|
|
1273
|
-
h: Math.max(
|
|
1274
|
-
region.h || 0.456,
|
|
1275
|
-
estimateTextHeight(captionText, captionW, captionSize, captionSpacing)
|
|
1276
|
-
),
|
|
1277
|
-
fontFace: captionRole.font || FONT_MEDIUM,
|
|
1278
|
-
fontSize: captionSize,
|
|
1279
|
-
color: (captionRole.color || "000000").replace("#", ""),
|
|
1280
|
-
align: region.align || captionRole.align || "center",
|
|
1281
|
-
charSpacing: captionRole.charSpacing != null ? captionRole.charSpacing : CHAR_SPACING,
|
|
1282
|
-
lineSpacingMultiple: captionRole.lineSpacing || 1.3,
|
|
1283
|
-
});
|
|
1284
|
-
return;
|
|
1285
|
-
}
|
|
1286
|
-
|
|
1287
|
-
// ── Table Caption ─────────────────────────────────────────────────
|
|
1288
|
-
if (content.type === "table-caption") {
|
|
1289
|
-
const num = content.number || 1;
|
|
1290
|
-
const prefix = content.prefix || "표";
|
|
1291
|
-
const captionText = content.caption ? `${prefix} ${num}. ${cleanMd(content.caption)}` : `${prefix} ${num}.`;
|
|
1292
|
-
const captionRole = fontRoles.table_caption || {};
|
|
1293
|
-
const captionW = region.w || 4;
|
|
1294
|
-
const captionSize = captionRole.size || 16;
|
|
1295
|
-
const captionSpacing = captionRole.lineSpacing || 1.3;
|
|
1296
|
-
slide.addText(captionText, {
|
|
1297
|
-
x: region.x || 0,
|
|
1298
|
-
y: region.y || 0,
|
|
1299
|
-
w: captionW,
|
|
1300
|
-
// Same rule as the figure caption: the declared region height is the
|
|
1301
|
-
// floor, not the cap, or a wrapping caption overflows its frame.
|
|
1302
|
-
h: Math.max(
|
|
1303
|
-
region.h || 0.456,
|
|
1304
|
-
estimateTextHeight(captionText, captionW, captionSize, captionSpacing)
|
|
1305
|
-
),
|
|
1306
|
-
fontFace: captionRole.font || FONT_MEDIUM,
|
|
1307
|
-
fontSize: captionSize,
|
|
1308
|
-
color: (captionRole.color || "000000").replace("#", ""),
|
|
1309
|
-
align: region.align || captionRole.align || "center",
|
|
1310
|
-
charSpacing: captionRole.charSpacing != null ? captionRole.charSpacing : CHAR_SPACING,
|
|
1311
|
-
lineSpacingMultiple: captionRole.lineSpacing || 1.3,
|
|
1312
|
-
});
|
|
1313
|
-
return;
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
// ── Summary Group → individual shapes ──────────────────────────────
|
|
1317
|
-
if (content.type === "summary-group") {
|
|
1318
|
-
addSummaryShapes(slide, content, region);
|
|
1319
|
-
return;
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
|
|
1323
|
-
// ── Rich card, KPI and table rendering (templates with the rich-blocks capability) ──
|
|
1324
|
-
|
|
1325
|
-
/** Split a title string into runs, emphasizing **word** in the primary color. */
|
|
1326
|
-
function azureTitleRuns(text, P) {
|
|
1327
|
-
return text.split(/(\*\*[^*]+\*\*)/g).filter(Boolean).map((p) =>
|
|
1328
|
-
p.startsWith("**") && p.endsWith("**")
|
|
1329
|
-
? { text: p.slice(2, -2), options: { color: hex(P.primary) } }
|
|
1330
|
-
: { text: p, options: {} }
|
|
1331
|
-
);
|
|
1332
|
-
}
|
|
1333
|
-
|
|
1334
|
-
/** Strip leftover markdown markers (### heading, list dashes, **bold**, `code`)
|
|
1335
|
-
* from inline text, returning a clean plain string. */
|
|
1336
|
-
function cleanMd(s) {
|
|
1337
|
-
return String(s || "")
|
|
1338
|
-
.replace(/^#{1,6}\s+/, "")
|
|
1339
|
-
.replace(/^\s*[*-]\s+/, "")
|
|
1340
|
-
.replace(/\*\*(.+?)\*\*/g, "$1")
|
|
1341
|
-
.replace(/(?<![\w*])\*([^*\s](?:[^*]*[^*\s])?)\*(?![\w*])/gu, "$1")
|
|
1342
|
-
.replace(/`/g, "")
|
|
1343
|
-
.trim();
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
|
-
/** Convert inline markdown in `text` to pptxgenjs styled runs: **x** → a bold
|
|
1347
|
-
* run, plain segments → normal runs. Leading heading/list markers and backticks
|
|
1348
|
-
* are stripped. `runOpts` styles every run; bold runs additionally set bold.
|
|
1349
|
-
* Guarantees no raw "**", "###", or backtick reaches the rendered <a:t>. */
|
|
1350
|
-
function mdRuns(text, runOpts = {}) {
|
|
1351
|
-
const clean = String(text || "")
|
|
1352
|
-
.replace(/^#{1,6}\s+/, "")
|
|
1353
|
-
.replace(/^\s*[*-]\s+/, "")
|
|
1354
|
-
.replace(/`/g, "");
|
|
1355
|
-
// **x** is bold; *x* (a journal or book name) is italic.
|
|
1356
|
-
const parts = clean.split(/(\*\*[^*]+\*\*|(?<![\w*])\*[^*\s](?:[^*]*[^*\s])?\*(?![\w*]))/gu).filter((p) => p !== "");
|
|
1357
|
-
if (parts.length === 0) return [{ text: "", options: { ...runOpts } }];
|
|
1358
|
-
return parts.map((p) =>
|
|
1359
|
-
p.startsWith("**") && p.endsWith("**")
|
|
1360
|
-
? { text: p.slice(2, -2), options: { ...runOpts, bold: true } }
|
|
1361
|
-
: p.length > 2 && p.startsWith("*") && p.endsWith("*")
|
|
1362
|
-
? { text: p.slice(1, -1), options: { ...runOpts, italic: true } }
|
|
1363
|
-
: { text: p, options: { ...runOpts } }
|
|
1364
|
-
);
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
/** Convert body items into up to 4 cards: {heading, lines[]}.
|
|
1368
|
-
* Handles both authoring styles:
|
|
1369
|
-
* - `### Title` (parsed as `text`) followed by `- **point**` (a `section`) → one card
|
|
1370
|
-
* titled by the heading, the section folded into the body.
|
|
1371
|
-
* - bare `- **Title**` sections → one card each (heading = bold title). */
|
|
1372
|
-
function bodyToCards(items) {
|
|
1373
|
-
const cards = [];
|
|
1374
|
-
let cur = null;
|
|
1375
|
-
for (const item of items || []) {
|
|
1376
|
-
if (item.type === "text" && item.text) {
|
|
1377
|
-
cur = { heading: cleanMd(item.text), lines: [] };
|
|
1378
|
-
cards.push(cur);
|
|
1379
|
-
} else if (item.type === "section" || item.type === "bullet") {
|
|
1380
|
-
const head = cleanMd(item.heading || item.text || "");
|
|
1381
|
-
const kids = (item.children || []).map((c) => cleanMd(c.text)).filter(Boolean);
|
|
1382
|
-
if (cur && cur.lines.length === 0 && cur.heading) {
|
|
1383
|
-
// fold this section under the preceding heading card
|
|
1384
|
-
if (head) cur.lines.push(head);
|
|
1385
|
-
kids.forEach((k) => cur.lines.push(" " + k));
|
|
1386
|
-
} else {
|
|
1387
|
-
cur = { heading: head, lines: kids };
|
|
1388
|
-
cards.push(cur);
|
|
1389
|
-
}
|
|
1390
|
-
} else if (item.type === "numbered" && item.text) {
|
|
1391
|
-
if (cur) cur.lines.push(cleanMd(item.text));
|
|
1392
|
-
else { cur = { heading: "", lines: [cleanMd(item.text)] }; cards.push(cur); }
|
|
1393
|
-
}
|
|
1394
|
-
}
|
|
1395
|
-
return cards.slice(0, 4);
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
// Card geometry shared by drawing and sizing, so a card is exactly as tall as its text
|
|
1399
|
-
// (template tokens card.*; reassigned in render()).
|
|
1400
|
-
let CARD_PAD = LEGACY_TOKENS.card.pad;
|
|
1401
|
-
let CARD_CHIP = LEGACY_TOKENS.card.chip;
|
|
1402
|
-
let CARD_MIN_H = LEGACY_TOKENS.card.minH;
|
|
1403
|
-
let CARD_GAP = LEGACY_TOKENS.card.gap;
|
|
1404
|
-
let CARD_RADIUS = LEGACY_TOKENS.card.radius;
|
|
1405
|
-
|
|
1406
|
-
// Primary reading text stays at or above the 12 pt floor of the design laws.
|
|
1407
|
-
function cardBodySize(card) {
|
|
1408
|
-
return card.lines && card.lines.length > 4 ? 12 : 13;
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
|
-
// Card text height: the conservative wrap count, but line boxes at their real
|
|
1412
|
-
// leading (estimateTextHeight pads every paragraph, which left short cards hollow).
|
|
1413
|
-
function cardTextHeight(text, innerW, fontSize, lineSpacing) {
|
|
1414
|
-
return (estimateWrappedLines(text, innerW, fontSize) * fontSize * lineSpacing * 1.2) / 72;
|
|
1415
|
-
}
|
|
1416
|
-
|
|
1417
|
-
function cardHeadingHeight(card, innerW) {
|
|
1418
|
-
return card.heading ? cardTextHeight(card.heading, innerW, 15, 1.05) + 0.06 : 0;
|
|
1419
|
-
}
|
|
1420
|
-
|
|
1421
|
-
function cardBodyHeight(card, innerW) {
|
|
1422
|
-
if (card.bodyRuns && card.bodyRuns.length) return card.bodyHeight || 0;
|
|
1423
|
-
const fs = cardBodySize(card);
|
|
1424
|
-
return (card.lines || []).reduce((sum, line) => sum + cardTextHeight(line, innerW, fs, 1.3), 0.1);
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
/** Height a card needs for its chip, heading and body at the width it is drawn. */
|
|
1428
|
-
function azureCardHeight(card, w) {
|
|
1429
|
-
const innerW = w - 2 * CARD_PAD;
|
|
1430
|
-
const head = card.heading ? 0.18 + Math.max(0.4, cardHeadingHeight(card, innerW)) + 0.04 : 0.18;
|
|
1431
|
-
return CARD_PAD + CARD_CHIP + head + cardBodyHeight(card, innerW) + CARD_PAD;
|
|
1432
|
-
}
|
|
1433
|
-
|
|
1434
|
-
/**
|
|
1435
|
-
* The height a row of cards is drawn at: the tallest card's content, never taller
|
|
1436
|
-
* than the region the template reserves and never shorter than CARD_MIN_H. A fixed
|
|
1437
|
-
* region height left short cards mostly empty; the space below a short row is free
|
|
1438
|
-
* for the takeaway, and the craft gate reports a slide that stays thin.
|
|
1439
|
-
*/
|
|
1440
|
-
function fittedCardHeight(cards, w, regionH) {
|
|
1441
|
-
const need = Math.max(...cards.map((c) => azureCardHeight(c, w)));
|
|
1442
|
-
return Math.min(regionH, Math.max(CARD_MIN_H, need));
|
|
1443
|
-
}
|
|
1444
|
-
|
|
1445
|
-
/** Draw one rounded card with a numbered chip, heading, and body lines. */
|
|
1446
|
-
function drawAzureCard(slide, x, y, w, h, card, idx, P, emphasized) {
|
|
1447
|
-
const bg = emphasized ? hex(P.primary_deep) : hex(P.tint);
|
|
1448
|
-
const headColor = emphasized ? "FFFFFF" : hex(P.ink);
|
|
1449
|
-
const bodyColor = emphasized ? hex(P.emphasisTextColor) : hex(P.ink_muted);
|
|
1450
|
-
slide.addShape("roundRect", { x, y, w, h, rectRadius: CARD_RADIUS, fill: { color: bg }, line: { type: "none" } });
|
|
1451
|
-
const pad = CARD_PAD;
|
|
1452
|
-
const innerW = w - 2 * pad;
|
|
1453
|
-
// numbered chip
|
|
1454
|
-
const chip = CARD_CHIP;
|
|
1455
|
-
slide.addShape("roundRect", {
|
|
1456
|
-
x: x + pad, y: y + pad, w: chip, h: chip, rectRadius: 0.12,
|
|
1457
|
-
fill: { color: emphasized ? "FFFFFF" : hex(P.primary) }, line: { type: "none" },
|
|
1458
|
-
});
|
|
1459
|
-
// Number sits in a text box whose rect is identical to the chip rect, with
|
|
1460
|
-
// margin:0 and tight line spacing so the numeral reads dead-centre.
|
|
1461
|
-
slide.addText(String(idx + 1).padStart(2, "0"), {
|
|
1462
|
-
x: x + pad, y: y + pad, w: chip, h: chip, align: "center", valign: "middle",
|
|
1463
|
-
fontFace: P.titleFont, fontSize: 15, bold: true,
|
|
1464
|
-
color: emphasized ? hex(P.primary_deep) : "FFFFFF",
|
|
1465
|
-
margin: 0, lineSpacingMultiple: 1,
|
|
1466
|
-
});
|
|
1467
|
-
const headH = cardHeadingHeight(card, innerW);
|
|
1468
|
-
if (card.heading) {
|
|
1469
|
-
slide.addText(card.heading, {
|
|
1470
|
-
x: x + pad, y: y + pad + chip + 0.18, w: innerW, h: Math.max(0.4, headH),
|
|
1471
|
-
fontFace: P.titleFont, fontSize: 15, bold: true, color: headColor, align: "left", valign: "top",
|
|
1472
|
-
charSpacing: -0.3,
|
|
1473
|
-
});
|
|
1474
|
-
}
|
|
1475
|
-
const bodyY = y + pad + chip + 0.18 + (card.heading ? Math.max(0.4, headH) + 0.04 : 0);
|
|
1476
|
-
const bodyH = Math.max(0.3, h - (bodyY - y) - 0.18);
|
|
1477
|
-
if (card.bodyRuns && card.bodyRuns.length) {
|
|
1478
|
-
slide.addText(card.bodyRuns, {
|
|
1479
|
-
x: x + pad, y: bodyY, w: innerW, h: bodyH,
|
|
1480
|
-
color: bodyColor, align: "left", valign: "top", lineSpacingMultiple: card.lineSpacing || 1.28, charSpacing: -0.2,
|
|
1481
|
-
});
|
|
1482
|
-
} else if (card.lines && card.lines.length) {
|
|
1483
|
-
// Shrink body font when there are many lines so it never exceeds the frame.
|
|
1484
|
-
const fs = cardBodySize(card);
|
|
1485
|
-
slide.addText(card.lines.join("\n"), {
|
|
1486
|
-
x: x + pad, y: bodyY, w: innerW, h: bodyH,
|
|
1487
|
-
fontFace: P.bodyFont, fontSize: fs, color: bodyColor, align: "left", valign: "top",
|
|
1488
|
-
lineSpacingMultiple: 1.3, charSpacing: -0.2,
|
|
1489
|
-
});
|
|
1490
|
-
}
|
|
1491
|
-
}
|
|
1492
|
-
|
|
1493
|
-
/** Render content/main body as a row of cards, as tall as the longest card needs. */
|
|
1494
|
-
function renderAzureCards(slide, items, region, P) {
|
|
1495
|
-
const cards = bodyToCards(items);
|
|
1496
|
-
if (!cards.length) return;
|
|
1497
|
-
const n = cards.length;
|
|
1498
|
-
const x0 = region.x || 0.6, y0 = region.y || 1.6, w = region.w || 12.1;
|
|
1499
|
-
const gap = CARD_GAP;
|
|
1500
|
-
const cardW = (w - (n - 1) * gap) / n;
|
|
1501
|
-
const h = fittedCardHeight(cards, cardW, region.h || 3.0);
|
|
1502
|
-
cards.forEach((c, i) => {
|
|
1503
|
-
drawAzureCard(slide, x0 + i * (cardW + gap), y0, cardW, h, c, i, P, n >= 3 && i === n - 1);
|
|
1504
|
-
});
|
|
1505
|
-
return y0 + h;
|
|
1506
|
-
}
|
|
1507
|
-
|
|
1508
|
-
/** Flatten body/summary items (sections + children + bullets) into styled runs.
|
|
1509
|
-
* `scale` (≤1) shrinks fonts to fit a fixed card height. */
|
|
1510
|
-
function itemsToRuns(items, P, scale = 1) {
|
|
1511
|
-
const s = (v) => Math.max(8, Math.round(v * scale * 10) / 10);
|
|
1512
|
-
const runs = [];
|
|
1513
|
-
for (const it of items || []) {
|
|
1514
|
-
if (it.type === "section") {
|
|
1515
|
-
runs.push({ text: cleanMd(it.heading || ""), options: { bold: true, fontFace: P.titleFont, fontSize: s(14), color: hex(P.ink), breakLine: true } });
|
|
1516
|
-
for (const c of it.children || []) {
|
|
1517
|
-
runs.push({ text: " " + cleanMd(c.text || ""), options: { fontFace: P.bodyFont, fontSize: s(12.5), color: hex(P.ink_muted), breakLine: true } });
|
|
1518
|
-
}
|
|
1519
|
-
} else if (it.text) {
|
|
1520
|
-
runs.push({ text: (it.type === "numbered" ? " " : "• ") + cleanMd(it.text), options: { fontFace: P.bodyFont, fontSize: s(13), color: hex(P.ink_muted), breakLine: true } });
|
|
1521
|
-
}
|
|
1522
|
-
}
|
|
1523
|
-
return runs;
|
|
1524
|
-
}
|
|
1525
|
-
|
|
1526
|
-
/** Estimate the rendered height of a group's items at a given font scale,
|
|
1527
|
-
* using the same conservative metric inventory respects (estimateTextHeight). */
|
|
1528
|
-
function estimateGroupHeight(items, innerW, scale, lineSpacing) {
|
|
1529
|
-
let hsum = 0;
|
|
1530
|
-
for (const it of items || []) {
|
|
1531
|
-
if (it.type === "section") {
|
|
1532
|
-
hsum += estimateTextHeight(it.heading || "", innerW, 14 * scale, lineSpacing);
|
|
1533
|
-
for (const c of it.children || []) hsum += estimateTextHeight(" " + (c.text || ""), innerW - 0.1, 12.5 * scale, lineSpacing);
|
|
1534
|
-
} else if (it.text) {
|
|
1535
|
-
hsum += estimateTextHeight(it.text, innerW, 13 * scale, lineSpacing);
|
|
1536
|
-
}
|
|
1537
|
-
}
|
|
1538
|
-
return hsum;
|
|
1539
|
-
}
|
|
1540
|
-
|
|
1541
|
-
/** The height a group's runs take at their real leading, for sizing (not fitting) the card. */
|
|
1542
|
-
function groupTextHeight(items, innerW, scale, lineSpacing) {
|
|
1543
|
-
let hsum = 0.1;
|
|
1544
|
-
for (const it of items || []) {
|
|
1545
|
-
if (it.type === "section") {
|
|
1546
|
-
hsum += cardTextHeight(it.heading || "", innerW, 14 * scale, lineSpacing);
|
|
1547
|
-
for (const c of it.children || []) hsum += cardTextHeight(" " + (c.text || ""), innerW - 0.1, 12.5 * scale, lineSpacing);
|
|
1548
|
-
} else if (it.text) {
|
|
1549
|
-
hsum += cardTextHeight("• " + it.text, innerW, 13 * scale, lineSpacing);
|
|
1550
|
-
}
|
|
1551
|
-
}
|
|
1552
|
-
return hsum;
|
|
1553
|
-
}
|
|
1554
|
-
|
|
1555
|
-
/** A summary group as a card: the largest font scale (1 down to 0.55) whose text fits
|
|
1556
|
-
* `maxH`, and the height that text needs at that scale. */
|
|
1557
|
-
function groupCard(content, w, maxH, P) {
|
|
1558
|
-
const innerW = w - 2 * CARD_PAD;
|
|
1559
|
-
const heading = cleanMd(content.heading || "");
|
|
1560
|
-
const headH = heading ? 0.18 + Math.max(0.4, cardHeadingHeight({ heading }, innerW)) + 0.04 : 0.18;
|
|
1561
|
-
const fixed = CARD_PAD + CARD_CHIP + headH + 0.18;
|
|
1562
|
-
let scale = 0.55, ls = 1.12;
|
|
1563
|
-
for (const s of [1, 0.92, 0.85, 0.78, 0.72, 0.66, 0.6, 0.55]) {
|
|
1564
|
-
if (estimateGroupHeight(content.items, innerW, s, s < 1 ? 1.12 : 1.28) <= maxH - fixed) { scale = s; ls = s < 1 ? 1.12 : 1.28; break; }
|
|
1565
|
-
}
|
|
1566
|
-
const bodyHeight = groupTextHeight(content.items, innerW, scale, ls);
|
|
1567
|
-
return {
|
|
1568
|
-
card: { heading, bodyRuns: itemsToRuns(content.items, P, scale), lineSpacing: ls, bodyHeight },
|
|
1569
|
-
need: Math.min(maxH, fixed + bodyHeight + CARD_PAD - 0.18),
|
|
1570
|
-
};
|
|
1571
|
-
}
|
|
1572
|
-
|
|
1573
|
-
/** Render a single summary-group as one card, at `opts.height` (shared by the
|
|
1574
|
-
* groups of one slide) or at the height its own text needs. */
|
|
1575
|
-
function renderAzureGroupCard(slide, content, region, P, idx, height) {
|
|
1576
|
-
const w = region.w || 5.6, maxH = region.h || 2.4;
|
|
1577
|
-
const { card, need } = groupCard(content, w, maxH, P);
|
|
1578
|
-
const h = Math.min(maxH, Math.max(CARD_MIN_H, height || need));
|
|
1579
|
-
drawAzureCard(slide, region.x || 0.6, region.y || 1.6, w, h, card, idx, P, false);
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
|
-
/** Render a general data table (palette-styled): primary header, zebra tint rows. */
|
|
1583
|
-
// ── Tables and charts that fill their region ────────────────────────────────
|
|
1584
|
-
|
|
1585
|
-
// A cell reads as a number when, after units and signs, only digits remain.
|
|
1586
|
-
const NUMERIC_CELL = /^(?:[▲▼△▽]\s*)?[+\-−±]?\s*[\d.,]+\s*(?:%p?|배|x|pt|bp|[가-힣]{1,3}|[A-Za-z]{1,3})?$/u;
|
|
1587
|
-
const isNumericCell = (text) => NUMERIC_CELL.test(cleanMd(String(text)).trim());
|
|
1588
|
-
|
|
1589
|
-
/** Columns whose data cells are all numbers are right-aligned; a dash for a missing value is blank. */
|
|
1590
|
-
function numericColumns(headers, rows) {
|
|
1591
|
-
return headers.map((_, ci) => {
|
|
1592
|
-
const cells = rows.map((r) => r[ci]).filter((c) => c != null && !/^[\s—–\-]*$/u.test(cleanMd(String(c))));
|
|
1593
|
-
return cells.length > 0 && cells.every(isNumericCell);
|
|
1594
|
-
});
|
|
1595
|
-
}
|
|
1596
|
-
|
|
1597
|
-
/** Column widths proportional to the longest cell, Hangul counted wider, with a floor. */
|
|
1598
|
-
function proportionalWidths(headers, rows, totalW) {
|
|
1599
|
-
const len = (t) => [...cleanMd(String(t || ""))].reduce((n, ch) => n + (/[\u3131-\uD7A3]/u.test(ch) ? 1.7 : 1), 0);
|
|
1600
|
-
const want = headers.map((h, ci) => Math.max(len(h), ...rows.map((r) => len(r[ci])), 4));
|
|
1601
|
-
const floor = Math.min(0.9, totalW / headers.length);
|
|
1602
|
-
const sum = want.reduce((a, b) => a + b, 0);
|
|
1603
|
-
let widths = want.map((w) => Math.max(floor, (w / sum) * totalW));
|
|
1604
|
-
const scale = totalW / widths.reduce((a, b) => a + b, 0);
|
|
1605
|
-
widths = widths.map((w) => w * scale);
|
|
1606
|
-
return widths;
|
|
1607
|
-
}
|
|
1608
|
-
|
|
1609
|
-
/**
|
|
1610
|
-
* Row height and font size for a table that should use the room it has: rows
|
|
1611
|
-
* grow toward `bottom` (up to a comfortable maximum) instead of leaving the lower
|
|
1612
|
-
* half of the slide empty, and a roomy table gets a larger font.
|
|
1613
|
-
*/
|
|
1614
|
-
function tableMetrics(rowCount, y, bottom, base) {
|
|
1615
|
-
const room = Math.max(0, (bottom || y + rowCount * base.rowH) - y);
|
|
1616
|
-
const rowH = Math.min(base.maxRowH, Math.max(base.rowH, (room * 0.92) / rowCount));
|
|
1617
|
-
const scale = rowH >= 0.55 ? 1.2 : rowH >= 0.47 ? 1.1 : 1;
|
|
1618
|
-
return { rowH, headerSize: +(base.headerSize * scale).toFixed(1), dataSize: +(base.dataSize * scale).toFixed(1) };
|
|
1619
|
-
}
|
|
1620
|
-
|
|
1621
|
-
function renderAzureTable(slide, content, region, P, bottom) {
|
|
1622
|
-
const headers = content.headers || [];
|
|
1623
|
-
const rows = content.rows || [];
|
|
1624
|
-
if (!headers.length) return;
|
|
1625
|
-
const x = region.x || 0.6, y = region.y || 1.6, w = region.w || 6.0;
|
|
1626
|
-
const numeric = numericColumns(headers, rows);
|
|
1627
|
-
const m = tableMetrics(rows.length + 1, y, bottom, { rowH: 0.4, maxRowH: 0.72, headerSize: 12, dataSize: 11.5 });
|
|
1628
|
-
const tableRows = [];
|
|
1629
|
-
tableRows.push(headers.map((h, ci) => ({
|
|
1630
|
-
text: cleanMd(h),
|
|
1631
|
-
options: { bold: true, color: "FFFFFF", fill: { color: hex(P.primary) }, fontFace: P.titleFont, fontSize: m.headerSize, align: numeric[ci] ? "right" : "left", valign: "middle" },
|
|
1632
|
-
})));
|
|
1633
|
-
rows.forEach((r, ri) => {
|
|
1634
|
-
tableRows.push(r.map((c, ci) => linkedTableCell(c, {
|
|
1635
|
-
color: hex(P.ink), fill: { color: ri % 2 ? "FFFFFF" : hex(P.tint) }, fontFace: P.bodyFont, fontSize: m.dataSize,
|
|
1636
|
-
align: numeric[ci] ? "right" : "left", valign: "middle", bold: ci === 0 && !numeric[0],
|
|
1637
|
-
})));
|
|
1638
|
-
});
|
|
1639
|
-
slide.addTable(tableRows, {
|
|
1640
|
-
x, y, w, colW: proportionalWidths(headers, rows, w),
|
|
1641
|
-
border: { type: "solid", color: hex(P.line), pt: 0.5 }, rowH: m.rowH, margin: [0.04, 0.12, 0.04, 0.12], autoPage: false,
|
|
1642
|
-
});
|
|
1643
|
-
}
|
|
1644
|
-
|
|
1645
|
-
/** Parse "1,234", "▲12.5%", "−3" into a number; anything else is null. */
|
|
1646
|
-
function chartNumber(text) {
|
|
1647
|
-
const t = cleanMd(String(text || "")).trim().replace(/[,\s]/g, "").replace(/^[▲△+]/u, "").replace(/^[▼▽−]/u, "-");
|
|
1648
|
-
const m = t.match(/^-?\d+(?:\.\d+)?/);
|
|
1649
|
-
return m ? parseFloat(m[0]) : null;
|
|
1650
|
-
}
|
|
1651
|
-
|
|
1652
|
-
const CHART_TYPE = { bar: "bar", column: "bar", stacked: "bar", line: "line", area: "area", pie: "pie", doughnut: "doughnut" };
|
|
1653
|
-
|
|
1654
|
-
/**
|
|
1655
|
-
* A native PowerPoint chart (editable in PowerPoint: right-click > Edit Data)
|
|
1656
|
-
* from a `::: chart` block. It takes the whole region down to `bottom`.
|
|
1657
|
-
*/
|
|
1658
|
-
function renderChart(slide, content, region, style, bottom) {
|
|
1659
|
-
const headers = content.headers || [];
|
|
1660
|
-
const rows = content.rows || [];
|
|
1661
|
-
const spec = content.chart || {};
|
|
1662
|
-
const kind = CHART_TYPE[spec.type] || "bar";
|
|
1663
|
-
const labels = rows.map((r) => cleanMd(String(r[0] || "")));
|
|
1664
|
-
const series = headers.slice(1).map((name, si) => ({
|
|
1665
|
-
name: cleanMd(String(name)),
|
|
1666
|
-
labels,
|
|
1667
|
-
// A dash or empty cell stays null and is written as a blank point; the line breaks instead of plunging to zero.
|
|
1668
|
-
values: rows.map((r) => chartNumber(r[si + 1])),
|
|
1669
|
-
}));
|
|
1670
|
-
// Data labels keep the decimals the table writes: beside a table that says 18.0 the chart says 18.0 too.
|
|
1671
|
-
const decimals = Math.min(3, Math.max(0, ...rows.flatMap((r) => r.slice(1).map((v) => ((/\.(\d+)/u.exec(cleanMd(String(v == null ? "" : v)).replace(/,/gu, "")) || [, ""])[1]).length))));
|
|
1672
|
-
const x = region.x || 0.6, y = region.y || 1.6, w = region.w || 8;
|
|
1673
|
-
const h = Math.max(1.6, ((bottom || y + (region.h || 3.2)) - y));
|
|
1674
|
-
const colors = style.series.map((c) => hex(c));
|
|
1675
|
-
const round = kind === "pie" || kind === "doughnut";
|
|
1676
|
-
const data = round ? series.slice(0, 1) : series;
|
|
1677
|
-
// Crowding, estimated in points: a category label wider than its slot is slanted; side-by-side
|
|
1678
|
-
// value labels wider than their bars collide, so the values are read off a shown axis instead.
|
|
1679
|
-
const ems = (t) => [...String(t)].reduce((a, ch) => a + (/[\u3131-\uD7A3]/u.test(ch) ? 0.94 : /\s/u.test(ch) ? 0.28 : 0.56), 0);
|
|
1680
|
-
const labelPt = style.labelSize || 11;
|
|
1681
|
-
const slot = (w * 72 * 0.85) / Math.max(1, labels.length);
|
|
1682
|
-
// A renderer adds space between Hangul and digits ("25 년 2Q"), so a label needs a quarter of its slot spare.
|
|
1683
|
-
// Horizontal bars run their categories down the side, where LibreOffice drops slanted labels.
|
|
1684
|
-
const slanted = !round && kind !== "line" && spec.type !== "bar" && Math.max(0, ...labels.map(ems)) * labelPt > slot * 0.75;
|
|
1685
|
-
const clustered = kind === "bar" && spec.type !== "stacked" && data.length > 1;
|
|
1686
|
-
const valueW = Math.max(0, ...data.flatMap((d) => d.values.map((v) => ems(Number(v).toLocaleString("en-US"))))) * labelPt;
|
|
1687
|
-
// Lines of two or more series share the plot's rows, so a label over each point covers its neighbours.
|
|
1688
|
-
const crowded = !round && ((clustered && valueW > (slot / 1.6 / data.length) * 1.1) || (kind === "line" && data.length > 1));
|
|
1689
|
-
const values = data.flatMap((d) => d.values).filter((v) => v !== null);
|
|
1690
|
-
// Each row of a multi-series horizontal bar chart holds several thin bars; thinner than a label line, their value labels touch.
|
|
1691
|
-
const horizontal = !round && spec.type === "bar";
|
|
1692
|
-
const dataPt = style.labelSize || (round ? 12 : 10);
|
|
1693
|
-
const barPt = (h * 72 - 54) / Math.max(1, labels.length) / (data.length + 0.6);
|
|
1694
|
-
const thin = horizontal && data.length > 1 && spec.labels !== false && barPt < dataPt * 1.2;
|
|
1695
|
-
// A narrow horizontal bar chart steps its value axis coarsely so the numbers stay level; LibreOffice slants a crowded axis.
|
|
1696
|
-
const top = Math.max(0, ...values), low = Math.min(0, ...values);
|
|
1697
|
-
const tickW = ems(Math.round(Math.max(top, -low)).toLocaleString("en-US")) * dataPt;
|
|
1698
|
-
const catW = Math.min(w * 72 * 0.4, Math.max(0, ...labels.map(ems)) * labelPt + 8);
|
|
1699
|
-
const ticks = Math.floor((w * 72 - catW - 16) / (tickW * 2));
|
|
1700
|
-
const nice = (step) => { const p = 10 ** Math.floor(Math.log10(step)); return [1, 2, 2.5, 5, 10].find((m) => m * p >= step) * p; };
|
|
1701
|
-
const majorUnit = horizontal && top > low && ticks < 6 ? nice((top - low) / Math.max(1, ticks - 1)) : null;
|
|
1702
|
-
const zeroBased = !round && values.length > 0 && values.every((v) => v >= 0);
|
|
1703
|
-
// The unit stands once: a legend whose series names carry it already says it, so the axis title goes;
|
|
1704
|
-
// otherwise it is set level (a rotated title stacks Hangul a glyph a line and reads tiny; 360 degrees, since
|
|
1705
|
-
// pptxgenjs writes no angle for 0 and the axis default is vertical).
|
|
1706
|
-
const unitShown = spec.unit && data.length > 1 && data.every((d) => String(d.name || "").includes(spec.unit));
|
|
1707
|
-
const axisTitle = spec.unit && !unitShown && !(Boolean(style.valAxisHidden) && !crowded && !thin);
|
|
1708
|
-
slide.addChart(kind, data, {
|
|
1709
|
-
x, y, w, h,
|
|
1710
|
-
barDir: spec.type === "bar" ? "bar" : "col",
|
|
1711
|
-
barGrouping: spec.type === "stacked" ? "stacked" : "clustered",
|
|
1712
|
-
barGapWidthPct: 60,
|
|
1713
|
-
chartColors: round ? labels.map((_, i) => colors[i % colors.length]) : colors,
|
|
1714
|
-
showLegend: round || data.length > 1,
|
|
1715
|
-
legendPos: round ? "r" : "t",
|
|
1716
|
-
legendFontFace: style.bodyFont, legendFontSize: 11, legendColor: hex(style.ink),
|
|
1717
|
-
showValue: !round && spec.labels !== false && !crowded && !thin,
|
|
1718
|
-
showPercent: round && spec.labels !== false, showLabel: false,
|
|
1719
|
-
dataLabelFontFace: style.bodyFont, dataLabelFontSize: style.labelSize || (round ? 12 : 10),
|
|
1720
|
-
dataLabelColor: round ? "FFFFFF" : hex(style.ink),
|
|
1721
|
-
dataLabelPosition: round ? "ctr" : kind === "line" ? "t" : "outEnd",
|
|
1722
|
-
dataLabelFormatCode: round ? "0%" : decimals ? `#,##0.${"0".repeat(decimals)}` : "#,##0",
|
|
1723
|
-
catAxisLabelFontFace: style.bodyFont, catAxisLabelFontSize: style.labelSize || 11, catAxisLabelColor: hex(style.ink),
|
|
1724
|
-
valAxisLabelFontFace: style.bodyFont, valAxisLabelFontSize: style.labelSize || 10, valAxisLabelColor: hex(style.ink_muted),
|
|
1725
|
-
valAxisLabelFormatCode: "#,##0",
|
|
1726
|
-
// A pack with direct labels and no gridlines drops the value axis: the labels carry the values.
|
|
1727
|
-
valAxisHidden: Boolean(style.valAxisHidden) && !crowded && !thin,
|
|
1728
|
-
...(majorUnit ? { valAxisMajorUnit: majorUnit } : {}),
|
|
1729
|
-
// Negative values bring the zero line into the plot; category names go to the axis minimum, off the bars.
|
|
1730
|
-
...(horizontal && low < 0 ? { catAxisLabelPos: "low" } : {}),
|
|
1731
|
-
// Blank points leave gaps.
|
|
1732
|
-
displayBlanksAs: "gap",
|
|
1733
|
-
...(style.valMax ? { valAxisMaxVal: style.valMax } : {}),
|
|
1734
|
-
...(zeroBased ? { valAxisMinVal: 0 } : {}),
|
|
1735
|
-
...(slanted ? { catAxisLabelRotate: -45 } : {}),
|
|
1736
|
-
catAxisLineShow: true, catAxisLineColor: hex(style.line),
|
|
1737
|
-
valGridLine: style.grid === false && !crowded && !thin ? { style: "none" } : { color: hex(style.line), size: 0.5 },
|
|
1738
|
-
valAxisLineShow: false,
|
|
1739
|
-
lineSize: 2.5, lineDataSymbol: "circle", lineDataSymbolSize: 7,
|
|
1740
|
-
holeSize: 58,
|
|
1741
|
-
...(axisTitle ? { showValAxisTitle: true, valAxisTitle: spec.unit, valAxisTitleRotate: 360, valAxisTitleFontSize: style.labelSize || 10, valAxisTitleColor: hex(style.ink_muted), valAxisTitleFontFace: style.bodyFont } : {}),
|
|
1742
|
-
...(spec.title ? { showTitle: true, title: spec.title, titleFontFace: style.titleFont, titleFontSize: 13, titleColor: hex(style.ink) } : {}),
|
|
1743
|
-
});
|
|
1744
|
-
}
|
|
1745
|
-
|
|
1746
|
-
/** A main-box as a tinted callout sized to its text, not an outlined frame. */
|
|
1747
|
-
function renderAzureCallout(slide, text, region, P) {
|
|
1748
|
-
const x = region.x || 0.84, y = region.y || 5.8, w = region.w || 11.6;
|
|
1749
|
-
const lines = Math.max(1, Math.ceil(cleanMd(text).length / Math.max(20, w * 9)));
|
|
1750
|
-
const h = Math.min(region.h || 1.0, 0.28 + lines * 0.3);
|
|
1751
|
-
slide.addShape("roundRect", { x, y, w, h, rectRadius: 0.1, fill: { color: hex(P.tint) }, line: { type: "none" } });
|
|
1752
|
-
slide.addText(cleanMd(text), {
|
|
1753
|
-
x: x + 0.24, y, w: w - 0.48, h, fontFace: P.bodyFont, fontSize: 13, bold: true,
|
|
1754
|
-
color: hex(P.primary_deep), align: "left", valign: "middle", margin: 0,
|
|
1755
|
-
});
|
|
1756
|
-
}
|
|
1757
|
-
|
|
1758
|
-
/** Render a kpi-table as a row of KPI cards (value + label); first card filled. */
|
|
1759
|
-
function renderAzureKpi(slide, content, region, P) {
|
|
1760
|
-
const cards = kpiCards(content);
|
|
1761
|
-
const values = cards.map((c) => c.value);
|
|
1762
|
-
const labels = cards.map((c) => c.label);
|
|
1763
|
-
const n = Math.min(values.length, 4);
|
|
1764
|
-
if (!n) return;
|
|
1765
|
-
const x0 = region.x || 0.6, y0 = region.y || 1.6, w = region.w || 12.1, h = region.h || 1.7;
|
|
1766
|
-
const gap = KPI_GAP;
|
|
1767
|
-
const cardW = (w - (n - 1) * gap) / n;
|
|
1768
|
-
for (let i = 0; i < n; i++) {
|
|
1769
|
-
const fill = i === 0;
|
|
1770
|
-
const x = x0 + i * (cardW + gap);
|
|
1771
|
-
slide.addShape("roundRect", {
|
|
1772
|
-
x, y: y0, w: cardW, h, rectRadius: 0.16,
|
|
1773
|
-
fill: { color: fill ? hex(P.primary) : "FFFFFF" },
|
|
1774
|
-
line: fill ? { type: "none" } : { color: hex(P.line), width: 1 },
|
|
1775
|
-
});
|
|
1776
|
-
slide.addText(cleanMd(values[i]), {
|
|
1777
|
-
x: x + 0.28, y: y0 + 0.20, w: cardW - 0.56, h: 0.8,
|
|
1778
|
-
fontFace: P.titleFont, fontSize: 30, bold: true,
|
|
1779
|
-
color: fill ? "FFFFFF" : hex(P.primary), align: "left", valign: "middle", charSpacing: -1, margin: 0,
|
|
1780
|
-
});
|
|
1781
|
-
slide.addText(cleanMd(labels[i]).toUpperCase(), {
|
|
1782
|
-
x: x + 0.30, y: y0 + h - 0.5, w: cardW - 0.56, h: 0.32,
|
|
1783
|
-
fontFace: P.bodyFont, fontSize: 9.5, bold: false,
|
|
1784
|
-
color: fill ? hex(P.emphasisTextColor) : hex(P.ink_muted), align: "left", valign: "middle", charSpacing: 0.4, margin: 0,
|
|
1785
|
-
});
|
|
1786
|
-
}
|
|
1787
|
-
}
|
|
1788
|
-
|
|
1789
|
-
/** A pipe table reads as KPI in two shapes:
|
|
1790
|
-
* - TALL: 2 cols of value|label pairs, ≤4 pairs, value cell short/metric-like.
|
|
1791
|
-
* - WIDE: 3–4 cols, a single data row of short metric values (labels = header).
|
|
1792
|
-
* Anything else (multi-row, long cells) is a real data table. */
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
/** Normalize a kpi-table into [{value,label}] cards for both KPI shapes. */
|
|
1796
|
-
/**
|
|
1797
|
-
* Is there room to draw this as KPI badges?
|
|
1798
|
-
*
|
|
1799
|
-
* A badge stacks a value over a label and reads as one glance, which stops
|
|
1800
|
-
* working the moment the card is narrower than its own words: in a column half
|
|
1801
|
-
* the slide wide, four badges leave 1.4in each and a label like "영업이익" wraps
|
|
1802
|
-
* to one character per line. Below the floor the same data is drawn as a table,
|
|
1803
|
-
* which narrows gracefully.
|
|
1804
|
-
*/
|
|
1805
|
-
function kpiFits(content, widthIn) {
|
|
1806
|
-
const cards = kpiCards(content).length;
|
|
1807
|
-
return cards > 0 && (widthIn || 0) / cards >= KPI_MIN_CARD_WIDTH_IN;
|
|
1808
|
-
}
|
|
1809
|
-
|
|
1810
|
-
function kpiCards(content) {
|
|
1811
|
-
const headers = content.headers || [];
|
|
1812
|
-
const rows = content.rows || [];
|
|
1813
|
-
if (headers.length === 2) {
|
|
1814
|
-
return [headers, ...rows].slice(0, 4).map((p) => ({ value: p[0], label: p[1] }));
|
|
1815
|
-
}
|
|
1816
|
-
const row = rows[0] || [];
|
|
1817
|
-
// A second body row is each figure's basis or comparison ("전년 대비 +12%", "목표 50%").
|
|
1818
|
-
const basis = rows[1] || [];
|
|
1819
|
-
return headers.slice(0, 6).map((lab, i) => ({ value: row[i] != null ? row[i] : "", label: lab, ...(basis[i] ? { note: basis[i] } : {}) }));
|
|
1820
|
-
}
|
|
1821
|
-
|
|
1822
|
-
/** Azure region dispatch. Returns true if it fully handled the region.
|
|
1823
|
-
* opts.hasImage = slide also has a populated image region (→ never card the body). */
|
|
1824
|
-
function renderAzureRegion(slide, layout, name, region, fontRoles, P, opts = {}) {
|
|
1825
|
-
const content = region.content;
|
|
1826
|
-
if (content == null) return false;
|
|
1827
|
-
if (name === "main_box" && typeof content === "string" && content.trim()) {
|
|
1828
|
-
// Below a card row that came out shorter than its region, the takeaway follows
|
|
1829
|
-
// the cards instead of leaving a band of empty slide between them.
|
|
1830
|
-
const follow = opts.flow && opts.flow.cardsBottom != null ? opts.flow.cardsBottom + 0.32 : null;
|
|
1831
|
-
renderAzureCallout(slide, content, follow != null && follow < (region.y || 5.8) ? { ...region, y: follow } : region, P);
|
|
1832
|
-
return true;
|
|
1833
|
-
}
|
|
1834
|
-
if (layout === "cover" && name === "title" && typeof content === "string" && content.includes("**")) {
|
|
1835
|
-
const role = fontRoles.cover_title || {};
|
|
1836
|
-
slide.addText(azureTitleRuns(content, P), {
|
|
1837
|
-
objectName: "title@legacy",
|
|
1838
|
-
x: region.x || 0, y: region.y || 0, w: region.w || 8, h: region.h || 1,
|
|
1839
|
-
fontFace: role.font || P.titleFont, fontSize: role.size || 48, bold: role.bold !== false,
|
|
1840
|
-
align: role.align || "left", valign: "middle",
|
|
1841
|
-
charSpacing: role.charSpacing != null ? role.charSpacing : -1, lineSpacingMultiple: role.lineSpacing || 1.08,
|
|
1842
|
-
});
|
|
1843
|
-
return true;
|
|
1844
|
-
}
|
|
1845
|
-
if (typeof content === "object") {
|
|
1846
|
-
if (content.type === "body" && (layout === "content" || layout === "main")) {
|
|
1847
|
-
// Cards ONLY for section-structured bodies (≥2 ### sections) with no image.
|
|
1848
|
-
// Prose bullets, or any body sharing the slide with an image, render as a
|
|
1849
|
-
// normal text column (fall through to addRegion) so nothing is cramped.
|
|
1850
|
-
const sections = (content.items || []).filter((i) => i.type === "section");
|
|
1851
|
-
if (sections.length >= 2 && !opts.hasImage) {
|
|
1852
|
-
const bottom = renderAzureCards(slide, content.items, region, P);
|
|
1853
|
-
if (opts.flow) opts.flow.cardsBottom = bottom;
|
|
1854
|
-
return true;
|
|
1855
|
-
}
|
|
1856
|
-
return false;
|
|
1857
|
-
}
|
|
1858
|
-
if (content.type === "kpi-table") {
|
|
1859
|
-
if (content.chart) {
|
|
1860
|
-
// Pie and doughnut slices carry white labels, so they use the darker tones only.
|
|
1861
|
-
const round = ["pie", "doughnut"].includes(content.chart.type);
|
|
1862
|
-
const series = round ? [P.primary, P.primary_deep, P.azure, P.ink_muted] : [P.primary, P.azure_soft, P.primary_deep, P.ink_muted];
|
|
1863
|
-
renderChart(slide, content, region, { ...P, series }, opts.bottom);
|
|
1864
|
-
}
|
|
1865
|
-
else if (looksLikeKpi(content) && kpiFits(content, region.w)) {
|
|
1866
|
-
renderAzureKpi(slide, content, region, P);
|
|
1867
|
-
}
|
|
1868
|
-
else renderAzureTable(slide, content, region, P, opts.bottom);
|
|
1869
|
-
return true;
|
|
1870
|
-
}
|
|
1871
|
-
if (content.type === "summary-group") {
|
|
1872
|
-
const idx = name === "group_bottom" ? 1 : 0;
|
|
1873
|
-
renderAzureGroupCard(slide, content, region, P, idx, opts.groupHeight);
|
|
1874
|
-
return true;
|
|
1875
|
-
}
|
|
1876
|
-
}
|
|
1877
|
-
return false;
|
|
1878
|
-
}
|
|
1879
|
-
|
|
1880
|
-
// ── Placements (author-positioned boxes and shapes) ─────────────────────────
|
|
1881
|
-
|
|
1882
|
-
/** Region name a placed block should be rendered as. */
|
|
1883
|
-
function partRegionName(blockType) {
|
|
1884
|
-
switch (blockType) {
|
|
1885
|
-
case "title": return "title";
|
|
1886
|
-
case "kpi-table": return "table";
|
|
1887
|
-
case "image": return "image";
|
|
1888
|
-
case "figure-caption": return "figure_caption";
|
|
1889
|
-
case "table-caption": return "table_caption";
|
|
1890
|
-
default: return "body";
|
|
1891
|
-
}
|
|
1892
|
-
}
|
|
1893
|
-
|
|
1894
|
-
/** font_role a placed block falls back to when its box names none. */
|
|
1895
|
-
function defaultPartRole(blockType) {
|
|
1896
|
-
switch (blockType) {
|
|
1897
|
-
case "title": return "section_title";
|
|
1898
|
-
case "figure-caption": return "figure_caption";
|
|
1899
|
-
case "table-caption": return "table_caption";
|
|
1900
|
-
case "notes": return "disclaimer";
|
|
1901
|
-
default: return "body_text";
|
|
1902
|
-
}
|
|
1903
|
-
}
|
|
1904
|
-
|
|
1905
|
-
function estimatePartHeight(content, widthIn, role) {
|
|
1906
|
-
const size = role.size || 13;
|
|
1907
|
-
const spacing = role.lineSpacing || 1.3;
|
|
1908
|
-
if (typeof content === "string") {
|
|
1909
|
-
return estimateTextHeight(content, widthIn, size, spacing) + 0.08;
|
|
1910
|
-
}
|
|
1911
|
-
if (content && content.type === "body") {
|
|
1912
|
-
const text = (content.items || []).map((i) => i.heading || i.text || "").join("\n");
|
|
1913
|
-
return estimateTextHeight(text, widthIn, size, spacing) + 0.12;
|
|
1914
|
-
}
|
|
1915
|
-
return 0.5;
|
|
1916
|
-
}
|
|
1917
|
-
|
|
1918
|
-
/**
|
|
1919
|
-
* Turn a shape placement into the decoration element addDecorations reads.
|
|
1920
|
-
*
|
|
1921
|
-
* A rule is drawn as a zero-height rect whose stroke is the visible mark, so it
|
|
1922
|
-
* reads its colour and thickness from `color`/`width` while every other
|
|
1923
|
-
* primitive reads `line`/`line_width`. Authors write one spelling — `line=` and
|
|
1924
|
-
* `lw=` — and the difference is absorbed here.
|
|
1925
|
-
*/
|
|
1926
|
-
function shapeToDecoration(placement, sourceDir) {
|
|
1927
|
-
const isRule = placement.shape === "line";
|
|
1928
|
-
const element = {
|
|
1929
|
-
type: placement.shape,
|
|
1930
|
-
x: placement.x, y: placement.y, w: placement.w, h: placement.h,
|
|
1931
|
-
};
|
|
1932
|
-
for (const [key, value] of Object.entries(placement)) {
|
|
1933
|
-
if (["kind", "z", "shape", "x", "y", "w", "h"].includes(key)) continue;
|
|
1934
|
-
if (key === "lw") element[isRule ? "width" : "line_width"] = value;
|
|
1935
|
-
else if (key === "line" && isRule) element.color = value;
|
|
1936
|
-
else if (key === "src") element.assetPath = resolveImagePath(value, sourceDir);
|
|
1937
|
-
else element[key] = value;
|
|
1938
|
-
}
|
|
1939
|
-
return element;
|
|
1940
|
-
}
|
|
1941
|
-
|
|
1942
|
-
/**
|
|
1943
|
-
* Render one placed box by flowing its blocks down the box.
|
|
1944
|
-
*
|
|
1945
|
-
* Every block but the last takes the height its content needs; the last takes
|
|
1946
|
-
* whatever is left, so a heading followed by body fills the box rather than
|
|
1947
|
-
* floating in its top edge. Nothing here consults the auto-nudge rules: a box
|
|
1948
|
-
* lands exactly where the author put it, and the QA gate reports the consequence.
|
|
1949
|
-
*/
|
|
1950
|
-
function renderPlacementBox(slide, placement, ctx) {
|
|
1951
|
-
const blocks = placement.blocks || [];
|
|
1952
|
-
if (blocks.length === 0) return;
|
|
1953
|
-
|
|
1954
|
-
// Columns replace the body region, so they render in the template's own idiom.
|
|
1955
|
-
// An explicit box is literal: the author asked for this content, here.
|
|
1956
|
-
const useRich = ctx.rich && placement.z === "content";
|
|
1957
|
-
const bottom = placement.y + (placement.h != null ? placement.h : 1.0);
|
|
1958
|
-
let y = placement.y;
|
|
1959
|
-
|
|
1960
|
-
blocks.forEach((block, i) => {
|
|
1961
|
-
const remaining = Math.max(0.2, bottom - y);
|
|
1962
|
-
const roleName = placement.role || defaultPartRole(block.type);
|
|
1963
|
-
const role = ctx.fontRoles[roleName] || {};
|
|
1964
|
-
const content = extractContent(block);
|
|
1965
|
-
const isLast = i === blocks.length - 1;
|
|
1966
|
-
const h = isLast
|
|
1967
|
-
? remaining
|
|
1968
|
-
: Math.min(remaining, Math.max(0.2, estimatePartHeight(content, placement.w, role)));
|
|
1969
|
-
|
|
1970
|
-
const region = {
|
|
1971
|
-
x: placement.x, y, w: placement.w, h,
|
|
1972
|
-
content,
|
|
1973
|
-
font_role: roleName,
|
|
1974
|
-
originX: placement.x,
|
|
1975
|
-
...(placement.align ? { align: placement.align } : {}),
|
|
1976
|
-
...(placement.valign ? { valign: placement.valign } : {}),
|
|
1977
|
-
};
|
|
1978
|
-
|
|
1979
|
-
const name = partRegionName(block.type);
|
|
1980
|
-
const rendered = useRich &&
|
|
1981
|
-
renderAzureRegion(slide, ctx.layout, name, region, ctx.fontRoles, ctx.P, {});
|
|
1982
|
-
if (!rendered) {
|
|
1983
|
-
addRegion(slide, name, region, ctx.fontRoles, ctx.pageH, y + h, false, ctx.sourceDir);
|
|
1984
|
-
}
|
|
1985
|
-
y += h;
|
|
1986
|
-
});
|
|
1987
|
-
}
|
|
1988
|
-
|
|
1989
|
-
function renderPlacements(slide, placements, band, ctx) {
|
|
1990
|
-
for (const placement of placements) {
|
|
1991
|
-
if (placement.z !== band) continue;
|
|
1992
|
-
if (placement.kind === "shape") {
|
|
1993
|
-
addDecorations(slide, [shapeToDecoration(placement, ctx.sourceDir)], ctx.fontRoles);
|
|
1994
|
-
} else {
|
|
1995
|
-
renderPlacementBox(slide, placement, ctx);
|
|
1996
|
-
}
|
|
1997
|
-
}
|
|
1998
|
-
}
|
|
1999
|
-
|
|
2000
|
-
// ── Main render function ────────────────────────────────────────────────────
|
|
2001
|
-
|
|
2002
|
-
/**
|
|
2003
|
-
* Render the full resolved spec to a PPTX file.
|
|
2004
|
-
*
|
|
2005
|
-
* @param {object} resolved - Resolved spec from layout-resolver
|
|
2006
|
-
* @param {object} templateObj - Template object from template-registry
|
|
2007
|
-
* @param {string} outputPath - Where to write the .pptx file
|
|
2008
|
-
* @returns {Promise<string>} Resolves with the output path
|
|
2009
|
-
*/
|
|
2010
|
-
/**
|
|
2011
|
-
* The deck-wide `notice:` (for example "예시 데이터 — 실제 수치로 바꿔 주세요") as a
|
|
2012
|
-
* coloured tag at the bottom left of every slide, cover included, so a sample or
|
|
2013
|
-
* draft deck can never be mistaken for a sourced one. A 9 pt grey footnote was easy
|
|
2014
|
-
* to miss; the tag keeps its own fill and text colour (template palette
|
|
2015
|
-
* `notice_fill` / `notice_ink`, default a light red with dark red text, 6:1).
|
|
2016
|
-
*/
|
|
2017
|
-
const NOTICE_FILL = "FDECEA";
|
|
2018
|
-
const NOTICE_INK = "A21B12";
|
|
2019
|
-
let NOTICE = { ...LEGACY_TOKENS.notice };
|
|
2020
|
-
|
|
2021
|
-
function addNotice(slide, text, pageW, pageH, pal) {
|
|
2022
|
-
const label = cleanMd(text);
|
|
2023
|
-
const fontSize = NOTICE.size;
|
|
2024
|
-
const textW = (weightedTextLength(label) * fontSize * 0.98) / 72;
|
|
2025
|
-
const w = Math.min(pageW * 0.55, textW + 0.46);
|
|
2026
|
-
const h = NOTICE.h;
|
|
2027
|
-
const x = NOTICE.x, y = pageH - h - NOTICE.bottom;
|
|
2028
|
-
const fill = ((pal && pal.notice_fill) || NOTICE_FILL).replace("#", "");
|
|
2029
|
-
const ink = ((pal && pal.notice_ink) || NOTICE_INK).replace("#", "");
|
|
2030
|
-
slide.addShape("roundRect", { x, y, w, h, rectRadius: 0.15, fill: { color: fill }, line: { type: "none" }, objectName: "lit-notice tag" });
|
|
2031
|
-
slide.addText(label, {
|
|
2032
|
-
objectName: "lit-notice text",
|
|
2033
|
-
x: x + 0.16, y, w: w - 0.32, h,
|
|
2034
|
-
fontFace: FONT_BOLD, fontSize, bold: true, color: ink,
|
|
2035
|
-
align: "left", valign: "middle", margin: 0, fit: "shrink",
|
|
2036
|
-
});
|
|
2037
|
-
}
|
|
2038
|
-
|
|
2039
|
-
/**
|
|
2040
|
-
* Give the first object drawn on a slide that has no name of its own the name `family@<id>`, so a
|
|
2041
|
-
* checker can read the layout family the source asked for and compare it with what it measures.
|
|
2042
|
-
*/
|
|
2043
|
-
function stampFamily(slide, family) {
|
|
2044
|
-
const at = { addText: 1, addShape: 1, addImage: 0, addTable: 1, addChart: 2 };
|
|
2045
|
-
const originals = {};
|
|
2046
|
-
let done = false;
|
|
2047
|
-
const restore = () => { for (const m of Object.keys(originals)) slide[m] = originals[m]; };
|
|
2048
|
-
for (const [method, index] of Object.entries(at)) {
|
|
2049
|
-
originals[method] = slide[method];
|
|
2050
|
-
slide[method] = (...args) => {
|
|
2051
|
-
if (!(args[index] && args[index].objectName)) {
|
|
2052
|
-
restore();
|
|
2053
|
-
done = true;
|
|
2054
|
-
args[index] = { ...(args[index] || {}), objectName: `family@${family}` };
|
|
2055
|
-
}
|
|
2056
|
-
return originals[method].apply(slide, args);
|
|
2057
|
-
};
|
|
2058
|
-
}
|
|
2059
|
-
/** Whether a shape took the name; unwraps the slide either way. */
|
|
2060
|
-
return () => { restore(); return done; };
|
|
2061
|
-
}
|
|
2062
|
-
|
|
2063
|
-
async function render(resolved, templateObj, outputPath) {
|
|
2064
|
-
const pack = (templateObj && templateObj.pack) || null;
|
|
2065
|
-
if (pack && !pack.legacy) return require("./render-pack").renderPack(resolved, templateObj, outputPath);
|
|
2066
|
-
applyTokens(pack && pack.tokens);
|
|
2067
|
-
const fontRoles = buildFontRoles(templateObj);
|
|
2068
|
-
|
|
2069
|
-
// Brand-neutralize: derive font/spacing/accent from the template, falling back
|
|
2070
|
-
// to Pretendard when a template names no family.
|
|
2071
|
-
const tpl = (templateObj && templateObj.template) || {};
|
|
2072
|
-
const tplFonts = tpl.fonts || {};
|
|
2073
|
-
const tplType = tpl.global_typography || {};
|
|
2074
|
-
FONT_BOLD = tplFonts.title || "Pretendard";
|
|
2075
|
-
FONT_MEDIUM = tplFonts.body || "Pretendard";
|
|
2076
|
-
FONT_LIGHT = tplFonts.light || "Pretendard";
|
|
2077
|
-
CHAR_SPACING = tplType.char_spacing != null ? tplType.char_spacing : -0.7;
|
|
2078
|
-
SEC_HEADER_FONT = FONT_BOLD;
|
|
2079
|
-
TOC_FONT = FONT_BOLD;
|
|
2080
|
-
BULLET_COLOR = (tplType.bullet_color || "1D4ED8").replace("#", "");
|
|
2081
|
-
SECTION_ACCENT = (tplType.section_accent || "1D4ED8").replace("#", "");
|
|
2082
|
-
|
|
2083
|
-
// Rich card/KPI/divider rendering — palette-driven, for templates with the rich-blocks
|
|
2084
|
-
// capability. Tokens come from the template palette.
|
|
2085
|
-
const rich = Boolean(pack && (pack.capabilities || []).includes("rich-blocks"));
|
|
2086
|
-
const pal = tpl.palette || {};
|
|
2087
|
-
PLAIN_CHART_STYLE = {
|
|
2088
|
-
series: [pal.ink || "#3C3836", pal.accent_green_2 || pal.primary || "#689D6A", pal.accent_amber || "#D79921", pal.accent_purple || "#B16286"],
|
|
2089
|
-
ink: pal.ink || "#3C3836", ink_muted: pal.ink_muted || "#6E6256", line: pal.line || "#CBC2B5",
|
|
2090
|
-
titleFont: FONT_BOLD, bodyFont: FONT_MEDIUM,
|
|
2091
|
-
};
|
|
2092
|
-
const P = {
|
|
2093
|
-
titleFont: FONT_BOLD, bodyFont: FONT_MEDIUM,
|
|
2094
|
-
primary: pal.primary || "#1D4ED8", primary_deep: pal.primary_deep || "#0B2E6F",
|
|
2095
|
-
ink: pal.ink || "#0E1B2C", ink_muted: pal.ink_muted || "#51607A",
|
|
2096
|
-
tint: pal.tint || "#EEF4FF", line: pal.line || "#D5DEEC", azure_soft: pal.azure_soft || "#93C5FD", azure: pal.azure || "#3B82F6",
|
|
2097
|
-
emphasisTextColor: pal.emphasis_text || "#C9D8F5",
|
|
2098
|
-
};
|
|
2099
|
-
|
|
2100
|
-
const dims =
|
|
2101
|
-
(templateObj && templateObj.template && templateObj.template.dimensions) || {};
|
|
2102
|
-
const pageW = dims.width || 10.833;
|
|
2103
|
-
const pageH = dims.height || 7.5;
|
|
2104
|
-
|
|
2105
|
-
// Loaded here, not at module load, so AST-only and --list-* runs never install.
|
|
2106
|
-
const PptxGenJS = require("pptxgenjs");
|
|
2107
|
-
const pptx = new PptxGenJS();
|
|
2108
|
-
pptx.defineLayout({ name: "A4_LANDSCAPE", width: pageW, height: pageH });
|
|
2109
|
-
pptx.layout = "A4_LANDSCAPE";
|
|
2110
|
-
|
|
2111
|
-
const title = (resolved.deck && resolved.deck.title) || "Presentation";
|
|
2112
|
-
pptx.title = title;
|
|
2113
|
-
const sourceDir = resolved.sourceDir;
|
|
2114
|
-
const cramStress = isCramStressDeck(resolved);
|
|
2115
|
-
const deckMeta = (resolved.deck && resolved.deck.metadata) || {};
|
|
2116
|
-
const notice = typeof deckMeta.notice === "string" && deckMeta.notice.trim() ? deckMeta.notice.trim() : null;
|
|
2117
|
-
|
|
2118
|
-
for (const slideSpec of resolved.slides || []) {
|
|
2119
|
-
const slide = pptx.addSlide();
|
|
2120
|
-
stampFamily(slide, slideSpec.family || slideSpec.layout);
|
|
2121
|
-
|
|
2122
|
-
// Draw order is a band at a time: template decorations, then anything the
|
|
2123
|
-
// author put under the content, then the template's regions, then the
|
|
2124
|
-
// columns that replaced the body, then anything sitting on top.
|
|
2125
|
-
addDecorations(slide, slideSpec.decorations, fontRoles);
|
|
2126
|
-
|
|
2127
|
-
const placements = slideSpec.placements || [];
|
|
2128
|
-
const placementCtx = {
|
|
2129
|
-
rich, P, fontRoles, pageH, sourceDir,
|
|
2130
|
-
layout: slideSpec.layout,
|
|
2131
|
-
};
|
|
2132
|
-
renderPlacements(slide, placements, "under", placementCtx);
|
|
2133
|
-
|
|
2134
|
-
const regions = adjustedRegionsForImageColumn(slideSpec.regions || {});
|
|
2135
|
-
if (cramStress && ["content", "main"].includes(slideSpec.layout) && regions.body) {
|
|
2136
|
-
renderCramStressSlide(slide, { ...slideSpec, regions }, pageW, pageH, sourceDir);
|
|
2137
|
-
continue;
|
|
2138
|
-
}
|
|
2139
|
-
|
|
2140
|
-
// Calculate bottom boundary for regions without explicit h
|
|
2141
|
-
const bottomBoundary = {};
|
|
2142
|
-
for (const [name, region] of Object.entries(regions)) {
|
|
2143
|
-
let bottom = pageH - 0.5;
|
|
2144
|
-
for (const [otherName, otherRegion] of Object.entries(regions)) {
|
|
2145
|
-
if (otherName === name || otherName === "title") continue;
|
|
2146
|
-
if (name === "body" && ["image", "figure_caption"].includes(otherName)) continue;
|
|
2147
|
-
if (otherRegion.y && otherRegion.y > (region.y || 0) && otherRegion.h) {
|
|
2148
|
-
const candidateBottom = otherRegion.y - 0.15;
|
|
2149
|
-
if (candidateBottom < bottom) bottom = candidateBottom;
|
|
2150
|
-
}
|
|
2151
|
-
}
|
|
2152
|
-
bottomBoundary[name] = bottom;
|
|
2153
|
-
}
|
|
2154
|
-
|
|
2155
|
-
const tocSlide = isTocSlide(regions);
|
|
2156
|
-
const hasImage = Object.entries(regions).some(([n, r]) =>
|
|
2157
|
-
(n === "image" || n === "figure") && r.content &&
|
|
2158
|
-
(r.content.type === "image" || r.content.type === "image-grid"));
|
|
2159
|
-
|
|
2160
|
-
// Summary groups sit side by side, so they share the height the fuller one needs.
|
|
2161
|
-
const groups = Object.values(regions).filter((r) => r.content && r.content.type === "summary-group");
|
|
2162
|
-
const groupHeight = rich && groups.length
|
|
2163
|
-
? Math.max(...groups.map((r) => groupCard(r.content, r.w || 5.6, r.h || 2.4, P).need))
|
|
2164
|
-
: null;
|
|
2165
|
-
|
|
2166
|
-
const flow = {};
|
|
2167
|
-
for (const [name, region] of Object.entries(regions)) {
|
|
2168
|
-
if (rich && renderAzureRegion(slide, slideSpec.layout, name, region, fontRoles, P, { hasImage, bottom: bottomBoundary[name], groupHeight, flow })) continue;
|
|
2169
|
-
addRegion(slide, name, region, fontRoles, pageH, bottomBoundary[name], tocSlide, sourceDir);
|
|
2170
|
-
}
|
|
2171
|
-
|
|
2172
|
-
renderPlacements(slide, placements, "content", placementCtx);
|
|
2173
|
-
renderPlacements(slide, placements, "over", placementCtx);
|
|
2174
|
-
if (notice) addNotice(slide, notice, pageW, pageH, pal);
|
|
2175
|
-
}
|
|
2176
|
-
|
|
2177
|
-
await writeTagged(pptx, outputPath);
|
|
2178
|
-
return outputPath;
|
|
2179
|
-
}
|
|
2180
|
-
|
|
2181
|
-
const HANGUL = /[\u1100-\u11FF\u3130-\u318F\uAC00-\uD7A3]/u;
|
|
2182
|
-
|
|
2183
|
-
/**
|
|
2184
|
-
* Korean runs carry their language. pptxgenjs writes lang="en-US" on every run, and a renderer then sets
|
|
2185
|
-
* Hangul by Latin rules (LibreOffice under a non-Korean system locale puts space between Hangul and
|
|
2186
|
-
* digits, so "주 2일" reads "주 2 일"). A run holding Hangul becomes ko-KR with en-US as its alternate
|
|
2187
|
-
* language, and a chart holding Hangul names ko-KR for its text; Latin-only runs keep en-US.
|
|
2188
|
-
*/
|
|
2189
|
-
function tagLanguages(name, xml) {
|
|
2190
|
-
if (/^ppt\/slides\/slide\d+\.xml$/u.test(name)) {
|
|
2191
|
-
return xml.replace(/<a:r><a:rPr\b([^>]*?)(\/?>)([\s\S]*?)<a:t>([^<]*)<\/a:t><\/a:r>/gu, (m, attrs, close, mid, text) => {
|
|
2192
|
-
if (!HANGUL.test(text)) return m;
|
|
2193
|
-
const rest = attrs.replace(/\s(?:lang|altLang)="[^"]*"/gu, "");
|
|
2194
|
-
return `<a:r><a:rPr lang="ko-KR" altLang="en-US"${rest}${close}${mid}<a:t>${text}</a:t></a:r>`;
|
|
2195
|
-
});
|
|
2196
|
-
}
|
|
2197
|
-
if (/^ppt\/charts\/chart\d+\.xml$/u.test(name) && HANGUL.test(xml)) {
|
|
2198
|
-
let out = xml.replace(/<a:defRPr\b(?![^>]*\blang=)/gu, '<a:defRPr lang="ko-KR" altLang="en-US"')
|
|
2199
|
-
.replace(/<a:endParaRPr lang="en-US"/gu, '<a:endParaRPr lang="ko-KR" altLang="en-US"');
|
|
2200
|
-
if (!/<c:lang\b/u.test(out)) out = out.replace(/(<c:date1904 val="[01]"\/>)/u, '$1<c:lang val="ko-KR"/>');
|
|
2201
|
-
return out;
|
|
2202
|
-
}
|
|
2203
|
-
return xml;
|
|
2204
|
-
}
|
|
2205
|
-
|
|
2206
|
-
/** Write a deck with its Korean runs and charts tagged (tagLanguages); `edit` may rewrite a part first. */
|
|
2207
|
-
async function writeTagged(pptx, outputPath, edit = (name, xml) => xml) {
|
|
2208
|
-
const JSZip = require("jszip");
|
|
2209
|
-
const zip = await JSZip.loadAsync(await pptx.write({ outputType: "nodebuffer" }));
|
|
2210
|
-
for (const name of Object.keys(zip.files).filter((n) => /^ppt\/(slides\/slide|charts\/chart|theme\/theme)\d+\.xml$/u.test(n))) {
|
|
2211
|
-
const xml = await zip.file(name).async("string");
|
|
2212
|
-
zip.file(name, tagLanguages(name, edit(name, xml)));
|
|
2213
|
-
}
|
|
2214
|
-
fs.writeFileSync(outputPath, await zip.generateAsync({ type: "nodebuffer", compression: "DEFLATE" }));
|
|
2215
|
-
}
|
|
2216
|
-
|
|
2217
|
-
// ── Tonality packs ──────────────────────────────────────────────────────────
|
|
2218
|
-
//
|
|
2219
|
-
// A pack deck is drawn by render-pack.js. It reuses the helpers below, and the font globals the
|
|
2220
|
-
// free placement boxes read are switched to the pack's faces before it draws.
|
|
2221
|
-
|
|
2222
|
-
function usePackFonts(titleFont, bodyFont, tokens) {
|
|
2223
|
-
FONT_BOLD = titleFont;
|
|
2224
|
-
FONT_MEDIUM = bodyFont;
|
|
2225
|
-
FONT_LIGHT = bodyFont;
|
|
2226
|
-
CHAR_SPACING = 0;
|
|
2227
|
-
applyTokens(tokens);
|
|
2228
|
-
}
|
|
2229
|
-
|
|
2230
|
-
module.exports = {
|
|
2231
|
-
render,
|
|
2232
|
-
shared: {
|
|
2233
|
-
hex, cleanMd, mdRuns, linkedTableCell, resolveImagePath, readImageSize, containBox,
|
|
2234
|
-
kpiCards, numericColumns, proportionalWidths, renderChart, stampFamily, renderPlacements, usePackFonts, writeTagged,
|
|
2235
|
-
},
|
|
2236
|
-
};
|