@atom63/slides 0.2.0 → 0.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.
@@ -1,10 +1,12 @@
1
- import { listTemplates, slideMdxComponents, SlidesPlayer } from '../chunk-AD3ZOVWR.js';
1
+ import { listTemplates, slideMdxComponents, SlidesPlayer, templateNames, getTemplate } from '../chunk-AD3ZOVWR.js';
2
2
  import { evaluate } from '@mdx-js/mdx';
3
3
  import yaml from 'js-yaml';
4
4
  import * as runtime from 'react/jsx-runtime';
5
- import { jsx, jsxs } from 'react/jsx-runtime';
5
+ import { jsx as jsx$1, jsxs, Fragment } from 'react/jsx-runtime';
6
6
  import remarkGfm from 'remark-gfm';
7
7
  import { useState, useEffect, useMemo, useRef, useCallback } from 'react';
8
+ import { Parser } from 'acorn';
9
+ import jsx from 'acorn-jsx';
8
10
 
9
11
  function parseFrontmatter(source) {
10
12
  const normalized = source.replace(/^/, "");
@@ -52,6 +54,384 @@ async function compileDeck(source) {
52
54
  return { ok: false, error };
53
55
  }
54
56
  }
57
+ var JsxParser = Parser.extend(jsx());
58
+ var IMPORT_RE2 = /^[ \t]*import\b[\s\S]*?(?:from\s*['"][^'"]*['"]|['"][^'"]*['"])[ \t]*;?[ \t]*$/gm;
59
+ function blankImports(block) {
60
+ return block.replace(IMPORT_RE2, (m) => m.replace(/[^\n]/g, " "));
61
+ }
62
+ function findTemplateElement(block) {
63
+ let ast;
64
+ try {
65
+ ast = JsxParser.parse(blankImports(block), { ecmaVersion: "latest", sourceType: "module" });
66
+ } catch {
67
+ return null;
68
+ }
69
+ const body = ast.body;
70
+ let jsxElement = null;
71
+ for (const node of body) {
72
+ if (node.type === "ImportDeclaration") {
73
+ continue;
74
+ }
75
+ if (node.type === "ExpressionStatement" && node.expression?.type === "JSXElement") {
76
+ if (jsxElement !== null) {
77
+ return null;
78
+ }
79
+ jsxElement = node.expression;
80
+ } else {
81
+ return null;
82
+ }
83
+ }
84
+ return jsxElement;
85
+ }
86
+ function parseSlide(block) {
87
+ const element = findTemplateElement(block);
88
+ if (element === null) {
89
+ return { kind: "opaque", reason: "block contains no single JSX template element" };
90
+ }
91
+ const openingEl = element.openingElement;
92
+ const nameNode = openingEl.name;
93
+ if (nameNode.type !== "JSXIdentifier") {
94
+ return { kind: "opaque", reason: "JSX element name is not a plain identifier" };
95
+ }
96
+ const componentName = nameNode.name;
97
+ if (!templateNames.includes(componentName)) {
98
+ return { kind: "opaque", reason: `"${componentName}" is not a registered template` };
99
+ }
100
+ const children = element.children ?? [];
101
+ for (const child of children) {
102
+ if (child.type === "JSXText") {
103
+ if (child.value.trim() !== "") {
104
+ return { kind: "opaque", reason: "element has non-whitespace children" };
105
+ }
106
+ } else {
107
+ return { kind: "opaque", reason: "element has non-whitespace children" };
108
+ }
109
+ }
110
+ const props = {};
111
+ const attributes = openingEl.attributes ?? [];
112
+ for (const attr of attributes) {
113
+ if (attr.type === "JSXSpreadAttribute") {
114
+ return { kind: "opaque", reason: "element has spread attribute" };
115
+ }
116
+ if (attr.type !== "JSXAttribute") {
117
+ return { kind: "opaque", reason: "unexpected attribute node type" };
118
+ }
119
+ const attrName = attr.name.name;
120
+ const attrValue = attr.value;
121
+ if (attrValue === null) {
122
+ return { kind: "opaque", reason: `attribute "${attrName}" has no string value (boolean shorthand)` };
123
+ }
124
+ if (attrValue.type !== "Literal" || typeof attrValue.value !== "string") {
125
+ return {
126
+ kind: "opaque",
127
+ reason: `attribute "${attrName}" is not a string literal`
128
+ };
129
+ }
130
+ props[attrName] = attrValue.value;
131
+ }
132
+ return { kind: "template", name: componentName, props };
133
+ }
134
+
135
+ // src/editor/serialize-slot.ts
136
+ function renderJsxAttr(name, value) {
137
+ const encoded = value.replaceAll("&", "&").replaceAll('"', """);
138
+ return `${name}="${encoded}"`;
139
+ }
140
+ function renderValue(value) {
141
+ const encoded = value.replaceAll("&", "&").replaceAll('"', """);
142
+ return `"${encoded}"`;
143
+ }
144
+ function setProp(block, name, value) {
145
+ const element = findTemplateElement(block);
146
+ if (element === null) {
147
+ return block;
148
+ }
149
+ const openingEl = element.openingElement;
150
+ const attributes = openingEl.attributes ?? [];
151
+ const existing = attributes.find(
152
+ (attr) => attr.type === "JSXAttribute" && attr.name?.name === name
153
+ );
154
+ if (existing !== void 0) {
155
+ const attrValue = existing.value;
156
+ if (attrValue === null || attrValue.type !== "Literal") {
157
+ return block;
158
+ }
159
+ const existingRaw = block.slice(attrValue.start, attrValue.end);
160
+ const rendered2 = renderValue(value);
161
+ if (existingRaw === rendered2) {
162
+ return block;
163
+ }
164
+ return block.slice(0, attrValue.start) + rendered2 + block.slice(attrValue.end);
165
+ }
166
+ const tagEnd = openingEl.end;
167
+ const rendered = renderValue(value);
168
+ let insertAt;
169
+ if (block[tagEnd - 1] === ">" && block[tagEnd - 2] === "/") {
170
+ insertAt = tagEnd - 2;
171
+ } else if (block[tagEnd - 1] === ">") {
172
+ insertAt = tagEnd - 1;
173
+ } else {
174
+ insertAt = tagEnd;
175
+ }
176
+ const insertion = ` ${name}=${rendered}`;
177
+ return block.slice(0, insertAt) + insertion + block.slice(insertAt);
178
+ }
179
+
180
+ // src/editor/slide-blocks.ts
181
+ function isFenceMarker(trimmed) {
182
+ return trimmed.startsWith("```") || trimmed.startsWith("~~~");
183
+ }
184
+ function splitBlocks(source) {
185
+ const blocks = [];
186
+ let idx = 0;
187
+ const normalized = source.replace(/^/, "");
188
+ const allLines = normalized.split("\n");
189
+ let remainder = source;
190
+ let frontmatterConsumed = 0;
191
+ if (allLines[0]?.trim() === "---") {
192
+ let closeIdx = -1;
193
+ for (let i = 1; i < allLines.length; i++) {
194
+ if (allLines[i].trim() === "---") {
195
+ closeIdx = i;
196
+ break;
197
+ }
198
+ }
199
+ if (closeIdx !== -1) {
200
+ const fmLines = allLines.slice(0, closeIdx + 1);
201
+ const fmText = `${fmLines.join("\n")}
202
+ `;
203
+ if (source.startsWith(fmText) || normalized.startsWith(fmText)) {
204
+ frontmatterConsumed = fmText.length;
205
+ blocks.push({ index: idx++, kind: "frontmatter", text: fmText });
206
+ remainder = source.slice(frontmatterConsumed);
207
+ }
208
+ }
209
+ }
210
+ const remLines = remainder.split("\n");
211
+ const lineRaws = [];
212
+ for (let i = 0; i < remLines.length; i++) {
213
+ const isLast = i === remLines.length - 1;
214
+ if (isLast && remLines[i] === "") {
215
+ break;
216
+ }
217
+ lineRaws.push(isLast ? remLines[i] : `${remLines[i]}
218
+ `);
219
+ }
220
+ let inFence = false;
221
+ let slideText = "";
222
+ const flushSlide = () => {
223
+ blocks.push({ index: idx++, kind: "slide", text: slideText });
224
+ slideText = "";
225
+ };
226
+ for (let i = 0; i < lineRaws.length; i++) {
227
+ const raw = lineRaws[i];
228
+ const trimmed = raw.trimEnd().replace(/\r$/, "");
229
+ if (!inFence && trimmed === "---") {
230
+ flushSlide();
231
+ blocks.push({ index: idx++, kind: "separator", text: raw });
232
+ } else {
233
+ if (isFenceMarker(trimmed)) {
234
+ inFence = !inFence;
235
+ }
236
+ slideText += raw;
237
+ }
238
+ }
239
+ if (slideText !== "" || blocks.filter((b) => b.kind === "slide").length === 0) {
240
+ flushSlide();
241
+ }
242
+ return blocks;
243
+ }
244
+ function joinBlocks(blocks) {
245
+ return blocks.map((b) => b.text).join("");
246
+ }
247
+
248
+ // src/editor/slide-edit.ts
249
+ function slideBlockIndices(source) {
250
+ const blocks = splitBlocks(source);
251
+ const indices = [];
252
+ for (const block of blocks) {
253
+ if (block.kind === "slide" && block.text.trim() !== "") {
254
+ indices.push(block.index);
255
+ }
256
+ }
257
+ return indices;
258
+ }
259
+ function getSlideBlock(source, slideOrdinal) {
260
+ const indices = slideBlockIndices(source);
261
+ if (slideOrdinal < 0 || slideOrdinal >= indices.length) return null;
262
+ const blockIndex = indices[slideOrdinal];
263
+ const blocks = splitBlocks(source);
264
+ const block = blocks.find((b) => b.index === blockIndex);
265
+ if (!block) return null;
266
+ return { blockIndex, text: block.text };
267
+ }
268
+ function setSlideProp(source, slideOrdinal, key, value) {
269
+ const indices = slideBlockIndices(source);
270
+ if (slideOrdinal < 0 || slideOrdinal >= indices.length) return source;
271
+ const blockIndex = indices[slideOrdinal];
272
+ const blocks = splitBlocks(source);
273
+ const updated = blocks.map((b) => {
274
+ if (b.index !== blockIndex) return b;
275
+ if (parseSlide(b.text).kind !== "template") return b;
276
+ return { ...b, text: setProp(b.text, key, value) };
277
+ });
278
+ return joinBlocks(updated);
279
+ }
280
+ function renderProps(props) {
281
+ return Object.entries(props).map(([k, v]) => renderJsxAttr(k, v)).join(" ");
282
+ }
283
+ function switchSlideTemplate(source, slideOrdinal, nextName, nextProps) {
284
+ const indices = slideBlockIndices(source);
285
+ if (slideOrdinal < 0 || slideOrdinal >= indices.length) return source;
286
+ const blockIndex = indices[slideOrdinal];
287
+ const blocks = splitBlocks(source);
288
+ const updated = blocks.map((b) => {
289
+ if (b.index !== blockIndex) return b;
290
+ if (parseSlide(b.text).kind !== "template") return b;
291
+ const element = findTemplateElement(b.text);
292
+ if (element === null) return b;
293
+ const propsStr = renderProps(nextProps);
294
+ const replacement = propsStr.length > 0 ? `<${nextName} ${propsStr} />` : `<${nextName} />`;
295
+ const newText = b.text.slice(0, element.start) + replacement + b.text.slice(element.end);
296
+ return { ...b, text: newText };
297
+ });
298
+ return joinBlocks(updated);
299
+ }
300
+ function fieldId(templateName, key) {
301
+ return `a63-slot-${templateName}-${key}`;
302
+ }
303
+ function isMultiline(kind) {
304
+ return kind === "richtext" || kind === "list";
305
+ }
306
+ function SlotField({
307
+ templateName,
308
+ slot,
309
+ value,
310
+ onChange
311
+ }) {
312
+ const id = fieldId(templateName, slot.key);
313
+ const multiline = isMultiline(slot.kind);
314
+ return /* @__PURE__ */ jsxs("div", { className: "a63-form__field", children: [
315
+ /* @__PURE__ */ jsxs("div", { className: "a63-form__label-row", children: [
316
+ /* @__PURE__ */ jsx$1("label", { className: "a63-form__label", htmlFor: id, children: slot.label }),
317
+ slot.required && /* @__PURE__ */ jsx$1("span", { className: "a63-form__required", "aria-hidden": "true", children: "*" })
318
+ ] }),
319
+ multiline ? /* @__PURE__ */ jsx$1(
320
+ "textarea",
321
+ {
322
+ id,
323
+ className: "a63-form__textarea",
324
+ value,
325
+ required: slot.required,
326
+ "aria-required": slot.required,
327
+ onChange: (e) => onChange(slot.key, e.target.value)
328
+ }
329
+ ) : /* @__PURE__ */ jsx$1(
330
+ "input",
331
+ {
332
+ type: "text",
333
+ id,
334
+ className: "a63-form__input",
335
+ value,
336
+ required: slot.required,
337
+ "aria-required": slot.required,
338
+ onChange: (e) => onChange(slot.key, e.target.value)
339
+ }
340
+ )
341
+ ] });
342
+ }
343
+ function SlotForm({ name, props, onChange }) {
344
+ const tpl = getTemplate(name);
345
+ if (!tpl) {
346
+ return /* @__PURE__ */ jsxs("p", { className: "a63-form__unknown", children: [
347
+ "Unknown template: ",
348
+ /* @__PURE__ */ jsx$1("code", { children: name })
349
+ ] });
350
+ }
351
+ const slotGroupNames = tpl.slots.map((s) => s.name);
352
+ return /* @__PURE__ */ jsxs("div", { className: "a63-form", children: [
353
+ tpl.props.map((slot) => /* @__PURE__ */ jsx$1(
354
+ SlotField,
355
+ {
356
+ templateName: name,
357
+ slot,
358
+ value: props[slot.key] ?? "",
359
+ onChange
360
+ },
361
+ slot.key
362
+ )),
363
+ slotGroupNames.length > 0 && /* @__PURE__ */ jsxs("p", { className: "a63-form__slots-note", children: [
364
+ "Repeatable sections (",
365
+ slotGroupNames.join(", "),
366
+ ") are edited in Source."
367
+ ] })
368
+ ] });
369
+ }
370
+ function TemplatePicker({ name, props, onSwitch }) {
371
+ const [warning, setWarning] = useState(
372
+ null
373
+ );
374
+ function handleChange(next) {
375
+ if (next === name) return;
376
+ const nextDef = getTemplate(next);
377
+ if (!nextDef) return;
378
+ const nextKeys = new Set(nextDef.props.map((s) => s.key));
379
+ const carried = {};
380
+ for (const [k, v] of Object.entries(props)) {
381
+ if (nextKeys.has(k)) {
382
+ carried[k] = v;
383
+ }
384
+ }
385
+ const dropped = Object.entries(props).filter(([k, v]) => !nextKeys.has(k) && v !== "").map(([k]) => k);
386
+ for (const slot of nextDef.props) {
387
+ if (!(slot.key in carried)) {
388
+ carried[slot.key] = "";
389
+ }
390
+ }
391
+ onSwitch(next, { props: carried, dropped });
392
+ if (dropped.length > 0) {
393
+ setWarning({ from: name, to: next, dropped });
394
+ } else {
395
+ setWarning(null);
396
+ }
397
+ }
398
+ const templates = listTemplates();
399
+ return /* @__PURE__ */ jsxs("div", { className: "a63-form__field", children: [
400
+ /* @__PURE__ */ jsx$1("label", { className: "a63-form__label", htmlFor: "a63-template-picker", children: "Template" }),
401
+ /* @__PURE__ */ jsx$1(
402
+ "select",
403
+ {
404
+ id: "a63-template-picker",
405
+ className: "a63-form__input",
406
+ "aria-label": "Template",
407
+ value: name,
408
+ onChange: (e) => handleChange(e.target.value),
409
+ children: templates.map((tpl) => /* @__PURE__ */ jsx$1("option", { value: tpl.name, children: tpl.label ?? tpl.name }, tpl.name))
410
+ }
411
+ ),
412
+ warning && /* @__PURE__ */ jsxs("output", { className: "a63-form__picker-warning", children: [
413
+ /* @__PURE__ */ jsxs("span", { children: [
414
+ "Switched ",
415
+ warning.from,
416
+ " \u2192 ",
417
+ warning.to,
418
+ "; dropped: ",
419
+ warning.dropped.join(", "),
420
+ "."
421
+ ] }),
422
+ /* @__PURE__ */ jsx$1(
423
+ "button",
424
+ {
425
+ type: "button",
426
+ className: "a63-form__picker-dismiss",
427
+ "aria-label": "Dismiss",
428
+ onClick: () => setWarning(null),
429
+ children: "\xD7"
430
+ }
431
+ )
432
+ ] })
433
+ ] });
434
+ }
55
435
 
56
436
  // src/editor/template-snippets.ts
57
437
  var PLACEHOLDER_IMG = "/images/placeholder-1920x1080.webp";
@@ -132,6 +512,8 @@ var templateSnippets = Object.fromEntries(
132
512
  );
133
513
  function EditPane({ source, onChange, onSave, deck, error, onPresent }) {
134
514
  const [theme, setTheme] = useState("light");
515
+ const [rightTab, setRightTab] = useState("source");
516
+ const [formSlideIdx, setFormSlideIdx] = useState(0);
135
517
  const textareaRef = useRef(null);
136
518
  const templates = useMemo(() => listTemplates(), []);
137
519
  const handleChange = useCallback(
@@ -163,21 +545,62 @@ ${snippet}`);
163
545
  },
164
546
  [source, onChange]
165
547
  );
548
+ const slideCount = useMemo(() => slideBlockIndices(source).length, [source]);
549
+ const clampedIdx = Math.min(formSlideIdx, Math.max(0, slideCount - 1));
550
+ const currentBlock = useMemo(() => getSlideBlock(source, clampedIdx), [source, clampedIdx]);
551
+ const parsedSlide = useMemo(
552
+ () => currentBlock ? parseSlide(currentBlock.text) : null,
553
+ [currentBlock]
554
+ );
555
+ const handleSlotChange = useCallback(
556
+ (key, value) => {
557
+ onChange(setSlideProp(source, clampedIdx, key, value));
558
+ },
559
+ [source, clampedIdx, onChange]
560
+ );
561
+ const handleTemplateSwitch = useCallback(
562
+ (next, mapped) => {
563
+ onChange(switchSlideTemplate(source, clampedIdx, next, mapped.props));
564
+ },
565
+ [source, clampedIdx, onChange]
566
+ );
166
567
  return /* @__PURE__ */ jsxs("div", { className: "a63-editor", children: [
167
568
  /* @__PURE__ */ jsxs("section", { className: "a63-editor__preview", "data-theme": theme, children: [
168
569
  error ? /* @__PURE__ */ jsxs("div", { className: "a63-editor__error", role: "alert", children: [
169
- /* @__PURE__ */ jsx("span", { className: "a63-editor__error-tag", children: "MDX error" }),
170
- /* @__PURE__ */ jsx("span", { className: "a63-editor__error-msg", children: error })
570
+ /* @__PURE__ */ jsx$1("span", { className: "a63-editor__error-tag", children: "MDX error" }),
571
+ /* @__PURE__ */ jsx$1("span", { className: "a63-editor__error-msg", children: error })
171
572
  ] }) : null,
172
- deck ? /* @__PURE__ */ jsx("div", { className: "a63-editor__preview-stage", children: /* @__PURE__ */ jsx(SlidesPlayer, { deck, onBack: () => {
173
- } }) }, theme) : /* @__PURE__ */ jsx("div", { className: "a63-editor__preview-empty", children: error ? "Fix the MDX error to render a preview." : "Compiling preview\u2026" })
573
+ deck ? /* @__PURE__ */ jsx$1("div", { className: "a63-editor__preview-stage", children: /* @__PURE__ */ jsx$1(SlidesPlayer, { deck, onBack: () => {
574
+ } }) }, theme) : /* @__PURE__ */ jsx$1("div", { className: "a63-editor__preview-empty", children: error ? "Fix the MDX error to render a preview." : "Compiling preview\u2026" })
174
575
  ] }),
175
576
  /* @__PURE__ */ jsxs("section", { className: "a63-editor__source", children: [
176
577
  /* @__PURE__ */ jsxs("div", { className: "a63-editor__toolbar", children: [
177
- /* @__PURE__ */ jsx("span", { className: "a63-editor__title", children: "Deck source \xB7 MDX" }),
578
+ /* @__PURE__ */ jsx$1("span", { className: "a63-editor__title", children: "Deck source \xB7 MDX" }),
178
579
  /* @__PURE__ */ jsxs("div", { className: "a63-editor__toolbar-actions", children: [
179
- onSave ? /* @__PURE__ */ jsx("button", { type: "button", className: "a63-editor__save", onClick: () => onSave(source), children: "Save" }) : null,
180
- /* @__PURE__ */ jsx(
580
+ /* @__PURE__ */ jsxs("div", { className: "a63-editor__subtoggle", role: "group", "aria-label": "Edit mode", children: [
581
+ /* @__PURE__ */ jsx$1(
582
+ "button",
583
+ {
584
+ type: "button",
585
+ className: "a63-editor__subtoggle-btn",
586
+ "aria-pressed": rightTab === "source",
587
+ onClick: () => setRightTab("source"),
588
+ children: "Source"
589
+ }
590
+ ),
591
+ /* @__PURE__ */ jsx$1(
592
+ "button",
593
+ {
594
+ type: "button",
595
+ className: "a63-editor__subtoggle-btn",
596
+ "aria-pressed": rightTab === "form",
597
+ onClick: () => setRightTab("form"),
598
+ children: "Form"
599
+ }
600
+ )
601
+ ] }),
602
+ onSave ? /* @__PURE__ */ jsx$1("button", { type: "button", className: "a63-editor__save", onClick: () => onSave(source), children: "Save" }) : null,
603
+ /* @__PURE__ */ jsx$1(
181
604
  "button",
182
605
  {
183
606
  type: "button",
@@ -187,37 +610,99 @@ ${snippet}`);
187
610
  children: theme === "light" ? "\u2600\uFE0E Light" : "\u263E Dark"
188
611
  }
189
612
  ),
190
- /* @__PURE__ */ jsx("button", { type: "button", className: "a63-editor__present", onClick: onPresent, children: "Present" })
613
+ /* @__PURE__ */ jsx$1("button", { type: "button", className: "a63-editor__present", onClick: onPresent, children: "Present" })
191
614
  ] })
192
615
  ] }),
193
- /* @__PURE__ */ jsx(
194
- "textarea",
195
- {
196
- ref: textareaRef,
197
- className: "a63-editor__textarea",
198
- value: source,
199
- onChange: handleChange,
200
- onKeyDown: handleKeyDown,
201
- spellCheck: false,
202
- "aria-label": "Deck MDX source"
203
- }
204
- ),
205
- /* @__PURE__ */ jsxs("div", { className: "a63-editor__palette", children: [
206
- /* @__PURE__ */ jsx("div", { className: "a63-editor__palette-label", children: "Templates \xB7 click to append" }),
207
- /* @__PURE__ */ jsx("div", { className: "a63-editor__palette-grid", children: templates.map((t) => /* @__PURE__ */ jsxs(
208
- "button",
616
+ rightTab === "source" ? /* @__PURE__ */ jsxs(Fragment, { children: [
617
+ /* @__PURE__ */ jsx$1(
618
+ "textarea",
209
619
  {
210
- type: "button",
211
- className: "a63-editor__chip",
212
- onClick: () => insert(t.name),
213
- title: `Insert ${t.name}`,
214
- children: [
215
- /* @__PURE__ */ jsx("span", { className: "a63-editor__chip-name", children: t.name }),
216
- /* @__PURE__ */ jsx("span", { className: "a63-editor__chip-meta", children: t.label })
217
- ]
218
- },
219
- t.name
220
- )) })
620
+ ref: textareaRef,
621
+ className: "a63-editor__textarea",
622
+ value: source,
623
+ onChange: handleChange,
624
+ onKeyDown: handleKeyDown,
625
+ spellCheck: false,
626
+ "aria-label": "Deck MDX source"
627
+ }
628
+ ),
629
+ /* @__PURE__ */ jsxs("div", { className: "a63-editor__palette", children: [
630
+ /* @__PURE__ */ jsx$1("div", { className: "a63-editor__palette-label", children: "Templates \xB7 click to append" }),
631
+ /* @__PURE__ */ jsx$1("div", { className: "a63-editor__palette-grid", children: templates.map((t) => /* @__PURE__ */ jsxs(
632
+ "button",
633
+ {
634
+ type: "button",
635
+ className: "a63-editor__chip",
636
+ onClick: () => insert(t.name),
637
+ title: `Insert ${t.name}`,
638
+ children: [
639
+ /* @__PURE__ */ jsx$1("span", { className: "a63-editor__chip-name", children: t.name }),
640
+ /* @__PURE__ */ jsx$1("span", { className: "a63-editor__chip-meta", children: t.label })
641
+ ]
642
+ },
643
+ t.name
644
+ )) })
645
+ ] })
646
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "a63-editor__form-panel", children: [
647
+ /* @__PURE__ */ jsxs("div", { className: "a63-editor__slide-stepper", children: [
648
+ /* @__PURE__ */ jsx$1(
649
+ "button",
650
+ {
651
+ type: "button",
652
+ className: "a63-editor__stepper-btn",
653
+ "aria-label": "Previous slide",
654
+ disabled: clampedIdx <= 0,
655
+ onClick: () => setFormSlideIdx((i) => Math.max(0, i - 1)),
656
+ children: "\u2039"
657
+ }
658
+ ),
659
+ /* @__PURE__ */ jsxs("span", { className: "a63-editor__stepper-label", children: [
660
+ "Slide ",
661
+ clampedIdx + 1,
662
+ " / ",
663
+ slideCount
664
+ ] }),
665
+ /* @__PURE__ */ jsx$1(
666
+ "button",
667
+ {
668
+ type: "button",
669
+ className: "a63-editor__stepper-btn",
670
+ "aria-label": "Next slide",
671
+ disabled: clampedIdx >= slideCount - 1,
672
+ onClick: () => setFormSlideIdx((i) => Math.min(slideCount - 1, i + 1)),
673
+ children: "\u203A"
674
+ }
675
+ )
676
+ ] }),
677
+ parsedSlide === null ? /* @__PURE__ */ jsx$1("p", { className: "a63-editor__form-empty", children: "No slides found." }) : parsedSlide.kind === "opaque" ? /* @__PURE__ */ jsxs("div", { className: "a63-editor__form-opaque", children: [
678
+ /* @__PURE__ */ jsx$1("p", { children: "This slide is only editable in Source." }),
679
+ /* @__PURE__ */ jsx$1(
680
+ "button",
681
+ {
682
+ type: "button",
683
+ className: "a63-editor__subtoggle-btn",
684
+ onClick: () => setRightTab("source"),
685
+ children: "Switch to Source"
686
+ }
687
+ )
688
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "a63-editor__form-fields", children: [
689
+ /* @__PURE__ */ jsx$1(
690
+ TemplatePicker,
691
+ {
692
+ name: parsedSlide.name,
693
+ props: parsedSlide.props,
694
+ onSwitch: handleTemplateSwitch
695
+ }
696
+ ),
697
+ /* @__PURE__ */ jsx$1(
698
+ SlotForm,
699
+ {
700
+ name: parsedSlide.name,
701
+ props: parsedSlide.props,
702
+ onChange: handleSlotChange
703
+ }
704
+ )
705
+ ] })
221
706
  ] })
222
707
  ] })
223
708
  ] });
@@ -271,7 +756,7 @@ function DeckSurface({
271
756
  return () => window.removeEventListener("keydown", onKey);
272
757
  }, [editable, mode]);
273
758
  if (editable && mode === "edit") {
274
- return /* @__PURE__ */ jsx(
759
+ return /* @__PURE__ */ jsx$1(
275
760
  EditPane,
276
761
  {
277
762
  source,
@@ -284,13 +769,13 @@ function DeckSurface({
284
769
  );
285
770
  }
286
771
  return /* @__PURE__ */ jsxs("div", { className: "a63-surface", children: [
287
- editable && /* @__PURE__ */ jsx("button", { type: "button", className: "a63-surface__edit", onClick: () => setMode("edit"), children: "Edit" }),
288
- deck ? /* @__PURE__ */ jsx(SlidesPlayer, { deck, onBack: () => {
289
- } }) : /* @__PURE__ */ jsx("div", { className: "a63-editor__preview-empty", children: error ? "Fix the MDX error to render a preview." : "Compiling preview\u2026" })
772
+ editable && /* @__PURE__ */ jsx$1("button", { type: "button", className: "a63-surface__edit", onClick: () => setMode("edit"), children: "Edit" }),
773
+ deck ? /* @__PURE__ */ jsx$1(SlidesPlayer, { deck, onBack: () => {
774
+ } }) : /* @__PURE__ */ jsx$1("div", { className: "a63-editor__preview-empty", children: error ? "Fix the MDX error to render a preview." : "Compiling preview\u2026" })
290
775
  ] });
291
776
  }
292
777
  function DeckEditor({ source, onChange, debounceMs }) {
293
- return /* @__PURE__ */ jsx(
778
+ return /* @__PURE__ */ jsx$1(
294
779
  DeckSurface,
295
780
  {
296
781
  source,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/editor/compile-deck.ts","../../src/editor/template-snippets.ts","../../src/editor/edit-pane.tsx","../../src/editor/deck-surface.tsx","../../src/editor/deck-editor.tsx"],"names":["useState","useMemo","jsx","jsxs"],"mappings":";;;;;;;;AAkBO,SAAS,iBAAiB,MAAA,EAAiE;AAEhG,EAAA,MAAM,UAAA,GAAa,MAAA,CAAO,OAAA,CAAQ,IAAA,EAAM,EAAE,CAAA;AAC1C,EAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,KAAA,CAAM,IAAI,CAAA;AACnC,EAAA,IAAI,KAAA,CAAM,CAAC,CAAA,EAAG,IAAA,OAAW,KAAA,EAAO;AAC9B,IAAA,OAAO,EAAE,IAAA,EAAM,EAAC,EAAG,MAAM,MAAA,EAAO;AAAA,EAClC;AAGA,EAAA,IAAI,GAAA,GAAM,EAAA;AACV,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACrC,IAAA,IAAI,KAAA,CAAM,CAAC,CAAA,CAAE,IAAA,OAAW,KAAA,EAAO;AAC7B,MAAA,GAAA,GAAM,CAAA;AACN,MAAA;AAAA,IACF;AAAA,EACF;AACA,EAAA,IAAI,QAAQ,EAAA,EAAI;AAEd,IAAA,OAAO,EAAE,IAAA,EAAM,EAAC,EAAG,MAAM,MAAA,EAAO;AAAA,EAClC;AAEA,EAAA,MAAM,YAAY,KAAA,CAAM,KAAA,CAAM,GAAG,GAAG,CAAA,CAAE,KAAK,IAAI,CAAA;AAC/C,EAAA,MAAM,OAAO,KAAA,CAAM,KAAA,CAAM,MAAM,CAAC,CAAA,CAAE,KAAK,IAAI,CAAA;AAC3C,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,IAAA,CAAK,SAAS,CAAA;AAClC,EAAA,MAAM,OAAO,MAAA,IAAU,OAAO,MAAA,KAAW,QAAA,GAAY,SAAqC,EAAC;AAC3F,EAAA,OAAO,EAAE,MAAM,IAAA,EAAK;AACtB;AA2BA,IAAM,SAAA,GAAY,kFAAA;AAGX,SAAS,aAAa,IAAA,EAAsB;AACjD,EAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,SAAA,EAAW,EAAE,CAAA;AACnC;AAEA,IAAM,YAAA,GAA8B;AAAA,EAClC,KAAA,EAAO,eAAA;AAAA,EACP,IAAA,EAAA,qBAAU,IAAA,EAAK,EAAE,aAAY,CAAE,KAAA,CAAM,GAAG,EAAE;AAC5C,CAAA;AAoBA,eAAsB,YAAY,MAAA,EAA4C;AAC5E,EAAA,IAAI;AACF,IAAA,MAAM,EAAE,IAAA,EAAM,IAAA,EAAM,MAAM,OAAA,EAAQ,GAAI,iBAAiB,MAAM,CAAA;AAC7D,IAAA,MAAM,IAAA,GAAsB,EAAE,GAAG,YAAA,EAAc,GAAI,IAAA,EAAgC;AACnF,IAAA,MAAM,IAAA,GAAO,aAAa,OAAO,CAAA;AAEjC,IAAA,MAAM,EAAE,OAAA,EAAS,OAAA,EAAQ,GAAI,MAAM,SAAS,IAAA,EAAM;AAAA,MAChD,GAAG,OAAA;AAAA,MACH,kBAAkB,MAAM,kBAAA;AAAA,MACxB,aAAA,EAAe,CAAC,SAAS;AAAA,KAC1B,CAAA;AAED,IAAA,OAAO,EAAE,EAAA,EAAI,IAAA,EAAM,IAAA,EAAM,OAAA,EAAkC;AAAA,EAC7D,SAAS,GAAA,EAAK;AACZ,IAAA,MAAM,QAAQ,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,OAAO,GAAG,CAAA;AAC7D,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,KAAA,EAAM;AAAA,EAC5B;AACF;;;ACrGA,IAAM,eAAA,GAAkB,oCAAA;AAExB,SAAS,aAAa,IAAA,EAAuB;AAC3C,EAAA,QAAQ,KAAK,IAAA;AAAM,IACjB,KAAK,OAAA;AACH,MAAA,OAAO,eAAA;AAAA,IACT;AAEE,MAAA,OAAO,CAAA,EAAG,KAAK,KAAK,CAAA,MAAA,CAAA;AAAA;AAE1B;AAGA,SAAS,eAAA,CAAgB,KAAA,EAAkB,MAAA,GAAS,IAAA,EAAgB;AAClE,EAAA,MAAM,QAAkB,EAAC;AACzB,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,IAAI,IAAA,CAAK,QAAQ,UAAA,EAAY;AAC7B,IAAA,IAAI,KAAK,KAAA,EAAO;AACd,MAAA,IAAI,IAAA,CAAK,SAAS,OAAA,EAAS;AACzB,QAAA,KAAA,CAAM,IAAA;AAAA,UACJ,CAAA,EAAG,MAAM,CAAA,EAAG,IAAA,CAAK,GAAG,OAAO,eAAe,CAAA,IAAA,EAAO,eAAe,CAAA,IAAA,EAAO,eAAe,CAAA,GAAA;AAAA,SACxF;AAAA,MACF,CAAA,MAAO;AACL,QAAA,KAAA,CAAM,KAAK,CAAA,EAAG,MAAM,CAAA,EAAG,IAAA,CAAK,GAAG,CAAA,+DAAA,CAA4D,CAAA;AAAA,MAC7F;AACA,MAAA;AAAA,IACF;AACA,IAAA,KAAA,CAAM,IAAA,CAAK,CAAA,EAAG,MAAM,CAAA,EAAG,IAAA,CAAK,GAAG,CAAA,EAAA,EAAK,YAAA,CAAa,IAAI,CAAC,CAAA,CAAA,CAAG,CAAA;AAAA,EAC3D;AACA,EAAA,OAAO,KAAA;AACT;AAGA,SAAS,eAAA,CAAgB,cAAsB,IAAA,EAA4B;AACzE,EAAA,MAAM,QAAkB,EAAC;AACzB,EAAA,IAAI,YAAA,GAA8B,IAAA;AAClC,EAAA,KAAA,MAAW,IAAA,IAAQ,KAAK,KAAA,EAAO;AAC7B,IAAA,IAAI,IAAA,CAAK,QAAQ,UAAA,EAAY;AAC3B,MAAA,YAAA,GAAe,CAAA,EAAG,KAAK,KAAK,CAAA,MAAA,CAAA;AAC5B,MAAA;AAAA,IACF;AACA,IAAA,IAAI,KAAK,KAAA,EAAO;AACd,MAAA,KAAA,CAAM,IAAA,CAAK,CAAA,EAAG,IAAA,CAAK,GAAG,CAAA,WAAA,CAAQ,CAAA;AAC9B,MAAA;AAAA,IACF;AACA,IAAA,MAAM,QAAQ,IAAA,CAAK,IAAA,KAAS,UAAU,eAAA,GAAkB,CAAA,EAAG,KAAK,KAAK,CAAA,MAAA,CAAA;AACrE,IAAA,KAAA,CAAM,KAAK,CAAA,EAAG,IAAA,CAAK,GAAG,CAAA,EAAA,EAAK,KAAK,CAAA,CAAA,CAAG,CAAA;AAAA,EACrC;AACA,EAAA,MAAM,GAAA,GAAM,CAAA,EAAG,YAAY,CAAA,CAAA,EAAI,KAAK,IAAI,CAAA,CAAA;AACxC,EAAA,MAAM,OAAA,GAAU,MAAM,MAAA,GAAS,CAAA,CAAA,EAAI,MAAM,IAAA,CAAK,GAAG,CAAC,CAAA,CAAA,GAAK,EAAA;AACvD,EAAA,IAAI,iBAAiB,IAAA,EAAM;AACzB,IAAA,OAAO,MAAM,GAAG,CAAA,EAAG,OAAO,CAAA,CAAA,EAAI,YAAY,KAAK,GAAG,CAAA,CAAA,CAAA;AAAA,EACpD;AACA,EAAA,OAAO,CAAA,GAAA,EAAM,GAAG,CAAA,EAAG,OAAO,CAAA,GAAA,CAAA;AAC5B;AAOO,SAAS,aAAa,CAAA,EAAwB;AACnD,EAAA,MAAM,QAAA,GAAW,CAAA,CAAE,KAAA,CAAM,MAAA,GAAS,CAAA;AAClC,EAAA,MAAM,SAAA,GAAY,eAAA,CAAgB,CAAA,CAAE,KAAK,CAAA;AAEzC,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,IAAI,UAAU,MAAA,KAAW,CAAA,EAAG,OAAO,CAAA,CAAA,EAAI,EAAE,IAAI,CAAA,GAAA,CAAA;AAC7C,IAAA,OAAO,CAAC,CAAA,CAAA,EAAI,CAAA,CAAE,IAAI,CAAA,CAAA,EAAI,GAAG,SAAA,EAAW,IAAI,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA;AAAA,EACrD;AAEA,EAAA,MAAM,OAAO,SAAA,CAAU,MAAA,GAAS,CAAC,CAAA,CAAA,EAAI,EAAE,IAAI,CAAA,CAAA,EAAI,GAAG,SAAA,EAAW,GAAG,CAAA,GAAI,CAAC,CAAA,CAAA,EAAI,CAAA,CAAE,IAAI,CAAA,CAAA,CAAG,CAAA;AAClF,EAAA,MAAM,aAAuB,EAAC;AAC9B,EAAA,KAAA,MAAW,IAAA,IAAQ,EAAE,KAAA,EAAO;AAC1B,IAAA,MAAM,KAAA,GAAQ,KAAK,GAAA,CAAI,IAAA,CAAK,IAAI,IAAA,CAAK,GAAA,EAAK,CAAC,CAAA,EAAG,CAAC,CAAA;AAC/C,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,EAAO,CAAA,EAAA,EAAK;AAC9B,MAAA,UAAA,CAAW,IAAA,CAAK,eAAA,CAAgB,CAAA,CAAE,IAAA,EAAM,IAAI,CAAC,CAAA;AAAA,IAC/C;AAAA,EACF;AACA,EAAA,OAAO,CAAC,GAAG,IAAA,EAAM,GAAG,UAAA,EAAY,CAAA,EAAA,EAAK,CAAA,CAAE,IAAI,CAAA,CAAA,CAAG,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA;AAC3D;AAOO,SAAS,gBAAgB,CAAA,EAAwB;AACtD,EAAA,OAAO;AAAA;;AAAA,EAAY,YAAA,CAAa,CAAC,CAAC;AAAA,CAAA;AACpC;AAGO,IAAM,mBAA2C,MAAA,CAAO,WAAA;AAAA,EAC7D,aAAA,EAAc,CAAE,GAAA,CAAI,CAAA,CAAA,KAAK,CAAC,EAAE,IAAA,EAAM,eAAA,CAAgB,CAAC,CAAC,CAAC;AACvD;AC/FO,SAAS,QAAA,CAAS,EAAE,MAAA,EAAQ,QAAA,EAAU,QAAQ,IAAA,EAAM,KAAA,EAAO,WAAU,EAAkB;AAC5F,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAA2B,OAAO,CAAA;AAC5D,EAAA,MAAM,WAAA,GAAc,OAA4B,IAAI,CAAA;AACpD,EAAA,MAAM,YAAY,OAAA,CAAQ,MAAM,aAAA,EAAc,EAAG,EAAE,CAAA;AAEnD,EAAA,MAAM,YAAA,GAAe,WAAA;AAAA,IACnB,CAAC,CAAA,KAAwC,QAAA,CAAS,CAAA,CAAE,OAAO,KAAK,CAAA;AAAA,IAChE,CAAC,QAAQ;AAAA,GACX;AACA,EAAA,MAAM,aAAA,GAAgB,WAAA;AAAA,IACpB,CAAC,CAAA,KAA0C;AACzC,MAAA,IAAA,CAAK,EAAE,OAAA,IAAW,CAAA,CAAE,OAAA,KAAY,CAAA,CAAE,QAAQ,GAAA,EAAK;AAC7C,QAAA,CAAA,CAAE,cAAA,EAAe;AACjB,QAAA,MAAA,GAAS,MAAM,CAAA;AAAA,MACjB;AAAA,IACF,CAAA;AAAA,IACA,CAAC,QAAQ,MAAM;AAAA,GACjB;AACA,EAAA,MAAM,MAAA,GAAS,WAAA;AAAA,IACb,CAAC,IAAA,KAAiB;AAChB,MAAA,MAAM,OAAA,GAAU,iBAAiB,IAAI,CAAA;AACrC,MAAA,IAAI,CAAC,OAAA,EAAS;AACd,MAAA,QAAA,CAAS,CAAA,EAAG,MAAA,CAAO,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAC;AAAA,EAAK,OAAO,CAAA,CAAE,CAAA;AACpD,MAAA,qBAAA,CAAsB,MAAM;AAC1B,QAAA,MAAM,KAAK,WAAA,CAAY,OAAA;AACvB,QAAA,IAAI,CAAC,EAAA,EAAI;AACT,QAAA,EAAA,CAAG,KAAA,EAAM;AACT,QAAA,EAAA,CAAG,cAAA,GAAiB,EAAA,CAAG,YAAA,GAAe,EAAA,CAAG,KAAA,CAAM,MAAA;AAC/C,QAAA,EAAA,CAAG,YAAY,EAAA,CAAG,YAAA;AAAA,MACpB,CAAC,CAAA;AAAA,IACH,CAAA;AAAA,IACA,CAAC,QAAQ,QAAQ;AAAA,GACnB;AAEA,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,YAAA,EACb,QAAA,EAAA;AAAA,oBAAA,IAAA,CAAC,SAAA,EAAA,EAAQ,SAAA,EAAU,qBAAA,EAAsB,YAAA,EAAY,KAAA,EAClD,QAAA,EAAA;AAAA,MAAA,KAAA,mBACC,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,mBAAA,EAAoB,MAAK,OAAA,EACtC,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,uBAAA,EAAwB,QAAA,EAAA,WAAA,EAAS,CAAA;AAAA,wBACjD,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,uBAAA,EAAyB,QAAA,EAAA,KAAA,EAAM;AAAA,OAAA,EACjD,CAAA,GACE,IAAA;AAAA,MACH,IAAA,uBACE,KAAA,EAAA,EAAI,SAAA,EAAU,6BACb,QAAA,kBAAA,GAAA,CAAC,YAAA,EAAA,EAAa,IAAA,EAAY,MAAA,EAAQ,MAAM;AAAA,MAAC,CAAA,EAAG,CAAA,EAAA,EADE,KAEhD,CAAA,mBAEA,GAAA,CAAC,SAAI,SAAA,EAAU,2BAAA,EACZ,QAAA,EAAA,KAAA,GAAQ,wCAAA,GAA2C,yBAAA,EACtD;AAAA,KAAA,EAEJ,CAAA;AAAA,oBACA,IAAA,CAAC,SAAA,EAAA,EAAQ,SAAA,EAAU,oBAAA,EACjB,QAAA,EAAA;AAAA,sBAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,qBAAA,EACb,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,mBAAA,EAAoB,QAAA,EAAA,sBAAA,EAAiB,CAAA;AAAA,wBACrD,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,6BAAA,EACZ,QAAA,EAAA;AAAA,UAAA,MAAA,mBACC,GAAA,CAAC,QAAA,EAAA,EAAO,IAAA,EAAK,QAAA,EAAS,SAAA,EAAU,kBAAA,EAAmB,OAAA,EAAS,MAAM,MAAA,CAAO,MAAM,CAAA,EAAG,QAAA,EAAA,MAAA,EAElF,CAAA,GACE,IAAA;AAAA,0BACJ,GAAA;AAAA,YAAC,QAAA;AAAA,YAAA;AAAA,cACC,IAAA,EAAK,QAAA;AAAA,cACL,SAAA,EAAU,0BAAA;AAAA,cACV,SAAS,MAAM,QAAA,CAAS,OAAM,CAAA,KAAM,OAAA,GAAU,SAAS,OAAQ,CAAA;AAAA,cAC/D,gBAAc,KAAA,KAAU,MAAA;AAAA,cAEvB,QAAA,EAAA,KAAA,KAAU,UAAU,oBAAA,GAAa;AAAA;AAAA,WACpC;AAAA,0BACA,GAAA,CAAC,YAAO,IAAA,EAAK,QAAA,EAAS,WAAU,qBAAA,EAAsB,OAAA,EAAS,WAAW,QAAA,EAAA,SAAA,EAE1E;AAAA,SAAA,EACF;AAAA,OAAA,EACF,CAAA;AAAA,sBACA,GAAA;AAAA,QAAC,UAAA;AAAA,QAAA;AAAA,UACC,GAAA,EAAK,WAAA;AAAA,UACL,SAAA,EAAU,sBAAA;AAAA,UACV,KAAA,EAAO,MAAA;AAAA,UACP,QAAA,EAAU,YAAA;AAAA,UACV,SAAA,EAAW,aAAA;AAAA,UACX,UAAA,EAAY,KAAA;AAAA,UACZ,YAAA,EAAW;AAAA;AAAA,OACb;AAAA,sBACA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,qBAAA,EACb,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,2BAAA,EAA4B,QAAA,EAAA,gCAAA,EAA2B,CAAA;AAAA,4BACrE,KAAA,EAAA,EAAI,SAAA,EAAU,0BAAA,EACZ,QAAA,EAAA,SAAA,CAAU,IAAI,CAAA,CAAA,qBACb,IAAA;AAAA,UAAC,QAAA;AAAA,UAAA;AAAA,YAEC,IAAA,EAAK,QAAA;AAAA,YACL,SAAA,EAAU,kBAAA;AAAA,YACV,OAAA,EAAS,MAAM,MAAA,CAAO,CAAA,CAAE,IAAI,CAAA;AAAA,YAC5B,KAAA,EAAO,CAAA,OAAA,EAAU,CAAA,CAAE,IAAI,CAAA,CAAA;AAAA,YAEvB,QAAA,EAAA;AAAA,8BAAA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,uBAAA,EAAyB,QAAA,EAAA,CAAA,CAAE,IAAA,EAAK,CAAA;AAAA,8BAChD,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,uBAAA,EAAyB,YAAE,KAAA,EAAM;AAAA;AAAA,WAAA;AAAA,UAP5C,CAAA,CAAE;AAAA,SASV,CAAA,EACH;AAAA,OAAA,EACF;AAAA,KAAA,EACF;AAAA,GAAA,EACF,CAAA;AAEJ;AClEO,SAAS,WAAA,CAAY;AAAA,EAC1B,MAAA,EAAQ,UAAA;AAAA,EACR,QAAA;AAAA,EACA,MAAA;AAAA,EACA,WAAA,GAAc,SAAA;AAAA,EACd,UAAA,GAAa;AACf,CAAA,EAAqB;AACnB,EAAA,MAAM,WAAW,QAAA,KAAa,MAAA;AAG9B,EAAA,MAAM,CAAC,cAAA,EAAgB,iBAAiB,CAAA,GAAIA,SAAS,UAAU,CAAA;AAC/D,EAAA,MAAM,MAAA,GAAS,WAAW,UAAA,GAAa,cAAA;AAEvC,EAAA,MAAM,SAAA,GAAY,CAAC,IAAA,KAAiB;AAClC,IAAA,IAAI,QAAA,WAAmB,IAAI,CAAA;AAAA,2BACJ,IAAI,CAAA;AAAA,EAC7B,CAAA;AAEA,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,IAAIA,QAAAA,CAA0B,QAAA,GAAW,cAAc,SAAS,CAAA;AAEpF,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIA,SAAkB,IAAI,CAAA;AACpD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,SAAwB,IAAI,CAAA;AAGtD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,SAAA,GAAY,KAAA;AAChB,IAAA,MAAM,CAAA,GAAI,WAAW,YAAY;AAC/B,MAAA,MAAM,CAAA,GAAI,MAAM,WAAA,CAAY,MAAM,CAAA;AAClC,MAAA,IAAI,SAAA,EAAW;AACf,MAAA,IAAI,EAAE,EAAA,EAAI;AACR,QAAA,UAAA,CAAW,EAAE,OAAA,EAAS,CAAA,CAAE,SAAS,IAAA,EAAM,CAAA,CAAE,MAAM,CAAA;AAC/C,QAAA,QAAA,CAAS,IAAI,CAAA;AAAA,MACf,CAAA,MAAO;AACL,QAAA,QAAA,CAAS,EAAE,KAAK,CAAA;AAAA,MAClB;AAAA,IACF,GAAG,UAAU,CAAA;AACb,IAAA,OAAO,MAAM;AACX,MAAA,SAAA,GAAY,IAAA;AACZ,MAAA,YAAA,CAAa,CAAC,CAAA;AAAA,IAChB,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,MAAA,EAAQ,UAAU,CAAC,CAAA;AAEvB,EAAA,MAAM,IAAA,GAA6BC,OAAAA;AAAA,IACjC,MAAO,OAAA,GAAU,EAAE,IAAA,EAAM,OAAA,EAAS,IAAA,EAAM,OAAA,CAAQ,IAAA,EAAM,OAAA,EAAS,OAAA,CAAQ,OAAA,EAAQ,GAAI,IAAA;AAAA,IACnF,CAAC,OAAO;AAAA,GACV;AAGA,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,QAAA,EAAU;AACf,IAAA,MAAM,KAAA,GAAQ,CAAC,CAAA,KAAqB;AAClC,MAAA,MAAM,MAAA,GAAU,CAAA,CAAE,MAAA,EAAwB,OAAA,KAAY,UAAA;AACtD,MAAA,IAAI,CAAA,CAAE,QAAQ,GAAA,IAAO,CAAC,UAAU,IAAA,KAAS,SAAA,UAAmB,MAAM,CAAA;AAClE,MAAA,IAAI,EAAE,GAAA,KAAQ,QAAA,IAAY,IAAA,KAAS,MAAA,UAAgB,SAAS,CAAA;AAAA,IAC9D,CAAA;AACA,IAAA,MAAA,CAAO,gBAAA,CAAiB,WAAW,KAAK,CAAA;AACxC,IAAA,OAAO,MAAM,MAAA,CAAO,mBAAA,CAAoB,SAAA,EAAW,KAAK,CAAA;AAAA,EAC1D,CAAA,EAAG,CAAC,QAAA,EAAU,IAAI,CAAC,CAAA;AAEnB,EAAA,IAAI,QAAA,IAAY,SAAS,MAAA,EAAQ;AAC/B,IAAA,uBACEC,GAAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACC,MAAA;AAAA,QACA,QAAA,EAAU,SAAA;AAAA,QACV,MAAA;AAAA,QACA,IAAA;AAAA,QACA,KAAA;AAAA,QACA,SAAA,EAAW,MAAM,OAAA,CAAQ,SAAS;AAAA;AAAA,KACpC;AAAA,EAEJ;AAEA,EAAA,uBACEC,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,aAAA,EACZ,QAAA,EAAA;AAAA,IAAA,QAAA,oBACCD,GAAAA,CAAC,QAAA,EAAA,EAAO,IAAA,EAAK,QAAA,EAAS,SAAA,EAAU,mBAAA,EAAoB,OAAA,EAAS,MAAM,OAAA,CAAQ,MAAM,CAAA,EAAG,QAAA,EAAA,MAAA,EAEpF,CAAA;AAAA,IAED,uBACCA,GAAAA,CAAC,YAAA,EAAA,EAAa,IAAA,EAAY,QAAQ,MAAM;AAAA,IAAC,CAAA,EAAG,oBAE5CA,GAAAA,CAAC,SAAI,SAAA,EAAU,2BAAA,EACZ,QAAA,EAAA,KAAA,GAAQ,wCAAA,GAA2C,yBAAA,EACtD;AAAA,GAAA,EAEJ,CAAA;AAEJ;AChIO,SAAS,UAAA,CAAW,EAAE,MAAA,EAAQ,QAAA,EAAU,YAAW,EAAoB;AAC5E,EAAA,uBACEA,GAAAA;AAAA,IAAC,WAAA;AAAA,IAAA;AAAA,MACC,MAAA;AAAA,MACA,QAAA,EAAU,aAAa,MAAM;AAAA,MAAC,CAAA,CAAA;AAAA,MAC9B,WAAA,EAAY,MAAA;AAAA,MACZ;AAAA;AAAA,GACF;AAEJ","file":"index.js","sourcesContent":["import { evaluate } from '@mdx-js/mdx'\nimport yaml from 'js-yaml'\nimport type { ComponentType } from 'react'\nimport * as runtime from 'react/jsx-runtime'\nimport remarkGfm from 'remark-gfm'\nimport { slideMdxComponents } from '../content/mdx-components'\nimport type { SlideDeckMeta } from '../types'\n\n/**\n * Browser-safe YAML frontmatter split. We deliberately avoid the common\n * \"gray matter\" style parser because it depends on Node's `Buffer`, which\n * doesn't exist in the browser — a browser library must not force consumers to\n * polyfill Node globals.\n *\n * If `source` begins with a `---` fence line, the block up to the next `---`\n * fence is parsed as YAML into `meta` and the remainder is returned as `body`.\n * Otherwise `meta` is empty and the whole source is the `body`.\n */\nexport function parseFrontmatter(source: string): { meta: Record<string, unknown>; body: string } {\n // Frontmatter must start at the very top of the file (allowing a UTF-8 BOM).\n const normalized = source.replace(/^/, '')\n const lines = normalized.split('\\n')\n if (lines[0]?.trim() !== '---') {\n return { meta: {}, body: source }\n }\n\n // Find the closing fence line.\n let end = -1\n for (let i = 1; i < lines.length; i++) {\n if (lines[i].trim() === '---') {\n end = i\n break\n }\n }\n if (end === -1) {\n // No closing fence — treat the whole thing as body, no frontmatter.\n return { meta: {}, body: source }\n }\n\n const yamlBlock = lines.slice(1, end).join('\\n')\n const body = lines.slice(end + 1).join('\\n')\n const loaded = yaml.load(yamlBlock)\n const meta = loaded && typeof loaded === 'object' ? (loaded as Record<string, unknown>) : {}\n return { meta, body }\n}\n\n/**\n * Result of a runtime deck compile.\n *\n * On success: the parsed frontmatter `meta` plus a `Content` component ready to\n * hand to `SlidesPlayer` as `deck.content`. On failure: a human-readable\n * `error` string (compile + frontmatter parsing both throw on bad input).\n */\nexport type CompileDeckResult =\n | { ok: true; meta: SlideDeckMeta; Content: ComponentType }\n | { ok: false; error: string }\n\n/**\n * Matches bare ES `import` statements at the start of a line. A deck's MDX body\n * carries lines like `import { CoverSlide } from \"@atom63/slides\"`. The runtime\n * `evaluate` below has no module resolver, so these must be stripped — the\n * components are injected via `useMDXComponents` instead (see compileDeck).\n *\n * Covers single- and multi-line import specifier blocks:\n * import Foo from \"x\"\n * import { A, B } from \"x\"\n * import {\n * A,\n * B,\n * } from \"x\"\n */\nconst IMPORT_RE = /^[ \\t]*import\\b[\\s\\S]*?(?:from\\s*['\"][^'\"]*['\"]|['\"][^'\"]*['\"])[ \\t]*;?[ \\t]*$/gm\n\n/** Remove bare `import ... from \"...\"` lines so runtime evaluate doesn't choke. */\nexport function stripImports(body: string): string {\n return body.replace(IMPORT_RE, '')\n}\n\nconst DEFAULT_META: SlideDeckMeta = {\n title: 'Untitled deck',\n date: new Date().toISOString().slice(0, 10),\n}\n\n/**\n * Compile a deck's raw MDX source into a renderable Content component using a\n * runtime MDX pipeline (no bundler step).\n *\n * Pipeline:\n * 1. `parseFrontmatter` splits the YAML frontmatter (the deck `meta`) from the\n * body using a browser-safe parser (no Node `Buffer`).\n * 2. `stripImports` removes bare `import ... from \"@atom63/slides\"` lines that\n * the runtime evaluator cannot resolve.\n * 3. `@mdx-js/mdx`'s `evaluate` compiles + runs the body. The slide components\n * (templates + primitives + markdown mappings) are provided through MDX\n * context via `useMDXComponents: () => slideMdxComponents`, so bare JSX such\n * as `<CoverSlide/>` resolves with no import. `---` thematic breaks are\n * preserved as the engine's slide separators.\n *\n * Async and can throw on malformed MDX/frontmatter — callers should debounce\n * and keep the last good render on failure (DeckEditor does this).\n */\nexport async function compileDeck(source: string): Promise<CompileDeckResult> {\n try {\n const { meta: data, body: content } = parseFrontmatter(source)\n const meta: SlideDeckMeta = { ...DEFAULT_META, ...(data as Partial<SlideDeckMeta>) }\n const body = stripImports(content)\n\n const { default: Content } = await evaluate(body, {\n ...runtime,\n useMDXComponents: () => slideMdxComponents,\n remarkPlugins: [remarkGfm],\n })\n\n return { ok: true, meta, Content: Content as ComponentType }\n } catch (err) {\n const error = err instanceof Error ? err.message : String(err)\n return { ok: false, error }\n }\n}\n","import {\n listTemplates,\n type SlotDef,\n type SlotGroupDef,\n type TemplateDef,\n} from '../content/template-registry'\n\n/**\n * Generates a minimal, paste-ready MDX snippet per template from the engine's\n * registry (`listTemplates()`), mirroring the synthesis logic in\n * `skill/scripts/gen-templates.mjs`. The palette in\n * <DeckEditor> appends `templateSnippets[name]` when a template is clicked.\n *\n * Registry is the single source of truth; snippets cannot drift from the\n * published template props/slots.\n */\n\nconst PLACEHOLDER_IMG = '/images/placeholder-1920x1080.webp'\n\nfunction exampleValue(prop: SlotDef): string {\n switch (prop.kind) {\n case 'media':\n return PLACEHOLDER_IMG\n default:\n // text / richtext / list-as-scalar\n return `${prop.label}…`\n }\n}\n\n/** Render the direct-prop attributes for a template's opening tag. */\nfunction renderPropAttrs(props: SlotDef[], indent = ' '): string[] {\n const lines: string[] = []\n for (const prop of props) {\n if (prop.key === 'children') continue\n if (prop.array) {\n if (prop.kind === 'media') {\n lines.push(\n `${indent}${prop.key}={[\"${PLACEHOLDER_IMG}\", \"${PLACEHOLDER_IMG}\", \"${PLACEHOLDER_IMG}\"]}`\n )\n } else {\n lines.push(`${indent}${prop.key}={[{ label: \"Label\", value: \"Value\", href: \"https://…\" }]}`)\n }\n continue\n }\n lines.push(`${indent}${prop.key}=\"${exampleValue(prop)}\"`)\n }\n return lines\n}\n\n/** Render one compound-slot child element, e.g. `<HeroBento.Card title=\"…\" />`. */\nfunction renderSlotChild(templateName: string, slot: SlotGroupDef): string {\n const attrs: string[] = []\n let childrenText: string | null = null\n for (const prop of slot.props) {\n if (prop.key === 'children') {\n childrenText = `${prop.label}…`\n continue\n }\n if (prop.array) {\n attrs.push(`${prop.key}={[…]}`)\n continue\n }\n const value = prop.kind === 'media' ? PLACEHOLDER_IMG : `${prop.label}…`\n attrs.push(`${prop.key}=\"${value}\"`)\n }\n const tag = `${templateName}.${slot.name}`\n const attrStr = attrs.length ? ` ${attrs.join(' ')}` : ''\n if (childrenText !== null) {\n return ` <${tag}${attrStr}>${childrenText}</${tag}>`\n }\n return ` <${tag}${attrStr} />`\n}\n\n/**\n * Synthesize a minimal, valid MDX usage example from a template's schema.\n * - Simple templates → self-closing tag with its props.\n * - Compound templates → open tag + one instance of each slot at `min` (or 1).\n */\nexport function synthExample(t: TemplateDef): string {\n const hasSlots = t.slots.length > 0\n const propLines = renderPropAttrs(t.props)\n\n if (!hasSlots) {\n if (propLines.length === 0) return `<${t.name} />`\n return [`<${t.name}`, ...propLines, '/>'].join('\\n')\n }\n\n const open = propLines.length ? [`<${t.name}`, ...propLines, '>'] : [`<${t.name}>`]\n const childLines: string[] = []\n for (const slot of t.slots) {\n const count = Math.min(Math.max(slot.min, 1), 3)\n for (let i = 0; i < count; i++) {\n childLines.push(renderSlotChild(t.name, slot))\n }\n }\n return [...open, ...childLines, `</${t.name}>`].join('\\n')\n}\n\n/**\n * Wrap a synthesized example as an insertable slide: a leading `---` separator\n * (the engine's slide break) followed by the example, so appending to a deck\n * body always starts a fresh slide.\n */\nexport function toInsertSnippet(t: TemplateDef): string {\n return `\\n---\\n\\n${synthExample(t)}\\n`\n}\n\n/** Map of `templateName -> insertable MDX snippet`, built from the live registry. */\nexport const templateSnippets: Record<string, string> = Object.fromEntries(\n listTemplates().map(t => [t.name, toInsertSnippet(t)])\n)\n","import { type ChangeEvent, type KeyboardEvent, useCallback, useMemo, useRef, useState } from 'react'\nimport { listTemplates } from '../content/template-registry'\nimport { SlidesPlayer } from '../player/slides-player'\nimport type { SlideDeckItem } from '../types'\nimport { templateSnippets } from './template-snippets'\n\nexport interface EditPaneProps {\n source: string\n onChange: (next: string) => void\n onSave?: (source: string) => void | Promise<void>\n deck: SlideDeckItem | null\n error: string | null\n onPresent: () => void\n}\n\nexport function EditPane({ source, onChange, onSave, deck, error, onPresent }: EditPaneProps) {\n const [theme, setTheme] = useState<'light' | 'dark'>('light')\n const textareaRef = useRef<HTMLTextAreaElement>(null)\n const templates = useMemo(() => listTemplates(), [])\n\n const handleChange = useCallback(\n (e: ChangeEvent<HTMLTextAreaElement>) => onChange(e.target.value),\n [onChange]\n )\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLTextAreaElement>) => {\n if ((e.metaKey || e.ctrlKey) && e.key === 's') {\n e.preventDefault()\n onSave?.(source)\n }\n },\n [onSave, source]\n )\n const insert = useCallback(\n (name: string) => {\n const snippet = templateSnippets[name]\n if (!snippet) return\n onChange(`${source.replace(/\\s*$/, '')}\\n${snippet}`)\n requestAnimationFrame(() => {\n const el = textareaRef.current\n if (!el) return\n el.focus()\n el.selectionStart = el.selectionEnd = el.value.length\n el.scrollTop = el.scrollHeight\n })\n },\n [source, onChange]\n )\n\n return (\n <div className=\"a63-editor\">\n <section className=\"a63-editor__preview\" data-theme={theme}>\n {error ? (\n <div className=\"a63-editor__error\" role=\"alert\">\n <span className=\"a63-editor__error-tag\">MDX error</span>\n <span className=\"a63-editor__error-msg\">{error}</span>\n </div>\n ) : null}\n {deck ? (\n <div className=\"a63-editor__preview-stage\" key={theme}>\n <SlidesPlayer deck={deck} onBack={() => {}} />\n </div>\n ) : (\n <div className=\"a63-editor__preview-empty\">\n {error ? 'Fix the MDX error to render a preview.' : 'Compiling preview…'}\n </div>\n )}\n </section>\n <section className=\"a63-editor__source\">\n <div className=\"a63-editor__toolbar\">\n <span className=\"a63-editor__title\">Deck source · MDX</span>\n <div className=\"a63-editor__toolbar-actions\">\n {onSave ? (\n <button type=\"button\" className=\"a63-editor__save\" onClick={() => onSave(source)}>\n Save\n </button>\n ) : null}\n <button\n type=\"button\"\n className=\"a63-editor__theme-toggle\"\n onClick={() => setTheme(t => (t === 'light' ? 'dark' : 'light'))}\n aria-pressed={theme === 'dark'}\n >\n {theme === 'light' ? '☀︎ Light' : '☾ Dark'}\n </button>\n <button type=\"button\" className=\"a63-editor__present\" onClick={onPresent}>\n Present\n </button>\n </div>\n </div>\n <textarea\n ref={textareaRef}\n className=\"a63-editor__textarea\"\n value={source}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n spellCheck={false}\n aria-label=\"Deck MDX source\"\n />\n <div className=\"a63-editor__palette\">\n <div className=\"a63-editor__palette-label\">Templates · click to append</div>\n <div className=\"a63-editor__palette-grid\">\n {templates.map(t => (\n <button\n key={t.name}\n type=\"button\"\n className=\"a63-editor__chip\"\n onClick={() => insert(t.name)}\n title={`Insert ${t.name}`}\n >\n <span className=\"a63-editor__chip-name\">{t.name}</span>\n <span className=\"a63-editor__chip-meta\">{t.label}</span>\n </button>\n ))}\n </div>\n </div>\n </section>\n </div>\n )\n}\n","import { type ComponentType, useEffect, useMemo, useState } from 'react'\nimport { SlidesPlayer } from '../player/slides-player'\nimport type { SlideDeckItem } from '../types'\nimport { compileDeck } from './compile-deck'\nimport { EditPane } from './edit-pane'\n\nexport type DeckSurfaceMode = 'present' | 'edit'\n\nexport interface DeckSurfaceProps {\n /**\n * Raw deck MDX source (frontmatter + `---`-separated slide body). The single\n * canonical input for both Present and Edit modes.\n *\n * **Controlled vs. uncontrolled:**\n * - When `onChange` is provided the surface is **controlled** — the parent\n * owns source state and must feed back the updated string on each change.\n * - When `onChange` is omitted the surface is **uncontrolled** and\n * **present-only**: `source` is read once on mount and later prop changes\n * are ignored. This is the production / read-only contract.\n */\n source: string\n /**\n * Called with the next source string on every edit or template insert\n * (in-memory, per-keystroke). Presence of this prop **enables Edit mode**\n * and makes the surface controlled — the parent holds source state.\n * When omitted the surface is present-only (no edit chrome, no keyboard\n * shortcut to enter Edit).\n */\n onChange?: (next: string) => void\n /**\n * Called on an **explicit** save action (Save button or Cmd/Ctrl-S in Edit\n * mode). This is the persistence hook, distinct from the per-keystroke\n * `onChange`. Wire it to the dev write-back plugin (or any async persistence\n * layer) to flush the source to disk on demand. No-op / absent in a\n * production build.\n */\n onSave?: (source: string) => void | Promise<void>\n /**\n * The mode the surface starts in. Defaults to `'present'`. Forced to\n * `'present'` when `onChange` is absent (present-only surfaces cannot enter\n * Edit mode).\n */\n initialMode?: DeckSurfaceMode\n /**\n * Debounce window in milliseconds before recompiling the preview after a\n * source change. Defaults to `300`. Pass `0` in tests to compile\n * synchronously on the next microtask tick.\n */\n debounceMs?: number\n}\n\ntype Preview = { Content: ComponentType; meta: SlideDeckItem['meta'] } | null\n\nexport function DeckSurface({\n source: sourceProp,\n onChange,\n onSave,\n initialMode = 'present',\n debounceMs = 300,\n}: DeckSurfaceProps) {\n const editable = onChange !== undefined\n\n // Controlled when onChange provided; uncontrolled otherwise.\n const [internalSource, setInternalSource] = useState(sourceProp)\n const source = editable ? sourceProp : internalSource\n\n const setSource = (next: string) => {\n if (editable) onChange(next)\n else setInternalSource(next)\n }\n\n const [mode, setMode] = useState<DeckSurfaceMode>(editable ? initialMode : 'present')\n\n const [preview, setPreview] = useState<Preview>(null)\n const [error, setError] = useState<string | null>(null)\n\n // Debounced runtime compile. Keeps the last good preview on error.\n useEffect(() => {\n let cancelled = false\n const h = setTimeout(async () => {\n const r = await compileDeck(source)\n if (cancelled) return\n if (r.ok) {\n setPreview({ Content: r.Content, meta: r.meta })\n setError(null)\n } else {\n setError(r.error)\n }\n }, debounceMs)\n return () => {\n cancelled = true\n clearTimeout(h)\n }\n }, [source, debounceMs])\n\n const deck: SlideDeckItem | null = useMemo(\n () => (preview ? { slug: 'draft', meta: preview.meta, content: preview.Content } : null),\n [preview]\n )\n\n // Keyboard shortcuts (only when editable).\n useEffect(() => {\n if (!editable) return\n const onKey = (e: KeyboardEvent) => {\n const typing = (e.target as HTMLElement)?.tagName === 'TEXTAREA'\n if (e.key === 'e' && !typing && mode === 'present') setMode('edit')\n if (e.key === 'Escape' && mode === 'edit') setMode('present')\n }\n window.addEventListener('keydown', onKey)\n return () => window.removeEventListener('keydown', onKey)\n }, [editable, mode])\n\n if (editable && mode === 'edit') {\n return (\n <EditPane\n source={source}\n onChange={setSource}\n onSave={onSave}\n deck={deck}\n error={error}\n onPresent={() => setMode('present')}\n />\n )\n }\n\n return (\n <div className=\"a63-surface\">\n {editable && (\n <button type=\"button\" className=\"a63-surface__edit\" onClick={() => setMode('edit')}>\n Edit\n </button>\n )}\n {deck ? (\n <SlidesPlayer deck={deck} onBack={() => {}} />\n ) : (\n <div className=\"a63-editor__preview-empty\">\n {error ? 'Fix the MDX error to render a preview.' : 'Compiling preview…'}\n </div>\n )}\n </div>\n )\n}\n","import { DeckSurface } from './deck-surface'\n\nexport interface DeckEditorProps {\n source: string\n onChange?: (next: string) => void\n debounceMs?: number\n}\n\n/**\n * Backward-compatible editor: DeckSurface locked to Edit mode. The unified\n * surface ({@link DeckSurface}) is the preferred API; this remains for existing\n * consumers.\n */\nexport function DeckEditor({ source, onChange, debounceMs }: DeckEditorProps) {\n return (\n <DeckSurface\n source={source}\n onChange={onChange ?? (() => {})}\n initialMode=\"edit\"\n debounceMs={debounceMs}\n />\n )\n}\n"]}
1
+ {"version":3,"sources":["../../src/editor/compile-deck.ts","../../src/editor/parse-slide.ts","../../src/editor/serialize-slot.ts","../../src/editor/slide-blocks.ts","../../src/editor/slide-edit.ts","../../src/editor/slot-form.tsx","../../src/editor/template-picker.tsx","../../src/editor/template-snippets.ts","../../src/editor/edit-pane.tsx","../../src/editor/deck-surface.tsx","../../src/editor/deck-editor.tsx"],"names":["IMPORT_RE","rendered","jsx","jsxs","useState","useMemo"],"mappings":";;;;;;;;;;AAkBO,SAAS,iBAAiB,MAAA,EAAiE;AAEhG,EAAA,MAAM,UAAA,GAAa,MAAA,CAAO,OAAA,CAAQ,IAAA,EAAM,EAAE,CAAA;AAC1C,EAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,KAAA,CAAM,IAAI,CAAA;AACnC,EAAA,IAAI,KAAA,CAAM,CAAC,CAAA,EAAG,IAAA,OAAW,KAAA,EAAO;AAC9B,IAAA,OAAO,EAAE,IAAA,EAAM,EAAC,EAAG,MAAM,MAAA,EAAO;AAAA,EAClC;AAGA,EAAA,IAAI,GAAA,GAAM,EAAA;AACV,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACrC,IAAA,IAAI,KAAA,CAAM,CAAC,CAAA,CAAE,IAAA,OAAW,KAAA,EAAO;AAC7B,MAAA,GAAA,GAAM,CAAA;AACN,MAAA;AAAA,IACF;AAAA,EACF;AACA,EAAA,IAAI,QAAQ,EAAA,EAAI;AAEd,IAAA,OAAO,EAAE,IAAA,EAAM,EAAC,EAAG,MAAM,MAAA,EAAO;AAAA,EAClC;AAEA,EAAA,MAAM,YAAY,KAAA,CAAM,KAAA,CAAM,GAAG,GAAG,CAAA,CAAE,KAAK,IAAI,CAAA;AAC/C,EAAA,MAAM,OAAO,KAAA,CAAM,KAAA,CAAM,MAAM,CAAC,CAAA,CAAE,KAAK,IAAI,CAAA;AAC3C,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,IAAA,CAAK,SAAS,CAAA;AAClC,EAAA,MAAM,OAAO,MAAA,IAAU,OAAO,MAAA,KAAW,QAAA,GAAY,SAAqC,EAAC;AAC3F,EAAA,OAAO,EAAE,MAAM,IAAA,EAAK;AACtB;AA2BA,IAAM,SAAA,GAAY,kFAAA;AAGX,SAAS,aAAa,IAAA,EAAsB;AACjD,EAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,SAAA,EAAW,EAAE,CAAA;AACnC;AAEA,IAAM,YAAA,GAA8B;AAAA,EAClC,KAAA,EAAO,eAAA;AAAA,EACP,IAAA,EAAA,qBAAU,IAAA,EAAK,EAAE,aAAY,CAAE,KAAA,CAAM,GAAG,EAAE;AAC5C,CAAA;AAoBA,eAAsB,YAAY,MAAA,EAA4C;AAC5E,EAAA,IAAI;AACF,IAAA,MAAM,EAAE,IAAA,EAAM,IAAA,EAAM,MAAM,OAAA,EAAQ,GAAI,iBAAiB,MAAM,CAAA;AAC7D,IAAA,MAAM,IAAA,GAAsB,EAAE,GAAG,YAAA,EAAc,GAAI,IAAA,EAAgC;AACnF,IAAA,MAAM,IAAA,GAAO,aAAa,OAAO,CAAA;AAEjC,IAAA,MAAM,EAAE,OAAA,EAAS,OAAA,EAAQ,GAAI,MAAM,SAAS,IAAA,EAAM;AAAA,MAChD,GAAG,OAAA;AAAA,MACH,kBAAkB,MAAM,kBAAA;AAAA,MACxB,aAAA,EAAe,CAAC,SAAS;AAAA,KAC1B,CAAA;AAED,IAAA,OAAO,EAAE,EAAA,EAAI,IAAA,EAAM,IAAA,EAAM,OAAA,EAAkC;AAAA,EAC7D,SAAS,GAAA,EAAK;AACZ,IAAA,MAAM,QAAQ,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,OAAO,GAAG,CAAA;AAC7D,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,KAAA,EAAM;AAAA,EAC5B;AACF;AC1GA,IAAM,SAAA,GAAY,MAAA,CAAO,MAAA,CAAO,GAAA,EAAK,CAAA;AAgBrC,IAAMA,UAAAA,GACJ,kFAAA;AAEF,SAAS,aAAa,KAAA,EAAuB;AAC3C,EAAA,OAAO,KAAA,CAAM,QAAQA,UAAAA,EAAW,CAAA,CAAA,KAAK,EAAE,OAAA,CAAQ,QAAA,EAAU,GAAG,CAAC,CAAA;AAC/D;AASO,SAAS,oBAAoB,KAAA,EAA+B;AACjE,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI;AAGF,IAAA,GAAA,GAAM,SAAA,CAAU,KAAA,CAAM,YAAA,CAAa,KAAK,CAAA,EAAG,EAAE,WAAA,EAAa,QAAA,EAAU,UAAA,EAAY,QAAA,EAAU,CAAA;AAAA,EAC5F,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,OAAkB,GAAA,CAAI,IAAA;AAE5B,EAAA,IAAI,UAAA,GAA6B,IAAA;AAEjC,EAAA,KAAA,MAAW,QAAQ,IAAA,EAAM;AACvB,IAAA,IAAI,IAAA,CAAK,SAAS,mBAAA,EAAqB;AAErC,MAAA;AAAA,IACF;AACA,IAAA,IAAI,KAAK,IAAA,KAAS,qBAAA,IAAyB,IAAA,CAAK,UAAA,EAAY,SAAS,YAAA,EAAc;AACjF,MAAA,IAAI,eAAe,IAAA,EAAM;AAEvB,QAAA,OAAO,IAAA;AAAA,MACT;AACA,MAAA,UAAA,GAAa,IAAA,CAAK,UAAA;AAAA,IACpB,CAAA,MAAO;AAEL,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF;AAEA,EAAA,OAAO,UAAA;AACT;AASO,SAAS,WAAW,KAAA,EAA4B;AACrD,EAAA,MAAM,OAAA,GAAU,oBAAoB,KAAK,CAAA;AAEzC,EAAA,IAAI,YAAY,IAAA,EAAM;AACpB,IAAA,OAAO,EAAE,IAAA,EAAM,QAAA,EAAU,MAAA,EAAQ,+CAAA,EAAgD;AAAA,EACnF;AAGA,EAAA,MAAM,YAAY,OAAA,CAAQ,cAAA;AAC1B,EAAA,MAAM,WAAW,SAAA,CAAU,IAAA;AAC3B,EAAA,IAAI,QAAA,CAAS,SAAS,eAAA,EAAiB;AACrC,IAAA,OAAO,EAAE,IAAA,EAAM,QAAA,EAAU,MAAA,EAAQ,4CAAA,EAA6C;AAAA,EAChF;AACA,EAAA,MAAM,gBAAwB,QAAA,CAAS,IAAA;AAEvC,EAAA,IAAI,CAAE,aAAA,CAAoC,QAAA,CAAS,aAAa,CAAA,EAAG;AACjE,IAAA,OAAO,EAAE,IAAA,EAAM,QAAA,EAAU,MAAA,EAAQ,CAAA,CAAA,EAAI,aAAa,CAAA,8BAAA,CAAA,EAAiC;AAAA,EACrF;AAGA,EAAA,MAAM,QAAA,GAAsB,OAAA,CAAQ,QAAA,IAAY,EAAC;AACjD,EAAA,KAAA,MAAW,SAAS,QAAA,EAAU;AAC5B,IAAA,IAAI,KAAA,CAAM,SAAS,SAAA,EAAW;AAE5B,MAAA,IAAI,KAAA,CAAM,KAAA,CAAM,IAAA,EAAK,KAAM,EAAA,EAAI;AAC7B,QAAA,OAAO,EAAE,IAAA,EAAM,QAAA,EAAU,MAAA,EAAQ,qCAAA,EAAsC;AAAA,MACzE;AAAA,IACF,CAAA,MAAO;AAEL,MAAA,OAAO,EAAE,IAAA,EAAM,QAAA,EAAU,MAAA,EAAQ,qCAAA,EAAsC;AAAA,IACzE;AAAA,EACF;AAGA,EAAA,MAAM,QAAgC,EAAC;AACvC,EAAA,MAAM,UAAA,GAAwB,SAAA,CAAU,UAAA,IAAc,EAAC;AACvD,EAAA,KAAA,MAAW,QAAQ,UAAA,EAAY;AAC7B,IAAA,IAAI,IAAA,CAAK,SAAS,oBAAA,EAAsB;AACtC,MAAA,OAAO,EAAE,IAAA,EAAM,QAAA,EAAU,MAAA,EAAQ,8BAAA,EAA+B;AAAA,IAClE;AACA,IAAA,IAAI,IAAA,CAAK,SAAS,cAAA,EAAgB;AAChC,MAAA,OAAO,EAAE,IAAA,EAAM,QAAA,EAAU,MAAA,EAAQ,gCAAA,EAAiC;AAAA,IACpE;AACA,IAAA,MAAM,QAAA,GAAmB,KAAK,IAAA,CAAK,IAAA;AACnC,IAAA,MAAM,YAAqB,IAAA,CAAK,KAAA;AAKhC,IAAA,IAAI,cAAc,IAAA,EAAM;AAEtB,MAAA,OAAO,EAAE,IAAA,EAAM,QAAA,EAAU,MAAA,EAAQ,CAAA,WAAA,EAAc,QAAQ,CAAA,yCAAA,CAAA,EAA4C;AAAA,IACrG;AACA,IAAA,IAAI,UAAU,IAAA,KAAS,SAAA,IAAa,OAAO,SAAA,CAAU,UAAU,QAAA,EAAU;AACvE,MAAA,OAAO;AAAA,QACL,IAAA,EAAM,QAAA;AAAA,QACN,MAAA,EAAQ,cAAc,QAAQ,CAAA,yBAAA;AAAA,OAChC;AAAA,IACF;AAEA,IAAA,KAAA,CAAM,QAAQ,IAAI,SAAA,CAAU,KAAA;AAAA,EAC9B;AAEA,EAAA,OAAO,EAAE,IAAA,EAAM,UAAA,EAAY,IAAA,EAAM,eAAe,KAAA,EAAM;AACxD;;;ACxHO,SAAS,aAAA,CAAc,MAAc,KAAA,EAAuB;AAEjE,EAAA,MAAM,OAAA,GAAU,MAAM,UAAA,CAAW,GAAA,EAAK,OAAO,CAAA,CAAE,UAAA,CAAW,KAAK,QAAQ,CAAA;AACvE,EAAA,OAAO,CAAA,EAAG,IAAI,CAAA,EAAA,EAAK,OAAO,CAAA,CAAA,CAAA;AAC5B;AAgBA,SAAS,YAAY,KAAA,EAAuB;AAC1C,EAAA,MAAM,OAAA,GAAU,MAAM,UAAA,CAAW,GAAA,EAAK,OAAO,CAAA,CAAE,UAAA,CAAW,KAAK,QAAQ,CAAA;AACvE,EAAA,OAAO,IAAI,OAAO,CAAA,CAAA,CAAA;AACpB;AAoBO,SAAS,OAAA,CAAQ,KAAA,EAAe,IAAA,EAAc,KAAA,EAAuB;AAC1E,EAAA,MAAM,OAAA,GAA0B,oBAAoB,KAAK,CAAA;AACzD,EAAA,IAAI,YAAY,IAAA,EAAM;AACpB,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,MAAM,YAAqB,OAAA,CAAQ,cAAA;AACnC,EAAA,MAAM,UAAA,GAAwB,SAAA,CAAU,UAAA,IAAc,EAAC;AAGvD,EAAA,MAAM,WAAgC,UAAA,CAAW,IAAA;AAAA,IAC/C,CAAC,IAAA,KAAkB,IAAA,CAAK,SAAS,cAAA,IAAkB,IAAA,CAAK,MAAM,IAAA,KAAS;AAAA,GACzE;AAEA,EAAA,IAAI,aAAa,MAAA,EAAW;AAC1B,IAAA,MAAM,YAAqB,QAAA,CAAS,KAAA;AACpC,IAAA,IAAI,SAAA,KAAc,IAAA,IAAQ,SAAA,CAAU,IAAA,KAAS,SAAA,EAAW;AAGtD,MAAA,OAAO,KAAA;AAAA,IACT;AAIA,IAAA,MAAM,cAAc,KAAA,CAAM,KAAA,CAAM,SAAA,CAAU,KAAA,EAAO,UAAU,GAAG,CAAA;AAC9D,IAAA,MAAMC,SAAAA,GAAW,YAAY,KAAK,CAAA;AAClC,IAAA,IAAI,gBAAgBA,SAAAA,EAAU;AAC5B,MAAA,OAAO,KAAA;AAAA,IACT;AAIA,IAAA,OAAO,KAAA,CAAM,KAAA,CAAM,CAAA,EAAG,SAAA,CAAU,KAAK,IAAIA,SAAAA,GAAW,KAAA,CAAM,KAAA,CAAM,SAAA,CAAU,GAAG,CAAA;AAAA,EAC/E;AAWA,EAAA,MAAM,SAAiB,SAAA,CAAU,GAAA;AACjC,EAAA,MAAM,QAAA,GAAW,YAAY,KAAK,CAAA;AAIlC,EAAA,IAAI,QAAA;AACJ,EAAA,IAAI,KAAA,CAAM,SAAS,CAAC,CAAA,KAAM,OAAO,KAAA,CAAM,MAAA,GAAS,CAAC,CAAA,KAAM,GAAA,EAAK;AAE1D,IAAA,QAAA,GAAW,MAAA,GAAS,CAAA;AAAA,EACtB,CAAA,MAAA,IAAW,KAAA,CAAM,MAAA,GAAS,CAAC,MAAM,GAAA,EAAK;AAEpC,IAAA,QAAA,GAAW,MAAA,GAAS,CAAA;AAAA,EACtB,CAAA,MAAO;AAEL,IAAA,QAAA,GAAW,MAAA;AAAA,EACb;AAKA,EAAA,MAAM,SAAA,GAAY,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA,EAAI,QAAQ,CAAA,CAAA;AACtC,EAAA,OAAO,KAAA,CAAM,MAAM,CAAA,EAAG,QAAQ,IAAI,SAAA,GAAY,KAAA,CAAM,MAAM,QAAQ,CAAA;AACpE;;;ACrGA,SAAS,cAAc,OAAA,EAA0B;AAC/C,EAAA,OAAO,QAAQ,UAAA,CAAW,KAAK,CAAA,IAAK,OAAA,CAAQ,WAAW,KAAK,CAAA;AAC9D;AAiBO,SAAS,YAAY,MAAA,EAAyB;AACnD,EAAA,MAAM,SAAkB,EAAC;AACzB,EAAA,IAAI,GAAA,GAAM,CAAA;AAIV,EAAA,MAAM,UAAA,GAAa,MAAA,CAAO,OAAA,CAAQ,IAAA,EAAM,EAAE,CAAA;AAC1C,EAAA,MAAM,QAAA,GAAW,UAAA,CAAW,KAAA,CAAM,IAAI,CAAA;AAEtC,EAAA,IAAI,SAAA,GAAY,MAAA;AAChB,EAAA,IAAI,mBAAA,GAAsB,CAAA;AAE1B,EAAA,IAAI,QAAA,CAAS,CAAC,CAAA,EAAG,IAAA,OAAW,KAAA,EAAO;AAEjC,IAAA,IAAI,QAAA,GAAW,EAAA;AACf,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,QAAA,CAAS,QAAQ,CAAA,EAAA,EAAK;AACxC,MAAA,IAAI,QAAA,CAAS,CAAC,CAAA,CAAE,IAAA,OAAW,KAAA,EAAO;AAChC,QAAA,QAAA,GAAW,CAAA;AACX,QAAA;AAAA,MACF;AAAA,IACF;AAEA,IAAA,IAAI,aAAa,EAAA,EAAI;AAGnB,MAAA,MAAM,OAAA,GAAU,QAAA,CAAS,KAAA,CAAM,CAAA,EAAG,WAAW,CAAC,CAAA;AAK9C,MAAA,MAAM,MAAA,GAAS,CAAA,EAAG,OAAA,CAAQ,IAAA,CAAK,IAAI,CAAC;AAAA,CAAA;AAGpC,MAAA,IAAI,OAAO,UAAA,CAAW,MAAM,KAAK,UAAA,CAAW,UAAA,CAAW,MAAM,CAAA,EAAG;AAC9D,QAAA,mBAAA,GAAsB,MAAA,CAAO,MAAA;AAC7B,QAAA,MAAA,CAAO,IAAA,CAAK,EAAE,KAAA,EAAO,GAAA,EAAA,EAAO,MAAM,aAAA,EAAe,IAAA,EAAM,QAAQ,CAAA;AAC/D,QAAA,SAAA,GAAY,MAAA,CAAO,MAAM,mBAAmB,CAAA;AAAA,MAC9C;AAAA,IACF;AAAA,EACF;AAMA,EAAA,MAAM,QAAA,GAAW,SAAA,CAAU,KAAA,CAAM,IAAI,CAAA;AAOrC,EAAA,MAAM,WAAqB,EAAC;AAC5B,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,QAAA,CAAS,QAAQ,CAAA,EAAA,EAAK;AACxC,IAAA,MAAM,MAAA,GAAS,CAAA,KAAM,QAAA,CAAS,MAAA,GAAS,CAAA;AACvC,IAAA,IAAI,MAAA,IAAU,QAAA,CAAS,CAAC,CAAA,KAAM,EAAA,EAAI;AAGhC,MAAA;AAAA,IACF;AACA,IAAA,QAAA,CAAS,IAAA,CAAK,SAAS,QAAA,CAAS,CAAC,IAAI,CAAA,EAAG,QAAA,CAAS,CAAC,CAAC;AAAA,CAAI,CAAA;AAAA,EACzD;AAEA,EAAA,IAAI,OAAA,GAAU,KAAA;AACd,EAAA,IAAI,SAAA,GAAY,EAAA;AAEhB,EAAA,MAAM,aAAa,MAAM;AAKvB,IAAA,MAAA,CAAO,IAAA,CAAK,EAAE,KAAA,EAAO,GAAA,EAAA,EAAO,MAAM,OAAA,EAAS,IAAA,EAAM,WAAW,CAAA;AAC5D,IAAA,SAAA,GAAY,EAAA;AAAA,EACd,CAAA;AAEA,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,QAAA,CAAS,QAAQ,CAAA,EAAA,EAAK;AACxC,IAAA,MAAM,GAAA,GAAM,SAAS,CAAC,CAAA;AACtB,IAAA,MAAM,UAAU,GAAA,CAAI,OAAA,EAAQ,CAAE,OAAA,CAAQ,OAAO,EAAE,CAAA;AAE/C,IAAA,IAAI,CAAC,OAAA,IAAW,OAAA,KAAY,KAAA,EAAO;AAGjC,MAAA,UAAA,EAAW;AAEX,MAAA,MAAA,CAAO,IAAA,CAAK,EAAE,KAAA,EAAO,GAAA,EAAA,EAAO,MAAM,WAAA,EAAa,IAAA,EAAM,KAAK,CAAA;AAAA,IAG5D,CAAA,MAAO;AAEL,MAAA,IAAI,aAAA,CAAc,OAAO,CAAA,EAAG;AAC1B,QAAA,OAAA,GAAU,CAAC,OAAA;AAAA,MACb;AACA,MAAA,SAAA,IAAa,GAAA;AAAA,IACf;AAAA,EACF;AAIA,EAAA,IAAI,SAAA,KAAc,EAAA,IAAM,MAAA,CAAO,MAAA,CAAO,CAAA,CAAA,KAAK,EAAE,IAAA,KAAS,OAAO,CAAA,CAAE,MAAA,KAAW,CAAA,EAAG;AAC3E,IAAA,UAAA,EAAW;AAAA,EACb;AAEA,EAAA,OAAO,MAAA;AACT;AAMO,SAAS,WAAW,MAAA,EAAyB;AAClD,EAAA,OAAO,OAAO,GAAA,CAAI,CAAA,CAAA,KAAK,EAAE,IAAI,CAAA,CAAE,KAAK,EAAE,CAAA;AACxC;;;ACjJO,SAAS,kBAAkB,MAAA,EAA0B;AAC1D,EAAA,MAAM,MAAA,GAAS,YAAY,MAAM,CAAA;AACjC,EAAA,MAAM,UAAoB,EAAC;AAC3B,EAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,IAAA,IAAI,MAAM,IAAA,KAAS,OAAA,IAAW,MAAM,IAAA,CAAK,IAAA,OAAW,EAAA,EAAI;AACtD,MAAA,OAAA,CAAQ,IAAA,CAAK,MAAM,KAAK,CAAA;AAAA,IAC1B;AAAA,EACF;AACA,EAAA,OAAO,OAAA;AACT;AAOO,SAAS,aAAA,CACd,QACA,YAAA,EAC6C;AAC7C,EAAA,MAAM,OAAA,GAAU,kBAAkB,MAAM,CAAA;AACxC,EAAA,IAAI,YAAA,GAAe,CAAA,IAAK,YAAA,IAAgB,OAAA,CAAQ,QAAQ,OAAO,IAAA;AAC/D,EAAA,MAAM,UAAA,GAAa,QAAQ,YAAY,CAAA;AACvC,EAAA,MAAM,MAAA,GAAS,YAAY,MAAM,CAAA;AACjC,EAAA,MAAM,QAAQ,MAAA,CAAO,IAAA,CAAK,CAAA,CAAA,KAAK,CAAA,CAAE,UAAU,UAAU,CAAA;AACrD,EAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,EAAA,OAAO,EAAE,UAAA,EAAY,IAAA,EAAM,KAAA,CAAM,IAAA,EAAK;AACxC;AAcO,SAAS,YAAA,CACd,MAAA,EACA,YAAA,EACA,GAAA,EACA,KAAA,EACQ;AACR,EAAA,MAAM,OAAA,GAAU,kBAAkB,MAAM,CAAA;AACxC,EAAA,IAAI,YAAA,GAAe,CAAA,IAAK,YAAA,IAAgB,OAAA,CAAQ,QAAQ,OAAO,MAAA;AAC/D,EAAA,MAAM,UAAA,GAAa,QAAQ,YAAY,CAAA;AAEvC,EAAA,MAAM,MAAA,GAAS,YAAY,MAAM,CAAA;AACjC,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,GAAA,CAAI,CAAA,CAAA,KAAK;AAC9B,IAAA,IAAI,CAAA,CAAE,KAAA,KAAU,UAAA,EAAY,OAAO,CAAA;AAEnC,IAAA,IAAI,WAAW,CAAA,CAAE,IAAI,CAAA,CAAE,IAAA,KAAS,YAAY,OAAO,CAAA;AACnD,IAAA,OAAO,EAAE,GAAG,CAAA,EAAG,IAAA,EAAM,QAAQ,CAAA,CAAE,IAAA,EAAM,GAAA,EAAK,KAAK,CAAA,EAAE;AAAA,EACnD,CAAC,CAAA;AACD,EAAA,OAAO,WAAW,OAAO,CAAA;AAC3B;AAWA,SAAS,YAAY,KAAA,EAAuC;AAC1D,EAAA,OAAO,OAAO,OAAA,CAAQ,KAAK,CAAA,CACxB,GAAA,CAAI,CAAC,CAAC,CAAA,EAAG,CAAC,CAAA,KAAM,cAAc,CAAA,EAAG,CAAC,CAAC,CAAA,CACnC,KAAK,GAAG,CAAA;AACb;AAgBO,SAAS,mBAAA,CACd,MAAA,EACA,YAAA,EACA,QAAA,EACA,SAAA,EACQ;AACR,EAAA,MAAM,OAAA,GAAU,kBAAkB,MAAM,CAAA;AACxC,EAAA,IAAI,YAAA,GAAe,CAAA,IAAK,YAAA,IAAgB,OAAA,CAAQ,QAAQ,OAAO,MAAA;AAC/D,EAAA,MAAM,UAAA,GAAa,QAAQ,YAAY,CAAA;AAEvC,EAAA,MAAM,MAAA,GAAS,YAAY,MAAM,CAAA;AACjC,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,GAAA,CAAI,CAAA,CAAA,KAAK;AAC9B,IAAA,IAAI,CAAA,CAAE,KAAA,KAAU,UAAA,EAAY,OAAO,CAAA;AAGnC,IAAA,IAAI,WAAW,CAAA,CAAE,IAAI,CAAA,CAAE,IAAA,KAAS,YAAY,OAAO,CAAA;AAEnD,IAAA,MAAM,OAAA,GAAU,mBAAA,CAAoB,CAAA,CAAE,IAAI,CAAA;AAC1C,IAAA,IAAI,OAAA,KAAY,MAAM,OAAO,CAAA;AAG7B,IAAA,MAAM,QAAA,GAAW,YAAY,SAAS,CAAA;AACtC,IAAA,MAAM,WAAA,GACJ,QAAA,CAAS,MAAA,GAAS,CAAA,GAAI,CAAA,CAAA,EAAI,QAAQ,CAAA,CAAA,EAAI,QAAQ,CAAA,GAAA,CAAA,GAAQ,CAAA,CAAA,EAAI,QAAQ,CAAA,GAAA,CAAA;AAGpE,IAAA,MAAM,OAAA,GACJ,CAAA,CAAE,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,OAAA,CAAQ,KAAK,CAAA,GAAI,WAAA,GAAc,CAAA,CAAE,IAAA,CAAK,KAAA,CAAM,QAAQ,GAAG,CAAA;AACzE,IAAA,OAAO,EAAE,GAAG,CAAA,EAAG,IAAA,EAAM,OAAA,EAAQ;AAAA,EAC/B,CAAC,CAAA;AAED,EAAA,OAAO,WAAW,OAAO,CAAA;AAC3B;ACjIA,SAAS,OAAA,CAAQ,cAAsB,GAAA,EAAqB;AAC1D,EAAA,OAAO,CAAA,SAAA,EAAY,YAAY,CAAA,CAAA,EAAI,GAAG,CAAA,CAAA;AACxC;AAEA,SAAS,YAAY,IAAA,EAAyB;AAC5C,EAAA,OAAO,IAAA,KAAS,cAAc,IAAA,KAAS,MAAA;AACzC;AAEA,SAAS,SAAA,CAAU;AAAA,EACjB,YAAA;AAAA,EACA,IAAA;AAAA,EACA,KAAA;AAAA,EACA;AACF,CAAA,EAKgB;AACd,EAAA,MAAM,EAAA,GAAK,OAAA,CAAQ,YAAA,EAAc,IAAA,CAAK,GAAG,CAAA;AACzC,EAAA,MAAM,SAAA,GAAY,WAAA,CAAY,IAAA,CAAK,IAAI,CAAA;AAEvC,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,iBAAA,EACb,QAAA,EAAA;AAAA,oBAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,qBAAA,EACb,QAAA,EAAA;AAAA,sBAAAC,MAAC,OAAA,EAAA,EAAM,SAAA,EAAU,mBAAkB,OAAA,EAAS,EAAA,EACzC,eAAK,KAAA,EACR,CAAA;AAAA,MACC,IAAA,CAAK,4BACJA,KAAAA,CAAC,UAAK,SAAA,EAAU,oBAAA,EAAqB,aAAA,EAAY,MAAA,EAAO,QAAA,EAAA,GAAA,EAExD;AAAA,KAAA,EAEJ,CAAA;AAAA,IACC,4BACCA,KAAAA;AAAA,MAAC,UAAA;AAAA,MAAA;AAAA,QACC,EAAA;AAAA,QACA,SAAA,EAAU,oBAAA;AAAA,QACV,KAAA;AAAA,QACA,UAAU,IAAA,CAAK,QAAA;AAAA,QACf,iBAAe,IAAA,CAAK,QAAA;AAAA,QACpB,UAAU,CAAA,CAAA,KAAK,QAAA,CAAS,KAAK,GAAA,EAAK,CAAA,CAAE,OAAO,KAAK;AAAA;AAAA,wBAGlDA,KAAAA;AAAA,MAAC,OAAA;AAAA,MAAA;AAAA,QACC,IAAA,EAAK,MAAA;AAAA,QACL,EAAA;AAAA,QACA,SAAA,EAAU,iBAAA;AAAA,QACV,KAAA;AAAA,QACA,UAAU,IAAA,CAAK,QAAA;AAAA,QACf,iBAAe,IAAA,CAAK,QAAA;AAAA,QACpB,UAAU,CAAA,CAAA,KAAK,QAAA,CAAS,KAAK,GAAA,EAAK,CAAA,CAAE,OAAO,KAAK;AAAA;AAAA;AAClD,GAAA,EAEJ,CAAA;AAEJ;AAEO,SAAS,QAAA,CAAS,EAAE,IAAA,EAAM,KAAA,EAAO,UAAS,EAA+B;AAC9E,EAAA,MAAM,GAAA,GAAM,YAAY,IAAI,CAAA;AAE5B,EAAA,IAAI,CAAC,GAAA,EAAK;AACR,IAAA,uBACE,IAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,mBAAA,EAAoB,QAAA,EAAA;AAAA,MAAA,oBAAA;AAAA,sBACbA,KAAAA,CAAC,MAAA,EAAA,EAAM,QAAA,EAAA,IAAA,EAAK;AAAA,KAAA,EAChC,CAAA;AAAA,EAEJ;AAEA,EAAA,MAAM,iBAAiB,GAAA,CAAI,KAAA,CAAM,GAAA,CAAI,CAAA,CAAA,KAAK,EAAE,IAAI,CAAA;AAEhD,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,UAAA,EACZ,QAAA,EAAA;AAAA,IAAA,GAAA,CAAI,KAAA,CAAM,GAAA,CAAI,CAAA,IAAA,qBACbA,KAAAA;AAAA,MAAC,SAAA;AAAA,MAAA;AAAA,QAEC,YAAA,EAAc,IAAA;AAAA,QACd,IAAA;AAAA,QACA,KAAA,EAAO,KAAA,CAAM,IAAA,CAAK,GAAG,CAAA,IAAK,EAAA;AAAA,QAC1B;AAAA,OAAA;AAAA,MAJK,IAAA,CAAK;AAAA,KAMb,CAAA;AAAA,IACA,eAAe,MAAA,GAAS,CAAA,oBACvB,IAAA,CAAC,GAAA,EAAA,EAAE,WAAU,sBAAA,EAAuB,QAAA,EAAA;AAAA,MAAA,uBAAA;AAAA,MACZ,cAAA,CAAe,KAAK,IAAI,CAAA;AAAA,MAAE;AAAA,KAAA,EAClD;AAAA,GAAA,EAEJ,CAAA;AAEJ;ACrFO,SAAS,cAAA,CAAe,EAAE,IAAA,EAAM,KAAA,EAAO,UAAS,EAAqC;AAC1F,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,QAAA;AAAA,IAC5B;AAAA,GACF;AAEA,EAAA,SAAS,aAAa,IAAA,EAAoB;AACxC,IAAA,IAAI,SAAS,IAAA,EAAM;AAEnB,IAAA,MAAM,OAAA,GAAU,YAAY,IAAI,CAAA;AAChC,IAAA,IAAI,CAAC,OAAA,EAAS;AAEd,IAAA,MAAM,QAAA,GAAW,IAAI,GAAA,CAAI,OAAA,CAAQ,MAAM,GAAA,CAAI,CAAA,CAAA,KAAK,CAAA,CAAE,GAAG,CAAC,CAAA;AAGtD,IAAA,MAAM,UAAkC,EAAC;AACzC,IAAA,KAAA,MAAW,CAAC,CAAA,EAAG,CAAC,KAAK,MAAA,CAAO,OAAA,CAAQ,KAAK,CAAA,EAAG;AAC1C,MAAA,IAAI,QAAA,CAAS,GAAA,CAAI,CAAC,CAAA,EAAG;AACnB,QAAA,OAAA,CAAQ,CAAC,CAAA,GAAI,CAAA;AAAA,MACf;AAAA,IACF;AAGA,IAAA,MAAM,OAAA,GAAoB,MAAA,CAAO,OAAA,CAAQ,KAAK,CAAA,CAC3C,OAAO,CAAC,CAAC,CAAA,EAAG,CAAC,CAAA,KAAM,CAAC,SAAS,GAAA,CAAI,CAAC,CAAA,IAAK,CAAA,KAAM,EAAE,CAAA,CAC/C,IAAI,CAAC,CAAC,CAAC,CAAA,KAAM,CAAC,CAAA;AAGjB,IAAA,KAAA,MAAW,IAAA,IAAQ,QAAQ,KAAA,EAAO;AAChC,MAAA,IAAI,EAAE,IAAA,CAAK,GAAA,IAAO,OAAA,CAAA,EAAU;AAC1B,QAAA,OAAA,CAAQ,IAAA,CAAK,GAAG,CAAA,GAAI,EAAA;AAAA,MACtB;AAAA,IACF;AAEA,IAAA,QAAA,CAAS,IAAA,EAAM,EAAE,KAAA,EAAO,OAAA,EAAS,SAAS,CAAA;AAE1C,IAAA,IAAI,OAAA,CAAQ,SAAS,CAAA,EAAG;AACtB,MAAA,UAAA,CAAW,EAAE,IAAA,EAAM,IAAA,EAAM,EAAA,EAAI,IAAA,EAAM,SAAS,CAAA;AAAA,IAC9C,CAAA,MAAO;AACL,MAAA,UAAA,CAAW,IAAI,CAAA;AAAA,IACjB;AAAA,EACF;AAEA,EAAA,MAAM,YAAY,aAAA,EAAc;AAEhC,EAAA,uBACEC,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,iBAAA,EACb,QAAA,EAAA;AAAA,oBAAAD,MAAC,OAAA,EAAA,EAAM,SAAA,EAAU,iBAAA,EAAkB,OAAA,EAAQ,uBAAsB,QAAA,EAAA,UAAA,EAEjE,CAAA;AAAA,oBACAA,KAAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACC,EAAA,EAAG,qBAAA;AAAA,QACH,SAAA,EAAU,iBAAA;AAAA,QACV,YAAA,EAAW,UAAA;AAAA,QACX,KAAA,EAAO,IAAA;AAAA,QACP,QAAA,EAAU,CAAA,CAAA,KAAK,YAAA,CAAa,CAAA,CAAE,OAAO,KAAK,CAAA;AAAA,QAEzC,QAAA,EAAA,SAAA,CAAU,GAAA,CAAI,CAAA,GAAA,qBACbA,MAAC,QAAA,EAAA,EAAsB,KAAA,EAAO,GAAA,CAAI,IAAA,EAC/B,cAAI,KAAA,IAAS,GAAA,CAAI,IAAA,EAAA,EADP,GAAA,CAAI,IAEjB,CACD;AAAA;AAAA,KACH;AAAA,IACC,OAAA,oBACCC,IAAAA,CAAC,QAAA,EAAA,EAAO,WAAU,0BAAA,EAChB,QAAA,EAAA;AAAA,sBAAAA,KAAC,MAAA,EAAA,EAAK,QAAA,EAAA;AAAA,QAAA,WAAA;AAAA,QACM,OAAA,CAAQ,IAAA;AAAA,QAAK,UAAA;AAAA,QAAS,OAAA,CAAQ,EAAA;AAAA,QAAG,aAAA;AAAA,QAAY,OAAA,CAAQ,OAAA,CAAQ,IAAA,CAAK,IAAI,CAAA;AAAA,QAAE;AAAA,OAAA,EACpF,CAAA;AAAA,sBACAD,KAAAA;AAAA,QAAC,QAAA;AAAA,QAAA;AAAA,UACC,IAAA,EAAK,QAAA;AAAA,UACL,SAAA,EAAU,0BAAA;AAAA,UACV,YAAA,EAAW,SAAA;AAAA,UACX,OAAA,EAAS,MAAM,UAAA,CAAW,IAAI,CAAA;AAAA,UAC/B,QAAA,EAAA;AAAA;AAAA;AAED,KAAA,EACF;AAAA,GAAA,EAEJ,CAAA;AAEJ;;;AC/EA,IAAM,eAAA,GAAkB,oCAAA;AAExB,SAAS,aAAa,IAAA,EAAuB;AAC3C,EAAA,QAAQ,KAAK,IAAA;AAAM,IACjB,KAAK,OAAA;AACH,MAAA,OAAO,eAAA;AAAA,IACT;AAEE,MAAA,OAAO,CAAA,EAAG,KAAK,KAAK,CAAA,MAAA,CAAA;AAAA;AAE1B;AAGA,SAAS,eAAA,CAAgB,KAAA,EAAkB,MAAA,GAAS,IAAA,EAAgB;AAClE,EAAA,MAAM,QAAkB,EAAC;AACzB,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,IAAI,IAAA,CAAK,QAAQ,UAAA,EAAY;AAC7B,IAAA,IAAI,KAAK,KAAA,EAAO;AACd,MAAA,IAAI,IAAA,CAAK,SAAS,OAAA,EAAS;AACzB,QAAA,KAAA,CAAM,IAAA;AAAA,UACJ,CAAA,EAAG,MAAM,CAAA,EAAG,IAAA,CAAK,GAAG,OAAO,eAAe,CAAA,IAAA,EAAO,eAAe,CAAA,IAAA,EAAO,eAAe,CAAA,GAAA;AAAA,SACxF;AAAA,MACF,CAAA,MAAO;AACL,QAAA,KAAA,CAAM,KAAK,CAAA,EAAG,MAAM,CAAA,EAAG,IAAA,CAAK,GAAG,CAAA,+DAAA,CAA4D,CAAA;AAAA,MAC7F;AACA,MAAA;AAAA,IACF;AACA,IAAA,KAAA,CAAM,IAAA,CAAK,CAAA,EAAG,MAAM,CAAA,EAAG,IAAA,CAAK,GAAG,CAAA,EAAA,EAAK,YAAA,CAAa,IAAI,CAAC,CAAA,CAAA,CAAG,CAAA;AAAA,EAC3D;AACA,EAAA,OAAO,KAAA;AACT;AAGA,SAAS,eAAA,CAAgB,cAAsB,IAAA,EAA4B;AACzE,EAAA,MAAM,QAAkB,EAAC;AACzB,EAAA,IAAI,YAAA,GAA8B,IAAA;AAClC,EAAA,KAAA,MAAW,IAAA,IAAQ,KAAK,KAAA,EAAO;AAC7B,IAAA,IAAI,IAAA,CAAK,QAAQ,UAAA,EAAY;AAC3B,MAAA,YAAA,GAAe,CAAA,EAAG,KAAK,KAAK,CAAA,MAAA,CAAA;AAC5B,MAAA;AAAA,IACF;AACA,IAAA,IAAI,KAAK,KAAA,EAAO;AACd,MAAA,KAAA,CAAM,IAAA,CAAK,CAAA,EAAG,IAAA,CAAK,GAAG,CAAA,WAAA,CAAQ,CAAA;AAC9B,MAAA;AAAA,IACF;AACA,IAAA,MAAM,QAAQ,IAAA,CAAK,IAAA,KAAS,UAAU,eAAA,GAAkB,CAAA,EAAG,KAAK,KAAK,CAAA,MAAA,CAAA;AACrE,IAAA,KAAA,CAAM,KAAK,CAAA,EAAG,IAAA,CAAK,GAAG,CAAA,EAAA,EAAK,KAAK,CAAA,CAAA,CAAG,CAAA;AAAA,EACrC;AACA,EAAA,MAAM,GAAA,GAAM,CAAA,EAAG,YAAY,CAAA,CAAA,EAAI,KAAK,IAAI,CAAA,CAAA;AACxC,EAAA,MAAM,OAAA,GAAU,MAAM,MAAA,GAAS,CAAA,CAAA,EAAI,MAAM,IAAA,CAAK,GAAG,CAAC,CAAA,CAAA,GAAK,EAAA;AACvD,EAAA,IAAI,iBAAiB,IAAA,EAAM;AACzB,IAAA,OAAO,MAAM,GAAG,CAAA,EAAG,OAAO,CAAA,CAAA,EAAI,YAAY,KAAK,GAAG,CAAA,CAAA,CAAA;AAAA,EACpD;AACA,EAAA,OAAO,CAAA,GAAA,EAAM,GAAG,CAAA,EAAG,OAAO,CAAA,GAAA,CAAA;AAC5B;AAOO,SAAS,aAAa,CAAA,EAAwB;AACnD,EAAA,MAAM,QAAA,GAAW,CAAA,CAAE,KAAA,CAAM,MAAA,GAAS,CAAA;AAClC,EAAA,MAAM,SAAA,GAAY,eAAA,CAAgB,CAAA,CAAE,KAAK,CAAA;AAEzC,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,IAAI,UAAU,MAAA,KAAW,CAAA,EAAG,OAAO,CAAA,CAAA,EAAI,EAAE,IAAI,CAAA,GAAA,CAAA;AAC7C,IAAA,OAAO,CAAC,CAAA,CAAA,EAAI,CAAA,CAAE,IAAI,CAAA,CAAA,EAAI,GAAG,SAAA,EAAW,IAAI,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA;AAAA,EACrD;AAEA,EAAA,MAAM,OAAO,SAAA,CAAU,MAAA,GAAS,CAAC,CAAA,CAAA,EAAI,EAAE,IAAI,CAAA,CAAA,EAAI,GAAG,SAAA,EAAW,GAAG,CAAA,GAAI,CAAC,CAAA,CAAA,EAAI,CAAA,CAAE,IAAI,CAAA,CAAA,CAAG,CAAA;AAClF,EAAA,MAAM,aAAuB,EAAC;AAC9B,EAAA,KAAA,MAAW,IAAA,IAAQ,EAAE,KAAA,EAAO;AAC1B,IAAA,MAAM,KAAA,GAAQ,KAAK,GAAA,CAAI,IAAA,CAAK,IAAI,IAAA,CAAK,GAAA,EAAK,CAAC,CAAA,EAAG,CAAC,CAAA;AAC/C,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,EAAO,CAAA,EAAA,EAAK;AAC9B,MAAA,UAAA,CAAW,IAAA,CAAK,eAAA,CAAgB,CAAA,CAAE,IAAA,EAAM,IAAI,CAAC,CAAA;AAAA,IAC/C;AAAA,EACF;AACA,EAAA,OAAO,CAAC,GAAG,IAAA,EAAM,GAAG,UAAA,EAAY,CAAA,EAAA,EAAK,CAAA,CAAE,IAAI,CAAA,CAAA,CAAG,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA;AAC3D;AAOO,SAAS,gBAAgB,CAAA,EAAwB;AACtD,EAAA,OAAO;AAAA;;AAAA,EAAY,YAAA,CAAa,CAAC,CAAC;AAAA,CAAA;AACpC;AAGO,IAAM,mBAA2C,MAAA,CAAO,WAAA;AAAA,EAC7D,aAAA,EAAc,CAAE,GAAA,CAAI,CAAA,CAAA,KAAK,CAAC,EAAE,IAAA,EAAM,eAAA,CAAgB,CAAC,CAAC,CAAC;AACvD;ACzFO,SAAS,QAAA,CAAS,EAAE,MAAA,EAAQ,QAAA,EAAU,QAAQ,IAAA,EAAM,KAAA,EAAO,WAAU,EAAkB;AAC5F,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIE,SAA2B,OAAO,CAAA;AAC5D,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAIA,SAAuB,QAAQ,CAAA;AAE/D,EAAA,MAAM,CAAC,YAAA,EAAc,eAAe,CAAA,GAAIA,SAAS,CAAC,CAAA;AAClD,EAAA,MAAM,WAAA,GAAc,OAA4B,IAAI,CAAA;AACpD,EAAA,MAAM,YAAY,OAAA,CAAQ,MAAM,aAAA,EAAc,EAAG,EAAE,CAAA;AAEnD,EAAA,MAAM,YAAA,GAAe,WAAA;AAAA,IACnB,CAAC,CAAA,KAAwC,QAAA,CAAS,CAAA,CAAE,OAAO,KAAK,CAAA;AAAA,IAChE,CAAC,QAAQ;AAAA,GACX;AACA,EAAA,MAAM,aAAA,GAAgB,WAAA;AAAA,IACpB,CAAC,CAAA,KAA0C;AACzC,MAAA,IAAA,CAAK,EAAE,OAAA,IAAW,CAAA,CAAE,OAAA,KAAY,CAAA,CAAE,QAAQ,GAAA,EAAK;AAC7C,QAAA,CAAA,CAAE,cAAA,EAAe;AACjB,QAAA,MAAA,GAAS,MAAM,CAAA;AAAA,MACjB;AAAA,IACF,CAAA;AAAA,IACA,CAAC,QAAQ,MAAM;AAAA,GACjB;AACA,EAAA,MAAM,MAAA,GAAS,WAAA;AAAA,IACb,CAAC,IAAA,KAAiB;AAChB,MAAA,MAAM,OAAA,GAAU,iBAAiB,IAAI,CAAA;AACrC,MAAA,IAAI,CAAC,OAAA,EAAS;AACd,MAAA,QAAA,CAAS,CAAA,EAAG,MAAA,CAAO,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAC;AAAA,EAAK,OAAO,CAAA,CAAE,CAAA;AACpD,MAAA,qBAAA,CAAsB,MAAM;AAC1B,QAAA,MAAM,KAAK,WAAA,CAAY,OAAA;AACvB,QAAA,IAAI,CAAC,EAAA,EAAI;AACT,QAAA,EAAA,CAAG,KAAA,EAAM;AACT,QAAA,EAAA,CAAG,cAAA,GAAiB,EAAA,CAAG,YAAA,GAAe,EAAA,CAAG,KAAA,CAAM,MAAA;AAC/C,QAAA,EAAA,CAAG,YAAY,EAAA,CAAG,YAAA;AAAA,MACpB,CAAC,CAAA;AAAA,IACH,CAAA;AAAA,IACA,CAAC,QAAQ,QAAQ;AAAA,GACnB;AAGA,EAAA,MAAM,UAAA,GAAa,QAAQ,MAAM,iBAAA,CAAkB,MAAM,CAAA,CAAE,MAAA,EAAQ,CAAC,MAAM,CAAC,CAAA;AAC3E,EAAA,MAAM,UAAA,GAAa,KAAK,GAAA,CAAI,YAAA,EAAc,KAAK,GAAA,CAAI,CAAA,EAAG,UAAA,GAAa,CAAC,CAAC,CAAA;AAErE,EAAA,MAAM,YAAA,GAAe,OAAA,CAAQ,MAAM,aAAA,CAAc,MAAA,EAAQ,UAAU,CAAA,EAAG,CAAC,MAAA,EAAQ,UAAU,CAAC,CAAA;AAC1F,EAAA,MAAM,WAAA,GAAc,OAAA;AAAA,IAClB,MAAO,YAAA,GAAe,UAAA,CAAW,YAAA,CAAa,IAAI,CAAA,GAAI,IAAA;AAAA,IACtD,CAAC,YAAY;AAAA,GACf;AAEA,EAAA,MAAM,gBAAA,GAAmB,WAAA;AAAA,IACvB,CAAC,KAAa,KAAA,KAAkB;AAC9B,MAAA,QAAA,CAAS,YAAA,CAAa,MAAA,EAAQ,UAAA,EAAY,GAAA,EAAK,KAAK,CAAC,CAAA;AAAA,IACvD,CAAA;AAAA,IACA,CAAC,MAAA,EAAQ,UAAA,EAAY,QAAQ;AAAA,GAC/B;AAEA,EAAA,MAAM,oBAAA,GAAuB,WAAA;AAAA,IAC3B,CAAC,MAAc,MAAA,KAAiE;AAC9E,MAAA,QAAA,CAAS,oBAAoB,MAAA,EAAQ,UAAA,EAAY,IAAA,EAAM,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,IACtE,CAAA;AAAA,IACA,CAAC,MAAA,EAAQ,UAAA,EAAY,QAAQ;AAAA,GAC/B;AAEA,EAAA,uBACED,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,YAAA,EACb,QAAA,EAAA;AAAA,oBAAAA,IAAAA,CAAC,SAAA,EAAA,EAAQ,SAAA,EAAU,qBAAA,EAAsB,cAAY,KAAA,EAClD,QAAA,EAAA;AAAA,MAAA,KAAA,mBACCA,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,mBAAA,EAAoB,MAAK,OAAA,EACtC,QAAA,EAAA;AAAA,wBAAAD,KAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,uBAAA,EAAwB,QAAA,EAAA,WAAA,EAAS,CAAA;AAAA,wBACjDA,KAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,yBAAyB,QAAA,EAAA,KAAA,EAAM;AAAA,OAAA,EACjD,CAAA,GACE,IAAA;AAAA,MACH,IAAA,mBACCA,KAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,2BAAA,EACb,QAAA,kBAAAA,KAAAA,CAAC,YAAA,EAAA,EAAa,IAAA,EAAY,MAAA,EAAQ,MAAM;AAAA,MAAC,CAAA,EAAG,CAAA,EAAA,EADE,KAEhD,CAAA,mBAEAA,KAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,2BAAA,EACZ,QAAA,EAAA,KAAA,GAAQ,wCAAA,GAA2C,yBAAA,EACtD;AAAA,KAAA,EAEJ,CAAA;AAAA,oBACAC,IAAAA,CAAC,SAAA,EAAA,EAAQ,SAAA,EAAU,oBAAA,EACjB,QAAA,EAAA;AAAA,sBAAAA,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,qBAAA,EACb,QAAA,EAAA;AAAA,wBAAAD,KAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,mBAAA,EAAoB,QAAA,EAAA,sBAAA,EAAiB,CAAA;AAAA,wBACrDC,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,6BAAA,EAEb,QAAA,EAAA;AAAA,0BAAAA,KAAC,KAAA,EAAA,EAAI,SAAA,EAAU,yBAAwB,IAAA,EAAK,OAAA,EAAQ,cAAW,WAAA,EAC7D,QAAA,EAAA;AAAA,4BAAAD,KAAAA;AAAA,cAAC,QAAA;AAAA,cAAA;AAAA,gBACC,IAAA,EAAK,QAAA;AAAA,gBACL,SAAA,EAAU,2BAAA;AAAA,gBACV,gBAAc,QAAA,KAAa,QAAA;AAAA,gBAC3B,OAAA,EAAS,MAAM,WAAA,CAAY,QAAQ,CAAA;AAAA,gBACpC,QAAA,EAAA;AAAA;AAAA,aAED;AAAA,4BACAA,KAAAA;AAAA,cAAC,QAAA;AAAA,cAAA;AAAA,gBACC,IAAA,EAAK,QAAA;AAAA,gBACL,SAAA,EAAU,2BAAA;AAAA,gBACV,gBAAc,QAAA,KAAa,MAAA;AAAA,gBAC3B,OAAA,EAAS,MAAM,WAAA,CAAY,MAAM,CAAA;AAAA,gBAClC,QAAA,EAAA;AAAA;AAAA;AAED,WAAA,EACF,CAAA;AAAA,UACC,MAAA,mBACCA,KAAAA,CAAC,QAAA,EAAA,EAAO,MAAK,QAAA,EAAS,SAAA,EAAU,kBAAA,EAAmB,OAAA,EAAS,MAAM,MAAA,CAAO,MAAM,CAAA,EAAG,kBAElF,CAAA,GACE,IAAA;AAAA,0BACJA,KAAAA;AAAA,YAAC,QAAA;AAAA,YAAA;AAAA,cACC,IAAA,EAAK,QAAA;AAAA,cACL,SAAA,EAAU,0BAAA;AAAA,cACV,SAAS,MAAM,QAAA,CAAS,OAAM,CAAA,KAAM,OAAA,GAAU,SAAS,OAAQ,CAAA;AAAA,cAC/D,gBAAc,KAAA,KAAU,MAAA;AAAA,cAEvB,QAAA,EAAA,KAAA,KAAU,UAAU,oBAAA,GAAa;AAAA;AAAA,WACpC;AAAA,0BACAA,MAAC,QAAA,EAAA,EAAO,IAAA,EAAK,UAAS,SAAA,EAAU,qBAAA,EAAsB,OAAA,EAAS,SAAA,EAAW,QAAA,EAAA,SAAA,EAE1E;AAAA,SAAA,EACF;AAAA,OAAA,EACF,CAAA;AAAA,MAEC,QAAA,KAAa,QAAA,mBACZC,IAAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,wBAAAD,KAAAA;AAAA,UAAC,UAAA;AAAA,UAAA;AAAA,YACC,GAAA,EAAK,WAAA;AAAA,YACL,SAAA,EAAU,sBAAA;AAAA,YACV,KAAA,EAAO,MAAA;AAAA,YACP,QAAA,EAAU,YAAA;AAAA,YACV,SAAA,EAAW,aAAA;AAAA,YACX,UAAA,EAAY,KAAA;AAAA,YACZ,YAAA,EAAW;AAAA;AAAA,SACb;AAAA,wBACAC,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,qBAAA,EACb,QAAA,EAAA;AAAA,0BAAAD,KAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,2BAAA,EAA4B,QAAA,EAAA,gCAAA,EAA2B,CAAA;AAAA,0BACtEA,MAAC,KAAA,EAAA,EAAI,SAAA,EAAU,4BACZ,QAAA,EAAA,SAAA,CAAU,GAAA,CAAI,uBACbC,IAAAA;AAAA,YAAC,QAAA;AAAA,YAAA;AAAA,cAEC,IAAA,EAAK,QAAA;AAAA,cACL,SAAA,EAAU,kBAAA;AAAA,cACV,OAAA,EAAS,MAAM,MAAA,CAAO,CAAA,CAAE,IAAI,CAAA;AAAA,cAC5B,KAAA,EAAO,CAAA,OAAA,EAAU,CAAA,CAAE,IAAI,CAAA,CAAA;AAAA,cAEvB,QAAA,EAAA;AAAA,gCAAAD,KAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,uBAAA,EAAyB,YAAE,IAAA,EAAK,CAAA;AAAA,gCAChDA,KAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,uBAAA,EAAyB,YAAE,KAAA,EAAM;AAAA;AAAA,aAAA;AAAA,YAP5C,CAAA,CAAE;AAAA,WASV,CAAA,EACH;AAAA,SAAA,EACF;AAAA,OAAA,EACF,CAAA,mBAEAC,IAAAA,CAAC,KAAA,EAAA,EAAI,WAAU,wBAAA,EAEb,QAAA,EAAA;AAAA,wBAAAA,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,2BAAA,EACb,QAAA,EAAA;AAAA,0BAAAD,KAAAA;AAAA,YAAC,QAAA;AAAA,YAAA;AAAA,cACC,IAAA,EAAK,QAAA;AAAA,cACL,SAAA,EAAU,yBAAA;AAAA,cACV,YAAA,EAAW,gBAAA;AAAA,cACX,UAAU,UAAA,IAAc,CAAA;AAAA,cACxB,OAAA,EAAS,MAAM,eAAA,CAAgB,CAAA,CAAA,KAAK,KAAK,GAAA,CAAI,CAAA,EAAG,CAAA,GAAI,CAAC,CAAC,CAAA;AAAA,cACvD,QAAA,EAAA;AAAA;AAAA,WAED;AAAA,0BACAC,IAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,2BAAA,EAA4B,QAAA,EAAA;AAAA,YAAA,QAAA;AAAA,YACnC,UAAA,GAAa,CAAA;AAAA,YAAE,KAAA;AAAA,YAAI;AAAA,WAAA,EAC5B,CAAA;AAAA,0BACAD,KAAAA;AAAA,YAAC,QAAA;AAAA,YAAA;AAAA,cACC,IAAA,EAAK,QAAA;AAAA,cACL,SAAA,EAAU,yBAAA;AAAA,cACV,YAAA,EAAW,YAAA;AAAA,cACX,QAAA,EAAU,cAAc,UAAA,GAAa,CAAA;AAAA,cACrC,OAAA,EAAS,MAAM,eAAA,CAAgB,CAAA,CAAA,KAAK,IAAA,CAAK,IAAI,UAAA,GAAa,CAAA,EAAG,CAAA,GAAI,CAAC,CAAC,CAAA;AAAA,cACpE,QAAA,EAAA;AAAA;AAAA;AAED,SAAA,EACF,CAAA;AAAA,QAGC,gBAAgB,IAAA,mBACfA,KAAAA,CAAC,GAAA,EAAA,EAAE,WAAU,wBAAA,EAAyB,QAAA,EAAA,kBAAA,EAAgB,CAAA,GACpD,WAAA,CAAY,SAAS,QAAA,mBACvBC,IAAAA,CAAC,KAAA,EAAA,EAAI,WAAU,yBAAA,EACb,QAAA,EAAA;AAAA,0BAAAD,KAAAA,CAAC,OAAE,QAAA,EAAA,wCAAA,EAAsC,CAAA;AAAA,0BACzCA,KAAAA;AAAA,YAAC,QAAA;AAAA,YAAA;AAAA,cACC,IAAA,EAAK,QAAA;AAAA,cACL,SAAA,EAAU,2BAAA;AAAA,cACV,OAAA,EAAS,MAAM,WAAA,CAAY,QAAQ,CAAA;AAAA,cACpC,QAAA,EAAA;AAAA;AAAA;AAED,SAAA,EACF,CAAA,mBAEAC,IAAAA,CAAC,KAAA,EAAA,EAAI,WAAU,yBAAA,EACb,QAAA,EAAA;AAAA,0BAAAD,KAAAA;AAAA,YAAC,cAAA;AAAA,YAAA;AAAA,cACC,MAAM,WAAA,CAAY,IAAA;AAAA,cAClB,OAAO,WAAA,CAAY,KAAA;AAAA,cACnB,QAAA,EAAU;AAAA;AAAA,WACZ;AAAA,0BACAA,KAAAA;AAAA,YAAC,QAAA;AAAA,YAAA;AAAA,cACC,MAAM,WAAA,CAAY,IAAA;AAAA,cAClB,OAAO,WAAA,CAAY,KAAA;AAAA,cACnB,QAAA,EAAU;AAAA;AAAA;AACZ,SAAA,EACF;AAAA,OAAA,EAEJ;AAAA,KAAA,EAEJ;AAAA,GAAA,EACF,CAAA;AAEJ;ACnLO,SAAS,WAAA,CAAY;AAAA,EAC1B,MAAA,EAAQ,UAAA;AAAA,EACR,QAAA;AAAA,EACA,MAAA;AAAA,EACA,WAAA,GAAc,SAAA;AAAA,EACd,UAAA,GAAa;AACf,CAAA,EAAqB;AACnB,EAAA,MAAM,WAAW,QAAA,KAAa,MAAA;AAG9B,EAAA,MAAM,CAAC,cAAA,EAAgB,iBAAiB,CAAA,GAAIE,SAAS,UAAU,CAAA;AAC/D,EAAA,MAAM,MAAA,GAAS,WAAW,UAAA,GAAa,cAAA;AAEvC,EAAA,MAAM,SAAA,GAAY,CAAC,IAAA,KAAiB;AAClC,IAAA,IAAI,QAAA,WAAmB,IAAI,CAAA;AAAA,2BACJ,IAAI,CAAA;AAAA,EAC7B,CAAA;AAEA,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,IAAIA,QAAAA,CAA0B,QAAA,GAAW,cAAc,SAAS,CAAA;AAEpF,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIA,SAAkB,IAAI,CAAA;AACpD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,SAAwB,IAAI,CAAA;AAGtD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,SAAA,GAAY,KAAA;AAChB,IAAA,MAAM,CAAA,GAAI,WAAW,YAAY;AAC/B,MAAA,MAAM,CAAA,GAAI,MAAM,WAAA,CAAY,MAAM,CAAA;AAClC,MAAA,IAAI,SAAA,EAAW;AACf,MAAA,IAAI,EAAE,EAAA,EAAI;AACR,QAAA,UAAA,CAAW,EAAE,OAAA,EAAS,CAAA,CAAE,SAAS,IAAA,EAAM,CAAA,CAAE,MAAM,CAAA;AAC/C,QAAA,QAAA,CAAS,IAAI,CAAA;AAAA,MACf,CAAA,MAAO;AACL,QAAA,QAAA,CAAS,EAAE,KAAK,CAAA;AAAA,MAClB;AAAA,IACF,GAAG,UAAU,CAAA;AACb,IAAA,OAAO,MAAM;AACX,MAAA,SAAA,GAAY,IAAA;AACZ,MAAA,YAAA,CAAa,CAAC,CAAA;AAAA,IAChB,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,MAAA,EAAQ,UAAU,CAAC,CAAA;AAEvB,EAAA,MAAM,IAAA,GAA6BC,OAAAA;AAAA,IACjC,MAAO,OAAA,GAAU,EAAE,IAAA,EAAM,OAAA,EAAS,IAAA,EAAM,OAAA,CAAQ,IAAA,EAAM,OAAA,EAAS,OAAA,CAAQ,OAAA,EAAQ,GAAI,IAAA;AAAA,IACnF,CAAC,OAAO;AAAA,GACV;AAGA,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,QAAA,EAAU;AACf,IAAA,MAAM,KAAA,GAAQ,CAAC,CAAA,KAAqB;AAClC,MAAA,MAAM,MAAA,GAAU,CAAA,CAAE,MAAA,EAAwB,OAAA,KAAY,UAAA;AACtD,MAAA,IAAI,CAAA,CAAE,QAAQ,GAAA,IAAO,CAAC,UAAU,IAAA,KAAS,SAAA,UAAmB,MAAM,CAAA;AAClE,MAAA,IAAI,EAAE,GAAA,KAAQ,QAAA,IAAY,IAAA,KAAS,MAAA,UAAgB,SAAS,CAAA;AAAA,IAC9D,CAAA;AACA,IAAA,MAAA,CAAO,gBAAA,CAAiB,WAAW,KAAK,CAAA;AACxC,IAAA,OAAO,MAAM,MAAA,CAAO,mBAAA,CAAoB,SAAA,EAAW,KAAK,CAAA;AAAA,EAC1D,CAAA,EAAG,CAAC,QAAA,EAAU,IAAI,CAAC,CAAA;AAEnB,EAAA,IAAI,QAAA,IAAY,SAAS,MAAA,EAAQ;AAC/B,IAAA,uBACEH,KAAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACC,MAAA;AAAA,QACA,QAAA,EAAU,SAAA;AAAA,QACV,MAAA;AAAA,QACA,IAAA;AAAA,QACA,KAAA;AAAA,QACA,SAAA,EAAW,MAAM,OAAA,CAAQ,SAAS;AAAA;AAAA,KACpC;AAAA,EAEJ;AAEA,EAAA,uBACEC,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,aAAA,EACZ,QAAA,EAAA;AAAA,IAAA,QAAA,oBACCD,KAAAA,CAAC,QAAA,EAAA,EAAO,IAAA,EAAK,QAAA,EAAS,SAAA,EAAU,mBAAA,EAAoB,OAAA,EAAS,MAAM,OAAA,CAAQ,MAAM,CAAA,EAAG,QAAA,EAAA,MAAA,EAEpF,CAAA;AAAA,IAED,uBACCA,KAAAA,CAAC,YAAA,EAAA,EAAa,IAAA,EAAY,QAAQ,MAAM;AAAA,IAAC,CAAA,EAAG,oBAE5CA,KAAAA,CAAC,SAAI,SAAA,EAAU,2BAAA,EACZ,QAAA,EAAA,KAAA,GAAQ,wCAAA,GAA2C,yBAAA,EACtD;AAAA,GAAA,EAEJ,CAAA;AAEJ;AChIO,SAAS,UAAA,CAAW,EAAE,MAAA,EAAQ,QAAA,EAAU,YAAW,EAAoB;AAC5E,EAAA,uBACEA,KAAAA;AAAA,IAAC,WAAA;AAAA,IAAA;AAAA,MACC,MAAA;AAAA,MACA,QAAA,EAAU,aAAa,MAAM;AAAA,MAAC,CAAA,CAAA;AAAA,MAC9B,WAAA,EAAY,MAAA;AAAA,MACZ;AAAA;AAAA,GACF;AAEJ","file":"index.js","sourcesContent":["import { evaluate } from '@mdx-js/mdx'\nimport yaml from 'js-yaml'\nimport type { ComponentType } from 'react'\nimport * as runtime from 'react/jsx-runtime'\nimport remarkGfm from 'remark-gfm'\nimport { slideMdxComponents } from '../content/mdx-components'\nimport type { SlideDeckMeta } from '../types'\n\n/**\n * Browser-safe YAML frontmatter split. We deliberately avoid the common\n * \"gray matter\" style parser because it depends on Node's `Buffer`, which\n * doesn't exist in the browser — a browser library must not force consumers to\n * polyfill Node globals.\n *\n * If `source` begins with a `---` fence line, the block up to the next `---`\n * fence is parsed as YAML into `meta` and the remainder is returned as `body`.\n * Otherwise `meta` is empty and the whole source is the `body`.\n */\nexport function parseFrontmatter(source: string): { meta: Record<string, unknown>; body: string } {\n // Frontmatter must start at the very top of the file (allowing a UTF-8 BOM).\n const normalized = source.replace(/^/, '')\n const lines = normalized.split('\\n')\n if (lines[0]?.trim() !== '---') {\n return { meta: {}, body: source }\n }\n\n // Find the closing fence line.\n let end = -1\n for (let i = 1; i < lines.length; i++) {\n if (lines[i].trim() === '---') {\n end = i\n break\n }\n }\n if (end === -1) {\n // No closing fence — treat the whole thing as body, no frontmatter.\n return { meta: {}, body: source }\n }\n\n const yamlBlock = lines.slice(1, end).join('\\n')\n const body = lines.slice(end + 1).join('\\n')\n const loaded = yaml.load(yamlBlock)\n const meta = loaded && typeof loaded === 'object' ? (loaded as Record<string, unknown>) : {}\n return { meta, body }\n}\n\n/**\n * Result of a runtime deck compile.\n *\n * On success: the parsed frontmatter `meta` plus a `Content` component ready to\n * hand to `SlidesPlayer` as `deck.content`. On failure: a human-readable\n * `error` string (compile + frontmatter parsing both throw on bad input).\n */\nexport type CompileDeckResult =\n | { ok: true; meta: SlideDeckMeta; Content: ComponentType }\n | { ok: false; error: string }\n\n/**\n * Matches bare ES `import` statements at the start of a line. A deck's MDX body\n * carries lines like `import { CoverSlide } from \"@atom63/slides\"`. The runtime\n * `evaluate` below has no module resolver, so these must be stripped — the\n * components are injected via `useMDXComponents` instead (see compileDeck).\n *\n * Covers single- and multi-line import specifier blocks:\n * import Foo from \"x\"\n * import { A, B } from \"x\"\n * import {\n * A,\n * B,\n * } from \"x\"\n */\nconst IMPORT_RE = /^[ \\t]*import\\b[\\s\\S]*?(?:from\\s*['\"][^'\"]*['\"]|['\"][^'\"]*['\"])[ \\t]*;?[ \\t]*$/gm\n\n/** Remove bare `import ... from \"...\"` lines so runtime evaluate doesn't choke. */\nexport function stripImports(body: string): string {\n return body.replace(IMPORT_RE, '')\n}\n\nconst DEFAULT_META: SlideDeckMeta = {\n title: 'Untitled deck',\n date: new Date().toISOString().slice(0, 10),\n}\n\n/**\n * Compile a deck's raw MDX source into a renderable Content component using a\n * runtime MDX pipeline (no bundler step).\n *\n * Pipeline:\n * 1. `parseFrontmatter` splits the YAML frontmatter (the deck `meta`) from the\n * body using a browser-safe parser (no Node `Buffer`).\n * 2. `stripImports` removes bare `import ... from \"@atom63/slides\"` lines that\n * the runtime evaluator cannot resolve.\n * 3. `@mdx-js/mdx`'s `evaluate` compiles + runs the body. The slide components\n * (templates + primitives + markdown mappings) are provided through MDX\n * context via `useMDXComponents: () => slideMdxComponents`, so bare JSX such\n * as `<CoverSlide/>` resolves with no import. `---` thematic breaks are\n * preserved as the engine's slide separators.\n *\n * Async and can throw on malformed MDX/frontmatter — callers should debounce\n * and keep the last good render on failure (DeckEditor does this).\n */\nexport async function compileDeck(source: string): Promise<CompileDeckResult> {\n try {\n const { meta: data, body: content } = parseFrontmatter(source)\n const meta: SlideDeckMeta = { ...DEFAULT_META, ...(data as Partial<SlideDeckMeta>) }\n const body = stripImports(content)\n\n const { default: Content } = await evaluate(body, {\n ...runtime,\n useMDXComponents: () => slideMdxComponents,\n remarkPlugins: [remarkGfm],\n })\n\n return { ok: true, meta, Content: Content as ComponentType }\n } catch (err) {\n const error = err instanceof Error ? err.message : String(err)\n return { ok: false, error }\n }\n}\n","import { Parser } from 'acorn'\nimport jsx from 'acorn-jsx'\nimport { templateNames } from '../content/template-registry'\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport type ParsedSlide =\n | { kind: 'template'; name: string; props: Record<string, string> }\n | { kind: 'opaque'; reason: string }\n\n// ── Internal acorn-jsx parser ──────────────────────────────────────────────\n\nconst JsxParser = Parser.extend(jsx())\n\n// ── Helpers ────────────────────────────────────────────────────────────────\n\n// biome-ignore lint/suspicious/noExplicitAny: acorn AST nodes are untyped\ntype AnyNode = any\n\n/**\n * Matches a leading ES `import` statement (with or without a trailing\n * semicolon). MDX decks routinely write `import { X } from '...'` with NO\n * semicolon, and acorn does NOT insert one before the following JSX — it\n * throws. We neutralize imports before parsing by replacing each import's\n * characters with EQUAL-LENGTH whitespace (newlines preserved), so the JSX\n * element's source offsets stay identical to the original block — which\n * `serialize-slot` relies on for span-based, byte-preserving edits.\n */\nconst IMPORT_RE =\n /^[ \\t]*import\\b[\\s\\S]*?(?:from\\s*['\"][^'\"]*['\"]|['\"][^'\"]*['\"])[ \\t]*;?[ \\t]*$/gm\n\nfunction blankImports(block: string): string {\n return block.replace(IMPORT_RE, m => m.replace(/[^\\n]/g, ' '))\n}\n\n/**\n * Given a parsed block string, return the single JSXElement acorn node if\n * the block contains exactly one JSX expression statement (with any number of\n * leading ImportDeclarations and nothing else). Returns null otherwise.\n *\n * Exported so Task 4 can reuse it (offset-based element finding).\n */\nexport function findTemplateElement(block: string): AnyNode | null {\n let ast: AnyNode\n try {\n // Blank imports first (offset-preserving) so a semicolon-less import before\n // the JSX doesn't make acorn throw. Offsets remain valid for `block`.\n ast = JsxParser.parse(blankImports(block), { ecmaVersion: 'latest', sourceType: 'module' })\n } catch {\n return null\n }\n\n const body: AnyNode[] = ast.body\n\n let jsxElement: AnyNode | null = null\n\n for (const node of body) {\n if (node.type === 'ImportDeclaration') {\n // allowed — skip\n continue\n }\n if (node.type === 'ExpressionStatement' && node.expression?.type === 'JSXElement') {\n if (jsxElement !== null) {\n // more than one JSX element → opaque\n return null\n }\n jsxElement = node.expression\n } else {\n // unexpected statement type (non-import, non-jsx-expression) → opaque\n return null\n }\n }\n\n return jsxElement\n}\n\n// ── Main export ────────────────────────────────────────────────────────────\n\n/**\n * Parse one MDX slide block and decide whether it is a known single-element\n * template with all literal-string props (→ \"template\") or anything else\n * (→ \"opaque\", edit-in-source only).\n */\nexport function parseSlide(block: string): ParsedSlide {\n const element = findTemplateElement(block)\n\n if (element === null) {\n return { kind: 'opaque', reason: 'block contains no single JSX template element' }\n }\n\n // Resolve component name (must be a plain JSXIdentifier, not a MemberExpression)\n const openingEl = element.openingElement\n const nameNode = openingEl.name\n if (nameNode.type !== 'JSXIdentifier') {\n return { kind: 'opaque', reason: 'JSX element name is not a plain identifier' }\n }\n const componentName: string = nameNode.name\n\n if (!(templateNames as readonly string[]).includes(componentName)) {\n return { kind: 'opaque', reason: `\"${componentName}\" is not a registered template` }\n }\n\n // Require attribute-only / self-closing (no non-whitespace children)\n const children: AnyNode[] = element.children ?? []\n for (const child of children) {\n if (child.type === 'JSXText') {\n // Allow whitespace-only JSXText (newlines, spaces between tags)\n if (child.value.trim() !== '') {\n return { kind: 'opaque', reason: 'element has non-whitespace children' }\n }\n } else {\n // JSXElement, JSXExpressionContainer, JSXFragment, JSXSpreadChild\n return { kind: 'opaque', reason: 'element has non-whitespace children' }\n }\n }\n\n // Extract props — all must be JSXAttribute with a string Literal value\n const props: Record<string, string> = {}\n const attributes: AnyNode[] = openingEl.attributes ?? []\n for (const attr of attributes) {\n if (attr.type === 'JSXSpreadAttribute') {\n return { kind: 'opaque', reason: 'element has spread attribute' }\n }\n if (attr.type !== 'JSXAttribute') {\n return { kind: 'opaque', reason: 'unexpected attribute node type' }\n }\n const attrName: string = attr.name.name\n const attrValue: AnyNode = attr.value\n\n // Allow bare boolean shorthand (e.g. `<Foo required />` — value is null)\n // but we only model it as a string flag \"true\" for now. Actually per spec:\n // only string Literals are permitted; anything else → opaque.\n if (attrValue === null) {\n // bare attribute (boolean shorthand) — not a string literal\n return { kind: 'opaque', reason: `attribute \"${attrName}\" has no string value (boolean shorthand)` }\n }\n if (attrValue.type !== 'Literal' || typeof attrValue.value !== 'string') {\n return {\n kind: 'opaque',\n reason: `attribute \"${attrName}\" is not a string literal`,\n }\n }\n\n props[attrName] = attrValue.value\n }\n\n return { kind: 'template', name: componentName, props }\n}\n","import { findTemplateElement } from './parse-slide'\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\n// biome-ignore lint/suspicious/noExplicitAny: acorn AST nodes are untyped\ntype AnyNode = any\n\n// ── Helpers ────────────────────────────────────────────────────────────────\n\n/**\n * Encode a plain string value as a JSX attribute string value with surrounding\n * double-quote delimiters.\n *\n * Acorn-jsx decodes JSX entity references inside quoted attribute string\n * values (`&amp;` → `&`, `&quot;` → `\"`), so our encoder must mirror exactly\n * what the parser decodes:\n * 1. `&` → `&amp;` (MUST be first to avoid double-encoding)\n * 2. `\"` → `&quot;`\n *\n * Other JSX-level entities (`&lt;`, `&gt;`, `&apos;`) are not emitted by this\n * encoder, but we do NOT need to escape `<` or `>` inside a JSX attribute\n * string — those are only significant in JSX *text* content, not attribute\n * values.\n *\n * Exported so `slide-edit.ts` can reuse the same escaping for\n * `switchSlideTemplate`, ensuring the fix lands in exactly one place.\n */\nexport function renderJsxAttr(name: string, value: string): string {\n // Encode & first (before \" to avoid double-encoding), then \"\n const encoded = value.replaceAll('&', '&amp;').replaceAll('\"', '&quot;')\n return `${name}=\"${encoded}\"`\n}\n\n/**\n * Render a string value as a valid JSX attribute value (including surrounding\n * delimiters).\n *\n * Always produces a double-quoted value with `&amp;` and `&quot;` entity\n * escaping so that the round-trip `parseProp(setProp(block, k, v)) === v`\n * holds for ALL string values, including those containing `&`, `\"`, or\n * pre-existing entity references such as `&quot;` or `&amp;`.\n *\n * (The previous single-quote optimisation was removed because acorn-jsx also\n * decodes entities inside single-quoted attributes, making the same escaping\n * necessary regardless of delimiter choice. Using double-quotes uniformly\n * keeps the implementation simple and correct.)\n */\nfunction renderValue(value: string): string {\n const encoded = value.replaceAll('&', '&amp;').replaceAll('\"', '&quot;')\n return `\"${encoded}\"`\n}\n\n// ── Main export ────────────────────────────────────────────────────────────\n\n/**\n * Apply one prop change to an MDX slide block string.\n *\n * Only the bytes belonging to the changed attribute value are mutated; every\n * other character in `block` (whitespace, other props, import lines, the tag\n * name, etc.) is byte-preserved.\n *\n * @param block The raw MDX block text (may include leading import lines).\n * @param name The JSX attribute name to set.\n * @param value The new plain-string value.\n * @returns The updated block string.\n *\n * If `block` cannot be parsed as a single template element (e.g. opaque block)\n * the function returns `block` unchanged — callers should only call this on\n * blocks that `parseSlide` classifies as `{ kind: 'template' }`.\n */\nexport function setProp(block: string, name: string, value: string): string {\n const element: AnyNode | null = findTemplateElement(block)\n if (element === null) {\n return block\n }\n\n const openingEl: AnyNode = element.openingElement\n const attributes: AnyNode[] = openingEl.attributes ?? []\n\n // ── Case 1: attribute already exists ──────────────────────────────────────\n const existing: AnyNode | undefined = attributes.find(\n (attr: AnyNode) => attr.type === 'JSXAttribute' && attr.name?.name === name\n )\n\n if (existing !== undefined) {\n const attrValue: AnyNode = existing.value\n if (attrValue === null || attrValue.type !== 'Literal') {\n // Non-string attribute (expression container, boolean shorthand, etc.) —\n // not supported; return block unchanged to avoid corrupting it.\n return block\n }\n\n // No-op fast path: if the existing raw bytes already match what renderValue\n // would produce, return unchanged (byte-identical).\n const existingRaw = block.slice(attrValue.start, attrValue.end)\n const rendered = renderValue(value)\n if (existingRaw === rendered) {\n return block\n }\n\n // Value or encoding changed — replace only the value span (offsets include\n // the surrounding quotes).\n return block.slice(0, attrValue.start) + rendered + block.slice(attrValue.end)\n }\n\n // ── Case 2: attribute not present — insert before closing `/>` or `>` ─────\n //\n // Strategy: walk backwards from `openingEl.end` to find the `/>` or `>`\n // boundary and insert ` name=\"value\"` just before it (before any preceding\n // whitespace that is part of the closing token itself).\n //\n // acorn-jsx sets `openingElement.end` to the position AFTER the closing\n // `>` (or `/>`) of the opening tag. We back-track to find its start.\n\n const tagEnd: number = openingEl.end // exclusive: char at [tagEnd] is AFTER `>`\n const rendered = renderValue(value)\n\n // Find the position of `/>` or `>` by scanning backwards from tagEnd\n // (the two characters before tagEnd for a self-closing tag are `/>`).\n let insertAt: number\n if (block[tagEnd - 1] === '>' && block[tagEnd - 2] === '/') {\n // Self-closing: `/>` sits at [tagEnd-2, tagEnd)\n insertAt = tagEnd - 2\n } else if (block[tagEnd - 1] === '>') {\n // Regular closing `>`\n insertAt = tagEnd - 1\n } else {\n // Unexpected — fall back to inserting at tagEnd (safe, may look odd)\n insertAt = tagEnd\n }\n\n // Strip any whitespace that already precedes the `/>` so we control spacing\n // (don't add double spaces). We keep existing whitespace and just prepend\n // a single space before our new attribute.\n const insertion = ` ${name}=${rendered}`\n return block.slice(0, insertAt) + insertion + block.slice(insertAt)\n}\n","/**\n * Split a deck's MDX source into typed blocks so a form editor can mutate ONE\n * slide's content while leaving all other bytes byte-identical.\n *\n * Partition rules\n * ---------------\n * • `frontmatter` — the leading `---`…`---` YAML fence (including both fence\n * lines and the trailing newline after the closing `---`). Present only when\n * the source begins with a `---` line that has a matching closing fence.\n * • `separator` — a single `---\\n` (or `---` at EOF) at the top level that\n * acts as a slide boundary. In-code `---` lines (inside ``` / ~~~ fences) are\n * NOT treated as separators.\n * • `slide` — all content between separators (may be empty string if two\n * separators are adjacent). Each slide block's `text` includes any surrounding\n * blank lines that are NOT part of a separator.\n *\n * Byte-fidelity guarantee\n * -----------------------\n * `joinBlocks(splitBlocks(source)) === source` for every valid input.\n * Each block stores its exact original text; `joinBlocks` is just concatenation.\n */\n\nexport interface Block {\n /** Sequential index across all emitted blocks (0-based). */\n index: number\n kind: 'frontmatter' | 'separator' | 'slide'\n /** Exact original text for this block — concatenating all texts reproduces the source. */\n text: string\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Returns true when a line (already trimmed) opens or closes a fenced code block. */\nfunction isFenceMarker(trimmed: string): boolean {\n return trimmed.startsWith('```') || trimmed.startsWith('~~~')\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Split `source` into typed blocks.\n *\n * Algorithm:\n * 1. Strip a leading frontmatter block if present (same rule as parseFrontmatter).\n * 2. Walk the remainder line-by-line, toggling a `inFence` flag on ``` / ~~~.\n * 3. Lines that are exactly `---` at top level (not inside a fence) become\n * `separator` blocks; everything else accumulates into a `slide` chunk.\n * 4. A trailing `---` with no content after it still emits a separator but\n * does NOT emit an additional empty slide.\n */\nexport function splitBlocks(source: string): Block[] {\n const blocks: Block[] = []\n let idx = 0\n\n // ── Step 1: peel frontmatter ──────────────────────────────────────────────\n // Mirrors parseFrontmatter exactly (same fence rule, same BOM strip).\n const normalized = source.replace(/^/, '')\n const allLines = normalized.split('\\n')\n\n let remainder = source\n let frontmatterConsumed = 0 // byte count consumed by frontmatter\n\n if (allLines[0]?.trim() === '---') {\n // Look for a closing fence starting at line 1.\n let closeIdx = -1\n for (let i = 1; i < allLines.length; i++) {\n if (allLines[i].trim() === '---') {\n closeIdx = i\n break\n }\n }\n\n if (closeIdx !== -1) {\n // Frontmatter text = lines[0..closeIdx] joined with '\\n', plus the '\\n'\n // that was between closeIdx and the next line (the split() consumed it).\n const fmLines = allLines.slice(0, closeIdx + 1)\n // Each line was split on '\\n', so we re-join with '\\n' and add the\n // trailing '\\n' that existed after the closing fence (unless it was the\n // very last character of source, in which case split produces an empty\n // string at the end and we must not double-add).\n const fmText = `${fmLines.join('\\n')}\\n`\n\n // Validate that source actually contains this prefix (handles BOM).\n if (source.startsWith(fmText) || normalized.startsWith(fmText)) {\n frontmatterConsumed = fmText.length\n blocks.push({ index: idx++, kind: 'frontmatter', text: fmText })\n remainder = source.slice(frontmatterConsumed)\n }\n }\n }\n\n // ── Step 2: walk the remainder line-by-line ───────────────────────────────\n // We need line-by-line iteration but must reconstruct exact text for each\n // block. To keep byte-fidelity we track character offsets into `remainder`.\n\n const remLines = remainder.split('\\n')\n // split('\\n') on \"a\\nb\\n\" → [\"a\",\"b\",\"\"] — the trailing \"\" represents the\n // final newline. We re-attach '\\n' to every line except the very last one\n // when it is the empty string produced by a trailing newline.\n\n // Build an array of {content, raw} where `raw` is the exact substring\n // including the '\\n' terminator (or nothing for a true EOF without newline).\n const lineRaws: string[] = []\n for (let i = 0; i < remLines.length; i++) {\n const isLast = i === remLines.length - 1\n if (isLast && remLines[i] === '') {\n // This empty string is the artifact of a trailing '\\n' — the newline\n // already belongs to the previous line's raw. Don't emit a line for it.\n break\n }\n lineRaws.push(isLast ? remLines[i] : `${remLines[i]}\\n`)\n }\n\n let inFence = false\n let slideText = ''\n\n const flushSlide = () => {\n // Always emit a slide block, even if empty — this preserves byte-fidelity\n // for adjacent separators. But we skip a trailing empty slide if the\n // remainder ended at a separator (handled in the loop by checking whether\n // lineRaws is exhausted after the last separator).\n blocks.push({ index: idx++, kind: 'slide', text: slideText })\n slideText = ''\n }\n\n for (let i = 0; i < lineRaws.length; i++) {\n const raw = lineRaws[i]\n const trimmed = raw.trimEnd().replace(/\\r$/, '') // trim CR for CRLF safety\n\n if (!inFence && trimmed === '---') {\n // This is a top-level separator.\n // Flush accumulated slide content before the separator.\n flushSlide()\n // Emit the separator block (just the `---\\n` line).\n blocks.push({ index: idx++, kind: 'separator', text: raw })\n // After the separator, check if there's more content. If not, don't\n // emit a phantom empty slide — the loop will simply end.\n } else {\n // Toggle fenced-code state.\n if (isFenceMarker(trimmed)) {\n inFence = !inFence\n }\n slideText += raw\n }\n }\n\n // Flush final slide (may be empty if source ended with a separator).\n // Only emit if non-empty OR if there were no separators at all (single slide).\n if (slideText !== '' || blocks.filter(b => b.kind === 'slide').length === 0) {\n flushSlide()\n }\n\n return blocks\n}\n\n/**\n * Reconstruct the original source from a block array.\n * Concatenation is sufficient because each block stores its exact original text.\n */\nexport function joinBlocks(blocks: Block[]): string {\n return blocks.map(b => b.text).join('')\n}\n","/**\n * Pure helpers that operate on the WHOLE-deck `source` string, tying together\n * `splitBlocks` / `joinBlocks`, `setProp`, and `findTemplateElement` so that\n * the Form view can edit individual slides without touching any other bytes.\n *\n * All functions are stateless and synchronous — safe to call on every render.\n */\n\nimport { findTemplateElement, parseSlide } from './parse-slide'\nimport { renderJsxAttr, setProp } from './serialize-slot'\nimport { joinBlocks, splitBlocks } from './slide-blocks'\n\n// ── Internal helpers ──────────────────────────────────────────────────────────\n\n/**\n * Returns the indices (into `splitBlocks(source)`) of non-empty slide blocks,\n * in document order. Empty slide blocks (text that is blank / whitespace-only)\n * that appear between two adjacent separators are skipped — they are not\n * rendered as visible slides.\n */\nexport function slideBlockIndices(source: string): number[] {\n const blocks = splitBlocks(source)\n const indices: number[] = []\n for (const block of blocks) {\n if (block.kind === 'slide' && block.text.trim() !== '') {\n indices.push(block.index)\n }\n }\n return indices\n}\n\n/**\n * Return the block (by its position in `splitBlocks`) and its exact text for\n * the Nth (0-based) rendered slide. Returns `null` when `slideOrdinal` is out\n * of range.\n */\nexport function getSlideBlock(\n source: string,\n slideOrdinal: number\n): { blockIndex: number; text: string } | null {\n const indices = slideBlockIndices(source)\n if (slideOrdinal < 0 || slideOrdinal >= indices.length) return null\n const blockIndex = indices[slideOrdinal]\n const blocks = splitBlocks(source)\n const block = blocks.find(b => b.index === blockIndex)\n if (!block) return null\n return { blockIndex, text: block.text }\n}\n\n/**\n * Apply one prop change to the Nth (0-based) rendered slide inside `source`.\n *\n * Uses `setProp` for byte-stable mutation — only the changed attribute value\n * is rewritten; all other bytes in the source are preserved exactly.\n *\n * Returns the original `source` unchanged when `slideOrdinal` is out of range\n * or the target block is opaque (i.e. `parseSlide` does not return\n * `{ kind: 'template' }`). This is a hard safety gate — opaque blocks are\n * never lossily rewritten, regardless of whether `findTemplateElement` would\n * have matched.\n */\nexport function setSlideProp(\n source: string,\n slideOrdinal: number,\n key: string,\n value: string\n): string {\n const indices = slideBlockIndices(source)\n if (slideOrdinal < 0 || slideOrdinal >= indices.length) return source\n const blockIndex = indices[slideOrdinal]\n\n const blocks = splitBlocks(source)\n const updated = blocks.map(b => {\n if (b.index !== blockIndex) return b\n // Opaque-safety gate: only rewrite template blocks\n if (parseSlide(b.text).kind !== 'template') return b\n return { ...b, text: setProp(b.text, key, value) }\n })\n return joinBlocks(updated)\n}\n\n// ── switchSlideTemplate ───────────────────────────────────────────────────────\n\n/**\n * Render a set of props as JSX attribute string (name=\"value\" pairs, single\n * space separated).\n *\n * Delegates to `renderJsxAttr` (from serialize-slot) so entity escaping is\n * consolidated in one place and both code paths benefit from the same fix.\n */\nfunction renderProps(props: Record<string, string>): string {\n return Object.entries(props)\n .map(([k, v]) => renderJsxAttr(k, v))\n .join(' ')\n}\n\n/**\n * Replace the JSX template element in the Nth (0-based) rendered slide with a\n * freshly-rendered self-closing `<NextName key=\"val\" … />` built from\n * `nextProps`.\n *\n * Only the JSX element span is replaced; any leading import declarations or\n * surrounding whitespace/text in the block are preserved byte-for-byte.\n *\n * Returns the original `source` unchanged when `slideOrdinal` is out of range\n * or the target block is opaque (i.e. `parseSlide` does not return\n * `{ kind: 'template' }`). This is a hard safety gate — opaque blocks are\n * never lossily rewritten, regardless of whether `findTemplateElement` would\n * have matched.\n */\nexport function switchSlideTemplate(\n source: string,\n slideOrdinal: number,\n nextName: string,\n nextProps: Record<string, string>\n): string {\n const indices = slideBlockIndices(source)\n if (slideOrdinal < 0 || slideOrdinal >= indices.length) return source\n const blockIndex = indices[slideOrdinal]\n\n const blocks = splitBlocks(source)\n const updated = blocks.map(b => {\n if (b.index !== blockIndex) return b\n\n // Opaque-safety gate: only rewrite template blocks\n if (parseSlide(b.text).kind !== 'template') return b\n\n const element = findTemplateElement(b.text)\n if (element === null) return b\n\n // Build the replacement element string\n const propsStr = renderProps(nextProps)\n const replacement =\n propsStr.length > 0 ? `<${nextName} ${propsStr} />` : `<${nextName} />`\n\n // element.start / element.end are offsets into b.text\n const newText =\n b.text.slice(0, element.start) + replacement + b.text.slice(element.end)\n return { ...b, text: newText }\n })\n\n return joinBlocks(updated)\n}\n","import type { JSX } from 'react'\nimport type { SlotDef, SlotKind } from '../content/template-registry'\nimport { getTemplate } from '../content/template-registry'\n\nexport interface SlotFormProps {\n /** Template component name, e.g. \"CoverSlide\". */\n name: string\n /** Current prop values keyed by SlotDef.key. */\n props: Record<string, string>\n /** Called when any field value changes. */\n onChange: (key: string, value: string) => void\n}\n\nfunction fieldId(templateName: string, key: string): string {\n return `a63-slot-${templateName}-${key}`\n}\n\nfunction isMultiline(kind: SlotKind): boolean {\n return kind === 'richtext' || kind === 'list'\n}\n\nfunction SlotField({\n templateName,\n slot,\n value,\n onChange,\n}: {\n templateName: string\n slot: SlotDef\n value: string\n onChange: (key: string, value: string) => void\n}): JSX.Element {\n const id = fieldId(templateName, slot.key)\n const multiline = isMultiline(slot.kind)\n\n return (\n <div className=\"a63-form__field\">\n <div className=\"a63-form__label-row\">\n <label className=\"a63-form__label\" htmlFor={id}>\n {slot.label}\n </label>\n {slot.required && (\n <span className=\"a63-form__required\" aria-hidden=\"true\">\n *\n </span>\n )}\n </div>\n {multiline ? (\n <textarea\n id={id}\n className=\"a63-form__textarea\"\n value={value}\n required={slot.required}\n aria-required={slot.required}\n onChange={e => onChange(slot.key, e.target.value)}\n />\n ) : (\n <input\n type=\"text\"\n id={id}\n className=\"a63-form__input\"\n value={value}\n required={slot.required}\n aria-required={slot.required}\n onChange={e => onChange(slot.key, e.target.value)}\n />\n )}\n </div>\n )\n}\n\nexport function SlotForm({ name, props, onChange }: SlotFormProps): JSX.Element {\n const tpl = getTemplate(name)\n\n if (!tpl) {\n return (\n <p className=\"a63-form__unknown\">\n Unknown template: <code>{name}</code>\n </p>\n )\n }\n\n const slotGroupNames = tpl.slots.map(s => s.name)\n\n return (\n <div className=\"a63-form\">\n {tpl.props.map(slot => (\n <SlotField\n key={slot.key}\n templateName={name}\n slot={slot}\n value={props[slot.key] ?? ''}\n onChange={onChange}\n />\n ))}\n {slotGroupNames.length > 0 && (\n <p className=\"a63-form__slots-note\">\n Repeatable sections ({slotGroupNames.join(', ')}) are edited in Source.\n </p>\n )}\n </div>\n )\n}\n","import { type JSX, useState } from 'react'\nimport { getTemplate, listTemplates } from '../content/template-registry'\n\nexport interface TemplateSwitch {\n props: Record<string, string>\n dropped: string[]\n}\n\nexport interface TemplatePickerProps {\n /** Current template component name, e.g. \"CoverSlide\". */\n name: string\n /** Current prop values keyed by SlotDef.key. */\n props: Record<string, string>\n /** Called when the user picks a different template. */\n onSwitch: (next: string, mapped: TemplateSwitch) => void\n}\n\nexport function TemplatePicker({ name, props, onSwitch }: TemplatePickerProps): JSX.Element {\n const [warning, setWarning] = useState<{ from: string; to: string; dropped: string[] } | null>(\n null\n )\n\n function handleChange(next: string): void {\n if (next === name) return\n\n const nextDef = getTemplate(next)\n if (!nextDef) return\n\n const nextKeys = new Set(nextDef.props.map(s => s.key))\n\n // Carry over props whose key exists in the next template\n const carried: Record<string, string> = {}\n for (const [k, v] of Object.entries(props)) {\n if (nextKeys.has(k)) {\n carried[k] = v\n }\n }\n\n // Dropped = current keys not in next, but only those with non-empty values\n const dropped: string[] = Object.entries(props)\n .filter(([k, v]) => !nextKeys.has(k) && v !== '')\n .map(([k]) => k)\n\n // Fill required slots not already carried\n for (const slot of nextDef.props) {\n if (!(slot.key in carried)) {\n carried[slot.key] = ''\n }\n }\n\n onSwitch(next, { props: carried, dropped })\n\n if (dropped.length > 0) {\n setWarning({ from: name, to: next, dropped })\n } else {\n setWarning(null)\n }\n }\n\n const templates = listTemplates()\n\n return (\n <div className=\"a63-form__field\">\n <label className=\"a63-form__label\" htmlFor=\"a63-template-picker\">\n Template\n </label>\n <select\n id=\"a63-template-picker\"\n className=\"a63-form__input\"\n aria-label=\"Template\"\n value={name}\n onChange={e => handleChange(e.target.value)}\n >\n {templates.map(tpl => (\n <option key={tpl.name} value={tpl.name}>\n {tpl.label ?? tpl.name}\n </option>\n ))}\n </select>\n {warning && (\n <output className=\"a63-form__picker-warning\">\n <span>\n Switched {warning.from} &rarr; {warning.to}; dropped: {warning.dropped.join(', ')}.\n </span>\n <button\n type=\"button\"\n className=\"a63-form__picker-dismiss\"\n aria-label=\"Dismiss\"\n onClick={() => setWarning(null)}\n >\n &times;\n </button>\n </output>\n )}\n </div>\n )\n}\n","import {\n listTemplates,\n type SlotDef,\n type SlotGroupDef,\n type TemplateDef,\n} from '../content/template-registry'\n\n/**\n * Generates a minimal, paste-ready MDX snippet per template from the engine's\n * registry (`listTemplates()`), mirroring the synthesis logic in\n * `skill/scripts/gen-templates.mjs`. The palette in\n * <DeckEditor> appends `templateSnippets[name]` when a template is clicked.\n *\n * Registry is the single source of truth; snippets cannot drift from the\n * published template props/slots.\n */\n\nconst PLACEHOLDER_IMG = '/images/placeholder-1920x1080.webp'\n\nfunction exampleValue(prop: SlotDef): string {\n switch (prop.kind) {\n case 'media':\n return PLACEHOLDER_IMG\n default:\n // text / richtext / list-as-scalar\n return `${prop.label}…`\n }\n}\n\n/** Render the direct-prop attributes for a template's opening tag. */\nfunction renderPropAttrs(props: SlotDef[], indent = ' '): string[] {\n const lines: string[] = []\n for (const prop of props) {\n if (prop.key === 'children') continue\n if (prop.array) {\n if (prop.kind === 'media') {\n lines.push(\n `${indent}${prop.key}={[\"${PLACEHOLDER_IMG}\", \"${PLACEHOLDER_IMG}\", \"${PLACEHOLDER_IMG}\"]}`\n )\n } else {\n lines.push(`${indent}${prop.key}={[{ label: \"Label\", value: \"Value\", href: \"https://…\" }]}`)\n }\n continue\n }\n lines.push(`${indent}${prop.key}=\"${exampleValue(prop)}\"`)\n }\n return lines\n}\n\n/** Render one compound-slot child element, e.g. `<HeroBento.Card title=\"…\" />`. */\nfunction renderSlotChild(templateName: string, slot: SlotGroupDef): string {\n const attrs: string[] = []\n let childrenText: string | null = null\n for (const prop of slot.props) {\n if (prop.key === 'children') {\n childrenText = `${prop.label}…`\n continue\n }\n if (prop.array) {\n attrs.push(`${prop.key}={[…]}`)\n continue\n }\n const value = prop.kind === 'media' ? PLACEHOLDER_IMG : `${prop.label}…`\n attrs.push(`${prop.key}=\"${value}\"`)\n }\n const tag = `${templateName}.${slot.name}`\n const attrStr = attrs.length ? ` ${attrs.join(' ')}` : ''\n if (childrenText !== null) {\n return ` <${tag}${attrStr}>${childrenText}</${tag}>`\n }\n return ` <${tag}${attrStr} />`\n}\n\n/**\n * Synthesize a minimal, valid MDX usage example from a template's schema.\n * - Simple templates → self-closing tag with its props.\n * - Compound templates → open tag + one instance of each slot at `min` (or 1).\n */\nexport function synthExample(t: TemplateDef): string {\n const hasSlots = t.slots.length > 0\n const propLines = renderPropAttrs(t.props)\n\n if (!hasSlots) {\n if (propLines.length === 0) return `<${t.name} />`\n return [`<${t.name}`, ...propLines, '/>'].join('\\n')\n }\n\n const open = propLines.length ? [`<${t.name}`, ...propLines, '>'] : [`<${t.name}>`]\n const childLines: string[] = []\n for (const slot of t.slots) {\n const count = Math.min(Math.max(slot.min, 1), 3)\n for (let i = 0; i < count; i++) {\n childLines.push(renderSlotChild(t.name, slot))\n }\n }\n return [...open, ...childLines, `</${t.name}>`].join('\\n')\n}\n\n/**\n * Wrap a synthesized example as an insertable slide: a leading `---` separator\n * (the engine's slide break) followed by the example, so appending to a deck\n * body always starts a fresh slide.\n */\nexport function toInsertSnippet(t: TemplateDef): string {\n return `\\n---\\n\\n${synthExample(t)}\\n`\n}\n\n/** Map of `templateName -> insertable MDX snippet`, built from the live registry. */\nexport const templateSnippets: Record<string, string> = Object.fromEntries(\n listTemplates().map(t => [t.name, toInsertSnippet(t)])\n)\n","import { type ChangeEvent, type KeyboardEvent, useCallback, useMemo, useRef, useState } from 'react'\nimport { listTemplates } from '../content/template-registry'\nimport { SlidesPlayer } from '../player/slides-player'\nimport type { SlideDeckItem } from '../types'\nimport { parseSlide } from './parse-slide'\nimport { getSlideBlock, setSlideProp, slideBlockIndices, switchSlideTemplate } from './slide-edit'\nimport { SlotForm } from './slot-form'\nimport { TemplatePicker } from './template-picker'\nimport { templateSnippets } from './template-snippets'\n\nexport interface EditPaneProps {\n source: string\n onChange: (next: string) => void\n onSave?: (source: string) => void | Promise<void>\n deck: SlideDeckItem | null\n error: string | null\n onPresent: () => void\n}\n\ntype RightPaneTab = 'source' | 'form'\n\nexport function EditPane({ source, onChange, onSave, deck, error, onPresent }: EditPaneProps) {\n const [theme, setTheme] = useState<'light' | 'dark'>('light')\n const [rightTab, setRightTab] = useState<RightPaneTab>('source')\n // TODO: sync with preview nav — currently uses a form-local stepper defaulting to slide 0.\n const [formSlideIdx, setFormSlideIdx] = useState(0)\n const textareaRef = useRef<HTMLTextAreaElement>(null)\n const templates = useMemo(() => listTemplates(), [])\n\n const handleChange = useCallback(\n (e: ChangeEvent<HTMLTextAreaElement>) => onChange(e.target.value),\n [onChange]\n )\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLTextAreaElement>) => {\n if ((e.metaKey || e.ctrlKey) && e.key === 's') {\n e.preventDefault()\n onSave?.(source)\n }\n },\n [onSave, source]\n )\n const insert = useCallback(\n (name: string) => {\n const snippet = templateSnippets[name]\n if (!snippet) return\n onChange(`${source.replace(/\\s*$/, '')}\\n${snippet}`)\n requestAnimationFrame(() => {\n const el = textareaRef.current\n if (!el) return\n el.focus()\n el.selectionStart = el.selectionEnd = el.value.length\n el.scrollTop = el.scrollHeight\n })\n },\n [source, onChange]\n )\n\n // ── Form view helpers ─────────────────────────────────────────────────────\n const slideCount = useMemo(() => slideBlockIndices(source).length, [source])\n const clampedIdx = Math.min(formSlideIdx, Math.max(0, slideCount - 1))\n\n const currentBlock = useMemo(() => getSlideBlock(source, clampedIdx), [source, clampedIdx])\n const parsedSlide = useMemo(\n () => (currentBlock ? parseSlide(currentBlock.text) : null),\n [currentBlock]\n )\n\n const handleSlotChange = useCallback(\n (key: string, value: string) => {\n onChange(setSlideProp(source, clampedIdx, key, value))\n },\n [source, clampedIdx, onChange]\n )\n\n const handleTemplateSwitch = useCallback(\n (next: string, mapped: { props: Record<string, string>; dropped: string[] }) => {\n onChange(switchSlideTemplate(source, clampedIdx, next, mapped.props))\n },\n [source, clampedIdx, onChange]\n )\n\n return (\n <div className=\"a63-editor\">\n <section className=\"a63-editor__preview\" data-theme={theme}>\n {error ? (\n <div className=\"a63-editor__error\" role=\"alert\">\n <span className=\"a63-editor__error-tag\">MDX error</span>\n <span className=\"a63-editor__error-msg\">{error}</span>\n </div>\n ) : null}\n {deck ? (\n <div className=\"a63-editor__preview-stage\" key={theme}>\n <SlidesPlayer deck={deck} onBack={() => {}} />\n </div>\n ) : (\n <div className=\"a63-editor__preview-empty\">\n {error ? 'Fix the MDX error to render a preview.' : 'Compiling preview…'}\n </div>\n )}\n </section>\n <section className=\"a63-editor__source\">\n <div className=\"a63-editor__toolbar\">\n <span className=\"a63-editor__title\">Deck source · MDX</span>\n <div className=\"a63-editor__toolbar-actions\">\n {/* Source | Form sub-toggle */}\n <div className=\"a63-editor__subtoggle\" role=\"group\" aria-label=\"Edit mode\">\n <button\n type=\"button\"\n className=\"a63-editor__subtoggle-btn\"\n aria-pressed={rightTab === 'source'}\n onClick={() => setRightTab('source')}\n >\n Source\n </button>\n <button\n type=\"button\"\n className=\"a63-editor__subtoggle-btn\"\n aria-pressed={rightTab === 'form'}\n onClick={() => setRightTab('form')}\n >\n Form\n </button>\n </div>\n {onSave ? (\n <button type=\"button\" className=\"a63-editor__save\" onClick={() => onSave(source)}>\n Save\n </button>\n ) : null}\n <button\n type=\"button\"\n className=\"a63-editor__theme-toggle\"\n onClick={() => setTheme(t => (t === 'light' ? 'dark' : 'light'))}\n aria-pressed={theme === 'dark'}\n >\n {theme === 'light' ? '☀︎ Light' : '☾ Dark'}\n </button>\n <button type=\"button\" className=\"a63-editor__present\" onClick={onPresent}>\n Present\n </button>\n </div>\n </div>\n\n {rightTab === 'source' ? (\n <>\n <textarea\n ref={textareaRef}\n className=\"a63-editor__textarea\"\n value={source}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n spellCheck={false}\n aria-label=\"Deck MDX source\"\n />\n <div className=\"a63-editor__palette\">\n <div className=\"a63-editor__palette-label\">Templates · click to append</div>\n <div className=\"a63-editor__palette-grid\">\n {templates.map(t => (\n <button\n key={t.name}\n type=\"button\"\n className=\"a63-editor__chip\"\n onClick={() => insert(t.name)}\n title={`Insert ${t.name}`}\n >\n <span className=\"a63-editor__chip-name\">{t.name}</span>\n <span className=\"a63-editor__chip-meta\">{t.label}</span>\n </button>\n ))}\n </div>\n </div>\n </>\n ) : (\n <div className=\"a63-editor__form-panel\">\n {/* Slide stepper */}\n <div className=\"a63-editor__slide-stepper\">\n <button\n type=\"button\"\n className=\"a63-editor__stepper-btn\"\n aria-label=\"Previous slide\"\n disabled={clampedIdx <= 0}\n onClick={() => setFormSlideIdx(i => Math.max(0, i - 1))}\n >\n ‹\n </button>\n <span className=\"a63-editor__stepper-label\">\n Slide {clampedIdx + 1} / {slideCount}\n </span>\n <button\n type=\"button\"\n className=\"a63-editor__stepper-btn\"\n aria-label=\"Next slide\"\n disabled={clampedIdx >= slideCount - 1}\n onClick={() => setFormSlideIdx(i => Math.min(slideCount - 1, i + 1))}\n >\n ›\n </button>\n </div>\n\n {/* Form content */}\n {parsedSlide === null ? (\n <p className=\"a63-editor__form-empty\">No slides found.</p>\n ) : parsedSlide.kind === 'opaque' ? (\n <div className=\"a63-editor__form-opaque\">\n <p>This slide is only editable in Source.</p>\n <button\n type=\"button\"\n className=\"a63-editor__subtoggle-btn\"\n onClick={() => setRightTab('source')}\n >\n Switch to Source\n </button>\n </div>\n ) : (\n <div className=\"a63-editor__form-fields\">\n <TemplatePicker\n name={parsedSlide.name}\n props={parsedSlide.props}\n onSwitch={handleTemplateSwitch}\n />\n <SlotForm\n name={parsedSlide.name}\n props={parsedSlide.props}\n onChange={handleSlotChange}\n />\n </div>\n )}\n </div>\n )}\n </section>\n </div>\n )\n}\n","import { type ComponentType, useEffect, useMemo, useState } from 'react'\nimport { SlidesPlayer } from '../player/slides-player'\nimport type { SlideDeckItem } from '../types'\nimport { compileDeck } from './compile-deck'\nimport { EditPane } from './edit-pane'\n\nexport type DeckSurfaceMode = 'present' | 'edit'\n\nexport interface DeckSurfaceProps {\n /**\n * Raw deck MDX source (frontmatter + `---`-separated slide body). The single\n * canonical input for both Present and Edit modes.\n *\n * **Controlled vs. uncontrolled:**\n * - When `onChange` is provided the surface is **controlled** — the parent\n * owns source state and must feed back the updated string on each change.\n * - When `onChange` is omitted the surface is **uncontrolled** and\n * **present-only**: `source` is read once on mount and later prop changes\n * are ignored. This is the production / read-only contract.\n */\n source: string\n /**\n * Called with the next source string on every edit or template insert\n * (in-memory, per-keystroke). Presence of this prop **enables Edit mode**\n * and makes the surface controlled — the parent holds source state.\n * When omitted the surface is present-only (no edit chrome, no keyboard\n * shortcut to enter Edit).\n */\n onChange?: (next: string) => void\n /**\n * Called on an **explicit** save action (Save button or Cmd/Ctrl-S in Edit\n * mode). This is the persistence hook, distinct from the per-keystroke\n * `onChange`. Wire it to the dev write-back plugin (or any async persistence\n * layer) to flush the source to disk on demand. No-op / absent in a\n * production build.\n */\n onSave?: (source: string) => void | Promise<void>\n /**\n * The mode the surface starts in. Defaults to `'present'`. Forced to\n * `'present'` when `onChange` is absent (present-only surfaces cannot enter\n * Edit mode).\n */\n initialMode?: DeckSurfaceMode\n /**\n * Debounce window in milliseconds before recompiling the preview after a\n * source change. Defaults to `300`. Pass `0` in tests to compile\n * synchronously on the next microtask tick.\n */\n debounceMs?: number\n}\n\ntype Preview = { Content: ComponentType; meta: SlideDeckItem['meta'] } | null\n\nexport function DeckSurface({\n source: sourceProp,\n onChange,\n onSave,\n initialMode = 'present',\n debounceMs = 300,\n}: DeckSurfaceProps) {\n const editable = onChange !== undefined\n\n // Controlled when onChange provided; uncontrolled otherwise.\n const [internalSource, setInternalSource] = useState(sourceProp)\n const source = editable ? sourceProp : internalSource\n\n const setSource = (next: string) => {\n if (editable) onChange(next)\n else setInternalSource(next)\n }\n\n const [mode, setMode] = useState<DeckSurfaceMode>(editable ? initialMode : 'present')\n\n const [preview, setPreview] = useState<Preview>(null)\n const [error, setError] = useState<string | null>(null)\n\n // Debounced runtime compile. Keeps the last good preview on error.\n useEffect(() => {\n let cancelled = false\n const h = setTimeout(async () => {\n const r = await compileDeck(source)\n if (cancelled) return\n if (r.ok) {\n setPreview({ Content: r.Content, meta: r.meta })\n setError(null)\n } else {\n setError(r.error)\n }\n }, debounceMs)\n return () => {\n cancelled = true\n clearTimeout(h)\n }\n }, [source, debounceMs])\n\n const deck: SlideDeckItem | null = useMemo(\n () => (preview ? { slug: 'draft', meta: preview.meta, content: preview.Content } : null),\n [preview]\n )\n\n // Keyboard shortcuts (only when editable).\n useEffect(() => {\n if (!editable) return\n const onKey = (e: KeyboardEvent) => {\n const typing = (e.target as HTMLElement)?.tagName === 'TEXTAREA'\n if (e.key === 'e' && !typing && mode === 'present') setMode('edit')\n if (e.key === 'Escape' && mode === 'edit') setMode('present')\n }\n window.addEventListener('keydown', onKey)\n return () => window.removeEventListener('keydown', onKey)\n }, [editable, mode])\n\n if (editable && mode === 'edit') {\n return (\n <EditPane\n source={source}\n onChange={setSource}\n onSave={onSave}\n deck={deck}\n error={error}\n onPresent={() => setMode('present')}\n />\n )\n }\n\n return (\n <div className=\"a63-surface\">\n {editable && (\n <button type=\"button\" className=\"a63-surface__edit\" onClick={() => setMode('edit')}>\n Edit\n </button>\n )}\n {deck ? (\n <SlidesPlayer deck={deck} onBack={() => {}} />\n ) : (\n <div className=\"a63-editor__preview-empty\">\n {error ? 'Fix the MDX error to render a preview.' : 'Compiling preview…'}\n </div>\n )}\n </div>\n )\n}\n","import { DeckSurface } from './deck-surface'\n\nexport interface DeckEditorProps {\n source: string\n onChange?: (next: string) => void\n debounceMs?: number\n}\n\n/**\n * Backward-compatible editor: DeckSurface locked to Edit mode. The unified\n * surface ({@link DeckSurface}) is the preferred API; this remains for existing\n * consumers.\n */\nexport function DeckEditor({ source, onChange, debounceMs }: DeckEditorProps) {\n return (\n <DeckSurface\n source={source}\n onChange={onChange ?? (() => {})}\n initialMode=\"edit\"\n debounceMs={debounceMs}\n />\n )\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atom63/slides",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -95,6 +95,8 @@
95
95
  "@mdx-js/mdx": "^3.1.1",
96
96
  "@mdx-js/react": "^3.1.1",
97
97
  "@tanstack/react-virtual": "^3.14.2",
98
+ "acorn": "8.17.0",
99
+ "acorn-jsx": "5.3.2",
98
100
  "class-variance-authority": "^0.7.1",
99
101
  "clsx": "^2.1.1",
100
102
  "gray-matter": "^4.0.3",
@@ -134,8 +136,8 @@
134
136
  "typescript": "~5.8.3",
135
137
  "vite": "^7.1.5",
136
138
  "vitest": "^4.1.8",
137
- "@atom63/tsconfig": "0.1.0",
138
- "@atom63/biome-config": "0.1.0"
139
+ "@atom63/biome-config": "0.1.0",
140
+ "@atom63/tsconfig": "0.1.0"
139
141
  },
140
142
  "scripts": {
141
143
  "build": "tsup",
@@ -263,3 +263,221 @@
263
263
  .a63-editor__present:hover {
264
264
  background: rgba(127, 127, 127, 0.12);
265
265
  }
266
+
267
+ /* ─── SlotForm — registry-driven prop editor ─────────────────────────────── */
268
+ .a63-form {
269
+ display: flex;
270
+ flex-direction: column;
271
+ gap: 0.75rem;
272
+ padding: 0.75rem;
273
+ }
274
+
275
+ .a63-form__field {
276
+ display: flex;
277
+ flex-direction: column;
278
+ gap: 0.25rem;
279
+ }
280
+
281
+ .a63-form__label-row {
282
+ display: flex;
283
+ align-items: baseline;
284
+ gap: 0.2rem;
285
+ }
286
+
287
+ .a63-form__label {
288
+ font-size: 0.75rem;
289
+ font-weight: 600;
290
+ color: #1a1a1a;
291
+ letter-spacing: 0.01em;
292
+ }
293
+
294
+ .a63-form__required {
295
+ color: #b91c1c;
296
+ }
297
+
298
+ .a63-form__input,
299
+ .a63-form__textarea {
300
+ width: 100%;
301
+ padding: 0.35rem 0.5rem;
302
+ border: 1px solid rgba(127, 127, 127, 0.35);
303
+ border-radius: 0.375rem;
304
+ background: #fff;
305
+ color: #1a1a1a;
306
+ font-size: 0.8rem;
307
+ font-family: inherit;
308
+ line-height: 1.4;
309
+ outline: none;
310
+ transition: border-color 0.12s ease;
311
+ }
312
+
313
+ .a63-form__input:focus,
314
+ .a63-form__textarea:focus {
315
+ border-color: rgba(59, 130, 246, 0.7);
316
+ }
317
+
318
+ .a63-form__textarea {
319
+ resize: vertical;
320
+ min-height: 4.5rem;
321
+ }
322
+
323
+ .a63-form__slots-note {
324
+ margin-top: 0.25rem;
325
+ padding: 0.4rem 0.55rem;
326
+ border-radius: 0.375rem;
327
+ background: rgba(127, 127, 127, 0.08);
328
+ font-size: 0.72rem;
329
+ color: #555;
330
+ line-height: 1.4;
331
+ }
332
+
333
+ .a63-form__unknown {
334
+ padding: 0.75rem;
335
+ font-size: 0.8rem;
336
+ color: #b91c1c;
337
+ }
338
+
339
+ /* TemplatePicker — loss warning + dismiss button */
340
+ .a63-form__picker-warning {
341
+ display: flex;
342
+ align-items: flex-start;
343
+ gap: 0.5rem;
344
+ margin-top: 0.35rem;
345
+ padding: 0.4rem 0.55rem;
346
+ border-radius: 0.375rem;
347
+ background: rgba(180, 83, 9, 0.1);
348
+ border: 1px solid rgba(180, 83, 9, 0.3);
349
+ font-size: 0.72rem;
350
+ color: #92400e;
351
+ line-height: 1.4;
352
+ }
353
+
354
+ .a63-form__picker-warning span {
355
+ flex: 1;
356
+ min-width: 0;
357
+ }
358
+
359
+ .a63-form__picker-dismiss {
360
+ flex: none;
361
+ background: transparent;
362
+ border: none;
363
+ padding: 0 0.25rem;
364
+ color: inherit;
365
+ font-size: 0.9rem;
366
+ line-height: 1;
367
+ cursor: pointer;
368
+ opacity: 0.7;
369
+ }
370
+
371
+ .a63-form__picker-dismiss:hover {
372
+ opacity: 1;
373
+ }
374
+
375
+ /* ─── Source | Form sub-toggle ───────────────────────────────────────────── */
376
+ .a63-editor__subtoggle {
377
+ display: inline-flex;
378
+ align-items: center;
379
+ border: 1px solid rgba(127, 127, 127, 0.4);
380
+ border-radius: 999px;
381
+ overflow: hidden;
382
+ }
383
+
384
+ .a63-editor__subtoggle-btn {
385
+ display: inline-flex;
386
+ align-items: center;
387
+ padding: 0.2rem 0.55rem;
388
+ border: none;
389
+ background: transparent;
390
+ color: inherit;
391
+ font-size: 0.72rem;
392
+ font-weight: 600;
393
+ cursor: pointer;
394
+ transition: background 0.1s ease;
395
+ }
396
+
397
+ .a63-editor__subtoggle-btn + .a63-editor__subtoggle-btn {
398
+ border-left: 1px solid rgba(127, 127, 127, 0.4);
399
+ }
400
+
401
+ .a63-editor__subtoggle-btn[aria-pressed='true'] {
402
+ background: rgba(59, 130, 246, 0.15);
403
+ color: #1d4ed8;
404
+ }
405
+
406
+ .a63-editor__subtoggle-btn:hover:not([aria-pressed='true']) {
407
+ background: rgba(127, 127, 127, 0.1);
408
+ }
409
+
410
+ /* ─── Form panel (right pane in Form mode) ───────────────────────────────── */
411
+ .a63-editor__form-panel {
412
+ flex: 1;
413
+ min-height: 0;
414
+ overflow-y: auto;
415
+ display: flex;
416
+ flex-direction: column;
417
+ }
418
+
419
+ /* Slide stepper — prev/next nav inside the form panel */
420
+ .a63-editor__slide-stepper {
421
+ display: flex;
422
+ align-items: center;
423
+ gap: 0.4rem;
424
+ padding: 0.4rem 0.75rem;
425
+ border-bottom: 1px solid rgba(127, 127, 127, 0.18);
426
+ flex: none;
427
+ }
428
+
429
+ .a63-editor__stepper-label {
430
+ flex: 1;
431
+ text-align: center;
432
+ font-size: 0.72rem;
433
+ font-weight: 600;
434
+ opacity: 0.7;
435
+ }
436
+
437
+ .a63-editor__stepper-btn {
438
+ display: inline-flex;
439
+ align-items: center;
440
+ justify-content: center;
441
+ width: 1.5rem;
442
+ height: 1.5rem;
443
+ border: 1px solid rgba(127, 127, 127, 0.4);
444
+ border-radius: 50%;
445
+ background: transparent;
446
+ color: inherit;
447
+ font-size: 1rem;
448
+ line-height: 1;
449
+ cursor: pointer;
450
+ }
451
+
452
+ .a63-editor__stepper-btn:disabled {
453
+ opacity: 0.3;
454
+ cursor: default;
455
+ }
456
+
457
+ .a63-editor__stepper-btn:not(:disabled):hover {
458
+ background: rgba(127, 127, 127, 0.1);
459
+ }
460
+
461
+ .a63-editor__form-fields {
462
+ flex: 1;
463
+ min-height: 0;
464
+ overflow-y: auto;
465
+ }
466
+
467
+ .a63-editor__form-opaque {
468
+ padding: 1rem 0.75rem;
469
+ display: flex;
470
+ flex-direction: column;
471
+ gap: 0.75rem;
472
+ }
473
+
474
+ .a63-editor__form-opaque p {
475
+ font-size: 0.82rem;
476
+ color: #555;
477
+ }
478
+
479
+ .a63-editor__form-empty {
480
+ padding: 1rem 0.75rem;
481
+ font-size: 0.82rem;
482
+ color: #555;
483
+ }