@ai-matrx/print 0.12.22 → 0.12.24

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 +1 @@
1
- {"version":3,"sources":["../src/core.ts","../src/katex-css.generated.ts","../src/math-text.ts","../src/document/math.ts","../src/flashcards.ts"],"sourcesContent":["/**\n * @ai-matrx/print/core — the Block Print contract & print-window composition.\n *\n * Ported VERBATIM from matrx-frontend `lib/block-print/block-print-utils.ts`\n * (2026-08-29 state, including the `number` and `select` PrintSetting types the\n * commerce label lane added). Zero dependencies; touches `window` only at call\n * time, so the module stays import-inert and RSC-safe.\n *\n * BlockPrinter is the only interface a host system needs to know about.\n * Each printable unit exports its own BlockPrinter alongside its render\n * component; host code only ever calls print(data, variantId, settings).\n *\n * `buildFitTextScript` is the ONE auto-shrink script (the flashcards\n * FIT_TEXT pattern, parameterized by selector so the label lane shares it).\n */\n\n// THE ONE HTML escaper (duplication census row 14). Nine copies of\n// `escapeHtml` were live across the fleet and THEY DID NOT ESCAPE THE SAME\n// CHARACTERS — a string safe through one was an attribute break-out through\n// another. It moved to `@ai-matrx/kit/html-escape` with a decided character\n// set: an HTML escaper must not require this package's jspdf + html2canvas\n// graph. Not re-exported from here; import `@ai-matrx/kit/html-escape`.\nimport { downloadFile } from \"@ai-matrx/kit/download\";\nimport { escapeHtml } from \"@ai-matrx/kit/html-escape\";\nimport { KATEX_CSS, KATEX_CSS_MARKER } from \"./katex-css.generated\";\n\n// ─── Contract ────────────────────────────────────────────────────────────────\n\nexport interface PrintVariant {\n id: string;\n label: string;\n description?: string;\n}\n\n/**\n * A single user-configurable setting shown in the print dialog.\n * Extend the discriminated union for new control types\n * without breaking existing printers.\n */\nexport type PrintSetting =\n | {\n type: \"boolean\";\n id: string;\n label: string;\n description?: string;\n /** Default value shown when the dialog first opens */\n defaultValue: boolean;\n /** Which variant IDs this setting applies to. Omit = applies to all. */\n appliesTo?: string[];\n }\n | {\n /**\n * A numeric \"from → to\" range control, rendered as two small number inputs.\n * Writes two entries into the resolved PrintSettings map — one under fromId,\n * one under toId. Printers interpret 0 (or missing) as \"no bound\" by convention.\n */\n type: \"range\";\n /** Stable id used as React key — not written to the settings map */\n id: string;\n label: string;\n description?: string;\n /** Settings map key for the lower bound value */\n fromId: string;\n /** Settings map key for the upper bound value */\n toId: string;\n defaultFrom: number;\n defaultTo: number;\n /** Minimum accepted value for each input (inclusive). Default 0. */\n min?: number;\n /** Placeholder shown when the input is empty (0). Default \"start\" / \"end\". */\n fromPlaceholder?: string;\n toPlaceholder?: string;\n /** Which variant IDs this setting applies to. Omit = applies to all. */\n appliesTo?: string[];\n }\n | {\n /** A single numeric input (e.g. \"start at label #\", \"copies\"). */\n type: \"number\";\n id: string;\n label: string;\n description?: string;\n defaultValue: number;\n /** Minimum accepted value (inclusive). Default 0. */\n min?: number;\n /** Maximum accepted value (inclusive). Omit = unbounded. */\n max?: number;\n /** Which variant IDs this setting applies to. Omit = applies to all. */\n appliesTo?: string[];\n }\n | {\n /** A one-of-N choice rendered as a compact segmented row. */\n type: \"select\";\n id: string;\n label: string;\n description?: string;\n options: { value: string; label: string }[];\n defaultValue: string;\n /** Which variant IDs this setting applies to. Omit = applies to all. */\n appliesTo?: string[];\n };\n\n/** A resolved map of setting id → current value, passed to print(). */\nexport type PrintSettings = Record<string, boolean | string | number>;\n\n/**\n * The self-contained print capability that each block can export.\n * Host code only ever calls print(data, variantId, settings) — it never inspects internals.\n */\nexport interface BlockPrinter {\n /** Tooltip / aria-label for the print button in the block header */\n label: string;\n\n /**\n * Variants offered to the user before printing.\n * Empty array = no options dialog shown; prints immediately with defaults.\n */\n variants: PrintVariant[];\n\n /**\n * Optional user-configurable settings shown below the variant picker.\n * Omit entirely if the block has no configurable settings.\n */\n settings?: PrintSetting[];\n\n /**\n * Execute the print.\n * data — the block's structured data (serverData or parsed content)\n * variantId — which variant was chosen (undefined = default / no variants)\n * settings — resolved setting values chosen by the user\n *\n * MAY resolve to a PrintOutcome (\"opened\" | \"downloaded\") so the host can\n * tell the user when the popup-blocked download fallback fired; `void`\n * stays legal for printers that don't go through a print window.\n */\n print: (\n data: unknown,\n variantId?: string,\n settings?: PrintSettings,\n ) => void | PrintOutcome | Promise<void | PrintOutcome>;\n\n /**\n * The block as it appears INSIDE a composed print (a message's Print / Full\n * Print, a document export): the same adapter the block's own print uses,\n * returning body HTML or a picture. A frame block (an HTML page) answers\n * with an image — a composed print window runs no author scripts — or an\n * honest `{ notice }` when no picture can be made. Absent ⇒ the composer\n * prints the block's source.\n */\n toPrintHtml?: (data: unknown, context: PrintBlockContext) => PrintBlockOutput | null | Promise<PrintBlockOutput | null>;\n}\n\n/** Where a block sits when a composer asks for its print form. */\nexport interface PrintBlockContext {\n /** The registry key the block was found under (artifact type or `__kind`). */\n readonly type: string;\n /** The block's raw source text, exactly as it sits in the message. */\n readonly raw: string;\n /** The block's title, when the wire form carries one. */\n readonly title?: string | undefined;\n /** The wire form's attributes (an `<artifact>`'s `id`, `version`) — how a printer finds the block's record. */\n readonly attributes?: Readonly<Record<string, string>> | undefined;\n}\n\n/**\n * A block's print form inside a composed print. `html` is trusted body HTML\n * from a printer (escape what you interpolate); `image` is a picture (a\n * captured page, a drawn diagram); `notice` is an honest line printed in the\n * block's place when neither exists.\n */\nexport type PrintBlockOutput =\n | {\n readonly html: string;\n /**\n * The block's stylesheet. A composed print sanitizes block HTML (no\n * `<style>`, no inline styles, only `matrx-*` / `print-*` classes), so a\n * printer that styles its layout passes its CSS here: the composer puts\n * each distinct sheet once in the document head. Scope it under your own\n * `matrx-…` class.\n */\n readonly css?: string | undefined;\n }\n | { readonly image: { readonly src: string; readonly alt?: string | undefined; readonly caption?: string | undefined } }\n | { readonly notice: string };\n\n// ─── The ONE block-printer registry ───────────────────────────────────────────\n//\n// Keyed by artifact type / `__kind`. A block's own print button, the canvas\n// tab's Print and the message composer all read the same entry, so a block\n// has one print adapter with every caller. Lives on globalThis under a\n// Symbol.for key so duplicated bundles (ESM + CJS) share one registry.\n\nconst PRINTER_REGISTRY = Symbol.for(\"ai-matrx.print.block-printers\");\n\nfunction printerRegistry(): Map<string, BlockPrinter> {\n const g = globalThis as unknown as { [PRINTER_REGISTRY]?: Map<string, BlockPrinter> };\n g[PRINTER_REGISTRY] ??= new Map();\n return g[PRINTER_REGISTRY];\n}\n\n/** Registers `printer` under each key (artifact type and/or `__kind`). Returns the unregister. */\nexport function registerBlockPrinter(keys: string | readonly string[], printer: BlockPrinter): () => void {\n const map = printerRegistry();\n const list = (typeof keys === \"string\" ? [keys] : keys).map((key) => key.toLowerCase());\n for (const key of list) map.set(key, printer);\n return () => {\n for (const key of list) if (map.get(key) === printer) map.delete(key);\n };\n}\n\n/** The printer registered for an artifact type or `__kind` (case-insensitive). */\nexport function getBlockPrinter(key: string | null | undefined): BlockPrinter | undefined {\n return key ? printerRegistry().get(key.toLowerCase()) : undefined;\n}\n\n/** Every registered key — for guards and diagnostics. */\nexport function listBlockPrinterKeys(): string[] {\n return [...printerRegistry().keys()];\n}\n\n/** The HTML a composer puts in a block's place for a `PrintBlockOutput`. */\nexport function printBlockOutputHtml(output: PrintBlockOutput): string {\n if (\"html\" in output) return `<div class=\"matrx-print-block\">${output.html}</div>`;\n if (\"image\" in output) {\n const { src, alt, caption } = output.image;\n return `<figure class=\"matrx-print-block matrx-print-image\"><img src=\"${escapeHtml(src)}\" alt=\"${escapeHtml(\n alt ?? \"Picture\",\n )}\" style=\"max-width:100%;height:auto\">${caption ? `<figcaption>${escapeHtml(caption)}</figcaption>` : \"\"}</figure>`;\n }\n return `<p class=\"matrx-print-block matrx-print-notice\" style=\"border:1px dashed #94a3b8;border-radius:6px;padding:10px 12px;color:#475569;font-style:italic\">${escapeHtml(\n output.notice,\n )}</p>`;\n}\n\n// ─── Shared Utilities ─────────────────────────────────────────────────────────\n\nconst PRINT_STYLES = `\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;\n font-size: 11pt;\n line-height: 1.6;\n color: #1a1a1a;\n background: #fff;\n max-width: 860px;\n margin: 0 auto;\n padding: 28px 36px;\n }\n\n .print-actions {\n display: flex;\n gap: 10px;\n margin-bottom: 24px;\n padding-bottom: 18px;\n border-bottom: 2px solid #e5e7eb;\n }\n\n .print-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 8px 20px;\n border-radius: 8px;\n font-size: 13px;\n font-family: inherit;\n cursor: pointer;\n border: none;\n font-weight: 600;\n }\n\n .print-btn-primary { background: #2563eb; color: #fff; }\n .print-btn-secondary { background: #f3f4f6; color: #374151; border: 1px solid #d1d5db; }\n\n h1 { font-size: 22pt; font-weight: 700; margin-bottom: 0.5em; }\n h2 { font-size: 16pt; font-weight: 700; margin: 1.2em 0 0.4em; border-bottom: 1px solid #e5e7eb; padding-bottom: 0.2em; }\n h3 { font-size: 13pt; font-weight: 600; margin: 1em 0 0.3em; }\n p { margin-bottom: 0.7em; }\n\n table {\n width: 100%;\n border-collapse: collapse;\n margin: 1em 0;\n font-size: 10pt;\n }\n th {\n background: #1e293b;\n color: #f8fafc;\n font-weight: 600;\n text-align: left;\n padding: 7px 12px;\n border: 1px solid #334155;\n }\n td {\n padding: 6px 12px;\n border: 1px solid #e2e8f0;\n vertical-align: top;\n }\n tr:nth-child(even) td { background: #f8fafc; }\n\n @media print {\n body { max-width: 100%; padding: 0; }\n .print-actions { display: none !important; }\n th { -webkit-print-color-adjust: exact; print-color-adjust: exact; }\n }\n`;\n\n/**\n * Wrap body HTML in a complete, styled print document.\n */\nexport function buildPrintDocument(\n bodyHtml: string,\n title = \"AI Response\",\n extraStyles = \"\",\n): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${escapeHtml(title)}</title>\n <style>\n${PRINT_STYLES}\n${extraStyles}\n </style>\n</head>\n<body>\n <div class=\"print-actions\">\n <button class=\"print-btn print-btn-primary\" onclick=\"window.print()\">Print / Save as PDF</button>\n <button class=\"print-btn print-btn-secondary\" onclick=\"window.close()\">Close</button>\n </div>\n <div class=\"content\">\n${bodyHtml}\n </div>\n</body>\n</html>`;\n}\n\n/**\n * How a print request actually reached the user.\n *\n * \"opened\" — a print window/tab was opened and the document written into it.\n * \"downloaded\" — the popup was blocked, so the document was downloaded as an\n * .html file instead (the contract's fallback). Hosts SHOULD\n * surface this to the user (e.g. a toast saying the print file\n * was downloaded) — a blocked popup plus a silent download is\n * indistinguishable from \"nothing happened\", which is exactly\n * how QA filed it (2026-08-30, F5/F6/F8).\n */\nexport type PrintOutcome = \"opened\" | \"downloaded\";\n\n/**\n * Open a new browser window and write a complete HTML document into it.\n * Falls back to downloading an .html file if the popup is blocked.\n *\n * Returns which path was taken so callers can tell the user when the\n * fallback fired. `windowFeatures` customizes the popup geometry (the\n * flashcards fixed-geometry variants pass a wider window).\n */\n/**\n * Every printed document leaves through here, so this is where typeset math\n * gets its stylesheet: when the page carries KaTeX output and does not already\n * carry the sheet, it is added to <head>. A page with no math is untouched,\n * byte for byte (the flashcard golden master holds every printer to that).\n */\nexport function withMathStyles(htmlDocument: string): string {\n if (!htmlDocument.includes('class=\"katex\"') || htmlDocument.includes(KATEX_CSS_MARKER)) {\n return htmlDocument;\n }\n const style = `<style data-matrx-katex>${KATEX_CSS}</style>`;\n const head = htmlDocument.search(/<\\/head>/i);\n return head >= 0\n ? htmlDocument.slice(0, head) + style + htmlDocument.slice(head)\n : style + htmlDocument;\n}\n\nexport function openPrintWindow(\n rawDocument: string,\n filename = \"print\",\n windowFeatures = \"width=920,height=720,scrollbars=yes\",\n): PrintOutcome {\n const htmlDocument = withMathStyles(rawDocument);\n const win = window.open(\"\", \"_blank\", windowFeatures);\n // Popup blocked — fall back to file download.\n if (!win) return openPrintWindowFallback(htmlDocument, filename);\n win.document.open();\n win.document.write(htmlDocument);\n win.document.close();\n return \"opened\";\n}\n\n/** A print window opened before its document is ready (see `openPendingPrintWindow`). */\nexport interface PendingPrintWindow {\n /** Write the finished document; downloads it when the popup was blocked or closed. */\n write(htmlDocument: string): PrintOutcome;\n}\n\n/**\n * Open the print window NOW — inside the click, where popup blockers allow it —\n * and write the document later, once async preparation (drawing diagrams) is\n * done. The window says \"Preparing…\" meanwhile.\n */\nexport function openPendingPrintWindow(\n filename = \"print\",\n windowFeatures = \"width=920,height=720,scrollbars=yes\",\n): PendingPrintWindow {\n const win = window.open(\"\", \"_blank\", windowFeatures);\n if (win) {\n win.document.open();\n win.document.write(\n '<!DOCTYPE html><html><head><meta charset=\"UTF-8\"><title>Preparing…</title></head><body style=\"font:14px -apple-system,BlinkMacSystemFont,sans-serif;color:#555;padding:2rem\">Preparing the document for print…</body></html>',\n );\n win.document.close();\n }\n return {\n write(rawDocument: string): PrintOutcome {\n const htmlDocument = withMathStyles(rawDocument);\n if (!win || win.closed) return openPrintWindowFallback(htmlDocument, filename);\n win.document.open();\n win.document.write(htmlDocument);\n win.document.close();\n return \"opened\";\n },\n };\n}\n\nfunction openPrintWindowFallback(htmlDocument: string, filename: string): PrintOutcome {\n downloadFile(`${filename.replace(/\\s+/g, \"-\").toLowerCase()}.html`, htmlDocument, \"text/html;charset=utf-8\");\n return \"downloaded\";\n}\n\n/**\n * Shorthand: build the document and open it in one call.\n * Returns the same PrintOutcome as openPrintWindow.\n */\nexport function printHtmlContent(\n bodyHtml: string,\n title = \"AI Response\",\n extraStyles = \"\",\n): PrintOutcome {\n const doc = buildPrintDocument(bodyHtml, title, extraStyles);\n return openPrintWindow(doc, title);\n}\n\n/**\n * Build the auto-shrink script that runs INSIDE a printed window: for every\n * element matching each selector, its font-size is reduced (0.5pt steps, 6pt\n * floor) until it fits its parent element's box. This is the flashcards\n * FIT_TEXT behavior verbatim, parameterized by selector so every lane\n * (flashcards card text, Avery text, label captions) shares the ONE loop.\n *\n * Pass the same selectors the original used to get byte-identical behavior:\n * flashcards passes `[\".card-text .card-text-inner\", \".avery-text-wrap .avery-text\"]`.\n */\nexport function buildFitTextScript(selectors: readonly string[]): string {\n const selectorsJson = JSON.stringify([...selectors]);\n return `\n<script>\n(function() {\n function fitText(el, container) {\n // container is the bounding box we must fit inside\n var maxW = container.clientWidth;\n var maxH = container.clientHeight;\n var size = parseFloat(window.getComputedStyle(el).fontSize);\n var minSize = 6;\n // Give the element a chance to reflow at current size before measuring\n el.style.fontSize = size + \"pt\";\n while (size > minSize && (el.scrollWidth > maxW + 1 || el.scrollHeight > maxH + 1)) {\n size -= 0.5;\n el.style.fontSize = size + \"pt\";\n }\n }\n function runAll() {\n var selectors = ${selectorsJson};\n for (var s = 0; s < selectors.length; s++) {\n var els = document.querySelectorAll(selectors[s]);\n for (var i = 0; i < els.length; i++) {\n fitText(els[i], els[i].parentElement);\n }\n }\n }\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", runAll);\n } else {\n // document.write pages may already be loaded\n setTimeout(runAll, 0);\n }\n})();\n</script>`;\n}\n\n\n// ─── DOM print: a rendered body, as it looks ─────────────────────────────────\n\nexport interface PrintElementOptions {\n /** The print window's title (and the download fallback's file name). */\n title?: string;\n /** Open the system print dialog once the window has loaded. Default true. */\n autoPrint?: boolean;\n /** Keep buttons, toolbars and menus in the print (default false: they are chrome, not content). */\n keepInteractive?: boolean;\n}\n\n/**\n * Interactive chrome that is not content: anything marked `data-print-hide`,\n * toolbars and menus, and buttons (\"Chart this\", \"Save to\", icon actions).\n * A button stays when it IS content — marked `data-print-keep`, or a disclosure\n * trigger (`aria-expanded`) whose label is a heading — and so does anything\n * inside a `data-print-keep` region. Removal runs on the print clone only.\n */\nconst PRINT_CHROME_SELECTOR = [\n \"[data-print-hide]\",\n '[role=\"toolbar\"]',\n '[role=\"menu\"]',\n '[role=\"menubar\"]',\n \"button\",\n '[role=\"button\"]',\n 'input[type=\"button\"]',\n 'input[type=\"submit\"]',\n 'input[type=\"reset\"]',\n].join(\",\");\n\nexport function stripInteractiveChrome(root: ParentNode): number {\n let removed = 0;\n root.querySelectorAll(PRINT_CHROME_SELECTOR).forEach((node) => {\n if (!root.contains(node)) return; // already gone with a removed ancestor\n const explicit = node.hasAttribute(\"data-print-hide\");\n if (!explicit) {\n if (node.closest(\"[data-print-keep]\")) return;\n if (node.matches(\"button, [role=\\\"button\\\"]\") && node.hasAttribute(\"aria-expanded\")) return;\n }\n node.remove();\n removed += 1;\n });\n return removed;\n}\n\nconst ELEMENT_PRINT_CSS = `\n html, body { height: auto !important; min-height: 0 !important; overflow: visible !important; background: #fff !important; }\n body { margin: 0; padding: 16px; }\n *, *::before, *::after { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; }\n .matrx-print-root, .matrx-print-root * { max-height: none !important; }\n .matrx-print-root [class*=\"overflow-\"], .matrx-print-root [class*=\"h-full\"], .matrx-print-root [class*=\"min-h-0\"] { overflow: visible !important; height: auto !important; }\n .matrx-print-frame-notice { border: 1px dashed #94a3b8; border-radius: 6px; padding: 10px 12px; color: #475569; font: italic 12px sans-serif; }\n @media print { .matrx-print-root * { break-inside: auto; } img, svg, figure, tr { break-inside: avoid; } }\n`;\n\n/**\n * Prints a same-origin DOM body exactly as it is drawn: the element's markup\n * with the page's own stylesheets, light theme, backgrounds kept, scroll\n * containers opened to full height. A `<canvas>` prints as its picture; an\n * `<iframe>` cannot be copied and prints an honest notice in its place (a\n * frame kind declares its own print instead). Opens inside the click.\n */\nexport function printElement(element: HTMLElement, options: PrintElementOptions = {}): PrintOutcome {\n const title = options.title ?? (document.title || \"Print\");\n const head = [...document.querySelectorAll<HTMLLinkElement | HTMLStyleElement>('link[rel=\"stylesheet\"], style')]\n .map((node) => {\n if (node instanceof HTMLLinkElement) return `<link rel=\"stylesheet\" href=\"${escapeHtml(node.href)}\">`;\n return `<style>${node.textContent ?? \"\"}</style>`;\n })\n .join(\"\\n\");\n const clone = element.cloneNode(true) as HTMLElement;\n const liveCanvases = element.querySelectorAll(\"canvas\");\n clone.querySelectorAll(\"canvas\").forEach((canvas, i) => {\n const live = liveCanvases[i];\n const img = document.createElement(\"img\");\n try {\n img.src = live ? live.toDataURL(\"image/png\") : \"\";\n } catch {\n img.alt = \"This drawing cannot be copied for print.\";\n }\n img.style.maxWidth = \"100%\";\n canvas.replaceWith(img);\n });\n clone.querySelectorAll(\"iframe\").forEach((frame) => {\n const note = document.createElement(\"p\");\n note.className = \"matrx-print-frame-notice\";\n note.textContent = \"An embedded page is here — print it from its own Print.\";\n frame.replaceWith(note);\n });\n clone.querySelectorAll(\"script\").forEach((node) => node.remove());\n if (options.keepInteractive !== true) stripInteractiveChrome(clone);\n const htmlClass = document.documentElement.className.replace(/\\bdark\\b/g, \"light\");\n const autoPrint = options.autoPrint ?? true;\n const doc = `<!DOCTYPE html><html lang=\"en\" class=\"${escapeHtml(htmlClass)}\"><head><meta charset=\"UTF-8\"><base href=\"${escapeHtml(\n document.baseURI,\n )}\"><title>${escapeHtml(title)}</title>${head}<style>${ELEMENT_PRINT_CSS}</style></head><body><div class=\"matrx-print-root\">${\n clone.outerHTML\n }</div>${\n autoPrint\n ? '<script>window.addEventListener(\"load\",function(){setTimeout(function(){window.focus();window.print();},250);});</script>'\n : \"\"\n }</body></html>`;\n return openPrintWindow(doc, title);\n}\n","// GENERATED by scripts/gen-katex-css.mjs from katex@0.18.9 — do not edit.\nexport const KATEX_VERSION = \"0.18.9\";\n/** Leads KATEX_CSS; lets openPrintWindow see the sheet is already present. */\nexport const KATEX_CSS_MARKER = \"/* matrx-katex@0.18.9 */\";\nexport const KATEX_CSS = \"/* matrx-katex@0.18.9 */@font-face{font-display:block;font-family:KaTeX_AMS;font-style:normal;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_AMS-Regular.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_AMS-Regular.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_AMS-Regular.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Caligraphic;font-style:normal;font-weight:700;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Caligraphic-Bold.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Caligraphic-Bold.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Caligraphic-Bold.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Caligraphic;font-style:normal;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Caligraphic-Regular.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Caligraphic-Regular.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Caligraphic-Regular.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Fraktur;font-style:normal;font-weight:700;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Fraktur-Bold.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Fraktur-Bold.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Fraktur-Bold.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Fraktur;font-style:normal;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Fraktur-Regular.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Fraktur-Regular.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Fraktur-Regular.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Main;font-style:normal;font-weight:700;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Main-Bold.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Main-Bold.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Main-Bold.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Main;font-style:italic;font-weight:700;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Main-BoldItalic.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Main-BoldItalic.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Main-BoldItalic.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Main;font-style:italic;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Main-Italic.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Main-Italic.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Main-Italic.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Main;font-style:normal;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Main-Regular.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Main-Regular.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Main-Regular.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Math;font-style:italic;font-weight:700;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Math-BoldItalic.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Math-BoldItalic.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Math-BoldItalic.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Math;font-style:italic;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Math-Italic.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Math-Italic.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Math-Italic.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:\\\"KaTeX_SansSerif\\\";font-style:normal;font-weight:700;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_SansSerif-Bold.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_SansSerif-Bold.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_SansSerif-Bold.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:\\\"KaTeX_SansSerif\\\";font-style:italic;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_SansSerif-Italic.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_SansSerif-Italic.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_SansSerif-Italic.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:\\\"KaTeX_SansSerif\\\";font-style:normal;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_SansSerif-Regular.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_SansSerif-Regular.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_SansSerif-Regular.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Script;font-style:normal;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Script-Regular.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Script-Regular.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Script-Regular.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Size1;font-style:normal;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Size1-Regular.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Size1-Regular.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Size1-Regular.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Size2;font-style:normal;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Size2-Regular.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Size2-Regular.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Size2-Regular.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Size3;font-style:normal;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Size3-Regular.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Size3-Regular.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Size3-Regular.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Size4;font-style:normal;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Size4-Regular.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Size4-Regular.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Size4-Regular.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Typewriter;font-style:normal;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Typewriter-Regular.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Typewriter-Regular.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Typewriter-Regular.ttf) format(\\\"truetype\\\")}.katex{font:normal 1.21em KaTeX_Main,Times New Roman,serif;line-height:1.2;position:relative;text-indent:0;text-rendering:auto}.katex *{-ms-high-contrast-adjust:none!important;border-color:currentColor}.katex .katex-version:after{content:\\\"0.18.9\\\"}.katex .katex-mathml{border:0;-webkit-clip-path:inset(50%);clip-path:inset(50%);height:1px;overflow:hidden;padding:0;position:absolute;width:1px}.katex .katex-html>.katex-newline{display:block}.katex .katex-base{position:relative;white-space:nowrap;width:-webkit-min-content;width:-moz-min-content;width:min-content}.katex .katex-base,.katex .katex-strut{display:inline-block}.katex .textbf{font-weight:700}.katex .textit{font-style:italic}.katex .textrm{font-family:KaTeX_Main}.katex .textsf{font-family:KaTeX_SansSerif}.katex .texttt{font-family:KaTeX_Typewriter}.katex .mathnormal{font-family:KaTeX_Math;font-style:italic}.katex .mathit{font-family:KaTeX_Main;font-style:italic}.katex .mathrm{font-style:normal}.katex .mathbf{font-family:KaTeX_Main;font-weight:700}.katex .boldsymbol{font-family:KaTeX_Math;font-style:italic;font-weight:700}.katex .amsrm,.katex .mathbb,.katex .textbb{font-family:KaTeX_AMS}.katex .mathcal{font-family:KaTeX_Caligraphic}.katex .mathfrak,.katex .textfrak{font-family:KaTeX_Fraktur}.katex .mathboldfrak,.katex .textboldfrak{font-family:KaTeX_Fraktur;font-weight:700}.katex .mathtt{font-family:KaTeX_Typewriter}.katex .mathscr,.katex .textscr{font-family:KaTeX_Script}.katex .mathsf,.katex .textsf{font-family:KaTeX_SansSerif}.katex .mathboldsf,.katex .textboldsf{font-family:KaTeX_SansSerif;font-weight:700}.katex .mathitsf,.katex .mathsfit,.katex .textitsf{font-family:KaTeX_SansSerif;font-style:italic}.katex .mainrm{font-family:KaTeX_Main;font-style:normal}.katex .vlist-t{border-collapse:collapse;display:inline-table;table-layout:fixed}.katex .vlist-r{display:table-row}.katex .vlist{display:table-cell;position:relative;vertical-align:bottom}.katex .vlist>span{display:block;height:0;position:relative}.katex .vlist>span>span{display:inline-block}.katex .vlist>span>.pstrut{overflow:hidden;width:0}.katex .vlist-t2{margin-right:-2px}.katex .vlist-s{display:table-cell;font-size:1px;min-width:2px;vertical-align:bottom;width:2px}.katex .katex-vbox{align-items:baseline;display:inline-flex;flex-direction:column}.katex .katex-thinbox{display:inline-flex;flex-direction:row;max-width:0;width:0}.katex .msupsub{text-align:left}.katex .mfrac>span>span{text-align:center}.katex .mfrac .frac-line{border-bottom-style:solid;display:inline-block;width:100%}.katex .katex-hdashline,.katex .katex-hline,.katex .katex-overline .overline-line,.katex .katex-rule,.katex .katex-underline .underline-line,.katex .mfrac .frac-line{min-height:1px}.katex .mspace{display:inline-block}.katex .katex-smash{display:inline;line-height:0}.katex .clap,.katex .llap,.katex .rlap{position:relative;width:0}.katex .clap>.katex-inner,.katex .llap>.katex-inner,.katex .rlap>.katex-inner{position:absolute}.katex .clap>.katex-fix,.katex .llap>.katex-fix,.katex .rlap>.katex-fix{display:inline-block}.katex .llap>.katex-inner{right:0}.katex .clap>.katex-inner,.katex .rlap>.katex-inner{left:0}.katex .clap>.katex-inner>span{margin-left:-50%;margin-right:50%}.katex .katex-rule{border:0 solid;display:inline-block;position:relative}.katex .katex-hline,.katex .katex-overline .overline-line,.katex .katex-underline .underline-line{border-bottom-style:solid;display:inline-block;width:100%}.katex .katex-hdashline{border-bottom-style:dashed;display:inline-block;width:100%}.katex .sqrt>.katex-root{margin-left:.2777777778em;margin-right:-.5555555556em}.katex .fontsize-ensurer.reset-size1.size1,.katex .katex-sizing.reset-size1.size1{font-size:1em}.katex .fontsize-ensurer.reset-size1.size2,.katex .katex-sizing.reset-size1.size2{font-size:1.2em}.katex .fontsize-ensurer.reset-size1.size3,.katex .katex-sizing.reset-size1.size3{font-size:1.4em}.katex .fontsize-ensurer.reset-size1.size4,.katex .katex-sizing.reset-size1.size4{font-size:1.6em}.katex .fontsize-ensurer.reset-size1.size5,.katex .katex-sizing.reset-size1.size5{font-size:1.8em}.katex .fontsize-ensurer.reset-size1.size6,.katex .katex-sizing.reset-size1.size6{font-size:2em}.katex .fontsize-ensurer.reset-size1.size7,.katex .katex-sizing.reset-size1.size7{font-size:2.4em}.katex .fontsize-ensurer.reset-size1.size8,.katex .katex-sizing.reset-size1.size8{font-size:2.88em}.katex .fontsize-ensurer.reset-size1.size9,.katex .katex-sizing.reset-size1.size9{font-size:3.456em}.katex .fontsize-ensurer.reset-size1.size10,.katex .katex-sizing.reset-size1.size10{font-size:4.148em}.katex .fontsize-ensurer.reset-size1.size11,.katex .katex-sizing.reset-size1.size11{font-size:4.976em}.katex .fontsize-ensurer.reset-size2.size1,.katex .katex-sizing.reset-size2.size1{font-size:.8333333333em}.katex .fontsize-ensurer.reset-size2.size2,.katex .katex-sizing.reset-size2.size2{font-size:1em}.katex .fontsize-ensurer.reset-size2.size3,.katex .katex-sizing.reset-size2.size3{font-size:1.1666666667em}.katex .fontsize-ensurer.reset-size2.size4,.katex .katex-sizing.reset-size2.size4{font-size:1.3333333333em}.katex .fontsize-ensurer.reset-size2.size5,.katex .katex-sizing.reset-size2.size5{font-size:1.5em}.katex .fontsize-ensurer.reset-size2.size6,.katex .katex-sizing.reset-size2.size6{font-size:1.6666666667em}.katex .fontsize-ensurer.reset-size2.size7,.katex .katex-sizing.reset-size2.size7{font-size:2em}.katex .fontsize-ensurer.reset-size2.size8,.katex .katex-sizing.reset-size2.size8{font-size:2.4em}.katex .fontsize-ensurer.reset-size2.size9,.katex .katex-sizing.reset-size2.size9{font-size:2.88em}.katex .fontsize-ensurer.reset-size2.size10,.katex .katex-sizing.reset-size2.size10{font-size:3.4566666667em}.katex .fontsize-ensurer.reset-size2.size11,.katex .katex-sizing.reset-size2.size11{font-size:4.1466666667em}.katex .fontsize-ensurer.reset-size3.size1,.katex .katex-sizing.reset-size3.size1{font-size:.7142857143em}.katex .fontsize-ensurer.reset-size3.size2,.katex .katex-sizing.reset-size3.size2{font-size:.8571428571em}.katex .fontsize-ensurer.reset-size3.size3,.katex .katex-sizing.reset-size3.size3{font-size:1em}.katex .fontsize-ensurer.reset-size3.size4,.katex .katex-sizing.reset-size3.size4{font-size:1.1428571429em}.katex .fontsize-ensurer.reset-size3.size5,.katex .katex-sizing.reset-size3.size5{font-size:1.2857142857em}.katex .fontsize-ensurer.reset-size3.size6,.katex .katex-sizing.reset-size3.size6{font-size:1.4285714286em}.katex .fontsize-ensurer.reset-size3.size7,.katex .katex-sizing.reset-size3.size7{font-size:1.7142857143em}.katex .fontsize-ensurer.reset-size3.size8,.katex .katex-sizing.reset-size3.size8{font-size:2.0571428571em}.katex .fontsize-ensurer.reset-size3.size9,.katex .katex-sizing.reset-size3.size9{font-size:2.4685714286em}.katex .fontsize-ensurer.reset-size3.size10,.katex .katex-sizing.reset-size3.size10{font-size:2.9628571429em}.katex .fontsize-ensurer.reset-size3.size11,.katex .katex-sizing.reset-size3.size11{font-size:3.5542857143em}.katex .fontsize-ensurer.reset-size4.size1,.katex .katex-sizing.reset-size4.size1{font-size:.625em}.katex .fontsize-ensurer.reset-size4.size2,.katex .katex-sizing.reset-size4.size2{font-size:.75em}.katex .fontsize-ensurer.reset-size4.size3,.katex .katex-sizing.reset-size4.size3{font-size:.875em}.katex .fontsize-ensurer.reset-size4.size4,.katex .katex-sizing.reset-size4.size4{font-size:1em}.katex .fontsize-ensurer.reset-size4.size5,.katex .katex-sizing.reset-size4.size5{font-size:1.125em}.katex .fontsize-ensurer.reset-size4.size6,.katex .katex-sizing.reset-size4.size6{font-size:1.25em}.katex .fontsize-ensurer.reset-size4.size7,.katex .katex-sizing.reset-size4.size7{font-size:1.5em}.katex .fontsize-ensurer.reset-size4.size8,.katex .katex-sizing.reset-size4.size8{font-size:1.8em}.katex .fontsize-ensurer.reset-size4.size9,.katex .katex-sizing.reset-size4.size9{font-size:2.16em}.katex .fontsize-ensurer.reset-size4.size10,.katex .katex-sizing.reset-size4.size10{font-size:2.5925em}.katex .fontsize-ensurer.reset-size4.size11,.katex .katex-sizing.reset-size4.size11{font-size:3.11em}.katex .fontsize-ensurer.reset-size5.size1,.katex .katex-sizing.reset-size5.size1{font-size:.5555555556em}.katex .fontsize-ensurer.reset-size5.size2,.katex .katex-sizing.reset-size5.size2{font-size:.6666666667em}.katex .fontsize-ensurer.reset-size5.size3,.katex .katex-sizing.reset-size5.size3{font-size:.7777777778em}.katex .fontsize-ensurer.reset-size5.size4,.katex .katex-sizing.reset-size5.size4{font-size:.8888888889em}.katex .fontsize-ensurer.reset-size5.size5,.katex .katex-sizing.reset-size5.size5{font-size:1em}.katex .fontsize-ensurer.reset-size5.size6,.katex .katex-sizing.reset-size5.size6{font-size:1.1111111111em}.katex .fontsize-ensurer.reset-size5.size7,.katex .katex-sizing.reset-size5.size7{font-size:1.3333333333em}.katex .fontsize-ensurer.reset-size5.size8,.katex .katex-sizing.reset-size5.size8{font-size:1.6em}.katex .fontsize-ensurer.reset-size5.size9,.katex .katex-sizing.reset-size5.size9{font-size:1.92em}.katex .fontsize-ensurer.reset-size5.size10,.katex .katex-sizing.reset-size5.size10{font-size:2.3044444444em}.katex .fontsize-ensurer.reset-size5.size11,.katex .katex-sizing.reset-size5.size11{font-size:2.7644444444em}.katex .fontsize-ensurer.reset-size6.size1,.katex .katex-sizing.reset-size6.size1{font-size:.5em}.katex .fontsize-ensurer.reset-size6.size2,.katex .katex-sizing.reset-size6.size2{font-size:.6em}.katex .fontsize-ensurer.reset-size6.size3,.katex .katex-sizing.reset-size6.size3{font-size:.7em}.katex .fontsize-ensurer.reset-size6.size4,.katex .katex-sizing.reset-size6.size4{font-size:.8em}.katex .fontsize-ensurer.reset-size6.size5,.katex .katex-sizing.reset-size6.size5{font-size:.9em}.katex .fontsize-ensurer.reset-size6.size6,.katex .katex-sizing.reset-size6.size6{font-size:1em}.katex .fontsize-ensurer.reset-size6.size7,.katex .katex-sizing.reset-size6.size7{font-size:1.2em}.katex .fontsize-ensurer.reset-size6.size8,.katex .katex-sizing.reset-size6.size8{font-size:1.44em}.katex .fontsize-ensurer.reset-size6.size9,.katex .katex-sizing.reset-size6.size9{font-size:1.728em}.katex .fontsize-ensurer.reset-size6.size10,.katex .katex-sizing.reset-size6.size10{font-size:2.074em}.katex .fontsize-ensurer.reset-size6.size11,.katex .katex-sizing.reset-size6.size11{font-size:2.488em}.katex .fontsize-ensurer.reset-size7.size1,.katex .katex-sizing.reset-size7.size1{font-size:.4166666667em}.katex .fontsize-ensurer.reset-size7.size2,.katex .katex-sizing.reset-size7.size2{font-size:.5em}.katex .fontsize-ensurer.reset-size7.size3,.katex .katex-sizing.reset-size7.size3{font-size:.5833333333em}.katex .fontsize-ensurer.reset-size7.size4,.katex .katex-sizing.reset-size7.size4{font-size:.6666666667em}.katex .fontsize-ensurer.reset-size7.size5,.katex .katex-sizing.reset-size7.size5{font-size:.75em}.katex .fontsize-ensurer.reset-size7.size6,.katex .katex-sizing.reset-size7.size6{font-size:.8333333333em}.katex .fontsize-ensurer.reset-size7.size7,.katex .katex-sizing.reset-size7.size7{font-size:1em}.katex .fontsize-ensurer.reset-size7.size8,.katex .katex-sizing.reset-size7.size8{font-size:1.2em}.katex .fontsize-ensurer.reset-size7.size9,.katex .katex-sizing.reset-size7.size9{font-size:1.44em}.katex .fontsize-ensurer.reset-size7.size10,.katex .katex-sizing.reset-size7.size10{font-size:1.7283333333em}.katex .fontsize-ensurer.reset-size7.size11,.katex .katex-sizing.reset-size7.size11{font-size:2.0733333333em}.katex .fontsize-ensurer.reset-size8.size1,.katex .katex-sizing.reset-size8.size1{font-size:.3472222222em}.katex .fontsize-ensurer.reset-size8.size2,.katex .katex-sizing.reset-size8.size2{font-size:.4166666667em}.katex .fontsize-ensurer.reset-size8.size3,.katex .katex-sizing.reset-size8.size3{font-size:.4861111111em}.katex .fontsize-ensurer.reset-size8.size4,.katex .katex-sizing.reset-size8.size4{font-size:.5555555556em}.katex .fontsize-ensurer.reset-size8.size5,.katex .katex-sizing.reset-size8.size5{font-size:.625em}.katex .fontsize-ensurer.reset-size8.size6,.katex .katex-sizing.reset-size8.size6{font-size:.6944444444em}.katex .fontsize-ensurer.reset-size8.size7,.katex .katex-sizing.reset-size8.size7{font-size:.8333333333em}.katex .fontsize-ensurer.reset-size8.size8,.katex .katex-sizing.reset-size8.size8{font-size:1em}.katex .fontsize-ensurer.reset-size8.size9,.katex .katex-sizing.reset-size8.size9{font-size:1.2em}.katex .fontsize-ensurer.reset-size8.size10,.katex .katex-sizing.reset-size8.size10{font-size:1.4402777778em}.katex .fontsize-ensurer.reset-size8.size11,.katex .katex-sizing.reset-size8.size11{font-size:1.7277777778em}.katex .fontsize-ensurer.reset-size9.size1,.katex .katex-sizing.reset-size9.size1{font-size:.2893518519em}.katex .fontsize-ensurer.reset-size9.size2,.katex .katex-sizing.reset-size9.size2{font-size:.3472222222em}.katex .fontsize-ensurer.reset-size9.size3,.katex .katex-sizing.reset-size9.size3{font-size:.4050925926em}.katex .fontsize-ensurer.reset-size9.size4,.katex .katex-sizing.reset-size9.size4{font-size:.462962963em}.katex .fontsize-ensurer.reset-size9.size5,.katex .katex-sizing.reset-size9.size5{font-size:.5208333333em}.katex .fontsize-ensurer.reset-size9.size6,.katex .katex-sizing.reset-size9.size6{font-size:.5787037037em}.katex .fontsize-ensurer.reset-size9.size7,.katex .katex-sizing.reset-size9.size7{font-size:.6944444444em}.katex .fontsize-ensurer.reset-size9.size8,.katex .katex-sizing.reset-size9.size8{font-size:.8333333333em}.katex .fontsize-ensurer.reset-size9.size9,.katex .katex-sizing.reset-size9.size9{font-size:1em}.katex .fontsize-ensurer.reset-size9.size10,.katex .katex-sizing.reset-size9.size10{font-size:1.2002314815em}.katex .fontsize-ensurer.reset-size9.size11,.katex .katex-sizing.reset-size9.size11{font-size:1.4398148148em}.katex .fontsize-ensurer.reset-size10.size1,.katex .katex-sizing.reset-size10.size1{font-size:.2410800386em}.katex .fontsize-ensurer.reset-size10.size2,.katex .katex-sizing.reset-size10.size2{font-size:.2892960463em}.katex .fontsize-ensurer.reset-size10.size3,.katex .katex-sizing.reset-size10.size3{font-size:.337512054em}.katex .fontsize-ensurer.reset-size10.size4,.katex .katex-sizing.reset-size10.size4{font-size:.3857280617em}.katex .fontsize-ensurer.reset-size10.size5,.katex .katex-sizing.reset-size10.size5{font-size:.4339440694em}.katex .fontsize-ensurer.reset-size10.size6,.katex .katex-sizing.reset-size10.size6{font-size:.4821600771em}.katex .fontsize-ensurer.reset-size10.size7,.katex .katex-sizing.reset-size10.size7{font-size:.5785920926em}.katex .fontsize-ensurer.reset-size10.size8,.katex .katex-sizing.reset-size10.size8{font-size:.6943105111em}.katex .fontsize-ensurer.reset-size10.size9,.katex .katex-sizing.reset-size10.size9{font-size:.8331726133em}.katex .fontsize-ensurer.reset-size10.size10,.katex .katex-sizing.reset-size10.size10{font-size:1em}.katex .fontsize-ensurer.reset-size10.size11,.katex .katex-sizing.reset-size10.size11{font-size:1.1996142719em}.katex .fontsize-ensurer.reset-size11.size1,.katex .katex-sizing.reset-size11.size1{font-size:.2009646302em}.katex .fontsize-ensurer.reset-size11.size2,.katex .katex-sizing.reset-size11.size2{font-size:.2411575563em}.katex .fontsize-ensurer.reset-size11.size3,.katex .katex-sizing.reset-size11.size3{font-size:.2813504823em}.katex .fontsize-ensurer.reset-size11.size4,.katex .katex-sizing.reset-size11.size4{font-size:.3215434084em}.katex .fontsize-ensurer.reset-size11.size5,.katex .katex-sizing.reset-size11.size5{font-size:.3617363344em}.katex .fontsize-ensurer.reset-size11.size6,.katex .katex-sizing.reset-size11.size6{font-size:.4019292605em}.katex .fontsize-ensurer.reset-size11.size7,.katex .katex-sizing.reset-size11.size7{font-size:.4823151125em}.katex .fontsize-ensurer.reset-size11.size8,.katex .katex-sizing.reset-size11.size8{font-size:.578778135em}.katex .fontsize-ensurer.reset-size11.size9,.katex .katex-sizing.reset-size11.size9{font-size:.6945337621em}.katex .fontsize-ensurer.reset-size11.size10,.katex .katex-sizing.reset-size11.size10{font-size:.8336012862em}.katex .fontsize-ensurer.reset-size11.size11,.katex .katex-sizing.reset-size11.size11{font-size:1em}.katex .delimsizing.size1{font-family:KaTeX_Size1}.katex .delimsizing.size2{font-family:KaTeX_Size2}.katex .delimsizing.size3{font-family:KaTeX_Size3}.katex .delimsizing.size4{font-family:KaTeX_Size4}.katex .delimsizing.mult .delim-size1>span{font-family:KaTeX_Size1}.katex .delimsizing.mult .delim-size4>span{font-family:KaTeX_Size4}.katex .nulldelimiter{display:inline-block;width:.12em}.katex .delimcenter,.katex .op-symbol{position:relative}.katex .op-symbol.small-op{font-family:KaTeX_Size1}.katex .op-symbol.large-op{font-family:KaTeX_Size2}.katex .katex-accent>.vlist-t,.katex .op-limits>.vlist-t{text-align:center}.katex .katex-accent .accent-body{position:relative}.katex .katex-accent .accent-body:not(.accent-full){width:0}.katex .katex-overlay{display:block}.katex .mtable .vertical-separator{display:inline-block;min-width:1px}.katex .mtable .arraycolsep{display:inline-block}.katex .mtable .col-align-c>.vlist-t{text-align:center}.katex .mtable .col-align-l>.vlist-t{text-align:left}.katex .mtable .col-align-r>.vlist-t{text-align:right}.katex .svg-align{text-align:left}.katex svg{fill:currentColor;stroke:currentColor;display:block;height:inherit;position:absolute;width:100%}.katex svg path{stroke:none}.katex svg{fill-rule:nonzero;fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1}.katex img{border-style:none;max-height:none;max-width:none;min-height:0;min-width:0}.katex .katex-stretchy{display:block;overflow:hidden;position:relative;width:100%}.katex .katex-stretchy:after,.katex .katex-stretchy:before{content:\\\"\\\"}.katex .hide-tail{overflow:hidden;position:relative;width:100%}.katex .halfarrow-left{left:0;overflow:hidden;position:absolute;width:50.2%}.katex .halfarrow-right{overflow:hidden;position:absolute;right:0;width:50.2%}.katex .brace-left{left:0;overflow:hidden;position:absolute;width:25.1%}.katex .brace-center{left:25%;overflow:hidden;position:absolute;width:50%}.katex .brace-right{overflow:hidden;position:absolute;right:0;width:25.1%}.katex .x-arrow-pad{padding:0 .5em}.katex .cd-arrow-pad{padding:0 .55556em 0 .27778em}.katex .mover,.katex .munder,.katex .x-arrow{text-align:center}.katex .boxpad{padding:0 .3em}.katex .fbox,.katex .fcolorbox{border:.04em solid;box-sizing:border-box}.katex .cancel-pad{padding:0 .2em}.katex .cancel-lap{margin-left:-.2em;margin-right:-.2em}.katex .katex-sout{border-bottom-style:solid;border-bottom-width:.08em}.katex .angl{border-right:.049em solid;border-top:.049em solid;box-sizing:border-box;margin-right:.03889em}.katex .anglpad{padding:0 .03889em}.katex .reflectbox{display:inline-block;transform:scaleX(-1)}.katex .eqn-num:before{content:\\\"(\\\" counter(katexEqnNo) \\\")\\\";counter-increment:katexEqnNo}.katex .mml-eqn-num:before{content:\\\"(\\\" counter(mmlEqnNo) \\\")\\\";counter-increment:mmlEqnNo}.katex .mtr-glue{width:50%}.katex .cd-vert-arrow{display:inline-block;position:relative}.katex .cd-label-left{display:inline-block;position:absolute;right:calc(50% + .3em);text-align:left}.katex .cd-label-right{display:inline-block;left:calc(50% + .3em);position:absolute;text-align:right}.katex-display{display:block;margin:1em 0;text-align:center}.katex-display>.katex{display:block;text-align:center;white-space:nowrap}.katex-display>.katex>.katex-html{display:block;position:relative}.katex-display>.katex>.katex-html>.katex-tag{position:absolute;right:0}.katex-display.leqno>.katex>.katex-html>.katex-tag{left:0;right:auto}.katex-display.fleqn>.katex{padding-left:2em;text-align:left}body{counter-reset:katexEqnNo mmlEqnNo}\\n\";\n","/**\n * Plain text with math → safe HTML: THE one way a printer turns authored text\n * (a flashcard face, an exam prompt, a glossary definition) into markup.\n *\n * Every non-markdown printer used to `escapeHtml()` its text, so `\\(\\text{IO}_3^-\\)`\n * on a flashcard printed as that literal source (Arman, 2026-09-27). What is math\n * is decided by the shared tokenizer (`extractMath` — the same rule the screen,\n * the markdown lane and the /document pipeline use: code and `$5` prices are\n * never math); everything else is escaped exactly as before.\n *\n * Typeset output is KaTeX's laid-out HTML plus MathML, chemistry (mhchem)\n * loaded. KaTeX runs with `trust: false`, which refuses every command that could\n * inject HTML (\\href, \\url, \\htmlClass, \\htmlId, \\htmlStyle, \\htmlData,\n * \\includegraphics). The stylesheet is added by `openPrintWindow` whenever a\n * document contains math, so no printer can forget it.\n */\nimport katex from \"katex\";\n// Registers \\ce{…} and \\pu{…} on the same katex instance.\nimport \"katex/contrib/mhchem\";\nimport { escapeHtml } from \"@ai-matrx/kit/html-escape\";\nimport { extractMath, MATH_TOKEN_RE } from \"./document/math\";\n\n/** Typeset one TeX span — the ONE KaTeX configuration every print lane uses. */\nexport function typesetTex(tex: string, display: boolean): string {\n return katex.renderToString(tex, {\n displayMode: display,\n output: \"htmlAndMathml\",\n throwOnError: false,\n trust: false,\n strict: \"ignore\",\n });\n}\n\n/**\n * Escape `text` for HTML, with every math span typeset. Newlines are kept as\n * `\\n` in the prose so callers that turn them into `<br>` keep doing so; a\n * newline inside a formula is folded to a space (TeX treats it as one).\n */\nexport function mathTextHtml(text: string | null | undefined): string {\n // The tokenizer's own markers can never come from an author.\n const source = (text ?? \"\").replace(/[\\uE100\\uE101]/g, \"\");\n if (!/[$\\\\]/.test(source)) return escapeHtml(source);\n const { text: prose, spans } = extractMath(source);\n if (spans.length === 0) return escapeHtml(source);\n return escapeHtml(prose).replace(MATH_TOKEN_RE, (_m: string, i: string) => {\n const span = spans[Number(i)];\n return span ? typesetTex(span.tex.replace(/\\s*\\n\\s*/g, \" \"), span.display) : \"\";\n });\n}\n\n/**\n * A field that IS a formula (a cheat-sheet \"formula\" slot): delimited math is\n * typeset as usual; undelimited TeX (it has a command, `^` or `_`) is typeset\n * whole as inline math; anything else is escaped text.\n */\nexport function formulaHtml(formula: string | null | undefined): string {\n const source = (formula ?? \"\").trim();\n const withMath = mathTextHtml(source);\n if (withMath.includes('class=\"katex\"')) return withMath;\n return /\\\\[a-zA-Z]|[\\^_]/.test(source) ? typesetTex(source, false) : withMath;\n}\n","/**\n * Math in documents — ONE math dialect, typeset in every format.\n *\n * What is math is decided ONLY by the shared tokenizer (`@ai-matrx/content-ir/\n * source`): its fence-nesting rule finds code, its pairing finds `$$…$$`,\n * `$…$` (math, not currency), `\\[…\\]` and `\\(…\\)`. Math is lifted out\n * BEFORE markdown parsing, so a TeX `\\,` is never eaten as a markdown escape\n * and `_`/`*` inside a formula never become emphasis.\n *\n * Typesetting: MathJax (TeX → its internal tree) produces\n * - SVG for PDF display math (drawn as vector paths; allow-list sanitized),\n * - MathML for HTML and EPUB (rendered natively by browsers and readers),\n * - MathML that the Word writer turns into native OMML equations and the\n * PDF writer turns into inline text with real super/subscripts.\n */\n\nimport { listIslands, tokenizeSource } from \"@ai-matrx/content-ir/source\";\nimport { sanitizeMathml, sanitizeMathSvg } from \"../safe-html\";\n\nexport const MATH_OPEN = \"\\uE100\";\nexport const MATH_CLOSE = \"\\uE101\";\nexport const MATH_TOKEN_RE = /\\uE100(\\d+)\\uE101/g;\n\nexport interface MathSpan {\n tex: string;\n display: boolean;\n}\n\nconst DELIMITERS: Record<string, { open: number; close: number; display: boolean }> = {\n $$: { open: 2, close: 2, display: true },\n \"\\\\[\": { open: 2, close: 2, display: true },\n $: { open: 1, close: 1, display: false },\n \"\\\\(\": { open: 2, close: 2, display: false },\n};\n\n/**\n * Replace every math span with a private-use token; returns the spans.\n *\n * Print keeps NO fence, code-span or math detection of its own: the spans are\n * the shared tokenizer's complete `math_block` / `math_inline` islands\n * (`tokenizeSource` + `listIslands` from `@ai-matrx/content-ir/source` — the\n * one fence-nesting rule and the one math pairing), so print, the renderer and\n * the editor can never disagree about what is math. The delimiter decides the\n * form: `$$`/`\\[` display, `$`/`\\(` inline.\n */\nexport function extractMath(body: string): { text: string; spans: MathSpan[] } {\n const spans: MathSpan[] = [];\n if (!/[$\\\\]/.test(body)) return { text: body, spans };\n const islands = listIslands(tokenizeSource(body))\n .filter((isl) => isl.complete && (isl.islandType === \"math_block\" || isl.islandType === \"math_inline\"))\n .sort((a, b) => a.start - b.start);\n let out = \"\";\n let at = 0;\n for (const isl of islands) {\n const d = DELIMITERS[String(isl.meta.delimiter ?? \"\")];\n if (!d || isl.start < at) continue;\n out += body.slice(at, isl.start);\n spans.push({ tex: body.slice(isl.start + d.open, isl.end - d.close).trim(), display: d.display });\n out += `${MATH_OPEN}${spans.length - 1}${MATH_CLOSE}`;\n at = isl.end;\n }\n return { text: out + body.slice(at), spans };\n}\n\n// ── typesetting ────────────────────────────────────────────────────────────\n\nexport interface TypesetMath {\n /** MathML (<math …>…</math>) for HTML/EPUB and the Word/PDF-inline writers. */\n mathml: string;\n /** SVG for PDF display math; widths in ex (1ex ≈ 0.442em in MathJax). */\n svg?: string | undefined;\n widthEx?: number | undefined;\n heightEx?: number | undefined;\n /** Error message when the TeX could not be typeset. */\n error?: string | undefined;\n}\n\nexport const mathKey = (tex: string, display: boolean): string => `${display ? \"D\" : \"I\"}:${tex}`;\n\ntype MathJaxKit = import(\"../mathjax\").MathJaxKit;\n\nconst MATHJAX = Symbol.for(\"ai-matrx.print.document.mathjax\");\n\nasync function loadMathJax(): Promise<MathJaxKit> {\n const g = globalThis as unknown as Record<symbol, Promise<MathJaxKit> | undefined>;\n if (g[MATHJAX]) return g[MATHJAX] as Promise<MathJaxKit>;\n // The self-referencing specifier keeps this a real lazy edge in dist (see src/mathjax.ts).\n const pending = import(\"@ai-matrx/print/mathjax\").then((m) => m.createMathJaxKit());\n pending.catch(() => {\n if (g[MATHJAX] === pending) g[MATHJAX] = undefined;\n });\n g[MATHJAX] = pending;\n return pending;\n}\n\n/** Typeset every math span once; keyed by `mathKey`. */\nexport async function typesetMathSpans(spans: MathSpan[]): Promise<Map<string, TypesetMath>> {\n const out = new Map<string, TypesetMath>();\n if (spans.length === 0) return out;\n const mj = await loadMathJax();\n for (const s of spans) {\n const key = mathKey(s.tex, s.display);\n if (out.has(key)) continue;\n try {\n const mathml = mj.mml(s.tex, s.display);\n const err = /<merror[\\s\\S]*?<mtext[^>]*>([\\s\\S]*?)<\\/mtext>/.exec(mathml);\n if (err) {\n out.set(key, { mathml: sanitizeMathml(mathml), error: decodeXml(err[1] as string) });\n continue;\n }\n // TeX is author input: `\\href`, `\\style`, `\\bbox[…]`, `\\mmlToken` reach\n // MathML attributes, so the typeset MathML goes through the allow-list.\n const entry: TypesetMath = { mathml: sanitizeMathml(mathml.replace(/\\n\\s*/g, \"\")) };\n if (s.display) {\n // The same author TeX reaches the SVG as <a href>, style, class and id\n // (LIST T26): only the drawing survives.\n const svg = sanitizeMathSvg(mj.svg(s.tex, true));\n entry.svg = svg;\n entry.widthEx = Number(/width=\"([\\d.]+)ex\"/.exec(svg)?.[1] ?? 0);\n entry.heightEx = Number(/height=\"([\\d.]+)ex\"/.exec(svg)?.[1] ?? 0);\n }\n out.set(key, entry);\n } catch (e) {\n out.set(key, { mathml: \"\", error: (e as Error).message });\n }\n }\n return out;\n}\n\n// ── a small MathML reader (MathJax's serializer output is well-formed XML) ──\n\nexport interface MNode {\n tag: string;\n attrs: Record<string, string>;\n children: MNode[];\n text: string;\n}\n\nexport function decodeXml(s: string): string {\n return s.replace(/&(#x[0-9a-f]+|#\\d+|amp|lt|gt|quot|apos);/gi, (m, e: string) => {\n if (e[0] === \"#\") return String.fromCodePoint(e[1] === \"x\" || e[1] === \"X\" ? parseInt(e.slice(2), 16) : Number(e.slice(1)));\n return ({ amp: \"&\", lt: \"<\", gt: \">\", quot: '\"', apos: \"'\" } as Record<string, string>)[e.toLowerCase()] ?? m;\n });\n}\n\nexport function parseMathML(xml: string): MNode {\n const root: MNode = { tag: \"#root\", attrs: {}, children: [], text: \"\" };\n const stack: MNode[] = [root];\n for (const m of xml.matchAll(/<(\\/?)([\\w:-]+)((?:\\s+[\\w:-]+=\"[^\"]*\")*)\\s*(\\/?)>|([^<]+)/g)) {\n const top = stack[stack.length - 1] as MNode;\n if (m[5] !== undefined) {\n const t = decodeXml(m[5]);\n if (t.trim() || top.tag === \"mtext\" || top.tag === \"mo\") top.text += t;\n continue;\n }\n if (m[1] === \"/\") {\n if (stack.length > 1) stack.pop();\n continue;\n }\n const attrs: Record<string, string> = {};\n for (const a of (m[3] ?? \"\").matchAll(/([\\w:-]+)=\"([^\"]*)\"/g)) attrs[a[1] as string] = decodeXml(a[2] as string);\n const node: MNode = { tag: (m[2] as string).replace(/^m:/, \"\"), attrs, children: [], text: \"\" };\n top.children.push(node);\n if (m[4] !== \"/\") stack.push(node);\n }\n return root.children[0] ?? root;\n}\n\n/** Flatten to plain text (the linear reading of a formula). */\nexport function mathText(n: MNode): string {\n if (n.children.length === 0) return n.text;\n return n.children.map(mathText).join(\"\");\n}\n","/**\n * FlashcardsBlock — BlockPrinter\n *\n * Self-contained print logic for flashcard blocks.\n * Exported alongside the component so it travels with the block to the database.\n *\n * Variants:\n * cut-cards — 4-per-page grid with physical cut lines; designed to be printed, cut, and used as real cards\n * both-sides — 2-column grid showing front + back together (review/overview)\n * study-sheet — compact table (front | back) for quick reference or cramming\n * front-only — questions/terms only, blank backs for self-testing\n * back-only — answers/definitions only\n */\n\nimport {\n buildPrintDocument,\n openPrintWindow,\n buildFitTextScript,\n type BlockPrinter,\n type PrintOutcome,\n type PrintSettings,\n} from \"./core\";\nimport { mathTextHtml } from \"./math-text\";\nimport { escapeHtml } from \"@ai-matrx/kit/html-escape\";\n\n/**\n * A single flashcard. Structural port of the host parser's `Flashcard`\n * (matrx-frontend `flashcard-parser.ts`) — identical fields, no import.\n */\nexport interface Flashcard {\n front: string;\n back: string;\n /** Optional face images (durable URLs — fc_detail front_image/back_image).\n * Parsed markdown never sets these; DB-backed callers may. Print + rich\n * renderers consume them when present. */\n frontImageUrl?: string | null;\n frontImageAlt?: string | null;\n backImageUrl?: string | null;\n backImageAlt?: string | null;\n}\n\n/**\n * Structural stand-in for the host's generated `FlashcardsBlockData` — the\n * package takes no generated-type dependency; `extractCards` reads `cards`\n * structurally from whatever the host hands it.\n */\ninterface FlashcardsBlockData {\n cards?: unknown;\n title?: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// Settings helpers\n// ---------------------------------------------------------------------------\n\n/** Resolved settings with all defaults filled in — safe to use without null checks */\ninterface CardPrintSettings {\n showSideLabel: boolean; // \"Front\" / \"Back\" label\n showCardNumber: boolean; // \"#1\", \"#2\" … badge\n showTickMarks: boolean; // 4 small tick marks at cut intersection\n showCutLines: boolean; // full dashed crossing lines\n lightBackText: boolean; // print back text in dark gray (~#555) to reduce show-through\n mirrorBack: boolean; // horizontally flip back page text so bleed-through is unreadable\n showImages: boolean; // print face images (durable URLs only) — default ON\n}\n\nconst SETTING_DEFAULTS: CardPrintSettings = {\n showSideLabel: false,\n showCardNumber: false,\n showTickMarks: false,\n showCutLines: false,\n lightBackText: false,\n mirrorBack: false,\n showImages: true,\n};\n\nfunction resolveSettings(raw?: PrintSettings): CardPrintSettings {\n if (!raw) return { ...SETTING_DEFAULTS };\n const b = (key: keyof CardPrintSettings) =>\n raw[key] !== undefined ? Boolean(raw[key]) : SETTING_DEFAULTS[key];\n return {\n showSideLabel: b(\"showSideLabel\"),\n showCardNumber: b(\"showCardNumber\"),\n showTickMarks: b(\"showTickMarks\"),\n showCutLines: b(\"showCutLines\"),\n lightBackText: b(\"lightBackText\"),\n mirrorBack: b(\"mirrorBack\"),\n showImages: b(\"showImages\"),\n };\n}\n\nexport type FlashcardsVariant =\n | \"landscape-duplex\"\n | \"landscape-stacked\"\n | \"portrait-6up-duplex\"\n | \"portrait-6up-stacked\"\n | \"avery-5388\"\n | \"cut-cards\"\n | \"both-sides\"\n | \"study-sheet\"\n | \"front-only\"\n | \"back-only\";\n\ntype FlashcardsData = { cards: Flashcard[] } | FlashcardsBlockData | string;\n\nfunction toCard(entry: unknown): Flashcard | null {\n if (!entry || typeof entry !== \"object\") return null;\n const e = entry as Record<string, unknown>;\n if (typeof e.front === \"string\") return entry as Flashcard;\n const front = e.question ?? e.term;\n if (typeof front !== \"string\") return null;\n const back = e.back ?? e.answer ?? e.definition;\n return { ...(entry as object), front, back: typeof back === \"string\" ? back : \"\" } as Flashcard;\n}\n\n/** Cards from `{ cards }` or a bare array; anything else is not a deck this printer can read. */\nfunction extractCards(data: unknown): Flashcard[] {\n if (!data) return [];\n const list = Array.isArray(data)\n ? data\n : typeof data === \"object\" && \"cards\" in data\n ? (data as { cards: unknown }).cards\n : null;\n if (!Array.isArray(list)) return [];\n return list.map(toCard).filter((card): card is Flashcard => card !== null);\n}\n\n/**\n * Face image for print — durable URL only (the print window is a fresh\n * document; it can load exactly what a browser can fetch unauthenticated).\n * Alt text prints in place when a hotlink has rotted. The custom-geometry\n * variants (landscape / 6-up / Avery) stay text-only by design — their fixed\n * cells can't absorb an image without breaking the die-cut math.\n */\nfunction faceImageHtml(\n card: Flashcard,\n face: \"front\" | \"back\",\n showImages = true,\n): string {\n if (!showImages) return \"\";\n const url = face === \"front\" ? card.frontImageUrl : card.backImageUrl;\n if (!url || !/^https?:\\/\\//.test(url)) return \"\";\n const alt = (face === \"front\" ? card.frontImageAlt : card.backImageAlt) ?? \"\";\n return `<img class=\"face-image\" src=\"${escapeHtml(url)}\" alt=\"${escapeHtml(alt)}\">`;\n}\n\n// ---------------------------------------------------------------------------\n// Shared base styles — appended after block-print-utils PRINT_STYLES so these win on specificity\n// ---------------------------------------------------------------------------\nconst BASE_STYLES = `\n img.face-image {\n display: block;\n max-width: 100%;\n max-height: 1.6in;\n margin: 0 auto 6px auto;\n object-fit: contain;\n }\n h1.deck-title {\n font-size: 13pt !important;\n font-weight: 700;\n color: #1e293b;\n margin: 0 0 4px 0;\n letter-spacing: -0.01em;\n border-bottom: none !important;\n }\n h2 { border-bottom: none !important; }\n .deck-meta {\n font-size: 8.5pt;\n color: #64748b;\n margin-bottom: 16px;\n }\n .deck-header {\n border-bottom: 1.5px solid #e2e8f0;\n padding-bottom: 10px;\n margin-bottom: 16px;\n }\n\n @media print {\n @page { margin: 14mm 12mm; }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Variant: Cut-out physical cards (4 per page, front face + scissor guides)\n// ---------------------------------------------------------------------------\nconst CUT_CARD_STYLES = `\n ${BASE_STYLES}\n\n .instructions {\n font-size: 8pt;\n color: #64748b;\n background: #f8fafc;\n border: 1px solid #e2e8f0;\n border-radius: 6px;\n padding: 7px 10px;\n margin-bottom: 14px;\n line-height: 1.5;\n }\n\n .cards-grid {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 0;\n }\n\n .card-cell {\n position: relative;\n border: 1px dashed #cbd5e1;\n padding: 0;\n min-height: 120px;\n }\n\n /* Cut-line corner markers */\n .card-cell::before, .card-cell::after {\n content: \"\";\n position: absolute;\n width: 8px;\n height: 8px;\n border-color: #94a3b8;\n border-style: solid;\n }\n .card-cell::before { top: 3px; left: 3px; border-width: 1px 0 0 1px; }\n .card-cell::after { bottom: 3px; right: 3px; border-width: 0 1px 1px 0; }\n\n .card-inner {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 120px;\n }\n\n .card-face {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n padding: 12px 14px;\n text-align: center;\n }\n\n .card-front-face {\n background: linear-gradient(135deg, #1e3a5f 0%, #1e40af 100%);\n color: #ffffff;\n border-radius: 0;\n }\n .card-back-face {\n background: #ffffff;\n border-top: 1.5px solid #e2e8f0;\n }\n\n .card-side-label {\n font-size: 7pt;\n font-weight: 700;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n margin-bottom: 6px;\n opacity: 0.65;\n }\n .card-front-face .card-side-label { color: #bfdbfe; }\n .card-back-face .card-side-label { color: #94a3b8; }\n\n .card-number-badge {\n position: absolute;\n top: 6px;\n right: 8px;\n font-size: 7pt;\n color: #94a3b8;\n font-weight: 600;\n }\n\n .front-text {\n font-size: 11pt;\n font-weight: 600;\n line-height: 1.4;\n color: #ffffff;\n }\n .front-text.long { font-size: 9.5pt; }\n .front-text.xlong { font-size: 8.5pt; }\n\n .back-text {\n font-size: 10pt;\n line-height: 1.5;\n color: #1a1a1a;\n white-space: pre-line;\n text-align: left;\n width: 100%;\n }\n .back-text.long { font-size: 9pt; }\n .back-text.xlong { font-size: 8pt; }\n\n .fold-hint {\n font-size: 7pt;\n color: #94a3b8;\n text-align: center;\n margin-top: 10px;\n letter-spacing: 0.04em;\n }\n\n .page-break { page-break-before: always; }\n\n @media print {\n .card-front-face { -webkit-print-color-adjust: exact; print-color-adjust: exact; }\n .card-cell { page-break-inside: avoid; break-inside: avoid; }\n .instructions { display: none; }\n }\n`;\n\nfunction sizeClass(text: string): string {\n const len = text.length;\n if (len > 200) return \"xlong\";\n if (len > 100) return \"long\";\n return \"\";\n}\n\nfunction renderCutCards(\n cards: Flashcard[],\n title: string,\n s: CardPrintSettings,\n startIndex = 0,\n): string {\n const PAGE_SIZE = 4; // cards per page\n const pages: string[] = [];\n\n for (let p = 0; p < Math.ceil(cards.length / PAGE_SIZE); p++) {\n const batch = cards.slice(p * PAGE_SIZE, (p + 1) * PAGE_SIZE);\n\n const cells = batch\n .map((card, i) => {\n const globalIdx = startIndex + p * PAGE_SIZE + i + 1;\n const frontSize = sizeClass(card.front);\n const backSize = sizeClass(card.back ?? \"\");\n const backContent = (card.back ?? \"\")\n .split(\"\\n\")\n .map((line) => mathTextHtml(line))\n .join(\"<br>\");\n\n return `<div class=\"card-cell\">\n <span class=\"card-number-badge\">#${globalIdx}</span>\n <div class=\"card-inner\">\n <div class=\"card-face card-front-face\">\n <div class=\"card-side-label\">Question / Term</div>\n ${faceImageHtml(card, \"front\", s.showImages)}<div class=\"front-text ${frontSize}\">${mathTextHtml(card.front)}</div>\n </div>\n <div class=\"card-face card-back-face\">\n <div class=\"card-side-label\">Answer / Definition</div>\n ${faceImageHtml(card, \"back\", s.showImages)}<div class=\"back-text ${backSize}\">${backContent}</div>\n </div>\n </div>\n</div>`;\n })\n .join(\"\\n\");\n\n const isFirst = p === 0;\n const pageClass = isFirst ? \"\" : ' class=\"page-break\"';\n pages.push(`<div${pageClass}>\n${\n isFirst\n ? `<div class=\"deck-header\">\n <h1 class=\"deck-title\">${mathTextHtml(title)}</h1>\n <div class=\"deck-meta\">${cards.length} card${cards.length !== 1 ? \"s\" : \"\"} &nbsp;·&nbsp; Cut along dashed lines</div>\n</div>\n<div class=\"instructions\">\n ✂ Print this page, then cut along the dashed lines. Each cell is one card — front (blue) on top, answer on the bottom.\n Fold in half for a double-sided card, or cut each half separately for separate front/back cards.\n</div>`\n : `<div class=\"deck-meta\" style=\"margin-bottom:10px;\">Cards ${startIndex + p * PAGE_SIZE + 1}–${startIndex + Math.min((p + 1) * PAGE_SIZE, cards.length)}</div>`\n}\n<div class=\"cards-grid\">\n${cells}\n</div>\n<div class=\"fold-hint\">✂ cut along dashed borders · fold on the center line for double-sided cards</div>\n</div>`);\n }\n\n return pages.join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// Variant: Both sides (2-column card grid, front + back visible)\n// ---------------------------------------------------------------------------\nconst BOTH_SIDES_STYLES = `\n ${BASE_STYLES}\n\n .cards-grid {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 10px;\n }\n .card-cell {\n border: 1.5px solid #e2e8f0;\n border-radius: 8px;\n overflow: hidden;\n page-break-inside: avoid;\n break-inside: avoid;\n background: #fff;\n box-shadow: 0 1px 3px rgba(0,0,0,0.06);\n }\n .card-front {\n background: #1e293b;\n color: #f1f5f9;\n padding: 10px 14px;\n font-weight: 600;\n font-size: 10.5pt;\n line-height: 1.4;\n position: relative;\n }\n .card-front .card-number { font-size: 7.5pt; color: #64748b; margin-bottom: 4px; }\n .card-back {\n padding: 10px 14px;\n font-size: 10pt;\n line-height: 1.5;\n color: #1a1a1a;\n background: #fff;\n border-top: 1px solid #e2e8f0;\n white-space: pre-line;\n }\n\n @media print {\n .card-front { -webkit-print-color-adjust: exact; print-color-adjust: exact; }\n }\n`;\n\nfunction renderBothSides(\n cards: Flashcard[],\n title: string,\n s: CardPrintSettings,\n startIndex = 0,\n): string {\n const cells = cards\n .map(\n (card, i) => `<div class=\"card-cell\">\n <div class=\"card-front\">\n <div class=\"card-number\">Card ${startIndex + i + 1}</div>\n ${faceImageHtml(card, \"front\", s.showImages)}${mathTextHtml(card.front)}\n </div>\n <div class=\"card-back\">${faceImageHtml(card, \"back\", s.showImages)}${mathTextHtml(card.back ?? \"\")}</div>\n</div>`,\n )\n .join(\"\\n\");\n\n return `<div class=\"deck-header\">\n <h1 class=\"deck-title\">${mathTextHtml(title)}</h1>\n <div class=\"deck-meta\">${cards.length} card${cards.length !== 1 ? \"s\" : \"\"} &nbsp;·&nbsp; Front and back</div>\n</div>\n<div class=\"cards-grid\">\n${cells}\n</div>`;\n}\n\n// ---------------------------------------------------------------------------\n// Variant: Study sheet (full-width table)\n// ---------------------------------------------------------------------------\nconst STUDY_SHEET_STYLES = `\n ${BASE_STYLES}\n\n table {\n width: 100%;\n border-collapse: collapse;\n margin-top: 8px;\n font-size: 10pt;\n }\n thead th {\n background: #1e293b;\n color: #f1f5f9;\n padding: 8px 12px;\n text-align: left;\n font-size: 8.5pt;\n font-weight: 700;\n letter-spacing: 0.05em;\n text-transform: uppercase;\n }\n thead th:first-child { width: 38%; border-radius: 0; }\n\n tbody tr { page-break-inside: avoid; break-inside: avoid; }\n tbody tr:nth-child(even) td { background: #f8fafc; }\n\n tbody td {\n padding: 8px 12px;\n vertical-align: top;\n border-bottom: 1px solid #e2e8f0;\n line-height: 1.5;\n white-space: pre-line;\n }\n tbody td:first-child {\n font-weight: 600;\n border-right: 1px solid #e2e8f0;\n color: #1e3a5f;\n }\n .row-num {\n display: inline-block;\n width: 18px;\n font-size: 8pt;\n color: #94a3b8;\n font-weight: 400;\n margin-right: 4px;\n }\n\n @media print {\n thead th { -webkit-print-color-adjust: exact; print-color-adjust: exact; }\n tbody tr:nth-child(even) td { -webkit-print-color-adjust: exact; print-color-adjust: exact; }\n }\n`;\n\n/** Class names of a study sheet: the standalone print keeps its original names (golden master). */\ninterface StudySheetClasses {\n header: string;\n title: string;\n meta: string;\n rowNum: string;\n}\nconst STUDY_SHEET_LEGACY_CLASSES: StudySheetClasses = {\n header: \"deck-header\",\n title: \"deck-title\",\n meta: \"deck-meta\",\n rowNum: \"row-num\",\n};\n\nfunction renderStudySheet(\n cards: Flashcard[],\n title: string,\n s: CardPrintSettings,\n startIndex = 0,\n c: StudySheetClasses = STUDY_SHEET_LEGACY_CLASSES,\n): string {\n const rows = cards\n .map(\n (card, i) => ` <tr>\n <td><span class=\"${c.rowNum}\">${startIndex + i + 1}.</span>${faceImageHtml(card, \"front\", s.showImages)}${mathTextHtml(card.front)}</td>\n <td>${faceImageHtml(card, \"back\", s.showImages)}${mathTextHtml(card.back ?? \"\")}</td>\n </tr>`,\n )\n .join(\"\\n\");\n\n return `<div class=\"${c.header}\">\n <h1 class=\"${c.title}\">${mathTextHtml(title)}</h1>\n <div class=\"${c.meta}\">${cards.length} card${cards.length !== 1 ? \"s\" : \"\"} &nbsp;·&nbsp; Study sheet</div>\n</div>\n<table>\n <thead>\n <tr>\n <th>Question / Term</th>\n <th>Answer / Definition</th>\n </tr>\n </thead>\n <tbody>\n${rows}\n </tbody>\n</table>`;\n}\n\n/**\n * The study sheet inside a composed print (a message's Print). The composer\n * keeps only `matrx-*` classes and a `css` channel, so the sheet is scoped\n * under `matrx-fc-sheet` and every class carries the `matrx-fc-` prefix.\n */\nconst COMPOSED_CLASSES: StudySheetClasses = {\n header: \"matrx-fc-header\",\n title: \"matrx-fc-title\",\n meta: \"matrx-fc-meta\",\n rowNum: \"matrx-fc-row-num\",\n};\n\nconst COMPOSED_STUDY_SHEET_CSS = `\n .matrx-fc-sheet .matrx-fc-header { border-bottom: 1.5px solid #e2e8f0; padding-bottom: 10px; margin-bottom: 16px; }\n .matrx-fc-sheet .matrx-fc-title { font-size: 13pt; font-weight: 700; color: #1e293b; margin: 0 0 4px 0; letter-spacing: -0.01em; border-bottom: none; }\n .matrx-fc-sheet .matrx-fc-meta { font-size: 8.5pt; color: #64748b; }\n .matrx-fc-sheet table { width: 100%; border-collapse: collapse; margin-top: 8px; font-size: 10pt; }\n .matrx-fc-sheet thead th { background: #1e293b; color: #f1f5f9; padding: 8px 12px; text-align: left; font-size: 8.5pt; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; }\n .matrx-fc-sheet thead th:first-child { width: 38%; }\n .matrx-fc-sheet tbody tr { page-break-inside: avoid; break-inside: avoid; }\n .matrx-fc-sheet tbody tr:nth-child(even) td { background: #f8fafc; }\n .matrx-fc-sheet tbody td { padding: 8px 12px; vertical-align: top; border-bottom: 1px solid #e2e8f0; line-height: 1.5; white-space: pre-line; }\n .matrx-fc-sheet tbody td:first-child { font-weight: 600; border-right: 1px solid #e2e8f0; color: #1e3a5f; }\n .matrx-fc-sheet .matrx-fc-row-num { display: inline-block; width: 18px; font-size: 8pt; color: #94a3b8; font-weight: 400; margin-right: 4px; }\n .matrx-fc-sheet .matrx-fc-face-image { display: block; max-width: 100%; max-height: 1.6in; margin: 0 auto 6px auto; object-fit: contain; }\n @media print {\n .matrx-fc-sheet thead th, .matrx-fc-sheet tbody tr:nth-child(even) td { -webkit-print-color-adjust: exact; print-color-adjust: exact; }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Variant: Front only (self-test — blank space for answers)\n// ---------------------------------------------------------------------------\nconst FRONT_ONLY_STYLES = `\n ${BASE_STYLES}\n\n .cards-grid {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 10px;\n }\n .card-cell {\n border: 1.5px solid #bfdbfe;\n border-radius: 8px;\n overflow: hidden;\n page-break-inside: avoid;\n break-inside: avoid;\n background: #fff;\n }\n .card-front {\n background: linear-gradient(135deg, #1e3a5f 0%, #1e40af 100%);\n color: #ffffff;\n padding: 12px 14px;\n font-weight: 600;\n font-size: 10.5pt;\n line-height: 1.4;\n }\n .card-front .card-number { font-size: 7.5pt; color: #bfdbfe; margin-bottom: 4px; }\n .answer-blank {\n height: 52px;\n border-top: 1px dashed #cbd5e1;\n background: #fafafa;\n position: relative;\n }\n .answer-blank::after {\n content: \"write your answer here\";\n position: absolute;\n bottom: 6px;\n left: 12px;\n font-size: 7pt;\n color: #cbd5e1;\n letter-spacing: 0.04em;\n }\n\n @media print {\n .card-front { -webkit-print-color-adjust: exact; print-color-adjust: exact; }\n }\n`;\n\nfunction renderFrontOnly(\n cards: Flashcard[],\n title: string,\n s: CardPrintSettings,\n startIndex = 0,\n): string {\n const cells = cards\n .map(\n (card, i) => `<div class=\"card-cell\">\n <div class=\"card-front\">\n <div class=\"card-number\">Card ${startIndex + i + 1}</div>\n ${faceImageHtml(card, \"front\", s.showImages)}${mathTextHtml(card.front)}\n </div>\n <div class=\"answer-blank\"></div>\n</div>`,\n )\n .join(\"\\n\");\n\n return `<div class=\"deck-header\">\n <h1 class=\"deck-title\">${mathTextHtml(title)}</h1>\n <div class=\"deck-meta\">${cards.length} card${cards.length !== 1 ? \"s\" : \"\"} &nbsp;·&nbsp; Self-test — write your answers in the blank space</div>\n</div>\n<div class=\"cards-grid\">\n${cells}\n</div>`;\n}\n\n// ---------------------------------------------------------------------------\n// Variant: Back only (answers / definitions)\n// ---------------------------------------------------------------------------\nconst BACK_ONLY_STYLES = `\n ${BASE_STYLES}\n\n .cards-grid {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 10px;\n }\n .card-cell {\n border: 1.5px solid #a7f3d0;\n border-radius: 8px;\n overflow: hidden;\n page-break-inside: avoid;\n break-inside: avoid;\n background: #fff;\n }\n .card-back-header {\n background: linear-gradient(135deg, #065f46 0%, #0f766e 100%);\n color: #ecfdf5;\n padding: 10px 14px;\n font-size: 7.5pt;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n }\n .card-back-content {\n padding: 10px 14px;\n font-size: 10pt;\n line-height: 1.5;\n color: #1a1a1a;\n white-space: pre-line;\n }\n\n @media print {\n .card-back-header { -webkit-print-color-adjust: exact; print-color-adjust: exact; }\n }\n`;\n\nfunction renderBackOnly(\n cards: Flashcard[],\n title: string,\n s: CardPrintSettings,\n startIndex = 0,\n): string {\n const cells = cards\n .map(\n (card, i) => `<div class=\"card-cell\">\n <div class=\"card-back-header\">Card ${startIndex + i + 1}</div>\n <div class=\"card-back-content\">${faceImageHtml(card, \"back\", s.showImages)}${mathTextHtml(card.back ?? \"\")}</div>\n</div>`,\n )\n .join(\"\\n\");\n\n return `<div class=\"deck-header\">\n <h1 class=\"deck-title\">${mathTextHtml(title)}</h1>\n <div class=\"deck-meta\">${cards.length} card${cards.length !== 1 ? \"s\" : \"\"} &nbsp;·&nbsp; Answers and definitions only</div>\n</div>\n<div class=\"cards-grid\">\n${cells}\n</div>`;\n}\n\n// ---------------------------------------------------------------------------\n// Variant: Landscape cut sheet (4 cards per page, 2×2, black & white)\n//\n// Geometry (landscape 11\" × 8.5\"):\n// Page is split into 4 equal quadrants: 5.5\" wide × 4.25\" tall each.\n// Outer margin: 0.5\" on every edge.\n// Inner gap at each cut line: 0.5\" each side → 1\" total dead zone at center.\n// Usable card area: (5.5 − 0.5 − 0.5)\" × (4.25 − 0.5 − 0.5)\" = 4.5\" × 3.25\"\n//\n// Text sizing: font-size uses vw/vh relative units so it always fills the cell.\n// `fit-text` class uses container-relative font that shrinks when content overflows\n// via a JS resize loop in the document itself.\n//\n// Double-sided (duplex) mirroring:\n// When a landscape sheet is flipped on the long (left→right) edge,\n// left and right columns swap. So back page layout is:\n// TL = back[1], TR = back[0], BL = back[3], BR = back[2]\n// ---------------------------------------------------------------------------\n\nconst LANDSCAPE_STYLES = `\n @page { size: landscape; margin: 0; }\n\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n\n html, body {\n width: 11in;\n background: #fff;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Helvetica, Arial, sans-serif;\n color: #000;\n /* Screen: scroll to see all pages. Print: locked to one page height per .page. */\n }\n\n /* The page wrapper takes the full physical page */\n .page {\n width: 11in;\n height: 8.5in;\n position: relative;\n page-break-after: always;\n break-after: page;\n display: grid;\n grid-template-columns: 1fr 1fr;\n grid-template-rows: 1fr 1fr;\n gap: 0;\n padding: 0.5in;\n /* Thin separator visible on screen between pages */\n border-bottom: 2px solid #e2e8f0;\n }\n .page:last-child {\n page-break-after: auto;\n break-after: auto;\n border-bottom: none;\n }\n\n @media print {\n /* Do NOT constrain html/body height — that clips pages 2+.\n Page sizing is handled by @page + .page { height: 8.5in } + break-after: page. */\n html, body { width: 11in; height: auto; overflow: visible; }\n .page { border-bottom: none; }\n }\n\n /* Each quadrant cell — full 5.5\" × 4.25\" including its half-gutter */\n .quad {\n display: flex;\n align-items: stretch;\n justify-content: stretch;\n padding: 0 0.25in 0.25in 0; /* right + bottom inner half-gutters */\n }\n /* Top row: add top inner half-gutter via bottom padding of the cell above */\n .quad.top { padding-bottom: 0.25in; padding-top: 0; }\n .quad.bottom { padding-bottom: 0; padding-top: 0.25in; }\n .quad.left { padding-left: 0; padding-right: 0.25in; }\n .quad.right { padding-left: 0.25in; padding-right: 0; }\n\n /* The actual card face — no border, no lines. Cuts are the separation. */\n .card-face {\n flex: 1;\n border: none;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 0.18in 0.22in;\n overflow: hidden;\n position: relative;\n }\n\n /*\n * Cut tick marks — 4 short marks at the exact center intersection.\n * These sit on the .page element (outside all card faces).\n * Each tick is a 0.18in line. Two ticks per axis: one on each side of center.\n */\n\n /* Horizontal axis ticks: sit at vertical midpoint, on the left and right edges */\n .tick-h-left, .tick-h-right {\n position: absolute;\n top: 50%;\n width: 0.18in;\n height: 0;\n border-top: 0.6px solid #bbb;\n pointer-events: none;\n }\n .tick-h-left { left: 0.1in; }\n .tick-h-right { right: 0.1in; }\n\n /* Vertical axis ticks: sit at horizontal midpoint, on the top and bottom edges */\n .tick-v-top, .tick-v-bottom {\n position: absolute;\n left: 50%;\n height: 0.18in;\n width: 0;\n border-left: 0.6px solid #bbb;\n pointer-events: none;\n }\n .tick-v-top { top: 0.1in; }\n .tick-v-bottom { bottom: 0.1in; }\n\n /* Full dashed cut lines (optional — only rendered when showCutLines = true) */\n .cut-line-h {\n position: absolute;\n left: 0;\n right: 0;\n top: 50%;\n height: 0;\n border-top: 0.6px dashed #bbb;\n pointer-events: none;\n }\n .cut-line-v {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 50%;\n width: 0;\n border-left: 0.6px dashed #bbb;\n pointer-events: none;\n }\n\n /* Card number badge */\n .card-num {\n position: absolute;\n top: 5px;\n right: 7px;\n font-size: 6.5pt;\n color: #999;\n font-weight: 600;\n }\n\n /* Side label (FRONT / BACK) */\n .side-label {\n font-size: 6.5pt;\n font-weight: 700;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: #888;\n margin-bottom: 8px;\n align-self: flex-start;\n }\n\n /* The text container — fills remaining space, centers content */\n .card-text {\n flex: 1;\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n overflow: hidden;\n }\n\n .card-text-inner {\n font-size: 22pt; /* fitText JS shrinks this to fit — start bigger for short fronts */\n font-weight: 600;\n line-height: 1.3;\n white-space: pre-wrap;\n word-break: break-word;\n text-align: center;\n -webkit-print-color-adjust: exact;\n print-color-adjust: exact;\n }\n\n /* Multiline prose back: left-align, slightly smaller start */\n .card-text-inner.multiline {\n text-align: left;\n font-size: 15pt;\n font-weight: 400;\n line-height: 1.45;\n }\n\n /* Bullet list back */\n ul.card-text-inner.card-list {\n text-align: left;\n font-size: 15pt;\n font-weight: 400;\n line-height: 1.5;\n padding-left: 1.1em;\n margin: 0;\n list-style-type: disc;\n white-space: normal;\n width: 100%;\n }\n ul.card-text-inner.card-list li {\n margin-bottom: 0.25em;\n }\n ul.card-text-inner.card-list li:last-child {\n margin-bottom: 0;\n }\n\n /* Print-only: hide the browser action bar */\n @media print {\n .screen-only { display: none !important; }\n }\n`;\n\n/**\n * Shrink font JS — runs inside the printed window.\n * For each .card-text-inner, reduces font-size until the text fits its container.\n */\nconst FIT_TEXT_SCRIPT = buildFitTextScript([\n \".card-text .card-text-inner\",\n \".avery-text-wrap .avery-text\",\n]);\n\n/** Returns true if the majority of non-empty lines look like bullet items */\nfunction isBulletContent(text: string): boolean {\n const lines = text.split(\"\\n\").filter((l) => l.trim().length > 0);\n if (lines.length < 2) return false;\n const bulletLines = lines.filter((l) => /^\\s*[-•*]\\s/.test(l));\n return bulletLines.length >= lines.length * 0.6; // 60%+ bullet lines = treat as list\n}\n\nfunction isMultiline(text: string): boolean {\n return text.includes(\"\\n\") || text.length > 120;\n}\n\n/**\n * Render card back text as either:\n * - A bullet list (<ul><li>…</li></ul>) when content is bullet-style — left-aligned\n * - Plain centered text for normal prose\n * - Front text is always plain centered\n */\nfunction renderCardText(\n text: string,\n isFront: boolean,\n extraStyle = \"\",\n): { html: string; isList: boolean } {\n if (isFront) {\n const styleAttr = extraStyle ? ` style=\"${extraStyle}\"` : \"\";\n return {\n html: `<div class=\"card-text-inner\"${styleAttr}>${mathTextHtml(text).replace(/\\n/g, \"<br>\")}</div>`,\n isList: false,\n };\n }\n\n const bullet = isBulletContent(text);\n\n if (bullet) {\n const lines = text.split(\"\\n\");\n const items = lines\n .map((l) => l.trim())\n .filter((l) => l.length > 0)\n .map((l) => l.replace(/^[-•*]\\s*/, \"\")) // strip leading dash/bullet\n .map((l) => `<li>${mathTextHtml(l)}</li>`)\n .join(\"\");\n const styleAttr = extraStyle ? ` style=\"${extraStyle}\"` : \"\";\n return {\n html: `<ul class=\"card-text-inner card-list\"${styleAttr}>${items}</ul>`,\n isList: true,\n };\n }\n\n // Plain multiline or single-line prose\n const ml = isMultiline(text);\n const cls = ml ? \"card-text-inner multiline\" : \"card-text-inner\";\n const styleAttr = extraStyle ? ` style=\"${extraStyle}\"` : \"\";\n return {\n html: `<div class=\"${cls}\"${styleAttr}>${mathTextHtml(text).replace(/\\n/g, \"<br>\")}</div>`,\n isList: false,\n };\n}\n\nfunction buildCardFace(\n text: string,\n cardNum: number,\n isFront: boolean,\n s: CardPrintSettings,\n): string {\n const numBadge = s.showCardNumber\n ? `<span class=\"card-num\">#${cardNum}</span>`\n : \"\";\n const sideLabel = s.showSideLabel\n ? `<span class=\"side-label\">${isFront ? \"Front\" : \"Back\"}</span>`\n : \"\";\n\n const textColor = !isFront && s.lightBackText ? \"color:#777;\" : \"\";\n const mirrorXform =\n !isFront && s.mirrorBack ? \"transform:scaleX(-1);display:block;\" : \"\";\n const innerStyle = textColor + mirrorXform;\n\n const { html, isList } = renderCardText(text, isFront, innerStyle);\n // Lists align to the start so bullets sit at top-left; prose/front stays centered\n const cardTextStyle = isList ? ' style=\"align-items:flex-start;\"' : \"\";\n\n return `<div class=\"card-face\">\n ${numBadge}\n ${sideLabel}\n <div class=\"card-text\"${cardTextStyle}>\n ${html}\n </div>\n</div>`;\n}\n\nconst QUAD_CLASSES = [\n \"quad top left\",\n \"quad top right\",\n \"quad bottom left\",\n \"quad bottom right\",\n] as const;\n\nfunction buildPage(cells: string[], s: CardPrintSettings): string {\n const ticks = s.showTickMarks\n ? `<div class=\"tick-h-left\"></div>\n <div class=\"tick-h-right\"></div>\n <div class=\"tick-v-top\"></div>\n <div class=\"tick-v-bottom\"></div>`\n : \"\";\n\n const lines = s.showCutLines\n ? `<div class=\"cut-line-h\"></div>\n <div class=\"cut-line-v\"></div>`\n : \"\";\n\n return `<div class=\"page\">\n ${ticks}\n ${lines}\n ${cells.map((c, i) => `<div class=\"${QUAD_CLASSES[i]!}\">${c}</div>`).join(\"\\n \")}\n</div>`;\n}\n\n/**\n * landscape-duplex: alternating front/back pages.\n * Front page → [card1F, card2F, card3F, card4F]\n * Back page → [card2B, card1B, card4B, card3B] (mirrored L↔R for duplex flip on long edge)\n *\n * landscape-stacked: all fronts first, then all backs (same column order both pages).\n */\nfunction renderLandscape(\n cards: Flashcard[],\n title: string,\n mode: \"duplex\" | \"stacked\",\n s: CardPrintSettings,\n startIndex = 0,\n): string {\n const BATCH = 4;\n const pages: string[] = [];\n // stacked mode collects backs separately so EVERY front page precedes EVERY\n // back page — interleaving them per batch (the pre-port behavior) made the\n // single-sided re-feed workflow this variant exists for impossible.\n const backPages: string[] = [];\n const empty = `<div class=\"card-face\"></div>`;\n\n for (let p = 0; p < Math.ceil(cards.length / BATCH); p++) {\n const batch = cards.slice(p * BATCH, (p + 1) * BATCH);\n while (batch.length < BATCH) batch.push({ front: \"\", back: \"\" });\n\n const fronts = batch.map((c, i) =>\n c.front\n ? buildCardFace(c.front, startIndex + p * BATCH + i + 1, true, s)\n : empty,\n );\n const backs = batch.map((c, i) =>\n c.back\n ? buildCardFace(c.back ?? \"\", startIndex + p * BATCH + i + 1, false, s)\n : empty,\n );\n\n if (mode === \"duplex\") {\n // batch is padded to BATCH, so every slot below is present\n const backsFlipped = [backs[1]!, backs[0]!, backs[3]!, backs[2]!];\n pages.push(buildPage(fronts, s));\n pages.push(buildPage(backsFlipped, s));\n } else {\n pages.push(buildPage(fronts, s));\n backPages.push(buildPage(backs, s));\n }\n }\n\n return [...pages, ...backPages].join(\"\\n\");\n}\n\n/**\n * Landscape cards need a fully custom document — no buildPrintDocument wrapper\n * because that adds max-width, padding, and chrome that fights the precise page geometry.\n */\nfunction openLandscapeWindow(bodyHtml: string, title: string): PrintOutcome {\n const html = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>${escapeHtml(title)}</title>\n <style>${LANDSCAPE_STYLES}</style>\n</head>\n<body>\n<div class=\"screen-only\" style=\"font-family:sans-serif;font-size:12px;padding:12px 16px;background:#f8fafc;border-bottom:1px solid #e2e8f0;display:flex;gap:10px;align-items:center;\">\n <button onclick=\"window.print()\" style=\"padding:7px 18px;background:#111;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;\">Print / Save PDF</button>\n <button onclick=\"window.close()\" style=\"padding:7px 14px;background:#f1f5f9;border:1px solid #cbd5e1;border-radius:6px;font-size:13px;cursor:pointer;\">Close</button>\n <span style=\"color:#64748b;font-size:11px;\">Tip: set your printer to <strong>Landscape</strong> with no additional margins</span>\n</div>\n${bodyHtml}\n${FIT_TEXT_SCRIPT}\n</body>\n</html>`;\n\n // Delegate to the ONE popup-or-download path in core (same fallback\n // filename as before: title, whitespace-dashed, lowercased, \"-flashcards.html\").\n return openPrintWindow(html, `${title}-flashcards`, \"width=1100,height=720,scrollbars=yes\");\n}\n\n// ---------------------------------------------------------------------------\n// Variant: Avery 5388 — 3×5\" index cards, 3 per page, portrait\n//\n// Geometry (portrait 8.5\" × 11\"):\n// Card size: 5\" wide × 3\" tall\n// 3 cards stacked = 9\" height\n// Remaining vertical: 11\" − 9\" = 2\" → 0.5\" top + 0.5\" gap × 2 + 0.5\" bottom\n// Side margins: (8.5\" − 5\") / 2 = 1.75\" each side\n//\n// Layout: page padding 0.5in top/bottom, 1.75in left/right\n// between cards: 0.5in gap\n//\n// Each page = 3 card fronts. Next page = matching 3 card backs (same order).\n// No borders, no lines, no cut marks. Just text centered in each card zone.\n// ---------------------------------------------------------------------------\n\nconst AVERY_5388_STYLES = `\n @page { size: portrait; margin: 0; }\n\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n\n html, body {\n width: 8.5in;\n background: #fff;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Helvetica, Arial, sans-serif;\n color: #000;\n }\n\n .avery-page {\n width: 8.5in;\n height: 11in;\n padding: 0.5in 1.75in;\n display: flex;\n flex-direction: column;\n gap: 0.5in;\n page-break-after: always;\n break-after: page;\n border-bottom: 1px solid #e2e8f0; /* screen only separator */\n }\n .avery-page:last-child {\n page-break-after: auto;\n break-after: auto;\n border-bottom: none;\n }\n\n /* Each card zone: exactly 5\" × 3\" — position:relative so the inner text can be absolutely centered */\n .avery-card {\n width: 5in;\n height: 3in;\n position: relative;\n overflow: hidden;\n flex-shrink: 0;\n }\n\n /* Text centering wrapper — fills the card and centers content in both axes */\n .avery-text-wrap {\n position: absolute;\n inset: 0.2in 0.3in; /* inner padding — 0.2in top/bottom, 0.3in left/right */\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n }\n\n /* Side label (tiny, top-left) */\n .avery-side-label {\n position: absolute;\n top: 6px;\n left: 8px;\n font-size: 6pt;\n font-weight: 700;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n color: #aaa;\n }\n\n /* Card number (tiny, top-right) */\n .avery-card-num {\n position: absolute;\n top: 6px;\n right: 8px;\n font-size: 6pt;\n color: #aaa;\n font-weight: 600;\n }\n\n /* The text itself — starts large, fitText JS shrinks it to fit the wrap container */\n .avery-text {\n font-size: 22pt;\n font-weight: 600;\n line-height: 1.3;\n white-space: pre-wrap;\n word-break: break-word;\n text-align: center;\n max-width: 100%;\n }\n .avery-text.multiline {\n font-size: 15pt;\n font-weight: 400;\n text-align: left;\n line-height: 1.45;\n }\n\n /* Bullet list back */\n ul.avery-text.card-list {\n text-align: left;\n font-size: 15pt;\n font-weight: 400;\n line-height: 1.5;\n padding-left: 1.1em;\n margin: 0;\n list-style-type: disc;\n white-space: normal;\n width: 100%;\n }\n ul.avery-text.card-list li {\n margin-bottom: 0.3em;\n }\n ul.avery-text.card-list li:last-child {\n margin-bottom: 0;\n }\n\n @media print {\n /* Ensure gray text isn't converted to black by color management */\n .avery-text { -webkit-print-color-adjust: exact; print-color-adjust: exact; }\n }\n\n @media print {\n html, body { width: 8.5in; height: auto; overflow: visible; }\n .avery-page { border-bottom: none; }\n .screen-only { display: none !important; }\n }\n`;\n\nfunction buildAveryCard(\n text: string,\n cardNum: number,\n isFront: boolean,\n s: CardPrintSettings,\n): string {\n const numBadge = s.showCardNumber\n ? `<span class=\"avery-card-num\">#${cardNum}</span>`\n : \"\";\n const sideLabel = s.showSideLabel\n ? `<span class=\"avery-side-label\">${isFront ? \"Front\" : \"Back\"}</span>`\n : \"\";\n\n const textColor = !isFront && s.lightBackText ? \"color:#777;\" : \"\";\n const mirrorXform = !isFront && s.mirrorBack ? \"transform:scaleX(-1);\" : \"\";\n const innerStyle = textColor + mirrorXform;\n\n // Render with shared logic — detects bullets, builds <ul> if needed\n const isBullet = !isFront && isBulletContent(text);\n const isML = !isFront && isMultiline(text);\n const cls = isBullet\n ? \"avery-text card-list\"\n : isML\n ? \"avery-text multiline\"\n : \"avery-text\";\n const styleAttr = innerStyle ? ` style=\"${innerStyle}\"` : \"\";\n\n let innerHtml: string;\n if (isBullet) {\n const items = text\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.length > 0)\n .map((l) => l.replace(/^[-•*]\\s*/, \"\"))\n .map((l) => `<li>${mathTextHtml(l)}</li>`)\n .join(\"\");\n innerHtml = `<ul class=\"${cls}\"${styleAttr}>${items}</ul>`;\n } else {\n const escaped = mathTextHtml(text).replace(/\\n/g, \"<br>\");\n innerHtml = `<div class=\"${cls}\"${styleAttr}>${escaped}</div>`;\n }\n\n // List content: wrap aligns to start; prose/front: centered\n const wrapExtra = isBullet ? ' style=\"align-items:flex-start;\"' : \"\";\n\n return `<div class=\"avery-card\">\n ${numBadge}\n ${sideLabel}\n <div class=\"avery-text-wrap\"${wrapExtra}>\n ${innerHtml}\n </div>\n</div>`;\n}\n\nfunction renderAvery5388(\n cards: Flashcard[],\n title: string,\n s: CardPrintSettings,\n startIndex = 0,\n): string {\n const BATCH = 3;\n const pages: string[] = [];\n\n for (let p = 0; p < Math.ceil(cards.length / BATCH); p++) {\n const batch = cards.slice(p * BATCH, (p + 1) * BATCH);\n\n const frontCells = batch\n .map((c, i) =>\n buildAveryCard(c.front, startIndex + p * BATCH + i + 1, true, s),\n )\n .join(\"\\n\");\n const backCells = batch\n .map((c, i) =>\n buildAveryCard(c.back ?? \"\", startIndex + p * BATCH + i + 1, false, s),\n )\n .join(\"\\n\");\n\n const padCount = BATCH - batch.length;\n const padCells = Array(padCount)\n .fill(`<div class=\"avery-card\"></div>`)\n .join(\"\\n\");\n\n pages.push(`<div class=\"avery-page\">${frontCells}${padCells}</div>`);\n pages.push(`<div class=\"avery-page\">${backCells}${padCells}</div>`);\n }\n\n return pages.join(\"\\n\");\n}\n\nfunction openAveryWindow(bodyHtml: string, title: string): PrintOutcome {\n const html = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>${escapeHtml(title)} — Avery 5388</title>\n <style>${AVERY_5388_STYLES}</style>\n</head>\n<body>\n<div class=\"screen-only\" style=\"font-family:sans-serif;font-size:12px;padding:10px 16px;background:#f8fafc;border-bottom:1px solid #e2e8f0;display:flex;gap:10px;align-items:center;\">\n <button onclick=\"window.print()\" style=\"padding:7px 18px;background:#111;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;\">Print / Save PDF</button>\n <button onclick=\"window.close()\" style=\"padding:7px 14px;background:#f1f5f9;border:1px solid #cbd5e1;border-radius:6px;font-size:13px;cursor:pointer;\">Close</button>\n <span style=\"color:#64748b;font-size:11px;\">Avery 5388 · 3×5\" index cards · 3 per page · set printer to <strong>Portrait, no margins</strong></span>\n</div>\n${bodyHtml}\n${FIT_TEXT_SCRIPT}\n</body>\n</html>`;\n\n // Delegate to the ONE popup-or-download path in core (same fallback\n // filename as before: title, whitespace-dashed, lowercased, \"-avery5388.html\").\n return openPrintWindow(html, `${title}-avery5388`, \"width=900,height=720,scrollbars=yes\");\n}\n\n// ---------------------------------------------------------------------------\n// Variant: Portrait 6-up (6 cards per page, 2 columns × 3 rows)\n//\n// Geometry (portrait 8.5\" × 11\"):\n// Page is split exactly in half down the middle and into thirds across.\n// Each cell: 4.25\" wide × 3.667\" tall.\n// Inner padding per cell: ~0.22\" gives the text breathing room without\n// pushing content off the cuttable area.\n//\n// Duplex (double-sided) mirroring for portrait:\n// Most printers default to \"flip on long edge\" for portrait duplex, which\n// pivots the sheet around its left (long) edge. Result: left ↔ right\n// columns swap on the back side, rows are unchanged.\n//\n// Front slot order: [0:TL, 1:TR, 2:ML, 3:MR, 4:BL, 5:BR]\n// Back page render: [B1, B0, B3, B2, B5, B4] (columns swapped per row)\n//\n// Content centering:\n// Each card face uses flex centering so short text sits dead-center,\n// multiline prose is left-aligned but still vertically centered, and\n// bullet lists align to the top-left of their cell for readability.\n// ---------------------------------------------------------------------------\n\nconst PORTRAIT_6UP_STYLES = `\n @page { size: portrait; margin: 0; }\n\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n\n html, body {\n width: 8.5in;\n background: #fff;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Helvetica, Arial, sans-serif;\n color: #000;\n }\n\n .page-6up {\n width: 8.5in;\n height: 11in;\n position: relative;\n page-break-after: always;\n break-after: page;\n display: grid;\n grid-template-columns: 1fr 1fr;\n grid-template-rows: 1fr 1fr 1fr;\n gap: 0;\n padding: 0;\n border-bottom: 2px solid #e2e8f0;\n }\n .page-6up:last-child {\n page-break-after: auto;\n break-after: auto;\n border-bottom: none;\n }\n\n @media print {\n html, body { width: 8.5in; height: auto; overflow: visible; }\n .page-6up { border-bottom: none; }\n }\n\n /* Each of the 6 cells — clean 4.25\" × 3.667\" zone split by the cuts */\n .cell-6 {\n display: flex;\n align-items: stretch;\n justify-content: stretch;\n padding: 0.2in 0.22in;\n overflow: hidden;\n }\n\n /* The actual card face — no border. Cuts are the separation. */\n .card-face {\n flex: 1;\n border: none;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 0.1in 0.12in;\n overflow: hidden;\n position: relative;\n }\n\n /* Cut tick marks — short marks at each cut intersection with the page edge */\n .tick-v-top, .tick-v-bottom {\n position: absolute;\n left: 50%;\n height: 0.18in;\n width: 0;\n border-left: 0.6px solid #bbb;\n pointer-events: none;\n }\n .tick-v-top { top: 0.1in; }\n .tick-v-bottom { bottom: 0.1in; }\n\n .tick-h-1-left, .tick-h-1-right,\n .tick-h-2-left, .tick-h-2-right {\n position: absolute;\n width: 0.18in;\n height: 0;\n border-top: 0.6px solid #bbb;\n pointer-events: none;\n }\n .tick-h-1-left { top: 33.333%; left: 0.1in; }\n .tick-h-1-right { top: 33.333%; right: 0.1in; }\n .tick-h-2-left { top: 66.667%; left: 0.1in; }\n .tick-h-2-right { top: 66.667%; right: 0.1in; }\n\n /* Full dashed cut lines (optional) */\n .cut-line-v {\n position: absolute;\n top: 0; bottom: 0;\n left: 50%;\n width: 0;\n border-left: 0.6px dashed #bbb;\n pointer-events: none;\n }\n .cut-line-h-1, .cut-line-h-2 {\n position: absolute;\n left: 0; right: 0;\n height: 0;\n border-top: 0.6px dashed #bbb;\n pointer-events: none;\n }\n .cut-line-h-1 { top: 33.333%; }\n .cut-line-h-2 { top: 66.667%; }\n\n /* Card number badge */\n .card-num {\n position: absolute;\n top: 5px;\n right: 7px;\n font-size: 6.5pt;\n color: #999;\n font-weight: 600;\n }\n\n /* Side label (FRONT / BACK) */\n .side-label {\n font-size: 6.5pt;\n font-weight: 700;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: #888;\n margin-bottom: 6px;\n align-self: flex-start;\n }\n\n /* Centered text container — fills the card, both axes centered */\n .card-text {\n flex: 1;\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n overflow: hidden;\n }\n\n .card-text-inner {\n font-size: 18pt; /* fitText JS shrinks this to fit */\n font-weight: 600;\n line-height: 1.3;\n white-space: pre-wrap;\n word-break: break-word;\n text-align: center;\n -webkit-print-color-adjust: exact;\n print-color-adjust: exact;\n }\n\n .card-text-inner.multiline {\n text-align: left;\n font-size: 13pt;\n font-weight: 400;\n line-height: 1.45;\n }\n\n ul.card-text-inner.card-list {\n text-align: left;\n font-size: 13pt;\n font-weight: 400;\n line-height: 1.5;\n padding-left: 1.1em;\n margin: 0;\n list-style-type: disc;\n white-space: normal;\n width: 100%;\n }\n ul.card-text-inner.card-list li { margin-bottom: 0.2em; }\n ul.card-text-inner.card-list li:last-child { margin-bottom: 0; }\n\n @media print {\n .screen-only { display: none !important; }\n }\n`;\n\nfunction buildPage6Up(cells: string[], s: CardPrintSettings): string {\n const ticks = s.showTickMarks\n ? `<div class=\"tick-v-top\"></div>\n <div class=\"tick-v-bottom\"></div>\n <div class=\"tick-h-1-left\"></div>\n <div class=\"tick-h-1-right\"></div>\n <div class=\"tick-h-2-left\"></div>\n <div class=\"tick-h-2-right\"></div>`\n : \"\";\n\n const lines = s.showCutLines\n ? `<div class=\"cut-line-v\"></div>\n <div class=\"cut-line-h-1\"></div>\n <div class=\"cut-line-h-2\"></div>`\n : \"\";\n\n return `<div class=\"page-6up\">\n ${ticks}\n ${lines}\n ${cells.map((c) => `<div class=\"cell-6\">${c}</div>`).join(\"\\n \")}\n</div>`;\n}\n\n/**\n * portrait-6up-duplex: alternating front/back pages, backs mirrored for duplex.\n * Front page → [F0, F1, F2, F3, F4, F5] in slot order TL, TR, ML, MR, BL, BR\n * Back page → [B1, B0, B3, B2, B5, B4] (columns swapped for long-edge flip)\n *\n * portrait-6up-stacked: all fronts first, then all backs (same slot order both).\n */\nfunction renderPortrait6Up(\n cards: Flashcard[],\n title: string,\n mode: \"duplex\" | \"stacked\",\n s: CardPrintSettings,\n startIndex = 0,\n): string {\n const BATCH = 6;\n const pages: string[] = [];\n // stacked mode collects backs separately so EVERY front page precedes EVERY\n // back page — see renderLandscape for the defect this fixes.\n const backPages: string[] = [];\n const empty = `<div class=\"card-face\"></div>`;\n\n for (let p = 0; p < Math.ceil(cards.length / BATCH); p++) {\n const batch = cards.slice(p * BATCH, (p + 1) * BATCH);\n while (batch.length < BATCH) batch.push({ front: \"\", back: \"\" });\n\n const fronts = batch.map((c, i) =>\n c.front\n ? buildCardFace(c.front, startIndex + p * BATCH + i + 1, true, s)\n : empty,\n );\n const backs = batch.map((c, i) =>\n c.back\n ? buildCardFace(c.back ?? \"\", startIndex + p * BATCH + i + 1, false, s)\n : empty,\n );\n\n if (mode === \"duplex\") {\n // Long-edge flip swaps columns per row: [TR, TL, MR, ML, BR, BL]\n // batch is padded to BATCH, so every slot below is present\n const backsFlipped = [\n backs[1]!,\n backs[0]!,\n backs[3]!,\n backs[2]!,\n backs[5]!,\n backs[4]!,\n ];\n pages.push(buildPage6Up(fronts, s));\n pages.push(buildPage6Up(backsFlipped, s));\n } else {\n pages.push(buildPage6Up(fronts, s));\n backPages.push(buildPage6Up(backs, s));\n }\n }\n\n return [...pages, ...backPages].join(\"\\n\");\n}\n\nfunction openPortrait6UpWindow(bodyHtml: string, title: string): PrintOutcome {\n const html = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>${escapeHtml(title)} — 6 per page</title>\n <style>${PORTRAIT_6UP_STYLES}</style>\n</head>\n<body>\n<div class=\"screen-only\" style=\"font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#fff;border-bottom:1px solid #e2e8f0;padding:14px 20px;\">\n <div style=\"display:flex;gap:10px;align-items:center;margin-bottom:10px;\">\n <button onclick=\"window.print()\" style=\"padding:8px 20px;background:#111;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;\">Print / Save PDF</button>\n <button onclick=\"window.close()\" style=\"padding:8px 14px;background:#f1f5f9;border:1px solid #cbd5e1;border-radius:6px;font-size:13px;cursor:pointer;\">Close</button>\n <span style=\"color:#111;font-size:12px;font-weight:600;margin-left:4px;\">6 flashcards per page · cut into 6</span>\n </div>\n <div style=\"background:#fef3c7;border:1px solid #fbbf24;border-radius:6px;padding:10px 14px;font-size:12px;line-height:1.55;color:#78350f;max-width:720px;\">\n <div style=\"font-weight:700;margin-bottom:4px;color:#92400e;\">⚠ Printer settings for duplex alignment</div>\n <strong>- Page Size:</strong> Letter (8.5\" × 11\")<br>\n <strong>- Orientation:</strong> Portrait<br>\n <strong>- Two-sided (Duplex):</strong> On<br>\n <strong>- Flip on:</strong> Long edge<br>\n <strong>- Pages per sheet:</strong> 1<br>\n <strong>- Scale:</strong> 100% / Actual size<br>\n <strong>- Margins:</strong> None<br>\n </div>\n</div>\n${bodyHtml}\n${FIT_TEXT_SCRIPT}\n</body>\n</html>`;\n\n // Delegate to the ONE popup-or-download path in core (same fallback\n // filename as before: title, whitespace-dashed, lowercased, \"-flashcards-6up.html\").\n return openPrintWindow(html, `${title}-flashcards-6up`, \"width=900,height=720,scrollbars=yes\");\n}\n\n// ---------------------------------------------------------------------------\n// Helper: derive a title from the data if available\n// ---------------------------------------------------------------------------\nfunction deriveTitle(data: unknown): string {\n if (data && typeof data === \"object\" && \"title\" in data) {\n const t = (data as { title?: unknown }).title;\n if (typeof t === \"string\" && t.trim()) return t.trim();\n }\n return \"Flashcards\";\n}\n\n// ---------------------------------------------------------------------------\n// Exported printer\n// ---------------------------------------------------------------------------\nexport const flashcardsPrinter: BlockPrinter = {\n label: \"Print flashcards\",\n variants: [\n {\n id: \"portrait-6up-duplex\",\n label: \"Portrait 6-up — duplex (recommended)\",\n description:\n \"6 cards per page (2 columns × 3 rows), portrait. Split down the middle and into thirds. Fronts and backs on alternating pages — backs are mirrored so they align perfectly when duplex-printed (flip on long edge) and cut into 6\",\n },\n {\n id: \"portrait-6up-stacked\",\n label: \"Portrait 6-up — all fronts then all backs\",\n description:\n \"6 cards per page (2 columns × 3 rows), portrait. All front pages first, then all back pages — cut all sheets together for a matching deck\",\n },\n {\n id: \"landscape-duplex\",\n label: \"Landscape cut sheet — duplex (double-sided)\",\n description:\n \"4 cards per page, landscape. Fronts and backs on alternating pages — backs are mirrored so they align perfectly when you flip and cut\",\n },\n {\n id: \"landscape-stacked\",\n label: \"Landscape cut sheet — all fronts then all backs\",\n description:\n \"4 cards per page, landscape. All front pages first, then all back pages — cut all sheets together for a matching deck\",\n },\n {\n id: \"avery-5388\",\n label: 'Avery 5388 — 3×5\" index cards (3 per page)',\n description:\n \"Prints to Avery 5388 card stock. 3 cards per page, portrait. Front page then back page per set of 3 — load the sheet twice to print both sides\",\n },\n {\n id: \"cut-cards\",\n label: \"Cut-out cards (portrait)\",\n description:\n \"4 per page with cut lines — front and back shown together per card\",\n },\n {\n id: \"both-sides\",\n label: \"Both sides per card\",\n description: \"Front and back shown together — best for review or sharing\",\n },\n {\n id: \"study-sheet\",\n label: \"Study sheet (table)\",\n description: \"Compact two-column table — all cards on one or two pages\",\n },\n {\n id: \"front-only\",\n label: \"Questions only (self-test)\",\n description:\n \"Front side with blank space to write answers — print and quiz yourself\",\n },\n {\n id: \"back-only\",\n label: \"Answers only\",\n description: \"Definitions / answers without the questions\",\n },\n ],\n\n settings: [\n {\n type: \"range\" as const,\n id: \"cardRange\",\n label: \"Cards to print\",\n description: \"Leave blank to print the whole deck\",\n fromId: \"fromCard\",\n toId: \"toCard\",\n defaultFrom: 0,\n defaultTo: 0,\n min: 1,\n fromPlaceholder: \"1\",\n toPlaceholder: \"end\",\n },\n {\n type: \"boolean\" as const,\n id: \"showSideLabel\",\n label: \"Show side label\",\n description: 'Prints \"Front\" or \"Back\" in the corner of each card',\n defaultValue: false,\n appliesTo: [\n \"landscape-duplex\",\n \"landscape-stacked\",\n \"portrait-6up-duplex\",\n \"portrait-6up-stacked\",\n \"avery-5388\",\n ],\n },\n {\n type: \"boolean\" as const,\n id: \"showCardNumber\",\n label: \"Show card number\",\n description: \"Prints #1, #2 … in the corner of each card\",\n defaultValue: false,\n appliesTo: [\n \"landscape-duplex\",\n \"landscape-stacked\",\n \"portrait-6up-duplex\",\n \"portrait-6up-stacked\",\n \"avery-5388\",\n ],\n },\n {\n type: \"boolean\" as const,\n id: \"showTickMarks\",\n label: \"Show cut tick marks\",\n description:\n \"Small marks at each cut intersection — where to make each cut\",\n defaultValue: false,\n appliesTo: [\n \"landscape-duplex\",\n \"landscape-stacked\",\n \"portrait-6up-duplex\",\n \"portrait-6up-stacked\",\n ],\n },\n {\n type: \"boolean\" as const,\n id: \"showCutLines\",\n label: \"Show full cut lines\",\n description:\n \"Dashed lines all the way across the page — helps if you don't have a ruler\",\n defaultValue: false,\n appliesTo: [\n \"landscape-duplex\",\n \"landscape-stacked\",\n \"portrait-6up-duplex\",\n \"portrait-6up-stacked\",\n ],\n },\n {\n type: \"boolean\" as const,\n id: \"lightBackText\",\n label: \"Light back text (reduce show-through)\",\n description:\n \"Prints the back side in dark gray instead of black — ink is lighter and less visible through the paper\",\n defaultValue: false,\n appliesTo: [\n \"landscape-duplex\",\n \"landscape-stacked\",\n \"portrait-6up-duplex\",\n \"portrait-6up-stacked\",\n \"avery-5388\",\n ],\n },\n {\n type: \"boolean\" as const,\n id: \"mirrorBack\",\n label: \"Mirror back text (best show-through prevention)\",\n description:\n \"Flips back text horizontally — any ink that bleeds through reads backwards and is unrecognizable from the front\",\n defaultValue: false,\n appliesTo: [\n \"landscape-duplex\",\n \"landscape-stacked\",\n \"portrait-6up-duplex\",\n \"portrait-6up-stacked\",\n \"avery-5388\",\n ],\n },\n {\n type: \"boolean\" as const,\n id: \"showImages\",\n label: \"Print face images\",\n description:\n \"Includes each card's front/back image when it has one. The fixed-geometry formats (landscape, 6-up, Avery) are text-only by design\",\n defaultValue: true,\n appliesTo: [\n \"cut-cards\",\n \"both-sides\",\n \"study-sheet\",\n \"front-only\",\n \"back-only\",\n ],\n },\n ],\n\n print(\n data: unknown,\n variantId: string = \"portrait-6up-duplex\",\n rawSettings?: PrintSettings,\n ) {\n const allCards = extractCards(data);\n const title = deriveTitle(data);\n const s = resolveSettings(rawSettings);\n\n if (allCards.length === 0) {\n return openPrintWindow(\n buildPrintDocument(\n \"<p>No flashcard data available to print.</p>\",\n title,\n BASE_STYLES,\n ),\n \"flashcards\",\n );\n }\n\n // Resolve the \"cards to print\" range. 0 / missing / out-of-bounds all\n // fall back to sensible ends. Any invalid input still produces a valid deck.\n const rawFrom =\n typeof rawSettings?.fromCard === \"number\" ? rawSettings.fromCard : 0;\n const rawTo =\n typeof rawSettings?.toCard === \"number\" ? rawSettings.toCard : 0;\n const rangeStart = rawFrom > 0 ? Math.min(rawFrom, allCards.length) : 1;\n const rangeEnd =\n rawTo > 0 ? Math.min(rawTo, allCards.length) : allCards.length;\n const [fromIdx, toIdx] =\n rangeStart <= rangeEnd ? [rangeStart, rangeEnd] : [rangeEnd, rangeStart]; // swap if user inverted them\n const startIndex = fromIdx - 1; // 0-based offset of the first card\n const cards = allCards.slice(startIndex, toIdx);\n\n // Landscape and Avery variants use fully custom documents (precise page geometry)\n if (variantId === \"landscape-duplex\") {\n return openLandscapeWindow(\n renderLandscape(cards, title, \"duplex\", s, startIndex),\n title,\n );\n }\n if (variantId === \"landscape-stacked\") {\n return openLandscapeWindow(\n renderLandscape(cards, title, \"stacked\", s, startIndex),\n title,\n );\n }\n if (variantId === \"portrait-6up-duplex\") {\n return openPortrait6UpWindow(\n renderPortrait6Up(cards, title, \"duplex\", s, startIndex),\n title,\n );\n }\n if (variantId === \"portrait-6up-stacked\") {\n return openPortrait6UpWindow(\n renderPortrait6Up(cards, title, \"stacked\", s, startIndex),\n title,\n );\n }\n if (variantId === \"avery-5388\") {\n return openAveryWindow(renderAvery5388(cards, title, s, startIndex), title);\n }\n\n let bodyHtml: string;\n let styles: string;\n\n switch (variantId as FlashcardsVariant) {\n case \"cut-cards\":\n bodyHtml = renderCutCards(cards, title, s, startIndex);\n styles = CUT_CARD_STYLES;\n break;\n case \"front-only\":\n bodyHtml = renderFrontOnly(cards, title, s, startIndex);\n styles = FRONT_ONLY_STYLES;\n break;\n case \"back-only\":\n bodyHtml = renderBackOnly(cards, title, s, startIndex);\n styles = BACK_ONLY_STYLES;\n break;\n case \"study-sheet\":\n bodyHtml = renderStudySheet(cards, title, s, startIndex);\n styles = STUDY_SHEET_STYLES;\n break;\n case \"both-sides\":\n default:\n bodyHtml = renderBothSides(cards, title, s, startIndex);\n styles = BOTH_SIDES_STYLES;\n break;\n }\n\n return openPrintWindow(\n buildPrintDocument(bodyHtml, title, styles),\n \"flashcards\",\n );\n },\n\n // Inside a composed print (a message's Print) a deck is its study sheet:\n // paper cannot flip, and a cut sheet belongs to the deck's own print.\n toPrintHtml(data: unknown) {\n const cards = extractCards(data);\n // A deck this printer cannot read is NOT an empty deck: answer null so the composer's default path runs.\n if (cards.length === 0) return null;\n const sheet = renderStudySheet(cards, deriveTitle(data), resolveSettings(undefined), 0, COMPOSED_CLASSES).replaceAll(\n 'class=\"face-image\"',\n 'class=\"matrx-fc-face-image\"',\n );\n return { html: `<div class=\"matrx-fc-sheet\">${sheet}</div>`, css: COMPOSED_STUDY_SHEET_CSS };\n },\n};\n"],"mappings":"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","names":["escapeHtml","escapeHtml","escapeHtml","styleAttr"]}
1
+ {"version":3,"sources":["../src/core.ts","../src/katex-css.generated.ts","../src/math-text.ts","../src/document/math.ts","../src/flashcards-meta.ts","../src/flashcards.ts"],"sourcesContent":["/**\n * @ai-matrx/print/core — the Block Print contract & print-window composition.\n *\n * Ported VERBATIM from matrx-frontend `lib/block-print/block-print-utils.ts`\n * (2026-08-29 state, including the `number` and `select` PrintSetting types the\n * commerce label lane added). Zero dependencies; touches `window` only at call\n * time, so the module stays import-inert and RSC-safe.\n *\n * BlockPrinter is the only interface a host system needs to know about.\n * Each printable unit exports its own BlockPrinter alongside its render\n * component; host code only ever calls print(data, variantId, settings).\n *\n * `buildFitTextScript` is the ONE auto-shrink script (the flashcards\n * FIT_TEXT pattern, parameterized by selector so the label lane shares it).\n */\n\n// THE ONE HTML escaper (duplication census row 14). Nine copies of\n// `escapeHtml` were live across the fleet and THEY DID NOT ESCAPE THE SAME\n// CHARACTERS — a string safe through one was an attribute break-out through\n// another. It moved to `@ai-matrx/kit/html-escape` with a decided character\n// set: an HTML escaper must not require this package's jspdf + html2canvas\n// graph. Not re-exported from here; import `@ai-matrx/kit/html-escape`.\nimport { downloadFile } from \"@ai-matrx/kit/download\";\nimport { escapeHtml } from \"@ai-matrx/kit/html-escape\";\nimport { KATEX_CSS, KATEX_CSS_MARKER } from \"./katex-css.generated\";\n\n// ─── Contract ────────────────────────────────────────────────────────────────\n\nexport interface PrintVariant {\n id: string;\n label: string;\n description?: string;\n}\n\n/**\n * A single user-configurable setting shown in the print dialog.\n * Extend the discriminated union for new control types\n * without breaking existing printers.\n */\nexport type PrintSetting =\n | {\n type: \"boolean\";\n id: string;\n label: string;\n description?: string;\n /** Default value shown when the dialog first opens */\n defaultValue: boolean;\n /** Which variant IDs this setting applies to. Omit = applies to all. */\n appliesTo?: string[];\n }\n | {\n /**\n * A numeric \"from → to\" range control, rendered as two small number inputs.\n * Writes two entries into the resolved PrintSettings map — one under fromId,\n * one under toId. Printers interpret 0 (or missing) as \"no bound\" by convention.\n */\n type: \"range\";\n /** Stable id used as React key — not written to the settings map */\n id: string;\n label: string;\n description?: string;\n /** Settings map key for the lower bound value */\n fromId: string;\n /** Settings map key for the upper bound value */\n toId: string;\n defaultFrom: number;\n defaultTo: number;\n /** Minimum accepted value for each input (inclusive). Default 0. */\n min?: number;\n /** Placeholder shown when the input is empty (0). Default \"start\" / \"end\". */\n fromPlaceholder?: string;\n toPlaceholder?: string;\n /** Which variant IDs this setting applies to. Omit = applies to all. */\n appliesTo?: string[];\n }\n | {\n /** A single numeric input (e.g. \"start at label #\", \"copies\"). */\n type: \"number\";\n id: string;\n label: string;\n description?: string;\n defaultValue: number;\n /** Minimum accepted value (inclusive). Default 0. */\n min?: number;\n /** Maximum accepted value (inclusive). Omit = unbounded. */\n max?: number;\n /** Which variant IDs this setting applies to. Omit = applies to all. */\n appliesTo?: string[];\n }\n | {\n /** A one-of-N choice rendered as a compact segmented row. */\n type: \"select\";\n id: string;\n label: string;\n description?: string;\n options: { value: string; label: string }[];\n defaultValue: string;\n /** Which variant IDs this setting applies to. Omit = applies to all. */\n appliesTo?: string[];\n };\n\n/** A resolved map of setting id → current value, passed to print(). */\nexport type PrintSettings = Record<string, boolean | string | number>;\n\n/**\n * The self-contained print capability that each block can export.\n * Host code only ever calls print(data, variantId, settings) — it never inspects internals.\n */\nexport interface BlockPrinter {\n /** Tooltip / aria-label for the print button in the block header */\n label: string;\n\n /**\n * Variants offered to the user before printing.\n * Empty array = no options dialog shown; prints immediately with defaults.\n */\n variants: PrintVariant[];\n\n /**\n * Optional user-configurable settings shown below the variant picker.\n * Omit entirely if the block has no configurable settings.\n */\n settings?: PrintSetting[];\n\n /**\n * Execute the print.\n * data — the block's structured data (serverData or parsed content)\n * variantId — which variant was chosen (undefined = default / no variants)\n * settings — resolved setting values chosen by the user\n *\n * MAY resolve to a PrintOutcome (\"opened\" | \"downloaded\") so the host can\n * tell the user when the popup-blocked download fallback fired; `void`\n * stays legal for printers that don't go through a print window.\n */\n print: (\n data: unknown,\n variantId?: string,\n settings?: PrintSettings,\n ) => void | PrintOutcome | Promise<void | PrintOutcome>;\n\n /**\n * The block as it appears INSIDE a composed print (a message's Print / Full\n * Print, a document export): the same adapter the block's own print uses,\n * returning body HTML or a picture. A frame block (an HTML page) answers\n * with an image — a composed print window runs no author scripts — or an\n * honest `{ notice }` when no picture can be made. Absent ⇒ the composer\n * prints the block's source.\n */\n toPrintHtml?: (data: unknown, context: PrintBlockContext) => PrintBlockOutput | null | Promise<PrintBlockOutput | null>;\n}\n\n/** Where a block sits when a composer asks for its print form. */\nexport interface PrintBlockContext {\n /** The registry key the block was found under (artifact type or `__kind`). */\n readonly type: string;\n /** The block's raw source text, exactly as it sits in the message. */\n readonly raw: string;\n /** The block's title, when the wire form carries one. */\n readonly title?: string | undefined;\n /** The wire form's attributes (an `<artifact>`'s `id`, `version`) — how a printer finds the block's record. */\n readonly attributes?: Readonly<Record<string, string>> | undefined;\n}\n\n/**\n * A block's print form inside a composed print. `html` is trusted body HTML\n * from a printer (escape what you interpolate); `image` is a picture (a\n * captured page, a drawn diagram); `notice` is an honest line printed in the\n * block's place when neither exists.\n */\nexport type PrintBlockOutput =\n | {\n readonly html: string;\n /**\n * The block's stylesheet. A composed print sanitizes block HTML (no\n * `<style>`, no inline styles, only `matrx-*` / `print-*` classes), so a\n * printer that styles its layout passes its CSS here: the composer puts\n * each distinct sheet once in the document head. Scope it under your own\n * `matrx-…` class.\n */\n readonly css?: string | undefined;\n }\n | { readonly image: { readonly src: string; readonly alt?: string | undefined; readonly caption?: string | undefined } }\n | { readonly notice: string };\n\n// ─── The ONE block-printer registry ───────────────────────────────────────────\n//\n// Keyed by artifact type / `__kind`. A block's own print button, the canvas\n// tab's Print and the message composer all read the same entry, so a block\n// has one print adapter with every caller. Lives on globalThis under a\n// Symbol.for key so duplicated bundles (ESM + CJS) share one registry.\n\nconst PRINTER_REGISTRY = Symbol.for(\"ai-matrx.print.block-printers\");\n\nfunction printerRegistry(): Map<string, BlockPrinter> {\n const g = globalThis as unknown as { [PRINTER_REGISTRY]?: Map<string, BlockPrinter> };\n g[PRINTER_REGISTRY] ??= new Map();\n return g[PRINTER_REGISTRY];\n}\n\n/** Registers `printer` under each key (artifact type and/or `__kind`). Returns the unregister. */\nexport function registerBlockPrinter(keys: string | readonly string[], printer: BlockPrinter): () => void {\n const map = printerRegistry();\n const list = (typeof keys === \"string\" ? [keys] : keys).map((key) => key.toLowerCase());\n for (const key of list) map.set(key, printer);\n return () => {\n for (const key of list) if (map.get(key) === printer) map.delete(key);\n };\n}\n\n/** The printer registered for an artifact type or `__kind` (case-insensitive). */\nexport function getBlockPrinter(key: string | null | undefined): BlockPrinter | undefined {\n return key ? printerRegistry().get(key.toLowerCase()) : undefined;\n}\n\n/** Every registered key — for guards and diagnostics. */\nexport function listBlockPrinterKeys(): string[] {\n return [...printerRegistry().keys()];\n}\n\n/** The HTML a composer puts in a block's place for a `PrintBlockOutput`. */\nexport function printBlockOutputHtml(output: PrintBlockOutput): string {\n if (\"html\" in output) return `<div class=\"matrx-print-block\">${output.html}</div>`;\n if (\"image\" in output) {\n const { src, alt, caption } = output.image;\n return `<figure class=\"matrx-print-block matrx-print-image\"><img src=\"${escapeHtml(src)}\" alt=\"${escapeHtml(\n alt ?? \"Picture\",\n )}\" style=\"max-width:100%;height:auto\">${caption ? `<figcaption>${escapeHtml(caption)}</figcaption>` : \"\"}</figure>`;\n }\n return `<p class=\"matrx-print-block matrx-print-notice\" style=\"border:1px dashed #94a3b8;border-radius:6px;padding:10px 12px;color:#475569;font-style:italic\">${escapeHtml(\n output.notice,\n )}</p>`;\n}\n\n// ─── Shared Utilities ─────────────────────────────────────────────────────────\n\nconst PRINT_STYLES = `\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;\n font-size: 11pt;\n line-height: 1.6;\n color: #1a1a1a;\n background: #fff;\n max-width: 860px;\n margin: 0 auto;\n padding: 28px 36px;\n }\n\n .print-actions {\n display: flex;\n gap: 10px;\n margin-bottom: 24px;\n padding-bottom: 18px;\n border-bottom: 2px solid #e5e7eb;\n }\n\n .print-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 8px 20px;\n border-radius: 8px;\n font-size: 13px;\n font-family: inherit;\n cursor: pointer;\n border: none;\n font-weight: 600;\n }\n\n .print-btn-primary { background: #2563eb; color: #fff; }\n .print-btn-secondary { background: #f3f4f6; color: #374151; border: 1px solid #d1d5db; }\n\n h1 { font-size: 22pt; font-weight: 700; margin-bottom: 0.5em; }\n h2 { font-size: 16pt; font-weight: 700; margin: 1.2em 0 0.4em; border-bottom: 1px solid #e5e7eb; padding-bottom: 0.2em; }\n h3 { font-size: 13pt; font-weight: 600; margin: 1em 0 0.3em; }\n p { margin-bottom: 0.7em; }\n\n table {\n width: 100%;\n border-collapse: collapse;\n margin: 1em 0;\n font-size: 10pt;\n }\n th {\n background: #1e293b;\n color: #f8fafc;\n font-weight: 600;\n text-align: left;\n padding: 7px 12px;\n border: 1px solid #334155;\n }\n td {\n padding: 6px 12px;\n border: 1px solid #e2e8f0;\n vertical-align: top;\n }\n tr:nth-child(even) td { background: #f8fafc; }\n\n @media print {\n body { max-width: 100%; padding: 0; }\n .print-actions { display: none !important; }\n th { -webkit-print-color-adjust: exact; print-color-adjust: exact; }\n }\n`;\n\n/**\n * Wrap body HTML in a complete, styled print document.\n */\nexport function buildPrintDocument(\n bodyHtml: string,\n title = \"AI Response\",\n extraStyles = \"\",\n): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${escapeHtml(title)}</title>\n <style>\n${PRINT_STYLES}\n${extraStyles}\n </style>\n</head>\n<body>\n <div class=\"print-actions\">\n <button class=\"print-btn print-btn-primary\" onclick=\"window.print()\">Print / Save as PDF</button>\n <button class=\"print-btn print-btn-secondary\" onclick=\"window.close()\">Close</button>\n </div>\n <div class=\"content\">\n${bodyHtml}\n </div>\n</body>\n</html>`;\n}\n\n/**\n * How a print request actually reached the user.\n *\n * \"opened\" — a print window/tab was opened and the document written into it.\n * \"downloaded\" — the popup was blocked, so the document was downloaded as an\n * .html file instead (the contract's fallback). Hosts SHOULD\n * surface this to the user (e.g. a toast saying the print file\n * was downloaded) — a blocked popup plus a silent download is\n * indistinguishable from \"nothing happened\", which is exactly\n * how QA filed it (2026-08-30, F5/F6/F8).\n */\nexport type PrintOutcome = \"opened\" | \"downloaded\";\n\n/**\n * Open a new browser window and write a complete HTML document into it.\n * Falls back to downloading an .html file if the popup is blocked.\n *\n * Returns which path was taken so callers can tell the user when the\n * fallback fired. `windowFeatures` customizes the popup geometry (the\n * flashcards fixed-geometry variants pass a wider window).\n */\n/**\n * Every printed document leaves through here, so this is where typeset math\n * gets its stylesheet: when the page carries KaTeX output and does not already\n * carry the sheet, it is added to <head>. A page with no math is untouched,\n * byte for byte (the flashcard golden master holds every printer to that).\n */\nexport function withMathStyles(htmlDocument: string): string {\n if (!htmlDocument.includes('class=\"katex\"') || htmlDocument.includes(KATEX_CSS_MARKER)) {\n return htmlDocument;\n }\n const style = `<style data-matrx-katex>${KATEX_CSS}</style>`;\n const head = htmlDocument.search(/<\\/head>/i);\n return head >= 0\n ? htmlDocument.slice(0, head) + style + htmlDocument.slice(head)\n : style + htmlDocument;\n}\n\nexport function openPrintWindow(\n rawDocument: string,\n filename = \"print\",\n windowFeatures = \"width=920,height=720,scrollbars=yes\",\n): PrintOutcome {\n const htmlDocument = withMathStyles(rawDocument);\n const win = window.open(\"\", \"_blank\", windowFeatures);\n // Popup blocked — fall back to file download.\n if (!win) return openPrintWindowFallback(htmlDocument, filename);\n win.document.open();\n win.document.write(htmlDocument);\n win.document.close();\n return \"opened\";\n}\n\n/** A print window opened before its document is ready (see `openPendingPrintWindow`). */\nexport interface PendingPrintWindow {\n /** Write the finished document; downloads it when the popup was blocked or closed. */\n write(htmlDocument: string): PrintOutcome;\n}\n\n/**\n * Open the print window NOW — inside the click, where popup blockers allow it —\n * and write the document later, once async preparation (drawing diagrams) is\n * done. The window says \"Preparing…\" meanwhile.\n */\nexport function openPendingPrintWindow(\n filename = \"print\",\n windowFeatures = \"width=920,height=720,scrollbars=yes\",\n): PendingPrintWindow {\n const win = window.open(\"\", \"_blank\", windowFeatures);\n if (win) {\n win.document.open();\n win.document.write(\n '<!DOCTYPE html><html><head><meta charset=\"UTF-8\"><title>Preparing…</title></head><body style=\"font:14px -apple-system,BlinkMacSystemFont,sans-serif;color:#555;padding:2rem\">Preparing the document for print…</body></html>',\n );\n win.document.close();\n }\n return {\n write(rawDocument: string): PrintOutcome {\n const htmlDocument = withMathStyles(rawDocument);\n if (!win || win.closed) return openPrintWindowFallback(htmlDocument, filename);\n win.document.open();\n win.document.write(htmlDocument);\n win.document.close();\n return \"opened\";\n },\n };\n}\n\nfunction openPrintWindowFallback(htmlDocument: string, filename: string): PrintOutcome {\n downloadFile(`${filename.replace(/\\s+/g, \"-\").toLowerCase()}.html`, htmlDocument, \"text/html;charset=utf-8\");\n return \"downloaded\";\n}\n\n/**\n * Shorthand: build the document and open it in one call.\n * Returns the same PrintOutcome as openPrintWindow.\n */\nexport function printHtmlContent(\n bodyHtml: string,\n title = \"AI Response\",\n extraStyles = \"\",\n): PrintOutcome {\n const doc = buildPrintDocument(bodyHtml, title, extraStyles);\n return openPrintWindow(doc, title);\n}\n\n/**\n * Build the auto-shrink script that runs INSIDE a printed window: for every\n * element matching each selector, its font-size is reduced (0.5pt steps, 6pt\n * floor) until it fits its parent element's box. This is the flashcards\n * FIT_TEXT behavior verbatim, parameterized by selector so every lane\n * (flashcards card text, Avery text, label captions) shares the ONE loop.\n *\n * Pass the same selectors the original used to get byte-identical behavior:\n * flashcards passes `[\".card-text .card-text-inner\", \".avery-text-wrap .avery-text\"]`.\n */\nexport function buildFitTextScript(selectors: readonly string[]): string {\n const selectorsJson = JSON.stringify([...selectors]);\n return `\n<script>\n(function() {\n function fitText(el, container) {\n // container is the bounding box we must fit inside\n var maxW = container.clientWidth;\n var maxH = container.clientHeight;\n var size = parseFloat(window.getComputedStyle(el).fontSize);\n var minSize = 6;\n // Give the element a chance to reflow at current size before measuring\n el.style.fontSize = size + \"pt\";\n while (size > minSize && (el.scrollWidth > maxW + 1 || el.scrollHeight > maxH + 1)) {\n size -= 0.5;\n el.style.fontSize = size + \"pt\";\n }\n }\n function runAll() {\n var selectors = ${selectorsJson};\n for (var s = 0; s < selectors.length; s++) {\n var els = document.querySelectorAll(selectors[s]);\n for (var i = 0; i < els.length; i++) {\n fitText(els[i], els[i].parentElement);\n }\n }\n }\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", runAll);\n } else {\n // document.write pages may already be loaded\n setTimeout(runAll, 0);\n }\n})();\n</script>`;\n}\n\n\n// ─── DOM print: a rendered body, as it looks ─────────────────────────────────\n\nexport interface PrintElementOptions {\n /** The print window's title (and the download fallback's file name). */\n title?: string;\n /** Open the system print dialog once the window has loaded. Default true. */\n autoPrint?: boolean;\n /** Keep buttons, toolbars and menus in the print (default false: they are chrome, not content). */\n keepInteractive?: boolean;\n}\n\n/**\n * Interactive chrome that is not content: anything marked `data-print-hide`,\n * toolbars and menus, and buttons (\"Chart this\", \"Save to\", icon actions).\n * A button stays when it IS content — marked `data-print-keep`, or a disclosure\n * trigger (`aria-expanded`) whose label is a heading — and so does anything\n * inside a `data-print-keep` region. Removal runs on the print clone only.\n */\nconst PRINT_CHROME_SELECTOR = [\n \"[data-print-hide]\",\n '[role=\"toolbar\"]',\n '[role=\"menu\"]',\n '[role=\"menubar\"]',\n \"button\",\n '[role=\"button\"]',\n 'input[type=\"button\"]',\n 'input[type=\"submit\"]',\n 'input[type=\"reset\"]',\n].join(\",\");\n\nexport function stripInteractiveChrome(root: ParentNode): number {\n let removed = 0;\n root.querySelectorAll(PRINT_CHROME_SELECTOR).forEach((node) => {\n if (!root.contains(node)) return; // already gone with a removed ancestor\n const explicit = node.hasAttribute(\"data-print-hide\");\n if (!explicit) {\n if (node.closest(\"[data-print-keep]\")) return;\n if (node.matches(\"button, [role=\\\"button\\\"]\") && node.hasAttribute(\"aria-expanded\")) return;\n }\n node.remove();\n removed += 1;\n });\n return removed;\n}\n\nconst ELEMENT_PRINT_CSS = `\n html, body { height: auto !important; min-height: 0 !important; overflow: visible !important; background: #fff !important; }\n body { margin: 0; padding: 16px; }\n *, *::before, *::after { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; }\n .matrx-print-root, .matrx-print-root * { max-height: none !important; }\n .matrx-print-root [class*=\"overflow-\"], .matrx-print-root [class*=\"h-full\"], .matrx-print-root [class*=\"min-h-0\"] { overflow: visible !important; height: auto !important; }\n .matrx-print-frame-notice { border: 1px dashed #94a3b8; border-radius: 6px; padding: 10px 12px; color: #475569; font: italic 12px sans-serif; }\n @media print { .matrx-print-root * { break-inside: auto; } img, svg, figure, tr { break-inside: avoid; } }\n`;\n\n/**\n * Prints a same-origin DOM body exactly as it is drawn: the element's markup\n * with the page's own stylesheets, light theme, backgrounds kept, scroll\n * containers opened to full height. A `<canvas>` prints as its picture; an\n * `<iframe>` cannot be copied and prints an honest notice in its place (a\n * frame kind declares its own print instead). Opens inside the click.\n */\nexport function printElement(element: HTMLElement, options: PrintElementOptions = {}): PrintOutcome {\n const title = options.title ?? (document.title || \"Print\");\n const head = [...document.querySelectorAll<HTMLLinkElement | HTMLStyleElement>('link[rel=\"stylesheet\"], style')]\n .map((node) => {\n if (node instanceof HTMLLinkElement) return `<link rel=\"stylesheet\" href=\"${escapeHtml(node.href)}\">`;\n return `<style>${node.textContent ?? \"\"}</style>`;\n })\n .join(\"\\n\");\n const clone = element.cloneNode(true) as HTMLElement;\n const liveCanvases = element.querySelectorAll(\"canvas\");\n clone.querySelectorAll(\"canvas\").forEach((canvas, i) => {\n const live = liveCanvases[i];\n const img = document.createElement(\"img\");\n try {\n img.src = live ? live.toDataURL(\"image/png\") : \"\";\n } catch {\n img.alt = \"This drawing cannot be copied for print.\";\n }\n img.style.maxWidth = \"100%\";\n canvas.replaceWith(img);\n });\n clone.querySelectorAll(\"iframe\").forEach((frame) => {\n const note = document.createElement(\"p\");\n note.className = \"matrx-print-frame-notice\";\n note.textContent = \"An embedded page is here — print it from its own Print.\";\n frame.replaceWith(note);\n });\n clone.querySelectorAll(\"script\").forEach((node) => node.remove());\n if (options.keepInteractive !== true) stripInteractiveChrome(clone);\n const htmlClass = document.documentElement.className.replace(/\\bdark\\b/g, \"light\");\n const autoPrint = options.autoPrint ?? true;\n const doc = `<!DOCTYPE html><html lang=\"en\" class=\"${escapeHtml(htmlClass)}\"><head><meta charset=\"UTF-8\"><base href=\"${escapeHtml(\n document.baseURI,\n )}\"><title>${escapeHtml(title)}</title>${head}<style>${ELEMENT_PRINT_CSS}</style></head><body><div class=\"matrx-print-root\">${\n clone.outerHTML\n }</div>${\n autoPrint\n ? '<script>window.addEventListener(\"load\",function(){setTimeout(function(){window.focus();window.print();},250);});</script>'\n : \"\"\n }</body></html>`;\n return openPrintWindow(doc, title);\n}\n","// GENERATED by scripts/gen-katex-css.mjs from katex@0.18.9 — do not edit.\nexport const KATEX_VERSION = \"0.18.9\";\n/** Leads KATEX_CSS; lets openPrintWindow see the sheet is already present. */\nexport const KATEX_CSS_MARKER = \"/* matrx-katex@0.18.9 */\";\nexport const KATEX_CSS = \"/* matrx-katex@0.18.9 */@font-face{font-display:block;font-family:KaTeX_AMS;font-style:normal;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_AMS-Regular.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_AMS-Regular.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_AMS-Regular.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Caligraphic;font-style:normal;font-weight:700;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Caligraphic-Bold.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Caligraphic-Bold.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Caligraphic-Bold.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Caligraphic;font-style:normal;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Caligraphic-Regular.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Caligraphic-Regular.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Caligraphic-Regular.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Fraktur;font-style:normal;font-weight:700;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Fraktur-Bold.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Fraktur-Bold.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Fraktur-Bold.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Fraktur;font-style:normal;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Fraktur-Regular.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Fraktur-Regular.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Fraktur-Regular.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Main;font-style:normal;font-weight:700;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Main-Bold.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Main-Bold.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Main-Bold.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Main;font-style:italic;font-weight:700;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Main-BoldItalic.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Main-BoldItalic.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Main-BoldItalic.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Main;font-style:italic;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Main-Italic.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Main-Italic.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Main-Italic.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Main;font-style:normal;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Main-Regular.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Main-Regular.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Main-Regular.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Math;font-style:italic;font-weight:700;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Math-BoldItalic.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Math-BoldItalic.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Math-BoldItalic.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Math;font-style:italic;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Math-Italic.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Math-Italic.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Math-Italic.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:\\\"KaTeX_SansSerif\\\";font-style:normal;font-weight:700;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_SansSerif-Bold.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_SansSerif-Bold.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_SansSerif-Bold.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:\\\"KaTeX_SansSerif\\\";font-style:italic;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_SansSerif-Italic.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_SansSerif-Italic.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_SansSerif-Italic.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:\\\"KaTeX_SansSerif\\\";font-style:normal;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_SansSerif-Regular.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_SansSerif-Regular.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_SansSerif-Regular.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Script;font-style:normal;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Script-Regular.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Script-Regular.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Script-Regular.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Size1;font-style:normal;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Size1-Regular.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Size1-Regular.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Size1-Regular.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Size2;font-style:normal;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Size2-Regular.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Size2-Regular.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Size2-Regular.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Size3;font-style:normal;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Size3-Regular.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Size3-Regular.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Size3-Regular.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Size4;font-style:normal;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Size4-Regular.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Size4-Regular.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Size4-Regular.ttf) format(\\\"truetype\\\")}@font-face{font-display:block;font-family:KaTeX_Typewriter;font-style:normal;font-weight:400;src:url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Typewriter-Regular.woff2) format(\\\"woff2\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Typewriter-Regular.woff) format(\\\"woff\\\"),url(https://cdn.jsdelivr.net/npm/katex@0.18.9/dist/fonts/KaTeX_Typewriter-Regular.ttf) format(\\\"truetype\\\")}.katex{font:normal 1.21em KaTeX_Main,Times New Roman,serif;line-height:1.2;position:relative;text-indent:0;text-rendering:auto}.katex *{-ms-high-contrast-adjust:none!important;border-color:currentColor}.katex .katex-version:after{content:\\\"0.18.9\\\"}.katex .katex-mathml{border:0;-webkit-clip-path:inset(50%);clip-path:inset(50%);height:1px;overflow:hidden;padding:0;position:absolute;width:1px}.katex .katex-html>.katex-newline{display:block}.katex .katex-base{position:relative;white-space:nowrap;width:-webkit-min-content;width:-moz-min-content;width:min-content}.katex .katex-base,.katex .katex-strut{display:inline-block}.katex .textbf{font-weight:700}.katex .textit{font-style:italic}.katex .textrm{font-family:KaTeX_Main}.katex .textsf{font-family:KaTeX_SansSerif}.katex .texttt{font-family:KaTeX_Typewriter}.katex .mathnormal{font-family:KaTeX_Math;font-style:italic}.katex .mathit{font-family:KaTeX_Main;font-style:italic}.katex .mathrm{font-style:normal}.katex .mathbf{font-family:KaTeX_Main;font-weight:700}.katex .boldsymbol{font-family:KaTeX_Math;font-style:italic;font-weight:700}.katex .amsrm,.katex .mathbb,.katex .textbb{font-family:KaTeX_AMS}.katex .mathcal{font-family:KaTeX_Caligraphic}.katex .mathfrak,.katex .textfrak{font-family:KaTeX_Fraktur}.katex .mathboldfrak,.katex .textboldfrak{font-family:KaTeX_Fraktur;font-weight:700}.katex .mathtt{font-family:KaTeX_Typewriter}.katex .mathscr,.katex .textscr{font-family:KaTeX_Script}.katex .mathsf,.katex .textsf{font-family:KaTeX_SansSerif}.katex .mathboldsf,.katex .textboldsf{font-family:KaTeX_SansSerif;font-weight:700}.katex .mathitsf,.katex .mathsfit,.katex .textitsf{font-family:KaTeX_SansSerif;font-style:italic}.katex .mainrm{font-family:KaTeX_Main;font-style:normal}.katex .vlist-t{border-collapse:collapse;display:inline-table;table-layout:fixed}.katex .vlist-r{display:table-row}.katex .vlist{display:table-cell;position:relative;vertical-align:bottom}.katex .vlist>span{display:block;height:0;position:relative}.katex .vlist>span>span{display:inline-block}.katex .vlist>span>.pstrut{overflow:hidden;width:0}.katex .vlist-t2{margin-right:-2px}.katex .vlist-s{display:table-cell;font-size:1px;min-width:2px;vertical-align:bottom;width:2px}.katex .katex-vbox{align-items:baseline;display:inline-flex;flex-direction:column}.katex .katex-thinbox{display:inline-flex;flex-direction:row;max-width:0;width:0}.katex .msupsub{text-align:left}.katex .mfrac>span>span{text-align:center}.katex .mfrac .frac-line{border-bottom-style:solid;display:inline-block;width:100%}.katex .katex-hdashline,.katex .katex-hline,.katex .katex-overline .overline-line,.katex .katex-rule,.katex .katex-underline .underline-line,.katex .mfrac .frac-line{min-height:1px}.katex .mspace{display:inline-block}.katex .katex-smash{display:inline;line-height:0}.katex .clap,.katex .llap,.katex .rlap{position:relative;width:0}.katex .clap>.katex-inner,.katex .llap>.katex-inner,.katex .rlap>.katex-inner{position:absolute}.katex .clap>.katex-fix,.katex .llap>.katex-fix,.katex .rlap>.katex-fix{display:inline-block}.katex .llap>.katex-inner{right:0}.katex .clap>.katex-inner,.katex .rlap>.katex-inner{left:0}.katex .clap>.katex-inner>span{margin-left:-50%;margin-right:50%}.katex .katex-rule{border:0 solid;display:inline-block;position:relative}.katex .katex-hline,.katex .katex-overline .overline-line,.katex .katex-underline .underline-line{border-bottom-style:solid;display:inline-block;width:100%}.katex .katex-hdashline{border-bottom-style:dashed;display:inline-block;width:100%}.katex .sqrt>.katex-root{margin-left:.2777777778em;margin-right:-.5555555556em}.katex .fontsize-ensurer.reset-size1.size1,.katex .katex-sizing.reset-size1.size1{font-size:1em}.katex .fontsize-ensurer.reset-size1.size2,.katex .katex-sizing.reset-size1.size2{font-size:1.2em}.katex .fontsize-ensurer.reset-size1.size3,.katex .katex-sizing.reset-size1.size3{font-size:1.4em}.katex .fontsize-ensurer.reset-size1.size4,.katex .katex-sizing.reset-size1.size4{font-size:1.6em}.katex .fontsize-ensurer.reset-size1.size5,.katex .katex-sizing.reset-size1.size5{font-size:1.8em}.katex .fontsize-ensurer.reset-size1.size6,.katex .katex-sizing.reset-size1.size6{font-size:2em}.katex .fontsize-ensurer.reset-size1.size7,.katex .katex-sizing.reset-size1.size7{font-size:2.4em}.katex .fontsize-ensurer.reset-size1.size8,.katex .katex-sizing.reset-size1.size8{font-size:2.88em}.katex .fontsize-ensurer.reset-size1.size9,.katex .katex-sizing.reset-size1.size9{font-size:3.456em}.katex .fontsize-ensurer.reset-size1.size10,.katex .katex-sizing.reset-size1.size10{font-size:4.148em}.katex .fontsize-ensurer.reset-size1.size11,.katex .katex-sizing.reset-size1.size11{font-size:4.976em}.katex .fontsize-ensurer.reset-size2.size1,.katex .katex-sizing.reset-size2.size1{font-size:.8333333333em}.katex .fontsize-ensurer.reset-size2.size2,.katex .katex-sizing.reset-size2.size2{font-size:1em}.katex .fontsize-ensurer.reset-size2.size3,.katex .katex-sizing.reset-size2.size3{font-size:1.1666666667em}.katex .fontsize-ensurer.reset-size2.size4,.katex .katex-sizing.reset-size2.size4{font-size:1.3333333333em}.katex .fontsize-ensurer.reset-size2.size5,.katex .katex-sizing.reset-size2.size5{font-size:1.5em}.katex .fontsize-ensurer.reset-size2.size6,.katex .katex-sizing.reset-size2.size6{font-size:1.6666666667em}.katex .fontsize-ensurer.reset-size2.size7,.katex .katex-sizing.reset-size2.size7{font-size:2em}.katex .fontsize-ensurer.reset-size2.size8,.katex .katex-sizing.reset-size2.size8{font-size:2.4em}.katex .fontsize-ensurer.reset-size2.size9,.katex .katex-sizing.reset-size2.size9{font-size:2.88em}.katex .fontsize-ensurer.reset-size2.size10,.katex .katex-sizing.reset-size2.size10{font-size:3.4566666667em}.katex .fontsize-ensurer.reset-size2.size11,.katex .katex-sizing.reset-size2.size11{font-size:4.1466666667em}.katex .fontsize-ensurer.reset-size3.size1,.katex .katex-sizing.reset-size3.size1{font-size:.7142857143em}.katex .fontsize-ensurer.reset-size3.size2,.katex .katex-sizing.reset-size3.size2{font-size:.8571428571em}.katex .fontsize-ensurer.reset-size3.size3,.katex .katex-sizing.reset-size3.size3{font-size:1em}.katex .fontsize-ensurer.reset-size3.size4,.katex .katex-sizing.reset-size3.size4{font-size:1.1428571429em}.katex .fontsize-ensurer.reset-size3.size5,.katex .katex-sizing.reset-size3.size5{font-size:1.2857142857em}.katex .fontsize-ensurer.reset-size3.size6,.katex .katex-sizing.reset-size3.size6{font-size:1.4285714286em}.katex .fontsize-ensurer.reset-size3.size7,.katex .katex-sizing.reset-size3.size7{font-size:1.7142857143em}.katex .fontsize-ensurer.reset-size3.size8,.katex .katex-sizing.reset-size3.size8{font-size:2.0571428571em}.katex .fontsize-ensurer.reset-size3.size9,.katex .katex-sizing.reset-size3.size9{font-size:2.4685714286em}.katex .fontsize-ensurer.reset-size3.size10,.katex .katex-sizing.reset-size3.size10{font-size:2.9628571429em}.katex .fontsize-ensurer.reset-size3.size11,.katex .katex-sizing.reset-size3.size11{font-size:3.5542857143em}.katex .fontsize-ensurer.reset-size4.size1,.katex .katex-sizing.reset-size4.size1{font-size:.625em}.katex .fontsize-ensurer.reset-size4.size2,.katex .katex-sizing.reset-size4.size2{font-size:.75em}.katex .fontsize-ensurer.reset-size4.size3,.katex .katex-sizing.reset-size4.size3{font-size:.875em}.katex .fontsize-ensurer.reset-size4.size4,.katex .katex-sizing.reset-size4.size4{font-size:1em}.katex .fontsize-ensurer.reset-size4.size5,.katex .katex-sizing.reset-size4.size5{font-size:1.125em}.katex .fontsize-ensurer.reset-size4.size6,.katex .katex-sizing.reset-size4.size6{font-size:1.25em}.katex .fontsize-ensurer.reset-size4.size7,.katex .katex-sizing.reset-size4.size7{font-size:1.5em}.katex .fontsize-ensurer.reset-size4.size8,.katex .katex-sizing.reset-size4.size8{font-size:1.8em}.katex .fontsize-ensurer.reset-size4.size9,.katex .katex-sizing.reset-size4.size9{font-size:2.16em}.katex .fontsize-ensurer.reset-size4.size10,.katex .katex-sizing.reset-size4.size10{font-size:2.5925em}.katex .fontsize-ensurer.reset-size4.size11,.katex .katex-sizing.reset-size4.size11{font-size:3.11em}.katex .fontsize-ensurer.reset-size5.size1,.katex .katex-sizing.reset-size5.size1{font-size:.5555555556em}.katex .fontsize-ensurer.reset-size5.size2,.katex .katex-sizing.reset-size5.size2{font-size:.6666666667em}.katex .fontsize-ensurer.reset-size5.size3,.katex .katex-sizing.reset-size5.size3{font-size:.7777777778em}.katex .fontsize-ensurer.reset-size5.size4,.katex .katex-sizing.reset-size5.size4{font-size:.8888888889em}.katex .fontsize-ensurer.reset-size5.size5,.katex .katex-sizing.reset-size5.size5{font-size:1em}.katex .fontsize-ensurer.reset-size5.size6,.katex .katex-sizing.reset-size5.size6{font-size:1.1111111111em}.katex .fontsize-ensurer.reset-size5.size7,.katex .katex-sizing.reset-size5.size7{font-size:1.3333333333em}.katex .fontsize-ensurer.reset-size5.size8,.katex .katex-sizing.reset-size5.size8{font-size:1.6em}.katex .fontsize-ensurer.reset-size5.size9,.katex .katex-sizing.reset-size5.size9{font-size:1.92em}.katex .fontsize-ensurer.reset-size5.size10,.katex .katex-sizing.reset-size5.size10{font-size:2.3044444444em}.katex .fontsize-ensurer.reset-size5.size11,.katex .katex-sizing.reset-size5.size11{font-size:2.7644444444em}.katex .fontsize-ensurer.reset-size6.size1,.katex .katex-sizing.reset-size6.size1{font-size:.5em}.katex .fontsize-ensurer.reset-size6.size2,.katex .katex-sizing.reset-size6.size2{font-size:.6em}.katex .fontsize-ensurer.reset-size6.size3,.katex .katex-sizing.reset-size6.size3{font-size:.7em}.katex .fontsize-ensurer.reset-size6.size4,.katex .katex-sizing.reset-size6.size4{font-size:.8em}.katex .fontsize-ensurer.reset-size6.size5,.katex .katex-sizing.reset-size6.size5{font-size:.9em}.katex .fontsize-ensurer.reset-size6.size6,.katex .katex-sizing.reset-size6.size6{font-size:1em}.katex .fontsize-ensurer.reset-size6.size7,.katex .katex-sizing.reset-size6.size7{font-size:1.2em}.katex .fontsize-ensurer.reset-size6.size8,.katex .katex-sizing.reset-size6.size8{font-size:1.44em}.katex .fontsize-ensurer.reset-size6.size9,.katex .katex-sizing.reset-size6.size9{font-size:1.728em}.katex .fontsize-ensurer.reset-size6.size10,.katex .katex-sizing.reset-size6.size10{font-size:2.074em}.katex .fontsize-ensurer.reset-size6.size11,.katex .katex-sizing.reset-size6.size11{font-size:2.488em}.katex .fontsize-ensurer.reset-size7.size1,.katex .katex-sizing.reset-size7.size1{font-size:.4166666667em}.katex .fontsize-ensurer.reset-size7.size2,.katex .katex-sizing.reset-size7.size2{font-size:.5em}.katex .fontsize-ensurer.reset-size7.size3,.katex .katex-sizing.reset-size7.size3{font-size:.5833333333em}.katex .fontsize-ensurer.reset-size7.size4,.katex .katex-sizing.reset-size7.size4{font-size:.6666666667em}.katex .fontsize-ensurer.reset-size7.size5,.katex .katex-sizing.reset-size7.size5{font-size:.75em}.katex .fontsize-ensurer.reset-size7.size6,.katex .katex-sizing.reset-size7.size6{font-size:.8333333333em}.katex .fontsize-ensurer.reset-size7.size7,.katex .katex-sizing.reset-size7.size7{font-size:1em}.katex .fontsize-ensurer.reset-size7.size8,.katex .katex-sizing.reset-size7.size8{font-size:1.2em}.katex .fontsize-ensurer.reset-size7.size9,.katex .katex-sizing.reset-size7.size9{font-size:1.44em}.katex .fontsize-ensurer.reset-size7.size10,.katex .katex-sizing.reset-size7.size10{font-size:1.7283333333em}.katex .fontsize-ensurer.reset-size7.size11,.katex .katex-sizing.reset-size7.size11{font-size:2.0733333333em}.katex .fontsize-ensurer.reset-size8.size1,.katex .katex-sizing.reset-size8.size1{font-size:.3472222222em}.katex .fontsize-ensurer.reset-size8.size2,.katex .katex-sizing.reset-size8.size2{font-size:.4166666667em}.katex .fontsize-ensurer.reset-size8.size3,.katex .katex-sizing.reset-size8.size3{font-size:.4861111111em}.katex .fontsize-ensurer.reset-size8.size4,.katex .katex-sizing.reset-size8.size4{font-size:.5555555556em}.katex .fontsize-ensurer.reset-size8.size5,.katex .katex-sizing.reset-size8.size5{font-size:.625em}.katex .fontsize-ensurer.reset-size8.size6,.katex .katex-sizing.reset-size8.size6{font-size:.6944444444em}.katex .fontsize-ensurer.reset-size8.size7,.katex .katex-sizing.reset-size8.size7{font-size:.8333333333em}.katex .fontsize-ensurer.reset-size8.size8,.katex .katex-sizing.reset-size8.size8{font-size:1em}.katex .fontsize-ensurer.reset-size8.size9,.katex .katex-sizing.reset-size8.size9{font-size:1.2em}.katex .fontsize-ensurer.reset-size8.size10,.katex .katex-sizing.reset-size8.size10{font-size:1.4402777778em}.katex .fontsize-ensurer.reset-size8.size11,.katex .katex-sizing.reset-size8.size11{font-size:1.7277777778em}.katex .fontsize-ensurer.reset-size9.size1,.katex .katex-sizing.reset-size9.size1{font-size:.2893518519em}.katex .fontsize-ensurer.reset-size9.size2,.katex .katex-sizing.reset-size9.size2{font-size:.3472222222em}.katex .fontsize-ensurer.reset-size9.size3,.katex .katex-sizing.reset-size9.size3{font-size:.4050925926em}.katex .fontsize-ensurer.reset-size9.size4,.katex .katex-sizing.reset-size9.size4{font-size:.462962963em}.katex .fontsize-ensurer.reset-size9.size5,.katex .katex-sizing.reset-size9.size5{font-size:.5208333333em}.katex .fontsize-ensurer.reset-size9.size6,.katex .katex-sizing.reset-size9.size6{font-size:.5787037037em}.katex .fontsize-ensurer.reset-size9.size7,.katex .katex-sizing.reset-size9.size7{font-size:.6944444444em}.katex .fontsize-ensurer.reset-size9.size8,.katex .katex-sizing.reset-size9.size8{font-size:.8333333333em}.katex .fontsize-ensurer.reset-size9.size9,.katex .katex-sizing.reset-size9.size9{font-size:1em}.katex .fontsize-ensurer.reset-size9.size10,.katex .katex-sizing.reset-size9.size10{font-size:1.2002314815em}.katex .fontsize-ensurer.reset-size9.size11,.katex .katex-sizing.reset-size9.size11{font-size:1.4398148148em}.katex .fontsize-ensurer.reset-size10.size1,.katex .katex-sizing.reset-size10.size1{font-size:.2410800386em}.katex .fontsize-ensurer.reset-size10.size2,.katex .katex-sizing.reset-size10.size2{font-size:.2892960463em}.katex .fontsize-ensurer.reset-size10.size3,.katex .katex-sizing.reset-size10.size3{font-size:.337512054em}.katex .fontsize-ensurer.reset-size10.size4,.katex .katex-sizing.reset-size10.size4{font-size:.3857280617em}.katex .fontsize-ensurer.reset-size10.size5,.katex .katex-sizing.reset-size10.size5{font-size:.4339440694em}.katex .fontsize-ensurer.reset-size10.size6,.katex .katex-sizing.reset-size10.size6{font-size:.4821600771em}.katex .fontsize-ensurer.reset-size10.size7,.katex .katex-sizing.reset-size10.size7{font-size:.5785920926em}.katex .fontsize-ensurer.reset-size10.size8,.katex .katex-sizing.reset-size10.size8{font-size:.6943105111em}.katex .fontsize-ensurer.reset-size10.size9,.katex .katex-sizing.reset-size10.size9{font-size:.8331726133em}.katex .fontsize-ensurer.reset-size10.size10,.katex .katex-sizing.reset-size10.size10{font-size:1em}.katex .fontsize-ensurer.reset-size10.size11,.katex .katex-sizing.reset-size10.size11{font-size:1.1996142719em}.katex .fontsize-ensurer.reset-size11.size1,.katex .katex-sizing.reset-size11.size1{font-size:.2009646302em}.katex .fontsize-ensurer.reset-size11.size2,.katex .katex-sizing.reset-size11.size2{font-size:.2411575563em}.katex .fontsize-ensurer.reset-size11.size3,.katex .katex-sizing.reset-size11.size3{font-size:.2813504823em}.katex .fontsize-ensurer.reset-size11.size4,.katex .katex-sizing.reset-size11.size4{font-size:.3215434084em}.katex .fontsize-ensurer.reset-size11.size5,.katex .katex-sizing.reset-size11.size5{font-size:.3617363344em}.katex .fontsize-ensurer.reset-size11.size6,.katex .katex-sizing.reset-size11.size6{font-size:.4019292605em}.katex .fontsize-ensurer.reset-size11.size7,.katex .katex-sizing.reset-size11.size7{font-size:.4823151125em}.katex .fontsize-ensurer.reset-size11.size8,.katex .katex-sizing.reset-size11.size8{font-size:.578778135em}.katex .fontsize-ensurer.reset-size11.size9,.katex .katex-sizing.reset-size11.size9{font-size:.6945337621em}.katex .fontsize-ensurer.reset-size11.size10,.katex .katex-sizing.reset-size11.size10{font-size:.8336012862em}.katex .fontsize-ensurer.reset-size11.size11,.katex .katex-sizing.reset-size11.size11{font-size:1em}.katex .delimsizing.size1{font-family:KaTeX_Size1}.katex .delimsizing.size2{font-family:KaTeX_Size2}.katex .delimsizing.size3{font-family:KaTeX_Size3}.katex .delimsizing.size4{font-family:KaTeX_Size4}.katex .delimsizing.mult .delim-size1>span{font-family:KaTeX_Size1}.katex .delimsizing.mult .delim-size4>span{font-family:KaTeX_Size4}.katex .nulldelimiter{display:inline-block;width:.12em}.katex .delimcenter,.katex .op-symbol{position:relative}.katex .op-symbol.small-op{font-family:KaTeX_Size1}.katex .op-symbol.large-op{font-family:KaTeX_Size2}.katex .katex-accent>.vlist-t,.katex .op-limits>.vlist-t{text-align:center}.katex .katex-accent .accent-body{position:relative}.katex .katex-accent .accent-body:not(.accent-full){width:0}.katex .katex-overlay{display:block}.katex .mtable .vertical-separator{display:inline-block;min-width:1px}.katex .mtable .arraycolsep{display:inline-block}.katex .mtable .col-align-c>.vlist-t{text-align:center}.katex .mtable .col-align-l>.vlist-t{text-align:left}.katex .mtable .col-align-r>.vlist-t{text-align:right}.katex .svg-align{text-align:left}.katex svg{fill:currentColor;stroke:currentColor;display:block;height:inherit;position:absolute;width:100%}.katex svg path{stroke:none}.katex svg{fill-rule:nonzero;fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1}.katex img{border-style:none;max-height:none;max-width:none;min-height:0;min-width:0}.katex .katex-stretchy{display:block;overflow:hidden;position:relative;width:100%}.katex .katex-stretchy:after,.katex .katex-stretchy:before{content:\\\"\\\"}.katex .hide-tail{overflow:hidden;position:relative;width:100%}.katex .halfarrow-left{left:0;overflow:hidden;position:absolute;width:50.2%}.katex .halfarrow-right{overflow:hidden;position:absolute;right:0;width:50.2%}.katex .brace-left{left:0;overflow:hidden;position:absolute;width:25.1%}.katex .brace-center{left:25%;overflow:hidden;position:absolute;width:50%}.katex .brace-right{overflow:hidden;position:absolute;right:0;width:25.1%}.katex .x-arrow-pad{padding:0 .5em}.katex .cd-arrow-pad{padding:0 .55556em 0 .27778em}.katex .mover,.katex .munder,.katex .x-arrow{text-align:center}.katex .boxpad{padding:0 .3em}.katex .fbox,.katex .fcolorbox{border:.04em solid;box-sizing:border-box}.katex .cancel-pad{padding:0 .2em}.katex .cancel-lap{margin-left:-.2em;margin-right:-.2em}.katex .katex-sout{border-bottom-style:solid;border-bottom-width:.08em}.katex .angl{border-right:.049em solid;border-top:.049em solid;box-sizing:border-box;margin-right:.03889em}.katex .anglpad{padding:0 .03889em}.katex .reflectbox{display:inline-block;transform:scaleX(-1)}.katex .eqn-num:before{content:\\\"(\\\" counter(katexEqnNo) \\\")\\\";counter-increment:katexEqnNo}.katex .mml-eqn-num:before{content:\\\"(\\\" counter(mmlEqnNo) \\\")\\\";counter-increment:mmlEqnNo}.katex .mtr-glue{width:50%}.katex .cd-vert-arrow{display:inline-block;position:relative}.katex .cd-label-left{display:inline-block;position:absolute;right:calc(50% + .3em);text-align:left}.katex .cd-label-right{display:inline-block;left:calc(50% + .3em);position:absolute;text-align:right}.katex-display{display:block;margin:1em 0;text-align:center}.katex-display>.katex{display:block;text-align:center;white-space:nowrap}.katex-display>.katex>.katex-html{display:block;position:relative}.katex-display>.katex>.katex-html>.katex-tag{position:absolute;right:0}.katex-display.leqno>.katex>.katex-html>.katex-tag{left:0;right:auto}.katex-display.fleqn>.katex{padding-left:2em;text-align:left}body{counter-reset:katexEqnNo mmlEqnNo}\\n\";\n","/**\n * Plain text with math → safe HTML: THE one way a printer turns authored text\n * (a flashcard face, an exam prompt, a glossary definition) into markup.\n *\n * Every non-markdown printer used to `escapeHtml()` its text, so `\\(\\text{IO}_3^-\\)`\n * on a flashcard printed as that literal source (Arman, 2026-09-27). What is math\n * is decided by the shared tokenizer (`extractMath` — the same rule the screen,\n * the markdown lane and the /document pipeline use: code and `$5` prices are\n * never math); everything else is escaped exactly as before.\n *\n * Typeset output is KaTeX's laid-out HTML plus MathML, chemistry (mhchem)\n * loaded. KaTeX runs with `trust: false`, which refuses every command that could\n * inject HTML (\\href, \\url, \\htmlClass, \\htmlId, \\htmlStyle, \\htmlData,\n * \\includegraphics). The stylesheet is added by `openPrintWindow` whenever a\n * document contains math, so no printer can forget it.\n */\nimport katex from \"katex\";\n// Registers \\ce{…} and \\pu{…} on the same katex instance.\nimport \"katex/contrib/mhchem\";\nimport { escapeHtml } from \"@ai-matrx/kit/html-escape\";\nimport { extractMath, MATH_TOKEN_RE } from \"./document/math\";\n\n/** Typeset one TeX span — the ONE KaTeX configuration every print lane uses. */\nexport function typesetTex(tex: string, display: boolean): string {\n return katex.renderToString(tex, {\n displayMode: display,\n output: \"htmlAndMathml\",\n throwOnError: false,\n trust: false,\n strict: \"ignore\",\n });\n}\n\n/**\n * Escape `text` for HTML, with every math span typeset. Newlines are kept as\n * `\\n` in the prose so callers that turn them into `<br>` keep doing so; a\n * newline inside a formula is folded to a space (TeX treats it as one).\n */\nexport function mathTextHtml(text: string | null | undefined): string {\n // The tokenizer's own markers can never come from an author.\n const source = (text ?? \"\").replace(/[\\uE100\\uE101]/g, \"\");\n if (!/[$\\\\]/.test(source)) return escapeHtml(source);\n const { text: prose, spans } = extractMath(source);\n if (spans.length === 0) return escapeHtml(source);\n return escapeHtml(prose).replace(MATH_TOKEN_RE, (_m: string, i: string) => {\n const span = spans[Number(i)];\n return span ? typesetTex(span.tex.replace(/\\s*\\n\\s*/g, \" \"), span.display) : \"\";\n });\n}\n\n/**\n * A field that IS a formula (a cheat-sheet \"formula\" slot): delimited math is\n * typeset as usual; undelimited TeX (it has a command, `^` or `_`) is typeset\n * whole as inline math; anything else is escaped text.\n */\nexport function formulaHtml(formula: string | null | undefined): string {\n const source = (formula ?? \"\").trim();\n const withMath = mathTextHtml(source);\n if (withMath.includes('class=\"katex\"')) return withMath;\n return /\\\\[a-zA-Z]|[\\^_]/.test(source) ? typesetTex(source, false) : withMath;\n}\n","/**\n * Math in documents — ONE math dialect, typeset in every format.\n *\n * What is math is decided ONLY by the shared tokenizer (`@ai-matrx/content-ir/\n * source`): its fence-nesting rule finds code, its pairing finds `$$…$$`,\n * `$…$` (math, not currency), `\\[…\\]` and `\\(…\\)`. Math is lifted out\n * BEFORE markdown parsing, so a TeX `\\,` is never eaten as a markdown escape\n * and `_`/`*` inside a formula never become emphasis.\n *\n * Typesetting: MathJax (TeX → its internal tree) produces\n * - SVG for PDF display math (drawn as vector paths; allow-list sanitized),\n * - MathML for HTML and EPUB (rendered natively by browsers and readers),\n * - MathML that the Word writer turns into native OMML equations and the\n * PDF writer turns into inline text with real super/subscripts.\n */\n\nimport { listIslands, tokenizeSource } from \"@ai-matrx/content-ir/source\";\nimport { sanitizeMathml, sanitizeMathSvg } from \"../safe-html\";\n\nexport const MATH_OPEN = \"\\uE100\";\nexport const MATH_CLOSE = \"\\uE101\";\nexport const MATH_TOKEN_RE = /\\uE100(\\d+)\\uE101/g;\n\nexport interface MathSpan {\n tex: string;\n display: boolean;\n}\n\nconst DELIMITERS: Record<string, { open: number; close: number; display: boolean }> = {\n $$: { open: 2, close: 2, display: true },\n \"\\\\[\": { open: 2, close: 2, display: true },\n $: { open: 1, close: 1, display: false },\n \"\\\\(\": { open: 2, close: 2, display: false },\n};\n\n/**\n * Replace every math span with a private-use token; returns the spans.\n *\n * Print keeps NO fence, code-span or math detection of its own: the spans are\n * the shared tokenizer's complete `math_block` / `math_inline` islands\n * (`tokenizeSource` + `listIslands` from `@ai-matrx/content-ir/source` — the\n * one fence-nesting rule and the one math pairing), so print, the renderer and\n * the editor can never disagree about what is math. The delimiter decides the\n * form: `$$`/`\\[` display, `$`/`\\(` inline.\n */\nexport function extractMath(body: string): { text: string; spans: MathSpan[] } {\n const spans: MathSpan[] = [];\n if (!/[$\\\\]/.test(body)) return { text: body, spans };\n const islands = listIslands(tokenizeSource(body))\n .filter((isl) => isl.complete && (isl.islandType === \"math_block\" || isl.islandType === \"math_inline\"))\n .sort((a, b) => a.start - b.start);\n let out = \"\";\n let at = 0;\n for (const isl of islands) {\n const d = DELIMITERS[String(isl.meta.delimiter ?? \"\")];\n if (!d || isl.start < at) continue;\n out += body.slice(at, isl.start);\n spans.push({ tex: body.slice(isl.start + d.open, isl.end - d.close).trim(), display: d.display });\n out += `${MATH_OPEN}${spans.length - 1}${MATH_CLOSE}`;\n at = isl.end;\n }\n return { text: out + body.slice(at), spans };\n}\n\n// ── typesetting ────────────────────────────────────────────────────────────\n\nexport interface TypesetMath {\n /** MathML (<math …>…</math>) for HTML/EPUB and the Word/PDF-inline writers. */\n mathml: string;\n /** SVG for PDF display math; widths in ex (1ex ≈ 0.442em in MathJax). */\n svg?: string | undefined;\n widthEx?: number | undefined;\n heightEx?: number | undefined;\n /** Error message when the TeX could not be typeset. */\n error?: string | undefined;\n}\n\nexport const mathKey = (tex: string, display: boolean): string => `${display ? \"D\" : \"I\"}:${tex}`;\n\ntype MathJaxKit = import(\"../mathjax\").MathJaxKit;\n\nconst MATHJAX = Symbol.for(\"ai-matrx.print.document.mathjax\");\n\nasync function loadMathJax(): Promise<MathJaxKit> {\n const g = globalThis as unknown as Record<symbol, Promise<MathJaxKit> | undefined>;\n if (g[MATHJAX]) return g[MATHJAX] as Promise<MathJaxKit>;\n // The self-referencing specifier keeps this a real lazy edge in dist (see src/mathjax.ts).\n const pending = import(\"@ai-matrx/print/mathjax\").then((m) => m.createMathJaxKit());\n pending.catch(() => {\n if (g[MATHJAX] === pending) g[MATHJAX] = undefined;\n });\n g[MATHJAX] = pending;\n return pending;\n}\n\n/** Typeset every math span once; keyed by `mathKey`. */\nexport async function typesetMathSpans(spans: MathSpan[]): Promise<Map<string, TypesetMath>> {\n const out = new Map<string, TypesetMath>();\n if (spans.length === 0) return out;\n const mj = await loadMathJax();\n for (const s of spans) {\n const key = mathKey(s.tex, s.display);\n if (out.has(key)) continue;\n try {\n const mathml = mj.mml(s.tex, s.display);\n const err = /<merror[\\s\\S]*?<mtext[^>]*>([\\s\\S]*?)<\\/mtext>/.exec(mathml);\n if (err) {\n out.set(key, { mathml: sanitizeMathml(mathml), error: decodeXml(err[1] as string) });\n continue;\n }\n // TeX is author input: `\\href`, `\\style`, `\\bbox[…]`, `\\mmlToken` reach\n // MathML attributes, so the typeset MathML goes through the allow-list.\n const entry: TypesetMath = { mathml: sanitizeMathml(mathml.replace(/\\n\\s*/g, \"\")) };\n if (s.display) {\n // The same author TeX reaches the SVG as <a href>, style, class and id\n // (LIST T26): only the drawing survives.\n const svg = sanitizeMathSvg(mj.svg(s.tex, true));\n entry.svg = svg;\n entry.widthEx = Number(/width=\"([\\d.]+)ex\"/.exec(svg)?.[1] ?? 0);\n entry.heightEx = Number(/height=\"([\\d.]+)ex\"/.exec(svg)?.[1] ?? 0);\n }\n out.set(key, entry);\n } catch (e) {\n out.set(key, { mathml: \"\", error: (e as Error).message });\n }\n }\n return out;\n}\n\n// ── a small MathML reader (MathJax's serializer output is well-formed XML) ──\n\nexport interface MNode {\n tag: string;\n attrs: Record<string, string>;\n children: MNode[];\n text: string;\n}\n\nexport function decodeXml(s: string): string {\n return s.replace(/&(#x[0-9a-f]+|#\\d+|amp|lt|gt|quot|apos);/gi, (m, e: string) => {\n if (e[0] === \"#\") return String.fromCodePoint(e[1] === \"x\" || e[1] === \"X\" ? parseInt(e.slice(2), 16) : Number(e.slice(1)));\n return ({ amp: \"&\", lt: \"<\", gt: \">\", quot: '\"', apos: \"'\" } as Record<string, string>)[e.toLowerCase()] ?? m;\n });\n}\n\nexport function parseMathML(xml: string): MNode {\n const root: MNode = { tag: \"#root\", attrs: {}, children: [], text: \"\" };\n const stack: MNode[] = [root];\n for (const m of xml.matchAll(/<(\\/?)([\\w:-]+)((?:\\s+[\\w:-]+=\"[^\"]*\")*)\\s*(\\/?)>|([^<]+)/g)) {\n const top = stack[stack.length - 1] as MNode;\n if (m[5] !== undefined) {\n const t = decodeXml(m[5]);\n if (t.trim() || top.tag === \"mtext\" || top.tag === \"mo\") top.text += t;\n continue;\n }\n if (m[1] === \"/\") {\n if (stack.length > 1) stack.pop();\n continue;\n }\n const attrs: Record<string, string> = {};\n for (const a of (m[3] ?? \"\").matchAll(/([\\w:-]+)=\"([^\"]*)\"/g)) attrs[a[1] as string] = decodeXml(a[2] as string);\n const node: MNode = { tag: (m[2] as string).replace(/^m:/, \"\"), attrs, children: [], text: \"\" };\n top.children.push(node);\n if (m[4] !== \"/\") stack.push(node);\n }\n return root.children[0] ?? root;\n}\n\n/** Flatten to plain text (the linear reading of a formula). */\nexport function mathText(n: MNode): string {\n if (n.children.length === 0) return n.text;\n return n.children.map(mathText).join(\"\");\n}\n","/**\n * The flashcards printer's DESCRIPTION: label, variants, settings. Pure data with no imports,\n * so a host registry can offer \"Print flashcards\" without loading the renderer (and the KaTeX\n * inside it). `flashcardsPrinter` (./flashcards) spreads this; `flashcardsPrinterLazy`\n * (./flashcards-lazy) pairs it with a print that loads the renderer on first use.\n */\nimport type { BlockPrinter } from \"./core\";\n\nexport const flashcardsPrinterMeta: Pick<BlockPrinter, \"label\" | \"variants\" | \"settings\"> = {\n label: \"Print flashcards\",\n variants: [\n {\n id: \"portrait-6up-duplex\",\n label: \"Portrait 6-up — duplex (recommended)\",\n description:\n \"6 cards per page (2 columns × 3 rows), portrait. Split down the middle and into thirds. Fronts and backs on alternating pages — backs are mirrored so they align perfectly when duplex-printed (flip on long edge) and cut into 6\",\n },\n {\n id: \"portrait-6up-stacked\",\n label: \"Portrait 6-up — all fronts then all backs\",\n description:\n \"6 cards per page (2 columns × 3 rows), portrait. All front pages first, then all back pages — cut all sheets together for a matching deck\",\n },\n {\n id: \"landscape-duplex\",\n label: \"Landscape cut sheet — duplex (double-sided)\",\n description:\n \"4 cards per page, landscape. Fronts and backs on alternating pages — backs are mirrored so they align perfectly when you flip and cut\",\n },\n {\n id: \"landscape-stacked\",\n label: \"Landscape cut sheet — all fronts then all backs\",\n description:\n \"4 cards per page, landscape. All front pages first, then all back pages — cut all sheets together for a matching deck\",\n },\n {\n id: \"avery-5388\",\n label: 'Avery 5388 — 3×5\" index cards (3 per page)',\n description:\n \"Prints to Avery 5388 card stock. 3 cards per page, portrait. Front page then back page per set of 3 — load the sheet twice to print both sides\",\n },\n {\n id: \"cut-cards\",\n label: \"Cut-out cards (portrait)\",\n description:\n \"4 per page with cut lines — front and back shown together per card\",\n },\n {\n id: \"both-sides\",\n label: \"Both sides per card\",\n description: \"Front and back shown together — best for review or sharing\",\n },\n {\n id: \"study-sheet\",\n label: \"Study sheet (table)\",\n description: \"Compact two-column table — all cards on one or two pages\",\n },\n {\n id: \"front-only\",\n label: \"Questions only (self-test)\",\n description:\n \"Front side with blank space to write answers — print and quiz yourself\",\n },\n {\n id: \"back-only\",\n label: \"Answers only\",\n description: \"Definitions / answers without the questions\",\n },\n ],\n\n settings: [\n {\n type: \"range\" as const,\n id: \"cardRange\",\n label: \"Cards to print\",\n description: \"Leave blank to print the whole deck\",\n fromId: \"fromCard\",\n toId: \"toCard\",\n defaultFrom: 0,\n defaultTo: 0,\n min: 1,\n fromPlaceholder: \"1\",\n toPlaceholder: \"end\",\n },\n {\n type: \"boolean\" as const,\n id: \"showSideLabel\",\n label: \"Show side label\",\n description: 'Prints \"Front\" or \"Back\" in the corner of each card',\n defaultValue: false,\n appliesTo: [\n \"landscape-duplex\",\n \"landscape-stacked\",\n \"portrait-6up-duplex\",\n \"portrait-6up-stacked\",\n \"avery-5388\",\n ],\n },\n {\n type: \"boolean\" as const,\n id: \"showCardNumber\",\n label: \"Show card number\",\n description: \"Prints #1, #2 … in the corner of each card\",\n defaultValue: false,\n appliesTo: [\n \"landscape-duplex\",\n \"landscape-stacked\",\n \"portrait-6up-duplex\",\n \"portrait-6up-stacked\",\n \"avery-5388\",\n ],\n },\n {\n type: \"boolean\" as const,\n id: \"showTickMarks\",\n label: \"Show cut tick marks\",\n description:\n \"Small marks at each cut intersection — where to make each cut\",\n defaultValue: false,\n appliesTo: [\n \"landscape-duplex\",\n \"landscape-stacked\",\n \"portrait-6up-duplex\",\n \"portrait-6up-stacked\",\n ],\n },\n {\n type: \"boolean\" as const,\n id: \"showCutLines\",\n label: \"Show full cut lines\",\n description:\n \"Dashed lines all the way across the page — helps if you don't have a ruler\",\n defaultValue: false,\n appliesTo: [\n \"landscape-duplex\",\n \"landscape-stacked\",\n \"portrait-6up-duplex\",\n \"portrait-6up-stacked\",\n ],\n },\n {\n type: \"boolean\" as const,\n id: \"lightBackText\",\n label: \"Light back text (reduce show-through)\",\n description:\n \"Prints the back side in dark gray instead of black — ink is lighter and less visible through the paper\",\n defaultValue: false,\n appliesTo: [\n \"landscape-duplex\",\n \"landscape-stacked\",\n \"portrait-6up-duplex\",\n \"portrait-6up-stacked\",\n \"avery-5388\",\n ],\n },\n {\n type: \"boolean\" as const,\n id: \"mirrorBack\",\n label: \"Mirror back text (best show-through prevention)\",\n description:\n \"Flips back text horizontally — any ink that bleeds through reads backwards and is unrecognizable from the front\",\n defaultValue: false,\n appliesTo: [\n \"landscape-duplex\",\n \"landscape-stacked\",\n \"portrait-6up-duplex\",\n \"portrait-6up-stacked\",\n \"avery-5388\",\n ],\n },\n {\n type: \"boolean\" as const,\n id: \"showImages\",\n label: \"Print face images\",\n description:\n \"Includes each card's front/back image when it has one. The fixed-geometry formats (landscape, 6-up, Avery) are text-only by design\",\n defaultValue: true,\n appliesTo: [\n \"cut-cards\",\n \"both-sides\",\n \"study-sheet\",\n \"front-only\",\n \"back-only\",\n ],\n },\n ],\n};\n","/**\n * FlashcardsBlock — BlockPrinter\n *\n * Self-contained print logic for flashcard blocks.\n * Exported alongside the component so it travels with the block to the database.\n *\n * Variants:\n * cut-cards — 4-per-page grid with physical cut lines; designed to be printed, cut, and used as real cards\n * both-sides — 2-column grid showing front + back together (review/overview)\n * study-sheet — compact table (front | back) for quick reference or cramming\n * front-only — questions/terms only, blank backs for self-testing\n * back-only — answers/definitions only\n */\n\nimport {\n buildPrintDocument,\n openPrintWindow,\n buildFitTextScript,\n type BlockPrinter,\n type PrintOutcome,\n type PrintSettings,\n} from \"./core\";\nimport { mathTextHtml } from \"./math-text\";\nimport { flashcardsPrinterMeta } from \"./flashcards-meta\";\nimport { escapeHtml } from \"@ai-matrx/kit/html-escape\";\n\n/**\n * A single flashcard. Structural port of the host parser's `Flashcard`\n * (matrx-frontend `flashcard-parser.ts`) — identical fields, no import.\n */\nexport interface Flashcard {\n front: string;\n back: string;\n /** Optional face images (durable URLs — fc_detail front_image/back_image).\n * Parsed markdown never sets these; DB-backed callers may. Print + rich\n * renderers consume them when present. */\n frontImageUrl?: string | null;\n frontImageAlt?: string | null;\n backImageUrl?: string | null;\n backImageAlt?: string | null;\n}\n\n/**\n * Structural stand-in for the host's generated `FlashcardsBlockData` — the\n * package takes no generated-type dependency; `extractCards` reads `cards`\n * structurally from whatever the host hands it.\n */\ninterface FlashcardsBlockData {\n cards?: unknown;\n title?: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// Settings helpers\n// ---------------------------------------------------------------------------\n\n/** Resolved settings with all defaults filled in — safe to use without null checks */\ninterface CardPrintSettings {\n showSideLabel: boolean; // \"Front\" / \"Back\" label\n showCardNumber: boolean; // \"#1\", \"#2\" … badge\n showTickMarks: boolean; // 4 small tick marks at cut intersection\n showCutLines: boolean; // full dashed crossing lines\n lightBackText: boolean; // print back text in dark gray (~#555) to reduce show-through\n mirrorBack: boolean; // horizontally flip back page text so bleed-through is unreadable\n showImages: boolean; // print face images (durable URLs only) — default ON\n}\n\nconst SETTING_DEFAULTS: CardPrintSettings = {\n showSideLabel: false,\n showCardNumber: false,\n showTickMarks: false,\n showCutLines: false,\n lightBackText: false,\n mirrorBack: false,\n showImages: true,\n};\n\nfunction resolveSettings(raw?: PrintSettings): CardPrintSettings {\n if (!raw) return { ...SETTING_DEFAULTS };\n const b = (key: keyof CardPrintSettings) =>\n raw[key] !== undefined ? Boolean(raw[key]) : SETTING_DEFAULTS[key];\n return {\n showSideLabel: b(\"showSideLabel\"),\n showCardNumber: b(\"showCardNumber\"),\n showTickMarks: b(\"showTickMarks\"),\n showCutLines: b(\"showCutLines\"),\n lightBackText: b(\"lightBackText\"),\n mirrorBack: b(\"mirrorBack\"),\n showImages: b(\"showImages\"),\n };\n}\n\nexport type FlashcardsVariant =\n | \"landscape-duplex\"\n | \"landscape-stacked\"\n | \"portrait-6up-duplex\"\n | \"portrait-6up-stacked\"\n | \"avery-5388\"\n | \"cut-cards\"\n | \"both-sides\"\n | \"study-sheet\"\n | \"front-only\"\n | \"back-only\";\n\ntype FlashcardsData = { cards: Flashcard[] } | FlashcardsBlockData | string;\n\nfunction toCard(entry: unknown): Flashcard | null {\n if (!entry || typeof entry !== \"object\") return null;\n const e = entry as Record<string, unknown>;\n if (typeof e.front === \"string\") return entry as Flashcard;\n const front = e.question ?? e.term;\n if (typeof front !== \"string\") return null;\n const back = e.back ?? e.answer ?? e.definition;\n return { ...(entry as object), front, back: typeof back === \"string\" ? back : \"\" } as Flashcard;\n}\n\n/** Cards from `{ cards }` or a bare array; anything else is not a deck this printer can read. */\nfunction extractCards(data: unknown): Flashcard[] {\n if (!data) return [];\n const list = Array.isArray(data)\n ? data\n : typeof data === \"object\" && \"cards\" in data\n ? (data as { cards: unknown }).cards\n : null;\n if (!Array.isArray(list)) return [];\n return list.map(toCard).filter((card): card is Flashcard => card !== null);\n}\n\n/**\n * Face image for print — durable URL only (the print window is a fresh\n * document; it can load exactly what a browser can fetch unauthenticated).\n * Alt text prints in place when a hotlink has rotted. The custom-geometry\n * variants (landscape / 6-up / Avery) stay text-only by design — their fixed\n * cells can't absorb an image without breaking the die-cut math.\n */\nfunction faceImageHtml(\n card: Flashcard,\n face: \"front\" | \"back\",\n showImages = true,\n): string {\n if (!showImages) return \"\";\n const url = face === \"front\" ? card.frontImageUrl : card.backImageUrl;\n if (!url || !/^https?:\\/\\//.test(url)) return \"\";\n const alt = (face === \"front\" ? card.frontImageAlt : card.backImageAlt) ?? \"\";\n return `<img class=\"face-image\" src=\"${escapeHtml(url)}\" alt=\"${escapeHtml(alt)}\">`;\n}\n\n// ---------------------------------------------------------------------------\n// Shared base styles — appended after block-print-utils PRINT_STYLES so these win on specificity\n// ---------------------------------------------------------------------------\nconst BASE_STYLES = `\n img.face-image {\n display: block;\n max-width: 100%;\n max-height: 1.6in;\n margin: 0 auto 6px auto;\n object-fit: contain;\n }\n h1.deck-title {\n font-size: 13pt !important;\n font-weight: 700;\n color: #1e293b;\n margin: 0 0 4px 0;\n letter-spacing: -0.01em;\n border-bottom: none !important;\n }\n h2 { border-bottom: none !important; }\n .deck-meta {\n font-size: 8.5pt;\n color: #64748b;\n margin-bottom: 16px;\n }\n .deck-header {\n border-bottom: 1.5px solid #e2e8f0;\n padding-bottom: 10px;\n margin-bottom: 16px;\n }\n\n @media print {\n @page { margin: 14mm 12mm; }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Variant: Cut-out physical cards (4 per page, front face + scissor guides)\n// ---------------------------------------------------------------------------\nconst CUT_CARD_STYLES = `\n ${BASE_STYLES}\n\n .instructions {\n font-size: 8pt;\n color: #64748b;\n background: #f8fafc;\n border: 1px solid #e2e8f0;\n border-radius: 6px;\n padding: 7px 10px;\n margin-bottom: 14px;\n line-height: 1.5;\n }\n\n .cards-grid {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 0;\n }\n\n .card-cell {\n position: relative;\n border: 1px dashed #cbd5e1;\n padding: 0;\n min-height: 120px;\n }\n\n /* Cut-line corner markers */\n .card-cell::before, .card-cell::after {\n content: \"\";\n position: absolute;\n width: 8px;\n height: 8px;\n border-color: #94a3b8;\n border-style: solid;\n }\n .card-cell::before { top: 3px; left: 3px; border-width: 1px 0 0 1px; }\n .card-cell::after { bottom: 3px; right: 3px; border-width: 0 1px 1px 0; }\n\n .card-inner {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 120px;\n }\n\n .card-face {\n flex: 1;\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n padding: 12px 14px;\n text-align: center;\n }\n\n .card-front-face {\n background: linear-gradient(135deg, #1e3a5f 0%, #1e40af 100%);\n color: #ffffff;\n border-radius: 0;\n }\n .card-back-face {\n background: #ffffff;\n border-top: 1.5px solid #e2e8f0;\n }\n\n .card-side-label {\n font-size: 7pt;\n font-weight: 700;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n margin-bottom: 6px;\n opacity: 0.65;\n }\n .card-front-face .card-side-label { color: #bfdbfe; }\n .card-back-face .card-side-label { color: #94a3b8; }\n\n .card-number-badge {\n position: absolute;\n top: 6px;\n right: 8px;\n font-size: 7pt;\n color: #94a3b8;\n font-weight: 600;\n }\n\n .front-text {\n font-size: 11pt;\n font-weight: 600;\n line-height: 1.4;\n color: #ffffff;\n }\n .front-text.long { font-size: 9.5pt; }\n .front-text.xlong { font-size: 8.5pt; }\n\n .back-text {\n font-size: 10pt;\n line-height: 1.5;\n color: #1a1a1a;\n white-space: pre-line;\n text-align: left;\n width: 100%;\n }\n .back-text.long { font-size: 9pt; }\n .back-text.xlong { font-size: 8pt; }\n\n .fold-hint {\n font-size: 7pt;\n color: #94a3b8;\n text-align: center;\n margin-top: 10px;\n letter-spacing: 0.04em;\n }\n\n .page-break { page-break-before: always; }\n\n @media print {\n .card-front-face { -webkit-print-color-adjust: exact; print-color-adjust: exact; }\n .card-cell { page-break-inside: avoid; break-inside: avoid; }\n .instructions { display: none; }\n }\n`;\n\nfunction sizeClass(text: string): string {\n const len = text.length;\n if (len > 200) return \"xlong\";\n if (len > 100) return \"long\";\n return \"\";\n}\n\nfunction renderCutCards(\n cards: Flashcard[],\n title: string,\n s: CardPrintSettings,\n startIndex = 0,\n): string {\n const PAGE_SIZE = 4; // cards per page\n const pages: string[] = [];\n\n for (let p = 0; p < Math.ceil(cards.length / PAGE_SIZE); p++) {\n const batch = cards.slice(p * PAGE_SIZE, (p + 1) * PAGE_SIZE);\n\n const cells = batch\n .map((card, i) => {\n const globalIdx = startIndex + p * PAGE_SIZE + i + 1;\n const frontSize = sizeClass(card.front);\n const backSize = sizeClass(card.back ?? \"\");\n const backContent = (card.back ?? \"\")\n .split(\"\\n\")\n .map((line) => mathTextHtml(line))\n .join(\"<br>\");\n\n return `<div class=\"card-cell\">\n <span class=\"card-number-badge\">#${globalIdx}</span>\n <div class=\"card-inner\">\n <div class=\"card-face card-front-face\">\n <div class=\"card-side-label\">Question / Term</div>\n ${faceImageHtml(card, \"front\", s.showImages)}<div class=\"front-text ${frontSize}\">${mathTextHtml(card.front)}</div>\n </div>\n <div class=\"card-face card-back-face\">\n <div class=\"card-side-label\">Answer / Definition</div>\n ${faceImageHtml(card, \"back\", s.showImages)}<div class=\"back-text ${backSize}\">${backContent}</div>\n </div>\n </div>\n</div>`;\n })\n .join(\"\\n\");\n\n const isFirst = p === 0;\n const pageClass = isFirst ? \"\" : ' class=\"page-break\"';\n pages.push(`<div${pageClass}>\n${\n isFirst\n ? `<div class=\"deck-header\">\n <h1 class=\"deck-title\">${mathTextHtml(title)}</h1>\n <div class=\"deck-meta\">${cards.length} card${cards.length !== 1 ? \"s\" : \"\"} &nbsp;·&nbsp; Cut along dashed lines</div>\n</div>\n<div class=\"instructions\">\n ✂ Print this page, then cut along the dashed lines. Each cell is one card — front (blue) on top, answer on the bottom.\n Fold in half for a double-sided card, or cut each half separately for separate front/back cards.\n</div>`\n : `<div class=\"deck-meta\" style=\"margin-bottom:10px;\">Cards ${startIndex + p * PAGE_SIZE + 1}–${startIndex + Math.min((p + 1) * PAGE_SIZE, cards.length)}</div>`\n}\n<div class=\"cards-grid\">\n${cells}\n</div>\n<div class=\"fold-hint\">✂ cut along dashed borders · fold on the center line for double-sided cards</div>\n</div>`);\n }\n\n return pages.join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// Variant: Both sides (2-column card grid, front + back visible)\n// ---------------------------------------------------------------------------\nconst BOTH_SIDES_STYLES = `\n ${BASE_STYLES}\n\n .cards-grid {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 10px;\n }\n .card-cell {\n border: 1.5px solid #e2e8f0;\n border-radius: 8px;\n overflow: hidden;\n page-break-inside: avoid;\n break-inside: avoid;\n background: #fff;\n box-shadow: 0 1px 3px rgba(0,0,0,0.06);\n }\n .card-front {\n background: #1e293b;\n color: #f1f5f9;\n padding: 10px 14px;\n font-weight: 600;\n font-size: 10.5pt;\n line-height: 1.4;\n position: relative;\n }\n .card-front .card-number { font-size: 7.5pt; color: #64748b; margin-bottom: 4px; }\n .card-back {\n padding: 10px 14px;\n font-size: 10pt;\n line-height: 1.5;\n color: #1a1a1a;\n background: #fff;\n border-top: 1px solid #e2e8f0;\n white-space: pre-line;\n }\n\n @media print {\n .card-front { -webkit-print-color-adjust: exact; print-color-adjust: exact; }\n }\n`;\n\nfunction renderBothSides(\n cards: Flashcard[],\n title: string,\n s: CardPrintSettings,\n startIndex = 0,\n): string {\n const cells = cards\n .map(\n (card, i) => `<div class=\"card-cell\">\n <div class=\"card-front\">\n <div class=\"card-number\">Card ${startIndex + i + 1}</div>\n ${faceImageHtml(card, \"front\", s.showImages)}${mathTextHtml(card.front)}\n </div>\n <div class=\"card-back\">${faceImageHtml(card, \"back\", s.showImages)}${mathTextHtml(card.back ?? \"\")}</div>\n</div>`,\n )\n .join(\"\\n\");\n\n return `<div class=\"deck-header\">\n <h1 class=\"deck-title\">${mathTextHtml(title)}</h1>\n <div class=\"deck-meta\">${cards.length} card${cards.length !== 1 ? \"s\" : \"\"} &nbsp;·&nbsp; Front and back</div>\n</div>\n<div class=\"cards-grid\">\n${cells}\n</div>`;\n}\n\n// ---------------------------------------------------------------------------\n// Variant: Study sheet (full-width table)\n// ---------------------------------------------------------------------------\nconst STUDY_SHEET_STYLES = `\n ${BASE_STYLES}\n\n table {\n width: 100%;\n border-collapse: collapse;\n margin-top: 8px;\n font-size: 10pt;\n }\n thead th {\n background: #1e293b;\n color: #f1f5f9;\n padding: 8px 12px;\n text-align: left;\n font-size: 8.5pt;\n font-weight: 700;\n letter-spacing: 0.05em;\n text-transform: uppercase;\n }\n thead th:first-child { width: 38%; border-radius: 0; }\n\n tbody tr { page-break-inside: avoid; break-inside: avoid; }\n tbody tr:nth-child(even) td { background: #f8fafc; }\n\n tbody td {\n padding: 8px 12px;\n vertical-align: top;\n border-bottom: 1px solid #e2e8f0;\n line-height: 1.5;\n white-space: pre-line;\n }\n tbody td:first-child {\n font-weight: 600;\n border-right: 1px solid #e2e8f0;\n color: #1e3a5f;\n }\n .row-num {\n display: inline-block;\n width: 18px;\n font-size: 8pt;\n color: #94a3b8;\n font-weight: 400;\n margin-right: 4px;\n }\n\n @media print {\n thead th { -webkit-print-color-adjust: exact; print-color-adjust: exact; }\n tbody tr:nth-child(even) td { -webkit-print-color-adjust: exact; print-color-adjust: exact; }\n }\n`;\n\n/** Class names of a study sheet: the standalone print keeps its original names (golden master). */\ninterface StudySheetClasses {\n header: string;\n title: string;\n meta: string;\n rowNum: string;\n}\nconst STUDY_SHEET_LEGACY_CLASSES: StudySheetClasses = {\n header: \"deck-header\",\n title: \"deck-title\",\n meta: \"deck-meta\",\n rowNum: \"row-num\",\n};\n\nfunction renderStudySheet(\n cards: Flashcard[],\n title: string,\n s: CardPrintSettings,\n startIndex = 0,\n c: StudySheetClasses = STUDY_SHEET_LEGACY_CLASSES,\n): string {\n const rows = cards\n .map(\n (card, i) => ` <tr>\n <td><span class=\"${c.rowNum}\">${startIndex + i + 1}.</span>${faceImageHtml(card, \"front\", s.showImages)}${mathTextHtml(card.front)}</td>\n <td>${faceImageHtml(card, \"back\", s.showImages)}${mathTextHtml(card.back ?? \"\")}</td>\n </tr>`,\n )\n .join(\"\\n\");\n\n return `<div class=\"${c.header}\">\n <h1 class=\"${c.title}\">${mathTextHtml(title)}</h1>\n <div class=\"${c.meta}\">${cards.length} card${cards.length !== 1 ? \"s\" : \"\"} &nbsp;·&nbsp; Study sheet</div>\n</div>\n<table>\n <thead>\n <tr>\n <th>Question / Term</th>\n <th>Answer / Definition</th>\n </tr>\n </thead>\n <tbody>\n${rows}\n </tbody>\n</table>`;\n}\n\n/**\n * The study sheet inside a composed print (a message's Print). The composer\n * keeps only `matrx-*` classes and a `css` channel, so the sheet is scoped\n * under `matrx-fc-sheet` and every class carries the `matrx-fc-` prefix.\n */\nconst COMPOSED_CLASSES: StudySheetClasses = {\n header: \"matrx-fc-header\",\n title: \"matrx-fc-title\",\n meta: \"matrx-fc-meta\",\n rowNum: \"matrx-fc-row-num\",\n};\n\nconst COMPOSED_STUDY_SHEET_CSS = `\n .matrx-fc-sheet .matrx-fc-header { border-bottom: 1.5px solid #e2e8f0; padding-bottom: 10px; margin-bottom: 16px; }\n .matrx-fc-sheet .matrx-fc-title { font-size: 13pt; font-weight: 700; color: #1e293b; margin: 0 0 4px 0; letter-spacing: -0.01em; border-bottom: none; }\n .matrx-fc-sheet .matrx-fc-meta { font-size: 8.5pt; color: #64748b; }\n .matrx-fc-sheet table { width: 100%; border-collapse: collapse; margin-top: 8px; font-size: 10pt; }\n .matrx-fc-sheet thead th { background: #1e293b; color: #f1f5f9; padding: 8px 12px; text-align: left; font-size: 8.5pt; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; }\n .matrx-fc-sheet thead th:first-child { width: 38%; }\n .matrx-fc-sheet tbody tr { page-break-inside: avoid; break-inside: avoid; }\n .matrx-fc-sheet tbody tr:nth-child(even) td { background: #f8fafc; }\n .matrx-fc-sheet tbody td { padding: 8px 12px; vertical-align: top; border-bottom: 1px solid #e2e8f0; line-height: 1.5; white-space: pre-line; }\n .matrx-fc-sheet tbody td:first-child { font-weight: 600; border-right: 1px solid #e2e8f0; color: #1e3a5f; }\n .matrx-fc-sheet .matrx-fc-row-num { display: inline-block; width: 18px; font-size: 8pt; color: #94a3b8; font-weight: 400; margin-right: 4px; }\n .matrx-fc-sheet .matrx-fc-face-image { display: block; max-width: 100%; max-height: 1.6in; margin: 0 auto 6px auto; object-fit: contain; }\n @media print {\n .matrx-fc-sheet thead th, .matrx-fc-sheet tbody tr:nth-child(even) td { -webkit-print-color-adjust: exact; print-color-adjust: exact; }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Variant: Front only (self-test — blank space for answers)\n// ---------------------------------------------------------------------------\nconst FRONT_ONLY_STYLES = `\n ${BASE_STYLES}\n\n .cards-grid {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 10px;\n }\n .card-cell {\n border: 1.5px solid #bfdbfe;\n border-radius: 8px;\n overflow: hidden;\n page-break-inside: avoid;\n break-inside: avoid;\n background: #fff;\n }\n .card-front {\n background: linear-gradient(135deg, #1e3a5f 0%, #1e40af 100%);\n color: #ffffff;\n padding: 12px 14px;\n font-weight: 600;\n font-size: 10.5pt;\n line-height: 1.4;\n }\n .card-front .card-number { font-size: 7.5pt; color: #bfdbfe; margin-bottom: 4px; }\n .answer-blank {\n height: 52px;\n border-top: 1px dashed #cbd5e1;\n background: #fafafa;\n position: relative;\n }\n .answer-blank::after {\n content: \"write your answer here\";\n position: absolute;\n bottom: 6px;\n left: 12px;\n font-size: 7pt;\n color: #cbd5e1;\n letter-spacing: 0.04em;\n }\n\n @media print {\n .card-front { -webkit-print-color-adjust: exact; print-color-adjust: exact; }\n }\n`;\n\nfunction renderFrontOnly(\n cards: Flashcard[],\n title: string,\n s: CardPrintSettings,\n startIndex = 0,\n): string {\n const cells = cards\n .map(\n (card, i) => `<div class=\"card-cell\">\n <div class=\"card-front\">\n <div class=\"card-number\">Card ${startIndex + i + 1}</div>\n ${faceImageHtml(card, \"front\", s.showImages)}${mathTextHtml(card.front)}\n </div>\n <div class=\"answer-blank\"></div>\n</div>`,\n )\n .join(\"\\n\");\n\n return `<div class=\"deck-header\">\n <h1 class=\"deck-title\">${mathTextHtml(title)}</h1>\n <div class=\"deck-meta\">${cards.length} card${cards.length !== 1 ? \"s\" : \"\"} &nbsp;·&nbsp; Self-test — write your answers in the blank space</div>\n</div>\n<div class=\"cards-grid\">\n${cells}\n</div>`;\n}\n\n// ---------------------------------------------------------------------------\n// Variant: Back only (answers / definitions)\n// ---------------------------------------------------------------------------\nconst BACK_ONLY_STYLES = `\n ${BASE_STYLES}\n\n .cards-grid {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 10px;\n }\n .card-cell {\n border: 1.5px solid #a7f3d0;\n border-radius: 8px;\n overflow: hidden;\n page-break-inside: avoid;\n break-inside: avoid;\n background: #fff;\n }\n .card-back-header {\n background: linear-gradient(135deg, #065f46 0%, #0f766e 100%);\n color: #ecfdf5;\n padding: 10px 14px;\n font-size: 7.5pt;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n }\n .card-back-content {\n padding: 10px 14px;\n font-size: 10pt;\n line-height: 1.5;\n color: #1a1a1a;\n white-space: pre-line;\n }\n\n @media print {\n .card-back-header { -webkit-print-color-adjust: exact; print-color-adjust: exact; }\n }\n`;\n\nfunction renderBackOnly(\n cards: Flashcard[],\n title: string,\n s: CardPrintSettings,\n startIndex = 0,\n): string {\n const cells = cards\n .map(\n (card, i) => `<div class=\"card-cell\">\n <div class=\"card-back-header\">Card ${startIndex + i + 1}</div>\n <div class=\"card-back-content\">${faceImageHtml(card, \"back\", s.showImages)}${mathTextHtml(card.back ?? \"\")}</div>\n</div>`,\n )\n .join(\"\\n\");\n\n return `<div class=\"deck-header\">\n <h1 class=\"deck-title\">${mathTextHtml(title)}</h1>\n <div class=\"deck-meta\">${cards.length} card${cards.length !== 1 ? \"s\" : \"\"} &nbsp;·&nbsp; Answers and definitions only</div>\n</div>\n<div class=\"cards-grid\">\n${cells}\n</div>`;\n}\n\n// ---------------------------------------------------------------------------\n// Variant: Landscape cut sheet (4 cards per page, 2×2, black & white)\n//\n// Geometry (landscape 11\" × 8.5\"):\n// Page is split into 4 equal quadrants: 5.5\" wide × 4.25\" tall each.\n// Outer margin: 0.5\" on every edge.\n// Inner gap at each cut line: 0.5\" each side → 1\" total dead zone at center.\n// Usable card area: (5.5 − 0.5 − 0.5)\" × (4.25 − 0.5 − 0.5)\" = 4.5\" × 3.25\"\n//\n// Text sizing: font-size uses vw/vh relative units so it always fills the cell.\n// `fit-text` class uses container-relative font that shrinks when content overflows\n// via a JS resize loop in the document itself.\n//\n// Double-sided (duplex) mirroring:\n// When a landscape sheet is flipped on the long (left→right) edge,\n// left and right columns swap. So back page layout is:\n// TL = back[1], TR = back[0], BL = back[3], BR = back[2]\n// ---------------------------------------------------------------------------\n\nconst LANDSCAPE_STYLES = `\n @page { size: landscape; margin: 0; }\n\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n\n html, body {\n width: 11in;\n background: #fff;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Helvetica, Arial, sans-serif;\n color: #000;\n /* Screen: scroll to see all pages. Print: locked to one page height per .page. */\n }\n\n /* The page wrapper takes the full physical page */\n .page {\n width: 11in;\n height: 8.5in;\n position: relative;\n page-break-after: always;\n break-after: page;\n display: grid;\n grid-template-columns: 1fr 1fr;\n grid-template-rows: 1fr 1fr;\n gap: 0;\n padding: 0.5in;\n /* Thin separator visible on screen between pages */\n border-bottom: 2px solid #e2e8f0;\n }\n .page:last-child {\n page-break-after: auto;\n break-after: auto;\n border-bottom: none;\n }\n\n @media print {\n /* Do NOT constrain html/body height — that clips pages 2+.\n Page sizing is handled by @page + .page { height: 8.5in } + break-after: page. */\n html, body { width: 11in; height: auto; overflow: visible; }\n .page { border-bottom: none; }\n }\n\n /* Each quadrant cell — full 5.5\" × 4.25\" including its half-gutter */\n .quad {\n display: flex;\n align-items: stretch;\n justify-content: stretch;\n padding: 0 0.25in 0.25in 0; /* right + bottom inner half-gutters */\n }\n /* Top row: add top inner half-gutter via bottom padding of the cell above */\n .quad.top { padding-bottom: 0.25in; padding-top: 0; }\n .quad.bottom { padding-bottom: 0; padding-top: 0.25in; }\n .quad.left { padding-left: 0; padding-right: 0.25in; }\n .quad.right { padding-left: 0.25in; padding-right: 0; }\n\n /* The actual card face — no border, no lines. Cuts are the separation. */\n .card-face {\n flex: 1;\n border: none;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 0.18in 0.22in;\n overflow: hidden;\n position: relative;\n }\n\n /*\n * Cut tick marks — 4 short marks at the exact center intersection.\n * These sit on the .page element (outside all card faces).\n * Each tick is a 0.18in line. Two ticks per axis: one on each side of center.\n */\n\n /* Horizontal axis ticks: sit at vertical midpoint, on the left and right edges */\n .tick-h-left, .tick-h-right {\n position: absolute;\n top: 50%;\n width: 0.18in;\n height: 0;\n border-top: 0.6px solid #bbb;\n pointer-events: none;\n }\n .tick-h-left { left: 0.1in; }\n .tick-h-right { right: 0.1in; }\n\n /* Vertical axis ticks: sit at horizontal midpoint, on the top and bottom edges */\n .tick-v-top, .tick-v-bottom {\n position: absolute;\n left: 50%;\n height: 0.18in;\n width: 0;\n border-left: 0.6px solid #bbb;\n pointer-events: none;\n }\n .tick-v-top { top: 0.1in; }\n .tick-v-bottom { bottom: 0.1in; }\n\n /* Full dashed cut lines (optional — only rendered when showCutLines = true) */\n .cut-line-h {\n position: absolute;\n left: 0;\n right: 0;\n top: 50%;\n height: 0;\n border-top: 0.6px dashed #bbb;\n pointer-events: none;\n }\n .cut-line-v {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 50%;\n width: 0;\n border-left: 0.6px dashed #bbb;\n pointer-events: none;\n }\n\n /* Card number badge */\n .card-num {\n position: absolute;\n top: 5px;\n right: 7px;\n font-size: 6.5pt;\n color: #999;\n font-weight: 600;\n }\n\n /* Side label (FRONT / BACK) */\n .side-label {\n font-size: 6.5pt;\n font-weight: 700;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: #888;\n margin-bottom: 8px;\n align-self: flex-start;\n }\n\n /* The text container — fills remaining space, centers content */\n .card-text {\n flex: 1;\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n overflow: hidden;\n }\n\n .card-text-inner {\n font-size: 22pt; /* fitText JS shrinks this to fit — start bigger for short fronts */\n font-weight: 600;\n line-height: 1.3;\n white-space: pre-wrap;\n word-break: break-word;\n text-align: center;\n -webkit-print-color-adjust: exact;\n print-color-adjust: exact;\n }\n\n /* Multiline prose back: left-align, slightly smaller start */\n .card-text-inner.multiline {\n text-align: left;\n font-size: 15pt;\n font-weight: 400;\n line-height: 1.45;\n }\n\n /* Bullet list back */\n ul.card-text-inner.card-list {\n text-align: left;\n font-size: 15pt;\n font-weight: 400;\n line-height: 1.5;\n padding-left: 1.1em;\n margin: 0;\n list-style-type: disc;\n white-space: normal;\n width: 100%;\n }\n ul.card-text-inner.card-list li {\n margin-bottom: 0.25em;\n }\n ul.card-text-inner.card-list li:last-child {\n margin-bottom: 0;\n }\n\n /* Print-only: hide the browser action bar */\n @media print {\n .screen-only { display: none !important; }\n }\n`;\n\n/**\n * Shrink font JS — runs inside the printed window.\n * For each .card-text-inner, reduces font-size until the text fits its container.\n */\nconst FIT_TEXT_SCRIPT = buildFitTextScript([\n \".card-text .card-text-inner\",\n \".avery-text-wrap .avery-text\",\n]);\n\n/** Returns true if the majority of non-empty lines look like bullet items */\nfunction isBulletContent(text: string): boolean {\n const lines = text.split(\"\\n\").filter((l) => l.trim().length > 0);\n if (lines.length < 2) return false;\n const bulletLines = lines.filter((l) => /^\\s*[-•*]\\s/.test(l));\n return bulletLines.length >= lines.length * 0.6; // 60%+ bullet lines = treat as list\n}\n\nfunction isMultiline(text: string): boolean {\n return text.includes(\"\\n\") || text.length > 120;\n}\n\n/**\n * Render card back text as either:\n * - A bullet list (<ul><li>…</li></ul>) when content is bullet-style — left-aligned\n * - Plain centered text for normal prose\n * - Front text is always plain centered\n */\nfunction renderCardText(\n text: string,\n isFront: boolean,\n extraStyle = \"\",\n): { html: string; isList: boolean } {\n if (isFront) {\n const styleAttr = extraStyle ? ` style=\"${extraStyle}\"` : \"\";\n return {\n html: `<div class=\"card-text-inner\"${styleAttr}>${mathTextHtml(text).replace(/\\n/g, \"<br>\")}</div>`,\n isList: false,\n };\n }\n\n const bullet = isBulletContent(text);\n\n if (bullet) {\n const lines = text.split(\"\\n\");\n const items = lines\n .map((l) => l.trim())\n .filter((l) => l.length > 0)\n .map((l) => l.replace(/^[-•*]\\s*/, \"\")) // strip leading dash/bullet\n .map((l) => `<li>${mathTextHtml(l)}</li>`)\n .join(\"\");\n const styleAttr = extraStyle ? ` style=\"${extraStyle}\"` : \"\";\n return {\n html: `<ul class=\"card-text-inner card-list\"${styleAttr}>${items}</ul>`,\n isList: true,\n };\n }\n\n // Plain multiline or single-line prose\n const ml = isMultiline(text);\n const cls = ml ? \"card-text-inner multiline\" : \"card-text-inner\";\n const styleAttr = extraStyle ? ` style=\"${extraStyle}\"` : \"\";\n return {\n html: `<div class=\"${cls}\"${styleAttr}>${mathTextHtml(text).replace(/\\n/g, \"<br>\")}</div>`,\n isList: false,\n };\n}\n\nfunction buildCardFace(\n text: string,\n cardNum: number,\n isFront: boolean,\n s: CardPrintSettings,\n): string {\n const numBadge = s.showCardNumber\n ? `<span class=\"card-num\">#${cardNum}</span>`\n : \"\";\n const sideLabel = s.showSideLabel\n ? `<span class=\"side-label\">${isFront ? \"Front\" : \"Back\"}</span>`\n : \"\";\n\n const textColor = !isFront && s.lightBackText ? \"color:#777;\" : \"\";\n const mirrorXform =\n !isFront && s.mirrorBack ? \"transform:scaleX(-1);display:block;\" : \"\";\n const innerStyle = textColor + mirrorXform;\n\n const { html, isList } = renderCardText(text, isFront, innerStyle);\n // Lists align to the start so bullets sit at top-left; prose/front stays centered\n const cardTextStyle = isList ? ' style=\"align-items:flex-start;\"' : \"\";\n\n return `<div class=\"card-face\">\n ${numBadge}\n ${sideLabel}\n <div class=\"card-text\"${cardTextStyle}>\n ${html}\n </div>\n</div>`;\n}\n\nconst QUAD_CLASSES = [\n \"quad top left\",\n \"quad top right\",\n \"quad bottom left\",\n \"quad bottom right\",\n] as const;\n\nfunction buildPage(cells: string[], s: CardPrintSettings): string {\n const ticks = s.showTickMarks\n ? `<div class=\"tick-h-left\"></div>\n <div class=\"tick-h-right\"></div>\n <div class=\"tick-v-top\"></div>\n <div class=\"tick-v-bottom\"></div>`\n : \"\";\n\n const lines = s.showCutLines\n ? `<div class=\"cut-line-h\"></div>\n <div class=\"cut-line-v\"></div>`\n : \"\";\n\n return `<div class=\"page\">\n ${ticks}\n ${lines}\n ${cells.map((c, i) => `<div class=\"${QUAD_CLASSES[i]!}\">${c}</div>`).join(\"\\n \")}\n</div>`;\n}\n\n/**\n * landscape-duplex: alternating front/back pages.\n * Front page → [card1F, card2F, card3F, card4F]\n * Back page → [card2B, card1B, card4B, card3B] (mirrored L↔R for duplex flip on long edge)\n *\n * landscape-stacked: all fronts first, then all backs (same column order both pages).\n */\nfunction renderLandscape(\n cards: Flashcard[],\n title: string,\n mode: \"duplex\" | \"stacked\",\n s: CardPrintSettings,\n startIndex = 0,\n): string {\n const BATCH = 4;\n const pages: string[] = [];\n // stacked mode collects backs separately so EVERY front page precedes EVERY\n // back page — interleaving them per batch (the pre-port behavior) made the\n // single-sided re-feed workflow this variant exists for impossible.\n const backPages: string[] = [];\n const empty = `<div class=\"card-face\"></div>`;\n\n for (let p = 0; p < Math.ceil(cards.length / BATCH); p++) {\n const batch = cards.slice(p * BATCH, (p + 1) * BATCH);\n while (batch.length < BATCH) batch.push({ front: \"\", back: \"\" });\n\n const fronts = batch.map((c, i) =>\n c.front\n ? buildCardFace(c.front, startIndex + p * BATCH + i + 1, true, s)\n : empty,\n );\n const backs = batch.map((c, i) =>\n c.back\n ? buildCardFace(c.back ?? \"\", startIndex + p * BATCH + i + 1, false, s)\n : empty,\n );\n\n if (mode === \"duplex\") {\n // batch is padded to BATCH, so every slot below is present\n const backsFlipped = [backs[1]!, backs[0]!, backs[3]!, backs[2]!];\n pages.push(buildPage(fronts, s));\n pages.push(buildPage(backsFlipped, s));\n } else {\n pages.push(buildPage(fronts, s));\n backPages.push(buildPage(backs, s));\n }\n }\n\n return [...pages, ...backPages].join(\"\\n\");\n}\n\n/**\n * Landscape cards need a fully custom document — no buildPrintDocument wrapper\n * because that adds max-width, padding, and chrome that fights the precise page geometry.\n */\nfunction openLandscapeWindow(bodyHtml: string, title: string): PrintOutcome {\n const html = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>${escapeHtml(title)}</title>\n <style>${LANDSCAPE_STYLES}</style>\n</head>\n<body>\n<div class=\"screen-only\" style=\"font-family:sans-serif;font-size:12px;padding:12px 16px;background:#f8fafc;border-bottom:1px solid #e2e8f0;display:flex;gap:10px;align-items:center;\">\n <button onclick=\"window.print()\" style=\"padding:7px 18px;background:#111;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;\">Print / Save PDF</button>\n <button onclick=\"window.close()\" style=\"padding:7px 14px;background:#f1f5f9;border:1px solid #cbd5e1;border-radius:6px;font-size:13px;cursor:pointer;\">Close</button>\n <span style=\"color:#64748b;font-size:11px;\">Tip: set your printer to <strong>Landscape</strong> with no additional margins</span>\n</div>\n${bodyHtml}\n${FIT_TEXT_SCRIPT}\n</body>\n</html>`;\n\n // Delegate to the ONE popup-or-download path in core (same fallback\n // filename as before: title, whitespace-dashed, lowercased, \"-flashcards.html\").\n return openPrintWindow(html, `${title}-flashcards`, \"width=1100,height=720,scrollbars=yes\");\n}\n\n// ---------------------------------------------------------------------------\n// Variant: Avery 5388 — 3×5\" index cards, 3 per page, portrait\n//\n// Geometry (portrait 8.5\" × 11\"):\n// Card size: 5\" wide × 3\" tall\n// 3 cards stacked = 9\" height\n// Remaining vertical: 11\" − 9\" = 2\" → 0.5\" top + 0.5\" gap × 2 + 0.5\" bottom\n// Side margins: (8.5\" − 5\") / 2 = 1.75\" each side\n//\n// Layout: page padding 0.5in top/bottom, 1.75in left/right\n// between cards: 0.5in gap\n//\n// Each page = 3 card fronts. Next page = matching 3 card backs (same order).\n// No borders, no lines, no cut marks. Just text centered in each card zone.\n// ---------------------------------------------------------------------------\n\nconst AVERY_5388_STYLES = `\n @page { size: portrait; margin: 0; }\n\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n\n html, body {\n width: 8.5in;\n background: #fff;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Helvetica, Arial, sans-serif;\n color: #000;\n }\n\n .avery-page {\n width: 8.5in;\n height: 11in;\n padding: 0.5in 1.75in;\n display: flex;\n flex-direction: column;\n gap: 0.5in;\n page-break-after: always;\n break-after: page;\n border-bottom: 1px solid #e2e8f0; /* screen only separator */\n }\n .avery-page:last-child {\n page-break-after: auto;\n break-after: auto;\n border-bottom: none;\n }\n\n /* Each card zone: exactly 5\" × 3\" — position:relative so the inner text can be absolutely centered */\n .avery-card {\n width: 5in;\n height: 3in;\n position: relative;\n overflow: hidden;\n flex-shrink: 0;\n }\n\n /* Text centering wrapper — fills the card and centers content in both axes */\n .avery-text-wrap {\n position: absolute;\n inset: 0.2in 0.3in; /* inner padding — 0.2in top/bottom, 0.3in left/right */\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n }\n\n /* Side label (tiny, top-left) */\n .avery-side-label {\n position: absolute;\n top: 6px;\n left: 8px;\n font-size: 6pt;\n font-weight: 700;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n color: #aaa;\n }\n\n /* Card number (tiny, top-right) */\n .avery-card-num {\n position: absolute;\n top: 6px;\n right: 8px;\n font-size: 6pt;\n color: #aaa;\n font-weight: 600;\n }\n\n /* The text itself — starts large, fitText JS shrinks it to fit the wrap container */\n .avery-text {\n font-size: 22pt;\n font-weight: 600;\n line-height: 1.3;\n white-space: pre-wrap;\n word-break: break-word;\n text-align: center;\n max-width: 100%;\n }\n .avery-text.multiline {\n font-size: 15pt;\n font-weight: 400;\n text-align: left;\n line-height: 1.45;\n }\n\n /* Bullet list back */\n ul.avery-text.card-list {\n text-align: left;\n font-size: 15pt;\n font-weight: 400;\n line-height: 1.5;\n padding-left: 1.1em;\n margin: 0;\n list-style-type: disc;\n white-space: normal;\n width: 100%;\n }\n ul.avery-text.card-list li {\n margin-bottom: 0.3em;\n }\n ul.avery-text.card-list li:last-child {\n margin-bottom: 0;\n }\n\n @media print {\n /* Ensure gray text isn't converted to black by color management */\n .avery-text { -webkit-print-color-adjust: exact; print-color-adjust: exact; }\n }\n\n @media print {\n html, body { width: 8.5in; height: auto; overflow: visible; }\n .avery-page { border-bottom: none; }\n .screen-only { display: none !important; }\n }\n`;\n\nfunction buildAveryCard(\n text: string,\n cardNum: number,\n isFront: boolean,\n s: CardPrintSettings,\n): string {\n const numBadge = s.showCardNumber\n ? `<span class=\"avery-card-num\">#${cardNum}</span>`\n : \"\";\n const sideLabel = s.showSideLabel\n ? `<span class=\"avery-side-label\">${isFront ? \"Front\" : \"Back\"}</span>`\n : \"\";\n\n const textColor = !isFront && s.lightBackText ? \"color:#777;\" : \"\";\n const mirrorXform = !isFront && s.mirrorBack ? \"transform:scaleX(-1);\" : \"\";\n const innerStyle = textColor + mirrorXform;\n\n // Render with shared logic — detects bullets, builds <ul> if needed\n const isBullet = !isFront && isBulletContent(text);\n const isML = !isFront && isMultiline(text);\n const cls = isBullet\n ? \"avery-text card-list\"\n : isML\n ? \"avery-text multiline\"\n : \"avery-text\";\n const styleAttr = innerStyle ? ` style=\"${innerStyle}\"` : \"\";\n\n let innerHtml: string;\n if (isBullet) {\n const items = text\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.length > 0)\n .map((l) => l.replace(/^[-•*]\\s*/, \"\"))\n .map((l) => `<li>${mathTextHtml(l)}</li>`)\n .join(\"\");\n innerHtml = `<ul class=\"${cls}\"${styleAttr}>${items}</ul>`;\n } else {\n const escaped = mathTextHtml(text).replace(/\\n/g, \"<br>\");\n innerHtml = `<div class=\"${cls}\"${styleAttr}>${escaped}</div>`;\n }\n\n // List content: wrap aligns to start; prose/front: centered\n const wrapExtra = isBullet ? ' style=\"align-items:flex-start;\"' : \"\";\n\n return `<div class=\"avery-card\">\n ${numBadge}\n ${sideLabel}\n <div class=\"avery-text-wrap\"${wrapExtra}>\n ${innerHtml}\n </div>\n</div>`;\n}\n\nfunction renderAvery5388(\n cards: Flashcard[],\n title: string,\n s: CardPrintSettings,\n startIndex = 0,\n): string {\n const BATCH = 3;\n const pages: string[] = [];\n\n for (let p = 0; p < Math.ceil(cards.length / BATCH); p++) {\n const batch = cards.slice(p * BATCH, (p + 1) * BATCH);\n\n const frontCells = batch\n .map((c, i) =>\n buildAveryCard(c.front, startIndex + p * BATCH + i + 1, true, s),\n )\n .join(\"\\n\");\n const backCells = batch\n .map((c, i) =>\n buildAveryCard(c.back ?? \"\", startIndex + p * BATCH + i + 1, false, s),\n )\n .join(\"\\n\");\n\n const padCount = BATCH - batch.length;\n const padCells = Array(padCount)\n .fill(`<div class=\"avery-card\"></div>`)\n .join(\"\\n\");\n\n pages.push(`<div class=\"avery-page\">${frontCells}${padCells}</div>`);\n pages.push(`<div class=\"avery-page\">${backCells}${padCells}</div>`);\n }\n\n return pages.join(\"\\n\");\n}\n\nfunction openAveryWindow(bodyHtml: string, title: string): PrintOutcome {\n const html = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>${escapeHtml(title)} — Avery 5388</title>\n <style>${AVERY_5388_STYLES}</style>\n</head>\n<body>\n<div class=\"screen-only\" style=\"font-family:sans-serif;font-size:12px;padding:10px 16px;background:#f8fafc;border-bottom:1px solid #e2e8f0;display:flex;gap:10px;align-items:center;\">\n <button onclick=\"window.print()\" style=\"padding:7px 18px;background:#111;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;\">Print / Save PDF</button>\n <button onclick=\"window.close()\" style=\"padding:7px 14px;background:#f1f5f9;border:1px solid #cbd5e1;border-radius:6px;font-size:13px;cursor:pointer;\">Close</button>\n <span style=\"color:#64748b;font-size:11px;\">Avery 5388 · 3×5\" index cards · 3 per page · set printer to <strong>Portrait, no margins</strong></span>\n</div>\n${bodyHtml}\n${FIT_TEXT_SCRIPT}\n</body>\n</html>`;\n\n // Delegate to the ONE popup-or-download path in core (same fallback\n // filename as before: title, whitespace-dashed, lowercased, \"-avery5388.html\").\n return openPrintWindow(html, `${title}-avery5388`, \"width=900,height=720,scrollbars=yes\");\n}\n\n// ---------------------------------------------------------------------------\n// Variant: Portrait 6-up (6 cards per page, 2 columns × 3 rows)\n//\n// Geometry (portrait 8.5\" × 11\"):\n// Page is split exactly in half down the middle and into thirds across.\n// Each cell: 4.25\" wide × 3.667\" tall.\n// Inner padding per cell: ~0.22\" gives the text breathing room without\n// pushing content off the cuttable area.\n//\n// Duplex (double-sided) mirroring for portrait:\n// Most printers default to \"flip on long edge\" for portrait duplex, which\n// pivots the sheet around its left (long) edge. Result: left ↔ right\n// columns swap on the back side, rows are unchanged.\n//\n// Front slot order: [0:TL, 1:TR, 2:ML, 3:MR, 4:BL, 5:BR]\n// Back page render: [B1, B0, B3, B2, B5, B4] (columns swapped per row)\n//\n// Content centering:\n// Each card face uses flex centering so short text sits dead-center,\n// multiline prose is left-aligned but still vertically centered, and\n// bullet lists align to the top-left of their cell for readability.\n// ---------------------------------------------------------------------------\n\nconst PORTRAIT_6UP_STYLES = `\n @page { size: portrait; margin: 0; }\n\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n\n html, body {\n width: 8.5in;\n background: #fff;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Helvetica, Arial, sans-serif;\n color: #000;\n }\n\n .page-6up {\n width: 8.5in;\n height: 11in;\n position: relative;\n page-break-after: always;\n break-after: page;\n display: grid;\n grid-template-columns: 1fr 1fr;\n grid-template-rows: 1fr 1fr 1fr;\n gap: 0;\n padding: 0;\n border-bottom: 2px solid #e2e8f0;\n }\n .page-6up:last-child {\n page-break-after: auto;\n break-after: auto;\n border-bottom: none;\n }\n\n @media print {\n html, body { width: 8.5in; height: auto; overflow: visible; }\n .page-6up { border-bottom: none; }\n }\n\n /* Each of the 6 cells — clean 4.25\" × 3.667\" zone split by the cuts */\n .cell-6 {\n display: flex;\n align-items: stretch;\n justify-content: stretch;\n padding: 0.2in 0.22in;\n overflow: hidden;\n }\n\n /* The actual card face — no border. Cuts are the separation. */\n .card-face {\n flex: 1;\n border: none;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 0.1in 0.12in;\n overflow: hidden;\n position: relative;\n }\n\n /* Cut tick marks — short marks at each cut intersection with the page edge */\n .tick-v-top, .tick-v-bottom {\n position: absolute;\n left: 50%;\n height: 0.18in;\n width: 0;\n border-left: 0.6px solid #bbb;\n pointer-events: none;\n }\n .tick-v-top { top: 0.1in; }\n .tick-v-bottom { bottom: 0.1in; }\n\n .tick-h-1-left, .tick-h-1-right,\n .tick-h-2-left, .tick-h-2-right {\n position: absolute;\n width: 0.18in;\n height: 0;\n border-top: 0.6px solid #bbb;\n pointer-events: none;\n }\n .tick-h-1-left { top: 33.333%; left: 0.1in; }\n .tick-h-1-right { top: 33.333%; right: 0.1in; }\n .tick-h-2-left { top: 66.667%; left: 0.1in; }\n .tick-h-2-right { top: 66.667%; right: 0.1in; }\n\n /* Full dashed cut lines (optional) */\n .cut-line-v {\n position: absolute;\n top: 0; bottom: 0;\n left: 50%;\n width: 0;\n border-left: 0.6px dashed #bbb;\n pointer-events: none;\n }\n .cut-line-h-1, .cut-line-h-2 {\n position: absolute;\n left: 0; right: 0;\n height: 0;\n border-top: 0.6px dashed #bbb;\n pointer-events: none;\n }\n .cut-line-h-1 { top: 33.333%; }\n .cut-line-h-2 { top: 66.667%; }\n\n /* Card number badge */\n .card-num {\n position: absolute;\n top: 5px;\n right: 7px;\n font-size: 6.5pt;\n color: #999;\n font-weight: 600;\n }\n\n /* Side label (FRONT / BACK) */\n .side-label {\n font-size: 6.5pt;\n font-weight: 700;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: #888;\n margin-bottom: 6px;\n align-self: flex-start;\n }\n\n /* Centered text container — fills the card, both axes centered */\n .card-text {\n flex: 1;\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n text-align: center;\n overflow: hidden;\n }\n\n .card-text-inner {\n font-size: 18pt; /* fitText JS shrinks this to fit */\n font-weight: 600;\n line-height: 1.3;\n white-space: pre-wrap;\n word-break: break-word;\n text-align: center;\n -webkit-print-color-adjust: exact;\n print-color-adjust: exact;\n }\n\n .card-text-inner.multiline {\n text-align: left;\n font-size: 13pt;\n font-weight: 400;\n line-height: 1.45;\n }\n\n ul.card-text-inner.card-list {\n text-align: left;\n font-size: 13pt;\n font-weight: 400;\n line-height: 1.5;\n padding-left: 1.1em;\n margin: 0;\n list-style-type: disc;\n white-space: normal;\n width: 100%;\n }\n ul.card-text-inner.card-list li { margin-bottom: 0.2em; }\n ul.card-text-inner.card-list li:last-child { margin-bottom: 0; }\n\n @media print {\n .screen-only { display: none !important; }\n }\n`;\n\nfunction buildPage6Up(cells: string[], s: CardPrintSettings): string {\n const ticks = s.showTickMarks\n ? `<div class=\"tick-v-top\"></div>\n <div class=\"tick-v-bottom\"></div>\n <div class=\"tick-h-1-left\"></div>\n <div class=\"tick-h-1-right\"></div>\n <div class=\"tick-h-2-left\"></div>\n <div class=\"tick-h-2-right\"></div>`\n : \"\";\n\n const lines = s.showCutLines\n ? `<div class=\"cut-line-v\"></div>\n <div class=\"cut-line-h-1\"></div>\n <div class=\"cut-line-h-2\"></div>`\n : \"\";\n\n return `<div class=\"page-6up\">\n ${ticks}\n ${lines}\n ${cells.map((c) => `<div class=\"cell-6\">${c}</div>`).join(\"\\n \")}\n</div>`;\n}\n\n/**\n * portrait-6up-duplex: alternating front/back pages, backs mirrored for duplex.\n * Front page → [F0, F1, F2, F3, F4, F5] in slot order TL, TR, ML, MR, BL, BR\n * Back page → [B1, B0, B3, B2, B5, B4] (columns swapped for long-edge flip)\n *\n * portrait-6up-stacked: all fronts first, then all backs (same slot order both).\n */\nfunction renderPortrait6Up(\n cards: Flashcard[],\n title: string,\n mode: \"duplex\" | \"stacked\",\n s: CardPrintSettings,\n startIndex = 0,\n): string {\n const BATCH = 6;\n const pages: string[] = [];\n // stacked mode collects backs separately so EVERY front page precedes EVERY\n // back page — see renderLandscape for the defect this fixes.\n const backPages: string[] = [];\n const empty = `<div class=\"card-face\"></div>`;\n\n for (let p = 0; p < Math.ceil(cards.length / BATCH); p++) {\n const batch = cards.slice(p * BATCH, (p + 1) * BATCH);\n while (batch.length < BATCH) batch.push({ front: \"\", back: \"\" });\n\n const fronts = batch.map((c, i) =>\n c.front\n ? buildCardFace(c.front, startIndex + p * BATCH + i + 1, true, s)\n : empty,\n );\n const backs = batch.map((c, i) =>\n c.back\n ? buildCardFace(c.back ?? \"\", startIndex + p * BATCH + i + 1, false, s)\n : empty,\n );\n\n if (mode === \"duplex\") {\n // Long-edge flip swaps columns per row: [TR, TL, MR, ML, BR, BL]\n // batch is padded to BATCH, so every slot below is present\n const backsFlipped = [\n backs[1]!,\n backs[0]!,\n backs[3]!,\n backs[2]!,\n backs[5]!,\n backs[4]!,\n ];\n pages.push(buildPage6Up(fronts, s));\n pages.push(buildPage6Up(backsFlipped, s));\n } else {\n pages.push(buildPage6Up(fronts, s));\n backPages.push(buildPage6Up(backs, s));\n }\n }\n\n return [...pages, ...backPages].join(\"\\n\");\n}\n\nfunction openPortrait6UpWindow(bodyHtml: string, title: string): PrintOutcome {\n const html = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>${escapeHtml(title)} — 6 per page</title>\n <style>${PORTRAIT_6UP_STYLES}</style>\n</head>\n<body>\n<div class=\"screen-only\" style=\"font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#fff;border-bottom:1px solid #e2e8f0;padding:14px 20px;\">\n <div style=\"display:flex;gap:10px;align-items:center;margin-bottom:10px;\">\n <button onclick=\"window.print()\" style=\"padding:8px 20px;background:#111;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;\">Print / Save PDF</button>\n <button onclick=\"window.close()\" style=\"padding:8px 14px;background:#f1f5f9;border:1px solid #cbd5e1;border-radius:6px;font-size:13px;cursor:pointer;\">Close</button>\n <span style=\"color:#111;font-size:12px;font-weight:600;margin-left:4px;\">6 flashcards per page · cut into 6</span>\n </div>\n <div style=\"background:#fef3c7;border:1px solid #fbbf24;border-radius:6px;padding:10px 14px;font-size:12px;line-height:1.55;color:#78350f;max-width:720px;\">\n <div style=\"font-weight:700;margin-bottom:4px;color:#92400e;\">⚠ Printer settings for duplex alignment</div>\n <strong>- Page Size:</strong> Letter (8.5\" × 11\")<br>\n <strong>- Orientation:</strong> Portrait<br>\n <strong>- Two-sided (Duplex):</strong> On<br>\n <strong>- Flip on:</strong> Long edge<br>\n <strong>- Pages per sheet:</strong> 1<br>\n <strong>- Scale:</strong> 100% / Actual size<br>\n <strong>- Margins:</strong> None<br>\n </div>\n</div>\n${bodyHtml}\n${FIT_TEXT_SCRIPT}\n</body>\n</html>`;\n\n // Delegate to the ONE popup-or-download path in core (same fallback\n // filename as before: title, whitespace-dashed, lowercased, \"-flashcards-6up.html\").\n return openPrintWindow(html, `${title}-flashcards-6up`, \"width=900,height=720,scrollbars=yes\");\n}\n\n// ---------------------------------------------------------------------------\n// Helper: derive a title from the data if available\n// ---------------------------------------------------------------------------\nfunction deriveTitle(data: unknown): string {\n if (data && typeof data === \"object\" && \"title\" in data) {\n const t = (data as { title?: unknown }).title;\n if (typeof t === \"string\" && t.trim()) return t.trim();\n }\n return \"Flashcards\";\n}\n\n// ---------------------------------------------------------------------------\n// Exported printer\n// ---------------------------------------------------------------------------\nexport const flashcardsPrinter: BlockPrinter = {\n ...flashcardsPrinterMeta,\n\n print(\n data: unknown,\n variantId: string = \"portrait-6up-duplex\",\n rawSettings?: PrintSettings,\n ) {\n const allCards = extractCards(data);\n const title = deriveTitle(data);\n const s = resolveSettings(rawSettings);\n\n if (allCards.length === 0) {\n return openPrintWindow(\n buildPrintDocument(\n \"<p>No flashcard data available to print.</p>\",\n title,\n BASE_STYLES,\n ),\n \"flashcards\",\n );\n }\n\n // Resolve the \"cards to print\" range. 0 / missing / out-of-bounds all\n // fall back to sensible ends. Any invalid input still produces a valid deck.\n const rawFrom =\n typeof rawSettings?.fromCard === \"number\" ? rawSettings.fromCard : 0;\n const rawTo =\n typeof rawSettings?.toCard === \"number\" ? rawSettings.toCard : 0;\n const rangeStart = rawFrom > 0 ? Math.min(rawFrom, allCards.length) : 1;\n const rangeEnd =\n rawTo > 0 ? Math.min(rawTo, allCards.length) : allCards.length;\n const [fromIdx, toIdx] =\n rangeStart <= rangeEnd ? [rangeStart, rangeEnd] : [rangeEnd, rangeStart]; // swap if user inverted them\n const startIndex = fromIdx - 1; // 0-based offset of the first card\n const cards = allCards.slice(startIndex, toIdx);\n\n // Landscape and Avery variants use fully custom documents (precise page geometry)\n if (variantId === \"landscape-duplex\") {\n return openLandscapeWindow(\n renderLandscape(cards, title, \"duplex\", s, startIndex),\n title,\n );\n }\n if (variantId === \"landscape-stacked\") {\n return openLandscapeWindow(\n renderLandscape(cards, title, \"stacked\", s, startIndex),\n title,\n );\n }\n if (variantId === \"portrait-6up-duplex\") {\n return openPortrait6UpWindow(\n renderPortrait6Up(cards, title, \"duplex\", s, startIndex),\n title,\n );\n }\n if (variantId === \"portrait-6up-stacked\") {\n return openPortrait6UpWindow(\n renderPortrait6Up(cards, title, \"stacked\", s, startIndex),\n title,\n );\n }\n if (variantId === \"avery-5388\") {\n return openAveryWindow(renderAvery5388(cards, title, s, startIndex), title);\n }\n\n let bodyHtml: string;\n let styles: string;\n\n switch (variantId as FlashcardsVariant) {\n case \"cut-cards\":\n bodyHtml = renderCutCards(cards, title, s, startIndex);\n styles = CUT_CARD_STYLES;\n break;\n case \"front-only\":\n bodyHtml = renderFrontOnly(cards, title, s, startIndex);\n styles = FRONT_ONLY_STYLES;\n break;\n case \"back-only\":\n bodyHtml = renderBackOnly(cards, title, s, startIndex);\n styles = BACK_ONLY_STYLES;\n break;\n case \"study-sheet\":\n bodyHtml = renderStudySheet(cards, title, s, startIndex);\n styles = STUDY_SHEET_STYLES;\n break;\n case \"both-sides\":\n default:\n bodyHtml = renderBothSides(cards, title, s, startIndex);\n styles = BOTH_SIDES_STYLES;\n break;\n }\n\n return openPrintWindow(\n buildPrintDocument(bodyHtml, title, styles),\n \"flashcards\",\n );\n },\n\n // Inside a composed print (a message's Print) a deck is its study sheet:\n // paper cannot flip, and a cut sheet belongs to the deck's own print.\n toPrintHtml(data: unknown) {\n const cards = extractCards(data);\n // A deck this printer cannot read is NOT an empty deck: answer null so the composer's default path runs.\n if (cards.length === 0) return null;\n const sheet = renderStudySheet(cards, deriveTitle(data), resolveSettings(undefined), 0, COMPOSED_CLASSES).replaceAll(\n 'class=\"face-image\"',\n 'class=\"matrx-fc-face-image\"',\n );\n return { html: `<div class=\"matrx-fc-sheet\">${sheet}</div>`, css: COMPOSED_STUDY_SHEET_CSS };\n },\n};\n"],"mappings":"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","names":["escapeHtml","escapeHtml","escapeHtml","styleAttr"]}