mdv-live 0.5.20 → 0.5.21
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 +29 -0
- package/package.json +1 -1
- package/src/static/app.js +157 -0
- package/src/static/index.html +1 -0
- package/src/static/lib/marpZoom.js +84 -0
- package/src/static/styles.css +27 -5
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,35 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.5.21] - 2026-05-22
|
|
9
|
+
|
|
10
|
+
### Fixed — Marp スライドが横長ペインで上下に見切れる
|
|
11
|
+
|
|
12
|
+
ウィンドウ表示(split モード)で、スライドペインがスライドのアスペクト比
|
|
13
|
+
(16:9)より横長になると、スライド(特に全面画像)の上下が見切れていた。
|
|
14
|
+
|
|
15
|
+
- 原因: スライドペイン内の `.marpit` に確定した高さがなく、active SVG の
|
|
16
|
+
`max-height: 100%` が無効化されていた。SVG が「幅 100%」だけで決まるため、
|
|
17
|
+
ペインが 16:9 より横長だと縦にあふれて上下がクリップされていた
|
|
18
|
+
(フルスクリーン表示は `.marpit` が `height: 100vh` を持つため影響なし)。
|
|
19
|
+
- 修正: `.marpit` に `height: 100%` を与え、active SVG を
|
|
20
|
+
`width/height: auto` + `max-width/height: 100%` の真の "contain" に変更。
|
|
21
|
+
ペインが横長・縦長どちらでもスライド全体が必ず収まる。
|
|
22
|
+
|
|
23
|
+
### Added — トラックパッドのピンチズーム / パン
|
|
24
|
+
|
|
25
|
+
Marp スライド表示で、トラックパッドのピンチ操作(macOS では ctrl+wheel、
|
|
26
|
+
マウスの ctrl+スクロールも同様)でスライドを拡大・縮小できるように。
|
|
27
|
+
|
|
28
|
+
- 二本指スクロールでパン(ペインのネイティブ overflow スクロール)。ピンチと
|
|
29
|
+
パンは分離(ctrl 付き wheel のみズーム)。
|
|
30
|
+
- カーソル位置を中心にズーム。ズーム範囲は fit(等倍)〜6倍。
|
|
31
|
+
- ダブルクリック / `0` キーで全体表示(fit)に復帰。`=` / `-` キーでも増減。
|
|
32
|
+
- スライド送り・フルスクリーン切替・ペインのリサイズで自動的に fit に追従。
|
|
33
|
+
- ズーム計算(contain fit / クランプ / wheel→zoom)は DOM 非依存の
|
|
34
|
+
`src/static/lib/marpZoom.js` に分離し、単体テスト(`tests/test-marp-zoom.js`、
|
|
35
|
+
12 件)を追加。
|
|
36
|
+
|
|
8
37
|
## [0.5.20] - 2026-05-18
|
|
9
38
|
|
|
10
39
|
### Fixed — Presenter View のノート編集が毎回 STALE エラー
|
package/package.json
CHANGED
package/src/static/app.js
CHANGED
|
@@ -1206,6 +1206,136 @@
|
|
|
1206
1206
|
}
|
|
1207
1207
|
};
|
|
1208
1208
|
|
|
1209
|
+
// ============================================================
|
|
1210
|
+
// Marp Slide Zoom (trackpad pinch-to-zoom + pan)
|
|
1211
|
+
// ============================================================
|
|
1212
|
+
//
|
|
1213
|
+
// At fit (zoom === 1) the slide is sized entirely by the CSS "contain"
|
|
1214
|
+
// rules so the whole slide — image and all — is always visible. Zooming
|
|
1215
|
+
// past 1 switches the active SVG to explicit pixel dimensions
|
|
1216
|
+
// (fitSize * zoom); the pane's native overflow then lets a two-finger
|
|
1217
|
+
// scroll pan around the enlarged slide. macOS trackpad pinch arrives as a
|
|
1218
|
+
// `wheel` event with `ctrlKey` set, so ctrl+scroll on a mouse zooms too.
|
|
1219
|
+
const MarpZoom = {
|
|
1220
|
+
area: null,
|
|
1221
|
+
zoom: 1,
|
|
1222
|
+
onWheel: null,
|
|
1223
|
+
onDblClick: null,
|
|
1224
|
+
ro: null,
|
|
1225
|
+
|
|
1226
|
+
// Pure zoom math lives in lib/marpZoom.js (globalThis.MDVMarpZoom) so
|
|
1227
|
+
// it can be unit-tested without a DOM. If that script failed to load,
|
|
1228
|
+
// the CSS-only fit still works; we just skip wiring the gestures.
|
|
1229
|
+
lib() { return (typeof globalThis !== 'undefined') ? globalThis.MDVMarpZoom : null; },
|
|
1230
|
+
|
|
1231
|
+
init(area) {
|
|
1232
|
+
this.detach();
|
|
1233
|
+
if (!this.lib()) return;
|
|
1234
|
+
this.area = area;
|
|
1235
|
+
this.zoom = 1;
|
|
1236
|
+
this.onWheel = (e) => {
|
|
1237
|
+
// Plain two-finger scroll is left to the pane so it pans the
|
|
1238
|
+
// zoomed slide natively. Only a pinch (ctrlKey) zooms.
|
|
1239
|
+
if (!e.ctrlKey) return;
|
|
1240
|
+
e.preventDefault();
|
|
1241
|
+
this.zoomTo(this.lib().zoomForWheel(this.zoom, e.deltaY), e.clientX, e.clientY);
|
|
1242
|
+
};
|
|
1243
|
+
// Double-click anywhere on the slide snaps back to fit.
|
|
1244
|
+
this.onDblClick = () => this.reset();
|
|
1245
|
+
area.addEventListener('wheel', this.onWheel, { passive: false });
|
|
1246
|
+
area.addEventListener('dblclick', this.onDblClick);
|
|
1247
|
+
// Re-apply the pixel size when the pane is resized (window resize,
|
|
1248
|
+
// dragging the notes splitter) so a zoomed slide tracks the new
|
|
1249
|
+
// fit instead of freezing at a stale size.
|
|
1250
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
1251
|
+
this.ro = new ResizeObserver(() => {
|
|
1252
|
+
if (!this.lib().isFit(this.zoom)) this.zoomTo(this.zoom);
|
|
1253
|
+
});
|
|
1254
|
+
this.ro.observe(area);
|
|
1255
|
+
}
|
|
1256
|
+
},
|
|
1257
|
+
|
|
1258
|
+
detach() {
|
|
1259
|
+
if (this.area && this.onWheel) {
|
|
1260
|
+
this.area.removeEventListener('wheel', this.onWheel);
|
|
1261
|
+
this.area.removeEventListener('dblclick', this.onDblClick);
|
|
1262
|
+
}
|
|
1263
|
+
if (this.ro) { this.ro.disconnect(); this.ro = null; }
|
|
1264
|
+
this.area = null;
|
|
1265
|
+
this.onWheel = null;
|
|
1266
|
+
this.onDblClick = null;
|
|
1267
|
+
this.zoom = 1;
|
|
1268
|
+
},
|
|
1269
|
+
|
|
1270
|
+
activeSvg() {
|
|
1271
|
+
return this.area
|
|
1272
|
+
? this.area.querySelector('.marpit > svg[data-marpit-svg].active')
|
|
1273
|
+
: null;
|
|
1274
|
+
},
|
|
1275
|
+
|
|
1276
|
+
// Slide dimensions at fit (zoom 1), resolved the same way the CSS
|
|
1277
|
+
// "contain" rule does — so the 1.0 → 1.01 transition doesn't jump.
|
|
1278
|
+
fitSize(svg) {
|
|
1279
|
+
const cs = getComputedStyle(this.area);
|
|
1280
|
+
const padX = parseFloat(cs.paddingLeft) + parseFloat(cs.paddingRight);
|
|
1281
|
+
const padY = parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom);
|
|
1282
|
+
const aw = this.area.clientWidth - padX;
|
|
1283
|
+
const ah = this.area.clientHeight - padY;
|
|
1284
|
+
const vb = svg.viewBox && svg.viewBox.baseVal;
|
|
1285
|
+
const ratio = (vb && vb.width) ? vb.height / vb.width : 9 / 16;
|
|
1286
|
+
return this.lib().containFit(aw, ah, ratio);
|
|
1287
|
+
},
|
|
1288
|
+
|
|
1289
|
+
// Apply `z` around a focal point (defaults to the pane centre). The
|
|
1290
|
+
// before/after rects already fold in centring and scroll offsets, so
|
|
1291
|
+
// the point under the cursor stays put as the slide grows.
|
|
1292
|
+
zoomTo(z, focalX, focalY) {
|
|
1293
|
+
const svg = this.activeSvg();
|
|
1294
|
+
if (!svg) return;
|
|
1295
|
+
z = this.lib().clampZoom(z);
|
|
1296
|
+
if (this.lib().isFit(z)) { this.reset(); return; }
|
|
1297
|
+
|
|
1298
|
+
if (focalX == null) {
|
|
1299
|
+
const r = this.area.getBoundingClientRect();
|
|
1300
|
+
focalX = r.left + r.width / 2;
|
|
1301
|
+
focalY = r.top + r.height / 2;
|
|
1302
|
+
}
|
|
1303
|
+
const before = svg.getBoundingClientRect();
|
|
1304
|
+
const relX = before.width ? (focalX - before.left) / before.width : 0.5;
|
|
1305
|
+
const relY = before.height ? (focalY - before.top) / before.height : 0.5;
|
|
1306
|
+
|
|
1307
|
+
this.zoom = z;
|
|
1308
|
+
const fit = this.fitSize(svg);
|
|
1309
|
+
svg.style.width = (fit.w * z) + 'px';
|
|
1310
|
+
svg.style.height = (fit.h * z) + 'px';
|
|
1311
|
+
this.area.classList.add('marp-zoomed');
|
|
1312
|
+
|
|
1313
|
+
const after = svg.getBoundingClientRect();
|
|
1314
|
+
this.area.scrollLeft += (after.left + relX * after.width) - focalX;
|
|
1315
|
+
this.area.scrollTop += (after.top + relY * after.height) - focalY;
|
|
1316
|
+
},
|
|
1317
|
+
|
|
1318
|
+
// Step zoom for keyboard (+/-): dir > 0 zooms in, else out.
|
|
1319
|
+
nudge(dir) {
|
|
1320
|
+
if (!this.lib()) return;
|
|
1321
|
+
this.zoomTo(this.lib().zoomForStep(this.zoom, dir));
|
|
1322
|
+
},
|
|
1323
|
+
|
|
1324
|
+
// Back to fit: clear the pixel sizing on every slide (the active one
|
|
1325
|
+
// may have changed since we zoomed) and hand sizing back to CSS.
|
|
1326
|
+
reset() {
|
|
1327
|
+
this.zoom = 1;
|
|
1328
|
+
if (!this.area) return;
|
|
1329
|
+
this.area.querySelectorAll('.marpit > svg[data-marpit-svg]').forEach(s => {
|
|
1330
|
+
s.style.width = '';
|
|
1331
|
+
s.style.height = '';
|
|
1332
|
+
});
|
|
1333
|
+
this.area.classList.remove('marp-zoomed');
|
|
1334
|
+
this.area.scrollLeft = 0;
|
|
1335
|
+
this.area.scrollTop = 0;
|
|
1336
|
+
}
|
|
1337
|
+
};
|
|
1338
|
+
|
|
1209
1339
|
// ============================================================
|
|
1210
1340
|
// Presenter View (separate window with speaker notes)
|
|
1211
1341
|
// ============================================================
|
|
@@ -1523,6 +1653,8 @@
|
|
|
1523
1653
|
gotoSlide(index) {
|
|
1524
1654
|
const slides = elements.content.querySelectorAll('.marpit > svg[data-marpit-svg]');
|
|
1525
1655
|
if (!slides.length || index < 0 || index >= slides.length) return;
|
|
1656
|
+
// Each slide opens at fit; clear any zoom carried from the last one.
|
|
1657
|
+
MarpZoom.reset();
|
|
1526
1658
|
slides.forEach((s, i) => s.classList.toggle('active', i === index));
|
|
1527
1659
|
const panels = elements.content.querySelectorAll(
|
|
1528
1660
|
'#marpNotesArea > .speaker-notes-panel'
|
|
@@ -1619,6 +1751,12 @@
|
|
|
1619
1751
|
MarpSplitHandle.attach(splitEl, handleEl);
|
|
1620
1752
|
}
|
|
1621
1753
|
|
|
1754
|
+
// Enable trackpad pinch-to-zoom / pan on the slide pane.
|
|
1755
|
+
const slideArea = document.getElementById('marpSlideArea');
|
|
1756
|
+
if (slideArea) {
|
|
1757
|
+
MarpZoom.init(slideArea);
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1622
1760
|
// Add navigation controls. The nav is appended to .content (NOT
|
|
1623
1761
|
// marpit) so its `position: fixed` doesn't get clipped by the
|
|
1624
1762
|
// grid container's overflow:hidden rule.
|
|
@@ -1692,6 +1830,8 @@
|
|
|
1692
1830
|
);
|
|
1693
1831
|
|
|
1694
1832
|
const showSlide = (index) => {
|
|
1833
|
+
// Each slide opens at fit; clear any zoom from the last one.
|
|
1834
|
+
MarpZoom.reset();
|
|
1695
1835
|
slides.forEach((slide, i) => {
|
|
1696
1836
|
slide.classList.toggle('active', i === index);
|
|
1697
1837
|
});
|
|
@@ -1731,6 +1871,11 @@
|
|
|
1731
1871
|
const expandIcon = '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" /></svg>';
|
|
1732
1872
|
const shrinkIcon = '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 9V4m0 5H4m5 0L4 4m11 5h5m-5 0V4m0 5l5-5M9 15v5m0-5H4m5 0l-5 5m11-5h5m-5 0v5m0-5l5 5" /></svg>';
|
|
1733
1873
|
const toggleFullscreen = () => {
|
|
1874
|
+
// Snap back to fit across the transition: the fullscreen and
|
|
1875
|
+
// windowed panes have different sizes, and the fullscreen CSS
|
|
1876
|
+
// owns the fit there, so a leftover pixel zoom would mis-size
|
|
1877
|
+
// the slide. The user can re-pinch on either side.
|
|
1878
|
+
MarpZoom.reset();
|
|
1734
1879
|
document.body.classList.toggle('marp-fullscreen');
|
|
1735
1880
|
const isFullscreen = document.body.classList.contains('marp-fullscreen');
|
|
1736
1881
|
if (fullscreenBtn) {
|
|
@@ -1826,6 +1971,17 @@
|
|
|
1826
1971
|
// shortcut and must not also open the presenter view.
|
|
1827
1972
|
e.preventDefault();
|
|
1828
1973
|
PresenterView.open();
|
|
1974
|
+
} else if ((e.key === '+' || e.key === '=') && !e.metaKey && !e.ctrlKey) {
|
|
1975
|
+
// Keyboard zoom (centre-anchored) mirrors the pinch gesture.
|
|
1976
|
+
// Skip Cmd/Ctrl which the browser owns for page zoom.
|
|
1977
|
+
e.preventDefault();
|
|
1978
|
+
MarpZoom.nudge(1);
|
|
1979
|
+
} else if ((e.key === '-' || e.key === '_') && !e.metaKey && !e.ctrlKey) {
|
|
1980
|
+
e.preventDefault();
|
|
1981
|
+
MarpZoom.nudge(-1);
|
|
1982
|
+
} else if (e.key === '0' && !e.metaKey && !e.ctrlKey) {
|
|
1983
|
+
e.preventDefault();
|
|
1984
|
+
MarpZoom.reset();
|
|
1829
1985
|
} else if (e.key === 'Escape') {
|
|
1830
1986
|
e.preventDefault();
|
|
1831
1987
|
if (document.body.classList.contains('marp-fullscreen')) {
|
|
@@ -1843,6 +1999,7 @@
|
|
|
1843
1999
|
// 800ms save timer doesn't fire after the editor element is gone.
|
|
1844
2000
|
InlineNotesPanel.detach();
|
|
1845
2001
|
MarpSplitHandle.detach();
|
|
2002
|
+
MarpZoom.detach();
|
|
1846
2003
|
elements.content.classList.remove('marp-viewer');
|
|
1847
2004
|
document.body.classList.remove('marp-fullscreen');
|
|
1848
2005
|
if (marpKeyHandler) {
|
package/src/static/index.html
CHANGED
|
@@ -181,6 +181,7 @@
|
|
|
181
181
|
<script src="/static/lib/apiClient.js"></script>
|
|
182
182
|
<script src="/static/lib/saveQueue.js"></script>
|
|
183
183
|
<script src="/static/lib/tabRegistry.js"></script>
|
|
184
|
+
<script src="/static/lib/marpZoom.js"></script>
|
|
184
185
|
<script src="/static/app.js"></script>
|
|
185
186
|
</body>
|
|
186
187
|
</html>
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure math for the Marp slide zoom (src/static/app.js → MarpZoom).
|
|
3
|
+
*
|
|
4
|
+
* Imported from a `<script>` tag (no module loader), so the functions are
|
|
5
|
+
* exposed on `globalThis.MDVMarpZoom`. Kept DOM-free so the contain/clamp
|
|
6
|
+
* logic — the part that decides whether the whole slide stays visible — can
|
|
7
|
+
* be unit-tested without a browser (see tests/test-marp-zoom.js).
|
|
8
|
+
*/
|
|
9
|
+
(function () {
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const ZOOM_MIN = 1;
|
|
13
|
+
const ZOOM_MAX = 6;
|
|
14
|
+
|
|
15
|
+
// Per-wheel-delta zoom sensitivity. Pinch deltas are small and frequent;
|
|
16
|
+
// the exponential keeps each step proportional so the gesture feels even
|
|
17
|
+
// across the whole range instead of accelerating near the top. Tuned for a
|
|
18
|
+
// snappy pinch (a 120-delta notch ≈ +62%; was 0.0015 ≈ +20%, 0.0025 ≈ +35%).
|
|
19
|
+
const WHEEL_FACTOR = 0.004;
|
|
20
|
+
|
|
21
|
+
// Keyboard +/- step ratio (zoom in / zoom out).
|
|
22
|
+
const STEP_IN = 1.25;
|
|
23
|
+
const STEP_OUT = 0.8;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* "Contain" fit: the largest w×h with aspect `ratio` (= height/width) that
|
|
27
|
+
* fits inside areaW×areaH. Mirrors the CSS `max-width/height:100%` +
|
|
28
|
+
* `width/height:auto` resolution so the JS-driven zoom (≥1) starts exactly
|
|
29
|
+
* where the CSS fit (=1) leaves off — no jump at the 1.0 boundary.
|
|
30
|
+
*
|
|
31
|
+
* @param {number} areaW available content width (px)
|
|
32
|
+
* @param {number} areaH available content height (px)
|
|
33
|
+
* @param {number} ratio slide height / slide width (e.g. 720/1280)
|
|
34
|
+
* @returns {{w:number,h:number}} fitted slide size, never below 1px
|
|
35
|
+
*/
|
|
36
|
+
function containFit(areaW, areaH, ratio) {
|
|
37
|
+
if (!(areaW > 0) || !(areaH > 0) || !(ratio > 0)) {
|
|
38
|
+
return { w: 1, h: 1 };
|
|
39
|
+
}
|
|
40
|
+
let w = areaW;
|
|
41
|
+
let h = areaW * ratio;
|
|
42
|
+
if (h > areaH) {
|
|
43
|
+
h = areaH;
|
|
44
|
+
w = areaH / ratio;
|
|
45
|
+
}
|
|
46
|
+
return { w: Math.max(1, w), h: Math.max(1, h) };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Clamp a zoom level to [ZOOM_MIN, ZOOM_MAX]. */
|
|
50
|
+
function clampZoom(z) {
|
|
51
|
+
if (!Number.isFinite(z)) return ZOOM_MIN;
|
|
52
|
+
return Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, z));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Next zoom level for a wheel/pinch delta. Negative deltaY (pinch open /
|
|
57
|
+
* scroll up) zooms in. Result is already clamped.
|
|
58
|
+
*/
|
|
59
|
+
function zoomForWheel(current, deltaY) {
|
|
60
|
+
return clampZoom(current * Math.exp(-deltaY * WHEEL_FACTOR));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Next zoom level for a keyboard step. dir > 0 zooms in, else out. */
|
|
64
|
+
function zoomForStep(current, dir) {
|
|
65
|
+
return clampZoom(current * (dir > 0 ? STEP_IN : STEP_OUT));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** True when a zoom level is effectively the fit (no pixel sizing needed). */
|
|
69
|
+
function isFit(z) {
|
|
70
|
+
return z <= ZOOM_MIN + 0.001;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (typeof globalThis !== 'undefined') {
|
|
74
|
+
globalThis.MDVMarpZoom = {
|
|
75
|
+
ZOOM_MIN,
|
|
76
|
+
ZOOM_MAX,
|
|
77
|
+
containFit,
|
|
78
|
+
clampZoom,
|
|
79
|
+
zoomForWheel,
|
|
80
|
+
zoomForStep,
|
|
81
|
+
isFit,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
})();
|
package/src/static/styles.css
CHANGED
|
@@ -1080,24 +1080,38 @@ body.marp-fullscreen .marpit > svg[data-marpit-svg] {
|
|
|
1080
1080
|
.marp-slide-area {
|
|
1081
1081
|
overflow: auto;
|
|
1082
1082
|
display: flex;
|
|
1083
|
-
|
|
1084
|
-
|
|
1083
|
+
/* `safe` keeps the slide reachable when zoomed larger than the pane:
|
|
1084
|
+
plain `center` would push the overflowing top/left out of the scroll
|
|
1085
|
+
range so you could never scroll back to them. */
|
|
1086
|
+
align-items: safe center;
|
|
1087
|
+
justify-content: safe center;
|
|
1085
1088
|
padding: 20px;
|
|
1086
1089
|
min-height: 0;
|
|
1087
1090
|
}
|
|
1088
1091
|
|
|
1092
|
+
/* The pane is the trackpad-zoom focus target. Pinch (ctrl+wheel) zooms the
|
|
1093
|
+
slide; two-finger scroll then pans via the pane's native overflow. */
|
|
1094
|
+
.marp-slide-area.marp-zoomed { cursor: grab; }
|
|
1095
|
+
.marp-slide-area.marp-zoomed.marp-panning { cursor: grabbing; }
|
|
1096
|
+
|
|
1089
1097
|
.marp-slide-area .marpit {
|
|
1098
|
+
/* Definite height (the pane's height is definite) so the active SVG's
|
|
1099
|
+
`max-height: 100%` actually clamps — without it the SVG was sized by
|
|
1100
|
+
width alone and overflowed vertically on wide/short panes. */
|
|
1090
1101
|
width: 100%;
|
|
1091
|
-
|
|
1102
|
+
height: 100%;
|
|
1092
1103
|
display: flex;
|
|
1093
|
-
align-items: center;
|
|
1094
|
-
justify-content: center;
|
|
1104
|
+
align-items: safe center;
|
|
1105
|
+
justify-content: safe center;
|
|
1095
1106
|
}
|
|
1096
1107
|
|
|
1097
1108
|
.marp-slide-area .marpit > svg[data-marpit-svg] {
|
|
1098
1109
|
display: none;
|
|
1110
|
+
/* width:auto + height:auto + the two max-* caps give "contain": the SVG
|
|
1111
|
+
(intrinsic 16:9 viewBox) scales down to fit BOTH pane dimensions. */
|
|
1099
1112
|
max-width: 100%;
|
|
1100
1113
|
max-height: 100%;
|
|
1114
|
+
width: auto;
|
|
1101
1115
|
height: auto;
|
|
1102
1116
|
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
|
|
1103
1117
|
border-radius: 4px;
|
|
@@ -1107,6 +1121,14 @@ body.marp-fullscreen .marpit > svg[data-marpit-svg] {
|
|
|
1107
1121
|
display: block;
|
|
1108
1122
|
}
|
|
1109
1123
|
|
|
1124
|
+
/* When zoomed past fit, MarpZoom sets explicit px width/height on the active
|
|
1125
|
+
SVG. The caps must be lifted or they'd clamp it back to the pane size. */
|
|
1126
|
+
.marp-slide-area.marp-zoomed .marpit { width: auto; height: auto; }
|
|
1127
|
+
.marp-slide-area.marp-zoomed .marpit > svg[data-marpit-svg].active {
|
|
1128
|
+
max-width: none;
|
|
1129
|
+
max-height: none;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1110
1132
|
.marp-split-handle {
|
|
1111
1133
|
cursor: row-resize;
|
|
1112
1134
|
background: var(--border);
|