@entropicwarrior/sdoc 0.2.18 → 0.2.20

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.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Irreversible Inc.
3
+ Copyright (c) 2026 Entropic Warrior
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -19,7 +19,7 @@ Markdown has no formal structure — section boundaries are ambiguous, extractio
19
19
 
20
20
  **A zero-dependency JavaScript parser** — `src/sdoc.js` parses SDOC into a format-neutral AST. No runtime dependencies, works anywhere Node runs. Parsing and rendering are cleanly separated — build your own renderers on top.
21
21
 
22
- **Slide deck generation** — turn any SDOC file into an HTML slide deck with themes, layouts (center, two-column), speaker notes, mermaid diagrams, and PDF export via headless Chrome.
22
+ **Slide deck generation** — turn any SDOC file into a slide deck with themes, structured layouts (columns, stats, pipeline, matrix, rows, bars, split, stack), speaker notes, mermaid diagrams, and export to PDF or PowerPoint / Google Slides via headless Chrome.
23
23
 
24
24
  **A document site builder** — serve a folder of SDOC files as a browsable site with sidebar navigation, search, and split-pane comparison.
25
25
 
@@ -53,6 +53,9 @@ Open any `.sdoc` file and click the preview icon in the editor title bar, or run
53
53
 
54
54
  ```bash
55
55
  node tools/build-slides.js deck.sdoc -o slides.html
56
+ node tools/build-slides.js deck.sdoc --pdf # one page per slide
57
+ node tools/build-slides.js deck.sdoc --pptx # Drive imports it as Google Slides
58
+ node tools/build-slides.js deck.sdoc --check # report slides whose content overflows
56
59
  ```
57
60
 
58
61
  Each top-level scope becomes a slide. Set `type: slides` in `@meta`. See `docs/reference/slide-authoring.sdoc` for the full authoring guide.
@@ -196,7 +199,7 @@ Tag any section with `@id` and cross-reference it anywhere with `@id` — render
196
199
 
197
200
  ### Slides
198
201
 
199
- Turn any SDOC file into an HTML slide deck with themes, layouts (center, two-column), speaker notes, and PDF export.
202
+ Turn any SDOC file into a slide deck with themes, structured layouts, speaker notes, and export to PDF or PowerPoint / Google Slides.
200
203
 
201
204
  ### Scope Types
202
205
 
@@ -582,7 +582,7 @@ Content of Section B.
582
582
 
583
583
  sdoc-version: 0.2
584
584
 
585
- company: Irreversible Inc.
585
+ company: Acme Corp
586
586
 
587
587
  confidential: true
588
588
 
@@ -9,10 +9,15 @@
9
9
 
10
10
  # About @about
11
11
  {
12
- How to create HTML presentation slides from SDOC files. Covers the slide
13
- deck structure, available layouts (center, two-column), speaker notes,
14
- theme system, and CLI usage. Read the Quick Reference for everyday
15
- authoring. Read the Full Example for a complete deck template.
12
+ How to create presentation slides from SDOC files, and how to get
13
+ them out as HTML, PDF or a Google Slides deck. Covers the deck
14
+ structure, the slide properties, the plain layouts (default, center,
15
+ two-column) and the structured ones (title, section, columns, stats,
16
+ pipeline, matrix, rows, bars, split, stack), speaker notes, the theme
17
+ system, and CLI usage. Read the Quick Reference for everyday
18
+ authoring, [Structured Layouts](#structured-layouts) when a slide
19
+ needs more than prose, and the Full Example for a complete deck
20
+ template.
16
21
  }
17
22
 
18
23
  # Quick Reference @quick-reference
@@ -70,10 +75,38 @@
70
75
  node tools/build-slides.js deck.sdoc --pdf
71
76
  ```
72
77
 
73
- This produces \`deck.pdf\` using headless Chrome in 16:9 landscape
74
- format. Each slide becomes one page. Requires Chrome or Chromium
78
+ This produces \`deck.pdf\` using headless Chrome, one page per
79
+ slide, at the theme's page size. Requires Chrome or Chromium
75
80
  installed on the system.
76
81
 
82
+ To export for Google Slides or PowerPoint:
83
+
84
+ ```
85
+ node tools/build-slides.js deck.sdoc --pptx
86
+ ```
87
+
88
+ This produces \`deck.pptx\`. See [PowerPoint and Google Slides
89
+ Export](#pptx-export) for what it contains and how to import it.
90
+
91
+ To choose what happens when the window is not the slide's shape:
92
+
93
+ ```
94
+ node tools/build-slides.js deck.sdoc --fit contain
95
+ ```
96
+
97
+ See [Aspect Ratio and Fit](#fit) for the three modes.
98
+
99
+ To check that nothing has overflowed its slide:
100
+
101
+ ```
102
+ node tools/build-slides.js deck.sdoc --check
103
+ ```
104
+
105
+ This builds the HTML, measures every slide in a browser, and
106
+ reports any whose content has run past the margin the theme
107
+ reserved. \`--pptx\` runs the same check for free, because it has
108
+ already measured the deck.
109
+
77
110
  To use a custom theme:
78
111
 
79
112
  ```
@@ -85,20 +118,48 @@
85
118
  ```
86
119
  node tools/build-slides.js deck.sdoc --pdf --theme path/to/theme -o presentation.pdf
87
120
  ```
121
+
122
+ \`--pdf\` and \`--pptx\` can run in one invocation; \`-o\` then names
123
+ the PDF and the PPTX takes the input's name.
88
124
  }
89
125
 
90
126
  # Slide Properties @properties
91
127
  {
92
- Add \`config:\` lines at the top of a slide scope (before content)
93
- to control layout:
128
+ A slide scope may open with a run of \`key: value\` lines. They set
129
+ the layout and the furniture around the content, and they must come
130
+ before any content — a recognised key further down is ordinary
131
+ text. Separate each with a blank line, as in \`@meta\`.
94
132
 
95
- \`config: center\` — centres content vertically and horizontally.
96
- Use for title slides, section dividers, and closing slides.
97
-
98
- \`config: two-column\` — child scopes become side-by-side columns.
99
- Use for comparisons, before/after, pros/cons.
133
+ {[table]
134
+ Key | Effect
135
+ \`config:\` or \`layout:\` | The layout. See [Layouts](#layouts) and [Structured Layouts](#structured-layouts).
136
+ \`kicker:\` | The small label above the title. On a \`title\` slide it moves below the statement.
137
+ \`lede:\` | A standfirst line under the title.
138
+ \`footnote:\` | Small print pinned under the body: provenance, caveats, sources.
139
+ \`status:\` | A second line under the kicker on a title slide, for a distribution notice.
140
+ \`accent:\` | \`primary\`, \`secondary\`, \`tertiary\` or \`neutral\`. An accent names a role; the theme chooses the colour.
141
+ \`numbered:\` | \`columns\` and \`rows\`. \`true\` numbers the children 01, 02, 03.
142
+ \`variant:\` | \`columns\` and \`rows\`. A named treatment, such as \`panel\` or \`mono\`.
143
+ \`weights:\` | \`split\` only. The ratio between panes, as in \`48 52\`.
144
+ \`highlight:\` | \`matrix\` only. \`last\`, or text matching the row's first cell.
145
+ \`arrow:\` | \`pipeline\` only. The glyph drawn between steps. Defaults to an arrow.
146
+ \`rule:\` | \`stack\` only. \`true\` draws a hairline between blocks.
147
+ \`value:\` | On a \`rows\` or \`bars\` child: the right-hand figure.
148
+ \`fill:\` | On a \`bars\` child: the bar length as a percentage, overriding the automatic scale.
149
+ \`caption:\` | On a \`columns\` child: a line set under the column.
150
+ }
100
151
 
101
- No config line means default layout: left-aligned, top-to-bottom.
152
+ A key is only configuration where it means something. \`config:\`,
153
+ \`kicker:\`, \`lede:\`, \`footnote:\`, \`accent:\` and \`status:\` are
154
+ understood by every scope; the rest are understood only by the
155
+ layout named against them above. Anywhere else they are ordinary
156
+ text — a slide opening "Value: the customer keeps their data."
157
+ renders that sentence, it does not silently swallow it. A
158
+ paragraph spanning more than one source line is never
159
+ configuration.
160
+
161
+ No config line means the default layout: left-aligned,
162
+ top-to-bottom.
102
163
  }
103
164
 
104
165
  # Speaker Notes @notes
@@ -172,7 +233,12 @@
172
233
  \`[text](url)\` | \`<a>\`
173
234
  \`![alt](src)\` / \`![alt](src =50% center)\` | \`<img>\` (with optional width/alignment)
174
235
  Nested scope | \`<section>\` within the slide
175
- \`@notes\` child | Hidden \`<aside class="notes">\`
236
+ Child scope, structured layout | One cell: a column, a stat, a pipeline row, a bar, a pane
237
+ \`kicker:\` | \`<div class="kicker">\` above the title
238
+ \`lede:\` | \`<p class="lede">\` under the title
239
+ \`footnote:\` | \`<div class="footnote">\` under the body
240
+ \`accent: secondary\` | \`accent-secondary\` class on the slide or cell
241
+ \`@notes\` child | Hidden \`<aside class="notes">\`, exported as PowerPoint speaker notes
176
242
  }
177
243
  }
178
244
  }
@@ -212,6 +278,14 @@
212
278
  ```
213
279
  }
214
280
 
281
+ # Beyond These Three @more-layouts
282
+ {
283
+ \`title\`, \`section\`, \`columns\`, \`stats\`, \`pipeline\`, \`matrix\`,
284
+ \`rows\`, \`bars\`, \`split\` and \`stack\` consume the slide's child
285
+ scopes and arrange them. See
286
+ [Structured Layouts](#structured-layouts).
287
+ }
288
+
215
289
  # Two-Column Layout @two-column-layout
216
290
  {
217
291
  Use \`config: two-column\`. Child scopes become columns.
@@ -239,6 +313,144 @@
239
313
  }
240
314
  }
241
315
 
316
+ # Structured Layouts @structured-layouts
317
+ {
318
+ The plain layouts arrange whatever content a slide holds. A structured
319
+ layout instead consumes the slide's *child scopes* and arranges them
320
+ into a known shape. Each child scope becomes one cell: its heading is
321
+ the cell's label and its content is the cell's body, with the exact
322
+ reading depending on the layout.
323
+
324
+ All of them are optional. A deck that uses none of them behaves exactly
325
+ as it did before they existed.
326
+
327
+ {[table]
328
+ Layout | Children are | Use for
329
+ \`title\` | — | The opening slide. The heading sets as display type and the kicker moves below it.
330
+ \`section\` | — | A divider carrying one statement.
331
+ \`columns\` | Columns, side by side | Parallel points, a set of figures, a row of cards, a people grid.
332
+ \`stats\` | A figure and its caption | Two to four numbers that carry the slide.
333
+ \`pipeline\` | Rows of chained steps | A signal path, a process, a comparison of two paths.
334
+ \`matrix\` | — (takes a table) | A competitive comparison, with per-column accents and one row picked out.
335
+ \`rows\` | Label, body, value rows | A layered model, a timeline, a ledger.
336
+ \`bars\` | Labelled quantities | An allocation of money, effort or time.
337
+ \`split\` | Two panes | Prose beside a figure, an argument beside its schedule.
338
+ \`stack\` | Blocks, one above another | A slide carrying two layouts, such as a diagram above columns.
339
+ }
340
+
341
+ # Composition @layout-composition
342
+ {
343
+ \`split\` and \`stack\` render each child scope as a *block*, and a
344
+ block reads its own \`config:\` line. That is how one slide carries a
345
+ pipeline above a set of numbered columns, or prose beside a
346
+ timeline, without a bespoke layout for the combination.
347
+
348
+ ```
349
+ # How a request is served {
350
+ config: stack
351
+
352
+ rule: true
353
+
354
+ kicker: THE PATH
355
+
356
+ # @paths {
357
+ config: pipeline
358
+
359
+ # Cached {
360
+ accent: primary
361
+
362
+ {[.]
363
+ - Request
364
+ - **Edge hit**
365
+ - Response
366
+ }
367
+ }
368
+ }
369
+
370
+ # @detail {
371
+ config: columns
372
+
373
+ numbered: true
374
+
375
+ # The hot path {
376
+ One hop, no origin round trip.
377
+ }
378
+
379
+ # The cold path {
380
+ Two hops, and the origin does the work.
381
+ }
382
+ }
383
+ }
384
+ ```
385
+
386
+ Nesting stops there. A block inside a block is ordinary content.
387
+ }
388
+
389
+ # Pipeline, and marking meaning @layout-pipeline
390
+ {
391
+ A pipeline row is a child scope: its heading is the row label and
392
+ its bullet list is the steps. **A step written entirely in bold is
393
+ marked** and takes the row's accent; every other step is neutral.
394
+
395
+ The distinction is semantic. Where a slide sets two paths against
396
+ each other, the marked steps are the ones a path cannot avoid and
397
+ the unmarked ones are the stages both paths share — so the reader
398
+ counts marks rather than reading a caption. The author marks what
399
+ is required; the theme decides what required looks like.
400
+ Restyling the deck must not lose that, which is why markedness
401
+ lives in the source rather than in a colour.
402
+
403
+ Set the row's accent with \`accent:\`. A theme may supply a default
404
+ — the built-in theme alternates between its first two accents —
405
+ but an explicit \`accent:\` always wins, so a deck that states its
406
+ accents is safe against a theme change.
407
+
408
+ \`arrow:\` sets the glyph drawn between steps, if the default arrow
409
+ is wrong for the diagram.
410
+ }
411
+
412
+ # Matrix @layout-matrix
413
+ {
414
+ A matrix slide holds one table. Column headings alternate between
415
+ two accents so a wide table stays scannable, and one row can be
416
+ picked out with \`highlight:\` — either \`last\`, or text the row's
417
+ first cell contains. A name that several rows share highlights all
418
+ of them, so pick something specific to the row you mean.
419
+
420
+ Within a cell, \`**bold**\` marks the name and \`*italic*\` marks the
421
+ trailing qualifier, which themes set smaller and dimmer. That keeps
422
+ a first column like **Beta Compute** *Series C, shipping* legible
423
+ as two pieces of information rather than one run-on line.
424
+ }
425
+
426
+ # Rows and bars @layout-rows
427
+ {
428
+ A \`rows\` child carries up to four parts: the automatic index when
429
+ the layout sets \`numbered: true\`, the heading as the label, the
430
+ content as the body, and \`value:\` as the right-hand figure.
431
+ \`variant: mono\` turns the label into a monospace accent, which is
432
+ what makes a timeline; \`variant: ledger\` drops the body and leaves
433
+ a label and a figure.
434
+
435
+ A \`bars\` child is a label, a \`value:\` and a note. Bars are scaled
436
+ against the largest numeric value in the set, so \`4,200 docs\` and
437
+ \`270 docs\` produce the right proportion without the author
438
+ computing percentages. Set \`fill:\` on a bar to override that.
439
+ }
440
+
441
+ # Images @layout-images
442
+ {
443
+ Images use the ordinary inline form, \`![alt](path)\`, inside
444
+ whichever cell they belong to. In a \`columns\` slide with
445
+ \`variant: card\`, an image at the top of each column with a heading
446
+ and a line of body beneath gives the standard figure row.
447
+
448
+ Paths are resolved relative to the \`.sdoc\` file. They are embedded
449
+ in the PPTX export; a remote URL is left out of it, and the build
450
+ says which.
451
+ }
452
+ }
453
+
242
454
  # Drilldown Slides @drilldown
243
455
  {
244
456
  A slide deck is normally a 1D spine: Right and Left move along it. A
@@ -250,14 +462,14 @@
250
462
  # Syntax @drilldown-syntax
251
463
  {
252
464
  ```
253
- # ETHyR Mechanism @ethyr {
465
+ # How the cache works @cache {
254
466
  Main spine content.
255
467
 
256
- # PCR primer @pcr :detail {
468
+ # Eviction policy @eviction :detail {
257
469
  Detail slide one.
258
470
  }
259
471
 
260
- # HCR primer @hcr :detail {
472
+ # Cold-start behaviour @cold-start :detail {
261
473
  Detail slide two.
262
474
  }
263
475
 
@@ -325,7 +537,102 @@
325
537
  \`theme.css\` — all visual styling (typography, colours, spacing, layouts).
326
538
 
327
539
  \`theme.js\` (optional) — runtime behaviour (keyboard nav, touch support,
328
- slide counter). The built-in default theme includes this.
540
+ slide counter). A theme that ships none inherits the default runtime,
541
+ so most themes should omit it.
542
+
543
+ \`theme.json\` (optional) — the design box and the print page.
544
+
545
+ Assets (optional) — fonts and images the CSS refers to, in any
546
+ subdirectory.
547
+
548
+ # The design box @theme-box
549
+ {
550
+ Slides are laid out at a fixed size and the whole slide is then
551
+ scaled to fill the window, so the author's layout is preserved
552
+ verbatim at every window size and the screen and the PDF are the
553
+ same geometry by construction.
554
+
555
+ \`theme.json\` declares that size, and may declare the default fit
556
+ mode (see [Aspect Ratio and Fit](#fit)). The box in CSS pixels and
557
+ the page in inches must agree at 96 dpi, or the PDF clips:
558
+
559
+ ```
560
+ {
561
+ "name": "wide-dark",
562
+ "slide": { "width": 1920, "height": 1080 },
563
+ "page": { "width": 20, "height": 11.25 },
564
+ "fit": "contain",
565
+ "fonts": { "display": "Georgia", "body": "Helvetica" }
566
+ }
567
+ ```
568
+
569
+ With no \`theme.json\` a theme gets 1280 by 720, which is 13.333 by
570
+ 7.5 inches — the same 16:9 proportion.
571
+
572
+ \`fonts\` names the typefaces the PPTX export falls back to when a
573
+ CSS font stack resolves to a generic family. It has no effect on
574
+ the HTML.
575
+
576
+ Every length in a theme's CSS is a pixel value at the design size.
577
+ A theme written against a 1920 by 1080 box sets a 68px heading and
578
+ means 68px on a 1920-wide slide, whatever the window is doing.
579
+ }
580
+
581
+ # Fonts and other assets @theme-assets
582
+ {
583
+ A theme may ship web fonts, background images or textures beside
584
+ its CSS and refer to them with ordinary relative URLs:
585
+
586
+ ```
587
+ @font-face {
588
+ font-family: "Deck Sans";
589
+ src: url("fonts/deck-sans-latin.woff2") format("woff2");
590
+ }
591
+ ```
592
+
593
+ The builder rewrites every relative \`url()\` in \`theme.css\` to a
594
+ \`data:\` URI, so the built deck is one file that carries its own
595
+ typefaces. Ship only fonts whose licence allows redistribution —
596
+ the SIL Open Font License does. It renders the same offline, on a machine without the
597
+ fonts installed, and inside headless Chrome during export —
598
+ which matters, because a PDF built against a fallback font is a
599
+ PDF with the wrong line breaks.
600
+
601
+ Absolute URLs, protocol-relative URLs and existing \`data:\` URIs
602
+ are left alone, and a path escaping the theme directory is
603
+ refused and reported.
604
+ }
605
+
606
+ # Writing against the layouts @theme-classes
607
+ {
608
+ A slide carries \`layout-<name>\` for whatever \`config:\` named, plus
609
+ \`accent-<name>\` if the slide set one. Target those. The bare layout
610
+ name is also emitted for \`center\` and \`two-column\` alone, because
611
+ themes written before \`layout-*\` existed rely on it; a theme that
612
+ styled some other layout by its bare name must move to
613
+ \`layout-<name>\`.
614
+
615
+ The head and body are wrapped in \`.slide-head\` and \`.slide-body\`,
616
+ both \`display: contents\` by default, so the *boxes* a theme sees
617
+ are the boxes it always saw: the heading and the content lay out
618
+ as direct children of the slide. A theme that wants a real header
619
+ band, or a body that takes the remaining height, overrides those
620
+ two rules.
621
+
622
+ **The DOM did change, even though the layout did not.**
623
+ \`display: contents\` removes a box, not an element, so a selector
624
+ written with a child combinator no longer matches: \`.slide > h2\`
625
+ and \`.slide > p\` have to become \`.slide h2\` and \`.slide p\`, or
626
+ name the wrapper. The same goes for \`:first-child\` and
627
+ \`:nth-child\` counted over a slide's children. Descendant
628
+ selectors are unaffected.
629
+
630
+ Structured layouts emit stable class names inside the body:
631
+ \`.columns > .column\`, \`.stats > .stat\`, \`.pipeline > .pipe-row\`,
632
+ \`.matrix\`, \`.rows > .row\`, \`.bars > .bar\`, \`.split > .pane\`,
633
+ \`.stack > .stack-block\`. A theme is free to style only the ones it
634
+ uses.
635
+ }
329
636
 
330
637
  # Built-in Default Theme @default-theme
331
638
  {
@@ -367,6 +674,109 @@
367
674
  }
368
675
  }
369
676
 
677
+ # Aspect Ratio and Fit @fit
678
+ {
679
+ A deck is laid out at one fixed size — the theme's design box — and the
680
+ whole slide is then scaled to meet the window. A window is rarely the
681
+ slide's exact shape, and what happens in that case is a build-time
682
+ choice:
683
+
684
+ {[table]
685
+ \`--fit\` | What happens off-ratio | Cost
686
+ \`contain\` | Scale to fit; the remainder is letterboxed | Unused screen at the edges
687
+ \`cover\` | Scale to fill; the overflow is cropped | Content near an edge is lost, including the footer
688
+ \`stretch\` | Scale each axis to the window | The slide is distorted
689
+ }
690
+
691
+ \`contain\` is the default and is almost always the right answer: the
692
+ deck keeps the proportions it was designed in, and nothing is lost or
693
+ misshapen. A theme may set its own default with \`"fit"\` in
694
+ \`theme.json\`, and \`--fit\` on the command line overrides it.
695
+
696
+ # Make the letterbox visible @fit-letterbox
697
+ {
698
+ With \`contain\`, the area the slide does not cover is painted with
699
+ \`--sdoc-letterbox\`. **A theme must not leave that equal to its own
700
+ slide background.** If it does, the slide has no visible edge, and
701
+ on an off-ratio window anything pinned to the bottom of the slide —
702
+ the footer, a footnote — appears to float in the middle of the
703
+ window instead of sitting on the slide's edge. Nothing is
704
+ misplaced; there is simply no edge to see it against.
705
+
706
+ Set the two apart:
707
+
708
+ ```
709
+ :root { --sdoc-letterbox: #020202; } /* behind the slide */
710
+ body { background: var(--sdoc-letterbox); }
711
+ .slide { background: #101010; } /* the slide itself */
712
+ ```
713
+
714
+ A hairline helps further on a dark theme, where the two grounds are
715
+ necessarily close:
716
+
717
+ ```
718
+ .slide { box-shadow: 0 0 0 1px #1E1E1E; }
719
+ ```
720
+ }
721
+
722
+ # For theme runtimes @fit-runtime
723
+ {
724
+ The renderer writes the mode to \`<html data-sdoc-fit>\` and the
725
+ theme runtime reads it, publishing \`--sdoc-slide-scale\` and
726
+ \`--sdoc-slide-scale-y\` on \`:root\`. Only \`stretch\` ever sets them
727
+ to different values.
728
+
729
+ \`--sdoc-slide-scale-y\` defaults to \`--sdoc-slide-scale\`, so a
730
+ theme shipping its own \`theme.js\` that writes only the one
731
+ variable still scales uniformly rather than collapsing vertically.
732
+ }
733
+ }
734
+
735
+ # PowerPoint and Google Slides Export @pptx-export
736
+ {
737
+ \`--pptx\` writes a PowerPoint file. Drive imports it as a native Google
738
+ Slides deck: upload it, then **File > Open with > Google Slides**, or
739
+ drag it into \`slides.google.com\`. That is the export route rather than
740
+ the Slides API because it needs no credentials in the toolchain, and
741
+ because the result is a useful file on its own.
742
+
743
+ # How it works @pptx-mechanism
744
+ {
745
+ The exporter has no layout engine. It builds the HTML deck, opens
746
+ it in headless Chrome, and asks the browser where every box, rule,
747
+ image and run of text ended up. Each of those becomes one
748
+ absolutely positioned shape in the exported file.
749
+
750
+ The consequence worth knowing: **the CSS is the single source of
751
+ layout truth**. A new layout, a new theme, a change to a margin —
752
+ all of it exports correctly without the exporter being taught
753
+ anything, because the exporter only ever reads the result.
754
+
755
+ Absolute positioning is also how a designed deck is built by hand.
756
+ The exported file is loose shapes on one blank layout, which is
757
+ what a designer would hand over, and it stays editable in Slides.
758
+ }
759
+
760
+ # What carries across @pptx-fidelity
761
+ {
762
+ Position, size, typeface, size, weight, italic, colour, letter
763
+ spacing, alignment and exact line spacing, per run. Fills,
764
+ borders, corner radii and alpha. Images, embedded from local paths
765
+ or \`data:\` URIs. Slide backgrounds. Speaker notes, as notes.
766
+
767
+ A border on only some sides is exported as a filled bar per side
768
+ rather than an outlined box, so a hairline under a heading is a
769
+ hairline rather than a rectangle with three invisible edges.
770
+
771
+ What does not carry: web fonts are named, not embedded, so the
772
+ fonts a theme uses must be available to the viewer. Choosing faces
773
+ that are on Google Fonts is the reliable route, because Slides
774
+ resolves those. Remote images are skipped and reported. Mermaid
775
+ diagrams and KaTeX export as the text and shapes the browser drew,
776
+ not as editable objects.
777
+ }
778
+ }
779
+
370
780
  # Full Example @full-example
371
781
  {
372
782
  A complete slide deck demonstrating all features:
@@ -489,7 +899,18 @@
489
899
  it will be rendered as regular text.
490
900
 
491
901
  **Using config on the document root.** \`config:\` only works on slide
492
- scopes (direct children of the root), not on the root itself.
902
+ scopes (direct children of the root), and on child scopes in a
903
+ \`split\` or \`stack\` — not on the root itself.
904
+
905
+ **Putting content in a structured layout's slide scope.** In
906
+ \`columns\`, \`stats\`, \`rows\` and the rest, the *child scopes* are the
907
+ content. A paragraph written directly in the slide scope appears above
908
+ the structure, which is occasionally what you want and usually not —
909
+ \`lede:\` is the way to put a line under the title.
910
+
911
+ **Expecting a bullet list to become a pipeline.** A \`pipeline\` slide's
912
+ steps are the items of a bullet list inside a *child scope*, one scope
913
+ per row. A list written directly in the slide scope is just a list.
493
914
 
494
915
  **Forgetting the @notes id.** Speaker notes require the \`@notes\` id
495
916
  on the child scope heading. Without it, the scope renders as a visible
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@entropicwarrior/sdoc",
3
3
  "displayName": "SDOC - Docs for Human/Agent Teams",
4
4
  "description": "A plain-text documentation format with explicit brace scoping — deterministic parsing, AI-agent efficiency, and 10-50x token savings vs Markdown.",
5
- "version": "0.2.18",
5
+ "version": "0.2.20",
6
6
  "publisher": "entropicwarrior-msenfin",
7
7
  "license": "MIT",
8
8
  "repository": {
@@ -28,6 +28,9 @@
28
28
  ".": "./index.js",
29
29
  "./slides": "./src/slide-renderer.js",
30
30
  "./slide-pdf": "./src/slide-pdf.js",
31
+ "./slide-pptx": "./src/slide-pptx.js",
32
+ "./slide-geometry": "./src/slide-geometry.js",
33
+ "./theme": "./src/theme.js",
31
34
  "./notion": "./src/notion-renderer.js",
32
35
  "./knowledge": "./knowledge.js"
33
36
  },