@sobree/core 0.1.1 → 0.1.2

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 (37) hide show
  1. package/dist/doc/types.d.ts +40 -30
  2. package/dist/docx/shared/units.d.ts +3 -2
  3. package/dist/docx/shared/xml.d.ts +12 -0
  4. package/dist/editor/commands.d.ts +14 -0
  5. package/dist/editor/context.d.ts +71 -0
  6. package/dist/editor/dom.d.ts +21 -0
  7. package/dist/editor/events.d.ts +29 -0
  8. package/dist/editor/index.d.ts +85 -663
  9. package/dist/editor/internal/mutations.d.ts +1 -1
  10. package/dist/editor/ops/blocks.d.ts +42 -0
  11. package/dist/editor/ops/comments.d.ts +12 -0
  12. package/dist/editor/ops/parts.d.ts +71 -0
  13. package/dist/editor/ops/review.d.ts +67 -0
  14. package/dist/editor/ops/runs.d.ts +83 -0
  15. package/dist/editor/ops/trackedInput.d.ts +44 -0
  16. package/dist/editor/query.d.ts +22 -0
  17. package/dist/editor/revisionRuns.d.ts +56 -0
  18. package/dist/editor/selection.d.ts +34 -0
  19. package/dist/editor/types.d.ts +292 -0
  20. package/dist/editor/view/docRenderer/anchorPosition.d.ts +18 -0
  21. package/dist/editor/view/docRenderer/sectionFlow.d.ts +23 -0
  22. package/dist/editor/view/docRenderer/table.d.ts +11 -2
  23. package/dist/index.css +1 -1
  24. package/dist/index.js +6099 -6020
  25. package/dist/index.js.map +1 -1
  26. package/dist/paperStack/paginationV2/apply.d.ts +18 -0
  27. package/dist/paperStack/paginationV2/engine.d.ts +7 -0
  28. package/dist/paperStack/paginationV2/measure.d.ts +9 -0
  29. package/dist/paperStack/paginationV2/types.d.ts +273 -0
  30. package/dist/paperStack/paper.d.ts +31 -9
  31. package/dist/paperStack/paperStack.d.ts +13 -1
  32. package/dist/paperStack/paperZone.d.ts +35 -0
  33. package/dist/plugins/marks.d.ts +4 -4
  34. package/dist/ydoc/schema.d.ts +5 -0
  35. package/package.json +1 -1
  36. package/dist/__vite-browser-external-DYxpcVy9.js +0 -5
  37. package/dist/__vite-browser-external-DYxpcVy9.js.map +0 -1
@@ -0,0 +1,292 @@
1
+ import { BlobStore } from '../blob';
2
+ import { Range as ApiRange, BlockRef, EditResult, InlinePosition, Selection } from '../doc/api';
3
+ import { RunPropertiesPatch } from '../doc/runs';
4
+ import { Block, ParagraphAlignment, ParagraphProperties, SobreeDocument } from '../doc/types';
5
+ /**
6
+ * Editor-surface types — the public type vocabulary of the `Editor`
7
+ * façade (events, payloads, command bus, options, tracked-change spans).
8
+ *
9
+ * Extracted from `editor/index.ts` so that:
10
+ * 1. the 3000+-line editor module stays focused on behaviour, and
11
+ * 2. helper modules (`internal/mutations`, `plugins/marks`) can import
12
+ * these shared types from a leaf module instead of reaching back
13
+ * into `editor/index.ts` — which created an import cycle.
14
+ *
15
+ * Re-exported from `editor/index.ts`, so the public surface is unchanged.
16
+ */
17
+ import type * as Y from "yjs";
18
+ export type ApiRangeType = ApiRange;
19
+ export type { CellRef, InsertAt, InsertColumnOpts, InsertRowOpts, MergeCellsOpts, } from './table';
20
+ /**
21
+ * One logical tracked change — a maximal run of consecutive inline
22
+ * runs that all carry a `revision` marker by the same author.
23
+ * `getRevisions()` returns these; pass `range` straight to
24
+ * `acceptRevision` / `rejectRevision`.
25
+ *
26
+ * `kinds` is the set of revision types in the span: `["ins"]` or
27
+ * `["del"]` for a plain change, both for a delete-then-insert
28
+ * replacement (which accepts/rejects as a single unit).
29
+ */
30
+ export interface RevisionSpan {
31
+ range: ApiRange;
32
+ author?: string;
33
+ kinds: ("ins" | "del")[];
34
+ /** ISO date of the span's first revision run, if recorded. */
35
+ date?: string;
36
+ /**
37
+ * Discriminator between revision levels:
38
+ * `"inline"` (default for backwards compat) — the span covers
39
+ * `ins`/`del` text runs inside a block. Pass `range` to
40
+ * `acceptRevision` / `rejectRevision`.
41
+ * `"paragraph"` — the span flags the *paragraph mark* itself on
42
+ * `range.from.block`. The range covers offset `[0, length]` of
43
+ * the block so it still selects the right element for UIs, but
44
+ * accept/reject must go through `acceptParagraphRevision` /
45
+ * `rejectParagraphRevision`.
46
+ * `"format"` — the span flags a tracked format change
47
+ * (`<w:rPrChange>`) on contiguous runs by the same author.
48
+ * `kinds` always reports `["ins"]` (the marker is binary: a
49
+ * format change exists or not). Pass `range` to
50
+ * `acceptFormatRevision` / `rejectFormatRevision`.
51
+ */
52
+ level?: "inline" | "paragraph" | "format";
53
+ }
54
+ /**
55
+ * Track-changes mode. When `enabled` is true, the editor reinterprets
56
+ * authoring mutations as tracked revisions rather than direct edits:
57
+ *
58
+ * - `insertRun` stamps `revision: { type: "ins", author }` on the
59
+ * inserted run instead of merging it in plainly.
60
+ * - `deleteRange` stamps `revision: { type: "del", author }` on the
61
+ * plain text runs in range instead of dropping them. A run that's
62
+ * already an `ins` *by the same author* is dropped instead — the
63
+ * author cancelling their own pending insert — and runs already
64
+ * carrying a peer's revision are left untouched (the API user must
65
+ * resolve those via `acceptRevision` / `rejectRevision` first).
66
+ * - All other mutations (`applyRunProperties`, block-level ops, etc.)
67
+ * pass through unchanged in this first cut; format-change tracking
68
+ * (`<w:rPrChange>`) and paragraph-mark tracking will land later.
69
+ *
70
+ * `author` is the human-readable name written into the revision
71
+ * marker. Optional — falls back to no `author` field, mirroring the
72
+ * Word semantics for anonymous-author tracked changes.
73
+ *
74
+ * This is the *authoring* side of the review feature. `getRevisions`
75
+ * / `acceptRevision` / `rejectRevision` are the *consumption* side
76
+ * and work the same regardless of this flag.
77
+ */
78
+ export interface TrackChangesState {
79
+ enabled: boolean;
80
+ author?: string;
81
+ }
82
+ /**
83
+ * Payload delivered to `change` subscribers. Plain data — safe to
84
+ * JSON-stringify and ship over a wire. `rawParts` is stripped from
85
+ * `document` so the payload never carries binary Uint8Arrays.
86
+ */
87
+ export interface ChangePayload {
88
+ doc: SobreeDocument;
89
+ /**
90
+ * @deprecated Use `doc` instead. This alias is kept for backwards
91
+ * compatibility within the pre-1.0 line and will be removed before
92
+ * v1. Same reference as `doc`.
93
+ */
94
+ document: SobreeDocument;
95
+ revision: number;
96
+ documentVersion: number;
97
+ }
98
+ /** Summary of a top-level block, for `getBlocks()` and list-style UIs. */
99
+ export interface BlockInfo {
100
+ /** Current position in the body (unstable across edits). */
101
+ index: number;
102
+ /** Stable id — pair with `version` to form a `BlockRef`. */
103
+ id: string;
104
+ /** Bumps on every modification of this block. */
105
+ version: number;
106
+ kind: Block["kind"];
107
+ styleId?: string;
108
+ alignment?: ParagraphAlignment;
109
+ /** Plain-text preview. */
110
+ text: string;
111
+ /** Total character-length of the block's content (see `runsLength`). */
112
+ length: number;
113
+ }
114
+ /** Outline entry — one per heading in document order. */
115
+ export interface OutlineItem {
116
+ level: number;
117
+ text: string;
118
+ blockIndex: number;
119
+ block: BlockRef;
120
+ }
121
+ export type ParagraphPropertiesPatch = {
122
+ [K in keyof ParagraphProperties]?: ParagraphProperties[K] | undefined;
123
+ };
124
+ export type WrapTag = "sup" | "sub" | "strong" | "em" | "u" | "s" | "mark";
125
+ /** The slice of selection state plugins read (see {@link EditorLike}). */
126
+ export interface EditorSelectionLike {
127
+ currentRange(): ApiRange | null;
128
+ currentCaret(): InlinePosition | null;
129
+ }
130
+ /**
131
+ * The minimal `Editor` contract that framework-free plugins depend on
132
+ * (mark toggles, etc.). Plugins type against this instead of the
133
+ * concrete `Editor` class so they don't import `editor/index.ts` — that
134
+ * kept a (type-only) import cycle alive and coupled plugins to the
135
+ * whole editor module. The `Editor` class structurally satisfies it.
136
+ */
137
+ export interface EditorLike {
138
+ getDocument(): SobreeDocument;
139
+ getBlocks(): BlockInfo[];
140
+ getBlockById(id: string): BlockInfo | null;
141
+ applyRunProperties(range: ApiRange, patch: RunPropertiesPatch, opts?: {
142
+ expect?: Record<string, number>;
143
+ }): EditResult<void>;
144
+ wrapRange(range: ApiRange, tag: WrapTag, opts?: {
145
+ expect?: Record<string, number>;
146
+ }): EditResult<void>;
147
+ readonly selection: EditorSelectionLike;
148
+ }
149
+ export type EditorEvent = "change" | "selection" | "keydown" | "track-changes-change";
150
+ export type EditorEventPayload = {
151
+ change: ChangePayload;
152
+ selection: SelectionPayload;
153
+ keydown: KeyDownPayload;
154
+ "track-changes-change": TrackChangesState;
155
+ };
156
+ export type Unsubscribe = () => void;
157
+ /**
158
+ * Payload delivered to `selection` subscribers. Fires whenever the live
159
+ * DOM selection changes — typing, clicking, arrow-key navigation, focus
160
+ * loss, programmatic restore. Subscribers should subscribe through the
161
+ * editor rather than `document.addEventListener("selectionchange")`
162
+ * directly so cleanup is centralised and the editor can later add
163
+ * dedup / throttling.
164
+ *
165
+ * `selection` is the model shape (`null` when focus is outside the
166
+ * editor). The convenience fields below mirror what `EditorSelection`
167
+ * exposes for ergonomics — read whichever one you need.
168
+ */
169
+ export interface SelectionPayload {
170
+ selection: Selection;
171
+ range: ApiRange | null;
172
+ caret: InlinePosition | null;
173
+ block: BlockRef | null;
174
+ }
175
+ /**
176
+ * Payload delivered to `keydown` subscribers. Fires for every key press
177
+ * inside the editor host. The editor binds NO shortcuts itself —
178
+ * plugins map keys to API calls via `preventDefault()` (stops the
179
+ * browser's default action) and `stopPropagation()` (stops the chain
180
+ * of remaining subscribers). Subscribers fire in registration order.
181
+ */
182
+ export interface KeyDownPayload {
183
+ /** `KeyboardEvent.key` — `"b"`, `"Enter"`, `"ArrowLeft"`, … (lowercased for letters). */
184
+ key: string;
185
+ /** `KeyboardEvent.code` — `"KeyB"`, `"Enter"`, `"ArrowLeft"`, … (layout-independent). */
186
+ code: string;
187
+ ctrl: boolean;
188
+ shift: boolean;
189
+ alt: boolean;
190
+ meta: boolean;
191
+ /** Stop the browser's default for this key (insertion, navigation, …). */
192
+ preventDefault(): void;
193
+ /** Stop further subscribers from receiving this key. */
194
+ stopPropagation(): void;
195
+ /** Underlying DOM event — for advanced needs (`isComposing`, repeat, …). */
196
+ originalEvent: KeyboardEvent;
197
+ }
198
+ /**
199
+ * A registered command — a named, callable unit of editor work that
200
+ * plugins coordinate around. Same definition gets reached by a
201
+ * keyboard shortcut, a toolbar click, a programmatic call from an
202
+ * agent, or an MCP request.
203
+ *
204
+ * The `run` function is the one place the work happens. `isActive` and
205
+ * `isAvailable` let UI plugins paint toggle / disabled state without
206
+ * understanding what the command actually does.
207
+ */
208
+ export interface CommandDefinition<Args = void> {
209
+ /** Dotted, namespaced — `"mark.toggle.bold"`, `"section.insertBreak"`, … */
210
+ name: string;
211
+ /** Short human label for tooltips / command palettes. */
212
+ title?: string;
213
+ /** Perform the work. Should be idempotent w.r.t. selection (a second
214
+ * invocation on an already-bold selection clears bold, etc.). */
215
+ run: (args: Args) => void;
216
+ /** True when the command represents an active state (mark already on,
217
+ * block is already a heading, …). Drives toolbar `is-active`. */
218
+ isActive?: () => boolean;
219
+ /** False when the command can't run (e.g. selection is wrong shape).
220
+ * Defaults to true. Drives toolbar `disabled`. */
221
+ isAvailable?: () => boolean;
222
+ }
223
+ /** Snapshot of one registered command — what `commands.list()` returns. */
224
+ export interface CommandSnapshot {
225
+ name: string;
226
+ title: string;
227
+ isActive: boolean;
228
+ isAvailable: boolean;
229
+ }
230
+ /**
231
+ * Registry every plugin uses to talk to every other plugin. The
232
+ * editor owns it; plugins register commands on attach and unregister
233
+ * on detach. Keyboard plugins, toolbar plugins, and a future MCP
234
+ * adapter all share the same dispatch path: `editor.commands.execute(name)`.
235
+ */
236
+ export interface CommandBus {
237
+ /** Register a command. Returns an unsubscribe that removes it. */
238
+ register<Args = void>(def: CommandDefinition<Args>): () => void;
239
+ /** Run a registered command. No-op (with a warning) if unknown. */
240
+ execute<Args = void>(name: string, args?: Args): void;
241
+ /** Snapshot every registered command — for command palettes,
242
+ * toolbars rendering toggle states, accessibility audits. */
243
+ list(): CommandSnapshot[];
244
+ /** Whether the named command is currently registered. */
245
+ has(name: string): boolean;
246
+ }
247
+ export interface EditorOptions {
248
+ initialDocument?: SobreeDocument;
249
+ changeDebounceMs?: number;
250
+ /**
251
+ * Elements whose children are editable blocks, in document order. Called
252
+ * fresh each time — the list can grow/shrink (e.g. during pagination).
253
+ */
254
+ contentHosts?: () => HTMLElement[];
255
+ /**
256
+ * Y.Doc backing the document. Optional — if absent, the editor creates
257
+ * one internally. Embedders pass their own when they need to attach
258
+ * a provider (`y-websocket`, `y-indexeddb`, `y-webrtc`, …) for
259
+ * persistence or collaboration.
260
+ *
261
+ * When supplied, the editor checks whether the Y.Doc already has body
262
+ * content. If empty, it seeds from `initialDocument`. If non-empty
263
+ * (Phase 2+: a peer joined an active room), the existing Y.Doc state
264
+ * wins and `initialDocument` is ignored. See `editor.ydoc` for the
265
+ * public escape hatch.
266
+ */
267
+ ydoc?: Y.Doc;
268
+ /**
269
+ * Optional content-hashed `BlobStore` for binary parts (images, fonts).
270
+ *
271
+ * Without one (default): bytes live inline in the Y.Doc's `parts`
272
+ * Y.Map and replicate to every peer through Y updates. Fine for
273
+ * small docs.
274
+ *
275
+ * With one: the editor hashes binary parts, uploads the bytes to the
276
+ * store, and writes only the hash into the Y.Doc's `partRefs` Y.Map.
277
+ * Y updates stay small regardless of image size. The editor maintains
278
+ * a local `BlobCache` that synchronously serves already-fetched bytes
279
+ * to the renderer; `editor.ensurePartsLoaded()` is the async hook for
280
+ * explicit pre-fetching (e.g. before `toDocx()`).
281
+ *
282
+ * See `@sobree/core/blob` for the interface + reference impls
283
+ * (`inMemoryBlobStore`, `fetchBlobStore`).
284
+ */
285
+ blobStore?: BlobStore;
286
+ /**
287
+ * Initial track-changes mode. When omitted, the editor starts in
288
+ * direct-edit mode (`{ enabled: false }`) and embedders can flip it
289
+ * later via `editor.setTrackChanges`. See `TrackChangesState`.
290
+ */
291
+ trackChanges?: TrackChangesState;
292
+ }
@@ -0,0 +1,18 @@
1
+ import { AnchoredFrame } from '../../../doc/types';
2
+ export interface AnchorGeometry {
3
+ /** Page top margin (`<w:pgMar w:top>`) in EMU, card-relative. */
4
+ marginTopEmu: number;
5
+ /** Page left margin (`<w:pgMar w:left>`) in EMU, card-relative. */
6
+ marginLeftEmu: number;
7
+ /**
8
+ * Rendered top of the anchor paragraph, card-relative, in EMU.
9
+ * Required when `verticalFrom === "paragraph"`; when absent (the
10
+ * paragraph couldn't be located) the frame falls back to margin-top —
11
+ * a paragraph-anchored frame is never page-relative.
12
+ */
13
+ anchorParaTopEmu?: number | null;
14
+ }
15
+ export declare function resolveAnchorPosition(frame: AnchoredFrame, geom: AnchorGeometry): {
16
+ xEmu: number;
17
+ yEmu: number;
18
+ };
@@ -0,0 +1,23 @@
1
+ import { SectionProperties } from '../../../doc/types';
2
+ /**
3
+ * If `section.columns.count > 1`, append a column container to `host`
4
+ * and return it as the new append target. Otherwise return `host` —
5
+ * single-column sections write directly to it.
6
+ */
7
+ export declare function openColumnContainerIfNeeded(host: HTMLElement, section: SectionProperties | undefined): HTMLElement;
8
+ /**
9
+ * If `container` is a column-flow container, move any trailing
10
+ * visually-empty paragraphs out of it and append them to `host`. Keeps
11
+ * Word's column balancing semantics: empties between content count,
12
+ * trailing empties do not.
13
+ */
14
+ export declare function evictTrailingEmptyParagraphs(container: HTMLElement, host: HTMLElement): void;
15
+ /**
16
+ * Visually collapse the empty paragraph immediately preceding the
17
+ * just-appended section_break. The paragraph stays in the DOM (and the
18
+ * AST) so round-trip and caret behaviour stay intact; CSS just zeros
19
+ * its height. Pulls back from the last element (which is the section
20
+ * break itself) by one position and checks if it's a visually-empty
21
+ * paragraph (no text, no images).
22
+ */
23
+ export declare function collapseSectionTrailerEmpty(host: HTMLElement): void;
@@ -1,4 +1,13 @@
1
- import { NamedStyle, NumberingDefinition, Table } from '../../../doc/types';
1
+ import { Block, NamedStyle, NumberingDefinition, Table } from '../../../doc/types';
2
+ /**
3
+ * Renderer for a table cell's block content, injected by the caller
4
+ * (`block.ts`) instead of imported — so `table.ts` never imports
5
+ * `block.ts`, breaking the block ↔ table render cycle. The recursion is
6
+ * genuine (a cell holds blocks; a block may be a nested table), but the
7
+ * *import* cycle isn't: the sole caller already owns `renderBlocks` and
8
+ * hands it in.
9
+ */
10
+ export type RenderCellBlocks = (blocks: readonly Block[], host: HTMLElement, numbering: readonly NumberingDefinition[], styles: readonly NamedStyle[], rawParts: Record<string, Uint8Array>) => void;
2
11
  /**
3
12
  * Render a Table to a `<table>` element. `vMerge: "restart"` cells
4
13
  * emit with `rowspan=N` computed from the following `"continue"` cells
@@ -12,4 +21,4 @@ import { NamedStyle, NumberingDefinition, Table } from '../../../doc/types';
12
21
  * 1.5× line-height because the cell's own renderer ignored per-paragraph
13
22
  * properties).
14
23
  */
15
- export declare function renderTable(table: Table, numbering?: readonly NumberingDefinition[], styles?: readonly NamedStyle[], rawParts?: Record<string, Uint8Array>): HTMLElement;
24
+ export declare function renderTable(table: Table, renderCellBlocks: RenderCellBlocks, numbering?: readonly NumberingDefinition[], styles?: readonly NamedStyle[], rawParts?: Record<string, Uint8Array>): 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:var(--margin-top, 25mm);right:var(--margin-right, 20mm);bottom:var(--margin-bottom, 25mm);left:var(--margin-left, 20mm);pointer-events:none;isolation:isolate;z-index:1}.paper-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{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}