gutterpress 0.11.17-alpha.5 → 0.11.17-alpha.6

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 (27) hide show
  1. package/dist/api/index.js +2 -2
  2. package/dist/{build-jr9mqwgr.js → build-yswp3npj.js} +4 -4
  3. package/dist/{cli-5sg6hcqa.js → cli-0wtnw98y.js} +1 -1
  4. package/dist/{cli-82tvm0sg.js → cli-an3pxzzk.js} +77 -110
  5. package/dist/{cli-2xpwjanr.js → cli-jjbeqx94.js} +1 -1
  6. package/dist/{cli-c3enmb58.js → cli-ycbg7mxk.js} +4 -4
  7. package/dist/cli.js +12 -12
  8. package/dist/{components-dhgv2sm7.yaml → components-zzqdpt3m.yaml} +1 -1
  9. package/dist/{doctor-zxk3pamr.js → doctor-xjgxr88y.js} +2 -2
  10. package/dist/{engine-eactk9nn.js → engine-5d1rpxxq.js} +2 -2
  11. package/dist/{engine-63r7pxsy.js → engine-yf2w9hj7.js} +1 -1
  12. package/dist/{ext-4vapkh0p.js → ext-n664e0ka.js} +4 -4
  13. package/dist/{gutterpress-viewer-1n793gwd.js → gutterpress-viewer-ez7k4ha7.js} +19 -1
  14. package/dist/{index-mb8mzwye.js → index-09h3n36d.js} +2 -2
  15. package/dist/{index-5ez30g3a.js → index-2vd5b3vy.js} +76 -109
  16. package/dist/{index-5gp6mkmf.js → index-vmcfahe2.js} +3 -3
  17. package/dist/index.js +3 -3
  18. package/dist/lib/markdown/gutterpress-css.d.ts +1 -1
  19. package/dist/lib/markdown/markers.d.ts +14 -6
  20. package/dist/{new-hzs75cmd.js → new-8xwac8nd.js} +4 -4
  21. package/dist/{page-templates-7md4ev7d.css → page-templates-tx9ryt19.css} +4 -11
  22. package/dist/{preflight-4k8dtsfs.js → preflight-62c2r37w.js} +4 -4
  23. package/dist/{preview-whg4rwg6.js → preview-qdby30fx.js} +4 -4
  24. package/dist/{publish-4vtd7qve.js → publish-v9tdq7kc.js} +4 -4
  25. package/dist/render.js +73 -106
  26. package/dist/{validate-c32mm2bh.js → validate-9nszbckg.js} +4 -4
  27. package/package.json +1 -1
@@ -9,7 +9,7 @@ import {
9
9
  getAssetsDir,
10
10
  lastPageMode,
11
11
  parseSize
12
- } from "./index-5gp6mkmf.js";
12
+ } from "./index-vmcfahe2.js";
13
13
  import {
14
14
  INSTALL_HINTS,
15
15
  execCapture,
@@ -1129,6 +1129,16 @@ function resolveContainerShape(name, pluginName, decl) {
1129
1129
  ...check
1130
1130
  };
1131
1131
  }
1132
+ function presetVariantOf(name, pluginName, rawDecl) {
1133
+ const preset = rawDecl.preset !== undefined ? rawDecl.preset : {};
1134
+ if (typeof preset !== "object" || preset === null || Array.isArray(preset)) {
1135
+ throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`preset\` that is not a plain object.`);
1136
+ }
1137
+ if (preset.variant !== undefined && typeof preset.variant !== "string") {
1138
+ throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`preset.variant\` that is not a string.`);
1139
+ }
1140
+ return preset.variant;
1141
+ }
1132
1142
  function resolveMarkerDeclaration(name, rawDecl, rawRegistry, originOf) {
1133
1143
  const pluginName = originOf.get(name);
1134
1144
  if (typeof rawDecl !== "object" || rawDecl === null || Array.isArray(rawDecl)) {
@@ -1157,22 +1167,22 @@ function resolveMarkerDeclaration(name, rawDecl, rawRegistry, originOf) {
1157
1167
  if (targetRaw.alias !== undefined) {
1158
1168
  throw new Error(`Plugin "${pluginName}"'s marker "@${name}" aliases "@${rawDecl.alias}", which is itself an alias. ` + `Chained aliases are not supported — alias the base marker directly.`);
1159
1169
  }
1160
- const preset = rawDecl.preset !== undefined ? rawDecl.preset : {};
1161
- if (typeof preset !== "object" || preset === null || Array.isArray(preset)) {
1162
- throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`preset\` that is not a plain object.`);
1163
- }
1164
- if (preset.variant !== undefined && typeof preset.variant !== "string") {
1165
- throw new Error(`Plugin "${pluginName}"'s marker "@${name}" has a \`preset.variant\` that is not a string.`);
1166
- }
1167
1170
  const container = resolveContainerShape(rawDecl.alias, originOf.get(rawDecl.alias), targetRaw);
1168
- return { name, baseKind: rawDecl.alias, presetVariant: preset.variant, ...container };
1171
+ return { name, baseKind: rawDecl.alias, presetVariant: presetVariantOf(name, pluginName, rawDecl), ...container };
1169
1172
  }
1170
- return { name, baseKind: name, ...resolveContainerShape(name, pluginName, rawDecl) };
1173
+ const presetVariant = presetVariantOf(name, pluginName, rawDecl);
1174
+ return {
1175
+ name,
1176
+ baseKind: name,
1177
+ ...presetVariant !== undefined ? { presetVariant } : {},
1178
+ ...resolveContainerShape(name, pluginName, rawDecl)
1179
+ };
1171
1180
  }
1172
1181
  var CORE_MARKER_OWNER = "Gutterpress";
1173
1182
  var CORE_DECLARED_MARKERS = {
1174
1183
  columns: {
1175
1184
  class: "gp-columns",
1185
+ preset: { variant: "2" },
1176
1186
  variants: { 1: "gp-columns-1", 2: "gp-columns-2", 3: "gp-columns-3", 4: "gp-columns-4", 5: "gp-columns-5" }
1177
1187
  }
1178
1188
  };
@@ -1348,20 +1358,35 @@ function plugin(md, pluginOptions = {}) {
1348
1358
  const at = declaredFrameIndex(kind);
1349
1359
  if (at === -1)
1350
1360
  return false;
1351
- while (declaredFrames.length > at) {
1352
- const frame = declaredFrames.pop();
1353
- out.push(new state.Token("layout_component_close", frame.tag, -1));
1354
- }
1361
+ drainDeclaredFrames(at);
1355
1362
  return true;
1356
1363
  }
1357
- function drainDeclaredFrames() {
1358
- while (declaredFrames.length) {
1364
+ function sectionAbove(length) {
1365
+ const section = stack.get("section");
1366
+ return !!section && section.inColumns && declaredFrameIndex("columns") === length - 1;
1367
+ }
1368
+ function sectionHostDepth() {
1369
+ const section = stack.get("section");
1370
+ if (section && !section.inColumns)
1371
+ return 0;
1372
+ return declaredFrameIndex("columns") + 1;
1373
+ }
1374
+ function drainDeclaredFrames(keep = 0) {
1375
+ while (declaredFrames.length > keep) {
1376
+ if (sectionAbove(declaredFrames.length)) {
1377
+ stack.close("section");
1378
+ continue;
1379
+ }
1359
1380
  const frame = declaredFrames.pop();
1360
1381
  out.push(new state.Token("layout_component_close", frame.tag, -1));
1361
1382
  }
1362
1383
  }
1363
1384
  function drainDeclaredFramesAtEof() {
1364
1385
  while (declaredFrames.length) {
1386
+ if (sectionAbove(declaredFrames.length)) {
1387
+ stack.close("section");
1388
+ continue;
1389
+ }
1365
1390
  const frame = declaredFrames.pop();
1366
1391
  if (!frame.decl.autoCloseAtEof) {
1367
1392
  warn(state.env, frame.line || 0, "declared_marker_eof_close", `An open @${frame.kind} reached end-of-document; closing it automatically. Add ` + `@end-${frame.kind} to close it explicitly, or declare autoCloseAt: ["eof"] on this ` + `marker if running to end-of-document is expected.`, null);
@@ -1505,7 +1530,7 @@ function plugin(md, pluginOptions = {}) {
1505
1530
  __component: { component: meta.kind, kind: decl.baseKind, variant, attrs }
1506
1531
  };
1507
1532
  }
1508
- function openSection(meta) {
1533
+ function openSection(meta, inColumns = false) {
1509
1534
  const t = new state.Token("layout_section_open", "div", 1);
1510
1535
  const component = meta.__component || null;
1511
1536
  addClasses(t, "section", meta.attrs && meta.attrs.class ? meta.attrs.class : "");
@@ -1518,25 +1543,11 @@ function plugin(md, pluginOptions = {}) {
1518
1543
  kind: "section",
1519
1544
  classes: meta.attrs && meta.attrs.class || "",
1520
1545
  sawContent: false,
1546
+ ...inColumns ? { inColumns: true } : {},
1521
1547
  meta: { name: meta.name || null, attrs: { ...meta.attrs || {} }, component },
1522
1548
  openToken: t
1523
1549
  }, t);
1524
1550
  }
1525
- function breakHost() {
1526
- return stack.get("section") || stack.get("page") || stack.get("spread");
1527
- }
1528
- function gridClassOf(frame) {
1529
- if (!frame || !frame.classes)
1530
- return null;
1531
- return frame.classes.split(/\s+/).find((c) => c.startsWith("gp-grid-")) || null;
1532
- }
1533
- function warnBreakInsideGrid(kind, line, meta) {
1534
- const host = breakHost();
1535
- const gridClass = gridClassOf(host);
1536
- if (!gridClass)
1537
- return;
1538
- warn(state.env, line, "break_inside_grid", `@${kind} inside a grid container: the enclosing @${host.kind} carries .${gridClass}, so this break's <div> becomes a grid ITEM — it takes a cell of its own, corrupts the grid's placement, and print and the live preview then disagree about which page the content after it lands on. Move the break outside the grid @${host.kind}, or remove it — grid rows already flow and fragment across pages on their own.`, meta);
1539
- }
1540
1551
  function warnIfEmptyDecoratedSection(closingKind, closingLine) {
1541
1552
  const sec = stack.get("section");
1542
1553
  if (!sec || sec.sawContent)
@@ -1601,14 +1612,15 @@ function plugin(md, pluginOptions = {}) {
1601
1612
  continue;
1602
1613
  }
1603
1614
  if (kind === "section") {
1604
- drainDeclaredFrames();
1615
+ const keep = sectionHostDepth();
1616
+ drainDeclaredFrames(keep);
1605
1617
  warnIfEmptyDecoratedSection("section", line);
1606
1618
  stack.close("section");
1607
1619
  const spread = stack.get("spread");
1608
1620
  if (spread && spread.noPagesYet) {
1609
1621
  warn(state.env, line, "spread_without_pages", "@section inside a spread without an explicit @page. Allowed (adhoc spread), but explicit @page markers give stronger control.", meta);
1610
1622
  }
1611
- openSection(meta);
1623
+ openSection(meta, keep > 0);
1612
1624
  continue;
1613
1625
  }
1614
1626
  if (kind === "continue") {
@@ -1617,7 +1629,8 @@ function plugin(md, pluginOptions = {}) {
1617
1629
  warn(state.env, line, "continue_without_section", "@continue used without an open @section; ignoring marker.", meta);
1618
1630
  continue;
1619
1631
  }
1620
- drainDeclaredFrames();
1632
+ const keep = sectionHostDepth();
1633
+ drainDeclaredFrames(keep);
1621
1634
  const contMeta = {
1622
1635
  name: section.meta.name,
1623
1636
  attrs: { ...section.meta.attrs || {} }
@@ -1632,11 +1645,10 @@ function plugin(md, pluginOptions = {}) {
1632
1645
  cls.push("gp-continued");
1633
1646
  contMeta.attrs.class = cls.join(" ");
1634
1647
  stack.close("section");
1635
- openSection(contMeta);
1648
+ openSection(contMeta, keep > 0);
1636
1649
  continue;
1637
1650
  }
1638
1651
  if (kind === "page-break") {
1639
- warnBreakInsideGrid("page-break", line, meta);
1640
1652
  const t = new state.Token("layout_page_break", "div", 0);
1641
1653
  t.meta = { line };
1642
1654
  t.attrSet("class", "gp-page-break");
@@ -1645,7 +1657,6 @@ function plugin(md, pluginOptions = {}) {
1645
1657
  continue;
1646
1658
  }
1647
1659
  if (kind === "column-break") {
1648
- warnBreakInsideGrid("column-break", line, meta);
1649
1660
  const t = new state.Token("layout_column_break", "div", 0);
1650
1661
  t.meta = { line };
1651
1662
  t.attrSet("class", "gp-column-break");
@@ -1654,7 +1665,7 @@ function plugin(md, pluginOptions = {}) {
1654
1665
  continue;
1655
1666
  }
1656
1667
  if (kind === "end-section") {
1657
- drainDeclaredFrames();
1668
+ drainDeclaredFrames(sectionHostDepth());
1658
1669
  warnIfEmptyDecoratedSection("end-section", line);
1659
1670
  stack.close("section");
1660
1671
  continue;
@@ -1752,11 +1763,20 @@ body { margin: 0; }
1752
1763
  .gp-column-break { break-after: column; height: 0; font-size: 0; line-height: 0; visibility: hidden; }
1753
1764
 
1754
1765
  :where(h1,h2,h3,h4,h5,h6) { break-after: avoid; }
1766
+ /* A lead-in paragraph ("**ROLL A DIE!**" above a roll table, "You will need:"
1767
+ above a list) stays with the table or list it introduces, the way a heading
1768
+ stays with what follows it -- otherwise the label can end a column or page
1769
+ with its table starting on the next (#370). Standard break-after; the
1770
+ :has() selector is Chromium's own. :where() keeps specificity at zero so an
1771
+ author rule such as p { break-after: auto } wins. Deliberately table/ul/ol only,
1772
+ also when a @section wraps it (the second rule): the lead-in then keeps with
1773
+ the section that opens with the table or list. */
1774
+ :where(p):has(+ :where(table, ul, ol)) { break-after: avoid; }
1775
+ :where(p):has(+ :where(.section) > :where(table, ul, ol):first-child) { break-after: avoid; }
1755
1776
  :where(img, svg, video) { max-width: 100%; }
1756
1777
  :where(p > img:only-child, figure > img) { width: fit-content; max-width: 100%; height: auto; vertical-align: bottom; }
1757
1778
  :where(p) > :where(img:not([class])) { max-height: calc(var(--gp-content-h) - 4px); object-fit: contain; }
1758
1779
  :where(figure) { break-inside: avoid; }
1759
- :where(.section, figure) > :where(:first-child) { break-before: avoid; }
1760
1780
 
1761
1781
  `;
1762
1782
 
@@ -1816,26 +1836,24 @@ var GUTTERPRESS_CSS = `
1816
1836
  .gp-tight { --gp-gap: 0.5em; }
1817
1837
  .gp-loose { --gp-gap: 2em; }
1818
1838
 
1819
- /* column runs — plain CSS Multi-column, exposed as author vocabulary so
1820
- "put this in two columns" does not require borrowing a styled container
1821
- from the book's own component layer. That borrowing is what this exists
1822
- to prevent: a book whose theme paints .section chrome by default gives
1823
- every author who opens a section just to start a column run a panel they
1824
- did not ask for, and the book then needs a reset rule to take it back.
1825
- With a neutral primitive the author opts into columns and nothing else.
1839
+ /* column runs — plain CSS Multi-column, emitted by the core @columns marker
1840
+ (markers.js): @columns [1-5] ... @end-columns is the ONE way to lay
1841
+ content out in columns, and @column-break moves to the next column.
1842
+ The classes below are the CSS that marker emits, not author vocabulary.
1826
1843
 
1827
1844
  Permanent vocabulary, not a shim: Chromium implements multicol natively
1828
1845
  and these rules are the standard properties verbatim, so there is no
1829
1846
  spec gap here to remove later. Deliberately minimal — column-fill is
1830
- NOT set, because the correct value depends on whether the run fragments
1831
- across pages (auto packs each page's columns; the CSS initial balance is
1832
- right for a run that fits on one page) and only the author knows which.
1833
- --gp-column-gap is author-settable.
1834
- .gp-columns is what the core @columns marker emits (markers.js); on its
1835
- own it is two columns, and @columns 1 to 5 add the matching count, which
1836
- wins because it comes later at the same specificity. */
1837
- .gp-columns, .gp-columns-1, .gp-columns-2, .gp-columns-3, .gp-columns-4, .gp-columns-5 {
1847
+ NOT set (the CSS initial balance applies).
1848
+ Themes style a run through two tokens instead of writing column rules:
1849
+ --gp-column-gap default 1.5em
1850
+ --gp-column-rule default none (any column-rule shorthand value)
1851
+ Both hooks sit at zero specificity (:where), so any author rule wins.
1852
+ .gp-columns is two columns on its own; a count class (bare @columns
1853
+ emits gp-columns-2) wins because it comes later at the same specificity. */
1854
+ :where(.gp-columns, .gp-columns-1, .gp-columns-2, .gp-columns-3, .gp-columns-4, .gp-columns-5) {
1838
1855
  column-gap: var(--gp-column-gap, 1.5em);
1856
+ column-rule: var(--gp-column-rule, none);
1839
1857
  }
1840
1858
  .gp-columns { columns: 2; }
1841
1859
  .gp-columns-1 { columns: 1; }
@@ -1844,54 +1862,8 @@ var GUTTERPRESS_CSS = `
1844
1862
  .gp-columns-4 { columns: 4; }
1845
1863
  .gp-columns-5 { columns: 5; }
1846
1864
 
1847
- /* the per-shape decisions the paragraph above deliberately leaves to the
1848
- author, named instead of left as raw CSS every book was reinventing
1849
- (CLAUDE.md §0: "behavior broadly useful to non-technical authors belongs
1850
- in core").
1851
- Permanent vocabulary, standard properties verbatim — same rationale as
1852
- the column/grid runs above. One name each, no aliases:
1853
- .gp-columns-all column-span: all a heading or block that
1854
- spans every column in the
1855
- run it sits inside.
1856
- .gp-columns-flow column-fill: auto a run that FRAGMENTS across
1857
- pages — every page's
1858
- columns fill instead of
1859
- only the last one
1860
- balancing (the dead-column
1861
- collapse the build's
1862
- engine.multicol.dead-column
1863
- warning names this fix
1864
- for).
1865
- .gp-columns-balanced column-fill: balance a run that fits on ONE
1866
- page (the CSS initial
1867
- value — ragged columns
1868
- would be wrong here). */
1869
- .gp-columns-all { column-span: all; }
1870
- .gp-columns-flow { column-fill: auto; }
1871
- .gp-columns-balanced { column-fill: balance; }
1872
-
1873
- /* grid runs — the SLOTTED counterpart to the column runs above. Grid places
1874
- each child into the next cell, across then down (deterministic slots: card
1875
- layouts, stat blocks, image-plus-caption pairs); columns FLOW one text run
1876
- down then across. Same neutral-primitive rationale as .gp-columns-*, and
1877
- permanent vocabulary for the same reason: standard CSS Grid verbatim, no
1878
- spec gap to remove later. MEASURED (Chromium 151, gp-grid evidence pack):
1879
- grid rows fragment across sheets with EXACT print/viewer parity — 2- and
1880
- 3-col, unequal item heights, mid-row cuts, multi-sheet overflow,
1881
- break-inside:avoid, gap geometry — so a grid taller than the page is safe,
1882
- no fit-one-page constraint. Two things to know, not fix:
1883
- - on a min-height page root (MARKER_CSS), default align-content
1884
- stretches rows apart to fill the page — identically in both engines.
1885
- Authors wanting packed rows set align-content: start.
1886
- - a @page-break / @column-break marker DIRECTLY inside a grid container
1887
- becomes a grid item and corrupts placement (the one measured parity
1888
- break); markers.js diagnoses it (break_inside_grid).
1889
- --gp-grid-gap is author-settable. */
1890
- .gp-grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--gp-grid-gap, 1.5em); }
1891
- .gp-grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gp-grid-gap, 1.5em); }
1892
-
1893
1865
  /* fragmentation controls — generic pagination utilities for any element,
1894
- independent of the column/grid vocabulary above (a book reached for
1866
+ independent of the column vocabulary above (a book reached for
1895
1867
  these just as often outside a multicol run: keeping a card whole,
1896
1868
  forcing a section to start a fresh page). Standard properties only —
1897
1869
  this project is Chromium-only (CLAUDE.md), so there are no legacy
@@ -2049,13 +2021,8 @@ var GP_CLASSES = new Set([
2049
2021
  "gp-columns-3",
2050
2022
  "gp-columns-4",
2051
2023
  "gp-columns-5",
2052
- "gp-columns-all",
2053
- "gp-columns-flow",
2054
- "gp-columns-balanced",
2055
2024
  "gp-no-break",
2056
2025
  "gp-break-before",
2057
- "gp-grid-2",
2058
- "gp-grid-3",
2059
2026
  "gp-shape",
2060
2027
  "gp-pin",
2061
2028
  "gp-top",
@@ -5674,7 +5641,7 @@ import git from "isomorphic-git";
5674
5641
  // package.json
5675
5642
  var package_default = {
5676
5643
  name: "gutterpress",
5677
- version: "0.11.17-alpha.5",
5644
+ version: "0.11.17-alpha.6",
5678
5645
  description: "Markdown-to-PDF converter for professional print layout using a native Chromium print engine and Ghostscript.",
5679
5646
  author: "itlackey",
5680
5647
  license: "MPL-2.0",
@@ -10230,7 +10197,7 @@ class PdfOutput {
10230
10197
  const rawPdf = pdfxMode ? path13.join(stage, "raw.pdf") : path13.resolve(pdfFile);
10231
10198
  await fsp2.mkdir(path13.dirname(path13.resolve(pdfFile)), { recursive: true });
10232
10199
  log.info("Rendering HTML to PDF via the Gutterpress engine (native Chromium pagination)");
10233
- const { buildNativePdf } = await import("./engine-eactk9nn.js");
10200
+ const { buildNativePdf } = await import("./engine-5d1rpxxq.js");
10234
10201
  const engineDiagnostics = await buildNativePdf(htmlFile, rawPdf, {
10235
10202
  title: config.title,
10236
10203
  author: config.authors.length > 0 ? config.authors.join(", ") : undefined,
@@ -163,7 +163,7 @@ var preview_shell_default = "./preview-shell-xwn84kqr.js";
163
163
  var CGATS21_CRPC1_default = "./CGATS21_CRPC1-g0e3k7kr.icc";
164
164
 
165
165
  // src/assets/engine/gutterpress-viewer.js
166
- var gutterpress_viewer_default = "./gutterpress-viewer-1n793gwd.js";
166
+ var gutterpress_viewer_default = "./gutterpress-viewer-ez7k4ha7.js";
167
167
 
168
168
  // src/assets/engine/gutterpress-agent.js
169
169
  var gutterpress_agent_default = "./gutterpress-agent-cazqstr1.js";
@@ -217,7 +217,7 @@ var package_default2 = "./package-aenwjew3.json";
217
217
  var README_default2 = "./README-3nj919rm.md";
218
218
 
219
219
  // src/assets/extension-templates/theme/components.yaml
220
- var components_default = "./components-dhgv2sm7.yaml";
220
+ var components_default = "./components-zzqdpt3m.yaml";
221
221
 
222
222
  // src/assets/extension-templates/theme/snippets/callout.md
223
223
  var callout_default = "./callout-hv56m16h.md";
@@ -232,7 +232,7 @@ var base_default = "./base-ypqh9n7f.css";
232
232
  var components_default2 = "./components-aam0fe2z.css";
233
233
 
234
234
  // src/assets/extension-templates/theme/styles/page-templates.css
235
- var page_templates_default = "./page-templates-7md4ev7d.css";
235
+ var page_templates_default = "./page-templates-tx9ryt19.css";
236
236
 
237
237
  // src/assets/extension-templates/theme/styles/page-rules.css
238
238
  var page_rules_default = "./page-rules-nsj3aw42.css";
package/dist/index.js CHANGED
@@ -152,14 +152,14 @@ import {
152
152
  testRemoteAccess,
153
153
  updateExtensions,
154
154
  validateProjectExtensions
155
- } from "./index-5ez30g3a.js";
155
+ } from "./index-2vd5b3vy.js";
156
156
  import"./index-ht1bcqdd.js";
157
157
  import {
158
158
  BUILD_DIAGNOSTIC_CODES
159
- } from "./index-mb8mzwye.js";
159
+ } from "./index-09h3n36d.js";
160
160
  import {
161
161
  BuildError
162
- } from "./index-5gp6mkmf.js";
162
+ } from "./index-vmcfahe2.js";
163
163
  import {
164
164
  DEFAULT_PRINT_OPTS,
165
165
  assertMilestone,
@@ -133,7 +133,7 @@
133
133
  * carrying gp-left could start shifting; that is standards-tracking per
134
134
  * CLAUDE.md ("Chrome wins once it ships"), not a bug in the author's book.
135
135
  */
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, exposed as author vocabulary so\n \"put this in two columns\" does not require borrowing a styled container\n from the book's own component layer. That borrowing is what this exists\n to prevent: a book whose theme paints .section chrome by default gives\n every author who opens a section just to start a column run a panel they\n did not ask for, and the book then needs a reset rule to take it back.\n With a neutral primitive the author opts into columns and nothing else.\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, because the correct value depends on whether the run fragments\n across pages (auto packs each page's columns; the CSS initial balance is\n right for a run that fits on one page) and only the author knows which.\n --gp-column-gap is author-settable.\n .gp-columns is what the core @columns marker emits (markers.js); on its\n own it is two columns, and @columns 1 to 5 add the matching count, which\n wins because it comes later at the same specificity. */\n.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}\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/* the per-shape decisions the paragraph above deliberately leaves to the\n author, named instead of left as raw CSS every book was reinventing\n (CLAUDE.md \u00A70: \"behavior broadly useful to non-technical authors belongs\n in core\").\n Permanent vocabulary, standard properties verbatim \u2014 same rationale as\n the column/grid runs above. One name each, no aliases:\n .gp-columns-all column-span: all a heading or block that\n spans every column in the\n run it sits inside.\n .gp-columns-flow column-fill: auto a run that FRAGMENTS across\n pages \u2014 every page's\n columns fill instead of\n only the last one\n balancing (the dead-column\n collapse the build's\n engine.multicol.dead-column\n warning names this fix\n for).\n .gp-columns-balanced column-fill: balance a run that fits on ONE\n page (the CSS initial\n value \u2014 ragged columns\n would be wrong here). */\n.gp-columns-all { column-span: all; }\n.gp-columns-flow { column-fill: auto; }\n.gp-columns-balanced { column-fill: balance; }\n\n/* grid runs \u2014 the SLOTTED counterpart to the column runs above. Grid places\n each child into the next cell, across then down (deterministic slots: card\n layouts, stat blocks, image-plus-caption pairs); columns FLOW one text run\n down then across. Same neutral-primitive rationale as .gp-columns-*, and\n permanent vocabulary for the same reason: standard CSS Grid verbatim, no\n spec gap to remove later. MEASURED (Chromium 151, gp-grid evidence pack):\n grid rows fragment across sheets with EXACT print/viewer parity \u2014 2- and\n 3-col, unequal item heights, mid-row cuts, multi-sheet overflow,\n break-inside:avoid, gap geometry \u2014 so a grid taller than the page is safe,\n no fit-one-page constraint. Two things to know, not fix:\n - on a min-height page root (MARKER_CSS), default align-content\n stretches rows apart to fill the page \u2014 identically in both engines.\n Authors wanting packed rows set align-content: start.\n - a @page-break / @column-break marker DIRECTLY inside a grid container\n becomes a grid item and corrupts placement (the one measured parity\n break); markers.js diagnoses it (break_inside_grid).\n --gp-grid-gap is author-settable. */\n.gp-grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--gp-grid-gap, 1.5em); }\n.gp-grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gp-grid-gap, 1.5em); }\n\n/* fragmentation controls \u2014 generic pagination utilities for any element,\n independent of the column/grid 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";
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";
137
137
  /**
138
138
  * GP_CLASSES — every class an author may legitimately write with a `gp-`
139
139
  * prefix: the `.gp-*` selectors in `GUTTERPRESS_CSS` above, the two
@@ -91,11 +91,20 @@ export default function plugin(md: any, pluginOptions?: {}): void;
91
91
  * pinned foot up by less than a point and absorbs sub-pixel rounding in the
92
92
  * published value. Do not "clean it up" — the sheet it costs is silent.
93
93
  *
94
- * The break/orphan rules below (`break-after` on headings, image sizing,
95
- * first-child glue) are all `:where()` so they carry zero specificity —
94
+ * The break/orphan rules below (`break-after` on headings, image sizing)
95
+ * are all `:where()` so they carry zero specificity —
96
96
  * author CSS at any specificity wins outright, reusing this same
97
97
  * after-author injection point.
98
98
  *
99
+ * There is deliberately NO `.section > :first-child { break-before: avoid }`
100
+ * "glue". A first child's break-before propagates to its parent's start
101
+ * edge, so it made the break BEFORE every section `avoid`: a section after
102
+ * other content could not move whole to the next page, and Chromium split it
103
+ * instead, ignoring its own `break-inside: avoid`. Measured with and without
104
+ * (marker-css-section-break.test.ts): it never prevented a stranded top edge
105
+ * or heading. Its one useful side effect, a lead-in paragraph travelling with
106
+ * a following @section that wraps a table, is the explicit rule below.
107
+ *
99
108
  * `vertical-align: bottom` on a lone image is a print-correctness rule, not
100
109
  * cosmetics. An `<img>` in a `<p>` is inline-level, so its line box adds
101
110
  * half-leading/descender space UNDER the image: an image sized to exactly the
@@ -158,8 +167,7 @@ export default function plugin(md: any, pluginOptions?: {}): void;
158
167
  * break-inside rule, at ANY specificity, still wins outright.
159
168
  *
160
169
  * The companion "spanner + unbreakable box" glue pattern (`column-span: all`
161
- * followed by a `break-inside: avoid` box, native-engine-styling-guide.md
162
- * §5) was measured for this same adoption pass and DELIBERATELY NOT
170
+ * followed by a `break-inside: avoid` box) was measured for this same adoption pass and DELIBERATELY NOT
163
171
  * adopted: a `:where()`-zero-specificity override can never win against
164
172
  * whatever real-specificity rule gave the box its `break-inside: avoid` in
165
173
  * the first place (a synthetic 2-column fixture with the candidate
@@ -169,7 +177,7 @@ export default function plugin(md: any, pluginOptions?: {}): void;
169
177
  * a context-blind version of the same idea is actively wrong for at least
170
178
  * one real, shipped case (a short, non-fragmenting instance that needed its
171
179
  * `avoid` restored after a general fragmenting-context rule took it away).
172
- * It stays an author remedy, named in the styling guide (`.gp-columns-all`).
180
+ * Core ships no spanner class: a spanning heading is the end of one @columns run and the start of the next.
173
181
  *
174
182
  */
175
- export const MARKER_CSS: "\n/* The UA default of 8px body margin is a screen affordance with no meaning\n in paged media, and engines disagree about it: a polyfill that treats the\n page div as the page box drops it, native print keeps it. Left in place it\n insets EVERY page's content by 8px per side, and -- measured, 300dpi,\n 6x4in sheet -- it is what stops a full-width block from reaching the\n paper: it lands at 0.080..5.917in of a 6in sheet instead of\n 0.000..6.000in, because width:100% resolves against the BODY content box,\n not the page's. Zeroing it here (first in the cascade) makes the two\n agree. Authors who want a body margin still just declare one. */\nbody { margin: 0; }\n\n.gp-page-break { break-before: page; }\n.page { break-before: page; }\n.spread { break-before: page; }\n:where(.page, .spread) { position: relative; display: flow-root; box-sizing: border-box; min-height: calc(var(--gp-content-h, 1px) - 1px); }\n.gp-column-break { break-after: column; height: 0; font-size: 0; line-height: 0; visibility: hidden; }\n\n:where(h1,h2,h3,h4,h5,h6) { break-after: avoid; }\n:where(img, svg, video) { max-width: 100%; }\n:where(p > img:only-child, figure > img) { width: fit-content; max-width: 100%; height: auto; vertical-align: bottom; }\n:where(p) > :where(img:not([class])) { max-height: calc(var(--gp-content-h) - 4px); object-fit: contain; }\n:where(figure) { break-inside: avoid; }\n:where(.section, figure) > :where(:first-child) { break-before: avoid; }\n\n";
183
+ export const MARKER_CSS: "\n/* The UA default of 8px body margin is a screen affordance with no meaning\n in paged media, and engines disagree about it: a polyfill that treats the\n page div as the page box drops it, native print keeps it. Left in place it\n insets EVERY page's content by 8px per side, and -- measured, 300dpi,\n 6x4in sheet -- it is what stops a full-width block from reaching the\n paper: it lands at 0.080..5.917in of a 6in sheet instead of\n 0.000..6.000in, because width:100% resolves against the BODY content box,\n not the page's. Zeroing it here (first in the cascade) makes the two\n agree. Authors who want a body margin still just declare one. */\nbody { margin: 0; }\n\n.gp-page-break { break-before: page; }\n.page { break-before: page; }\n.spread { break-before: page; }\n:where(.page, .spread) { position: relative; display: flow-root; box-sizing: border-box; min-height: calc(var(--gp-content-h, 1px) - 1px); }\n.gp-column-break { break-after: column; height: 0; font-size: 0; line-height: 0; visibility: hidden; }\n\n:where(h1,h2,h3,h4,h5,h6) { break-after: avoid; }\n/* A lead-in paragraph (\"**ROLL A DIE!**\" above a roll table, \"You will need:\"\n above a list) stays with the table or list it introduces, the way a heading\n stays with what follows it -- otherwise the label can end a column or page\n with its table starting on the next (#370). Standard break-after; the\n :has() selector is Chromium's own. :where() keeps specificity at zero so an\n author rule such as p { break-after: auto } wins. Deliberately table/ul/ol only,\n also when a @section wraps it (the second rule): the lead-in then keeps with\n the section that opens with the table or list. */\n:where(p):has(+ :where(table, ul, ol)) { break-after: avoid; }\n:where(p):has(+ :where(.section) > :where(table, ul, ol):first-child) { break-after: avoid; }\n:where(img, svg, video) { max-width: 100%; }\n:where(p > img:only-child, figure > img) { width: fit-content; max-width: 100%; height: auto; vertical-align: bottom; }\n:where(p) > :where(img:not([class])) { max-height: calc(var(--gp-content-h) - 4px); object-fit: contain; }\n:where(figure) { break-inside: avoid; }\n\n";
@@ -7,17 +7,17 @@ import {
7
7
  TARGET_IDS,
8
8
  scaffoldExtension,
9
9
  scaffoldProject
10
- } from "./cli-82tvm0sg.js";
10
+ } from "./cli-an3pxzzk.js";
11
11
  import {
12
12
  resolveGhostscript
13
- } from "./cli-5sg6hcqa.js";
13
+ } from "./cli-0wtnw98y.js";
14
14
  import {
15
15
  UsageError,
16
16
  rejectExtraPositionals,
17
17
  rejectUnknownFlags
18
- } from "./cli-2xpwjanr.js";
18
+ } from "./cli-jjbeqx94.js";
19
19
  import"./cli-ht1bcqdd.js";
20
- import"./cli-c3enmb58.js";
20
+ import"./cli-ycbg7mxk.js";
21
21
  import {
22
22
  EXIT_CODES
23
23
  } from "./cli-46ycxe6r.js";
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * OWNS
5
5
  * The layout of a `@page .name` wrapper — how content is arranged WITHIN
6
- * one page of the book. Columns, full-bleed art, a centred opener.
6
+ * one page of the book. Full-bleed art, a centred opener.
7
7
  *
8
8
  * Authored as:
9
9
  *
@@ -43,17 +43,10 @@
43
43
  max-width: 24em;
44
44
  }
45
45
 
46
- /* A two-column reference page. Core already ships `.gp-columns-2` as
47
- general author vocabulary — reach for that first, and use a named
48
- template like this only when the page needs more than the columns. */
49
- .page.{{PREFIX}}reference {
50
- columns: 2;
51
- column-gap: var(--{{PREFIX}}space-4);
52
- column-rule: 1px solid var(--{{PREFIX}}rule);
53
- }
54
-
46
+ /* A reference page. Columns are not a template concern: put the content in
47
+ core's `@columns 2` ... `@end-columns` (and `@column-break`) and core owns
48
+ the column geometry. This template only styles what sits around them. */
55
49
  .page.{{PREFIX}}reference h2 {
56
- column-span: all;
57
50
  border-block-end: 1px solid var(--{{PREFIX}}rule);
58
51
  padding-block-end: var(--{{PREFIX}}space-1);
59
52
  }
@@ -2,17 +2,17 @@ import {
2
2
  executeValidation,
3
3
  publishTargetFor,
4
4
  reportMissingTools
5
- } from "./cli-82tvm0sg.js";
5
+ } from "./cli-an3pxzzk.js";
6
6
  import {
7
7
  log
8
- } from "./cli-5sg6hcqa.js";
8
+ } from "./cli-0wtnw98y.js";
9
9
  import {
10
10
  UsageError,
11
11
  rejectExtraPositionals,
12
12
  rejectUnknownFlags
13
- } from "./cli-2xpwjanr.js";
13
+ } from "./cli-jjbeqx94.js";
14
14
  import"./cli-ht1bcqdd.js";
15
- import"./cli-c3enmb58.js";
15
+ import"./cli-ycbg7mxk.js";
16
16
  import {
17
17
  BuildError,
18
18
  EXIT_CODES
@@ -7,10 +7,10 @@ import {
7
7
  runBuild,
8
8
  splitOutPath,
9
9
  startPreviewServer
10
- } from "./cli-82tvm0sg.js";
10
+ } from "./cli-an3pxzzk.js";
11
11
  import {
12
12
  log
13
- } from "./cli-5sg6hcqa.js";
13
+ } from "./cli-0wtnw98y.js";
14
14
  import {
15
15
  UsageError,
16
16
  parseFormat,
@@ -18,9 +18,9 @@ import {
18
18
  rejectExtraPositionals,
19
19
  rejectUnknownFlags,
20
20
  resolvePort
21
- } from "./cli-2xpwjanr.js";
21
+ } from "./cli-jjbeqx94.js";
22
22
  import"./cli-ht1bcqdd.js";
23
- import"./cli-c3enmb58.js";
23
+ import"./cli-ycbg7mxk.js";
24
24
  import {
25
25
  BuildError
26
26
  } from "./cli-46ycxe6r.js";
@@ -11,18 +11,18 @@ import {
11
11
  publishProviderFor,
12
12
  resolvePublishFormat,
13
13
  runPublish
14
- } from "./cli-82tvm0sg.js";
14
+ } from "./cli-an3pxzzk.js";
15
15
  import {
16
16
  FileTokenStore,
17
17
  log
18
- } from "./cli-5sg6hcqa.js";
18
+ } from "./cli-0wtnw98y.js";
19
19
  import {
20
20
  UsageError,
21
21
  rejectExtraPositionals,
22
22
  rejectUnknownFlags
23
- } from "./cli-2xpwjanr.js";
23
+ } from "./cli-jjbeqx94.js";
24
24
  import"./cli-ht1bcqdd.js";
25
- import"./cli-c3enmb58.js";
25
+ import"./cli-ycbg7mxk.js";
26
26
  import {
27
27
  EXIT_CODES
28
28
  } from "./cli-46ycxe6r.js";