gutterpress 0.11.20-alpha.3 → 0.11.20-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/api/index.js +2 -2
  2. package/dist/{build-a8ycsg80.js → build-epdcrm2c.js} +4 -4
  3. package/dist/{cli-9k2tb493.js → cli-0ey902sx.js} +90 -28
  4. package/dist/{cli-x4p99w8d.js → cli-61vd4ews.js} +83 -41
  5. package/dist/{cli-hn9pk0hp.js → cli-fmksazej.js} +1 -1
  6. package/dist/{cli-y9bcya7r.js → cli-h4tfmpg3.js} +2 -2
  7. package/dist/cli.js +12 -12
  8. package/dist/{doctor-wbjkrcpc.js → doctor-78es5scm.js} +2 -2
  9. package/dist/engine/compiler/build.d.ts +1 -1
  10. package/dist/engine/shared/margin-box-support.d.ts +2 -2
  11. package/dist/engine/shared/pin-edges.d.ts +127 -0
  12. package/dist/engine/shared/print-area.d.ts +68 -0
  13. package/dist/{engine-yxnnp3hx.js → engine-67f68bxs.js} +1 -1
  14. package/dist/{engine-fdtv01a2.js → engine-7ha27n2b.js} +2 -2
  15. package/dist/{ext-pesyrs68.js → ext-9mc7sr0d.js} +4 -4
  16. package/dist/{gutterpress-agent-cazqstr1.js → gutterpress-agent-mrdnwktc.js} +78 -13
  17. package/dist/{gutterpress-viewer-1yse8cx3.js → gutterpress-viewer-jr30yw8t.js} +143 -52
  18. package/dist/{index-r87y7wzw.js → index-ad95fws8.js} +82 -40
  19. package/dist/{index-zagagnwq.js → index-gm5xbx5w.js} +3 -3
  20. package/dist/{index-tjvrchxj.js → index-t13p8y08.js} +199 -137
  21. package/dist/index.js +3 -3
  22. package/dist/lib/extension-manifest.d.ts +13 -0
  23. package/dist/lib/markdown/assemble.d.ts +12 -2
  24. package/dist/lib/markdown/gutterpress-css.d.ts +46 -30
  25. package/dist/lib/markdown/index.d.ts +5 -0
  26. package/dist/{manifest.schema-c9x8912a.json → manifest.schema-13dfax80.json} +3 -3
  27. package/dist/{new-8yah8z91.js → new-s4f0tnya.js} +4 -4
  28. package/dist/{preflight-8ccx4kdc.js → preflight-7ck4rbsq.js} +4 -4
  29. package/dist/preview/file-watcher.d.ts +4 -10
  30. package/dist/{preview-w7v9w36q.js → preview-j91sxadd.js} +4 -4
  31. package/dist/{publish-g5nh5myh.js → publish-f12xaenm.js} +4 -4
  32. package/dist/render.js +57 -6
  33. package/dist/schema/manifest.types.d.ts +8 -6
  34. package/dist/{validate-1yh59x0x.js → validate-pb5gysvr.js} +4 -4
  35. package/package.json +1 -1
  36. package/dist/engine/shared/flush.d.ts +0 -59
@@ -31,7 +31,10 @@
31
31
  * .gp-full — fills the page's content width.
32
32
  * .gp-small — width 25% of the column. Sizes compose with any
33
33
  * .gp-medium — width 50%. position (including .gp-pin, where
34
- * .gp-large — width 75%. the % resolves against the page box).
34
+ * .gp-large — width 60%. the % resolves against the page
35
+ * .gp-xl — width 80%. root: the page box, reaching the
36
+ * .gp-xxl — width 90%. paper on an edge a pin uses).
37
+ * There is no 100% step: that is .gp-full, a position.
35
38
  * .gp-tight — float/shape clearance 0.5em (sets --gp-gap; default 1em).
36
39
  * .gp-loose — float/shape clearance 2em (sets --gp-gap).
37
40
  * .gp-shape — wraps text to a floated image's alpha silhouette
@@ -47,16 +50,14 @@
47
50
  * frame instead, BY DESIGN: that is how a pin is scoped to
48
51
  * a card rather than a sheet. Page-level furniture must
49
52
  * therefore be authored outside such a wrapper.
50
- * Centered on both axes unless combined with the edge
51
- * modifiers .gp-top/.gp-bottom/.gp-left/.gp-right.
52
- * .gp-flush — with .gp-pin + an edge, the art sits on the PAPER's
53
- * edge rather than on the text block's. No CSS rule here:
54
- * the class is a marker both ENGINES implement (see
55
- * engine/shared/flush.ts), because reaching the paper
56
- * requires freeing that page's margin — per page — and
57
- * relocating the furniture that lived in it, neither of
58
- * which a stylesheet can do. Inert without .gp-pin + an
59
- * edge word, and inert under plain markdown-it.
53
+ * Centered in the text block unless combined with the
54
+ * edge modifiers .gp-top/.gp-bottom/.gp-left/.gp-right;
55
+ * an edge pin sits on the printer's ART-SAFE line of that
56
+ * edge and never moves the text — see "the pin box" below.
57
+ * .gp-flush — with .gp-pin + an edge, the escape hatch: the art runs
58
+ * to the PAPER's edge, past the printer's safe area. It
59
+ * only zeroes the pin box's insets (the rule after the
60
+ * edge modifiers). Inert without .gp-pin + an edge word.
60
61
  * .gp-bleed — forces its own page (break-before) and spans it
61
62
  * edge-to-edge horizontally. This does NOT cancel the
62
63
  * top/bottom margins, extend past the trim into printer
@@ -102,12 +103,12 @@
102
103
  * paragraph with other inline content keeps its margin.
103
104
  *
104
105
  * Rule ORDER within the gp-* block is the contract: flow positions → sizes
105
- * → spacing → .gp-pin → pin-edge modifiers. Everything is flat 0-1-0
106
- * specificity, so combining classes resolves by source order: a later flow
107
- * position wins (left < right < center < full < bleed); an explicit size's
108
- * max-width:100% lifts the floats' 50% cap; .gp-pin's margin:0 /
109
- * max-width:100% beat the float declarations; the edge modifiers beat
110
- * .gp-pin's center defaults.
106
+ * → spacing → .gp-pin → pin-edge modifiers → .gp-flush. Everything else is
107
+ * flat 0-1-0 specificity, so combining classes resolves by source order: a
108
+ * later flow position wins (left < right < center < full < bleed); an
109
+ * explicit size's max-width:100% lifts the floats' 50% cap; .gp-pin's
110
+ * margin:0 / max-width:100% beat the float declarations; the edge modifiers
111
+ * beat .gp-pin's center defaults.
111
112
  *
112
113
  * .gp-pin semantics, stated honestly: it pins within its @page/@spread
113
114
  * CONTAINER, not "the sheet edge". A .page div that fragments across
@@ -120,12 +121,29 @@
120
121
  * compiler's engine.abspos.leak diagnostic catches the raw-HTML cases the
121
122
  * token walk can't see.
122
123
  *
124
+ * The PIN BOX — where a pin may sit, and why the text never moves. An edge
125
+ * pin has to be able to reach the paper, and Chromium's printable area IS
126
+ * the page area (engine/shared/pin-edges.ts has the measurements), so both
127
+ * engines free the page margin on each edge a pin names: print through a
128
+ * generated named page, the preview through the run's strip geometry. The
129
+ * page root gets that margin back as padding, on top of any of its own
130
+ * (`box-decoration-break: clone`), so its content box — the text — is the
131
+ * author's content box, same size, same place, same breaks, wherever the art
132
+ * is pinned. The pin's containing
133
+ * block is the root's padding box, and the engine sets `--gp-pin-<edge>` on
134
+ * the root to the art-safe line's distance from the sheet edge (the bleed
135
+ * plus page.artSafe on the top, bottom and outside edge; page.safe at the
136
+ * binding, from the manifest); `.gp-pin` reads them as its four insets.
137
+ * `.gp-flush` zeroes them on the pin: the paper's edge. Under plain
138
+ * markdown-it nothing sets them and every inset is 0.
139
+ *
123
140
  * Pin CSS mechanics (all three are load-bearing, verified by
124
- * paged-css-image-pin.test.ts): `inset: 0` is REQUIRED — abspos
125
- * self-alignment aligns within the inset-modified containing block, and
126
- * with auto insets that collapses to the static-position rectangle, where
127
- * alignment does nothing. The center defaults must be EXPLICIT — `normal`
128
- * alignment behaves as `start` for abspos replaced elements, not center.
141
+ * marker-css-image-pin.test.ts): four non-auto insets are REQUIRED — abspos
142
+ * self-alignment aligns within the inset-modified containing block (the pin
143
+ * box), and with auto insets that collapses to the static-position
144
+ * rectangle, where alignment does nothing. The center defaults must be
145
+ * EXPLICIT — `normal` alignment behaves as `start` for abspos replaced
146
+ * elements, not center.
129
147
  * And `justify-self` on .gp-left/.gp-right does double duty (flow float +
130
148
  * pin edge) because self-alignment does not apply to in-flow floats — it is
131
149
  * inert until .gp-pin makes the element abspos. If Chrome ever ships
@@ -133,16 +151,14 @@
133
151
  * carrying gp-left could start shifting; that is standards-tracking per
134
152
  * CLAUDE.md ("Chrome wins once it ships"), not a bug in the author's book.
135
153
  */
136
- export declare const GUTTERPRESS_CSS = "\n/* gp-* author image/block vocabulary. One vocabulary, gp-* only \u2014 the\n pre-vocabulary utility names (.center/.float-left/.float-right/\n .full-width/.full-bleed) were REMOVED when gp-* shipped; books rename\n the classes in their markdown (see the migration note). Source ORDER is\n the contract \u2014 see the doctrine comment above. */\n\n/* flow positions */\n.gp-left {\n float: left;\n margin: 0 var(--gp-gap, 1em) var(--gp-gap, 1em) 0;\n max-width: 50%;\n}\n.gp-right {\n float: right;\n margin: 0 0 var(--gp-gap, 1em) var(--gp-gap, 1em);\n max-width: 50%;\n}\n.gp-center {\n display: block;\n float: none;\n margin-left: auto;\n margin-right: auto;\n max-width: 100%;\n}\n.gp-full {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n}\n@page gp-full-bleed { margin-left: 0; margin-right: 0; }\n.gp-bleed {\n display: block;\n float: none;\n break-before: page;\n page: gp-full-bleed;\n max-width: none;\n width: 100%;\n margin-left: 0;\n margin-right: 0;\n}\n\n/* sizes \u2014 AFTER the flow positions so max-width:100% lifts the floats' 50%\n cap at equal specificity */\n.gp-small { width: 25%; max-width: 100%; }\n.gp-medium { width: 50%; max-width: 100%; }\n.gp-large { width: 75%; max-width: 100%; }\n\n/* float clearance presets \u2014 consumed by var(--gp-gap) in the float rules\n above and by .gp-shape's shape-margin below; --gp-gap itself is\n author-settable CSS */\n.gp-tight { --gp-gap: 0.5em; }\n.gp-loose { --gp-gap: 2em; }\n\n/* column runs \u2014 plain CSS Multi-column, emitted by the core @columns marker\n (markers.js): @columns [1-5] ... @end-columns is the ONE way to lay\n content out in columns, and @column-break moves to the next column.\n The classes below are the CSS that marker emits, not author vocabulary.\n\n Permanent vocabulary, not a shim: Chromium implements multicol natively\n and these rules are the standard properties verbatim, so there is no\n spec gap here to remove later. Deliberately minimal \u2014 column-fill is\n NOT set (the CSS initial balance applies).\n Themes style a run through two tokens instead of writing column rules:\n --gp-column-gap default 1.5em\n --gp-column-rule default none (any column-rule shorthand value)\n Both hooks sit at zero specificity (:where), so any author rule wins.\n .gp-columns is two columns on its own; a count class (bare @columns\n emits gp-columns-2) wins because it comes later at the same specificity. */\n:where(.gp-columns, .gp-columns-1, .gp-columns-2, .gp-columns-3, .gp-columns-4, .gp-columns-5) {\n column-gap: var(--gp-column-gap, 1.5em);\n column-rule: var(--gp-column-rule, none);\n}\n.gp-columns { columns: 2; }\n.gp-columns-1 { columns: 1; }\n.gp-columns-2 { columns: 2; }\n.gp-columns-3 { columns: 3; }\n.gp-columns-4 { columns: 4; }\n.gp-columns-5 { columns: 5; }\n\n/* fragmentation controls \u2014 generic pagination utilities for any element,\n independent of the column vocabulary above (a book reached for\n these just as often outside a multicol run: keeping a card whole,\n forcing a section to start a fresh page). Standard properties only \u2014\n this project is Chromium-only (CLAUDE.md), so there are no legacy\n page-break-* twins to also emit. One name each. */\n.gp-no-break { break-inside: avoid; }\n.gp-break-before { break-before: page; }\n\n/* shape wrap \u2014 text follows the image's alpha silhouette instead of its\n rectangular box. shape-outside only applies to floats, so this is inert\n without .gp-left/.gp-right (and under .gp-pin, which un-floats). The\n shape URL cannot be written in CSS (url() contexts can't read attr()),\n so the image renderer rule (images.ts) mirrors the src into an inline\n --gp-shape:url(...) custom property whenever it sees this class --\n authors only ever type the class. threshold 0.2 ignores near-transparent\n anti-aliasing halos; shape-margin shares the float-gap vocabulary. */\nimg.gp-shape {\n shape-outside: var(--gp-shape);\n shape-image-threshold: 0.2;\n shape-margin: var(--gp-gap, 1em);\n}\n\n/* pin \u2014 within the nearest positioned ancestor (.page/.spread, rule above).\n inset:0 and the explicit centers are load-bearing; see doctrine comment. */\n.gp-pin {\n position: absolute;\n inset: 0;\n align-self: center;\n justify-self: center;\n margin: 0;\n max-width: 100%;\n}\n\n/* pin edge modifiers \u2014 AFTER .gp-pin to beat its center defaults;\n justify-self is inert on in-flow floats, so gp-left/gp-right safely do\n double duty as flow float + pin edge */\n.gp-top { align-self: start; }\n.gp-bottom { align-self: end; }\n.gp-left { justify-self: start; }\n.gp-right { justify-self: end; }\n\n/* wrapper-margin neutralization (same pattern and rationale as the\n .gp-bleed paragraph-margin note in the doctrine comment; for pin, the\n emptied paragraph would otherwise leave a phantom margin gap in flow) */\n:where(p:has(> img.gp-bleed:only-child)) { margin: 0; }\n:where(p:has(> img.gp-pin:only-child)) { margin: 0; }\n\n/* depth \u2014 a named ladder for z-index, so books stop hand-tuning bare\n integers. A real book measured 21 z-index declarations using only four\n distinct values (-1, 0, 1, 2), each written literally at its use site.\n The custom properties are the author-settable surface (a book needing a\n deeper stack raises them once); the classes are the shorthand.\n\n NOT named \"layer\": CSS Paged Media 3 \u00A73.1 already defines \"page layers\"\n (page background, canvas, borders, contents, margin boxes) and those are\n parts of the PAGE BOX, not a z-ladder for content. Reusing the word for a\n different concept would collide with the spec vocabulary this project\n tracks. The pin EDGE modifiers already own .gp-top/.gp-bottom, so the\n ladder avoids those words too.\n\n .gp-behind is the one that earns its place: it puts a pinned image UNDER\n the page's text, which is otherwise impossible to express without a bare\n negative z-index. \"Above\" needs no class \u2014 an out-of-flow pin already\n paints above in-flow content.\n\n Two things silently defeat .gp-behind, neither visible at the use site:\n - a stacking context on the .page/.spread ancestor (z-index, isolation,\n opacity, filter, transform on it traps the negative layer inside).\n Core keeps .page/.spread at 'position: relative; z-index: auto'\n precisely so they are not stacking contexts.\n - a clipping ancestor (overflow other than visible) \u2014 but only where\n the art actually overhangs that ancestor's clip box on a clipped\n axis: the overhang is cut off, the same mechanism that clips a\n .gp-bleed plate back to the wrapper's width. Clipping never reorders\n layers \u2014 within-bounds art under a clipping .page prints whole and\n still behind (measured; see the build audit's comment in\n engine/compiler/build.ts), and a static wrapper's overflow never\n binds an abspos .gp-pin at all.\n The build-time engine.layer.trapped audit reports both against the live\n ancestor chain. printsafe/page-containment is only an early source hint for\n declarations written directly on .page/.spread. */\n:root {\n --gp-z-behind: -1;\n --gp-z-base: 0;\n --gp-z-raised: 1;\n --gp-z-front: 2;\n}\n.gp-behind { z-index: var(--gp-z-behind); }\n.gp-base { z-index: var(--gp-z-base); }\n.gp-raised { z-index: var(--gp-z-raised); }\n.gp-front { z-index: var(--gp-z-front); }\n\n/* GFM-style alert/callout boxes (#237) \u2014 the DOM gfm-alerts.ts emits from\n \"> [!TYPE]\" blockquote syntax when a project opts into the bundled\n gutterpress-gfm-alerts feature (BUILTIN_OPTIONAL_PLUGINS, renderer.ts).\n The plugin is optional and inert by default; these rules are equally\n inert wherever the classes never appear, exactly like every other\n utility above, so shipping them costs nothing to a book that has not\n turned the feature on.\n\n This is authored-COMPONENT vocabulary, not the @marker structural family\n (markers.js/MARKER_CSS) \u2014 CLAUDE.md \u00A76 splits the two core CSS blocks by\n ROLE, and an author-facing box an author invokes via inline syntax is the\n same role as every other gp-* utility here, not page/chapter/section DOM.\n\n Deliberately minimal (CLAUDE.md \"thin over capable\" / the issue's own\n \"a rule and a label\"): a left border plus a bold label, :where()'d to\n zero specificity so a theme's own (unlayered) rule for any of these\n classes wins outright regardless of selector weight \u2014 see the\n cascade-layers note atop this file. Five semantic accent colors (the same\n NOTE/TIP/IMPORTANT/WARNING/CAUTION hues GitHub's own alerts use \u2014 a\n long-established, non-proprietary convention, not DC branding) are\n exposed as :root custom properties, the same author-settable-surface\n pattern as the --gp-z-* ladder above: a book retints every alert of one\n type by overriding a single property, no specificity fight required.\n --gp-alert-color cascades from the type modifier down to .gp-alert-title\n through ordinary CSS inheritance (the title is always a DOM descendant of\n the box), so it is set in exactly five places, not ten. */\n:root {\n --gp-alert-note-color: #0969da;\n --gp-alert-tip-color: #1a7f37;\n --gp-alert-important-color: #8250df;\n --gp-alert-warning-color: #9a6700;\n --gp-alert-caution-color: #cf222e;\n}\n:where(.gp-alert) {\n margin: 1em 0;\n padding: 0.5em 1em;\n border-left: 0.25em solid var(--gp-alert-color, currentColor);\n}\n:where(.gp-alert-title) {\n margin: 0 0 0.5em;\n font-weight: bold;\n color: var(--gp-alert-color, inherit);\n}\n:where(.gp-alert-note) { --gp-alert-color: var(--gp-alert-note-color); }\n:where(.gp-alert-tip) { --gp-alert-color: var(--gp-alert-tip-color); }\n:where(.gp-alert-important) { --gp-alert-color: var(--gp-alert-important-color); }\n:where(.gp-alert-warning) { --gp-alert-color: var(--gp-alert-warning-color); }\n:where(.gp-alert-caution) { --gp-alert-color: var(--gp-alert-caution-color); }\n";
154
+ export declare const GUTTERPRESS_CSS = "\n/* gp-* author image/block vocabulary. One vocabulary, gp-* only \u2014 the\n pre-vocabulary utility names (.center/.float-left/.float-right/\n .full-width/.full-bleed) were REMOVED when gp-* shipped; books rename\n the classes in their markdown (see the migration note). Source ORDER is\n the contract \u2014 see the doctrine comment above. */\n\n/* flow positions */\n.gp-left {\n float: left;\n margin: 0 var(--gp-gap, 1em) var(--gp-gap, 1em) 0;\n max-width: 50%;\n}\n.gp-right {\n float: right;\n margin: 0 0 var(--gp-gap, 1em) var(--gp-gap, 1em);\n max-width: 50%;\n}\n.gp-center {\n display: block;\n float: none;\n margin-left: auto;\n margin-right: auto;\n max-width: 100%;\n}\n.gp-full {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n}\n@page gp-full-bleed { margin-left: 0; margin-right: 0; }\n.gp-bleed {\n display: block;\n float: none;\n break-before: page;\n page: gp-full-bleed;\n max-width: none;\n width: 100%;\n margin-left: 0;\n margin-right: 0;\n}\n\n/* sizes \u2014 AFTER the flow positions so max-width:100% lifts the floats' 50%\n cap at equal specificity */\n.gp-small { width: 25%; max-width: 100%; }\n.gp-medium { width: 50%; max-width: 100%; }\n.gp-large { width: 60%; max-width: 100%; }\n.gp-xl { width: 80%; max-width: 100%; }\n.gp-xxl { width: 90%; max-width: 100%; }\n\n/* float clearance presets \u2014 consumed by var(--gp-gap) in the float rules\n above and by .gp-shape's shape-margin below; --gp-gap itself is\n author-settable CSS */\n.gp-tight { --gp-gap: 0.5em; }\n.gp-loose { --gp-gap: 2em; }\n\n/* column runs \u2014 plain CSS Multi-column, emitted by the core @columns marker\n (markers.js): @columns [1-5] ... @end-columns is the ONE way to lay\n content out in columns, and @column-break moves to the next column.\n The classes below are the CSS that marker emits, not author vocabulary.\n\n Permanent vocabulary, not a shim: Chromium implements multicol natively\n and these rules are the standard properties verbatim, so there is no\n spec gap here to remove later. Deliberately minimal \u2014 column-fill is\n NOT set (the CSS initial balance applies).\n Themes style a run through two tokens instead of writing column rules:\n --gp-column-gap default 1.5em\n --gp-column-rule default none (any column-rule shorthand value)\n Both hooks sit at zero specificity (:where), so any author rule wins.\n .gp-columns is two columns on its own; a count class (bare @columns\n emits gp-columns-2) wins because it comes later at the same specificity. */\n:where(.gp-columns, .gp-columns-1, .gp-columns-2, .gp-columns-3, .gp-columns-4, .gp-columns-5) {\n column-gap: var(--gp-column-gap, 1.5em);\n column-rule: var(--gp-column-rule, none);\n}\n.gp-columns { columns: 2; }\n.gp-columns-1 { columns: 1; }\n.gp-columns-2 { columns: 2; }\n.gp-columns-3 { columns: 3; }\n.gp-columns-4 { columns: 4; }\n.gp-columns-5 { columns: 5; }\n\n/* fragmentation controls \u2014 generic pagination utilities for any element,\n independent of the column vocabulary above (a book reached for\n these just as often outside a multicol run: keeping a card whole,\n forcing a section to start a fresh page). Standard properties only \u2014\n this project is Chromium-only (CLAUDE.md), so there are no legacy\n page-break-* twins to also emit. One name each. */\n.gp-no-break { break-inside: avoid; }\n.gp-break-before { break-before: page; }\n\n/* shape wrap \u2014 text follows the image's alpha silhouette instead of its\n rectangular box. shape-outside only applies to floats, so this is inert\n without .gp-left/.gp-right (and under .gp-pin, which un-floats). The\n shape URL cannot be written in CSS (url() contexts can't read attr()),\n so the image renderer rule (images.ts) mirrors the src into an inline\n --gp-shape:url(...) custom property whenever it sees this class --\n authors only ever type the class. threshold 0.2 ignores near-transparent\n anti-aliasing halos; shape-margin shares the float-gap vocabulary. */\nimg.gp-shape {\n shape-outside: var(--gp-shape);\n shape-image-threshold: 0.2;\n shape-margin: var(--gp-gap, 1em);\n}\n\n/* pin \u2014 within the nearest positioned ancestor (.page/.spread, rule above).\n The four insets are the pin box: 0 (the text block's edge) unless an\n engine set --gp-pin-<edge> on the page root for an edge a pin uses (the\n art-safe line). Non-auto insets and the explicit centers are\n load-bearing; see doctrine comment. */\n.gp-pin {\n position: absolute;\n top: var(--gp-pin-top, 0px);\n right: var(--gp-pin-right, 0px);\n bottom: var(--gp-pin-bottom, 0px);\n left: var(--gp-pin-left, 0px);\n align-self: center;\n justify-self: center;\n margin: 0;\n max-width: 100%;\n}\n\n/* pin edge modifiers \u2014 AFTER .gp-pin to beat its center defaults;\n justify-self is inert on in-flow floats, so gp-left/gp-right safely do\n double duty as flow float + pin edge */\n.gp-top { align-self: start; }\n.gp-bottom { align-self: end; }\n.gp-left { justify-self: start; }\n.gp-right { justify-self: end; }\n\n/* the escape hatch \u2014 the paper's edge, past the printer's safe area: the\n pin box loses its art-safe insets. Set on the pin itself, so it beats the\n values the page root hands down. */\n.gp-pin.gp-flush {\n --gp-pin-top: 0px;\n --gp-pin-right: 0px;\n --gp-pin-bottom: 0px;\n --gp-pin-left: 0px;\n}\n\n/* wrapper-margin neutralization (same pattern and rationale as the\n .gp-bleed paragraph-margin note in the doctrine comment; for pin, the\n emptied paragraph would otherwise leave a phantom margin gap in flow) */\n:where(p:has(> img.gp-bleed:only-child)) { margin: 0; }\n:where(p:has(> img.gp-pin:only-child)) { margin: 0; }\n\n/* depth \u2014 a named ladder for z-index, so books stop hand-tuning bare\n integers. A real book measured 21 z-index declarations using only four\n distinct values (-1, 0, 1, 2), each written literally at its use site.\n The custom properties are the author-settable surface (a book needing a\n deeper stack raises them once); the classes are the shorthand.\n\n NOT named \"layer\": CSS Paged Media 3 \u00A73.1 already defines \"page layers\"\n (page background, canvas, borders, contents, margin boxes) and those are\n parts of the PAGE BOX, not a z-ladder for content. Reusing the word for a\n different concept would collide with the spec vocabulary this project\n tracks. The pin EDGE modifiers already own .gp-top/.gp-bottom, so the\n ladder avoids those words too.\n\n .gp-behind is the one that earns its place: it puts a pinned image UNDER\n the page's text, which is otherwise impossible to express without a bare\n negative z-index. \"Above\" needs no class \u2014 an out-of-flow pin already\n paints above in-flow content.\n\n Two things silently defeat .gp-behind, neither visible at the use site:\n - a stacking context on the .page/.spread ancestor (z-index, isolation,\n opacity, filter, transform on it traps the negative layer inside).\n Core keeps .page/.spread at 'position: relative; z-index: auto'\n precisely so they are not stacking contexts.\n - a clipping ancestor (overflow other than visible) \u2014 but only where\n the art actually overhangs that ancestor's clip box on a clipped\n axis: the overhang is cut off, the same mechanism that clips a\n .gp-bleed plate back to the wrapper's width. Clipping never reorders\n layers \u2014 within-bounds art under a clipping .page prints whole and\n still behind (measured; see the build audit's comment in\n engine/compiler/build.ts), and a static wrapper's overflow never\n binds an abspos .gp-pin at all.\n The build-time engine.layer.trapped audit reports both against the live\n ancestor chain. printsafe/page-containment is only an early source hint for\n declarations written directly on .page/.spread. */\n:root {\n --gp-z-behind: -1;\n --gp-z-base: 0;\n --gp-z-raised: 1;\n --gp-z-front: 2;\n}\n.gp-behind { z-index: var(--gp-z-behind); }\n.gp-base { z-index: var(--gp-z-base); }\n.gp-raised { z-index: var(--gp-z-raised); }\n.gp-front { z-index: var(--gp-z-front); }\n\n/* GFM-style alert/callout boxes (#237) \u2014 the DOM gfm-alerts.ts emits from\n \"> [!TYPE]\" blockquote syntax when a project opts into the bundled\n gutterpress-gfm-alerts feature (BUILTIN_OPTIONAL_PLUGINS, renderer.ts).\n The plugin is optional and inert by default; these rules are equally\n inert wherever the classes never appear, exactly like every other\n utility above, so shipping them costs nothing to a book that has not\n turned the feature on.\n\n This is authored-COMPONENT vocabulary, not the @marker structural family\n (markers.js/MARKER_CSS) \u2014 CLAUDE.md \u00A76 splits the two core CSS blocks by\n ROLE, and an author-facing box an author invokes via inline syntax is the\n same role as every other gp-* utility here, not page/chapter/section DOM.\n\n Deliberately minimal (CLAUDE.md \"thin over capable\" / the issue's own\n \"a rule and a label\"): a left border plus a bold label, :where()'d to\n zero specificity so a theme's own (unlayered) rule for any of these\n classes wins outright regardless of selector weight \u2014 see the\n cascade-layers note atop this file. Five semantic accent colors (the same\n NOTE/TIP/IMPORTANT/WARNING/CAUTION hues GitHub's own alerts use \u2014 a\n long-established, non-proprietary convention, not DC branding) are\n exposed as :root custom properties, the same author-settable-surface\n pattern as the --gp-z-* ladder above: a book retints every alert of one\n type by overriding a single property, no specificity fight required.\n --gp-alert-color cascades from the type modifier down to .gp-alert-title\n through ordinary CSS inheritance (the title is always a DOM descendant of\n the box), so it is set in exactly five places, not ten. */\n:root {\n --gp-alert-note-color: #0969da;\n --gp-alert-tip-color: #1a7f37;\n --gp-alert-important-color: #8250df;\n --gp-alert-warning-color: #9a6700;\n --gp-alert-caution-color: #cf222e;\n}\n:where(.gp-alert) {\n margin: 1em 0;\n padding: 0.5em 1em;\n border-left: 0.25em solid var(--gp-alert-color, currentColor);\n}\n:where(.gp-alert-title) {\n margin: 0 0 0.5em;\n font-weight: bold;\n color: var(--gp-alert-color, inherit);\n}\n:where(.gp-alert-note) { --gp-alert-color: var(--gp-alert-note-color); }\n:where(.gp-alert-tip) { --gp-alert-color: var(--gp-alert-tip-color); }\n:where(.gp-alert-important) { --gp-alert-color: var(--gp-alert-important-color); }\n:where(.gp-alert-warning) { --gp-alert-color: var(--gp-alert-warning-color); }\n:where(.gp-alert-caution) { --gp-alert-color: var(--gp-alert-caution-color); }\n";
137
155
  /**
138
156
  * GP_CLASSES — every class an author may legitimately write with a `gp-`
139
157
  * prefix: the `.gp-*` selectors in `GUTTERPRESS_CSS` above, the two
140
158
  * structural classes `markers.js`'s `MARKER_CSS` styles (`gp-page-break`,
141
- * `gp-column-break`), and two classes the marker plugin EMITS but that carry
142
- * no CSS rule of their own — `gp-continued` (a `@continue`d section's marker,
143
- * for author/theme styling — see markers.js's header) and `gp-flush` (a
144
- * `.gp-pin` edge modifier implemented in the engine's layout code, not CSS —
145
- * see the `.gp-flush` doctrine note above).
159
+ * `gp-column-break`), and one class the marker plugin EMITS but that carries
160
+ * no CSS rule of its own — `gp-continued` (a `@continue`d section's marker,
161
+ * for author/theme styling — see markers.js's header).
146
162
  *
147
163
  * This is the vocabulary `gp-pin-scope.js`'s `unknown_gp_class` diagnostic
148
164
  * (#226) checks every author-facing class against: any `gp-`-prefixed class
@@ -151,8 +167,8 @@ export declare const GUTTERPRESS_CSS = "\n/* gp-* author image/block vocabulary.
151
167
  * classes are none of core's business.
152
168
  *
153
169
  * `gutterpress-css.test.ts` asserts this set and the `.gp-*` selectors
154
- * textually present in `GUTTERPRESS_CSS` + `MARKER_CSS` agree (modulo the two
155
- * marker-only exceptions above), so the two cannot silently drift apart —
170
+ * textually present in `GUTTERPRESS_CSS` + `MARKER_CSS` agree (modulo the
171
+ * marker-only exception above), so the two cannot silently drift apart —
156
172
  * whoever adds a class to one CSS block and forgets this list finds out from
157
173
  * a failing test, not from a future bug report.
158
174
  */
@@ -2,6 +2,7 @@ import { type LayoutWarning } from "./assemble";
2
2
  import { type AssetCopy } from "../asset-inline";
3
3
  import type { LoadedPlugin, PluginStyleGroup } from "./renderer";
4
4
  import type { PrintPadding } from "../../engine/shared/synthesis.ts";
5
+ import type { PrintAreaSource } from "../../engine/shared/print-area.ts";
5
6
  export type { LayoutWarning } from "./assemble";
6
7
  /**
7
8
  * THE canonical "which markdown files make up this book, and in what order?"
@@ -33,6 +34,8 @@ export declare function renderChapters(inputDir: string, opts?: {
33
34
  title?: string;
34
35
  /** See {@link AssembleBookHtmlOptions.print}. */
35
36
  print?: PrintPadding;
37
+ /** See {@link AssembleBookHtmlOptions.printArea}. */
38
+ printArea?: PrintAreaSource;
36
39
  styles?: string[];
37
40
  files?: string[] | null;
38
41
  plugins?: LoadedPlugin[];
@@ -72,6 +75,8 @@ export declare function renderChaptersToFile(inputDir: string, outDir: string, o
72
75
  title?: string;
73
76
  /** See {@link AssembleBookHtmlOptions.print}. */
74
77
  print?: PrintPadding;
78
+ /** See {@link AssembleBookHtmlOptions.printArea}. */
79
+ printArea?: PrintAreaSource;
75
80
  styles?: string[];
76
81
  files?: string[] | null;
77
82
  plugins?: LoadedPlugin[];
@@ -222,19 +222,19 @@
222
222
  },
223
223
  "bleed": {
224
224
  "type": "number",
225
- "description": "A bleed, in points, that the page width x height ALREADY CONTAINS on the top, bottom and OUTER edges (none on the binding edge) — how DriveThruRPG's interior file is cut (8.625 x 11.25in for an 8.5 x 11in book). It tells the preview's safe area & bleed overlay and the `pdf.print.safe-margins` check where the trim is, and changes nothing in the PDF. Preset default: dtrpg 9 (0.125in); book and custom none.",
225
+ "description": "A bleed, in points, that the page width x height ALREADY CONTAINS on the top, bottom and OUTER edges (none on the binding edge) — how DriveThruRPG's interior file is cut (8.625 x 11.25in for an 8.5 x 11in book). It tells the preview's safe area & bleed overlay, the `pdf.print.safe-margins` check and pinned art where the trim is, and by itself changes nothing in the PDF. Preset default: dtrpg 9 (0.125in); book and custom none.",
226
226
  "minimum": 0,
227
227
  "examples": [9]
228
228
  },
229
229
  "safe": {
230
230
  "type": "number",
231
- "description": "The TEXT rule, in points: how far inside the trim glyphs must stay, on every edge. For DriveThruRPG it is the \"text at least 0.5in inside the trim\" rule. The preview's overlay outlines it (dashed) and flags text outside it, and the `pdf.print.safe-margins` check fails a PDF whose text is closer. It changes nothing in the PDF, and no book CSS moves it. Preset default: dtrpg 36 (0.5in), book 18 (0.25in); custom none.",
231
+ "description": "The TEXT rule, in points: how far inside the trim glyphs must stay, on every edge. For DriveThruRPG it is the \"text at least 0.5in inside the trim\" rule. The preview's overlay outlines it (dashed) and flags text outside it, and the `pdf.print.safe-margins` check fails a PDF whose text is closer. An image pinned to the binding edge (`.gp-pin`) sits on it, and to any edge where `artSafe` is not set; no book CSS moves it. Preset default: dtrpg 36 (0.5in), book 18 (0.25in); custom none.",
232
232
  "minimum": 0,
233
233
  "examples": [18, 36]
234
234
  },
235
235
  "artSafe": {
236
236
  "type": "number",
237
- "description": "The ART rule, in points: how far inside the trim art that does not bleed (images, backgrounds, borders, rules) must stay on the top, bottom and OUTER edge; at the binding edge art uses `safe`. For DriveThruRPG it is the \"non-bleeding art at least 0.25in from the outside edges (0.5in at the binding)\" rule. Art that runs out to the page's edge is a bleed and is not judged on that edge. The preview's overlay outlines it (dotted) and flags art outside it. Preview overlay only: it changes nothing in the PDF. Absent = the same as `safe`. Preset default: dtrpg 18 (0.25in); book and custom none.",
237
+ "description": "The ART rule, in points: how far inside the trim art that does not bleed (images, backgrounds, borders, rules) must stay on the top, bottom and OUTER edge; at the binding edge art uses `safe`. For DriveThruRPG it is the \"non-bleeding art at least 0.25in from the outside edges (0.5in at the binding)\" rule. Art that runs out to the page's edge is a bleed and is not judged on that edge. The preview's overlay outlines it (dotted) and flags art outside it, and an image pinned to the top, bottom or outer edge (`.gp-pin`) sits on it unless it carries `.gp-flush`. Absent = the same as `safe`. Preset default: dtrpg 18 (0.25in); book and custom none.",
238
238
  "minimum": 0,
239
239
  "examples": [18]
240
240
  }
@@ -7,17 +7,17 @@ import {
7
7
  TARGET_IDS,
8
8
  scaffoldExtension,
9
9
  scaffoldProject
10
- } from "./cli-9k2tb493.js";
10
+ } from "./cli-0ey902sx.js";
11
11
  import {
12
12
  resolveGhostscript
13
- } from "./cli-y9bcya7r.js";
13
+ } from "./cli-h4tfmpg3.js";
14
14
  import {
15
15
  UsageError,
16
16
  rejectExtraPositionals,
17
17
  rejectUnknownFlags
18
- } from "./cli-hn9pk0hp.js";
18
+ } from "./cli-fmksazej.js";
19
19
  import"./cli-ht1bcqdd.js";
20
- import"./cli-x4p99w8d.js";
20
+ import"./cli-61vd4ews.js";
21
21
  import {
22
22
  EXIT_CODES
23
23
  } from "./cli-46ycxe6r.js";
@@ -2,17 +2,17 @@ import {
2
2
  executeValidation,
3
3
  publishTargetFor,
4
4
  reportMissingTools
5
- } from "./cli-9k2tb493.js";
5
+ } from "./cli-0ey902sx.js";
6
6
  import {
7
7
  log
8
- } from "./cli-y9bcya7r.js";
8
+ } from "./cli-h4tfmpg3.js";
9
9
  import {
10
10
  UsageError,
11
11
  rejectExtraPositionals,
12
12
  rejectUnknownFlags
13
- } from "./cli-hn9pk0hp.js";
13
+ } from "./cli-fmksazej.js";
14
14
  import"./cli-ht1bcqdd.js";
15
- import"./cli-x4p99w8d.js";
15
+ import"./cli-61vd4ews.js";
16
16
  import {
17
17
  BuildError,
18
18
  EXIT_CODES
@@ -20,17 +20,11 @@ import type { PrintPadding } from '../engine/shared/synthesis.ts';
20
20
  *
21
21
  * The book always paginates as ONE document, exactly as the print path does:
22
22
  * no per-source wrappers or forced breaks are added, so preview pagination
23
- * never diverges from the PDF where a source file begins mid-page.
23
+ * never diverges from the PDF where a source file begins mid-page. The
24
+ * project's print area is not added here: the markdown assembly writes it
25
+ * into every book, the preview's included (`AssembleBookHtmlOptions.printArea`).
24
26
  */
25
- export declare function injectPreviewScripts(html: string, page?: {
26
- width: number;
27
- height: number;
28
- bleed?: number;
29
- safe?: number;
30
- artSafe?: number;
31
- preset?: string;
32
- spec?: PrintSpec;
33
- }): string;
27
+ export declare function injectPreviewScripts(html: string): string;
34
28
  /**
35
29
  * Generate HTML from markdown and write book.html to the temp directory.
36
30
  * The desktop's iframe loads `book.html` via a relative URL — same name in
@@ -6,10 +6,10 @@ import {
6
6
  runBuild,
7
7
  splitOutPath,
8
8
  startPreviewServer
9
- } from "./cli-9k2tb493.js";
9
+ } from "./cli-0ey902sx.js";
10
10
  import {
11
11
  log
12
- } from "./cli-y9bcya7r.js";
12
+ } from "./cli-h4tfmpg3.js";
13
13
  import {
14
14
  UsageError,
15
15
  parseFormat,
@@ -17,9 +17,9 @@ import {
17
17
  rejectExtraPositionals,
18
18
  rejectUnknownFlags,
19
19
  resolvePort
20
- } from "./cli-hn9pk0hp.js";
20
+ } from "./cli-fmksazej.js";
21
21
  import"./cli-ht1bcqdd.js";
22
- import"./cli-x4p99w8d.js";
22
+ import"./cli-61vd4ews.js";
23
23
  import {
24
24
  BuildError
25
25
  } from "./cli-46ycxe6r.js";
@@ -11,18 +11,18 @@ import {
11
11
  publishProviderFor,
12
12
  resolvePublishFormat,
13
13
  runPublish
14
- } from "./cli-9k2tb493.js";
14
+ } from "./cli-0ey902sx.js";
15
15
  import {
16
16
  FileTokenStore,
17
17
  log
18
- } from "./cli-y9bcya7r.js";
18
+ } from "./cli-h4tfmpg3.js";
19
19
  import {
20
20
  UsageError,
21
21
  rejectExtraPositionals,
22
22
  rejectUnknownFlags
23
- } from "./cli-hn9pk0hp.js";
23
+ } from "./cli-fmksazej.js";
24
24
  import"./cli-ht1bcqdd.js";
25
- import"./cli-x4p99w8d.js";
25
+ import"./cli-61vd4ews.js";
26
26
  import {
27
27
  EXIT_CODES
28
28
  } from "./cli-46ycxe6r.js";
package/dist/render.js CHANGED
@@ -1231,7 +1231,9 @@ var GUTTERPRESS_CSS = `
1231
1231
  cap at equal specificity */
1232
1232
  .gp-small { width: 25%; max-width: 100%; }
1233
1233
  .gp-medium { width: 50%; max-width: 100%; }
1234
- .gp-large { width: 75%; max-width: 100%; }
1234
+ .gp-large { width: 60%; max-width: 100%; }
1235
+ .gp-xl { width: 80%; max-width: 100%; }
1236
+ .gp-xxl { width: 90%; max-width: 100%; }
1235
1237
 
1236
1238
  /* float clearance presets — consumed by var(--gp-gap) in the float rules
1237
1239
  above and by .gp-shape's shape-margin below; --gp-gap itself is
@@ -1289,10 +1291,16 @@ img.gp-shape {
1289
1291
  }
1290
1292
 
1291
1293
  /* pin — within the nearest positioned ancestor (.page/.spread, rule above).
1292
- inset:0 and the explicit centers are load-bearing; see doctrine comment. */
1294
+ The four insets are the pin box: 0 (the text block's edge) unless an
1295
+ engine set --gp-pin-<edge> on the page root for an edge a pin uses (the
1296
+ art-safe line). Non-auto insets and the explicit centers are
1297
+ load-bearing; see doctrine comment. */
1293
1298
  .gp-pin {
1294
1299
  position: absolute;
1295
- inset: 0;
1300
+ top: var(--gp-pin-top, 0px);
1301
+ right: var(--gp-pin-right, 0px);
1302
+ bottom: var(--gp-pin-bottom, 0px);
1303
+ left: var(--gp-pin-left, 0px);
1296
1304
  align-self: center;
1297
1305
  justify-self: center;
1298
1306
  margin: 0;
@@ -1307,6 +1315,16 @@ img.gp-shape {
1307
1315
  .gp-left { justify-self: start; }
1308
1316
  .gp-right { justify-self: end; }
1309
1317
 
1318
+ /* the escape hatch — the paper's edge, past the printer's safe area: the
1319
+ pin box loses its art-safe insets. Set on the pin itself, so it beats the
1320
+ values the page root hands down. */
1321
+ .gp-pin.gp-flush {
1322
+ --gp-pin-top: 0px;
1323
+ --gp-pin-right: 0px;
1324
+ --gp-pin-bottom: 0px;
1325
+ --gp-pin-left: 0px;
1326
+ }
1327
+
1310
1328
  /* wrapper-margin neutralization (same pattern and rationale as the
1311
1329
  .gp-bleed paragraph-margin note in the doctrine comment; for pin, the
1312
1330
  emptied paragraph would otherwise leave a phantom margin gap in flow) */
@@ -1416,6 +1434,8 @@ var GP_CLASSES = new Set([
1416
1434
  "gp-small",
1417
1435
  "gp-medium",
1418
1436
  "gp-large",
1437
+ "gp-xl",
1438
+ "gp-xxl",
1419
1439
  "gp-tight",
1420
1440
  "gp-loose",
1421
1441
  "gp-columns",
@@ -1432,6 +1452,7 @@ var GP_CLASSES = new Set([
1432
1452
  "gp-pin",
1433
1453
  "gp-top",
1434
1454
  "gp-bottom",
1455
+ "gp-flush",
1435
1456
  "gp-behind",
1436
1457
  "gp-base",
1437
1458
  "gp-raised",
@@ -1445,8 +1466,7 @@ var GP_CLASSES = new Set([
1445
1466
  "gp-alert-caution",
1446
1467
  "gp-page-break",
1447
1468
  "gp-column-break",
1448
- "gp-continued",
1449
- "gp-flush"
1469
+ "gp-continued"
1450
1470
  ]);
1451
1471
 
1452
1472
  // src/lib/markdown/table-align.ts
@@ -1550,6 +1570,21 @@ function hasPinClass(token) {
1550
1570
  const cls = token.attrGet && token.attrGet("class");
1551
1571
  return typeof cls === "string" && cls.split(/\s+/).includes("gp-pin");
1552
1572
  }
1573
+ var PIN_EDGES = ["gp-top", "gp-right", "gp-bottom", "gp-left"];
1574
+ var FLUSH_ART_MSG = "`.gp-flush` puts this image on the paper's edge, past the printer's safe area. Printers trim up to 0.125in there, so keep it for art that is meant to bleed off the page.";
1575
+ function checkFlushArt(token, env, line) {
1576
+ if (token.type !== "image")
1577
+ return;
1578
+ const cls = token.attrGet && token.attrGet("class");
1579
+ if (typeof cls !== "string")
1580
+ return;
1581
+ const classes = cls.split(/\s+/);
1582
+ if (!classes.includes("gp-pin") || !classes.includes("gp-flush"))
1583
+ return;
1584
+ if (!classes.some((c) => PIN_EDGES.includes(c)))
1585
+ return;
1586
+ warn2(env, line, "flush_art", FLUSH_ART_MSG);
1587
+ }
1553
1588
  function elementNameFor(token) {
1554
1589
  switch (token.type) {
1555
1590
  case "layout_chapter_open":
@@ -1609,6 +1644,7 @@ function gpPinScope(md) {
1609
1644
  if (token.type === "inline" && token.children) {
1610
1645
  for (const child of token.children) {
1611
1646
  checkUnknownGpClasses(child, state.env, line);
1647
+ checkFlushArt(child, state.env, line);
1612
1648
  }
1613
1649
  }
1614
1650
  }
@@ -2343,6 +2379,20 @@ function lastPageMode(print) {
2343
2379
  }
2344
2380
  var WHICH_VALUES = new Set(["first", "start", "last", "first-except"]);
2345
2381
 
2382
+ // src/engine/shared/print-area.ts
2383
+ function printAreaMeta(page) {
2384
+ const area = JSON.stringify({
2385
+ width: page.width,
2386
+ height: page.height,
2387
+ bleed: page.bleed ?? 0,
2388
+ safe: page.safe ?? 0,
2389
+ artSafe: page.artSafe,
2390
+ preset: page.preset,
2391
+ spec: page.spec
2392
+ });
2393
+ return `<meta name="gp-print-area" content='${area.replace(/&/g, "&amp;").replace(/'/g, "&#39;")}'>`;
2394
+ }
2395
+
2346
2396
  // src/lib/markdown/assemble.ts
2347
2397
  async function assembleBookHtml(opts) {
2348
2398
  const title = opts.title ?? "Document";
@@ -2408,7 +2458,8 @@ ${projectCss.trim()}` : null
2408
2458
  <html lang="en"${printAttrs}>
2409
2459
  <head>
2410
2460
  <meta charset="UTF-8">
2411
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
2461
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">${opts.printArea ? `
2462
+ ${printAreaMeta(opts.printArea)}` : ""}
2412
2463
  <title>${title}</title>${preloadTags}
2413
2464
  <style data-project-css>
2414
2465
  ${inlineCss}
@@ -171,17 +171,18 @@ export interface GutterpressManifest {
171
171
  * A bleed (pt) the page `width` x `height` ALREADY CONTAINS, on the top,
172
172
  * bottom and OUTER edges only (the binding edge has none) — how
173
173
  * DriveThruRPG's interior file is cut (8.625 x 11.25in for an 8.5 x 11in
174
- * book). It changes nothing in the PDF: it only tells the preview's safe
175
- * area & bleed overlay and `pdf.print.safe-margins` where the trim is.
174
+ * book). By itself it changes nothing in the PDF: it tells the preview's
175
+ * safe area & bleed overlay, `pdf.print.safe-margins` and pinned art
176
+ * (`.gp-pin` on an edge sits on the art-safe line) where the trim is.
176
177
  */
177
178
  bleed?: number;
178
179
  /**
179
180
  * The TEXT rule (pt): glyphs stay at least this far inside the trim on
180
181
  * EVERY edge — DriveThruRPG's "keep text 0.5in inside the trim" (`dtrpg`
181
182
  * sets 36). The preview's overlay outlines it and flags text outside it,
182
- * and `pdf.print.safe-margins` fails a built PDF whose text is closer. Like
183
- * `bleed`, it changes nothing in the PDF, and nothing in a book's CSS moves
184
- * it.
183
+ * and `pdf.print.safe-margins` fails a built PDF whose text is closer. An
184
+ * image pinned to the binding edge sits on it (and to any edge, where
185
+ * `artSafe` is not set); nothing in a book's CSS moves it.
185
186
  */
186
187
  safe?: number;
187
188
  /**
@@ -189,7 +190,8 @@ export interface GutterpressManifest {
189
190
  * borders, rules — stays at least this far inside the trim on the top,
190
191
  * bottom and OUTER edge; at the binding edge it uses `safe`. DriveThruRPG's
191
192
  * "keep non-bleeding art 0.25in from the outside edges" (`dtrpg` sets 18).
192
- * Absent = the same as `safe`.
193
+ * An image pinned to the top, bottom or outer edge (`.gp-pin`) sits on this
194
+ * line unless it carries `.gp-flush`. Absent = the same as `safe`.
193
195
  */
194
196
  artSafe?: number;
195
197
  };
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  executeAndReport
3
- } from "./cli-9k2tb493.js";
3
+ } from "./cli-0ey902sx.js";
4
4
  import {
5
5
  log
6
- } from "./cli-y9bcya7r.js";
6
+ } from "./cli-h4tfmpg3.js";
7
7
  import {
8
8
  UsageError,
9
9
  rejectExtraPositionals,
10
10
  rejectUnknownFlags
11
- } from "./cli-hn9pk0hp.js";
11
+ } from "./cli-fmksazej.js";
12
12
  import"./cli-ht1bcqdd.js";
13
- import"./cli-x4p99w8d.js";
13
+ import"./cli-61vd4ews.js";
14
14
  import {
15
15
  BuildError,
16
16
  EXIT_CODES
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gutterpress",
3
- "version": "0.11.20-alpha.3",
3
+ "version": "0.11.20-alpha.4",
4
4
  "description": "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
5
5
  "author": "itlackey",
6
6
  "license": "MPL-2.0",
@@ -1,59 +0,0 @@
1
- /**
2
- * `.gp-flush` policy — shared by the compiler and the viewer, like every rule
3
- * that DECIDES something (see synthesis.ts's header).
4
- *
5
- * A pinned image reaches the paper only if the page's own margin is removed
6
- * on that edge: Chromium's printable area IS the page area — MEASURED, a
7
- * pinned box pulled into the margin with negative insets fragments onto the
8
- * NEXT sheet, one moved there with a transform is clipped away entirely, and
9
- * a margin box in a ~zero margin (1px, with padding/height compensation)
10
- * simply does not render. So flush is implemented as page GEOMETRY, per page:
11
- *
12
- * - The COMPILER aliases the flush root's own page context under a
13
- * generated name (verbatim rule copies — never a resolved flatten, which
14
- * is the "re-implement the @page cascade" trap tier2's history records),
15
- * zeroes the flushed margins on the alias, and assigns the root to it.
16
- * - The VIEWER keeps the author's page context and adjusts the strip
17
- * geometry in JS from these same functions.
18
- *
19
- * Margin boxes live IN the margin, so the flushed edge's furniture (folio,
20
- * running head) cannot render natively there — MEASURED, no compensation
21
- * trick survives. Both renderers therefore keep painting that furniture
22
- * themselves at its ORIGINAL coordinates: the viewer already synthesizes all
23
- * furniture as DOM; the compiler injects the flushed edge's boxes into the
24
- * page root as positioned elements with engine-resolved text. Nothing the
25
- * author declared is lost — that is the contract this module's box map
26
- * exists to keep.
27
- */
28
- import type { PageGeometry } from "./gcpm-extract.ts";
29
- export declare const FLUSH_EDGES: readonly ["top", "right", "bottom", "left"];
30
- export type FlushEdge = (typeof FLUSH_EDGES)[number];
31
- /** Minimal DOM shape needed by `flushEdgesIn` — keeps this module node-safe. */
32
- interface QueryRoot {
33
- querySelector(sel: string): unknown;
34
- }
35
- /**
36
- * Which edges the flush pins inside `root` ask for, in canonical t/r/b/l
37
- * order. A `.gp-flush` without `.gp-pin` or without an edge word is inert by
38
- * construction — every selector here requires all three.
39
- */
40
- export declare function flushEdgesIn(root: QueryRoot): FlushEdge[];
41
- /** Canonical short key for an edge set: "b", "rb", "trbl"… (t/r/b/l order). */
42
- export declare function flushKey(edges: readonly FlushEdge[]): string;
43
- /**
44
- * Generated page name for (author page context, edge set). The `gp--` double
45
- * dash marks an engine-generated page (the `gp--blank` convention); these
46
- * exist only in builds that actually contain a flush pin, which is why the
47
- * width check may treat them as real author contexts.
48
- */
49
- export declare function flushPageName(authorPage: string | undefined, edges: readonly FlushEdge[]): string;
50
- /** The page's margins with the flushed edges freed. */
51
- export declare function flushMargins(margin: PageGeometry["margin"], edges: readonly FlushEdge[]): PageGeometry["margin"];
52
- /**
53
- * Margin boxes that live (wholly or partly) in a flushed edge's margin area —
54
- * the set both renderers must keep painting themselves. A corner box belongs
55
- * to BOTH of its edges: `bottom-left-corner` dies when either the bottom or
56
- * the left margin is freed.
57
- */
58
- export declare function marginBoxesOnEdges(edges: readonly FlushEdge[]): string[];
59
- export {};