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
@@ -1450,7 +1450,7 @@ var Docxodus = (() => {
1450
1450
  var DEFAULT_PAGE_WIDTH = 612;
1451
1451
  var DEFAULT_PAGE_HEIGHT = 792;
1452
1452
  var DEFAULT_MARGIN = 72;
1453
- var DEFAULT_HEADER_FOOTER_HEIGHT = 36;
1453
+ var DEFAULT_HEADER_FOOTER_DISTANCE = 36;
1454
1454
  function pxToPt(px) {
1455
1455
  return px * 0.75;
1456
1456
  }
@@ -1466,8 +1466,8 @@ var Docxodus = (() => {
1466
1466
  const marginRight = parseFloat(section.dataset.marginRight || "") || DEFAULT_MARGIN;
1467
1467
  const marginBottom = parseFloat(section.dataset.marginBottom || "") || DEFAULT_MARGIN;
1468
1468
  const marginLeft = parseFloat(section.dataset.marginLeft || "") || DEFAULT_MARGIN;
1469
- const headerHeight = parseFloat(section.dataset.headerHeight || "") || DEFAULT_HEADER_FOOTER_HEIGHT;
1470
- const footerHeight = parseFloat(section.dataset.footerHeight || "") || DEFAULT_HEADER_FOOTER_HEIGHT;
1469
+ const headerDistance = parseFloat(section.dataset.headerHeight || "") || DEFAULT_HEADER_FOOTER_DISTANCE;
1470
+ const footerDistance = parseFloat(section.dataset.footerHeight || "") || DEFAULT_HEADER_FOOTER_DISTANCE;
1471
1471
  return {
1472
1472
  pageWidth,
1473
1473
  pageHeight,
@@ -1477,8 +1477,37 @@ var Docxodus = (() => {
1477
1477
  marginRight,
1478
1478
  marginBottom,
1479
1479
  marginLeft,
1480
- headerHeight,
1481
- footerHeight
1480
+ headerDistance,
1481
+ footerDistance,
1482
+ headerHeight: headerDistance,
1483
+ footerHeight: footerDistance
1484
+ };
1485
+ }
1486
+ var MIN_BODY_BAND_PT = 12;
1487
+ function resolvePageBands(dims, headerContentHeight, footerContentHeight) {
1488
+ const headerTop = dims.headerDistance;
1489
+ const footerBottom = dims.pageHeight - dims.footerDistance;
1490
+ const headerReach = headerContentHeight > 0 ? headerTop + headerContentHeight : 0;
1491
+ const footerReach = footerContentHeight > 0 ? footerBottom - footerContentHeight : dims.pageHeight;
1492
+ let bodyTop = Math.max(dims.marginTop, headerReach);
1493
+ let bodyBottom = Math.min(dims.pageHeight - dims.marginBottom, footerReach);
1494
+ const shortfall = MIN_BODY_BAND_PT - (bodyBottom - bodyTop);
1495
+ if (shortfall > 0) {
1496
+ const headerGrowth = bodyTop - dims.marginTop;
1497
+ const footerGrowth = dims.pageHeight - dims.marginBottom - bodyBottom;
1498
+ const growth = headerGrowth + footerGrowth;
1499
+ if (growth > 0) {
1500
+ bodyTop -= shortfall * headerGrowth / growth;
1501
+ bodyBottom += shortfall * footerGrowth / growth;
1502
+ }
1503
+ }
1504
+ return {
1505
+ headerTop,
1506
+ headerHeight: Math.max(0, Math.min(headerContentHeight, bodyTop - headerTop)),
1507
+ bodyTop,
1508
+ bodyHeight: Math.max(0, bodyBottom - bodyTop),
1509
+ footerTop: footerBottom - Math.max(0, Math.min(footerContentHeight, footerBottom - bodyBottom)),
1510
+ footerHeight: Math.max(0, Math.min(footerContentHeight, footerBottom - bodyBottom))
1482
1511
  };
1483
1512
  }
1484
1513
  function sectionWrappers(root) {
@@ -1755,9 +1784,9 @@ var Docxodus = (() => {
1755
1784
  */
1756
1785
  smallestEffectiveContentHeight(dims, sectionIndex) {
1757
1786
  return Math.min(
1758
- this.getEffectiveHeights(dims, sectionIndex, 1, 1).contentHeight,
1759
- this.getEffectiveHeights(dims, sectionIndex, 2, 1).contentHeight,
1760
- this.getEffectiveHeights(dims, sectionIndex, 2, 2).contentHeight
1787
+ this.getPageBands(dims, sectionIndex, 1, 1).bodyHeight,
1788
+ this.getPageBands(dims, sectionIndex, 2, 1).bodyHeight,
1789
+ this.getPageBands(dims, sectionIndex, 2, 2).bodyHeight
1761
1790
  );
1762
1791
  }
1763
1792
  /**
@@ -2281,7 +2310,7 @@ var Docxodus = (() => {
2281
2310
  /**
2282
2311
  * Adds footnotes to a page container, including continuation content.
2283
2312
  */
2284
- addPageFootnotes(pageBox, footnoteIds, dims, footnoteHeight, continuation, partialFootnotes) {
2313
+ addPageFootnotes(pageBox, footnoteIds, dims, bands, footnoteHeight, continuation, partialFootnotes) {
2285
2314
  const hasContinuation = continuation && continuation.remainingElements.length > 0;
2286
2315
  if (footnoteIds.length === 0 && !hasContinuation) {
2287
2316
  return;
@@ -2292,12 +2321,12 @@ var Docxodus = (() => {
2292
2321
  const partialFootnoteIds = new Set(partialFootnotes?.map((p) => p.footnoteId) || []);
2293
2322
  const maxFootnoteHeight = Math.min(
2294
2323
  footnoteHeight,
2295
- dims.contentHeight * MAX_FOOTNOTE_AREA_RATIO
2324
+ bands.bodyHeight * MAX_FOOTNOTE_AREA_RATIO
2296
2325
  );
2297
2326
  const footnotesDiv = document.createElement("div");
2298
2327
  footnotesDiv.className = `${this.cssPrefix}footnotes`;
2299
2328
  footnotesDiv.style.position = "absolute";
2300
- footnotesDiv.style.bottom = `${dims.marginBottom}pt`;
2329
+ footnotesDiv.style.bottom = `${dims.pageHeight - (bands.bodyTop + bands.bodyHeight)}pt`;
2301
2330
  footnotesDiv.style.left = `${dims.marginLeft}pt`;
2302
2331
  footnotesDiv.style.width = `${dims.contentWidth}pt`;
2303
2332
  footnotesDiv.style.boxSizing = "border-box";
@@ -2371,50 +2400,51 @@ var Docxodus = (() => {
2371
2400
  return sectionHf.footerDefault;
2372
2401
  }
2373
2402
  /**
2374
- * Computes effective header, footer, and content heights for a specific page position.
2375
- * Uses pre-measured header/footer heights from the registry.
2376
- * This method is deterministic - same inputs always produce same outputs.
2377
- * This enables lazy loading compatibility since available height can be computed
2378
- * for any page position without knowing the page's content.
2403
+ * The header, body, and footer bands for one page position.
2404
+ *
2405
+ * The single owner of "where does anything sit vertically on this page" — placement in
2406
+ * {@link createPage}, the body budget the flow loop spends, and the note area's anchor all
2407
+ * read it, so those three cannot disagree about where the body ends.
2408
+ *
2409
+ * Deterministic: it depends only on the section's page setup and the registry's pre-measured
2410
+ * story heights, never on the page's content, which is what keeps it lazy-loading compatible.
2379
2411
  */
2380
- getEffectiveHeights(dims, sectionIndex, pageInSection, globalPageNumber) {
2412
+ getPageBands(dims, sectionIndex, pageInSection, globalPageNumber) {
2381
2413
  const sectionHf = this.hfRegistry.get(sectionIndex);
2382
- let headerHeight = dims.marginTop;
2383
- if (sectionHf) {
2384
- let measuredHeaderHeight;
2385
- if (pageInSection === 1 && sectionHf.headerFirstHeight != null) {
2386
- measuredHeaderHeight = sectionHf.headerFirstHeight;
2387
- } else if (globalPageNumber % 2 === 0 && sectionHf.headerEvenHeight != null) {
2388
- measuredHeaderHeight = sectionHf.headerEvenHeight;
2389
- } else if (sectionHf.headerDefaultHeight != null) {
2390
- measuredHeaderHeight = sectionHf.headerDefaultHeight;
2391
- }
2392
- if (measuredHeaderHeight != null) {
2393
- headerHeight = Math.max(dims.marginTop, measuredHeaderHeight);
2394
- }
2395
- }
2396
- let footerHeight = dims.marginBottom;
2397
- if (sectionHf) {
2398
- let measuredFooterHeight;
2399
- if (pageInSection === 1 && sectionHf.footerFirstHeight != null) {
2400
- measuredFooterHeight = sectionHf.footerFirstHeight;
2401
- } else if (globalPageNumber % 2 === 0 && sectionHf.footerEvenHeight != null) {
2402
- measuredFooterHeight = sectionHf.footerEvenHeight;
2403
- } else if (sectionHf.footerDefaultHeight != null) {
2404
- measuredFooterHeight = sectionHf.footerDefaultHeight;
2405
- }
2406
- if (measuredFooterHeight != null) {
2407
- footerHeight = Math.max(dims.marginBottom, measuredFooterHeight);
2408
- }
2409
- }
2410
- const headerExpansion = headerHeight - dims.marginTop;
2411
- const footerExpansion = footerHeight - dims.marginBottom;
2412
- const contentHeight = dims.contentHeight - headerExpansion - footerExpansion;
2413
- return { headerHeight, footerHeight, contentHeight };
2414
+ return resolvePageBands(
2415
+ dims,
2416
+ this.selectStoryHeight(
2417
+ sectionHf?.headerFirstHeight,
2418
+ sectionHf?.headerEvenHeight,
2419
+ sectionHf?.headerDefaultHeight,
2420
+ pageInSection,
2421
+ globalPageNumber
2422
+ ),
2423
+ this.selectStoryHeight(
2424
+ sectionHf?.footerFirstHeight,
2425
+ sectionHf?.footerEvenHeight,
2426
+ sectionHf?.footerDefaultHeight,
2427
+ pageInSection,
2428
+ globalPageNumber
2429
+ )
2430
+ );
2431
+ }
2432
+ /**
2433
+ * The measured height of the running story this page position selects, mirroring
2434
+ * {@link selectHeader}/{@link selectFooter}. Zero when the page has no such story.
2435
+ */
2436
+ selectStoryHeight(first, even, fallback, pageInSection, globalPageNumber) {
2437
+ if (pageInSection === 1 && first != null) return first;
2438
+ if (globalPageNumber % 2 === 0 && even != null) return even;
2439
+ return fallback ?? 0;
2414
2440
  }
2415
2441
  /**
2416
2442
  * Measures the content height of a header or footer element.
2417
- * This is needed because headers/footers can contain more content than fits in the margin area.
2443
+ *
2444
+ * This is what tells {@link resolvePageBands} whether the story stays inside its margin or
2445
+ * pushes the body, so it must measure the story ALONE — any padding added here would have to
2446
+ * be added to the rendered band too, and the two drifting apart is exactly how a header
2447
+ * silently starts overlapping body text.
2418
2448
  */
2419
2449
  measureHeaderFooterHeight(source, contentWidth) {
2420
2450
  const measureContainer = document.createElement("div");
@@ -2422,7 +2452,6 @@ var Docxodus = (() => {
2422
2452
  measureContainer.style.visibility = "hidden";
2423
2453
  measureContainer.style.width = `${contentWidth}pt`;
2424
2454
  measureContainer.style.left = "-9999px";
2425
- measureContainer.style.paddingBottom = "4pt";
2426
2455
  for (const child of Array.from(source.childNodes)) {
2427
2456
  measureContainer.appendChild(child.cloneNode(true));
2428
2457
  }
@@ -2442,7 +2471,7 @@ var Docxodus = (() => {
2442
2471
  let currentContent = [];
2443
2472
  let pageNumber = startPageNumber;
2444
2473
  let pageInSection = 1;
2445
- let { contentHeight: effectiveContentHeight } = this.getEffectiveHeights(
2474
+ let { bodyHeight: effectiveContentHeight } = this.getPageBands(
2446
2475
  dims,
2447
2476
  sectionIndex,
2448
2477
  pageInSection,
@@ -2477,8 +2506,8 @@ var Docxodus = (() => {
2477
2506
  pageNumber++;
2478
2507
  pageInSection++;
2479
2508
  currentContent = [];
2480
- const newHeights = this.getEffectiveHeights(dims, sectionIndex, pageInSection, pageNumber);
2481
- effectiveContentHeight = newHeights.contentHeight;
2509
+ const newBands = this.getPageBands(dims, sectionIndex, pageInSection, pageNumber);
2510
+ effectiveContentHeight = newBands.bodyHeight;
2482
2511
  remainingHeight = effectiveContentHeight;
2483
2512
  prevMarginBottomPt = 0;
2484
2513
  currentFootnoteIds = [];
@@ -2540,7 +2569,7 @@ var Docxodus = (() => {
2540
2569
  ) + additionalChainFootnoteHeight;
2541
2570
  const currentAvailableHeight = remainingHeight - currentFootnoteHeight;
2542
2571
  if (currentChainHeight > currentAvailableHeight) {
2543
- const nextPageHeights = this.getEffectiveHeights(
2572
+ const nextPageBands = this.getPageBands(
2544
2573
  dims,
2545
2574
  sectionIndex,
2546
2575
  pageInSection + 1,
@@ -2556,7 +2585,7 @@ var Docxodus = (() => {
2556
2585
  dims.contentWidth,
2557
2586
  nextPageContinuation
2558
2587
  );
2559
- if (freshChainBodyHeight + freshChainFootnoteHeight <= nextPageHeights.contentHeight) {
2588
+ if (freshChainBodyHeight + freshChainFootnoteHeight <= nextPageBands.bodyHeight) {
2560
2589
  finishPage();
2561
2590
  }
2562
2591
  }
@@ -2725,6 +2754,161 @@ var Docxodus = (() => {
2725
2754
  if (el.getAttribute("contenteditable") !== null) el.setAttribute("contenteditable", "false");
2726
2755
  }
2727
2756
  }
2757
+ /**
2758
+ * Resolves floating DrawingML objects after their anchor paragraphs have landed on a page.
2759
+ *
2760
+ * The converter deliberately carries the OOXML bases and offsets as data instead of flattening
2761
+ * them into CSS: page/margin/column bases belong to the page, while paragraph/line/character
2762
+ * bases belong to the laid-out anchor. Once both coordinate systems exist, promote the object
2763
+ * from the clipped text column into the page box and position it in one shared point space.
2764
+ */
2765
+ positionDrawingAnchors(pageBox, contentArea, dims, pageNumber) {
2766
+ const anchors = Array.from(
2767
+ contentArea.querySelectorAll('[data-docx-drawing-anchor="true"]')
2768
+ );
2769
+ if (anchors.length === 0) return;
2770
+ const pageRect = pageBox.getBoundingClientRect();
2771
+ const pixelsPerPoint = pageRect.width / dims.pageWidth;
2772
+ if (!(pixelsPerPoint > 0)) return;
2773
+ for (const anchor of anchors) {
2774
+ const staticRect = anchor.getBoundingClientRect();
2775
+ const paragraph = anchor.closest("p, h1, h2, h3, h4, h5, h6");
2776
+ const paragraphRect = paragraph?.getBoundingClientRect() ?? staticRect;
2777
+ const toPageX = (x) => (x - pageRect.left) / pixelsPerPoint;
2778
+ const toPageY = (y) => (y - pageRect.top) / pixelsPerPoint;
2779
+ const context = {
2780
+ staticLeft: toPageX(staticRect.left),
2781
+ staticTop: toPageY(staticRect.top),
2782
+ lineHeight: paragraph ? (parseFloat(getComputedStyle(paragraph).lineHeight) || staticRect.height) / pixelsPerPoint : staticRect.height / pixelsPerPoint,
2783
+ paragraphLeft: toPageX(paragraphRect.left),
2784
+ paragraphTop: toPageY(paragraphRect.top),
2785
+ paragraphWidth: paragraphRect.width / pixelsPerPoint,
2786
+ paragraphHeight: paragraphRect.height / pixelsPerPoint
2787
+ };
2788
+ const widthReference = this.horizontalAnchorReference(
2789
+ anchor.getAttribute("data-docx-anchor-width-relative") ?? "margin",
2790
+ dims,
2791
+ pageNumber,
2792
+ context
2793
+ );
2794
+ const widthPercent = this.anchorNumber(anchor, "width-percent");
2795
+ if (widthPercent !== void 0) {
2796
+ anchor.style.width = `${widthReference.size * widthPercent / 100}pt`;
2797
+ }
2798
+ const heightReference = this.verticalAnchorReference(
2799
+ anchor.getAttribute("data-docx-anchor-height-relative") ?? "margin",
2800
+ dims,
2801
+ pageNumber,
2802
+ context
2803
+ );
2804
+ const heightPercent = this.anchorNumber(anchor, "height-percent");
2805
+ if (heightPercent !== void 0 && anchor.dataset.docxAnchorAutofit !== "true") {
2806
+ anchor.style.height = `${heightReference.size * heightPercent / 100}pt`;
2807
+ }
2808
+ const sizedRect = anchor.getBoundingClientRect();
2809
+ const width = sizedRect.width / pixelsPerPoint;
2810
+ const height = sizedRect.height / pixelsPerPoint;
2811
+ const horizontal2 = this.horizontalAnchorReference(
2812
+ anchor.getAttribute("data-docx-anchor-h-relative") ?? "column",
2813
+ dims,
2814
+ pageNumber,
2815
+ context
2816
+ );
2817
+ const vertical2 = this.verticalAnchorReference(
2818
+ anchor.getAttribute("data-docx-anchor-v-relative") ?? "paragraph",
2819
+ dims,
2820
+ pageNumber,
2821
+ context
2822
+ );
2823
+ const left2 = this.resolveAnchorAxis(
2824
+ horizontal2,
2825
+ width,
2826
+ anchor.getAttribute("data-docx-anchor-h-align"),
2827
+ this.anchorNumber(anchor, "h-offset"),
2828
+ pageNumber
2829
+ );
2830
+ const top2 = this.resolveAnchorAxis(
2831
+ vertical2,
2832
+ height,
2833
+ anchor.getAttribute("data-docx-anchor-v-align"),
2834
+ this.anchorNumber(anchor, "v-offset"),
2835
+ pageNumber
2836
+ );
2837
+ pageBox.appendChild(anchor);
2838
+ anchor.style.position = "absolute";
2839
+ anchor.style.left = `${left2}pt`;
2840
+ anchor.style.top = `${top2}pt`;
2841
+ anchor.style.margin = "0";
2842
+ }
2843
+ }
2844
+ anchorNumber(anchor, suffix) {
2845
+ const raw = anchor.getAttribute(`data-docx-anchor-${suffix}`);
2846
+ if (raw === null) return void 0;
2847
+ const value = Number(raw);
2848
+ return Number.isFinite(value) ? value : void 0;
2849
+ }
2850
+ horizontalAnchorReference(relativeFrom, dims, pageNumber, context) {
2851
+ const leftMargin = { start: 0, size: dims.marginLeft };
2852
+ const rightMargin = {
2853
+ start: dims.marginLeft + dims.contentWidth,
2854
+ size: dims.marginRight
2855
+ };
2856
+ switch (relativeFrom) {
2857
+ case "page":
2858
+ return { start: 0, size: dims.pageWidth };
2859
+ case "character":
2860
+ return { start: context.staticLeft, size: 0 };
2861
+ case "leftMargin":
2862
+ return leftMargin;
2863
+ case "rightMargin":
2864
+ return rightMargin;
2865
+ case "insideMargin":
2866
+ return pageNumber % 2 === 1 ? leftMargin : rightMargin;
2867
+ case "outsideMargin":
2868
+ return pageNumber % 2 === 1 ? rightMargin : leftMargin;
2869
+ case "paragraph":
2870
+ return { start: context.paragraphLeft, size: context.paragraphWidth };
2871
+ case "column":
2872
+ case "margin":
2873
+ default:
2874
+ return { start: dims.marginLeft, size: dims.contentWidth };
2875
+ }
2876
+ }
2877
+ verticalAnchorReference(relativeFrom, dims, pageNumber, context) {
2878
+ const topMargin = { start: 0, size: dims.marginTop };
2879
+ const bottomMargin = {
2880
+ start: dims.marginTop + dims.contentHeight,
2881
+ size: dims.marginBottom
2882
+ };
2883
+ switch (relativeFrom) {
2884
+ case "page":
2885
+ return { start: 0, size: dims.pageHeight };
2886
+ case "paragraph":
2887
+ return { start: context.paragraphTop, size: context.paragraphHeight };
2888
+ case "line":
2889
+ return { start: context.staticTop, size: context.lineHeight };
2890
+ case "topMargin":
2891
+ return topMargin;
2892
+ case "bottomMargin":
2893
+ return bottomMargin;
2894
+ case "insideMargin":
2895
+ return pageNumber % 2 === 1 ? topMargin : bottomMargin;
2896
+ case "outsideMargin":
2897
+ return pageNumber % 2 === 1 ? bottomMargin : topMargin;
2898
+ case "margin":
2899
+ default:
2900
+ return { start: dims.marginTop, size: dims.contentHeight };
2901
+ }
2902
+ }
2903
+ resolveAnchorAxis(reference, objectSize, alignment, offset, pageNumber) {
2904
+ if (offset !== void 0) return reference.start + offset;
2905
+ let fraction = 0;
2906
+ if (alignment === "center") fraction = 0.5;
2907
+ else if (alignment === "right" || alignment === "bottom") fraction = 1;
2908
+ else if (alignment === "inside") fraction = pageNumber % 2 === 1 ? 0 : 1;
2909
+ else if (alignment === "outside") fraction = pageNumber % 2 === 1 ? 1 : 0;
2910
+ return reference.start + (reference.size - objectSize) * fraction;
2911
+ }
2728
2912
  /**
2729
2913
  * Creates a page container element.
2730
2914
  */
@@ -2751,22 +2935,22 @@ var Docxodus = (() => {
2751
2935
  pageBox.dataset.pageNumber = String(pageNumber);
2752
2936
  pageBox.dataset.sectionIndex = String(sectionIndex);
2753
2937
  pageBox.dataset.pageInSection = String(pageInSection);
2754
- const effectiveHeights = this.getEffectiveHeights(dims, sectionIndex, pageInSection, pageNumber);
2938
+ const bands = this.getPageBands(dims, sectionIndex, pageInSection, pageNumber);
2755
2939
  const headerSource = this.selectHeader(sectionIndex, pageInSection, pageNumber);
2756
2940
  if (headerSource) {
2757
2941
  const headerDiv = document.createElement("div");
2758
2942
  headerDiv.className = `${this.cssPrefix}header`;
2759
2943
  headerDiv.style.position = "absolute";
2760
- headerDiv.style.top = "0";
2944
+ headerDiv.style.top = `${bands.headerTop}pt`;
2945
+ headerDiv.style.bottom = "auto";
2761
2946
  headerDiv.style.left = `${dims.marginLeft}pt`;
2762
2947
  headerDiv.style.width = `${dims.contentWidth}pt`;
2763
- headerDiv.style.height = `${effectiveHeights.headerHeight}pt`;
2948
+ headerDiv.style.height = `${bands.headerHeight}pt`;
2764
2949
  headerDiv.style.overflow = "hidden";
2765
2950
  headerDiv.style.boxSizing = "border-box";
2766
2951
  headerDiv.style.display = "flex";
2767
2952
  headerDiv.style.flexDirection = "column";
2768
- headerDiv.style.justifyContent = "flex-end";
2769
- headerDiv.style.paddingBottom = "4pt";
2953
+ headerDiv.style.justifyContent = "flex-start";
2770
2954
  for (const child of Array.from(headerSource.childNodes)) {
2771
2955
  const clonedheaderDiv = child.cloneNode(true);
2772
2956
  if (clonedheaderDiv.nodeType === 1) this.makeClonedStoryInert(clonedheaderDiv);
@@ -2774,8 +2958,8 @@ var Docxodus = (() => {
2774
2958
  }
2775
2959
  pageBox.appendChild(headerDiv);
2776
2960
  }
2777
- const contentAreaTop = effectiveHeights.headerHeight;
2778
- const contentAreaHeight = effectiveHeights.contentHeight;
2961
+ const contentAreaTop = bands.bodyTop;
2962
+ const contentAreaHeight = bands.bodyHeight;
2779
2963
  const contentArea = document.createElement("div");
2780
2964
  contentArea.className = `${this.cssPrefix}content`;
2781
2965
  contentArea.style.position = "absolute";
@@ -2790,23 +2974,23 @@ var Docxodus = (() => {
2790
2974
  pageBox.appendChild(contentArea);
2791
2975
  const hasContinuation = continuation && continuation.remainingElements.length > 0;
2792
2976
  if (footnoteIds.length > 0 || hasContinuation) {
2793
- this.addPageFootnotes(pageBox, footnoteIds, dims, footnoteHeight, continuation, partialFootnotes);
2977
+ this.addPageFootnotes(pageBox, footnoteIds, dims, bands, footnoteHeight, continuation, partialFootnotes);
2794
2978
  }
2795
2979
  const footerSource = this.selectFooter(sectionIndex, pageInSection, pageNumber);
2796
2980
  if (footerSource) {
2797
2981
  const footerDiv = document.createElement("div");
2798
2982
  footerDiv.className = `${this.cssPrefix}footer`;
2799
2983
  footerDiv.style.position = "absolute";
2800
- footerDiv.style.bottom = "0";
2984
+ footerDiv.style.top = "auto";
2985
+ footerDiv.style.bottom = `${dims.footerDistance}pt`;
2801
2986
  footerDiv.style.left = `${dims.marginLeft}pt`;
2802
2987
  footerDiv.style.width = `${dims.contentWidth}pt`;
2803
- footerDiv.style.height = `${effectiveHeights.footerHeight}pt`;
2988
+ footerDiv.style.height = `${bands.footerHeight}pt`;
2804
2989
  footerDiv.style.overflow = "hidden";
2805
2990
  footerDiv.style.boxSizing = "border-box";
2806
2991
  footerDiv.style.display = "flex";
2807
2992
  footerDiv.style.flexDirection = "column";
2808
- footerDiv.style.justifyContent = "flex-start";
2809
- footerDiv.style.paddingTop = "4pt";
2993
+ footerDiv.style.justifyContent = "flex-end";
2810
2994
  for (const child of Array.from(footerSource.childNodes)) {
2811
2995
  const clonedfooterDiv = child.cloneNode(true);
2812
2996
  if (clonedfooterDiv.nodeType === 1) this.makeClonedStoryInert(clonedfooterDiv);
@@ -2821,6 +3005,7 @@ var Docxodus = (() => {
2821
3005
  pageBox.appendChild(pageNum);
2822
3006
  }
2823
3007
  this.containerElement.appendChild(pageBox);
3008
+ this.positionDrawingAnchors(pageBox, contentArea, dims, pageNumber);
2824
3009
  const notesEl = pageBox.querySelector(`.${this.cssPrefix}footnotes`);
2825
3010
  if (notesEl) {
2826
3011
  const notesHeightPt = pxToPt(notesEl.getBoundingClientRect().height);
@@ -6195,6 +6380,19 @@ var Docxodus = (() => {
6195
6380
  function isListBlock(block) {
6196
6381
  return !!block.querySelector(":scope > [data-list-marker]");
6197
6382
  }
6383
+ function ensureListMarkerSeparator(block) {
6384
+ const marker = block.querySelector(":scope > [data-list-marker]");
6385
+ if (!marker) return;
6386
+ const next = marker.nextElementSibling;
6387
+ if (next?.hasAttribute("data-editor-list-separator")) return;
6388
+ const separator = block.ownerDocument.createElement("span");
6389
+ separator.setAttribute("data-editor-list-separator", "");
6390
+ separator.setAttribute("data-list-marker", "true");
6391
+ separator.setAttribute("aria-hidden", "true");
6392
+ separator.style.display = "none";
6393
+ separator.textContent = " ";
6394
+ marker.after(separator);
6395
+ }
6198
6396
  var GENERATED_CHROME_SELECTOR = '[data-list-marker], a.footnote-ref, a.endnote-ref, a[class$="-backref"]';
6199
6397
  function isGeneratedChrome(node) {
6200
6398
  return node?.nodeType === 1 && !!node.matches?.(GENERATED_CHROME_SELECTOR);
@@ -7485,6 +7683,7 @@ var Docxodus = (() => {
7485
7683
  const unid = el.getAttribute("data-anchor");
7486
7684
  if (!unid || !this.anchorIdOf(el)) return;
7487
7685
  el.setAttribute("contenteditable", "true");
7686
+ ensureListMarkerSeparator(el);
7488
7687
  el.querySelectorAll(GENERATED_CHROME_SELECTOR).forEach((m) => m.setAttribute("contenteditable", "false"));
7489
7688
  el.dataset.committedText = blockContentText(el);
7490
7689
  el.addEventListener("focus", () => {
@@ -8347,8 +8546,8 @@ var Docxodus = (() => {
8347
8546
  * editable `data-anchor` blocks in the notes section, so editing it afterwards needs no new op.
8348
8547
  *
8349
8548
  * Body blocks only: Word disallows a note reference inside a header/footer story or inside
8350
- * another note, and the session rejects those with `AnchorWrongKind`. Remounts, because a new
8351
- * note renumbers the citations after it and can add a whole part.
8549
+ * another note, and the session rejects those with `AnchorWrongKind`. Reconciliation creates
8550
+ * the first notes section when needed and renumbers every affected citation in place.
8352
8551
  */
8353
8552
  insertFootnote(markdown = "New footnote.") {
8354
8553
  this.insertNote("footnote", markdown);
@@ -8740,7 +8939,7 @@ var Docxodus = (() => {
8740
8939
  * the current node — never a section div (multi-child) or the edit root. */
8741
8940
  unitWrapperOf(el) {
8742
8941
  let n = el;
8743
- while (n.parentElement && n.parentElement !== this.editRoot && n.parentElement.tagName === "DIV" && !n.parentElement.hasAttribute("data-anchor") && n.parentElement.childElementCount === 1) {
8942
+ 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) {
8744
8943
  n = n.parentElement;
8745
8944
  }
8746
8945
  return n;
@@ -8776,6 +8975,7 @@ var Docxodus = (() => {
8776
8975
  const pureRemoved = diff.removed.filter(
8777
8976
  (i) => !diff.substituted.some((s) => s.oldIndex === i) && !movedOld.has(i)
8778
8977
  );
8978
+ const appliedFresh = /* @__PURE__ */ new Set();
8779
8979
  const nodeAt = (j) => {
8780
8980
  const oi = diff.keep.get(j);
8781
8981
  if (oi !== void 0) return oldNodes[oi];
@@ -8783,7 +8983,7 @@ var Docxodus = (() => {
8783
8983
  if (movedOi !== void 0) return oldNodes[movedOi];
8784
8984
  if (subOldByNew.has(j)) return fresh.get(j);
8785
8985
  const f = fresh.get(j);
8786
- return f && f.isConnected ? f : null;
8986
+ return f && appliedFresh.has(j) ? f : null;
8787
8987
  };
8788
8988
  for (const j of pureAdded) {
8789
8989
  const el = fresh.get(j);
@@ -8798,6 +8998,7 @@ var Docxodus = (() => {
8798
8998
  if (prevW) prevW.after(el);
8799
8999
  else if (nextW) nextW.before(el);
8800
9000
  else return "insert with no anchored neighbor";
9001
+ appliedFresh.add(j);
8801
9002
  this.wireUnit(el, units[j]);
8802
9003
  }
8803
9004
  for (const i of pureRemoved) {
@@ -8836,13 +9037,13 @@ var Docxodus = (() => {
8836
9037
  if (anchorEl) this.wireBlock(anchorEl);
8837
9038
  root.querySelectorAll("[data-anchor]").forEach((b) => this.wireBlock(b));
8838
9039
  }
8839
- /** Old-sequence diff state for one notes section. `null` requests remount (DOM not
8840
- * stampable/consistent). */
9040
+ /** Old-sequence diff state for one notes section. `null` requests remount when an
9041
+ * existing list is not stampable; an absent list is the valid first-note case. */
8841
9042
  notesDiff(sectionClass, units) {
8842
9043
  const ol = this.editRoot.querySelector(`section.${sectionClass} > ol`);
8843
9044
  const lis = ol ? Array.from(ol.children).filter((c) => c.tagName === "LI") : [];
8844
9045
  if (units.length === 0 && lis.length === 0) return { lis, diff: diffUnits([], []) };
8845
- if (!ol) return null;
9046
+ if (!ol) return { lis, diff: diffUnits([], units) };
8846
9047
  const tokens = [];
8847
9048
  for (const li of lis) {
8848
9049
  const unid = li.getAttribute("data-note-anchor");
@@ -8859,7 +9060,20 @@ var Docxodus = (() => {
8859
9060
  this.editRoot.querySelector(`section.${sectionClass}`)?.remove();
8860
9061
  return;
8861
9062
  }
8862
- const ol = this.editRoot.querySelector(`section.${sectionClass} > ol`);
9063
+ let section = this.editRoot.querySelector(`:scope > section.${sectionClass}`);
9064
+ let ol = section?.querySelector(`:scope > ol`) ?? null;
9065
+ if (!ol) {
9066
+ if (!section) {
9067
+ section = document.createElement("section");
9068
+ section.className = sectionClass;
9069
+ const following = sectionClass === "footnotes" ? this.editRoot.querySelector(`:scope > section.endnotes`) : null;
9070
+ if (following) following.before(section);
9071
+ else this.editRoot.append(section);
9072
+ }
9073
+ if (!section.querySelector(":scope > hr")) section.prepend(document.createElement("hr"));
9074
+ ol = document.createElement("ol");
9075
+ section.append(ol);
9076
+ }
8863
9077
  const prefix = sectionClass === "footnotes" ? "fn" : "en";
8864
9078
  const nodes = [];
8865
9079
  for (let j = 0; j < units.length; j++) {
@@ -8996,23 +9210,38 @@ var Docxodus = (() => {
8996
9210
  };
8997
9211
 
8998
9212
  // src/ribbon-chrome.ts
8999
- var RIBBON_STYLE_VERSION = "5";
9213
+ var RIBBON_STYLE_VERSION = "8";
9000
9214
  var RIBBON_STYLE_ATTR = "data-docxodus-ribbon-styles";
9001
9215
  var RIBBON_CSS = `
9002
9216
  .dxr {
9003
- --dxr-ink: #101418;
9217
+ /* OS-Legal design tokens (github.com/Open-Source-Legal/OS-Legal-Style):
9218
+ deep-teal accent, slate foreground scale, warm neutral surfaces, and
9219
+ "light touch" shadows at 0.03-0.06 opacity \u2014 structure over decoration. */
9220
+ --dxr-ink: #1e293b;
9004
9221
  --dxr-sheet: #ffffff;
9005
- --dxr-chrome: #eef1f6;
9006
- --dxr-chrome-sunk: #e3e8f0;
9007
- --dxr-rule: #d3dae4;
9008
- --dxr-accent: #1f5fd0;
9009
- --dxr-wash: #dde8fb;
9010
- --dxr-muted: #5d6975;
9011
- --dxr-data: #0b6f6a;
9012
- --dxr-danger: #a3341f;
9013
- --dxr-desk: #dfe3ea;
9014
- --dxr-ui: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
9222
+ --dxr-chrome: #ffffff;
9223
+ --dxr-chrome-sunk: #f8fafc;
9224
+ --dxr-rule: #e2e8f0;
9225
+ --dxr-rule-strong: #cbd5e1;
9226
+ --dxr-accent: #0f766e;
9227
+ --dxr-accent-hover: #0d9488;
9228
+ --dxr-accent-active: #115e59;
9229
+ --dxr-wash: #f0fdfa;
9230
+ --dxr-wash-on: #ccfbf1;
9231
+ --dxr-muted: #475569;
9232
+ --dxr-faint: #94a3b8;
9233
+ --dxr-data: #0f766e;
9234
+ --dxr-danger: #dc2626;
9235
+ --dxr-danger-wash: #fef2f2;
9236
+ --dxr-desk: #f1f5f9;
9237
+ --dxr-ui: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
9238
+ --dxr-serif: Georgia, "Times New Roman", serif;
9015
9239
  --dxr-mono: ui-monospace, SFMono-Regular, "Cascadia Mono", Consolas, monospace;
9240
+ --dxr-shadow-sm: 0 1px 2px rgba(15, 23, 42, .04);
9241
+ --dxr-shadow-md: 0 1px 3px rgba(15, 23, 42, .06), 0 4px 6px rgba(15, 23, 42, .03);
9242
+ --dxr-shadow-lg: 0 4px 6px rgba(15, 23, 42, .05), 0 10px 15px rgba(15, 23, 42, .04);
9243
+ --dxr-shadow-xl: 0 8px 16px rgba(15, 23, 42, .06), 0 20px 25px rgba(15, 23, 42, .05);
9244
+ --dxr-ease: cubic-bezier(.4, 0, .2, 1);
9016
9245
  --dxr-tap: 30px;
9017
9246
 
9018
9247
  position: relative;
@@ -9028,6 +9257,18 @@ var Docxodus = (() => {
9028
9257
  }
9029
9258
  .dxr *, .dxr *::before, .dxr *::after { box-sizing: border-box; }
9030
9259
 
9260
+ /* \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
9261
+ A drop-in embed carries its OWN boundary instead of hoping the host clips it:
9262
+ rounded corners, a hairline, and the house elevation. overflow: clip keeps
9263
+ the sticky chrome, scrollbars, and the loading overlay inside the curve.
9264
+ Full-bleed hosts mount with data-frame="flush" and stay edge-to-edge. */
9265
+ .dxr[data-frame="card"] {
9266
+ border: 1px solid var(--dxr-rule);
9267
+ border-radius: 14px;
9268
+ box-shadow: var(--dxr-shadow-xl);
9269
+ overflow: clip;
9270
+ }
9271
+
9031
9272
  /* Touch devices get bigger hit targets everywhere the token is used. */
9032
9273
  @media (pointer: coarse) { .dxr { --dxr-tap: 40px; } }
9033
9274
 
@@ -9056,12 +9297,13 @@ var Docxodus = (() => {
9056
9297
  font-weight: 600;
9057
9298
  letter-spacing: -.01em;
9058
9299
  }
9300
+ /* A filled circle, echoing the OS-Legal "Node" motif rather than a diamond. */
9059
9301
  .dxr-brand .dxr-mark {
9060
9302
  flex: 0 0 auto;
9061
9303
  width: 9px;
9062
9304
  height: 9px;
9305
+ border-radius: 50%;
9063
9306
  background: var(--dxr-accent);
9064
- clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
9065
9307
  }
9066
9308
  .dxr-brand .dxr-docname {
9067
9309
  overflow: hidden;
@@ -9093,25 +9335,25 @@ var Docxodus = (() => {
9093
9335
  scrollbar-width: none;
9094
9336
  }
9095
9337
  .dxr-tabs::-webkit-scrollbar { display: none; }
9338
+ /* Line-variant tabs (the house Tabs default): no boxes, no fills \u2014 the selected
9339
+ tab is a 2px accent underline sitting on the panel's own hairline. */
9096
9340
  .dxr-tab {
9097
9341
  flex: 0 0 auto;
9098
- padding: 6px 15px 7px;
9099
- border: 1px solid transparent;
9100
- border-bottom: none;
9101
- border-radius: 5px 5px 0 0;
9342
+ padding: 7px 14px 8px;
9343
+ border: none;
9102
9344
  background: none;
9103
9345
  color: var(--dxr-muted);
9104
9346
  font: inherit;
9105
9347
  font-size: 12.5px;
9348
+ font-weight: 500;
9106
9349
  cursor: pointer;
9350
+ transition: color .15s var(--dxr-ease), box-shadow .15s var(--dxr-ease);
9107
9351
  }
9108
- .dxr-tab:hover { color: var(--dxr-ink); background: #e6ebf3; }
9352
+ .dxr-tab:hover { color: var(--dxr-ink); }
9109
9353
  .dxr-tab[aria-selected="true"] {
9110
9354
  color: var(--dxr-ink);
9111
9355
  font-weight: 600;
9112
- background: var(--dxr-chrome-sunk);
9113
- border-color: var(--dxr-rule);
9114
- box-shadow: inset 0 2px 0 var(--dxr-accent);
9356
+ box-shadow: inset 0 -2px 0 var(--dxr-accent);
9115
9357
  }
9116
9358
  /* Contextual tab \u2014 present only while the caret is inside a table. */
9117
9359
  .dxr-tab[data-contextual] { color: var(--dxr-accent); }
@@ -9141,7 +9383,7 @@ var Docxodus = (() => {
9141
9383
  .dxr-group:last-child { border-right: none; }
9142
9384
  .dxr-group:first-child { padding-left: 0; }
9143
9385
  .dxr-glabel {
9144
- color: var(--dxr-muted);
9386
+ color: var(--dxr-faint);
9145
9387
  font-size: 9.5px;
9146
9388
  font-weight: 600;
9147
9389
  letter-spacing: .11em;
@@ -9150,48 +9392,69 @@ var Docxodus = (() => {
9150
9392
  .dxr-row { display: flex; gap: 3px; align-items: center; }
9151
9393
 
9152
9394
  /* \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 */
9395
+ /* One button family, house-style: every control is a ghost \u2014 no borders, no
9396
+ fills, no per-button boxes. Hover paints a quiet slate wash; the active
9397
+ (toggled) state paints a teal one. Save alone is the flat primary, so the
9398
+ surface has exactly one accent-colored action. */
9153
9399
  .dxr button, .dxr label.dxr-btn {
9154
9400
  min-height: var(--dxr-tap);
9155
9401
  padding: 5px 9px;
9156
- border: 1px solid transparent;
9157
- border-radius: 4px;
9402
+ border: none;
9403
+ border-radius: 6px;
9158
9404
  background: none;
9159
9405
  color: var(--dxr-ink);
9160
9406
  font: inherit;
9161
9407
  font-size: 13px;
9408
+ font-weight: 500;
9162
9409
  line-height: 1.15;
9163
9410
  cursor: pointer;
9411
+ transition: background-color .15s var(--dxr-ease), color .15s var(--dxr-ease),
9412
+ box-shadow .15s var(--dxr-ease), transform .15s var(--dxr-ease);
9164
9413
  }
9165
9414
  .dxr label.dxr-btn { display: inline-flex; align-items: center; }
9166
- .dxr button:hover, .dxr label.dxr-btn:hover { background: #d6deea; }
9167
- .dxr button:active { background: #c8d3e3; }
9415
+ .dxr button:hover, .dxr label.dxr-btn:hover { background: #f1f5f9; }
9416
+ .dxr button:active { background: #e2e8f0; }
9168
9417
  .dxr button:disabled { opacity: .38; cursor: default; background: none; }
9169
- .dxr button.dxr-on { color: var(--dxr-accent); background: var(--dxr-wash); border-color: #b3ccf2; }
9170
- .dxr-quick button, .dxr-quick label.dxr-btn {
9171
- padding: 4px 10px;
9172
- border-color: var(--dxr-rule);
9173
- background: #f7f9fc;
9174
- font-size: 12.5px;
9418
+ .dxr button.dxr-on { color: var(--dxr-accent); background: var(--dxr-wash-on); }
9419
+ .dxr-quick button, .dxr-quick label.dxr-btn { padding: 4px 10px; font-size: 12.5px; }
9420
+ /* Save \u2014 the house primary: flat accent, sm shadow, the 1px lift on hover. */
9421
+ .dxr-quick button[data-dxr="save"]:not(:disabled) {
9422
+ background: var(--dxr-accent);
9423
+ color: #fff;
9424
+ font-weight: 600;
9425
+ box-shadow: var(--dxr-shadow-sm);
9426
+ }
9427
+ .dxr-quick button[data-dxr="save"]:not(:disabled):hover {
9428
+ background: var(--dxr-accent-hover);
9429
+ box-shadow: var(--dxr-shadow-md);
9430
+ transform: translateY(-1px);
9431
+ }
9432
+ .dxr-quick button[data-dxr="save"]:not(:disabled):active {
9433
+ background: var(--dxr-accent-active);
9434
+ box-shadow: var(--dxr-shadow-sm);
9435
+ transform: translateY(0);
9175
9436
  }
9176
- .dxr-quick button:hover, .dxr-quick label.dxr-btn:hover { background: #fff; border-color: #b9c4d4; }
9177
- .dxr-quick button:disabled:hover { background: #f7f9fc; border-color: var(--dxr-rule); }
9178
- .dxr button.dxr-icon { min-width: var(--dxr-tap); text-align: center; }
9437
+ /* Icon controls sit quieter than text ones until touched (house IconButton). */
9438
+ .dxr button.dxr-icon { min-width: var(--dxr-tap); text-align: center; color: var(--dxr-muted); }
9439
+ .dxr button.dxr-icon:hover:not(:disabled) { color: var(--dxr-ink); }
9440
+ .dxr button.dxr-icon.dxr-on { color: var(--dxr-accent); }
9179
9441
  /* Icons are inline SVG drawn from the same shapes the control acts on (text lines,
9180
9442
  rules), so alignment and indent read at a glance instead of relying on arrow
9181
9443
  glyphs that collide with undo/redo. currentColor keeps them in step with state. */
9182
9444
  .dxr button svg { display: block; margin: 0 auto; fill: currentColor; }
9183
9445
  .dxr button.dxr-wide { display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
9184
- .dxr button.dxr-danger:hover { color: var(--dxr-danger); background: #f6dcd6; }
9446
+ .dxr button.dxr-danger:hover { color: var(--dxr-danger); background: var(--dxr-danger-wash); }
9185
9447
  .dxr select, .dxr input[type="number"], .dxr input[type="text"] {
9186
9448
  min-height: var(--dxr-tap);
9187
9449
  padding: 4px 6px;
9188
9450
  border: 1px solid var(--dxr-rule);
9189
- border-radius: 4px;
9190
- background: #f7f9fc;
9451
+ border-radius: 6px;
9452
+ background: var(--dxr-sheet);
9191
9453
  color: var(--dxr-ink);
9192
9454
  font: inherit;
9193
9455
  font-size: 12.5px;
9194
9456
  }
9457
+ .dxr select:hover, .dxr input:hover { border-color: var(--dxr-rule-strong); }
9195
9458
  .dxr select:focus-visible, .dxr input:focus-visible, .dxr button:focus-visible,
9196
9459
  .dxr .dxr-tab:focus-visible, .dxr label.dxr-btn:focus-within {
9197
9460
  outline: 2px solid var(--dxr-accent);
@@ -9222,7 +9485,7 @@ var Docxodus = (() => {
9222
9485
  height: 25px;
9223
9486
  padding: 0 10px;
9224
9487
  overflow-x: auto;
9225
- background: #f7f9fc;
9488
+ background: #fafafa;
9226
9489
  border-top: 1px solid var(--dxr-rule);
9227
9490
  color: var(--dxr-muted);
9228
9491
  font-family: var(--dxr-mono);
@@ -9241,7 +9504,7 @@ var Docxodus = (() => {
9241
9504
  .dxr-rail .dxr-cell:first-child { padding-left: 0; }
9242
9505
  .dxr-rail .dxr-cell:last-child { border-right: none; }
9243
9506
  .dxr-rail .dxr-k {
9244
- color: #8b96a3;
9507
+ color: var(--dxr-faint);
9245
9508
  font-family: var(--dxr-ui);
9246
9509
  font-size: 9.5px;
9247
9510
  letter-spacing: .1em;
@@ -9249,7 +9512,7 @@ var Docxodus = (() => {
9249
9512
  }
9250
9513
  .dxr-rail .dxr-v { color: var(--dxr-data); }
9251
9514
  .dxr-rail .dxr-v.dxr-flash { animation: dxr-railflash .45s ease-out; }
9252
- @keyframes dxr-railflash { from { background: #b9ecdf; } to { background: transparent; } }
9515
+ @keyframes dxr-railflash { from { background: #ccfbf1; } to { background: transparent; } }
9253
9516
 
9254
9517
  /* \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 */
9255
9518
  .dxr-hint {
@@ -9264,8 +9527,8 @@ var Docxodus = (() => {
9264
9527
  .dxr-hint kbd {
9265
9528
  padding: 0 4px;
9266
9529
  border: 1px solid var(--dxr-rule);
9267
- border-radius: 3px;
9268
- background: #e8ecf3;
9530
+ border-radius: 4px;
9531
+ background: #f1f5f9;
9269
9532
  font-family: var(--dxr-mono);
9270
9533
  font-size: 11px;
9271
9534
  }
@@ -9280,13 +9543,34 @@ var Docxodus = (() => {
9280
9543
  SHOULD win.
9281
9544
  (No backticks in this file's comments: the stylesheet is a template literal.) */
9282
9545
  .dxr-scroll { flex: 1 1 auto; min-height: 0; overflow: auto; -webkit-overflow-scrolling: touch; }
9546
+ /* Soft scroll boundaries: content dissolves at the chrome and at the bottom edge
9547
+ instead of hard-clipping. Sticky gradient veils cost one paint layer each and
9548
+ never intercept input. */
9549
+ .dxr-scroll::before, .dxr-scroll::after {
9550
+ content: "";
9551
+ position: sticky;
9552
+ z-index: 3;
9553
+ display: block;
9554
+ height: 26px;
9555
+ pointer-events: none;
9556
+ }
9557
+ .dxr-scroll::before {
9558
+ top: 0;
9559
+ margin-bottom: -26px;
9560
+ background: linear-gradient(to bottom, var(--dxr-desk), transparent);
9561
+ }
9562
+ .dxr-scroll::after {
9563
+ bottom: 0;
9564
+ margin-top: -26px;
9565
+ background: linear-gradient(to top, var(--dxr-desk), transparent);
9566
+ }
9283
9567
  .dxr[data-chrome] .dxr-surface { margin: 26px auto; padding: 0 16px 96px; }
9284
9568
  .dxr-surface [contenteditable="true"]:focus {
9285
9569
  outline: 2px solid var(--dxr-accent);
9286
9570
  outline-offset: 2px;
9287
9571
  border-radius: 2px;
9288
9572
  }
9289
- @media (hover: hover) { .dxr-surface [contenteditable="true"]:hover { background: #f2f7ff; } }
9573
+ @media (hover: hover) { .dxr-surface [contenteditable="true"]:hover { background: var(--dxr-wash); } }
9290
9574
 
9291
9575
  /* Header/footer bands: stories live in their own OOXML parts outside the body,
9292
9576
  so they dock as their own regions. Styled from the same tokens as the ribbon
@@ -9298,9 +9582,10 @@ var Docxodus = (() => {
9298
9582
  margin: 0 auto 14px;
9299
9583
  padding: 9px 72px 13px;
9300
9584
  border: 1px solid var(--dxr-rule);
9301
- border-left: 2px solid #c2ccd9;
9302
- border-radius: 3px;
9585
+ border-left: 2px solid #5eead4;
9586
+ border-radius: 6px;
9303
9587
  background: var(--dxr-sheet);
9588
+ box-shadow: var(--dxr-shadow-sm);
9304
9589
  }
9305
9590
  .dxr-surface .docx-hf-band + .docx-body-flow { margin-top: 0; }
9306
9591
  .dxr-surface .docx-hf-band[data-hf-band="footer"] { margin: 14px auto 0; }
@@ -9319,8 +9604,8 @@ var Docxodus = (() => {
9319
9604
  .dxr-surface .docx-hf-chrome select, .dxr-surface .docx-hf-chrome input {
9320
9605
  padding: 3px 5px;
9321
9606
  border: 1px solid var(--dxr-rule);
9322
- border-radius: 4px;
9323
- background: #f7f9fc;
9607
+ border-radius: 6px;
9608
+ background: var(--dxr-sheet);
9324
9609
  color: var(--dxr-ink);
9325
9610
  font: inherit;
9326
9611
  font-family: var(--dxr-ui);
@@ -9334,22 +9619,22 @@ var Docxodus = (() => {
9334
9619
  .dxr-surface .docx-hf-warning {
9335
9620
  margin: 0 0 8px -60px;
9336
9621
  padding: 7px 9px;
9337
- border: 1px solid #e8d9a8;
9338
- border-left: 2px solid #c9a227;
9339
- border-radius: 3px;
9340
- background: #fdf6e3;
9341
- color: #6b5310;
9622
+ border: 1px solid #fde68a;
9623
+ border-left: 2px solid #d97706;
9624
+ border-radius: 6px;
9625
+ background: #fffbeb;
9626
+ color: #92400e;
9342
9627
  font-size: 12px;
9343
9628
  line-height: 1.45;
9344
9629
  }
9345
9630
  .dxr-surface .docx-hf-warning button {
9346
9631
  margin-left: 6px;
9347
9632
  padding: 2px 8px;
9348
- border-color: #e8d9a8;
9633
+ border-color: #fde68a;
9349
9634
  background: #fff;
9350
9635
  font-size: 12px;
9351
9636
  }
9352
- .dxr-surface .docx-hf-placeholder { color: #9aa4b1; font-style: italic; }
9637
+ .dxr-surface .docx-hf-placeholder { color: var(--dxr-faint); font-style: italic; }
9353
9638
  .dxr-surface .docx-hf-inherited {
9354
9639
  color: var(--dxr-muted);
9355
9640
  font-style: italic;
@@ -9369,7 +9654,7 @@ var Docxodus = (() => {
9369
9654
  padding: 56px 0;
9370
9655
  border-radius: 3px;
9371
9656
  background: var(--dxr-sheet);
9372
- box-shadow: 0 1px 3px rgba(16, 20, 24, .14), 0 8px 24px rgba(16, 20, 24, .05);
9657
+ box-shadow: 0 1px 3px rgba(15, 23, 42, .08), 0 12px 24px rgba(15, 23, 42, .06);
9373
9658
  }
9374
9659
 
9375
9660
  /* \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 */
@@ -9379,9 +9664,9 @@ var Docxodus = (() => {
9379
9664
  z-index: 30;
9380
9665
  padding: 9px;
9381
9666
  border: 1px solid var(--dxr-rule);
9382
- border-radius: 6px;
9667
+ border-radius: 12px;
9383
9668
  background: #fff;
9384
- box-shadow: 0 4px 16px rgba(16, 20, 24, .18);
9669
+ box-shadow: var(--dxr-shadow-xl);
9385
9670
  }
9386
9671
  .dxr-pop[data-open] { display: block; }
9387
9672
  .dxr-gridcells { display: grid; grid-template-columns: repeat(10, 16px); gap: 2px; }
@@ -9389,11 +9674,11 @@ var Docxodus = (() => {
9389
9674
  width: 16px;
9390
9675
  height: 16px;
9391
9676
  border: 1px solid var(--dxr-rule);
9392
- border-radius: 2px;
9393
- background: #f2f5f9;
9677
+ border-radius: 3px;
9678
+ background: var(--dxr-chrome-sunk);
9394
9679
  cursor: pointer;
9395
9680
  }
9396
- .dxr-gridcells div[data-on] { background: var(--dxr-wash); border-color: var(--dxr-accent); }
9681
+ .dxr-gridcells div[data-on] { background: #ccfbf1; border-color: var(--dxr-accent); }
9397
9682
  .dxr-popfoot {
9398
9683
  display: flex;
9399
9684
  justify-content: space-between;
@@ -9476,11 +9761,11 @@ var Docxodus = (() => {
9476
9761
  display: grid;
9477
9762
  place-items: center;
9478
9763
  padding: 24px;
9479
- color: #f4f9ff;
9764
+ color: #f8fafc;
9480
9765
  background:
9481
- radial-gradient(circle at 20% 20%, rgba(63, 128, 255, .24), transparent 22rem),
9482
- radial-gradient(circle at 82% 80%, rgba(176, 91, 255, .22), transparent 24rem),
9483
- linear-gradient(145deg, #071221 0%, #0b1930 52%, #101426 100%);
9766
+ radial-gradient(circle at 20% 20%, rgba(13, 148, 136, .2), transparent 22rem),
9767
+ radial-gradient(circle at 82% 80%, rgba(45, 212, 191, .1), transparent 24rem),
9768
+ linear-gradient(150deg, #0b1220 0%, #0f172a 55%, #0f2723 100%);
9484
9769
  transition: opacity .55s ease, visibility .55s ease;
9485
9770
  }
9486
9771
  .dxr-loader[hidden] { display: none; }
@@ -9499,14 +9784,14 @@ var Docxodus = (() => {
9499
9784
  .dxr-orbit {
9500
9785
  position: absolute;
9501
9786
  inset: 7%;
9502
- border: 1px solid rgba(115, 204, 255, .22);
9787
+ border: 1px solid rgba(45, 212, 191, .24);
9503
9788
  border-radius: 50%;
9504
9789
  animation: dxr-spin 9s linear infinite;
9505
9790
  }
9506
9791
  .dxr-orbit.dxr-two {
9507
9792
  inset: 20%;
9508
9793
  border-style: dashed;
9509
- border-color: rgba(196, 128, 255, .32);
9794
+ border-color: rgba(94, 234, 212, .3);
9510
9795
  animation-duration: 6s;
9511
9796
  animation-direction: reverse;
9512
9797
  }
@@ -9516,19 +9801,19 @@ var Docxodus = (() => {
9516
9801
  height: 10px;
9517
9802
  border-radius: 50%;
9518
9803
  content: "";
9519
- background: #52e5ff;
9520
- box-shadow: 0 0 18px #52e5ff;
9804
+ background: #2dd4bf;
9805
+ box-shadow: 0 0 18px rgba(45, 212, 191, .8);
9521
9806
  }
9522
9807
  .dxr-orbit::before { top: -5px; left: 50%; }
9523
- .dxr-orbit::after { right: 7%; bottom: 12%; background: #b26cff; box-shadow: 0 0 18px #b26cff; }
9808
+ .dxr-orbit::after { right: 7%; bottom: 12%; background: #5eead4; box-shadow: 0 0 18px rgba(94, 234, 212, .7); }
9524
9809
  .dxr-card {
9525
9810
  position: absolute;
9526
9811
  inset: 24% 28%;
9527
9812
  padding: 20px 15px;
9528
- border: 1px solid rgba(255, 255, 255, .33);
9813
+ border: 1px solid rgba(255, 255, 255, .3);
9529
9814
  border-radius: 14px;
9530
- background: linear-gradient(150deg, rgba(255, 255, 255, .18), rgba(255, 255, 255, .06));
9531
- box-shadow: 0 22px 50px rgba(0, 0, 0, .35), 0 0 45px rgba(71, 150, 255, .13);
9815
+ background: linear-gradient(150deg, rgba(255, 255, 255, .16), rgba(255, 255, 255, .05));
9816
+ box-shadow: 0 22px 50px rgba(0, 0, 0, .35), 0 0 45px rgba(13, 148, 136, .15);
9532
9817
  backdrop-filter: blur(10px);
9533
9818
  animation: dxr-float 3.6s ease-in-out infinite;
9534
9819
  }
@@ -9538,8 +9823,8 @@ var Docxodus = (() => {
9538
9823
  right: -9px;
9539
9824
  padding: 4px 7px;
9540
9825
  border-radius: 6px;
9541
- background: #52e5ff;
9542
- color: #06111d;
9826
+ background: #2dd4bf;
9827
+ color: #042f2e;
9543
9828
  content: "DOCX";
9544
9829
  font-size: 8px;
9545
9830
  font-weight: 900;
@@ -9556,46 +9841,49 @@ var Docxodus = (() => {
9556
9841
  }
9557
9842
  .dxr-card i:nth-child(2) { width: 76%; animation-delay: -.45s; }
9558
9843
  .dxr-card i:nth-child(3) { width: 88%; animation-delay: -.85s; }
9559
- .dxr-card i:nth-child(4) { width: 58%; background: rgba(255, 111, 139, .75); animation-delay: -1.2s; }
9844
+ .dxr-card i:nth-child(4) { width: 58%; background: rgba(248, 113, 113, .75); animation-delay: -1.2s; }
9560
9845
  .dxr-chip {
9561
9846
  position: absolute;
9562
9847
  display: grid;
9563
9848
  width: 34px;
9564
9849
  height: 34px;
9565
9850
  place-items: center;
9566
- border: 1px solid rgba(255, 255, 255, .17);
9851
+ border: 1px solid rgba(255, 255, 255, .16);
9567
9852
  border-radius: 11px;
9568
- background: rgba(12, 28, 49, .88);
9853
+ background: rgba(15, 30, 46, .88);
9569
9854
  box-shadow: 0 12px 30px rgba(0, 0, 0, .28);
9570
9855
  font-size: 11px;
9571
9856
  font-weight: 850;
9572
9857
  }
9573
- .dxr-chip.dxr-b { left: 2%; top: 30%; color: #52e5ff; animation: dxr-chip 3s ease-in-out infinite; }
9574
- .dxr-chip.dxr-r { right: -2%; bottom: 24%; color: #ff8da0; animation: dxr-chip 3s -1.4s ease-in-out infinite; }
9575
- .dxr-chip.dxr-s { left: 20%; bottom: 0; color: #52e0a2; animation: dxr-chip 3s -.7s ease-in-out infinite; }
9576
- .dxr-eyebrow { color: #52e5ff; font-size: 10px; font-weight: 850; letter-spacing: .16em; text-transform: uppercase; }
9858
+ .dxr-chip.dxr-b { left: 2%; top: 30%; color: #5eead4; animation: dxr-chip 3s ease-in-out infinite; }
9859
+ .dxr-chip.dxr-r { right: -2%; bottom: 24%; color: #fca5a5; animation: dxr-chip 3s -1.4s ease-in-out infinite; }
9860
+ .dxr-chip.dxr-s { left: 20%; bottom: 0; color: #6ee7b7; animation: dxr-chip 3s -.7s ease-in-out infinite; }
9861
+ .dxr-eyebrow { color: #5eead4; font-size: 10px; font-weight: 850; letter-spacing: .16em; text-transform: uppercase; }
9862
+ /* Georgia headline \u2014 the house pairing of a serif headline over Inter UI copy. */
9577
9863
  .dxr-loader h2 {
9578
9864
  margin: 10px auto 10px;
9579
9865
  max-width: 520px;
9580
- font-size: clamp(23px, 5vw, 40px);
9581
- line-height: 1.05;
9582
- letter-spacing: -.045em;
9866
+ font-family: var(--dxr-serif);
9867
+ font-size: clamp(23px, 5vw, 38px);
9868
+ font-weight: 500;
9869
+ line-height: 1.08;
9870
+ letter-spacing: -.02em;
9583
9871
  }
9584
- .dxr-loader-copy > p { margin: 0 auto; max-width: 46ch; color: #9eb2c9; font-size: 13.5px; line-height: 1.6; }
9872
+ .dxr-loader-copy > p { margin: 0 auto; max-width: 46ch; color: #94a3b8; font-size: 13.5px; line-height: 1.6; }
9585
9873
  .dxr-ad {
9586
9874
  display: flex;
9587
9875
  gap: 12px;
9588
9876
  margin: 20px auto 0;
9589
9877
  max-width: 420px;
9590
9878
  padding: 13px;
9591
- border: 1px solid rgba(126, 160, 200, .16);
9592
- border-radius: 13px;
9879
+ border: 1px solid rgba(148, 163, 184, .16);
9880
+ border-radius: 12px;
9593
9881
  background: rgba(255, 255, 255, .04);
9594
9882
  text-align: left;
9595
9883
  }
9596
- .dxr-ad .dxr-num { color: #b26cff; font: 800 10px/1.4 var(--dxr-mono); }
9884
+ .dxr-ad .dxr-num { color: #2dd4bf; font: 800 10px/1.4 var(--dxr-mono); }
9597
9885
  .dxr-ad strong { display: block; font-size: 12.5px; }
9598
- .dxr-ad .dxr-adcopy { display: block; margin-top: 4px; color: #849bb5; font-size: 11.5px; line-height: 1.45; }
9886
+ .dxr-ad .dxr-adcopy { display: block; margin-top: 4px; color: #8ba0b9; font-size: 11.5px; line-height: 1.45; }
9599
9887
  .dxr-ad[data-swap] { animation: dxr-swap .4s ease; }
9600
9888
  .dxr-track {
9601
9889
  height: 3px;
@@ -9609,8 +9897,8 @@ var Docxodus = (() => {
9609
9897
  width: 12%;
9610
9898
  height: 100%;
9611
9899
  border-radius: inherit;
9612
- background: linear-gradient(90deg, #52e5ff, #5c7cff, #b26cff);
9613
- box-shadow: 0 0 16px rgba(82, 229, 255, .55);
9900
+ background: linear-gradient(90deg, #14b8a6, #2dd4bf, #5eead4);
9901
+ box-shadow: 0 0 16px rgba(45, 212, 191, .5);
9614
9902
  transition: width .65s cubic-bezier(.22, .8, .28, 1);
9615
9903
  }
9616
9904
  .dxr-meta {
@@ -9619,7 +9907,7 @@ var Docxodus = (() => {
9619
9907
  gap: 14px;
9620
9908
  margin: 9px auto 0;
9621
9909
  max-width: 420px;
9622
- color: #647e9c;
9910
+ color: #64748b;
9623
9911
  font: 700 9px/1 var(--dxr-mono);
9624
9912
  letter-spacing: .09em;
9625
9913
  text-transform: uppercase;
@@ -9628,9 +9916,9 @@ var Docxodus = (() => {
9628
9916
  display: none;
9629
9917
  margin-top: 18px;
9630
9918
  padding: 9px 14px;
9631
- border: 1px solid rgba(255, 127, 145, .4);
9919
+ border: 1px solid rgba(248, 113, 113, .4);
9632
9920
  border-radius: 9px;
9633
- background: rgba(255, 127, 145, .12);
9921
+ background: rgba(248, 113, 113, .12);
9634
9922
  color: #fff;
9635
9923
  cursor: pointer;
9636
9924
  }
@@ -9745,8 +10033,8 @@ var Docxodus = (() => {
9745
10033
  )}</button>
9746
10034
  </div>
9747
10035
  <div class="dxr-row">
9748
- <button type="button" data-list="bullet" title="Bullet list">&#8226; List</button>
9749
- <button type="button" data-list="decimal" title="Numbered list">1. List</button>
10036
+ <button type="button" data-list="bullet" title="Bullet list">Bullets</button>
10037
+ <button type="button" data-list="decimal" title="Numbered list">Numbered</button>
9750
10038
  <button type="button" data-pagebreak title="Start this block on a new page">Page break</button>
9751
10039
  </div>
9752
10040
  </div>
@@ -10069,6 +10357,7 @@ var Docxodus = (() => {
10069
10357
  const root = doc.createElement("div");
10070
10358
  root.className = "dxr";
10071
10359
  root.dataset.state = "idle";
10360
+ root.dataset.frame = this.options.frame ?? "flush";
10072
10361
  root.innerHTML = RIBBON_HTML;
10073
10362
  for (const el of Array.from(root.querySelectorAll("[data-dxr]"))) {
10074
10363
  el.id = this.idPrefix + el.dataset.dxr;
@@ -12138,6 +12427,9 @@ ${parsed.documentElement.outerHTML}`;
12138
12427
  mount2.root.style.minHeight = "0";
12139
12428
  const ribbon = mountRibbon(mount2.root, {
12140
12429
  documentName: ribbonOptions.documentName ?? nameFromSource(source),
12430
+ // A drop-in embed sits in the middle of someone's page, so it carries its own
12431
+ // boundary (rounded card + shadow). Full-bleed hosts pass frame: "flush".
12432
+ frame: "card",
12141
12433
  ...ribbonOptions,
12142
12434
  // Exports arrive after the runtime boots; the loader covers that gap.
12143
12435
  exports: void 0