@sobree/core 0.1.2 → 0.1.3

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.
@@ -17,3 +17,15 @@ export declare function resolveStyleCascade(styles: readonly NamedStyle[] | Sobr
17
17
  runDefaults: RunProperties;
18
18
  paragraphDefaults: ParagraphProperties;
19
19
  };
20
+ /**
21
+ * Resolve a RUN character style (`<w:rStyle>`) to the run properties it
22
+ * contributes: its own rPr merged up its `basedOn` chain — but WITHOUT
23
+ * the Normal / DocDefaults anchor that `resolveStyleCascade` appends.
24
+ *
25
+ * A character style layers on top of the run's INHERITED paragraph
26
+ * formatting; folding the document defaults back in here would reset the
27
+ * run's font / size to the doc default (e.g. a colour-only "Blue" char
28
+ * style must not drag Times/12pt onto a Helvetica/10pt contact line). So
29
+ * we walk only the explicit `basedOn` chain and stop — no Normal anchor.
30
+ */
31
+ export declare function resolveRunStyle(styles: readonly NamedStyle[], styleId: string): RunProperties;
@@ -138,6 +138,20 @@ export interface AnchoredFrame {
138
138
  * floats over text and reserves nothing. Absent ⇒ unknown (treated
139
139
  * as non-displacing). */
140
140
  wrap?: "square" | "topAndBottom" | "tight" | "through" | "none";
141
+ /** `wrapText` side from `<wp:wrapSquare|Tight|Through wrapText="…">` —
142
+ * which sides of the frame body text flows on. Default `bothSides`.
143
+ * Only meaningful for the displacing wrap modes; drives whether a
144
+ * floated image goes `float: left` (text on the right) or `right`. */
145
+ wrapText?: "bothSides" | "left" | "right" | "largest";
146
+ /** Text-distance insets — `distT/B/L/R` on `<wp:anchor>`, in EMU. The
147
+ * gap Word keeps between the frame and the text wrapping around it;
148
+ * rendered as margins on the floated frame. */
149
+ textDistancesEmu?: {
150
+ topEmu: number;
151
+ rightEmu: number;
152
+ bottomEmu: number;
153
+ leftEmu: number;
154
+ };
141
155
  /** What this frame contains. */
142
156
  content: AnchoredContent;
143
157
  }
@@ -453,10 +467,23 @@ export interface DrawingRun {
453
467
  * - "inline" — flows in the paragraph like a tall character.
454
468
  * - "anchor" — positioned absolutely (`<wp:anchor>`); `anchor`
455
469
  * carries the offset + frame-of-reference.
470
+ * - "floatLeft" / "floatRight" — a `<wp:anchor>` image with a
471
+ * displacing wrap (square/tight/through), converted to a
472
+ * CSS float at the head of its anchor paragraph so body
473
+ * text flows around it. `floatMarginsEmu` carries the
474
+ * `distT/B/L/R` clearance.
456
475
  */
457
- placement: "inline" | "anchor";
476
+ placement: "inline" | "anchor" | "floatLeft" | "floatRight";
458
477
  /** Set when `placement === "anchor"`. */
459
478
  anchor?: DrawingAnchor;
479
+ /** Set for `floatLeft` / `floatRight` — the text-clearance margins
480
+ * (from the frame's `distT/B/L/R`), applied as CSS margins. */
481
+ floatMarginsEmu?: {
482
+ topEmu: number;
483
+ rightEmu: number;
484
+ bottomEmu: number;
485
+ leftEmu: number;
486
+ };
460
487
  /**
461
488
  * Vertical alignment for an `inline` image relative to the text on
462
489
  * its line. Defaults to the browser baseline (image bottom on the
@@ -4,6 +4,7 @@ declare const REL_TYPES: {
4
4
  readonly image: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image";
5
5
  readonly hyperlink: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
6
6
  readonly fontTable: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable";
7
+ readonly numbering: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering";
7
8
  readonly font: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/font";
8
9
  };
9
10
  type RelKind = keyof typeof REL_TYPES;
@@ -13,7 +13,7 @@ export interface ExportContext {
13
13
  /** Rels to append to `word/_rels/document.xml.rels`. */
14
14
  relationships: Array<{
15
15
  id: string;
16
- type: "header" | "footer" | "image" | "hyperlink" | "fontTable";
16
+ type: "header" | "footer" | "image" | "hyperlink" | "fontTable" | "numbering";
17
17
  target: string;
18
18
  /** External targets (URLs) need `TargetMode="External"`. */
19
19
  external?: boolean;
@@ -0,0 +1,2 @@
1
+ import { NumberingDefinition } from '../../doc/types';
2
+ export declare function renderNumberingXml(numbering: readonly NumberingDefinition[]): string | null;
@@ -1,4 +1,5 @@
1
1
  import { AnchoredFrame, Block } from '../../doc/types';
2
+ import { ThemePalette } from '../shared/drawingColor';
2
3
  export interface AnchoredFramesContext {
3
4
  /** RelationshipId → part path lookup, e.g. `"rId4" → "media/image1.png"`. */
4
5
  rels: Map<string, string>;
@@ -20,6 +21,9 @@ export interface AnchoredFramesContext {
20
21
  * its true layout. When absent, falls back to flat text (tests).
21
22
  */
22
23
  parseBlockBody?: (txbxContent: Element) => Block[];
24
+ /** Theme colour palette (from `word/theme/theme1.xml`) so shape fills /
25
+ * strokes declared as `<a:schemeClr>` resolve instead of vanishing. */
26
+ theme?: ThemePalette;
23
27
  }
24
28
  /**
25
29
  * Walk every `<w:drawing>/<wp:anchor>` in the document and return one
@@ -0,0 +1,12 @@
1
+ import { ParagraphBorders } from '../../doc/types';
2
+ /**
3
+ * Read a `<w:pBdr>` paragraph-border element into `ParagraphBorders`.
4
+ *
5
+ * Shared by the direct-paragraph parser AND the named-style parser: Word
6
+ * puts the divider rules of letterhead / résumé headers on a STYLE (e.g. a
7
+ * "Name" style's `<w:top>` rule), not on each paragraph. Reading it only
8
+ * for direct paragraphs dropped those rules entirely (the renderer's
9
+ * `effective.borders` cascade had nothing to apply). `none` / `nil` sides
10
+ * are skipped (Word's explicit "no border on this side").
11
+ */
12
+ export declare function readParagraphBorders(pPr: Element): ParagraphBorders | undefined;
@@ -0,0 +1,11 @@
1
+ import { AnchoredFrame, Block, SectionProperties } from '../../doc/types';
2
+ /**
3
+ * Prepend a float `DrawingRun` to each floatable frame's anchor paragraph
4
+ * and drop those frames from the overlay set. Pure — inputs untouched.
5
+ * Body indices are stable (we modify paragraphs in place, never insert),
6
+ * so it composes after `flowDisplacingTextboxes` without an index remap.
7
+ */
8
+ export declare function floatWrappingImages(body: readonly Block[], frames: readonly AnchoredFrame[], sections: readonly SectionProperties[]): {
9
+ body: Block[];
10
+ frames: AnchoredFrame[];
11
+ };
@@ -1,4 +1,5 @@
1
1
  import { Block, InlineFrame } from '../../doc/types';
2
+ import { ThemePalette } from '../shared/drawingColor';
2
3
  export interface InlineFramesContext {
3
4
  /** RelationshipId → part path lookup. */
4
5
  rels: Map<string, string>;
@@ -26,6 +27,9 @@ export interface InlineFramesContext {
26
27
  * Without this flag, only explicit directives count.
27
28
  */
28
29
  honorLastRenderedPageBreaks?: boolean;
30
+ /** Theme colour palette (from `word/theme/theme1.xml`) so textbox /
31
+ * shape fills declared as `<a:schemeClr>` resolve instead of vanishing. */
32
+ theme?: ThemePalette;
29
33
  }
30
34
  /**
31
35
  * One InlineFrame plus the source DOM nodes it came from.
@@ -4,9 +4,14 @@ import { ParagraphFormat } from '../types';
4
4
  export type ImportedItem = {
5
5
  kind: "run";
6
6
  run: ImportedRun;
7
- } | {
7
+ }
8
+ /** `href` is set for HYPERLINK *fields* (the target lives in the field
9
+ * instruction); `relId` for `<w:hyperlink r:id>` elements (resolved
10
+ * against the rels table downstream). */
11
+ | {
8
12
  kind: "hyperlink";
9
13
  relId?: string;
14
+ href?: string;
10
15
  runs: ImportedRun[];
11
16
  };
12
17
  export interface ImportedParagraph {
@@ -1,3 +1,16 @@
1
1
  import { NamedStyle } from '../../doc/types';
2
2
  import { DocSettings } from './settings';
3
+ /**
4
+ * Canonicalise a heading style id to `HeadingN`.
5
+ *
6
+ * Word and OpenOffice name the heading styles inconsistently across docs:
7
+ * `Heading2`, `Heading 2`, `heading 2`. The paragraph importer already
8
+ * maps a heading PARAGRAPH's styleId to the canonical `HeadingN` (so it
9
+ * renders as `<hN>` and joins the `HeadingN` convention used by builders /
10
+ * serialize / markdown). The STYLE definition must canonicalise the same
11
+ * way, or `resolveStyleCascade` looks up `HeadingN` and misses the
12
+ * actual style — dropping its colour, caps, etc. Non-heading ids pass
13
+ * through unchanged.
14
+ */
15
+ export declare function canonicalStyleId(id: string): string;
3
16
  export declare function parseStylesXml(xml: string | undefined, settings?: DocSettings): NamedStyle[] | null;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * DrawingML colour resolution — literal AND theme colours.
3
+ *
4
+ * A DrawingML colour container (`<a:solidFill>`, the children of
5
+ * `<a:ln>`, …) holds either a literal `<a:srgbClr val="RRGGBB">` or a
6
+ * theme reference `<a:schemeClr val="accent1">`. Both can carry child
7
+ * TRANSFORM elements that adjust the base colour (`hueOff`, `satOff`,
8
+ * `lumOff`, `lumMod`, `shade`, `tint`, …). Word resolves the scheme slot
9
+ * against `word/theme/theme1.xml`'s `<a:clrScheme>` and applies the
10
+ * transforms in document order — reading only `srgbClr` renders every
11
+ * theme-coloured shape invisible (no fill, no stroke).
12
+ *
13
+ * Transform units (ECMA-376 §20.1.2.3):
14
+ * - `hueOff` — 60000ths of a degree, added to the hue.
15
+ * - `satOff` / `lumOff` — 1000ths of a percent-POINT, added to S / L.
16
+ * - `satMod` / `lumMod` — 100000ths, multiplied onto S / L.
17
+ * - `shade` — scale toward black (val/100000).
18
+ * - `tint` — scale toward white (val/100000).
19
+ */
20
+ /** Theme slot → `#RRGGBB`. Slots: dk1/lt1/dk2/lt2/accent1-6/hlink/folHlink. */
21
+ export type ThemePalette = Record<string, string>;
22
+ /** Parse `word/theme/theme1.xml` into the colour-scheme palette.
23
+ * Returns undefined when the part is absent or malformed. */
24
+ export declare function parseThemeXml(xml: string | undefined): ThemePalette | undefined;
25
+ /**
26
+ * Resolve the colour child of `parent` (an `<a:solidFill>` or `<a:ln>`-
27
+ * style container): literal `srgbClr` or theme `schemeClr`, transforms
28
+ * applied. Returns `#RRGGBB` or undefined when no resolvable colour.
29
+ */
30
+ export declare function readDrawingColor(parent: Element, theme?: ThemePalette): string | undefined;
@@ -18,6 +18,10 @@ export interface DocxExportResult {
18
18
  }
19
19
  /** A single inline run's formatting flags — what `<w:rPr>` tells us. */
20
20
  export interface RunFormat {
21
+ /** `<w:rStyle w:val="…">` — a character style applied to the run. Its
22
+ * rPr (colour, underline, …) is resolved against the style cascade at
23
+ * render time, under any direct run formatting. */
24
+ styleId?: string;
21
25
  bold?: boolean;
22
26
  italic?: boolean;
23
27
  underline?: boolean;
@@ -19,10 +19,23 @@
19
19
  * `pnpm fixtures:compare --pages` on user-contract.docx (Bookman not
20
20
  * installed on macOS by default). See [diagnosis notes].
21
21
  */
22
+ /** A face name resolved for CSS: the family fallback stack, plus the
23
+ * weight / italic the face name implied (absent when the name carried
24
+ * no face tokens — the run's own bold/italic then fully decide). */
25
+ export interface ResolvedFontFace {
26
+ stack: string;
27
+ weight?: number;
28
+ italic?: boolean;
29
+ }
22
30
  /**
23
- * Wrap `fontFamily` in quotes if it contains spaces or unusual
24
- * characters, and append a metric-compatible fallback chain. If the
25
- * font already matches a curated entry, use that chain directly so the
26
- * primary font isn't double-listed.
31
+ * Resolve an OOXML font NAME into a CSS family stack + implied weight.
32
+ *
33
+ * Curated whole-name chains win first ("Calibri Light" is a real family
34
+ * with its own calibrated chain — corpus baselines depend on it). Then
35
+ * trailing face tokens are stripped ("Helvetica Neue Light" → base
36
+ * "Helvetica Neue" + weight 300) and the BASE resolves through the same
37
+ * curated chains; the full face name stays first in the stack so hosts
38
+ * that do ship the exact face still use it. A name with no tokens and no
39
+ * curated chain keeps the documented unknown-font default (`, serif`).
27
40
  */
28
- export declare function withFallbacks(fontFamily: string): string;
41
+ export declare function resolveFontFace(fontFamily: string): ResolvedFontFace;
@@ -1,4 +1,4 @@
1
- import { InlineRun } from '../../../doc/types';
1
+ import { InlineRun, NamedStyle } from '../../../doc/types';
2
2
  /**
3
3
  * Render a list of InlineRuns into DOM children of `parent`. Empty run
4
4
  * lists produce a `<br>` placeholder so contenteditable can place a
@@ -7,7 +7,7 @@ import { InlineRun } from '../../../doc/types';
7
7
  * `rawParts` is threaded through so `DrawingRun` can resolve its
8
8
  * `partPath` to an `<img src>` via a blob URL / data URI.
9
9
  */
10
- export declare function appendInlineRuns(parent: HTMLElement, runs: readonly InlineRun[], rawParts?: Record<string, Uint8Array>): void;
10
+ export declare function appendInlineRuns(parent: HTMLElement, runs: readonly InlineRun[], rawParts?: Record<string, Uint8Array>, styles?: readonly NamedStyle[]): void;
11
11
  /** Convert a raw part's bytes (from `doc.rawParts`) into a blob URL
12
12
  * the browser can render as `<img src>` / `background-image`. Exported
13
13
  * for the section-frame renderer in `block.ts` which paints the
@@ -2,16 +2,27 @@ import { Block, NumberingDefinition } from '../../../doc/types';
2
2
  export interface ListInfo {
3
3
  numId: number;
4
4
  ordered: boolean;
5
- /** Effective left indent (text wrap position) for this list level,
6
- * in twips from the numbering definition's `<w:lvl><w:pPr><w:ind>`.
7
- * Applied as `padding-left` on the OL / UL so wrapped text lands at
8
- * the right position and the marker hangs to its left. */
5
+ /** Text-column indent (`<w:ind w:left>`), twips the OL/UL
6
+ * `padding-left`. First line and wraps both align here. */
9
7
  leftTwips?: number;
10
- /** Twips the FIRST line hangs to the left of `leftTwips` (= `@w:hanging`).
11
- * The marker sits at `(leftTwips - hangingTwips)` from the content edge. */
8
+ /** Marker hang (`<w:ind w:hanging>`), twips the width of the
9
+ * `::before` marker box; the marker sits at `leftTwips - hangingTwips`. */
12
10
  hangingTwips?: number;
13
- /** The level's lvlText glyph (post-Wingdings remapping), for bullets. */
11
+ /** Bullet glyph (post-Wingdings remapping), for unordered lists. */
14
12
  bulletGlyph?: string;
13
+ /** Marker glyph's own run formatting (`<w:lvl><w:rPr>`): colour, font,
14
+ * size. The font also matters for layout — Word lets the marker
15
+ * font's strut set the bullet line height (see paperStack.css). */
16
+ markerColor?: string;
17
+ markerFont?: string;
18
+ markerSizePt?: number;
19
+ /** CSS counter-style class suffix for ordered lists (`decimal`,
20
+ * `lower-latin`, …) — selects the matching `::before` rule. */
21
+ counterStyle?: string;
22
+ /** Literal text around the number in `lvlText` (`%1.` → suffix `.`;
23
+ * `(%1)` → prefix `(`, suffix `)`). */
24
+ markerPrefix?: string;
25
+ markerSuffix?: string;
15
26
  }
16
27
  /**
17
28
  * Resolve a paragraph's list membership from the numbering table.
@@ -20,9 +31,9 @@ export interface ListInfo {
20
31
  */
21
32
  export declare function paragraphListInfo(block: Block, numbering: readonly NumberingDefinition[]): ListInfo | null;
22
33
  /**
23
- * Build the `<ol>` / `<ul>` container for a run of list items sharing
24
- * a `numId`. Sets marker geometry (padding-left + hanging custom
25
- * property) and bullet glyph (native CSS keyword where one exists,
26
- * else a `::marker`-content custom property).
34
+ * Build the `<ol>` / `<ul>` container for a run of list items sharing a
35
+ * `numId`. Sets the text-column `padding-left`, the `--sobree-list-hang`
36
+ * marker-box width, and the marker content (glyph or counter format) the
37
+ * CSS `::before` rules consume.
27
38
  */
28
39
  export declare function createListContainer(info: ListInfo, sectionIndex: number): HTMLElement;
package/dist/index.css CHANGED
@@ -1 +1 @@
1
- .paper-stack{display:flex;flex-direction:column;padding:48px;gap:28px;outline:none}.paper-row{display:flex;flex-direction:row;align-items:flex-start;gap:24px}.paper{position:relative;background:#fff;border:1px solid rgba(0,0,0,.3);box-shadow:0 1px 2px #00000014,0 18px 60px #0000001f;padding-top:var(--margin-top, 25mm);padding-right:var(--margin-right, 20mm);padding-bottom:var(--margin-bottom, 25mm);padding-left:var(--margin-left, 20mm);overflow:hidden;flex:none}.paper-header,.paper-footer{position:absolute;left:var(--margin-left, 20mm);right:var(--margin-right, 20mm);font-size:10pt;color:#555;white-space:pre-wrap}.paper-header{top:0;min-height:var(--margin-top, 25mm);padding-top:var(--header-offset-mm, 12.7mm);padding-bottom:4mm;border-bottom:1px dashed transparent}.paper-footer{bottom:0;min-height:var(--margin-bottom, 25mm);padding-top:4mm;text-align:center;border-top:1px dashed transparent}.paper-anchors{position:absolute;top:0;right:0;bottom:0;left:0;pointer-events:none;isolation:isolate;z-index:1}.paper-anchors.is-empty{display:none}.paper-zone-anchors{position:absolute;top:0;right:0;bottom:0;left:0;pointer-events:none;isolation:isolate;z-index:2}.paper-zone-anchors.is-empty{display:none}.paper-anchor{position:absolute;box-sizing:border-box;overflow:hidden}.paper-header>p,.paper-footer>p,.paper-header>ul,.paper-header>ol,.paper-footer>ul,.paper-footer>ol{margin:0}.paper-footnotes{position:absolute;left:var(--margin-left, 20mm);right:var(--margin-right, 20mm);bottom:var(--margin-bottom, 25mm);font-size:.85em;padding-top:.5em;border-top:1pt solid currentColor;background:#fffffff5;z-index:1;max-height:40%;overflow:hidden}.paper-footnotes.is-empty{display:none}.paper-comments{flex:0 0 70mm;max-width:70mm;font-size:.85em;padding:0 .5em;align-self:stretch;overflow-y:auto}.paper-comments.is-empty{display:none}.paper-footnotes .sobree-footnotes__list{margin:0;padding-left:1.5em}.paper-footnotes .sobree-footnotes__item{margin:.25em 0}.paper-content{position:relative;height:100%;min-height:100px;display:flex;flex-direction:column;justify-content:flex-start}.paper-content td p:not([style*=line-height]),.paper-content td li:not([style*=line-height]),.paper-content th p:not([style*=line-height]),.paper-content th li:not([style*=line-height]){line-height:1}.paper-content td p:not([style*=margin]),.paper-content th p:not([style*=margin]){margin:0}.paper-content ul.sobree-list-custom-bullet>li::marker,.paper-content ol.sobree-list-custom-bullet>li::marker{content:var(--sobree-bullet-glyph) " "}.paper-content .sobree-tab-spread{display:flex;justify-content:space-between;align-items:baseline;gap:1em;white-space:nowrap}.paper-content .sobree-tab-spread .sobree-tab-spread__before,.paper-content .sobree-tab-spread .sobree-tab-spread__after{white-space:pre-wrap;min-width:0}.paper-content table.sobree-table-bordered td,.paper-content table.sobree-table-bordered th{border:var(--table-cell-border, none);padding:0 4px}.sobree-editor{position:relative;outline:none}.sobree-editor>:first-child{margin-top:0}.sobree-editor p,.sobree-editor h1,.sobree-editor h2,.sobree-editor h3,.sobree-editor h4,.sobree-editor h5,.sobree-editor h6,.sobree-editor ul,.sobree-editor ol,.sobree-editor li,.sobree-editor blockquote,.sobree-editor pre{margin:0;padding:0}.sobree-editor ul,.sobree-editor ol{padding-left:1.5em}.sobree-editor li.sobree-li-continuation{list-style-type:none}.sobree-editor li.sobree-li-continuation::marker{content:""}.sobree-editor .sobree-fragment-continued{text-align-last:justify}.sobree-editor .sobree-footnote-ref a{color:inherit;text-decoration:none}.sobree-editor .sobree-footnote-ref a:hover{text-decoration:underline}.sobree-editor .sobree-footnotes{margin-top:2em;padding-top:.5em;border-top:1pt solid currentColor;font-size:.85em}.sobree-editor .sobree-footnotes__list{padding-left:1.5em}.sobree-editor .sobree-footnotes__item{margin:.25em 0}.sobree-editor ins.sobree-revision-ins{text-decoration:underline}.sobree-editor del.sobree-revision-del{text-decoration:line-through}.sobree-editor .sobree-revision-format{text-decoration:underline dashed var(--sobree-format-revision-color, currentColor);text-underline-offset:3px}.sobree-editor [data-block-revision=ins]:after,.sobree-editor [data-block-revision=del]:after{content:" ¶";color:var(--sobree-block-revision-color, currentColor);opacity:.65;font-weight:600;-webkit-user-select:none;user-select:none}.sobree-editor [data-block-revision=del]:after{text-decoration:line-through}.sobree-editor .sobree-comment-range{background:var(--sobree-comment-range-bg, rgba(255, 217, 0, .25));border-bottom:1px dotted var(--sobree-comment-range-border, rgba(180, 130, 0, .5))}.sobree-editor .sobree-comment-ref{display:inline;font-size:.85em;margin:0 .1em}.sobree-editor .sobree-comment-ref a{color:inherit;text-decoration:none}.sobree-editor .sobree-comment-ref a:hover{filter:brightness(.7)}.sobree-editor h1,.sobree-editor h2,.sobree-editor h3,.sobree-editor h4,.sobree-editor h5,.sobree-editor h6{font-size:inherit;font-weight:inherit}.sobree-editor table{border-collapse:collapse;width:100%;position:relative}.sobree-editor th,.sobree-editor td{border:none;padding:0 .08in;vertical-align:top;transition:border-color .12s ease}.sobree-editor .sobree-section-break{display:flex;align-items:center;gap:8px;margin:8px 0;color:var(--fg-subtle, #7c7764);font-size:11px;letter-spacing:.04em;text-transform:uppercase;-webkit-user-select:none;user-select:none}.sobree-editor .sobree-section-break--continuous{visibility:hidden;height:0;margin:0;padding:0;border:none;overflow:hidden;font-size:0;line-height:0}.sobree-editor .sobree-section-break:before,.sobree-editor .sobree-section-break:after{content:"";flex:1;border-top:1px dashed var(--border, #dedbd0)}.sobree-editor .sobree-section-break__label{flex:none;padding:0 6px;background:var(--bg-elevated, #fff)}.sobree-editor .sobree-textbox-frame p{line-height:1.2}.sobree-editor .paper-content p,.sobree-editor .paper-content li,.sobree-editor .paper-header p,.sobree-editor .paper-header li,.sobree-editor .paper-footer p,.sobree-editor .paper-footer li,.sobree-editor .sobree-textbox-frame p{white-space:pre-wrap}.sobree-editor .sobree-section-trailer-empty{height:0;line-height:0;font-size:0;margin:0;padding:0;overflow:hidden}.sobree-editor .sobree-textbox-frame--placeholder{border:1px solid var(--border, #c8c4b8)}.sobree-editor .sobree-textbox-frame--placeholder.sobree-textbox-frame--filled{border:none;z-index:-1}.sobree-editor img.is-selected{outline:2px solid var(--sobree-primary, #d4521f);outline-offset:2px}.sobree-image-resize-handle{position:absolute;width:16px;height:16px;background:var(--sobree-primary, #d4521f);border:2px solid #fff;border-radius:3px;cursor:nwse-resize;z-index:1000;-webkit-user-select:none;user-select:none}.sobree-viewport{position:relative;overflow:hidden;overscroll-behavior:contain;touch-action:none;background:#ececee}.sobree-viewport__stage{position:absolute;top:0;left:0;transform-origin:0 0;will-change:transform}.sobree-viewport__stage.is-animating{transition:transform .32s cubic-bezier(.2,.7,.2,1)}.sobree-viewport__slot{display:block}.paper-stack .paper-content,.paper-stack .paper-header,.paper-stack .paper-footer{transition:opacity .2s ease}.paper-stack.is-zone-editing .paper-content,.paper-stack.is-zone-editing .paper-header,.paper-stack.is-zone-editing .paper-footer{opacity:.2}.paper-stack.is-zone-editing .paper-header[contenteditable=true],.paper-stack.is-zone-editing .paper-footer[contenteditable=true]{opacity:1}.paper-header[contenteditable=true],.paper-footer[contenteditable=true]{outline:2px dotted var(--primary, #c96f22);outline-offset:4px;background:var(--primary-soft, #fdf6ee);color:var(--fg-strong, #14130f);border-radius:2px}
1
+ .paper-stack{display:flex;flex-direction:column;padding:48px;gap:28px;outline:none}.paper-row{display:flex;flex-direction:row;align-items:flex-start;gap:24px}.paper{position:relative;background:#fff;border:1px solid rgba(0,0,0,.3);box-shadow:0 1px 2px #00000014,0 18px 60px #0000001f;padding-top:var(--margin-top, 25mm);padding-right:var(--margin-right, 20mm);padding-bottom:var(--margin-bottom, 25mm);padding-left:var(--margin-left, 20mm);overflow:hidden;flex:none}.paper-header,.paper-footer{position:absolute;left:var(--margin-left, 20mm);right:var(--margin-right, 20mm);font-size:10pt;color:#555;white-space:pre-wrap}.paper-header{top:0;min-height:var(--margin-top, 25mm);padding-top:var(--header-offset-mm, 12.7mm);padding-bottom:4mm;border-bottom:1px dashed transparent}.paper-footer{bottom:0;min-height:var(--margin-bottom, 25mm);padding-top:4mm;text-align:center;border-top:1px dashed transparent}.paper-anchors{position:absolute;top:0;right:0;bottom:0;left:0;pointer-events:none;isolation:isolate;z-index:1}.paper-anchors.is-empty{display:none}.paper-zone-anchors{position:absolute;top:0;right:0;bottom:0;left:0;pointer-events:none;isolation:isolate;z-index:2}.paper-zone-anchors.is-empty{display:none}.paper-anchor{position:absolute;box-sizing:border-box;overflow:hidden}.paper-header>p,.paper-footer>p,.paper-header>ul,.paper-header>ol,.paper-footer>ul,.paper-footer>ol{margin:0}.paper-footnotes{position:absolute;left:var(--margin-left, 20mm);right:var(--margin-right, 20mm);bottom:var(--margin-bottom, 25mm);font-size:.85em;padding-top:.5em;border-top:1pt solid currentColor;background:#fffffff5;z-index:1;max-height:40%;overflow:hidden}.paper-footnotes.is-empty{display:none}.paper-comments{flex:0 0 70mm;max-width:70mm;font-size:.85em;padding:0 .5em;align-self:stretch;overflow-y:auto}.paper-comments.is-empty{display:none}.paper-footnotes .sobree-footnotes__list{margin:0;padding-left:1.5em}.paper-footnotes .sobree-footnotes__item{margin:.25em 0}.paper-content a{color:inherit;text-decoration:none}.paper-content{position:relative;height:100%;min-height:100px;display:flow-root}.paper-content td p:not([style*=line-height]),.paper-content td li:not([style*=line-height]),.paper-content th p:not([style*=line-height]),.paper-content th li:not([style*=line-height]){line-height:1}.paper-content td p:not([style*=margin]),.paper-content th p:not([style*=margin]){margin:0}.paper-content .sobree-hang>li{list-style:none}.paper-content .sobree-hang>li:before{display:inline-block;box-sizing:border-box;width:var(--sobree-list-hang, 0);margin-left:calc(-1 * var(--sobree-list-hang, 0));white-space:nowrap;color:var(--sobree-marker-color, currentColor);font-family:var(--sobree-marker-font, inherit);font-size:var(--sobree-marker-size, inherit)}.paper-content .sobree-hang.lst-bullet>li:before{content:var(--sobree-bullet, "•")}.paper-content .sobree-hang.lst-decimal>li:before{content:var(--mk-pre, "") counter(list-item,decimal) var(--mk-suf, ".")}.paper-content .sobree-hang.lst-decimal-zero>li:before{content:var(--mk-pre, "") counter(list-item,decimal-leading-zero) var(--mk-suf, ".")}.paper-content .sobree-hang.lst-lower-latin>li:before{content:var(--mk-pre, "") counter(list-item,lower-latin) var(--mk-suf, ".")}.paper-content .sobree-hang.lst-upper-latin>li:before{content:var(--mk-pre, "") counter(list-item,upper-latin) var(--mk-suf, ".")}.paper-content .sobree-hang.lst-lower-roman>li:before{content:var(--mk-pre, "") counter(list-item,lower-roman) var(--mk-suf, ".")}.paper-content .sobree-hang.lst-upper-roman>li:before{content:var(--mk-pre, "") counter(list-item,upper-roman) var(--mk-suf, ".")}.paper-content .sobree-tab-spread{display:flex;justify-content:space-between;align-items:baseline;gap:1em;white-space:nowrap}.paper-content .sobree-tab-spread .sobree-tab-spread__before,.paper-content .sobree-tab-spread .sobree-tab-spread__after{white-space:pre-wrap;min-width:0}.paper-content table.sobree-table-bordered td,.paper-content table.sobree-table-bordered th{border:var(--table-cell-border, none);padding:0 4px}.sobree-editor{position:relative;outline:none}.sobree-editor>:first-child{margin-top:0}.sobree-editor p,.sobree-editor h1,.sobree-editor h2,.sobree-editor h3,.sobree-editor h4,.sobree-editor h5,.sobree-editor h6,.sobree-editor ul,.sobree-editor ol,.sobree-editor li,.sobree-editor blockquote,.sobree-editor pre{margin:0;padding:0}.sobree-editor ul,.sobree-editor ol{padding-left:1.5em}.sobree-editor li.sobree-li-continuation{list-style-type:none}.sobree-editor li.sobree-li-continuation::marker{content:""}.sobree-editor .sobree-fragment-continued{text-align-last:justify}.sobree-editor .sobree-footnote-ref a{color:inherit;text-decoration:none}.sobree-editor .sobree-footnote-ref a:hover{text-decoration:underline}.sobree-editor .sobree-footnotes{margin-top:2em;padding-top:.5em;border-top:1pt solid currentColor;font-size:.85em}.sobree-editor .sobree-footnotes__list{padding-left:1.5em}.sobree-editor .sobree-footnotes__item{margin:.25em 0}.sobree-editor ins.sobree-revision-ins{text-decoration:underline}.sobree-editor del.sobree-revision-del{text-decoration:line-through}.sobree-editor .sobree-revision-format{text-decoration:underline dashed var(--sobree-format-revision-color, currentColor);text-underline-offset:3px}.sobree-editor [data-block-revision=ins]:after,.sobree-editor [data-block-revision=del]:after{content:" ¶";color:var(--sobree-block-revision-color, currentColor);opacity:.65;font-weight:600;-webkit-user-select:none;user-select:none}.sobree-editor [data-block-revision=del]:after{text-decoration:line-through}.sobree-editor .sobree-comment-range{background:var(--sobree-comment-range-bg, rgba(255, 217, 0, .25));border-bottom:1px dotted var(--sobree-comment-range-border, rgba(180, 130, 0, .5))}.sobree-editor .sobree-comment-ref{display:inline;font-size:.85em;margin:0 .1em}.sobree-editor .sobree-comment-ref a{color:inherit;text-decoration:none}.sobree-editor .sobree-comment-ref a:hover{filter:brightness(.7)}.sobree-editor h1,.sobree-editor h2,.sobree-editor h3,.sobree-editor h4,.sobree-editor h5,.sobree-editor h6{font-size:inherit;font-weight:inherit}.sobree-editor table{border-collapse:collapse;width:100%;position:relative}.sobree-editor th,.sobree-editor td{border:none;padding:0 .08in;vertical-align:top;transition:border-color .12s ease}.sobree-editor .sobree-section-break{display:flex;align-items:center;gap:8px;margin:8px 0;color:var(--fg-subtle, #7c7764);font-size:11px;letter-spacing:.04em;text-transform:uppercase;-webkit-user-select:none;user-select:none}.sobree-editor .sobree-section-break--continuous{visibility:hidden;height:0;margin:0;padding:0;border:none;overflow:hidden;font-size:0;line-height:0}.sobree-editor .sobree-section-break:before,.sobree-editor .sobree-section-break:after{content:"";flex:1;border-top:1px dashed var(--border, #dedbd0)}.sobree-editor .sobree-section-break__label{flex:none;padding:0 6px;background:var(--bg-elevated, #fff)}.sobree-editor .sobree-textbox-frame p{line-height:1.2}.sobree-editor .paper-content p,.sobree-editor .paper-content li,.sobree-editor .paper-header p,.sobree-editor .paper-header li,.sobree-editor .paper-footer p,.sobree-editor .paper-footer li,.sobree-editor .sobree-textbox-frame p{white-space:pre-wrap}.sobree-editor .sobree-section-trailer-empty{height:0;line-height:0;font-size:0;margin:0;padding:0;overflow:hidden}.sobree-editor .sobree-textbox-frame--placeholder{border:1px solid var(--border, #c8c4b8)}.sobree-editor .sobree-textbox-frame--placeholder.sobree-textbox-frame--filled{border:none;z-index:-1}.sobree-editor img.is-selected{outline:2px solid var(--sobree-primary, #d4521f);outline-offset:2px}.sobree-image-resize-handle{position:absolute;width:16px;height:16px;background:var(--sobree-primary, #d4521f);border:2px solid #fff;border-radius:3px;cursor:nwse-resize;z-index:1000;-webkit-user-select:none;user-select:none}.sobree-viewport{position:relative;overflow:hidden;overscroll-behavior:contain;touch-action:none;background:#ececee}.sobree-viewport__stage{position:absolute;top:0;left:0;transform-origin:0 0;will-change:transform}.sobree-viewport__stage.is-animating{transition:transform .32s cubic-bezier(.2,.7,.2,1)}.sobree-viewport__slot{display:block}.paper-stack .paper-content,.paper-stack .paper-header,.paper-stack .paper-footer{transition:opacity .2s ease}.paper-stack.is-zone-editing .paper-content,.paper-stack.is-zone-editing .paper-header,.paper-stack.is-zone-editing .paper-footer{opacity:.2}.paper-stack.is-zone-editing .paper-header[contenteditable=true],.paper-stack.is-zone-editing .paper-footer[contenteditable=true]{opacity:1}.paper-header[contenteditable=true],.paper-footer[contenteditable=true]{outline:2px dotted var(--primary, #c96f22);outline-offset:4px;background:var(--primary-soft, #fdf6ee);color:var(--fg-strong, #14130f);border-radius:2px}