@litfamily/litmuse 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/README.md +20 -0
- package/README_ko-KR.md +20 -0
- package/bin/litmuse.mjs +4 -0
- package/lib/stage-card.mjs +180 -5
- package/package.json +1 -1
- package/plugin/.muse-plugin/plugin.json +26 -11
- package/plugin/commands/lit-status.md +2 -1
- package/plugin/commands/lit.md +1 -1
- package/plugin/hooks/lib/digest.mjs +1 -1
- package/plugin/hooks/lib/next-step.mjs +68 -0
- package/plugin/hooks/lib/routes.mjs +59 -0
- package/plugin/hooks/lib/workflow-stack.mjs +318 -0
- package/plugin/hooks/session-start.mjs +29 -2
- package/plugin/hooks/user-prompt-submit.mjs +138 -7
- package/plugin/lib/capped-fetch.mjs +25 -0
- package/plugin/lib/pinned-fetch.mjs +79 -0
- package/plugin/lib/plan-task-rows.mjs +32 -0
- package/plugin/lib/render_pages.py +29 -1
- package/plugin/lib/rewind/collect.mjs +679 -0
- package/plugin/lib/rewind/gauge.mjs +72 -0
- package/plugin/lib/rewind/next-step.mjs +91 -0
- package/plugin/lib/rewind/redact.mjs +83 -0
- package/plugin/lib/rewind/transcripts.mjs +227 -0
- package/plugin/skills/frontend-ui-ux/SKILL.md +24 -9
- package/plugin/skills/frontend-ui-ux/kit/components.css +414 -0
- package/plugin/skills/frontend-ui-ux/kit/fonts.json +22 -0
- package/plugin/skills/frontend-ui-ux/kit/layouts/landing.html +164 -0
- package/plugin/skills/frontend-ui-ux/kit/layouts/settings.html +114 -0
- package/plugin/skills/frontend-ui-ux/kit/layouts/workspace.html +115 -0
- package/plugin/skills/frontend-ui-ux/kit/styles/computational-brutalism.json +227 -0
- package/plugin/skills/frontend-ui-ux/kit/styles/field-notes.json +205 -0
- package/plugin/skills/frontend-ui-ux/kit/styles/graphite.json +200 -0
- package/plugin/skills/frontend-ui-ux/kit/styles/meadow.json +199 -0
- package/plugin/skills/frontend-ui-ux/kit/styles/neo-brutalism.json +222 -0
- package/plugin/skills/frontend-ui-ux/kit/ui.js +226 -0
- package/plugin/skills/frontend-ui-ux/references/complete-contract.md +2 -1
- package/plugin/skills/frontend-ui-ux/references/craft-floor.md +9 -4
- package/plugin/skills/frontend-ui-ux/references/default-editorial-pixel.json +59 -7
- package/plugin/skills/frontend-ui-ux/references/house-styles.md +234 -0
- package/plugin/skills/frontend-ui-ux/references/motion-guide.md +3 -3
- package/plugin/skills/frontend-ui-ux/references/slop-register.md +7 -7
- package/plugin/skills/frontend-ui-ux/scripts/design-lock.mjs +400 -0
- package/plugin/skills/frontend-ui-ux/scripts/interface-probe-lock.mjs +196 -0
- package/plugin/skills/frontend-ui-ux/scripts/interface-probe-page.mjs +31 -10
- package/plugin/skills/frontend-ui-ux/scripts/interface-probe-rules.mjs +19 -7
- package/plugin/skills/frontend-ui-ux/scripts/interface-probe-static.mjs +4 -0
- package/plugin/skills/frontend-ui-ux/scripts/interface-probe.mjs +59 -7
- package/plugin/skills/frontend-ui-ux/scripts/pixel-art.mjs +60 -0
- package/plugin/skills/lit-adhd/SKILL.md +188 -0
- package/plugin/skills/lit-adhd/references/card-examples.md +121 -0
- package/plugin/skills/lit-adhd/references/status-taxonomy.md +43 -0
- package/plugin/skills/lit-adhd/scripts/rewind.mjs +11 -0
- package/plugin/skills/lit-diagram-drawer/SKILL.md +3 -3
- package/plugin/skills/lit-diagram-drawer/scripts/diagram-craft-rules.mjs +1 -2
- package/plugin/skills/lit-diagram-drawer/scripts/visual-quality.mjs +0 -1
- package/plugin/skills/lit-docx/SKILL.md +1 -1
- package/plugin/skills/lit-hijack/SKILL.md +589 -0
- package/plugin/skills/lit-hijack/references/capture-and-provenance.md +123 -0
- package/plugin/skills/lit-hijack/references/clean-room-build.md +133 -0
- package/plugin/skills/lit-hijack/references/distil-and-licence.md +128 -0
- package/plugin/skills/lit-hijack/references/gap-and-port-design.md +152 -0
- package/plugin/skills/lit-hijack/references/parity-and-report.md +133 -0
- package/plugin/skills/lit-hijack/scripts/hijack.mjs +879 -0
- package/plugin/skills/lit-pptx/SKILL.md +20 -9
- package/plugin/skills/lit-pptx/fonts/jetbrains-mono/JetBrainsMono-Bold.ttf +0 -0
- package/plugin/skills/lit-pptx/fonts/jetbrains-mono/JetBrainsMono-Regular.ttf +0 -0
- package/plugin/skills/lit-pptx/fonts/jetbrains-mono/OFL.txt +93 -0
- package/plugin/skills/lit-pptx/fonts/provenance.json +32 -1
- package/plugin/skills/lit-pptx/references/direction-step.md +19 -2
- package/plugin/skills/lit-pptx/references/examples/13-engineering-review-computational-brutalism-ko.md +173 -0
- package/plugin/skills/lit-pptx/references/examples/14-launch-pitch-neo-brutalism-ko.md +154 -0
- package/plugin/skills/lit-pptx/references/examples/15-design-lecture-bauhaus-ko.md +139 -0
- package/plugin/skills/lit-pptx/references/examples/16-community-programme-riso-ko.md +158 -0
- package/plugin/skills/lit-pptx/references/examples/17-public-data-briefing-newsroom-ko.md +179 -0
- package/plugin/skills/lit-pptx/references/tonalities/bauhaus.md +112 -0
- package/plugin/skills/lit-pptx/references/tonalities/computational-brutalism.md +118 -0
- package/plugin/skills/lit-pptx/references/tonalities/neo-brutalism.md +125 -0
- package/plugin/skills/lit-pptx/references/tonalities/newsroom.md +115 -0
- package/plugin/skills/lit-pptx/references/tonalities/riso.md +109 -0
- package/plugin/skills/lit-pptx/scripts/compile-deck.js +1 -1
- package/plugin/skills/lit-pptx/scripts/craft_extras.py +136 -92
- package/plugin/skills/lit-pptx/scripts/embed_fonts.py +13 -1
- package/plugin/skills/lit-pptx/scripts/lib/grid-resolver.js +105 -116
- package/plugin/skills/lit-pptx/scripts/lib/layout-resolver.js +9 -15
- package/plugin/skills/lit-pptx/scripts/lib/render-pack.js +368 -561
- package/plugin/skills/lit-pptx/scripts/lib/slide-grid.js +104 -0
- package/plugin/skills/lit-pptx/scripts/lib/template-registry.js +57 -6
- package/plugin/skills/lit-pptx/scripts/qa_deck.py +4 -4
- package/plugin/skills/lit-pptx/templates/tonalities/atlas/pack.yaml +1 -1
- package/plugin/skills/lit-pptx/templates/tonalities/bauhaus/pack.yaml +98 -0
- package/plugin/skills/lit-pptx/templates/tonalities/chalk/pack.yaml +1 -1
- package/plugin/skills/lit-pptx/templates/tonalities/computational-brutalism/pack.yaml +99 -0
- package/plugin/skills/lit-pptx/templates/tonalities/gazette/pack.yaml +1 -1
- package/plugin/skills/lit-pptx/templates/tonalities/ledger/pack.yaml +1 -1
- package/plugin/skills/lit-pptx/templates/tonalities/neo-brutalism/pack.yaml +107 -0
- package/plugin/skills/lit-pptx/templates/tonalities/newsroom/pack.yaml +99 -0
- package/plugin/skills/lit-pptx/templates/tonalities/night/pack.yaml +1 -1
- package/plugin/skills/lit-pptx/templates/tonalities/paper/pack.yaml +1 -1
- package/plugin/skills/lit-pptx/templates/tonalities/riso/pack.yaml +99 -0
- package/plugin/skills/lit-pptx/templates/tonalities/signal/pack.yaml +2 -2
- package/plugin/skills/lit-pptx/templates/tonalities/studio/pack.yaml +1 -1
- package/plugin/skills/lit-recap/SKILL.md +3 -0
- package/plugin/skills/lit-schematic/SKILL.md +241 -0
- package/plugin/skills/lit-schematic/examples/iso-heat-exchanger/build.mjs +14 -0
- package/plugin/skills/lit-schematic/examples/iso-heat-exchanger/scene.mjs +8 -0
- package/plugin/skills/lit-schematic/examples/journal-adsorption-rig/build.mjs +22 -0
- package/plugin/skills/lit-schematic/examples/pid-dosing-skid/build.mjs +73 -0
- package/plugin/skills/lit-schematic/references/cutaway-outputs.md +25 -0
- package/plugin/skills/lit-schematic/references/isometric-craft.md +24 -0
- package/plugin/skills/lit-schematic/references/journal-figures.md +19 -0
- package/plugin/skills/lit-schematic/references/kit-api.md +27 -0
- package/plugin/skills/lit-schematic/references/pfd-and-layout.md +20 -0
- package/plugin/skills/lit-schematic/references/pid-drafting.md +26 -0
- package/plugin/skills/lit-schematic/references/pid-symbols.md +27 -0
- package/plugin/skills/lit-schematic/references/verification.md +24 -0
- package/plugin/skills/lit-schematic/scripts/doctor.mjs +13 -0
- package/plugin/skills/lit-schematic/scripts/lib/figure.mjs +49 -0
- package/plugin/skills/lit-schematic/scripts/lib/isa.mjs +18 -0
- package/plugin/skills/lit-schematic/scripts/lib/iso.mjs +58 -0
- package/plugin/skills/lit-schematic/scripts/lib/legend-sheet.mjs +10 -0
- package/plugin/skills/lit-schematic/scripts/lib/qa-core.mjs +241 -0
- package/plugin/skills/lit-schematic/scripts/lib/sheet.mjs +446 -0
- package/plugin/skills/lit-schematic/scripts/lib/svg.mjs +39 -0
- package/plugin/skills/lit-schematic/scripts/lib/theme.mjs +25 -0
- package/plugin/skills/lit-schematic/scripts/motion.mjs +8 -0
- package/plugin/skills/lit-schematic/scripts/qa.mjs +21 -0
- package/plugin/skills/lit-schematic/scripts/render.mjs +38 -0
- package/plugin/skills/lit-schematic/scripts/scaffold.mjs +73 -0
- package/plugin/skills/lit-scientific-visualization/SKILL.md +2 -0
- package/plugin/skills/lit-typographic-motion/SKILL.md +12 -8
- package/plugin/skills/lit-typographic-motion/engine/THIRD_PARTY_NOTICES +11 -0
- package/plugin/skills/lit-typographic-motion/engine/blender/runner.py +218 -0
- package/plugin/skills/lit-typographic-motion/engine/core/presets.mjs +1 -1
- package/plugin/skills/lit-typographic-motion/engine/node/assets.mjs +218 -0
- package/plugin/skills/lit-typographic-motion/engine/node/blender.mjs +144 -0
- package/plugin/skills/lit-typographic-motion/engine/node/board.mjs +136 -0
- package/plugin/skills/lit-typographic-motion/engine/node/done.mjs +42 -3
- package/plugin/skills/lit-typographic-motion/engine/node/gallery.mjs +120 -0
- package/plugin/skills/lit-typographic-motion/engine/node/house.mjs +44 -0
- package/plugin/skills/lit-typographic-motion/engine/node/pipeline.mjs +18 -7
- package/plugin/skills/lit-typographic-motion/engine/node/runtime.mjs +6 -3
- package/plugin/skills/lit-typographic-motion/engine/node/scaffold.mjs +210 -0
- package/plugin/skills/lit-typographic-motion/engine/node/score.mjs +639 -0
- package/plugin/skills/lit-typographic-motion/engine/node/sound-cli.mjs +21 -4
- package/plugin/skills/lit-typographic-motion/engine/node/sound.mjs +126 -27
- package/plugin/skills/lit-typographic-motion/engine/node/stage-serve.mjs +29 -5
- package/plugin/skills/lit-typographic-motion/engine/node/stage.mjs +82 -14
- package/plugin/skills/lit-typographic-motion/engine/node/styles.mjs +214 -0
- package/plugin/skills/lit-typographic-motion/engine/node/treatment.mjs +311 -10
- package/plugin/skills/lit-typographic-motion/engine/vendor/three/LICENSE +21 -0
- package/plugin/skills/lit-typographic-motion/engine/vendor/three/addons/environments/RoomEnvironment.js +144 -0
- package/plugin/skills/lit-typographic-motion/engine/vendor/three/addons/geometries/RoundedBoxGeometry.js +155 -0
- package/plugin/skills/lit-typographic-motion/engine/vendor/three/three.module.js +54571 -0
- package/plugin/skills/lit-typographic-motion/recipes/hero-3d/expected.json +69 -0
- package/plugin/skills/lit-typographic-motion/recipes/hero-3d/index.html +139 -0
- package/plugin/skills/lit-typographic-motion/references/depth.md +122 -0
- package/plugin/skills/lit-typographic-motion/references/sound.md +74 -16
- package/plugin/skills/lit-typographic-motion/references/stage.md +29 -5
- package/plugin/skills/lit-typographic-motion/references/studio.md +245 -0
- package/plugin/skills/lit-typographic-motion/references/treatment.md +31 -8
- package/plugin/skills/lit-typographic-motion/scripts/motion.mjs +48 -7
- package/plugin/skills/lit-typographic-motion/styles/paper.json +41 -0
- package/plugin/skills/lit-typographic-motion/styles/swiss-signal.json +41 -0
- package/plugin/skills/lit-typographic-motion/styles/terminalcore.json +42 -0
- package/plugin/skills/lit-typographic-motion/styles/tidal.json +41 -0
- package/plugin/skills/readme-studio/SKILL.md +1 -1
- package/plugin/skills/start-work/SKILL.md +6 -0
|
@@ -4,20 +4,14 @@
|
|
|
4
4
|
* grid-resolver.js — where things go on a slide, read from a tonality pack and two dials.
|
|
5
5
|
*
|
|
6
6
|
* A pack never reaches this file as a name: it arrives as values (palette roles, a ramp, the
|
|
7
|
-
* treatments it allows, its density and variance). The density dial picks
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* treatments it allows, its density and variance). The density dial picks the spacing tokens; a
|
|
8
|
+
* title treatment styles the title inside its fixed zone and decides what is drawn with it; a
|
|
9
|
+
* layout family decides which treatments a slide may take. Geometry is worked out in points on
|
|
10
|
+
* the 960 x 540 (16:9) or 720 x 540 (4:3) canvas; the renderer converts to inches.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
// Side margin, column width and gutter of the airy, standard, dense and compact grids. Each sums
|
|
16
|
-
// exactly to the canvas width over 12 columns and 11 gutters.
|
|
17
|
-
const GRIDS = {
|
|
18
|
-
"16:9": { airy: { margin: 60, col: 48, gutter: 24 }, standard: { margin: 48, col: 50, gutter: 24 }, dense: { margin: 36, col: 52, gutter: 24 }, compact: { margin: 24, col: 54, gutter: 24 } },
|
|
19
|
-
"4:3": { airy: { margin: 54, col: 40, gutter: 12 }, standard: { margin: 42, col: 42, gutter: 12 }, dense: { margin: 30, col: 44, gutter: 12 }, compact: { margin: 24, col: 45, gutter: 12 } },
|
|
20
|
-
};
|
|
13
|
+
const SG = require("./slide-grid");
|
|
14
|
+
const { CANVAS } = SG;
|
|
21
15
|
|
|
22
16
|
const RAMPS = {
|
|
23
17
|
presented: { source: 12, label: 14, body: 18, lead: 24, title: 32, display: 44, cover: 56, hero: 72 },
|
|
@@ -29,19 +23,22 @@ const RAMPS = {
|
|
|
29
23
|
const RAMP_ORDER = ["source", "label", "body", "lead", "title", "display", "cover", "hero"];
|
|
30
24
|
const SCALE = [6, 12, 18, 24, 36, 48, 72];
|
|
31
25
|
|
|
32
|
-
//
|
|
33
|
-
const ZONE =
|
|
26
|
+
// The content zones of the shared slide grid, the same on both canvases.
|
|
27
|
+
const ZONE = (() => {
|
|
28
|
+
const z = SG.ZONES.content;
|
|
29
|
+
return { top: z.title.y, body: z.body.y, floor: z.body.y + z.body.h, footer: z.footer.y, footerH: z.footer.h };
|
|
30
|
+
})();
|
|
34
31
|
|
|
35
|
-
// What the density dial sets:
|
|
36
|
-
//
|
|
37
|
-
// density 10 is the compact step:
|
|
32
|
+
// What the density dial sets: ramp, gaps, padding, table rows and the empty band a content slide
|
|
33
|
+
// may leave under its last line; the zones themselves never move. From density 5 a deck reads on
|
|
34
|
+
// the reading ramp (body 14 pt); density 10 is the compact step: body 13 pt and the tightest gaps.
|
|
38
35
|
const DENSITY_BANDS = [
|
|
39
|
-
{ upTo: 2,
|
|
40
|
-
{ upTo: 4,
|
|
41
|
-
{ upTo: 6,
|
|
42
|
-
{ upTo: 8,
|
|
43
|
-
{ upTo: 9,
|
|
44
|
-
{ upTo: 10,
|
|
36
|
+
{ upTo: 2, ramp: "presented", item: 18, group: 36, block: 48, pad: 24, rowMin: 36, maxLines: 6, cap: 0.28, stepUp: true },
|
|
37
|
+
{ upTo: 4, ramp: "presented", item: 12, group: 24, block: 36, pad: 24, rowMin: 32, maxLines: 8, cap: 0.24, stepUp: true },
|
|
38
|
+
{ upTo: 6, ramp: "reading", item: 6, group: 18, block: 24, pad: 18, rowMin: 24, maxLines: 12, cap: 0.18, stepUp: true },
|
|
39
|
+
{ upTo: 8, ramp: "reading", item: 6, group: 12, block: 24, pad: 12, rowMin: 22, maxLines: 16, cap: 0.14, stepUp: true },
|
|
40
|
+
{ upTo: 9, ramp: "reading", item: 6, group: 12, block: 24, pad: 12, rowMin: 20, maxLines: 18, cap: 0.12, stepUp: false },
|
|
41
|
+
{ upTo: 10, ramp: "compact", item: 6, group: 12, block: 18, pad: 12, rowMin: 18, maxLines: 22, cap: 0.12, stepUp: false, tight: true },
|
|
45
42
|
];
|
|
46
43
|
|
|
47
44
|
const TREATMENTS = ["top-rule", "top-plain-large", "side-rail", "band", "statement", "overlay", "kicker-numeral", "bottom-anchor"];
|
|
@@ -95,10 +92,15 @@ function clampDial(value, fallback) {
|
|
|
95
92
|
return Number.isFinite(n) ? Math.max(1, Math.min(10, n)) : fallback;
|
|
96
93
|
}
|
|
97
94
|
|
|
95
|
+
/**
|
|
96
|
+
* The density band's tokens. A bold pack (`ramp: presented`, allowed only with `register: bold`)
|
|
97
|
+
* sets its type on the presented ramp at any density, so its titles and figures fill the same zones
|
|
98
|
+
* at presentation size; every other pack takes the band's ramp.
|
|
99
|
+
*/
|
|
98
100
|
function densityTokens(density, packRamp) {
|
|
99
101
|
const d = clampDial(density, 5);
|
|
100
102
|
const band = DENSITY_BANDS.find((b) => d <= b.upTo);
|
|
101
|
-
const ramp =
|
|
103
|
+
const ramp = packRamp === "presented" ? "presented" : band.ramp;
|
|
102
104
|
return { density: d, ...band, ramp, sizes: RAMPS[ramp] };
|
|
103
105
|
}
|
|
104
106
|
|
|
@@ -109,12 +111,13 @@ function varianceMax(variance, treatmentCount) {
|
|
|
109
111
|
return Math.min(max, treatmentCount);
|
|
110
112
|
}
|
|
111
113
|
|
|
112
|
-
|
|
114
|
+
/** The shared slide grid's columns on a canvas (the same for every pack and density). */
|
|
115
|
+
function makeGrid(canvas) {
|
|
113
116
|
const { W, H } = CANVAS[canvas];
|
|
114
|
-
const g =
|
|
117
|
+
const g = SG.COLUMNS[canvas];
|
|
115
118
|
const pitch = g.col + g.gutter;
|
|
116
119
|
return {
|
|
117
|
-
canvas, name, W, H, ...g, pitch,
|
|
120
|
+
canvas, name: "compact", W, H, ...g, pitch,
|
|
118
121
|
/** Columns a…b (1-based, inclusive) as x and width in points. */
|
|
119
122
|
span(a, b) { return { x: g.margin + pitch * (a - 1), w: pitch * (b - a + 1) - g.gutter }; },
|
|
120
123
|
colX(a) { return g.margin + pitch * (a - 1); },
|
|
@@ -279,7 +282,16 @@ function leading(text) {
|
|
|
279
282
|
* How much wider than the estimate a face sets: heavier weights of the bundled faces run wider,
|
|
280
283
|
* so a frame measured at the regular width would lose its last line.
|
|
281
284
|
*/
|
|
282
|
-
function faceWidth(face) {
|
|
285
|
+
function faceWidth(face, text) {
|
|
286
|
+
// A monospaced Latin face sets every Latin character and space at 0.6 em, and its Hangul in the
|
|
287
|
+
// East Asian face; the factor is that advance over the estimate, or the Latin worst case unread.
|
|
288
|
+
if (face && face.ea) {
|
|
289
|
+
const est = textEms(text);
|
|
290
|
+
if (text == null || !est) return 1.26;
|
|
291
|
+
let mono = 0;
|
|
292
|
+
for (const ch of String(text)) mono += /[ᄀ-ᇿ-가-힣一-鿿]/u.test(ch) ? 0.94 * (face.bold ? 1.08 : 1.0) : 0.6;
|
|
293
|
+
return Math.max(1, mono / est);
|
|
294
|
+
}
|
|
283
295
|
const font = (face && face.font) || "";
|
|
284
296
|
return /9 Black/u.test(font) ? 1.14 : /7 Bold/u.test(font) ? 1.08 : /5 Medium/u.test(font) ? 1.05 : face && face.bold ? 1.04 : 1.0;
|
|
285
297
|
}
|
|
@@ -375,86 +387,89 @@ function pickTreatment({ pack, family, role, override, slideNumber, used, max, a
|
|
|
375
387
|
/**
|
|
376
388
|
* Title frame, companion decoration and body zone of one treatment, in points.
|
|
377
389
|
*
|
|
390
|
+
* Every treatment draws its title inside the shared title zone and leaves the body zone alone:
|
|
391
|
+
* a treatment picks the title's face, size and colour plus what is drawn with it; placement of
|
|
392
|
+
* title and body comes from the zone. A statement draws its sentence in the display title zone.
|
|
393
|
+
*
|
|
378
394
|
* ctx: { grid, sizes, title, hero, decoration (Set), numeral, rail ("surface" or "field") }
|
|
379
|
-
* Returns { title, decor[], numeral?, body: {a, b, y, bottom},
|
|
380
|
-
* a…b
|
|
395
|
+
* Returns { title, decor[], numeral?, body: {a, b, y, bottom}, support?, lines } where body
|
|
396
|
+
* columns a…b sit on the deck grid. `fits` is false when the title overflows its zone.
|
|
381
397
|
*/
|
|
382
398
|
function treatmentGeometry(id, ctx) {
|
|
383
399
|
const { grid, sizes } = ctx;
|
|
384
|
-
// Lines of the title at a size, in the face that sets it (ctx.wide: face role → width factor).
|
|
385
|
-
const fit = (t, w, size, face = "title") =>
|
|
400
|
+
// Lines of the title at a size, in the face that sets it (ctx.wide: face role → width factor or factor fn).
|
|
401
|
+
const fit = (t, w, size, face = "title") => {
|
|
402
|
+
const k = ctx.wide && ctx.wide[face];
|
|
403
|
+
return lineCount(t, w / ((typeof k === "function" ? k(t) : k) || 1), size);
|
|
404
|
+
};
|
|
386
405
|
const text = ctx.title || "";
|
|
387
406
|
const decor = [];
|
|
388
407
|
const full = grid.span(1, 12);
|
|
389
408
|
const out = { id, decor, fits: true };
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
409
|
+
const T = SG.zone(grid.canvas, "content", "title");
|
|
410
|
+
const B = SG.zone(grid.canvas, "content", "body");
|
|
411
|
+
const body = { a: B.a, b: B.b, y: B.y, bottom: B.y + B.h };
|
|
412
|
+
// The title frame is the zone itself. Two lines at the title step take 1.15 leading, or 1.1 when
|
|
413
|
+
// that keeps them inside the zone; display type may set solid (1.0) to keep two of its lines in.
|
|
414
|
+
const frame = (size, face, colour) => {
|
|
415
|
+
const lines = fit(text, T.w, size, face);
|
|
416
|
+
const lineSpacing = [1.15, 1.1, ...(face === "display" ? [1.0] : [])].find((k) => lines * size * k <= T.h + 0.5) || 1.1;
|
|
417
|
+
return { x: T.x, y: T.y, w: T.w, h: T.h, size, face, colour, lines, lineSpacing, fits: lines * size * lineSpacing <= T.h + 0.5 };
|
|
393
418
|
};
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
if (id === "top-rule" || id === "band") {
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
out.
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
decor.push({ x: 0, y: 0, w: grid.W, h: 108, role: "field", kind: "band" });
|
|
406
|
-
out.body = { a: 1, b: 12, y: ctx.tight ? 120 : 132, bottom: ZONE.floor };
|
|
407
|
-
} else {
|
|
408
|
-
const ruleY = 36 + h + 6;
|
|
409
|
-
if (ctx.decoration.has("accent-rule")) decor.push({ x: out.title.x, y: ruleY, w: grid.col, h: 4, role: "accent", kind: "rule" });
|
|
419
|
+
// Where the title's last line ends inside the frame (a rule below it follows the text, not the zone).
|
|
420
|
+
const textBottom = (f) => T.y + Math.ceil(Math.max(1, f.lines) * f.size * f.lineSpacing) + 2;
|
|
421
|
+
|
|
422
|
+
if (id === "top-rule" || id === "band" || id === "bottom-anchor" || id === "side-rail" || id === "kicker-numeral") {
|
|
423
|
+
const f = frame(sizes.title, "title", id === "band" ? "on-field" : "ink");
|
|
424
|
+
out.fits = f.fits && f.lines <= 2 && (id !== "kicker-numeral" || ctx.numeral != null);
|
|
425
|
+
out.title = f;
|
|
426
|
+
out.body = body;
|
|
427
|
+
if (id === "top-rule") {
|
|
428
|
+
const ruleY = textBottom(f) + 4;
|
|
429
|
+
if (ctx.decoration.has("accent-rule")) decor.push({ x: T.x, y: ruleY, w: grid.col, h: 4, role: "accent", kind: "rule" });
|
|
410
430
|
else decor.push({ x: full.x, y: ruleY, w: full.w, h: 0.75, role: "line", kind: "rule" });
|
|
411
|
-
|
|
431
|
+
} else if (id === "band") {
|
|
432
|
+
// The field runs from the page's top edge to the foot of the title zone.
|
|
433
|
+
decor.push({ x: 0, y: 0, w: grid.W, h: T.y + T.h, role: "field", kind: "band" });
|
|
434
|
+
} else if (id === "bottom-anchor") {
|
|
435
|
+
// The title zone closes on a heavy ink rule across the page's columns.
|
|
436
|
+
decor.push({ x: full.x, y: T.y + T.h + 2, w: full.w, h: 3, role: "ink", kind: "rule" });
|
|
437
|
+
} else if (id === "side-rail") {
|
|
438
|
+
// A bar in the left margin: full page height in the rail colour where the pack fills its
|
|
439
|
+
// rail, else as tall as the title zone in the accent.
|
|
440
|
+
const filled = ctx.decoration.has("rail-fill");
|
|
441
|
+
const w = Math.round(grid.margin / 2);
|
|
442
|
+
decor.push(filled ? { x: 0, y: 0, w, h: grid.H, role: ctx.rail === "field" ? "field" : "accent", kind: "rail" }
|
|
443
|
+
: { x: 0, y: T.y, w, h: T.h, role: "accent", kind: "rail" });
|
|
444
|
+
} else {
|
|
445
|
+
const A = SG.zone(grid.canvas, "content", "aside");
|
|
446
|
+
out.numeral = { x: A.x, y: A.y, w: A.w, h: A.h, size: sizes.cover, text: String(ctx.numeral || ""), align: "right" };
|
|
412
447
|
}
|
|
413
448
|
return out;
|
|
414
449
|
}
|
|
415
450
|
|
|
416
451
|
if (id === "top-plain-large") {
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
out.
|
|
420
|
-
|
|
421
|
-
const h = lines <= 1 ? 54 : 102;
|
|
422
|
-
out.title = { ...box([1, 10], 36, sizes.display, lines, "display", "ink"), h: h + 8 };
|
|
423
|
-
out.body = { a: 1, b: 12, y: 36 + h + (ctx.tight ? 18 : 24), bottom: ZONE.floor };
|
|
424
|
-
return out;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
if (id === "side-rail") {
|
|
428
|
-
const s = grid.span(1, 4);
|
|
429
|
-
const lines = fit(text, s.w, sizes.title);
|
|
430
|
-
out.fits = lines <= 4;
|
|
431
|
-
const filled = ctx.decoration.has("rail-fill");
|
|
432
|
-
const rail = filled && ctx.rail === "field" ? "field" : "surface";
|
|
433
|
-
out.title = box([1, 4], 36, sizes.title, lines, "title", rail === "field" ? "on-field" : "ink");
|
|
434
|
-
// In a rail a few words fill a line, so one word moving down can add a line the estimate does
|
|
435
|
-
// not see; the frame keeps a spare line (the text is top-aligned, so nothing moves).
|
|
436
|
-
out.title.h = Math.ceil((lines + 1) * sizes.title * 1.15);
|
|
437
|
-
const railRight = s.x + s.w + grid.gutter / 2;
|
|
438
|
-
if (filled) decor.push({ x: 0, y: 0, w: railRight, h: grid.H, role: rail, kind: "rail" });
|
|
439
|
-
else if (ctx.decoration.has("hairline-rule")) decor.push({ x: railRight, y: 36, w: 0.75, h: 450, role: "line", kind: "rule" });
|
|
440
|
-
out.body = { a: 5, b: 12, y: 36, bottom: ZONE.floor };
|
|
452
|
+
const f = frame(sizes.display, "display", "ink");
|
|
453
|
+
out.fits = f.fits && f.lines <= 2;
|
|
454
|
+
out.title = f;
|
|
455
|
+
out.body = body;
|
|
441
456
|
return out;
|
|
442
457
|
}
|
|
443
458
|
|
|
444
459
|
if (id === "statement") {
|
|
445
|
-
//
|
|
446
|
-
//
|
|
447
|
-
const
|
|
448
|
-
const
|
|
449
|
-
const heroLines = fit(text, w, sizes.hero, "display");
|
|
450
|
-
const useHero = ctx.hero && heroLines <= 3;
|
|
460
|
+
// The sentence centres in the display title zone, at the hero step in a hero pack while it
|
|
461
|
+
// holds to three lines there, else at the display step.
|
|
462
|
+
const D = SG.zone(grid.canvas, "display", "title");
|
|
463
|
+
const tall = (size, lines) => lines * size * 1.15 <= D.h + 0.5;
|
|
464
|
+
const heroLines = fit(text, D.w, sizes.hero, "display");
|
|
465
|
+
const useHero = ctx.hero && heroLines <= 3 && tall(sizes.hero, heroLines);
|
|
451
466
|
const size = useHero ? sizes.hero : sizes.display;
|
|
452
|
-
const lines = useHero ? heroLines : fit(text, w, sizes.display, "display");
|
|
453
|
-
out.fits = lines <= 3;
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
out.support = {
|
|
467
|
+
const lines = useHero ? heroLines : fit(text, D.w, sizes.display, "display");
|
|
468
|
+
out.fits = lines <= 3 && tall(size, lines);
|
|
469
|
+
out.title = { x: D.x, y: D.y, w: D.w, h: D.h, size, face: "display", colour: "ink", lines, lineSpacing: 1.15, valign: "middle",
|
|
470
|
+
textH: Math.ceil(lines * size * 1.15) };
|
|
471
|
+
const P = SG.zone(grid.canvas, "display", "support");
|
|
472
|
+
out.support = { x: P.x, y: P.y, w: P.w, bottom: P.y + P.h };
|
|
458
473
|
out.body = null;
|
|
459
474
|
return out;
|
|
460
475
|
}
|
|
@@ -466,37 +481,11 @@ function treatmentGeometry(id, ctx) {
|
|
|
466
481
|
// The panel is as tall as the title and its caption line (ending 24 pt under the caption at 432), so its
|
|
467
482
|
// lower part never stands empty; the sample tag in the footer zone sits on the picture, clear of its edge.
|
|
468
483
|
decor.push({ x: 0, y: 324, w: s.x + s.w + 24, h: 146, role: "field", kind: "panel", alpha: 12 });
|
|
469
|
-
out.title = {
|
|
484
|
+
out.title = { x: s.x, y: 348, w: s.w, h: 72, size: sizes.title, face: "title", colour: "on-field", lines };
|
|
470
485
|
out.image = { x: 0, y: 0, w: grid.W, h: grid.H };
|
|
471
486
|
out.body = null;
|
|
472
487
|
return out;
|
|
473
488
|
}
|
|
474
|
-
|
|
475
|
-
if (id === "kicker-numeral") {
|
|
476
|
-
const w = grid.span(3, 12).w;
|
|
477
|
-
const lines = fit(text, w, sizes.title);
|
|
478
|
-
out.fits = lines <= 1 && ctx.numeral != null;
|
|
479
|
-
const n = grid.span(1, 2);
|
|
480
|
-
out.numeral = { x: n.x, y: 36, w: n.w, h: 72, size: sizes.cover, text: String(ctx.numeral || "") };
|
|
481
|
-
out.title = { ...box([3, 12], 48, sizes.title, lines, "title", "ink"), h: 60 };
|
|
482
|
-
out.body = { a: 1, b: 12, y: ctx.tight ? 120 : 132, bottom: ZONE.floor };
|
|
483
|
-
return out;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
if (id === "bottom-anchor") {
|
|
487
|
-
// The title's last line ends on the body floor, so a one-line title leaves no band under itself;
|
|
488
|
-
// the hairline sits 12 pt above the title and the visual's zone ends 24 pt above it.
|
|
489
|
-
const s = grid.span(1, 9);
|
|
490
|
-
const lines = fit(text, s.w, sizes.title);
|
|
491
|
-
out.fits = lines <= 2;
|
|
492
|
-
const h = Math.ceil(Math.max(1, lines) * sizes.title * 1.15) + 2;
|
|
493
|
-
const y = ZONE.floor - h;
|
|
494
|
-
out.title = { ...box([1, 9], y, sizes.title, lines, "title", "ink"), h };
|
|
495
|
-
if (ctx.decoration.has("hairline-rule")) decor.push({ x: full.x, y: y - 12, w: full.w, h: 0.75, role: "line", kind: "rule" });
|
|
496
|
-
out.side = { ...grid.span(10, 12), y, bottom: ZONE.floor };
|
|
497
|
-
out.body = { a: 1, b: 12, y: 36, bottom: y - 24 };
|
|
498
|
-
return out;
|
|
499
|
-
}
|
|
500
489
|
throw new Error(`unknown title treatment "${id}"`);
|
|
501
490
|
}
|
|
502
491
|
|
|
@@ -514,7 +503,7 @@ function treatmentApplies(id, facts, ctx) {
|
|
|
514
503
|
}
|
|
515
504
|
|
|
516
505
|
module.exports = {
|
|
517
|
-
CANVAS,
|
|
506
|
+
CANVAS, RAMPS, SCALE, ZONE, TREATMENTS, FAMILIES, VARIANTS, LEGACY_LAYOUTS,
|
|
518
507
|
densityTokens, varianceMax, makeGrid, stepGap, stepRole, clampDial,
|
|
519
508
|
textEms, lineCount, leading, textHeight, isKorean, faceWidth, balanceLines, keepLines,
|
|
520
509
|
familyIds, resolveFamily, familyAllows, pickTreatment, treatmentGeometry, treatmentApplies,
|
|
@@ -831,6 +831,7 @@ function comparisonRole(blocks) {
|
|
|
831
831
|
|
|
832
832
|
/** The ordered content of a pack slide, captions bound to the visual before them. */
|
|
833
833
|
// Families whose geometry is a chart: a table of numbers there is drawn as one.
|
|
834
|
+
const DISPLAY_FAMILIES = new Set(["statement", "quote", "closing-statement"]);
|
|
834
835
|
const CHART_FAMILIES = new Set(["chart-insight", "full-chart", "kpi-over-chart", "dashboard-grid"]);
|
|
835
836
|
const NUMBER_CELL = /^[+\-−±▲▼△▽]?\s*[\d.,]+\s*(?:%p?|배|x|pt|bp|[가-힣]{1,3}|[A-Za-z]{1,3})?$/u;
|
|
836
837
|
|
|
@@ -998,7 +999,7 @@ function resolvePack(ast, templateObj) {
|
|
|
998
999
|
const numbering = { figure: 0, table: 0, FIGURE, TABLE };
|
|
999
1000
|
const facts = deckFacts(ast);
|
|
1000
1001
|
const families = pack["layout-families"] || [];
|
|
1001
|
-
const wide = { title: grid.faceWidth(pack.faces.title), display: grid.faceWidth(pack.faces.display) };
|
|
1002
|
+
const wide = { title: (t) => grid.faceWidth(pack.faces.title, t), display: (t) => grid.faceWidth(pack.faces.display, t) };
|
|
1002
1003
|
let part = 0;
|
|
1003
1004
|
|
|
1004
1005
|
const slides = ast.slides.map((slide) => {
|
|
@@ -1046,23 +1047,13 @@ function resolvePack(ast, templateObj) {
|
|
|
1046
1047
|
} else if (!families.includes(family)) {
|
|
1047
1048
|
notes.push(`slide ${n}: ${family} is not a ${pack.id} layout family (${families.join(", ")}); drawn with its own geometry`);
|
|
1048
1049
|
}
|
|
1049
|
-
let titleOnly = null;
|
|
1050
|
-
if (family === "statement" && !pack.treatments.includes("statement")) {
|
|
1051
|
-
// A pack without a statement title sets the sentence as a top title, the largest it has.
|
|
1052
|
-
titleOnly = ["top-plain-large", "band", "top-rule"].find((t) => pack.treatments.includes(t));
|
|
1053
|
-
if (!titleOnly) throw new Error(`Slide ${n}: ${pack.id} has no statement or top title to set a statement slide with; choose another tonality`);
|
|
1054
|
-
notes.push(`slide ${n}: ${pack.id} has no statement title, so the sentence is drawn as a ${titleOnly} title`);
|
|
1055
|
-
family = "text-column";
|
|
1056
|
-
role = "content";
|
|
1057
|
-
}
|
|
1058
1050
|
const items = packItems(slide.blocks, numbering, CHART_FAMILIES.has(family));
|
|
1059
1051
|
const paras = slide.blocks.filter((b) => b.type === "body").flatMap((b) => b.items || []);
|
|
1060
1052
|
// A closing is not part of a section, so it never carries the part number.
|
|
1061
1053
|
const closing = grid.VARIANTS.closing.includes(family);
|
|
1062
1054
|
// An agenda numbers its own rows: a count beside its title reads as a stray section number (spec-v2 Amendments 8).
|
|
1063
1055
|
const numeral = family === "agenda" ? null : part > 0 && !closing ? part : null;
|
|
1064
|
-
const
|
|
1065
|
-
const ctx = { grid: pack.grid, sizes, title: plain(title), hero: pack.hero, decoration, numeral, rail: pack.rail, wide, statementCols, tight: Boolean((pack.tok || {}).tight) };
|
|
1056
|
+
const ctx = { grid: pack.grid, sizes, title: plain(title), hero: pack.hero, decoration, numeral, rail: pack.rail, wide, tight: Boolean((pack.tok || {}).tight) };
|
|
1066
1057
|
const facts1 = slideFacts(slide.blocks);
|
|
1067
1058
|
// A quote under a statement title says the quote; the source line becomes the support line.
|
|
1068
1059
|
let statementText = null;
|
|
@@ -1080,8 +1071,11 @@ function resolvePack(ast, templateObj) {
|
|
|
1080
1071
|
};
|
|
1081
1072
|
let allow = grid.familyAllows(family);
|
|
1082
1073
|
let treatment;
|
|
1083
|
-
|
|
1084
|
-
|
|
1074
|
+
// A statement, a quote and a closing statement sit in the display zone in every pack (the shared
|
|
1075
|
+
// slide grid): the statement title is theirs whether or not the pack lists it among its treatments.
|
|
1076
|
+
const display = DISPLAY_FAMILIES.has(family) && !meta.title && applicable("statement") === true;
|
|
1077
|
+
if (display) {
|
|
1078
|
+
treatment = "statement";
|
|
1085
1079
|
} else if (!families.includes(family) && !meta.title && !pack.treatments.some((t) => allow.includes(t) && applicable(t) === true)) {
|
|
1086
1080
|
// A family from outside the pack that takes none of its titles gets the pack's title for its role.
|
|
1087
1081
|
allow = [pack["role-defaults"][role] || pack.treatments[0], ...grid.familyAllows("text-column")];
|
|
@@ -1093,7 +1087,7 @@ function resolvePack(ast, templateObj) {
|
|
|
1093
1087
|
applicable, onNote: (note) => notes.push(note),
|
|
1094
1088
|
});
|
|
1095
1089
|
}
|
|
1096
|
-
const candidates = meta.title ||
|
|
1090
|
+
const candidates = meta.title || display ? [treatment] : [treatment, ...pack.treatments.filter((t) =>
|
|
1097
1091
|
t !== treatment && allow.includes(t) && !["statement", "overlay"].includes(t) && applicable(t) === true)];
|
|
1098
1092
|
return {
|
|
1099
1093
|
...base,
|