slidemind 0.3.13 → 0.3.14

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.
Files changed (49) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/AGENTS.md +86 -21
  4. package/README.md +14 -17
  5. package/bin/slidemind.mjs +65 -4969
  6. package/package.json +1 -1
  7. package/skills/create-slides/SKILL.md +113 -74
  8. package/skills/search/SKILL.md +25 -7
  9. package/src/bridge/build-and-push.mjs +108 -15
  10. package/src/bridge/bundle.mjs +69 -0
  11. package/src/bridge/capture.mjs +19 -0
  12. package/src/bridge/client/brands.mjs +622 -0
  13. package/src/bridge/client/engine-routing.mjs +237 -0
  14. package/src/bridge/client/engine-search.mjs +310 -0
  15. package/src/bridge/client/engine-slides.mjs +435 -0
  16. package/src/bridge/client/pane-ops.mjs +335 -0
  17. package/src/bridge/client/presentations.mjs +587 -0
  18. package/src/bridge/client/render.mjs +317 -0
  19. package/src/bridge/client/transport.mjs +695 -0
  20. package/src/bridge/client.mjs +80 -3452
  21. package/src/bridge/config.mjs +1 -1
  22. package/src/bridge/engine-download.mjs +15 -2
  23. package/src/builders/build.mjs +11 -804
  24. package/src/builders/draft-check.mjs +376 -0
  25. package/src/builders/lint.mjs +38 -2
  26. package/src/builders/preview.html +799 -0
  27. package/src/builders/review.mjs +23 -4
  28. package/src/builders/visual-families.mjs +6 -8
  29. package/src/cli/common.mjs +179 -0
  30. package/src/cli/concepts.mjs +310 -0
  31. package/src/cli/connection.mjs +147 -0
  32. package/src/cli/deck.mjs +608 -0
  33. package/src/cli/designs.mjs +113 -0
  34. package/src/cli/diagnose.mjs +141 -0
  35. package/src/cli/download-bundle.mjs +144 -0
  36. package/src/cli/engine-helpers.mjs +429 -0
  37. package/src/cli/engine-results.mjs +320 -0
  38. package/src/cli/help.mjs +201 -0
  39. package/src/cli/install.mjs +257 -0
  40. package/src/cli/read.mjs +120 -0
  41. package/src/cli/ready.mjs +805 -0
  42. package/src/cli/search.mjs +608 -0
  43. package/src/cli/session.mjs +177 -0
  44. package/src/cli/setup.mjs +282 -0
  45. package/src/cli/slide-reuse.mjs +334 -0
  46. package/src/cli/status.mjs +118 -0
  47. package/src/cli/workspace.mjs +302 -0
  48. package/src/installers/codex.mjs +1 -1
  49. package/src/update-check.mjs +231 -0
@@ -0,0 +1,799 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>__TITLE__ — slidemind</title>
7
+ __FAVICON_LINK__
8
+ <!-- Arimo: open-source Arial-metric-compatible font. We alias it to
9
+ "Arial" so master text pinned to Arial gets a font with the same
10
+ horizontal extents PowerPoint/LibreOffice use, instead of macOS
11
+ Chrome's slightly wider system Arial that pushes long strings
12
+ (addresses, single-line headers) past their bounding boxes. -->
13
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
14
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
15
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Arimo:ital,wght@0,400..700;1,400..700&display=swap" />
16
+ <link rel="stylesheet" href="./corporate-design.css" />
17
+ <link rel="stylesheet" href="./layouts.css" />
18
+ <style>
19
+ /* Compatibility aliases for decks/slides authored against the older
20
+ statworx token names. New materialized bundles expose semantic tokens
21
+ (--fg-*, --bg-*, --border-*); existing slide JSX may still refer
22
+ to --sw-*. Keep those declarations valid in the HTML preview. */
23
+ :root {
24
+ --sw-black: var(--fg-1, #000000);
25
+ --sw-white: var(--bg-1, #ffffff);
26
+ --sw-ink: var(--fg-2, #283440);
27
+ --sw-slate: var(--fg-3, #6C7D8C);
28
+ --sw-gray: var(--fg-4, #B6BDCC);
29
+ --sw-fog: var(--bg-2, #EBF0F2);
30
+ --sw-ice-blue: var(--bg-3, #E6E6FF);
31
+ --sw-tech-blue: var(--fg-accent, #0000FF);
32
+ --sw-tech-blue-10: #E6E6FF;
33
+ --sw-tech-blue-20: #CCCCFF;
34
+ --sw-tech-blue-40: #9999FF;
35
+ --sw-tech-blue-80: #3333FF;
36
+ --sw-magenta: #E6007E;
37
+ }
38
+ /* A data-connector div is a directive, not a painted box (the render
39
+ derives the line between its two referenced nodes). Same rule the
40
+ bundle's corporate-design.css carries; repeated here so a preview
41
+ against an older bundle lays out like the render will. */
42
+ [data-connector] { position: absolute; width: 0; height: 0; padding: 0; margin: 0; border-color: transparent; pointer-events: none; }
43
+ [data-sm-connector-overlay] { position: absolute; left: 0; top: 0; width: 1280px; height: 720px; overflow: visible; pointer-events: none; z-index: 2147483000; }
44
+ </style>
45
+ <style>
46
+ /* Substitute Arial with Arimo (a metric clone) so wrap behaviour
47
+ matches PowerPoint's rendering. Local Arial wins only if Arimo
48
+ hasn't loaded yet. */
49
+ @font-face {
50
+ font-family: "Arial";
51
+ src: local("Arimo"), local("Arial");
52
+ font-weight: 400;
53
+ font-style: normal;
54
+ }
55
+ @font-face {
56
+ font-family: "Arial";
57
+ src: local("Arimo Bold"), local("Arial Bold");
58
+ font-weight: 700;
59
+ font-style: normal;
60
+ }
61
+ @font-face {
62
+ font-family: "Arial";
63
+ src: local("Arimo Italic"), local("Arial Italic");
64
+ font-weight: 400;
65
+ font-style: italic;
66
+ }
67
+ @font-face {
68
+ font-family: "Arial";
69
+ src: local("Arimo Bold Italic"), local("Arial Bold Italic");
70
+ font-weight: 700;
71
+ font-style: italic;
72
+ }
73
+ </style>
74
+ <style id="brand-font-faces">__BRAND_FONT_FACES__</style>
75
+ <!-- Chart palette for the <Chart> stand-in (from the bundle's capture engine;
76
+ empty when the bundle carries no chart runtime). -->
77
+ <style id="chart-theme-tokens">__CHART_THEME_TOKENS__</style>
78
+ <style>
79
+ html, body { margin: 0; padding: 0; height: 100%; background: #0a0a0a; overflow: hidden; }
80
+ :root {
81
+ --deck-rail-w: 220px;
82
+ --deck-chrome-h: 62px;
83
+ --deck-chrome-bg: #111113;
84
+ --deck-chrome-bg-2: #161618;
85
+ --deck-chrome-border: #2b2b30;
86
+ --deck-chrome-fg: #ededed;
87
+ --deck-chrome-dim: #9a9aa0;
88
+ }
89
+ .deck {
90
+ position: fixed;
91
+ inset: 0 0 0 var(--deck-rail-w);
92
+ display: flex;
93
+ align-items: center;
94
+ justify-content: center;
95
+ }
96
+ .deck__stage {
97
+ width: 1280px;
98
+ height: 720px;
99
+ transform-origin: center center;
100
+ background: var(--bg-1);
101
+ box-shadow: 0 20px 60px rgba(0,0,0,0.35);
102
+ }
103
+ .deck__nav {
104
+ position: fixed;
105
+ bottom: 16px;
106
+ right: 16px;
107
+ padding: 6px 12px;
108
+ border-radius: 999px;
109
+ color: rgba(255,255,255,0.65);
110
+ background: rgba(255,255,255,0.08);
111
+ backdrop-filter: blur(6px);
112
+ font-family: var(--font-sans, sans-serif);
113
+ font-size: 12px;
114
+ letter-spacing: 0.02em;
115
+ user-select: none;
116
+ }
117
+ .deck__rail {
118
+ position: fixed;
119
+ top: 0;
120
+ left: 0;
121
+ bottom: 0;
122
+ width: var(--deck-rail-w);
123
+ z-index: 2147483590;
124
+ display: flex;
125
+ flex-direction: column;
126
+ background: var(--deck-chrome-bg);
127
+ border-right: 1px solid var(--deck-chrome-border);
128
+ color: var(--deck-chrome-fg);
129
+ font-family: var(--font-sans, sans-serif);
130
+ }
131
+ .deck__thumbs {
132
+ flex: 1 1 auto;
133
+ overflow-y: auto;
134
+ padding: 12px 14px 18px;
135
+ scrollbar-width: none;
136
+ -ms-overflow-style: none;
137
+ }
138
+ .deck__thumbs::-webkit-scrollbar {
139
+ width: 0;
140
+ height: 0;
141
+ display: none;
142
+ }
143
+ .deck__thumb {
144
+ width: 100%;
145
+ margin: 0 0 12px;
146
+ padding: 8px;
147
+ border: 1px solid var(--deck-chrome-border);
148
+ border-radius: 8px;
149
+ background: rgba(255,255,255,0.025);
150
+ color: inherit;
151
+ cursor: pointer;
152
+ text-align: left;
153
+ transition: border-color 120ms ease, background 120ms ease, box-shadow 120ms ease;
154
+ }
155
+ .deck__thumb:hover {
156
+ background: rgba(255,255,255,0.06);
157
+ border-color: #3a3a40;
158
+ }
159
+ .deck__thumb.is-active {
160
+ border-color: rgba(237,237,237,0.7);
161
+ background: rgba(255,255,255,0.085);
162
+ box-shadow: 0 0 0 2px rgba(237,237,237,0.08);
163
+ }
164
+ .deck__thumb-preview {
165
+ width: 176px;
166
+ height: 99px;
167
+ overflow: hidden;
168
+ background: #fff;
169
+ box-shadow: 0 4px 14px rgba(0,0,0,0.28);
170
+ pointer-events: none;
171
+ }
172
+ .deck__thumb-frame {
173
+ display: block;
174
+ width: 176px;
175
+ height: 99px;
176
+ border: 0;
177
+ background: #fff;
178
+ pointer-events: none;
179
+ }
180
+ .deck__thumb-meta {
181
+ display: flex;
182
+ align-items: baseline;
183
+ gap: 7px;
184
+ margin-top: 7px;
185
+ min-width: 0;
186
+ font-size: 11px;
187
+ line-height: 1.25;
188
+ }
189
+ .deck__thumb-number {
190
+ flex: 0 0 auto;
191
+ color: var(--deck-chrome-dim);
192
+ font-variant-numeric: tabular-nums;
193
+ }
194
+ .deck__thumb-name {
195
+ min-width: 0;
196
+ overflow: hidden;
197
+ text-overflow: ellipsis;
198
+ white-space: nowrap;
199
+ }
200
+ .deck__error {
201
+ padding: 32px;
202
+ color: #ff6b6b;
203
+ background: #1a0000;
204
+ font-family: var(--font-mono, monospace);
205
+ font-size: 14px;
206
+ white-space: pre-wrap;
207
+ }
208
+ /* Export mode (?export=1) — used by the PPTX exporter's headless Chrome
209
+ so it captures a clean 1280x720 stage with no letterbox/nav/scaling. */
210
+ body.deck--export { background: #ffffff !important; overflow: hidden; }
211
+ body.deck--export .deck { position: static; display: block; inset: auto; }
212
+ body.deck--export .deck__stage {
213
+ transform: none !important;
214
+ box-shadow: none !important;
215
+ margin: 0;
216
+ position: static;
217
+ }
218
+ body.deck--export .deck__nav,
219
+ body.deck--export .deck__rail { display: none !important; }
220
+ /* Thumbnail mode runs inside the left rail's isolated iframes. It must
221
+ render exactly one slide and no chrome, so per-slide <style> tags do not
222
+ leak into the main review document. */
223
+ body.deck--thumb { background: transparent !important; overflow: hidden; }
224
+ body.deck--thumb .deck { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; }
225
+ body.deck--thumb .deck__stage { box-shadow: none !important; }
226
+ body.deck--thumb .deck__nav,
227
+ body.deck--thumb .deck__rail { display: none !important; }
228
+ /* Print mode (?print=1) — stacks every slide as its own page so the
229
+ browser's Print / Cmd-P renders one slide per page (proofing). */
230
+ body.deck--print {
231
+ background: #ffffff !important;
232
+ overflow: visible !important;
233
+ height: auto !important;
234
+ }
235
+ body.deck--print .deck,
236
+ body.deck--print .deck__nav,
237
+ body.deck--print .deck__rail { display: none !important; }
238
+ .print-deck { background: #ffffff; }
239
+ .print-page {
240
+ width: 1280px;
241
+ height: 720px;
242
+ position: relative;
243
+ overflow: hidden;
244
+ background: var(--bg-1);
245
+ page-break-after: always;
246
+ break-after: page;
247
+ }
248
+ .print-page:last-child {
249
+ page-break-after: auto;
250
+ break-after: auto;
251
+ }
252
+ .print-page__error {
253
+ padding: 32px;
254
+ color: #ff6b6b;
255
+ background: #1a0000;
256
+ font-family: var(--font-mono, monospace);
257
+ font-size: 14px;
258
+ white-space: pre-wrap;
259
+ width: 100%;
260
+ height: 100%;
261
+ box-sizing: border-box;
262
+ }
263
+ @media print {
264
+ @page { size: 1280px 720px; margin: 0; }
265
+ html, body { background: #ffffff !important; }
266
+ }
267
+ </style>
268
+ </head>
269
+ <body>
270
+ <aside class="deck__rail" id="slide-rail" aria-label="Slide thumbnails">
271
+ <div class="deck__thumbs" id="slide-thumbs"></div>
272
+ </aside>
273
+ <div class="deck">
274
+ <div class="deck__stage" id="stage"></div>
275
+ </div>
276
+ <div class="deck__nav" id="nav">loading…</div>
277
+
278
+ <!-- Layout sources (inlined from skills/create-slides/layouts/*.jsx).
279
+ Each one is evaluated once and exposed under its binding name
280
+ (SlideCover, SlideAgendaBlack, ...) so slides can reference it. -->
281
+ __LAYOUTS__
282
+
283
+ <!-- Slide sources (inlined from this deck's slides/ folder, filename order). -->
284
+ __SLIDES__
285
+
286
+ <!-- Runtime: React + Babel-standalone from CDN. -->
287
+ <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
288
+ <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
289
+ <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
290
+ <!-- The <Chart> stand-in runtime: the add-in's own vendored scripts, inlined
291
+ from the bundle's capture engine (this CLI ships no chart source). They
292
+ install window.SlidemindChart, which the bootstrap below binds as Chart
293
+ before any slide is evaluated. -->
294
+ __CHART_PREVIEW_SCRIPTS__
295
+ <!-- The exporter's connector geometry (connector-derive), inlined from the
296
+ bundle's capture engine: the overlay below derives every data-connector
297
+ route and arrowhead exactly as the .pptx export does. -->
298
+ __CONNECTOR_PREVIEW_SCRIPT__
299
+ <script>window.__SLIDE_EDITOR_OVERRIDES__ = __OVERRIDES__;</script>
300
+ <script>window.SVG_ASSETS = __SVG_ASSETS__;</script>
301
+ <script>
302
+ __RUNTIME_APPLIER__
303
+ </script>
304
+
305
+ <script>
306
+ (function () {
307
+ // Map kebab-case filename stem -> PascalCase layout binding name.
308
+ function layoutBindingName(stem) {
309
+ return "Slide" + stem.split("-").map(function (p) {
310
+ return p ? p[0].toUpperCase() + p.slice(1) : "";
311
+ }).join("");
312
+ }
313
+
314
+ // Stamp every intrinsic JSX element with data-sm-loc="<prefix>:<line>:<col>"
315
+ // so the preview-review overlay (review-overlay.js) can resolve a click back
316
+ // to the exact slide source location. Runs as a Babel plugin BEFORE the react
317
+ // preset (plugins run before presets) so it sees JSX nodes, not createElement
318
+ // calls. Inert without the overlay. Mirrors the add-in's instrument-source-loc.
319
+ function makeSourceLocPlugin(locPrefix) {
320
+ return function (babel) {
321
+ var t = babel.types;
322
+ return {
323
+ visitor: {
324
+ JSXOpeningElement: function (path) {
325
+ var node = path.node;
326
+ var nameNode = node.name;
327
+ // Intrinsic elements only: a lowercase JSXIdentifier. Skip
328
+ // components (<SlideCover>), member/namespaced names, fragments.
329
+ // <Chart> is the one component that opts in (as in the add-in's
330
+ // instrument-source-loc): it forwards the attribute onto its
331
+ // root div, so a review pin on a chart resolves to its tag.
332
+ if (!nameNode || nameNode.type !== "JSXIdentifier") return;
333
+ if (!/^[a-z]/.test(nameNode.name) && nameNode.name !== "Chart") return;
334
+ for (var i = 0; i < node.attributes.length; i++) {
335
+ var a = node.attributes[i];
336
+ if (a.type === "JSXAttribute" && a.name &&
337
+ a.name.name === "data-sm-loc") return;
338
+ }
339
+ var start = node.loc && node.loc.start;
340
+ if (!start) return;
341
+ var value = locPrefix + ":" + start.line + ":" + (start.column + 1);
342
+ node.attributes.push(
343
+ t.jsxAttribute(t.jsxIdentifier("data-sm-loc"),
344
+ t.stringLiteral(value))
345
+ );
346
+ }
347
+ }
348
+ };
349
+ };
350
+ }
351
+
352
+ function evalJsx(src, filename, extra) {
353
+ // The inliner wraps each source as "\n" + src + "\n" inside the <script>
354
+ // block; strip that one leading newline so Babel's line numbers (and thus
355
+ // data-sm-loc) align with the author's pristine workspace file.
356
+ if (src.charAt(0) === "\n") src = src.slice(1);
357
+ var result = Babel.transform(src, {
358
+ presets: [["react", { runtime: "classic" }]],
359
+ plugins: [makeSourceLocPlugin(filename)],
360
+ sourceType: "module",
361
+ filename: filename
362
+ });
363
+ var body = result.code.replace(
364
+ /export\s+default\s+/g,
365
+ "__module.default = "
366
+ );
367
+ var names = ["__module", "React"].concat(Object.keys(extra || {}));
368
+ var values = [{}, React].concat(Object.keys(extra || {}).map(function (k) {
369
+ return extra[k];
370
+ }));
371
+ var factory = new Function(
372
+ names.join(","),
373
+ body + "\nreturn __module.default;"
374
+ );
375
+ return factory.apply(null, values);
376
+ }
377
+
378
+ // Preview parity: replace <img src="*.svg"> whose file we inlined into
379
+ // window.SVG_ASSETS with the SVG markup itself, carrying the <img>'s box
380
+ // and color onto a wrapper so fill="currentColor" resolves — the same
381
+ // tint the .pptx renderer applies. Re-runs on every React render via a
382
+ // MutationObserver so navigating slides keeps icons inlined.
383
+ function smInlineSvgAssets() {
384
+ var ASSETS = (typeof window !== "undefined" && window.SVG_ASSETS) || null;
385
+ if (!ASSETS || !Object.keys(ASSETS).length) return;
386
+
387
+ function lookup(src) {
388
+ if (!src) return null;
389
+ var s = src.split("?")[0].split("#")[0];
390
+ var alt = s.indexOf("./") === 0 ? s.slice(2) : s;
391
+ var base = s.split("/").pop();
392
+ return ASSETS[s] || ASSETS[alt] || ASSETS[base] || null;
393
+ }
394
+
395
+ function inlineWithin(root) {
396
+ if (!root || !root.querySelectorAll) return;
397
+ var imgs = root.querySelectorAll("img");
398
+ for (var i = 0; i < imgs.length; i++) {
399
+ var img = imgs[i];
400
+ if (img.getAttribute("data-sm-inlined") === "1") continue;
401
+ var markup = lookup(img.getAttribute("src"));
402
+ if (!markup) continue;
403
+ var span = document.createElement("span");
404
+ span.setAttribute("data-sm-inlined", "1");
405
+ if (img.className) span.className = img.className;
406
+ var styleAttr = img.getAttribute("style");
407
+ if (styleAttr) span.setAttribute("style", styleAttr);
408
+ // Pull the laid-out box + colour even when they came from CSS, not
409
+ // an inline style, so sizing holds and currentColor resolves.
410
+ var cs = window.getComputedStyle ? window.getComputedStyle(img) : null;
411
+ var hasW = !!span.style.width;
412
+ var hasH = !!span.style.height;
413
+ if (cs) {
414
+ if (!hasW && cs.width && cs.width !== "auto" && cs.width !== "0px") {
415
+ span.style.width = cs.width;
416
+ hasW = true;
417
+ }
418
+ if (!hasH && cs.height && cs.height !== "auto" && cs.height !== "0px") {
419
+ span.style.height = cs.height;
420
+ hasH = true;
421
+ }
422
+ if (!span.style.color && cs.color) span.style.color = cs.color;
423
+ }
424
+ if (!span.style.display || span.style.display === "inline") {
425
+ span.style.display = "inline-flex";
426
+ }
427
+ if (!span.style.alignItems) span.style.alignItems = "center";
428
+ if (!span.style.justifyContent) span.style.justifyContent = "center";
429
+ span.innerHTML = markup;
430
+ var svg = span.querySelector("svg");
431
+ if (svg) {
432
+ svg.style.display = "block";
433
+ if (hasW || hasH) {
434
+ svg.style.width = "100%";
435
+ svg.style.height = "100%";
436
+ svg.removeAttribute("width");
437
+ svg.removeAttribute("height");
438
+ }
439
+ }
440
+ if (img.parentNode) img.parentNode.replaceChild(span, img);
441
+ }
442
+ }
443
+
444
+ var scheduled = false;
445
+ var raf =
446
+ window.requestAnimationFrame ||
447
+ function (cb) { return setTimeout(cb, 16); };
448
+ function schedule() {
449
+ if (scheduled) return;
450
+ scheduled = true;
451
+ raf(function () {
452
+ scheduled = false;
453
+ inlineWithin(document.body);
454
+ });
455
+ }
456
+ if (typeof MutationObserver !== "undefined") {
457
+ new MutationObserver(schedule).observe(document.body, {
458
+ childList: true,
459
+ subtree: true,
460
+ });
461
+ }
462
+ schedule();
463
+ }
464
+
465
+ var LAYOUTS = {};
466
+ document.querySelectorAll('script[data-kind="layout"]').forEach(function (s) {
467
+ var name = s.dataset.binding || layoutBindingName(s.dataset.name);
468
+ try {
469
+ LAYOUTS[name] = evalJsx(
470
+ s.textContent,
471
+ "layouts/" + s.dataset.name + ".jsx",
472
+ {}
473
+ );
474
+ window[name] = LAYOUTS[name];
475
+ } catch (err) {
476
+ console.error("Layout " + name + " failed to load:", err);
477
+ throw err;
478
+ }
479
+ });
480
+
481
+ // The Chart binding slide jsx is compiled against — installed by the
482
+ // bundle's chart-preview scripts above; without them a chart renders
483
+ // nothing, exactly as the add-in's page behaves.
484
+ var Chart = window.SlidemindChart
485
+ ? window.SlidemindChart.createComponent(React)
486
+ : function () { return null; };
487
+ var slideScope = Object.assign({ Chart: Chart }, LAYOUTS);
488
+
489
+ var slides = [];
490
+ document.querySelectorAll('script[data-kind="slide"]').forEach(function (s) {
491
+ var idx = parseInt(s.dataset.index, 10);
492
+ var name = s.dataset.name;
493
+ try {
494
+ var Component = evalJsx(
495
+ s.textContent,
496
+ "slides/" + name + ".jsx",
497
+ slideScope
498
+ );
499
+ slides[idx] = { name: name, Component: Component };
500
+ } catch (err) {
501
+ console.error("Slide " + name + " failed to load:", err);
502
+ slides[idx] = { name: name, error: err };
503
+ }
504
+ });
505
+
506
+ var stage = document.getElementById("stage");
507
+ var nav = document.getElementById("nav");
508
+ var rail = document.getElementById("slide-rail");
509
+ var thumbs = document.getElementById("slide-thumbs");
510
+ var thumbButtons = [];
511
+ var params = new URLSearchParams(location.search);
512
+ var exportMode = params.get("export") === "1";
513
+ var printMode = params.get("print") === "1";
514
+ var thumbMode = params.get("thumb") === "1";
515
+ if (exportMode) document.body.classList.add("deck--export");
516
+ if (thumbMode) document.body.classList.add("deck--thumb");
517
+
518
+ smInlineSvgAssets();
519
+
520
+ // Connector overlay — the preview's stand-in for the native connectors
521
+ // the render derives from every data-connector directive (the capture
522
+ // engine injects an equivalent SVG before its screenshot). Straight,
523
+ // elbow and arc routes, side pins, arrowheads, dash and colour follow
524
+ // the same rules as the exporter's connector-derive; the exporter's
525
+ // measured output at view-draft / in the .pptx stays the truth.
526
+ function smDrawConnectors(container) {
527
+ if (!container) return;
528
+ var old = container.querySelector(":scope > svg[data-sm-connector-overlay]");
529
+ if (old) old.remove();
530
+ var dirs = container.querySelectorAll("[data-connector]");
531
+ if (!dirs.length) return;
532
+ var derive = window.SlidemindConnectorDerive;
533
+ if (!derive || typeof derive.resolveConnector !== "function") {
534
+ console.warn("connector overlay skipped: the bundle's capture engine exports no connector geometry — the connected add-in predates it; view-draft / build-pptx still derive the real connectors server-side");
535
+ return;
536
+ }
537
+ var cRect = container.getBoundingClientRect();
538
+ var scale = cRect.width / 1280 || 1;
539
+ var NS = "http://www.w3.org/2000/svg";
540
+ var svg = document.createElementNS(NS, "svg");
541
+ svg.setAttribute("data-sm-connector-overlay", "1");
542
+ svg.setAttribute("viewBox", "0 0 1280 720");
543
+ var defs = document.createElementNS(NS, "defs");
544
+ svg.appendChild(defs);
545
+ var rectOf = function (el) {
546
+ var r = el.getBoundingClientRect();
547
+ return { x: (r.left - cRect.left) / scale, y: (r.top - cRect.top) / scale, w: r.width / scale, h: r.height / scale };
548
+ };
549
+ var resolveRef = function (ref) {
550
+ if (!ref) return null;
551
+ try { return container.querySelector(ref.charAt(0) === "#" ? ref : "#" + ref); } catch (e) { return null; }
552
+ };
553
+ var sideOrNull = function (raw) {
554
+ var s = (raw || "").toLowerCase();
555
+ return (s === "top" || s === "bottom" || s === "left" || s === "right") ? s : null;
556
+ };
557
+ // Computed CSS colour ("rgb(a)(...)") → { hex, alpha } as the serialiser reports it.
558
+ var colorOf = function (css) {
559
+ var m = /rgba?(([^)]+))/.exec(css || "");
560
+ if (!m) return { hex: "000000", alpha: 1 };
561
+ var p = m[1].split(",").map(function (v) { return parseFloat(v); });
562
+ var hex = p.slice(0, 3).map(function (v) {
563
+ var h = Math.max(0, Math.min(255, Math.round(v))).toString(16);
564
+ return h.length < 2 ? "0" + h : h;
565
+ }).join("").toUpperCase();
566
+ return { hex: hex, alpha: p.length > 3 && isFinite(p[3]) ? p[3] : 1 };
567
+ };
568
+ dirs.forEach(function (el, idx) {
569
+ var fromEl = resolveRef(el.getAttribute("data-from"));
570
+ var toEl = resolveRef(el.getAttribute("data-to"));
571
+ var route = (el.getAttribute("data-route") || "straight").toLowerCase();
572
+ var centerEl = route === "arc" ? resolveRef(el.getAttribute("data-center")) : null;
573
+ var arrowRaw = (el.getAttribute("data-arrow") || "none").toLowerCase();
574
+ var cs = getComputedStyle(el);
575
+ var stroke = colorOf(cs.color);
576
+ // The same payload the serialiser hands the exporter; resolveConnector
577
+ // derives endpoints, route, bends, arc, and arrowhead size from it.
578
+ var r = derive.resolveConnector({
579
+ from: el.getAttribute("data-from") || "",
580
+ to: el.getAttribute("data-to") || "",
581
+ arrow: (arrowRaw === "start" || arrowRaw === "end" || arrowRaw === "both") ? arrowRaw : "none",
582
+ route: route,
583
+ fromSide: sideOrNull(el.getAttribute("data-from-side")),
584
+ toSide: sideOrNull(el.getAttribute("data-to-side")),
585
+ center: el.getAttribute("data-center") || null,
586
+ fromRect: fromEl ? rectOf(fromEl) : null,
587
+ toRect: toEl ? rectOf(toEl) : null,
588
+ centerRect: centerEl ? rectOf(centerEl) : null,
589
+ strokeHex: stroke.hex,
590
+ strokeAlpha: stroke.alpha,
591
+ strokeWidthPx: parseFloat(cs.borderTopWidth) || 0,
592
+ dashed: /dashed|dotted/.test(cs.borderTopStyle || ""),
593
+ missing: [],
594
+ });
595
+ if (!r) return;
596
+ var color = "#" + r.strokeHex;
597
+ var d;
598
+ if (r.arc) {
599
+ var large = Math.abs(r.arc.sweepDeg) > 180 ? 1 : 0;
600
+ var sweep = r.arc.sweepDeg > 0 ? 1 : 0;
601
+ d = "M" + r.start.x + " " + r.start.y + " A" + r.arc.r + " " + r.arc.r + " 0 " + large + " " + sweep + " " + r.end.x + " " + r.end.y;
602
+ } else {
603
+ d = r.points.map(function (p, i) { return (i ? "L" : "M") + p.x + " " + p.y; }).join(" ");
604
+ }
605
+ var path = document.createElementNS(NS, "path");
606
+ path.setAttribute("d", d);
607
+ path.setAttribute("fill", "none");
608
+ path.setAttribute("stroke", color);
609
+ path.setAttribute("stroke-opacity", String(r.strokeAlpha));
610
+ path.setAttribute("stroke-width", String(r.strokeWidthPx));
611
+ if (r.dashed) {
612
+ var dash = Math.max(4, r.strokeWidthPx * 3);
613
+ path.setAttribute("stroke-dasharray", dash + " " + dash);
614
+ }
615
+ if (r.arrowStart || r.arrowEnd) {
616
+ // Marker sized to the head PowerPoint renders (r.arrowheadPx).
617
+ var markerId = "sm-conn-arrow-" + idx;
618
+ var marker = document.createElementNS(NS, "marker");
619
+ marker.setAttribute("id", markerId);
620
+ marker.setAttribute("viewBox", "0 0 10 10");
621
+ marker.setAttribute("refX", "9");
622
+ marker.setAttribute("refY", "5");
623
+ marker.setAttribute("markerWidth", String(r.arrowheadPx));
624
+ marker.setAttribute("markerHeight", String(r.arrowheadPx));
625
+ marker.setAttribute("orient", "auto-start-reverse");
626
+ marker.setAttribute("markerUnits", "userSpaceOnUse");
627
+ var tri = document.createElementNS(NS, "path");
628
+ tri.setAttribute("d", "M0,0 L10,5 L0,10 z");
629
+ tri.setAttribute("fill", color);
630
+ tri.setAttribute("fill-opacity", String(r.strokeAlpha));
631
+ marker.appendChild(tri);
632
+ defs.appendChild(marker);
633
+ if (r.arrowStart) path.setAttribute("marker-start", "url(#" + markerId + ")");
634
+ if (r.arrowEnd) path.setAttribute("marker-end", "url(#" + markerId + ")");
635
+ }
636
+ svg.appendChild(path);
637
+ });
638
+ if (svg.childNodes.length > 1) container.appendChild(svg);
639
+ }
640
+ function smScheduleConnectors(container) {
641
+ requestAnimationFrame(function () { requestAnimationFrame(function () { smDrawConnectors(container); }); });
642
+ }
643
+
644
+ if (printMode) {
645
+ document.body.classList.add("deck--print");
646
+ var printDeck = document.createElement("div");
647
+ printDeck.className = "print-deck";
648
+ document.body.appendChild(printDeck);
649
+ slides.forEach(function (slide) {
650
+ var page = document.createElement("div");
651
+ page.className = "print-page";
652
+ printDeck.appendChild(page);
653
+ if (slide.error) {
654
+ var errEl = document.createElement("div");
655
+ errEl.className = "print-page__error";
656
+ errEl.textContent = slide.name + ".jsx failed:\n" + slide.error.message;
657
+ page.appendChild(errEl);
658
+ return;
659
+ }
660
+ ReactDOM.createRoot(page).render(
661
+ React.createElement(slide.Component, { assetsPath: "./assets" })
662
+ );
663
+ smScheduleConnectors(page);
664
+ });
665
+ return;
666
+ }
667
+
668
+ var startIndex = parseInt(params.get("slide") || "0", 10);
669
+ if (isNaN(startIndex) || startIndex < 0) startIndex = 0;
670
+ if (startIndex >= slides.length) startIndex = slides.length - 1;
671
+ var current = startIndex;
672
+ var root = ReactDOM.createRoot(stage);
673
+
674
+ function buildThumbs() {
675
+ if (!thumbs || printMode || exportMode || thumbMode) return;
676
+ thumbs.innerHTML = "";
677
+ thumbButtons = [];
678
+ slides.forEach(function (slide, idx) {
679
+ var btn = document.createElement("button");
680
+ btn.type = "button";
681
+ btn.className = "deck__thumb";
682
+ btn.setAttribute("aria-label", "Go to slide " + (idx + 1) + ": " + slide.name);
683
+
684
+ var preview = document.createElement("div");
685
+ preview.className = "deck__thumb-preview";
686
+ var frame = document.createElement("iframe");
687
+ frame.className = "deck__thumb-frame";
688
+ frame.loading = "lazy";
689
+ frame.setAttribute("aria-hidden", "true");
690
+ frame.tabIndex = -1;
691
+ frame.src = location.pathname + "?slide=" + idx + "&thumb=1";
692
+ preview.appendChild(frame);
693
+
694
+ var meta = document.createElement("div");
695
+ meta.className = "deck__thumb-meta";
696
+ var num = document.createElement("span");
697
+ num.className = "deck__thumb-number";
698
+ num.textContent = String(idx + 1).padStart(2, "0");
699
+ var name = document.createElement("span");
700
+ name.className = "deck__thumb-name";
701
+ name.textContent = slide.name;
702
+ meta.appendChild(num);
703
+ meta.appendChild(name);
704
+
705
+ btn.appendChild(preview);
706
+ btn.appendChild(meta);
707
+ btn.addEventListener("click", function () {
708
+ setCurrent(idx);
709
+ stage.focus && stage.focus();
710
+ });
711
+ thumbs.appendChild(btn);
712
+ thumbButtons[idx] = btn;
713
+ });
714
+ }
715
+
716
+ function updateThumbs() {
717
+ thumbButtons.forEach(function (btn, idx) {
718
+ if (!btn) return;
719
+ var active = idx === current;
720
+ btn.classList.toggle("is-active", active);
721
+ btn.setAttribute("aria-current", active ? "true" : "false");
722
+ if (active && thumbs) {
723
+ var top = btn.offsetTop;
724
+ var bottom = top + btn.offsetHeight;
725
+ if (top < thumbs.scrollTop) {
726
+ thumbs.scrollTop = Math.max(0, top - 12);
727
+ } else if (bottom > thumbs.scrollTop + thumbs.clientHeight) {
728
+ thumbs.scrollTop = bottom - thumbs.clientHeight + 12;
729
+ }
730
+ }
731
+ });
732
+ }
733
+
734
+ function render() {
735
+ var slide = slides[current];
736
+ if (!slide) return;
737
+ nav.textContent =
738
+ (current + 1) + " / " + slides.length + " · " + slide.name;
739
+ if (slide.error) {
740
+ root.render(
741
+ React.createElement(
742
+ "div",
743
+ { className: "deck__error" },
744
+ slide.name + ".jsx failed:\n" + slide.error.message
745
+ )
746
+ );
747
+ updateThumbs();
748
+ return;
749
+ }
750
+ root.render(
751
+ React.createElement(slide.Component, { assetsPath: "./assets" })
752
+ );
753
+ smScheduleConnectors(stage);
754
+ updateThumbs();
755
+ }
756
+
757
+ function setCurrent(index) {
758
+ current = Math.max(0, Math.min(index, slides.length - 1));
759
+ render();
760
+ }
761
+
762
+ function fit() {
763
+ if (exportMode) {
764
+ stage.style.transform = "none";
765
+ return;
766
+ }
767
+ var railW = rail && getComputedStyle(rail).display !== "none" ? rail.offsetWidth : 0;
768
+ var sx = Math.max(1, window.innerWidth - railW) / 1280;
769
+ var sy = window.innerHeight / 720;
770
+ var s = Math.min(sx, sy) * (thumbMode ? 1 : 0.95);
771
+ stage.style.transform = "scale(" + s + ")";
772
+ }
773
+
774
+ window.addEventListener("resize", fit);
775
+ buildThumbs();
776
+ fit();
777
+ render();
778
+
779
+ document.addEventListener("keydown", function (e) {
780
+ var k = e.key;
781
+ if (k === "ArrowRight" || k === "PageDown" || k === " ") {
782
+ setCurrent(current + 1);
783
+ e.preventDefault();
784
+ } else if (k === "ArrowLeft" || k === "PageUp") {
785
+ setCurrent(current - 1);
786
+ e.preventDefault();
787
+ } else if (k === "Home") {
788
+ setCurrent(0);
789
+ } else if (k === "End") {
790
+ setCurrent(slides.length - 1);
791
+ } else if (k === "f" || k === "F") {
792
+ if (document.fullscreenElement) document.exitFullscreen();
793
+ else document.documentElement.requestFullscreen();
794
+ }
795
+ });
796
+ })();
797
+ </script>
798
+ </body>
799
+ </html>