docxodus 9.6.0 → 9.8.0

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 (44) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +0 -1
  3. package/dist/editor.bundle.js +462 -173
  4. package/dist/editor.d.ts +4 -4
  5. package/dist/editor.d.ts.map +1 -1
  6. package/dist/editor.js +55 -8
  7. package/dist/editor.js.map +1 -1
  8. package/dist/embed.bundle.js +466 -174
  9. package/dist/embed.d.ts.map +1 -1
  10. package/dist/embed.iife.js +465 -173
  11. package/dist/embed.js +3 -0
  12. package/dist/embed.js.map +1 -1
  13. package/dist/page-geometry.d.ts +50 -3
  14. package/dist/page-geometry.d.ts.map +1 -1
  15. package/dist/page-geometry.js +63 -6
  16. package/dist/page-geometry.js.map +1 -1
  17. package/dist/pagination.bundle.js +253 -68
  18. package/dist/pagination.d.ts +33 -8
  19. package/dist/pagination.d.ts.map +1 -1
  20. package/dist/pagination.js +198 -78
  21. package/dist/pagination.js.map +1 -1
  22. package/dist/ribbon-chrome.d.ts +2 -2
  23. package/dist/ribbon-chrome.d.ts.map +1 -1
  24. package/dist/ribbon-chrome.js +171 -97
  25. package/dist/ribbon-chrome.js.map +1 -1
  26. package/dist/ribbon.d.ts +8 -0
  27. package/dist/ribbon.d.ts.map +1 -1
  28. package/dist/ribbon.js +1 -0
  29. package/dist/ribbon.js.map +1 -1
  30. package/dist/wasm/_framework/Docxodus.wasm +0 -0
  31. package/dist/wasm/_framework/Docxodus.wasm.br +0 -0
  32. package/dist/wasm/_framework/DocxodusWasm.wasm +0 -0
  33. package/dist/wasm/_framework/DocxodusWasm.wasm.br +0 -0
  34. package/dist/wasm/_framework/System.Linq.wasm +0 -0
  35. package/dist/wasm/_framework/System.Linq.wasm.br +0 -0
  36. package/dist/wasm/_framework/System.Private.CoreLib.wasm +0 -0
  37. package/dist/wasm/_framework/System.Private.CoreLib.wasm.br +0 -0
  38. package/dist/wasm/_framework/dotnet.boot.js +6 -6
  39. package/dist/wasm/_framework/dotnet.boot.js.br +0 -0
  40. package/dist/wasm/_framework/dotnet.native.wasm +0 -0
  41. package/dist/wasm/_framework/dotnet.native.wasm.br +0 -0
  42. package/package.json +13 -5
  43. package/dist/wasm/index.html +0 -238
  44. package/dist/wasm/main.js +0 -26
@@ -165,7 +165,7 @@ var DocxodusEditor = (() => {
165
165
  var DEFAULT_PAGE_WIDTH = 612;
166
166
  var DEFAULT_PAGE_HEIGHT = 792;
167
167
  var DEFAULT_MARGIN = 72;
168
- var DEFAULT_HEADER_FOOTER_HEIGHT = 36;
168
+ var DEFAULT_HEADER_FOOTER_DISTANCE = 36;
169
169
  function pxToPt(px) {
170
170
  return px * 0.75;
171
171
  }
@@ -181,8 +181,8 @@ var DocxodusEditor = (() => {
181
181
  const marginRight = parseFloat(section.dataset.marginRight || "") || DEFAULT_MARGIN;
182
182
  const marginBottom = parseFloat(section.dataset.marginBottom || "") || DEFAULT_MARGIN;
183
183
  const marginLeft = parseFloat(section.dataset.marginLeft || "") || DEFAULT_MARGIN;
184
- const headerHeight = parseFloat(section.dataset.headerHeight || "") || DEFAULT_HEADER_FOOTER_HEIGHT;
185
- const footerHeight = parseFloat(section.dataset.footerHeight || "") || DEFAULT_HEADER_FOOTER_HEIGHT;
184
+ const headerDistance = parseFloat(section.dataset.headerHeight || "") || DEFAULT_HEADER_FOOTER_DISTANCE;
185
+ const footerDistance = parseFloat(section.dataset.footerHeight || "") || DEFAULT_HEADER_FOOTER_DISTANCE;
186
186
  return {
187
187
  pageWidth,
188
188
  pageHeight,
@@ -192,8 +192,37 @@ var DocxodusEditor = (() => {
192
192
  marginRight,
193
193
  marginBottom,
194
194
  marginLeft,
195
- headerHeight,
196
- footerHeight
195
+ headerDistance,
196
+ footerDistance,
197
+ headerHeight: headerDistance,
198
+ footerHeight: footerDistance
199
+ };
200
+ }
201
+ var MIN_BODY_BAND_PT = 12;
202
+ function resolvePageBands(dims, headerContentHeight, footerContentHeight) {
203
+ const headerTop = dims.headerDistance;
204
+ const footerBottom = dims.pageHeight - dims.footerDistance;
205
+ const headerReach = headerContentHeight > 0 ? headerTop + headerContentHeight : 0;
206
+ const footerReach = footerContentHeight > 0 ? footerBottom - footerContentHeight : dims.pageHeight;
207
+ let bodyTop = Math.max(dims.marginTop, headerReach);
208
+ let bodyBottom = Math.min(dims.pageHeight - dims.marginBottom, footerReach);
209
+ const shortfall = MIN_BODY_BAND_PT - (bodyBottom - bodyTop);
210
+ if (shortfall > 0) {
211
+ const headerGrowth = bodyTop - dims.marginTop;
212
+ const footerGrowth = dims.pageHeight - dims.marginBottom - bodyBottom;
213
+ const growth = headerGrowth + footerGrowth;
214
+ if (growth > 0) {
215
+ bodyTop -= shortfall * headerGrowth / growth;
216
+ bodyBottom += shortfall * footerGrowth / growth;
217
+ }
218
+ }
219
+ return {
220
+ headerTop,
221
+ headerHeight: Math.max(0, Math.min(headerContentHeight, bodyTop - headerTop)),
222
+ bodyTop,
223
+ bodyHeight: Math.max(0, bodyBottom - bodyTop),
224
+ footerTop: footerBottom - Math.max(0, Math.min(footerContentHeight, footerBottom - bodyBottom)),
225
+ footerHeight: Math.max(0, Math.min(footerContentHeight, footerBottom - bodyBottom))
197
226
  };
198
227
  }
199
228
  function sectionWrappers(root) {
@@ -470,9 +499,9 @@ var DocxodusEditor = (() => {
470
499
  */
471
500
  smallestEffectiveContentHeight(dims, sectionIndex) {
472
501
  return Math.min(
473
- this.getEffectiveHeights(dims, sectionIndex, 1, 1).contentHeight,
474
- this.getEffectiveHeights(dims, sectionIndex, 2, 1).contentHeight,
475
- this.getEffectiveHeights(dims, sectionIndex, 2, 2).contentHeight
502
+ this.getPageBands(dims, sectionIndex, 1, 1).bodyHeight,
503
+ this.getPageBands(dims, sectionIndex, 2, 1).bodyHeight,
504
+ this.getPageBands(dims, sectionIndex, 2, 2).bodyHeight
476
505
  );
477
506
  }
478
507
  /**
@@ -996,7 +1025,7 @@ var DocxodusEditor = (() => {
996
1025
  /**
997
1026
  * Adds footnotes to a page container, including continuation content.
998
1027
  */
999
- addPageFootnotes(pageBox, footnoteIds, dims, footnoteHeight, continuation, partialFootnotes) {
1028
+ addPageFootnotes(pageBox, footnoteIds, dims, bands, footnoteHeight, continuation, partialFootnotes) {
1000
1029
  const hasContinuation = continuation && continuation.remainingElements.length > 0;
1001
1030
  if (footnoteIds.length === 0 && !hasContinuation) {
1002
1031
  return;
@@ -1007,12 +1036,12 @@ var DocxodusEditor = (() => {
1007
1036
  const partialFootnoteIds = new Set(partialFootnotes?.map((p) => p.footnoteId) || []);
1008
1037
  const maxFootnoteHeight = Math.min(
1009
1038
  footnoteHeight,
1010
- dims.contentHeight * MAX_FOOTNOTE_AREA_RATIO
1039
+ bands.bodyHeight * MAX_FOOTNOTE_AREA_RATIO
1011
1040
  );
1012
1041
  const footnotesDiv = document.createElement("div");
1013
1042
  footnotesDiv.className = `${this.cssPrefix}footnotes`;
1014
1043
  footnotesDiv.style.position = "absolute";
1015
- footnotesDiv.style.bottom = `${dims.marginBottom}pt`;
1044
+ footnotesDiv.style.bottom = `${dims.pageHeight - (bands.bodyTop + bands.bodyHeight)}pt`;
1016
1045
  footnotesDiv.style.left = `${dims.marginLeft}pt`;
1017
1046
  footnotesDiv.style.width = `${dims.contentWidth}pt`;
1018
1047
  footnotesDiv.style.boxSizing = "border-box";
@@ -1086,50 +1115,51 @@ var DocxodusEditor = (() => {
1086
1115
  return sectionHf.footerDefault;
1087
1116
  }
1088
1117
  /**
1089
- * Computes effective header, footer, and content heights for a specific page position.
1090
- * Uses pre-measured header/footer heights from the registry.
1091
- * This method is deterministic - same inputs always produce same outputs.
1092
- * This enables lazy loading compatibility since available height can be computed
1093
- * for any page position without knowing the page's content.
1118
+ * The header, body, and footer bands for one page position.
1119
+ *
1120
+ * The single owner of "where does anything sit vertically on this page" — placement in
1121
+ * {@link createPage}, the body budget the flow loop spends, and the note area's anchor all
1122
+ * read it, so those three cannot disagree about where the body ends.
1123
+ *
1124
+ * Deterministic: it depends only on the section's page setup and the registry's pre-measured
1125
+ * story heights, never on the page's content, which is what keeps it lazy-loading compatible.
1094
1126
  */
1095
- getEffectiveHeights(dims, sectionIndex, pageInSection, globalPageNumber) {
1127
+ getPageBands(dims, sectionIndex, pageInSection, globalPageNumber) {
1096
1128
  const sectionHf = this.hfRegistry.get(sectionIndex);
1097
- let headerHeight = dims.marginTop;
1098
- if (sectionHf) {
1099
- let measuredHeaderHeight;
1100
- if (pageInSection === 1 && sectionHf.headerFirstHeight != null) {
1101
- measuredHeaderHeight = sectionHf.headerFirstHeight;
1102
- } else if (globalPageNumber % 2 === 0 && sectionHf.headerEvenHeight != null) {
1103
- measuredHeaderHeight = sectionHf.headerEvenHeight;
1104
- } else if (sectionHf.headerDefaultHeight != null) {
1105
- measuredHeaderHeight = sectionHf.headerDefaultHeight;
1106
- }
1107
- if (measuredHeaderHeight != null) {
1108
- headerHeight = Math.max(dims.marginTop, measuredHeaderHeight);
1109
- }
1110
- }
1111
- let footerHeight = dims.marginBottom;
1112
- if (sectionHf) {
1113
- let measuredFooterHeight;
1114
- if (pageInSection === 1 && sectionHf.footerFirstHeight != null) {
1115
- measuredFooterHeight = sectionHf.footerFirstHeight;
1116
- } else if (globalPageNumber % 2 === 0 && sectionHf.footerEvenHeight != null) {
1117
- measuredFooterHeight = sectionHf.footerEvenHeight;
1118
- } else if (sectionHf.footerDefaultHeight != null) {
1119
- measuredFooterHeight = sectionHf.footerDefaultHeight;
1120
- }
1121
- if (measuredFooterHeight != null) {
1122
- footerHeight = Math.max(dims.marginBottom, measuredFooterHeight);
1123
- }
1124
- }
1125
- const headerExpansion = headerHeight - dims.marginTop;
1126
- const footerExpansion = footerHeight - dims.marginBottom;
1127
- const contentHeight = dims.contentHeight - headerExpansion - footerExpansion;
1128
- return { headerHeight, footerHeight, contentHeight };
1129
+ return resolvePageBands(
1130
+ dims,
1131
+ this.selectStoryHeight(
1132
+ sectionHf?.headerFirstHeight,
1133
+ sectionHf?.headerEvenHeight,
1134
+ sectionHf?.headerDefaultHeight,
1135
+ pageInSection,
1136
+ globalPageNumber
1137
+ ),
1138
+ this.selectStoryHeight(
1139
+ sectionHf?.footerFirstHeight,
1140
+ sectionHf?.footerEvenHeight,
1141
+ sectionHf?.footerDefaultHeight,
1142
+ pageInSection,
1143
+ globalPageNumber
1144
+ )
1145
+ );
1146
+ }
1147
+ /**
1148
+ * The measured height of the running story this page position selects, mirroring
1149
+ * {@link selectHeader}/{@link selectFooter}. Zero when the page has no such story.
1150
+ */
1151
+ selectStoryHeight(first, even, fallback, pageInSection, globalPageNumber) {
1152
+ if (pageInSection === 1 && first != null) return first;
1153
+ if (globalPageNumber % 2 === 0 && even != null) return even;
1154
+ return fallback ?? 0;
1129
1155
  }
1130
1156
  /**
1131
1157
  * Measures the content height of a header or footer element.
1132
- * This is needed because headers/footers can contain more content than fits in the margin area.
1158
+ *
1159
+ * This is what tells {@link resolvePageBands} whether the story stays inside its margin or
1160
+ * pushes the body, so it must measure the story ALONE — any padding added here would have to
1161
+ * be added to the rendered band too, and the two drifting apart is exactly how a header
1162
+ * silently starts overlapping body text.
1133
1163
  */
1134
1164
  measureHeaderFooterHeight(source, contentWidth) {
1135
1165
  const measureContainer = document.createElement("div");
@@ -1137,7 +1167,6 @@ var DocxodusEditor = (() => {
1137
1167
  measureContainer.style.visibility = "hidden";
1138
1168
  measureContainer.style.width = `${contentWidth}pt`;
1139
1169
  measureContainer.style.left = "-9999px";
1140
- measureContainer.style.paddingBottom = "4pt";
1141
1170
  for (const child of Array.from(source.childNodes)) {
1142
1171
  measureContainer.appendChild(child.cloneNode(true));
1143
1172
  }
@@ -1157,7 +1186,7 @@ var DocxodusEditor = (() => {
1157
1186
  let currentContent = [];
1158
1187
  let pageNumber = startPageNumber;
1159
1188
  let pageInSection = 1;
1160
- let { contentHeight: effectiveContentHeight } = this.getEffectiveHeights(
1189
+ let { bodyHeight: effectiveContentHeight } = this.getPageBands(
1161
1190
  dims,
1162
1191
  sectionIndex,
1163
1192
  pageInSection,
@@ -1192,8 +1221,8 @@ var DocxodusEditor = (() => {
1192
1221
  pageNumber++;
1193
1222
  pageInSection++;
1194
1223
  currentContent = [];
1195
- const newHeights = this.getEffectiveHeights(dims, sectionIndex, pageInSection, pageNumber);
1196
- effectiveContentHeight = newHeights.contentHeight;
1224
+ const newBands = this.getPageBands(dims, sectionIndex, pageInSection, pageNumber);
1225
+ effectiveContentHeight = newBands.bodyHeight;
1197
1226
  remainingHeight = effectiveContentHeight;
1198
1227
  prevMarginBottomPt = 0;
1199
1228
  currentFootnoteIds = [];
@@ -1255,7 +1284,7 @@ var DocxodusEditor = (() => {
1255
1284
  ) + additionalChainFootnoteHeight;
1256
1285
  const currentAvailableHeight = remainingHeight - currentFootnoteHeight;
1257
1286
  if (currentChainHeight > currentAvailableHeight) {
1258
- const nextPageHeights = this.getEffectiveHeights(
1287
+ const nextPageBands = this.getPageBands(
1259
1288
  dims,
1260
1289
  sectionIndex,
1261
1290
  pageInSection + 1,
@@ -1271,7 +1300,7 @@ var DocxodusEditor = (() => {
1271
1300
  dims.contentWidth,
1272
1301
  nextPageContinuation
1273
1302
  );
1274
- if (freshChainBodyHeight + freshChainFootnoteHeight <= nextPageHeights.contentHeight) {
1303
+ if (freshChainBodyHeight + freshChainFootnoteHeight <= nextPageBands.bodyHeight) {
1275
1304
  finishPage();
1276
1305
  }
1277
1306
  }
@@ -1440,6 +1469,161 @@ var DocxodusEditor = (() => {
1440
1469
  if (el.getAttribute("contenteditable") !== null) el.setAttribute("contenteditable", "false");
1441
1470
  }
1442
1471
  }
1472
+ /**
1473
+ * Resolves floating DrawingML objects after their anchor paragraphs have landed on a page.
1474
+ *
1475
+ * The converter deliberately carries the OOXML bases and offsets as data instead of flattening
1476
+ * them into CSS: page/margin/column bases belong to the page, while paragraph/line/character
1477
+ * bases belong to the laid-out anchor. Once both coordinate systems exist, promote the object
1478
+ * from the clipped text column into the page box and position it in one shared point space.
1479
+ */
1480
+ positionDrawingAnchors(pageBox, contentArea, dims, pageNumber) {
1481
+ const anchors = Array.from(
1482
+ contentArea.querySelectorAll('[data-docx-drawing-anchor="true"]')
1483
+ );
1484
+ if (anchors.length === 0) return;
1485
+ const pageRect = pageBox.getBoundingClientRect();
1486
+ const pixelsPerPoint = pageRect.width / dims.pageWidth;
1487
+ if (!(pixelsPerPoint > 0)) return;
1488
+ for (const anchor of anchors) {
1489
+ const staticRect = anchor.getBoundingClientRect();
1490
+ const paragraph = anchor.closest("p, h1, h2, h3, h4, h5, h6");
1491
+ const paragraphRect = paragraph?.getBoundingClientRect() ?? staticRect;
1492
+ const toPageX = (x) => (x - pageRect.left) / pixelsPerPoint;
1493
+ const toPageY = (y) => (y - pageRect.top) / pixelsPerPoint;
1494
+ const context = {
1495
+ staticLeft: toPageX(staticRect.left),
1496
+ staticTop: toPageY(staticRect.top),
1497
+ lineHeight: paragraph ? (parseFloat(getComputedStyle(paragraph).lineHeight) || staticRect.height) / pixelsPerPoint : staticRect.height / pixelsPerPoint,
1498
+ paragraphLeft: toPageX(paragraphRect.left),
1499
+ paragraphTop: toPageY(paragraphRect.top),
1500
+ paragraphWidth: paragraphRect.width / pixelsPerPoint,
1501
+ paragraphHeight: paragraphRect.height / pixelsPerPoint
1502
+ };
1503
+ const widthReference = this.horizontalAnchorReference(
1504
+ anchor.getAttribute("data-docx-anchor-width-relative") ?? "margin",
1505
+ dims,
1506
+ pageNumber,
1507
+ context
1508
+ );
1509
+ const widthPercent = this.anchorNumber(anchor, "width-percent");
1510
+ if (widthPercent !== void 0) {
1511
+ anchor.style.width = `${widthReference.size * widthPercent / 100}pt`;
1512
+ }
1513
+ const heightReference = this.verticalAnchorReference(
1514
+ anchor.getAttribute("data-docx-anchor-height-relative") ?? "margin",
1515
+ dims,
1516
+ pageNumber,
1517
+ context
1518
+ );
1519
+ const heightPercent = this.anchorNumber(anchor, "height-percent");
1520
+ if (heightPercent !== void 0 && anchor.dataset.docxAnchorAutofit !== "true") {
1521
+ anchor.style.height = `${heightReference.size * heightPercent / 100}pt`;
1522
+ }
1523
+ const sizedRect = anchor.getBoundingClientRect();
1524
+ const width = sizedRect.width / pixelsPerPoint;
1525
+ const height = sizedRect.height / pixelsPerPoint;
1526
+ const horizontal2 = this.horizontalAnchorReference(
1527
+ anchor.getAttribute("data-docx-anchor-h-relative") ?? "column",
1528
+ dims,
1529
+ pageNumber,
1530
+ context
1531
+ );
1532
+ const vertical2 = this.verticalAnchorReference(
1533
+ anchor.getAttribute("data-docx-anchor-v-relative") ?? "paragraph",
1534
+ dims,
1535
+ pageNumber,
1536
+ context
1537
+ );
1538
+ const left2 = this.resolveAnchorAxis(
1539
+ horizontal2,
1540
+ width,
1541
+ anchor.getAttribute("data-docx-anchor-h-align"),
1542
+ this.anchorNumber(anchor, "h-offset"),
1543
+ pageNumber
1544
+ );
1545
+ const top2 = this.resolveAnchorAxis(
1546
+ vertical2,
1547
+ height,
1548
+ anchor.getAttribute("data-docx-anchor-v-align"),
1549
+ this.anchorNumber(anchor, "v-offset"),
1550
+ pageNumber
1551
+ );
1552
+ pageBox.appendChild(anchor);
1553
+ anchor.style.position = "absolute";
1554
+ anchor.style.left = `${left2}pt`;
1555
+ anchor.style.top = `${top2}pt`;
1556
+ anchor.style.margin = "0";
1557
+ }
1558
+ }
1559
+ anchorNumber(anchor, suffix) {
1560
+ const raw = anchor.getAttribute(`data-docx-anchor-${suffix}`);
1561
+ if (raw === null) return void 0;
1562
+ const value = Number(raw);
1563
+ return Number.isFinite(value) ? value : void 0;
1564
+ }
1565
+ horizontalAnchorReference(relativeFrom, dims, pageNumber, context) {
1566
+ const leftMargin = { start: 0, size: dims.marginLeft };
1567
+ const rightMargin = {
1568
+ start: dims.marginLeft + dims.contentWidth,
1569
+ size: dims.marginRight
1570
+ };
1571
+ switch (relativeFrom) {
1572
+ case "page":
1573
+ return { start: 0, size: dims.pageWidth };
1574
+ case "character":
1575
+ return { start: context.staticLeft, size: 0 };
1576
+ case "leftMargin":
1577
+ return leftMargin;
1578
+ case "rightMargin":
1579
+ return rightMargin;
1580
+ case "insideMargin":
1581
+ return pageNumber % 2 === 1 ? leftMargin : rightMargin;
1582
+ case "outsideMargin":
1583
+ return pageNumber % 2 === 1 ? rightMargin : leftMargin;
1584
+ case "paragraph":
1585
+ return { start: context.paragraphLeft, size: context.paragraphWidth };
1586
+ case "column":
1587
+ case "margin":
1588
+ default:
1589
+ return { start: dims.marginLeft, size: dims.contentWidth };
1590
+ }
1591
+ }
1592
+ verticalAnchorReference(relativeFrom, dims, pageNumber, context) {
1593
+ const topMargin = { start: 0, size: dims.marginTop };
1594
+ const bottomMargin = {
1595
+ start: dims.marginTop + dims.contentHeight,
1596
+ size: dims.marginBottom
1597
+ };
1598
+ switch (relativeFrom) {
1599
+ case "page":
1600
+ return { start: 0, size: dims.pageHeight };
1601
+ case "paragraph":
1602
+ return { start: context.paragraphTop, size: context.paragraphHeight };
1603
+ case "line":
1604
+ return { start: context.staticTop, size: context.lineHeight };
1605
+ case "topMargin":
1606
+ return topMargin;
1607
+ case "bottomMargin":
1608
+ return bottomMargin;
1609
+ case "insideMargin":
1610
+ return pageNumber % 2 === 1 ? topMargin : bottomMargin;
1611
+ case "outsideMargin":
1612
+ return pageNumber % 2 === 1 ? bottomMargin : topMargin;
1613
+ case "margin":
1614
+ default:
1615
+ return { start: dims.marginTop, size: dims.contentHeight };
1616
+ }
1617
+ }
1618
+ resolveAnchorAxis(reference, objectSize, alignment, offset, pageNumber) {
1619
+ if (offset !== void 0) return reference.start + offset;
1620
+ let fraction = 0;
1621
+ if (alignment === "center") fraction = 0.5;
1622
+ else if (alignment === "right" || alignment === "bottom") fraction = 1;
1623
+ else if (alignment === "inside") fraction = pageNumber % 2 === 1 ? 0 : 1;
1624
+ else if (alignment === "outside") fraction = pageNumber % 2 === 1 ? 1 : 0;
1625
+ return reference.start + (reference.size - objectSize) * fraction;
1626
+ }
1443
1627
  /**
1444
1628
  * Creates a page container element.
1445
1629
  */
@@ -1466,22 +1650,22 @@ var DocxodusEditor = (() => {
1466
1650
  pageBox.dataset.pageNumber = String(pageNumber);
1467
1651
  pageBox.dataset.sectionIndex = String(sectionIndex);
1468
1652
  pageBox.dataset.pageInSection = String(pageInSection);
1469
- const effectiveHeights = this.getEffectiveHeights(dims, sectionIndex, pageInSection, pageNumber);
1653
+ const bands = this.getPageBands(dims, sectionIndex, pageInSection, pageNumber);
1470
1654
  const headerSource = this.selectHeader(sectionIndex, pageInSection, pageNumber);
1471
1655
  if (headerSource) {
1472
1656
  const headerDiv = document.createElement("div");
1473
1657
  headerDiv.className = `${this.cssPrefix}header`;
1474
1658
  headerDiv.style.position = "absolute";
1475
- headerDiv.style.top = "0";
1659
+ headerDiv.style.top = `${bands.headerTop}pt`;
1660
+ headerDiv.style.bottom = "auto";
1476
1661
  headerDiv.style.left = `${dims.marginLeft}pt`;
1477
1662
  headerDiv.style.width = `${dims.contentWidth}pt`;
1478
- headerDiv.style.height = `${effectiveHeights.headerHeight}pt`;
1663
+ headerDiv.style.height = `${bands.headerHeight}pt`;
1479
1664
  headerDiv.style.overflow = "hidden";
1480
1665
  headerDiv.style.boxSizing = "border-box";
1481
1666
  headerDiv.style.display = "flex";
1482
1667
  headerDiv.style.flexDirection = "column";
1483
- headerDiv.style.justifyContent = "flex-end";
1484
- headerDiv.style.paddingBottom = "4pt";
1668
+ headerDiv.style.justifyContent = "flex-start";
1485
1669
  for (const child of Array.from(headerSource.childNodes)) {
1486
1670
  const clonedheaderDiv = child.cloneNode(true);
1487
1671
  if (clonedheaderDiv.nodeType === 1) this.makeClonedStoryInert(clonedheaderDiv);
@@ -1489,8 +1673,8 @@ var DocxodusEditor = (() => {
1489
1673
  }
1490
1674
  pageBox.appendChild(headerDiv);
1491
1675
  }
1492
- const contentAreaTop = effectiveHeights.headerHeight;
1493
- const contentAreaHeight = effectiveHeights.contentHeight;
1676
+ const contentAreaTop = bands.bodyTop;
1677
+ const contentAreaHeight = bands.bodyHeight;
1494
1678
  const contentArea = document.createElement("div");
1495
1679
  contentArea.className = `${this.cssPrefix}content`;
1496
1680
  contentArea.style.position = "absolute";
@@ -1505,23 +1689,23 @@ var DocxodusEditor = (() => {
1505
1689
  pageBox.appendChild(contentArea);
1506
1690
  const hasContinuation = continuation && continuation.remainingElements.length > 0;
1507
1691
  if (footnoteIds.length > 0 || hasContinuation) {
1508
- this.addPageFootnotes(pageBox, footnoteIds, dims, footnoteHeight, continuation, partialFootnotes);
1692
+ this.addPageFootnotes(pageBox, footnoteIds, dims, bands, footnoteHeight, continuation, partialFootnotes);
1509
1693
  }
1510
1694
  const footerSource = this.selectFooter(sectionIndex, pageInSection, pageNumber);
1511
1695
  if (footerSource) {
1512
1696
  const footerDiv = document.createElement("div");
1513
1697
  footerDiv.className = `${this.cssPrefix}footer`;
1514
1698
  footerDiv.style.position = "absolute";
1515
- footerDiv.style.bottom = "0";
1699
+ footerDiv.style.top = "auto";
1700
+ footerDiv.style.bottom = `${dims.footerDistance}pt`;
1516
1701
  footerDiv.style.left = `${dims.marginLeft}pt`;
1517
1702
  footerDiv.style.width = `${dims.contentWidth}pt`;
1518
- footerDiv.style.height = `${effectiveHeights.footerHeight}pt`;
1703
+ footerDiv.style.height = `${bands.footerHeight}pt`;
1519
1704
  footerDiv.style.overflow = "hidden";
1520
1705
  footerDiv.style.boxSizing = "border-box";
1521
1706
  footerDiv.style.display = "flex";
1522
1707
  footerDiv.style.flexDirection = "column";
1523
- footerDiv.style.justifyContent = "flex-start";
1524
- footerDiv.style.paddingTop = "4pt";
1708
+ footerDiv.style.justifyContent = "flex-end";
1525
1709
  for (const child of Array.from(footerSource.childNodes)) {
1526
1710
  const clonedfooterDiv = child.cloneNode(true);
1527
1711
  if (clonedfooterDiv.nodeType === 1) this.makeClonedStoryInert(clonedfooterDiv);
@@ -1536,6 +1720,7 @@ var DocxodusEditor = (() => {
1536
1720
  pageBox.appendChild(pageNum);
1537
1721
  }
1538
1722
  this.containerElement.appendChild(pageBox);
1723
+ this.positionDrawingAnchors(pageBox, contentArea, dims, pageNumber);
1539
1724
  const notesEl = pageBox.querySelector(`.${this.cssPrefix}footnotes`);
1540
1725
  if (notesEl) {
1541
1726
  const notesHeightPt = pxToPt(notesEl.getBoundingClientRect().height);
@@ -4910,6 +5095,19 @@ var DocxodusEditor = (() => {
4910
5095
  function isListBlock(block) {
4911
5096
  return !!block.querySelector(":scope > [data-list-marker]");
4912
5097
  }
5098
+ function ensureListMarkerSeparator(block) {
5099
+ const marker = block.querySelector(":scope > [data-list-marker]");
5100
+ if (!marker) return;
5101
+ const next = marker.nextElementSibling;
5102
+ if (next?.hasAttribute("data-editor-list-separator")) return;
5103
+ const separator = block.ownerDocument.createElement("span");
5104
+ separator.setAttribute("data-editor-list-separator", "");
5105
+ separator.setAttribute("data-list-marker", "true");
5106
+ separator.setAttribute("aria-hidden", "true");
5107
+ separator.style.display = "none";
5108
+ separator.textContent = " ";
5109
+ marker.after(separator);
5110
+ }
4913
5111
  var GENERATED_CHROME_SELECTOR = '[data-list-marker], a.footnote-ref, a.endnote-ref, a[class$="-backref"]';
4914
5112
  function isGeneratedChrome(node) {
4915
5113
  return node?.nodeType === 1 && !!node.matches?.(GENERATED_CHROME_SELECTOR);
@@ -6200,6 +6398,7 @@ var DocxodusEditor = (() => {
6200
6398
  const unid = el.getAttribute("data-anchor");
6201
6399
  if (!unid || !this.anchorIdOf(el)) return;
6202
6400
  el.setAttribute("contenteditable", "true");
6401
+ ensureListMarkerSeparator(el);
6203
6402
  el.querySelectorAll(GENERATED_CHROME_SELECTOR).forEach((m) => m.setAttribute("contenteditable", "false"));
6204
6403
  el.dataset.committedText = blockContentText(el);
6205
6404
  el.addEventListener("focus", () => {
@@ -7062,8 +7261,8 @@ var DocxodusEditor = (() => {
7062
7261
  * editable `data-anchor` blocks in the notes section, so editing it afterwards needs no new op.
7063
7262
  *
7064
7263
  * Body blocks only: Word disallows a note reference inside a header/footer story or inside
7065
- * another note, and the session rejects those with `AnchorWrongKind`. Remounts, because a new
7066
- * note renumbers the citations after it and can add a whole part.
7264
+ * another note, and the session rejects those with `AnchorWrongKind`. Reconciliation creates
7265
+ * the first notes section when needed and renumbers every affected citation in place.
7067
7266
  */
7068
7267
  insertFootnote(markdown = "New footnote.") {
7069
7268
  this.insertNote("footnote", markdown);
@@ -7455,7 +7654,7 @@ var DocxodusEditor = (() => {
7455
7654
  * the current node — never a section div (multi-child) or the edit root. */
7456
7655
  unitWrapperOf(el) {
7457
7656
  let n = el;
7458
- while (n.parentElement && n.parentElement !== this.editRoot && n.parentElement.tagName === "DIV" && !n.parentElement.hasAttribute("data-anchor") && n.parentElement.childElementCount === 1) {
7657
+ while (n.parentElement && n.parentElement !== this.editRoot && n.parentElement.tagName === "DIV" && !n.parentElement.hasAttribute("data-section-index") && !n.parentElement.hasAttribute("data-anchor") && n.parentElement.childElementCount === 1) {
7459
7658
  n = n.parentElement;
7460
7659
  }
7461
7660
  return n;
@@ -7491,6 +7690,7 @@ var DocxodusEditor = (() => {
7491
7690
  const pureRemoved = diff.removed.filter(
7492
7691
  (i) => !diff.substituted.some((s) => s.oldIndex === i) && !movedOld.has(i)
7493
7692
  );
7693
+ const appliedFresh = /* @__PURE__ */ new Set();
7494
7694
  const nodeAt = (j) => {
7495
7695
  const oi = diff.keep.get(j);
7496
7696
  if (oi !== void 0) return oldNodes[oi];
@@ -7498,7 +7698,7 @@ var DocxodusEditor = (() => {
7498
7698
  if (movedOi !== void 0) return oldNodes[movedOi];
7499
7699
  if (subOldByNew.has(j)) return fresh.get(j);
7500
7700
  const f = fresh.get(j);
7501
- return f && f.isConnected ? f : null;
7701
+ return f && appliedFresh.has(j) ? f : null;
7502
7702
  };
7503
7703
  for (const j of pureAdded) {
7504
7704
  const el = fresh.get(j);
@@ -7513,6 +7713,7 @@ var DocxodusEditor = (() => {
7513
7713
  if (prevW) prevW.after(el);
7514
7714
  else if (nextW) nextW.before(el);
7515
7715
  else return "insert with no anchored neighbor";
7716
+ appliedFresh.add(j);
7516
7717
  this.wireUnit(el, units[j]);
7517
7718
  }
7518
7719
  for (const i of pureRemoved) {
@@ -7551,13 +7752,13 @@ var DocxodusEditor = (() => {
7551
7752
  if (anchorEl) this.wireBlock(anchorEl);
7552
7753
  root.querySelectorAll("[data-anchor]").forEach((b) => this.wireBlock(b));
7553
7754
  }
7554
- /** Old-sequence diff state for one notes section. `null` requests remount (DOM not
7555
- * stampable/consistent). */
7755
+ /** Old-sequence diff state for one notes section. `null` requests remount when an
7756
+ * existing list is not stampable; an absent list is the valid first-note case. */
7556
7757
  notesDiff(sectionClass, units) {
7557
7758
  const ol = this.editRoot.querySelector(`section.${sectionClass} > ol`);
7558
7759
  const lis = ol ? Array.from(ol.children).filter((c) => c.tagName === "LI") : [];
7559
7760
  if (units.length === 0 && lis.length === 0) return { lis, diff: diffUnits([], []) };
7560
- if (!ol) return null;
7761
+ if (!ol) return { lis, diff: diffUnits([], units) };
7561
7762
  const tokens = [];
7562
7763
  for (const li of lis) {
7563
7764
  const unid = li.getAttribute("data-note-anchor");
@@ -7574,7 +7775,20 @@ var DocxodusEditor = (() => {
7574
7775
  this.editRoot.querySelector(`section.${sectionClass}`)?.remove();
7575
7776
  return;
7576
7777
  }
7577
- const ol = this.editRoot.querySelector(`section.${sectionClass} > ol`);
7778
+ let section = this.editRoot.querySelector(`:scope > section.${sectionClass}`);
7779
+ let ol = section?.querySelector(`:scope > ol`) ?? null;
7780
+ if (!ol) {
7781
+ if (!section) {
7782
+ section = document.createElement("section");
7783
+ section.className = sectionClass;
7784
+ const following = sectionClass === "footnotes" ? this.editRoot.querySelector(`:scope > section.endnotes`) : null;
7785
+ if (following) following.before(section);
7786
+ else this.editRoot.append(section);
7787
+ }
7788
+ if (!section.querySelector(":scope > hr")) section.prepend(document.createElement("hr"));
7789
+ ol = document.createElement("ol");
7790
+ section.append(ol);
7791
+ }
7578
7792
  const prefix = sectionClass === "footnotes" ? "fn" : "en";
7579
7793
  const nodes = [];
7580
7794
  for (let j = 0; j < units.length; j++) {
@@ -7711,23 +7925,38 @@ var DocxodusEditor = (() => {
7711
7925
  };
7712
7926
 
7713
7927
  // src/ribbon-chrome.ts
7714
- var RIBBON_STYLE_VERSION = "5";
7928
+ var RIBBON_STYLE_VERSION = "8";
7715
7929
  var RIBBON_STYLE_ATTR = "data-docxodus-ribbon-styles";
7716
7930
  var RIBBON_CSS = `
7717
7931
  .dxr {
7718
- --dxr-ink: #101418;
7932
+ /* OS-Legal design tokens (github.com/Open-Source-Legal/OS-Legal-Style):
7933
+ deep-teal accent, slate foreground scale, warm neutral surfaces, and
7934
+ "light touch" shadows at 0.03-0.06 opacity \u2014 structure over decoration. */
7935
+ --dxr-ink: #1e293b;
7719
7936
  --dxr-sheet: #ffffff;
7720
- --dxr-chrome: #eef1f6;
7721
- --dxr-chrome-sunk: #e3e8f0;
7722
- --dxr-rule: #d3dae4;
7723
- --dxr-accent: #1f5fd0;
7724
- --dxr-wash: #dde8fb;
7725
- --dxr-muted: #5d6975;
7726
- --dxr-data: #0b6f6a;
7727
- --dxr-danger: #a3341f;
7728
- --dxr-desk: #dfe3ea;
7729
- --dxr-ui: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
7937
+ --dxr-chrome: #ffffff;
7938
+ --dxr-chrome-sunk: #f8fafc;
7939
+ --dxr-rule: #e2e8f0;
7940
+ --dxr-rule-strong: #cbd5e1;
7941
+ --dxr-accent: #0f766e;
7942
+ --dxr-accent-hover: #0d9488;
7943
+ --dxr-accent-active: #115e59;
7944
+ --dxr-wash: #f0fdfa;
7945
+ --dxr-wash-on: #ccfbf1;
7946
+ --dxr-muted: #475569;
7947
+ --dxr-faint: #94a3b8;
7948
+ --dxr-data: #0f766e;
7949
+ --dxr-danger: #dc2626;
7950
+ --dxr-danger-wash: #fef2f2;
7951
+ --dxr-desk: #f1f5f9;
7952
+ --dxr-ui: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
7953
+ --dxr-serif: Georgia, "Times New Roman", serif;
7730
7954
  --dxr-mono: ui-monospace, SFMono-Regular, "Cascadia Mono", Consolas, monospace;
7955
+ --dxr-shadow-sm: 0 1px 2px rgba(15, 23, 42, .04);
7956
+ --dxr-shadow-md: 0 1px 3px rgba(15, 23, 42, .06), 0 4px 6px rgba(15, 23, 42, .03);
7957
+ --dxr-shadow-lg: 0 4px 6px rgba(15, 23, 42, .05), 0 10px 15px rgba(15, 23, 42, .04);
7958
+ --dxr-shadow-xl: 0 8px 16px rgba(15, 23, 42, .06), 0 20px 25px rgba(15, 23, 42, .05);
7959
+ --dxr-ease: cubic-bezier(.4, 0, .2, 1);
7731
7960
  --dxr-tap: 30px;
7732
7961
 
7733
7962
  position: relative;
@@ -7743,6 +7972,18 @@ var DocxodusEditor = (() => {
7743
7972
  }
7744
7973
  .dxr *, .dxr *::before, .dxr *::after { box-sizing: border-box; }
7745
7974
 
7975
+ /* \u2500\u2500 Card frame \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
7976
+ A drop-in embed carries its OWN boundary instead of hoping the host clips it:
7977
+ rounded corners, a hairline, and the house elevation. overflow: clip keeps
7978
+ the sticky chrome, scrollbars, and the loading overlay inside the curve.
7979
+ Full-bleed hosts mount with data-frame="flush" and stay edge-to-edge. */
7980
+ .dxr[data-frame="card"] {
7981
+ border: 1px solid var(--dxr-rule);
7982
+ border-radius: 14px;
7983
+ box-shadow: var(--dxr-shadow-xl);
7984
+ overflow: clip;
7985
+ }
7986
+
7746
7987
  /* Touch devices get bigger hit targets everywhere the token is used. */
7747
7988
  @media (pointer: coarse) { .dxr { --dxr-tap: 40px; } }
7748
7989
 
@@ -7771,12 +8012,13 @@ var DocxodusEditor = (() => {
7771
8012
  font-weight: 600;
7772
8013
  letter-spacing: -.01em;
7773
8014
  }
8015
+ /* A filled circle, echoing the OS-Legal "Node" motif rather than a diamond. */
7774
8016
  .dxr-brand .dxr-mark {
7775
8017
  flex: 0 0 auto;
7776
8018
  width: 9px;
7777
8019
  height: 9px;
8020
+ border-radius: 50%;
7778
8021
  background: var(--dxr-accent);
7779
- clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
7780
8022
  }
7781
8023
  .dxr-brand .dxr-docname {
7782
8024
  overflow: hidden;
@@ -7808,25 +8050,25 @@ var DocxodusEditor = (() => {
7808
8050
  scrollbar-width: none;
7809
8051
  }
7810
8052
  .dxr-tabs::-webkit-scrollbar { display: none; }
8053
+ /* Line-variant tabs (the house Tabs default): no boxes, no fills \u2014 the selected
8054
+ tab is a 2px accent underline sitting on the panel's own hairline. */
7811
8055
  .dxr-tab {
7812
8056
  flex: 0 0 auto;
7813
- padding: 6px 15px 7px;
7814
- border: 1px solid transparent;
7815
- border-bottom: none;
7816
- border-radius: 5px 5px 0 0;
8057
+ padding: 7px 14px 8px;
8058
+ border: none;
7817
8059
  background: none;
7818
8060
  color: var(--dxr-muted);
7819
8061
  font: inherit;
7820
8062
  font-size: 12.5px;
8063
+ font-weight: 500;
7821
8064
  cursor: pointer;
8065
+ transition: color .15s var(--dxr-ease), box-shadow .15s var(--dxr-ease);
7822
8066
  }
7823
- .dxr-tab:hover { color: var(--dxr-ink); background: #e6ebf3; }
8067
+ .dxr-tab:hover { color: var(--dxr-ink); }
7824
8068
  .dxr-tab[aria-selected="true"] {
7825
8069
  color: var(--dxr-ink);
7826
8070
  font-weight: 600;
7827
- background: var(--dxr-chrome-sunk);
7828
- border-color: var(--dxr-rule);
7829
- box-shadow: inset 0 2px 0 var(--dxr-accent);
8071
+ box-shadow: inset 0 -2px 0 var(--dxr-accent);
7830
8072
  }
7831
8073
  /* Contextual tab \u2014 present only while the caret is inside a table. */
7832
8074
  .dxr-tab[data-contextual] { color: var(--dxr-accent); }
@@ -7856,7 +8098,7 @@ var DocxodusEditor = (() => {
7856
8098
  .dxr-group:last-child { border-right: none; }
7857
8099
  .dxr-group:first-child { padding-left: 0; }
7858
8100
  .dxr-glabel {
7859
- color: var(--dxr-muted);
8101
+ color: var(--dxr-faint);
7860
8102
  font-size: 9.5px;
7861
8103
  font-weight: 600;
7862
8104
  letter-spacing: .11em;
@@ -7865,48 +8107,69 @@ var DocxodusEditor = (() => {
7865
8107
  .dxr-row { display: flex; gap: 3px; align-items: center; }
7866
8108
 
7867
8109
  /* \u2500\u2500 Controls \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
8110
+ /* One button family, house-style: every control is a ghost \u2014 no borders, no
8111
+ fills, no per-button boxes. Hover paints a quiet slate wash; the active
8112
+ (toggled) state paints a teal one. Save alone is the flat primary, so the
8113
+ surface has exactly one accent-colored action. */
7868
8114
  .dxr button, .dxr label.dxr-btn {
7869
8115
  min-height: var(--dxr-tap);
7870
8116
  padding: 5px 9px;
7871
- border: 1px solid transparent;
7872
- border-radius: 4px;
8117
+ border: none;
8118
+ border-radius: 6px;
7873
8119
  background: none;
7874
8120
  color: var(--dxr-ink);
7875
8121
  font: inherit;
7876
8122
  font-size: 13px;
8123
+ font-weight: 500;
7877
8124
  line-height: 1.15;
7878
8125
  cursor: pointer;
8126
+ transition: background-color .15s var(--dxr-ease), color .15s var(--dxr-ease),
8127
+ box-shadow .15s var(--dxr-ease), transform .15s var(--dxr-ease);
7879
8128
  }
7880
8129
  .dxr label.dxr-btn { display: inline-flex; align-items: center; }
7881
- .dxr button:hover, .dxr label.dxr-btn:hover { background: #d6deea; }
7882
- .dxr button:active { background: #c8d3e3; }
8130
+ .dxr button:hover, .dxr label.dxr-btn:hover { background: #f1f5f9; }
8131
+ .dxr button:active { background: #e2e8f0; }
7883
8132
  .dxr button:disabled { opacity: .38; cursor: default; background: none; }
7884
- .dxr button.dxr-on { color: var(--dxr-accent); background: var(--dxr-wash); border-color: #b3ccf2; }
7885
- .dxr-quick button, .dxr-quick label.dxr-btn {
7886
- padding: 4px 10px;
7887
- border-color: var(--dxr-rule);
7888
- background: #f7f9fc;
7889
- font-size: 12.5px;
8133
+ .dxr button.dxr-on { color: var(--dxr-accent); background: var(--dxr-wash-on); }
8134
+ .dxr-quick button, .dxr-quick label.dxr-btn { padding: 4px 10px; font-size: 12.5px; }
8135
+ /* Save \u2014 the house primary: flat accent, sm shadow, the 1px lift on hover. */
8136
+ .dxr-quick button[data-dxr="save"]:not(:disabled) {
8137
+ background: var(--dxr-accent);
8138
+ color: #fff;
8139
+ font-weight: 600;
8140
+ box-shadow: var(--dxr-shadow-sm);
8141
+ }
8142
+ .dxr-quick button[data-dxr="save"]:not(:disabled):hover {
8143
+ background: var(--dxr-accent-hover);
8144
+ box-shadow: var(--dxr-shadow-md);
8145
+ transform: translateY(-1px);
7890
8146
  }
7891
- .dxr-quick button:hover, .dxr-quick label.dxr-btn:hover { background: #fff; border-color: #b9c4d4; }
7892
- .dxr-quick button:disabled:hover { background: #f7f9fc; border-color: var(--dxr-rule); }
7893
- .dxr button.dxr-icon { min-width: var(--dxr-tap); text-align: center; }
8147
+ .dxr-quick button[data-dxr="save"]:not(:disabled):active {
8148
+ background: var(--dxr-accent-active);
8149
+ box-shadow: var(--dxr-shadow-sm);
8150
+ transform: translateY(0);
8151
+ }
8152
+ /* Icon controls sit quieter than text ones until touched (house IconButton). */
8153
+ .dxr button.dxr-icon { min-width: var(--dxr-tap); text-align: center; color: var(--dxr-muted); }
8154
+ .dxr button.dxr-icon:hover:not(:disabled) { color: var(--dxr-ink); }
8155
+ .dxr button.dxr-icon.dxr-on { color: var(--dxr-accent); }
7894
8156
  /* Icons are inline SVG drawn from the same shapes the control acts on (text lines,
7895
8157
  rules), so alignment and indent read at a glance instead of relying on arrow
7896
8158
  glyphs that collide with undo/redo. currentColor keeps them in step with state. */
7897
8159
  .dxr button svg { display: block; margin: 0 auto; fill: currentColor; }
7898
8160
  .dxr button.dxr-wide { display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
7899
- .dxr button.dxr-danger:hover { color: var(--dxr-danger); background: #f6dcd6; }
8161
+ .dxr button.dxr-danger:hover { color: var(--dxr-danger); background: var(--dxr-danger-wash); }
7900
8162
  .dxr select, .dxr input[type="number"], .dxr input[type="text"] {
7901
8163
  min-height: var(--dxr-tap);
7902
8164
  padding: 4px 6px;
7903
8165
  border: 1px solid var(--dxr-rule);
7904
- border-radius: 4px;
7905
- background: #f7f9fc;
8166
+ border-radius: 6px;
8167
+ background: var(--dxr-sheet);
7906
8168
  color: var(--dxr-ink);
7907
8169
  font: inherit;
7908
8170
  font-size: 12.5px;
7909
8171
  }
8172
+ .dxr select:hover, .dxr input:hover { border-color: var(--dxr-rule-strong); }
7910
8173
  .dxr select:focus-visible, .dxr input:focus-visible, .dxr button:focus-visible,
7911
8174
  .dxr .dxr-tab:focus-visible, .dxr label.dxr-btn:focus-within {
7912
8175
  outline: 2px solid var(--dxr-accent);
@@ -7937,7 +8200,7 @@ var DocxodusEditor = (() => {
7937
8200
  height: 25px;
7938
8201
  padding: 0 10px;
7939
8202
  overflow-x: auto;
7940
- background: #f7f9fc;
8203
+ background: #fafafa;
7941
8204
  border-top: 1px solid var(--dxr-rule);
7942
8205
  color: var(--dxr-muted);
7943
8206
  font-family: var(--dxr-mono);
@@ -7956,7 +8219,7 @@ var DocxodusEditor = (() => {
7956
8219
  .dxr-rail .dxr-cell:first-child { padding-left: 0; }
7957
8220
  .dxr-rail .dxr-cell:last-child { border-right: none; }
7958
8221
  .dxr-rail .dxr-k {
7959
- color: #8b96a3;
8222
+ color: var(--dxr-faint);
7960
8223
  font-family: var(--dxr-ui);
7961
8224
  font-size: 9.5px;
7962
8225
  letter-spacing: .1em;
@@ -7964,7 +8227,7 @@ var DocxodusEditor = (() => {
7964
8227
  }
7965
8228
  .dxr-rail .dxr-v { color: var(--dxr-data); }
7966
8229
  .dxr-rail .dxr-v.dxr-flash { animation: dxr-railflash .45s ease-out; }
7967
- @keyframes dxr-railflash { from { background: #b9ecdf; } to { background: transparent; } }
8230
+ @keyframes dxr-railflash { from { background: #ccfbf1; } to { background: transparent; } }
7968
8231
 
7969
8232
  /* \u2500\u2500 Hint \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
7970
8233
  .dxr-hint {
@@ -7979,8 +8242,8 @@ var DocxodusEditor = (() => {
7979
8242
  .dxr-hint kbd {
7980
8243
  padding: 0 4px;
7981
8244
  border: 1px solid var(--dxr-rule);
7982
- border-radius: 3px;
7983
- background: #e8ecf3;
8245
+ border-radius: 4px;
8246
+ background: #f1f5f9;
7984
8247
  font-family: var(--dxr-mono);
7985
8248
  font-size: 11px;
7986
8249
  }
@@ -7995,13 +8258,34 @@ var DocxodusEditor = (() => {
7995
8258
  SHOULD win.
7996
8259
  (No backticks in this file's comments: the stylesheet is a template literal.) */
7997
8260
  .dxr-scroll { flex: 1 1 auto; min-height: 0; overflow: auto; -webkit-overflow-scrolling: touch; }
8261
+ /* Soft scroll boundaries: content dissolves at the chrome and at the bottom edge
8262
+ instead of hard-clipping. Sticky gradient veils cost one paint layer each and
8263
+ never intercept input. */
8264
+ .dxr-scroll::before, .dxr-scroll::after {
8265
+ content: "";
8266
+ position: sticky;
8267
+ z-index: 3;
8268
+ display: block;
8269
+ height: 26px;
8270
+ pointer-events: none;
8271
+ }
8272
+ .dxr-scroll::before {
8273
+ top: 0;
8274
+ margin-bottom: -26px;
8275
+ background: linear-gradient(to bottom, var(--dxr-desk), transparent);
8276
+ }
8277
+ .dxr-scroll::after {
8278
+ bottom: 0;
8279
+ margin-top: -26px;
8280
+ background: linear-gradient(to top, var(--dxr-desk), transparent);
8281
+ }
7998
8282
  .dxr[data-chrome] .dxr-surface { margin: 26px auto; padding: 0 16px 96px; }
7999
8283
  .dxr-surface [contenteditable="true"]:focus {
8000
8284
  outline: 2px solid var(--dxr-accent);
8001
8285
  outline-offset: 2px;
8002
8286
  border-radius: 2px;
8003
8287
  }
8004
- @media (hover: hover) { .dxr-surface [contenteditable="true"]:hover { background: #f2f7ff; } }
8288
+ @media (hover: hover) { .dxr-surface [contenteditable="true"]:hover { background: var(--dxr-wash); } }
8005
8289
 
8006
8290
  /* Header/footer bands: stories live in their own OOXML parts outside the body,
8007
8291
  so they dock as their own regions. Styled from the same tokens as the ribbon
@@ -8013,9 +8297,10 @@ var DocxodusEditor = (() => {
8013
8297
  margin: 0 auto 14px;
8014
8298
  padding: 9px 72px 13px;
8015
8299
  border: 1px solid var(--dxr-rule);
8016
- border-left: 2px solid #c2ccd9;
8017
- border-radius: 3px;
8300
+ border-left: 2px solid #5eead4;
8301
+ border-radius: 6px;
8018
8302
  background: var(--dxr-sheet);
8303
+ box-shadow: var(--dxr-shadow-sm);
8019
8304
  }
8020
8305
  .dxr-surface .docx-hf-band + .docx-body-flow { margin-top: 0; }
8021
8306
  .dxr-surface .docx-hf-band[data-hf-band="footer"] { margin: 14px auto 0; }
@@ -8034,8 +8319,8 @@ var DocxodusEditor = (() => {
8034
8319
  .dxr-surface .docx-hf-chrome select, .dxr-surface .docx-hf-chrome input {
8035
8320
  padding: 3px 5px;
8036
8321
  border: 1px solid var(--dxr-rule);
8037
- border-radius: 4px;
8038
- background: #f7f9fc;
8322
+ border-radius: 6px;
8323
+ background: var(--dxr-sheet);
8039
8324
  color: var(--dxr-ink);
8040
8325
  font: inherit;
8041
8326
  font-family: var(--dxr-ui);
@@ -8049,22 +8334,22 @@ var DocxodusEditor = (() => {
8049
8334
  .dxr-surface .docx-hf-warning {
8050
8335
  margin: 0 0 8px -60px;
8051
8336
  padding: 7px 9px;
8052
- border: 1px solid #e8d9a8;
8053
- border-left: 2px solid #c9a227;
8054
- border-radius: 3px;
8055
- background: #fdf6e3;
8056
- color: #6b5310;
8337
+ border: 1px solid #fde68a;
8338
+ border-left: 2px solid #d97706;
8339
+ border-radius: 6px;
8340
+ background: #fffbeb;
8341
+ color: #92400e;
8057
8342
  font-size: 12px;
8058
8343
  line-height: 1.45;
8059
8344
  }
8060
8345
  .dxr-surface .docx-hf-warning button {
8061
8346
  margin-left: 6px;
8062
8347
  padding: 2px 8px;
8063
- border-color: #e8d9a8;
8348
+ border-color: #fde68a;
8064
8349
  background: #fff;
8065
8350
  font-size: 12px;
8066
8351
  }
8067
- .dxr-surface .docx-hf-placeholder { color: #9aa4b1; font-style: italic; }
8352
+ .dxr-surface .docx-hf-placeholder { color: var(--dxr-faint); font-style: italic; }
8068
8353
  .dxr-surface .docx-hf-inherited {
8069
8354
  color: var(--dxr-muted);
8070
8355
  font-style: italic;
@@ -8084,7 +8369,7 @@ var DocxodusEditor = (() => {
8084
8369
  padding: 56px 0;
8085
8370
  border-radius: 3px;
8086
8371
  background: var(--dxr-sheet);
8087
- box-shadow: 0 1px 3px rgba(16, 20, 24, .14), 0 8px 24px rgba(16, 20, 24, .05);
8372
+ box-shadow: 0 1px 3px rgba(15, 23, 42, .08), 0 12px 24px rgba(15, 23, 42, .06);
8088
8373
  }
8089
8374
 
8090
8375
  /* \u2500\u2500 Table size picker \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
@@ -8094,9 +8379,9 @@ var DocxodusEditor = (() => {
8094
8379
  z-index: 30;
8095
8380
  padding: 9px;
8096
8381
  border: 1px solid var(--dxr-rule);
8097
- border-radius: 6px;
8382
+ border-radius: 12px;
8098
8383
  background: #fff;
8099
- box-shadow: 0 4px 16px rgba(16, 20, 24, .18);
8384
+ box-shadow: var(--dxr-shadow-xl);
8100
8385
  }
8101
8386
  .dxr-pop[data-open] { display: block; }
8102
8387
  .dxr-gridcells { display: grid; grid-template-columns: repeat(10, 16px); gap: 2px; }
@@ -8104,11 +8389,11 @@ var DocxodusEditor = (() => {
8104
8389
  width: 16px;
8105
8390
  height: 16px;
8106
8391
  border: 1px solid var(--dxr-rule);
8107
- border-radius: 2px;
8108
- background: #f2f5f9;
8392
+ border-radius: 3px;
8393
+ background: var(--dxr-chrome-sunk);
8109
8394
  cursor: pointer;
8110
8395
  }
8111
- .dxr-gridcells div[data-on] { background: var(--dxr-wash); border-color: var(--dxr-accent); }
8396
+ .dxr-gridcells div[data-on] { background: #ccfbf1; border-color: var(--dxr-accent); }
8112
8397
  .dxr-popfoot {
8113
8398
  display: flex;
8114
8399
  justify-content: space-between;
@@ -8191,11 +8476,11 @@ var DocxodusEditor = (() => {
8191
8476
  display: grid;
8192
8477
  place-items: center;
8193
8478
  padding: 24px;
8194
- color: #f4f9ff;
8479
+ color: #f8fafc;
8195
8480
  background:
8196
- radial-gradient(circle at 20% 20%, rgba(63, 128, 255, .24), transparent 22rem),
8197
- radial-gradient(circle at 82% 80%, rgba(176, 91, 255, .22), transparent 24rem),
8198
- linear-gradient(145deg, #071221 0%, #0b1930 52%, #101426 100%);
8481
+ radial-gradient(circle at 20% 20%, rgba(13, 148, 136, .2), transparent 22rem),
8482
+ radial-gradient(circle at 82% 80%, rgba(45, 212, 191, .1), transparent 24rem),
8483
+ linear-gradient(150deg, #0b1220 0%, #0f172a 55%, #0f2723 100%);
8199
8484
  transition: opacity .55s ease, visibility .55s ease;
8200
8485
  }
8201
8486
  .dxr-loader[hidden] { display: none; }
@@ -8214,14 +8499,14 @@ var DocxodusEditor = (() => {
8214
8499
  .dxr-orbit {
8215
8500
  position: absolute;
8216
8501
  inset: 7%;
8217
- border: 1px solid rgba(115, 204, 255, .22);
8502
+ border: 1px solid rgba(45, 212, 191, .24);
8218
8503
  border-radius: 50%;
8219
8504
  animation: dxr-spin 9s linear infinite;
8220
8505
  }
8221
8506
  .dxr-orbit.dxr-two {
8222
8507
  inset: 20%;
8223
8508
  border-style: dashed;
8224
- border-color: rgba(196, 128, 255, .32);
8509
+ border-color: rgba(94, 234, 212, .3);
8225
8510
  animation-duration: 6s;
8226
8511
  animation-direction: reverse;
8227
8512
  }
@@ -8231,19 +8516,19 @@ var DocxodusEditor = (() => {
8231
8516
  height: 10px;
8232
8517
  border-radius: 50%;
8233
8518
  content: "";
8234
- background: #52e5ff;
8235
- box-shadow: 0 0 18px #52e5ff;
8519
+ background: #2dd4bf;
8520
+ box-shadow: 0 0 18px rgba(45, 212, 191, .8);
8236
8521
  }
8237
8522
  .dxr-orbit::before { top: -5px; left: 50%; }
8238
- .dxr-orbit::after { right: 7%; bottom: 12%; background: #b26cff; box-shadow: 0 0 18px #b26cff; }
8523
+ .dxr-orbit::after { right: 7%; bottom: 12%; background: #5eead4; box-shadow: 0 0 18px rgba(94, 234, 212, .7); }
8239
8524
  .dxr-card {
8240
8525
  position: absolute;
8241
8526
  inset: 24% 28%;
8242
8527
  padding: 20px 15px;
8243
- border: 1px solid rgba(255, 255, 255, .33);
8528
+ border: 1px solid rgba(255, 255, 255, .3);
8244
8529
  border-radius: 14px;
8245
- background: linear-gradient(150deg, rgba(255, 255, 255, .18), rgba(255, 255, 255, .06));
8246
- box-shadow: 0 22px 50px rgba(0, 0, 0, .35), 0 0 45px rgba(71, 150, 255, .13);
8530
+ background: linear-gradient(150deg, rgba(255, 255, 255, .16), rgba(255, 255, 255, .05));
8531
+ box-shadow: 0 22px 50px rgba(0, 0, 0, .35), 0 0 45px rgba(13, 148, 136, .15);
8247
8532
  backdrop-filter: blur(10px);
8248
8533
  animation: dxr-float 3.6s ease-in-out infinite;
8249
8534
  }
@@ -8253,8 +8538,8 @@ var DocxodusEditor = (() => {
8253
8538
  right: -9px;
8254
8539
  padding: 4px 7px;
8255
8540
  border-radius: 6px;
8256
- background: #52e5ff;
8257
- color: #06111d;
8541
+ background: #2dd4bf;
8542
+ color: #042f2e;
8258
8543
  content: "DOCX";
8259
8544
  font-size: 8px;
8260
8545
  font-weight: 900;
@@ -8271,46 +8556,49 @@ var DocxodusEditor = (() => {
8271
8556
  }
8272
8557
  .dxr-card i:nth-child(2) { width: 76%; animation-delay: -.45s; }
8273
8558
  .dxr-card i:nth-child(3) { width: 88%; animation-delay: -.85s; }
8274
- .dxr-card i:nth-child(4) { width: 58%; background: rgba(255, 111, 139, .75); animation-delay: -1.2s; }
8559
+ .dxr-card i:nth-child(4) { width: 58%; background: rgba(248, 113, 113, .75); animation-delay: -1.2s; }
8275
8560
  .dxr-chip {
8276
8561
  position: absolute;
8277
8562
  display: grid;
8278
8563
  width: 34px;
8279
8564
  height: 34px;
8280
8565
  place-items: center;
8281
- border: 1px solid rgba(255, 255, 255, .17);
8566
+ border: 1px solid rgba(255, 255, 255, .16);
8282
8567
  border-radius: 11px;
8283
- background: rgba(12, 28, 49, .88);
8568
+ background: rgba(15, 30, 46, .88);
8284
8569
  box-shadow: 0 12px 30px rgba(0, 0, 0, .28);
8285
8570
  font-size: 11px;
8286
8571
  font-weight: 850;
8287
8572
  }
8288
- .dxr-chip.dxr-b { left: 2%; top: 30%; color: #52e5ff; animation: dxr-chip 3s ease-in-out infinite; }
8289
- .dxr-chip.dxr-r { right: -2%; bottom: 24%; color: #ff8da0; animation: dxr-chip 3s -1.4s ease-in-out infinite; }
8290
- .dxr-chip.dxr-s { left: 20%; bottom: 0; color: #52e0a2; animation: dxr-chip 3s -.7s ease-in-out infinite; }
8291
- .dxr-eyebrow { color: #52e5ff; font-size: 10px; font-weight: 850; letter-spacing: .16em; text-transform: uppercase; }
8573
+ .dxr-chip.dxr-b { left: 2%; top: 30%; color: #5eead4; animation: dxr-chip 3s ease-in-out infinite; }
8574
+ .dxr-chip.dxr-r { right: -2%; bottom: 24%; color: #fca5a5; animation: dxr-chip 3s -1.4s ease-in-out infinite; }
8575
+ .dxr-chip.dxr-s { left: 20%; bottom: 0; color: #6ee7b7; animation: dxr-chip 3s -.7s ease-in-out infinite; }
8576
+ .dxr-eyebrow { color: #5eead4; font-size: 10px; font-weight: 850; letter-spacing: .16em; text-transform: uppercase; }
8577
+ /* Georgia headline \u2014 the house pairing of a serif headline over Inter UI copy. */
8292
8578
  .dxr-loader h2 {
8293
8579
  margin: 10px auto 10px;
8294
8580
  max-width: 520px;
8295
- font-size: clamp(23px, 5vw, 40px);
8296
- line-height: 1.05;
8297
- letter-spacing: -.045em;
8581
+ font-family: var(--dxr-serif);
8582
+ font-size: clamp(23px, 5vw, 38px);
8583
+ font-weight: 500;
8584
+ line-height: 1.08;
8585
+ letter-spacing: -.02em;
8298
8586
  }
8299
- .dxr-loader-copy > p { margin: 0 auto; max-width: 46ch; color: #9eb2c9; font-size: 13.5px; line-height: 1.6; }
8587
+ .dxr-loader-copy > p { margin: 0 auto; max-width: 46ch; color: #94a3b8; font-size: 13.5px; line-height: 1.6; }
8300
8588
  .dxr-ad {
8301
8589
  display: flex;
8302
8590
  gap: 12px;
8303
8591
  margin: 20px auto 0;
8304
8592
  max-width: 420px;
8305
8593
  padding: 13px;
8306
- border: 1px solid rgba(126, 160, 200, .16);
8307
- border-radius: 13px;
8594
+ border: 1px solid rgba(148, 163, 184, .16);
8595
+ border-radius: 12px;
8308
8596
  background: rgba(255, 255, 255, .04);
8309
8597
  text-align: left;
8310
8598
  }
8311
- .dxr-ad .dxr-num { color: #b26cff; font: 800 10px/1.4 var(--dxr-mono); }
8599
+ .dxr-ad .dxr-num { color: #2dd4bf; font: 800 10px/1.4 var(--dxr-mono); }
8312
8600
  .dxr-ad strong { display: block; font-size: 12.5px; }
8313
- .dxr-ad .dxr-adcopy { display: block; margin-top: 4px; color: #849bb5; font-size: 11.5px; line-height: 1.45; }
8601
+ .dxr-ad .dxr-adcopy { display: block; margin-top: 4px; color: #8ba0b9; font-size: 11.5px; line-height: 1.45; }
8314
8602
  .dxr-ad[data-swap] { animation: dxr-swap .4s ease; }
8315
8603
  .dxr-track {
8316
8604
  height: 3px;
@@ -8324,8 +8612,8 @@ var DocxodusEditor = (() => {
8324
8612
  width: 12%;
8325
8613
  height: 100%;
8326
8614
  border-radius: inherit;
8327
- background: linear-gradient(90deg, #52e5ff, #5c7cff, #b26cff);
8328
- box-shadow: 0 0 16px rgba(82, 229, 255, .55);
8615
+ background: linear-gradient(90deg, #14b8a6, #2dd4bf, #5eead4);
8616
+ box-shadow: 0 0 16px rgba(45, 212, 191, .5);
8329
8617
  transition: width .65s cubic-bezier(.22, .8, .28, 1);
8330
8618
  }
8331
8619
  .dxr-meta {
@@ -8334,7 +8622,7 @@ var DocxodusEditor = (() => {
8334
8622
  gap: 14px;
8335
8623
  margin: 9px auto 0;
8336
8624
  max-width: 420px;
8337
- color: #647e9c;
8625
+ color: #64748b;
8338
8626
  font: 700 9px/1 var(--dxr-mono);
8339
8627
  letter-spacing: .09em;
8340
8628
  text-transform: uppercase;
@@ -8343,9 +8631,9 @@ var DocxodusEditor = (() => {
8343
8631
  display: none;
8344
8632
  margin-top: 18px;
8345
8633
  padding: 9px 14px;
8346
- border: 1px solid rgba(255, 127, 145, .4);
8634
+ border: 1px solid rgba(248, 113, 113, .4);
8347
8635
  border-radius: 9px;
8348
- background: rgba(255, 127, 145, .12);
8636
+ background: rgba(248, 113, 113, .12);
8349
8637
  color: #fff;
8350
8638
  cursor: pointer;
8351
8639
  }
@@ -8460,8 +8748,8 @@ var DocxodusEditor = (() => {
8460
8748
  )}</button>
8461
8749
  </div>
8462
8750
  <div class="dxr-row">
8463
- <button type="button" data-list="bullet" title="Bullet list">&#8226; List</button>
8464
- <button type="button" data-list="decimal" title="Numbered list">1. List</button>
8751
+ <button type="button" data-list="bullet" title="Bullet list">Bullets</button>
8752
+ <button type="button" data-list="decimal" title="Numbered list">Numbered</button>
8465
8753
  <button type="button" data-pagebreak title="Start this block on a new page">Page break</button>
8466
8754
  </div>
8467
8755
  </div>
@@ -8784,6 +9072,7 @@ var DocxodusEditor = (() => {
8784
9072
  const root = doc.createElement("div");
8785
9073
  root.className = "dxr";
8786
9074
  root.dataset.state = "idle";
9075
+ root.dataset.frame = this.options.frame ?? "flush";
8787
9076
  root.innerHTML = RIBBON_HTML;
8788
9077
  for (const el of Array.from(root.querySelectorAll("[data-dxr]"))) {
8789
9078
  el.id = this.idPrefix + el.dataset.dxr;