@entropicwarrior/sdoc 0.2.16 → 0.2.18
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/docs/reference/sdoc-authoring.sdoc +19 -0
- package/lexica/specification.sdoc +20 -1
- package/package.json +1 -1
- package/src/sdoc.js +69 -11
- package/src/slide-renderer.js +51 -4
|
@@ -333,6 +333,22 @@ Content of Section B.
|
|
|
333
333
|
|
|
334
334
|
The \`Amount\` column is right-aligned and formatted as currency. The formula result and data cells all display as \`$1,000,000\` style. The \`**Total**\` label renders as bold text.
|
|
335
335
|
}
|
|
336
|
+
|
|
337
|
+
# Copyable Columns
|
|
338
|
+
{
|
|
339
|
+
A \`copy\` token in the directive row gives every body cell in that column a click-to-copy icon (copying the cell's raw text). Ideal for columns of sequences, IDs, or commands.
|
|
340
|
+
|
|
341
|
+
```
|
|
342
|
+
{[table]
|
|
343
|
+
Gene | Sequence
|
|
344
|
+
| copy
|
|
345
|
+
BRCA1 | ACGTACGTGGCA
|
|
346
|
+
TP53 | GGCATTACGATT
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
\`copy\` can share a directive row with alignment/format directives on other columns.
|
|
351
|
+
}
|
|
336
352
|
}
|
|
337
353
|
|
|
338
354
|
# Table Formulas @table-formulas
|
|
@@ -385,10 +401,13 @@ Content of Section B.
|
|
|
385
401
|
\`\{-text-\}\` | Negative marker (red)
|
|
386
402
|
\`\{~text~\}\` | Highlight (yellow)
|
|
387
403
|
\`#1a73e8\` | Hex color swatch (auto-readable text)
|
|
404
|
+
\`\{copy\}text\{/copy\}\` | Copyable text with click-to-copy icon
|
|
388
405
|
}
|
|
389
406
|
|
|
390
407
|
Hex color codes (\`#rgb\`, \`#rgba\`, \`#rrggbb\`, \`#rrggbbaa\`) render as inline swatches — a rounded box filled with the color and labelled with the code, with the text color chosen automatically (black or white) for legibility. They are detected in normal text, list items, table cells, and headings, but not inside \`\\\`inline code\\\`\` (which stays literal). A bare \`#\` at the start of a line is a heading; escape with \`\\#\` to keep a literal hash.
|
|
391
408
|
|
|
409
|
+
Copyable text: \`\{copy\}ACGTACGTGGCA\{/copy\}\` renders the inner text verbatim in monospace with a copy icon; clicking it copies the text. Great for DNA/protein sequences, IDs, hashes, and commands. Escape with \`\\\{copy\}\` for a literal. Copy icons show in HTML output (preview, exported HTML, web viewer) and are hidden when printed/exported to PDF.
|
|
410
|
+
|
|
392
411
|
Links: \`[Link text](https://example.com)\` or \`[Other doc](./other-file.sdoc)\`. Relative paths resolve from the document's directory.
|
|
393
412
|
|
|
394
413
|
Images: \`\`
|
|
@@ -435,9 +435,25 @@ Content of Section B.
|
|
|
435
435
|
All numeric values and formula results in the `Amount` column display as currency (`$3,500,000`). The `**Total**` label renders normally as bold text.
|
|
436
436
|
}
|
|
437
437
|
|
|
438
|
+
# Copyable Columns @column-copy
|
|
439
|
+
{
|
|
440
|
+
A `copy` token (case-insensitive) in the directive row marks a column copyable: every body cell in that column is rendered with a click-to-copy icon whose target is the cell's raw text. Useful for sequences, identifiers, hashes, and commands.
|
|
441
|
+
|
|
442
|
+
```
|
|
443
|
+
{[table]
|
|
444
|
+
Gene | Sequence
|
|
445
|
+
| copy
|
|
446
|
+
BRCA1 | ACGTACGTGGCA
|
|
447
|
+
TP53 | GGCATTACGATT
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
The `copy` token occupies a directive cell of its own and may coexist with alignment/format directives on other columns in the same row. The literal (unformatted) cell text is copied. Copy icons are interactive HTML only — they appear in preview, exported HTML, and the web viewer, and are hidden in printed/PDF output.
|
|
452
|
+
}
|
|
453
|
+
|
|
438
454
|
# Directive Row Detection @directive-detection
|
|
439
455
|
{
|
|
440
|
-
A row is identified as a directive row when every non-empty cell contains only an alignment character (`<`, `>`, `=`), a format token, or
|
|
456
|
+
A row is identified as a directive row when every non-empty cell contains only an alignment character (`<`, `>`, `=`), a format token, the `copy` keyword, or a combination of alignment and format. It must be the first row after the header (or the first row in a headerless table), and at least one cell must contain a directive.
|
|
441
457
|
|
|
442
458
|
For headerless tables, the directive row functions as a virtual header — it is consumed for its directives but no `<thead>` is rendered.
|
|
443
459
|
}
|
|
@@ -720,6 +736,7 @@ Content of Section B.
|
|
|
720
736
|
- Negative marker: `{-text-}` (red highlight)
|
|
721
737
|
- Highlight: `{~text~}` (yellow highlight)
|
|
722
738
|
- Hex color swatch: `#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa` (e.g. `#9aa0a8`) renders as a filled inline box labelled with the code
|
|
739
|
+
- Copyable text: `{copy}...{/copy}` renders the inner text verbatim (monospace) with a click-to-copy icon
|
|
723
740
|
- Citation reference: `[@key]` (numbered superscript link to citation entry)
|
|
724
741
|
- Multiple citation references: `[@key1, @key2]` (each individually linked)
|
|
725
742
|
}
|
|
@@ -728,6 +745,8 @@ Content of Section B.
|
|
|
728
745
|
|
|
729
746
|
Hex color swatches are detected only when the `#` is at the start of the inline text or preceded by a non-alphanumeric character, the digit run is exactly 3, 4, 6, or 8 hexadecimal digits, and it is not immediately followed by another letter, digit, or underscore. The background is the literal color and the text color (black or white) is chosen by perceptual brightness (YIQ); for `#rgba`/`#rrggbbaa` the alpha channel is ignored when choosing the text color. Hex inside `` `inline code` `` stays literal, and a `#` at the start of a line is parsed as a heading (escape with `\#`).
|
|
730
747
|
|
|
748
|
+
Copyable text spans from `{copy}` to the next unescaped `{/copy}`. The inner text is captured verbatim — no nested inline formatting is applied — and is both displayed (monospace) and used as the copy target. An unterminated `{copy}` (no closing `{/copy}`) falls through as literal text. Escape with `\{copy}` to write a literal. As with other `{`-delimited constructs, a `{copy}` at the very start of a line is subject to block-level parsing; use it mid-line or within a cell.
|
|
749
|
+
|
|
731
750
|
Citation references (`[@key]`) are parsed before link syntax. `[@key]` is always a citation reference even if followed by `(url)`. Use `\[@key]` for a literal `[@key]` in text.
|
|
732
751
|
}
|
|
733
752
|
|
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.
|
|
5
|
+
"version": "0.2.18",
|
|
6
6
|
"publisher": "entropicwarrior-msenfin",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"repository": {
|
package/src/sdoc.js
CHANGED
|
@@ -72,6 +72,7 @@ function isDirectiveRow(cells) {
|
|
|
72
72
|
for (const cell of cells) {
|
|
73
73
|
const trimmed = cell.trim();
|
|
74
74
|
if (trimmed === "") continue;
|
|
75
|
+
if (trimmed.toLowerCase() === "copy") { hasDirective = true; continue; }
|
|
75
76
|
if (!pattern.test(trimmed)) return false;
|
|
76
77
|
hasDirective = true;
|
|
77
78
|
}
|
|
@@ -102,12 +103,23 @@ function parseFormatSpec(spec) {
|
|
|
102
103
|
function parseDirectiveRow(cells) {
|
|
103
104
|
const align = [];
|
|
104
105
|
const format = [];
|
|
106
|
+
const copy = [];
|
|
105
107
|
let hasAlign = false;
|
|
106
108
|
let hasFormat = false;
|
|
109
|
+
let hasCopy = false;
|
|
107
110
|
const fmtPattern = /(\$(?:\.\d+)?|,(?:\.\d+)?|\.\d+|%(?:\.\d+)?)$/;
|
|
108
111
|
|
|
109
112
|
for (const cell of cells) {
|
|
110
113
|
const trimmed = cell.trim();
|
|
114
|
+
|
|
115
|
+
if (trimmed.toLowerCase() === "copy") {
|
|
116
|
+
align.push(null);
|
|
117
|
+
format.push(null);
|
|
118
|
+
copy.push(true);
|
|
119
|
+
hasCopy = true;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
111
123
|
const alignMatch = trimmed.match(/^([<>=])/);
|
|
112
124
|
const fmtMatch = trimmed.match(fmtPattern);
|
|
113
125
|
|
|
@@ -124,11 +136,13 @@ function parseDirectiveRow(cells) {
|
|
|
124
136
|
hasFormat = true;
|
|
125
137
|
}
|
|
126
138
|
format.push(f);
|
|
139
|
+
copy.push(false);
|
|
127
140
|
}
|
|
128
141
|
|
|
129
142
|
return {
|
|
130
143
|
align: hasAlign ? align : null,
|
|
131
144
|
format: hasFormat ? format : null,
|
|
145
|
+
copy: hasCopy ? copy : null,
|
|
132
146
|
};
|
|
133
147
|
}
|
|
134
148
|
|
|
@@ -940,10 +954,12 @@ function parseTableBody(cursor, tableStartLine, options) {
|
|
|
940
954
|
const directiveIndex = options.headerless ? 0 : 1;
|
|
941
955
|
let columnAlign = null;
|
|
942
956
|
let columnFormat = null;
|
|
957
|
+
let columnCopy = null;
|
|
943
958
|
if (rows.length > directiveIndex && isDirectiveRow(rows[directiveIndex])) {
|
|
944
959
|
const directives = parseDirectiveRow(rows[directiveIndex]);
|
|
945
960
|
columnAlign = directives.align;
|
|
946
961
|
columnFormat = directives.format;
|
|
962
|
+
columnCopy = directives.copy;
|
|
947
963
|
rows.splice(directiveIndex, 1);
|
|
948
964
|
}
|
|
949
965
|
|
|
@@ -960,6 +976,7 @@ function parseTableBody(cursor, tableStartLine, options) {
|
|
|
960
976
|
if (hasOptions) tableNode.options = options;
|
|
961
977
|
if (columnAlign) tableNode.columnAlign = columnAlign;
|
|
962
978
|
if (columnFormat) tableNode.columnFormat = columnFormat;
|
|
979
|
+
if (columnCopy) tableNode.columnCopy = columnCopy;
|
|
963
980
|
return tableNode;
|
|
964
981
|
}
|
|
965
982
|
|
|
@@ -1429,6 +1446,18 @@ function parseInline(text) {
|
|
|
1429
1446
|
}
|
|
1430
1447
|
}
|
|
1431
1448
|
|
|
1449
|
+
// Copyable inline text: {copy}literal text{/copy}. The inner text is copied
|
|
1450
|
+
// verbatim (no nested formatting), rendered monospace with a copy icon.
|
|
1451
|
+
if (text.startsWith("{copy}", i)) {
|
|
1452
|
+
const end = findUnescaped(text, i + 6, "{/copy}");
|
|
1453
|
+
if (end !== -1) {
|
|
1454
|
+
flush();
|
|
1455
|
+
nodes.push({ type: "copyable", value: text.slice(i + 6, end) });
|
|
1456
|
+
i = end + 7; // length of "{/copy}"
|
|
1457
|
+
continue;
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1432
1461
|
if (ch === "{") {
|
|
1433
1462
|
const mc = next;
|
|
1434
1463
|
let mt = null;
|
|
@@ -1821,6 +1850,8 @@ function renderInlineNodes(nodes) {
|
|
|
1821
1850
|
}
|
|
1822
1851
|
case "code":
|
|
1823
1852
|
return `<code class="sdoc-inline-code">${escapeHtml(node.value)}</code>`;
|
|
1853
|
+
case "copyable":
|
|
1854
|
+
return `<span class="sdoc-copyable"><code class="sdoc-inline-code">${escapeHtml(node.value)}</code><button class="sdoc-copy-btn sdoc-copy-inline" data-copy="${escapeAttr(node.value)}" title="Copy">⧉</button></span>`;
|
|
1824
1855
|
case "color_swatch":
|
|
1825
1856
|
return colorSwatchHtml(node.value);
|
|
1826
1857
|
case "em":
|
|
@@ -2304,6 +2335,14 @@ function renderTable(table) {
|
|
|
2304
2335
|
const opts = table.options || {};
|
|
2305
2336
|
const colAlign = table.columnAlign || [];
|
|
2306
2337
|
const colFormat = table.columnFormat || [];
|
|
2338
|
+
const colCopy = table.columnCopy || [];
|
|
2339
|
+
|
|
2340
|
+
// Copy icon appended to body cells in a `copy` directive column. data-copy
|
|
2341
|
+
// carries the raw cell text so the shared copy handler copies it verbatim.
|
|
2342
|
+
function copyBtn(cell, c) {
|
|
2343
|
+
if (!colCopy[c]) return "";
|
|
2344
|
+
return ` <button class="sdoc-copy-btn sdoc-copy-inline" data-copy="${escapeAttr(cell.trim())}" title="Copy">⧉</button>`;
|
|
2345
|
+
}
|
|
2307
2346
|
const classes = ["sdoc-table"];
|
|
2308
2347
|
if (opts.borderless) classes.push("sdoc-table-borderless");
|
|
2309
2348
|
if (opts.headerless) classes.push("sdoc-table-headerless");
|
|
@@ -2343,20 +2382,20 @@ function renderTable(table) {
|
|
|
2343
2382
|
}
|
|
2344
2383
|
const formula = escapeAttr(cell.trim());
|
|
2345
2384
|
if (result.error) {
|
|
2346
|
-
return `<td class="sdoc-table-td sdoc-formula-error"${style} title="${formula}">${display}</td>`;
|
|
2385
|
+
return `<td class="sdoc-table-td sdoc-formula-error"${style} title="${formula}">${display}${copyBtn(cell, c)}</td>`;
|
|
2347
2386
|
}
|
|
2348
|
-
return `<td class="sdoc-table-td sdoc-formula-cell"${style} title="${formula}">${display}</td>`;
|
|
2387
|
+
return `<td class="sdoc-table-td sdoc-formula-cell"${style} title="${formula}">${display}${copyBtn(cell, c)}</td>`;
|
|
2349
2388
|
}
|
|
2350
2389
|
|
|
2351
2390
|
// Apply column format to numeric data cells
|
|
2352
2391
|
if (fmt) {
|
|
2353
2392
|
const parsed = parseCellValue(cell);
|
|
2354
2393
|
if (!isNaN(parsed.value)) {
|
|
2355
|
-
return `<td class="sdoc-table-td"${style}>${escapeHtml(formatNumber(parsed.value, fmt))}</td>`;
|
|
2394
|
+
return `<td class="sdoc-table-td"${style}>${escapeHtml(formatNumber(parsed.value, fmt))}${copyBtn(cell, c)}</td>`;
|
|
2356
2395
|
}
|
|
2357
2396
|
}
|
|
2358
2397
|
|
|
2359
|
-
return `<td class="sdoc-table-td"${style}>${renderInline(cell)}</td>`;
|
|
2398
|
+
return `<td class="sdoc-table-td"${style}>${renderInline(cell)}${copyBtn(cell, c)}</td>`;
|
|
2360
2399
|
})
|
|
2361
2400
|
.join("");
|
|
2362
2401
|
return `<tr>${cells}</tr>`;
|
|
@@ -3052,10 +3091,6 @@ const PRINT_STYLE = `
|
|
|
3052
3091
|
position: relative;
|
|
3053
3092
|
}
|
|
3054
3093
|
.sdoc-copy-btn {
|
|
3055
|
-
position: absolute;
|
|
3056
|
-
top: 6px;
|
|
3057
|
-
right: 6px;
|
|
3058
|
-
z-index: 1;
|
|
3059
3094
|
background: transparent;
|
|
3060
3095
|
border: none;
|
|
3061
3096
|
border-radius: 4px;
|
|
@@ -3063,15 +3098,35 @@ const PRINT_STYLE = `
|
|
|
3063
3098
|
font-size: 0.85rem;
|
|
3064
3099
|
line-height: 1;
|
|
3065
3100
|
padding: 2px 6px;
|
|
3101
|
+
color: inherit;
|
|
3102
|
+
}
|
|
3103
|
+
.sdoc-code-wrap .sdoc-copy-btn {
|
|
3104
|
+
position: absolute;
|
|
3105
|
+
top: 6px;
|
|
3106
|
+
right: 6px;
|
|
3107
|
+
z-index: 1;
|
|
3066
3108
|
opacity: 0;
|
|
3067
3109
|
transition: opacity 0.15s;
|
|
3068
3110
|
}
|
|
3069
3111
|
.sdoc-code-wrap:hover .sdoc-copy-btn {
|
|
3070
3112
|
opacity: 0.75;
|
|
3071
3113
|
}
|
|
3072
|
-
.sdoc-copy-btn:hover {
|
|
3114
|
+
.sdoc-code-wrap .sdoc-copy-btn:hover {
|
|
3073
3115
|
opacity: 1 !important;
|
|
3074
3116
|
}
|
|
3117
|
+
.sdoc-copyable {
|
|
3118
|
+
white-space: nowrap;
|
|
3119
|
+
}
|
|
3120
|
+
.sdoc-copy-inline {
|
|
3121
|
+
font-size: 0.8em;
|
|
3122
|
+
padding: 0 0.15em;
|
|
3123
|
+
opacity: 0.45;
|
|
3124
|
+
vertical-align: baseline;
|
|
3125
|
+
transition: opacity 0.15s;
|
|
3126
|
+
}
|
|
3127
|
+
.sdoc-copy-inline:hover {
|
|
3128
|
+
opacity: 1;
|
|
3129
|
+
}
|
|
3075
3130
|
@media print {
|
|
3076
3131
|
html {
|
|
3077
3132
|
font-size: 80%;
|
|
@@ -3110,7 +3165,7 @@ const PRINT_STYLE = `
|
|
|
3110
3165
|
|
|
3111
3166
|
const COLLAPSE_SCRIPT = `document.addEventListener("click",function(e){if(!e.target.classList.contains("sdoc-toggle"))return;e.stopPropagation();var s=e.target.closest(".sdoc-scope");if(s)s.classList.toggle("sdoc-collapsed")});`;
|
|
3112
3167
|
|
|
3113
|
-
const COPY_SCRIPT = `document.addEventListener("click",function(e){
|
|
3168
|
+
const COPY_SCRIPT = `document.addEventListener("click",function(e){var b=e.target.closest(".sdoc-copy-btn");if(!b)return;e.stopPropagation();e.preventDefault();var t=b.getAttribute("data-copy");if(t===null){var w=b.closest(".sdoc-code-wrap");if(!w)return;var c=w.querySelector("code");if(!c)return;t=c.textContent}if(navigator.clipboard){navigator.clipboard.writeText(t).then(function(){b.textContent="\\u2713";setTimeout(function(){b.textContent="\\u29C9"},1500)})}else{var a=document.createElement("textarea");a.value=t;a.style.position="fixed";a.style.opacity="0";document.body.appendChild(a);a.select();document.execCommand("copy");document.body.removeChild(a);b.textContent="\\u2713";setTimeout(function(){b.textContent="\\u29C9"},1500)}});`;
|
|
3114
3169
|
|
|
3115
3170
|
const MERMAID_CDN = "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js";
|
|
3116
3171
|
const KATEX_CDN_CSS = "https://cdn.jsdelivr.net/npm/katex@0.16/dist/katex.min.css";
|
|
@@ -3232,7 +3287,10 @@ function renderHtmlDocumentFromParsed(parsed, title, options = {}) {
|
|
|
3232
3287
|
const footerHtml = meta.footerNodes ? renderFragment(meta.footerNodes, 2)
|
|
3233
3288
|
: meta.footerText ? renderTextParagraphs(meta.footerText)
|
|
3234
3289
|
: renderTextParagraphs(config.footer);
|
|
3235
|
-
|
|
3290
|
+
// A specific footer (footer: meta, or config.footer) overrides the company
|
|
3291
|
+
// auto-footer so the two do not stack into a doubled footer. The company name
|
|
3292
|
+
// is used only as the fallback footer when no footer text is provided.
|
|
3293
|
+
const footerContent = footerHtml || companyHtml;
|
|
3236
3294
|
|
|
3237
3295
|
const cssBase = options.cssOverride ?? DEFAULT_STYLE;
|
|
3238
3296
|
const cssAppend = options.cssAppend ? `\n${options.cssAppend}\n${PRINT_STYLE}` : `\n${PRINT_STYLE}`;
|
package/src/slide-renderer.js
CHANGED
|
@@ -21,6 +21,9 @@ function renderInlineNodes(nodes) {
|
|
|
21
21
|
return escapeHtml(node.value);
|
|
22
22
|
case "code":
|
|
23
23
|
return `<code>${escapeHtml(node.value)}</code>`;
|
|
24
|
+
case "copyable":
|
|
25
|
+
// Slides have no copy handler; render the literal text as monospace.
|
|
26
|
+
return `<code>${escapeHtml(node.value)}</code>`;
|
|
24
27
|
case "color_swatch":
|
|
25
28
|
return colorSwatchHtml(node.value);
|
|
26
29
|
case "em":
|
|
@@ -319,8 +322,15 @@ function renderSlides(nodes, options = {}) {
|
|
|
319
322
|
slideScopes = nodes;
|
|
320
323
|
}
|
|
321
324
|
|
|
322
|
-
// Filter to scope nodes only (skip stray paragraphs
|
|
323
|
-
|
|
325
|
+
// Filter to scope nodes only (skip stray paragraphs, :comment scopes, and the
|
|
326
|
+
// reserved @meta / @about metadata scopes — these are document metadata, not slides).
|
|
327
|
+
const RESERVED_SCOPE_IDS = new Set(["meta", "about"]);
|
|
328
|
+
const slides = slideScopes.filter(
|
|
329
|
+
(n) =>
|
|
330
|
+
n.type === "scope" &&
|
|
331
|
+
n.scopeType !== "comment" &&
|
|
332
|
+
!(n.id && RESERVED_SCOPE_IDS.has(n.id.toLowerCase()))
|
|
333
|
+
);
|
|
324
334
|
|
|
325
335
|
// Build per-slide footer:
|
|
326
336
|
// < CONFIDENTIAL ---gap--- Company N/Total >
|
|
@@ -384,7 +394,37 @@ function renderSlides(nodes, options = {}) {
|
|
|
384
394
|
|
|
385
395
|
// Structural styles — always injected regardless of theme.
|
|
386
396
|
const structuralCss = `
|
|
387
|
-
|
|
397
|
+
/* --- Fixed design box + fit-to-window scaling ---------------------------
|
|
398
|
+
Slides are laid out at a fixed design size (1280x720 CSS px, which is
|
|
399
|
+
exactly the 13.333in x 7.5in print page at 96dpi) and then scaled as a
|
|
400
|
+
whole to fill the window. This is the standard deck technique: the
|
|
401
|
+
author's layout is preserved verbatim at every window size, and screen
|
|
402
|
+
and PDF are the same geometry by construction.
|
|
403
|
+
|
|
404
|
+
--sdoc-slide-scale is written by the theme runtime (fitSlidesToWindow in
|
|
405
|
+
themes/default/theme.js) on load and on resize. If JS never runs the
|
|
406
|
+
scale stays 1 and the deck renders at its natural design size, which is
|
|
407
|
+
the pre-scaling behaviour rather than a broken one.
|
|
408
|
+
|
|
409
|
+
Themes must NOT set width/height/max-width/margin on .slide — those come
|
|
410
|
+
from the design-box variables below. A theme that wants a different
|
|
411
|
+
aspect ratio should override --sdoc-slide-w / --sdoc-slide-h on :root
|
|
412
|
+
(and the @page size in the print block, if PDF output matters). */
|
|
413
|
+
:root {
|
|
414
|
+
--sdoc-slide-w: 1280px;
|
|
415
|
+
--sdoc-slide-h: 720px;
|
|
416
|
+
--sdoc-slide-scale: 1;
|
|
417
|
+
}
|
|
418
|
+
.slide {
|
|
419
|
+
position: absolute;
|
|
420
|
+
top: 50%;
|
|
421
|
+
left: 50%;
|
|
422
|
+
width: var(--sdoc-slide-w);
|
|
423
|
+
height: var(--sdoc-slide-h);
|
|
424
|
+
transform: translate(-50%, -50%) scale(var(--sdoc-slide-scale));
|
|
425
|
+
transform-origin: center center;
|
|
426
|
+
overflow: hidden;
|
|
427
|
+
}
|
|
388
428
|
.slide-footer {
|
|
389
429
|
position: absolute; bottom: 20px; left: 32px; right: 32px;
|
|
390
430
|
display: flex; align-items: baseline;
|
|
@@ -430,7 +470,14 @@ function renderSlides(nodes, options = {}) {
|
|
|
430
470
|
opacity: 1 !important;
|
|
431
471
|
pointer-events: auto !important;
|
|
432
472
|
page-break-after: always; break-after: page;
|
|
433
|
-
|
|
473
|
+
/* The design box IS the page (1280x720px == 13.333in x 7.5in @96dpi),
|
|
474
|
+
so no fit-to-window scaling applies here — each slide flows as one
|
|
475
|
+
page at its natural size. Overflowing content is still shrunk by
|
|
476
|
+
the inner .slide-content-scale wrapper (see fitSlidesForPrint). */
|
|
477
|
+
top: auto !important; left: auto !important;
|
|
478
|
+
transform: none !important;
|
|
479
|
+
margin: 0 !important;
|
|
480
|
+
width: var(--sdoc-slide-w); height: var(--sdoc-slide-h); max-width: none;
|
|
434
481
|
overflow: hidden;
|
|
435
482
|
page-break-inside: avoid; break-inside: avoid;
|
|
436
483
|
}
|