orz-markdown 1.2.2 → 1.3.0

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.
@@ -0,0 +1,195 @@
1
+ # Embedding orz-markdown in a host app (custom stylesheet)
2
+
3
+ Read this when you call `md.render()` and supply **your own CSS and page shell**
4
+ instead of using a bundled theme — e.g. a slide engine, a doc viewer, an editor,
5
+ or any app that embeds rendered output. The bundled themes already do everything
6
+ below; this guide is the checklist for when you *don't* use them.
7
+
8
+ > The sibling `orz-slides` project hit a long tail of bugs (unstyled containers,
9
+ > bold that changed colour instead of weight, broken sub/sup, diagrams
10
+ > overflowing, copy losing `{{sp}}`/`:::` source) precisely because these points
11
+ > weren't gathered in one place. Follow this and you skip all of it.
12
+
13
+ There are **three** things to get right: CSS, JavaScript, and copy-as-Markdown.
14
+
15
+ > **Shortcut for iframe hosts — `getPreviewFrameAssets()`.** Most of the
16
+ > JavaScript below is the same everywhere, so it lives in one helper:
17
+ >
18
+ > ```js
19
+ > import { getPreviewFrameAssets } from 'orz-markdown/preview-frame';
20
+ > const pa = getPreviewFrameAssets();
21
+ > ```
22
+ >
23
+ > It returns the pinned CDN URLs (`pa.cdn`), the runtime (`pa.runtimeScript`), and
24
+ > ready-made strings: `pa.headLinks(scheme)` (KaTeX + highlight.js CSS, the hljs
25
+ > link carries `id="orz-hljs"`) for the iframe `<head>`, and `pa.bodyScripts()`
26
+ > (loads the libs + runtime and defines `window.__orzEnhance()`) for the `<body>`.
27
+ > After each render call `window.__orzEnhance()` — it highlights code, runs
28
+ > mermaid, draws SMILES (honoring `window.__orzSmilesTheme`) and charts, and
29
+ > re-inits the runtime (tabs + QR). On theme change swap
30
+ > `doc.getElementById('orz-hljs').href = pa.hljsCss(scheme)`. `__orzEnhance`
31
+ > scopes to `.markdown-body`, so it works whatever id/wrapper you use. The rest of
32
+ > this page is what that helper does under the hood — read it if you hand-roll the
33
+ > wiring or need to understand a failure.
34
+
35
+ ---
36
+
37
+ ## 1. CSS
38
+
39
+ The parser emits plain HTML plus plugin/container classes. Two reference docs
40
+ have the full detail:
41
+
42
+ - `references/css-classes.md` — every class and element the parser emits.
43
+ - `references/themes.md` — the element checklist and design guidance.
44
+
45
+ When you bring your own stylesheet, the four things that bite hardest:
46
+
47
+ ### 1a. Wrap output in `.markdown-body` and scope your rules to it
48
+
49
+ ```html
50
+ <article class="markdown-body"> …md.render() output… </article>
51
+ ```
52
+ Every theme rule is scoped under `.markdown-body`; copy-as-Markdown also keys off
53
+ this class (§3). For a region-based layout, each rendered region gets its own
54
+ `.markdown-body`.
55
+
56
+ ### 1b. A host CSS *reset* will strip inline semantics — restore them
57
+
58
+ This is the #1 non-obvious failure. Many app/framework resets (notably
59
+ **reveal.js**'s `reset.css`) apply to `strong, b, em, i, sub, sup, small, …`:
60
+
61
+ ```css
62
+ em, strong, sub, sup, b, i, small, … { font: inherit; font-size: 100%; vertical-align: baseline; }
63
+ ```
64
+
65
+ `font: inherit` wipes `font-weight`/`font-style`, and `vertical-align: baseline`
66
+ + `font-size: 100%` flattens sub/sup. The result: **bold isn't bold, italic
67
+ isn't italic, H~2~O / x^2^ render flat.** (A theme that "fixed" bold with
68
+ `strong { color: accent }` then makes bold *vanish* wherever the accent equals
69
+ the background.) If your host has any such reset, restore the semantics
70
+ explicitly — **bold is weight, not colour**:
71
+
72
+ ```css
73
+ .markdown-body strong, .markdown-body b { font-weight: 700; }
74
+ .markdown-body em, .markdown-body i { font-style: italic; }
75
+ .markdown-body s, .markdown-body del { text-decoration: line-through; }
76
+ .markdown-body ins,.markdown-body u { text-decoration: underline; }
77
+ .markdown-body sub, .markdown-body sup { font-size: 0.75em; line-height: 0; position: relative; vertical-align: baseline; }
78
+ .markdown-body sub { bottom: -0.3em; }
79
+ .markdown-body sup { top: -0.5em; }
80
+ ```
81
+
82
+ ### 1c. Style **every** plugin/container class, or it renders unstyled
83
+
84
+ If you don't ship the bundled `common.css`/theme, these have **no** styling and
85
+ look broken. Minimum set (see `css-classes.md` for the exact selectors):
86
+
87
+ | Construct | Selectors to style |
88
+ |---|---|
89
+ | Admonitions | `div.info`, `div.success`, `div.warning`, `div.danger` |
90
+ | Spans — colour | `span.red`, `span.green`, `span.blue`, `span.yellow` (colour only) |
91
+ | Spans — badge | `span.success/info/warning/danger` — **need** `display:inline-flex; align-items:center; line-height:1; white-space:nowrap` + padding/radius (normally in `common.css`) |
92
+ | Columns | `.cols` (CSS grid), `.col` |
93
+ | Tabs | `.tabs`, `.tabs-bar`, `.tabs-bar-btn`(`.active`), `.tab`; **`.tabs:not([data-js]) .tab { display:block }`** (no-JS fallback) and `.tabs[data-js] .tab{display:none}` / `.tab.active{display:block}` |
94
+ | Spoiler | `details.spoil`, `summary` |
95
+ | Alignment | `div.left`, `div.center`, `div.right` (`text-align`) |
96
+ | QR | `span.qrcode` — **must** have `background:#fff; padding` (the SVG is black-on-transparent) |
97
+ | YouTube | `.youtube-embed` — the iframe has **no width/height**; make it a 16:9 responsive box or it defaults to 300×150 |
98
+ | Math | `.katex`, `.katex-display` (load `katex.min.css`) |
99
+ | Diagrams/chem | `.mermaid svg`, `.smiles-render canvas` / `canvas[data-smiles]` |
100
+
101
+ ### 1d. Constrain generated graphics so they fit
102
+
103
+ Mermaid SVGs and smiles/chart canvases have intrinsic sizes and will overflow a
104
+ bounded container. At minimum cap the width; in a fixed-size layout (slides),
105
+ size them to the container in JS (measure the container, set explicit
106
+ width/height by the SVG's `viewBox` aspect; for Chart.js use `responsive:false`
107
+ + explicit `resize(w,h)` because it can't read a definite height through a CSS
108
+ grid). Mermaid also sets an inline `max-width` you may need to override.
109
+
110
+ ```css
111
+ .markdown-body .mermaid svg,
112
+ .markdown-body canvas[data-smiles],
113
+ .markdown-body canvas.orz-chart { max-width: 100%; height: auto; }
114
+ ```
115
+
116
+ ---
117
+
118
+ ## 2. JavaScript
119
+
120
+ Rendering is pure HTML, but several constructs need client-side JS **after mount**.
121
+
122
+ ### 2a. Load the runtime — tabs, QR expand, and copy
123
+
124
+ ```js
125
+ import { getBrowserRuntimeScript } from 'orz-markdown/runtime';
126
+ const s = document.createElement('script');
127
+ s.textContent = getBrowserRuntimeScript();
128
+ document.body.appendChild(s);
129
+ ```
130
+
131
+ It auto-runs `OrzMarkdownRuntime.init(document)` **once** on `DOMContentLoaded`
132
+ and wires the global copy handler. It exposes
133
+ `OrzMarkdownRuntime.{ init, initTabs, initQrCodes, elementToMarkdown }`.
134
+
135
+ **If you render content dynamically** (after load, or re-render — e.g. an editor
136
+ or a slide engine), the one-time `init` won't see it. Call
137
+ `OrzMarkdownRuntime.init(newRoot)` (or `initTabs`/`initQrCodes`) on the new
138
+ content yourself. `initTabs` guards on `data-js="1"` — if you also have your own
139
+ tab init, use the **same** `data-js="1"` marker so the two don't both build a bar
140
+ (double tab bar bug).
141
+
142
+ ### 2b. The runtime does **not** draw diagrams/charts — you do
143
+
144
+ mermaid, smiles, and chart canvases are placeholders. Load the libraries and draw
145
+ them (the bundled themes/`tests/example.html` show the calls):
146
+
147
+ | Construct | You must load | Then |
148
+ |---|---|---|
149
+ | `.mermaid` | mermaid.js | `mermaid.run({ querySelector: '.mermaid' })` |
150
+ | `canvas[data-smiles]` | smiles-drawer | `SmilesDrawer.parse(...)` → `drawer.draw(tree, canvas, scheme, false)` — pass `'dark'` on dark backgrounds so bonds are light |
151
+ | `canvas.orz-chart` | Chart.js | `new Chart(canvas, JSON.parse(canvas.dataset.chart))` |
152
+ | `$…$` / `$$…$$` | KaTeX **CSS** only | math is pre-rendered by `md.render()`; you just need `katex.min.css` |
153
+
154
+ Draw on first display and re-draw when the container resizes; canvases drawn
155
+ while their container is hidden (`display:none`) size to 0.
156
+
157
+ ---
158
+
159
+ ## 3. Copy-as-Markdown
160
+
161
+ Selecting rendered content and copying yields **Markdown source** when (and only
162
+ when) all of this holds:
163
+
164
+ 1. **The runtime is loaded** (§2a) — it installs the `copy` handler and the
165
+ DOM→Markdown walker (`elementToMarkdown`).
166
+ 2. **Content is inside `.markdown-body`** (or an element with `data-orz-copy`).
167
+ The handler ignores selections elsewhere and inside inputs/textareas.
168
+ 3. **`data-md` breadcrumbs are preserved.** Generated constructs whose source is
169
+ otherwise lost — `mermaid`, `smiles`, `qrcode`, `youtube`, `toc`, `chart`,
170
+ `{{sp}}` (and any plugin that adds one) — carry `data-md` with their original
171
+ directive. The walker emits it verbatim. **Never strip `data-md`** when you
172
+ post-process HTML.
173
+ 4. **Plugin/container classes are preserved.** Constructs without a `data-md`
174
+ are recovered *by class*: `<span class="red">` → `{{sp[red] …}}`,
175
+ `<div class="center">` → `::: center … :::`, nested `cols`/`tabs` get the
176
+ right fence length. If you rewrite/strip these classes, copy loses the source.
177
+
178
+ Caveat: a container (`::: center`, `:::: cols`, spoiler, tabs) is recovered only
179
+ when the **selection includes the container element**, not just the text inside
180
+ it — select the whole block/region, not the inner words.
181
+
182
+ Convert a node directly: `OrzMarkdownRuntime.elementToMarkdown(node)`.
183
+
184
+ ---
185
+
186
+ ## Pre-flight checklist
187
+
188
+ - [ ] Output wrapped in `.markdown-body`; rules scoped under it.
189
+ - [ ] Inline semantics restored if a host reset touches `strong/em/sub/sup/…` (§1b).
190
+ - [ ] Every admonition / span / cols / tabs / spoil / align / qr / youtube / math / diagram class is styled (§1c, `css-classes.md`).
191
+ - [ ] Span badges have the inline-flex structural CSS; QR has a white plate; tabs have the no-JS fallback; YouTube is a 16:9 box.
192
+ - [ ] Graphics are width-capped (and size-fitted in fixed layouts) (§1d).
193
+ - [ ] Runtime loaded; `init` re-run on dynamically rendered content; tab `data-js="1"` aligned (§2a).
194
+ - [ ] mermaid / smiles-drawer / Chart.js loaded and drawn; KaTeX CSS loaded (§2b).
195
+ - [ ] `data-md` and plugin/container classes preserved end-to-end for copy (§3).
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "orz-markdown",
3
- "version": "1.2.2",
3
+ "version": "1.3.0",
4
4
  "description": "Customized markdown-it parser with official and custom plugins",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "exports": {
8
8
  ".": "./dist/index.js",
9
9
  "./runtime": "./dist/runtime.js",
10
+ "./preview-frame": "./dist/preview-frame.js",
10
11
  "./themes/*": "./themes/*"
11
12
  },
12
13
  "files": [
@@ -64,7 +65,9 @@
64
65
  "@types/markdown-it-footnote": "^3.0.4",
65
66
  "@types/node": "^25.3.5",
66
67
  "@types/qrcode-svg": "^1.1.5",
68
+ "esbuild": "^0.21.5",
67
69
  "happy-dom": "^15.11.0",
70
+ "path-browserify": "^1.0.1",
68
71
  "tsx": "^4.21.0",
69
72
  "typescript": "^5.9.3",
70
73
  "vitest": "^4.0.18"
@@ -0,0 +1,43 @@
1
+ # orz-markdown themes
2
+
3
+ Twelve ready-to-use CSS themes for the parser's output. All rendered HTML lives
4
+ inside `<article class="markdown-body">`, so every theme is scoped to that class.
5
+ Each theme `@import`s the shared `common.css` (structural rules for tables,
6
+ images, KaTeX blocks, QR overlays, tabs/columns, and print), so you only ever
7
+ import a **single** theme file.
8
+
9
+ ```js
10
+ // With a bundler:
11
+ import 'orz-markdown/themes/light-academic-1.css';
12
+
13
+ // Or over a CDN (jsDelivr):
14
+ // https://cdn.jsdelivr.net/npm/orz-markdown/themes/light-academic-1.css
15
+ ```
16
+
17
+ ## Bundled themes
18
+
19
+ | File | Style | Scheme |
20
+ |---|---|---|
21
+ | `light-neat-1.css` | Figtree · clean modern sans | Light |
22
+ | `light-neat-2.css` | Light neat variant | Light |
23
+ | `light-neat-3.css` | Bricolage · calm green — "Orchard" | Light |
24
+ | `light-academic-1.css` | Alegreya · justified scholarly prose | Light |
25
+ | `light-academic-2.css` | Light academic variant | Light |
26
+ | `beige-decent-1.css` | Warm beige · print-like prose | Light |
27
+ | `beige-decent-2.css` | Beige decent variant | Light |
28
+ | `light-playful-1.css` | Casual · personal blog | Light |
29
+ | `light-playful-2.css` | Light playful variant | Light |
30
+ | `dark-elegant-1.css` | Cinzel headings · scholarly serif | Dark |
31
+ | `dark-elegant-2.css` | Dark elegant variant | Dark |
32
+ | `dark-elegant-3.css` | Lora · VS Code-dark, colourful headings — "Nocturne" | Dark |
33
+
34
+ `common.css` is not a theme — it is imported automatically by each theme above.
35
+
36
+ ## Theming your own output
37
+
38
+ If you supply your **own** stylesheet instead of a bundled theme, the
39
+ [embedding guide](../orz-markdown-skills/references/embedding.md) lists every CSS
40
+ class the parser emits (semantic containers, tabs/columns, spoilers, KaTeX
41
+ blocks, Mermaid/SMILES/chart canvases, clickable QR codes) and the JavaScript the
42
+ browser runtime needs. Start from `common.css` for the structural rules and layer
43
+ your visual design on top.
@@ -27,7 +27,7 @@
27
27
  --line-height: 1.7;
28
28
 
29
29
  --markdown-body-max-width: 800px;
30
- --markdown-body-padding: 4rem 2rem;
30
+ --markdown-body-padding: 2rem 2rem 4rem;
31
31
  --markdown-body-border: none;
32
32
  --markdown-body-radius: 0;
33
33
  --markdown-body-shadow: none;
@@ -56,6 +56,8 @@ body {
56
56
  }
57
57
 
58
58
  /* 2. Headings and body copy */
59
+ .markdown-body > :first-child { margin-top: 0; }
60
+
59
61
  .markdown-body h1,
60
62
  .markdown-body h2,
61
63
  .markdown-body h3,
@@ -556,4 +558,27 @@ body {
556
558
  box-shadow: none;
557
559
  }
558
560
 
559
- }
561
+ }
562
+ /* code block — warm paper panel */
563
+ .markdown-body pre { background: #f5f0e6; border: 1px solid #e7ddc9; border-radius: 8px; }
564
+ .markdown-body pre code, .markdown-body pre code.hljs { background: transparent; padding: 0; }
565
+
566
+
567
+ /* ============ modern decorative refresh ============ */
568
+ /* blockquote — warm card with a soft corner quote glyph (replaces the top/bottom rules) */
569
+ .markdown-body blockquote { position: relative; border: none; background: #f3ece0; border-radius: 12px;
570
+ padding: 1.1rem 1.4rem 1.1rem 1.6rem; margin: 1.9rem 0; font-style: italic; color: #5a4d42;
571
+ box-shadow: 0 2px 10px rgba(120, 90, 50, 0.06); }
572
+ .markdown-body blockquote::before { content: '“'; position: absolute; left: 0.55rem; top: -0.3rem;
573
+ font-family: Georgia, serif; font-size: 2.6em; line-height: 1; color: #c8a98a; opacity: 0.55; }
574
+ .markdown-body blockquote p { margin: 0 0 0.6em; } .markdown-body blockquote > :last-child { margin-bottom: 0; }
575
+ .markdown-body blockquote blockquote { background: rgba(255, 255, 255, 0.4); box-shadow: none; }
576
+ .markdown-body blockquote blockquote::before { display: none; }
577
+ /* callouts — rounder, softer */
578
+ .markdown-body .info, .markdown-body .success, .markdown-body .warning, .markdown-body .danger { border-radius: 10px; }
579
+
580
+ /* spoiler — add a chevron indicator */
581
+ .markdown-body details.spoil > summary { display: flex; align-items: center; gap: 0.55rem; list-style: none; }
582
+ .markdown-body details.spoil > summary::-webkit-details-marker { display: none; }
583
+ .markdown-body details.spoil > summary::before { content: '▸'; color: #b09a78; transition: transform 0.2s; }
584
+ .markdown-body details.spoil[open] > summary::before { transform: rotate(90deg); }
@@ -31,7 +31,7 @@
31
31
  --line-height: 1.8;
32
32
 
33
33
  --markdown-body-max-width: 760px;
34
- --markdown-body-padding: 5rem 2rem;
34
+ --markdown-body-padding: 2.5rem 2rem 5rem;
35
35
  --markdown-body-border: none;
36
36
  --markdown-body-radius: 0;
37
37
  --markdown-body-shadow: none;
@@ -59,6 +59,8 @@ body {
59
59
  }
60
60
 
61
61
  /* 2. Headings and body copy */
62
+ .markdown-body > :first-child { margin-top: 0; }
63
+
62
64
  .markdown-body h1,
63
65
  .markdown-body h2,
64
66
  .markdown-body h3,
@@ -545,7 +547,6 @@ body {
545
547
  @media (max-width: 640px) {
546
548
  .markdown-body {
547
549
  padding: 2.5rem 1.25rem;
548
- font-size: 15px;
549
550
  }
550
551
  .markdown-body .left {
551
552
  float: none;
@@ -575,4 +576,23 @@ body {
575
576
  box-shadow: none;
576
577
  }
577
578
 
578
- }
579
+ }
580
+ /* code block — warm paper panel */
581
+ .markdown-body pre { background: #f6f1e7; border: 1px solid #e6dac3; border-radius: 8px; box-shadow: 0 1px 2px rgba(120,90,50,.06); }
582
+ .markdown-body pre code, .markdown-body pre code.hljs { background: transparent; padding: 0; color: inherit; }
583
+
584
+
585
+ /* ============ modern decorative refresh ============ */
586
+ /* blockquote — warm tint with a teal accent edge (pseudo-bar, not a heavy border) */
587
+ .markdown-body blockquote { position: relative; background: #efe7d8; border: none; border-radius: 0 10px 10px 0;
588
+ padding: 0.9rem 1.3rem; margin: 1.9rem 0; font-style: italic; color: var(--text-muted); overflow: hidden; }
589
+ .markdown-body blockquote::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 4px;
590
+ background: var(--link-color); }
591
+ .markdown-body blockquote p:last-child { margin-bottom: 0; }
592
+ .markdown-body blockquote blockquote { background: rgba(255, 255, 255, 0.35); }
593
+
594
+ /* spoiler — add a chevron indicator */
595
+ .markdown-body details.spoil > summary { display: flex; align-items: center; gap: 0.55rem; list-style: none; }
596
+ .markdown-body details.spoil > summary::-webkit-details-marker { display: none; }
597
+ .markdown-body details.spoil > summary::before { content: '▸'; color: #9c8a6f; transition: transform 0.2s; }
598
+ .markdown-body details.spoil[open] > summary::before { transform: rotate(90deg); }
@@ -727,3 +727,14 @@ details.spoil > summary:focus-visible {
727
727
  page-break-inside: avoid;
728
728
  }
729
729
  }
730
+
731
+ /* code: uniform dark panel (no highlight.js inner box) */
732
+ .markdown-body pre code, .markdown-body pre code.hljs { background: transparent; padding: 0; }
733
+
734
+
735
+ /* ---- mermaid & charts: light panel for legibility on the dark background ---- */
736
+ .markdown-body div.mermaid { background: rgba(255, 255, 255, 0.88); border: 1px solid rgba(255, 255, 255, 0.1);
737
+ border-radius: 10px; padding: 1.1rem; box-shadow: 0 1px 8px rgba(0, 0, 0, 0.22); }
738
+ .markdown-body div.mermaid svg { max-width: 100%; height: auto; }
739
+ .markdown-body canvas.orz-chart { background: rgba(255, 255, 255, 0.88); border-radius: 10px; padding: 0.6rem;
740
+ box-shadow: 0 1px 8px rgba(0, 0, 0, 0.22); max-width: 100%; height: auto; }
@@ -118,10 +118,10 @@
118
118
  /* markdown body container toggles */
119
119
  --markdown-body-max-width: 920px;
120
120
  --markdown-body-padding: clamp(1.5rem, 4vw, 3.5rem);
121
- --markdown-body-border: 1px solid var(--border);
122
- --markdown-body-radius: var(--radius-lg);
123
- --markdown-body-shadow: var(--shadow);
124
- --markdown-body-decorations-display: block;
121
+ --markdown-body-border: none;
122
+ --markdown-body-radius: 0;
123
+ --markdown-body-shadow: none;
124
+ --markdown-body-decorations-display: none;
125
125
  }
126
126
 
127
127
  /* --------------------------------------------------------------------------
@@ -187,10 +187,7 @@ body {
187
187
  margin: 0;
188
188
  min-height: 100vh;
189
189
  padding: clamp(1.25rem, 3vw, 2.5rem);
190
- background:
191
- radial-gradient(circle at top left, rgba(224, 124, 74, 0.08), transparent 28%),
192
- radial-gradient(circle at top right, rgba(176, 141, 207, 0.06), transparent 22%),
193
- linear-gradient(180deg, var(--bg-ambient) 0%, var(--bg) 58%, #050b10 100%);
190
+ background: var(--bg);
194
191
  color: var(--text);
195
192
  font-family: var(--font-body);
196
193
  font-size: var(--fs-100);
@@ -232,7 +229,7 @@ body {
232
229
  max-width: var(--markdown-body-max-width);
233
230
  margin: 0 auto;
234
231
  padding: var(--markdown-body-padding);
235
- background: linear-gradient(180deg, rgba(255, 255, 255, 0.02), var(--surface-2) 16%, var(--surface) 100%);
232
+ background: transparent;
236
233
  border: var(--markdown-body-border);
237
234
  border-radius: var(--markdown-body-radius);
238
235
  box-shadow: var(--markdown-body-shadow);
@@ -1374,3 +1371,14 @@ details.spoil > summary:focus-visible {
1374
1371
  page-break-inside: avoid;
1375
1372
  }
1376
1373
  }
1374
+
1375
+ /* code: uniform dark panel (no highlight.js inner box) */
1376
+ .markdown-body pre code, .markdown-body pre code.hljs { background: transparent; padding: 0; }
1377
+
1378
+
1379
+ /* ---- mermaid & charts: light panel for legibility on the dark background ---- */
1380
+ .markdown-body div.mermaid { background: rgba(255, 255, 255, 0.88); border: 1px solid rgba(255, 255, 255, 0.1);
1381
+ border-radius: 10px; padding: 1.1rem; box-shadow: 0 1px 8px rgba(0, 0, 0, 0.22); }
1382
+ .markdown-body div.mermaid svg { max-width: 100%; height: auto; }
1383
+ .markdown-body canvas.orz-chart { background: rgba(255, 255, 255, 0.88); border-radius: 10px; padding: 0.6rem;
1384
+ box-shadow: 0 1px 8px rgba(0, 0, 0, 0.22); max-width: 100%; height: auto; }