gutterpress 0.11.20-alpha.4 → 0.11.20-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/api/index.js +2 -2
  2. package/dist/{build-epdcrm2c.js → build-3bv5hgez.js} +4 -4
  3. package/dist/{cli-0ey902sx.js → cli-4d25xvs2.js} +45 -32
  4. package/dist/{cli-61vd4ews.js → cli-cyej3p9v.js} +4 -4
  5. package/dist/{cli-fmksazej.js → cli-gxqxn3sf.js} +1 -1
  6. package/dist/{cli-h4tfmpg3.js → cli-tsdrvrff.js} +1 -1
  7. package/dist/cli.js +12 -12
  8. package/dist/{doctor-78es5scm.js → doctor-x9vjqhfk.js} +2 -2
  9. package/dist/engine/shared/pin-edges.d.ts +5 -5
  10. package/dist/engine/shared/print-area.d.ts +12 -7
  11. package/dist/{engine-67f68bxs.js → engine-4rkhv5ga.js} +1 -1
  12. package/dist/{engine-7ha27n2b.js → engine-5fw55dfb.js} +2 -2
  13. package/dist/{ext-9mc7sr0d.js → ext-9wafdrps.js} +4 -4
  14. package/dist/{gutterpress-agent-mrdnwktc.js → gutterpress-agent-yvd8w8my.js} +1 -0
  15. package/dist/{gutterpress-viewer-jr30yw8t.js → gutterpress-viewer-w7njnkj7.js} +115 -20
  16. package/dist/{index-gm5xbx5w.js → index-crys9951.js} +3 -3
  17. package/dist/{index-t13p8y08.js → index-pjw7trb8.js} +63 -39
  18. package/dist/{index-ad95fws8.js → index-yw16cfm8.js} +2 -2
  19. package/dist/index.js +3 -3
  20. package/dist/lib/markdown/assemble.d.ts +1 -1
  21. package/dist/lib/markdown/gutterpress-css.d.ts +8 -8
  22. package/dist/lib/page-size.d.ts +5 -3
  23. package/dist/{manifest.schema-13dfax80.json → manifest.schema-c4wfpg6g.json} +8 -2
  24. package/dist/{new-s4f0tnya.js → new-zwfmqf9t.js} +4 -4
  25. package/dist/{preflight-7ck4rbsq.js → preflight-pfhbrhdb.js} +4 -4
  26. package/dist/preview/file-watcher.d.ts +1 -0
  27. package/dist/{preview-j91sxadd.js → preview-6bc098rf.js} +4 -4
  28. package/dist/{publish-f12xaenm.js → publish-pev62kc6.js} +4 -4
  29. package/dist/render.js +22 -13
  30. package/dist/schema/manifest.types.d.ts +20 -6
  31. package/dist/{validate-pb5gysvr.js → validate-x00gjxgb.js} +4 -4
  32. package/package.json +1 -1
@@ -18,7 +18,7 @@
18
18
  * Also ships the author-facing `gp-*` image/block vocabulary (CLAUDE.md §0 —
19
19
  * a behavior broadly useful to non-technical authors belongs in core, not a
20
20
  * project layer). markdown-it-attrs is bundled by
21
- * default (renderer.ts), so `![Art](x.jpg){.gp-right .gp-small}` already
21
+ * default (renderer.ts), so `![Art](x.jpg){.gp-right .gp-size-30}` already
22
22
  * attaches the classes to the rendered `<img>` — these rules are what make
23
23
  * them actually do something print-safe. One vocabulary, no aliases: there
24
24
  * is no duplicate way to spell each layout (the desktop editor rewrites the
@@ -29,11 +29,11 @@
29
29
  * .gp-right — floats right, text wraps.
30
30
  * .gp-center — centers a block element.
31
31
  * .gp-full — fills the page's content width.
32
- * .gp-small — width 25% of the column. Sizes compose with any
33
- * .gp-medium — width 50%. position (including .gp-pin, where
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).
32
+ * .gp-size-10 … .gp-size-90
33
+ * — width 10% … 90% of the column, in 10% steps. Sizes
34
+ * compose with any position (including .gp-pin, where
35
+ * the % resolves against the page root: the page box,
36
+ * reaching the paper on an edge a pin uses).
37
37
  * There is no 100% step: that is .gp-full, a position.
38
38
  * .gp-tight — float/shape clearance 0.5em (sets --gp-gap; default 1em).
39
39
  * .gp-loose — float/shape clearance 2em (sets --gp-gap).
@@ -132,7 +132,7 @@
132
132
  * is pinned. The pin's containing
133
133
  * block is the root's padding box, and the engine sets `--gp-pin-<edge>` on
134
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
135
+ * plus page.artSafe on the top, bottom and outside edge; page.gutter at the
136
136
  * binding, from the manifest); `.gp-pin` reads them as its four insets.
137
137
  * `.gp-flush` zeroes them on the pin: the paper's edge. Under plain
138
138
  * markdown-it nothing sets them and every inset is 0.
@@ -151,7 +151,7 @@
151
151
  * carrying gp-left could start shifting; that is standards-tracking per
152
152
  * CLAUDE.md ("Chrome wins once it ships"), not a bug in the author's book.
153
153
  */
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";
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-size-10 { width: 10%; max-width: 100%; }\n.gp-size-20 { width: 20%; max-width: 100%; }\n.gp-size-30 { width: 30%; max-width: 100%; }\n.gp-size-40 { width: 40%; max-width: 100%; }\n.gp-size-50 { width: 50%; max-width: 100%; }\n.gp-size-60 { width: 60%; max-width: 100%; }\n.gp-size-70 { width: 70%; max-width: 100%; }\n.gp-size-80 { width: 80%; max-width: 100%; }\n.gp-size-90 { 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";
155
155
  /**
156
156
  * GP_CLASSES — every class an author may legitimately write with a `gp-`
157
157
  * prefix: the `.gp-*` selectors in `GUTTERPRESS_CSS` above, the two
@@ -37,7 +37,8 @@ export declare function isPositivePoints(value: unknown): value is number;
37
37
  /**
38
38
  * Write the manifest's `page:` bounds. `tolerance` defaults to whatever the
39
39
  * block already carries, so re-sizing a book never silently resets a
40
- * hand-tuned tolerance.
40
+ * hand-tuned tolerance. The block's other keys stay as written; a `page:`
41
+ * that is not a map is replaced.
41
42
  */
42
43
  export declare function writePageBounds(doc: Document, page: CustomPageOptions): void;
43
44
  /** The size (points) a choice means: `custom` brings its own, the others are the preset's. */
@@ -59,8 +60,9 @@ export declare function readPageSetup(projectDir: string): Promise<PageSetup>;
59
60
  /**
60
61
  * Change a book's page size: the manifest's `preset:` + `page:` bounds and the
61
62
  * stylesheet's `@page` size, together. Mirrors what the wizard records —
62
- * `dtrpg` / `book` carry their own bounds (any `page:` block is removed so it
63
- * cannot shadow them), `custom` records the typed trim. Nothing is written
63
+ * `dtrpg` / `book` carry their own bounds (the `page:` block's width, height
64
+ * and tolerance are removed so they cannot shadow them; its print-area keys
65
+ * stay), `custom` records the typed trim. Nothing is written
64
66
  * unless the whole choice validates and the stylesheet parses.
65
67
  */
66
68
  export declare function setPageSetup(projectDir: string, choice: PageSizeChoice): Promise<PageSetup>;
@@ -228,15 +228,21 @@
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. 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.",
231
+ "description": "The TEXT rule, in points: how far inside the trim glyphs must stay, on every edge (the binding edge too, unless `gutter` is set). 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 an edge (`.gp-pin`) where `artSafe` is not set sits on it; 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, 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.",
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 `gutter`. 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
+ },
241
+ "gutter": {
242
+ "type": "number",
243
+ "description": "The binding allowance, in points: how far content stays from the spine, measured from the trim at the binding edge (the left edge of a right-hand page, the right edge of a left-hand one). It stands in for `safe` there, for text and for art alike: the preview's overlay draws and judges that edge against it, the `pdf.print.safe-margins` check fails text closer to the spine than it, and an image pinned to the binding edge (`.gp-pin`) sits on it. The other edges keep `safe` and `artSafe`. Use it when your book keeps its text further from the spine than the printer's minimum: the Field Guide keeps 0.75in (54) where DriveThruRPG asks for 0.5in. Absent = the same as `safe`. Presets leave it unset.",
244
+ "minimum": 0,
245
+ "examples": [54]
240
246
  }
241
247
  }
242
248
  },
@@ -7,17 +7,17 @@ import {
7
7
  TARGET_IDS,
8
8
  scaffoldExtension,
9
9
  scaffoldProject
10
- } from "./cli-0ey902sx.js";
10
+ } from "./cli-4d25xvs2.js";
11
11
  import {
12
12
  resolveGhostscript
13
- } from "./cli-h4tfmpg3.js";
13
+ } from "./cli-tsdrvrff.js";
14
14
  import {
15
15
  UsageError,
16
16
  rejectExtraPositionals,
17
17
  rejectUnknownFlags
18
- } from "./cli-fmksazej.js";
18
+ } from "./cli-gxqxn3sf.js";
19
19
  import"./cli-ht1bcqdd.js";
20
- import"./cli-61vd4ews.js";
20
+ import"./cli-cyej3p9v.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-0ey902sx.js";
5
+ } from "./cli-4d25xvs2.js";
6
6
  import {
7
7
  log
8
- } from "./cli-h4tfmpg3.js";
8
+ } from "./cli-tsdrvrff.js";
9
9
  import {
10
10
  UsageError,
11
11
  rejectExtraPositionals,
12
12
  rejectUnknownFlags
13
- } from "./cli-fmksazej.js";
13
+ } from "./cli-gxqxn3sf.js";
14
14
  import"./cli-ht1bcqdd.js";
15
- import"./cli-61vd4ews.js";
15
+ import"./cli-cyej3p9v.js";
16
16
  import {
17
17
  BuildError,
18
18
  EXIT_CODES
@@ -53,6 +53,7 @@ export declare function generateAndWriteHtml(inputPath: string, tempDir: string,
53
53
  bleed?: number;
54
54
  safe?: number;
55
55
  artSafe?: number;
56
+ gutter?: number;
56
57
  };
57
58
  preset?: string;
58
59
  spec?: PrintSpec;
@@ -6,10 +6,10 @@ import {
6
6
  runBuild,
7
7
  splitOutPath,
8
8
  startPreviewServer
9
- } from "./cli-0ey902sx.js";
9
+ } from "./cli-4d25xvs2.js";
10
10
  import {
11
11
  log
12
- } from "./cli-h4tfmpg3.js";
12
+ } from "./cli-tsdrvrff.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-fmksazej.js";
20
+ } from "./cli-gxqxn3sf.js";
21
21
  import"./cli-ht1bcqdd.js";
22
- import"./cli-61vd4ews.js";
22
+ import"./cli-cyej3p9v.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-0ey902sx.js";
14
+ } from "./cli-4d25xvs2.js";
15
15
  import {
16
16
  FileTokenStore,
17
17
  log
18
- } from "./cli-h4tfmpg3.js";
18
+ } from "./cli-tsdrvrff.js";
19
19
  import {
20
20
  UsageError,
21
21
  rejectExtraPositionals,
22
22
  rejectUnknownFlags
23
- } from "./cli-fmksazej.js";
23
+ } from "./cli-gxqxn3sf.js";
24
24
  import"./cli-ht1bcqdd.js";
25
- import"./cli-61vd4ews.js";
25
+ import"./cli-cyej3p9v.js";
26
26
  import {
27
27
  EXIT_CODES
28
28
  } from "./cli-46ycxe6r.js";
package/dist/render.js CHANGED
@@ -1229,11 +1229,15 @@ var GUTTERPRESS_CSS = `
1229
1229
 
1230
1230
  /* sizes — AFTER the flow positions so max-width:100% lifts the floats' 50%
1231
1231
  cap at equal specificity */
1232
- .gp-small { width: 25%; max-width: 100%; }
1233
- .gp-medium { width: 50%; 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%; }
1232
+ .gp-size-10 { width: 10%; max-width: 100%; }
1233
+ .gp-size-20 { width: 20%; max-width: 100%; }
1234
+ .gp-size-30 { width: 30%; max-width: 100%; }
1235
+ .gp-size-40 { width: 40%; max-width: 100%; }
1236
+ .gp-size-50 { width: 50%; max-width: 100%; }
1237
+ .gp-size-60 { width: 60%; max-width: 100%; }
1238
+ .gp-size-70 { width: 70%; max-width: 100%; }
1239
+ .gp-size-80 { width: 80%; max-width: 100%; }
1240
+ .gp-size-90 { width: 90%; max-width: 100%; }
1237
1241
 
1238
1242
  /* float clearance presets — consumed by var(--gp-gap) in the float rules
1239
1243
  above and by .gp-shape's shape-margin below; --gp-gap itself is
@@ -1431,11 +1435,15 @@ var GP_CLASSES = new Set([
1431
1435
  "gp-center",
1432
1436
  "gp-full",
1433
1437
  "gp-bleed",
1434
- "gp-small",
1435
- "gp-medium",
1436
- "gp-large",
1437
- "gp-xl",
1438
- "gp-xxl",
1438
+ "gp-size-10",
1439
+ "gp-size-20",
1440
+ "gp-size-30",
1441
+ "gp-size-40",
1442
+ "gp-size-50",
1443
+ "gp-size-60",
1444
+ "gp-size-70",
1445
+ "gp-size-80",
1446
+ "gp-size-90",
1439
1447
  "gp-tight",
1440
1448
  "gp-loose",
1441
1449
  "gp-columns",
@@ -1537,10 +1545,10 @@ import markdownItAttrs from "markdown-it-attrs";
1537
1545
  import markdownItFootnote from "markdown-it-footnote";
1538
1546
 
1539
1547
  // src/lib/markdown/gp-pin-scope.js
1540
- function warn2(env, line, type, message, marker) {
1548
+ function warn2(env, line, type, message, severity) {
1541
1549
  if (!env.layoutWarnings)
1542
1550
  env.layoutWarnings = [];
1543
- env.layoutWarnings.push({ line, type, message, marker });
1551
+ env.layoutWarnings.push(severity ? { line, type, message, severity } : { line, type, message });
1544
1552
  }
1545
1553
  function editDistance2(a, b) {
1546
1554
  let prev = Array.from({ length: b.length + 1 }, (_, j) => j);
@@ -1583,7 +1591,7 @@ function checkFlushArt(token, env, line) {
1583
1591
  return;
1584
1592
  if (!classes.some((c) => PIN_EDGES.includes(c)))
1585
1593
  return;
1586
- warn2(env, line, "flush_art", FLUSH_ART_MSG);
1594
+ warn2(env, line, "flush_art", FLUSH_ART_MSG, "info");
1587
1595
  }
1588
1596
  function elementNameFor(token) {
1589
1597
  switch (token.type) {
@@ -2387,6 +2395,7 @@ function printAreaMeta(page) {
2387
2395
  bleed: page.bleed ?? 0,
2388
2396
  safe: page.safe ?? 0,
2389
2397
  artSafe: page.artSafe,
2398
+ gutter: page.gutter,
2390
2399
  preset: page.preset,
2391
2400
  spec: page.spec
2392
2401
  });
@@ -178,22 +178,34 @@ export interface GutterpressManifest {
178
178
  bleed?: number;
179
179
  /**
180
180
  * The TEXT rule (pt): glyphs stay at least this far inside the trim on
181
- * EVERY edge — DriveThruRPG's "keep text 0.5in inside the trim" (`dtrpg`
182
- * sets 36). The preview's overlay outlines it and flags text outside 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.
181
+ * EVERY edge, the binding edge too unless the book sets a `gutter` —
182
+ * DriveThruRPG's "keep text 0.5in inside the trim" (`dtrpg` sets 36). The
183
+ * preview's overlay outlines it and flags text outside it, and
184
+ * `pdf.print.safe-margins` fails a built PDF whose text is closer. An
185
+ * image pinned to an edge where `artSafe` is not set sits on it; nothing
186
+ * in a book's CSS moves it.
186
187
  */
187
188
  safe?: number;
188
189
  /**
189
190
  * The ART rule (pt): art that does not bleed — images, backgrounds,
190
191
  * borders, rules — stays at least this far inside the trim on the top,
191
- * bottom and OUTER edge; at the binding edge it uses `safe`. DriveThruRPG's
192
+ * bottom and OUTER edge; at the binding edge it uses `gutter`. DriveThruRPG's
192
193
  * "keep non-bleeding art 0.25in from the outside edges" (`dtrpg` sets 18).
193
194
  * An image pinned to the top, bottom or outer edge (`.gp-pin`) sits on this
194
195
  * line unless it carries `.gp-flush`. Absent = the same as `safe`.
195
196
  */
196
197
  artSafe?: number;
198
+ /**
199
+ * The binding allowance (pt): how far content stays from the spine — the
200
+ * binding edge, the left of a right-hand page and the right of a left-hand
201
+ * one — measured from the trim. It stands in for `safe` at the binding
202
+ * edge, for text and for art alike, so a book that keeps its text further
203
+ * from the spine than the printer's minimum says so here, not in its CSS
204
+ * (the Field Guide keeps 0.75in = 54 where DriveThruRPG asks for 0.5in).
205
+ * An image pinned to the binding edge sits on it. The other edges keep
206
+ * `safe` and `artSafe`. Absent = the same as `safe`; presets leave it unset.
207
+ */
208
+ gutter?: number;
197
209
  };
198
210
  ink?: {
199
211
  maxTac?: number;
@@ -327,6 +339,8 @@ export interface ResolvedConfig {
327
339
  safe?: number;
328
340
  /** The art rule's inset from the trim in pt, top/bottom/outer edge (see the manifest field); absent = same as `safe`. */
329
341
  artSafe?: number;
342
+ /** The binding edge's inset from the trim in pt, text and art alike (see the manifest field); absent = same as `safe`. */
343
+ gutter?: number;
330
344
  };
331
345
  ink: {
332
346
  maxTac: number;
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  executeAndReport
3
- } from "./cli-0ey902sx.js";
3
+ } from "./cli-4d25xvs2.js";
4
4
  import {
5
5
  log
6
- } from "./cli-h4tfmpg3.js";
6
+ } from "./cli-tsdrvrff.js";
7
7
  import {
8
8
  UsageError,
9
9
  rejectExtraPositionals,
10
10
  rejectUnknownFlags
11
- } from "./cli-fmksazej.js";
11
+ } from "./cli-gxqxn3sf.js";
12
12
  import"./cli-ht1bcqdd.js";
13
- import"./cli-61vd4ews.js";
13
+ import"./cli-cyej3p9v.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.4",
3
+ "version": "0.11.20-beta.1",
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",