pi-studio 0.5.31 → 0.5.33

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/CHANGELOG.md CHANGED
@@ -4,6 +4,30 @@ All notable changes to `pi-studio` are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.5.33] — 2026-03-27
8
+
9
+ ### Changed
10
+ - Studio browser tabs now use `π Studio` branding plus a simple theme-reactive `π` favicon instead of the generic browser globe.
11
+
12
+ ### Fixed
13
+ - Markdown preview now preserves `[an: ...]` markers more reliably by replacing them with preview-safe placeholders before pandoc and restoring annotation pills afterwards, preventing long or markdown-like annotations from leaking through as raw text.
14
+ - Preview/PDF markdown preparation now normalizes fenced blocks whose contents contain competing backtick/tilde fence runs, avoiding broken rendering/export for diff-heavy content that itself contains code fences.
15
+ - Diff PDF exports now route highlighted diff content through the generated-LaTeX path more reliably, keeping add/delete/meta/hunk styling and line wrapping on exports that previously rendered poorly or fell back unnecessarily.
16
+ - PDF annotation badges now wrap within the page width instead of overflowing on long notes, preserve inline math inside annotation text, and also render correctly inside diff token lines such as `+[an: ...]`.
17
+
18
+ ## [0.5.32] — 2026-03-25
19
+
20
+ ### Added
21
+ - `/studio-pdf <path>` now accepts a curated set of advanced layout controls for file-based exports, including font size, margins, line stretch, main font, paper size, geometry, heading sizes, heading spacing, and footer skip.
22
+
23
+ ### Changed
24
+ - Large-font Markdown/QMD Studio PDF exports now switch to a more suitable LaTeX document class and use a safer default footer skip unless you explicitly override the geometry.
25
+ - PDF callout blocks now render more compactly, reducing extra vertical whitespace around note/tip/warning content.
26
+
27
+ ### Fixed
28
+ - Studio preview/PDF preparation now treats `.qmd` files like Markdown, strips HTML comments more narrowly, shows standalone LaTeX page-break commands as subtle preview dividers, and supports common Quarto-style callout and `fig-align` patterns in preview/PDF output.
29
+ - Markdown/QMD preview now renders embedded local PDF figures more reliably via `pdf.js`, avoiding grey-box browser embed failures in the Studio preview surface.
30
+
7
31
  ## [0.5.31] — 2026-03-24
8
32
 
9
33
  ### Fixed
package/README.md CHANGED
@@ -43,7 +43,7 @@ Extension for [pi](https://pi.dev) that opens a local two-pane browser workspace
43
43
  | `/studio --stop` | Stop studio server |
44
44
  | `/studio --help` | Show help |
45
45
  | `/studio-current <path>` | Load a file into currently open Studio tab(s) without opening a new browser window |
46
- | `/studio-pdf <path>` | Export a local file to `<name>.studio.pdf` via the Studio PDF pipeline |
46
+ | `/studio-pdf <path> [options]` | Export a local file to `<name>.studio.pdf` via the Studio PDF pipeline, with optional layout controls |
47
47
 
48
48
  ## Install
49
49
 
@@ -188,7 +188,7 @@
188
188
  const EDITOR_LANGUAGE_STORAGE_KEY = "piStudio.editorLanguage";
189
189
  // Single source of truth: language -> file extensions (and display label)
190
190
  var LANG_EXT_MAP = {
191
- markdown: { label: "Markdown", exts: ["md", "markdown", "mdx"] },
191
+ markdown: { label: "Markdown", exts: ["md", "markdown", "mdx", "qmd"] },
192
192
  javascript: { label: "JavaScript", exts: ["js", "mjs", "cjs", "jsx"] },
193
193
  typescript: { label: "TypeScript", exts: ["ts", "mts", "cts", "tsx"] },
194
194
  python: { label: "Python", exts: ["py", "pyw"] },
@@ -242,9 +242,12 @@
242
242
  let editorHighlightRenderRaf = null;
243
243
  let annotationsEnabled = true;
244
244
  const ANNOTATION_MARKER_REGEX = /\[an:\s*([^\]]+?)\]/gi;
245
+ const PREVIEW_ANNOTATION_PLACEHOLDER_PREFIX = "PISTUDIOANNOT";
245
246
  const EMPTY_OVERLAY_LINE = "\u200b";
246
247
  const MERMAID_CDN_URL = "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
247
248
  const MATHJAX_CDN_URL = "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js";
249
+ const PDFJS_CDN_URL = "https://cdn.jsdelivr.net/npm/pdfjs-dist@4.10.38/legacy/build/pdf.min.mjs";
250
+ const PDFJS_WORKER_CDN_URL = "https://cdn.jsdelivr.net/npm/pdfjs-dist@4.10.38/legacy/build/pdf.worker.min.mjs";
248
251
  const BOOT = (typeof window.__PI_STUDIO_BOOT__ === "object" && window.__PI_STUDIO_BOOT__)
249
252
  ? window.__PI_STUDIO_BOOT__
250
253
  : {};
@@ -255,9 +258,12 @@
255
258
  const MERMAID_RENDER_FAIL_MESSAGE = "Mermaid render failed. Showing diagram source text.";
256
259
  const MATHJAX_UNAVAILABLE_MESSAGE = "Math fallback unavailable. Some unsupported equations may remain as raw TeX.";
257
260
  const MATHJAX_RENDER_FAIL_MESSAGE = "Math fallback could not render some unsupported equations.";
261
+ const PDF_PREVIEW_UNAVAILABLE_MESSAGE = "PDF figure preview unavailable. Inline PDF rendering is not supported in this Studio browser environment.";
262
+ const PDF_PREVIEW_RENDER_FAIL_MESSAGE = "PDF figure preview could not be rendered.";
258
263
  let mermaidModulePromise = null;
259
264
  let mermaidInitialized = false;
260
265
  let mathJaxPromise = null;
266
+ let pdfJsPromise = null;
261
267
 
262
268
  const DEBUG_ENABLED = (() => {
263
269
  try {
@@ -1178,6 +1184,80 @@
1178
1184
  return annotationsEnabled ? raw : stripAnnotationMarkers(raw);
1179
1185
  }
1180
1186
 
1187
+ function normalizePreviewAnnotationLabel(text) {
1188
+ return String(text || "")
1189
+ .replace(/\r\n/g, "\n")
1190
+ .replace(/\s*\n\s*/g, " ")
1191
+ .replace(/\s{2,}/g, " ")
1192
+ .trim();
1193
+ }
1194
+
1195
+ function prepareMarkdownForPandocPreview(markdown) {
1196
+ const source = String(markdown || "").replace(/\r\n/g, "\n");
1197
+ const placeholders = [];
1198
+ if (!source) {
1199
+ return { markdown: source, placeholders: placeholders };
1200
+ }
1201
+
1202
+ const lines = source.split("\n");
1203
+ const out = [];
1204
+ let plainBuffer = [];
1205
+ let inFence = false;
1206
+ let fenceChar = null;
1207
+ let fenceLength = 0;
1208
+
1209
+ function flushPlain() {
1210
+ if (plainBuffer.length === 0) return;
1211
+ const segment = plainBuffer.join("\n").replace(/\[an:\s*([^\]]+?)\]/gi, function(_match, markerText) {
1212
+ const label = normalizePreviewAnnotationLabel(markerText);
1213
+ if (!label) return "";
1214
+ const token = PREVIEW_ANNOTATION_PLACEHOLDER_PREFIX + placeholders.length + "TOKEN";
1215
+ placeholders.push({ token: token, text: label, title: "[an: " + label + "]" });
1216
+ return token;
1217
+ });
1218
+ out.push(segment);
1219
+ plainBuffer = [];
1220
+ }
1221
+
1222
+ for (const line of lines) {
1223
+ const trimmed = line.trimStart();
1224
+ const fenceMatch = trimmed.match(/^(`{3,}|~{3,})/);
1225
+
1226
+ if (fenceMatch) {
1227
+ const marker = fenceMatch[1] || "";
1228
+ const markerChar = marker.charAt(0);
1229
+ const markerLength = marker.length;
1230
+
1231
+ if (!inFence) {
1232
+ flushPlain();
1233
+ inFence = true;
1234
+ fenceChar = markerChar;
1235
+ fenceLength = markerLength;
1236
+ out.push(line);
1237
+ continue;
1238
+ }
1239
+
1240
+ if (fenceChar === markerChar && markerLength >= fenceLength) {
1241
+ inFence = false;
1242
+ fenceChar = null;
1243
+ fenceLength = 0;
1244
+ }
1245
+
1246
+ out.push(line);
1247
+ continue;
1248
+ }
1249
+
1250
+ if (inFence) {
1251
+ out.push(line);
1252
+ } else {
1253
+ plainBuffer.push(line);
1254
+ }
1255
+ }
1256
+
1257
+ flushPlain();
1258
+ return { markdown: out.join("\n"), placeholders: placeholders };
1259
+ }
1260
+
1181
1261
  function wrapAsFencedCodeBlock(text, language) {
1182
1262
  const source = String(text || "").trimEnd();
1183
1263
  const lang = String(language || "").trim();
@@ -1248,11 +1328,210 @@
1248
1328
  mathMl: true,
1249
1329
  svg: true,
1250
1330
  },
1331
+ ADD_TAGS: ["embed"],
1332
+ ADD_ATTR: ["src", "type", "title", "width", "height", "style", "data-fig-align"],
1333
+ ADD_DATA_URI_TAGS: ["embed"],
1251
1334
  });
1252
1335
  }
1253
1336
  return buildPreviewErrorHtml("Preview sanitizer unavailable. Showing plain markdown.", markdown);
1254
1337
  }
1255
1338
 
1339
+ function isPdfPreviewSource(src) {
1340
+ return Boolean(src) && (/^data:application\/pdf(?:;|,)/i.test(src) || /\.pdf(?:$|[?#])/i.test(src));
1341
+ }
1342
+
1343
+ function decoratePdfEmbeds(targetEl) {
1344
+ if (!targetEl || typeof targetEl.querySelectorAll !== "function") {
1345
+ return;
1346
+ }
1347
+
1348
+ const embeds = targetEl.querySelectorAll("embed[src]");
1349
+ embeds.forEach(function(embedEl) {
1350
+ const src = typeof embedEl.getAttribute === "function" ? (embedEl.getAttribute("src") || "") : "";
1351
+ if (!isPdfPreviewSource(src)) {
1352
+ return;
1353
+ }
1354
+ if (!embedEl.getAttribute("type")) {
1355
+ embedEl.setAttribute("type", "application/pdf");
1356
+ }
1357
+ if (!embedEl.getAttribute("title")) {
1358
+ embedEl.setAttribute("title", "Embedded PDF figure");
1359
+ }
1360
+ });
1361
+ }
1362
+
1363
+ function decodePdfDataUri(src) {
1364
+ const match = String(src || "").match(/^data:application\/pdf(?:;[^,]*)?,([A-Za-z0-9+/=\s]+)$/i);
1365
+ if (!match) return null;
1366
+ const payload = (match[1] || "").replace(/\s+/g, "");
1367
+ if (!payload) return null;
1368
+ const binary = window.atob(payload);
1369
+ const bytes = new Uint8Array(binary.length);
1370
+ for (let i = 0; i < binary.length; i += 1) {
1371
+ bytes[i] = binary.charCodeAt(i);
1372
+ }
1373
+ return bytes;
1374
+ }
1375
+
1376
+ function ensurePdfJs() {
1377
+ if (window.pdfjsLib && typeof window.pdfjsLib.getDocument === "function") {
1378
+ return Promise.resolve(window.pdfjsLib);
1379
+ }
1380
+ if (pdfJsPromise) {
1381
+ return pdfJsPromise;
1382
+ }
1383
+
1384
+ pdfJsPromise = import(PDFJS_CDN_URL)
1385
+ .then((module) => {
1386
+ const api = module && typeof module.getDocument === "function"
1387
+ ? module
1388
+ : (module && module.default && typeof module.default.getDocument === "function" ? module.default : null);
1389
+ if (!api || typeof api.getDocument !== "function") {
1390
+ throw new Error("pdf.js did not initialize.");
1391
+ }
1392
+ if (api.GlobalWorkerOptions && !api.GlobalWorkerOptions.workerSrc) {
1393
+ api.GlobalWorkerOptions.workerSrc = PDFJS_WORKER_CDN_URL;
1394
+ }
1395
+ window.pdfjsLib = api;
1396
+ return api;
1397
+ })
1398
+ .catch((error) => {
1399
+ pdfJsPromise = null;
1400
+ throw error;
1401
+ });
1402
+
1403
+ return pdfJsPromise;
1404
+ }
1405
+
1406
+ function appendPdfPreviewNotice(targetEl, message) {
1407
+ if (!targetEl || typeof targetEl.querySelector !== "function" || typeof targetEl.appendChild !== "function") {
1408
+ return;
1409
+ }
1410
+ if (targetEl.querySelector(".preview-pdf-warning")) {
1411
+ return;
1412
+ }
1413
+ const warningEl = document.createElement("div");
1414
+ warningEl.className = "preview-warning preview-pdf-warning";
1415
+ warningEl.textContent = String(message || PDF_PREVIEW_UNAVAILABLE_MESSAGE);
1416
+ targetEl.appendChild(warningEl);
1417
+ }
1418
+
1419
+ async function loadPdfDocumentSource(src) {
1420
+ const embedded = decodePdfDataUri(src);
1421
+ if (embedded) {
1422
+ return { data: embedded };
1423
+ }
1424
+ const response = await fetch(src);
1425
+ if (!response.ok) {
1426
+ throw new Error("Failed to fetch PDF figure for preview.");
1427
+ }
1428
+ const bytes = new Uint8Array(await response.arrayBuffer());
1429
+ return { data: bytes };
1430
+ }
1431
+
1432
+ async function renderSinglePdfPreviewEmbed(embedEl, pdfjsLib) {
1433
+ if (!embedEl || embedEl.dataset.studioPdfPreviewRendered === "1") {
1434
+ return false;
1435
+ }
1436
+
1437
+ const src = embedEl.getAttribute("src") || "";
1438
+ if (!isPdfPreviewSource(src)) {
1439
+ return false;
1440
+ }
1441
+
1442
+ const measuredWidth = Math.max(1, Math.round(embedEl.getBoundingClientRect().width || 0));
1443
+ const styleText = embedEl.getAttribute("style") || "";
1444
+ const widthAttr = embedEl.getAttribute("width") || "";
1445
+ const figAlign = embedEl.getAttribute("data-fig-align") || "";
1446
+ const pdfSource = await loadPdfDocumentSource(src);
1447
+ const loadingTask = pdfjsLib.getDocument(pdfSource);
1448
+ const pdfDocument = await loadingTask.promise;
1449
+
1450
+ try {
1451
+ const page = await pdfDocument.getPage(1);
1452
+ const baseViewport = page.getViewport({ scale: 1 });
1453
+ const cssWidth = Math.max(1, measuredWidth || Math.round(baseViewport.width));
1454
+ const renderScale = Math.max(0.25, cssWidth / baseViewport.width) * Math.min(window.devicePixelRatio || 1, 2);
1455
+ const viewport = page.getViewport({ scale: renderScale });
1456
+ const canvas = document.createElement("canvas");
1457
+ const context = canvas.getContext("2d", { alpha: false });
1458
+ if (!context) {
1459
+ throw new Error("Canvas 2D context unavailable.");
1460
+ }
1461
+
1462
+ canvas.width = Math.max(1, Math.ceil(viewport.width));
1463
+ canvas.height = Math.max(1, Math.ceil(viewport.height));
1464
+ canvas.style.width = "100%";
1465
+ canvas.style.height = "auto";
1466
+ canvas.setAttribute("aria-label", "PDF figure preview");
1467
+
1468
+ await page.render({
1469
+ canvasContext: context,
1470
+ viewport,
1471
+ }).promise;
1472
+
1473
+ const wrapper = document.createElement("div");
1474
+ wrapper.className = "studio-pdf-preview";
1475
+ if (styleText) {
1476
+ wrapper.style.cssText = styleText;
1477
+ } else if (widthAttr) {
1478
+ wrapper.style.width = /^\d+(?:\.\d+)?$/.test(widthAttr) ? (widthAttr + "px") : widthAttr;
1479
+ } else {
1480
+ wrapper.style.width = "100%";
1481
+ }
1482
+ if (figAlign) {
1483
+ wrapper.setAttribute("data-fig-align", figAlign);
1484
+ }
1485
+ wrapper.title = "PDF figure preview (page 1)";
1486
+ wrapper.appendChild(canvas);
1487
+ embedEl.dataset.studioPdfPreviewRendered = "1";
1488
+ embedEl.replaceWith(wrapper);
1489
+ return true;
1490
+ } finally {
1491
+ if (typeof pdfDocument.cleanup === "function") {
1492
+ try { pdfDocument.cleanup(); } catch {}
1493
+ }
1494
+ if (typeof pdfDocument.destroy === "function") {
1495
+ try { await pdfDocument.destroy(); } catch {}
1496
+ }
1497
+ }
1498
+ }
1499
+
1500
+ async function renderPdfPreviewsInElement(targetEl) {
1501
+ if (!targetEl || typeof targetEl.querySelectorAll !== "function") {
1502
+ return;
1503
+ }
1504
+
1505
+ const embeds = Array.from(targetEl.querySelectorAll("embed[src]"))
1506
+ .filter((embedEl) => isPdfPreviewSource(embedEl.getAttribute("src") || ""));
1507
+ if (embeds.length === 0) {
1508
+ return;
1509
+ }
1510
+
1511
+ let pdfjsLib;
1512
+ try {
1513
+ pdfjsLib = await ensurePdfJs();
1514
+ } catch (error) {
1515
+ console.error("pdf.js load failed:", error);
1516
+ appendPdfPreviewNotice(targetEl, PDF_PREVIEW_UNAVAILABLE_MESSAGE);
1517
+ return;
1518
+ }
1519
+
1520
+ let hadFailure = false;
1521
+ for (const embedEl of embeds) {
1522
+ try {
1523
+ await renderSinglePdfPreviewEmbed(embedEl, pdfjsLib);
1524
+ } catch (error) {
1525
+ hadFailure = true;
1526
+ console.error("PDF preview render failed:", error);
1527
+ }
1528
+ }
1529
+
1530
+ if (hadFailure) {
1531
+ appendPdfPreviewNotice(targetEl, PDF_PREVIEW_RENDER_FAIL_MESSAGE);
1532
+ }
1533
+ }
1534
+
1256
1535
  function appendMathFallbackNotice(targetEl, message) {
1257
1536
  if (!targetEl || typeof targetEl.querySelector !== "function" || typeof targetEl.appendChild !== "function") {
1258
1537
  return;
@@ -1418,6 +1697,79 @@
1418
1697
  }
1419
1698
  }
1420
1699
 
1700
+ function applyPreviewAnnotationPlaceholdersToElement(targetEl, placeholders) {
1701
+ if (!targetEl || !Array.isArray(placeholders) || placeholders.length === 0) return;
1702
+ if (typeof document.createTreeWalker !== "function") return;
1703
+
1704
+ const placeholderMap = new Map();
1705
+ const placeholderTokens = [];
1706
+ placeholders.forEach(function(entry) {
1707
+ const token = entry && typeof entry.token === "string" ? entry.token : "";
1708
+ if (!token) return;
1709
+ placeholderMap.set(token, entry);
1710
+ placeholderTokens.push(token);
1711
+ });
1712
+ if (placeholderTokens.length === 0) return;
1713
+
1714
+ const placeholderPattern = new RegExp(placeholderTokens.map(escapeRegExp).join("|"), "g");
1715
+ const walker = document.createTreeWalker(targetEl, NodeFilter.SHOW_TEXT);
1716
+ const textNodes = [];
1717
+ let node = walker.nextNode();
1718
+ while (node) {
1719
+ const textNode = node;
1720
+ const value = typeof textNode.nodeValue === "string" ? textNode.nodeValue : "";
1721
+ if (value && value.indexOf(PREVIEW_ANNOTATION_PLACEHOLDER_PREFIX) !== -1) {
1722
+ const parent = textNode.parentElement;
1723
+ const tag = parent && parent.tagName ? parent.tagName.toUpperCase() : "";
1724
+ if (tag !== "CODE" && tag !== "PRE" && tag !== "SCRIPT" && tag !== "STYLE" && tag !== "TEXTAREA") {
1725
+ textNodes.push(textNode);
1726
+ }
1727
+ }
1728
+ node = walker.nextNode();
1729
+ }
1730
+
1731
+ textNodes.forEach(function(textNode) {
1732
+ const text = typeof textNode.nodeValue === "string" ? textNode.nodeValue : "";
1733
+ if (!text) return;
1734
+ placeholderPattern.lastIndex = 0;
1735
+ if (!placeholderPattern.test(text)) return;
1736
+ placeholderPattern.lastIndex = 0;
1737
+
1738
+ const fragment = document.createDocumentFragment();
1739
+ let lastIndex = 0;
1740
+ let match;
1741
+ while ((match = placeholderPattern.exec(text)) !== null) {
1742
+ const token = match[0] || "";
1743
+ const entry = placeholderMap.get(token);
1744
+ const start = typeof match.index === "number" ? match.index : 0;
1745
+ if (start > lastIndex) {
1746
+ fragment.appendChild(document.createTextNode(text.slice(lastIndex, start)));
1747
+ }
1748
+ if (entry) {
1749
+ const markerEl = document.createElement("span");
1750
+ markerEl.className = "annotation-preview-marker";
1751
+ markerEl.textContent = typeof entry.text === "string" ? entry.text : token;
1752
+ markerEl.title = typeof entry.title === "string" ? entry.title : markerEl.textContent;
1753
+ fragment.appendChild(markerEl);
1754
+ } else {
1755
+ fragment.appendChild(document.createTextNode(token));
1756
+ }
1757
+ lastIndex = start + token.length;
1758
+ if (token.length === 0) {
1759
+ placeholderPattern.lastIndex += 1;
1760
+ }
1761
+ }
1762
+
1763
+ if (lastIndex < text.length) {
1764
+ fragment.appendChild(document.createTextNode(text.slice(lastIndex)));
1765
+ }
1766
+
1767
+ if (textNode.parentNode) {
1768
+ textNode.parentNode.replaceChild(fragment, textNode);
1769
+ }
1770
+ });
1771
+ }
1772
+
1421
1773
  function applyAnnotationMarkersToElement(targetEl, mode) {
1422
1774
  if (!targetEl || mode === "none") return;
1423
1775
  if (typeof document.createTreeWalker !== "function") return;
@@ -1890,8 +2242,12 @@
1890
2242
  }
1891
2243
 
1892
2244
  async function applyRenderedMarkdown(targetEl, markdown, pane, nonce) {
2245
+ const previewPrepared = annotationsEnabled
2246
+ ? prepareMarkdownForPandocPreview(markdown)
2247
+ : { markdown: stripAnnotationMarkers(String(markdown || "")), placeholders: [] };
2248
+
1893
2249
  try {
1894
- const renderedHtml = await renderMarkdownWithPandoc(markdown);
2250
+ const renderedHtml = await renderMarkdownWithPandoc(previewPrepared.markdown);
1895
2251
 
1896
2252
  if (pane === "source") {
1897
2253
  if (nonce !== sourcePreviewRenderNonce || editorView !== "preview") return;
@@ -1901,6 +2257,9 @@
1901
2257
 
1902
2258
  finishPreviewRender(targetEl);
1903
2259
  targetEl.innerHTML = sanitizeRenderedHtml(renderedHtml, markdown);
2260
+ applyPreviewAnnotationPlaceholdersToElement(targetEl, previewPrepared.placeholders);
2261
+ decoratePdfEmbeds(targetEl);
2262
+ await renderPdfPreviewsInElement(targetEl);
1904
2263
  const annotationMode = (pane === "source" || pane === "response")
1905
2264
  ? (annotationsEnabled ? "highlight" : "hide")
1906
2265
  : "none";
@@ -2338,6 +2697,10 @@
2338
2697
  .replace(/'/g, "&#39;");
2339
2698
  }
2340
2699
 
2700
+ function escapeRegExp(text) {
2701
+ return String(text || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2702
+ }
2703
+
2341
2704
  function wrapHighlight(className, text) {
2342
2705
  return "<span class='" + className + "'>" + escapeHtml(String(text || "")) + "</span>";
2343
2706
  }
package/client/studio.css CHANGED
@@ -516,7 +516,11 @@
516
516
  background: var(--accent-soft);
517
517
  border: 1px solid var(--marker-border);
518
518
  border-radius: 4px;
519
+ display: inline-block;
520
+ max-width: 100%;
519
521
  padding: 0 4px;
522
+ white-space: normal;
523
+ vertical-align: baseline;
520
524
  }
521
525
 
522
526
  #sourcePreview {
@@ -633,6 +637,91 @@
633
637
  color: var(--md-quote);
634
638
  }
635
639
 
640
+ .rendered-markdown .callout-note,
641
+ .rendered-markdown .callout-tip,
642
+ .rendered-markdown .callout-warning,
643
+ .rendered-markdown .callout-important,
644
+ .rendered-markdown .callout-caution {
645
+ margin: 1.15em 0;
646
+ padding: 0.8em 1rem 0.95em;
647
+ border: 1px solid var(--border-muted);
648
+ border-left-width: 4px;
649
+ border-radius: 10px;
650
+ background: var(--panel-2);
651
+ color: inherit;
652
+ }
653
+
654
+ .rendered-markdown .callout-note::before,
655
+ .rendered-markdown .callout-tip::before,
656
+ .rendered-markdown .callout-warning::before,
657
+ .rendered-markdown .callout-important::before,
658
+ .rendered-markdown .callout-caution::before {
659
+ display: inline-block;
660
+ margin-bottom: 0.45rem;
661
+ font-size: 0.76em;
662
+ font-weight: 700;
663
+ letter-spacing: 0.08em;
664
+ text-transform: uppercase;
665
+ }
666
+
667
+ .rendered-markdown .callout-note::before {
668
+ content: "Note";
669
+ color: var(--accent);
670
+ }
671
+
672
+ .rendered-markdown .callout-tip::before {
673
+ content: "Tip";
674
+ color: var(--ok);
675
+ }
676
+
677
+ .rendered-markdown .callout-warning::before {
678
+ content: "Warning";
679
+ color: var(--warn);
680
+ }
681
+
682
+ .rendered-markdown .callout-important::before {
683
+ content: "Important";
684
+ color: var(--error);
685
+ }
686
+
687
+ .rendered-markdown .callout-caution::before {
688
+ content: "Caution";
689
+ color: var(--error);
690
+ }
691
+
692
+ .rendered-markdown .callout-note {
693
+ border-left-color: var(--accent);
694
+ }
695
+
696
+ .rendered-markdown .callout-tip {
697
+ border-left-color: var(--ok);
698
+ }
699
+
700
+ .rendered-markdown .callout-warning {
701
+ border-left-color: var(--warn);
702
+ }
703
+
704
+ .rendered-markdown .callout-important,
705
+ .rendered-markdown .callout-caution {
706
+ border-left-color: var(--error);
707
+ }
708
+
709
+ .rendered-markdown .callout-note > :first-child,
710
+ .rendered-markdown .callout-tip > :first-child,
711
+ .rendered-markdown .callout-warning > :first-child,
712
+ .rendered-markdown .callout-important > :first-child,
713
+ .rendered-markdown .callout-caution > :first-child {
714
+ margin-top: 0;
715
+ }
716
+
717
+ .rendered-markdown .callout-note > :last-child,
718
+ .rendered-markdown .callout-tip > :last-child,
719
+ .rendered-markdown .callout-warning > :last-child,
720
+ .rendered-markdown .callout-important > :last-child,
721
+ .rendered-markdown .callout-caution > :last-child {
722
+ margin-bottom: 0;
723
+ }
724
+
636
725
  .rendered-markdown pre {
637
726
  background: var(--panel-2);
638
727
  border: 1px solid var(--md-codeblock-border);
@@ -765,10 +854,83 @@
765
854
  margin: 1.25em 0;
766
855
  }
767
856
 
857
+ .rendered-markdown .studio-page-break {
858
+ display: flex;
859
+ align-items: center;
860
+ gap: 0.85rem;
861
+ margin: 1.75em 0;
862
+ color: var(--muted);
863
+ font-size: 0.88em;
864
+ text-transform: uppercase;
865
+ letter-spacing: 0.08em;
866
+ }
867
+
868
+ .rendered-markdown .studio-page-break-rule {
869
+ flex: 1 1 auto;
870
+ height: 1px;
871
+ background: var(--md-hr);
872
+ opacity: 0.95;
873
+ }
874
+
875
+ .rendered-markdown .studio-page-break-label {
876
+ flex: 0 0 auto;
877
+ white-space: nowrap;
878
+ }
879
+
768
880
  .rendered-markdown img {
769
881
  max-width: 100%;
770
882
  }
771
883
 
884
+ .rendered-markdown img[data-fig-align="center"],
885
+ .rendered-markdown embed[data-fig-align="center"],
886
+ .rendered-markdown .studio-pdf-preview[data-fig-align="center"] {
887
+ display: block;
888
+ margin-left: auto;
889
+ margin-right: auto;
890
+ }
891
+
892
+ .rendered-markdown img[data-fig-align="right"],
893
+ .rendered-markdown embed[data-fig-align="right"],
894
+ .rendered-markdown .studio-pdf-preview[data-fig-align="right"] {
895
+ display: block;
896
+ margin-left: auto;
897
+ margin-right: 0;
898
+ }
899
+
900
+ .rendered-markdown embed[type="application/pdf"],
901
+ .rendered-markdown embed[src^="data:application/pdf"],
902
+ .rendered-markdown embed[src$=".pdf"] {
903
+ display: block;
904
+ width: 100%;
905
+ min-height: 18rem;
906
+ border: 1px solid var(--md-table-border);
907
+ border-radius: 10px;
908
+ background: #fff;
909
+ overflow: hidden;
910
+ }
911
+
912
+ .rendered-markdown .studio-pdf-preview {
913
+ display: block;
914
+ max-width: 100%;
915
+ border: 1px solid var(--md-table-border);
916
+ border-radius: 10px;
917
+ background: #fff;
918
+ overflow: hidden;
919
+ line-height: 0;
920
+ }
921
+
922
+ .rendered-markdown .studio-pdf-preview[data-fig-align="center"] {
923
+ margin-left: auto;
924
+ margin-right: auto;
925
+ }
926
+
927
+ .rendered-markdown .studio-pdf-preview canvas {
928
+ display: block;
929
+ width: 100%;
930
+ height: auto;
931
+ max-width: 100%;
932
+ }
933
+
772
934
  .rendered-markdown .studio-subfigure-group {
773
935
  margin: 1.25em auto;
774
936
  }