#!/usr/bin/env node // answer-me-with-html CLI — generated by scripts/build.mjs, do not edit. Source: https://github.com/QingYunA/answer-me-with-html var __defProp = Object.defineProperty; var __getOwnPropNames = Object.getOwnPropertyNames; var __esm = (fn3, res, err) => function __init() { if (err) throw err[0]; try { return fn3 && (res = (0, fn3[__getOwnPropNames(fn3)[0]])(fn3 = 0)), res; } catch (e) { throw err = [e], e; } }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; // temml-string:temml var temml_exports = {}; __export(temml_exports, { default: () => temml_default }); import { runInThisContext } from "node:vm"; var temml_default; var init_temml = __esm({ "temml-string:temml"() { temml_default = runInThisContext('(function () {\nvar temml=function(){"use strict";class e{constructor(t,r){let n,s=" "+t;const o=r&&r.loc;if(o&&o.start<=o.end){const e=o.lexer.input;n=o.start;const t=o.end;n===e.length?s+=" at end of input: ":s+=" at position "+(n+1)+": \\n";const r=e.slice(n,t).replace(/[^]/g,"$&\u0332");let a,l;a=n>15?"\u2026"+e.slice(n-15,n):e.slice(0,n),l=t+15":">","<":"<",\'"\':""","\'":"'"},o=/[&><"\']/g;function a(e){return String(e).replace(o,e=>s[e])}const l=function(e){return"ordgroup"===e.type||"color"===e.type?1===e.body.length?l(e.body[0]):e:"font"===e.type?l(e.body):e},i=function(e){const t=l(e);return"mathord"===t.type||"textord"===t.type||"atom"===t.type},c=function(e){return+e.toFixed(4)},m="aceg\u0131\u0237mnopqrsuvwxyz\u03B1\u03B3\u03B5\u03B7\u03B9\u03BA\u03BC\u03BD\u03BF\u03C0\u03C1\u03C2\u03C3\u03C4\u03C5\u03C7\u03C9\u03D5\u{1D41A}\u{1D41C}\u{1D41E}\u{1D420}\u{1D426}\u{1D427}\u{1D428}\u{1D429}\u{1D42A}\u{1D42B}\u{1D42C}\u{1D42E}\u{1D42F}\u{1D430}\u{1D431}\u{1D432}\u{1D433}",p=["__proto__","prototype","constructor"];class u{constructor(e){e=e||{},this.displayMode=t(e,"displayMode",!1),this.annotate=t(e,"annotate",!1),this.leqno=t(e,"leqno",!1),this.throwOnError=t(e,"throwOnError",!1),this.errorColor=t(e,"errorColor","#b22222"),this.macros=(e=>{if(null==e||"object"!=typeof e)return Object.create(null);for(const t of p)Object.prototype.hasOwnProperty.call(e,t)&&delete e[t];return Object.setPrototypeOf(e,null)})(Object.prototype.hasOwnProperty.call(e,"macros")?e.macros:void 0),this.wrap=t(e,"wrap","none"),this.xml=t(e,"xml",!1),this.colorIsTextColor=t(e,"colorIsTextColor",!1),this.strict=t(e,"strict",!1),this.trust=t(e,"trust",!1);const r=Object.prototype.hasOwnProperty.call(e,"maxSize")?e.maxSize:void 0;this.maxSize=void 0===r?[1/0,1/0]:Array.isArray(r)?r:[1/0,1/0],this.maxExpand=Math.max(0,t(e,"maxExpand",1e3)),this.wrapDelimiterPairs=!0}isTrusted(e){if(e.url&&!e.protocol){const t=function(e){const t=/^[\\x00-\\x20]*([^\\\\/#?]*?)(:|�*58|�*3a|&colon)/i.exec(e);return t?":"!==t[2]?null:/^[a-zA-Z][a-zA-Z0-9+\\-.]*$/.test(t[1])?t[1].toLowerCase():null:"_relative"}(e.url);if(null==t)return!1;e.protocol=t}const t="function"==typeof this.trust?this.trust(e):this.trust;return Boolean(t)}}const d={},h={};function g({type:e,names:t,props:r,handler:n,mathmlBuilder:s}){const o={type:e,numArgs:r.numArgs,argTypes:r.argTypes,allowedInArgument:!!r.allowedInArgument,allowedInText:!!r.allowedInText,allowedInMath:void 0===r.allowedInMath||r.allowedInMath,numOptionalArgs:r.numOptionalArgs||0,infix:!!r.infix,primitive:!!r.primitive,handler:n};for(let e=0;ee.toText()).join("")}}const x=function(e){return e.filter(e=>e).join(" ")},k=function(e,t){this.classes=e||[],this.attributes={},this.style=t||{}},v=function(e){const t=document.createElement(e);t.className=x(this.classes);for(const e in this.style)Object.prototype.hasOwnProperty.call(this.style,e)&&(t.style[e]=this.style[e]);for(const e in this.attributes)Object.prototype.hasOwnProperty.call(this.attributes,e)&&t.setAttribute(e,this.attributes[e]);for(let e=0;e`,t};class T{constructor(e,t,r){k.call(this,e,r),this.children=t||[]}setAttribute(e,t){this.attributes[e]=t}toNode(){return v.call(this,"span")}toMarkup(){return A.call(this,"span")}}let S=class{constructor(e){this.text=e}toNode(){return document.createTextNode(this.text)}toMarkup(){return a(this.text)}};class O{constructor(e,t,r){this.href=e,this.classes=t,this.children=r||[]}toNode(){const e=document.createElement("a");e.setAttribute("href",this.href),this.classes.length>0&&(e.className=x(this.classes));for(let t=0;t0&&(e+=` class="${a(x(this.classes))}"`),e+=">";for(let t=0;t0&&(e.className=x(this.classes));for(const t in this.style)Object.prototype.hasOwnProperty.call(this.style,t)&&(e.style[t]=this.style[t]);for(let t=0;t0&&(e+=` class="${a(x(this.classes))}"`);let t="";for(const e in this.style)Object.prototype.hasOwnProperty.call(this.style,e)&&(t+=`${n(e)}:${this.style[e]};`);t&&(e+=` style="${t}"`),e+=">";for(let t=0;t",e}toText(){return this.children.map(e=>e.toText()).join("")}}class N{constructor(e){this.text=e}toNode(){return document.createTextNode(this.text)}toMarkup(){return a(this.toText())}toText(){return this.text}}const z=e=>{let t;return 1===e.length&&"mrow"===e[0].type?(t=e.pop(),t.type="mstyle"):t=new C("mstyle",e),t},$=e=>{let t=0;if(e.body&&Array.isArray(e.body))for(const r of e.body)t+=$(r);else if(e.body)t+=$(e.body);else if("supsub"===e.type)t+=$(e.base),e.sub&&(t+=.7*$(e.sub)),e.sup&&(t+=.7*$(e.sup));else if("mathord"===e.type||"textord"===e.type)for(const r of e.text.split("")){const e=r.codePointAt(0);t+=96{const t=I(e.label);if(e.label.indexOf("tilde")>-1){const r=$(e.base);1","\\\\gt",!0),G(P,J,"\u2208","\\\\in",!0),G(P,J,"\u2209","\\\\notin",!0),G(P,J,"\uE020","\\\\@not"),G(P,J,"\u2282","\\\\subset",!0),G(P,J,"\u2283","\\\\supset",!0),G(P,J,"\u2286","\\\\subseteq",!0),G(P,J,"\u2287","\\\\supseteq",!0),G(P,J,"\u2288","\\\\nsubseteq",!0),G(P,J,"\u2288","\\\\nsubseteqq"),G(P,J,"\u2289","\\\\nsupseteq",!0),G(P,J,"\u2289","\\\\nsupseteqq"),G(P,J,"\u22A8","\\\\models"),G(P,J,"\u2190","\\\\leftarrow",!0),G(P,J,"\u2264","\\\\le"),G(P,J,"\u2264","\\\\leq",!0),G(P,J,"<","\\\\lt",!0),G(P,J,"\u2192","\\\\rightarrow",!0),G(P,J,"\u2192","\\\\to"),G(P,J,"\u2271","\\\\ngeq",!0),G(P,J,"\u2271","\\\\ngeqq"),G(P,J,"\u2271","\\\\ngeqslant"),G(P,J,"\u2270","\\\\nleq",!0),G(P,J,"\u2270","\\\\nleqq"),G(P,J,"\u2270","\\\\nleqslant"),G(P,J,"\u2AEB","\\\\Perp",!0),G(P,K,"\xA0","\\\\ "),G(P,K,"\xA0","\\\\space"),G(P,K,"\xA0","\\\\nobreakspace"),G(R,K,"\xA0","\\\\ "),G(R,K,"\xA0"," "),G(R,K,"\xA0","\\\\space"),G(R,K,"\xA0","\\\\nobreakspace"),G(P,K,null,"\\\\nobreak"),G(P,K,null,"\\\\allowbreak"),G(P,Y,",",","),G(R,Y,":",":"),G(P,Y,";",";"),G(P,H,"\u22BC","\\\\barwedge"),G(P,H,"\u22BB","\\\\veebar"),G(P,H,"\u2299","\\\\odot",!0),G(P,H,"\u2295\uFE0E","\\\\oplus"),G(P,H,"\u2297","\\\\otimes",!0),G(P,Q,"\u2202","\\\\partial",!0),G(P,H,"\u2298","\\\\oslash",!0),G(P,H,"\u229A","\\\\circledcirc",!0),G(P,H,"\u22A1","\\\\boxdot",!0),G(P,H,"\u25B3","\\\\bigtriangleup"),G(P,H,"\u25BD","\\\\bigtriangledown"),G(P,H,"\u2020","\\\\dagger"),G(P,H,"\u22C4","\\\\diamond"),G(P,H,"\u25C3","\\\\triangleleft"),G(P,H,"\u25B9","\\\\triangleright"),G(P,Z,"{","\\\\{"),G(R,Q,"{","\\\\{"),G(R,Q,"{","\\\\textbraceleft"),G(P,_,"}","\\\\}"),G(R,Q,"}","\\\\}"),G(R,Q,"}","\\\\textbraceright"),G(P,Z,"{","\\\\lbrace"),G(P,_,"}","\\\\rbrace"),G(P,Z,"[","\\\\lbrack",!0),G(R,Q,"[","\\\\lbrack",!0),G(P,_,"]","\\\\rbrack",!0),G(R,Q,"]","\\\\rbrack",!0),G(P,Z,"(","\\\\lparen",!0),G(P,_,")","\\\\rparen",!0),G(P,Z,"\u2987","\\\\llparenthesis",!0),G(P,_,"\u2988","\\\\rrparenthesis",!0),G(R,Q,"<","\\\\textless",!0),G(R,Q,">","\\\\textgreater",!0),G(P,Z,"\u230A","\\\\lfloor",!0),G(P,_,"\u230B","\\\\rfloor",!0),G(P,Z,"\u2308","\\\\lceil",!0),G(P,_,"\u2309","\\\\rceil",!0),G(P,Q,"\\\\","\\\\backslash"),G(P,Q,"|","|"),G(P,Q,"|","\\\\vert"),G(R,Q,"|","\\\\textbar",!0),G(P,Q,"\u2016","\\\\|"),G(P,Q,"\u2016","\\\\Vert"),G(R,Q,"\u2016","\\\\textbardbl"),G(R,Q,"~","\\\\textasciitilde"),G(R,Q,"\\\\","\\\\textbackslash"),G(R,Q,"^","\\\\textasciicircum"),G(P,J,"\u2191","\\\\uparrow",!0),G(P,J,"\u21D1","\\\\Uparrow",!0),G(P,J,"\u2193","\\\\downarrow",!0),G(P,J,"\u21D3","\\\\Downarrow",!0),G(P,J,"\u2195","\\\\updownarrow",!0),G(P,J,"\u21D5","\\\\Updownarrow",!0),G(P,X,"\u2210","\\\\coprod"),G(P,X,"\u22C1","\\\\bigvee"),G(P,X,"\u22C0","\\\\bigwedge"),G(P,X,"\u2A04","\\\\biguplus"),G(P,X,"\u2A04","\\\\bigcupplus"),G(P,X,"\u2A03","\\\\bigcupdot"),G(P,X,"\u2A07","\\\\bigdoublevee"),G(P,X,"\u2A08","\\\\bigdoublewedge"),G(P,X,"\u22C2","\\\\bigcap"),G(P,X,"\u22C3","\\\\bigcup"),G(P,X,"\u222B","\\\\int"),G(P,X,"\u222B","\\\\intop"),G(P,X,"\u222C","\\\\iint"),G(P,X,"\u222D","\\\\iiint"),G(P,X,"\u220F","\\\\prod"),G(P,X,"\u2211","\\\\sum"),G(P,X,"\u2A02","\\\\bigotimes"),G(P,X,"\u2A01","\\\\bigoplus"),G(P,X,"\u2A00","\\\\bigodot"),G(P,X,"\u2A09","\\\\bigtimes"),G(P,X,"\u222E","\\\\oint"),G(P,X,"\u222F","\\\\oiint"),G(P,X,"\u2230","\\\\oiiint"),G(P,X,"\u2231","\\\\intclockwise"),G(P,X,"\u2232","\\\\varointclockwise"),G(P,X,"\u2A0C","\\\\iiiint"),G(P,X,"\u2A0D","\\\\intbar"),G(P,X,"\u2A0E","\\\\intBar"),G(P,X,"\u2A0F","\\\\fint"),G(P,X,"\u2A12","\\\\rppolint"),G(P,X,"\u2A13","\\\\scpolint"),G(P,X,"\u2A15","\\\\pointint"),G(P,X,"\u2A16","\\\\sqint"),G(P,X,"\u2A17","\\\\intlarhk"),G(P,X,"\u2A18","\\\\intx"),G(P,X,"\u2A19","\\\\intcap"),G(P,X,"\u2A1A","\\\\intcup"),G(P,X,"\u2A05","\\\\bigsqcap"),G(P,X,"\u2A06","\\\\bigsqcup"),G(P,X,"\u222B","\\\\smallint"),G(R,V,"\u2026","\\\\textellipsis"),G(P,V,"\u2026","\\\\mathellipsis"),G(R,V,"\u2026","\\\\ldots",!0),G(P,V,"\u2026","\\\\dotso"),G(P,V,"\u2026","\\\\dotsc"),G(P,V,"\u2026","\\\\ldots",!0),G(P,V,"\u22F0","\\\\iddots",!0),G(P,V,"\u22EF","\\\\cdots",!0),G(P,V,"\u22F1","\\\\ddots",!0),G(P,Q,"\u22EE","\\\\varvdots"),G(R,Q,"\u22EE","\\\\varvdots"),G(P,U,"\xB4","\\\\acute"),G(P,U,"`","\\\\grave"),G(P,U,"\xA8","\\\\ddot"),G(P,U,"\u2026","\\\\dddot"),G(P,U,"\u2026.","\\\\ddddot"),G(P,U,"~","\\\\tilde"),G(P,U,"\u203E","\\\\bar"),G(P,U,"\u02D8","\\\\breve"),G(P,U,"\u02C7","\\\\check"),G(P,U,"\u02C6","\\\\hat"),G(P,U,"\u2192","\\\\vec"),G(P,U,"\u02D9","\\\\dot"),G(P,U,"\u02DA","\\\\mathring"),G(P,W,"\u0131","\\\\imath",!0),G(P,W,"\u0237","\\\\jmath",!0),G(P,Q,"\u0131","\u0131"),G(P,Q,"\u0237","\u0237"),G(R,Q,"\u0131","\\\\i",!0),G(R,Q,"\u0237","\\\\j",!0),G(R,Q,"\xF8","\\\\o",!0),G(P,W,"\xF8","\\\\o",!0),G(R,Q,"\xD8","\\\\O",!0),G(P,W,"\xD8","\\\\O",!0),G(R,U,"\u02CA","\\\\\'"),G(R,U,"\u02CB","\\\\`"),G(R,U,"\u02C6","\\\\^"),G(R,U,"~","\\\\~"),G(R,U,"\u02C9","\\\\="),G(R,U,"\u02D8","\\\\u"),G(R,U,"\u02D9","\\\\."),G(R,U,"\xB8","\\\\c");G(R,U,"\u02DA","\\\\r"),G(R,U,"\u02C7","\\\\v"),G(R,U,"\xA8",\'\\\\"\'),G(R,U,"\u02DD","\\\\H"),G(P,U,"\u02CA","\\\\\'"),G(P,U,"\u02CB","\\\\`"),G(P,U,"\u02C6","\\\\^"),G(P,U,"~","\\\\~"),G(P,U,"\u02C9","\\\\="),G(P,U,"\u02D8","\\\\u"),G(P,U,"\u02D9","\\\\."),G(P,U,"\xB8","\\\\c"),G(P,U,"\u02DA","\\\\r"),G(P,U,"\u02C7","\\\\v"),G(P,U,"\xA8",\'\\\\"\'),G(P,U,"\u02DD","\\\\H");const ee={"--":!0,"---":!0,"``":!0,"\'\'":!0};G(R,Q,"\u2013","--",!0),G(R,Q,"\u2013","\\\\textendash"),G(R,Q,"\u2014","---",!0),G(R,Q,"\u2014","\\\\textemdash"),G(R,Q,"\u2018","`",!0),G(R,Q,"\u2018","\\\\textquoteleft"),G(R,Q,"\u2019","\'",!0),G(R,Q,"\u2019","\\\\textquoteright"),G(R,Q,"\u201C","``",!0),G(R,Q,"\u201C","\\\\textquotedblleft"),G(R,Q,"\u201D","\'\'",!0),G(R,Q,"\u201D","\\\\textquotedblright"),G(P,Q,"\xB0","\\\\degree",!0),G(R,Q,"\xB0","\\\\degree"),G(R,Q,"\xB0","\\\\textdegree",!0),G(P,Q,"\xA3","\\\\pounds"),G(P,Q,"\xA3","\\\\mathsterling",!0),G(R,Q,"\xA3","\\\\pounds"),G(R,Q,"\xA3","\\\\textsterling",!0),G(P,Q,"\u2720","\\\\maltese"),G(R,Q,"\u2720","\\\\maltese"),G(P,Q,"\u20AC","\\\\euro",!0),G(R,Q,"\u20AC","\\\\euro",!0),G(R,Q,"\u20AC","\\\\texteuro"),G(P,Q,"\xA9","\\\\copyright",!0),G(R,Q,"\xA9","\\\\textcopyright"),G(P,Q,"\u2300","\\\\diameter",!0),G(R,Q,"\u2300","\\\\diameter"),G(P,Q,"\u{1D6E4}","\\\\varGamma"),G(P,Q,"\u{1D6E5}","\\\\varDelta"),G(P,Q,"\u{1D6E9}","\\\\varTheta"),G(P,Q,"\u{1D6EC}","\\\\varLambda"),G(P,Q,"\u{1D6EF}","\\\\varXi"),G(P,Q,"\u{1D6F1}","\\\\varPi"),G(P,Q,"\u{1D6F4}","\\\\varSigma"),G(P,Q,"\u{1D6F6}","\\\\varUpsilon"),G(P,Q,"\u{1D6F7}","\\\\varPhi"),G(P,Q,"\u{1D6F9}","\\\\varPsi"),G(P,Q,"\u{1D6FA}","\\\\varOmega"),G(R,Q,"\u{1D6E4}","\\\\varGamma"),G(R,Q,"\u{1D6E5}","\\\\varDelta"),G(R,Q,"\u{1D6E9}","\\\\varTheta"),G(R,Q,"\u{1D6EC}","\\\\varLambda"),G(R,Q,"\u{1D6EF}","\\\\varXi"),G(R,Q,"\u{1D6F1}","\\\\varPi"),G(R,Q,"\u{1D6F4}","\\\\varSigma"),G(R,Q,"\u{1D6F6}","\\\\varUpsilon"),G(R,Q,"\u{1D6F7}","\\\\varPhi"),G(R,Q,"\u{1D6F9}","\\\\varPsi"),G(R,Q,"\u{1D6FA}","\\\\varOmega");const te=\'0123456789/@."\';for(let e=0;e<14;e++){const t=te.charAt(e);G(P,Q,t,t)}const re=\'0123456789!@*()-=+";:?/.,\';for(let e=0;e<25;e++){const t=re.charAt(e);G(R,Q,t,t)}const ne="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";for(let e=0;e<52;e++){const t=ne.charAt(e);G(P,W,t,t),G(R,Q,t,t)}const se="\xC7\xD0\xDE\xE7\xFE\u2102\u210D\u2115\u2119\u211A\u211D\u2124\u210E\u210F\u210A\u210B\u210C\u2110\u2111\u2112\u2113\u2118\u211B\u211C\u212C\u2130\u2131\u2133\u212D\u2128";for(let e=0;e<30;e++){const t=se.charAt(e);G(P,W,t,t),G(R,Q,t,t)}let oe="";for(let e=0;e<52;e++){oe=String.fromCharCode(55349,56320+e),G(P,W,oe,oe),G(R,Q,oe,oe),oe=String.fromCharCode(55349,56372+e),G(P,W,oe,oe),G(R,Q,oe,oe),oe=String.fromCharCode(55349,56424+e),G(P,W,oe,oe),G(R,Q,oe,oe),oe=String.fromCharCode(55349,56580+e),G(P,W,oe,oe),G(R,Q,oe,oe),oe=String.fromCharCode(55349,56736+e),G(P,W,oe,oe),G(R,Q,oe,oe),oe=String.fromCharCode(55349,56788+e),G(P,W,oe,oe),G(R,Q,oe,oe),oe=String.fromCharCode(55349,56840+e),G(P,W,oe,oe),G(R,Q,oe,oe),oe=String.fromCharCode(55349,56944+e),G(P,W,oe,oe),G(R,Q,oe,oe),oe=String.fromCharCode(55349,56632+e),G(P,W,oe,oe),G(R,Q,oe,oe);const t=ne.charAt(e);oe=String.fromCharCode(55349,56476+e),G(P,W,t,oe),G(R,Q,t,oe)}for(let e=0;e<10;e++)oe=String.fromCharCode(55349,57294+e),G(P,W,oe,oe),G(R,Q,oe,oe),oe=String.fromCharCode(55349,57314+e),G(P,W,oe,oe),G(R,Q,oe,oe),oe=String.fromCharCode(55349,57324+e),G(P,W,oe,oe),G(R,Q,oe,oe),oe=String.fromCharCode(55349,57334+e),G(P,W,oe,oe),G(R,Q,oe,oe);const ae=function(e,t,r){return!D[t][e]||!D[t][e].replace||55349===e.charCodeAt(0)||Object.prototype.hasOwnProperty.call(ee,e)&&r&&(r.fontFamily&&"tt"===r.fontFamily.slice(4,6)||r.font&&"tt"===r.font.slice(4,6))||(e=D[t][e].replace),new N(e)},le=(e,t)=>{if(0===e.children.length||"mtext"!==e.children[e.children.length-1].type){const r=new C("mtext",[new N(t.children[0].text)]);e.children.push(r)}else e.children[e.children.length-1].children[0].text+=t.children[0].text},ie=e=>{if("mrow"!==e.type&&"mstyle"!==e.type)return e;if(0===e.children.length)return e;const t=new C("mrow");for(let r=0;r0&&" "===n.children[0].text.charAt(s-1)&&(n.children[0].text=n.children[0].text.slice(0,-1)+"\xA0");for(const[t,r]of Object.entries(e.attributes))n.attributes[t]=r}return 1===t.children.length&&"mtext"===t.children[0].type?t.children[0]:t},ce=function(e,t=!1){if(!(1!==e.length||e[0]instanceof w))return e[0];if(!t){e[0]instanceof C&&"mo"===e[0].type&&!e[0].attributes.fence&&(e[0].attributes.lspace="0em",e[0].attributes.rspace="0em");const t=e.length-1;e[t]instanceof C&&"mo"===e[t].type&&!e[t].attributes.fence&&(e[t].attributes.lspace="0em",e[t].attributes.rspace="0em")}return new C("mrow",e)};function me(e){if(!e)return!1;if("mi"===e.type&&1===e.children.length){const t=e.children[0];return t instanceof N&&"."===t.text}if("mtext"===e.type&&1===e.children.length){const t=e.children[0];return t instanceof N&&"\u2008"===t.text}if("mo"===e.type&&1===e.children.length&&"true"===e.getAttribute("separator")&&"0em"===e.getAttribute("lspace")&&"0em"===e.getAttribute("rspace")){const t=e.children[0];return t instanceof N&&","===t.text}return!1}const pe=(e,t)=>{const r=e[t],n=e[t+1];return"atom"===r.type&&","===r.text&&r.loc&&n.loc&&r.loc.end===n.loc.start},ue=e=>"atom"===e.type&&"rel"===e.family||"mclass"===e.type&&"mrel"===e.mclass,de=function(e,t,r=!1){if(!r&&1===e.length){const r=ge(e[0],t);return r instanceof C&&"mo"===r.type&&(r.setAttribute("lspace","0em"),r.setAttribute("rspace","0em")),[r]}const n=[],s=[];let o;for(let r=0;r0&&ue(e[t])&&ue(e[t-1])&&r.setAttribute("lspace","0em"),"mn"===r.type&&o&&"mn"===o.type)o.children.push(...r.children);else if(me(r)&&o&&"mn"===o.type)o.children.push(...r.children);else if(o&&"mn"===o.type&&t=1&&o&&("mn"===o.type||me(o))){const e=r.children[0];e instanceof C&&"mn"===e.type&&o&&(e.children=[...o.children,...e.children],n.pop())}n.push(r),o=r}}return n},he=function(e,t,r=!1){return ce(de(e,t,r),r)},ge=function(t,r){if(!t)return new C("mrow");if(h[t.type]){return h[t.type](t,r)}throw new e("Got group of unknown type: \'"+t.type+"\'")},fe=e=>new C("mtd",[],[],{padding:"0",width:"50%"}),be=["mrow","mtd","mtable","mtr"],ye=e=>{for(const t of e.children)if(t.type&&be.includes(t.type)){if(t.classes&&"tml-label"===t.classes[0]){return t.label}{const e=ye(t);if(e)return e}}else if(!t.type){const e=ye(t);if(e)return e}};function we(e,t,r,n){let s=null;1===e.length&&"tag"===e[0].type&&(s=e[0].tag,e=e[0].body);const o=de(e,r);if(1===o.length&&o[0]instanceof O)return o[0];const a=n.displayMode||n.annotate?"none":n.wrap,l=0===o.length?null:o[0];let i=1===o.length&&null===s&&l instanceof C?o[0]:function(e,t,r){const n=[];let s=[],o=[],a=0,l=0;for(;l0&&s.push(new C("mrow",o)),s.push(r),o=[];const e=new C("mtd",s);e.style.textAlign="left",n.push(new C("mtr",[e])),s=[],l+=1;continue}if(o.push(r),r.type&&"mo"===r.type&&1===r.children.length&&(!r.attributes.form||"prefix"!==r.attributes.form)&&!Object.prototype.hasOwnProperty.call(r.attributes,"movablelimits")){const n=r.children[0].text;if("="===t&&"="===n){if(a+=1,a>1){o.pop();const e=new C("mrow",o);s.push(e),o=[r]}}else if("tex"===t){const t=l0){const e=new C("mrow",o);s.push(e)}if(n.length>0){const e=new C("mtd",s);e.style.textAlign="left";const t=new C("mtr",[e]);n.push(t);const o=new C("mtable",n);return r||(o.setAttribute("columnalign","left"),o.setAttribute("rowspacing","0em")),o}return B(s)}(o,a,n.displayMode);if(s&&(i=((e,t,r,n)=>{t=he(t[0].body,r),(t=ie(t)).classes.push("tml-tag");const s=ye(e);e=new C("mtd",[e]);const o=[fe(),e,fe()];o[n?0:2].children.push(t);const a=new C("mtr",o,["tml-tageqn"]);s&&a.setAttribute("id",s);const l=new C("mtable",[a]);return l.style.width="100%",l.setAttribute("displaystyle","true"),l})(i,s,r,n.leqno)),n.annotate){const e=new C("annotation",[new N(t)]);e.setAttribute("encoding","application/x-tex"),i=new C("semantics",[i,e])}const c=new C("math",[i]);return n.xml&&c.setAttribute("xmlns","http://www.w3.org/1998/Math/MathML"),n.displayMode&&(c.setAttribute("display","block"),c.style.display="block math",c.classes=["tml-display"]),c}const xe=(e,t)=>{const r=e.isStretchy?F(e):new C("mo",[ae(e.label,e.mode)]);e.isStretchy||r.setAttribute("stretchy","false"),"\\\\vec"!==e.label&&(r.style.mathDepth="0");const n="\\\\c"===e.label?"munder":"mover",s=ve.has(e.label);if("mover"===n&&"math"===e.mode&&!e.isStretchy&&e.base.text&&1===e.base.text.length){const t=e.base.text,n="\\\\vec"===e.label,o="\\\\vec"===n?"-vec":"";n&&r.classes.push("tml-vec");const a=n?"-vec":s?"-acc":"";"DHKLUcegorsuvxyz\u03A0\u03A5\u03A8\u03B1\u03B4\u03B7\u03B9\u03BC\u03BD\u03BF\u03C4\u03C5\u03C7\u03F5".indexOf(t)>-1?(r.classes.push(`chr-sml${o}`),r.classes.push(`wbk-sml${a}`)):"BCEGIMNOPQRSTXZlpqtw\u0393\u0398\u039E\u03A3\u03A6\u03A9\u03B2\u03B5\u03B6\u03B8\u03BE\u03C1\u03C2\u03C6\u03C8\u03D1\u03D5\u03F1".indexOf(t)>-1?(r.classes.push(`chr-med${o}`),r.classes.push(`wbk-med${a}`)):"AFJdf\u0394\u039B".indexOf(t)>-1?(r.classes.push(`chr-lrg${o}`),r.classes.push(`wbk-lrg${a}`)):n?r.classes.push("wbk-vec"):s&&r.classes.push("wbk-acc")}else s&&r.classes.push("wbk-acc");return new C(n,[ge(e.base,t),r])},ke=new Set(["\\\\acute","\\\\check","\\\\grave","\\\\ddot","\\\\dddot","\\\\ddddot","\\\\tilde","\\\\bar","\\\\breve","\\\\check","\\\\hat","\\\\vec","\\\\dot","\\\\mathring"]),ve=new Set(["\\\\acute","\\\\bar","\\\\breve","\\\\check","\\\\dot","\\\\ddot","\\\\grave","\\\\hat","\\\\mathring","\\\\`","\\\\\'","\\\\^","\\\\=","\\\\u","\\\\.",\'\\\\"\',"\\\\r","\\\\H","\\\\v"]),Ae={"\\\\`":"\u0300","\\\\\'":"\u0301","\\\\^":"\u0302","\\\\~":"\u0303","\\\\=":"\u0304","\\\\u":"\u0306","\\\\.":"\u0307",\'\\\\"\':"\u0308","\\\\r":"\u030A","\\\\H":"\u030B","\\\\v":"\u030C","\\\\c":"\u0327"};g({type:"accent",names:["\\\\acute","\\\\grave","\\\\ddot","\\\\dddot","\\\\ddddot","\\\\tilde","\\\\bar","\\\\breve","\\\\check","\\\\hat","\\\\vec","\\\\dot","\\\\mathring","\\\\overparen","\\\\widecheck","\\\\widehat","\\\\wideparen","\\\\widetilde","\\\\overrightarrow","\\\\overleftarrow","\\\\Overrightarrow","\\\\overleftrightarrow","\\\\overgroup","\\\\overleftharpoon","\\\\overrightharpoon"],props:{numArgs:1},handler:(e,t)=>{const r=b(t[0]),n=!ke.has(e.funcName);return{type:"accent",mode:e.parser.mode,label:e.funcName,isStretchy:n,base:r}},mathmlBuilder:xe}),g({type:"accent",names:["\\\\\'","\\\\`","\\\\^","\\\\~","\\\\=","\\\\c","\\\\u","\\\\.",\'\\\\"\',"\\\\r","\\\\H","\\\\v"],props:{numArgs:1,allowedInText:!0,allowedInMath:!0,argTypes:["primitive"]},handler:(e,t)=>{const r=b(t[0]),n=e.parser.mode;return"math"===n&&e.parser.settings.strict&&console.log(`Temml parse error: Command ${e.funcName} is invalid in math mode.`),"text"===n&&r.text&&1===r.text.length&&e.funcName in Ae&&m.indexOf(r.text)>-1?{type:"textord",mode:"text",text:r.text+Ae[e.funcName]}:"\\\\c"===e.funcName&&"text"===n&&r.text&&1===r.text.length?{type:"textord",mode:"text",text:r.text+"\u0327"}:{type:"accent",mode:n,label:e.funcName,isStretchy:!1,base:r}},mathmlBuilder:xe}),g({type:"accentUnder",names:["\\\\underleftarrow","\\\\underrightarrow","\\\\underleftrightarrow","\\\\undergroup","\\\\underparen","\\\\utilde"],props:{numArgs:1},handler:({parser:e,funcName:t},r)=>{const n=r[0];return{type:"accentUnder",mode:e.mode,label:t,base:n}},mathmlBuilder:(e,t)=>{const r=F(e);r.style["math-depth"]=0;return new C("munder",[ge(e.base,t),r])}});const Te={pt:800/803,pc:9600/803,dd:1238/1157*800/803,cc:12.792133216944668,nd:685/642*800/803,nc:1370/107*800/803,sp:1/65536*800/803,mm:25.4/72,cm:2.54/72,in:1/72,px:96/72},Se=["em","ex","mu","pt","mm","cm","in","px","bp","pc","dd","cc","nd","nc","sp"],Oe=function(e){return"string"!=typeof e&&(e=e.unit),Se.indexOf(e)>-1},qe=e=>[1,.7,.5][Math.max(e-1,0)],Be=function(t,r){let n=t.number;if(r.maxSize[0]<0&&n>0)return{number:0,unit:"em"};const s=t.unit;switch(s){case"mm":case"cm":case"in":case"px":return n*Te[s]>r.maxSize[1]?{number:r.maxSize[1],unit:"pt"}:{number:n,unit:s};case"em":case"ex":return"ex"===s&&(n*=.431),n=Math.min(n/qe(r.level),r.maxSize[0]),{number:c(n),unit:"em"};case"bp":return n>r.maxSize[1]&&(n=r.maxSize[1]),{number:n,unit:"pt"};case"pt":case"pc":case"dd":case"cc":case"nd":case"nc":case"sp":return n=Math.min(n*Te[s],r.maxSize[1]),{number:c(n),unit:"pt"};case"mu":return n=Math.min(n/18,r.maxSize[0]),{number:c(n),unit:"em"};default:throw new e("Invalid unit: \'"+s+"\'")}},Ce=e=>{const t=new C("mspace");return t.setAttribute("width",e+"em"),t},Ne=(e,t=.3,r=0,n=!1)=>{if(null==e&&0===r)return Ce(t);const s=e?[e]:[];if(0!==t&&s.unshift(Ce(t)),r>0&&s.push(Ce(r)),n){const e=new C("mpadded",s);return e.setAttribute("height","0.1px"),e}return new C("mrow",s)},ze=(e,t)=>Number(e)/qe(t),$e=(e,t,r,n)=>{const s=I(e),o="eq"===e.slice(1,3),a="x"===e.charAt(1)?"1.75":"cd"===e.slice(2,4)?"3.0":o?"1.0":"2.0";s.setAttribute("lspace","0"),s.setAttribute("rspace",o?"0.5em":"0");const l=n.withLevel(n.level<2?2:3),i=ze(a,l.level),c=ze(a,3),m=Ne(null,i.toFixed(4),0),p=Ne(null,c.toFixed(4),0),u=ze(o?0:.3,l.level).toFixed(4);let d,h;const g=t&&t.body&&(t.body.body||t.body.length>0);if(g){let r=ge(t,l);r=Ne(r,u,u,"\\\\\\\\cdrightarrow"===e||"\\\\\\\\cdleftarrow"===e),d=new C("mover",[r,p])}const f=r&&r.body&&(r.body.body||r.body.length>0);if(f){let e=ge(r,l);e=Ne(e,u,u),h=new C("munder",[e,p])}let b;return b=g||f?g&&f?new C("munderover",[s,h,d]):g?new C("mover",[s,d]):new C("munder",[s,h]):new C("mover",[s,m]),"3.0"===a&&(b.style.height="1em"),b.setAttribute("accent","false"),b};g({type:"xArrow",names:["\\\\xleftarrow","\\\\xrightarrow","\\\\xLeftarrow","\\\\xRightarrow","\\\\xleftrightarrow","\\\\xLeftrightarrow","\\\\xhookleftarrow","\\\\xhookrightarrow","\\\\xmapsto","\\\\xrightharpoondown","\\\\xrightharpoonup","\\\\xleftharpoondown","\\\\xleftharpoonup","\\\\xlongequal","\\\\xtwoheadrightarrow","\\\\xtwoheadleftarrow","\\\\xtofrom","\\\\xleftrightharpoons","\\\\xrightleftharpoons","\\\\yields","\\\\yieldsLeft","\\\\mesomerism","\\\\longrightharpoonup","\\\\longleftharpoondown","\\\\yieldsLeftRight","\\\\chemequilibrium","\\\\\\\\cdrightarrow","\\\\\\\\cdleftarrow","\\\\\\\\cdlongequal"],props:{numArgs:1,numOptionalArgs:1},handler:({parser:e,funcName:t},r,n)=>({type:"xArrow",mode:e.mode,name:t,body:r[0],below:n[0]}),mathmlBuilder(e,t){const r=[$e(e.name,e.body,e.below,t)];return r.unshift(Ce(.2778)),r.push(Ce(.2778)),new C("mrow",r)}});const Ee={"\\\\equilibriumRight":["\\\\longrightharpoonup","\\\\eqleftharpoondown"],"\\\\equilibriumLeft":["\\\\eqrightharpoonup","\\\\longleftharpoondown"]};g({type:"stackedArrow",names:["\\\\equilibriumRight","\\\\equilibriumLeft"],props:{numArgs:1,numOptionalArgs:1},handler({parser:e,funcName:t},r,n){const s=r[0]?{type:"hphantom",mode:e.mode,body:r[0]}:null,o=n[0]?{type:"hphantom",mode:e.mode,body:n[0]}:null;return{type:"stackedArrow",mode:e.mode,name:t,body:r[0],upperArrowBelow:o,lowerArrowBody:s,below:n[0]}},mathmlBuilder(e,t){const r=Ee[e.name][0],n=Ee[e.name][1],s=$e(r,e.body,e.upperArrowBelow,t),o=$e(n,e.lowerArrowBody,e.below,t);let a;const l=new C("mpadded",[s]);if(l.setAttribute("voffset","0.3em"),l.setAttribute("height","+0.3em"),l.setAttribute("depth","-0.3em"),"\\\\equilibriumLeft"===e.name){const e=new C("mpadded",[o]);e.setAttribute("width","0.5em"),a=new C("mpadded",[Ce(.2778),e,l,Ce(.2778)])}else l.setAttribute("width","\\\\equilibriumRight"===e.name?"0.5em":"0"),a=new C("mpadded",[Ce(.2778),l,o,Ce(.2778)]);return a.setAttribute("voffset","-0.18em"),a.setAttribute("height","-0.18em"),a.setAttribute("depth","+0.18em"),a}});const Ie={};function Le({type:e,names:t,props:r,handler:n,mathmlBuilder:s}){const o={type:e,numArgs:r.numArgs||0,allowedInText:!1,numOptionalArgs:0,handler:n};for(let e=0;e":"\\\\\\\\cdrightarrow","<":"\\\\\\\\cdleftarrow","=":"\\\\\\\\cdlongequal",A:"\\\\uparrow",V:"\\\\downarrow","|":"\\\\Vert",".":"no arrow"},Ge=()=>({type:"styling",body:[],mode:"math",scriptLevel:"display"}),Pe=e=>"textord"===e.type&&"@"===e.text,Re=(e,t)=>("mathord"===e.type||"atom"===e.type)&&e.text===t;function Ue(e,t,r){const n=De[e];switch(n){case"\\\\\\\\cdrightarrow":case"\\\\\\\\cdleftarrow":return r.callFunction(n,[t[0]],[t[1]]);case"\\\\uparrow":case"\\\\downarrow":{const e={type:"atom",text:n,mode:"math",family:"rel"},s={type:"ordgroup",mode:"math",body:[r.callFunction("\\\\\\\\cdleft",[t[0]],[]),r.callFunction("\\\\Big",[e],[]),r.callFunction("\\\\\\\\cdright",[t[1]],[])],semisimple:!0};return r.callFunction("\\\\\\\\cdparent",[s],[])}case"\\\\\\\\cdlongequal":return r.callFunction("\\\\\\\\cdlongequal",[],[]);case"\\\\Vert":{const e={type:"textord",text:"\\\\Vert",mode:"math"};return r.callFunction("\\\\Big",[e],[])}default:return{type:"textord",text:" ",mode:"math"}}}g({type:"cdlabel",names:["\\\\\\\\cdleft","\\\\\\\\cdright"],props:{numArgs:1},handler:({parser:e,funcName:t},r)=>({type:"cdlabel",mode:e.mode,side:t.slice(4),label:r[0]}),mathmlBuilder(e,t){if(0===e.label.body.length)return new C("mrow",t);const r=ge(e.label,t);"left"===e.side&&r.classes.push("tml-shift-left");const n=new C("mtd",[r]);n.style.padding="0";const s=new C("mtr",[n]),o=new C("mtable",[s]),a=new C("mpadded",[o]);return a.setAttribute("width","0.1px"),a.setAttribute("displaystyle","false"),a.setAttribute("scriptlevel","1"),a}}),g({type:"cdlabelparent",names:["\\\\\\\\cdparent"],props:{numArgs:1},handler:({parser:e},t)=>({type:"cdlabelparent",mode:e.mode,fragment:t[0]}),mathmlBuilder:(e,t)=>new C("mrow",[ge(e.fragment,t)])});const He=e=>({type:"ordgroup",mode:"math",body:e,semisimple:!0}),_e=(e,t)=>({type:t,mode:"math",body:He(e)});class Ve{constructor(e,t,r){this.lexer=e,this.start=t,this.end=r}static range(e,t){return t?e&&e.loc&&t.loc&&e.loc.lexer===t.loc.lexer?new Ve(e.loc.lexer,e.loc.start,t.loc.end):null:e&&e.loc}}class We{constructor(e,t){this.text=e,this.loc=t}range(e,t){return new We(t,Ve.range(this,e))}}const Xe=0,Ze=1,Ye=2,Je=3,Ke=Object.create(null);function Qe(e,t){Ke[e]=t}const et=Ke;Qe("\\\\noexpand",function(e){const t=e.popToken();return e.isExpandable(t.text)&&(t.noexpand=!0,t.treatAsRelax=!0),{tokens:[t],numArgs:0}}),Qe("\\\\expandafter",function(e){const t=e.popToken();return e.expandOnce(!0),{tokens:[t],numArgs:0}}),Qe("\\\\@firstoftwo",function(e){return{tokens:e.consumeArgs(2)[0],numArgs:0}}),Qe("\\\\@secondoftwo",function(e){return{tokens:e.consumeArgs(2)[1],numArgs:0}}),Qe("\\\\@ifnextchar",function(e){const t=e.consumeArgs(3);e.consumeSpaces();const r=e.future();return 1===t[0].length&&t[0][0].text===r.text?{tokens:t[1],numArgs:0}:{tokens:t[2],numArgs:0}}),Qe("\\\\@ifstar","\\\\@ifnextchar *{\\\\@firstoftwo{#1}}"),Qe("\\\\TextOrMath",function(e){const t=e.consumeArgs(2);return"text"===e.mode?{tokens:t[0],numArgs:0}:{tokens:t[1],numArgs:0}});const tt=e=>{let t="";for(let r=e.length-1;r>-1;r--)t+=e[r].text;return t},rt={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,a:10,A:10,b:11,B:11,c:12,C:12,d:13,D:13,e:14,E:14,f:15,F:15},nt=e=>{const t=e.future().text;return"EOF"===t?[null,""]:[rt[t.charAt(0)],t]},st=(e,t,r)=>{for(let n=1;n=0;e--){const s=t[e].loc.start;s>n&&(r+=" ",n=s),r+=t[e].text,n+=t[e].text.length}return r}Qe("\\\\char",function(t){let r,n=t.popToken(),s="";if("\'"===n.text)r=8,n=t.popToken();else if(\'"\'===n.text)r=16,n=t.popToken();else if("`"===n.text)if(n=t.popToken(),"\\\\"===n.text[0])s=n.text.charCodeAt(1);else{if("EOF"===n.text)throw new e("\\\\char` missing argument");s=n.text.charCodeAt(0)}else r=10;if(r){let o,a=n.text;if(s=rt[a.charAt(0)],null==s||s>=r)throw new e(`Invalid base-${r} digit ${n.text}`);for(s=st(s,a,r),[o,a]=nt(t);null!=o&&o":"\\\\dotsb","-":"\\\\dotsb","*":"\\\\dotsb",":":"\\\\dotsb","\\\\DOTSB":"\\\\dotsb","\\\\coprod":"\\\\dotsb","\\\\bigvee":"\\\\dotsb","\\\\bigwedge":"\\\\dotsb","\\\\biguplus":"\\\\dotsb","\\\\bigcap":"\\\\dotsb","\\\\bigcup":"\\\\dotsb","\\\\prod":"\\\\dotsb","\\\\sum":"\\\\dotsb","\\\\bigotimes":"\\\\dotsb","\\\\bigoplus":"\\\\dotsb","\\\\bigodot":"\\\\dotsb","\\\\bigsqcap":"\\\\dotsb","\\\\bigsqcup":"\\\\dotsb","\\\\bigtimes":"\\\\dotsb","\\\\And":"\\\\dotsb","\\\\longrightarrow":"\\\\dotsb","\\\\Longrightarrow":"\\\\dotsb","\\\\longleftarrow":"\\\\dotsb","\\\\Longleftarrow":"\\\\dotsb","\\\\longleftrightarrow":"\\\\dotsb","\\\\Longleftrightarrow":"\\\\dotsb","\\\\mapsto":"\\\\dotsb","\\\\longmapsto":"\\\\dotsb","\\\\hookrightarrow":"\\\\dotsb","\\\\doteq":"\\\\dotsb","\\\\mathbin":"\\\\dotsb","\\\\mathrel":"\\\\dotsb","\\\\relbar":"\\\\dotsb","\\\\Relbar":"\\\\dotsb","\\\\xrightarrow":"\\\\dotsb","\\\\xleftarrow":"\\\\dotsb","\\\\DOTSI":"\\\\dotsi","\\\\int":"\\\\dotsi","\\\\oint":"\\\\dotsi","\\\\iint":"\\\\dotsi","\\\\iiint":"\\\\dotsi","\\\\iiiint":"\\\\dotsi","\\\\DOTSX":"\\\\dotsx"};Qe("\\\\dots",function(e){let t="\\\\dotso";const r=e.expandAfterFuture().text;return r in at?t=at[r]:("\\\\not"===r.slice(0,4)||r in D.math&&["bin","rel"].includes(D.math[r].group))&&(t="\\\\dotsb"),t}),Qe("\\\\dotsb","\\\\cdots"),Qe("\\\\dotsm","\\\\cdots"),Qe("\\\\dotsi","\\\\!\\\\cdots"),Qe("\\\\idotsint","\\\\int\\\\!\\\\cdots\\\\!\\\\int"),Qe("\\\\dotsx","\\\\ldots\\\\,"),Qe("\\\\DOTSI","\\\\relax"),Qe("\\\\DOTSB","\\\\relax"),Qe("\\\\DOTSX","\\\\relax"),Qe("\\\\tmspace","\\\\TextOrMath{\\\\kern#1#3}{\\\\mskip#1#2}\\\\relax"),Qe("\\\\,","{\\\\tmspace+{3mu}{.1667em}}"),Qe("\\\\thinspace","\\\\,"),Qe("\\\\>","\\\\mskip{4mu}"),Qe("\\\\:","{\\\\tmspace+{4mu}{.2222em}}"),Qe("\\\\medspace","\\\\:"),Qe("\\\\;","{\\\\tmspace+{5mu}{.2777em}}"),Qe("\\\\thickspace","\\\\;"),Qe("\\\\!","{\\\\tmspace-{3mu}{.1667em}}"),Qe("\\\\negthinspace","\\\\!"),Qe("\\\\negmedspace","{\\\\tmspace-{4mu}{.2222em}}"),Qe("\\\\negthickspace","{\\\\tmspace-{5mu}{.277em}}"),Qe("\\\\enspace","\\\\kern.5em "),Qe("\\\\enskip","\\\\hskip.5em\\\\relax"),Qe("\\\\quad","\\\\hskip1em\\\\relax"),Qe("\\\\qquad","\\\\hskip2em\\\\relax"),Qe("\\\\AA","\\\\TextOrMath{\\\\Angstrom}{\\\\mathring{A}}\\\\relax"),Qe("\\\\tag","\\\\@ifstar\\\\tag@literal\\\\tag@paren"),Qe("\\\\tag@paren","\\\\tag@literal{({#1})}"),Qe("\\\\tag@literal",t=>{if(t.macros.get("\\\\df@tag"))throw new e("Multiple \\\\tag");return"\\\\gdef\\\\df@tag{\\\\text{#1}}"}),Qe("\\\\notag","\\\\nonumber"),Qe("\\\\nonumber","\\\\gdef\\\\@eqnsw{0}"),Qe("\\\\bmod","\\\\mathbin{\\\\text{mod}}"),Qe("\\\\pod","\\\\allowbreak\\\\mathchoice{\\\\mkern18mu}{\\\\mkern8mu}{\\\\mkern8mu}{\\\\mkern8mu}(#1)"),Qe("\\\\pmod","\\\\pod{{\\\\rm mod}\\\\mkern6mu#1}"),Qe("\\\\mod","\\\\allowbreak\\\\mathchoice{\\\\mkern18mu}{\\\\mkern12mu}{\\\\mkern12mu}{\\\\mkern12mu}{\\\\rm mod}\\\\,\\\\,#1"),Qe("\\\\newline","\\\\\\\\\\\\relax"),Qe("\\\\TeX","\\\\textrm{T}\\\\kern-.1667em\\\\raisebox{-.5ex}{E}\\\\kern-.125em\\\\textrm{X}"),Qe("\\\\LaTeX","\\\\textrm{L}\\\\kern-.35em\\\\raisebox{0.2em}{\\\\scriptstyle A}\\\\kern-.15em\\\\TeX"),Qe("\\\\Temml","\\\\textrm{T}\\\\kern-0.2em\\\\lower{0.2em}{\\\\textrm{E}}\\\\kern-0.08em{\\\\textrm{M}\\\\kern-0.08em\\\\raise{0.2em}\\\\textrm{M}\\\\kern-0.08em\\\\textrm{L}}"),Qe("\\\\hspace","\\\\@ifstar\\\\@hspacer\\\\@hspace"),Qe("\\\\@hspace","\\\\hskip #1\\\\relax"),Qe("\\\\@hspacer","\\\\rule{0pt}{0pt}\\\\hskip #1\\\\relax"),Qe("\\\\colon",\'\\\\mathpunct{\\\\char"3a}\'),Qe("\\\\prescript","\\\\pres@cript{^{#1}_{#2}}{}{#3}"),Qe("\\\\ordinarycolon",\'\\\\char"3a\'),Qe("\\\\vcentcolon","\\\\mathrel{\\\\raisebox{0.035em}{\\\\ordinarycolon}}"),Qe("\\\\coloneq",\'\\\\mathrel{\\\\raisebox{0.035em}{\\\\ordinarycolon}\\\\char"2212}\'),Qe("\\\\Coloneq",\'\\\\mathrel{\\\\char"2237\\\\char"2212}\'),Qe("\\\\Eqqcolon",\'\\\\mathrel{\\\\char"3d\\\\char"2237}\'),Qe("\\\\Eqcolon",\'\\\\mathrel{\\\\char"2212\\\\char"2237}\'),Qe("\\\\colonapprox",\'\\\\mathrel{\\\\raisebox{0.035em}{\\\\ordinarycolon}\\\\char"2248}\'),Qe("\\\\Colonapprox",\'\\\\mathrel{\\\\char"2237\\\\char"2248}\'),Qe("\\\\colonsim",\'\\\\mathrel{\\\\raisebox{0.035em}{\\\\ordinarycolon}\\\\char"223c}\'),Qe("\\\\Colonsim",\'\\\\mathrel{\\\\raisebox{0.035em}{\\\\ordinarycolon}\\\\char"223c}\'),Qe("\\\\ratio","\\\\vcentcolon"),Qe("\\\\coloncolon","\\\\dblcolon"),Qe("\\\\colonequals","\\\\coloneqq"),Qe("\\\\coloncolonequals","\\\\Coloneqq"),Qe("\\\\equalscolon","\\\\eqqcolon"),Qe("\\\\equalscoloncolon","\\\\Eqqcolon"),Qe("\\\\colonminus","\\\\coloneq"),Qe("\\\\coloncolonminus","\\\\Coloneq"),Qe("\\\\minuscolon","\\\\eqcolon"),Qe("\\\\minuscoloncolon","\\\\Eqcolon"),Qe("\\\\coloncolonapprox","\\\\Colonapprox"),Qe("\\\\coloncolonsim","\\\\Colonsim"),Qe("\\\\notni","\\\\mathrel{\\\\char`\u220C}"),Qe("\\\\limsup","\\\\DOTSB\\\\operatorname*{lim\\\\,sup}"),Qe("\\\\liminf","\\\\DOTSB\\\\operatorname*{lim\\\\,inf}"),Qe("\\\\injlim","\\\\DOTSB\\\\operatorname*{inj\\\\,lim}"),Qe("\\\\projlim","\\\\DOTSB\\\\operatorname*{proj\\\\,lim}"),Qe("\\\\varlimsup","\\\\DOTSB\\\\operatorname*{\\\\overline{\\\\text{lim}}}"),Qe("\\\\varliminf","\\\\DOTSB\\\\operatorname*{\\\\underline{\\\\text{lim}}}"),Qe("\\\\varinjlim","\\\\DOTSB\\\\operatorname*{\\\\underrightarrow{\\\\text{lim}}}"),Qe("\\\\varprojlim","\\\\DOTSB\\\\operatorname*{\\\\underleftarrow{\\\\text{lim}}}"),Qe("\\\\centerdot","{\\\\medspace\\\\rule{0.167em}{0.189em}\\\\medspace}"),Qe("\\\\argmin","\\\\DOTSB\\\\operatorname*{arg\\\\,min}"),Qe("\\\\argmax","\\\\DOTSB\\\\operatorname*{arg\\\\,max}"),Qe("\\\\plim","\\\\DOTSB\\\\operatorname*{plim}"),Qe("\\\\leftmodels","\\\\mathop{\\\\reflectbox{$\\\\models$}}"),Qe("\\\\bra","\\\\mathinner{\\\\langle{#1}|}"),Qe("\\\\ket","\\\\mathinner{|{#1}\\\\rangle}"),Qe("\\\\braket","\\\\mathinner{\\\\langle{#1}\\\\rangle}"),Qe("\\\\Bra","\\\\left\\\\langle#1\\\\right|"),Qe("\\\\Ket","\\\\left|#1\\\\right\\\\rangle");const lt=(e,t)=>{const r=`}\\\\,\\\\middle${"|"===t[0]?"\\\\vert":"\\\\Vert"}\\\\,{`;return e.slice(0,t.index)+r+e.slice(t.index+t[0].length)};Qe("\\\\Braket",function(e){let t=ot(e);const r=/\\|\\||\\||\\\\\\|/g;let n;for(;null!==(n=r.exec(t));)t=lt(t,n);return"\\\\left\\\\langle{"+t+"}\\\\right\\\\rangle"}),Qe("\\\\Set",function(e){let t=ot(e);const r=/\\|\\||\\||\\\\\\|/.exec(t);return r&&(t=lt(t,r)),"\\\\left\\\\{\\\\:{"+t+"}\\\\:\\\\right\\\\}"}),Qe("\\\\set",function(e){return"\\\\{{"+ot(e).replace(/\\|/,"}\\\\mid{")+"}\\\\}"}),Qe("\\\\angln","{\\\\angl n}"),Qe("\\\\odv","\\\\@ifstar\\\\odv@next\\\\odv@numerator"),Qe("\\\\odv@numerator","\\\\frac{\\\\mathrm{d}#1}{\\\\mathrm{d}#2}"),Qe("\\\\odv@next","\\\\frac{\\\\mathrm{d}}{\\\\mathrm{d}#2}#1"),Qe("\\\\pdv","\\\\@ifstar\\\\pdv@next\\\\pdv@numerator");const it=e=>{const t=e[0][0].text,r=tt(e[1]).split(","),n=String(r.length),s="1"===n?"\\\\partial":`\\\\partial^${n}`;let o="";return r.map(e=>{o+="\\\\partial "+e.trim()+"\\\\,"}),[t,s,o.replace(/\\\\,$/,"")]};function ct(e){const t=[];e.consumeSpaces();let r=e.fetch().text;for("\\\\relax"===r&&(e.consume(),e.consumeSpaces(),r=e.fetch().text);"\\\\hline"===r||"\\\\hdashline"===r;)e.consume(),t.push("\\\\hdashline"===r),e.consumeSpaces(),r=e.fetch().text;return t}Qe("\\\\pdv@numerator",function(e){const[t,r,n]=it(e.consumeArgs(2));return`\\\\frac{${r} ${t}}{${n}}`}),Qe("\\\\pdv@next",function(e){const[t,r,n]=it(e.consumeArgs(2));return`\\\\frac{${r}}{${n}} ${t}`}),Qe("\\\\upalpha","\\\\up@greek{\\\\alpha}"),Qe("\\\\upbeta","\\\\up@greek{\\\\beta}"),Qe("\\\\upgamma","\\\\up@greek{\\\\gamma}"),Qe("\\\\updelta","\\\\up@greek{\\\\delta}"),Qe("\\\\upepsilon","\\\\up@greek{\\\\epsilon}"),Qe("\\\\upzeta","\\\\up@greek{\\\\zeta}"),Qe("\\\\upeta","\\\\up@greek{\\\\eta}"),Qe("\\\\uptheta","\\\\up@greek{\\\\theta}"),Qe("\\\\upiota","\\\\up@greek{\\\\iota}"),Qe("\\\\upkappa","\\\\up@greek{\\\\kappa}"),Qe("\\\\uplambda","\\\\up@greek{\\\\lambda}"),Qe("\\\\upmu","\\\\up@greek{\\\\mu}"),Qe("\\\\upnu","\\\\up@greek{\\\\nu}"),Qe("\\\\upxi","\\\\up@greek{\\\\xi}"),Qe("\\\\upomicron","\\\\up@greek{\\\\omicron}"),Qe("\\\\uppi","\\\\up@greek{\\\\pi}"),Qe("\\\\upalpha","\\\\up@greek{\\\\alpha}"),Qe("\\\\uprho","\\\\up@greek{\\\\rho}"),Qe("\\\\upsigma","\\\\up@greek{\\\\sigma}"),Qe("\\\\uptau","\\\\up@greek{\\\\tau}"),Qe("\\\\upupsilon","\\\\up@greek{\\\\upsilon}"),Qe("\\\\upphi","\\\\up@greek{\\\\phi}"),Qe("\\\\upchi","\\\\up@greek{\\\\chi}"),Qe("\\\\uppsi","\\\\up@greek{\\\\psi}"),Qe("\\\\upomega","\\\\up@greek{\\\\omega}"),Qe("\\\\invamp",\'\\\\mathbin{\\\\char"214b}\'),Qe("\\\\parr",\'\\\\mathbin{\\\\char"214b}\'),Qe("\\\\upand",\'\\\\mathbin{\\\\char"214b}\'),Qe("\\\\with",\'\\\\mathbin{\\\\char"26}\'),Qe("\\\\multimapinv",\'\\\\mathrel{\\\\char"27dc}\'),Qe("\\\\multimapboth",\'\\\\mathrel{\\\\char"29df}\'),Qe("\\\\scoh",\'{\\\\mkern5mu\\\\char"2322\\\\mkern5mu}\'),Qe("\\\\sincoh",\'{\\\\mkern5mu\\\\char"2323\\\\mkern5mu}\'),Qe("\\\\coh",\'{\\\\mkern5mu\\\\rule{}{0.7em}\\\\mathrlap{\\\\smash{\\\\raise2mu{\\\\char"2322}}}\\n{\\\\smash{\\\\lower4mu{\\\\char"2323}}}\\\\mkern5mu}\'),Qe("\\\\incoh",\'{\\\\mkern5mu\\\\rule{}{0.7em}\\\\mathrlap{\\\\smash{\\\\raise2mu{\\\\char"2323}}}\\n{\\\\smash{\\\\lower4mu{\\\\char"2322}}}\\\\mkern5mu}\'),Qe("\\\\standardstate","\\\\text{\\\\tiny\\\\char`\u29B5}");const mt=t=>{if(!t.parser.settings.displayMode)throw new e(`{${t.envName}} can be used only in display mode.`)},pt=/([-+]?) *(\\d+(?:\\.\\d*)?|\\.\\d+) *([a-z]{2})/,ut=e=>{let t=e.get("\\\\arraystretch");"string"!=typeof t&&(t=tt(t.tokens)),t=isNaN(t)?null:Number(t);let r=e.get("\\\\arraycolsep");"string"!=typeof r&&(r=tt(r.tokens));const n=pt.exec(r);return[t,n?{number:+(n[1]+n[2]),unit:n[3]}:null]},dt=t=>{let r="";for(let n=0;n1||!a)&&h.pop(),f.push(dt(r.body)),b.length{const t=new C("mtd",[]);return t.style={padding:"0",width:"50%"},e.envClasses.includes("multline")&&(t.style.width="7.5%"),t},wt=function(e,t){const r=[],n=e.body.length,s=e.hLinesBeforeRow,o=e.tags&&e.tags.some(e=>e);for(let a=0;a0&&(2===s[0].length?p.children.forEach(e=>{e.style.borderTop="0.15em double"}):p.children.forEach(e=>{e.style.borderTop=s[0][0]?"0.06em dashed":"0.06em solid"})),s[a+1].length>0&&(2===s[a+1].length?p.children.forEach(e=>{e.style.borderBottom="0.15em double"}):p.children.forEach(e=>{e.style.borderBottom=s[a+1][0]?"0.06em dashed":"0.06em solid"}));let d=!0;for(let e=0;e0&&(a=e.envClasses.includes("abut")||e.envClasses.includes("cases")?"0":e.envClasses.includes("small")?"0.1389":e.envClasses.includes("cd")?"0.25":"0.4",l="em"),e.arraycolsep){const r=Be(e.arraycolsep,t);a=r.number.toFixed(4),l=r.unit}if(a){const t=0===r.length?0:r[0].children.length,n=(r,n)=>0===r&&0===n||r===t-1&&1===n?"0":"align"!==e.envClasses[0]?a:1===n?"0":o?r%2?"1":"0":r%2?"0":"1";for(let e=0;e0){const t=e.envClasses.includes("align")||e.envClasses.includes("alignat");for(let n=0;n1&&e.envClasses.includes("cases")&&(s.children[1].style.paddingLeft="1em"),e.envClasses.includes("cases")||e.envClasses.includes("subarray"))for(const e of s.children)e.classes.push("tml-left")}}let i=new C("mtable",r);if(e.envClasses.length>0&&(e.envClasses.includes("jot")?i.classes.push("tml-jot"):e.envClasses.includes("small")&&i.classes.push("tml-small")),"display"===e.scriptLevel&&i.setAttribute("displaystyle","true"),(e.autoTag||e.envClasses.includes("multline"))&&(i.style.width="100%"),e.cols&&e.cols.length>0){const t=e.cols;let r=!1,n=0,s=t.length;for(;"separator"===t[n].type;)n+=1;for(;"separator"===t[s-1].type;)s-=1;if("separator"===t[0].type){const e="separator"===t[1].type?"0.15em double":"|"===t[0].separator?"0.06em solid ":"0.06em dashed ";for(const t of i.children)t.children[0].style.borderLeft=e}let a=o?0:-1;for(let e=n;e-1;if(r[0]&&i){let t="";for(let e=0;e0?new Array(o.body[0].length).fill({type:"align",align:n}):[];const[a,l]=ut(t.parser.gullet.macros);return o.arraystretch=a,!l||6===l&&"pt"===l||(o.arraycolsep=l),r?{type:"leftright",mode:t.mode,body:[o],left:r[0],right:r[1],rightColor:void 0}:o},mathmlBuilder:wt}),Le({type:"array",names:["bordermatrix"],props:{numArgs:0},handler(e){const t=gt(e.parser,{cols:[],envClasses:["bordermatrix"]},"text");return t.cols=t.body.length>0?new Array(t.body[0].length).fill({type:"align",align:"c"}):[],t.envClasses=[],t.arraystretch=1,"matrix"===e.envName?t:((e,t)=>{const r=e.body;r[0].shift();const n=new Array(r.length-1).fill().map(()=>[]);for(let e=1;e[]);for(let e=0;e[]),envClasses:[],scriptLevel:"text",arraystretch:1,labels:new Array(n.length).fill(""),arraycolsep:{number:.04,unit:"em"}},a={type:"styling",mode:"math",scriptLevel:"text",body:[{type:"array",mode:"math",body:s,cols:new Array(s.length).fill({type:"align",align:"c"}),rowGaps:new Array(s.length-1).fill(null),hLinesBeforeRow:new Array(s.length+1).fill().map(()=>[]),envClasses:[],scriptLevel:"text",arraystretch:1,labels:new Array(s.length).fill(""),arraycolsep:null}]},l={type:"leftright",mode:"math",body:[e],left:t?t[0]:"(",right:t?t[1]:")",rightColor:void 0};return He([o,{type:"supsub",mode:"math",stack:!0,base:{type:"op",mode:"math",limits:!0,alwaysHandleSupSub:!0,parentIsSupSub:!0,symbol:!1,suppressBaseShift:!0,body:[l]},sup:a,sub:null}])})(t,e.delimiters)},mathmlBuilder:wt}),Le({type:"array",names:["smallmatrix"],props:{numArgs:0},handler:e=>gt(e.parser,{envClasses:["small"]},"script"),mathmlBuilder:wt}),Le({type:"array",names:["subarray"],props:{numArgs:1},handler(t,r){const n=(Me(r[0])?[r[0]]:Fe(r[0],"ordgroup").body).map(function(t){const r=je(t).text;if(-1!=="lc".indexOf(r))return{type:"align",align:r};throw new e("Unknown column alignment: "+r,t)});if(n.length>1)throw new e("{subarray} can contain only one column");let s={cols:n,envClasses:["small"]};if(s=gt(t.parser,s,"script"),s.body.length>0&&s.body[0].length>1)throw new e("{subarray} can contain only one column");return s},mathmlBuilder:wt}),Le({type:"array",names:["cases","dcases","rcases","drcases"],props:{numArgs:0},handler(e){const t=gt(e.parser,{cols:[],envClasses:["cases"]},ft(e.envName));return{type:"leftright",mode:e.mode,body:[t],left:e.envName.indexOf("r")>-1?".":"\\\\{",right:e.envName.indexOf("r")>-1?"\\\\}":".",rightColor:void 0}},mathmlBuilder:wt}),Le({type:"array",names:["align","align*","aligned","split"],props:{numArgs:0},handler:xt,mathmlBuilder:wt}),Le({type:"array",names:["alignat","alignat*","alignedat"],props:{numArgs:1},handler:xt,mathmlBuilder:wt}),Le({type:"array",names:["gathered","gather","gather*"],props:{numArgs:0},handler(e){"gathered"!==e.envName&&mt(e);const t={cols:[],envClasses:["abut","jot"],autoTag:ht(e.envName),emptySingleRow:!0,leqno:e.parser.settings.leqno};return gt(e.parser,t,"display")},mathmlBuilder:wt}),Le({type:"array",names:["equation","equation*"],props:{numArgs:0},handler(e){mt(e);const t={autoTag:ht(e.envName),emptySingleRow:!0,singleRow:!0,maxNumCols:1,envClasses:["align"],leqno:e.parser.settings.leqno};return gt(e.parser,t,"display")},mathmlBuilder:wt}),Le({type:"array",names:["multline","multline*"],props:{numArgs:0},handler(e){mt(e);const t={autoTag:"multline"===e.envName,maxNumCols:1,envClasses:["jot","multline"],leqno:e.parser.settings.leqno};return gt(e.parser,t,"display")},mathmlBuilder:wt}),Le({type:"array",names:["CD"],props:{numArgs:0},handler:t=>(mt(t),function(t){const r=[];for(t.gullet.beginGroup(),t.gullet.macros.set("\\\\cr","\\\\\\\\\\\\relax"),t.gullet.beginGroup();;){r.push(t.parseExpression(!1,"\\\\\\\\")),t.gullet.endGroup(),t.gullet.beginGroup();const n=t.fetch().text;if("&"!==n&&"\\\\\\\\"!==n){if("\\\\end"===n){0===r[r.length-1].length&&r.pop();break}throw new e("Expected \\\\\\\\ or \\\\cr or \\\\end",t.nextToken)}t.consume()}let n=[];const s=[n];for(let o=0;o-1);else{if(!("<>AV".indexOf(s)>-1))throw new e(\'Expected one of "<>AV=|." after @.\');for(let t=0;t<2;t++){let n=!0;for(let l=r+1;l{let s=["(",")"];if("\\\\bordermatrix"===t&&n[0]&&n[0].body){const e=n[0].body;1===e.length&&"delimiter"===e[0].type&&(s=[e[0].left,e[0].right])}e.consumeSpaces(),e.consume();const o=kt.bordermatrix,a={mode:e.mode,envName:t.slice(1),delimiters:s,parser:e},l=o.handler(a);return e.expect("}",!0),l}}),g({type:"cancelto",names:["\\\\cancelto"],props:{numArgs:2},handler({parser:e},t){const r=t[0],n=t[1];return{type:"cancelto",mode:e.mode,body:n,to:r,isCharacterBox:i(n)}},mathmlBuilder(e,t){const r=new C("mrow",[ge(e.body,t)],["ff-narrow"]),n=new C("mphantom",[ge(e.body,t)]),s=new C("mrow",[n],["tml-cancelto"]);s.style.color=t.color,e.isCharacterBox&&m.indexOf(e.body.body[0].text)>-1&&(s.style.left="0.1em",s.style.width="90%");const o=new C("mrow",[r,s],["menclose"]);if(!e.isCharacterBox||/[f\u222B\u2211]/.test(e.body.body[0].text))n.style.paddingRight="0.2em";else{n.style.padding="0.5ex 0.1em 0 0";const e=new C("mspace",[]);e.setAttribute("height","0.85em"),r.children.push(e)}let a;if(e.isCharacterBox)a=new C("mspace",[]),a.setAttribute("height","1em");else{const r=ge(e.body,t),n=new C("mpadded",[r]);n.setAttribute("width","0.1px"),a=new C("mphantom",[n])}const l=ge(e.to,t);l.style.color=t.color;const i=new C("mpadded",[l]);if(!e.isCharacterBox||/[f\u222B\u2211]/.test(e.body.body[0].text)){const e=new C("mspace",[]);e.setAttribute("width","0.2em"),i.children.unshift(e)}i.setAttribute("width","0.1px");const c=new C("mover",[a,i]),p=new C("mrow",[],["ff-nudge-left"]);return B([ce([o,c]),p])}}),g({type:"textord",names:["\\\\@char"],props:{numArgs:1,allowedInText:!0},handler({parser:t,token:r},n){const s=Fe(n[0],"ordgroup").body;let o="";for(let e=0;e{let t=e.toString(16);return 1===t.length&&(t="0"+t),t},Bt=JSON.parse(\'{\\n "Apricot": "#ffb484",\\n "Aquamarine": "#08b4bc",\\n "Bittersweet": "#c84c14",\\n "blue": "#0000FF",\\n "Blue": "#303494",\\n "BlueGreen": "#08b4bc",\\n "BlueViolet": "#503c94",\\n "BrickRed": "#b8341c",\\n "brown": "#BF8040",\\n "Brown": "#802404",\\n "BurntOrange": "#f8941c",\\n "CadetBlue": "#78749c",\\n "CarnationPink": "#f884b4",\\n "Cerulean": "#08a4e4",\\n "CornflowerBlue": "#40ace4",\\n "cyan": "#00FFFF",\\n "Cyan": "#08acec",\\n "Dandelion": "#ffbc44",\\n "darkgray": "#404040",\\n "DarkOrchid": "#a8548c",\\n "Emerald": "#08ac9c",\\n "ForestGreen": "#089c54",\\n "Fuchsia": "#90348c",\\n "Goldenrod": "#ffdc44",\\n "gray": "#808080",\\n "Gray": "#98949c",\\n "green": "#00FF00",\\n "Green": "#08a44c",\\n "GreenYellow": "#e0e474",\\n "JungleGreen": "#08ac9c",\\n "Lavender": "#f89cc4",\\n "lightgray": "#c0c0c0",\\n "lime": "#BFFF00",\\n "LimeGreen": "#90c43c",\\n "magenta": "#FF00FF",\\n "Magenta": "#f0048c",\\n "Mahogany": "#b0341c",\\n "Maroon": "#b03434",\\n "Melon": "#f89c7c",\\n "MidnightBlue": "#086494",\\n "Mulberry": "#b03c94",\\n "NavyBlue": "#086cbc",\\n "olive": "#7F7F00",\\n "OliveGreen": "#407c34",\\n "orange": "#FF8000",\\n "Orange": "#f8843c",\\n "OrangeRed": "#f0145c",\\n "Orchid": "#b074ac",\\n "Peach": "#f8945c",\\n "Periwinkle": "#8074bc",\\n "PineGreen": "#088c74",\\n "pink": "#ff7f7f",\\n "Plum": "#98248c",\\n "ProcessBlue": "#08b4ec",\\n "purple": "#BF0040",\\n "Purple": "#a0449c",\\n "RawSienna": "#983c04",\\n "red": "#ff0000",\\n "Red": "#f01c24",\\n "RedOrange": "#f86434",\\n "RedViolet": "#a0246c",\\n "Rhodamine": "#f0549c",\\n "Royallue": "#0874bc",\\n "RoyalPurple": "#683c9c",\\n "RubineRed": "#f0047c",\\n "Salmon": "#f8948c",\\n "SeaGreen": "#30bc9c",\\n "Sepia": "#701404",\\n "SkyBlue": "#48c4dc",\\n "SpringGreen": "#c8dc64",\\n "Tan": "#e09c74",\\n "teal": "#007F7F",\\n "TealBlue": "#08acb4",\\n "Thistle": "#d884b4",\\n "Turquoise": "#08b4cc",\\n "violet": "#800080",\\n "Violet": "#60449c",\\n "VioletRed": "#f054a4",\\n "WildStrawberry": "#f0246c",\\n "yellow": "#FFFF00",\\n "Yellow": "#fff404",\\n "YellowGreen": "#98cc6c",\\n "YellowOrange": "#ffa41c"\\n}\'),Ct=(t,r)=>{let n="";if("HTML"===t){if(!vt.test(r))throw new e("Invalid HTML input.");n=r}else if("RGB"===t){if(!Tt.test(r))throw new e("Invalid RGB input.");r.split(",").map(e=>{n+=qt(Number(e.trim()))})}else{if(!St.test(r))throw new e("Invalid rbg input.");r.split(",").map(t=>{const r=Number(t.trim());if(r>1)throw new e("Color rgb input must be < 1.");n+=qt(Number((255*r).toFixed(0)))})}return"#"!==n.charAt(0)&&(n="#"+n),n},Nt=(t,r,n)=>{const s=`\\\\\\\\color@${t}`;if(!At.exec(t))throw new e("Invalid color: \'"+t+"\'",n);return Ot.test(t)?"#"+t:("#"===t.charAt(0)||(r.has(s)?t=r.get(s).tokens[0].text:Bt[t]&&(t=Bt[t])),t)},zt=(e,t)=>{let r=de(e.body,t.withColor(e.color));return 0===r.length&&r.push(new C("mrow")),r=r.map(t=>(t.style.color=e.color,t)),B(r)};g({type:"color",names:["\\\\textcolor"],props:{numArgs:2,numOptionalArgs:1,allowedInText:!0,argTypes:["raw","raw","original"]},handler({parser:e,token:t},r,n){const s=n[0]&&Fe(n[0],"raw").string;let o="";if(s){const e=Fe(r[0],"raw").string;o=Ct(s,e)}else o=Nt(Fe(r[0],"raw").string,e.gullet.macros,t);const a=r[1];return{type:"color",mode:e.mode,color:o,isTextColor:!0,body:y(a)}},mathmlBuilder:zt}),g({type:"color",names:["\\\\color"],props:{numArgs:1,numOptionalArgs:1,allowedInText:!0,argTypes:["raw","raw"]},handler({parser:e,breakOnTokenText:t,token:r},n,s){const o=s[0]&&Fe(s[0],"raw").string;let a="";if(o){const e=Fe(n[0],"raw").string;a=Ct(o,e)}else a=Nt(Fe(n[0],"raw").string,e.gullet.macros,r);const l=e.parseExpression(!0,t,!0);return{type:"color",mode:e.mode,color:a,isTextColor:!1,body:l}},mathmlBuilder:zt}),g({type:"color",names:["\\\\definecolor"],props:{numArgs:3,allowedInText:!0,argTypes:["raw","raw","raw"]},handler({parser:t,funcName:r,token:n},s){const o=Fe(s[0],"raw").string;if(!/^[A-Za-z]+$/.test(o))throw new e("Color name must be latin letters.",n);const a=Fe(s[1],"raw").string;if(!["HTML","RGB","rgb"].includes(a))throw new e("Color model must be HTML, RGB, or rgb.",n);const l=Fe(s[2],"raw").string,i=Ct(a,l);return t.gullet.macros.set(`\\\\\\\\color@${o}`,{tokens:[{text:i}],numArgs:0}),{type:"internal",mode:t.mode}}}),g({type:"cr",names:["\\\\\\\\"],props:{numArgs:0,numOptionalArgs:0,allowedInText:!0},handler({parser:e},t,r){const n="["===e.gullet.future().text?e.parseSizeGroup(!0):null,s=!e.settings.displayMode;return{type:"cr",mode:e.mode,newLine:s,size:n&&Fe(n,"size").value}},mathmlBuilder(e,t){const r=new C("mo");if(e.newLine&&(r.setAttribute("linebreak","newline"),e.size)){const n=Be(e.size,t);r.setAttribute("height",n.number+n.unit)}return r}});const $t={"\\\\global":"\\\\global","\\\\long":"\\\\\\\\globallong","\\\\\\\\globallong":"\\\\\\\\globallong","\\\\def":"\\\\gdef","\\\\gdef":"\\\\gdef","\\\\edef":"\\\\xdef","\\\\xdef":"\\\\xdef","\\\\let":"\\\\\\\\globallet","\\\\futurelet":"\\\\\\\\globalfuture"},Et=t=>{const r=t.text;if(/^(?:[\\\\{}$&#^_]|EOF)$/.test(r))throw new e("Expected a control sequence",t);return r},It=(e,t,r,n)=>{let s=e.gullet.macros.get(r.text);null==s&&(r.noexpand=!0,s={tokens:[r],numArgs:0,unexpandable:!e.gullet.isExpandable(r.text)}),e.gullet.macros.set(t,s,n)};g({type:"internal",names:["\\\\global","\\\\long","\\\\\\\\globallong"],props:{numArgs:0,allowedInText:!0},handler({parser:t,funcName:r}){t.consumeSpaces();const n=t.fetch();if($t[n.text])return"\\\\global"!==r&&"\\\\\\\\globallong"!==r||(n.text=$t[n.text]),Fe(t.parseFunction(),"internal");throw new e("Invalid token after macro prefix",n)}}),g({type:"internal",names:["\\\\def","\\\\gdef","\\\\edef","\\\\xdef"],props:{numArgs:0,allowedInText:!0,primitive:!0},handler({parser:t,funcName:r}){let n=t.gullet.popToken();const s=n.text;if(/^(?:[\\\\{}$&#^_]|EOF)$/.test(s))throw new e("Expected a control sequence",n);let o,a=0;const l=[[]];for(;"{"!==t.gullet.future().text;)if(n=t.gullet.popToken(),"#"===n.text){if("{"===t.gullet.future().text){o=t.gullet.future(),l[a].push("{");break}if(n=t.gullet.popToken(),!/^[1-9]$/.test(n.text))throw new e(`Invalid argument number "${n.text}"`);if(parseInt(n.text)!==a+1)throw new e(`Argument number "${n.text}" out of order`);a++,l.push([])}else{if("EOF"===n.text)throw new e("Expected a macro definition");l[a].push(n.text)}let{tokens:i}=t.gullet.consumeArg();if(o&&i.unshift(o),"\\\\edef"===r||"\\\\xdef"===r){if(i=t.gullet.expandTokens(i),i.length>t.gullet.settings.maxExpand)throw new e("Too many expansions in an "+r);i.reverse()}return t.gullet.macros.set(s,{tokens:i,numArgs:a,delimiters:l},r===$t[r]),{type:"internal",mode:t.mode}}}),g({type:"internal",names:["\\\\let","\\\\\\\\globallet"],props:{numArgs:0,allowedInText:!0,primitive:!0},handler({parser:e,funcName:t}){const r=Et(e.gullet.popToken());e.gullet.consumeSpaces();const n=(e=>{let t=e.gullet.popToken();return"="===t.text&&(t=e.gullet.popToken()," "===t.text&&(t=e.gullet.popToken())),t})(e);return It(e,r,n,"\\\\\\\\globallet"===t),{type:"internal",mode:e.mode}}}),g({type:"internal",names:["\\\\futurelet","\\\\\\\\globalfuture"],props:{numArgs:0,allowedInText:!0,primitive:!0},handler({parser:e,funcName:t}){const r=Et(e.gullet.popToken()),n=e.gullet.popToken(),s=e.gullet.popToken();return It(e,r,s,"\\\\\\\\globalfuture"===t),e.gullet.pushToken(s),e.gullet.pushToken(n),{type:"internal",mode:e.mode}}}),g({type:"internal",names:["\\\\newcommand","\\\\renewcommand","\\\\providecommand"],props:{numArgs:0,allowedInText:!0,primitive:!0},handler({parser:t,funcName:r}){let n="";const s=t.gullet.popToken();"{"===s.text?(n=Et(t.gullet.popToken()),t.gullet.popToken()):n=Et(s);const o=t.gullet.isDefined(n);if(o&&"\\\\newcommand"===r)throw new e(`\\\\newcommand{${n}} attempting to redefine ${n}; use \\\\renewcommand`);if(!o&&"\\\\renewcommand"===r)throw new e(`\\\\renewcommand{${n}} when command ${n} does not yet exist; use \\\\newcommand`);let a=0;if("["===t.gullet.future().text){let r=t.gullet.popToken();if(r=t.gullet.popToken(),!/^[0-9]$/.test(r.text))throw new e(`Invalid number of arguments: "${r.text}"`);if(a=parseInt(r.text),r=t.gullet.popToken(),"]"!==r.text)throw new e(`Invalid argument "${r.text}"`)}const{tokens:l}=t.gullet.consumeArg();return"\\\\providecommand"===r&&t.gullet.macros.has(n)||t.gullet.macros.set(n,{tokens:l,numArgs:a}),{type:"internal",mode:t.mode}}});const Lt={"\\\\bigl":{mclass:"mopen",size:1},"\\\\Bigl":{mclass:"mopen",size:2},"\\\\biggl":{mclass:"mopen",size:3},"\\\\Biggl":{mclass:"mopen",size:4},"\\\\bigr":{mclass:"mclose",size:1},"\\\\Bigr":{mclass:"mclose",size:2},"\\\\biggr":{mclass:"mclose",size:3},"\\\\Biggr":{mclass:"mclose",size:4},"\\\\bigm":{mclass:"mrel",size:1},"\\\\Bigm":{mclass:"mrel",size:2},"\\\\biggm":{mclass:"mrel",size:3},"\\\\Biggm":{mclass:"mrel",size:4},"\\\\big":{mclass:"mord",size:1},"\\\\Big":{mclass:"mord",size:2},"\\\\bigg":{mclass:"mord",size:3},"\\\\Bigg":{mclass:"mord",size:4}},Ft={"(":")","\\\\lparen":"\\\\rparen","[":"]","\\\\lbrack":"\\\\rbrack","\\\\{":"\\\\}","\\\\lbrace":"\\\\rbrace","\u2987":"\u2988","\\\\llparenthesis":"\\\\rrparenthesis","\\\\lfloor":"\\\\rfloor","\u230A":"\u230B","\\\\lceil":"\\\\rceil","\u2308":"\u2309","\\\\langle":"\\\\rangle","\u27E8":"\u27E9","\\\\lAngle":"\\\\rAngle","\u27EA":"\u27EB","\\\\llangle":"\\\\rrangle","\u2989":"\u298A","\\\\lvert":"\\\\rvert","\\\\lVert":"\\\\rVert","\\\\lgroup":"\\\\rgroup","\u27EE":"\u27EF","\\\\lmoustache":"\\\\rmoustache","\u23B0":"\u23B1","\\\\llbracket":"\\\\rrbracket","\u27E6":"\u27E7","\\\\lBrace":"\\\\rBrace","\u2983":"\u2984"},jt=new Set(Object.keys(Ft));new Set(Object.values(Ft));const Mt=new Set(["(","\\\\lparen",")","\\\\rparen","[","\\\\lbrack","]","\\\\rbrack","\\\\{","\\\\lbrace","\\\\}","\\\\rbrace","\u2987","\\\\llparenthesis","\u2988","\\\\rrparenthesis","\\\\lfloor","\\\\rfloor","\u230A","\u230B","\\\\lceil","\\\\rceil","\u2308","\u2309","<",">","\\\\langle","\u27E8","\\\\rangle","\u27E9","\\\\lAngle","\u27EA","\\\\rAngle","\u27EB","\\\\llangle","\u2989","\\\\rrangle","\u298A","\\\\lt","\\\\gt","\\\\lvert","\\\\rvert","\\\\lVert","\\\\rVert","\\\\lgroup","\\\\rgroup","\u27EE","\u27EF","\\\\lmoustache","\\\\rmoustache","\u23B0","\u23B1","\\\\llbracket","\\\\rrbracket","\u27E6","\u27E7","\\\\lBrace","\\\\rBrace","\u2983","\u2984","/","\\\\backslash","|","\\\\vert","\\\\|","\\\\Vert","\u2016","\\\\uparrow","\\\\Uparrow","\\\\downarrow","\\\\Downarrow","\\\\updownarrow","\\\\Updownarrow","."]),Dt=new Set(["}","\\\\left","\\\\middle","\\\\right"]),Gt=e=>e.length>0&&(Mt.has(e)||Lt[e]||Dt.has(e)),Pt=[0,1.2,1.8,2.4,3];function Rt(t,r){"ordgroup"===t.type&&1===t.body.length&&(t=t.body[0]);const n=Me(t);if(n&&Mt.has(n.text))return"<"!==n.text&&"\\\\lt"!==n.text||(n.text="\u27E8"),">"!==n.text&&"\\\\gt"!==n.text||(n.text="\u27E9"),n;throw new e(n?`Invalid delimiter \'${n.text}\' after \'${r.funcName}\'`:`Invalid delimiter type \'${t.type}\'`,t)}const Ut=new Set(["/","\\\\","\\\\backslash","\u2216","\\\\vert","|"]),Ht=(e,t,r,n)=>{const s=new C("mo",[ae("."===e?"":e,t)]);return s.setAttribute("fence","true"),s.setAttribute("form",r),s.setAttribute("stretchy",n?"true":"false"),s};function _t(e){if(!e.body)throw new Error("Bug: The delim ParseNode wasn\'t fully parsed.")}g({type:"delimsizing",names:["\\\\bigl","\\\\Bigl","\\\\biggl","\\\\Biggl","\\\\bigr","\\\\Bigr","\\\\biggr","\\\\Biggr","\\\\bigm","\\\\Bigm","\\\\biggm","\\\\Biggm","\\\\big","\\\\Big","\\\\bigg","\\\\Bigg"],props:{numArgs:1,argTypes:["primitive"]},handler:(e,t)=>{const r=Rt(t[0],e),n={type:"delimsizing",mode:e.parser.mode,size:Lt[e.funcName].size,mclass:Lt[e.funcName].mclass,delim:r.text},s=e.parser.fetch().text;return"^"!==s&&"_"!==s?n:{type:"ordgroup",mode:"math",body:[n,{type:"ordgroup",mode:"math",body:[]}]}},mathmlBuilder:e=>{const t=[],r="."===e.delim?"":e.delim;t.push(ae(r,e.mode));const n=new C("mo",t);return"mopen"===e.mclass||"mclose"===e.mclass?n.setAttribute("fence","true"):n.setAttribute("fence","false"),(Ut.has(r)||r.indexOf("arrow")>-1)&&n.setAttribute("stretchy","true"),n.setAttribute("symmetric","true"),n.setAttribute("minsize",Pt[e.size]+"em"),n.setAttribute("maxsize",Pt[e.size]+"em"),n}}),g({type:"leftright-right",names:["\\\\right"],props:{numArgs:1,argTypes:["primitive"]},handler:(e,t)=>({type:"leftright-right",mode:e.parser.mode,delim:Rt(t[0],e).text})}),g({type:"leftright",names:["\\\\left"],props:{numArgs:1,argTypes:["primitive"]},handler:(t,r)=>{const n=Rt(r[0],t),s=t.parser;++s.leftrightDepth;let o=s.parseExpression(!1,"\\\\right",!0),a=s.fetch();for(;"\\\\middle"===a.text;){s.consume();const t=s.fetch().text;if(!D.math[t])throw new e(`Invalid delimiter \'${t}\' after \'\\\\middle\'`);Rt({type:"atom",mode:"math",text:t},{funcName:"\\\\middle"}),o.push({type:"middle",mode:"math",delim:t}),s.consume(),o=o.concat(s.parseExpression(!1,"\\\\right",!0)),a=s.fetch()}--s.leftrightDepth,s.expect("\\\\right",!1);const l=Fe(s.parseFunction(),"leftright-right");return{type:"leftright",mode:s.mode,body:o,left:n.text,right:l.delim,isStretchy:!0}},mathmlBuilder:(e,t)=>{_t(e);const r=de(e.body,t),n=Ht(e.left,e.mode,"prefix",!0);r.unshift(n);const s=Ht(e.right,e.mode,"postfix",!0);if(e.body.length>0){const t=e.body[e.body.length-1];"color"!==t.type||t.isTextColor||s.setAttribute("mathcolor",t.color)}return r.push(s),ce(r)}}),g({type:"delimiter",names:Array.from(jt),props:{numArgs:0,allowedInText:!0,allowedInMath:!0,allowedInArgument:!0},handler:({parser:t,funcName:r,token:n})=>{if("text"===t.mode)return{type:"textord",mode:"text",text:r,loc:n.loc};if(!t.settings.wrapDelimiterPairs)return{type:"atom",mode:"math",family:"open",loc:n.loc,text:r};const s=Ft[r],o=t.parseExpression(!1,s,!1);if(t.fetch().text!==s)throw new e("Unmatched delimiter");return t.consume(),{type:"delimiter",mode:t.mode,body:o,left:r,right:s}},mathmlBuilder:(e,t)=>{_t(e);const r=de(e.body,t),n=Ht(e.left,e.mode,"prefix",!1);r.unshift(n);const s=Ht(e.right,e.mode,"postfix",!1);if(e.body.length>0){const t=e.body[e.body.length-1];"color"!==t.type||t.isTextColor||s.setAttribute("mathcolor",t.color)}return r.push(s),ce(r)}}),g({type:"middle",names:["\\\\middle"],props:{numArgs:1,argTypes:["primitive"]},handler:(t,r)=>{const n=Rt(r[0],t);if(!t.parser.leftrightDepth)throw new e("\\\\middle without preceding \\\\left",n);return{type:"middle",mode:t.parser.mode,delim:n.text}},mathmlBuilder:e=>{const t=ae(e.delim,e.mode),r=new C("mo",[t]);return r.setAttribute("stretchy","true"),r.setAttribute("form","infix"),"/"!==t.text&&(r.setAttribute("lspace","0.05em"),r.setAttribute("rspace","0.05em")),r}});const Vt=["\\\\boxed","\\\\fcolorbox","\\\\colorbox"],Wt=(e,t)=>{const r=Vt.includes(e.label)?"mrow":"menclose",n=new C(r,[ge(e.body,t)]);switch(e.label){case"\\\\overline":n.setAttribute("notation","top"),n.classes.push("tml-overline");break;case"\\\\underline":n.setAttribute("notation","bottom"),n.classes.push("tml-underline");break;case"\\\\cancel":n.setAttribute("notation","updiagonalstrike"),n.children.push(new C("mrow",[],["tml-cancel","upstrike"]));break;case"\\\\bcancel":n.setAttribute("notation","downdiagonalstrike"),n.children.push(new C("mrow",[],["tml-cancel","downstrike"]));break;case"\\\\sout":n.setAttribute("notation","horizontalstrike"),n.children.push(new C("mrow",[],["tml-cancel","sout"]));break;case"\\\\xcancel":n.setAttribute("notation","updiagonalstrike downdiagonalstrike"),n.children.push(new C("mrow",[],["tml-cancel","tml-xcancel"]));break;case"\\\\longdiv":n.setAttribute("notation","longdiv"),n.classes.push("longdiv-top"),n.children.push(new C("mrow",[],["longdiv-arc"]));break;case"\\\\phase":n.setAttribute("notation","phasorangle"),n.classes.push("phasor-bottom"),n.children.push(new C("mrow",[],["phasor-angle"]));break;case"\\\\textcircled":n.setAttribute("notation","circle"),n.classes.push("circle-pad"),n.children.push(new C("mrow",[],["textcircle"]));break;case"\\\\angl":n.setAttribute("notation","actuarial"),n.classes.push("actuarial");break;case"\\\\boxed":n.style.padding="3pt",n.style.border="1px solid",n.setAttribute("scriptlevel","0"),n.setAttribute("displaystyle","true");break;case"\\\\fbox":n.setAttribute("notation","box"),n.classes.push("tml-fbox");break;case"\\\\fcolorbox":case"\\\\colorbox":n.style.padding="0.3em","\\\\fcolorbox"===e.label&&(n.style.border="0.0667em solid "+String(e.borderColor))}return e.backgroundColor&&n.setAttribute("mathbackground",e.backgroundColor),n};g({type:"enclose",names:["\\\\colorbox"],props:{numArgs:2,numOptionalArgs:1,allowedInText:!0,argTypes:["raw","raw","text"]},handler({parser:e,funcName:t},r,n){const s=n[0]&&Fe(n[0],"raw").string;let o="";if(s){const e=Fe(r[0],"raw").string;o=Ct(s,e)}else o=Nt(Fe(r[0],"raw").string,e.gullet.macros);const a=r[1];return{type:"enclose",mode:e.mode,label:t,backgroundColor:o,body:a}},mathmlBuilder:Wt}),g({type:"enclose",names:["\\\\fcolorbox"],props:{numArgs:3,numOptionalArgs:1,allowedInText:!0,argTypes:["raw","raw","raw","text"]},handler({parser:e,funcName:t},r,n){const s=n[0]&&Fe(n[0],"raw").string;let o,a="";if(s){const e=Fe(r[0],"raw").string,t=Fe(r[0],"raw").string;a=Ct(s,e),o=Ct(s,t)}else a=Nt(Fe(r[0],"raw").string,e.gullet.macros),o=Nt(Fe(r[1],"raw").string,e.gullet.macros);const l=r[2];return{type:"enclose",mode:e.mode,label:t,backgroundColor:o,borderColor:a,body:l}},mathmlBuilder:Wt}),g({type:"enclose",names:["\\\\fbox"],props:{numArgs:1,argTypes:["hbox"],allowedInText:!0},handler:({parser:e},t)=>({type:"enclose",mode:e.mode,label:"\\\\fbox",body:t[0]})}),g({type:"enclose",names:["\\\\angl","\\\\cancel","\\\\bcancel","\\\\xcancel","\\\\overline","\\\\boxed","\\\\longdiv","\\\\phase"],props:{numArgs:1},handler({parser:e,funcName:t},r){const n=r[0];return{type:"enclose",mode:e.mode,label:t,body:n}},mathmlBuilder:Wt}),g({type:"enclose",names:["\\\\sout"],props:{numArgs:1,allowedInText:!0},handler({parser:e,funcName:t},r){const n=r[0];return{type:"enclose",mode:e.mode,label:t,body:n}},mathmlBuilder:Wt}),g({type:"enclose",names:["\\\\underline"],props:{numArgs:1,allowedInText:!0},handler({parser:e,funcName:t},r){const n=r[0];return{type:"enclose",mode:e.mode,label:t,body:n}},mathmlBuilder:Wt}),g({type:"enclose",names:["\\\\textcircled"],props:{numArgs:1,argTypes:["text"],allowedInArgument:!0,allowedInText:!0},handler({parser:e,funcName:t},r){const n=r[0];return{type:"enclose",mode:e.mode,label:t,body:n}},mathmlBuilder:Wt}),g({type:"environment",names:["\\\\begin","\\\\end"],props:{numArgs:1,argTypes:["text"]},handler({parser:t,funcName:r},n){const s=n[0];if("ordgroup"!==s.type)throw new e("Invalid environment name",s);let o="";for(let e=0;e({type:"envTag",mode:e.mode,body:t[0]}),mathmlBuilder:(e,t)=>new C("mrow")}),g({type:"noTag",names:["\\\\env@notag"],props:{numArgs:0},handler:({parser:e})=>({type:"noTag",mode:e.mode}),mathmlBuilder:(e,t)=>new C("mrow")});const Xt=Object.freeze({B:8426,E:8427,F:8427,H:8387,I:8391,L:8390,M:8422,R:8393,e:8394,g:8355,o:8389}),Zt=Object.freeze({C:8426,H:8388,I:8392,R:8394,Z:8398}),Yt=Object.freeze({C:8383,H:8389,N:8391,P:8393,Q:8393,R:8395,Z:8394}),Jt=Object.freeze({"\u03F5":119527,"\u03D1":119564,"\u03F0":119534,"\u03C6":119577,"\u03F1":119535,"\u03D6":119563}),Kt=Object.freeze({"\u03F5":119643,"\u03D1":119680,"\u03F0":119650,"\u03C6":119693,"\u03F1":119651,"\u03D6":119679}),Qt=Object.freeze({"\u03F5":119701,"\u03D1":119738,"\u03F0":119708,"\u03C6":119751,"\u03F1":119709,"\u03D6":119737}),er=Object.freeze({"\u03F5":119759,"\u03D1":119796,"\u03F0":119766,"\u03C6":119809,"\u03F1":119767,"\u03D6":119795}),tr=Object.freeze({upperCaseLatin:{normal:e=>0,bold:e=>119743,italic:e=>119795,"bold-italic":e=>119847,script:e=>Xt[e]||119899,"script-bold":e=>119951,fraktur:e=>Zt[e]||120003,"fraktur-bold":e=>120107,"double-struck":e=>Yt[e]||120055,"sans-serif":e=>120159,"sans-serif-bold":e=>120211,"sans-serif-italic":e=>120263,"sans-serif-bold-italic":e=>120380,monospace:e=>120367},lowerCaseLatin:{normal:e=>0,bold:e=>119737,italic:e=>"h"===e?8358:119789,"bold-italic":e=>119841,script:e=>Xt[e]||119893,"script-bold":e=>119945,fraktur:e=>119997,"fraktur-bold":e=>120101,"double-struck":e=>120049,"sans-serif":e=>120153,"sans-serif-bold":e=>120205,"sans-serif-italic":e=>120257,"sans-serif-bold-italic":e=>120309,monospace:e=>120361},upperCaseGreek:{normal:e=>0,bold:e=>119575,italic:e=>119633,"bold-italic":e=>119575,script:e=>0,"script-bold":e=>0,fraktur:e=>0,"fraktur-bold":e=>0,"double-struck":e=>0,"sans-serif":e=>119749,"sans-serif-bold":e=>119749,"sans-serif-italic":e=>0,"sans-serif-bold-italic":e=>119807,monospace:e=>0},lowerCaseGreek:{normal:e=>0,bold:e=>119569,italic:e=>119627,"bold-italic":e=>"\u03D5"===e?119678:119685,script:e=>0,"script-bold":e=>0,fraktur:e=>0,"fraktur-bold":e=>0,"double-struck":e=>0,"sans-serif":e=>119743,"sans-serif-bold":e=>119743,"sans-serif-italic":e=>0,"sans-serif-bold-italic":e=>119801,monospace:e=>0},varGreek:{normal:e=>0,bold:e=>Jt[e]||-51,italic:e=>0,"bold-italic":e=>Kt[e]||58,script:e=>0,"script-bold":e=>0,fraktur:e=>0,"fraktur-bold":e=>0,"double-struck":e=>0,"sans-serif":e=>Qt[e]||116,"sans-serif-bold":e=>Qt[e]||116,"sans-serif-italic":e=>0,"sans-serif-bold-italic":e=>er[e]||174,monospace:e=>0},numeral:{normal:e=>0,bold:e=>120734,italic:e=>0,"bold-italic":e=>0,script:e=>0,"script-bold":e=>0,fraktur:e=>0,"fraktur-bold":e=>0,"double-struck":e=>120744,"sans-serif":e=>120754,"sans-serif-bold":e=>120764,"sans-serif-italic":e=>0,"sans-serif-bold-italic":e=>0,monospace:e=>120774}}),rr=(e,t)=>{const r=e.codePointAt(0),n=64{const r=e.font,n=t.withFont(r),s=ge(e.body,n);if(0===s.children.length)return s;if("boldsymbol"===r&&["mo","mpadded","mrow"].includes(s.type))return s.style.fontWeight="bold",s;if(((e,t)=>{if(!sr.includes(t)||!e.body||"ordgroup"!==e.body.type||1===e.body.body.length)return!1;if("mathord"!==e.body.body[0].type)return!1;for(let t=1;trr(e,"italic")).join(""),e;const t=new C("mpadded",[e]);return t.setAttribute("lspace","0"),t}let o="mo"===s.children[0].type;for(let e=1;e{const n=b(r[0]);let s=t;return s in ar&&(s=ar[s]),{type:"font",mode:e.mode,font:s.slice(1),body:n}},mathmlBuilder:or}),g({type:"font",names:["\\\\rm","\\\\sf","\\\\tt","\\\\bf","\\\\it","\\\\cal"],props:{numArgs:0,allowedInText:!0},handler:({parser:e,funcName:t,breakOnTokenText:r},n)=>{const{mode:s}=e,o=e.parseExpression(!0,r,!0);return{type:"font",mode:s,font:`math${t.slice(1)}`,body:{type:"ordgroup",mode:e.mode,body:o}}},mathmlBuilder:or});const lr=["display","text","script","scriptscript"],ir={auto:-1,display:0,text:0,script:1,scriptscript:2},cr=(e,t)=>{t=((e,t)=>{let r=t;if("display"===e){const e=r.level>=Ye?Ze:Xe;r=r.withLevel(e)}else"text"===e&&r.level===Xe?r=r.withLevel(Ze):"auto"===e?r=r.incrementLevel():"script"===e?r=r.withLevel(Ye):"scriptscript"===e&&(r=r.withLevel(Je));return r})(e.scriptLevel,t);const r=ge(e.numer,t),n=ge(e.denom,t);3===t.level&&(r.style.mathDepth="2",r.setAttribute("scriptlevel","2"),n.style.mathDepth="2",n.setAttribute("scriptlevel","2"));let s=new C("mfrac",[r,n]);if(e.hasBarLine){if(e.barSize){const r=Be(e.barSize,t);s.setAttribute("linethickness",r.number+r.unit)}}else s.setAttribute("linethickness","0px");if(null!=e.leftDelim||null!=e.rightDelim){const t=[];if(null!=e.leftDelim){const r=new C("mo",[new N(e.leftDelim.replace("\\\\",""))]);r.setAttribute("fence","true"),t.push(r)}if(t.push(s),null!=e.rightDelim){const r=new C("mo",[new N(e.rightDelim.replace("\\\\",""))]);r.setAttribute("fence","true"),t.push(r)}s=ce(t)}return"auto"!==e.scriptLevel&&(s=new C("mstyle",[s]),s.setAttribute("displaystyle",String("display"===e.scriptLevel)),s.setAttribute("scriptlevel",ir[e.scriptLevel])),s};g({type:"genfrac",names:["\\\\cfrac","\\\\dfrac","\\\\frac","\\\\tfrac","\\\\dbinom","\\\\binom","\\\\tbinom","\\\\\\\\atopfrac","\\\\\\\\bracefrac","\\\\\\\\brackfrac"],props:{numArgs:2,allowedInArgument:!0},handler:({parser:e,funcName:t},r)=>{const n=r[0],s=r[1];let o=!1,a=null,l=null,i="auto";switch(t){case"\\\\cfrac":case"\\\\dfrac":case"\\\\frac":case"\\\\tfrac":o=!0;break;case"\\\\\\\\atopfrac":o=!1;break;case"\\\\dbinom":case"\\\\binom":case"\\\\tbinom":a="(",l=")";break;case"\\\\\\\\bracefrac":a="\\\\{",l="\\\\}";break;case"\\\\\\\\brackfrac":a="[",l="]";break;default:throw new Error("Unrecognized genfrac command")}return"\\\\cfrac"===t||t.startsWith("\\\\d")?i="display":t.startsWith("\\\\t")&&(i="text"),{type:"genfrac",mode:e.mode,continued:!1,numer:n,denom:s,hasBarLine:o,leftDelim:a,rightDelim:l,scriptLevel:i,barSize:null}},mathmlBuilder:cr}),g({type:"infix",names:["\\\\over","\\\\choose","\\\\atop","\\\\brace","\\\\brack"],props:{numArgs:0,infix:!0},handler({parser:e,funcName:t,token:r}){let n;switch(t){case"\\\\over":n="\\\\frac";break;case"\\\\choose":n="\\\\binom";break;case"\\\\atop":n="\\\\\\\\atopfrac";break;case"\\\\brace":n="\\\\\\\\bracefrac";break;case"\\\\brack":n="\\\\\\\\brackfrac";break;default:throw new Error("Unrecognized infix genfrac command")}return{type:"infix",mode:e.mode,replaceWith:n,token:r}}});const mr=function(e){let t=null;return e.length>0&&(t=e,t="."===t?null:t),t};g({type:"genfrac",names:["\\\\genfrac"],props:{numArgs:6,allowedInArgument:!0,argTypes:["math","math","size","text","math","math"]},handler({parser:e},t){const r=t[4],n=t[5],s=b(t[0]),o="atom"===s.type&&"open"===s.family?mr(s.text):null,a=b(t[1]),l="atom"===a.type&&"close"===a.family?mr(a.text):null,i=Fe(t[2],"size");let c,m=null;i.isBlank?c=!0:(m=i.value,c=m.number>0);let p="auto",u=t[3];if("ordgroup"===u.type){if(u.body.length>0){const e=Fe(u.body[0],"textord");p=lr[Number(e.text)]}}else u=Fe(u,"textord"),p=lr[Number(u.text)];return{type:"genfrac",mode:e.mode,numer:r,denom:n,continued:!1,hasBarLine:c,barSize:m,leftDelim:o,rightDelim:l,scriptLevel:p}},mathmlBuilder:cr}),g({type:"infix",names:["\\\\above"],props:{numArgs:1,argTypes:["size"],infix:!0},handler:({parser:e,funcName:t,token:r},n)=>({type:"infix",mode:e.mode,replaceWith:"\\\\\\\\abovefrac",barSize:Fe(n[0],"size").value,token:r})}),g({type:"genfrac",names:["\\\\\\\\abovefrac"],props:{numArgs:3,argTypes:["math","size","math"]},handler:({parser:e,funcName:t},r)=>{const n=r[0],s=function(e){if(!e)throw new Error("Expected non-null, but got "+String(e));return e}(Fe(r[1],"infix").barSize),o=r[2],a=s.number>0;return{type:"genfrac",mode:e.mode,numer:n,denom:o,continued:!1,hasBarLine:a,barSize:s,leftDelim:null,rightDelim:null,scriptLevel:"auto"}},mathmlBuilder:cr}),g({type:"hbox",names:["\\\\hbox"],props:{numArgs:1,argTypes:["hbox"],allowedInArgument:!0,allowedInText:!1},handler:({parser:e},t)=>({type:"hbox",mode:e.mode,body:y(t[0])}),mathmlBuilder(e,t){const r=t.withLevel(Ze),n=he(e.body,r);return ie(n)}});g({type:"horizBracket",names:["\\\\overbrace","\\\\underbrace","\\\\overbracket","\\\\underbracket"],props:{numArgs:1},handler:({parser:e,funcName:t},r)=>({type:"horizBracket",mode:e.mode,label:t,isOver:/^\\\\over/.test(t),base:r[0]}),mathmlBuilder:(e,t)=>{const r=I(e.label);return r.style["math-depth"]=0,new C(e.isOver?"mover":"munder",[ge(e.base,t),r])}}),g({type:"html",names:["\\\\class","\\\\id","\\\\style","\\\\data"],props:{numArgs:2,argTypes:["raw","original"],allowedInText:!0},handler:({parser:t,funcName:r,token:n},s)=>{const o=Fe(s[0],"raw").string,a=s[1];if(t.settings.strict)throw new e(`Function "${r}" is disabled in strict mode`,n);let l;const i={};switch(r){case"\\\\class":i.class=o,l={command:"\\\\class",class:o};break;case"\\\\id":i.id=o,l={command:"\\\\id",id:o};break;case"\\\\style":i.style=o,l={command:"\\\\style",style:o};break;case"\\\\data":{const t=o.split(",");for(let r=0;r{const r=he(e.body,t),n=[];e.attributes.class&&n.push(...e.attributes.class.trim().split(/\\s+/)),r.classes=n;for(const t in e.attributes)"class"!==t&&Object.prototype.hasOwnProperty.call(e.attributes,t)&&r.setAttribute(t,e.attributes[t]);return r}});const pr=function(t){if(/^[-+]? *(\\d+(\\.\\d*)?|\\.\\d+)$/.test(t))return{number:+t,unit:"bp"};{const r=/([-+]?) *(\\d+(?:\\.\\d*)?|\\.\\d+) *([a-z]{2})/.exec(t);if(!r)throw new e("Invalid size: \'"+t+"\' in \\\\includegraphics");const n={number:+(r[1]+r[2]),unit:r[3]};if(!Oe(n))throw new e("Invalid unit: \'"+n.unit+"\' in \\\\includegraphics.");return n}};g({type:"includegraphics",names:["\\\\includegraphics"],props:{numArgs:1,numOptionalArgs:1,argTypes:["raw","url"],allowedInText:!1},handler:({parser:t,token:r},n,s)=>{let o={number:0,unit:"em"},a={number:.9,unit:"em"},l={number:0,unit:"em"},i="";if(s[0]){const t=Fe(s[0],"raw").string.split(",");for(let r=0;r{const r=Be(e.height,t),n={number:0,unit:"em"};e.totalheight.number>0&&e.totalheight.unit===r.unit&&e.totalheight.number>r.number&&(n.number=e.totalheight.number-r.number,n.unit=r.unit);let s=0;e.width.number>0&&(s=Be(e.width,t));const o={height:r.number+n.number+"em"};s.number>0&&(o.width=s.number+s.unit),n.number>0&&(o.verticalAlign=-n.number+n.unit);const a=new q(e.src,e.alt,o);return a.height=r,a.depth=n,new C("mtext",[a])}}),g({type:"kern",names:["\\\\kern","\\\\mkern","\\\\hskip","\\\\mskip"],props:{numArgs:1,argTypes:["size"],primitive:!0,allowedInText:!0},handler({parser:t,funcName:r,token:n},s){const o=Fe(s[0],"size");if(t.settings.strict){const s="m"===r[1],a="mu"===o.value.unit;if(s){if(!a)throw new e(`LaTeX\'s ${r} supports only mu units, not ${o.value.unit} units`,n);if("math"!==t.mode)throw new e(`LaTeX\'s ${r} works only in math mode`,n)}else if(a)throw new e(`LaTeX\'s ${r} doesn\'t support mu units`,n)}return{type:"kern",mode:t.mode,dimension:o.value}},mathmlBuilder(e,t){const r=Be(e.dimension,t),n=r.number>0&&"em"===r.unit?ur(r.number):"";if("text"===e.mode&&n.length>0){const e=new N(n);return new C("mtext",[e])}if(r.number>=0){const e=new C("mspace");return e.setAttribute("width",r.number+r.unit),e}{const e=new C("mrow");return e.style.marginLeft=r.number+r.unit,e}}});const ur=function(e){return e>=.05555&&e<=.05556?"\u200A":e>=.1666&&e<=.1667?"\u2009":e>=.2222&&e<=.2223?"\u2005":e>=.2777&&e<=.2778?"\u2005\u200A":""},dr=/[^A-Za-z_0-9-]/g;g({type:"label",names:["\\\\label"],props:{numArgs:1,argTypes:["raw"]},handler:({parser:e},t)=>({type:"label",mode:e.mode,string:t[0].string.replace(dr,"")}),mathmlBuilder(e,t){const r=new C("mrow",[],["tml-label"]);return e.string.length>0&&r.setLabel(e.string),r}});const hr=["\\\\clap","\\\\llap","\\\\rlap"];g({type:"lap",names:["\\\\mathllap","\\\\mathrlap","\\\\mathclap","\\\\clap","\\\\llap","\\\\rlap"],props:{numArgs:1,allowedInText:!0},handler:({parser:t,funcName:r,token:n},s)=>{if(hr.includes(r)){if(t.settings.strict&&"text"!==t.mode)throw new e(`{${r}} can be used only in text mode.\\n Try \\\\math${r.slice(1)}`,n);r=r.slice(1)}else r=r.slice(5);const o=s[0];return{type:"lap",mode:t.mode,alignment:r,body:o}},mathmlBuilder:(e,t)=>{let r;if("llap"===e.alignment){const n=de(y(e.body),t),s=new C("mphantom",n);r=new C("mpadded",[s]),r.setAttribute("width","0.1px")}const n=ge(e.body,t);let s;if("llap"===e.alignment?(n.style.position="absolute",n.style.right="0",n.style.bottom="0",s=new C("mpadded",[r,n])):s=new C("mpadded",[n]),"rlap"===e.alignment)e.body.body.length>0&&"genfrac"===e.body.body[0].type&&s.setAttribute("lspace","0.16667em");else{const t="llap"===e.alignment?"-1":"-0.5";s.setAttribute("lspace",t+"width"),"llap"===e.alignment?s.style.position="relative":(s.style.display="flex",s.style.justifyContent="center")}return s.setAttribute("width","0.1px"),s}}),g({type:"ordgroup",names:["\\\\(","$"],props:{numArgs:0,allowedInText:!0,allowedInMath:!1},handler({funcName:e,parser:t},r){const n=t.mode;t.switchMode("math");const s="\\\\("===e?"\\\\)":"$",o=t.parseExpression(!1,s);return t.expect(s),t.switchMode(n),{type:"ordgroup",mode:t.mode,body:o}}}),g({type:"text",names:["\\\\)","\\\\]"],props:{numArgs:0,allowedInText:!0,allowedInMath:!1},handler(t,r){throw new e(`Mismatched ${t.funcName}`,r)}});g({type:"mathchoice",names:["\\\\mathchoice"],props:{numArgs:4,primitive:!0},handler:({parser:e},t)=>({type:"mathchoice",mode:e.mode,display:y(t[0]),text:y(t[1]),script:y(t[2]),scriptscript:y(t[3])}),mathmlBuilder:(e,t)=>{const r=((e,t)=>{switch(t.level){case Xe:return e.display;case Ze:return e.text;case Ye:return e.script;case Je:return e.scriptscript;default:return e.text}})(e,t);return he(r,t)}});const gr=["text","textord","mathord","atom"];function fr(e,t){let r;const n=de(e.body,t);if("minner"===e.mclass)r=new C("mpadded",n);else if("mord"===e.mclass)e.isCharacterBox||"mathord"===n[0].type?(r=n[0],r.type="mi",1===r.children.length&&r.children[0].text&&"\u2207"===r.children[0].text&&r.setAttribute("mathvariant","normal")):r=new C("mi",n);else{r=new C("mrow",n),e.mustPromote?(r=n[0],r.type="mo",e.isCharacterBox&&e.body[0].text&&/[A-Za-z]/.test(e.body[0].text)&&r.setAttribute("mathvariant","italic")):r=new C("mrow",n);const s=t.level<2;"mrow"===r.type?s&&("mbin"===e.mclass?(r.children.unshift(Ce(.2222)),r.children.push(Ce(.2222))):"mrel"===e.mclass?(r.children.unshift(Ce(.2778)),r.children.push(Ce(.2778))):"mpunct"===e.mclass?r.children.push(Ce(.1667)):"minner"===e.mclass&&(r.children.unshift(Ce(.0556)),r.children.push(Ce(.0556)))):"mbin"===e.mclass?(r.attributes.lspace=s?"0.2222em":"0",r.attributes.rspace=s?"0.2222em":"0"):"mrel"===e.mclass?(r.attributes.lspace=s?"0.2778em":"0",r.attributes.rspace=s?"0.2778em":"0"):"mpunct"===e.mclass?(r.attributes.lspace="0em",r.attributes.rspace=s?"0.1667em":"0"):"mopen"===e.mclass||"mclose"===e.mclass?(r.attributes.lspace="0em",r.attributes.rspace="0em"):"minner"===e.mclass&&s&&(r.attributes.lspace="0.0556em",r.attributes.width="+0.1111em"),"mopen"!==e.mclass&&"mclose"!==e.mclass&&(delete r.attributes.stretchy,delete r.attributes.form)}return r}g({type:"mclass",names:["\\\\mathord","\\\\mathbin","\\\\mathrel","\\\\mathopen","\\\\mathclose","\\\\mathpunct","\\\\mathinner"],props:{numArgs:1,primitive:!0},handler({parser:e,funcName:t},r){const n=r[0],s=i(n);let o=!0;const a={type:"mathord",text:"",mode:e.mode},l=n.body?n.body:[n];for(const t of l){if(!gr.includes(t.type)){o=!1;break}D[e.mode][t.text]?a.text+=D[e.mode][t.text].replace:t.text?a.text+=t.text:t.body&&t.body.map(e=>{a.text+=e.text})}return o&&"\\\\mathord"===t&&"mathord"===a.type&&a.text.length>1?a:{type:"mclass",mode:e.mode,mclass:"m"+t.slice(5),body:y(o?a:n),isCharacterBox:s,mustPromote:o}},mathmlBuilder:fr});const br=e=>{const t="ordgroup"===e.type&&e.body.length&&1===e.body.length?e.body[0]:e;if("atom"===t.type){const r=e.body.length>0&&e.body[0].text&&D.math[e.body[0].text]?D.math[e.body[0].text].group:t.family;return"bin"===r||"rel"===r?"m"+r:"mord"}return"mord"};g({type:"mclass",names:["\\\\@binrel"],props:{numArgs:2},handler:({parser:e},t)=>({type:"mclass",mode:e.mode,mclass:br(t[0]),body:y(t[1]),isCharacterBox:i(t[1])})}),g({type:"mclass",names:["\\\\stackrel","\\\\overset","\\\\underset"],props:{numArgs:2},handler({parser:e,funcName:t},r){const n=r[1],s=r[0];let o;o="\\\\stackrel"!==t?br(n):"mrel";const a={type:"mrel"===o||"mbin"===o?"op":"ordgroup",mode:n.mode,limits:!0,alwaysHandleSupSub:!0,parentIsSupSub:!1,symbol:!1,suppressBaseShift:"\\\\stackrel"!==t,body:y(n)};return{type:"supsub",mode:s.mode,stack:!0,base:a,sup:"\\\\underset"===t?null:s,sub:"\\\\underset"===t?s:null}},mathmlBuilder:fr});const yr=(e,t,r)=>{if(!e)return r;const n=ge(e,t);return"mrow"===n.type&&0===n.children.length?r:n};g({type:"multiscript",names:["\\\\sideset","\\\\pres@cript"],props:{numArgs:3},handler({parser:t,funcName:r,token:n},s){if(0===s[2].body.length)throw new e(r+"cannot parse an empty base.");const o=s[2].body[0];if(t.settings.strict&&"\\\\sideset"===r&&!o.symbol)throw new e("The base of \\\\sideset must be a big operator. Try \\\\prescript.");if(s[0].body.length>0&&"supsub"!==s[0].body[0].type||s[1].body.length>0&&"supsub"!==s[1].body[0].type)throw new e("\\\\sideset can parse only subscripts and superscripts in its first two arguments",n);const a=s[0].body.length>0?s[0].body[0]:null,l=s[1].body.length>0?s[1].body[0]:null;return a||l?a?{type:"multiscript",mode:t.mode,isSideset:"\\\\sideset"===r,prescripts:a,postscripts:l,base:o}:{type:"styling",mode:t.mode,scriptLevel:"text",body:[{type:"supsub",mode:t.mode,base:o,sup:l.sup,sub:l.sub}]}:o},mathmlBuilder(e,t){const r=ge(e.base,t),n=new C("mprescripts"),s=new C("none");let o=[];const a=yr(e.prescripts.sub,t,s),l=yr(e.prescripts.sup,t,s);if(e.isSideset&&(a.setAttribute("style","text-align: left;"),l.setAttribute("style","text-align: left;")),e.postscripts){o=[r,yr(e.postscripts.sub,t,s),yr(e.postscripts.sup,t,s),n,a,l]}else o=[r,n,a,l];return new C("mmultiscripts",o)}}),g({type:"not",names:["\\\\not"],props:{numArgs:1,primitive:!0,allowedInText:!1},handler({parser:e},t){const r=i(t[0]);let n;if(r)n=y(t[0]),"\\\\"===n[0].text.charAt(0)&&(n[0].text=D.math[n[0].text].replace),n[0].text=n[0].text.slice(0,1)+"\u0338"+n[0].text.slice(1);else{n=[{type:"textord",mode:"math",text:"\u0338"},{type:"kern",mode:"math",dimension:{number:-.6,unit:"em"}},t[0]]}return{type:"not",mode:e.mode,body:n,isCharacterBox:r}},mathmlBuilder(e,t){if(e.isCharacterBox){return de(e.body,t,!0)[0]}return he(e.body,t)}});const wr=["textord","mathord","atom"],xr=["\\\\smallint"],kr=["textord","mathord","ordgroup","close","leftright","font"],vr=e=>{e.attributes.lspace="0.1667em",e.attributes.rspace="0.1667em"},Ar=(e,t)=>{let r;if(e.symbol)r=new C("mo",[ae(e.name,e.mode)]),xr.includes(e.name)?r.setAttribute("largeop","false"):r.setAttribute("movablelimits","false"),e.fromMathOp&&vr(r);else if(e.body)r=new C("mo",de(e.body,t)),e.fromMathOp&&vr(r);else if(r=new C("mi",[new N(e.name.slice(1))]),!e.parentIsSupSub){const t=[r,new C("mo",[ae("\u2061","text")])];if(e.needsLeadingSpace){const e=new C("mspace");e.setAttribute("width","0.1667em"),t.unshift(e)}if(!e.isFollowedByDelimiter){const e=new C("mspace");e.setAttribute("width","0.1667em"),t.push(e)}r=new C("mrow",t)}return r},Tr={"\u220F":"\\\\prod","\u2210":"\\\\coprod","\u2211":"\\\\sum","\u22C0":"\\\\bigwedge","\u22C1":"\\\\bigvee","\u22C2":"\\\\bigcap","\u22C3":"\\\\bigcup","\u2A00":"\\\\bigodot","\u2A01":"\\\\bigoplus","\u2A02":"\\\\bigotimes","\u2A04":"\\\\biguplus","\u2A05":"\\\\bigsqcap","\u2A06":"\\\\bigsqcup","\u2A03":"\\\\bigcupdot","\u2A07":"\\\\bigdoublevee","\u2A08":"\\\\bigdoublewedge","\u2A09":"\\\\bigtimes"};g({type:"op",names:["\\\\coprod","\\\\bigvee","\\\\bigwedge","\\\\biguplus","\\\\bigcupplus","\\\\bigcupdot","\\\\bigcap","\\\\bigcup","\\\\bigdoublevee","\\\\bigdoublewedge","\\\\intop","\\\\prod","\\\\sum","\\\\bigotimes","\\\\bigoplus","\\\\bigodot","\\\\bigsqcap","\\\\bigsqcup","\\\\bigtimes","\\\\smallint","\u220F","\u2210","\u2211","\u22C0","\u22C1","\u22C2","\u22C3","\u2A00","\u2A01","\u2A02","\u2A03","\u2A04","\u2A05","\u2A06","\u2A07","\u2A08","\u2A09"],props:{numArgs:0},handler:({parser:e,funcName:t},r)=>{let n=t;return 1===n.length&&(n=Tr[n]),{type:"op",mode:e.mode,limits:!0,parentIsSupSub:!1,symbol:!0,stack:!1,name:n}},mathmlBuilder:Ar}),g({type:"op",names:["\\\\mathop"],props:{numArgs:1,primitive:!0},handler:({parser:e},t)=>{const r=t[0],n=r.body?r.body:[r],s=1===n.length&&wr.includes(n[0].type);return{type:"op",mode:e.mode,limits:!0,parentIsSupSub:!1,symbol:s,fromMathOp:!0,stack:!1,name:s?n[0].text:null,body:s?null:y(r)}},mathmlBuilder:Ar});const Sr={"\u222B":"\\\\int","\u222C":"\\\\iint","\u222D":"\\\\iiint","\u222E":"\\\\oint","\u222F":"\\\\oiint","\u2230":"\\\\oiiint","\u2231":"\\\\intclockwise","\u2232":"\\\\varointclockwise","\u2A0C":"\\\\iiiint","\u2A0D":"\\\\intbar","\u2A0E":"\\\\intBar","\u2A0F":"\\\\fint","\u2A12":"\\\\rppolint","\u2A13":"\\\\scpolint","\u2A15":"\\\\pointint","\u2A16":"\\\\sqint","\u2A17":"\\\\intlarhk","\u2A18":"\\\\intx","\u2A19":"\\\\intcap","\u2A1A":"\\\\intcup"};g({type:"op",names:["\\\\arcsin","\\\\arccos","\\\\arctan","\\\\arctg","\\\\arcctg","\\\\arg","\\\\ch","\\\\cos","\\\\cosec","\\\\cosh","\\\\cot","\\\\cotg","\\\\coth","\\\\csc","\\\\ctg","\\\\cth","\\\\deg","\\\\dim","\\\\exp","\\\\hom","\\\\ker","\\\\lg","\\\\ln","\\\\log","\\\\sec","\\\\sin","\\\\sinh","\\\\sh","\\\\sgn","\\\\tan","\\\\tanh","\\\\tg","\\\\th"],props:{numArgs:0},handler({parser:e,funcName:t}){const r=e.prevAtomType,n=e.gullet.future().text;return{type:"op",mode:e.mode,limits:!1,parentIsSupSub:!1,symbol:!1,stack:!1,isFollowedByDelimiter:Gt(n),needsLeadingSpace:r.length>0&&kr.includes(r),name:t}},mathmlBuilder:Ar}),g({type:"op",names:["\\\\det","\\\\gcd","\\\\inf","\\\\lim","\\\\max","\\\\min","\\\\Pr","\\\\sup"],props:{numArgs:0},handler({parser:e,funcName:t}){const r=e.prevAtomType,n=e.gullet.future().text;return{type:"op",mode:e.mode,limits:!0,parentIsSupSub:!1,symbol:!1,stack:!1,isFollowedByDelimiter:Gt(n),needsLeadingSpace:r.length>0&&kr.includes(r),name:t}},mathmlBuilder:Ar}),g({type:"op",names:["\\\\int","\\\\iint","\\\\iiint","\\\\iiiint","\\\\oint","\\\\oiint","\\\\oiiint","\\\\intclockwise","\\\\varointclockwise","\\\\intbar","\\\\intBar","\\\\fint","\\\\rppolint","\\\\scpolint","\\\\pointint","\\\\sqint","\\\\intlarhk","\\\\intx","\\\\intcap","\\\\intcup","\u222B","\u222C","\u222D","\u222E","\u222F","\u2230","\u2231","\u2232","\u2A0C","\u2A0D","\u2A0E","\u2A0F","\u2A12","\u2A13","\u2A15","\u2A16","\u2A17","\u2A18","\u2A19","\u2A1A"],props:{numArgs:0,allowedInArgument:!0},handler({parser:e,funcName:t}){let r=t;return 1===r.length&&(r=Sr[r]),{type:"op",mode:e.mode,limits:!1,parentIsSupSub:!1,symbol:!0,stack:!1,name:r}},mathmlBuilder:Ar});g({type:"operatorname",names:["\\\\operatorname@","\\\\operatornamewithlimits"],props:{numArgs:1,allowedInArgument:!0},handler:({parser:e,funcName:t},r)=>{const n=r[0],s=e.prevAtomType,o=e.gullet.future().text;return{type:"operatorname",mode:e.mode,body:y(n),alwaysHandleSupSub:"\\\\operatornamewithlimits"===t,limits:!1,parentIsSupSub:!1,isFollowedByDelimiter:Gt(o),needsLeadingSpace:s.length>0&&kr.includes(s)}},mathmlBuilder:(e,t)=>{let r,n=de(e.body,t.withFont("mathrm")),s=!0;for(let e=0;ee.toText()).join("");n=[new N(e)]}else if(1===n.length&&["mover","munder"].includes(n[0].type)&&("mi"===n[0].children[0].type||"mtext"===n[0].children[0].type)){if(n[0].children[0].type="mi",e.parentIsSupSub)return new C("mrow",n);{const e=new C("mo",[ae("\u2061","text")]);return B([n[0],e])}}if(s?(r=new C("mi",n),1===n[0].text.length&&r.setAttribute("mathvariant","normal")):r=new C("mrow",n),!e.parentIsSupSub){const t=[r,new C("mo",[ae("\u2061","text")])];if(e.needsLeadingSpace){const e=new C("mspace");e.setAttribute("width","0.1667em"),t.unshift(e)}if(!e.isFollowedByDelimiter){const e=new C("mspace");e.setAttribute("width","0.1667em"),t.push(e)}return B(t)}return r}}),Qe("\\\\operatorname","\\\\@ifstar\\\\operatornamewithlimits\\\\operatorname@"),f({type:"ordgroup",mathmlBuilder:(e,t)=>he(e.body,t,e.semisimple)}),g({type:"phantom",names:["\\\\phantom"],props:{numArgs:1,allowedInText:!0},handler:({parser:e},t)=>{const r=t[0];return{type:"phantom",mode:e.mode,body:y(r)}},mathmlBuilder:(e,t)=>{const r=de(e.body,t);return new C("mphantom",r)}}),g({type:"hphantom",names:["\\\\hphantom"],props:{numArgs:1,allowedInText:!0},handler:({parser:e},t)=>{const r=t[0];return{type:"hphantom",mode:e.mode,body:r}},mathmlBuilder:(e,t)=>{const r=de(y(e.body),t),n=new C("mphantom",r),s=new C("mpadded",[n]);return s.setAttribute("height","0px"),s.setAttribute("depth","0px"),s}}),g({type:"vphantom",names:["\\\\vphantom"],props:{numArgs:1,allowedInText:!0},handler:({parser:e},t)=>{const r=t[0];return{type:"vphantom",mode:e.mode,body:r}},mathmlBuilder:(e,t)=>{const r=de(y(e.body),t),n=new C("mphantom",r),s=new C("mpadded",[n]);return s.setAttribute("width","0.1px"),s}}),g({type:"pmb",names:["\\\\pmb"],props:{numArgs:1,allowedInText:!0},handler:({parser:e},t)=>({type:"pmb",mode:e.mode,body:y(t[0])}),mathmlBuilder(e,t){const r=de(e.body,t),n=z(r);return n.setAttribute("style","font-weight:bold"),n}});const Or=(e,t)=>{const r=t.withLevel(Ze),n=new C("mpadded",[ge(e.body,r)]),s=Be(e.dy,t);return n.setAttribute("voffset",s.number+s.unit),s.number>0?n.style.padding=s.number+s.unit+" 0 0 0":n.style.padding="0 0 "+Math.abs(s.number)+s.unit+" 0",n};g({type:"raise",names:["\\\\raise","\\\\lower"],props:{numArgs:2,argTypes:["size","primitive"],primitive:!0},handler({parser:e,funcName:t},r){const n=Fe(r[0],"size").value;"\\\\lower"===t&&(n.number*=-1);const s=r[1];return{type:"raise",mode:e.mode,dy:n,body:s}},mathmlBuilder:Or}),g({type:"raise",names:["\\\\raisebox"],props:{numArgs:2,argTypes:["size","hbox"],allowedInText:!0},handler({parser:e,funcName:t},r){const n=Fe(r[0],"size").value,s=r[1];return{type:"raise",mode:e.mode,dy:n,body:s}},mathmlBuilder:Or}),g({type:"ref",names:["\\\\ref","\\\\eqref"],props:{numArgs:1,argTypes:["raw"]},handler:({parser:e,funcName:t},r)=>({type:"ref",mode:e.mode,funcName:t,string:r[0].string.replace(dr,"")}),mathmlBuilder(e,t){const r="\\\\ref"===e.funcName?["tml-ref"]:["tml-ref","tml-eqref"];return new O("#"+e.string,r,null)}}),g({type:"reflect",names:["\\\\reflectbox"],props:{numArgs:1,argTypes:["hbox"],allowedInText:!0},handler:({parser:e},t)=>({type:"reflect",mode:e.mode,body:t[0]}),mathmlBuilder(e,t){const r=ge(e.body,t);return r.style.transform="scaleX(-1)",r}}),g({type:"internal",names:["\\\\relax"],props:{numArgs:0,allowedInText:!0,allowedInArgument:!0},handler:({parser:e})=>({type:"internal",mode:e.mode})}),g({type:"rule",names:["\\\\rule"],props:{numArgs:2,numOptionalArgs:1,allowedInText:!0,allowedInMath:!0,argTypes:["size","size","size"]},handler({parser:e},t,r){const n=r[0],s=Fe(t[0],"size"),o=Fe(t[1],"size");return{type:"rule",mode:e.mode,shift:n&&Fe(n,"size").value,width:s.value,height:o.value}},mathmlBuilder(e,t){const r=Be(e.width,t),n=Be(e.height,t),s=e.shift?Be(e.shift,t):{number:0,unit:"em"},o=t.color&&t.getColor()||"black",a=new C("mspace");if(r.number>0&&n.number>0&&a.setAttribute("mathbackground",o),a.setAttribute("width",r.number+r.unit),a.setAttribute("height",n.number+n.unit),0===s.number)return a;const l=new C("mpadded",[a]);return s.number>=0?l.setAttribute("height","+"+s.number+s.unit):(l.setAttribute("height",s.number+s.unit),l.setAttribute("depth","+"+-s.number+s.unit)),l.setAttribute("voffset",s.number+s.unit),l}});const qr=/^[0-9]$/,Br={0:"\u2080",1:"\u2081",2:"\u2082",3:"\u2083",4:"\u2084",5:"\u2085",6:"\u2086",7:"\u2087",8:"\u2088",9:"\u2089"},Cr={0:"\u2070",1:"\xB9",2:"\xB2",3:"\xB3",4:"\u2074",5:"\u2075",6:"\u2076",7:"\u2077",8:"\u2078",9:"\u2079"};g({type:"sfrac",names:["\\\\sfrac"],props:{numArgs:2,allowedInText:!0,allowedInMath:!0},handler({parser:t},r){let n="";for(const t of r[0].body){if("textord"!==t.type||!qr.test(t.text))throw new e("Numerator must be an integer.",t);n+=t.text}let s="";for(const t of r[1].body){if("textord"!==t.type||!qr.test(t.text))throw new e("Denominator must be an integer.",t);s+=t.text}return{type:"sfrac",mode:t.mode,numerator:n,denominator:s}},mathmlBuilder(e,t){const r=e.numerator.split("").map(e=>Cr[e]).join(""),n=e.denominator.split("").map(e=>Br[e]).join(""),s=new N(r+"\u2044"+n,e.mode,t);return new C("mn",[s],["special-fraction"])}});const Nr={"\\\\tiny":.5,"\\\\sixptsize":.6,"\\\\Tiny":.6,"\\\\scriptsize":.7,"\\\\footnotesize":.8,"\\\\small":.9,"\\\\normalsize":1,"\\\\large":1.2,"\\\\Large":1.44,"\\\\LARGE":1.728,"\\\\huge":2.074,"\\\\Huge":2.488};g({type:"sizing",names:["\\\\tiny","\\\\sixptsize","\\\\Tiny","\\\\scriptsize","\\\\footnotesize","\\\\small","\\\\normalsize","\\\\large","\\\\Large","\\\\LARGE","\\\\huge","\\\\Huge"],props:{numArgs:0,allowedInText:!0},handler:({breakOnTokenText:e,funcName:t,parser:r},n)=>{r.settings.strict&&"math"===r.mode&&console.log(`Temml strict-mode warning: Command ${t} is invalid in math mode.`);const s=r.parseExpression(!1,e,!0);return{type:"sizing",mode:r.mode,funcName:t,body:s}},mathmlBuilder:(e,t)=>{const r=t.withFontSize(Nr[e.funcName]),n=de(e.body,r),s=z(n),o=(Nr[e.funcName]/t.fontSize).toFixed(4);return s.setAttribute("mathsize",o+"em"),s}}),g({type:"smash",names:["\\\\smash"],props:{numArgs:1,numOptionalArgs:1,allowedInText:!0},handler:({parser:e},t,r)=>{let n=!1,s=!1;const o=r[0]&&Fe(r[0],"ordgroup");if(o){let e="";for(let t=0;t{const r=new C("mpadded",[ge(e.body,t)]);return e.smashHeight&&r.setAttribute("height","0px"),e.smashDepth&&r.setAttribute("depth","0px"),r}});const zr=["a","c","e","\u0131","m","n","o","r","s","u","v","w","x","z","\u03B1","\u03B5","\u03B9","\u03BA","\u03BD","\u03BF","\u03C0","\u03C3","\u03C4","\u03C5","\u03C9","\\\\alpha","\\\\epsilon","\\\\iota","\\\\kappa","\\\\nu","\\\\omega","\\\\pi","\\\\tau","\\\\omega"];g({type:"sqrt",names:["\\\\sqrt"],props:{numArgs:1,numOptionalArgs:1},handler({parser:e},t,r){const n=r[0],s=t[0];return s.body&&1===s.body.length&&s.body[0].text&&zr.includes(s.body[0].text)&&s.body.push({type:"rule",mode:"math",shift:null,width:{number:0,unit:"pt"},height:{number:.5,unit:"em"}}),{type:"sqrt",mode:e.mode,body:s,index:n}},mathmlBuilder(e,t){const{body:r,index:n}=e;return n?new C("mroot",[ge(r,t),ge(n,t.incrementLevel())]):new C("msqrt",[ge(r,t)])}});const $r={display:0,text:1,script:2,scriptscript:3},Er={display:["0","true"],text:["0","false"],script:["1","false"],scriptscript:["2","false"]};g({type:"styling",names:["\\\\displaystyle","\\\\textstyle","\\\\scriptstyle","\\\\scriptscriptstyle"],props:{numArgs:0,allowedInText:!0,primitive:!0},handler({breakOnTokenText:e,funcName:t,parser:r},n){const s=r.parseExpression(!0,e,!0),o=t.slice(1,t.length-5);return{type:"styling",mode:r.mode,scriptLevel:o,body:s}},mathmlBuilder(e,t){const r=t.withLevel($r[e.scriptLevel]),n=de(e.body,r),s=z(n),o=Er[e.scriptLevel];return s.setAttribute("scriptlevel",o[0]),s.setAttribute("displaystyle",o[1]),s}});const Ir=/^m(over|under|underover)$/;f({type:"supsub",mathmlBuilder(e,t){let r,n,s=!1,o=!1,a=!1,l=!1;e.base&&"horizBracket"===e.base.type&&(n=!!e.sup,n===e.base.isOver&&(s=!0,r=e.base.isOver)),!e.base||e.stack||"op"!==e.base.type&&"operatorname"!==e.base.type||(e.base.parentIsSupSub=!0,o=!e.base.symbol,a=o&&!e.isFollowedByDelimiter,l=e.base.needsLeadingSpace);const i=e.stack&&1===e.base.body.length?[ge(e.base.body[0],t)]:[ge(e.base,t)],c=t.inSubOrSup();if(e.sub){const r=ge(e.sub,c);3===t.level&&r.setAttribute("scriptlevel","2"),i.push(r)}if(e.sup){const r=ge(e.sup,c);if(3===t.level&&r.setAttribute("scriptlevel","2"),e.base&&e.base.text&&1===e.base.text.length){const t=e.base.text;"DHKLUcegorsuvxyz\u03A0\u03A5\u03A8\u03B1\u03B4\u03B7\u03B9\u03BC\u03BD\u03BF\u03C4\u03C5\u03C7\u03F5".indexOf(t)>-1?r.classes.push("tml-sml-pad"):"BCEFGIMNOPQRSTXZlpqtw\u0393\u0398\u039E\u03A3\u03A6\u03A9\u03B2\u03B5\u03B6\u03B8\u03BE\u03C1\u03C2\u03C6\u03C8\u03D1\u03D5\u03F1".indexOf(t)>-1?r.classes.push("tml-med-pad"):"AJdf\u0394\u039B".indexOf(t)>-1&&r.classes.push("tml-lrg-pad")}i.push(r)}let m;if(s)m=r?"mover":"munder";else if(e.sub)if(e.sup){const r=e.base;m=r&&("op"===r.type&&r.limits||"multiscript"===r.type)&&(t.level===Xe||r.alwaysHandleSupSub)||r&&"operatorname"===r.type&&r.alwaysHandleSupSub&&(t.level===Xe||r.limits)?"munderover":"msubsup"}else{const r=e.base;m=e.stack||r&&"op"===r.type&&r.limits&&(t.level===Xe||r.alwaysHandleSupSub)||r&&"operatorname"===r.type&&r.alwaysHandleSupSub&&(r.limits||t.level===Xe)?"munder":"msub"}else{const r=e.base;m=r&&"op"===r.type&&r.limits&&(t.level===Xe||r.alwaysHandleSupSub)||r&&"operatorname"===r.type&&r.alwaysHandleSupSub&&(r.limits||t.level===Xe)?"mover":"msup"}let p=new C(m,i);if(o){const e=new C("mo",[ae("\u2061","text")]);if(l){const t=new C("mspace");t.setAttribute("width","0.1667em"),p=B([t,p,e])}else p=B([p,e]);if(a){const e=new C("mspace");e.setAttribute("width","0.1667em"),p.children.push(e)}}else Ir.test(m)&&(p=new C("mrow",[p]));return p}});const Lr=["\\\\shortmid","\\\\nshortmid","\\\\shortparallel","\\\\nshortparallel","\\\\smallsetminus"],Fr=["\\\\Rsh","\\\\Lsh","\\\\restriction"];f({type:"atom",mathmlBuilder(e,t){if("inner"===e.family){const t="text"===e.mode?"mtext":"mi";return new C(t,[ae(e.text,e.mode)])}const r=new C("mo",[ae(e.text,e.mode)]);if("punct"===e.family)r.setAttribute("separator","true");else if("open"===e.family||"close"===e.family)"open"===e.family?(r.setAttribute("form","prefix"),r.setAttribute("stretchy","false")):"close"===e.family&&(r.setAttribute("form","postfix"),r.setAttribute("stretchy","false"));else if("\\\\mid"===e.text)r.setAttribute("lspace","0.22em"),r.setAttribute("rspace","0.22em"),r.setAttribute("stretchy","false");else if("rel"===e.family&&(e=>{if(1===e.length){const t=e.codePointAt(0);return 8591-1||e.indexOf("harpoon")>-1||Fr.includes(e)})(e.text))r.setAttribute("stretchy","false");else if(Lr.includes(e.text))r.setAttribute("mathsize","70%");else if(":"===e.text)r.attributes.lspace="0.2222em",r.attributes.rspace="0.2222em";else if(e.needsSpacing)return"bin"===e.family?new C("mrow",[Ce(.222),r,Ce(.222)]):new C("mrow",[Ce(.2778),r,Ce(.2778)]);return r}});const jr={mathbf:"bold",mathrm:"normal",textit:"italic",mathit:"italic",mathnormal:"italic",mathbb:"double-struck",mathcal:"script",mathfrak:"fraktur",mathscr:"script",mathsf:"sans-serif",mathtt:"monospace"},Mr=function(e,t){if("texttt"===t.fontFamily)return"monospace";if("textsc"===t.fontFamily)return"normal";if("textsf"===t.fontFamily)return"textit"===t.fontShape&&"textbf"===t.fontWeight?"sans-serif-bold-italic":"textit"===t.fontShape?"sans-serif-italic":"textbf"===t.fontWeight?"sans-serif-bold":"sans-serif";if("textit"===t.fontShape&&"textbf"===t.fontWeight)return"bold-italic";if("textit"===t.fontShape)return"italic";if("textbf"===t.fontWeight)return"bold";const r=t.font;if(!r||"mathnormal"===r)return null;const n=e.mode;switch(r){case"mathit":case"greekItalic":return"italic";case"mathrm":{const t=e.text.codePointAt(0);return 939{const n=new C(r,[e]),s=new C("mstyle",[n]);return s.style["font-style"]="italic",s.style["font-family"]="Cambria, \'Times New Roman\', serif","bold-italic"===t&&(s.style["font-weight"]="bold"),s})(s,o,t);"normal"!==o&&(s.text=s.text.split("").map(e=>rr(e,o)).join("")),a=new C(t,[s])}else if("text"===e.mode)"normal"!==o&&(s.text=rr(s.text,o)),a=new C("mtext",[s]);else if(Pr.has(e.text))a=new C("mo",[s]),a.classes.push("tml-prime");else{const e=s.text;"italic"!==o&&(s.text=rr(s.text,o)),a=new C("mi",[s]),s.text===e&&Gr.test(e)&&a.setAttribute("mathvariant","italic")}return a}});const Rr={"\\\\nobreak":"nobreak","\\\\allowbreak":"allowbreak"},Ur={" ":{},"\\\\ ":{},"~":{className:"nobreak"},"\\\\space":{},"\\\\nobreakspace":{className:"nobreak"}};f({type:"spacing",mathmlBuilder(t,r){let n;if(Object.prototype.hasOwnProperty.call(Ur,t.text))n=new C("mtext",[new N("\xA0")]);else{if(!Object.prototype.hasOwnProperty.call(Rr,t.text))throw new e(`Unknown type of space "${t.text}"`);n=new C("mo"),"\\\\nobreak"===t.text&&n.setAttribute("linebreak","nobreak")}return n}}),f({type:"tag"});const Hr={"\\\\text":void 0,"\\\\textrm":"textrm","\\\\textsf":"textsf","\\\\texttt":"texttt","\\\\textnormal":"textrm","\\\\textsc":"textsc"},_r={"\\\\textbf":"textbf","\\\\textmd":"textmd"},Vr={"\\\\textit":"textit","\\\\textup":"textup"};g({type:"text",names:["\\\\text","\\\\textrm","\\\\textsf","\\\\texttt","\\\\textnormal","\\\\textsc","\\\\textbf","\\\\textmd","\\\\textit","\\\\textup","\\\\emph"],props:{numArgs:1,argTypes:["text"],allowedInArgument:!0,allowedInText:!0},handler({parser:e,funcName:t},r){const n=r[0];return{type:"text",mode:e.mode,body:y(n),font:t}},mathmlBuilder(e,t){const r=((e,t)=>{const r=e.font;return r?Hr[r]?t.withTextFontFamily(Hr[r]):_r[r]?t.withTextFontWeight(_r[r]):"\\\\emph"===r?"textit"===t.fontShape?t.withTextFontShape("textup"):t.withTextFontShape("textit"):t.withTextFontShape(Vr[r]):t})(e,t),n=he(e.body,r);return ie(n)}}),g({type:"vcenter",names:["\\\\vcenter"],props:{numArgs:1,argTypes:["original"],allowedInText:!1},handler:({parser:e},t)=>({type:"vcenter",mode:e.mode,body:t[0]}),mathmlBuilder(e,t){const r=new C("mtd",[ge(e.body,t)]);r.style.padding="0";const n=new C("mtr",[r]);return new C("mtable",[n])}}),g({type:"verb",names:["\\\\verb"],props:{numArgs:0,allowedInText:!0},handler(t,r,n){throw new e("\\\\verb ended by end of line instead of matching delimiter")},mathmlBuilder(e,t){const r=new N(Wr(e)),n=new C("mtext",[r]);return n.setAttribute("mathvariant","monospace"),n}});const Wr=e=>e.body.replace(/ /g,e.star?"\u2423":"\xA0"),Xr=d,Zr="[ \\r\\n\\t]",Yr=`(\\\\\\\\[a-zA-Z@]+)${Zr}*`,Jr="[\u0300-\u036F]",Kr=new RegExp(`${Jr}+$`),Qr=`(${Zr}+)|\\\\\\\\(\\n|[ \\r\\t]+\\n?)[ \\r\\t]*|([!-\\\\[\\\\]-\u2027\u202A-\uD7FF\uF900-\uFFFF]${Jr}*|[\\ud800-\\udbff][\\udc00-\\udfff]${Jr}*|\\\\\\\\verb\\\\*([^]).*?\\\\4|\\\\\\\\verb([^*a-zA-Z]).*?\\\\5|${Yr}|\\\\\\\\[^\\ud800-\\udfff])`;class en{constructor(e,t){this.input=e,this.settings=t,this.tokenRegex=new RegExp(Qr,"g"),this.catcodes={"%":14,"~":13}}setCatcode(e,t){this.catcodes[e]=t}lex(){const t=this.input,r=this.tokenRegex.lastIndex;if(r===t.length)return new We("EOF",new Ve(this,r,r));const n=this.tokenRegex.exec(t);if(null===n||n.index!==r)throw new e(`Unexpected character: \'${t[r]}\'`,new We(t[r],new Ve(this,r,r+1)));const s=n[6]||n[3]||(n[2]?"\\\\ ":" ");if(14===this.catcodes[s]){const r=t.indexOf("\\n",this.tokenRegex.lastIndex);if(-1===r){if(this.tokenRegex.lastIndex=t.length,this.settings.strict)throw new e("% comment has no terminating newline; LaTeX would fail because of commenting the end of math mode")}else this.tokenRegex.lastIndex=r+1;return this.lex()}return new We(s,new Ve(this,r,this.tokenRegex.lastIndex))}}class tn{constructor(e=Object.create(null),t=Object.create(null)){this.current=t,this.builtins=e,this.undefStack=[]}beginGroup(){this.undefStack.push(Object.create(null))}endGroup(){if(0===this.undefStack.length)throw new e("Unbalanced namespace destruction: attempt to pop global namespace; please report this as a bug");const t=this.undefStack.pop();for(const e in t)Object.prototype.hasOwnProperty.call(t,e)&&(void 0===t[e]?delete this.current[e]:this.current[e]=t[e])}has(e){return Object.prototype.hasOwnProperty.call(this.current,e)||Object.prototype.hasOwnProperty.call(this.builtins,e)}get(e){return Object.prototype.hasOwnProperty.call(this.current,e)?this.current[e]:this.builtins[e]}set(e,t,r=!1){if(r){for(let t=0;t0&&(this.undefStack[this.undefStack.length-1][e]=t)}else{const t=this.undefStack[this.undefStack.length-1];t&&!Object.prototype.hasOwnProperty.call(t,e)&&(t[e]=this.current[e])}this.current[e]=t}}const rn={"^":!0,_:!0,"\\\\limits":!0,"\\\\nolimits":!0};class nn{constructor(e,t,r){this.settings=t,this.expansionCount=0,this.feed(e),this.macros=new tn(et,t.macros),this.mode=r,this.stack=[]}feed(e){this.lexer=new en(e,this.settings)}switchMode(e){this.mode=e}beginGroup(){this.macros.beginGroup()}endGroup(){this.macros.endGroup()}future(){return 0===this.stack.length&&this.pushToken(this.lexer.lex()),this.stack[this.stack.length-1]}popToken(){return this.future(),this.stack.pop()}pushToken(e){this.stack.push(e)}pushTokens(e){this.stack.push(...e)}scanArgument(e){let t,r,n;if(e){if(this.consumeSpaces(),"["!==this.future().text)return null;t=this.popToken(),({tokens:n,end:r}=this.consumeArg(["]"]))}else({tokens:n,start:t,end:r}=this.consumeArg());return this.pushToken(new We("EOF",r.loc)),this.pushTokens(n),new We("",Ve.range(t,r))}consumeSpaces(){for(;;){if(" "!==this.future().text)break;this.stack.pop()}}consumeArg(t){const r=[],n=t&&t.length>0;n||this.consumeSpaces();const s=this.future();let o,a=0,l=0;do{if(o=this.popToken(),r.push(o),"{"===o.text)++a;else if("}"===o.text){if(--a,-1===a)throw new e("Extra }",o)}else if("EOF"===o.text)throw new e("Unexpected end of input in a macro argument, expected \'"+(t&&n?t[l]:"}")+"\'",o);if(t&&n)if((0===a||1===a&&"{"===t[l])&&o.text===t[l]){if(++l,l===t.length){r.splice(-l,l);break}}else l=0}while(0!==a||n);return"{"===s.text&&"}"===r[r.length-1].text&&(r.pop(),r.shift()),r.reverse(),{tokens:r,start:s,end:o}}consumeArgs(t,r){if(r){if(r.length!==t+1)throw new e("The length of delimiters doesn\'t match the number of args!");const n=r[0];for(let t=0;tthis.settings.maxExpand)throw new e("Too many expansions: infinite loop or need to increase maxExpand setting");let o=s.tokens;const a=this.consumeArgs(s.numArgs,s.delimiters);if(s.numArgs){o=o.slice();for(let t=o.length-1;t>=0;--t){let r=o[t];if("#"===r.text){if(0===t)throw new e("Incomplete placeholder at end of macro body",r);if(r=o[--t],"#"===r.text)o.splice(t+1,1);else{if(!/^[1-9]$/.test(r.text))throw new e("Not a valid argument number",r);o.splice(t,2,...a[+r.text-1])}}}}return this.pushTokens(o),o.length}expandAfterFuture(){return this.expandOnce(),this.future()}expandNextToken(){for(;;)if(!1===this.expandOnce()){const e=this.stack.pop();return e.treatAsRelax&&(e.text="\\\\relax"),e}throw new Error}expandMacro(e){return this.macros.has(e)?this.expandTokens([new We(e)]):void 0}expandTokens(e){const t=[],r=this.stack.length;for(this.pushTokens(e);this.stack.length>r;)if(!1===this.expandOnce(!0)){const e=this.stack.pop();e.treatAsRelax&&(e.noexpand=!1,e.treatAsRelax=!1),t.push(e)}return t}expandMacroAsText(e){const t=this.expandMacro(e);return t?t.map(e=>e.text).join(""):t}_getExpansion(e){const t=this.macros.get(e);if(null==t)return t;if(1===e.length){const t=this.lexer.catcodes[e];if(null!=t&&13!==t)return}const r="function"==typeof t?t(this):t;if("string"==typeof r){let e=0;if(-1!==r.indexOf("#")){const t=r.replace(/##/g,"");for(;-1!==t.indexOf("#"+(e+1));)++e}const t=new en(r,this.settings),n=[];let s=t.lex();for(;"EOF"!==s.text;)n.push(s),s=t.lex();n.reverse();return{tokens:n,numArgs:e}}return r}isDefined(e){return this.macros.has(e)||Object.prototype.hasOwnProperty.call(Xr,e)||Object.prototype.hasOwnProperty.call(D.math,e)||Object.prototype.hasOwnProperty.call(D.text,e)||Object.prototype.hasOwnProperty.call(rn,e)}isExpandable(e){const t=this.macros.get(e);return null!=t?"string"==typeof t||"function"==typeof t||!t.unexpandable:Object.prototype.hasOwnProperty.call(Xr,e)&&!Xr[e].primitive}}const sn=/^[\u208A\u208B\u208C\u208D\u208E\u2080\u2081\u2082\u2083\u2084\u2085\u2086\u2087\u2088\u2089\u2090\u2091\u2095\u1D62\u2C7C\u2096\u2097\u2098\u2099\u2092\u209A\u1D63\u209B\u209C\u1D64\u1D65\u2093\u1D66\u1D67\u1D68\u1D69\u1D6A]/,on=Object.freeze({"\u208A":"+","\u208B":"-","\u208C":"=","\u208D":"(","\u208E":")","\u2080":"0","\u2081":"1","\u2082":"2","\u2083":"3","\u2084":"4","\u2085":"5","\u2086":"6","\u2087":"7","\u2088":"8","\u2089":"9","\u2090":"a","\u2091":"e","\u2095":"h","\u1D62":"i","\u2C7C":"j","\u2096":"k","\u2097":"l","\u2098":"m","\u2099":"n","\u2092":"o","\u209A":"p","\u1D63":"r","\u209B":"s","\u209C":"t","\u1D64":"u","\u1D65":"v","\u2093":"x","\u1D66":"\u03B2","\u1D67":"\u03B3","\u1D68":"\u03C1","\u1D69":"\u03D5","\u1D6A":"\u03C7","\u207A":"+","\u207B":"-","\u207C":"=","\u207D":"(","\u207E":")","\u2070":"0","\xB9":"1","\xB2":"2","\xB3":"3","\u2074":"4","\u2075":"5","\u2076":"6","\u2077":"7","\u2078":"8","\u2079":"9","\u1D2C":"A","\u1D2E":"B","\u1D30":"D","\u1D31":"E","\u1D33":"G","\u1D34":"H","\u1D35":"I","\u1D36":"J","\u1D37":"K","\u1D38":"L","\u1D39":"M","\u1D3A":"N","\u1D3C":"O","\u1D3E":"P","\u1D3F":"R","\u1D40":"T","\u1D41":"U","\u2C7D":"V","\u1D42":"W","\u1D43":"a","\u1D47":"b","\u1D9C":"c","\u1D48":"d","\u1D49":"e","\u1DA0":"f","\u1D4D":"g","\u02B0":"h","\u2071":"i","\u02B2":"j","\u1D4F":"k","\u02E1":"l","\u1D50":"m","\u207F":"n","\u1D52":"o","\u1D56":"p","\u02B3":"r","\u02E2":"s","\u1D57":"t","\u1D58":"u","\u1D5B":"v","\u02B7":"w","\u02E3":"x","\u02B8":"y","\u1DBB":"z","\u1D5D":"\u03B2","\u1D5E":"\u03B3","\u1D5F":"\u03B4","\u1D60":"\u03D5","\u1D61":"\u03C7","\u1DBF":"\u03B8"}),an=Object.freeze({"\u{1D49C}":"A","\u212C":"B","\u{1D49E}":"C","\u{1D49F}":"D","\u2130":"E","\u2131":"F","\u{1D4A2}":"G","\u210B":"H","\u2110":"I","\u{1D4A5}":"J","\u{1D4A6}":"K","\u2112":"L","\u2133":"M","\u{1D4A9}":"N","\u{1D4AA}":"O","\u{1D4AB}":"P","\u{1D4AC}":"Q","\u211B":"R","\u{1D4AE}":"S","\u{1D4AF}":"T","\u{1D4B0}":"U","\u{1D4B1}":"V","\u{1D4B2}":"W","\u{1D4B3}":"X","\u{1D4B4}":"Y","\u{1D4B5}":"Z"});var ln={"\u0301":{text:"\\\\\'",math:"\\\\acute"},"\u0300":{text:"\\\\`",math:"\\\\grave"},"\u0308":{text:\'\\\\"\',math:"\\\\ddot"},"\u0303":{text:"\\\\~",math:"\\\\tilde"},"\u0304":{text:"\\\\=",math:"\\\\bar"},"\u0306":{text:"\\\\u",math:"\\\\breve"},"\u030C":{text:"\\\\v",math:"\\\\check"},"\u0302":{text:"\\\\^",math:"\\\\hat"},"\u0307":{text:"\\\\.",math:"\\\\dot"},"\u030A":{text:"\\\\r",math:"\\\\mathring"},"\u030B":{text:"\\\\H"},"\u0327":{text:"\\\\c"}},cn={"\xE1":"a\u0301","\xE0":"a\u0300","\xE4":"a\u0308","\u01DF":"a\u0308\u0304","\xE3":"a\u0303","\u0101":"a\u0304","\u0103":"a\u0306","\u1EAF":"a\u0306\u0301","\u1EB1":"a\u0306\u0300","\u1EB5":"a\u0306\u0303","\u01CE":"a\u030C","\xE2":"a\u0302","\u1EA5":"a\u0302\u0301","\u1EA7":"a\u0302\u0300","\u1EAB":"a\u0302\u0303","\u0227":"a\u0307","\u01E1":"a\u0307\u0304","\xE5":"a\u030A","\u01FB":"a\u030A\u0301","\u1E03":"b\u0307","\u0107":"c\u0301","\u010D":"c\u030C","\u0109":"c\u0302","\u010B":"c\u0307","\u010F":"d\u030C","\u1E0B":"d\u0307","\xE9":"e\u0301","\xE8":"e\u0300","\xEB":"e\u0308","\u1EBD":"e\u0303","\u0113":"e\u0304","\u1E17":"e\u0304\u0301","\u1E15":"e\u0304\u0300","\u0115":"e\u0306","\u011B":"e\u030C","\xEA":"e\u0302","\u1EBF":"e\u0302\u0301","\u1EC1":"e\u0302\u0300","\u1EC5":"e\u0302\u0303","\u0117":"e\u0307","\u1E1F":"f\u0307","\u01F5":"g\u0301","\u1E21":"g\u0304","\u011F":"g\u0306","\u01E7":"g\u030C","\u011D":"g\u0302","\u0121":"g\u0307","\u1E27":"h\u0308","\u021F":"h\u030C","\u0125":"h\u0302","\u1E23":"h\u0307","\xED":"i\u0301","\xEC":"i\u0300","\xEF":"i\u0308","\u1E2F":"i\u0308\u0301","\u0129":"i\u0303","\u012B":"i\u0304","\u012D":"i\u0306","\u01D0":"i\u030C","\xEE":"i\u0302","\u01F0":"j\u030C","\u0135":"j\u0302","\u1E31":"k\u0301","\u01E9":"k\u030C","\u013A":"l\u0301","\u013E":"l\u030C","\u1E3F":"m\u0301","\u1E41":"m\u0307","\u0144":"n\u0301","\u01F9":"n\u0300","\xF1":"n\u0303","\u0148":"n\u030C","\u1E45":"n\u0307","\xF3":"o\u0301","\xF2":"o\u0300","\xF6":"o\u0308","\u022B":"o\u0308\u0304","\xF5":"o\u0303","\u1E4D":"o\u0303\u0301","\u1E4F":"o\u0303\u0308","\u022D":"o\u0303\u0304","\u014D":"o\u0304","\u1E53":"o\u0304\u0301","\u1E51":"o\u0304\u0300","\u014F":"o\u0306","\u01D2":"o\u030C","\xF4":"o\u0302","\u1ED1":"o\u0302\u0301","\u1ED3":"o\u0302\u0300","\u1ED7":"o\u0302\u0303","\u022F":"o\u0307","\u0231":"o\u0307\u0304","\u0151":"o\u030B","\u1E55":"p\u0301","\u1E57":"p\u0307","\u0155":"r\u0301","\u0159":"r\u030C","\u1E59":"r\u0307","\u015B":"s\u0301","\u1E65":"s\u0301\u0307","\u0161":"s\u030C","\u1E67":"s\u030C\u0307","\u015D":"s\u0302","\u1E61":"s\u0307","\u1E97":"t\u0308","\u0165":"t\u030C","\u1E6B":"t\u0307","\xFA":"u\u0301","\xF9":"u\u0300","\xFC":"u\u0308","\u01D8":"u\u0308\u0301","\u01DC":"u\u0308\u0300","\u01D6":"u\u0308\u0304","\u01DA":"u\u0308\u030C","\u0169":"u\u0303","\u1E79":"u\u0303\u0301","\u016B":"u\u0304","\u1E7B":"u\u0304\u0308","\u016D":"u\u0306","\u01D4":"u\u030C","\xFB":"u\u0302","\u016F":"u\u030A","\u0171":"u\u030B","\u1E7D":"v\u0303","\u1E83":"w\u0301","\u1E81":"w\u0300","\u1E85":"w\u0308","\u0175":"w\u0302","\u1E87":"w\u0307","\u1E98":"w\u030A","\u1E8D":"x\u0308","\u1E8B":"x\u0307","\xFD":"y\u0301","\u1EF3":"y\u0300","\xFF":"y\u0308","\u1EF9":"y\u0303","\u0233":"y\u0304","\u0177":"y\u0302","\u1E8F":"y\u0307","\u1E99":"y\u030A","\u017A":"z\u0301","\u017E":"z\u030C","\u1E91":"z\u0302","\u017C":"z\u0307","\xC1":"A\u0301","\xC0":"A\u0300","\xC4":"A\u0308","\u01DE":"A\u0308\u0304","\xC3":"A\u0303","\u0100":"A\u0304","\u0102":"A\u0306","\u1EAE":"A\u0306\u0301","\u1EB0":"A\u0306\u0300","\u1EB4":"A\u0306\u0303","\u01CD":"A\u030C","\xC2":"A\u0302","\u1EA4":"A\u0302\u0301","\u1EA6":"A\u0302\u0300","\u1EAA":"A\u0302\u0303","\u0226":"A\u0307","\u01E0":"A\u0307\u0304","\xC5":"A\u030A","\u01FA":"A\u030A\u0301","\u1E02":"B\u0307","\u0106":"C\u0301","\u010C":"C\u030C","\u0108":"C\u0302","\u010A":"C\u0307","\u010E":"D\u030C","\u1E0A":"D\u0307","\xC9":"E\u0301","\xC8":"E\u0300","\xCB":"E\u0308","\u1EBC":"E\u0303","\u0112":"E\u0304","\u1E16":"E\u0304\u0301","\u1E14":"E\u0304\u0300","\u0114":"E\u0306","\u011A":"E\u030C","\xCA":"E\u0302","\u1EBE":"E\u0302\u0301","\u1EC0":"E\u0302\u0300","\u1EC4":"E\u0302\u0303","\u0116":"E\u0307","\u1E1E":"F\u0307","\u01F4":"G\u0301","\u1E20":"G\u0304","\u011E":"G\u0306","\u01E6":"G\u030C","\u011C":"G\u0302","\u0120":"G\u0307","\u1E26":"H\u0308","\u021E":"H\u030C","\u0124":"H\u0302","\u1E22":"H\u0307","\xCD":"I\u0301","\xCC":"I\u0300","\xCF":"I\u0308","\u1E2E":"I\u0308\u0301","\u0128":"I\u0303","\u012A":"I\u0304","\u012C":"I\u0306","\u01CF":"I\u030C","\xCE":"I\u0302","\u0130":"I\u0307","\u0134":"J\u0302","\u1E30":"K\u0301","\u01E8":"K\u030C","\u0139":"L\u0301","\u013D":"L\u030C","\u1E3E":"M\u0301","\u1E40":"M\u0307","\u0143":"N\u0301","\u01F8":"N\u0300","\xD1":"N\u0303","\u0147":"N\u030C","\u1E44":"N\u0307","\xD3":"O\u0301","\xD2":"O\u0300","\xD6":"O\u0308","\u022A":"O\u0308\u0304","\xD5":"O\u0303","\u1E4C":"O\u0303\u0301","\u1E4E":"O\u0303\u0308","\u022C":"O\u0303\u0304","\u014C":"O\u0304","\u1E52":"O\u0304\u0301","\u1E50":"O\u0304\u0300","\u014E":"O\u0306","\u01D1":"O\u030C","\xD4":"O\u0302","\u1ED0":"O\u0302\u0301","\u1ED2":"O\u0302\u0300","\u1ED6":"O\u0302\u0303","\u022E":"O\u0307","\u0230":"O\u0307\u0304","\u0150":"O\u030B","\u1E54":"P\u0301","\u1E56":"P\u0307","\u0154":"R\u0301","\u0158":"R\u030C","\u1E58":"R\u0307","\u015A":"S\u0301","\u1E64":"S\u0301\u0307","\u0160":"S\u030C","\u1E66":"S\u030C\u0307","\u015C":"S\u0302","\u1E60":"S\u0307","\u0164":"T\u030C","\u1E6A":"T\u0307","\xDA":"U\u0301","\xD9":"U\u0300","\xDC":"U\u0308","\u01D7":"U\u0308\u0301","\u01DB":"U\u0308\u0300","\u01D5":"U\u0308\u0304","\u01D9":"U\u0308\u030C","\u0168":"U\u0303","\u1E78":"U\u0303\u0301","\u016A":"U\u0304","\u1E7A":"U\u0304\u0308","\u016C":"U\u0306","\u01D3":"U\u030C","\xDB":"U\u0302","\u016E":"U\u030A","\u0170":"U\u030B","\u1E7C":"V\u0303","\u1E82":"W\u0301","\u1E80":"W\u0300","\u1E84":"W\u0308","\u0174":"W\u0302","\u1E86":"W\u0307","\u1E8C":"X\u0308","\u1E8A":"X\u0307","\xDD":"Y\u0301","\u1EF2":"Y\u0300","\u0178":"Y\u0308","\u1EF8":"Y\u0303","\u0232":"Y\u0304","\u0176":"Y\u0302","\u1E8E":"Y\u0307","\u0179":"Z\u0301","\u017D":"Z\u030C","\u1E90":"Z\u0302","\u017B":"Z\u0307","\u03AC":"\u03B1\u0301","\u1F70":"\u03B1\u0300","\u1FB1":"\u03B1\u0304","\u1FB0":"\u03B1\u0306","\u03AD":"\u03B5\u0301","\u1F72":"\u03B5\u0300","\u03AE":"\u03B7\u0301","\u1F74":"\u03B7\u0300","\u03AF":"\u03B9\u0301","\u1F76":"\u03B9\u0300","\u03CA":"\u03B9\u0308","\u0390":"\u03B9\u0308\u0301","\u1FD2":"\u03B9\u0308\u0300","\u1FD1":"\u03B9\u0304","\u1FD0":"\u03B9\u0306","\u03CC":"\u03BF\u0301","\u1F78":"\u03BF\u0300","\u03CD":"\u03C5\u0301","\u1F7A":"\u03C5\u0300","\u03CB":"\u03C5\u0308","\u03B0":"\u03C5\u0308\u0301","\u1FE2":"\u03C5\u0308\u0300","\u1FE1":"\u03C5\u0304","\u1FE0":"\u03C5\u0306","\u03CE":"\u03C9\u0301","\u1F7C":"\u03C9\u0300","\u038E":"\u03A5\u0301","\u1FEA":"\u03A5\u0300","\u03AB":"\u03A5\u0308","\u1FE9":"\u03A5\u0304","\u1FE8":"\u03A5\u0306","\u038F":"\u03A9\u0301","\u1FFA":"\u03A9\u0300"};const mn=["bin","op","open","punct","rel"],pn=/([-+]?) *(\\d+(?:\\.\\d*)?|\\.\\d+) *([a-z]{2})/,un=/^ *\\\\text/;class dn{constructor(e,t,r=!1){this.mode="math",this.gullet=new nn(e,t,this.mode),this.settings=t,this.isPreamble=r,this.leftrightDepth=0,this.prevAtomType=""}expect(t,r=!0){if(this.fetch().text!==t)throw new e(`Expected \'${t}\', got \'${this.fetch().text}\'`,this.fetch());r&&this.consume()}consume(){this.nextToken=null}fetch(){return null==this.nextToken&&(this.nextToken=this.gullet.expandNextToken()),this.nextToken}switchMode(e){this.mode=e,this.gullet.switchMode(e)}parse(){this.gullet.beginGroup(),this.settings.colorIsTextColor&&this.gullet.macros.set("\\\\color","\\\\textcolor");const e=this.parseExpression(!1);if(this.expect("EOF"),this.isPreamble){const e=Object.create(null);return Object.entries(this.gullet.macros.current).forEach(([t,r])=>{e[t]=r}),this.gullet.endGroup(),e}const t=this.gullet.macros.get("\\\\df@tag");return this.gullet.endGroup(),t&&(this.gullet.macros.current["\\\\df@tag"]=t),e}static get endOfExpression(){return["}","\\\\endgroup","\\\\end","\\\\right","\\\\endtoggle","&"]}subparse(e){const t=this.nextToken;this.consume(),this.gullet.pushToken(new We("}")),this.gullet.pushTokens(e);const r=this.parseExpression(!1);return this.expect("}"),this.nextToken=t,r}parseExpression(e,t,r){const n=[];for(this.prevAtomType="";;){"math"===this.mode&&this.consumeSpaces();const s=this.fetch();if(-1!==dn.endOfExpression.indexOf(s.text))break;if(t&&s.text===t)break;if(r&&"\\\\middle"===s.text)break;if(e&&Xr[s.text]&&Xr[s.text].infix)break;const o=this.parseAtom(t);if(!o)break;"internal"!==o.type&&(n.push(o),this.prevAtomType="atom"===o.type?o.family:o.type)}return"text"===this.mode&&this.formLigatures(n),this.handleInfixNodes(n)}handleInfixNodes(t){let r,n=-1;for(let s=0;s=128||Kr.exec(r)))return null;if(this.settings.strict&&"math"===this.mode)throw new e(`Unicode text character "${r[0]}" used in math mode`,t);s={type:"textord",mode:"text",loc:Ve.range(t),text:r}}if(this.consume(),n)for(let r=0;r0&&n[0].type&&"array"===n[0].type&&n[0].addEqnNum)&&s.gullet.macros.get("\\\\df@tag")){if(!r.displayMode)throw new e("\\\\tag works only in display mode");s.gullet.feed("\\\\df@tag"),n=[{type:"tag",mode:"text",body:n,tag:s.parse()}]}return n},gn=[2,2,3,3];class fn{constructor(e){this.level=e.level,this.color=e.color,this.font=e.font||"",this.fontFamily=e.fontFamily||"",this.fontSize=e.fontSize||1,this.fontWeight=e.fontWeight||"",this.fontShape=e.fontShape||"",this.maxSize=e.maxSize}extend(e){const t={level:this.level,color:this.color,font:this.font,fontFamily:this.fontFamily,fontSize:this.fontSize,fontWeight:this.fontWeight,fontShape:this.fontShape,maxSize:this.maxSize};for(const r in e)Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return new fn(t)}withLevel(e){return this.extend({level:e})}incrementLevel(){return this.extend({level:Math.min(this.level+1,3)})}inSubOrSup(){return this.extend({level:gn[this.level]})}withColor(e){return this.extend({color:e})}withFont(e){return this.extend({font:e})}withTextFontFamily(e){return this.extend({fontFamily:e,font:""})}withFontSize(e){return this.extend({fontSize:e})}withTextFontWeight(e){return this.extend({fontWeight:e,font:""})}withTextFontShape(e){return this.extend({fontShape:e,font:""})}getColor(){return this.color}}function bn(e){const t={};let r=0;const n=document.getElementsByClassName("tml-eqn");for(let e of n)for(r+=1,e.setAttribute("id","tml-eqn-"+String(r));"mtable"!==e.tagName;){if(e.getElementsByClassName("tml-label").length>0){const n=e.attributes.id.value;t[n]=String(r);break}e=e.parentElement}const s=document.getElementsByClassName("tml-tageqn");for(const e of s){if(e.getElementsByClassName("tml-label").length>0){const r=e.getElementsByClassName("tml-tag");if(r.length>0){const n=e.attributes.id.value;t[n]=r[0].textContent}}}[...e.getElementsByClassName("tml-ref")].forEach(e=>{const r=e.getAttribute("href");let n=t[r.slice(1)];-1===e.className.indexOf("tml-eqref")?(n=n.replace(/^\\(/,""),n=n.replace(/\\)$/,"")):("("!==n.charAt(0)&&(n="("+n),")"!==n.slice(-1)&&(n+=")"));const s=document.createElementNS("http://www.w3.org/1998/Math/MathML","mtext");s.appendChild(document.createTextNode(n));const o=document.createElementNS("http://www.w3.org/1998/Math/MathML","math");o.appendChild(s),e.textContent="",e.appendChild(o)})}const yn=function(e,t,r){let n=r,s=0;const o=e.length;for(;ne.left.replace(/[-/\\\\^$*+?.()|[\\]{}]/g,"\\\\$&")).join("|")+")");for(;r=e.search(s),-1!==r;){r>0&&(n.push({type:"text",data:e.slice(0,r)}),e=e.slice(r));const s=t.findIndex(t=>e.startsWith(t.left));if(r=yn(t[s].right,e,t[s].left.length),-1===r)break;const o=e.slice(0,r+t[s].right.length),a=wn.test(o)?o:e.slice(t[s].left.length,r);n.push({type:"math",data:a,rawData:o,display:t[s].display}),e=e.slice(r+t[s].right.length)}return""!==e&&n.push({type:"text",data:e}),n}(t,r.delimiters);if(1===n.length&&"text"===n[0].type)return null;const s=document.createDocumentFragment();for(let t=0;t-1===e.indexOf(" "+t+" "))&&Tn(n,t)}}};let Sn=function(e,t,r={}){t.textContent="";const n="math"===t.tagName.toLowerCase();n&&(r.wrap="none");const s=On(e,r);n||s.children.length>1?(t.textContent="",s.children.forEach(e=>{t.appendChild(e.toNode())})):t.appendChild(s.toNode())};"undefined"!=typeof document&&"CSS1Compat"!==document.compatMode&&("undefined"!=typeof console&&console.warn("Warning: Temml doesn\'t work in quirks mode. Make sure your website has a suitable doctype."),Sn=function(){throw new e("Temml doesn\'t work in quirks mode.")});const On=function(t,r){const n=new u(r);try{const e=hn(t,n);return we(e,t,new fn({level:n.displayMode?Xe:Ze,maxSize:n.maxSize}),n)}catch(r){return function(t,r,n){if(n.throwOnError||!(t instanceof e))throw t;const s=new T(["temml-error"],[new S(r+"\\n\\n"+t.toString())]);return s.style.color=n.errorColor,s.style.whiteSpace="pre-line",s}(r,t,n)}};return{version:"0.14.0",render:Sn,renderToString:function(e,t){return On(e,t).toMarkup()},renderMathInElement:function(e,t){if(!e)throw new Error("No element provided to render");const r={};for(const e in t)Object.prototype.hasOwnProperty.call(t,e)&&(r[e]=t[e]);r.fences?r.delimiters=(e=>{if("$"===e||"("===e)return kn[e];if("$+"===e||"(+"===e)return kn[e.slice(0,1)].concat(vn);return"ams"===e?vn:"all"===e?kn["("].concat(kn.$).concat(vn):xn})(r.fences):r.delimiters=r.delimiters||xn,r.ignoredTags=r.ignoredTags||["script","noscript","style","textarea","pre","code","option"],r.ignoredClasses=r.ignoredClasses||[],r.errorCallback=r.errorCallback||console.error,r.macros=r.macros||Object.create(null),Tn(e,r),bn(e)},postProcess:bn,ParseError:e,definePreamble:function(e,t){const r=new u(t);if(r.macros=Object.create(null),!("string"==typeof e||e instanceof String))throw new TypeError("Temml can only parse string typed expression");const n=new dn(e,r,!0);delete n.gullet.macros.current["\\\\df@tag"];return n.parse()},__parse:function(e,t){const r=new u(t);return hn(e,r)},__renderToMathMLTree:On,__defineSymbol:G,__defineMacro:Qe}}();\nreturn temml;\n})()'); } }); // src/cli.js import { parseArgs } from "node:util"; import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, mkdirSync as mkdirSync3, existsSync as existsSync6, rmSync as rmSync6 } from "node:fs"; // src/assets.js var VERSION = "0.5.0"; var BASE_CSS = '/* Answer me with HTML base \u2014 uses theme variables only, never hard-coded colors (theme tokens and decorations: themes/.js). Exception: the var() fallbacks of diagrams (flow / sequence) equal the blueprint light tokens, guarded by a test. */\n*, *::before, *::after { box-sizing: border-box; }\nhtml, body { margin: 0; padding: 0; }\nbody {\n background: var(--bg); color: var(--ink);\n font-family: var(--font-sans); font-size: 14px; line-height: 1.55;\n -webkit-font-smoothing: antialiased;\n}\ncode, pre, kbd { font-family: var(--font-mono); }\n\n/* \u2500\u2500 Toolbar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.am-toolbar {\n position: fixed; top: 12px; inset-inline-end: 12px; z-index: 10; display: flex; gap: 6px;\n}\n.am-btn {\n font: 12px/1 var(--font-sans); color: var(--ink); background: var(--paper);\n border: 1px solid var(--line-2); border-radius: var(--radius); padding: 7px 10px; cursor: pointer;\n}\n.am-btn:hover { border-color: var(--ink-3); }\n.am-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n/* Theme and mode lists: a label and a native select inside one button-like box */\n.am-pick {\n display: flex; align-items: center; gap: 6px; font: 12px/1 var(--font-sans); color: var(--ink-2); background: var(--paper);\n border: 1px solid var(--line-2); border-radius: var(--radius); padding-block: 0; padding-inline: 10px 4px;\n}\n.am-pick:hover { border-color: var(--ink-3); }\n.am-pick:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }\n.am-pick select {\n font: inherit; color: var(--ink); background: transparent; border: 0; padding: 6px 2px; cursor: pointer; outline: none;\n}\n.am-pick option { color: var(--ink); background: var(--paper); }\n\n/* \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.am-head { margin: 0 0 20px; padding-inline-end: 300px; }\n.am-head h1 { margin: 0; font-size: 24px; line-height: 1.25; letter-spacing: -0.01em; }\n.am-sub { margin: 4px 0 0; color: var(--ink-2); }\n.am-head-meta { display: flex; flex-wrap: wrap; gap: 6px 18px; margin-top: 10px; font-size: 12px; color: var(--ink-2); }\n.am-head-meta b { font-family: var(--font-mono); font-weight: 400; color: var(--ink-3); margin-inline-end: 6px; }\n.am-intro { margin-top: 12px; max-width: 80ch; }\n\n/* \u2500\u2500 sheet template \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.am-sheet { max-width: 1680px; margin: 0 auto; padding: 32px 28px 40px; }\n.am-frame { position: relative; }\n.am-grid {\n display: grid; grid-template-columns: repeat(var(--cols, 3), minmax(0, 1fr));\n gap: 20px; align-items: start;\n}\n.am-ruler { display: none; }\n.am-ruler span { flex: 1; display: flex; align-items: center; justify-content: center; }\n.am-ruler--top, .am-ruler--bottom { left: 18px; right: 18px; height: 18px; }\n.am-ruler--top { top: 0; }\n.am-ruler--bottom { bottom: 0; }\n.am-ruler--left, .am-ruler--right { top: 18px; bottom: 18px; width: 18px; flex-direction: column; }\n.am-ruler--left { left: 0; }\n.am-ruler--right { right: 0; }\n.am-ruler--top span + span, .am-ruler--bottom span + span { border-inline-start: 1px solid var(--line); }\n.am-ruler--left span + span, .am-ruler--right span + span { border-top: 1px solid var(--line); }\n\n/* \u2500\u2500 Panels \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.am-panel {\n background: var(--paper); border: var(--bw) solid var(--line); border-radius: var(--radius);\n box-shadow: var(--shadow); min-width: 0; overflow: hidden;\n}\n.am-panel-head {\n display: flex; align-items: stretch; gap: 0; border-bottom: var(--bw) solid var(--line); min-height: 34px;\n}\n.am-panel-id {\n display: flex; align-items: center; justify-content: center; min-width: 34px; padding: 0 8px;\n background: var(--head-bg); color: var(--head-fg); font-weight: 600; font-size: 14px;\n}\n.am-panel-head h2 { margin: 0; padding: 7px 12px; font-size: 15px; font-weight: 600; flex: 1; display: flex; align-items: center; }\n.am-panel-meta { align-self: center; padding: 0 12px; font: 11px/1.3 var(--font-mono); color: var(--ink-2); text-align: end; }\n.am-panel-body { padding: 14px 16px 16px; }\n.am-panel-body > * + * { margin-top: 12px; }\n\n/* \u2500\u2500 Markdown body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.am-md > :first-child { margin-top: 0; }\n.am-md > :last-child { margin-bottom: 0; }\n.am-md p { margin: 0 0 8px; }\n.am-md ul, .am-md ol { margin: 0 0 8px; padding-inline-start: 20px; }\n.am-md li + li { margin-top: 3px; }\n.am-md h3, .am-md h4 { margin: 14px 0 6px; font-size: 13px; }\n.am-md a { color: var(--accent); }\n.am-md blockquote { margin: 0 0 8px; padding: 2px 12px; border-inline-start: 3px solid var(--line-2); color: var(--ink-2); }\n.am-md :not(pre) > code { font-size: 0.9em; background: var(--fill); padding: 1px 5px; border-radius: 4px; }\n.am-md hr { border: 0; border-top: 1px solid var(--line-2); margin: 12px 0; }\n.am-md table { width: 100%; border-collapse: collapse; font-size: 13px; }\n.am-md th {\n text-align: start; font: 11px/1.3 var(--font-mono); color: var(--ink-2); font-weight: 400;\n padding: 6px 10px; border-bottom: 1px solid var(--line-2);\n}\n/* th { text-align: start } outranks the align attribute from Markdown alignment, so restore right and center columns explicitly. */\n.am-md th[align="right"] { text-align: right; }\n.am-md th[align="center"] { text-align: center; }\n.am-md td { padding: 7px 10px; border-bottom: 1px solid var(--line-2); vertical-align: top; }\n.am-md tbody tr:nth-child(even) td { background: var(--fill); }\n.am-table-wrap { overflow-x: auto; }\n/* Images scale down to the panel and never past 70% of the window height, keeping their shape. */\n.am-md img { max-width: 100%; max-height: 70vh; height: auto; }\n.am-figure { margin: 0; text-align: center; }\n.am-figure img { display: block; margin: 0 auto; border: 1px solid var(--line-2); border-radius: var(--radius); }\n.am-figure figcaption { margin-top: 6px; font-size: 12px; line-height: 1.4; color: var(--ink-2); }\n.am-code {\n margin: 0; padding: 12px 14px; background: var(--fill); border: 1px solid var(--line-2);\n border-radius: var(--radius); overflow-x: auto; font-size: 12.5px; line-height: 1.5;\n}\n/* Ask: a decision the reader makes; the suggested option starts selected */\n.am-ask { margin: 10px 0; padding: 10px 12px 8px; border: 1px solid var(--line-2); border-radius: var(--radius); background: var(--fill); min-width: 0; }\n/* The question floats inside the box, so a long question wraps like text instead of riding on the border */\n.am-ask-q { float: left; width: 100%; padding: 0; margin-bottom: 4px; font-weight: 600; }\n/* Two columns: the control, then the label, tag and note, which wrap under the label and never under the control */\n.am-ask-opt { clear: both; display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: 8px; align-items: baseline; padding: 5px 2px; cursor: pointer; }\n.am-ask-opt input { margin: 0; accent-color: var(--accent); position: relative; top: 2px; }\n.am-ask-tag { display: inline-block; font-size: 11px; line-height: 1.5; color: var(--accent); border: 1px solid var(--accent); border-radius: 999px; padding: 0 6px; white-space: nowrap; }\n.am-ask-note { display: block; color: var(--ink-2); }\n/* Comment box under each panel, and the Reply sheet */\n.am-comment-btn {\n display: flex; flex-shrink: 0; align-self: center; margin-block: 0; margin-inline: auto 6px; color: var(--ink-3); background: transparent;\n border: 1px solid transparent; border-radius: var(--radius); padding: 3px; cursor: pointer;\n}\n.am-panel-meta + .am-comment-btn { margin-inline-start: 0; }\n.am-panel:hover .am-comment-btn, .am-comment-btn:focus-visible, .am-comment-btn[aria-expanded="true"] { border-color: var(--line-2); color: var(--ink-2); }\n.am-panel .am-comment-btn.am-comment-btn--on { color: var(--accent); border-color: var(--accent); }\n.am-panel--bare { position: relative; }\n.am-panel--bare > .am-comment-btn { position: absolute; top: 4px; inset-inline-end: 0; }\n.am-comment { padding: 0 14px 12px; }\n.am-comment textarea, .am-reply textarea {\n width: 100%; font: 13px/1.5 var(--font-sans); color: var(--ink); background: var(--paper);\n border: 1px solid var(--line-2); border-radius: var(--radius); padding: 8px 10px; resize: vertical;\n}\n.am-comment textarea:focus-visible, .am-reply textarea:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }\n.am-btn--reply { color: var(--paper); background: var(--ink); border-color: var(--ink); }\n.am-btn--reply:hover { opacity: .88; }\n.am-reply {\n width: min(640px, calc(100vw - 32px)); padding: 16px; color: var(--ink); background: var(--paper);\n border: 1px solid var(--line-2); border-radius: var(--radius); box-shadow: 0 12px 40px rgba(0,0,0,.25);\n}\n.am-reply::backdrop { background: rgba(0,0,0,.35); }\n.am-reply-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; margin-bottom: 10px; }\n.am-reply-head span { color: var(--ink-2); font-size: 13px; }\n.am-reply textarea { font-family: var(--font-mono); font-size: 12.5px; }\n.am-reply-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 10px; }\n.am-reply .am-btn:disabled { opacity: .5; cursor: default; }\n/* Code block: a header (title or path:lines, language, copy), then one span per line; numbered blocks show the line number in a gutter */\n.am-codeblock { margin: 10px 0; min-width: 0; }\n.am-codeblock .am-code { border-top-left-radius: 0; border-top-right-radius: 0; padding: 10px 0; }\n.am-code-head {\n display: flex; align-items: center; gap: 8px; padding: 5px 6px 5px 12px; font: 12px/1.3 var(--font-sans); color: var(--ink-2);\n background: var(--paper); border: 1px solid var(--line-2); border-bottom: 0; border-radius: var(--radius) var(--radius) 0 0;\n}\n.am-code-title { font-family: var(--font-mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }\n.am-code-lang { margin-left: auto; color: var(--ink-3); text-transform: lowercase; }\n.am-code-copy { font: 12px/1 var(--font-sans); color: var(--ink-2); background: transparent; border: 1px solid var(--line-2); border-radius: var(--radius); padding: 4px 8px; cursor: pointer; margin-left: auto; }\n.am-code-lang + .am-code-copy { margin-left: 0; }\n.am-code-copy:hover { border-color: var(--ink-3); color: var(--ink); }\n.am-code-copy:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n.am-code code { display: block; min-width: max-content; }\n.am-ln { display: block; padding: 0 14px; min-height: 1.5em; white-space: pre; }\n.am-ln--hl { background: var(--accent-bg); box-shadow: inset 3px 0 0 var(--accent); }\n.am-code--num .am-ln::before {\n content: attr(data-n); display: inline-block; width: 3.5ch; margin-right: 12px; text-align: right; color: var(--ink-3);\n user-select: none; -webkit-user-select: none;\n}\n\n/* \u2500\u2500 Status badges ok / no / warn \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.am-status { white-space: nowrap; font-weight: 500; }\n.am-status--ok { color: var(--ok); }\n.am-status--no { color: var(--err); }\n.am-status--warn { color: var(--warn); }\n.am-status-icon { display: inline-block; width: 1.1em; font-weight: 700; }\n\n/* \u2500\u2500 callout \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.am-callout {\n border: 1px solid var(--line-2); border-inline-start: 3px solid var(--accent); background: var(--accent-bg);\n padding: 10px 14px; border-radius: var(--radius);\n}\n.am-callout--ok { border-inline-start-color: var(--ok); background: var(--ok-bg); }\n.am-callout--warn { border-inline-start-color: var(--warn); background: var(--warn-bg); }\n.am-callout--err { border-inline-start-color: var(--err); background: var(--err-bg); }\n.am-callout-title { font-weight: 600; margin-bottom: 4px; }\n\n/* \u2500\u2500 kv title block \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.am-kv {\n display: grid; grid-template-columns: repeat(var(--kv-cols, 2), minmax(0, 1fr)); margin: 0;\n border-top: var(--bw) solid var(--line); border-inline-start: var(--bw) solid var(--line);\n}\n.am-kv-cell { border-inline-end: var(--bw) solid var(--line); border-bottom: var(--bw) solid var(--line); padding: 6px 10px 8px; min-width: 0; }\n.am-kv-cell--wide { grid-column: 1 / -1; }\n.am-kv dt { font: 11px/1.4 var(--font-mono); color: var(--ink-2); }\n.am-kv dd { margin: 2px 0 0; font-size: 14px; font-weight: 500; overflow-wrap: anywhere; }\n.am-kv-cell--wide dd { font-size: 17px; font-weight: 600; }\n\n/* \u2500\u2500 timeline \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.am-timeline { list-style: none; margin: 0; padding: 0; }\n.am-timeline--h { display: grid; grid-template-columns: repeat(var(--n, 1), minmax(0, 1fr)); padding-top: 4px; }\n.am-timeline--h li { position: relative; text-align: center; padding: 0 6px; }\n.am-timeline--h li::before {\n content: ""; position: absolute; top: 31px; left: 0; right: 0; border-top: var(--bw) solid var(--line);\n}\n.am-timeline--h li:first-child::before { inset-inline-start: 50%; }\n.am-timeline--h li:last-child::before { inset-inline-end: 50%; }\n.am-tl-when { display: block; font-size: 15px; font-weight: 500; height: 24px; }\n.am-tl-dot {\n position: relative; display: block; width: 11px; height: 11px; margin: 2px auto 8px;\n border: var(--bw) solid var(--line); border-radius: 50%; background: var(--paper);\n}\n.am-tl-item--hi .am-tl-dot { background: var(--accent); border-color: var(--accent); }\n.am-tl-title { display: block; font-size: 12.5px; font-weight: 500; }\n.am-tl-text { display: block; font-size: 12px; color: var(--ink-2); line-height: 1.45; }\n.am-timeline--v li { position: relative; padding: 0 0 14px; padding-inline-start: 22px; }\n.am-timeline--v li::before { content: ""; position: absolute; inset-inline-start: 5px; top: 6px; bottom: -6px; border-inline-start: var(--bw) solid var(--line-2); }\n.am-timeline--v li:last-child::before { display: none; }\n.am-timeline--v .am-tl-dot { position: absolute; inset-inline-start: 0; top: 4px; margin: 0; }\n.am-timeline--v .am-tl-when { display: inline; height: auto; font: 12px var(--font-mono); color: var(--ink-2); margin-inline-end: 8px; }\n.am-timeline--v .am-tl-title { display: inline; font-size: 14px; }\n.am-timeline--v .am-tl-text { margin-top: 2px; }\n/* .am-tl-wrap is the container (src/components/timeline.js): a horizontal timeline answers to the width it has, not to the width of the window, since a\n sheet puts panels two or three to a row. Below 560 px (about 90 px for each of the six items a timeline has by default) its dates\n wrap and the dots cover them, so it lays out like timeline v: line and dots at the start, text beside them. */\n.am-tl-wrap { container-type: inline-size; }\n@container (max-width: 560px) {\n .am-timeline--h { display: block; padding-top: 0; }\n .am-timeline--h li { text-align: start; padding: 0 0 14px; padding-inline-start: 22px; }\n .am-timeline--h li::before, .am-timeline--h li:first-child::before {\n top: 6px; bottom: -6px; inset-inline-start: 5px; inset-inline-end: auto; border-top: 0; border-inline-start: var(--bw) solid var(--line-2);\n }\n .am-timeline--h li:last-child::before { display: none; }\n .am-timeline--h .am-tl-dot { position: absolute; inset-inline-start: 0; top: 4px; margin: 0; }\n .am-timeline--h .am-tl-when { display: inline; height: auto; font: 12px var(--font-mono); color: var(--ink-2); margin-inline-end: 8px; }\n .am-timeline--h .am-tl-title { display: inline; font-size: 14px; }\n .am-timeline--h .am-tl-text { margin-top: 2px; }\n}\n\n/* \u2500\u2500 annot sentence notes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.am-annot + .am-annot { border-top: 1px solid var(--line-2); padding-top: 12px; }\n.am-annot-head { display: flex; justify-content: space-between; gap: 12px; font-size: 13px; font-weight: 600; margin-bottom: 8px; }\n.am-annot-meta { font: 11px var(--font-mono); font-weight: 400; color: var(--ink-2); }\n.am-annot-scroll { overflow-x: auto; }\n.am-annot-line {\n position: relative; display: inline-block; white-space: pre; font: 14px/1.6 var(--font-mono);\n padding-bottom: calc(var(--rows, 0) * 17px + 14px);\n}\n.am-annot-line--wrap { display: block; white-space: normal; padding-bottom: 10px; }\n.am-annot-line--wrap .am-seg { white-space: nowrap; }\n.am-seg { position: relative; }\n.am-seg::after {\n content: ""; position: absolute; left: 1px; right: 1px; top: calc(100% + 1px); height: 5px;\n border: 1px solid var(--accent); border-top: 0;\n}\n.am-seg-n {\n position: absolute; inset-inline-start: 0; top: calc(100% + 8px + var(--row, 0) * 17px);\n font: 11px/16px var(--font-sans); color: var(--accent); white-space: nowrap;\n}\n.am-seg--err { color: var(--err); }\n.am-seg--err::after { border-color: var(--err); }\n.am-seg--err .am-seg-n { color: var(--err); }\n.am-annot-caption { font-size: 12px; color: var(--ink-2); }\n\n/* \u2500\u2500 limits bars \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.am-lim + .am-lim { margin-top: 14px; }\n.am-lim-head { display: flex; justify-content: space-between; gap: 12px; font-size: 13px; margin-bottom: 4px; }\n.am-lim-val { font: 12px var(--font-mono); color: var(--accent); white-space: nowrap; }\n.am-lim-track { position: relative; height: 12px; border: 1px solid var(--line-2); background: var(--fill); border-radius: calc(var(--radius) / 2); }\n.am-lim-fill { position: absolute; inset-inline-start: 0; top: 0; bottom: 0; background: var(--accent-bg); border-inline-end: 1px solid var(--accent); }\n.am-lim-mark { position: absolute; top: -4px; bottom: -4px; border-left: 2px solid var(--accent); }\n.am-lim.is-over .am-lim-fill { background: var(--err-bg); border-inline-end-color: var(--err); }\n.am-lim.is-over .am-lim-val { color: var(--err); }\n.am-lim-ticks { position: relative; height: 16px; font: 10px/16px var(--font-mono); color: var(--ink-3); }\n.am-lim-ticks span { position: absolute; transform: translateX(-50%); }\n.am-lim-ticks span:first-child { transform: none; }\n.am-lim-note { font-size: 11px; color: var(--ink-2); margin-inline-start: 6px; }\n\n/* \u2500\u2500 tree \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.am-tree { font-size: 13px; }\n.am-tree-root { display: flex; justify-content: center; position: relative; padding-bottom: 18px; }\n.am-tree-root::after { content: ""; position: absolute; bottom: 0; left: 50%; height: 18px; border-left: var(--bw) solid var(--line); }\n.am-tree-root--solo { padding-bottom: 12px; }\n.am-tree-root--solo::after { display: none; }\n.am-tree-box {\n border: var(--bw) solid var(--line); background: var(--paper); padding: 6px 14px; text-align: center;\n border-radius: var(--radius); font-weight: 600;\n}\n.am-tree-box small { display: block; font-weight: 400; color: var(--ink-2); font-size: 12px; }\n.am-tree-box--root { background: var(--accent-bg); font-size: 15px; padding: 8px 28px; }\n.am-tree-cols { display: grid; grid-template-columns: repeat(var(--n, 1), minmax(0, 1fr)); }\n.am-tree-col { position: relative; padding: 18px 8px 0; min-width: 0; }\n.am-tree-col::before { content: ""; position: absolute; top: 0; left: 0; right: 0; border-top: var(--bw) solid var(--line); }\n.am-tree-col:first-child::before { inset-inline-start: 50%; }\n.am-tree-col:last-child::before { inset-inline-end: 50%; }\n.am-tree-col::after { content: ""; position: absolute; top: 0; left: 50%; height: 18px; border-left: var(--bw) solid var(--line); }\n.am-tree-list, .am-tree-list ul { list-style: none; margin: 0; padding: 0; }\n.am-tree-col > .am-tree-list { margin: 8px 0 0; margin-inline-start: 14px; }\n.am-tree-list ul { margin-inline-start: 16px; }\n.am-tree-list li { position: relative; padding: 3px 0; padding-inline-start: 18px; }\n.am-tree-list li::before { content: ""; position: absolute; inset-inline-start: 0; top: 0.95em; width: 12px; border-top: 1px solid var(--ink-3); }\n.am-tree-list li::after { content: ""; position: absolute; inset-inline-start: 0; top: 0; bottom: 0; border-inline-start: 1px solid var(--ink-3); }\n.am-tree-list li:last-child::after { bottom: auto; height: 0.95em; }\n.am-tree-tag { font: 11px var(--font-mono); color: var(--ink-3); margin-inline-end: 4px; }\n.am-tree code { font: 12px var(--font-mono); }\n.am-tree-sub { display: block; font-size: 11.5px; color: var(--ink-2); }\n.am-tree-hi > .am-tree-label { color: var(--accent); font-weight: 600; }\n\n/* \u2500\u2500 Diagrams (flow / sequence) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.am-diagram { margin: 0; overflow-x: auto; text-align: center; }\n/* Embedded previews can lose html[data-theme]; the fallbacks keep diagrams readable, and defined theme variables still win. */\n.am-diagram svg { max-width: 100%; height: auto; font-family: var(--font-sans, sans-serif); }\n.am-diagram text { fill: var(--ink, #16181d); font-size: 13px; }\n.am-node-shape { fill: var(--paper, #ffffff); stroke: var(--line, #1d2026); stroke-width: var(--bw, 1.5px); }\n.am-node--hi .am-node-shape { fill: var(--accent-bg, #e4ecf8); stroke: var(--accent, #1d5fbf); }\n.am-node--hi text { fill: var(--accent, #1d5fbf); font-weight: 600; }\n.am-edge { fill: none; stroke: var(--ink-2, #4b5260); stroke-width: 1.3; }\n.am-edge--dashed { stroke-dasharray: 5 4; }\n.am-arrow { fill: var(--ink-2, #4b5260); }\n.am-edge-label rect { fill: var(--paper, #ffffff); }\n.am-diagram .am-edge-label text { fill: var(--accent, #1d5fbf); font-size: 11.5px; }\n.am-cluster { fill: var(--fill, #f3f5f8); stroke: var(--line-2, #d6dae1); stroke-width: 1; stroke-dasharray: 4 3; }\n.am-diagram .am-cluster-label { fill: var(--ink-2, #4b5260); font: 11px var(--font-mono, monospace); }\n.am-lifeline { stroke: var(--ink-3, #8b929e); stroke-width: 1; stroke-dasharray: 4 4; }\n.am-actor { fill: var(--paper, #ffffff); stroke: var(--line, #1d2026); stroke-width: var(--bw, 1.5px); }\n.am-note { fill: var(--warn-bg, #fdf3e2); stroke: var(--warn, #a8620a); stroke-width: 1; }\n.am-diagram .am-step { fill: var(--ink-3, #8b929e); font: 10px var(--font-mono, monospace); }\n\n.am-diagram-expand {\n /* In flow and sticky: it sits at the end edge (right on a left-to-right page) and stays in view when the diagram scrolls sideways.\n It takes its own row above the drawing, so it never covers a node at the top-right corner of the drawing. */\n position: sticky; inset-inline-start: calc(100% - 36px); z-index: 2;\n display: flex; align-items: center; justify-content: center;\n width: 28px; height: 28px; padding: 0; margin: 0 0 4px; margin-inline: auto 8px;\n color: var(--ink-2); background: var(--paper);\n border: var(--bw, 1px) solid var(--line-2); border-radius: var(--radius);\n cursor: pointer; opacity: 0.72;\n transition: opacity 0.15s, color 0.15s, border-color 0.15s, background 0.15s;\n}\n.am-diagram:hover .am-diagram-expand,\n.am-diagram-expand:focus-visible { opacity: 1; }\n.am-diagram-expand:hover {\n opacity: 1; color: var(--ink); border-color: var(--accent); background: var(--fill);\n}\n.am-diagram-expand:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }\n\n/* \u2500\u2500 Diagram Lightbox and Pan-Zoom Viewer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.am-lightbox {\n position: fixed; inset: 0; z-index: 1000;\n display: flex; flex-direction: column;\n touch-action: none;\n}\n.am-lightbox[hidden] { display: none !important; }\n\n.am-lightbox-backdrop {\n position: absolute; inset: 0;\n background: rgba(0, 0, 0, 0.72);\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n}\n\n.am-lightbox-header {\n position: absolute; top: 12px; left: 24px; right: 24px; height: 36px;\n display: flex; align-items: center; justify-content: space-between;\n z-index: 10; pointer-events: none;\n}\n.am-lightbox-title {\n display: inline-flex; align-items: center; gap: 8px;\n color: rgba(255, 255, 255, 0.92);\n font: 13px/1 var(--font-sans, sans-serif); font-weight: 500;\n text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);\n pointer-events: auto;\n overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 60vw;\n}\n.am-lightbox-title svg { stroke: rgba(255, 255, 255, 0.85); flex-shrink: 0; }\n.am-lightbox-actions {\n display: flex; align-items: center; gap: 8px; pointer-events: auto;\n}\n.am-lightbox-close {\n width: 32px; height: 32px;\n background: var(--paper);\n border: var(--bw, 1px) solid var(--line-2);\n border-radius: 50%;\n color: var(--ink);\n font-size: 15px; line-height: 1;\n cursor: pointer;\n display: flex; align-items: center; justify-content: center;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);\n transition: background 0.15s, color 0.15s, transform 0.15s, border-color 0.15s;\n}\n.am-lightbox-close:hover {\n color: var(--accent); border-color: var(--accent); background: var(--fill); transform: scale(1.05);\n}\n.am-lightbox-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }\n\n.am-lightbox-stage {\n position: absolute;\n top: 54px; bottom: 20px; left: 24px; right: 24px;\n background: var(--paper);\n border: var(--bw, 1px) solid var(--line-2);\n border-radius: max(var(--radius), 14px);\n box-shadow: 0 24px 64px rgba(0, 0, 0, 0.45);\n overflow: hidden;\n cursor: grab;\n user-select: none;\n -webkit-user-select: none;\n}\n.am-lightbox-stage.am-panning { cursor: grabbing; }\n\n.am-lightbox-canvas {\n position: absolute; left: 0; top: 0;\n transform-origin: 0 0;\n will-change: transform;\n pointer-events: none;\n}\n.am-lightbox-canvas svg {\n max-width: none !important;\n max-height: none !important;\n display: block;\n font-family: var(--font-sans, sans-serif);\n}\n\n/* \u2500\u2500 doc template \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.am-doc { max-width: 1120px; margin: 0 auto; padding: 40px 28px 64px; }\n.am-doc-layout { display: grid; grid-template-columns: 200px minmax(0, 1fr); gap: 32px; align-items: start; }\n.am-doc-layout--notoc { grid-template-columns: minmax(0, 1fr); max-width: 860px; }\n.am-toc { position: sticky; top: 24px; font-size: 13px; }\n.am-toc a { display: block; color: var(--ink-2); text-decoration: none; padding: 4px 0; padding-inline-start: 10px; border-inline-start: 2px solid var(--line-2); }\n.am-toc a:hover { color: var(--ink); border-inline-start-color: var(--accent); }\n.am-doc-body > .am-panel + .am-panel { margin-top: 20px; }\n\n/* \u2500\u2500 Responsive and print \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n@media (max-width: 1100px) {\n .am-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }\n .am-grid > .am-panel { grid-column: auto !important; }\n .am-grid > .am-panel.am-span-wide { grid-column: 1 / -1 !important; }\n}\n@media (max-width: 760px) {\n .am-sheet, .am-doc { padding: 56px 12px 24px; }\n .am-head { padding-inline-end: 0; }\n .am-grid { grid-template-columns: minmax(0, 1fr); }\n .am-grid > .am-panel.am-span-wide { grid-column: auto !important; }\n .am-doc-layout { grid-template-columns: minmax(0, 1fr); }\n .am-toc { position: static; }\n .am-tree-cols { grid-template-columns: minmax(0, 1fr); }\n /* Tables and diagrams keep their size: the wrapper scrolls horizontally when they overflow. A cell is never narrower than about\n two short words, so a phone does not stack them one per line. On wider screens the sheet layout (src/runtime/layout-dom.js)\n gives a table panel the width its columns read well at. */\n .am-md th, .am-md td { min-width: 8em; }\n .am-diagram svg { max-width: none; }\n .am-lightbox-header { top: 8px; left: 12px; right: 12px; }\n .am-lightbox-stage { top: 46px; bottom: 12px; left: 12px; right: 12px; border-radius: max(var(--radius), 10px); }\n}\n.am-colophon { text-align: center; padding: 0 0 28px; font: 11px var(--font-mono); color: var(--ink-3); }\n.am-colophon a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }\n\n@media print {\n .am-toolbar, .am-diagram-expand, .am-lightbox, .am-code-copy, .am-comment-btn, .am-comment, .am-reply { display: none !important; }\n body { background: var(--paper); }\n .am-panel { break-inside: avoid; box-shadow: none; }\n}\n/* Printed on paper narrower than three columns, the two-column grid left a hole beside every panel next to a full-width one\n (a wide table, a wide diagram): one panel per row instead. */\n@media print and (max-width: 1100px) {\n .am-grid { grid-template-columns: minmax(0, 1fr); }\n .am-grid > .am-panel { grid-column: auto !important; }\n}\n.am-panel--bare { border: 0; background: transparent; box-shadow: none; }\n.am-panel--bare > .am-panel-body { padding: 0; }\n.am-panel--bare .am-kv { background: var(--paper); }\n\n/* Remark mode: block marks with a note, collected into the Reply copy (src/runtime/remark.js). Each kind borrows a status colour of\n the theme, so a mark reads in light and dark mode on every theme. */\n.am-btn--remark.is-on { background: var(--ink); border-color: var(--ink); color: var(--paper); }\n.am-remark-hover { outline: 1px dashed var(--ink-3); outline-offset: 2px; cursor: pointer; }\n:is(.am-mark, .am-remark-kind)[data-kind="suggestion"] { --mark: var(--warn); --mark-bg: var(--warn-bg); }\n:is(.am-mark, .am-remark-kind)[data-kind="keep"] { --mark: var(--ok); --mark-bg: var(--ok-bg); }\n:is(.am-mark, .am-remark-kind)[data-kind="question"] { --mark: var(--accent); --mark-bg: var(--accent-bg); }\n:is(.am-mark, .am-remark-kind)[data-kind="concern"] { --mark: var(--err); --mark-bg: var(--err-bg); }\n.am-mark { border-inline-start: 3px solid var(--mark); padding-inline-start: 10px; background: var(--mark-bg); border-radius: 1px; }\n/* The note chip sits inside the marked block (a table takes it as its caption, a list item puts it before its sub-list), so lists and\n grids keep their children. */\n.am-mark-note {\n display: block; margin-block-start: 4px; font: 12px/1.5 var(--font-sans); letter-spacing: normal; text-transform: none;\n color: var(--ink-2); white-space: pre-wrap; caption-side: bottom; text-align: start;\n}\n.am-mark-note b { color: var(--ink); font-weight: 600; }\ncaption.am-mark-note { display: table-caption; }\n/* Table rows have their own backgrounds, so a tint on the table would show on every other row only: the bar marks a table. */\ntable.am-mark { background: none; }\n.am-remark-pop {\n position: absolute; z-index: 40; width: min(264px, calc(100vw - 16px)); padding: 8px; display: grid; gap: 6px;\n color: var(--ink); background: var(--paper); border: 1px solid var(--line-2); border-radius: var(--radius); box-shadow: 0 6px 24px rgba(0, 0, 0, .14);\n}\n.am-remark-pop[hidden] { display: none; }\n.am-remark-kinds { display: flex; gap: 6px; flex-wrap: wrap; }\n.am-remark-kind { font: 12px/1 var(--font-sans); color: var(--ink-2); background: var(--paper); border: 1px solid var(--line-2); border-radius: 999px; padding: 4px 10px; cursor: pointer; }\n.am-remark-kind:hover { border-color: var(--ink-3); }\n.am-remark-kind[aria-pressed="true"] { color: var(--ink); border-color: var(--mark); background: var(--mark-bg); }\n.am-remark-kind:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }\n.am-remark-pop textarea { font: 13px/1.5 var(--font-sans); color: var(--ink); background: var(--paper); border: 1px solid var(--line-2); border-radius: var(--radius); padding: 6px 8px; resize: vertical; }\n.am-remark-actions { display: flex; gap: 6px; }\n.am-remark-actions .am-btn:disabled { opacity: .5; cursor: default; }\n@media print { .am-remark-pop { display: none !important; } }\n'; var RUNTIME_JS = "(() => {\n// How a formula reads in a quote. MathML has no plain text, so src/math.js writes the formula as the draft wrote it, delimiters included, in\n// data-am-tex on the element, and the Remark and Reply scripts read page text through this file. On a page without math there is no\n// such element, so formulaOf is null and every text comes out as it did before.\n// A plain ES module for tests; the page gets it with its `export` keyword dropped (src/runtime/compose.js).\n\nconst TEX = 'data-am-tex';\n\n// The formula that holds a node, or null.\nconst formulaOf = (node) => node.parentElement?.closest(`[${TEX}]`) ?? null;\n\nconst firstText = (el) => {\n for (const child of el.childNodes) {\n const found = child.nodeType === 3 ? child : firstText(child);\n if (found) return found;\n }\n return null;\n};\n\n// What a text node adds to a quote: its data. The first text node of a formula adds the formula, and the others add nothing.\n// (A formula in an error style is plain text with its delimiters, not a , so it comes out once.)\nfunction nodeText(node) {\n const math = formulaOf(node);\n if (!math) return node.data;\n return firstText(math) === node ? math.getAttribute(TEX) : '';\n}\n\n// The text of a node as textContent reads it, but with each formula as written.\nconst textWithMath = (node) => (node.nodeType === 3 ? nodeText(node) : [...node.childNodes].map(textWithMath).join(''));\n\n(() => {\n const root = document.documentElement;\n // A host that serves this page inside its own document leaves the real root without the page's settings. The toolbar keeps a copy;\n // set back what the root lacks, before anything below reads the root. A root's own value, even an empty one, is never replaced.\n const carrier = document.querySelector('.am-toolbar');\n for (const [attr, key] of [['lang', 'data-am-root-lang'], ['dir', 'data-am-root-dir'], ['data-theme', 'data-am-root-theme'], ['data-mode', 'data-am-root-mode'], ['data-style', 'data-am-root-style']]) {\n if (root.getAttribute(attr) === null && carrier?.getAttribute(key) != null) root.setAttribute(attr, carrier.getAttribute(key));\n }\n // Each toolbar list sets one root attribute; its options name the values, so the runtime names no theme or mode.\n for (const [name, attr] of [['theme', 'data-theme'], ['mode', 'data-mode']]) {\n const select = document.querySelector(`select[data-am=\"${name}\"]`);\n if (!select) continue;\n select.value = root.getAttribute(attr);\n select.addEventListener('change', () => root.setAttribute(attr, select.value));\n }\n\n const copyText = async (text) => {\n try {\n await navigator.clipboard.writeText(text);\n } catch {\n const ta = Object.assign(document.createElement('textarea'), { value: text });\n document.body.append(ta);\n ta.select();\n document.execCommand('copy');\n ta.remove();\n }\n };\n // The button reads its label from data-done for a moment after a copy.\n const flash = (btn) => {\n const original = btn.textContent;\n btn.textContent = btn.dataset.done;\n setTimeout(() => { btn.textContent = original; }, 1400);\n };\n\n const copyBtn = document.querySelector('[data-am=\"copy\"]');\n copyBtn?.addEventListener('click', async () => {\n const nodes = document.querySelectorAll('#am-source');\n await copyText(nodes[nodes.length - 1]?.value ?? '');\n flash(copyBtn);\n });\n\n // Each code block copies its own lines, without the line numbers.\n for (const btn of document.querySelectorAll('[data-am=\"copy-code\"]')) {\n btn.addEventListener('click', async () => {\n const lines = btn.closest('.am-codeblock')?.querySelectorAll('.am-ln') ?? [];\n await copyText([...lines].map((l) => l.textContent).join('\\n'));\n flash(btn);\n });\n }\n\n // \u2500\u2500 Diagram Lightbox & Pan-Zoom Viewer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const diagrams = document.querySelectorAll('.am-diagram:not(.am-lightbox-canvas)');\n const lb = document.querySelector('.am-lightbox');\n if (diagrams.length && lb) {\n const backdrop = lb.querySelector('.am-lightbox-backdrop');\n const titleText = lb.querySelector('.am-lightbox-title-text');\n const stage = lb.querySelector('.am-lightbox-stage');\n const canvas = lb.querySelector('.am-lightbox-canvas');\n const closeBtn = lb.querySelector('.am-lightbox-close');\n const expandLabel = lb.getAttribute('data-expand') || 'Expand diagram';\n\n let scale = 1, x = 0, y = 0, fitScale = 1, curVw = 800, curVh = 600;\n let isDragging = false, activePointerId = null, startX = 0, startY = 0, origX = 0, origY = 0;\n let lastTrigger = null;\n\n const apply = () => {\n canvas.style.transform = `translate3d(${x.toFixed(2)}px, ${y.toFixed(2)}px, 0) scale(${scale.toFixed(4)})`;\n };\n\n const zoomTo = (newScale, pivotX, pivotY) => {\n newScale = Math.max(0.2, Math.min(6.0, newScale));\n if (Math.abs(newScale - scale) < 0.0001) return;\n const r = newScale / scale;\n x = pivotX - (pivotX - x) * r;\n y = pivotY - (pivotY - y) * r;\n scale = newScale;\n apply();\n };\n\n const open = (diag, trigger) => {\n const svg = [...diag.children].find((el) => el.tagName.toLowerCase() === 'svg');\n if (!svg) return;\n lastTrigger = trigger;\n\n const panel = diag.closest('.am-panel');\n const panelTitle = panel?.querySelector('.am-panel-head h2')?.textContent?.trim() || panel?.querySelector('h2')?.textContent?.trim() || lb.getAttribute('aria-label') || 'Diagram viewer';\n if (titleText) titleText.textContent = panelTitle;\n\n const clone = svg.cloneNode(true);\n const defElements = clone.querySelectorAll('[id]');\n if (defElements.length) {\n const idMap = new Map();\n defElements.forEach((el, i) => {\n const oldId = el.id;\n const newId = `${oldId}-lb-${i}`;\n idMap.set(oldId, newId);\n el.id = newId;\n });\n const urlAttrs = ['marker-start', 'marker-mid', 'marker-end', 'fill', 'stroke', 'filter', 'clip-path', 'mask'];\n clone.querySelectorAll('*').forEach((el) => {\n for (const attr of urlAttrs) {\n const val = el.getAttribute(attr);\n if (val && val.startsWith('url(#')) {\n const id = val.slice(5, -1);\n if (idMap.has(id)) el.setAttribute(attr, `url(#${idMap.get(id)})`);\n }\n }\n const href = el.getAttribute('href') || el.getAttribute('xlink:href');\n if (href && href.startsWith('#')) {\n const id = href.slice(1);\n if (idMap.has(id)) {\n if (el.hasAttribute('href')) el.setAttribute('href', `#${idMap.get(id)}`);\n if (el.hasAttribute('xlink:href')) el.setAttribute('xlink:href', `#${idMap.get(id)}`);\n }\n }\n });\n }\n\n const vb = svg.viewBox?.baseVal;\n curVw = (vb && vb.width > 0) ? vb.width : (parseFloat(svg.getAttribute('width')) || svg.clientWidth || 800);\n curVh = (vb && vb.height > 0) ? vb.height : (parseFloat(svg.getAttribute('height')) || svg.clientHeight || 600);\n clone.style.width = `${curVw}px`;\n clone.style.height = `${curVh}px`;\n canvas.innerHTML = '';\n canvas.append(clone);\n lb.removeAttribute('hidden');\n document.body.style.overflow = 'hidden';\n\n const rect = stage.getBoundingClientRect();\n const pad = 48;\n const availW = Math.max(100, rect.width - pad * 2);\n const availH = Math.max(100, rect.height - pad * 2);\n fitScale = Math.min(availW / curVw, availH / curVh, 1.0);\n scale = fitScale;\n x = (rect.width - curVw * scale) / 2;\n y = (rect.height - curVh * scale) / 2;\n apply();\n closeBtn.focus();\n };\n\n const close = () => {\n if (lb.hasAttribute('hidden')) return;\n lb.setAttribute('hidden', '');\n canvas.innerHTML = '';\n document.body.style.overflow = '';\n lastTrigger?.focus();\n };\n\n stage.addEventListener('wheel', (e) => {\n e.preventDefault();\n const rect = stage.getBoundingClientRect();\n const dy = e.deltaMode === 1 ? e.deltaY * 16 : (e.deltaMode === 2 ? e.deltaY * 400 : e.deltaY);\n const factor = Math.exp(-Math.max(-200, Math.min(200, dy)) * 0.0015);\n zoomTo(scale * factor, e.clientX - rect.left, e.clientY - rect.top);\n }, { passive: false });\n\n stage.addEventListener('pointerdown', (e) => {\n if (e.button !== 0 || isDragging) return;\n isDragging = true;\n activePointerId = e.pointerId;\n startX = e.clientX;\n startY = e.clientY;\n origX = x;\n origY = y;\n stage.setPointerCapture(e.pointerId);\n stage.classList.add('am-panning');\n });\n\n stage.addEventListener('pointermove', (e) => {\n if (!isDragging || e.pointerId !== activePointerId) return;\n x = origX + (e.clientX - startX);\n y = origY + (e.clientY - startY);\n apply();\n });\n\n const endDrag = (e) => {\n if (!isDragging || e.pointerId !== activePointerId) return;\n isDragging = false;\n activePointerId = null;\n stage.classList.remove('am-panning');\n try { stage.releasePointerCapture(e.pointerId); } catch {}\n };\n stage.addEventListener('pointerup', endDrag);\n stage.addEventListener('pointercancel', endDrag);\n\n stage.addEventListener('dblclick', () => {\n const rect = stage.getBoundingClientRect();\n scale = fitScale;\n x = (rect.width - curVw * scale) / 2;\n y = (rect.height - curVh * scale) / 2;\n apply();\n });\n\n closeBtn.addEventListener('click', close);\n backdrop.addEventListener('click', close);\n window.addEventListener('keydown', (e) => {\n if (e.key === 'Escape' && !lb.hasAttribute('hidden')) close();\n });\n\n lb.addEventListener('keydown', (e) => {\n if (e.key !== 'Tab') return;\n const focusables = lb.querySelectorAll('button:not([disabled]), [tabindex]:not([tabindex=\"-1\"])');\n if (!focusables.length) return;\n const first = focusables[0];\n const last = focusables[focusables.length - 1];\n if (e.shiftKey && document.activeElement === first) {\n e.preventDefault();\n last.focus();\n } else if (!e.shiftKey && document.activeElement === last) {\n e.preventDefault();\n first.focus();\n }\n });\n\n const expandSvg = '';\n diagrams.forEach((diag) => {\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = 'am-diagram-expand';\n btn.title = expandLabel;\n btn.setAttribute('aria-label', expandLabel);\n btn.innerHTML = expandSvg;\n btn.addEventListener('click', () => open(diag, btn));\n diag.prepend(btn);\n });\n }\n})();\n(() => {\n// The reply a reader copies from the page: one Markdown text with the decisions, the panel comments and the block remarks.\n// A plain ES module for tests; the page gets it with its `export` keyword dropped (src/runtime/compose.js).\n// decisions: [{ panel, question, picked: [label], suggested: [label], touched }]; comments: [{ panel, title, text }];\n// remarks: [{ panel, quote, kind, note }], where quote is the start of the marked block and kind is a key of ui.kinds.\n// Comment and remark text is quoted line by line, so text the reader typed cannot pass as part of the structure.\n// rtl: the page reads right to left, so the arrow before each answer points left, the way the text runs.\nfunction replyText({ title, decisions, comments, remarks = [], ui, rtl = false }) {\n const arrow = rtl ? '\u2190' : '\u2192';\n const out = [`# Re: ${title}`];\n if (decisions.length) {\n out.push('', `## ${ui.decisions}`);\n decisions.forEach((d, i) => {\n const answer = d.picked.length ? d.picked.map((l) => `**${l}**`).join(', ') : '**\u2014**';\n const same = d.picked.length === d.suggested.length && d.picked.every((l) => d.suggested.includes(l));\n const why = same ? (d.touched ? ui.confirmed : ui.untouched) : `${ui.was}: ${d.suggested.join(', ') || '\u2014'}`;\n out.push(`${i + 1}. [${d.panel}] ${d.question}`, ` ${arrow} ${answer} _(${why})_`);\n });\n }\n const written = comments.filter((c) => c.text.trim());\n const quoted = (text) => (text.trim() ? text.trim().split('\\n').map((l) => ` > ${l}`) : []);\n if (written.length) {\n out.push('', `## ${ui.comments}`);\n for (const c of written) out.push(`- **${c.panel} \xB7 ${c.title}**`, ...quoted(c.text));\n }\n if (remarks.length) {\n out.push('', `## ${ui.remarks}`);\n for (const r of remarks) {\n const label = [r.panel, ui.kinds?.[r.kind] ?? r.kind].filter(Boolean).join(' \xB7 ');\n out.push(`- **${label}** \"${r.quote}\"`, ...quoted(r.note));\n }\n }\n if (written.length || remarks.some((r) => r.note.trim())) out.push('', `_${ui.typed}_`);\n return `${out.join('\\n')}\\n`;\n}\n\n// Answer on the page: every panel gets a Comment box, and the Reply button collects the ask answers, the comments and the remarks\n// into one Markdown reply (replyText) to copy back to the agent. Answers and comments stay in localStorage across reloads.\n// The remark script (remark.js) adds the blocks the reader marked, through window.__amRemarkData.\nconst replyBtn = document.querySelector('[data-am=\"reply\"]');\nconst COMMENT_ICON = '';\nif (replyBtn) {\n const ui = JSON.parse(replyBtn.dataset.ui);\n const storeKey = `am-reply:${location.pathname}`;\n const panels = [...document.querySelectorAll('.am-panel')];\n const asks = [...document.querySelectorAll('.am-ask')];\n // Answers are saved by question text, so they still match after am patch has renumbered the asks.\n // Page text without the word joiners a right-to-left page puts after a Hebrew prefix hyphen (src/bidi.js): the reply and the saved\n // answers hold the text as the draft wrote it, a formula too (src/runtime/math-text.js).\n const WJ = new RegExp(String.fromCharCode(0x2060), 'g');\n const textOf = (el) => el && textWithMath(el).replace(WJ, '').trim();\n const keyOf = (ask) => textOf(ask.querySelector('legend')) ?? ask.dataset.ask;\n const touched = new Set();\n const boxes = new Map();\n\n const load = () => {\n try {\n return JSON.parse(localStorage.getItem(storeKey)) || {};\n } catch {\n return {};\n }\n };\n const save = () => {\n const state = {\n asks: Object.fromEntries(asks.map((a) => [keyOf(a), { picked: picked(a), touched: touched.has(a) }])),\n comments: Object.fromEntries([...boxes].map(([id, box]) => [id, box.value]).filter(([, text]) => text.trim())),\n };\n try {\n localStorage.setItem(storeKey, JSON.stringify(state));\n } catch {\n // Storage may be off (private window, file:// policy); answers then last until the page closes.\n }\n };\n const picked = (ask) => [...ask.querySelectorAll('input')].filter((i) => i.checked).map((i) => i.value);\n const panelOf = (el) => {\n const panel = el.closest('.am-panel');\n return { id: panel?.id.replace(/^panel-/, '') ?? '', title: textOf(panel?.querySelector('.am-panel-head h2')) ?? '' };\n };\n\n const saved = load();\n for (const ask of asks) {\n const inputs = [...ask.querySelectorAll('input')];\n const state = saved.asks?.[keyOf(ask)];\n // A saved answer applies only while every picked option still exists on the page.\n if (state && state.picked.every((v) => inputs.some((i) => i.value === v))) {\n for (const input of inputs) input.checked = state.picked.includes(input.value);\n if (state.touched) touched.add(ask);\n }\n // A click counts even on the option that is already picked: the reader confirms the suggestion.\n const answer = () => {\n touched.add(ask);\n save();\n };\n ask.addEventListener('change', answer);\n ask.addEventListener('click', (e) => { if (e.target.matches('input')) answer(); });\n }\n\n for (const panel of panels) {\n const id = panel.id.replace(/^panel-/, '');\n // An icon only: narrow panels have no room for a word next to the title.\n const btn = Object.assign(document.createElement('button'), { type: 'button', className: 'am-comment-btn', title: ui.comment, innerHTML: COMMENT_ICON });\n btn.setAttribute('aria-label', `${ui.comment} ${id}`);\n btn.setAttribute('aria-expanded', 'false');\n const wrap = Object.assign(document.createElement('div'), { className: 'am-comment', hidden: true });\n const box = Object.assign(document.createElement('textarea'), { rows: 3, placeholder: ui.commentHint, value: saved.comments?.[id] ?? '' });\n box.setAttribute('aria-label', `${ui.comment} ${id}`);\n wrap.append(box);\n const show = (open) => {\n wrap.hidden = !open;\n btn.setAttribute('aria-expanded', String(open));\n };\n btn.addEventListener('click', () => {\n show(wrap.hidden);\n if (!wrap.hidden) box.focus();\n });\n box.addEventListener('input', () => {\n btn.classList.toggle('am-comment-btn--on', Boolean(box.value.trim()));\n save();\n });\n btn.classList.toggle('am-comment-btn--on', Boolean(box.value.trim()));\n if (box.value.trim()) show(true);\n (panel.querySelector('.am-panel-head') ?? panel).append(btn);\n panel.append(wrap);\n boxes.set(id, box);\n }\n\n const sheet = document.createElement('dialog');\n sheet.className = 'am-reply';\n sheet.setAttribute('aria-label', ui.title);\n sheet.innerHTML = '
';\n sheet.querySelector('strong').textContent = ui.title;\n sheet.querySelector('span').textContent = ui.hint;\n const text = sheet.querySelector('textarea');\n const copy = sheet.querySelector('[data-act=\"copy\"]');\n const close = sheet.querySelector('[data-act=\"close\"]');\n copy.textContent = ui.copy;\n close.textContent = ui.close;\n document.body.append(sheet);\n\n const compose = () => {\n const decisions = asks.map((ask) => ({\n panel: panelOf(ask).id,\n question: textOf(ask.querySelector('legend')) ?? '',\n picked: picked(ask),\n suggested: [...ask.querySelectorAll('input[data-suggested]')].map((i) => i.value),\n touched: touched.has(ask),\n }));\n const comments = [...boxes].map(([id, box]) => ({ panel: id, title: panelOf(box).title, text: box.value }));\n const remarks = window.__amRemarkData?.() ?? [];\n const empty = !decisions.length && !comments.some((c) => c.text.trim()) && !remarks.length;\n const title = textOf(document.querySelector('.am-head h1')) ?? document.title;\n return empty ? '' : replyText({ title, decisions, comments, remarks, ui, rtl: document.documentElement.dir === 'rtl' });\n };\n\n replyBtn.addEventListener('click', () => {\n const reply = compose();\n text.value = reply || ui.empty;\n copy.disabled = !reply;\n if (sheet.showModal) sheet.showModal();\n else sheet.setAttribute('open', '');\n (reply ? copy : close).focus();\n });\n close.addEventListener('click', () => (sheet.close ? sheet.close() : sheet.removeAttribute('open')));\n copy.addEventListener('click', async () => {\n try {\n await navigator.clipboard.writeText(text.value);\n } catch {\n text.select();\n document.execCommand('copy');\n }\n copy.textContent = ui.done;\n setTimeout(() => { copy.textContent = ui.copy; }, 1400);\n });\n}\n})();\n(() => {\n// Text helpers for remark mode (src/runtime/remark.js): how a marked block is named and quoted.\n// A plain ES module for tests; the page gets it with its `export` keyword dropped (src/runtime/compose.js).\n// A block keeps its mark across `am patch` because the key is its panel, its tag and the start of its text, not its position.\n\nconst collapse = (text) => String(text).replace(/\\s+/g, ' ').trim();\n\n// The start of a block's text with whitespace collapsed: the readable identity of the block.\nfunction fingerprint(text, len = 80) {\n return collapse(text).slice(0, len);\n}\n\n// What the reply quotes of a block: the start of its text, with an ellipsis when it is cut.\nfunction quote(text, len = 60) {\n const t = collapse(text);\n return t.length > len ? `${t.slice(0, len).trimEnd()}\u2026` : t;\n}\n\n// Two blocks with the same text (two identical list items) get the same base key. The n-th one in the page gets \"\\t\" added;\n// a tab cannot be in a fingerprint, which has its whitespace collapsed, so the new key never meets another base key.\nfunction occurrenceKeys(bases) {\n const seen = new Map();\n return bases.map((base) => {\n const n = seen.get(base) ?? 0;\n seen.set(base, n + 1);\n return n ? `${base}\\t${n}` : base;\n });\n}\n\n// Remark mode: the reader turns it on, clicks a block, tags it (suggestion, keep, question, concern) and may add a note.\n// A mark shows as a bar and a note chip inside the block, lives in localStorage like panel comments, and the Reply button carries it\n// back to the agent: reply.js calls window.__amRemarkData, and replyText (reply-text.js) writes the Remarks section.\n(() => {\n const btn = document.querySelector('[data-am=\"remark\"]');\n if (!btn) return;\n const ui = JSON.parse(btn.dataset.ui);\n const KINDS = ['suggestion', 'keep', 'question', 'concern'];\n const storeKey = `am-remark:${location.pathname}`;\n // The one definition of a block a reader can mark: the content under
. The page and panel titles are left out, because the\n // reply reads them as text; a panel has its Comment button.\n // A $$ block on its own is a div; a $$ formula inside a sentence is a span of its paragraph.\n const BLOCK = 'main :is(h1,h2,h3,h4,h5,h6,p,li,blockquote,pre,table,dd,dt,figure,div.am-math-block):not(.am-head *, .am-panel-head *)';\n // A click on these does what it always did.\n const LIVE = '.am-remark-pop, .am-toolbar, a, button, input, textarea, select, label';\n const blockAt = (target) => (target.closest(LIVE) ? null : target.closest(BLOCK));\n\n const WJ = new RegExp(String.fromCharCode(0x2060), 'g'); // word joiners of a right-to-left page (src/bidi.js); not part of the draft's text\n const inlineBox = new WeakMap();\n const isInline = (el) => {\n if (!inlineBox.has(el)) inlineBox.set(el, getComputedStyle(el).display.startsWith('inline'));\n return inlineBox.get(el);\n };\n // The text of a block as the reader sees it: separate lines, cells and timeline parts are joined by a space (textContent would run\n // \"2026\" and \"Ship\" together), and the note chip is not part of it. A formula is one box and reads as the TeX the draft wrote\n // (src/runtime/math-text.js), not as the MathML tokens.\n const blockText = (el) => {\n const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);\n let text = '';\n let last = null;\n for (let node = walker.nextNode(); node; node = walker.nextNode()) {\n if (node.parentElement.closest('.am-mark-note')) continue;\n let box = formulaOf(node) ?? node.parentElement;\n while (box !== el && isInline(box)) box = box.parentElement;\n if (last && box !== last) text += ' ';\n text += nodeText(node).replace(WJ, '');\n last = box;\n }\n return text;\n };\n\n const panelOf = (el) => el.closest('.am-panel')?.id.replace(/^panel-/, '') ?? '';\n // Block keys, in page order. Built when first needed: a page nobody marked pays nothing.\n let keys = null;\n const keyMap = () => {\n if (!keys) {\n const blocks = [...document.querySelectorAll(BLOCK)];\n const ids = occurrenceKeys(blocks.map((el) => `${panelOf(el)}|${el.localName}|${fingerprint(blockText(el))}`));\n keys = new Map(blocks.map((el, i) => [el, ids[i]]));\n }\n return keys;\n };\n\n const load = () => {\n try {\n const saved = JSON.parse(localStorage.getItem(storeKey));\n return Array.isArray(saved) ? saved.filter((m) => typeof m?.key === 'string' && KINDS.includes(m.kind)) : [];\n } catch {\n return [];\n }\n };\n // A mark whose block is gone from the page stays stored (it may return with `am patch`); it is neither painted nor sent.\n const marks = new Map(load().map((m) => [m.key, { key: m.key, kind: m.kind, note: String(m.note ?? '') }]));\n const save = () => {\n try {\n localStorage.setItem(storeKey, JSON.stringify([...marks.values()]));\n } catch {\n // Storage may be off (private window, a sandboxed page); marks then last until the page closes.\n }\n };\n\n const chip = (el, mark) => {\n const note = document.createElement(el.localName === 'table' ? 'caption' : 'span');\n note.className = 'am-mark-note';\n const kind = document.createElement('b');\n kind.textContent = ui.kinds[mark.kind];\n note.append(kind, mark.note ? ` ${mark.note}` : '');\n return note;\n };\n const clearChip = (el) => el.querySelector(':scope > .am-mark-note')?.remove();\n const paint = (el, mark) => {\n clearChip(el);\n el.classList.add('am-mark');\n el.dataset.kind = mark.kind;\n // Inside the block, so a list item stays a child of its list. A table takes it as its caption; a list item with a sub-list shows\n // it under its own text, before the sub-list.\n const sub = el.querySelector(':scope > ul, :scope > ol');\n if (el.localName === 'table') el.prepend(chip(el, mark));\n else if (sub) sub.before(chip(el, mark));\n else el.append(chip(el, mark));\n };\n const unpaint = (el) => {\n clearChip(el);\n el.classList.remove('am-mark');\n delete el.dataset.kind;\n };\n if (marks.size) for (const [el, key] of keyMap()) if (marks.has(key)) paint(el, marks.get(key));\n\n // The popover edits one block. Save stays off until a kind is picked.\n const pop = document.createElement('div');\n pop.className = 'am-remark-pop';\n pop.hidden = true;\n pop.setAttribute('role', 'dialog');\n pop.setAttribute('aria-label', ui.button);\n document.body.append(pop);\n let opener = null;\n let anchor = null;\n const close = (restoreFocus) => {\n if (pop.hidden) return;\n pop.hidden = true;\n if (restoreFocus) (opener?.isConnected && opener !== document.body ? opener : btn).focus();\n };\n const place = () => {\n const rect = anchor.getBoundingClientRect();\n const rtl = getComputedStyle(document.documentElement).direction === 'rtl';\n const x = rtl ? rect.right - pop.offsetWidth : rect.left;\n // Under the block; a block taller than the window gets the popover at the bottom of the window instead.\n const y = Math.min(rect.bottom + 6, window.innerHeight - pop.offsetHeight - 8);\n pop.style.left = `${window.scrollX + Math.min(Math.max(8, x), window.innerWidth - pop.offsetWidth - 8)}px`;\n pop.style.top = `${window.scrollY + Math.max(8, y)}px`;\n };\n const make = (tag, props) => Object.assign(document.createElement(tag), props);\n const open = (el) => {\n const key = keyMap().get(el);\n const mark = marks.get(key);\n let kind = mark?.kind ?? '';\n opener = document.activeElement;\n anchor = el;\n\n const note = make('textarea', { rows: 2, placeholder: ui.hint, value: mark?.note ?? '' });\n note.setAttribute('aria-label', ui.hint);\n const keep = make('button', { type: 'button', className: 'am-btn', textContent: ui.save, disabled: !kind });\n keep.addEventListener('click', () => {\n const saved = { key, kind, note: note.value.trim() };\n marks.set(key, saved);\n save();\n paint(el, saved);\n close(true);\n });\n const actions = make('div', { className: 'am-remark-actions' });\n actions.append(keep);\n if (mark) {\n const drop = make('button', { type: 'button', className: 'am-btn', textContent: ui.remove });\n drop.addEventListener('click', () => {\n marks.delete(key);\n save();\n unpaint(el);\n close(true);\n });\n actions.append(drop);\n }\n const kindBtns = KINDS.map((k) => {\n const kb = make('button', { type: 'button', className: 'am-remark-kind', textContent: ui.kinds[k] });\n kb.dataset.kind = k;\n kb.setAttribute('aria-pressed', String(k === kind));\n kb.addEventListener('click', () => {\n kind = k;\n for (const other of kindBtns) other.setAttribute('aria-pressed', String(other === kb));\n keep.disabled = false;\n });\n return kb;\n });\n pop.replaceChildren(make('div', { className: 'am-remark-kinds' }), note, actions);\n pop.firstChild.append(...kindBtns);\n pop.hidden = false;\n place();\n (kindBtns.find((b) => b.dataset.kind === kind) ?? kindBtns[0]).focus({ preventScroll: true });\n };\n\n // Picking works only while remark mode is on. A click on a marked block edits its mark, on a plain block starts one.\n // The block under the pointer gets an outline: the same blockAt decides what is outlined and what a click picks.\n let hover = null;\n const track = (e) => {\n const el = e.type === 'mouseout' && e.relatedTarget === null ? null : blockAt(e.target);\n if (el === hover) return;\n hover?.classList.remove('am-remark-hover');\n hover = el;\n hover?.classList.add('am-remark-hover');\n };\n const pick = (e) => {\n const el = blockAt(e.target);\n if (el) open(el);\n };\n const setMode = (on) => {\n btn.classList.toggle('is-on', on);\n btn.setAttribute('aria-pressed', String(on));\n const toggle = on ? 'addEventListener' : 'removeEventListener';\n document.body[toggle]('click', pick);\n document.body[toggle]('mouseover', track);\n document.body[toggle]('mouseout', track);\n if (!on) {\n track({ type: 'mouseout', relatedTarget: null });\n close(false);\n }\n };\n btn.addEventListener('click', () => setMode(btn.getAttribute('aria-pressed') !== 'true'));\n document.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') close(true);\n });\n // A click outside the popover closes it; the same click may then start another mark.\n document.addEventListener('click', (e) => {\n if (!pop.hidden && !pop.contains(e.target)) close(false);\n }, true);\n setMode(false);\n\n // What the Reply button reads: the marked blocks that are on the page, in page order.\n window.__amRemarkData = () => {\n if (!marks.size) return [];\n const live = [];\n for (const [el, key] of keyMap()) {\n const mark = marks.get(key);\n if (mark) live.push({ panel: panelOf(el), quote: quote(blockText(el)), kind: mark.kind, note: mark.note });\n }\n return live;\n };\n})();\n})();\n(() => {\n// Pure planner for justified (\"photo wall\") rows on sheet pages. No DOM access.\n//\n// This file must stay inlinable into the page script: top-level declarations only, no imports, and the exports are\n// `planLayout` and the constants the DOM adapter shares (STEP, MAX_SCALE, MIN_SCALE). The page build strips the `export ` keyword,\n// and test/layout-plan.test.js checks that this still works.\n//\n// planLayout({ width, gap, cols, panels }) -> { rows: [{ columns: [{ panels: [index, ...], width }], height }] }\n//\n// Input (all lengths in px):\n// width container width\n// gap space between columns, and between two panels stacked in one column\n// cols most columns in one row (the planner never uses more than MAX_COLUMNS, but spans stay shares of `cols`)\n// panels one entry per panel, in reading order:\n// samples [{ w, h }] panel height at sampled widths, ascending by w (heights between samples are interpolated)\n// minWidth narrowest feasible width (clamped to `width`)\n// maxWidth widest useful width; beyond it the panel only gains empty space (default: no limit)\n// natural natural diagram width for a diagram-only panel (default: none)\n// pad panel width not used by the diagram (padding and border; default 0)\n// span the author's width hint in grid columns (default 1); span >= cols keeps the panel alone in its row\n//\n// Output: { rows, maxScale }. Rows are in reading order; a row is a list of columns (at most `cols`); a column holds one panel or two\n// consecutive panels, stacked. For integer inputs the column widths plus the gaps between them equal `width`.\n// Rows are chosen by dynamic programming over break points; widths by a search in STEP px steps. Cost per row:\n// H = tallest column; waste = sum((H - columnHeight) * columnWidth) + sum over diagrams wider than maxWidth of (w - maxWidth) * h\n// dev = sum(((w - preferred) / oneColumn)^2) + SCALE_WEIGHT * sum over diagrams of (ln(scale) / ln(MAX_SCALE))^2\n// cost = waste / 1000 + PREF_WEIGHT * dev * H\n// Scale band: diagrams on one page stay at similar sizes. With the result's `maxScale` as the diagram size limit, a diagram is\n// shown at scale min(maxScale, (width - pad) / natural). The planner tries a few bands [lo, lo * BAND_RATIO] (lo in BAND_LOS) and\n// keeps the cheapest plan in which no diagram is narrower than scale lo and none is shown above the band's top, so the largest and\n// smallest diagram scale on the page differ by at most BAND_RATIO. A panel wider than its diagram's top size keeps the diagram at\n// that size, and the page script must set the diagram's max-width to natural * maxScale. The band starts no higher than the\n// largest scale the page allows the least roomy diagram, so one wide diagram lowers the band for the others.\n// If no band is feasible the plan is made without a band (maxScale = MAX_SCALE); if no plan fits (or the input is unusable) the\n// result is a single column: one panel per row at the full width.\n\nconst STEP = 10;\nconst MAX_SCALE = 1.25;\nconst MIN_SCALE = 0.75; // the narrowest a diagram is shown, as a share of its natural width\nconst MAX_COLUMNS = 6; // rows with more columns are unreadable, and the search grows steeply with the column count\nconst BAND_RATIO = 1.25; // the largest and smallest diagram scale on a page differ by at most this\nconst BAND_LOS = [MIN_SCALE, 0.85, 0.95, 1]; // lower ends tried for the band; lo * BAND_RATIO is the upper end (1.25 means no upper limit)\nconst SCALE_WEIGHT = 3;\nconst PREF_WEIGHT = 0.15;\n\nfunction heightAt(samples, w) {\n const i = samples.findIndex((s) => s.w >= w);\n if (i === -1) return samples[samples.length - 1].h;\n if (i === 0) return samples[0].h;\n const a = samples[i - 1];\n const b = samples[i];\n return a.h + ((b.h - a.h) * (w - a.w)) / (b.w - a.w);\n}\n\n// Every way to split `len` consecutive panels into columns of 1 or 2 panels, as lists of column sizes.\nfunction splits(len) {\n if (len === 0) return [[]];\n return [1, 2].filter((k) => k <= len).flatMap((k) => splits(len - k).map((rest) => [k, ...rest]));\n}\n\nfunction singleColumn(width, panels) {\n return { rows: panels.map((_, i) => ({ columns: [{ panels: [i], width }], height: 0 })) };\n}\n\nfunction planLayout({ width, gap = 0, cols = 3, panels }) {\n const usable = Number.isFinite(width) && width > 0 && panels.every((p) => Array.isArray(p.samples) && p.samples.length > 0);\n if (!usable) return singleColumn(width, panels);\n\n // `cols` stays the unit of the author's spans; no row gets more than MAX_COLUMNS columns however large `cols` is.\n const maxColumns = Math.min(cols, MAX_COLUMNS);\n const oneColumn = Math.max(1, (width - gap * (cols - 1)) / cols);\n // Per-panel limits. With a band [lo, lo * BAND_RATIO], a diagram is no narrower than scale lo and shown at most at the top of the\n // band: a wider panel keeps it at that size and gains empty space, which the cost counts as waste.\n const prepare = (lo) => panels.map((p) => {\n const pad = p.pad ?? 0;\n const diagram = lo > 0 && p.natural > 0;\n const scaleCap = diagram ? Math.min(MAX_SCALE, lo * BAND_RATIO) : MAX_SCALE;\n return {\n ...p,\n minWidth: Math.min(width, Math.max(Math.ceil(p.minWidth || 0), diagram ? Math.ceil(p.natural * lo + pad) : 0)),\n maxWidth: Math.min(p.maxWidth ?? Infinity, diagram ? p.natural * scaleCap + pad : Infinity),\n scaleCap,\n pad,\n preferred: Math.min(width, (p.span ?? 1) * oneColumn + ((p.span ?? 1) - 1) * gap),\n alone: (p.span ?? 1) >= cols,\n };\n });\n let info = prepare(0);\n // Beyond its maxWidth a panel only gains empty space, so its height stops changing there (matters when a band lowers maxWidth).\n const heightOf = (p, w) => heightAt(p.samples, Math.min(w, p.maxWidth));\n const columnHeight = (col, w) => col.reduce((h, k) => h + heightOf(info[k], w), 0) + gap * (col.length - 1);\n\n function bestColumns(columns) {\n const avail = width - gap * (columns.length - 1);\n const mins = columns.map((c) => Math.max(...c.map((k) => info[k].minWidth)));\n const minsFrom = mins.map((_, n) => mins.slice(n).reduce((s, w) => s + w, 0));\n if (minsFrom[0] > avail) return null;\n let best = null;\n const evaluate = (ws) => {\n const hs = ws.map((w, n) => columnHeight(columns[n], w));\n const H = Math.max(...hs);\n let waste = 0;\n let dev = 0;\n ws.forEach((w, n) => {\n waste += (H - hs[n]) * w;\n for (const k of columns[n]) {\n const p = info[k];\n if (w > p.maxWidth) waste += (w - p.maxWidth) * heightOf(p, w);\n dev += ((w - p.preferred) / oneColumn) ** 2;\n if (p.natural > 0) {\n const scale = Math.min(p.scaleCap, Math.max(w - p.pad, 1) / p.natural);\n dev += SCALE_WEIGHT * (Math.log(scale) / Math.log(MAX_SCALE)) ** 2;\n }\n }\n });\n const cost = waste / 1000 + PREF_WEIGHT * dev * H;\n if (!best || cost < best.cost) best = { cost, ws, height: H, columns };\n };\n const choose = (n, used, ws) => {\n if (n === columns.length - 1) {\n const w = avail - used;\n if (w >= mins[n]) evaluate([...ws, w]);\n return;\n }\n for (let w = mins[n]; used + w + minsFrom[n + 1] <= avail; w += STEP) choose(n + 1, used + w, [...ws, w]);\n };\n choose(0, 0, []);\n return best;\n }\n\n // Best columns for one row of panels i..j.\n function bestRow(i, j) {\n if (j > i && info.slice(i, j + 1).some((p) => p.alone)) return null;\n let best = null;\n for (const split of splits(j - i + 1)) {\n if (split.length > maxColumns) continue;\n let next = i;\n const columns = split.map((size) => Array.from({ length: size }, () => next++));\n const r = bestColumns(columns);\n if (r && (!best || r.cost < best.cost)) best = r;\n }\n return best;\n }\n\n // Row breaks in reading order, for the band set up in `info`. A row holds at most 2 * maxColumns panels. Null if nothing fits.\n function solve() {\n const n = panels.length;\n const total = Array(n + 1).fill(Infinity);\n const from = Array(n + 1).fill(-1);\n const chosen = Array(n + 1).fill(null);\n total[0] = 0;\n for (let j = 1; j <= n; j++) {\n for (let i = Math.max(0, j - 2 * maxColumns); i < j; i++) {\n if (total[i] === Infinity) continue;\n const r = bestRow(i, j - 1);\n if (r && total[i] + r.cost < total[j]) {\n total[j] = total[i] + r.cost;\n from[j] = i;\n chosen[j] = r;\n }\n }\n }\n if (total[n] === Infinity) return null;\n const rows = [];\n for (let j = n; j > 0; j = from[j]) {\n const r = chosen[j];\n rows.unshift({ columns: r.columns.map((panelsInColumn, c) => ({ panels: panelsInColumn, width: r.ws[c] })), height: r.height });\n }\n return { rows, cost: total[n] };\n }\n\n let best = null;\n const diagrams = panels.filter((p) => p.natural > 0);\n if (diagrams.length > 1) {\n // No diagram can be shown larger than the page allows, so the band cannot start above the smallest of those limits.\n const reach = Math.min(...diagrams.map((p) => Math.min(MAX_SCALE, Math.max(width - (p.pad ?? 0), 1) / p.natural)));\n for (const lo of new Set([...BAND_LOS.filter((x) => x < reach), reach])) {\n info = prepare(lo);\n const r = solve();\n if (r && (!best || r.cost < best.cost)) best = { ...r, maxScale: Math.min(MAX_SCALE, lo * BAND_RATIO) };\n }\n info = prepare(0);\n }\n best ??= solve();\n return best ? { rows: best.rows, maxScale: best.maxScale ?? MAX_SCALE } : singleColumn(width, panels);\n}\n\n// DOM adapter for the sheet's justified (\"photo wall\") layout. Not a module: compose.js puts it after src/runtime/layout-plan.js\n// (which defines planLayout and the shared STEP, MAX_SCALE, MIN_SCALE) inside one function scope of the page script.\n//\n// It measures every panel at sampled widths, asks planLayout for rows and column widths, and applies them with flexbox.\n// The rendered HTML keeps the plain CSS grid: with JavaScript off, at the single-column breakpoint, or while printing, the grid is what\n// shows (the planned widths belong to the screen width, so print never gets a mix of the two); after printing the layout comes back.\n\nconst grid = document.querySelector('.am-grid');\nconst panels = grid ? [...grid.children].filter((el) => el.classList.contains('am-panel')) : [];\n\nif (panels.length > 1) {\n const SINGLE_COLUMN = '(max-width: 760px)'; // the single-column breakpoint in src/themes/base.css\n const TWO_COLUMNS = '(max-width: 1100px)'; // below this the CSS grid has two columns\n const SAMPLE_STEP = 20; // width sampling step, px; the planner interpolates between samples\n const TEXT_MIN = 260; // text keeps at least about 16 CJK characters per line\n const TABLE_COL_MIN = 96; // per table column, px\n const TABLE_COL_COMFORT = 220; // a table column this wide reads three or four words per line\n const KV_COL_MIN = 120; // per key-value column, px: a value of two short words stays on one line\n const DIAGRAM_MIN = 160;\n const RESIZE_DELAY = 150;\n const OVERFLOWING = '.am-table-wrap, .am-diagram, .am-annot-scroll, pre';\n\n // The first-row cells of each table carry the column widths fitTables() sets, so restore() resets them too.\n const tables = [...grid.querySelectorAll('.am-table-wrap > table')];\n const headCells = (table) => [...(table.rows[0]?.cells ?? [])];\n const original = new Map([grid, ...panels, ...grid.querySelectorAll('.am-diagram > svg'), ...tables.flatMap(headCells)].map((el) => [el, el.getAttribute('style')]));\n const restoreStyle = (el) => (original.get(el) === null ? el.removeAttribute('style') : el.setAttribute('style', original.get(el)));\n\n // Back to the plain grid markup and styles.\n const restore = () => {\n for (const box of grid.querySelectorAll(':scope > .am-col')) box.replaceWith(...box.children);\n for (const el of original.keys()) restoreStyle(el);\n };\n\n // The author's width hint, rendered as data-span only when the author wrote one. The inline grid-column is the no-JavaScript fallback\n // and may hold spans the server added, so it is never read here.\n const spanHint = (el) => Number(el.dataset.span) || 1;\n const diagramOnly = (el) => {\n const body = el.querySelector(':scope > .am-panel-body');\n return body && body.children.length === 1 ? body.querySelector(':scope > .am-diagram > svg') : null;\n };\n const naturalWidth = (svg) => Number(svg.getAttribute('width')) || 0;\n\n // The width each column of a table reads well at: its natural width, but no wider than TABLE_COL_COMFORT unless its longest word\n // needs more. Below it the cells wrap one word per line, so a panel with a table is not planned narrower than the sum, and\n // fitTables() keeps each column at its share (the browser would otherwise give the room to the longest column).\n const columnWidths = new Map();\n function comfortableWidth(table) {\n const saved = table.getAttribute('style');\n const columns = (width) => {\n table.style.width = width;\n return headCells(table).map((cell) => cell.getBoundingClientRect().width);\n };\n const most = columns('max-content');\n const least = columns('min-content');\n if (saved === null) table.removeAttribute('style');\n else table.setAttribute('style', saved);\n // A max-content column comes out a fraction under its text in Chrome, enough to wrap the last word, so it gets one pixel more.\n const want = most.map((w, i) => Math.min(Math.ceil(w) + 1, Math.max(least[i], TABLE_COL_COMFORT)));\n columnWidths.set(table, { least, want });\n return want.reduce((sum, w) => sum + w, 0);\n }\n\n // After the layout: each column gets at least its comfortable width, scaled down toward its longest word when the panel came out\n // narrower than planned (a page narrower than the table), so a table scrolls sideways only when even its longest words do not fit.\n function fitTables() {\n for (const [table, { least, want }] of columnWidths) {\n // Each width is rounded up to a whole pixel (a column a fraction too narrow wraps its last word), so keep a pixel per column spare.\n const room = table.parentElement.clientWidth - want.length;\n const low = least.reduce((s, w) => s + w, 0);\n const high = want.reduce((s, w) => s + w, 0);\n const k = high > room ? Math.max(0, (room - low) / (high - low || 1)) : 1;\n headCells(table).forEach((cell, i) => { cell.style.minWidth = `${Math.ceil(least[i] + (want[i] - least[i]) * k)}px`; });\n }\n }\n\n // Height (and, for panels with tables or code, the narrowest width without sideways scrolling) at sampled widths.\n // Only one panel is displayed while it is measured, so each width change lays out that panel alone.\n function measure(width) {\n grid.style.display = 'block';\n for (const el of panels) {\n el.style.display = 'none';\n el.style.boxSizing = 'border-box';\n for (const svg of el.querySelectorAll('.am-diagram > svg')) {\n svg.style.width = '100%';\n svg.style.maxWidth = `${naturalWidth(svg) * MAX_SCALE}px`;\n }\n }\n const info = panels.map((el) => {\n const svg = diagramOnly(el);\n const svgs = [...el.querySelectorAll('.am-diagram > svg')];\n const scrollers = el.querySelectorAll(OVERFLOWING);\n const tableCols = Math.max(0, ...[...el.querySelectorAll('table tr:first-child')].map((tr) => tr.children.length));\n el.style.display = '';\n el.style.width = `${width}px`;\n const pad = svg ? el.offsetWidth - svg.parentElement.clientWidth : 0;\n const natural = svg ? naturalWidth(svg) : 0;\n const shrunk = Math.max(0, ...svgs.map((s) => naturalWidth(s) * MIN_SCALE)) + (svg ? pad : 34);\n const tableFloors = [...el.querySelectorAll('.am-table-wrap > table')].map((t) => comfortableWidth(t) + el.offsetWidth - t.parentElement.clientWidth);\n // A key-value grid gets the same care: the room a table gains must not squeeze a neighbouring grid to one word per line.\n const kvs = [...el.querySelectorAll('.am-kv')].map((kv) => (Number(kv.style.getPropertyValue('--kv-cols')) || 1) * KV_COL_MIN + el.offsetWidth - kv.clientWidth);\n const floor = svg ? Math.max(DIAGRAM_MIN, natural * MIN_SCALE + pad) : Math.max(TEXT_MIN, shrunk, tableCols * TABLE_COL_MIN + 34, ...tableFloors, ...kvs);\n const from = Math.min(width, Math.floor(floor / STEP) * STEP);\n const samples = [];\n let fits = null;\n for (let w = from; ; w += SAMPLE_STEP) {\n w = Math.min(w, width);\n el.style.width = `${w}px`;\n if (fits === null && !svg && scrollers.length && ![...scrollers].some((s) => s.scrollWidth > s.clientWidth + 1)) fits = w;\n samples.push({ w, h: el.offsetHeight });\n if (w === width) break;\n }\n el.style.display = 'none';\n return {\n samples,\n minWidth: svg ? floor : Math.max(floor, fits ?? (scrollers.length ? width : 0)),\n maxWidth: svg ? natural * MAX_SCALE + pad : Infinity,\n natural,\n pad,\n span: spanHint(el),\n };\n });\n for (const el of panels) {\n el.style.display = '';\n el.style.width = '';\n }\n return info;\n }\n\n // Each column gets a fixed width. A row adds up to the full width, so flex-wrap breaks rows by itself.\n // Stacked panels go into a column wrapper whose last panel absorbs the extra height.\n function apply(plan, gap) {\n grid.style.display = 'flex';\n grid.style.flexWrap = 'wrap';\n grid.style.alignItems = 'stretch';\n grid.style.gap = `${gap}px`;\n for (const el of panels) {\n el.style.gridColumn = '';\n el.style.gridRow = '';\n el.style.flex = '0 0 auto';\n }\n for (const row of plan.rows) {\n for (const col of row.columns) {\n const width = `${col.width}px`;\n if (col.panels.length === 1) {\n panels[col.panels[0]].style.width = width;\n continue;\n }\n const box = document.createElement('div');\n box.className = 'am-col';\n box.style.cssText = `width:${width};flex:0 0 auto;display:flex;flex-direction:column;gap:${gap}px`;\n panels[col.panels[0]].before(box);\n for (const k of col.panels) {\n panels[k].style.width = '';\n box.append(panels[k]);\n }\n panels[col.panels[col.panels.length - 1]].style.flex = '1 1 auto';\n }\n }\n }\n\n // Diagram-only panels show their diagram at most at the top of the page's scale band; a wider panel gains empty space instead.\n function capDiagrams(maxScale) {\n for (const el of panels) {\n const svg = diagramOnly(el);\n if (svg) svg.style.maxWidth = `${naturalWidth(svg) * maxScale}px`;\n }\n }\n\n const containerWidth = () => Math.floor(grid.getBoundingClientRect().width);\n\n function justify() {\n if (printing || printQuery.matches) return;\n try {\n // A vertical scrollbar can appear or vanish once the rows change height; plan again if the width moved.\n let planned = -1;\n for (let pass = 0; pass < 3 && planned !== containerWidth(); pass++) {\n restore();\n if (matchMedia(SINGLE_COLUMN).matches) return;\n planned = containerWidth();\n const gap = parseFloat(getComputedStyle(grid).columnGap) || 0;\n const cols = Math.max(1, Number(getComputedStyle(grid).getPropertyValue('--cols')) || 3);\n const plan = planLayout({ width: planned, gap, cols: matchMedia(TWO_COLUMNS).matches ? Math.min(cols, 2) : cols, panels: measure(planned) });\n apply(plan, gap);\n capDiagrams(plan.maxScale);\n fitTables();\n }\n // The width never settled: columns planned for another width would overflow or leave gaps, so show the plain grid.\n if (planned !== containerWidth()) restore();\n } catch {\n restore();\n }\n }\n\n // Printing: back to the plain grid (spans and all), and the layout again afterwards. Browsers disagree on which of the\n // `beforeprint` event and the print media query change fires first, or at all, so listen to both; both are idempotent.\n // While `printing` is set (beforeprint to afterprint) justify() does nothing, so a late resize cannot bring flex widths into the print layout.\n let printing = false;\n const printQuery = matchMedia('print');\n let timer = 0;\n const later = () => {\n clearTimeout(timer);\n timer = setTimeout(justify, RESIZE_DELAY);\n };\n const toPrint = () => {\n clearTimeout(timer);\n restore();\n };\n justify();\n addEventListener('resize', later);\n addEventListener('beforeprint', () => {\n printing = true;\n toPrint();\n });\n addEventListener('afterprint', () => {\n printing = false;\n later();\n });\n printQuery.addEventListener('change', (e) => (e.matches ? toPrint() : later()));\n // Late changes to panel heights: web fonts arriving, and images inside the grid finishing their load (load does not bubble, so capture it).\n document.fonts?.ready.then(later);\n grid.addEventListener('load', later, true);\n // Switching theme changes paddings and fonts, hence panel heights.\n document.querySelector('[data-am=\"theme\"]')?.addEventListener('click', later);\n}\n})();\n})();\n"; var RTL_JS = `(() => { // A letter of a right-to-left script. One definition for the page builder (src/bidi.js) and for the right-to-left page script // (src/runtime/reply-view.js), so the two cannot drift apart. A plain ES module; the page script gets this file in front of // reply-view.js, with its \`export\` keyword dropped and reply-view.js's import of it removed (src/runtime/compose.js). const RTL_LETTER = /[\\p{Script=Hebrew}\\p{Script=Arabic}\\p{Script=Syriac}\\p{Script=Thaana}\\p{Script=Nko}\\p{Script=Samaritan}\\p{Script=Mandaic}]/u; // Right-to-left pages only: src/render.js adds this script after the page script when the page reads right to left. // The Reply sheet holds the reply as Markdown in a textarea, and a textarea gives the whole text one direction. Lines that start // with markup or Latin letters ("# Re: ...", "1. [E] ...", "- **A \xB7 ...**") then come out reordered for a Hebrew reader. // This script shows the reply drawn instead: headings, bold answers and quoted comments, each line in its own direction. // The textarea stays in the sheet as what Copy copies, so the copied text does not change. // A plain ES module for tests; the page gets it with its \`export\` keyword dropped and rtl-letter.js in front of it in place of the // import (src/runtime/compose.js). const escHtml = (s) => s.replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' })[c]); // A line with a right-to-left letter reads right to left; a line without one (an English comment, a path) reads left to right. const dirOf = (s) => (RTL_LETTER.test(s) ? 'rtl' : 'ltr'); // The only inline Markdown src/runtime/reply-text.js writes: **answer**, _(note)_ and the "quote" of a remark. Each piece is isolated in its own direction, // so a Latin answer inside a Hebrew line keeps its place, and "A \xB7 title" still starts with the panel letter. const piece = (tag, text) => \`<\${tag}>\${text}\`; function inline(s) { return escHtml(s) .replace(/\\*\\*(.+?)\\*\\*/g, (_, t) => piece('strong', t)) .replace(/(^|\\s)_(.+?)_(?=\\s|$)/g, (_, pre, t) => pre + piece('em', t)) // The quoted start of a marked block ("- **A \xB7 concern** "text""): isolated, so Latin text in a Hebrew line keeps its place. .replace(/"(.+)"$/, (_, t) => \`"\${t}"\`); } // The reply text (src/runtime/reply-text.js) as HTML, one block per line. function replyViewHtml(text) { return text .replace(/\\n$/, '') .split('\\n') .map((line) => { const dir = dirOf(line); let m; if (!line.trim()) return '
'; if ((m = line.match(/^(#{1,2}) (.*)$/))) return \`
\${inline(m[2])}
\`; if ((m = line.match(/^(\\d+)\\. \\[([^\\]]*)\\] (.*)$/))) { return \`
\${m[1]}. \${escHtml(m[2])} \${inline(m[3])}
\`; } if ((m = line.match(/^ {3}([\u2190\u2192]) (.*)$/))) return \`
\${m[1]} \${inline(m[2])}
\`; if ((m = line.match(/^- (.*)$/))) return \`
\${inline(m[1])}
\`; // A comment line is shown exactly as the reader typed it. if ((m = line.match(/^ {2}> ?(.*)$/))) return \`
\${escHtml(m[1]) || ' '}
\`; return \`
\${inline(line)}
\`; }) .join(''); } if (typeof document !== 'undefined') { const button = document.querySelector('[data-am="reply"]'); const sheet = document.querySelector('dialog.am-reply'); const source = sheet?.querySelector('textarea'); if (button && source) { const view = Object.assign(document.createElement('div'), { className: 'am-reply-view', tabIndex: 0 }); source.before(view); sheet.classList.add('am-reply--view'); // Runs after the page script's own handler, which has just filled the textarea. button.addEventListener('click', () => { view.innerHTML = replyViewHtml(source.value); view.scrollTop = 0; }); } } })(); `; var DIFF_CSS = "/* Diff block (only on pages that have one): red and green lines, a hunk row, and two gutters, old and new, drawn from data-o / data-n.\n The hues are fixed and mixed into the theme's own --fill and --ink, so they read as green and red in every theme, light and dark. */\n.am-codeblock--diff { --diff-add: #1f9d4d; --diff-del: #d1344a; }\n.am-code-stat { display: inline-flex; gap: 6px; flex: none; font: 12px/1.3 var(--font-mono); }\n.am-code-stat-add { color: color-mix(in srgb, var(--diff-add) 75%, var(--ink)); }\n.am-code-stat-del { color: color-mix(in srgb, var(--diff-del) 75%, var(--ink)); }\n.am-code--diff .am-ln { display: flex; padding: 0 14px 0 0; }\n.am-code--diff:not(.am-code--dnum) .am-ln { padding-left: 14px; }\n.am-code--dnum .am-ln::before, .am-code--dnum .am-ln::after {\n flex: none; box-sizing: content-box; width: 3.5ch; padding: 0 6px; text-align: right; color: var(--ink-3);\n user-select: none; -webkit-user-select: none;\n}\n.am-code--dnum .am-ln::before { content: attr(data-o); order: -2; }\n.am-code--dnum .am-ln::after { content: attr(data-n); order: -1; margin-right: 10px; }\n.am-ln--add { background: color-mix(in srgb, var(--diff-add) 14%, var(--fill)); }\n.am-ln--del { background: color-mix(in srgb, var(--diff-del) 14%, var(--fill)); }\n.am-ln--add::before, .am-ln--add::after { background: color-mix(in srgb, var(--diff-add) 28%, var(--fill)); }\n.am-ln--del::before, .am-ln--del::after { background: color-mix(in srgb, var(--diff-del) 28%, var(--fill)); }\n.am-code--diff .am-ln--hl { box-shadow: none; }\n.am-code--diff .am-ln--hl::before { box-shadow: inset 3px 0 0 var(--accent); }\n.am-code--diff .am-ln--ctx.am-ln--hl { background: var(--accent-bg); }\n.am-code--diff .am-ln--hunk { display: block; min-height: 0; padding: 1px 14px; font-size: 11.5px; color: var(--ink-3); background: color-mix(in srgb, var(--accent) 8%, var(--fill)); }\n.am-code--dnum .am-ln--hunk::before, .am-code--dnum .am-ln--hunk::after { content: none; }\n.am-code--diff .am-ln--meta { display: none; }\n"; var DELTA_CSS = `/* Change markers (only on pages that have one). Only the Changes view (.am-view-changes) draws change styling: colors, strike-through, faded items and badges. Before and After look like a plain diagram, and the items that do not exist in the view are hidden. The colors are the theme's --ok, --err and --warn, so every theme and mode works. A view hides items with visibility, so nothing moves. */ .am-view-before [data-delta="added"], .am-view-after [data-delta="removed"] { visibility: hidden; } .am-view-before .am-delta-badge, .am-view-after .am-delta-badge { visibility: hidden; } /* A list label's badge is inline and would indent the label like a nesting level, so there it takes no space; the other badges are positioned and move nothing. */ .am-view-before .am-tree-label > .am-delta-badge, .am-view-after .am-tree-label > .am-delta-badge { display: none; } .am-delta-badge--added { --badge: var(--ok, #1f9d4d); } .am-delta-badge--removed { --badge: var(--err, #c62828); } .am-delta-badge--changed { --badge: var(--warn, #a8620a); } /* flow */ .am-view-changes .am-node[data-delta="added"] .am-node-shape { stroke: var(--ok, #1f9d4d); } .am-view-changes .am-node[data-delta="removed"] .am-node-shape { stroke: var(--err, #c62828); } .am-view-changes .am-node[data-delta="changed"] .am-node-shape { stroke: var(--warn, #a8620a); } .am-view-changes .am-node[data-delta="added"] > text { fill: var(--ok, #1f9d4d); } .am-view-changes .am-node[data-delta="removed"] > text { fill: var(--err, #c62828); } .am-view-changes [data-delta="added"] > .am-edge { stroke: var(--ok, #1f9d4d); } .am-view-changes [data-delta="removed"] > .am-edge { stroke: var(--err, #c62828); } .am-view-changes .am-arrow--added { fill: var(--ok, #1f9d4d); } .am-view-changes .am-arrow--removed { fill: var(--err, #c62828); } .am-view-changes [data-delta="added"] > .am-edge-label text { fill: var(--ok, #1f9d4d); } .am-view-changes [data-delta="removed"] > .am-edge-label text { fill: var(--err, #c62828); } .am-view-changes .am-cluster[data-delta="added"] { stroke: var(--ok, #1f9d4d); } .am-view-changes .am-cluster[data-delta="removed"] { stroke: var(--err, #c62828); } .am-view-changes .am-cluster-label[data-delta="added"] { fill: var(--ok, #1f9d4d); } .am-view-changes .am-cluster-label[data-delta="removed"] { fill: var(--err, #c62828); } .am-view-changes g[data-delta="removed"]:not(.am-delta-badge), .am-view-changes .am-cluster[data-delta="removed"], .am-view-changes .am-cluster-label[data-delta="removed"] { opacity: 0.55; } .am-view-changes .am-node[data-delta="removed"] > text, .am-view-changes g[data-delta="removed"] > .am-edge-label text, .am-view-changes .am-cluster-label[data-delta="removed"] { text-decoration: line-through; } .am-diagram .am-delta-badge circle { fill: var(--badge); stroke: var(--paper, #ffffff); stroke-width: 1.5; } .am-diagram .am-delta-badge text { fill: var(--paper, #ffffff); font: 700 11px var(--font-sans, sans-serif); text-decoration: none; } /* er: entities and relationships use the flow rules above (an entity is an .am-node, a relationship path and its end marks are .am-edge). A marked field row adds a tinted band, colored text and its sign at the start of the row; Before and After show a plain schema, so the bands and signs hide there and the rows keep their place. */ .am-view-before .am-er-band, .am-view-after .am-er-band, .am-view-before .am-er-sign, .am-view-after .am-er-sign { visibility: hidden; } .am-er-sign { font-weight: 700; } .am-view-changes .am-er-band[data-delta="added"] { fill: var(--ok-bg); } .am-view-changes .am-er-band[data-delta="removed"] { fill: var(--err-bg); } .am-view-changes .am-er-band[data-delta="changed"] { fill: var(--warn-bg); } .am-view-changes .am-er-sign[data-delta="added"], .am-view-changes .am-er-field[data-delta="added"], .am-view-changes .am-er-key[data-delta="added"] { fill: var(--ok); } .am-view-changes .am-er-sign[data-delta="removed"], .am-view-changes .am-er-field[data-delta="removed"], .am-view-changes .am-er-key[data-delta="removed"] { fill: var(--err); } .am-view-changes .am-er-sign[data-delta="changed"], .am-view-changes .am-er-field[data-delta="changed"], .am-view-changes .am-er-key[data-delta="changed"] { fill: var(--warn); } .am-view-changes .am-er-field[data-delta="removed"] { text-decoration: line-through; } .am-view-changes .am-node .am-er-sign[data-delta] { text-decoration: none; } /* tree */ .am-tree .am-delta-badge { display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box; width: 15px; height: 15px; flex: none; border-radius: 50%; background: var(--badge); color: var(--paper); font: 700 11px/1 var(--font-mono, monospace); text-decoration: none; } .am-tree-box[data-delta] { position: relative; } .am-tree-box > .am-delta-badge { position: absolute; top: -8px; inset-inline-end: -8px; } .am-tree-label > .am-delta-badge { margin-inline-end: 6px; vertical-align: 1px; } .am-view-changes .am-tree-box[data-delta="added"] { border-color: var(--ok); } .am-view-changes .am-tree-box[data-delta="removed"] { border-color: var(--err); opacity: 0.55; text-decoration: line-through; } .am-view-changes .am-tree-box[data-delta="changed"] { border-color: var(--warn); } .am-view-changes .am-tree-list li[data-delta="added"] > .am-tree-label { color: var(--ok); } .am-view-changes .am-tree-list li[data-delta="removed"] > .am-tree-label { color: var(--err); text-decoration: line-through; } .am-view-changes .am-tree-list li[data-delta="changed"] > .am-tree-label { color: var(--warn); } .am-view-changes .am-tree-list li[data-delta="removed"] > .am-tree-label, .am-view-changes .am-tree-list li[data-delta="removed"] > .am-tree-sub { opacity: 0.55; } /* Connector lines in a view that hides siblings. A hidden node keeps its stretch of the line, so the line stays whole between the nodes that remain and stops at the last one; the markup (data-line-before / data-line-after, see src/components/tree.js) names the stretches that differ from the usual look. */ .am-view-before .am-tree-list li::after, .am-view-after .am-tree-list li::after, .am-view-before .am-tree-col::before, .am-view-after .am-tree-col::before { visibility: visible; } .am-view-before .am-tree-list li[data-line-before="full"]::after, .am-view-after .am-tree-list li[data-line-after="full"]::after { bottom: 0; height: auto; } .am-view-before .am-tree-list li[data-line-before="short"]::after, .am-view-after .am-tree-list li[data-line-after="short"]::after { bottom: auto; height: 0.95em; } .am-view-before .am-tree-list li[data-line-before="none"]::after, .am-view-after .am-tree-list li[data-line-after="none"]::after, .am-view-before .am-tree-col[data-line-before="none"]::before, .am-view-after .am-tree-col[data-line-after="none"]::before, .am-view-before .am-tree-root[data-line-before="none"]::after, .am-view-after .am-tree-root[data-line-after="none"]::after { display: none; } .am-view-before .am-tree-col[data-line-before="full"]::before, .am-view-after .am-tree-col[data-line-after="full"]::before { inset-inline: 0; } .am-view-before .am-tree-col[data-line-before="start"]::before, .am-view-after .am-tree-col[data-line-after="start"]::before { inset-inline: 50% 0; } .am-view-before .am-tree-col[data-line-before="end"]::before, .am-view-after .am-tree-col[data-line-after="end"]::before { inset-inline: 0 50%; } /* the count row and the switch */ .am-delta-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 14px; margin-top: 10px; text-align: start; font: 12px/1.3 var(--font-mono, monospace); } .am-delta-count--added { color: var(--ok); } .am-delta-count--removed { color: var(--err); } .am-delta-count--changed { color: var(--warn); } .am-delta-switch { display: inline-flex; margin-inline-start: auto; border: var(--bw, 1px) solid var(--line-2); border-radius: var(--radius); overflow: hidden; } .am-delta-switch[hidden] { display: none; } .am-delta-switch button { font: inherit; padding: 3px 10px; color: var(--ink-2); background: var(--paper); border: 0; border-inline-start: 1px solid var(--line-2); cursor: pointer; } .am-delta-switch button:first-child { border-inline-start: 0; } .am-delta-switch button[aria-pressed="true"] { color: var(--ink); background: var(--accent-bg); font-weight: 600; } .am-delta-switch button:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; } @media print { .am-delta-switch { display: none !important; } } `; var RTL_CSS = '/* Right-to-left pages (html[dir="rtl"]: Hebrew, Arabic, Persian, Urdu, Yiddish). base.css uses logical properties, so the layout\n mirrors by itself; this file holds only what logical properties cannot say. A left-to-right page never carries it. */\n\n/* Code, diffs and paths read left to right in every language. Inline code is isolated, so the text around it keeps its order. */\nhtml[dir="rtl"] .am-codeblock, html[dir="rtl"] .am-code, html[dir="rtl"] pre { direction: ltr; text-align: left; }\nhtml[dir="rtl"] :not(pre) > code { direction: ltr; unicode-bidi: isolate; }\n\n/* Small labels use the monospace font. No common monospace font has Hebrew or Arabic letters, so the browser borrows a fixed-width\n fallback that spaces the letters apart; letter spacing and capitals also break these scripts. Labels use the sans font instead. */\nhtml[dir="rtl"] .am-md th, html[dir="rtl"] .am-kv dt, html[dir="rtl"] .am-head-meta b, html[dir="rtl"] .am-panel-meta,\nhtml[dir="rtl"] .am-annot-meta, html[dir="rtl"] .am-tree-tag, html[dir="rtl"] .am-timeline--v .am-tl-when,\nhtml[dir="rtl"] .am-diagram .am-cluster-label, html[dir="rtl"] .am-delta-bar, html[dir="rtl"] .am-lim-val,\nhtml[dir="rtl"] .am-colophon, html[dir="rtl"] .am-reply textarea {\n font-family: var(--font-sans); letter-spacing: normal; text-transform: none;\n}\nhtml[dir="rtl"] .am-head h1 { letter-spacing: normal; }\n/* The annotated sentence is set in the sans font too; src/components/annot.js measures the placement of its notes in that font. */\nhtml[dir="rtl"] .am-annot-line { font-family: var(--font-sans); }\n\n/* Diagram text: the drawing is already mirrored (src/components/flow.js, sequence.js); the text inside reads right to left. */\nhtml[dir="rtl"] .am-diagram svg { direction: rtl; }\n\n/* Limits scale: the ticks are placed from the right (src/components/limits.js), so each one centres on its point from that side. */\nhtml[dir="rtl"] .am-lim-ticks span { transform: translateX(50%); }\nhtml[dir="rtl"] .am-lim-ticks span:first-child { transform: none; }\n\n/* The limits value is written in the page language (src/languages/he.js: "420 of 1000 KB" in Hebrew words), so it reads right to left\n like the label. The label, its note and the value are isolated from each other: "Redis" at the end of a label and "70%" at the\n start of its note would otherwise run together as one left-to-right piece. */\nhtml[dir="rtl"] .am-lim-head > span, html[dir="rtl"] .am-lim-note, html[dir="rtl"] .am-lim-val { unicode-bidi: isolate; }\n\n/* Horizontal timeline: a date written the Hebrew way (15.11.2026) is wider than a year, and in a narrow panel the dates of\n neighbouring items ran into each other ("1.12.202615.11.2026"). The date shrinks to fit its column, never above its usual 15px. */\nhtml[dir="rtl"] .am-timeline--h .am-tl-when { font-size: min(15px, calc(100cqi / var(--n, 1) / 6.2)); white-space: nowrap; }\n@container (max-width: 560px) {\n html[dir="rtl"] .am-timeline--h .am-tl-when { font-size: 15px; }\n}\n\n/* Reply sheet: a textarea gives the whole Markdown reply one direction, so "1. [E] ..." and "# Re: ..." came out reordered.\n src/runtime/reply-view.js draws the reply instead, each line in its own direction; the textarea stays as the copy source. */\nhtml[dir="rtl"] .am-reply--view textarea {\n position: absolute; width: 1px; height: 1px; padding: 0; border: 0; opacity: 0; pointer-events: none; resize: none;\n}\nhtml[dir="rtl"] .am-reply-view {\n max-height: min(60vh, 26em); overflow: auto; padding: 10px 12px; font: 14px/1.6 var(--font-sans); color: var(--ink);\n background: var(--paper); border: 1px solid var(--line-2); border-radius: var(--radius); overflow-wrap: anywhere;\n}\nhtml[dir="rtl"] .am-reply-view:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }\nhtml[dir="rtl"] .am-reply-view > div { text-align: start; unicode-bidi: isolate; }\nhtml[dir="rtl"] .am-rv-gap { height: .5em; }\nhtml[dir="rtl"] .am-rv-h1 { font-weight: 700; font-size: 15px; }\nhtml[dir="rtl"] .am-rv-h2 { font-weight: 700; color: var(--ink-2); margin-top: 2px; }\nhtml[dir="rtl"] .am-rv-tag {\n display: inline-block; min-width: 1.6em; padding: 0 4px; font-size: 12px; line-height: 1.5; text-align: center;\n color: var(--ink-2); border: 1px solid var(--line-2); border-radius: var(--radius);\n}\nhtml[dir="rtl"] .am-rv-a { padding-inline-start: 1.6em; }\nhtml[dir="rtl"] .am-rv-a em { color: var(--ink-2); }\nhtml[dir="rtl"] .am-rv-quote { margin-inline-start: 1em; padding-inline-start: 10px; border-inline-start: 3px solid var(--line-2); color: var(--ink-2); white-space: pre-wrap; }\n\n/* Video player (src/video/render.js): the scenes lay out right to left like a page, and what video.css places with left / right mirrors here.\n A theme mirrors its own placements with video.rtlCss. The controls row stays left to right, as in common video players: the play\n button on the left, then the clock, the progress bar with its chapter ticks, the speed and the export button. */\nhtml[dir="rtl"][data-video] .amv-ruler--top span + span, html[dir="rtl"][data-video] .amv-ruler--bottom span + span { border-left: 0; border-right: 1px solid var(--line); }\nhtml[dir="rtl"][data-video] .amv-titleblock { text-align: right; }\n/* The title block labels and the scene header numbers use the monospace font in video.css: sans here, as for the small page labels above. */\nhtml[dir="rtl"][data-video] .amv-titleblock b, html[dir="rtl"][data-video] .amv-scene-meta { font-family: var(--font-sans); letter-spacing: normal; }\nhtml[dir="rtl"][data-video] .amv-titleblock div + div { border-left: 0; border-right: 1px solid var(--line); }\nhtml[dir="rtl"][data-video] .amv-controls { direction: ltr; }\n\n/* Remark mode: a note inside a code block reads in the page direction, not in the left-to-right direction of the code. */\nhtml[dir="rtl"] pre .am-mark-note { direction: rtl; }\n/* A code block reads left to right, so its inline start is its left edge; the mark bar goes to the right like the other marks. */\nhtml[dir="rtl"] pre.am-mark { border-inline-start: 0; padding-inline-start: 0; border-inline-end: 3px solid var(--mark); }\n'; var MATH_CSS = `/* Temml.woff2 is a clone of KaTeX_Script-Regular, except that the code points have been changed from ASCII to Unicode Mathematical Alphanumeric Symbols Script capitals, Unicode range 1D49C to 1D4B5. */ @font-face { font-family: 'Temml'; src: url('data:font/woff2;base64,d09GMgABAAAAACS8AA4AAAAAQFQAACRnAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAABmAAgnAILgmXFxEICthwxX8BNgIkA4EQC0oABCAFhlQHIAyBCh+jNTMDwcYBQMLe3iiCjQOEGjwO+K8PuDEE76D6jHAQlli8ik4tRHgIIzcV0wnC7xzHcbCq6swiTZnK574sIY6Ixf/prXV/hCSzw9Oc/r2T5OJ3cSQQI1gIEUiCBGIGxBGTlrpREdv6R8VmtYnVtv12rJ1080618/LQN0b7O7PtzqsjopFHNquaoVRCEbd2oWMeksqaKjb7W6WWSJbzxbdsR1lrSx7QDvMG/P9r+58VJXiaEy5iM4h9GSzPUEnQzLOipihZ6aqdAW/vbwDZV6rmqwXaA0+h/RSm/3cu2pBqN1XEtnvt4hYk6AwSJwFWiFQkPHiliBXPRef/zWlJV+n6bWnqlJPYrb3jlLMbT+P3XOL8VEsA6TW9BgdugHRaYLUq0erG0wBhBa6pJ/z/m1rS+drrhV5KRZ3fIhx0gHn+WBvNl5yVRt57lpRia7uc4rO3ap3ils4a66gVgLT2lVJQWkEX1mBYqYgEAF46CgwO/P//vab7nP/SrnKUbvpL6ctD0TYVx2yAsnwaMI+tJTaosLU1CRVY+7o5W+K8dKqlBMvD5CggNP4oG2EgeG9cBkFrr68M3tEVGLoWFoGQ5FI8kkK7JstNK2lr2dsuVRcfluUgCNh4X+M1mFJiibYFiIzytdwKE+xWI+grUSOeGIEzwUkdHDWNbh43lPWO6x83MG7muHnjFrTG3O56be6PV3vftnW7a2/++O2zD2LA3SKmJe9ZQMpIDwDmHmTwQul8uSQGBDbDZkZTso7GBejQC73AgH7oByYMwACwYCbMBDbMhXnAsRa0S0Ek8OgwF+DCUTgGPDgJJ0EAZ+AsCOECXAYxXIU7D5HqKjW6oUQy5bS42z6GALTXdOEPn1CoBRSPkyQ9/guxrx9Duf0QKTgk6zmvVeJPwN7/wwWX9oXNnfdAtUAOGw8QEEcODQBIy7oBhpbiuRBo/jBk6YCTxeTVx+jNu1KTCF1Fj/XZCfPuZvNzUiUs9Euy0vo7KY6mJS01e65Ea5U1NWR0mbMknU/0V5qRGe2cXN4B4i4xoFBc6RvegEATgXq/FBhfRDALUCw47xOgNKAwb17Liq2C5qYFwGeckwoKIEtfy1HFH9oZjaptO2h+GfML179K0RLDs97GFznF0r3zUY0BKi8MEGweRjGoLW8CwkgPvFqr/Jo5sOX0qZCgMQYXf1FpE5cQa0lkbh5PO8IxUIfimITz3uCMVusAi87JA2G1bYF61qQoqpbWUlCKRW9DeojVRikbcOvH3TwYBgsH5UmAGq4ZE5VKHhiD5nn0znw678cUFircOKeDWWhgY9qSKj5iwYPIQoL1DkSAVdnAgSDMsjP9RaW9KCpsZpUWGGwSsyQCj9/Ein6P08ljK37sm0FmB17Gx68PjvOzi3dxLirE4VuDs4sBaoATSakI9lCZR2BNAeyYQHxRNaANmg7KmtS1t/R2tOe15WgYzL/FuTTB5AyW8EoddzXjxD1WCDpgYwCBygageto5DxsVfP4mwrGgCYzdZaEboATlN6T+ItY2hS32UFr+uZ1gPNyJ1MBHhXgXGgSoYJciNIzICGKjJBkj2RRSTCXVOBLTLrOhFvCuTQ6/TS5/D52GDbpCTqUrTdKckBlGbgSFUZTGyDAFlalkGifLtKla6CjkMcoyRNFIDBq5hHeFOua+NBqQF+QtG/IhzeSN0Podg7LmqXLOp0+v1kWdwQ75y4rp9iReTkqaOus0R9Kvh27ILA3chyjnHQWAEKDhvBAS/oTuvjzjO7ZT9bp9vqEA6jSTOQfkwBeooRDtka7BSF4AcHFkqgkVFuEpteYDKaJ75GFZc2k+6hXnDhoPxcJgVN0+eL7BDCAhqH8EWTI0XlgVjUldMVM2Ksqq5ROCWqD0RcuY3631Ov6A03ZyP+C8fZACDhZxqiiWeI02zaAcTpSxKA34CTDz0kDYL8ETTu8GZRyqu9wh9SaBZpv2O87AOQX2rejpIbPWZE1albL9Pcz777qEPmjWynRBFnhOI91d+jPSzYo9SCjtw7IdctbtlLpKLgNuQdIUO0SPUek8oYRu1R1BsdiqM++AZ5Dk6TG7Q/HMfR5m4LSzGkax7h2yUCbLTVhcP1NO0XXV7HFnVAZTa7nE6auoLrVVo1sXSvPLu72Yb9VeNaZ7fhswB+O4HRRFkaYuYB6qmKctQNmiofc6okzLehjmTZ2wstxh+dhOVLzlIRz/xSwCU4XEmM+d7Tjv86NPclaXQcY2sPvobZO6fFBbnoVggO4GaCxOD9S6Bct+vO99qBDYOkBhfPID05bAX5BwYmo1LYpqq10QIbklEF5VYICgsanSgKoAopC1huk0cSQb0QCimLXGm+47kY3qAKIaa61tGrkuG/UBRA3W2tg0dVM2mgOIWqy1tSm4LRvtAUQd1nMnKDZ17XpjC3hZ1HSrnJ4dJGyy0rsuWYK+UkJ/KakD0+DKYKeUoVLKcCllpJQy2rEwVixMKxamF8uLcfV5GgoXUFP7eol3nkGUoVl55Ys8CACt4bgLJbTwHrqci13urkELXMBmYL9EtqC+p7cuZBWA6mcG9O3GYJNIRekMRuJpIpY0i0Ps5PKoe+QjXEu19uHLalli4XRBt4C9iN3D45+lNUrj61hjwiZy9vzB9Y+vO0eyGBgDTTAQerdVHMPXwXcZN6deOXxG55dr5zRuZW8E31oKq3tb7UF8v2rcarppqV1GoY3E9OC0m9wrr3BUkGoQIAXhVSH4geCm0ATrb9tO2UYYes6jzWGHUih4aG4RMiW4KRS+L72nWkkFxTJuuFYYxTEC/ogFcdHCSwmE7x8rZn5SzMMEKu9mitiEgvP7SKZKiulAEUqlXE/lDIft4cgfcHOqcvMHYJgWFEvzCeFVW4L/1wkMbVsjMgOe4nJKpb9EJxTE38YoX2Bu2gQQCNb4OSVlJWD3BJ9oTn0fPhZyWU8pVWsUhrsd9puCBoiyRp6kiHg6MoITb4rjH8yfRCw4Sp/gQwTxalThPTeuT7SeSZWeW2doH20+ViyZRyjH1KXZgpux5Ngx7OaB4DOHzjQviDcBbixoFprC2JiMwOynX2PrOu80MTA/nfCQLzwRvtQWdstwPIswelZp3a6fv8Xj1aMsBzoOucdbygT2+9XYBCdGStvsmSID6LS85YqE74oLDgi+IW9pHytPkGbwFwWrzeqVOcFNOXprrITjq2if0o727INIP+ySk4Q1OMJKGGaHt20rWPkLAmzLEIJp0TgazVmAkwsULojw6vgF2B11o3H6kTnmmb7mWUtx8m5RxKIw4dznzEOF8F1A+b+bYsQobURx9a5t0qcyKajmz8nUOagiYXifgvFbZTx0rQjAQ1GeQrajThhmg5cSiZ6i8OYzCsUyTuRjk79aeKzGylSehnEbGhW9tlGrJSMRHSaJ21Ot8vKGGdPlEyejkL1BIcv5gGUB1qpMkT+ssBAFtQophEv+eIK52em0IUlAtevqUXC80ta2lk6pu8XNsLKv83Rbk7B0TWWdwmIXRIIFP+jStheeHKz/ofFUam1xM5AQZ5c/YxnmR5r+6tYKN4XSOGzkdvoEdPQFyCLBfzCE54AYjg8XJuOW4Zxg1Ut7M7JE4aDgM4KteVXRrOiDRUU/3WQ2yqeZwbfUYT7pXI2VDtZHxaOJuuylNLNMS98/8dsCN0eY7+3FI+KShat1U+BtBdwcAZ0FugofcUajDyVykQwP2nWN6xq1vC8KB0PgVNdBVNz55fYxHyfp2l4gucoyxbYZJW+UnZlpxyBGkYIyPRXXuVFlyONMt3mYEA89mcsZdixQUF59plxM+F+e2OZ7R8R0lJbYoUTu5h9pYjhoKZRIoijHloqbth28OtBuyd3lZj3SXmnXnCIPTZ3Y25PqvMjCSKVucJpqNVDEAkuqYjg+Wego4Ws4P5Epbm8E2j9oXBJU0b/l6Cv/6sEmAnl1DxED3UwiWJSx8mZ8Afttw34qceWp6LQ84NGbUmQfxCdBIXufd1rGseo0twXtFJomHyDwEfcDwfdTKCUnxD1u3N3EdzOFMAqjmrAmohLZWCWzXvFUQty4Hin+Ls2aBdBkG7Pka3EpnOANpkTl9r9rHMPuNxME7ndSUJS82DLhcFncY3AjFHBh//mqWY34vCl3eV0rMBrdo2R4mAi3aqGTWPzj148DZcqiTFT6LYBdfPELplOqNmKQgnXbFIbynwqFD0byrj7GvzNbIgvXQKOj6Y0h9QRlTtEw6isx8G81kJ9M9WWVk6AG+B6AEn6ZjrVahbC6O1fbwi+fqCWhAUajyLifLmA5w8kDCqFXXgk/fYIGtVe+YFwG15wnZF2n/OFi1uFIC1ZIrRZQrcZZbBXHfrA60o2+ZjiDNMS/G1+D3JjUu8AcoEz4Q4DhG+lfq2P1ZM5Ta28+ptDtS2Oj/JpGlKWRTqXEwEB85o35Ns7esGXpxFVxUdIXeBz/BQopEOlT11lg4Cp3q9TPZ84IJ2y2bXK/GLg4ZTHuzvOXjMuWB4+K/Xog+/+DMV+GQgjjebnvLxjzqmdmTWb2Y3XCxgZDHdBZGTunVD+qU+iWPVd6F50noxQqSSNZwowSYBiomuCKJI3T4M3/K4TSihRih2zDGIj0plcV5YdJI+TpgDqsFKhqW8fTKdjsUegnKWvBWKd+ML65swfmAdhQEuZEL+XAxZNd7otlYJhnGmri3wh3Ctc1ROPAZndQPGQeDYIytMH+a3QFOhYJzKvgSA4r2rUc/ZkGdiuKJw5/EZ9kxHKPgAbiRO5Lra/v0jNLzVudveI48SOZk30oSGEiQfvO38l4qgqNdb8c8Z16xnDA3Khwj3JcMF0viVY441HHelwT8NesyYudDisyw43qPHErPDbJWje8kzbNJkiZ7XfZSjYpKa4zHO6FvrC2N3ZfrqmU1IYDtW3yh/44HRpu6mib0uLm45O0ILujzo9VbMw/0rurTLIUgKNKeuhZy1BWajsyE3zjBIXs2czQ7+DuKmx8DWyyedVlKRSbrsIkP8YUVhnq/EVRZszz+8MXS4mACH+2jnSGeuqwgiqqau67v2gsbVQhv3s2qMrpU3649s4g03MdHG7e4LkyP8x2oqhfclXOtQ+OMMP6R97dKB+2JabWtuOTGauop+M+RetinTnYaAjIsyU6L9/AfRjAG8BK0q7K3018fyZijAx6BDu2hssFtKW5A5g30x5ICnFzRfZh+7CJpSBScrmeDkuFw1uvAmmhMsC+KeUgsa8YYRUP1z6AanoU0V22ylKBJtIGwOgsuIXPaR2Zym5QUlUAZFhFB6V2dbDzJ3extld2HkabyCNQAS/ZsszuzwkrRuwo5vMLMTeLOqmkKx61h8KSqk0DL/h9KY1PBaqSpMScgVdlhm13GOgonGhcDqqejDUDnNmMZEYsGefF4MmwTyBoj/SHk27+wqF93SKA0TsVo9u9ZtTEcrcilfZmlvnAHQVxl+FpCiLIPbck+HFsXEpExAKSgg+U4UixqEtxrhvtwRccWQ5uLzwZtSd7yF5xUx1zZFdPIhKCRr0Rl87o3kkvOZ7CXwx1/OkCMw/W3gE06eCHI5fuR5UepRew4ldmFrg6tYnDLUkaCMC0o8vNkI2fiXLVG64Io+gGIxdO9OpKAZ3d4EGKG/VqLiDvY4MllSqHlXfWKESlWUNRzi3VbfDSeLZtZWvfeWJELBBVs7qh8IPQtryUOJbRvng9GxydZPrH3MkUAQbLeOfUOCyPiDZY/ET33Bi/ZFQ6ZC/QK0J7c+VxsOJgs/3x2Nb0I92qz4A9tk5JFwvnmnDuaHAnK6HMZho8r7Vg1HSDgOjB1e5bNZnCDdIXMI55F1ck4YUT5yFnyqjAW/YQhL6tHgh6wXZBu05/Vg9Ff+DM+2SaavLYtzLR9+9sKnCQEP73g6ABQbPkJc2ZH0+bx4meRkhsrF89CruPza49Nuy/9dWfJGjhZ/6OCbPU25ORjXCINeNQccTXwk7qTTI5CnpIUkhBeiPDF8VcYBhycl2AbG0xp8Zz2efKSDbBifckn4rhVCGFurvQf0fMwiV1yYK/DmMMie5uEVIZvXkRoVqycJ314NvQwY9FE10rjk9UsUbFfguRe/TdxulTBDISAtgWa71UH9PmF/GdTdy4ggQkSdau+fLCCvpichjtTupb8eBlg0cMkj8qmsMtiXra18xfWnfgmKIjmMVk2868LuV2yicd8vwMFR31pMw/ZLCSixYdbtksKgvlQ5OeX0nzHxEBa1oroAr3vV7IJH6tFETjF4vOYDWSalb6Y9XzV3mH0xYpFj2OoEto6Pbt4yOxQHsks14RHPWI8q0bc4TJJrlsNYJlyipIyb3R4ap9L85xzWpzpFVnV+l8aQuR+w4mAEgoaU728eMBjXuDkMiftgXZX+gr7zRXJOJQCfe1bJoqb36OrVL31+R2N4cH1laNYO968q8DH786/bpfdL2HFFGw7r9avFC/PHVkelyyaus3TrpOJTmYQOS0jPp/O42meMuRKIA3fX+z+9bQmhvud5LXzN+fQTOMl8SzNjerVBJ3wf4aAwxbEVkoi/O+wp2W3vPdn4syFPIvIz3e+vj3XzjKwiJOJ01//QWmcslqZbBi7neo9gUNGmJKwZP3dqEMj2plORNaOFswO4ku1ZI4tjonJb9KNfPooiebW3udBd/yZhnrnG7hMnWk+f85wQaMZbYLGxdeXptVu4OkkZAGppFoSo2r9V2pw+amWel5pbl8ETscDr+/acPAPiZipY67lK/coFQlAezLq7P4amHuCXrApTxq9jwuov27MN+R6JWIOTOdgKWKVzSXVvILguV17TgVImDxec4XpsYFS2I5yy9+zmOGSFLID16aKSM7zk0qHk74Z9Rfnx+8I8XlCNgUSTFUQJpRGouKJV6JOVMEdEQGyeZWBJc89+h2LgIg0As9xD5ruZQN/qHD66Y/St7GNtKp0zzSl6+qUEplfvSrY5OdeuEMkTNivzmtUy5I3n3j0pt1ngrF89JkpSv8WwWgy1wn59puVSNZgXtkhtFeVlXVWaI6oP9inl6kQFu8HU5r+aaqOy1Exv9+KVQbZkqj8f9BnpCzUv/Ood0+6fW9Xl5Tv8QdbRqqd1yAGdJYNj/I2hBQXckLzReRJkcRs0Pj0gcVbBvZjeIEKZQcrw41xsrqZpKfX3L8cPRhd1al3FNvjSJaTItZGRT0DA5ILsQvZIPQPaCFPmuh4kKs6AeduJVrdKvGEAaRK/Cc7Ip42nT/tCdLuvI2P2Y5AuA+CV/ECS6cXSk+zmbu6mahE96qsx4yNS85Nce2+M6nbPbqHvvi5y46wShdJGNueunYu4y2Vo7wvxt+QaiH8Vdx1MRVRmhG/Uwa//NUfUYHzMjEAh3sK0l0S2V/8XcvmNx7hWwmX13KD19S22erCbDrOSG8mABoJmThTrCoWEmSAaINrPrigj99+SalRTeQUd78n0xdbgkVPJWZAmfA5KhUO74pi4198FXCM6O+go2tKS8KXB6KU1tIieOYPfXCgz/GuWYLlXvv2drsXeU2/5DG0UvpTHLy+qp5rDhC+X7nYnXiJ5DcbI51rKl9nyAJgC6hYsjCmV25rXzGpujZmt1f3GGmpzP3V7a/CSWmnFhoAVQc+x1HrJ1Qlss7L9Rf35X8itxam5WndS47lJshmCUmjiOMNyGtq6hQleZWprD1itPNtYbc1d0u5+HZgxF70d8UHyjYKxteWvhJqKUgcGg9FxPXbTtjCwJb+Vhk75zJnQg+W4WUeEuDUzkOXWvpeHljSbWGrqQpbCXXn5AbgY2CE3EYW6S3WRI1fMmPkXx/gcNQae6ounnj1g+e2H3m3TaaTV1njh/9zBJLgAFmtevjWZWN9sqZ95qe//yQ0NLUy07jycg2kr9GCAVKhgpMPsOxOLR547THboqF/rPrOz9deWHPmrdXXF23DyIIHLdTkJOi50wv1Sx5dZWOFS2e+Z6Xc55bnUECIl8TE67bB/MKbTh85J3OQFV+138icjG4ksnhOkler5g2ZXghA0L+bGob6CBrAZzAJl6eeHn+r13dDMGXfwh2vZQrkOWeX/i0HkrED7uag57/Cve5F3JrwGqnZ/VbitVLtldiJGIdC5ockOkIQpWFKKh8z32DtwnGkdoTlDM4gE+VRSP+R7eNssFH2Mf5EuGuL6aqWl8pzsPRqQL/20qvXrkiQ7L7N3uBazeC87aJiCUllCMS4psn3eQehHbpe1Ha6PbK0rolwvKmQZKaZ4bHA1d/4tpdXgenj+czxU8D3rJd7+cU1rXMLTCSt8SyTNb9MzWJ6/7O+wAA2GpdrP1IG5rNhT6VZ8/dz+e2cmK+q1PFPpYLI0psDKnAX3TIaOe6EFR4VitdglGpcJysWLjBKflWWnBfV6rxodlxe0/STd2keWwH0NrGSQ4Vm3OSivwPyZUn0xidxYNFAwlfe6RRVX30vF+b+tHjFQyUZ+rXvLbla1ewYrB6ttNxaYjRTOGzL35ct8DR/Js/6sheDq8yiuRnl5qqjhsGkBCWhbXgmJSySYFDwGuWXDRrQeyj3rl3+hrsjLN0Xwlb1Le+enxN1JZ15zdyLk2Vp/1TNnsBrIOp7TgwNNXffu7MsnqRfTMxYXWWRNPeHeTyh1/Fs3+0jN8AiVFRRsYWj0jLH9326N1LOf44zw12rHffpuWfUOFIVNb0ENJm1iucHlPCZmfh6Jx9OLF3d3nTfi13YKwozk9Pk18a3HWdHS7aUzl6rBJy2a9vAdSyLBRJ3TYQWjwj3ZmxbYSXkckD4daHy++tRRg8IxeQeyiot5nLb4VmttrYn8zztMzOg0f9mUIXiylzzXqjNdErVcq58l2KeetWNEbMlw/xqlx84aja9fh7Y+maXmHHp/OPU2Ta4PUcybnk0FfT+3VNyfPkMhJUibtka9XW1gsxXuanqXJvYKYlGo0aPvVqipAW+BGXeag52AnBtU5wVg1/eF6HQF1ZVAcCl1UHh4Z8rdbKQnemmFdZnfz8Cpi/o6K0ATVfeIp8zdlcMeBN9Eygii0BRcrkw7tMxoNcOlOAX23Z+TwMSBgmqdjAm9bCmBRPH0gSJ8+jsFKLwu3dCW7ig/guHUwZw5FCz743EIhACC1H9S8yTnTUFXWv/3vYg8EbEYL7LQt/Dj6DKOryX3Bd7f57dvepbTmtnH3/7U0yFzLCblv1PpKUufZil6qq/trMMCwwGrbc+LAQPreHQU8aYy5SaQrDnzxR2p4c/iYvWduwJjigPmEnCSKOHtSGqlI5C1OyKxAEA27Pzge9Ay5XQL4UwWmIDRjgBxPDEVPdgn3vD9zdJiflZOEilT8sUmGeXI3x6Kmz+z/MXXpgSLlTyGgv4P58Qk0vsIffn1RSUmLFrq2PU6TGJGfbxHBPbR1rET8AJ8JBgkLvji2YcfJ4wpr+Rm9wpyQyl+HXVfTUzQnvK82foeOc6zu9uzQRmykKrKLBG2/9+dwqEkUm30PuJHV7cBhFRB9ZWEvoicBWo8s0yAgUEECCwHGYPsjvKmuIzun3qv5YzNHc2J6Nwrq6HbMR6MI9FIdJqlx42i9C2ZMXsNmHiue0Tf6+vqZG2Z/riZzM8fXbmMmpaYyz7vkf7TC5xe0ULkFOwrRkUpy0+HuNa9PgvZ/wPHVucucnK8YSE/0HWULTR0P0q+2PfYdgS6lI52skMMwttWk9rJn2B6Ni3nOuqprwVGtDT4V9/qGkVlnIDo5gdZJMyUsuyije4PPY2/8vmtFrKe93lysZD6ar4JYl3Rsh+vFQkLmUIoZ8S8zeemtk+okyTsqtLj5S/bn2lXefTksZV41v1f2QF1m+ODXHZx5b6QnlhKgjpHpXTJxqe02V/qik6//vrw7a7XoA/vCzbndXGs73kDDCtDE/CTkKXnZzWb4ahVDPd/GspohSlueUUfTZvZNzP9imVIXs/vbmBdx4Pr8wWGCVbZ+RGh0x+TYnPVawicIf9VH5d56UprsPZf8PwF9t+Xk0wD4HqaeyONN6hUOjnn86fD2AMwtzqCM7UZY644UZqlpJGOY5KX01BgJgiHWR8ob222Jm/sO3PcwNXzh2Ts0iz1Jy88nECWvDaL2vVzmFCUz+Jc7PHkwMb+aNSBr3HxCwrNu3u3DMTu/m33ft0TlVLpN3apbWGb+y6yDU2lgXJDBe3f3z89ghMo1LdafvuGpOK0Z++JyxZZ8iKOqvnrJPZteF7Ikn5lQxPhEVRRDw30/uqp9b0o6H5Z0rUj0D9rZrrNtfw5E2osyZJtqYln3Hy1rYzPmJy+k+nydxOogc18lMOJUKwGap9um4NMdQIQobIUWz8pcsZihCfWzEGFl2QEhw+FezM+/z15uj23VSiLhMwXHqNP6IvchWHGKfFVjlIoIUEeFnzgwqD6tt/vISpoTu0HqMl+nVGk8WG4dvF9aUiMmS6Y+sPm2LnsmnjSAEDc3d+9LMR0pjf33KAtiOm+lT1TYOWUCZfb+1sszLwLT5xLEjX7pytI6E4Sdl26JwT76d+x3UmMLSfrBMwy0k5VIMpbLkMgQzbGt27FzMD6uJgrUsjNiSdfGdTn9DoP8fA0HI3LurCj4WNr9Bi20Aj930OSx+YyQ8bzURWo5i0RIKNm3q9H5nh8Z1dKfTa2tJfPjhyuPSOIx+UGPbLRpm2b9BMwUy3a6MthzRnivQFw+Y6bnCo2fKmzzeSwTOwtFZoqwqmVM7WxL4prihL9S0oabVHeggCRqTir6o9WnH1F05oTxnbG4PD3q73tsVPSoubMr2PGFwuTJv4XAcRm1APqHLcSVyAy+Evt/1/njDcJz4nhBSsDnEhaOhx308feR3Kjxv09XPmtuvAKjn4+m2ODv/UzU/e+ReRk02y9jLJfmfgv9M2xF0ryWuGyVle0XJJ45DTU2qVXLJrYVdPda0V/MWSVs6ncOlcnvg0PqakaSW0QUs/oNUMs6hFGGOqRfzg97Kjukgg6pFOEwYJQm1eu85exvmpUeqUf7nztSqoQlDCEMBzGf4Gp8Je0u+sBKRmyb1IwS5Ul5ZUak58G5I7CN/FCffyi0RqjLoPE4efQznTM40k0JHarT9vLFiSSrJpSEUGSGXm8Ivm9vLNcNaQ49ludZ6GmVq9OxR49PDpkqmFRHCMPkMoC0lOZp9rG1R07FRaLfIC+byRNyG0Y77E/a7nI5V0AepgtGtR+YM7ctYb7wnlKRxjW09LWuP/kL59IfvdLYsDF2Po9tv1Cd5Jywzlpn8h+cGkwiNPaoKQXXzWU3nDRh4eBtCpXlFeUxrI2PGuluIFr6NwxuRwjeykq6ZCoPO5s1vSEsbiJ9CzAP9BZWns5MbZdwNL2mdOvdlIU0MwLMw9lVVtpCN0u1mr/+rinLI0JR9V/XJ4Kxb5uoOduAEB81bJk8qPcggTy6bU+rbuGDuIb7mh0GCt/neeNvizJ8IRHU1hcvjgkGIB8GFr+TugQhLac/axfXv3Hdx9bk1ufVkgQJw4/M34pSl35VzViR+/q2D/geRDE/1NUDnOfva8ubxLkzVjNhAysZlZ7kuUo6iydKpE6PPvbpotogQXWMSCsl4k7Ai9DDHZUao6hLW0sLrTcfzWRAYg9EPTA187v76l+4/xDFrdIs8Wa+jj5B7ASDvXrMosWDhuTDDqX/5FXjJpKX8fR50Z4FDx85ny2XNnNLz/oaRX/55f9Wefy4cx8erdM/t//+j+jzegiAcvKQNv60U1NVuBrvCd2vvRpX+op8adDGwjyNWLsiZNa8X1O5PTV4nyWwl2YedkfY/10oPPU0ulFnN0lJBEbslJwVyRhtmTdWQm0lyBUl8oiV/U5fTGLODJL7RkFKmGGLODJo8ozteUZSFolKep3QLuiNYjmIzQdDTtphOMwYBKq2tfRd0ASD25QUwtndvQhTMuzdKdwFmTkm9gEJYtl/A8ma5/PqwtcUiDsNGzDamV7ce00llaZdNSk/rgyJ5r6QOlVJOnabRyyFjUd6gU7bvpDpr+qbHpkHnvl6nAMwywDNbOYPNa9WBYNDs1hI/qUdRKmKYxyUDhvWLjHYbN9CCMbFJDmYoT3LBTY6YVyxRNcf00oZ5DzS0902uRktUaf+FVcPaxOUmPbVE0sAid+PXSocxal1Cp6ZCXvqjp1fUpbFmGm3yjDa9pktcu/VieiI5rWe6+Gek72UGbRZIzeQR9JA+W9lJKoc5KztJzY6trhPvrKsZWaUGsjbKjPcgZ9vKIcNSmjennR9oukyZLk2pvWexM8fixGs1UJEj5+XYjoa58EUaf3LIkdgrlu+1mThP97bMOyuHI+qNLJ1zb36fSIFzMQphWjFPq25dqLN/Qeci7q7x4VJ6Ww/ch66tq6kArN8K+CNuuLtClL9iKjkvbr7FQgG+WcyDR6krHiKLKJKmBh4vgazJCTlvKMGqtRHr9xqwDk3l/Mf5Rk377W8imdzqdvWDgwJmzv3ycPn1uu5GnPWxjpU=') format('woff2'); font-weight: normal; font-style: normal; } math { font-family: "Cambria Math", 'STIXTwoMath-Regular', 'NotoSansMath-Regular', math; font-style: normal; font-weight: normal; line-height: normal; font-size-adjust: none; text-indent: 0; text-transform: none; letter-spacing: normal; word-wrap: normal; direction: ltr; /* Prevent Firefox from omitting the dot on i or j. */ font-feature-settings: "dtls" off; } math * { border-color: currentColor; } /* display: block is necessary in Firefox and Safari. * Not in Chromium, which recognizes display: "block math" written inline. */ math.tml-display { display: block; width: 100%; } *.mathcal { /* NotoSans */ font-feature-settings: 'ss01'; } math .mathscr { font-family: "Temml"; } mo.tml-prime { font-family: Temml; } /* Cramped superscripts in WebKit */ mfrac > :nth-child(2), msqrt, mover > :first-child { math-shift: compact } .menclose { display: inline-block; position: relative; padding: 0.5ex 0ex; } .tml-cancelto { display: inline-block; position: absolute; top: 0; left: 0; padding: 0.5ex 0ex; background-color: currentColor; /* Use the SVG as an alpha mask (painted by background-color) */ -webkit-mask-image: url("data:image/svg+xml,"); mask-image: url("data:image/svg+xml,"); -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; -webkit-mask-size: 100% 100%; mask-size: 100% 100%; -webkit-mask-position: 0 0; mask-position: 0 0; } @supports (-moz-appearance: none) { /* \\vec w/o italic correction for Firefox */ .tml-vec { transform: scale(0.75) } /* Fix \\cancelto in Firefox */ .ff-narrow { width: 0em; } .ff-nudge-left { margin-left: -0.2em; } /* Squash hidden mrows in \\bordermatrix. Messes with centering, but necessary to squash. */ .ff-squash mtd { display: block; height: 0; } } @supports (not (-moz-appearance: none)) { /* Chromium and WebKit */ /* prime vertical alignment */ mo.tml-prime { font-family: Temml; } /* Italic correction on superscripts */ .tml-sml-pad { padding-left: 0.05em; } .tml-med-pad { padding-left: 0.10em; } .tml-lrg-pad { padding-left: 0.15em; } } @supports (-webkit-backdrop-filter: blur(1px)) { /* WebKit vertical & italic correction on accents */ .wbk-acc { /* lower by x-height distance */ transform: translate(0em, 0.431em); } .wbk-sml { transform: translate(0.07em, 0); } .wbk-sml-acc { transform: translate(0.07em, 0.431em); } .wbk-sml-vec { transform: scale(0.75) translate(0.07em, 0); } .wbk-med { transform: translate(0.14em, 0); } .wbk-med-acc { transform: translate(0.14em, 0.431em); } .wbk-med-vec { transform: scale(0.75) translate(0.14em, 0); } .wbk-lrg { transform: translate(0.21em, 0); } .wbk-lrg-acc { transform: translate(0.21em, 0.431em); } .wbk-lrg-vec { transform: scale(0.75) translate(0.21em, 0); } } /* \\cancel & \\phase use background images. Get them to print. */ menclose { -webkit-print-color-adjust: exact; /* Chrome & Edge */ print-color-adjust: exact; } /* Array cell justification in Firefox & WebKit */ .tml-right { text-align: right; } .tml-left { text-align: left; } /* For CD labels that grow to the left in Firefox and WebKit */ .tml-shift-left { margin-left:-200% } /* Styles for Chromium only */ @supports (not (-webkit-backdrop-filter: blur(1px))) and (not (-moz-appearance: none)) { /* Italic correction on accents */ .chr-sml { transform: translate(0.07em, 0) } .chr-sml-vec { transform: scale(0.75) translate(0.07em, 0) } .chr-med { transform: translate(0.14em, 0) } .chr-med-vec { transform: scale(0.75) translate(0.14em, 0) } .chr-lrg { transform: translate(0.21em, 0) } .chr-lrg-vec { transform: scale(0.75) translate(0.21em, 0) } /* For CD labels that grow to the left */ .tml-shift-left { margin-left:-100% } /* MathML Core & Chromium do not support the MathML 3.0 element attributes. */ /* So use styles. */ menclose { position: relative; padding: 0.5ex 0ex; } .tml-overline { padding: 0.1em 0 0 0; border-top: 0.065em solid; } .tml-underline { padding: 0 0 0.1em 0; border-bottom: 0.065em solid; } .tml-cancel { display: inline-block; position: absolute; left: 0.5px; bottom: 0; width: 100%; height: 100%; background-color: currentColor; } .upstrike { clip-path: polygon(0.05em 100%, 0em calc(100% - 0.05em), calc(100% - 0.05em) 0em, 100% 0.05em); } .downstrike { clip-path: polygon(0em 0.05em, 0.05em 0em, 100% calc(100% - 0.05em), calc(100% - 0.05em) 100%); } .sout { clip-path: polygon(0em calc(55% + 0.0333em), 0em calc(55% - 0.0333em), 100% calc(55% - 0.0333em), 100% calc(55% + 0.0333em)); } .tml-xcancel { clip-path: polygon(0.05em 0em, 0em 0.05em, calc(50% - 0.05em) 50%, 0em calc(100% - 0.05em), 0.05em 100%, 50% calc(50% + 0.05em), calc(100% - 0.05em) 100%, 100% calc(100% - 0.05em), calc(50% + 0.05em) 50%, 100% 0.05em, calc(100% - 0.05em) 0%, 50% calc(50% - 0.05em)); } .longdiv-top { border-top: 0.067em solid; padding: 0.1em 0.2em 0.2em 0.433em; } .longdiv-arc { position: absolute; top: 0; bottom: 0.1em; left: -0.4em; width: 0.7em; border: 0.067em solid; transform: translateY(-0.067em); border-radius: 70%; clip-path: inset(0 0 0 0.4em); box-sizing: border-box;} .menclose {display: inline-block; text-align: left; position: relative; } .phasor-bottom { border-bottom: 0.067em solid; padding: 0.2em 0.2em 0.1em 0.6em; } .phasor-angle { display: inline-block; position: absolute; left: 0.5px; bottom: -0.04em; height: 100%; aspect-ratio: 0.5; background-color: currentColor; clip-path: polygon(0.05em 100%, 0em calc(100% - 0.05em), calc(100% - 0.05em) 0em, 100% 0.05em); } .tml-fbox { padding: 3pt; border: 1px solid; } .circle-pad { padding: 0.267em; } .textcircle { position: absolute; top: 0; bottom: 0; right: 0; left: 0; border: 0.067em solid; border-radius: 50%; } .actuarial { padding: 0.03889em 0.03889em 0 0.03889em; border-width: 0.08em 0.08em 0em 0em; border-style: solid; margin-right: 0.03889em; } /* Stretch \\widetilde */ .tml-tilde-2 { transform: scale(2.0, 1.1) } .tml-tilde-3 { transform: scale(3.0, 1.1) } .tml-tilde-4 { transform: scale(4.0, 1.1) } /* Stretch \\widehat */ .tml-hat-1 { transform: scale(1.9, 1.0) } .tml-hat-2 { transform: scale(3.9, 1.0) } .tml-hat-3 { transform: scale(5.8, 1.0) } .tml-hat-4 { transform: scale(7.8, 1.0) } /* set array cell justification */ .tml-right { text-align: -webkit-right; } .tml-left { text-align: -webkit-left; } } .special-fraction { font-family: 'STIX TWO', 'Times New Roman', Times, Tinos, serif; } /* flex-wrap for line-breaking in Chromium */ math { display: inline-flex; flex-wrap: wrap; align-items: baseline; } math > mrow { padding: 0.5ex 0ex; } /* Default mtd top padding is 0.5ex per MathML-Core and user-agent CSS */ /* We adjust for jot and small */ mtable.tml-jot > mtr > mtd { padding-top: 0.7ex; padding-bottom: 0.7ex; } mtable.tml-small mtd { padding-top: 0.35ex; padding-bottom: 0.35ex; } /* Firefox */ @-moz-document url-prefix() { /* Avoid flex-wrap */ math { display: inline; } math > mrow { padding: 0 } /* Adjust Firefox spacing between array rows */ mtd, mtable.tml-small mtd { padding-top: 0; padding-bottom: 0; } mtable.tml-jot > mtr > mtd { padding-top: 0.2ex; padding-bottom: 0.2ex; } } /* AMS environment auto-numbering via CSS counter. */ .tml-eqn::before { counter-increment: tmlEqnNo; content: "(" counter(tmlEqnNo) ")"; } body { counter-reset: tmlEqnNo; } /* Math (src/math.js). Temml's own rules come before this file (src/themes/math-css.js). MathML takes its colour from the text around it, so every theme and mode works. */ .am-math-block { display: block; max-width: 100%; overflow-x: auto; margin: 10px 0; } /* A marked $$ block is dir="ltr", but the note chip inside it is the reader's text and follows the page. */ html[dir="rtl"] .am-math-block .am-mark-note { direction: rtl; } /* Inline formulas wrap at relations and operators (Chromium, wrap: "tex"); one part that cannot break scrolls inside its box and never widens the panel. */ math:not(.tml-display) { max-width: 100%; overflow-x: auto; overflow-y: hidden; } /* A formula that Temml could not read: the TeX as written, in the error colour. */ .am-math-error { font-family: var(--font-mono); font-size: 0.9em; color: var(--err); background: var(--err-bg); padding: 1px 5px; border-radius: 4px; white-space: pre-wrap; overflow-wrap: anywhere; } `; var DELTA_JS = "\n// \u2500\u2500 Change markers: the Before / Changes / After switch \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// The switch starts hidden because it needs this script. It swaps the am-view-* class of its diagram; the delta styles show the colors and badges only in\n// the Changes view and hide the items that do not exist in the view.\n(() => {\n const VIEWS = ['before', 'changes', 'after'];\n const setView = (el, view) => {\n el.classList.remove(...VIEWS.map((v) => `am-view-${v}`));\n el.classList.add(`am-view-${view}`);\n };\n for (const bar of document.querySelectorAll('.am-delta-bar')) {\n const view = bar.parentElement;\n const group = bar.querySelector('.am-delta-switch');\n if (!group) continue;\n group.hidden = false;\n group.addEventListener('click', (e) => {\n const btn = e.target.closest('button[data-view]');\n if (!btn) return;\n setView(view, btn.dataset.view);\n for (const b of group.querySelectorAll('button')) b.setAttribute('aria-pressed', String(b === btn));\n });\n }\n // The expand button copies only the drawing into the viewer; the viewer shows the view the diagram is in.\n document.addEventListener('click', (e) => {\n const host = e.target.closest?.('.am-diagram-expand')?.parentElement;\n const canvas = document.querySelector('.am-lightbox-canvas');\n const view = host && VIEWS.find((v) => host.classList.contains(`am-view-${v}`));\n if (view && canvas) setView(canvas, view);\n });\n})();\n"; var VIDEO_CSS = `/* Video player: the 1920\xD71080 stage scales to the window. All colors come from theme variables. Theme-specific video tokens and decorations live in each theme's definition (themes/.js). */ html[data-video] { /* Player controls stay out of the picture and ignore the theme: one fixed set of dark tokens */ --v-ctl-bg: #0b0d11; --v-ctl-line: #1d2330; --v-ctl-fg: #c9d1dc; --v-ctl-muted: #5b6576; --v-ctl-btn: #141922; --v-ctl-btn-line: #2a3240; --v-ctl-accent: #58c4dd; --v-ctl-white: #fff; --v-letterbox: #000; --v-stage: var(--bg); --v-cap-fg: var(--ink); --v-cap-bg: var(--paper); --v-cap-border: var(--line); --v-title-font: var(--head-font); --v-glow: none; } html[data-video], html[data-video] body { background: var(--v-letterbox); height: 100%; overflow: hidden; } html[data-video] body { font-size: 22px; } .amv-viewport { position: fixed; inset: 0 0 56px 0; overflow: hidden; } /* A video with more than one scene keeps a chapter strip above the controls (the player sets the attribute before anything measures). */ html[data-chapters] .amv-viewport { bottom: 96px; } .amv-stage { position: absolute; left: 50%; top: 50%; width: 1920px; height: 1080px; overflow: hidden; background: var(--v-stage); color: var(--ink); transform-origin: 50% 50%; } .amv-camera { position: absolute; inset: 0; transform-origin: 0 0; } .amv-overlay { position: absolute; inset: 0; pointer-events: none; } .amv-ghost { position: absolute; left: 0; top: 0; transform-origin: 0 0; margin: 0; } .amv-ghost svg { display: block; max-width: none; overflow: visible; } /* \u2500\u2500 Sheet frame (hidden unless a theme shows it) \u2500\u2500 */ .amv-sheet { display: none; } .amv-ruler { position: absolute; display: flex; font: 13px/1 var(--font-mono); color: var(--ink-3); } .amv-ruler span { flex: 1; display: flex; align-items: center; justify-content: center; } .amv-ruler--top, .amv-ruler--bottom { left: 24px; right: 24px; height: 24px; } .amv-ruler--top { top: 0; } .amv-ruler--bottom { bottom: 0; } .amv-ruler--left, .amv-ruler--right { top: 24px; bottom: 24px; width: 24px; flex-direction: column; } .amv-ruler--left { left: 0; } .amv-ruler--right { right: 0; } .amv-ruler--top span + span, .amv-ruler--bottom span + span { border-left: 1px solid var(--line); } .amv-ruler--left span + span, .amv-ruler--right span + span { border-top: 1px solid var(--line); } /* \u2500\u2500 Scenes \u2500\u2500 */ .amv-scene { position: absolute; inset: 0; visibility: hidden; } .amv-scene-head { position: absolute; left: 72px; right: 72px; top: 72px; height: 64px; visibility: hidden; display: flex; align-items: stretch; border: var(--bw) solid var(--line); background: var(--paper); border-radius: var(--radius); overflow: hidden; color: var(--ink); } .amv-scene-n { display: flex; align-items: center; justify-content: center; min-width: 64px; padding: 0 14px; background: var(--head-bg); color: var(--head-fg); font: 700 30px var(--font-sans); } .amv-scene-title { flex: 1; display: flex; align-items: center; padding: 0 22px; font: 600 32px var(--v-title-font); } .amv-scene-meta { display: flex; align-items: center; padding: 0 22px; font: 16px var(--font-mono); color: var(--ink-2); letter-spacing: 0.06em; } .amv-body { position: absolute; left: 96px; right: 96px; top: 160px; height: 760px; display: grid; place-items: center; } .amv-fit { width: max-content; max-width: 1100px; transform-origin: 50% 50%; display: flex; flex-direction: column; gap: 28px; } .amv-fit .am-diagram { overflow: visible; } .amv-fit .am-diagram svg { max-width: none; overflow: visible; } /* The stage sizes .amv-fit to its content, which a size container would collapse to nothing; the stage is 1920 px wide, so a timeline stays horizontal. */ .amv-fit .am-tl-wrap { container-type: normal; } .amv-fit .am-md { font-size: 26px; line-height: 1.6; } .amv-fit table { font-size: 24px; } .amv-fit th { font-size: 17px; } /* \u2500\u2500 Title card \u2500\u2500 */ .amv-scene--title { display: grid; place-items: center; text-align: center; } .amv-title-wrap { max-width: 1500px; display: flex; flex-direction: column; align-items: center; } .amv-title { margin: 0; font: 700 96px/1.15 var(--v-title-font); color: var(--ink); letter-spacing: -0.01em; text-wrap: balance; } .amv-subtitle { margin: 26px 0 0; font: 36px var(--v-title-font); color: var(--accent); } .amv-intro { margin-top: 36px; font-size: 28px; color: var(--ink-2); } .amv-titleblock { display: flex; margin-top: 56px; border: var(--bw) solid var(--line); border-radius: var(--radius); background: var(--paper); text-align: left; overflow: hidden; } .amv-titleblock div { padding: 16px 30px; display: flex; flex-direction: column; gap: 6px; } .amv-titleblock div + div { border-left: 1px solid var(--line); } .amv-titleblock b { font: 600 16px var(--font-mono); color: var(--ink-3); letter-spacing: 0.08em; } .amv-titleblock span { font: 28px var(--font-sans); color: var(--ink); } /* \u2500\u2500 Captions \u2500\u2500 */ .amv-caption { position: absolute; left: 0; right: 0; bottom: 76px; display: flex; justify-content: center; pointer-events: none; } .amv-caption span { max-width: 1500px; padding: 12px 28px; border-radius: var(--radius); text-align: center; font: 500 38px/1.45 var(--font-sans); color: var(--v-cap-fg); background: var(--v-cap-bg); border: var(--bw) solid var(--v-cap-border); } .amv-caption span:empty { display: none; } .amv-caption b { color: var(--accent); font-weight: 700; } /* \u2500\u2500 Highlight when narration names an element; edge labels stay grey until named \u2500\u2500 */ html[data-video] .am-diagram .am-edge-label text { fill: var(--ink-2); } .amv-hl .am-node-shape, .amv-hl .am-actor { stroke: var(--accent); stroke-width: 3; fill: var(--accent-bg); filter: var(--v-glow); } html[data-video] .am-diagram .amv-hl text, html[data-video] .am-diagram text.amv-hl { fill: var(--accent); font-weight: 600; } .amv-hl .am-edge { stroke: var(--accent); stroke-width: 2.4; } .amv-hl.am-tree-box, .amv-hl.am-tl-item, .amv-hl.am-lim, .amv-hl.am-kv-cell, li.amv-hl > .am-tree-label, tr.amv-hl td, .amv-hl.am-seg { color: var(--accent); outline: 2.5px solid var(--accent); outline-offset: 4px; border-radius: 4px; } /* \u2500\u2500 Player controls (outside the picture) \u2500\u2500 */ .amv-bigplay { position: absolute; left: 50%; top: 50%; width: 132px; height: 132px; margin: -66px 0 0 -66px; border-radius: 50%; border: 2px solid var(--v-ctl-white); background: rgba(0, 0, 0, 0.55); color: var(--v-ctl-white); font-size: 54px; cursor: pointer; padding-left: 10px; } .amv-bigplay[hidden] { display: none; } .amv-controls { position: fixed; left: 0; right: 0; bottom: 0; height: 56px; display: flex; align-items: center; gap: 14px; padding: 0 18px; background: var(--v-ctl-bg); border-top: 1px solid var(--v-ctl-line); color: var(--v-ctl-fg); font: 13px -apple-system, "Segoe UI", sans-serif; } .amv-btn { width: 36px; height: 32px; border: 1px solid var(--v-ctl-btn-line); border-radius: 6px; background: var(--v-ctl-btn); color: var(--v-ctl-white); cursor: pointer; } .amv-time { font-family: ui-monospace, Menlo, monospace; min-width: 96px; } .amv-track { position: relative; flex: 1; } .amv-seek { width: 100%; accent-color: var(--v-ctl-accent); } .amv-marks { position: absolute; left: 0; right: 0; top: -6px; height: 6px; pointer-events: none; } .amv-marks i { position: absolute; top: 0; width: 2px; height: 6px; background: var(--v-ctl-accent); pointer-events: auto; cursor: pointer; } /* The mark stays 2 px wide; the pseudo element widens what can be clicked. */ .amv-marks i::before { content: ''; position: absolute; left: -5px; right: -5px; top: -6px; bottom: -4px; } .amv-rate { width: auto; min-width: 46px; padding: 0 8px; font: inherit; font-variant-numeric: tabular-nums; } .amv-export { width: auto; min-width: 46px; padding: 0 8px; font: inherit; font-variant-numeric: tabular-nums; } .amv-export:hover, .amv-export[aria-busy="true"] { border-color: var(--v-ctl-accent); color: var(--v-ctl-accent); } .amv-brand { color: var(--v-ctl-muted); font-size: 12px; white-space: nowrap; } /* \u2500\u2500 Export cover: the engine poses every frame behind it, so the picture is out of the way but the controls stay usable \u2500\u2500 */ .amv-exporting { position: fixed; inset: 0; display: grid; place-content: center; gap: 16px; text-align: center; cursor: pointer; background: color-mix(in srgb, var(--v-ctl-bg) 92%, transparent); color: var(--v-ctl-fg); font: 14px -apple-system, "Segoe UI", sans-serif; } .amv-exporting > span { width: 320px; height: 4px; border-radius: 2px; background: var(--v-ctl-btn); overflow: hidden; } .amv-exporting > span > i { display: block; width: 0; height: 100%; background: var(--v-ctl-accent); } .amv-exporting > b { font-weight: 600; font-variant-numeric: tabular-nums; } html[data-export] .amv-exporting { display: none; } /* \u2500\u2500 Chapter strip (one chip per scene; scrolls when the titles are long) \u2500\u2500 */ .amv-chapters { position: fixed; left: 0; right: 0; bottom: 56px; height: 40px; display: flex; align-items: center; gap: 8px; padding: 0 18px; overflow-x: auto; overscroll-behavior-x: contain; background: var(--v-ctl-bg); border-top: 1px solid var(--v-ctl-line); color: var(--v-ctl-fg); font: 13px -apple-system, "Segoe UI", sans-serif; scrollbar-width: thin; } .amv-chapters:empty { display: none; } .amv-chap { display: flex; align-items: center; gap: 7px; flex: 0 0 auto; max-width: 320px; padding: 5px 12px; cursor: pointer; border: 1px solid var(--v-ctl-btn-line); border-radius: 999px; background: var(--v-ctl-btn); color: var(--v-ctl-white); font: inherit; text-align: left; } .amv-chap b { font-weight: 700; color: var(--v-ctl-accent); } .amv-chap span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .amv-chap:hover { border-color: var(--v-ctl-accent); } .amv-chap[aria-current="true"] { border-color: var(--v-ctl-accent); color: var(--v-ctl-accent); background: color-mix(in srgb, var(--v-ctl-accent) 18%, var(--v-ctl-btn)); } /* Export mode: stage only, 1:1, no scaling */ html[data-export] .amv-controls, html[data-export] .amv-bigplay, html[data-export] .amv-chapters { display: none; } html[data-export] .amv-viewport { inset: 0; } html[data-export] .amv-stage { left: 0; top: 0; transform: none !important; } `; var VIDEO_JS = "(() => {\n const D = JSON.parse(document.getElementById('amv-data').textContent);\n const W = 1920;\n const H = 1080;\n const T = 0.9; // scene transition, matches TIMING.transition\n const R = 0.6; // one step appearing\n const CAM = 0.8; // camera move\n const root = document.documentElement;\n const stage = document.querySelector('.amv-stage');\n const camera = document.querySelector('.amv-camera');\n const overlay = document.querySelector('.amv-overlay');\n const caption = document.querySelector('.amv-caption span');\n const scenes = [...document.querySelectorAll('.amv-scene')];\n const segs = D.segments;\n // One chapter per scene (the title card is not a chapter). The strip needs its space before anything measures the viewport,\n // so the attribute goes on before section 1.\n const chapterList = segs.slice(1);\n if (chapterList.length > 1) root.setAttribute('data-chapters', '');\n const clamp = (x, a = 0, b = 1) => Math.min(b, Math.max(a, x));\n const ease = (x) => { const v = clamp(x); return v < 0.5 ? 4 * v * v * v : 1 - (-2 * v + 2) ** 3 / 2; };\n const lerp = (a, b, p) => a + (b - a) * p;\n const STEP_SEL = '.am-tl-item, .am-lim, .am-seg, tbody tr, .am-kv-cell, .am-md > ul > li, .am-md > ol > li, .am-md > p, .am-md > blockquote, .am-callout';\n\n // \u2500\u2500 1. Fit each scene's content to the frame \u2500\u2500\n for (const sc of scenes) {\n const fit = sc.querySelector('.amv-fit');\n if (!fit || !fit.children.length) continue;\n const s = Math.min(1600 / fit.offsetWidth, 740 / fit.offsetHeight, 3.4);\n fit.style.transform = `scale(${s})`;\n }\n\n // Scene titles sit outside the camera, so they stay put when it zooms.\n const heads = scenes.map((sc) => {\n const h = sc.querySelector('.amv-scene-head');\n if (h) stage.insertBefore(h, camera.nextSibling);\n return h;\n });\n\n // Measure elements in stage coordinates (the camera is the identity transform here).\n const sr = stage.getBoundingClientRect();\n const k = sr.width / W;\n const rectOf = (el) => {\n const r = el.getBoundingClientRect();\n return { x: (r.left - sr.left) / k, y: (r.top - sr.top) / k, w: r.width / k, h: r.height / k };\n };\n\n // \u2500\u2500 2. Steps: group by data-step when a component sets it, else one step per row / item \u2500\u2500\n const items = []; // { el, at, paths: [{ el, len }] }\n scenes.forEach((sc, i) => {\n if (i === 0) return;\n const groups = [];\n for (const block of sc.querySelectorAll('.amv-fit > *')) {\n const marked = [...block.querySelectorAll('[data-step]')];\n if (marked.length) {\n const by = new Map();\n for (const el of marked) {\n const n = Number(el.dataset.step);\n if (!by.has(n)) by.set(n, []);\n by.get(n).push(el);\n }\n [...by.keys()].sort((a, b) => a - b).forEach((n) => groups.push(by.get(n)));\n } else {\n const found = [...block.querySelectorAll(STEP_SEL)].filter((el) => !el.parentElement.closest(STEP_SEL));\n if (found.length) found.forEach((el) => groups.push([el]));\n else groups.push([block]);\n }\n }\n const beats = segs[i].beats;\n const S = groups.length;\n const B = beats.length;\n const perBeat = new Map();\n groups.forEach((g, gi) => {\n // With more beats than steps, the extra beats open the scene: steps align to the last beats.\n const b = S <= B ? gi + (B - S) : Math.floor((gi * B) / S);\n const rank = perBeat.get(b) ?? 0;\n perBeat.set(b, rank + 1);\n const beat = beats[b];\n const count = S <= B ? 1 : Math.ceil(S / B) || 1;\n const slot = Math.min(0.45, (beat.end - beat.start) / count);\n for (const el of g) {\n const paths = (el.matches('path.am-edge') ? [el] : [...el.querySelectorAll('path.am-edge')])\n .filter((p) => !p.classList.contains('am-edge--dashed'))\n .map((p) => ({ el: p, len: p.getTotalLength() }));\n items.push({ el, scene: i, at: beat.start + rank * slot, paths });\n }\n });\n });\n\n // \u2500\u2500 3. Morphs: elements with the same data-key in consecutive scenes \u2500\u2500\n const morphs = []; // { scene, from, to, ghost, a, b }\n const keyed = (sc) => {\n const m = new Map();\n for (const el of sc.querySelectorAll('[data-key]')) if (!m.has(el.dataset.key)) m.set(el.dataset.key, el);\n return m;\n };\n for (let i = 2; i < scenes.length; i++) {\n const prev = keyed(scenes[i - 1]);\n for (const [key, to] of keyed(scenes[i])) {\n const from = prev.get(key);\n // Morph only like with like (SVG to SVG, HTML to HTML); otherwise the shapes do not match.\n if (!from || (from instanceof SVGElement) !== (to instanceof SVGElement)) continue;\n try {\n const ghost = makeGhost(from);\n overlay.append(ghost.node);\n morphs.push({ scene: i, from, to, ghost: ghost.node, a: ghost.place(rectOf(from)), b: ghost.place(rectOf(to)) });\n } catch {\n // Morphs are a nicety: skip an element whose measurement fails; playback is unaffected.\n }\n }\n }\n const carried = new Set(morphs.map((m) => m.to));\n\n function makeGhost(el) {\n const wrap = document.createElement('div');\n const host = el.closest('.am-diagram, .am-tree, .am-timeline, .am-kv, .am-limits');\n wrap.className = `amv-ghost ${host ? host.className : ''}`;\n if (el instanceof SVGGraphicsElement) {\n const bb = el.getBBox();\n const pad = 4;\n const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n svg.setAttribute('viewBox', `${bb.x - pad} ${bb.y - pad} ${bb.width + pad * 2} ${bb.height + pad * 2}`);\n svg.setAttribute('width', bb.width + pad * 2);\n svg.setAttribute('height', bb.height + pad * 2);\n const clone = el.cloneNode(true);\n clone.removeAttribute('style');\n svg.append(clone);\n wrap.append(svg);\n return {\n node: wrap,\n place: (r) => {\n const s = r.w / (bb.width || 1);\n return { x: r.x - pad * s, y: r.y - pad * s, s };\n },\n };\n }\n const clone = el.cloneNode(true);\n clone.removeAttribute('style');\n clone.querySelectorAll('[data-key], ul').forEach((n) => n.remove());\n const box = document.createElement(el.tagName === 'LI' ? 'ul' : 'div');\n box.className = el.tagName === 'LI' ? 'am-tree-list' : '';\n box.style.margin = '0';\n box.append(clone);\n wrap.append(box);\n wrap.style.width = `${el.offsetWidth}px`;\n const w0 = el.offsetWidth || 1;\n return { node: wrap, place: (r) => ({ x: r.x, y: r.y, s: r.w / w0 }) };\n }\n\n // \u2500\u2500 4. Camera: the element named by [name] in the narration \u2500\u2500\n const findKey = (sc, key) => {\n const all = [...sc.querySelectorAll('[data-key]')];\n const norm = (s) => s.replace(/[`*]/g, '').trim().toLowerCase();\n return all.find((el) => norm(el.dataset.key) === norm(key))\n ?? all.find((el) => norm(el.dataset.key).includes(norm(key)))\n ?? [...sc.querySelectorAll(`${STEP_SEL}, text`)].find((el) => norm(el.textContent).includes(norm(key)));\n };\n const IDENT = { s: 1, x: 0, y: 0 };\n // Zoom without cropping: the whole diagram must stay in the safe area between title and captions.\n const SAFE = { left: 60, right: W - 60, top: 150, bottom: H - 200 }; // leave room for captions at the bottom\n function focusCam(r, sc) {\n const fit = sc.querySelector('.amv-fit');\n const c = fit ? rectOf(fit) : { x: 0, y: 0, w: W, h: H };\n const room = Math.min((SAFE.right - SAFE.left) / c.w, (SAFE.bottom - SAFE.top) / c.h);\n const s = clamp(Math.min(0.5 * W / r.w, 0.42 * H / r.h, room, 1.4), 1, 1.4);\n const fitAxis = (want, lo, hi, a, b) => (s * (b - a) <= hi - lo ? clamp(want, lo - s * a, hi - s * b) : want);\n return {\n s,\n x: fitAxis(W / 2 - s * (r.x + r.w / 2), SAFE.left, SAFE.right, c.x, c.x + c.w),\n y: fitAxis(H * 0.5 - s * (r.y + r.h / 2), SAFE.top, SAFE.bottom, c.y, c.y + c.h),\n };\n }\n const camEvents = []; // { t, cam, hl }\n segs.forEach((seg, i) => {\n camEvents.push({ t: seg.start, cam: IDENT, hl: null });\n seg.beats.forEach((b) => {\n const el = b.focus ? findKey(scenes[i], b.focus) : null;\n let cam = IDENT;\n if (el) cam = focusCam(rectOf(el), scenes[i]);\n camEvents.push({ t: b.start, cam, hl: el });\n });\n });\n const hlTargets = new Set(camEvents.map((e) => e.hl).filter(Boolean));\n\n // Scene and title fades. Titles never overlap: the old one fades out in the first half of the transition, the new one fades in in the second half.\n const show = (el, op) => {\n el.style.opacity = op;\n el.style.visibility = op > 0 ? 'visible' : 'hidden';\n };\n function drawScenes(t) {\n scenes.forEach((sc, i) => {\n const seg = segs[i];\n const next = segs[i + 1];\n const before = t < seg.start;\n const fadeIn = i === 0 ? ease(t / 0.8) : ease((t - seg.start) / T);\n const fadeOut = next ? 1 - ease((t - next.start) / T) : 1;\n show(sc, before ? 0 : Math.min(fadeIn, fadeOut));\n if (!heads[i]) return;\n const hin = ease((t - seg.start - T / 2) / (T / 2));\n const hout = next ? 1 - ease((t - next.start) / (T / 2)) : 1;\n show(heads[i], before ? 0 : Math.min(hin, hout));\n });\n }\n\n // Steps appear: edges draw in one stroke, other elements fade in and rise slightly.\n function drawSteps(t) {\n for (const it of items) {\n if (carried.has(it.el)) continue;\n const p = ease((t - it.at) / R);\n it.el.style.opacity = clamp((t - it.at) / 0.25);\n if (!it.paths.length) {\n it.el.style.transform = p < 1 ? `translateY(${(1 - p) * 14}px)` : '';\n continue;\n }\n for (const { el, len } of it.paths) {\n el.style.strokeDasharray = `${len}`;\n el.style.strokeDashoffset = `${len * (1 - p)}`;\n el.style.markerEnd = p < 0.97 ? 'none' : '';\n }\n }\n }\n\n // Morphs: during the transition a stand-in moves from the old to the new position while the real elements are hidden.\n // An element can end one morph and start the next; collect the elements to hide first, then apply, so the two do not overwrite each other.\n const morphed = [...new Set(morphs.flatMap((m) => [m.from, m.to]))];\n function drawMorphs(t) {\n const hidden = new Set();\n for (const m of morphs) {\n const s0 = segs[m.scene].start;\n const during = t >= s0 && t < s0 + T;\n m.ghost.style.display = during ? '' : 'none';\n if (during) {\n const p = ease((t - s0) / T);\n m.ghost.style.transform = `translate(${lerp(m.a.x, m.b.x, p)}px, ${lerp(m.a.y, m.b.y, p)}px) scale(${lerp(m.a.s, m.b.s, p)})`;\n hidden.add(m.from);\n }\n if (t < s0 + T) hidden.add(m.to);\n m.to.style.opacity = 1;\n }\n for (const el of morphed) el.style.visibility = hidden.has(el) ? 'hidden' : '';\n }\n\n // Camera and highlight: interpolate between the previous camera position and the current target.\n function drawCamera(t) {\n const ev = camEvents.findLastIndex((e) => t >= e.t);\n const e = ev >= 0 ? camEvents[ev] : null;\n const prev = ev > 0 ? camEvents[ev - 1].cam : IDENT;\n const p = e ? ease((t - e.t) / CAM) : 0;\n const to = e ? e.cam : IDENT;\n camera.style.transform = `translate(${lerp(prev.x, to.x, p)}px, ${lerp(prev.y, to.y, p)}px) scale(${lerp(prev.s, to.s, p)})`;\n for (const el of hlTargets) el.classList.toggle('amv-hl', el === e?.hl);\n }\n\n function drawCaption(t) {\n const cur = segs.findLastIndex((s) => t >= s.start);\n const beats = cur >= 0 ? segs[cur].beats : [];\n const b = beats.find((x) => t >= x.start && t < x.end + 0.3);\n const html = b ? b.html : '';\n if (caption.dataset.html !== html) {\n caption.innerHTML = html;\n caption.dataset.html = html;\n }\n caption.style.opacity = b ? clamp((t - b.start) / 0.2) : 0;\n }\n\n // \u2500\u2500 5. Deterministic rendering: the same time always draws the same frame \u2500\u2500\n function render(time) {\n const t = clamp(time, 0, D.duration);\n drawScenes(t);\n drawSteps(t);\n drawMorphs(t);\n drawCamera(t);\n drawCaption(t);\n updateUi(t);\n }\n\n // \u2500\u2500 6. Player \u2500\u2500\n const audio = document.getElementById('amv-audio');\n const seek = document.querySelector('.amv-seek');\n const timeEl = document.querySelector('.amv-time');\n const toggleBtn = document.querySelector('[data-amv=\"toggle\"]');\n const bigPlay = document.querySelector('.amv-bigplay');\n const marks = document.querySelector('.amv-marks');\n const bar = document.querySelector('.amv-chapters');\n const rateBtn = document.querySelector('[data-amv=\"rate\"]');\n const RATES = [0.5, 0.75, 1, 1.25, 1.5, 2];\n const fmt = (x) => `${Math.floor(x / 60)}:${String(Math.floor(x % 60)).padStart(2, '0')}`;\n seek.max = D.duration;\n\n // A tick on the track and a chip in the strip per scene; both jump to the start of the scene and keep playing.\n const chips = chapterList.map((s, i) => {\n const mark = document.createElement('i');\n mark.style.left = `${(s.start / D.duration) * 100}%`;\n mark.title = `${s.id} ${s.title} \xB7 ${fmt(s.start)}`;\n mark.addEventListener('click', () => jump(s.start));\n marks.append(mark);\n\n const chip = document.createElement('button');\n chip.type = 'button';\n chip.className = 'amv-chap';\n chip.title = `${s.title} \xB7 ${fmt(s.start)}`;\n const letter = document.createElement('b');\n letter.textContent = s.id || String.fromCharCode(65 + i);\n const label = document.createElement('span');\n label.textContent = s.title;\n chip.append(letter, label);\n chip.addEventListener('click', () => jump(s.start));\n bar.append(chip);\n return chip;\n });\n\n let playing = false;\n let base = 0;\n let t0 = 0;\n let rate = 1;\n // The clock: the audio element when there is one, otherwise wall-clock time scaled by the playback rate.\n const now = () => (!playing ? base : audio ? audio.currentTime : base + ((performance.now() - t0) / 1000) * rate);\n\n function jump(t) {\n seekTo(t);\n if (!playing) play();\n }\n\n // The chip of the scene the picture is in, kept in view while the strip scrolls.\n let activeChip = -1;\n function markChapter(t) {\n const i = chapterList.findLastIndex((s) => t >= s.start);\n if (i === activeChip) return;\n activeChip = i;\n chips.forEach((c, k) => (k === i ? c.setAttribute('aria-current', 'true') : c.removeAttribute('aria-current')));\n const chip = chips[i];\n if (chip && bar.scrollWidth > bar.clientWidth) {\n // By where the chip is on screen, so the strip scrolls the same in either direction (a right-to-left strip scrolls with negative offsets).\n const b = bar.getBoundingClientRect();\n const c = chip.getBoundingClientRect();\n bar.scrollBy({ left: c.left + c.width / 2 - (b.left + b.width / 2), behavior: 'smooth' });\n }\n }\n\n // Changing the rate must not move the clock: anchor it again at the time it shows now.\n function setRate(r) {\n const at = now();\n rate = r;\n if (audio) audio.playbackRate = r;\n rateBtn.textContent = `${r}\xD7`;\n rateBtn.setAttribute('aria-label', `${rateBtn.dataset.speed ?? 'Speed'} ${r}\xD7`);\n if (playing) {\n base = at;\n t0 = performance.now();\n }\n }\n\n function updateUi(t) {\n if (document.activeElement !== seek) seek.value = t;\n timeEl.textContent = `${fmt(t)} / ${fmt(D.duration)}`;\n markChapter(t);\n }\n\n function play() {\n if (base >= D.duration - 0.05) base = 0;\n playing = true;\n bigPlay.hidden = true;\n toggleBtn.textContent = '\u275A\u275A';\n toggleBtn.setAttribute('aria-label', toggleBtn.dataset.pause);\n if (audio) {\n audio.currentTime = base;\n audio.play().catch(() => {});\n } else {\n t0 = performance.now();\n }\n requestAnimationFrame(tick);\n }\n\n function pause() {\n base = now();\n playing = false;\n audio?.pause();\n toggleBtn.textContent = '\u25B6';\n toggleBtn.setAttribute('aria-label', toggleBtn.dataset.play);\n }\n\n function seekTo(x) {\n base = clamp(x, 0, D.duration);\n if (audio) audio.currentTime = base;\n t0 = performance.now();\n render(base);\n }\n\n function tick() {\n if (!playing) return;\n const t = now();\n if (t >= D.duration) {\n pause();\n base = D.duration;\n render(D.duration);\n return;\n }\n render(t);\n requestAnimationFrame(tick);\n }\n\n const toggle = () => (playing ? pause() : play());\n toggleBtn.addEventListener('click', toggle);\n bigPlay.addEventListener('click', play);\n stage.addEventListener('click', (e) => { if (e.target !== bigPlay) toggle(); });\n seek.addEventListener('input', () => seekTo(Number(seek.value)));\n rateBtn.addEventListener('click', () => setRate(RATES[(RATES.indexOf(rate) + 1) % RATES.length]));\n\n // \u2500\u2500 Export: the page encodes itself (src/runtime/video-export.js) and offers the file as a download \u2500\u2500\n // The engine needs no help from the page behind it: the stage is pinned into the frame while it copies styles, so\n // the picture is 1920x1080 whatever the window shows. The overlay covers the scrubbing stage and the button keeps\n // its place in the controls, so a second click stops the export.\n const exportBtn = document.querySelector('[data-amv=\"export\"]');\n const SLICE = 1_048_572; // 1 MiB rounded to a multiple of 3, so every base64 slice stands on its own\n let exporting = false;\n\n const decode = (b64) => {\n const bin = atob(b64);\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n };\n\n async function runExport() {\n if (exporting) { window.__amvEnc.cancel(); return; }\n exporting = true;\n const label = exportBtn.dataset.label || 'Export';\n const overlay = document.createElement('div');\n overlay.className = 'amv-exporting';\n overlay.innerHTML = '';\n overlay.title = label;\n overlay.addEventListener('click', () => window.__amvEnc.cancel());\n // After the viewport and before the controls: the cover hides the picture, the controls stay clickable.\n document.querySelector('.amv-viewport').after(overlay);\n window.__amv.pauseForExport();\n try {\n const started = await window.__amvEnc.start({});\n if (started.error) throw new Error(started.error);\n for (;;) {\n const p = window.__amvEnc.progress();\n const pct = Math.round((p.done / Math.max(1, p.total)) * 100);\n exportBtn.textContent = `${pct}%`;\n overlay.querySelector('b').textContent = `${label} ${pct}%`;\n overlay.querySelector('i').style.width = `${pct}%`;\n if (p.error) throw new Error(p.error);\n if (!p.running) break;\n await new Promise((r) => setTimeout(r, 200));\n }\n const size = window.__amvEnc.size();\n const parts = [];\n for (let at = 0; at < size; at += SLICE) parts.push(decode(window.__amvEnc.bytes(at, Math.min(SLICE, size - at))));\n const url = URL.createObjectURL(new Blob(parts, { type: 'video/webm' }));\n const link = document.createElement('a');\n link.href = url;\n link.download = `${(document.title || 'video').replace(/[\\\\/:*?\"<>|]+/g, '-')}.webm`;\n document.body.appendChild(link);\n link.click();\n link.remove();\n setTimeout(() => URL.revokeObjectURL(url), 60_000);\n } catch (e) {\n exportBtn.textContent = '\u2715';\n overlay.querySelector('b').textContent = String((e && e.message) || e);\n await new Promise((r) => setTimeout(r, 2500));\n } finally {\n overlay.remove();\n exportBtn.textContent = exportBtn.dataset.icon || '\u2913';\n exporting = false;\n window.__amv.resumeAfterExport();\n }\n }\n\n if (window.__amvEnc && window.__amvEnc.supported()) exportBtn.addEventListener('click', runExport);\n else exportBtn.remove();\n document.addEventListener('keydown', (e) => {\n if (e.key === ' ') { e.preventDefault(); toggle(); }\n if (e.key === 'ArrowRight') seekTo(now() + 5);\n if (e.key === 'ArrowLeft') seekTo(now() - 5);\n });\n audio?.addEventListener('ended', () => { pause(); base = D.duration; });\n\n function fitStage() {\n if (root.hasAttribute('data-export')) return;\n const vp = stage.parentElement;\n const s = Math.min(vp.clientWidth / W, vp.clientHeight / H);\n stage.style.transform = `translate(-50%, -50%) scale(${s})`;\n }\n window.addEventListener('resize', fitStage);\n\n // Export: place the stage 1:1 at the top left and call render(t) frame by frame. exportMode is for the CLI, which\n // hides the controls and either screenshots the frames (ffmpeg) or drives the built-in encoder (see video-export.js).\n window.render = render;\n let resumeAt; // undefined: no export ran; null: playback was paused already; a number: carry on there\n window.__amv = {\n duration: D.duration,\n fps: D.fps,\n exportMode() { root.setAttribute('data-export', ''); stage.style.transform = ''; },\n // The encoder poses every frame itself, so playback stops for the length of an export and carries on afterwards.\n // The button and the engine both call this; only the first call decides where playback resumes.\n pauseForExport() { if (resumeAt === undefined) resumeAt = playing ? now() : null; pause(); return resumeAt; },\n resumeAfterExport() {\n if (resumeAt === undefined || resumeAt === null) { resumeAt = undefined; return; }\n const at = resumeAt;\n resumeAt = undefined;\n seekTo(at);\n if (at < D.duration - 0.05) play();\n },\n };\n fitStage();\n setRate(rate); // the button carries the speed on load\n // Poster: show the fully faded-in title card, but playback still starts at 0.\n render(Math.min(1, segs[0].end));\n updateUi(0);\n})();\n"; var VIDEO_EXPORT_JS = "// The export engine for the path that has no ffmpeg: the player page drives its own render(t), rasterizes the stage\n// into a canvas, encodes the picture and the narration with WebCodecs and muxes the WebM container with the writer\n// from src/video/webm.js (injected ahead of this script as window.__amvWebm). Every video page carries it: the export\n// button in the controls drives it, and `am video --mp4` drives the same code through the DevTools protocol.\n//\n// The page hands out the finished file in slices (window.__amvEnc.bytes(at, len), base64) so no bytes travel twice.\n(() => {\n const W = 1920;\n const H = 1080;\n const CODECS = ['vp09.00.31.08', 'vp09.00.10.08', 'vp8']; // the first one the browser really encodes\n const AUDIO_FRAME_SECONDS = 0.02; // Opus encodes 20 ms frames\n const KEY_SECONDS = 5; // a key frame every 5 s, so seeking stays cheap\n const BITRATE = 3_500_000; // VP9 at 1080p30: flat slides stay clean at this rate\n const QUEUE_LIMIT = 8; // frames in flight; a software encoder runs slower than the page renders\n\n const state = { running: false, done: 0, total: 0, error: null, bytes: null, codec: '', audio: false, cancel: false };\n\n const yieldToPage = () => new Promise((r) => {\n // A timer is clamped in a hidden tab; a message port keeps the export running at full speed in the background.\n const ch = new MessageChannel();\n ch.port1.onmessage = () => r();\n ch.port2.postMessage(0);\n });\n\n const asBytes = (x) => (x instanceof ArrayBuffer ? new Uint8Array(x) : new Uint8Array(x.buffer, x.byteOffset, x.byteLength));\n\n // \u2500\u2500 Rasterizing: the stage, with every node's computed style copied into the SVG \u2500\u2500\n // The clone needs the styles inline: an of an SVG that embeds the page's own stylesheets does not lay out the\n // page the same way (the fixed-position chain and the html/body height rules break). Computed styles are exact and\n // cost about 300 ms for the whole stage, so unchanged nodes keep their string from the previous frame: the player\n // changes only a handful of nodes per frame (14 of 261 measured on a five-scene video).\n const styles = new Map();\n const canvas = document.createElement('canvas');\n canvas.width = W;\n canvas.height = H;\n const ctx = canvas.getContext('2d', { alpha: false });\n const image = new Image();\n let stageEl = null;\n\n // The picture is always 1920x1080: the stage is pinned at the top left of the SVG and the fit transform the player\n // applies to the live stage (translate + scale) is dropped, so the frame does not depend on the window size.\n const PIN = `;position:absolute;left:0;top:0;width:${W}px;height:${H}px;transform:none;`;\n\n function styleOf(el) {\n const cs = getComputedStyle(el);\n let out = '';\n for (let i = 0; i < cs.length; i++) out += `${cs[i]}:${cs.getPropertyValue(cs[i])};`;\n return out;\n }\n\n function stageSvg(stage) {\n const animated = new Set();\n for (const anim of document.getAnimations()) {\n const target = anim.effect && anim.effect.target;\n if (target) animated.add(target);\n }\n const clone = stage.cloneNode(true);\n const stamp = `${window.innerWidth}x${window.innerHeight}`;\n let recomputed = 0;\n const walk = (src, dst, parentSign) => {\n const sign = `${parentSign}\\u0001${src.getAttribute('class') || ''}\\u0001${src.getAttribute('style') || ''}${animated.has(src) ? '\\u0001a' : ''}`;\n let entry = styles.get(src);\n if (!entry || entry.sign !== sign) {\n entry = { sign, text: styleOf(src) + (src === stage ? PIN : '') };\n styles.set(src, entry);\n recomputed++;\n }\n dst.setAttribute('style', entry.text);\n const a = src.children;\n const b = dst.children;\n for (let i = 0; i < a.length; i++) walk(a[i], b[i], sign);\n };\n walk(stage, clone, stamp);\n const svg = `${new XMLSerializer().serializeToString(clone)}`;\n return { svg, recomputed };\n }\n\n async function paint(stage) {\n const { svg } = stageSvg(stage);\n // A data URL keeps the canvas origin clean; a blob URL taints it and the encoder then refuses the frame.\n image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;\n await image.decode();\n ctx.fillStyle = '#000';\n ctx.fillRect(0, 0, W, H);\n ctx.drawImage(image, 0, 0, W, H);\n }\n\n // \u2500\u2500 The narration the page already carries: encoded from the WAV the player holds as a data URL \u2500\u2500\n function readWav() {\n const el = document.getElementById('amv-audio');\n const src = (el && el.getAttribute('src')) || '';\n const comma = src.indexOf(',');\n if (!src.startsWith('data:') || comma < 0) return null;\n const bin = atob(src.slice(comma + 1));\n const wav = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) wav[i] = bin.charCodeAt(i);\n const view = new DataView(wav.buffer, wav.byteOffset, wav.byteLength);\n const tag = (o) => String.fromCharCode(wav[o], wav[o + 1], wav[o + 2], wav[o + 3]);\n if (tag(0) !== 'RIFF' || tag(8) !== 'WAVE') throw new Error('the narration track is not a WAV file');\n let format = null;\n let pcm = null;\n for (let o = 12; o + 8 <= wav.byteLength;) {\n const id = tag(o);\n const len = view.getUint32(o + 4, true);\n if (id === 'fmt ') format = { channels: view.getUint16(o + 10, true), sampleRate: view.getUint32(o + 12, true), bits: view.getUint16(o + 22, true) };\n else if (id === 'data') pcm = { at: o + 8, len };\n o += 8 + len + (len % 2);\n }\n if (!format || !pcm || format.bits !== 16) throw new Error('the narration track must be 16-bit PCM WAV');\n return { wav, pcm, ...format };\n }\n\n async function encodeAudio(writer) {\n const sound = readWav();\n if (!sound) return false;\n const { wav, pcm, channels, sampleRate } = sound;\n let head = null;\n const audio = new AudioEncoder({\n output(chunk, meta) {\n if (!head && meta && meta.decoderConfig && meta.decoderConfig.description) head = asBytes(meta.decoderConfig.description).slice();\n const data = new Uint8Array(chunk.byteLength);\n chunk.copyTo(data);\n writer.block({ track: 2, key: false, tsUs: chunk.timestamp, data });\n },\n error(e) { state.error = String((e && e.message) || e); },\n });\n audio.configure({ codec: 'opus', sampleRate, numberOfChannels: channels, bitrate: 64000 * channels });\n const perBlock = Math.round(sampleRate * AUDIO_FRAME_SECONDS);\n const total = Math.floor(pcm.len / (2 * channels));\n for (let i = 0; i < total; i += perBlock) {\n const samples = perBlock * channels;\n const data = new Uint8Array(samples * 2); // zero filled: a short last block becomes a full Opus frame\n const from = pcm.at + i * channels * 2;\n data.set(wav.subarray(from, from + Math.min(samples, (total - i) * channels) * 2));\n const block = new AudioData({\n format: 's16', sampleRate, numberOfFrames: perBlock, numberOfChannels: channels,\n timestamp: Math.round((i / sampleRate) * 1e6), data,\n });\n audio.encode(block);\n block.close();\n while (audio.encodeQueueSize > 16 && !state.error) await yieldToPage();\n if (state.cancel) break;\n }\n await audio.flush();\n audio.close();\n if (state.error) throw new Error(state.error);\n // The encoder adds pre-skip samples of silence; CodecDelay makes the player drop them again.\n const preSkip = head && head.length >= 19 ? new DataView(head.buffer, head.byteOffset, head.byteLength).getUint16(10, true) : 312;\n if (!head || head.length < 19) {\n head = new Uint8Array(19);\n head.set(new TextEncoder().encode('OpusHead'), 0);\n head[8] = 1;\n head[9] = channels;\n new DataView(head.buffer).setUint16(10, 312, true);\n new DataView(head.buffer).setUint32(12, sampleRate, true);\n }\n writer.audio = { channels, codecPrivate: head, preSkipSamples: preSkip };\n return true;\n }\n\n // \u2500\u2500 The whole export, run inside this page \u2500\u2500\n // start() only validates and kicks the work off: the caller polls progress() and then asks for the bytes, so the\n // DevTools protocol never has to wait minutes for one call to come back.\n function start(options = {}) {\n if (state.running) return { error: 'an export is already running' };\n const api = window.__amv;\n const Mux = window.__amvWebm && window.__amvWebm.WebmWriter;\n if (!Mux) return { error: 'the WebM writer is missing from this page' };\n if (typeof VideoEncoder !== 'function' || typeof AudioEncoder !== 'function') {\n return { error: 'this browser cannot encode video (WebCodecs is missing)' };\n }\n Object.assign(state, { running: true, done: 0, total: Math.ceil(api.duration * api.fps), error: null, bytes: null, cancel: false });\n run(Mux, options).catch((e) => { state.error = String((e && e.message) || e); }).finally(() => { state.running = false; styles.clear(); });\n return { total: state.total, fps: api.fps, duration: api.duration };\n }\n\n async function run(Mux, options) {\n const api = window.__amv;\n const fps = api.fps;\n const total = state.total;\n const config = { width: W, height: H, bitrate: options.bitrate || BITRATE, framerate: fps, latencyMode: 'quality' };\n let codec = null;\n for (const candidate of CODECS) {\n try {\n if ((await VideoEncoder.isConfigSupported({ ...config, codec: candidate })).supported) { codec = candidate; break; }\n } catch {\n // an unknown codec string is not an error, try the next one\n }\n }\n if (!codec) throw new Error('this browser encodes neither VP9 nor VP8');\n state.codec = codec;\n\n const writer = new Mux({ width: W, height: H, durationMs: api.duration * 1000, videoCodec: codec.startsWith('vp8') ? 'V_VP8' : 'V_VP9' });\n state.audio = await encodeAudio(writer);\n if (state.cancel) throw new Error('cancelled');\n\n // The player keeps its own clock; the export poses every frame itself, so playback is paused first.\n api.pauseForExport();\n stageEl = stageEl || document.querySelector('.amv-stage');\n const video = new VideoEncoder({\n output(chunk) {\n const data = new Uint8Array(chunk.byteLength);\n chunk.copyTo(data);\n writer.block({ track: 1, key: chunk.type === 'key', tsUs: chunk.timestamp, data });\n },\n error(e) { state.error = String((e && e.message) || e); },\n });\n video.configure({ ...config, codec });\n const keyEvery = Math.max(1, Math.round(fps * KEY_SECONDS));\n for (let i = 0; i < total; i++) {\n if (state.cancel) { video.close(); throw new Error('cancelled'); }\n if (state.error) throw new Error(state.error);\n window.render(i / fps);\n await paint(stageEl);\n const frame = new VideoFrame(canvas, { timestamp: Math.round((i / fps) * 1e6), duration: Math.round(1e6 / fps) });\n video.encode(frame, { keyFrame: i % keyEvery === 0 });\n frame.close();\n state.done = i + 1;\n if (video.encodeQueueSize > QUEUE_LIMIT || i % 5 === 0) await yieldToPage();\n }\n await video.flush();\n video.close();\n if (state.error) throw new Error(state.error);\n state.bytes = writer.build();\n }\n\n const base64 = (from, to) => {\n let out = '';\n for (let i = from; i < to; i += 0x8000) out += String.fromCharCode.apply(null, state.bytes.subarray(i, Math.min(to, i + 0x8000)));\n return btoa(out);\n };\n\n window.__amvEnc = {\n supported() {\n return typeof VideoEncoder === 'function' && typeof AudioEncoder === 'function' && Boolean(window.__amvWebm && window.__amvWebm.WebmWriter);\n },\n start,\n progress() {\n return { running: state.running, done: state.done, total: state.total, error: state.error, audio: state.audio, codec: state.codec };\n },\n // The caller asks for the file in slices: one big string would travel badly over a debugger protocol.\n size() { return state.bytes ? state.bytes.length : 0; },\n bytes(from, len) { return state.bytes ? base64(from, Math.min(from + len, state.bytes.length)) : ''; },\n cancel() { state.cancel = true; },\n };\n})();\n"; var VIDEO_MUX_JS = "// Minimal WebM (Matroska) writer for the export path that has no ffmpeg: the browser encodes the frames (WebCodecs,\n// see src/runtime/video.js) and this module writes the container. One VP8/VP9 video track and an optional Opus audio\n// track. The blocks are buffered, sorted by time and cut into clusters of about a second, so the sound and the\n// picture of a moment sit next to each other in the file. The Segment and the Clusters carry the unknown size, which\n// is how a stream is written: nothing needs to be measured first and nothing is patched afterwards.\n// Plain bytes only (no Buffer, no imports), so the same source runs in Node and inside a page (scripts/inline-assets.mjs\n// turns it into a script for the player page).\nconst APP = 'answer-me-with-html'; // MuxingApp / WritingApp\nconst CLUSTER_MS = 1000; // how much time one cluster holds\nconst SEEK_PRE_ROLL_NS = 80_000_000; // what the Opus specification asks for\n\nconst ID = {\n EBML: [0x1a, 0x45, 0xdf, 0xa3],\n EBMLVersion: [0x42, 0x86], EBMLReadVersion: [0x42, 0xf7], EBMLMaxIDLength: [0x42, 0xf2], EBMLMaxSizeLength: [0x42, 0xf3],\n DocType: [0x42, 0x82], DocTypeVersion: [0x42, 0x87], DocTypeReadVersion: [0x42, 0x85],\n Segment: [0x18, 0x53, 0x80, 0x67], Info: [0x15, 0x49, 0xa9, 0x66], TimecodeScale: [0x2a, 0xd7, 0xb1],\n MuxingApp: [0x4d, 0x80], WritingApp: [0x57, 0x41], Duration: [0x44, 0x89],\n Tracks: [0x16, 0x54, 0xae, 0x6b], TrackEntry: [0xae], TrackNumber: [0xd7], TrackUID: [0x73, 0xc5], FlagLacing: [0x9c],\n CodecID: [0x86], TrackType: [0x83], Video: [0xe0], PixelWidth: [0xb0], PixelHeight: [0xba],\n Audio: [0xe1], SamplingFrequency: [0xb5], Channels: [0x9f], CodecPrivate: [0x63, 0xa2],\n CodecDelay: [0x56, 0xaa], SeekPreRoll: [0x56, 0xbb],\n Cluster: [0x1f, 0x43, 0xb6, 0x75], Timecode: [0xe7], SimpleBlock: [0xa3],\n};\nconst UNKNOWN_SIZE = new Uint8Array([0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]);\n\nconst concat = (parts) => {\n let total = 0;\n for (const p of parts) total += p.length;\n const out = new Uint8Array(total);\n let at = 0;\n for (const p of parts) { out.set(p, at); at += p.length; }\n return out;\n};\nconst bytes = (id) => new Uint8Array(id);\nconst text = (s) => new TextEncoder().encode(s);\n\n// Element size as a variable-size integer, in the shortest length that fits; the all-ones value is reserved.\nfunction size(n) {\n for (let len = 1; len <= 8; len++) {\n if (len === 8 || n < 2 ** (7 * len) - 1) {\n const out = new Uint8Array(len);\n let v = n;\n for (let i = len - 1; i >= 0; i--) {\n out[i] = v & 0xff;\n v = Math.floor(v / 256);\n }\n out[0] |= 0x80 >> (len - 1);\n return out;\n }\n }\n}\n\n// Unsigned integer, big endian, as short as it can be (a Matroska integer drops leading zero bytes).\nfunction uint(n) {\n const out = [];\n let v = Math.round(n);\n do {\n out.unshift(v & 0xff);\n v = Math.floor(v / 256);\n } while (v > 0);\n return new Uint8Array(out);\n}\n\nfunction f64(x) {\n const out = new Uint8Array(8);\n new DataView(out.buffer).setFloat64(0, x);\n return out;\n}\n\nconst elem = (id, payload) => concat([bytes(id), size(payload.length), payload]);\n\n// WebM writer. video: { width, height, codec } with codec 'V_VP9' or 'V_VP8'; audio: null or\n// { channels, codecPrivate, preSkipSamples }. Duration is written up front, so a player knows the length at once.\nclass WebmWriter {\n constructor({ width, height, durationMs, videoCodec = 'V_VP9', audio = null }) {\n this.width = width;\n this.height = height;\n this.durationMs = durationMs;\n this.videoCodec = videoCodec;\n this.audio = audio;\n this.blocks = [];\n }\n\n // track 1 is the picture, track 2 the sound. key marks a key frame; tsUs is the time in microseconds.\n block({ track, key, tsUs, data }) {\n this.blocks.push({ track, key: Boolean(key), ms: Math.round(tsUs / 1000), data });\n }\n\n count(track) {\n return this.blocks.reduce((n, b) => n + (b.track === track ? 1 : 0), 0);\n }\n\n build() {\n const parts = [\n elem(ID.EBML, concat([\n elem(ID.EBMLVersion, uint(1)),\n elem(ID.EBMLReadVersion, uint(1)),\n elem(ID.EBMLMaxIDLength, uint(4)),\n elem(ID.EBMLMaxSizeLength, uint(8)),\n elem(ID.DocType, text('webm')),\n elem(ID.DocTypeVersion, uint(4)),\n elem(ID.DocTypeReadVersion, uint(2)),\n ])),\n concat([bytes(ID.Segment), UNKNOWN_SIZE]),\n elem(ID.Info, concat([\n elem(ID.TimecodeScale, uint(1_000_000)),\n elem(ID.MuxingApp, text(APP)),\n elem(ID.WritingApp, text(APP)),\n elem(ID.Duration, f64(this.durationMs)),\n ])),\n elem(ID.Tracks, concat([this.#videoTrack(), ...(this.audio ? [this.#audioTrack()] : [])])),\n ];\n for (const cluster of this.#clusters()) parts.push(cluster);\n return concat(parts);\n }\n\n #track(num, type, codecId, extra) {\n return elem(ID.TrackEntry, concat([\n elem(ID.TrackNumber, uint(num)),\n elem(ID.TrackUID, uint(num)),\n elem(ID.FlagLacing, uint(0)),\n elem(ID.CodecID, text(codecId)),\n elem(ID.TrackType, uint(type)),\n ...extra,\n ]));\n }\n\n #videoTrack() {\n return this.#track(1, 1, this.videoCodec, [\n elem(ID.Video, concat([elem(ID.PixelWidth, uint(this.width)), elem(ID.PixelHeight, uint(this.height))])),\n ]);\n }\n\n #audioTrack() {\n const { channels, codecPrivate, preSkipSamples } = this.audio;\n const extra = [elem(ID.Audio, concat([elem(ID.SamplingFrequency, f64(48000)), elem(ID.Channels, uint(channels))]))];\n if (codecPrivate) extra.unshift(elem(ID.CodecPrivate, codecPrivate));\n // The encoder adds pre-skip samples of silence; CodecDelay tells the player to drop them again.\n extra.push(elem(ID.CodecDelay, uint((preSkipSamples / 48000) * 1e9)), elem(ID.SeekPreRoll, uint(SEEK_PRE_ROLL_NS)));\n return this.#track(2, 2, 'A_OPUS', extra);\n }\n\n #clusters() {\n const blocks = [...this.blocks].sort((a, b) => a.ms - b.ms || a.track - b.track);\n const out = [];\n for (let i = 0; i < blocks.length; ) {\n const start = blocks[i].ms;\n const body = [elem(ID.Timecode, uint(start))];\n while (i < blocks.length && blocks[i].ms - start <= CLUSTER_MS) {\n body.push(this.#block(blocks[i], start));\n i++;\n }\n out.push(concat([bytes(ID.Cluster), UNKNOWN_SIZE, ...body]));\n }\n return out;\n }\n\n #block(b, clusterStart) {\n const head = new Uint8Array(4);\n head[0] = 0x80 | b.track; // the track number as a variable-size integer\n new DataView(head.buffer).setInt16(1, b.ms - clusterStart);\n head[3] = b.key ? 0x80 : 0x00;\n return elem(ID.SimpleBlock, concat([head, b.data]));\n }\n}\n\nwindow.__amvWebm = { WebmWriter };\n"; // src/cli.js import { join as join9, resolve as resolve4, dirname as dirname3, basename as basename4, sep as sep3 } from "node:path"; import { spawn as spawn4 } from "node:child_process"; // src/languages/zh.js var zh_default = { id: "zh", language: "zh", script: "Hans", voice: { say: "zh_CN", espeak: "cmn" }, ui: { remark: { button: "\u6807\u6CE8", hint: "\u6279\u6CE8\uFF08\u53EF\u9009\uFF09", save: "\u4FDD\u5B58", remove: "\u5220\u9664", section: "\u6807\u6CE8", kinds: { suggestion: "\u5EFA\u8BAE", keep: "\u4FDD\u7559", question: "\u7591\u95EE", concern: "\u5173\u6CE8" } }, theme: "\u4E3B\u9898", modeLabel: "\u660E\u6697", mode: { auto: "\u8DDF\u968F\u7CFB\u7EDF", light: "\u4EAE", dark: "\u6697" }, copy: "\u590D\u5236\u6E90\u7A3F", done: "\u5DF2\u590D\u5236 \u2713", copyCode: "\u590D\u5236", reply: { button: "\u56DE\u590D", comment: "\u8BC4\u8BBA", commentHint: "\u5BF9\u8FD9\u4E2A\u9762\u677F\u7684\u610F\u89C1", title: "\u4F60\u7684\u56DE\u590D", hint: "\u590D\u5236\u540E\u7C98\u8D34\u5230\u5BF9\u8BDD\u91CC\u3002", copy: "\u590D\u5236\u56DE\u590D", close: "\u5173\u95ED", suggested: "\u5EFA\u8BAE", empty: "\u5148\u9009\u62E9\u9009\u9879\uFF0C\u6216\u5728\u9762\u677F\u4E0A\u5199\u8BC4\u8BBA\u3002", decisions: "\u51B3\u5B9A", comments: "\u8BC4\u8BBA", confirmed: "\u786E\u8BA4\u4E86\u5EFA\u8BAE", untouched: "\u672A\u4F5C\u7B54\uFF0C\u4FDD\u7559\u5EFA\u8BAE", was: "\u539F\u4E3A", typed: '\u4EE5 ">" \u5F00\u5934\u7684\u884C\u662F\u8BFB\u8005\u8F93\u5165\u7684\u6587\u5B57\u3002' }, toc: "\u76EE\u5F55", flow: "\u6D41\u7A0B\u56FE", sequence: "\u65F6\u5E8F\u56FE", er: "\u5B9E\u4F53\u5173\u7CFB\u56FE", colon: "\uFF1A", sep: "\u3001", expand: "\u5C55\u5F00\u67E5\u770B\u56FE\u8868", close: "\u5173\u95ED", diagram: "\u56FE\u8868\u67E5\u770B", delta: { added: "\u65B0\u589E", removed: "\u5220\u9664", changed: "\u4FEE\u6539", view: "\u89C6\u56FE", before: "\u6539\u524D", changes: "\u53D8\u66F4", after: "\u6539\u540E" }, generated: "Generated by", limits: { value: "{value} / max {limit}", limit: "max {limit}" } }, videoUi: { play: "\u64AD\u653E", pause: "\u6682\u505C", chapters: "\u7AE0\u8282", speed: "\u901F\u5EA6", export: "\u5BFC\u51FA", drawn: "\u5236\u56FE", date: "\u65E5\u671F", scenes: "\u573A\u666F", duration: "\u65F6\u957F", sheet: "\u56FE {n} / {total}" } }; // src/languages/zh-Hant.js var zh_Hant_default = { id: "zh-Hant", language: "zh", script: "Hant", voice: { say: "zh_TW", espeak: "cmn" }, langs: ["zh-Hant", "zh-TW", "zh-HK", "zh-MO"], fonts: { sans: '-apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang TC", "Heiti TC", "Microsoft JhengHei", "Noto Sans CJK TC", "Noto Sans TC", "PingFang SC", "Microsoft YaHei", Roboto, "Helvetica Neue", Arial, sans-serif', serif: '"Songti TC", "PMingLiU", "MingLiU", "Noto Serif CJK TC", "Noto Serif TC", "Source Han Serif TC", "Songti SC"' }, ui: { remark: { button: "\u6A19\u8A3B", hint: "\u6279\u8A3B\uFF08\u53EF\u9078\uFF09", save: "\u5132\u5B58", remove: "\u522A\u9664", section: "\u6A19\u8A3B", kinds: { suggestion: "\u5EFA\u8B70", keep: "\u4FDD\u7559", question: "\u7591\u554F", concern: "\u95DC\u6CE8" } }, theme: "\u4E3B\u984C", modeLabel: "\u660E\u6697", mode: { auto: "\u8DDF\u96A8\u7CFB\u7D71", light: "\u6DFA\u8272", dark: "\u6DF1\u8272" }, copy: "\u8907\u88FD\u6E90\u7A3F", done: "\u5DF2\u8907\u88FD \u2713", copyCode: "\u8907\u88FD", reply: { button: "\u56DE\u8986", comment: "\u8A55\u8AD6", commentHint: "\u5C0D\u9019\u500B\u9762\u677F\u7684\u610F\u898B", title: "\u4F60\u7684\u56DE\u8986", hint: "\u8907\u88FD\u5F8C\u8CBC\u5230\u5C0D\u8A71\u88E1\u3002", copy: "\u8907\u88FD\u56DE\u8986", close: "\u95DC\u9589", suggested: "\u5EFA\u8B70", empty: "\u5148\u9078\u64C7\u9078\u9805\uFF0C\u6216\u5728\u9762\u677F\u4E0A\u5BEB\u8A55\u8AD6\u3002", decisions: "\u6C7A\u5B9A", comments: "\u8A55\u8AD6", confirmed: "\u78BA\u8A8D\u4E86\u5EFA\u8B70", untouched: "\u672A\u4F5C\u7B54\uFF0C\u4FDD\u7559\u5EFA\u8B70", was: "\u539F\u70BA", typed: '\u4EE5 ">" \u958B\u982D\u7684\u884C\u662F\u8B80\u8005\u8F38\u5165\u7684\u6587\u5B57\u3002' }, toc: "\u76EE\u9304", flow: "\u6D41\u7A0B\u5716", sequence: "\u6642\u5E8F\u5716", er: "\u5BE6\u9AD4\u95DC\u806F\u5716", colon: "\uFF1A", sep: "\u3001", expand: "\u5C55\u958B\u67E5\u770B\u5716\u8868", close: "\u95DC\u9589", diagram: "\u5716\u8868\u6AA2\u8996", delta: { added: "\u65B0\u589E", removed: "\u522A\u9664", changed: "\u4FEE\u6539", view: "\u6AA2\u8996", before: "\u6539\u524D", changes: "\u8B8A\u66F4", after: "\u6539\u5F8C" }, generated: "Generated by", limits: { value: "{value} / max {limit}", limit: "max {limit}" } }, videoUi: { play: "\u64AD\u653E", pause: "\u66AB\u505C", chapters: "\u7AE0\u7BC0", speed: "\u901F\u5EA6", export: "\u532F\u51FA", drawn: "\u88FD\u5716", date: "\u65E5\u671F", scenes: "\u5834\u666F", duration: "\u6642\u9577", sheet: "\u5716 {n} / {total}" } }; // src/languages/en.js var en_default = { id: "en", language: "en", voice: { say: "en_US", espeak: "en-us" }, ui: { remark: { button: "Remark", hint: "Optional note", save: "Save", remove: "Remove", section: "Remarks", kinds: { suggestion: "suggestion", keep: "keep", question: "question", concern: "concern" } }, theme: "Theme", modeLabel: "Mode", mode: { auto: "Auto", light: "Light", dark: "Dark" }, copy: "Copy source", done: "Copied \u2713", copyCode: "Copy", reply: { button: "Reply", comment: "Comment", commentHint: "Your comment on this panel", title: "Your reply", hint: "Copy it and paste it into the chat.", copy: "Copy reply", close: "Close", suggested: "suggested", empty: "Pick options, or comment on a panel first.", decisions: "Decisions", comments: "Comments", confirmed: "suggestion confirmed", untouched: "not answered; suggestion kept", was: "was", typed: 'Lines that start with ">" are text the reader typed.' }, toc: "Contents", flow: "Flowchart", sequence: "Sequence diagram", er: "Entity relationship diagram", colon: ": ", sep: ", ", expand: "Expand diagram", close: "Close", diagram: "Diagram viewer", delta: { added: "added", removed: "removed", changed: "changed", view: "View", before: "Before", changes: "Changes", after: "After" }, generated: "Generated by", limits: { value: "{value} / max {limit}", limit: "max {limit}" } }, // Player labels. drawn..duration are the title block cells; sheet is the scene header, with {n} and {total} the zero-padded numbers. videoUi: { play: "Play", pause: "Pause", chapters: "Chapters", speed: "Speed", export: "Export", drawn: "DRAWN", date: "DATE", scenes: "SCENES", duration: "DURATION", sheet: "SHEET {n} / {total}" } }; // src/languages/ja.js var ja_default = { id: "ja", language: "ja", voice: { say: "ja_JP", espeak: "ja" }, langs: ["ja"], fonts: { sans: '-apple-system, BlinkMacSystemFont, "Segoe UI", "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Yu Gothic UI", "Yu Gothic", Meiryo, "Noto Sans CJK JP", "Noto Sans JP", "PingFang SC", "Microsoft YaHei", Roboto, "Helvetica Neue", Arial, sans-serif', serif: '"Hiragino Mincho ProN", "Yu Mincho", "Noto Serif CJK JP", "Noto Serif JP", "Songti SC"' }, ui: { remark: { button: "\u6CE8\u8A18", hint: "\u30E1\u30E2\uFF08\u4EFB\u610F\uFF09", save: "\u4FDD\u5B58", remove: "\u524A\u9664", section: "\u6CE8\u8A18", kinds: { suggestion: "\u63D0\u6848", keep: "\u7DAD\u6301", question: "\u8CEA\u554F", concern: "\u61F8\u5FF5" } }, theme: "\u30C6\u30FC\u30DE", modeLabel: "\u8868\u793A", mode: { auto: "\u81EA\u52D5", light: "\u30E9\u30A4\u30C8", dark: "\u30C0\u30FC\u30AF" }, copy: "\u539F\u7A3F\u3092\u30B3\u30D4\u30FC", done: "\u30B3\u30D4\u30FC\u3057\u307E\u3057\u305F \u2713", copyCode: "\u30B3\u30D4\u30FC", reply: { button: "\u8FD4\u4FE1", comment: "\u30B3\u30E1\u30F3\u30C8", commentHint: "\u3053\u306E\u30D1\u30CD\u30EB\u3078\u306E\u30B3\u30E1\u30F3\u30C8", title: "\u3042\u306A\u305F\u306E\u8FD4\u4FE1", hint: "\u30B3\u30D4\u30FC\u3057\u3066\u30C1\u30E3\u30C3\u30C8\u306B\u8CBC\u308A\u4ED8\u3051\u3066\u304F\u3060\u3055\u3044\u3002", copy: "\u8FD4\u4FE1\u3092\u30B3\u30D4\u30FC", close: "\u9589\u3058\u308B", suggested: "\u63A8\u5968", empty: "\u9078\u629E\u80A2\u3092\u9078\u3076\u304B\u3001\u30D1\u30CD\u30EB\u306B\u30B3\u30E1\u30F3\u30C8\u3057\u3066\u304F\u3060\u3055\u3044\u3002", decisions: "\u6C7A\u5B9A", comments: "\u30B3\u30E1\u30F3\u30C8", confirmed: "\u63A8\u5968\u3092\u78BA\u8A8D", untouched: "\u672A\u56DE\u7B54\uFF08\u63A8\u5968\u306E\u307E\u307E\uFF09", was: "\u5909\u66F4\u524D", typed: "\u300C>\u300D\u3067\u59CB\u307E\u308B\u884C\u306F\u8AAD\u8005\u304C\u5165\u529B\u3057\u305F\u6587\u5B57\u3067\u3059\u3002" }, toc: "\u76EE\u6B21", flow: "\u30D5\u30ED\u30FC\u30C1\u30E3\u30FC\u30C8", sequence: "\u30B7\u30FC\u30B1\u30F3\u30B9\u56F3", er: "ER \u56F3", colon: "\uFF1A", sep: "\u3001", expand: "\u62E1\u5927\u8868\u793A", close: "\u9589\u3058\u308B", diagram: "\u30C0\u30A4\u30A2\u30B0\u30E9\u30E0", delta: { added: "\u8FFD\u52A0", removed: "\u524A\u9664", changed: "\u5909\u66F4", view: "\u8868\u793A", before: "\u5909\u66F4\u524D", changes: "\u5DEE\u5206", after: "\u5909\u66F4\u5F8C" }, generated: "Generated by", limits: { value: "{value} / max {limit}", limit: "max {limit}" } }, videoUi: { play: "\u518D\u751F", pause: "\u4E00\u6642\u505C\u6B62", chapters: "\u7AE0", speed: "\u901F\u5EA6", export: "\u66F8\u304D\u51FA\u3057", drawn: "\u4F5C\u56F3", date: "\u65E5\u4ED8", scenes: "\u30B7\u30FC\u30F3", duration: "\u518D\u751F\u6642\u9593", sheet: "\u30B7\u30FC\u30C8 {n} / {total}" } }; // src/languages/he.js var he_default = { id: "he", language: "he", langs: ["he"], fonts: { sans: '-apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans Hebrew", "Arial Hebrew", Arial, Roboto, "Helvetica Neue", sans-serif', serif: '"Noto Serif Hebrew", "Frank Ruehl CLM", David, "Times New Roman"' }, ui: { remark: { button: "\u05E1\u05D9\u05DE\u05D5\u05DF", hint: "\u05D4\u05E2\u05E8\u05D4 (\u05D0\u05D5\u05E4\u05E6\u05D9\u05D5\u05E0\u05DC\u05D9)", save: "\u05E9\u05DE\u05D9\u05E8\u05D4", remove: "\u05D4\u05E1\u05E8\u05D4", section: "\u05E1\u05D9\u05DE\u05D5\u05E0\u05D9\u05DD", kinds: { suggestion: "\u05D4\u05E6\u05E2\u05D4", keep: "\u05DC\u05D4\u05E9\u05D0\u05D9\u05E8", question: "\u05E9\u05D0\u05DC\u05D4", concern: "\u05D7\u05E9\u05E9" } }, theme: "\u05E2\u05E8\u05DB\u05EA \u05E2\u05D9\u05E6\u05D5\u05D1", modeLabel: "\u05DE\u05E6\u05D1", mode: { auto: "\u05D0\u05D5\u05D8\u05D5\u05DE\u05D8\u05D9", light: "\u05D1\u05D4\u05D9\u05E8", dark: "\u05DB\u05D4\u05D4" }, copy: "\u05D4\u05E2\u05EA\u05E7\u05EA \u05D4\u05DE\u05E7\u05D5\u05E8", done: "\u05D4\u05D5\u05E2\u05EA\u05E7 \u2713", copyCode: "\u05D4\u05E2\u05EA\u05E7\u05D4", reply: { button: "\u05EA\u05D2\u05D5\u05D1\u05D4", comment: "\u05D4\u05E2\u05E8\u05D4", commentHint: "\u05D4\u05D4\u05E2\u05E8\u05D4 \u05E9\u05DC\u05DA \u05E2\u05DC \u05D4\u05D7\u05DC\u05E7 \u05D4\u05D6\u05D4", title: "\u05D4\u05EA\u05D2\u05D5\u05D1\u05D4 \u05E9\u05DC\u05DA", hint: "\u05D4\u05E2\u05EA\u05D9\u05E7\u05D5 \u05D5\u05D4\u05D3\u05D1\u05D9\u05E7\u05D5 \u05D0\u05D5\u05EA\u05D4 \u05D1\u05E6\u05F3\u05D0\u05D8.", copy: "\u05D4\u05E2\u05EA\u05E7\u05EA \u05D4\u05EA\u05D2\u05D5\u05D1\u05D4", close: "\u05E1\u05D2\u05D9\u05E8\u05D4", suggested: "\u05DE\u05D5\u05DE\u05DC\u05E5", empty: "\u05E7\u05D5\u05D3\u05DD \u05D1\u05D7\u05E8\u05D5 \u05D0\u05E4\u05E9\u05E8\u05D5\u05EA \u05D0\u05D5 \u05DB\u05EA\u05D1\u05D5 \u05D4\u05E2\u05E8\u05D4 \u05E2\u05DC \u05D0\u05D7\u05D3 \u05D4\u05D7\u05DC\u05E7\u05D9\u05DD.", decisions: "\u05D4\u05D7\u05DC\u05D8\u05D5\u05EA", comments: "\u05D4\u05E2\u05E8\u05D5\u05EA", confirmed: "\u05D4\u05D4\u05DE\u05DC\u05E6\u05D4 \u05D0\u05D5\u05E9\u05E8\u05D4", untouched: "\u05DC\u05D0 \u05E0\u05E2\u05E0\u05D4; \u05D4\u05D4\u05DE\u05DC\u05E6\u05D4 \u05E0\u05E9\u05D0\u05E8\u05D4", was: "\u05D4\u05D9\u05D4", typed: '\u05E9\u05D5\u05E8\u05D5\u05EA \u05E9\u05DE\u05EA\u05D7\u05D9\u05DC\u05D5\u05EA \u05D1-">" \u05D4\u05DF \u05D8\u05E7\u05E1\u05D8 \u05E9\u05D4\u05E7\u05D5\u05E8\u05D0 \u05D4\u05E7\u05DC\u05D9\u05D3.' }, toc: "\u05EA\u05D5\u05DB\u05DF \u05D4\u05E2\u05E0\u05D9\u05D9\u05E0\u05D9\u05DD", flow: "\u05EA\u05E8\u05E9\u05D9\u05DD \u05D6\u05E8\u05D9\u05DE\u05D4", sequence: "\u05EA\u05E8\u05E9\u05D9\u05DD \u05E8\u05E6\u05E3", er: "\u05EA\u05E8\u05E9\u05D9\u05DD \u05D9\u05E9\u05D5\u05D9\u05D5\u05EA \u05D5\u05E7\u05E9\u05E8\u05D9\u05DD", colon: ": ", sep: ", ", expand: "\u05D4\u05D2\u05D3\u05DC\u05EA \u05D4\u05EA\u05E8\u05E9\u05D9\u05DD", close: "\u05E1\u05D2\u05D9\u05E8\u05D4", diagram: "\u05DE\u05E6\u05D9\u05D2 \u05D4\u05EA\u05E8\u05E9\u05D9\u05DD", delta: { added: "\u05E0\u05D5\u05E1\u05E3", removed: "\u05D4\u05D5\u05E1\u05E8", changed: "\u05E9\u05D5\u05E0\u05D4", view: "\u05EA\u05E6\u05D5\u05D2\u05D4", before: "\u05DC\u05E4\u05E0\u05D9", changes: "\u05E9\u05D9\u05E0\u05D5\u05D9\u05D9\u05DD", after: "\u05D0\u05D7\u05E8\u05D9" }, generated: "\u05E0\u05D5\u05E6\u05E8 \u05D1\u05D0\u05DE\u05E6\u05E2\u05D5\u05EA", limits: { value: "{value} \u05DE\u05EA\u05D5\u05DA {limit}", limit: "\u05E2\u05D3 {limit}" } }, // Names for the frontmatter keys an agent writes most (`author: …`), shown under the title. Other keys show as written. metaKeys: { author: "\u05DE\u05D0\u05EA", by: "\u05DE\u05D0\u05EA", date: "\u05EA\u05D0\u05E8\u05D9\u05DA", updated: "\u05E2\u05D5\u05D3\u05DB\u05DF", time: "\u05E9\u05E2\u05D4", source: "\u05DE\u05E7\u05D5\u05E8", sources: "\u05DE\u05E7\u05D5\u05E8\u05D5\u05EA", version: "\u05D2\u05E8\u05E1\u05D4", status: "\u05E1\u05D8\u05D8\u05D5\u05E1", owner: "\u05D0\u05D7\u05E8\u05D0\u05D9", team: "\u05E6\u05D5\u05D5\u05EA", project: "\u05E4\u05E8\u05D5\u05D9\u05E7\u05D8", audience: "\u05E7\u05D4\u05DC \u05D9\u05E2\u05D3", reviewer: "\u05D1\u05D5\u05D3\u05E7", reviewers: "\u05D1\u05D5\u05D3\u05E7\u05D9\u05DD", license: "\u05E8\u05D9\u05E9\u05D9\u05D5\u05DF", licence: "\u05E8\u05D9\u05E9\u05D9\u05D5\u05DF", model: "\u05DE\u05D5\u05D3\u05DC", repo: "\u05E8\u05D9\u05E4\u05D5", branch: "\u05E2\u05E0\u05E3", tags: "\u05EA\u05D2\u05D9\u05D5\u05EA", for: "\u05E2\u05D1\u05D5\u05E8", ref: "\u05D4\u05E4\u05E0\u05D9\u05D4" }, // The word after a change count above one ("+4 נוספו"); the ui.delta words are the singular, for one item and for a node badge. deltaCounts: { added: "\u05E0\u05D5\u05E1\u05E4\u05D5", removed: "\u05D4\u05D5\u05E1\u05E8\u05D5", changed: "\u05E9\u05D5\u05E0\u05D5" }, // The render time under the page reads day.month.year, the Israeli way. dateOrder: "dmy", videoUi: { play: "\u05D4\u05E4\u05E2\u05DC\u05D4", pause: "\u05D4\u05E9\u05D4\u05D9\u05D4", chapters: "\u05E4\u05E8\u05E7\u05D9\u05DD", speed: "\u05DE\u05D4\u05D9\u05E8\u05D5\u05EA", export: "\u05D9\u05D9\u05E6\u05D5\u05D0", drawn: "\u05E9\u05D5\u05E8\u05D8\u05D8", date: "\u05EA\u05D0\u05E8\u05D9\u05DA", scenes: "\u05E1\u05E6\u05E0\u05D5\u05EA", duration: "\u05DE\u05E9\u05DA", sheet: "\u05D2\u05D9\u05DC\u05D9\u05D5\u05DF {n} \u05DE\u05EA\u05D5\u05DA {total}" } }; // src/languages/registry.js var LANGUAGES = Object.freeze([zh_default, zh_Hant_default, en_default, ja_default, he_default]); var FALLBACK = en_default; var languageIds = () => LANGUAGES.map((l3) => l3.id); function findLanguage(language, script) { return LANGUAGES.find((l3) => l3.language === language && (!l3.script || l3.script === script)); } // src/themes/fonts.js var SANS = '-apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Roboto, "Helvetica Neue", Arial, sans-serif'; var MONO = 'ui-monospace, SFMono-Regular, "JetBrains Mono", Menlo, Consolas, "Liberation Mono", monospace'; var fontLanguages = () => LANGUAGES.filter((l3) => l3.fonts); var langSelector = (language, base, rest) => language.langs.map((tag) => `${base}:lang(${tag})${rest}`).join(", "); var serifByLanguage = (variable, head) => fontLanguages().map((l3) => `${langSelector(l3, "&", "[data-mode]")} { ${variable}: ${head}, ${l3.fonts.serif}, serif; }`).join("\n"); // src/themes/blueprint.js var blueprint_default = { name: "blueprint", summary: "Blueprint drawing", label: { zh: "\u56FE\u7EB8", "zh-Hant": "\u5716\u7D19", en: "Blueprint", ja: "\u56F3\u9762", he: "\u05E9\u05E8\u05D8\u05D5\u05D8" }, // lang-ok: viewer-facing theme labels scope: ["page", "video"], tokens: { common: { "--font-sans": SANS, "--font-mono": MONO, "--radius": "0px", "--shadow": "none", "--bw": "1.5px", "--head-font": "var(--font-sans)" }, light: { "--bg": "#f6f6f3", "--paper": "#ffffff", "--ink": "#16181d", "--ink-2": "#4b5260", "--ink-3": "#8b929e", "--line": "#1d2026", "--line-2": "#d6dae1", "--fill": "#f3f5f8", "--accent": "#1d5fbf", "--accent-bg": "#e4ecf8", "--ok": "#1d5fbf", "--ok-bg": "#e4ecf8", "--err": "#c62828", "--err-bg": "#fbeaea", "--warn": "#a8620a", "--warn-bg": "#fdf3e2", "--head-bg": "#16181d", "--head-fg": "#ffffff" }, dark: { "--bg": "#081322", "--paper": "#0d1c31", "--ink": "#e6edf7", "--ink-2": "#a9b8cc", "--ink-3": "#6b7f99", "--line": "#c9d6e8", "--line-2": "#23385a", "--fill": "#12253f", "--accent": "#6ea8ff", "--accent-bg": "#16305a", "--ok": "#6ea8ff", "--ok-bg": "#16305a", "--err": "#ff7070", "--err-bg": "#3b1620", "--warn": "#f0b14a", "--warn-bg": "#3a2a10", "--head-bg": "#e6edf7", "--head-fg": "#081322" } }, css: `& .am-frame { border: 1px solid var(--line); padding: 30px; } & .am-frame::before { content: ""; position: absolute; inset: 18px; border: 1px solid var(--line); pointer-events: none; } & .am-ruler { display: flex; position: absolute; font: 10px/1 var(--font-mono); color: var(--ink-3); } @media (max-width: 760px) { & .am-frame { padding: 0; border: 0; } & .am-frame::before, & .am-ruler { display: none; } }`, video: { // Drawing-sheet ground with a fine grid. tokens: { light: { "--v-stage": `linear-gradient(var(--line-2) 1px, transparent 1px) 0 0 / 40px 40px, linear-gradient(90deg, var(--line-2) 1px, transparent 1px) 0 0 / 40px 40px, var(--paper)` }, dark: { "--v-stage": `linear-gradient(rgba(201, 214, 232, 0.07) 1px, transparent 1px) 0 0 / 40px 40px, linear-gradient(90deg, rgba(201, 214, 232, 0.07) 1px, transparent 1px) 0 0 / 40px 40px, var(--bg)` } }, css: `& .amv-sheet { display: block; position: absolute; inset: 24px; border: 1.5px solid var(--line); pointer-events: none; } & .amv-sheet::before { content: ""; position: absolute; inset: 24px; border: 1.5px solid var(--line); }` } }; // src/themes/shadcn.js var shadcn_default = { name: "shadcn", summary: "shadcn cards", label: { zh: "\u5361\u7247", "zh-Hant": "\u5361\u7247", en: "Cards", ja: "\u30AB\u30FC\u30C9", he: "\u05DB\u05E8\u05D8\u05D9\u05E1\u05D9\u05DD" }, // lang-ok: viewer-facing theme labels scope: ["page", "video"], tokens: { common: { "--font-sans": SANS, "--font-mono": MONO, "--radius": "8px", "--shadow": "0 1px 2px 0 rgba(0,0,0,0.05)", "--bw": "1px", "--head-font": "var(--font-sans)" }, light: { "--bg": "#fafafa", "--paper": "#ffffff", "--ink": "#09090b", "--ink-2": "#71717a", "--ink-3": "#a1a1aa", "--line": "#e4e4e7", "--line-2": "#f0f0f2", "--fill": "#f4f4f5", "--accent": "#2563eb", "--accent-bg": "#eff6ff", "--ok": "#16a34a", "--ok-bg": "#f0fdf4", "--err": "#dc2626", "--err-bg": "#fef2f2", "--warn": "#d97706", "--warn-bg": "#fffbeb", "--head-bg": "#18181b", "--head-fg": "#fafafa" }, dark: { "--bg": "#09090b", "--paper": "#121215", "--ink": "#fafafa", "--ink-2": "#a1a1aa", "--ink-3": "#71717a", "--line": "#27272a", "--line-2": "#1c1c1f", "--fill": "#18181b", "--accent": "#60a5fa", "--accent-bg": "#172554", "--ok": "#4ade80", "--ok-bg": "#052e16", "--err": "#f87171", "--err-bg": "#450a0a", "--warn": "#fbbf24", "--warn-bg": "#451a03", "--head-bg": "#fafafa", "--head-fg": "#18181b" } }, css: `& .am-panel-id { border-radius: 6px; min-width: 24px; height: 24px; margin: 9px 0 9px 14px; font-size: 12px; } & .am-panel-head { border-bottom-width: 1px; } & .am-kv { border-color: var(--line); border-radius: var(--radius); overflow: hidden; }`, video: { css: `& .amv-scene-n { margin: 12px 0 12px 14px; min-width: 40px; border-radius: 8px; font-size: 22px; } & .amv-scene-head { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); }`, rtlCss: "& .amv-scene-n { margin: 12px 14px 12px 0; }" } }; // src/themes/paper.js var SERIF = '"Iowan Old Style", "Palatino Linotype", Palatino, Georgia, "Songti SC", "STSong", "Noto Serif CJK SC", "Source Han Serif SC", serif'; var LANGUAGE_SERIF_HEAD = '"Iowan Old Style", Palatino, Georgia'; var paper_default = { name: "paper", summary: "Paper, for long reading", label: { zh: "\u7EB8\u5F20", "zh-Hant": "\u7D19\u5F35", en: "Paper", ja: "\u7D19", he: "\u05E0\u05D9\u05D9\u05E8" }, // lang-ok: viewer-facing theme labels scope: ["page"], tokens: { common: { "--font-sans": SANS, "--font-mono": MONO, "--font-serif": SERIF, "--radius": "2px", "--shadow": "none", "--bw": "1px", "--head-font": "var(--font-serif)" }, light: { "--bg": "#f5f2ea", "--paper": "#fdfbf6", "--ink": "#1f1c17", "--ink-2": "#57514a", "--ink-3": "#8c867b", "--line": "#cbc3b4", "--line-2": "#e5dfd3", "--fill": "#f3eee4", "--accent": "#8c2f1e", "--accent-bg": "#f5e5df", "--ok": "#2e6a3b", "--ok-bg": "#e4efe5", "--err": "#a3251b", "--err-bg": "#f7e3e0", "--warn": "#7f5300", "--warn-bg": "#f6ecd6", "--head-bg": "#1f1c17", "--head-fg": "#fdfbf6" }, dark: { "--bg": "#15130f", "--paper": "#1c1a15", "--ink": "#ebe5d8", "--ink-2": "#b5ad9e", "--ink-3": "#7b7467", "--line": "#4a443a", "--line-2": "#2d2a24", "--fill": "#242119", "--accent": "#e59a7d", "--accent-bg": "#3a2219", "--ok": "#8cc79a", "--ok-bg": "#1c3122", "--err": "#f0928a", "--err-bg": "#3a1d1a", "--warn": "#e3b866", "--warn-bg": "#352a13", "--head-bg": "#ebe5d8", "--head-fg": "#15130f" } }, css: `${serifByLanguage("--font-serif", LANGUAGE_SERIF_HEAD)} & .am-head h1, & .am-panel-head h2, & .am-md, & .am-intro, & .am-callout { font-family: var(--font-serif); } & .am-head h1 { font-weight: 600; letter-spacing: 0; } & .am-md p, & .am-md li, & .am-md blockquote, & .am-intro { font-size: 15.5px; line-height: 1.75; } & .am-md p { margin-bottom: 12px; } & .am-md table { font-family: var(--font-sans); }` }; // src/themes/3b1b.js var TITLE_HEAD = '"CMU Serif", "Latin Modern Roman", "Iowan Old Style", "Palatino"'; var b1b_default = { name: "3b1b", summary: "3Blue1Brown dark", scope: ["video"], mode: "dark", video: { tokens: { common: { "--bg": "#0e1015", "--paper": "#141922", "--ink": "#eceff4", "--ink-2": "#a9b4c4", "--ink-3": "#6c7789", "--line": "#58c4dd", "--line-2": "#2a3444", "--fill": "#171d27", "--accent": "#f7d96f", "--accent-bg": "rgba(247, 217, 111, 0.12)", "--ok": "#83c167", "--ok-bg": "rgba(131, 193, 103, 0.14)", "--err": "#fc6255", "--err-bg": "rgba(252, 98, 85, 0.14)", "--warn": "#f7d96f", "--warn-bg": "rgba(247, 217, 111, 0.12)", "--head-bg": "#eceff4", "--head-fg": "#0e1015", "--radius": "6px", "--bw": "1.6px", "--shadow": "none", "--font-sans": '-apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Roboto, sans-serif', "--font-mono": "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace", "--head-font": "var(--font-sans)", "--v-stage": "radial-gradient(ellipse at 50% 40%, #151a23 0%, #0e1015 70%)", "--v-cap-fg": "#fff", "--v-cap-bg": "rgba(8, 10, 14, 0.55)", "--v-cap-border": "transparent", "--v-title-font": '"CMU Serif", "Latin Modern Roman", "Iowan Old Style", "Palatino", "Songti SC", "STSong", "Noto Serif CJK SC", serif', "--v-glow": "drop-shadow(0 0 6px rgba(247, 217, 111, 0.55))" } }, css: `${serifByLanguage("--v-title-font", TITLE_HEAD)} & .amv-scene-head { left: 96px; top: 56px; border: 0; background: none; align-items: baseline; gap: 22px; } & .amv-scene-n { background: none; color: var(--line); padding: 0; min-width: 0; font: 500 30px var(--font-mono); } & .amv-scene-title { padding: 0; font-weight: 400; font-size: 46px; } & .amv-scene-meta { display: none; } & .amv-title { font-weight: 400; font-size: 104px; letter-spacing: 0.005em; } & .amv-subtitle { color: var(--line); } & .amv-titleblock { display: none; } & .amv-caption { bottom: 56px; } & .amv-caption span { text-shadow: 0 2px 8px rgba(0, 0, 0, 0.6); font-size: 40px; }`, // Right-to-left: the floating title keeps its 96 px inset on the side it starts from. rtlCss: "& .amv-scene-head { left: 72px; right: 96px; }" } }; // src/themes/user.js import { existsSync, readdirSync, readFileSync } from "node:fs"; import { basename, join } from "node:path"; // src/themes/color.js var HEX = /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i; var FN = /^(rgba?|hsla?)\(\s*([^)]*)\)$/i; function parseColor(value) { const v = String(value).trim(); const hex = v.match(HEX); if (hex) { let h3 = hex[1]; if (h3.length <= 4) h3 = [...h3].map((c) => c + c).join(""); const n = [0, 2, 4, 6].map((i) => parseInt(h3.slice(i, i + 2) || "ff", 16)); return [n[0], n[1], n[2], n[3] / 255]; } const fn3 = v.match(FN); if (!fn3) return null; const parts = fn3[2].split(/[\s,/]+/).filter(Boolean); if (parts.length < 3 || parts.length > 4) return null; const num = (s2, scale) => s2.endsWith("%") ? parseFloat(s2) / 100 * scale : parseFloat(s2); const alpha = parts[3] === void 0 ? 1 : num(parts[3], 1); if (fn3[1].toLowerCase().startsWith("rgb")) { const rgb = parts.slice(0, 3).map((s2) => num(s2, 255)); return [...rgb, alpha].some(Number.isNaN) ? null : [...rgb, alpha]; } const [h2, s, l3] = [parseFloat(parts[0]), num(parts[1], 1), num(parts[2], 1)]; if ([h2, s, l3, alpha].some(Number.isNaN)) return null; const k2 = (n) => (n + h2 / 30) % 12; const f2 = (n) => l3 - s * Math.min(l3, 1 - l3) * Math.max(-1, Math.min(k2(n) - 3, 9 - k2(n), 1)); return [f2(0) * 255, f2(8) * 255, f2(4) * 255, alpha]; } var over = ([r, g, b, a], [br2, bg, bb]) => [r * a + br2 * (1 - a), g * a + bg * (1 - a), b * a + bb * (1 - a), 1]; var luminance = ([r, g, b]) => { const c = [r, g, b].map((x2) => x2 / 255).map((x2) => x2 <= 0.03928 ? x2 / 12.92 : ((x2 + 0.055) / 1.055) ** 2.4); return 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2]; }; function contrast(fg, bg, base = [255, 255, 255, 1]) { const back = over(bg, base); const [x2, y2] = [luminance(over(fg, back)), luminance(back)]; return (Math.max(x2, y2) + 0.05) / (Math.min(x2, y2) + 0.05); } // src/themes/check.js var NAME = /^[a-z0-9][a-z0-9-]*$/; var COLOR_TOKENS = Object.freeze([ "--bg", "--paper", "--ink", "--ink-2", "--ink-3", "--line", "--line-2", "--fill", "--accent", "--accent-bg", "--ok", "--ok-bg", "--err", "--err-bg", "--warn", "--warn-bg", "--head-bg", "--head-fg" ]); var LANGS = languageIds(); var UNSAFE_VALUE = /[;{}<]/; function normalizeTheme(name, data, builtinNames = []) { const errors = []; if (!NAME.test(name)) errors.push(`the name "${name}" must use lowercase letters, digits and -`); if (builtinNames.includes(name)) errors.push(`"${name}" is a built-in theme; rename the file`); if (!data || typeof data !== "object" || Array.isArray(data)) return { theme: null, errors: [...errors, "the file must hold a JSON object"] }; const tokens = data.tokens ?? {}; const group = (where, g) => { if (g === void 0) return {}; if (!g || typeof g !== "object" || Array.isArray(g)) { errors.push(`${where} must be an object of CSS variables`); return {}; } for (const [k2, v] of Object.entries(g)) { if (!k2.startsWith("--")) errors.push(`${where}: "${k2}" must start with --`); else if (typeof v !== "string" || UNSAFE_VALUE.test(v)) errors.push(`${where}: ${k2} must be a string without ; { } <`); } return g; }; const common = group("tokens.common", tokens.common); const light = group("tokens.light", tokens.light); const dark = group("tokens.dark", tokens.dark); for (const [mode, set] of [["light", { ...common, ...light }], ["dark", { ...common, ...dark }]]) { const missing = COLOR_TOKENS.filter((k2) => set[k2] === void 0); if (missing.length) errors.push(`${mode} mode is missing ${missing.join(" ")}`); } const css = cssField("css", data.css, errors); let video; if (data.video !== void 0) { const v = data.video && typeof data.video === "object" ? data.video : {}; const vt2 = v.tokens ?? {}; video = { tokens: { common: group("video.tokens.common", vt2.common), light: group("video.tokens.light", vt2.light), dark: group("video.tokens.dark", vt2.dark) }, css: cssField("video.css", v.css, errors) }; } const label = labelField(data.label, name, errors); const fonts = {}; if (common["--font-sans"]) fonts["--font-sans"] = `${common["--font-sans"]}, ${SANS}`; if (common["--font-mono"]) fonts["--font-mono"] = `${common["--font-mono"]}, ${MONO}`; const theme = { name, summary: label.en, label, scope: ["page", "video"], user: true, ownFont: Boolean(common["--font-sans"]), tokens: { common: { ...blueprint_default.tokens.common, ...common, ...fonts }, light, dark }, css, video }; return { theme, errors }; } function labelField(label, name, errors) { if (label === void 0) return Object.fromEntries(LANGS.map((l3) => [l3, name])); if (typeof label === "string") return Object.fromEntries(LANGS.map((l3) => [l3, label])); if (!label || typeof label !== "object" || Object.values(label).some((v) => typeof v !== "string")) { errors.push(`label must be a string or an object of ${LANGS.join(" / ")} strings`); return Object.fromEntries(LANGS.map((l3) => [l3, name])); } const fallback = label.en ?? Object.values(label)[0] ?? name; return Object.fromEntries(LANGS.map((l3) => [l3, label[l3] ?? fallback])); } function cssField(where, css, errors) { if (css === void 0) return void 0; if (typeof css !== "string") { errors.push(`${where} must be a string`); return void 0; } if (/<\/style/i.test(css)) errors.push(`${where} must not contain `); const text = css.replace(/\/\*[\s\S]*?\*\//g, ""); for (const m of text.matchAll(/(?:^|[{};])\s*([^{};]+?)\s*\{/g)) { const prelude = m[1]; if (prelude.startsWith("@")) continue; const bad = prelude.split(",").map((s) => s.trim()).find((s) => !s.startsWith("&")); if (bad !== void 0) errors.push(`${where}: selector "${bad}" must start with & (the theme's root)`); } return css; } var TEXT_PAIRS = [["--ink", "--paper"], ["--ink", "--bg"], ["--ink-2", "--paper"], ["--ink-2", "--bg"], ["--head-fg", "--head-bg"], ["--accent", "--paper"]]; var BADGE_PAIRS = [["--accent", "--accent-bg"], ["--ok", "--ok-bg"], ["--err", "--err-bg"], ["--warn", "--warn-bg"]]; function checkColors(theme) { const errors = []; const warnings = []; const { common = {}, light = {}, dark = {} } = theme.tokens ?? {}; for (const [mode, set] of [["light", { ...common, ...light }], ["dark", { ...common, ...light, ...dark }]]) { const colors = {}; for (const k2 of COLOR_TOKENS) { if (set[k2] === void 0) continue; const c = parseColor(set[k2]); if (c) colors[k2] = c; else errors.push(`${mode}: ${k2} "${set[k2]}" is not a color this check reads (use hex, rgb() or hsl())`); } const ratio = (fg, bg) => colors[fg] && colors[bg] ? contrast(colors[fg], colors[bg], colors["--paper"]) : null; const say = (fg, bg, r, need) => `${mode}: ${fg} on ${bg} has contrast ${r.toFixed(2)}:1, needs ${need}:1`; for (const [fg, bg] of TEXT_PAIRS) { const r = ratio(fg, bg); if (r !== null && r < 4.5) errors.push(say(fg, bg, r, 4.5)); } for (const [fg, bg] of BADGE_PAIRS) { const r = ratio(fg, bg); if (r !== null && r < 3) errors.push(say(fg, bg, r, 3)); else if (r !== null && r < 4.5) warnings.push(say(fg, bg, r, 4.5)); } } return { errors, warnings }; } // src/themes/user.js function readThemeFile(file, builtinNames) { const name = basename(file).replace(/\.json$/i, ""); let data; try { data = JSON.parse(readFileSync(file, "utf8")); } catch (e) { return { name, theme: null, errors: [`cannot read it as JSON (${e.message})`] }; } return { name, ...normalizeTheme(name, data, builtinNames) }; } function readUserThemes(home, builtinNames) { const dir = join(home, "themes"); const themes2 = []; const broken = /* @__PURE__ */ new Map(); if (!existsSync(dir)) return { themes: themes2, broken }; for (const f2 of readdirSync(dir).filter((x2) => x2.toLowerCase().endsWith(".json")).sort()) { const { name, theme, errors } = readThemeFile(join(dir, f2), builtinNames); if (errors.length) broken.set(name, { file: `themes/${f2}`, reason: errors.join("; ") }); else themes2.push(theme); } return { themes: themes2, broken }; } // src/themes/registry.js var ALL = Object.freeze([blueprint_default, shadcn_default, paper_default, b1b_default]); var BUILTIN_NAMES = ALL.map((t) => t.name); var themes = (scope) => ALL.filter((t) => t.scope.includes(scope)); var themeNames = (scope) => themes(scope).map((t) => t.name); var getTheme = (name) => ALL.find((t) => t.name === name); var AUTO = "auto"; function pickTheme({ scope, template, visuals }) { if (scope === "video") return "blueprint"; return template === "doc" || !visuals ? "paper" : "blueprint"; } function themeSet(user = [], broken = /* @__PURE__ */ new Map()) { const all = [...ALL, ...user]; const list = (scope) => all.filter((t) => t.scope.includes(scope)); const names = (scope) => list(scope).map((t) => t.name); const unusable = [...broken].filter(([name]) => !BUILTIN_NAMES.includes(name)); return Object.freeze({ list, names, get: (name) => all.find((t) => t.name === name), // Names a draft or flag may give: usable themes plus broken ones, so the error can say what is wrong with the file. choices: (scope) => [AUTO, ...names(scope), ...unusable.map(([name]) => name)], // Why a name cannot be used, or null. problem(name, scope) { if (name === AUTO) return null; if (names(scope).includes(name)) return null; const bad = unusable.find(([n]) => n === name); if (bad) return `Theme "${name}" cannot be used: ${bad[1].reason} (${bad[1].file})`; return `Theme "${name}" is not installed. Choose one of: ${names(scope).join(" | ")}`; }, // The themes a page carries: the built-in ones for its scope, plus its own theme when that is a user theme. embedFor: (name, scope) => [...themes(scope), ...user.filter((t) => t.name === name && t.scope.includes(scope))], warnings: [...broken.values()].map(({ file, reason: reason2 }) => `${file} skipped: ${reason2}`) }); } var BUILTIN = themeSet(); function loadThemes(home, { extra } = {}) { const { themes: user, broken } = home ? readUserThemes(home, BUILTIN_NAMES) : { themes: [], broken: /* @__PURE__ */ new Map() }; if (!extra) return themeSet(user, broken); const one = readThemeFile(extra, BUILTIN_NAMES); const rest = user.filter((t) => t.name !== one.name); if (one.errors.length) return themeSet(rest, new Map([...broken, [one.name, { file: extra, reason: one.errors.join("; ") }]])); return themeSet([...rest, one.theme], broken); } // src/parse.js var ParseError = class extends Error { constructor(message, line) { super(message); this.name = "ParseError"; this.line = line; } }; var CHOICES = Object.freeze({ template: ["sheet", "doc", "video"], theme: [AUTO, ...themeNames("page")], style: ["off", "80", "strict"], mode: ["auto", "light", "dark"] }); function applyOverrides(meta, overrides, choices = CHOICES) { const set = Object.entries(overrides).filter(([, v]) => v !== void 0); for (const [key, value] of set) { if (choices[key] && !choices[key].includes(String(value))) { throw new ParseError(`Invalid ${key} value "${value}". Choose one of: ${choices[key].join(" | ")}`, 0); } } return { ...meta, ...Object.fromEntries(set) }; } var VOICES = Object.freeze(["auto", "elevenlabs", "local", "system", "off"]); var DEFAULT_META = Object.freeze({ template: "sheet", theme: AUTO, style: "80", mode: "auto", cols: 3, title: "" }); var NUMERIC_KEYS = /* @__PURE__ */ new Set(["cols", "span"]); var FENCE_OPEN = /^(`{3,}|~{3,})\s*([^\s`]*)\s*(.*)$/; var PANEL_HEADING = /^##\s+(.+?)\s*$/; var ATTR_BLOCK = /\s*\{([^{}]*)\}\s*$/; var PANEL_ID = /^([A-Z][0-9]?)\s+(.+)$/; var ATTR_TOKEN = /([\w-]+)(?:=("[^"]*"|'[^']*'|\S+))?/g; function parseDoc(source, { defaults: defaults2 = {}, choices = {} } = {}) { const lines = String(source).replace(/\r\n?/g, "\n").split("\n"); const { meta, bodyStart } = parseFrontmatter(lines, { ...DEFAULT_META, ...defaults2 }, { ...CHOICES, ...choices }); const sections = splitSections(lines, bodyStart); const intro = extractTitle(sections.intro, meta); const panels = assignIds(sections.panels); return { meta, intro, panels }; } function parseFrontmatter(lines, base, allowed) { if (lines[0]?.trim() !== "---") return { meta: { ...base }, bodyStart: 0 }; const end = lines.findIndex((l3, i) => i > 0 && l3.trim() === "---"); if (end === -1) throw new ParseError("frontmatter is not closed: missing the closing --- line", 1); const entries = {}; for (let i = 1; i < end; i++) { const raw = stripLineComment(lines[i]).trim(); if (!raw || raw.startsWith("#")) continue; const m = raw.match(/^([\p{L}\p{M}\p{N}_-]+(?: [\p{L}\p{M}\p{N}_-]+)*)\s*:\s*(.*)$/u); if (!m) throw new ParseError(`Cannot parse frontmatter line "${lines[i]}"; expected key: value`, i + 1); entries[m[1]] = { value: coerce(m[1], unquote(m[2])), line: i + 1 }; } const meta = { ...base }; for (const [key, { value, line }] of Object.entries(entries)) { if (allowed[key] && !allowed[key].includes(String(value))) { throw new ParseError(`Invalid ${key} value "${value}". Choose one of: ${allowed[key].join(" | ")}`, line); } meta[key] = allowed[key] ? String(value) : value; } return { meta, bodyStart: end + 1 }; } function splitSections(lines, start) { const intro = []; const panels = []; let current = { blocks: intro }; let mdBuf = null; const flushMd = () => { if (mdBuf && mdBuf.lines.some((l3) => l3.trim())) { current.blocks.push({ type: "md", text: mdBuf.lines.join("\n"), line: mdBuf.line }); } mdBuf = null; }; for (let i = start; i < lines.length; i++) { const line = lines[i]; const fence = line.match(FENCE_OPEN); if (fence) { flushMd(); const close = findFenceClose(lines, i, fence[1]); if (close === -1) throw new ParseError(`fenced block ${fence[1]}${fence[2]} is not closed`, i + 1); const bare = fence[2].includes("="); current.blocks.push({ type: "fence", lang: bare ? "" : fence[2].toLowerCase(), args: (bare ? `${fence[2]} ${fence[3]}` : fence[3]).trim(), text: lines.slice(i + 1, close).join("\n"), line: i + 1 }); i = close; continue; } const heading = line.match(PANEL_HEADING); if (heading) { flushMd(); current = { ...parseHeading(heading[1]), line: i + 1, blocks: [] }; panels.push(current); continue; } if (!mdBuf) mdBuf = { line: i + 1, lines: [] }; mdBuf.lines.push(line); } flushMd(); return { intro, panels }; } function findFenceClose(lines, openIdx, marker) { const closeRe = new RegExp(`^${marker[0] === "`" ? "`" : "~"}{${marker.length},}\\s*$`); for (let j2 = openIdx + 1; j2 < lines.length; j2++) { if (closeRe.test(lines[j2])) return j2; } return -1; } function parseHeading(text) { let rest = text; let attrs = {}; const attrMatch = rest.match(ATTR_BLOCK); if (attrMatch) { attrs = parseAttrs(attrMatch[1]); rest = rest.slice(0, attrMatch.index); } const idMatch = rest.match(PANEL_ID); return idMatch ? { id: idMatch[1], title: idMatch[2].trim(), attrs } : { id: null, title: rest.trim(), attrs }; } function parseAttrs(text) { const attrs = {}; for (const m of text.matchAll(ATTR_TOKEN)) { attrs[m[1]] = m[2] === void 0 ? true : coerce(m[1], unquote(m[2])); } return attrs; } function extractTitle(intro, meta) { if (meta.title || intro[0]?.type !== "md") return intro; const [first, ...rest] = intro; const lines = first.text.split("\n"); const idx = lines.findIndex((l3) => l3.trim()); const m = lines[idx]?.match(/^#\s+(.+)$/); if (!m) return intro; meta.title = m[1].trim(); const remaining = lines.slice(idx + 1); if (!remaining.some((l3) => l3.trim())) return rest; return [{ ...first, text: remaining.join("\n"), line: first.line + idx + 1 }, ...rest]; } function assignIds(panels) { const used = new Set(panels.map((p) => p.id).filter(Boolean)); let code = "A".charCodeAt(0); const nextFree = () => { while (used.has(String.fromCharCode(code))) code++; const id = code <= 90 ? String.fromCharCode(code) : `P${code - 64}`; used.add(id); code++; return id; }; return panels.map((p) => p.id ? p : { ...p, id: nextFree() }); } function unquote(v) { const s = v.trim(); return /^(["']).*\1$/.test(s) ? s.slice(1, -1) : s; } function stripLineComment(line) { let quote = ""; for (let i = 0; i < line.length; i++) { const c = line[i]; if (quote) { if (c === quote) quote = ""; continue; } if (c === '"' || c === "'") { quote = c; continue; } if (c === "#" && (i === 0 || /\s/.test(line[i - 1]))) return line.slice(0, i); } return line; } function coerce(key, value) { if (NUMERIC_KEYS.has(key) && /^\d+$/.test(value)) return Number(value); return value; } // node_modules/marked/lib/marked.esm.js function I() { return { async: false, breaks: false, extensions: null, gfm: true, hooks: null, pedantic: false, renderer: null, silent: false, tokenizer: null, walkTokens: null }; } var y = I(); function W(l3) { y = l3; } var A = { exec: () => null }; function C(l3) { let e = []; return (t) => { let n = Math.max(0, Math.min(3, t - 1)), s = e[n]; return s || (s = l3(n), e[n] = s), s; }; } function h(l3, e = "") { let t = typeof l3 == "string" ? l3 : l3.source, n = { replace: (s, r) => { let o = typeof r == "string" ? r : r.source; return o = o.replace(x.caret, "$1"), t = t.replace(s, o), n; }, getRegex: () => new RegExp(t, e) }; return n; } var _e = ((l3 = "") => { try { return !!new RegExp("(?<=1)(?/, blockquoteSetextReplace: /\n {0,3}((?:=+|-+) *)(?=\n|$)/g, blockquoteSetextReplace2: /^ {0,3}>[ \t]?/gm, listReplaceNesting: /^ {1,4}(?=( {4})*[^ ])/g, listIsTask: /^\[[ xX]\] +\S/, listReplaceTask: /^\[[ xX]\] +/, listTaskCheckbox: /\[[ xX]\]/, anyLine: /\n.*\n/, hrefBrackets: /^<(.*)>$/, tableDelimiter: /[:|]/, tableAlignChars: /^\||\| *$/g, tableRowBlankLine: /\n[ \t]*$/, tableAlignRight: /^ *-+: *$/, tableAlignCenter: /^ *:-+: *$/, tableAlignLeft: /^ *:-+ *$/, startATag: /^/i, startPreScriptTag: /^<(pre|code|kbd|script)(\s|>)/i, endPreScriptTag: /^<\/(pre|code|kbd|script)(\s|>)/i, startAngleBracket: /^$/, pedanticHrefTitle: /^([^'"]*[^\s])\s+(['"])(.*)\2/, unicodeAlphaNumeric: /[\p{L}\p{N}]/u, numericCharacterReference: /&#(?:(\d{1,7})|[Xx]([A-Fa-f0-9]{1,6}));/g, escapeTest: /[&<>"']/, escapeReplace: /[&<>"']/g, escapeTestNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/, escapeReplaceNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g, caret: /(^|[^\[])\^/g, percentDecode: /%25/g, findPipe: /\|/g, splitPipe: / \|/, slashPipe: /\\\|/g, carriageReturn: /\r\n|\r/g, spaceLine: /^ +$/gm, notSpaceStart: /^\S*/, endingNewline: /\n$/, listItemRegex: (l3) => new RegExp(`^( {0,3}${l3})((?:[ ][^\\n]*)?(?:\\n|$))`), nextBulletRegex: C((l3) => new RegExp(`^ {0,${l3}}(?:[*+-]|\\d{1,9}[.)])((?:[ ][^\\n]*)?(?:\\n|$))`)), hrRegex: C((l3) => new RegExp(`^ {0,${l3}}((?:-[ ]*){3,}|(?:_[ ]*){3,}|(?:\\*[ ]*){3,})(?:\\n+|$)`)), fencesBeginRegex: C((l3) => new RegExp(`^ {0,${l3}}(?:\`\`\`|~~~)`)), headingBeginRegex: C((l3) => new RegExp(`^ {0,${l3}}#`)), htmlBeginRegex: C((l3) => new RegExp(`^ {0,${l3}}(?:)|<(?:script|pre|style|textarea|!--))`, "i")), blockquoteBeginRegex: C((l3) => new RegExp(`^ {0,${l3}}>`)) }; var $e = /^(?:[ \t]*(?:\n|$))+/; var Le = /^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/; var ze = /^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/; var G = /^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/; var Ae = /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/; var J = / {0,3}(?:[*+-]|\d{1,9}[.)])/; var ce = /^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |fences|blockquote|heading|hr|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/; var he = h(ce).replace(/bull/g, J).replace(/blockCode/g, /(?: {4}| {0,3}\t)/).replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g, / {0,3}>/).replace(/heading/g, / {0,3}#{1,6}(?:\s|$)/).replace(/hr/g, / {0,3}(?:(?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/).replace(/html/g, / {0,3}<[^\n>]+>\n/).replace(/\|table/g, "").getRegex(); var Ee = h(ce).replace(/bull/g, J).replace(/blockCode/g, /(?: {4}| {0,3}\t)/).replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g, / {0,3}>/).replace(/heading/g, / {0,3}#{1,6}(?:\s|$)/).replace(/hr/g, / {0,3}(?:(?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/).replace(/html/g, / {0,3}<[^\n>]+>\n/).replace(/table/g, / {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/).getRegex(); var V = /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table|[ \t]+\n)[^\n]+)*)/; var Me = /^[^\n]+/; var Y = /(?!\s*\])(?:\\[\s\S]|[^\[\]\\])+/; var Ie = h(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label", Y).replace("title", /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(); var Ce = h(/^(bull)([ \t][^\n]*?)?(?:\n|$)/).replace(/bull/g, J).getRegex(); var N = "address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul"; var ee = /|$))/; var Be = h("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:[^\\n]*\\n*|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>[^\\n]*\\n*|$)|[^\\n]*\\n*|$)|[^\\n]*\\n*|$)|)[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][a-z0-9-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$))", "i").replace("comment", ee).replace("tag", N).replace("attribute", / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(); var de = (l3) => h(V).replace("hr", G).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("|table", "").replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*(?:\\n|$))|~~~)[^\\n]*(?:\\n|$)").replace("list", l3).replace("html", ")|<(?:script|pre|style|textarea|!--)").replace("tag", N).getRegex(); var De = de(/ {0,3}(?:[*+-]|1[.)])[ \t]+[^ \t\n]/); var qe = de(/ {0,3}(?:[*+-]|\d{1,9}[.)])(?:[ \t]|\n|$)/); var ve = h(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph", qe).getRegex(); var te = { blockquote: ve, code: Le, def: Ie, fences: ze, heading: Ae, hr: G, html: Be, lheading: he, list: Ce, newline: $e, paragraph: De, table: A, text: Me }; var le = h("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr", G).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("blockquote", " {0,3}>").replace("code", "(?: {4}| {0,3} )[^\\n]").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*(?:\\n|$))|~~~)[^\\n]*(?:\\n|$)").replace("list", " {0,3}(?:[*+-]|1[.)])[ \\t]").replace("html", ")|<(?:script|pre|style|textarea|!--)").replace("tag", N).getRegex(); var Ze = { ...te, lheading: Ee, table: le, paragraph: h(V).replace("hr", G).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("table", le).replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*(?:\\n|$))|~~~)[^\\n]*(?:\\n|$)").replace("list", " {0,3}(?:[*+-]|1[.)])[ \\t]+[^ \\t\\n]").replace("html", ")|<(?:script|pre|style|textarea|!--)").replace("tag", N).getRegex() }; var He = { ...te, html: h(`^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))`).replace("comment", ee).replace(/tag/g, "(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(), def: /^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/, heading: /^(#{1,6})(.*)(?:\n+|$)/, fences: A, lheading: /^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/, paragraph: h(V).replace("hr", G).replace("heading", ` *#{1,6} *[^ ]`).replace("lheading", he).replace("|table", "").replace("blockquote", " {0,3}>").replace("|fences", "").replace("|list", "").replace("|html", "").replace("|tag", "").getRegex() }; var Ge = /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/; var Ne = /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/; var ke = /^( {2,}|\\)\n(?!\s*$)[ \t]*/; var Qe = /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\`+)[^`]+\k(?!`))*?\]\((?:\\[\s\S]|[^\\\(\)]|\((?:\\[\s\S]|[^\\\(\)])*\))*\)/).replace("precode-", _e ? "(?`+)[^`]+\k(?!`)/).replace("html", /<(?! )[^<>]*?>/).getRegex(); var fe = /^(?:\*+(?:((?!\*)punct)|([^\s*]))?)|^_+(?:((?!_)punct)|([^\s_]))?/; var Xe = h(fe, "u").replace(/punct/g, $).getRegex(); var Je = h(fe, "u").replace(/punct/g, ge).getRegex(); var Ve = /^(?:\*+(?:((?!\*)(?!openQuote)punct)|([^\s*]))?)|^_+(?:((?!_)(?!openQuote)punct)|([^\s_]))?/; var Ye = h(Ve, "u").replace(/openQuote/g, Fe).replace(/punct/g, $).getRegex(); var me = "^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|notPunctSpace(\\*+)(?=notPunctSpace)"; var et = h(me, "gu").replace(/notPunctSpace/g, Q).replace(/punctSpace/g, B).replace(/punct/g, $).getRegex(); var tt = h(me, "gu").replace(/notPunctSpace/g, Ke).replace(/punctSpace/g, Ue).replace(/punct/g, ge).getRegex(); var nt = "^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)[\\s](\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|(?:(?!\\*)punct|notPunctSpace)(\\*+)(?!\\*)(?=notPunctSpace)"; var rt = h(nt, "gu").replace(/notPunctSpace/g, Q).replace(/punctSpace/g, B).replace(/punct/g, $).getRegex(); var st = h("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)", "gu").replace(/notPunctSpace/g, Q).replace(/punctSpace/g, B).replace(/punct/g, $).getRegex(); var it = "^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)[\\s](_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)|(?:(?!_)punct|notPunctSpace)(_+)(?!_)(?=notPunctSpace)"; var ot = h(it, "gu").replace(/notPunctSpace/g, Q).replace(/punctSpace/g, B).replace(/punct/g, $).getRegex(); var at = h(/^~~?(?:((?!~)punct)|[^\s~])/, "u").replace(/punct/g, $).getRegex(); var lt = "^[^~]+(?=[^~])|(?!~)punct(~~?)(?=[\\s]|$)|notPunctSpace(~~?)(?!~)(?=punctSpace|$)|(?!~)punctSpace(~~?)(?=notPunctSpace)|[\\s](~~?)(?!~)(?=punct)|(?!~)punct(~~?)(?!~)(?=punct)|notPunctSpace(~~?)(?=notPunctSpace)"; var ut = h(lt, "gu").replace(/notPunctSpace/g, Q).replace(/punctSpace/g, B).replace(/punct/g, $).getRegex(); var pt = h(/\\(punct)/, "gu").replace(/punct/g, $).getRegex(); var ct = h(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme", /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email", /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(); var ht = h(ee).replace("(?:-->|$)", "-->").getRegex(); var dt = h("^comment|^|^<[a-zA-Z][a-zA-Z0-9-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^").replace("comment", ht).replace("attribute", /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(); var xe = /\[(?:\\[\s\S]|[^\[\]\\])*\]/; var U = h(/(?:\[(?:brackets|\\[\s\S]|[^\[\]\\])*\]|\\[\s\S]|`+(?!`)[^`]*?`+(?!`)|``+(?=\])|[^\[\]\\`])*?/).replace("brackets", xe).getRegex(); var kt = h(/^!?\[(label)\]\(\s*(href)(?:(?:[ \t]+(?:\n[ \t]*)?|\n[ \t]*)(title))?\s*\)/).replace("label", U).replace("href", /<(?:\\.|[^\n<>\\])+>|[^ \t\n\x00-\x1f]+|(?=\))/).replace("title", /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(); var gt = h(/^!?\[(label)\]\[(ref)\]/).replace("label", U).replace("ref", Y).getRegex(); var ft = h(/^!?\[(ref)\](?:\[\])?/).replace("ref", Y).getRegex(); var ue = /(?!\s*\])(?:\\[\s\S]|[^\[\]\\]){1,999}/; var mt = h(/(?:[^\[\]\\`]*(?:\[(?:brackets|\\[\s\S]|[^\[\]\\])*\]|\\[\s\S]|`+(?!`)[^`]*?`+(?!`)|``+(?=\]))){0,999}?[^\[\]\\`]*?/).replace("brackets", xe).getRegex(); var xt = h("reflink|nolink(?!\\()", "g").replace("reflink", h(/^!?\[(label)\]\[(ref)\]/).replace("label", mt).replace("ref", ue).getRegex()).replace("nolink", h(/^!?\[(ref)\](?:\[\])?/).replace("ref", ue).getRegex()).getRegex(); var pe = /[hH][tT][tT][pP][sS]?|[fF][tT][pP]/; var bt = /[A-Za-z0-9._+-]+@[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![\w-])/; var Rt = h(/(?:mailto:email|xmpp:email(?:\/[A-Za-z0-9@.]+)?)/).replace(/email/g, bt).getRegex(); var ne = { _backpedal: A, anyPunctuation: pt, autolink: ct, blockSkip: We, br: ke, code: Ne, del: A, delLDelim: A, delRDelim: A, emStrongLDelim: Xe, emStrongRDelimAst: et, emStrongRDelimUnd: st, escape: Ge, link: kt, nolink: ft, punctuation: je, reflink: gt, reflinkSearch: xt, tag: dt, text: Qe, url: A }; var Tt = { ...ne, emStrongLDelim: Ye, emStrongRDelimAst: rt, emStrongRDelimUnd: ot, link: h(/^!?\[(label)\]\((.*?)\)/).replace("label", U).getRegex(), reflink: h(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label", U).getRegex() }; var X = { ...ne, emStrongRDelimAst: tt, emStrongLDelim: Je, delLDelim: at, delRDelim: ut, url: h(/^emailProtocol|^((?:protocol):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/).replace("emailProtocol", Rt).replace("protocol", pe).replace("email", /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![\w-])/).getRegex(), _backpedal: /(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/, del: /^(~~?)(?=[^\s~])((?:\\[\s\S]|[^\\])*?(?:\\[\s\S]|[^\s~\\]))\1(?=[^~]|$)/, text: h(/^(?:[^a-zA-Z0-9](?=emailProtocol)|(`+|~+|[^`~])(?:(?=[`~])|(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\": ">", '"': """, "'": "'" }; var be = (l3) => wt[l3]; function O(l3, e) { if (e) { if (x.escapeTest.test(l3)) return l3.replace(x.escapeReplace, be); } else if (x.escapeTestNoEncode.test(l3)) return l3.replace(x.escapeReplaceNoEncode, be); return l3; } function Re(l3) { return l3.replace(x.numericCharacterReference, (e, t, n) => { let s = t === void 0 ? Number.parseInt(n, 16) : Number.parseInt(t, 10); return s === 0 || s > 1114111 || s >= 55296 && s <= 57343 ? "\uFFFD" : String.fromCodePoint(s); }); } function re(l3) { try { l3 = encodeURI(l3).replace(x.percentDecode, "%"); } catch { return null; } return l3; } function se(l3, e) { let t = l3.replace(x.findPipe, (r, o, i) => { let u = false, a = o; for (; --a >= 0 && i[a] === "\\"; ) u = !u; return u ? "|" : " |"; }), n = t.split(x.splitPipe), s = 0; if (n[0].trim() || n.shift(), n.length > 0 && !n.at(-1)?.trim() && n.pop(), e) if (n.length > e) n.splice(e); else for (; n.length < e; ) n.push(""); for (; s < n.length; s++) n[s] = n[s].trim().replace(x.slashPipe, "|"); return n; } function L(l3, e, t) { let n = l3.length; if (n === 0) return ""; let s = 0; for (; s < n; ) { let r = l3.charAt(n - s - 1); if (r === e && !t) s++; else if (r !== e && t) s++; else break; } return l3.slice(0, n - s); } function ie(l3) { let e = l3.split(` `), t = e.length - 1; for (; t >= 0 && x.blankLine.test(e[t]); ) t--; return e.length - t <= 2 ? l3 : e.slice(0, t + 1).join(` `); } function q(l3) { return l3.trim().toLowerCase().toUpperCase().toLowerCase(); } function Te(l3, e) { if (l3.indexOf(e[1]) === -1) return -1; let t = 0; for (let n = 0; n < l3.length; n++) if (l3[n] === "\\") n++; else if (l3[n] === e[0]) t++; else if (l3[n] === e[1] && (t--, t < 0)) return n; return t > 0 ? -2 : -1; } function oe(l3, e = 0) { let t = e, n = ""; for (let s of l3) if (s === " ") { let r = 4 - t % 4; n += " ".repeat(r), t += r; } else n += s, t++; return n; } function Oe(l3, e, t, n, s) { let r = e.href, o = e.title || null, i = l3[1].replace(s.other.outputLinkReplace, "$1"), u = l3[0].charAt(0) === "!"; n.state.inLink = true; let a = n.state.linkEmitted, p = n.state.inRawBlock; n.state.linkEmitted = false; let c = n.inlineTokens(i), d = n.state.linkEmitted; if (n.state.linkEmitted = a, n.state.inLink = false, !u) { if (d) { n.state.inRawBlock = p; return; } n.state.linkEmitted = true; } return { type: u ? "image" : "link", raw: t, href: r, title: o, text: i, tokens: c }; } function yt(l3, e, t) { let n = l3.match(t.other.indentCodeCompensation); if (n === null) return e; let s = n[1]; return e.split(` `).map((r) => { let o = r.match(t.other.beginningSpace); if (o === null) return r; let [i] = o; return r.slice(Math.min(i.length, s.length)); }).join(` `); } function we(l3, e, t, n) { if (!e.includes("<")) return false; for (let s = 0; s < e.length; s++) { if (e[s] === "\\") { s++; continue; } if (e[s] === "`") { let i = n.inline.code.exec(e.slice(s)); if (i) { s += i[0].length - 1; continue; } } if (e[s] !== "<") continue; let r = l3.slice(t + s), o = n.inline.tag.exec(r) || n.inline.autolink.exec(r); if (o) { if (o[0].length > e.length - s) return true; s += o[0].length - 1; } } return false; } var P = class { options; rules; lexer; constructor(e) { this.options = e || y; } space(e) { let t = this.rules.block.newline.exec(e); if (t && t[0].length > 0) return { type: "space", raw: t[0] }; } code(e) { let t = this.rules.block.code.exec(e); if (t) { let n = this.options.pedantic ? t[0] : ie(t[0]), s = n.replace(this.rules.other.codeRemoveIndent, ""); return { type: "code", raw: n, codeBlockStyle: "indented", text: s }; } } fences(e) { let t = this.rules.block.fences.exec(e); if (t) { let n = t[0], s = yt(n, t[3] || "", this.rules); return { type: "code", raw: n, lang: t[2] ? t[2].trim().replace(this.rules.inline.anyPunctuation, "$1") : t[2], text: s }; } } heading(e) { let t = this.rules.block.heading.exec(e); if (t) { let n = t[2].trim(); if (this.rules.other.endingHash.test(n)) { let s = L(n, "#"); (this.options.pedantic || !s || this.rules.other.endingSpaceTabChar.test(s)) && (n = s.trim()); } return { type: "heading", raw: L(t[0], ` `), depth: t[1].length, text: n, tokens: this.lexer.inline(n) }; } } hr(e) { let t = this.rules.block.hr.exec(e); if (t) return { type: "hr", raw: L(t[0], ` `) }; } blockquote(e) { let t = this.rules.block.blockquote.exec(e); if (t) { let n = L(t[0], ` `).split(` `), s = "", r = "", o = []; for (; n.length > 0; ) { let i = false, u = [], a; for (a = 0; a < n.length; a++) if (this.rules.other.blockquoteStart.test(n[a])) u.push(n[a]), i = true; else if (!i) u.push(n[a]); else break; n = n.slice(a); let p = u.join(` `), c = p.replace(this.rules.other.blockquoteSetextReplace, ` $1`).replace(this.rules.other.blockquoteSetextReplace2, ""); s = s ? `${s} ${p}` : p, r = r ? `${r} ${c}` : c; let d = this.lexer.state.top; if (this.lexer.state.top = true, this.lexer.blockTokens(c, o, true), this.lexer.state.top = d, n.length === 0) break; let m = o.at(-1); if (m?.type === "code") break; if (m?.type === "blockquote") { let b = m, g = n.join(` `), w = b.raw + ` ` + g.replace(this.rules.other.blockquoteSetextReplace2, ""), f2 = this.blockquote(w); o[o.length - 1] = f2; let M2 = w.substring(f2.raw.length).replace(/^\n/, ""), v = M2 ? M2.split(` `).length : 0, Z2 = v ? n.slice(0, -v) : n; Z2.length > 0 && (s = `${s} ${Z2.join(` `)}`), r = r.substring(0, r.length - b.text.length) + f2.text; break; } else if (m?.type === "list") { let b = m, g = b.raw + ` ` + n.join(` `), w = this.list(g); o[o.length - 1] = w, s = s.substring(0, s.length - m.raw.length) + w.raw, r = r.substring(0, r.length - b.raw.length) + w.raw, n = g.substring(o.at(-1).raw.length).split(` `); continue; } } return { type: "blockquote", raw: s, tokens: o, text: r }; } } list(e) { let t = this.rules.block.list.exec(e); if (t) { let n = t[1].trim(), s = n.length > 1, r = { type: "list", raw: "", ordered: s, start: s ? +n.slice(0, -1) : "", loose: false, items: [] }; n = s ? `\\d{1,9}\\${n.slice(-1)}` : `\\${n}`, this.options.pedantic && (n = s ? n : "[*+-]"); let o = this.rules.other.listItemRegex(n), i = false; for (; e; ) { let a = false, p = "", c = ""; if (!(t = o.exec(e)) || this.rules.block.hr.test(e)) break; p = t[0], e = e.substring(p.length); let d = t[2].split(` `, 1)[0], m = t[1].length, b = this.options.pedantic ? oe(d, m) : d.replace(this.rules.other.leadingSpaceTab, (M2) => oe(M2, m)), g = e.split(` `, 1)[0], w = !b.trim(), f2 = 0; if (this.options.pedantic ? (f2 = 2, c = b.trimStart()) : w ? f2 = m + 1 : (f2 = b.search(this.rules.other.nonSpaceChar), f2 = f2 > 4 ? 1 : f2, c = b.slice(f2), f2 += m), w && this.rules.other.blankLine.test(g) && (p += g + ` `, e = e.substring(g.length + 1), a = true), !a) { let M2 = this.rules.other.nextBulletRegex(f2), v = this.rules.other.hrRegex(f2), Z2 = this.rules.other.fencesBeginRegex(f2), ae2 = this.rules.other.headingBeginRegex(f2), ye2 = this.rules.other.htmlBeginRegex(f2), Pe2 = this.rules.other.blockquoteBeginRegex(f2); for (; e; ) { let K2 = e.split(` `, 1)[0], H2; if (g = K2, this.options.pedantic ? (g = g.replace(this.rules.other.listReplaceNesting, " "), H2 = g) : H2 = g.replace(this.rules.other.leadingSpaceTab, (Se2) => Se2.replace(this.rules.other.tabCharGlobal, " ")), Z2.test(g) || ae2.test(g) || ye2.test(g) || Pe2.test(g) || M2.test(g) || v.test(g)) break; if (H2.search(this.rules.other.nonSpaceChar) >= f2 || !g.trim()) c += ` ` + H2.slice(f2); else { if (w || b.replace(this.rules.other.tabCharGlobal, " ").search(this.rules.other.nonSpaceChar) >= 4 || Z2.test(b) || ae2.test(b) || v.test(b)) break; c += ` ` + g; } w = !g.trim(), p += K2 + ` `, e = e.substring(K2.length + 1), b = H2.slice(f2); } } r.loose || (i ? r.loose = true : this.rules.other.doubleBlankLine.test(p) && (i = true)), r.items.push({ type: "list_item", raw: p, task: !!this.options.gfm && this.rules.other.listIsTask.test(c), loose: false, text: c, tokens: [] }), r.raw += p; } let u = r.items.at(-1); if (u) u.raw = u.raw.trimEnd(), u.text = u.text.trimEnd(); else return; r.raw = r.raw.trimEnd(); for (let a of r.items) if (this.lexer.state.top = false, a.tokens = this.lexer.blockTokens(a.text, []), !r.loose) { let p = a.tokens.filter((d) => d.type === "space"), c = p.length > 0 && p.some((d) => this.rules.other.anyLine.test(d.raw)); r.loose = c; } for (let a of r.items) { let p = a.tokens[0]; if (a.task && (p?.type === "text" || p?.type === "paragraph")) { a.text = a.text.replace(this.rules.other.listReplaceTask, ""), p.raw = p.raw.replace(this.rules.other.listReplaceTask, ""), p.text = p.text.replace(this.rules.other.listReplaceTask, ""); for (let d = this.lexer.inlineQueue.length - 1; d >= 0; d--) if (this.rules.other.listIsTask.test(this.lexer.inlineQueue[d].src)) { this.lexer.inlineQueue[d].src = this.lexer.inlineQueue[d].src.replace(this.rules.other.listReplaceTask, ""); break; } let c = this.rules.other.listTaskCheckbox.exec(a.raw); if (c) { let d = { type: "checkbox", raw: c[0] + " ", checked: c[0] !== "[ ]" }; a.checked = d.checked, r.loose ? a.tokens[0] && ["paragraph", "text"].includes(a.tokens[0].type) && "tokens" in a.tokens[0] && a.tokens[0].tokens ? (a.tokens[0].raw = d.raw + a.tokens[0].raw, a.tokens[0].text = d.raw + a.tokens[0].text, a.tokens[0].tokens.unshift(d)) : a.tokens.unshift({ type: "paragraph", raw: d.raw, text: d.raw, tokens: [d] }) : a.tokens.unshift(d); } } else a.task && (a.task = false); } if (r.loose) for (let a of r.items) { a.loose = true; for (let p of a.tokens) p.type === "text" && (p.type = "paragraph"); } return r; } } html(e) { let t = this.rules.block.html.exec(e); if (t) { let n = ie(t[0]); return { type: "html", block: true, raw: n, pre: t[1] === "pre" || t[1] === "script" || t[1] === "style", text: n }; } } def(e) { let t = this.rules.block.def.exec(e); if (t) { let n = q(t[1]).replace(this.rules.other.multipleSpaceGlobal, " "), s = t[2] ? t[2].replace(this.rules.other.hrefBrackets, "$1").replace(this.rules.inline.anyPunctuation, "$1") : "", r = t[3] ? t[3].substring(1, t[3].length - 1).replace(this.rules.inline.anyPunctuation, "$1") : t[3]; return { type: "def", tag: n, raw: L(t[0], ` `), href: s, title: r }; } } table(e) { let t = this.rules.block.table.exec(e); if (!t || !this.rules.other.tableDelimiter.test(t[2])) return; let n = se(t[1]), s = t[2].replace(this.rules.other.tableAlignChars, "").split("|"), r = t[3]?.trim() ? t[3].replace(this.rules.other.tableRowBlankLine, "").split(` `) : [], o = { type: "table", raw: L(t[0], ` `), header: [], align: [], rows: [] }; if (n.length === s.length) { for (let i of s) this.rules.other.tableAlignRight.test(i) ? o.align.push("right") : this.rules.other.tableAlignCenter.test(i) ? o.align.push("center") : this.rules.other.tableAlignLeft.test(i) ? o.align.push("left") : o.align.push(null); for (let i = 0; i < n.length; i++) o.header.push({ text: n[i], tokens: this.lexer.inline(n[i]), header: true, align: o.align[i] }); for (let i of r) o.rows.push(se(i, o.header.length).map((u, a) => ({ text: u, tokens: this.lexer.inline(u), header: false, align: o.align[a] }))); return o; } } lheading(e) { let t = this.rules.block.lheading.exec(e); if (t) { let n = t[1].trim(); return { type: "heading", raw: L(t[0], ` `), depth: t[2].charAt(0) === "=" ? 1 : 2, text: n, tokens: this.lexer.inline(n) }; } } paragraph(e) { let t = this.rules.block.paragraph.exec(e); if (t) { let n = t[1].charAt(t[1].length - 1) === ` ` ? t[1].slice(0, -1) : t[1]; return { type: "paragraph", raw: t[0], text: n, tokens: this.lexer.inline(n) }; } } text(e) { let t = this.rules.block.text.exec(e); if (t) return { type: "text", raw: t[0], text: t[0], tokens: this.lexer.inline(t[0]) }; } escape(e) { let t = this.rules.inline.escape.exec(e); if (t) return { type: "escape", raw: t[0], text: t[1] }; } tag(e) { let t = this.rules.inline.tag.exec(e); if (t) return !this.lexer.state.inLink && this.rules.other.startATag.test(t[0]) ? this.lexer.state.inLink = true : this.lexer.state.inLink && this.rules.other.endATag.test(t[0]) && (this.lexer.state.inLink = false), !this.lexer.state.inRawBlock && this.rules.other.startPreScriptTag.test(t[0]) ? this.lexer.state.inRawBlock = true : this.lexer.state.inRawBlock && this.rules.other.endPreScriptTag.test(t[0]) && (this.lexer.state.inRawBlock = false), { type: "html", raw: t[0], inLink: this.lexer.state.inLink, inRawBlock: this.lexer.state.inRawBlock, block: false, text: t[0] }; } link(e) { let t = this.rules.inline.link.exec(e); if (t) { let n = t[0].charAt(0) === "!" ? 2 : 1; if (!this.options.pedantic && we(e, t[1], n, this.rules)) return; let s = t[2].trim(); if (!this.options.pedantic && this.rules.other.startAngleBracket.test(s)) { if (!this.rules.other.endAngleBracket.test(s)) return; let i = L(s.slice(0, -1), "\\"); if ((s.length - i.length) % 2 === 0) return; } else { let i = Te(t[2], "()"); if (i === -2) return; if (i > -1) { let a = (t[0].indexOf("!") === 0 ? 5 : 4) + t[1].length + i; t[2] = t[2].substring(0, i), t[0] = t[0].substring(0, a).trim(), t[3] = ""; } } let r = t[2], o = ""; if (this.options.pedantic) { let i = this.rules.other.pedanticHrefTitle.exec(r); i && (r = i[1], o = i[3]); } else o = t[3] ? t[3].slice(1, -1) : ""; return r = r.trim(), this.rules.other.startAngleBracket.test(r) && (this.options.pedantic && !this.rules.other.endAngleBracket.test(s) ? r = r.slice(1) : r = r.slice(1, -1)), Oe(t, { href: r && r.replace(this.rules.inline.anyPunctuation, "$1"), title: o && o.replace(this.rules.inline.anyPunctuation, "$1") }, t[0], this.lexer, this.rules); } } reflink(e, t) { let n; if ((n = this.rules.inline.reflink.exec(e)) || (n = this.rules.inline.nolink.exec(e))) { let s = n[0].charAt(0) === "!" ? 2 : 1; if (!this.options.pedantic && we(e, n[1], s, this.rules)) return; let r = (n[2] || n[1]).replace(this.rules.other.multipleSpaceGlobal, " "), o = t[q(r)]; if (!o) { let i = n[0].charAt(0); return { type: "text", raw: i, text: i }; } return Oe(n, o, n[0], this.lexer, this.rules); } } emStrong(e, t, n = "") { let s = this.rules.inline.emStrongLDelim.exec(e); if (!s || !s[1] && !s[2] && !s[3] && !s[4] || s[4] && n.match(this.rules.other.unicodeAlphaNumeric)) return; if (!(s[1] || s[3] || "") || !n || this.rules.inline.punctuation.exec(n)) { let o = [...s[0]].length - 1, i, u, a = o, p = 0, c = s[0][0], d = n === c, m = c === "*" ? this.rules.inline.emStrongRDelimAst : this.rules.inline.emStrongRDelimUnd; for (m.lastIndex = 0, t = t.slice(-1 * e.length + o); (s = m.exec(t)) !== null; ) { if (i = s[1] || s[2] || s[3] || s[4] || s[5] || s[6], !i) continue; if (u = [...i].length, s[3] || s[4]) { a += u; continue; } else if (s[5] || s[6]) { if (o % 3 && !((o + u) % 3)) { p += u; continue; } if (d) break; } if (a -= u, a > 0) continue; u = Math.min(u, u + a + p); let b = [...s[0]][0].length, g = e.slice(0, o + s.index + b + u); if (Math.min(o, u) % 2) { let f2 = g.slice(1, -1); return { type: "em", raw: g, text: f2, tokens: this.lexer.inlineTokens(f2) }; } let w = g.slice(2, -2); return { type: "strong", raw: g, text: w, tokens: this.lexer.inlineTokens(w) }; } } } codespan(e) { let t = this.rules.inline.code.exec(e); if (t) { let n = t[2].replace(this.rules.other.newLineCharGlobal, " "), s = this.rules.other.nonSpaceChar.test(n), r = this.rules.other.startingSpaceChar.test(n) && this.rules.other.endingSpaceChar.test(n); return s && r && (n = n.substring(1, n.length - 1)), { type: "codespan", raw: t[0], text: n }; } } br(e) { let t = this.rules.inline.br.exec(e); if (t) return { type: "br", raw: t[0] }; } del(e, t, n = "") { let s = this.rules.inline.delLDelim.exec(e); if (!s) return; if (!(s[1] || "") || !n || this.rules.inline.punctuation.exec(n)) { let o = [...s[0]].length - 1, i, u, a = o, p = this.rules.inline.delRDelim; for (p.lastIndex = 0, t = t.slice(-1 * e.length + o); (s = p.exec(t)) !== null; ) { if (i = s[1] || s[2] || s[3] || s[4] || s[5] || s[6], !i || (u = [...i].length, u !== o)) continue; if (s[3] || s[4]) { a += u; continue; } if (a -= u, a > 0) continue; u = Math.min(u, u + a); let c = [...s[0]][0].length, d = e.slice(0, o + s.index + c + u), m = d.slice(o, -o); return { type: "del", raw: d, text: m, tokens: this.lexer.inlineTokens(m) }; } } } autolink(e) { let t = this.rules.inline.autolink.exec(e); if (t) { let n, s; return t[2] === "@" ? (n = t[1], s = "mailto:" + n) : (n = t[1], s = n), { type: "link", raw: t[0], text: n, href: s, autolink: true, tokens: [{ type: "text", raw: n, text: n }] }; } } url(e) { let t; if (t = this.rules.inline.url.exec(e)) { let n, s; if (t[2] === "@") n = t[0], s = "mailto:" + n; else { let r; do r = t[0], t[0] = this.rules.inline._backpedal.exec(t[0])?.[0] ?? ""; while (r !== t[0]); n = t[0], t[1] === "www." ? s = "http://" + t[0] : s = t[0]; } return { type: "link", raw: t[0], text: n, href: s, autolink: true, tokens: [{ type: "text", raw: n, text: n }] }; } } inlineText(e) { let t = this.rules.inline.text.exec(e); if (t) { let n = this.lexer.state.inRawBlock; return { type: "text", raw: t[0], text: n ? t[0] : Re(t[0]), escaped: n }; } } }; var R = class l { tokens; options; state; inlineQueue; tokenizer; constructor(e) { this.tokens = [], this.tokens.links = /* @__PURE__ */ Object.create(null), this.options = e || y, this.options.tokenizer = this.options.tokenizer || new P(), this.tokenizer = this.options.tokenizer, this.tokenizer.options = this.options, this.tokenizer.lexer = this, this.inlineQueue = [], this.state = { inLink: false, inRawBlock: false, linkEmitted: false, top: true }; let t = { other: x, block: j.normal, inline: D.normal }; this.options.pedantic ? (t.block = j.pedantic, t.inline = D.pedantic) : this.options.gfm && (t.block = j.gfm, this.options.breaks ? t.inline = D.breaks : t.inline = D.gfm), this.tokenizer.rules = t; } static get rules() { return { block: j, inline: D }; } static lex(e, t) { return new l(t).lex(e); } static lexInline(e, t) { return new l(t).inlineTokens(e); } lex(e) { e = e.replace(x.carriageReturn, ` `), this.blockTokens(e, this.tokens); for (let t = 0; t < this.inlineQueue.length; t++) { let n = this.inlineQueue[t]; this.inlineTokens(n.src, n.tokens); } return this.inlineQueue = [], this.tokens; } blockTokens(e, t = [], n = false) { this.tokenizer.lexer = this, this.options.pedantic && (e = e.replace(x.tabCharGlobal, " ").replace(x.spaceLine, "")); let s = 1 / 0; for (; e; ) { if (e.length < s) s = e.length; else { this.infiniteLoopError(e.charCodeAt(0)); break; } let r; if (this.options.extensions?.block?.some((i) => (r = i.call({ lexer: this }, e, t)) ? (e = e.substring(r.raw.length), t.push(r), true) : false)) continue; if (r = this.tokenizer.space(e)) { e = e.substring(r.raw.length); let i = t.at(-1); r.raw.length === 1 && i !== void 0 ? i.raw += ` ` : t.push(r); continue; } if (r = this.tokenizer.code(e)) { e = e.substring(r.raw.length); let i = t.at(-1); i?.type === "paragraph" || i?.type === "text" ? (i.raw += (i.raw.endsWith(` `) ? "" : ` `) + r.raw, i.text += ` ` + r.text, this.inlineQueue.at(-1).src = i.text) : t.push(r); continue; } if (r = this.tokenizer.fences(e)) { e = e.substring(r.raw.length), t.push(r); continue; } if (r = this.tokenizer.heading(e)) { e = e.substring(r.raw.length), t.push(r); continue; } if (r = this.tokenizer.hr(e)) { e = e.substring(r.raw.length), t.push(r); continue; } if (r = this.tokenizer.blockquote(e)) { e = e.substring(r.raw.length), t.push(r); continue; } if (r = this.tokenizer.list(e)) { e = e.substring(r.raw.length), t.push(r); continue; } if (r = this.tokenizer.html(e)) { e = e.substring(r.raw.length), t.push(r); continue; } if (r = this.tokenizer.def(e)) { e = e.substring(r.raw.length); let i = t.at(-1); i?.type === "paragraph" || i?.type === "text" ? (i.raw += (i.raw.endsWith(` `) ? "" : ` `) + r.raw, i.text += ` ` + r.raw, this.inlineQueue.at(-1).src = i.text) : this.tokens.links[r.tag] || (this.tokens.links[r.tag] = { href: r.href, title: r.title }, t.push(r)); continue; } if (r = this.tokenizer.table(e)) { e = e.substring(r.raw.length), t.push(r); continue; } if (r = this.tokenizer.lheading(e)) { e = e.substring(r.raw.length), t.push(r); continue; } let o = e; if (this.options.extensions?.startBlock) { let i = 1 / 0, u = e.slice(1), a; this.options.extensions.startBlock.forEach((p) => { a = p.call({ lexer: this }, u), typeof a == "number" && a >= 0 && (i = Math.min(i, a)); }), i < 1 / 0 && i >= 0 && (o = e.substring(0, i + 1)); } if (this.state.top && (r = this.tokenizer.paragraph(o))) { let i = t.at(-1); n && i?.type === "paragraph" ? (i.raw += (i.raw.endsWith(` `) ? "" : ` `) + r.raw, i.text += ` ` + r.text, this.inlineQueue.pop(), this.inlineQueue.at(-1).src = i.text) : t.push(r), n = o.length !== e.length, e = e.substring(r.raw.length); continue; } if (r = this.tokenizer.text(e)) { e = e.substring(r.raw.length); let i = t.at(-1); i?.type === "text" ? (i.raw += (i.raw.endsWith(` `) ? "" : ` `) + r.raw, i.text += ` ` + r.text, this.inlineQueue.pop(), this.inlineQueue.at(-1).src = i.text) : t.push(r); continue; } if (e) { this.infiniteLoopError(e.charCodeAt(0)); break; } } return this.state.top = true, t; } inline(e, t = []) { return this.inlineQueue.push({ src: e, tokens: t }), t; } linkInText(e) { if (!e.includes("[")) return false; let t = this.tokenizer.rules.inline.link; for (let n of e.matchAll(this.tokenizer.rules.inline.blockSkip)) if (t.test(n[0]) && e.charAt(n.index - 1) !== "!") return true; for (let n of e.matchAll(this.tokenizer.rules.inline.reflinkSearch)) { let s = n[0], r = s.lastIndexOf("["); if (!(s.charAt(0) === "!" || !Object.hasOwn(this.tokens.links, q(s.slice(r + 1, -1)))) && !(r > 1 && this.linkInText(s.slice(1, r - 1)))) return true; } return false; } inlineTokens(e, t = []) { this.tokenizer.lexer = this; let n = e; if (this.tokens.links && e.includes("[")) { let i = this.tokenizer.rules.inline.reflinkSearch, u = (a) => { let p = a.lastIndexOf("["); if (!Object.hasOwn(this.tokens.links, q(a.slice(p + 1, -1)))) return a; if (p > 1 && a.charAt(0) !== "!") { let c = a.slice(1, p - 1); if (this.linkInText(c)) return "[" + c.replace(i, u) + "][" + "a".repeat(a.length - p - 2) + "]"; } return "[" + "a".repeat(a.length - 2) + "]"; }; n = n.replace(i, u); } n = n.replace(this.tokenizer.rules.inline.anyPunctuation, (i) => "+".repeat(i.length)), n = n.replace(this.tokenizer.rules.inline.blockSkip, (i, u, a) => { let p = a ? a.length : 0; return i.slice(0, p) + "[" + "a".repeat(i.length - p - 2) + "]"; }), n = this.options.hooks?.emStrongMask?.call({ lexer: this }, n) ?? n; let s = false, r = "", o = 1 / 0; for (; e; ) { if (e.length < o) o = e.length; else { this.infiniteLoopError(e.charCodeAt(0)); break; } s || (r = ""), s = false; let i; if (this.options.extensions?.inline?.some((a) => (i = a.call({ lexer: this }, e, t)) ? (e = e.substring(i.raw.length), t.push(i), true) : false)) continue; if (i = this.tokenizer.escape(e)) { e = e.substring(i.raw.length), t.push(i); continue; } if (i = this.tokenizer.tag(e)) { e = e.substring(i.raw.length), t.push(i); continue; } if (i = this.tokenizer.link(e)) { e = e.substring(i.raw.length), t.push(i); continue; } if (i = this.tokenizer.reflink(e, this.tokens.links)) { e = e.substring(i.raw.length); let a = t.at(-1); i.type === "text" && a?.type === "text" ? (a.raw += i.raw, a.text += i.text) : t.push(i); continue; } if (i = this.tokenizer.emStrong(e, n, r)) { e = e.substring(i.raw.length), t.push(i); continue; } if (i = this.tokenizer.codespan(e)) { e = e.substring(i.raw.length), t.push(i); continue; } if (i = this.tokenizer.br(e)) { e = e.substring(i.raw.length), t.push(i); continue; } if (i = this.tokenizer.del(e, n, r)) { e = e.substring(i.raw.length), t.push(i); continue; } if (i = this.tokenizer.autolink(e)) { e = e.substring(i.raw.length), t.push(i); continue; } if (!this.state.inLink && (i = this.tokenizer.url(e))) { e = e.substring(i.raw.length), t.push(i); continue; } let u = e; if (this.options.extensions?.startInline) { let a = 1 / 0, p = e.slice(1), c; this.options.extensions.startInline.forEach((d) => { c = d.call({ lexer: this }, p), typeof c == "number" && c >= 0 && (a = Math.min(a, c)); }), a < 1 / 0 && a >= 0 && (u = e.substring(0, a + 1)); } if (i = this.tokenizer.inlineText(u)) { e = e.substring(i.raw.length), i.raw.slice(-1) !== "_" && (r = i.raw.slice(-1)), s = true; let a = t.at(-1); a?.type === "text" ? (a.raw += i.raw, a.text += i.text) : t.push(i); continue; } if (e) { this.infiniteLoopError(e.charCodeAt(0)); break; } } return t; } infiniteLoopError(e) { let t = "Infinite loop on byte: " + e; if (this.options.silent) console.error(t); else throw new Error(t); } }; var S = class { options; parser; constructor(e) { this.options = e || y; } space(e) { return ""; } code({ text: e, lang: t, escaped: n }) { let s = (t || "").match(x.notSpaceStart)?.[0], r = e ? e.replace(x.endingNewline, "") + ` ` : ""; return s ? '
' + (n ? r : O(r, true)) + `
` : "
" + (n ? r : O(r, true)) + `
`; } blockquote({ tokens: e }) { return `
${this.parser.parse(e)}
`; } html({ text: e }) { return e; } def(e) { return ""; } heading({ tokens: e, depth: t }) { return `${this.parser.parseInline(e)} `; } hr(e) { return `
`; } list(e) { let t = e.ordered, n = e.start, s = ""; for (let i = 0; i < e.items.length; i++) { let u = e.items[i]; s += this.listitem(u); } let r = t ? "ol" : "ul", o = t && n !== 1 ? ' start="' + n + '"' : ""; return "<" + r + o + `> ` + s + " `; } listitem(e) { return `
  • ${this.parser.parse(e.tokens)}
  • `; } checkbox({ checked: e }) { return " '; } paragraph({ tokens: e }) { return `

    ${this.parser.parseInline(e)}

    `; } table(e) { let t = "", n = ""; for (let r = 0; r < e.header.length; r++) n += this.tablecell(e.header[r]); t += this.tablerow({ text: n }); let s = ""; for (let r = 0; r < e.rows.length; r++) { let o = e.rows[r]; n = ""; for (let i = 0; i < o.length; i++) n += this.tablecell(o[i]); s += this.tablerow({ text: n }); } return s && (s = `${s}`), ` ` + t + ` ` + s + `
    `; } tablerow({ text: e }) { return ` ${e} `; } tablecell(e) { let t = this.parser.parseInline(e.tokens), n = e.header ? "th" : "td"; return (e.align ? `<${n} align="${e.align}">` : `<${n}>`) + t + ` `; } strong({ tokens: e }) { return `${this.parser.parseInline(e)}`; } em({ tokens: e }) { return `${this.parser.parseInline(e)}`; } codespan({ text: e }) { return `${O(e, true)}`; } br(e) { return "
    "; } del({ tokens: e }) { return `${this.parser.parseInline(e)}`; } link({ href: e, title: t, text: n, tokens: s, autolink: r }) { let o = r ? O(n, true) : this.parser.parseInline(s), i = re(e); if (i === null) return o; e = O(i, r); let u = '
    ", u; } image({ href: e, title: t, text: n, tokens: s }) { s && (n = this.parser.parseInline(s, this.parser.textRenderer)); let r = re(e); if (r === null) return O(n); e = r; let o = `${O(n)} { let i = r[o].flat(1 / 0); n = n.concat(this.walkTokens(i, t)); }) : r.tokens && (n = n.concat(this.walkTokens(r.tokens, t))); } } return n; } use(...e) { let t = this.defaults.extensions || { renderers: {}, childTokens: {} }; return e.forEach((n) => { let s = { ...n }; if (s.async = this.defaults.async || s.async || false, n.extensions && (n.extensions.forEach((r) => { if (!r.name) throw new Error("extension name required"); if ("renderer" in r) { let o = t.renderers[r.name]; o ? t.renderers[r.name] = function(...i) { let u = r.renderer.apply(this, i); return u === false && (u = o.apply(this, i)), u; } : t.renderers[r.name] = r.renderer; } if ("tokenizer" in r) { if (!r.level || r.level !== "block" && r.level !== "inline") throw new Error("extension level must be 'block' or 'inline'"); let o = t[r.level]; o ? o.unshift(r.tokenizer) : t[r.level] = [r.tokenizer], r.start && (r.level === "block" ? t.startBlock ? t.startBlock.push(r.start) : t.startBlock = [r.start] : r.level === "inline" && (t.startInline ? t.startInline.push(r.start) : t.startInline = [r.start])); } "childTokens" in r && r.childTokens && (t.childTokens[r.name] = r.childTokens); }), s.extensions = t), n.renderer) { let r = this.defaults.renderer || new S(this.defaults); for (let o in n.renderer) { if (!(o in r)) throw new Error(`renderer '${o}' does not exist`); if (["options", "parser"].includes(o)) continue; let i = o, u = n.renderer[i], a = r[i]; r[i] = (...p) => { let c = u.apply(r, p); return c === false && (c = a.apply(r, p)), c || ""; }; } s.renderer = r; } if (n.tokenizer) { let r = this.defaults.tokenizer || new P(this.defaults); for (let o in n.tokenizer) { if (!(o in r)) throw new Error(`tokenizer '${o}' does not exist`); if (["options", "rules", "lexer"].includes(o)) continue; let i = o, u = n.tokenizer[i], a = r[i]; r[i] = (...p) => { let c = u.apply(r, p); return c === false && (c = a.apply(r, p)), c; }; } s.tokenizer = r; } if (n.hooks) { let r = this.defaults.hooks || new _(); for (let o in n.hooks) { if (!(o in r)) throw new Error(`hook '${o}' does not exist`); if (["options", "block"].includes(o)) continue; let i = o, u = n.hooks[i], a = r[i]; _.passThroughHooks.has(o) ? r[i] = (p) => { if (this.defaults.async && _.passThroughHooksRespectAsync.has(o)) return (async () => { let d = await u.call(r, p); return a.call(r, d); })(); let c = u.call(r, p); return a.call(r, c); } : r[i] = (...p) => { if (this.defaults.async) return (async () => { let d = await u.apply(r, p); return d === false && (d = await a.apply(r, p)), d; })(); let c = u.apply(r, p); return c === false && (c = a.apply(r, p)), c; }; } s.hooks = r; } if (n.walkTokens) { let r = this.defaults.walkTokens, o = n.walkTokens; s.walkTokens = function(i) { let u = []; return u.push(o.call(this, i)), r && (u = u.concat(r.call(this, i))), u; }; } this.defaults = { ...this.defaults, ...s }; }), this; } setOptions(e) { return this.defaults = { ...this.defaults, ...e }, this; } lexer(e, t) { return R.lex(e, t ?? this.defaults); } parser(e, t) { return T.parse(e, t ?? this.defaults); } parseMarkdown(e) { return (n, s) => { let r = { ...s }, o = { ...this.defaults, ...r }, i = this.onError(!!o.silent, !!o.async); if (this.defaults.async === true && r.async === false) return i(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise.")); if (typeof n > "u" || n === null) return i(new Error("marked(): input parameter is undefined or null")); if (typeof n != "string") return i(new Error("marked(): input parameter is of type " + Object.prototype.toString.call(n) + ", string expected")); if (o.hooks && (o.hooks.options = o, o.hooks.block = e), o.async) return (async () => { let u = o.hooks ? await o.hooks.preprocess(n) : n, p = await (o.hooks ? await o.hooks.provideLexer(e) : e ? R.lex : R.lexInline)(u, o), c = o.hooks ? await o.hooks.processAllTokens(p) : p; o.walkTokens && await Promise.all(this.walkTokens(c, o.walkTokens)); let m = await (o.hooks ? await o.hooks.provideParser(e) : e ? T.parse : T.parseInline)(c, o); return o.hooks ? await o.hooks.postprocess(m) : m; })().catch(i); try { o.hooks && (n = o.hooks.preprocess(n)); let a = (o.hooks ? o.hooks.provideLexer(e) : e ? R.lex : R.lexInline)(n, o); o.hooks && (a = o.hooks.processAllTokens(a)), o.walkTokens && this.walkTokens(a, o.walkTokens); let c = (o.hooks ? o.hooks.provideParser(e) : e ? T.parse : T.parseInline)(a, o); return o.hooks && (c = o.hooks.postprocess(c)), c; } catch (u) { return i(u); } }; } onError(e, t) { return (n) => { if (n.message += ` Please report this to https://github.com/markedjs/marked.`, e) { let s = "

    An error occurred:

    " + O(n.message + "", true) + "
    "; return t ? Promise.resolve(s) : s; } if (t) return Promise.reject(n); throw n; }; } }; var E = new F(); function k(l3, e) { return E.parse(l3, e); } k.options = k.setOptions = function(l3) { return E.setOptions(l3), k.defaults = E.defaults, W(k.defaults), k; }; k.getDefaults = I; k.defaults = y; function Pt(...l3) { return E.use(...l3), k.defaults = E.defaults, W(k.defaults), k; } k.use = Pt; k.walkTokens = function(l3, e) { return E.walkTokens(l3, e); }; k.parseInline = E.parseInline; k.Parser = T; k.parser = T.parse; k.Renderer = S; k.TextRenderer = z; k.Lexer = R; k.lexer = R.lex; k.Tokenizer = P; k.Hooks = _; k.parse = k; var gn = k.options; var fn = k.setOptions; var mn = k.walkTokens; var xn = k.parseInline; var Rn = T.parse; var Tn = R.lex; // src/svg/text.js var CJK_RE = /[⺀-鿿가-힯豈-﫿︰-﹏＀-￯ -〿]/; var NARROW = /* @__PURE__ */ new Set([..."iljtfrI.,:;|!'`()[]{}"]); var WIDE = /* @__PURE__ */ new Set([..."mwMWOQGD@%&"]); var HIRAGANA_RE = /[\u3040-\u309f]/; var HIRAGANA_SHARE = 0.05; function isJapanese(text) { let hira = 0; let cjk = 0; for (const ch of String(text)) { if (HIRAGANA_RE.test(ch)) hira++; if (CJK_RE.test(ch)) cjk++; } return hira > 0 && hira / cjk >= HIRAGANA_SHARE; } function isCJK(ch) { return CJK_RE.test(ch); } function charWidth(ch, mono) { if (isCJK(ch)) return 1; if (mono) return 0.6; if (ch === " ") return 0.3; if (NARROW.has(ch)) return 0.32; if (WIDE.has(ch)) return 0.86; if (ch >= "A" && ch <= "Z") return 0.68; return 0.56; } var BOLD = 1.06; function measure(str, size = 13, { mono = false, bold = false } = {}) { let units = 0; for (const ch of String(str ?? "")) units += charWidth(ch, mono); return Math.round(units * size * (bold ? BOLD : 1) * 100) / 100; } var HANGUL_SYLLABLE = /[가-힯]/; var HAN_KANA = new RegExp(CJK_RE.source.replace(HANGUL_SYLLABLE.source.slice(1, -1), "")); var UNSPACED = [ ["th", new RegExp("\\p{Script=Thai}", "u")], ["lo", new RegExp("\\p{Script=Lao}", "u")], ["km", new RegExp("\\p{Script=Khmer}", "u")], ["my", new RegExp("\\p{Script=Myanmar}", "u")] ]; var SEGMENTERS = /* @__PURE__ */ new Map(); function segmenter(locale, granularity) { const key = `${locale}:${granularity}`; if (!SEGMENTERS.has(key)) SEGMENTERS.set(key, new Intl.Segmenter(locale, { granularity })); return SEGMENTERS.get(key); } var graphemes = (text, locale) => [...segmenter(locale, "grapheme").segment(text)].map((g) => g.segment); function countWords(text) { const unspaced = UNSPACED.find(([, re3]) => re3.test(text)); return unspaced ? [...segmenter(unspaced[0], "word").segment(text)].filter((s) => s.isWordLike).length : text.match(/[\p{L}\p{N}][\p{L}\p{M}\p{N}'’-]*/gu)?.length ?? 0; } function words(text, locale) { const out = []; for (const s of segmenter(locale, "word").segment(text)) { if (out.length && !s.isWordLike) out[out.length - 1] += s.segment; else out.push(s.segment); } return out; } function runUnits(run2, maxWidth, size, opts) { const unspaced = UNSPACED.find(([, re3]) => re3.test(run2)); if (unspaced) { const [locale] = unspaced; return words(run2, locale).flatMap((w) => measure(w, size, opts) > maxWidth ? graphemes(w, locale) : [w]); } return HANGUL_SYLLABLE.test(run2) && measure(run2, size, opts) > maxWidth ? graphemes(run2, "ko") : [run2]; } function tokenize(str, maxWidth, size, opts) { const chars = [...String(str)]; const out = []; let i = 0; while (i < chars.length) { const ch = chars[i]; if (HAN_KANA.test(ch)) { out.push(ch); i++; } else if (/\s/u.test(ch)) { let j2 = i; while (j2 < chars.length && /\s/u.test(chars[j2])) j2++; out.push(chars.slice(i, j2).join("")); i = j2; } else { let j2 = i; while (j2 < chars.length && !/\s/u.test(chars[j2]) && !HAN_KANA.test(chars[j2])) j2++; out.push(...runUnits(chars.slice(i, j2).join(""), maxWidth, size, opts)); i = j2; } } return out; } function wrap(str, maxWidth, size = 13, opts = {}) { const lines = []; let line = ""; for (const tok of tokenize(str, maxWidth, size, opts)) { if (/^\s+$/.test(tok)) { if (line) line += " "; continue; } const candidate = line + tok; if (line.trim() && measure(candidate, size, opts) > maxWidth) { lines.push(line.trimEnd()); line = tok; } else { line = candidate; } } lines.push(line.trimEnd()); return lines; } var ESC = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }; function esc(str) { return String(str ?? "").replace(/[&<>"']/g, (c) => ESC[c]); } // src/raw-html.js var words2 = (list) => new Set(list.split(" ")); var NEVER = words2("title textarea style xmp iframe noembed noframes script plaintext link meta base object embed frame frameset template html head body noscript svg math"); var PHRASING = words2("a abbr b bdi bdo br cite code data del dfn em i img ins kbd mark q rp rt ruby s samp small span strong sub sup time u var wbr"); var BLOCK = words2("address area article aside audio blockquote button canvas caption col colgroup datalist dd details dialog div dl dt fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 header hgroup hr input label legend li main map menu meter nav ol optgroup option output p picture pre progress search section select slot source summary table tbody td tfoot th thead tr track ul video acronym big center font strike tt"); var VOID = words2("br wbr img"); var REASON = { never: (t) => `${t} is shown as text; raw markup belongs in an html fence`, unknown: (t) => `${t} is not an HTML element, shown as text; put code in backticks`, block: (t) => `${t} is a block element and cannot sit inside a sentence, shown as text; put it on its own line`, unclosed: (t) => `${t} has no closing tag in the same text, shown as text; put code in backticks`, stray: (t) => `${t} has no opening tag in the same text, shown as text`, open: (t) => `${t} is never closed with >, shown as text`, comment: (t) => `${t} is never closed with -->, shown as text`, bogus: (t) => `${t} is not a tag a page can hold, shown as text; put code in backticks` }; function kind(name, inline2) { if (NEVER.has(name)) return "never"; if (!PHRASING.has(name) && !BLOCK.has(name)) return "unknown"; return inline2 && !PHRASING.has(name) ? "block" : null; } var shown = (tag) => tag.label ?? `<${tag.close ? "/" : ""}${tag.name}>`; function hide(tag, reason2, note2) { note2(tag.text, REASON[reason2](shown(tag))); return esc(tag.text); } var SCAN = /|<(!--|[?!]\[?\w*)|<(\/?)([A-Za-z][^\s/>]*)/g; var OPENING = /^ {0,3}<(\/?)([A-Za-z][^\s/>]*)/; var CHUNK = /"[^"]*"|'[^']*'|[^"'>]+/y; function tagEnd(text, from) { let i = from; while (i < text.length && text[i] !== ">") { CHUNK.lastIndex = i; if (!CHUNK.exec(text)) return -1; i = CHUNK.lastIndex; } return i < text.length ? i + 1 : -1; } function readMatch(text, m) { if (m[1]) { const end2 = m[1] === "!--" ? -1 : text.indexOf(">", m.index); return { reason: m[1] === "!--" ? "comment" : "bogus", tag: { text: end2 === -1 ? m[0] : text.slice(m.index, end2 + 1), label: m[0] } }; } const end = tagEnd(text, m.index + m[0].length); return { reason: end === -1 ? "open" : null, tag: { text: end === -1 ? m[0] : text.slice(m.index, end), close: m[2] === "/", name: m[3].toLowerCase() } }; } function mapTags(text, decide, note2) { let out = ""; let last = 0; for (const m of text.matchAll(SCAN)) { if (m.index < last || !m[1] && !m[3]) continue; const { reason: reason2, tag } = readMatch(text, m); out += text.slice(last, m.index) + (reason2 ? hide(tag, reason2, note2) : decide(tag)); last = m.index + tag.text.length; } return out + text.slice(last); } var readTag = (text) => { const m = /^<(\/?)([A-Za-z][^\s/>]*)/.exec(text); if (m) return { text, close: m[1] === "/", name: m[2].toLowerCase() }; const bogus = /^<[?!](?!--)\[?\w*/.exec(text); return bogus && { text, label: bogus[0] }; }; var ATTR = /\s*([^\s"'<>/=]+)(?:\s*=\s*("[^"]*"|'[^']*'|[^\s>]*))?/g; var URL_ATTRS = words2("href src action xlink:href poster cite background"); var ENTITY = /&#x([0-9a-f]+);?|&#(\d+);?|&(tab|newline|colon);?/gi; var NAMED = { tab: " ", newline: "\n", colon: ":" }; function decode(text) { return text.replace(ENTITY, (_2, hex, dec, name) => { if (name) return NAMED[name.toLowerCase()]; const code = hex ? parseInt(hex, 16) : Number(dec); return code > 1114111 ? "" : String.fromCodePoint(code); }); } function unsafeUrl(element, attr, value) { const url = decode(value.replace(/^["']|["']$/g, "")).replace(/[\x00-\x20\x7f]/g, "").toLowerCase(); if (/^(javascript|vbscript):/.test(url)) return true; return url.startsWith("data:") && !(element === "img" && attr === "src" && url.startsWith("data:image/")); } function unsafe(element, attr, value) { if (/^on|^(srcdoc|formaction)$/.test(attr)) return true; return URL_ATTRS.has(attr) && unsafeUrl(element, attr, value); } function cleanTag(tag, note2) { if (tag.close) return tag.text; const head = 1 + tag.name.length; const removed = []; const attrs = tag.text.slice(head).replace(ATTR, (attr, name, value = "") => { if (!unsafe(tag.name, name.toLowerCase(), value)) return attr; removed.push(name); return ""; }); if (removed.length) note2(tag.text, `removed ${removed.join(", ")} from <${tag.name}>${removed.some((n) => URL_ATTRS.has(n.toLowerCase())) ? " (unsafe URL)" : ""}`); return tag.text.slice(0, head) + attrs; } function htmlTokens(tokens, out = []) { for (const t of tokens ?? []) { if (t.type === "html") out.push(t); else htmlTokens(t.tokens, out); } return out; } function unpaired(found) { const hidden = /* @__PURE__ */ new Map(); const open = []; for (const item of found) { const { tag } = item; const reason2 = tag.label ? "bogus" : kind(tag.name, true); if (reason2) hidden.set(item, reason2); else if (VOID.has(tag.name)) continue; else if (!tag.close) open.push(item); else { const i = open.findLastIndex((o) => o.tag.name === tag.name); if (i === -1) hidden.set(item, "stray"); else open.splice(i).slice(1).forEach((o) => hidden.set(o, "unclosed")); } } open.forEach((o) => hidden.set(o, "unclosed")); return hidden; } function filterRun(tokens, note2) { const found = htmlTokens(tokens).map((token) => ({ token, tag: readTag(token.text) })).filter((item) => item.tag); const hidden = unpaired(found); for (const item of found) { const reason2 = hidden.get(item); item.token.text = reason2 ? hide(item.tag, reason2, note2) : cleanTag(item.tag, note2); } } function filterInline(tokens, note2) { filterRun(tokens, note2); return tokens; } function relexReason(raw) { const special = /^ {0,3}(<[?!](?!--)\[?\w*|") ? null : { reason: comment ? "comment" : "bogus", at: special[1], label: special[1] }; } const m = OPENING.exec(raw); if (!m) return null; const tag = { close: m[1] === "/", name: m[2].toLowerCase() }; const reason2 = kind(tag.name, false) ?? (tag.name === "pre" && !tag.close && !/<\/pre>/i.test(raw) ? "unclosed" : null); return reason2 && { reason: reason2, at: m[0].trim(), label: shown(tag) }; } function relex(token, { reason: reason2, at: at3, label }, { lex, note: note2 }) { note2(at3, REASON[reason2](label)); return filterBlocks(lex(token.raw.replace("<", "<")), { lex, note: note2 }); } var filterHtmlBlock = (text, note2) => mapTags(text, (tag) => { const reason2 = kind(tag.name, false); return reason2 ? hide(tag, reason2, note2) : cleanTag(tag, note2); }, note2); var tight = (block2) => block2.type === "paragraph" ? { ...block2, type: "text" } : block2; function filterBlocks(tokens, ctx) { for (let k2 = 0; k2 < tokens.length; k2++) { const t = tokens[k2]; const plan = t.type === "html" ? relexReason(t.raw) : null; if (plan) { const again = relex(t, plan, ctx); tokens.splice(k2, 1, ...again); k2 += again.length - 1; } else if (t.type === "html") { t.text = filterHtmlBlock(t.text, ctx.note); } else if (t.type === "table") { [...t.header, ...t.rows.flat()].forEach((cell) => filterRun(cell.tokens, ctx.note)); } else if (t.type === "list") { t.items.forEach((item) => { filterBlocks(item.tokens, ctx); if (!t.loose) item.tokens = item.tokens.map(tight); }); } else if (t.type === "blockquote") { filterBlocks(t.tokens, ctx); } else if (t.type === "paragraph" || t.type === "heading" || t.type === "text") { filterRun(t.tokens, ctx.note); } } return tokens; } // src/math.js var temml = null; function useTemml(lib) { temml = lib; } var SPACE = /\s/; var NO_CLOSE_BEFORE = /[0-9A-Za-z_]/; function closing(src, at3, n) { if (src.length <= at3 + n || SPACE.test(src[at3 + n])) return -1; for (let i = at3 + n; i < src.length; i++) { const c = src[i]; if (c === "\n" || c === "`") return -1; if (c === "\\") { i++; continue; } if (c !== "$") continue; if (i === at3 + n || SPACE.test(src[i - 1])) return -1; if (n === 2) return src[i + 1] === "$" ? i : -1; return NO_CLOSE_BEFORE.test(src[i + 1] ?? "") ? -1 : i; } return -1; } function stripMath(text) { let out = ""; let from = 0; for (let i = text.indexOf("$"); i !== -1; i = text.indexOf("$", i)) { if (i > 0 && text[i - 1] === "\\") { i++; continue; } const n = text[i + 1] === "$" ? 2 : 1; const end = closing(text, i, n); if (end < 0) { i++; continue; } out += text.slice(from, i); from = end + n; i = from; } return out + text.slice(from); } var mayHaveMath = (source) => source.includes("$") && (stripMath(source) !== source || /^ {0,3}\$\$/m.test(source)); var opensDisplay = (line) => /^ {0,3}\$\$[ \t]*$/.test(line); var closesDisplay = (line) => /\$\$[ \t]*$/.test(line); var ONE_LINE = /^ {0,3}\$\$((?:[^$\\\n]|\\.)+)\$\$[ \t]*(?:\n|$)/; var OPEN = /^ {0,3}\$\$[ \t]*(?:\n|$)/; var isDisplayLine = (line) => ONE_LINE.test(`${line} `); function displayBlock(src) { const one = ONE_LINE.exec(src); if (one) return one[1].trim() ? { raw: one[0], tex: one[1].trim() } : null; const open = OPEN.exec(src); if (!open) return null; const lines = []; let pos = open[0].length; while (pos < src.length) { const nl = src.indexOf("\n", pos); const end = nl < 0 ? src.length : nl; const line = src.slice(pos, end); pos = nl < 0 ? end : end + 1; if (!line.trim()) return null; if (closesDisplay(line)) { const last = line.replace(/\$\$[ \t]*$/, ""); if (last.trim()) lines.push(last); return lines.length ? { raw: src.slice(0, pos), tex: lines.join("\n") } : null; } lines.push(line); } return null; } function displayStart(src) { for (let i = src.indexOf("$$"); i !== -1; i = src.indexOf("$$", i + 1)) { const from = src.lastIndexOf("\n", i - 1) + 1; if (i - from <= 3 && !src.slice(from, i).trim()) return from; } return void 0; } var reason = (err) => String(err?.message ?? err).replace(/\u0332/g, "").replace(/( at position \d+): [\s\S]*$/, "$1").replace(/( at end of input): [\s\S]*$/, "$1").replace(/\s+/g, " ").trim(); var preview = (raw) => { const s = raw.replace(/\s+/g, " ").trim(); return s.length > 40 ? `${s.slice(0, 40)}\u2026` : s; }; var NEEDS_SCRIPT = /\\(?:ref|eqref|label)(?![A-Za-z])/; function typeset({ raw: padded, tex, display }, block2, note2) { const raw = padded.trim(); const wrap2 = (inner) => display ? `<${block2 ? "div" : "span"} class="am-math-block" dir="ltr">${inner}` : inner; try { const html = temml.renderToString(tex, { displayMode: display, throwOnError: true, ...display ? {} : { wrap: "tex" } }); if (NEEDS_SCRIPT.test(tex)) note2(raw, `${preview(raw)} uses \\ref, \\eqref or \\label; a page has no script to resolve them, so they show nothing. Write a number with \\tag{1} in a $$ block`); return wrap2(html.replace(" `${esc(raw.trim())}`); } } var hasMath = (html) => html.includes(' temml !== null && enabled(); return { extensions: [ { name: "mathInline", level: "inline", start: (src) => { const i = src.indexOf("$"); return i < 0 ? void 0 : i; }, tokenizer(src) { if (src[0] !== "$" || !ready()) return void 0; const n = src[1] === "$" ? 2 : 1; const end = closing(src, 0, n); return end < 0 ? void 0 : { type: "mathInline", raw: src.slice(0, end + n), tex: src.slice(n, end), display: n === 2 }; }, renderer: (token) => typeset(token, false, note2) }, { name: "mathBlock", level: "block", start: displayStart, tokenizer(src) { if (!/^ {0,3}\$\$/.test(src) || !ready()) return void 0; const found = displayBlock(src); return found && { type: "mathBlock", raw: found.raw, tex: found.tex, display: true }; }, renderer: (token) => `${typeset(token, true, note2)} ` } ] }; } // src/markdown.js var sink = null; var note = (at3, message) => sink?.push({ at: at3, message }); var mathOn = true; function collectHtmlNotes(render, { math: math2 = true } = {}) { const outer = sink; const outerMath = mathOn; const notes = []; sink = notes; mathOn = math2; try { return { result: render(), notes }; } finally { sink = outer; mathOn = outerMath; } } var marked = new F({ gfm: true }); var inline = new F({ gfm: true }); marked.use({ hooks: { processAllTokens: (tokens) => filterBlocks(tokens, { lex: (source) => marked.lexer(source), note }) } }); inline.use({ hooks: { processAllTokens: (tokens) => filterInline(tokens, note) } }); var math = mathExtension({ enabled: () => mathOn, note: (at3, message) => sink?.push({ at: at3, message, math: true }) }); marked.use(math); inline.use(math); var STATUS = { ok: { cls: "ok", icon: "\u2713" }, no: { cls: "no", icon: "\u2717" }, warn: { cls: "warn", icon: "!" } }; var STATUS_ALIAS = { "\u2713": "ok", "\u2714": "ok", "\u2717": "no", "\u2718": "no", "\u26A0": "warn" }; function statusHtml(word, label = "") { const kind2 = STATUS[STATUS_ALIAS[word] ?? word]; if (!kind2) return null; const text = label.trim(); return `${text}`; } var IMAGE_ONLY = /

    \s*(]*>)\s*<\/p>/g; var CELL_STATUS = /]*)>\s*(ok|no|warn|✓|✔|✗|✘|⚠)(?:\s+((?:(?!<\/?td\b)[\s\S])*?))?\s*<\/td>/g; function figure(img) { const alt = img.match(/\salt="([^"]*)"/)?.[1]; return `

    ${img}${alt ? `
    ${alt}
    ` : ""}
    `; } function decorate(html) { return html.replace(//g, '
    ').replace(/<\/table>/g, "
    ").replace(IMAGE_ONLY, (_2, img) => figure(img)).replace(CELL_STATUS, (_2, attrs, word, label = "") => `${statusHtml(word, label)}`); } var SPACED_IMAGE = /(`[^`\n]*`)|(!\[[^\]\n]*\]\()\s*((?:[^()<>"\n]|\([^()<>"\n]*\))*?)(\s+"[^"\n]*")?\s*\)/g; function wrapSpacedImages(text) { return text.replace(SPACED_IMAGE, (whole, code, head, dest, title = "") => code || !/\s/.test(dest) ? whole : `${head}<${dest}>${title})`); } function md(text) { return decorate(marked.parse(wrapSpacedImages(String(text ?? "")))); } function mdInline(text) { return inline.parseInline(wrapSpacedImages(String(text ?? ""))); } // src/components/error.js var ComponentError = class extends Error { constructor(message, line = 0) { super(message); this.name = "ComponentError"; this.line = line; } }; function contentLines(text) { return String(text).split("\n").map((raw, i) => ({ raw, text: raw.trim(), line: i + 1 })).filter((l3) => l3.text && !l3.text.startsWith("//")); } function fields(text) { return text.split("|").map((s) => s.trim()); } // src/components/callout.js var KINDS = /* @__PURE__ */ new Set(["info", "ok", "warn", "err"]); var ALIASES = { warning: "warn", error: "err", note: "info" }; var callout_default = { name: "callout", summary: "Conclusion / tip / warning bar", syntax: `\`\`\`callout [title] Body (Markdown) \`\`\` - If the first argument is not a type, the whole argument string is the title and the type is info.`, example: "```callout warn Caution\nClose the valve before you remove the pump.\n```", render(text, { args }) { const [first = "", ...rest] = args.split(/\s+/).filter(Boolean); const kind2 = KINDS.has(first) ? first : "info"; const title = (KINDS.has(first) ? rest.join(" ") : args).trim(); if (!title && !text.trim()) throw new ComponentError("callout needs a title or a body", 1); const firstLine = contentLines(text)[0]; const typeLine = firstLine && firstLine.text.match(/^type\s*[::]\s*([a-z][\w-]*)\s*(?:#.*)?$/i); if (typeLine) { const word = typeLine[1].toLowerCase(); const wrongKind = KINDS.has(word) ? word : ALIASES[word] ?? ""; throw new ComponentError(`callout: put the type on the fence line, not in the body: \`\`\`callout ${wrongKind} [title]. The line "${firstLine.text}" would show as body text`, firstLine.line); } const head = title ? `
    ${esc(title)}
    ` : ""; const body = text.trim() ? `
    ${md(text)}
    ` : ""; return `
    ${head}${body}
    `; } }; // src/components/kv.js var kv_default = { name: "kv", summary: "Key-value grid / title block (metadata)", syntax: `\`\`\`kv [cols=2] key: value * wide key: value \u2190 starts with *: spans the full row, larger text \`\`\` - Splits at the first colon (: or the fullwidth colon); the value may contain more colons.`, example: "```kv cols=2\n* Title: Simplified Technical English\nSpecification: ASD-STE100\nOwner: ASD\n```", render(text, { args }) { const cols = Math.max(1, Math.min(Number(parseAttrs(args).cols) || 2, 6)); const cells = contentLines(text).map(({ text: t, line }) => { const wide = t.startsWith("*"); const body = wide ? t.slice(1).trim() : t; const m = body.match(/^([^::]+)[::]\s*(.*)$/); if (!m) throw new ComponentError(`kv line has no colon: "${t}"; expected key: value`, line); return `
    ${esc(m[1].trim())}
    ${mdInline(m[2])}
    `; }); if (!cells.length) throw new ComponentError("kv needs at least one key: value line", 1); return `
    ${cells.join("")}
    `; } }; // src/components/timeline.js var timeline_default = { name: "timeline", summary: "Timeline / phases", syntax: `\`\`\`timeline [h|v] time | title | note (optional) *time | title \u2190 starts with *: highlights the item \`\`\` - By default \u22646 items are horizontal and >6 are vertical; the h / v argument forces a direction.`, example: "```timeline\n1979 | AECMA starts the study\n1986 | First guide published\n*Now | Free download | Maintained by ASD STEMG\n```", render(text, { args }) { const items = contentLines(text).map(({ text: t, line }) => { const parts = fields(t); if (parts.length < 2 || !parts[1]) throw new ComponentError(`timeline line must be time | title | note: "${t}"`, line); const hi = parts[0].startsWith("*"); return { when: hi ? parts[0].slice(1).trim() : parts[0], title: parts[1], detail: parts[2] ?? "", hi }; }); if (!items.length) throw new ComponentError("timeline needs at least one item", 1); const vertical = /\bv(ertical)?\b/.test(args) || !/\bh(orizontal)?\b/.test(args) && items.length > 6; const lis = items.map((it3) => `
  • ${esc(it3.when)}${mdInline(it3.title)}${it3.detail ? `${mdInline(it3.detail)}` : ""}
  • `); return vertical ? `
      ${lis.join("")}
    ` : `
      ${lis.join("")}
    `; } }; // src/components/annot.js var SEG = /\[([^\]]+)\]\{(!?)([^}]*)\}/g; var TEXT_SIZE = 14; var NOTE_SIZE = 11; var NOTE_GAP = 10; var annot_default = { name: "annot", summary: "Sentence annotation by segment (underline bracket + note)", syntax: `\`\`\`annot # Heading | right-side note (optional) Sentence text, [annotated span]{note}, [wrong span]{!red note}. > Caption below (optional) \`\`\` - Each # starts a group; a group can hold several sentences. Overlapping notes move to separate rows automatically.`, example: '```annot\n# 1 Procedural sentence | 13 words, limit 20\nMake sure that [the hydraulic reservoir]{Technical name} is [full]{!Not "replenished"}.\n> Write one instruction per sentence\n```', render(text, { dir = "ltr" } = {}) { const groups = []; let group = null; const ensure = () => group ?? (group = pushGroup(groups, {})); for (const { text: t, line } of contentLines(text)) { if (t.startsWith("#")) { const [title, meta = ""] = fields(t.replace(/^#+\s*/, "")); group = pushGroup(groups, { title, meta }); } else if (t.startsWith(">")) { ensure().captions.push(t.replace(/^>\s*/, "")); } else { ensure().lines.push(sentenceHtml(t, line, dir === "rtl")); } } if (!groups.length) throw new ComponentError("annot needs at least one sentence", 1); return groups.map(groupHtml).join(""); } }; function pushGroup(groups, { title = "", meta = "" }) { const g = { title, meta, lines: [], captions: [] }; groups.push(g); return g; } function groupHtml(g) { const head = g.title || g.meta ? `
    ${esc(g.title)}${g.meta ? `${esc(g.meta)}` : ""}
    ` : ""; const lines = g.lines.map((l3) => `
    ${l3}
    `).join(""); const caps = g.captions.map((c) => `
    ${esc(c)}
    `).join(""); return `
    ${head}${lines}${caps}
    `; } function sentenceHtml(sentence, line, rtl) { const stripped = sentence.replace(SEG, ""); if (/\[[^\]]*\]\{|\]\{[^}]*$/.test(stripped)) { throw new ComponentError(`annot has an unclosed annotation; expected [span]{note}: "${sentence}"`, line); } const rows = []; let out = ""; let plain = ""; let last = 0; for (const m of sentence.matchAll(SEG)) { const before = sentence.slice(last, m.index); out += esc(before); plain += before; const [, seg, bang, note2] = m; const x2 = measure(plain, TEXT_SIZE, { mono: !rtl }); const noteHtml2 = note2.trim() ? `${esc(note2.trim())}` : ""; out += `${esc(seg)}${noteHtml2}`; plain += seg; last = m.index + m[0].length; } out += esc(sentence.slice(last)); const wrapCls = rows.length ? "" : " am-annot-line--wrap"; return `
    ${out}
    `; } function placeNote(rows, start, end) { const idx = rows.findIndex((ranges) => ranges.every(([s, e]) => end <= s || start >= e)); if (idx !== -1) { rows[idx].push([start, end]); return idx; } rows.push([[start, end]]); return rows.length - 1; } // src/components/delta.js var STATE_OF = { "+": "added", "-": "removed", "~": "changed" }; var SIGN = { added: "+", removed: "\u2212", changed: "~" }; var STATES = Object.keys(SIGN); var EN_DELTA = { added: "added", removed: "removed", changed: "changed", view: "View", before: "Before", changes: "Changes", after: "After" }; var labelsOf = (ui) => ({ ...EN_DELTA, ...ui?.delta }); function splitMarker(text) { const m = text.match(/^([+\-~]) +(\S.*)$/); return m ? { mark: m[1], text: m[2] } : { mark: null, text }; } var markState = (mark) => STATE_OF[mark] ?? null; var deltaAttr = (state) => state ? ` data-delta="${state}"` : ""; function deltaBadge(state, ui) { if (!state) return ""; return `${SIGN[state]}`; } function withDelta(html, states, { ui, video = false } = {}) { const used = STATES.map((s) => [s, states.filter((x2) => x2 === s).length]).filter(([, n]) => n > 0); if (!used.length) return html; const t = labelsOf(ui); const word = (s, n) => n === 1 ? t[s] : t.counts?.[s] ?? t[s]; const counts = used.map(([s, n]) => `${SIGN[s]}${n} ${esc(word(s, n))}`).join(" "); const views = ["before", "changes", "after"].map((v) => ``).join(""); const switcher = video ? "" : ``; const open = html.indexOf(">"); const close = html.lastIndexOf("${counts}${switcher}${html.slice(close)}`; } // src/components/tree.js var tree_default = { name: "tree", summary: "Hierarchy tree (org chart / indented list)", syntax: `\`\`\`tree [list] Root | subtitle Child Grandchild | one-line note *Highlighted child \`\`\` - Indentation (spaces or tabs) sets the level; "label | note" adds a gray note. - One root with 2 to 4 children \u2192 org chart; more children or the list argument \u2192 indented list; several roots \u2192 side by side. - Labels support inline Markdown, such as \`Section 1\` Words. - Change markers show what a plan adds, removes and changes. A line can start with + (added), - (removed) or ~ (changed), followed by a space, after the indentation: \`\`\`tree list src/ components/ + delta.js | parses change markers ~ flow.js ~ tree.js - legacy/ old-flow.js \`\`\` - Indentation still sets the level. The children of a + or - node inherit it; a child that carries a different marker is an error. ~ marks one node and is not inherited. - The Changes view draws added in the theme's ok color, removed faded with a struck-through label, changed with a warn outline, and each marked node with a +, \u2212 or ~ badge. A count row and a Before / Changes / After switch sit under the tree: Before and After show the tree as it was and as it will be, plain and without the items that are not in that view. - A line that starts with a marker and a space is always read as a marker. To keep a label that starts with "- ", write \\- item.`, example: "```tree\nASD-STE100 | Simplified Technical English\n Part 1: Writing rules\n `Section 1` Words\n Part 2: Dictionary\n Approved words | one word, one meaning\n```", render(text, { args, ui, video }) { const roots = buildTree(text); if (!roots.length) throw new ComponentError("tree needs at least one node", 1); return withDelta(treeHtml(roots, /\blist\b/.test(args), ui), statesOf(roots), { ui, video }); } }; function treeHtml(roots, listMode, ui) { if (roots.length === 1) { const [root] = roots; const n = root.children.length; if (!listMode && n >= 2 && n <= 4) return orgHtml(root, ui); return `
    ${rootBox(root, ui, true)}${listHtml(root.children, ui)}
    `; } if (!listMode && roots.length <= 4) { return `
    ${colsHtml(roots, ui)}
    `; } return `
    ${listHtml(roots, ui)}
    `; } var statesOf = (nodes) => nodes.flatMap((n) => [n.state, ...statesOf(n.children)]); function buildTree(text) { const roots = []; const stack = []; let step = 0; String(text).split("\n").forEach((raw, i) => { if (!raw.trim()) return; const indent = raw.replace(/\t/g, " ").match(/^ */)[0].length; const node = { ...parseLabel(raw.trim()), indent, step: step++, line: i + 1, children: [] }; while (stack.length && stack.at(-1).indent >= indent) stack.pop(); (stack.length ? stack.at(-1).children : roots).push(node); stack.push(node); }); return roots.map((root) => settle(root, null)); } function settle(node, inherited) { const own = markState(node.mark); if (inherited && own && own !== inherited) { throw new ComponentError(`tree: "${node.mark}" under a ${inherited} node contradicts it. The children of a ${inherited} node are ${inherited} too; remove the marker or move the node`, node.line); } const state = inherited ?? own; const below = state === "added" || state === "removed" ? state : null; return { ...node, state, children: node.children.map((child) => settle(child, below)) }; } function parseLabel(t) { const escaped = /^\\[+\-~] /.test(t); const { mark, text } = escaped ? { mark: null, text: t.slice(1) } : splitMarker(t); const hi = text.startsWith("*"); const [label, sub = ""] = fields(hi ? text.slice(1) : text); return { label, sub, hi, mark }; } var labelHtml = (label) => mdInline(label).replace(/^([^<]*)<\/code>(?=\s*\S)/, '$1'); var vattrs = (n) => ` data-key="${esc(n.label)}" data-step="${n.step}"${deltaAttr(n.state)}`; var HIDDEN_IN = { before: "added", after: "removed" }; function lineAttrs(siblings, i, cols) { const n = siblings.length; return Object.entries(HIDDEN_IN).map(([view, hidden]) => { const shown2 = siblings.map((x2) => x2.state !== hidden); const first = shown2.indexOf(true); const last = shown2.lastIndexOf(true); const natural = cols ? n === 1 ? "none" : i === 0 ? "start" : i === n - 1 ? "end" : "full" : i < n - 1 ? "full" : "short"; const kind2 = cols ? first === last || i < first || i > last ? "none" : i === first ? "start" : i === last ? "end" : "full" : i < last ? "full" : i === last ? "short" : "none"; return kind2 === natural ? "" : ` data-line-${view}="${kind2}"`; }).join(""); } var dropAttrs = (children) => Object.entries(HIDDEN_IN).map(([view, hidden]) => children.every((c) => c.state === hidden) ? ` data-line-${view}="none"` : "").join(""); var boxInner = (n, ui) => `${deltaBadge(n.state, ui)}${labelHtml(n.label)}${n.sub ? `${mdInline(n.sub)}` : ""}`; function rootBox(root, ui, solo = false) { const drop = solo ? "" : dropAttrs(root.children); return `
    ${boxInner(root, ui)}
    `; } function colHtml(node, ui, siblings, i) { const children = node.children.length ? listHtml(node.children, ui) : ""; return `
    ${boxInner(node, ui)}
    ${children}
    `; } var colsHtml = (nodes, ui) => nodes.map((n, i) => colHtml(n, ui, nodes, i)).join(""); function orgHtml(root, ui) { return `
    ${rootBox(root, ui)}
    ${colsHtml(root.children, ui)}
    `; } function listHtml(nodes, ui) { return `
      ${nodes.map((n, i) => liHtml(n, ui, nodes, i)).join("")}
    `; } function liHtml(n, ui, siblings, i) { const sub = n.sub ? `${mdInline(n.sub)}` : ""; const kids = n.children.length ? `
      ${n.children.map((c, k2) => liHtml(c, ui, n.children, k2)).join("")}
    ` : ""; return `${deltaBadge(n.state, ui)}${labelHtml(n.label)}${sub}${kids}`; } // src/components/limits.js var NICE_MAX = [1, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10]; var NICE_STEP = [1, 2, 2.5, 5, 10]; function niceScale(peak) { if (!Number.isFinite(peak) || peak <= 0) return { max: 1, step: 1 }; const target = peak * 1.4; const pow = 10 ** Math.floor(Math.log10(target)); const integral = Number.isInteger(peak); const nice = NICE_MAX.map((m) => m * pow).find((m) => m >= target - 1e-9) ?? 10 * pow; const max = integral ? Math.ceil(nice) : nice; const stepPow = 10 ** Math.floor(Math.log10(max)); const step = [...NICE_STEP.map((s) => s * stepPow / 10), ...NICE_STEP.map((s) => s * stepPow)].find((s) => max / s <= 7 && Number.isInteger(round(max / s)) && (integral ? s >= 1 : true)) ?? max; return { max: round(max), step: round(step) }; } var round = (n) => Math.round(n * 1e3) / 1e3; var pct = (v, max) => `${Math.round(v / max * 1e4) / 100}%`; var NUM = /^(?:max\s+)?(-?\d+(?:\.\d+)?)$/i; var limits_default = { name: "limits", summary: "Value vs limit bars", syntax: `\`\`\`limits label | value / limit | unit (optional) | note (optional) label | limit | unit \u2190 limit only: the bar fills to the limit \`\`\` - A row turns red when the value is over the limit. The limit may be written as "max 20".`, example: "```limits\nProcedural sentence | 13 / 20 | words\nDescriptive sentence | max 25 | words\nNoun cluster | 4 / 3 | words | over\n```", render(text, { dir = "ltr", ui } = {}) { const rows = contentLines(text).map(({ text: t, line }) => parseRow(t, line)); if (!rows.length) throw new ComponentError("limits needs at least one line", 1); const words3 = { ...EN_WORDS, ...ui?.limits }; return `
    ${rows.map((row) => rowHtml(row, dir === "rtl" ? "right" : "left", words3)).join("")}
    `; } }; function parseRow(t, line) { const [label, spec = "", unit = "", note2 = ""] = fields(t); const [a, b] = spec.split("/").map((s) => s.trim()); const nums = (b === void 0 ? [a] : [a, b]).map((s) => s?.match(NUM)?.[1]); if (!spec || nums.some((n) => n === void 0)) { throw new ComponentError(`limits line must be label | value / limit | unit: "${t}"`, line); } const [value, limit] = b === void 0 ? [null, Number(nums[0])] : nums.map(Number); return { label, value, limit, unit, note: note2 }; } var EN_WORDS = { value: "{value} / max {limit}", limit: "max {limit}" }; var fill = (template, value, limit) => template.replace("{value}", value).replace("{limit}", limit); function rowHtml({ label, value, limit, unit, note: note2 }, side = "left", words3 = EN_WORDS) { const { max, step } = niceScale(Math.max(limit, value ?? 0)); const shown2 = value ?? limit; const over2 = value !== null && value > limit; const valText = `${value !== null ? fill(words3.value, value, limit) : fill(words3.limit, value, limit)}${unit ? ` ${unit}` : ""}`; const ticks = []; if (step > 0 && max > 0) { for (let v = 0; v <= max + 1e-9; v += step) ticks.push(`${round(v)}`); } return `
    ${esc(label)}${note2 ? `${esc(note2)}` : ""}${esc(valText)}
    `; } // src/runtime/rtl-letter.js var RTL_LETTER = /[\p{Script=Hebrew}\p{Script=Arabic}\p{Script=Syriac}\p{Script=Thaana}\p{Script=Nko}\p{Script=Samaritan}\p{Script=Mandaic}]/u; // src/bidi.js var LETTER = new RegExp("\\p{L}", "u"); function isLtrOnly(text) { const s = decode2(text); return !RTL_LETTER.test(s) && [...s].some((ch) => LETTER.test(ch)); } var hasRtl = (text) => RTL_LETTER.test(decode2(text)); var decode2 = (text) => String(text ?? "").replace(/&(amp|lt|gt|quot|#39);/g, (_2, e) => ({ amp: "&", lt: "<", gt: ">", quot: '"', "#39": "'" })[e]).replace(/&[#\w]+;/g, " "); var NEUTRAL = /[\p{P}\p{S}]/u; var WJ = String.fromCharCode(8288); var PREFIX = new RegExp("((?:^|[\\s(])\\p{Script=Hebrew}{1,3}-)(?=[^\\s-])", "gu"); var SIGNED = /(^|[\s(])([+\-−±~]\d[\d.,]*%?)/g; var WORD = /\S+/g; var WORD_LEAD = new RegExp(`^(?:\\p{Script=Hebrew}{1,3}[-\\u05BE]|[([{\xAB\u201C\u2018"']|"|')+`, "u"); var WORD_TRAIL = /(?:[.,:!?)\]}»”’…"'،؛؟]|(?${core}${trail}` : word; } var LRI = String.fromCharCode(8294); var PDI = String.fromCharCode(8297); function svgLine(line, dir) { if (dir !== "rtl") return line; return isLtrOnly(line) ? `${LRI}${line}${PDI}` : line; } var INLINE = /* @__PURE__ */ new Set(["a", "abbr", "b", "cite", "code", "del", "dfn", "em", "i", "ins", "kbd", "mark", "q", "s", "samp", "strong", "sub", "sup", "u", "var"]); var SKIP = /* @__PURE__ */ new Set(["svg", "math", "pre", "script", "style", "textarea", "select", "option", "title"]); var TOKEN = /|<\/?([a-zA-Z][\w-]*)\b(?:[^>"']|"[^"]*"|'[^']*')*>|[^<]+| { if (!run2.length) return; const text = run2.filter((t) => !t.startsWith("<")).join(""); const tags = run2.filter((t) => t.startsWith("<") && !t.startsWith("|->)\s*([^::]+?)\s*(?:[::]\s*(.*))?$/ }; function parseSequence(text) { const participants = []; const add = (p) => { if (!participants.includes(p)) participants.push(p); }; const steps = []; for (const { text: t, line } of contentLines(text)) { let m; if (m = t.match(RE.participants)) { m[1].split(/[,,]/).map((s) => s.trim()).filter(Boolean).forEach(add); } else if (m = t.match(RE.note)) { const over2 = m[1].split(/[,,]/).map((s) => s.trim()).filter(Boolean); over2.forEach(add); steps.push({ kind: "note", over: over2, text: m[2].trim(), line }); } else if (m = t.match(RE.divider)) { steps.push({ kind: "divider", text: m[1], line }); } else if (m = t.match(RE.msg)) { add(m[1]); add(m[3]); steps.push({ kind: "msg", from: m[1], to: m[3], dashed: m[2] === "-->", label: (m[4] ?? "").trim(), line }); } else { throw new ComponentError(`sequence cannot parse "${t}". Write a message as A -> B: label (--> is a dashed return) and a note as note A: text`, line); } } if (!participants.length) throw new ComponentError("sequence needs at least one message", 1); return { participants, steps }; } var sequence_default = { name: "sequence", summary: "Sequence diagram (messages between participants)", syntax: `\`\`\`sequence [num] participants: A, B, C \u2190 optional, fixes the participant order A -> B: request \u2190 solid line B --> A: response \u2190 dashed line (return) B -> B: self call note A: note on one participant note A, C: note across several participants == Phase divider == \`\`\` - Argument num: number the messages.`, example: "```sequence\nClient -> Server: SYN\nServer --> Client: SYN-ACK\nClient -> Server: ACK\nnote Client, Server: ESTABLISHED\n```", render(text, { args, uid, ui, dir = "ltr" }) { const model = parseSequence(text); return `
    ${layout(model, { num: /\bnum\b/.test(args), id: uid(), ui, dir })}
    `; } }; function layout({ participants: ps, steps }, { num, id, ui, dir = "ltr" }) { const idx = new Map(ps.map((p, i) => [p, i])); const actorW = ps.map((p) => Math.max(measure(p, FS) + 28, 84)); const gaps = ps.slice(1).map((_2, i) => (actorW[i] + actorW[i + 1]) / 2 + 28); let extraRight = 0; let extraLeft = 0; const prepared = steps.map((s) => { if (s.kind === "msg") { const lines2 = s.label ? wrap(s.label, LABEL_MAX, FS) : []; const width2 = Math.max(0, ...lines2.map((l3) => measure(l3, FS))) + (num ? 34 : 22); const a = idx.get(s.from); const b = idx.get(s.to); if (a === b) { if (a < gaps.length) gaps[a] = Math.max(gaps[a], width2 + 48); else extraRight = Math.max(extraRight, width2 + 40); } else { const [lo, hi] = [Math.min(a, b), Math.max(a, b)]; const span = gaps.slice(lo, hi).reduce((x2, y3) => x2 + y3, 0); if (span < width2) gaps[hi - 1] += width2 - span; } return { ...s, lines: lines2, a, b }; } const lines = wrap(s.text, 220, 12); const w = Math.max(...lines.map((l3) => measure(l3, 12))) + 20; if (s.kind === "note" && s.over.length === 1) { const i = idx.get(s.over[0]); if (i === 0) extraLeft = Math.max(extraLeft, w / 2 - actorW[0] / 2); if (i === ps.length - 1) extraRight = Math.max(extraRight, w / 2 - actorW[i] / 2); } return { ...s, lines, w }; }); const xs = [MARGIN + extraLeft + actorW[0] / 2]; gaps.forEach((g) => xs.push(xs.at(-1) + g)); const width = xs.at(-1) + actorW.at(-1) / 2 + MARGIN + extraRight; const rtl = dir === "rtl"; const X3 = mirror(width, rtl); const dx = rtl ? -1 : 1; const body = []; let y2 = TOP + ACTOR_H + 22; let n = 0; for (const [k2, s] of prepared.entries()) { const start = body.length; if (s.kind === "msg") { n++; const cls = `am-edge${s.dashed ? " am-edge--dashed" : ""}`; const marker = ` marker-end="url(#${id}-arrow)"`; const step = num ? `${n}` : ""; if (s.a === s.b) { const x2 = X3(xs[s.a]); const labelX = x2 + dx * 40; body.push(s.lines.map((l3, k3) => `${esc(svgLine(l3, dir))}`).join("")); body.push(``, step); y2 += Math.max(s.lines.length * LH, 20) + 28; } else { y2 += s.lines.length * LH; const [x1, x2] = [X3(xs[s.a]), X3(xs[s.b])]; const mx = (x1 + x2) / 2; body.push(s.lines.map((l3, k3) => `${esc(svgLine(l3, dir))}`).join("")); body.push(``, step); y2 += 24; } } else if (s.kind === "note") { const xsOver = s.over.map((p) => X3(xs[idx.get(p)])); const lo = Math.min(...xsOver); const hi = Math.max(...xsOver); const w = Math.max(s.w, hi - lo + 40); const h2 = s.lines.length * LH + 12; const cx = (lo + hi) / 2; body.push(``); body.push(textLines(s.lines, cx, y2 + h2 / 2, LH, ' font-size="12"', dir)); y2 += h2 + 16; } else { const w = Math.max(...s.lines.map((l3) => measure(l3, 12))) + 20; body.push(``); body.push(``); body.push(textLines(s.lines.slice(0, 1), width / 2, y2 + 10, LH, ' font-size="12"', dir)); y2 += 34; } body.splice(start, body.length - start, `${body.slice(start).join("")}`); } const height = y2 + 6; const actors = ps.map((p, i) => { const x2 = X3(xs[i]); return `${textLines([p], x2, TOP + ACTOR_H / 2, LH, ' font-weight="600"', dir)}`; }); return `${svgOpen(width, height, diagramLabel(ui, "sequence", ps), dir)}${arrowDefs(id)}${actors.join("")}${body.join("")}`; } // node_modules/@dagrejs/dagre/dist/dagre.esm.js var Te2 = Object.defineProperty; var In = (e, n, t) => n in e ? Te2(e, n, { enumerable: true, configurable: true, writable: true, value: t }) : e[n] = t; var Sn = (e, n) => { for (var t in n) Te2(e, t, { get: n[t], enumerable: true }); }; var je2 = (e, n, t) => In(e, typeof n != "symbol" ? n + "" : n, t); var ie2 = {}; Sn(ie2, { Graph: () => T2, alg: () => H }); var Mn = Object.defineProperty; var Se = (e, n) => { for (var t in n) Mn(e, t, { get: n[t], enumerable: true }); }; var Q2 = class { constructor(e) { this._isDirected = true, this._isMultigraph = false, this._isCompound = false, this._nodes = {}, this._in = {}, this._preds = {}, this._out = {}, this._sucs = {}, this._edgeObjs = {}, this._edgeLabels = {}, this._nodeCount = 0, this._edgeCount = 0, this._defaultNodeLabelFn = () => { }, this._defaultEdgeLabelFn = () => { }, e && (this._isDirected = "directed" in e ? e.directed : true, this._isMultigraph = "multigraph" in e ? e.multigraph : false, this._isCompound = "compound" in e ? e.compound : false), this._isCompound && (this._parent = {}, this._children = {}, this._children["\0"] = {}); } isDirected() { return this._isDirected; } isMultigraph() { return this._isMultigraph; } isCompound() { return this._isCompound; } setGraph(e) { return this._label = e, this; } graph() { return this._label; } setDefaultNodeLabel(e) { return typeof e != "function" ? this._defaultNodeLabelFn = () => e : this._defaultNodeLabelFn = e, this; } nodeCount() { return this._nodeCount; } nodes() { return Object.keys(this._nodes); } sources() { return this.nodes().filter((e) => Object.keys(this._in[e]).length === 0); } sinks() { return this.nodes().filter((e) => Object.keys(this._out[e]).length === 0); } setNodes(e, n) { return e.forEach((t) => { n !== void 0 ? this.setNode(t, n) : this.setNode(t); }), this; } setNode(e, n) { return e in this._nodes ? (arguments.length > 1 && (this._nodes[e] = n), this) : (this._nodes[e] = arguments.length > 1 ? n : this._defaultNodeLabelFn(e), this._isCompound && (this._parent[e] = "\0", this._children[e] = {}, this._children["\0"][e] = true), this._in[e] = {}, this._preds[e] = {}, this._out[e] = {}, this._sucs[e] = {}, ++this._nodeCount, this); } node(e) { return this._nodes[e]; } hasNode(e) { return e in this._nodes; } removeNode(e) { if (e in this._nodes) { let n = (t) => this.removeEdge(this._edgeObjs[t]); delete this._nodes[e], this._isCompound && (this._removeFromParentsChildList(e), delete this._parent[e], this.children(e).forEach((t) => { this.setParent(t); }), delete this._children[e]), Object.keys(this._in[e]).forEach(n), delete this._in[e], delete this._preds[e], Object.keys(this._out[e]).forEach(n), delete this._out[e], delete this._sucs[e], --this._nodeCount; } return this; } setParent(e, n) { if (!this._isCompound) throw new Error("Cannot set parent in a non-compound graph"); if (n === void 0) n = "\0"; else { n += ""; for (let t = n; t !== void 0; t = this.parent(t)) if (t === e) throw new Error("Setting " + n + " as parent of " + e + " would create a cycle"); this.setNode(n); } return this.setNode(e), this._removeFromParentsChildList(e), this._parent[e] = n, this._children[n][e] = true, this; } parent(e) { if (this._isCompound) { let n = this._parent[e]; if (n !== "\0") return n; } } children(e = "\0") { if (this._isCompound) { let n = this._children[e]; if (n) return Object.keys(n); } else { if (e === "\0") return this.nodes(); if (this.hasNode(e)) return []; } return []; } predecessors(e) { let n = this._preds[e]; if (n) return Object.keys(n); } successors(e) { let n = this._sucs[e]; if (n) return Object.keys(n); } neighbors(e) { let n = this.predecessors(e); if (n) { let t = new Set(n), r = this.successors(e); if (r) for (let o of r) t.add(o); return Array.from(t.values()); } } isLeaf(e) { var n; let t; return this.isDirected() ? t = this.successors(e) : t = this.neighbors(e), ((n = t == null ? void 0 : t.length) != null ? n : 0) === 0; } filterNodes(e) { let n = new this.constructor({ directed: this._isDirected, multigraph: this._isMultigraph, compound: this._isCompound }); n.setGraph(this.graph()), Object.entries(this._nodes).forEach(([o, i]) => { e(o) && n.setNode(o, i); }), Object.values(this._edgeObjs).forEach((o) => { n.hasNode(o.v) && n.hasNode(o.w) && n.setEdge(o, this.edge(o)); }); let t = {}, r = (o) => { let i = this.parent(o); return !i || n.hasNode(i) ? (t[o] = i, i) : i in t ? t[i] : r(i); }; return this._isCompound && n.nodes().forEach((o) => n.setParent(o, r(o))), n; } setDefaultEdgeLabel(e) { return typeof e != "function" ? this._defaultEdgeLabelFn = () => e : this._defaultEdgeLabelFn = e, this; } edgeCount() { return this._edgeCount; } edges() { return Object.values(this._edgeObjs); } setPath(e, n) { return e.reduce((t, r) => (n !== void 0 ? this.setEdge(t, r, n) : this.setEdge(t, r), r)), this; } setEdge(e, n, t, r) { let o, i, s, a, l3 = false; typeof e == "object" && e !== null && "v" in e ? (o = e.v, i = e.w, s = e.name, arguments.length === 2 && (a = n, l3 = true)) : (o = e, i = n, s = r, arguments.length > 2 && (a = t, l3 = true)), o = "" + o, i = "" + i, s !== void 0 && (s = "" + s); let u = z2(this._isDirected, o, i, s); if (u in this._edgeLabels) return l3 && (this._edgeLabels[u] = a), this; if (s !== void 0 && !this._isMultigraph) throw new Error("Cannot set a named edge when isMultigraph = false"); this.setNode(o), this.setNode(i), this._edgeLabels[u] = l3 ? a : this._defaultEdgeLabelFn(o, i, s); let d = Pn(this._isDirected, o, i, s); return o = d.v, i = d.w, Object.freeze(d), this._edgeObjs[u] = d, Re2(this._preds[i], o), Re2(this._sucs[o], i), this._in[i][u] = d, this._out[o][u] = d, this._edgeCount++, this; } edge(e, n, t) { let r = arguments.length === 1 ? oe2(this._isDirected, e) : z2(this._isDirected, e, n, t); return this._edgeLabels[r]; } edgeAsObj(e, n, t) { let r = arguments.length === 1 ? this.edge(e) : this.edge(e, n, t); return typeof r != "object" || r === null ? { label: r } : r; } hasEdge(e, n, t) { return (arguments.length === 1 ? oe2(this._isDirected, e) : z2(this._isDirected, e, n, t)) in this._edgeLabels; } removeEdge(e, n, t) { let r = arguments.length === 1 ? oe2(this._isDirected, e) : z2(this._isDirected, e, n, t), o = this._edgeObjs[r]; if (o) { let i = o.v, s = o.w; delete this._edgeLabels[r], delete this._edgeObjs[r], Ie2(this._preds[s], i), Ie2(this._sucs[i], s), delete this._in[s][r], delete this._out[i][r], this._edgeCount--; } return this; } inEdges(e, n) { return this.isDirected() ? this.filterEdges(this._in[e], e, n) : this.nodeEdges(e, n); } outEdges(e, n) { return this.isDirected() ? this.filterEdges(this._out[e], e, n) : this.nodeEdges(e, n); } nodeEdges(e, n) { if (e in this._nodes) return this.filterEdges({ ...this._in[e], ...this._out[e] }, e, n); } _removeFromParentsChildList(e) { delete this._children[this._parent[e]][e]; } filterEdges(e, n, t) { if (!e) return; let r = Object.values(e); return t ? r.filter((o) => o.v === n && o.w === t || o.v === t && o.w === n) : r; } }; function Re2(e, n) { e[n] ? e[n]++ : e[n] = 1; } function Ie2(e, n) { e[n] !== void 0 && !--e[n] && delete e[n]; } function z2(e, n, t, r) { let o = "" + n, i = "" + t; if (!e && o > i) { let s = o; o = i, i = s; } return o + "" + i + "" + (r === void 0 ? "\0" : r); } function Pn(e, n, t, r) { let o = "" + n, i = "" + t; if (!e && o > i) { let a = o; o = i, i = a; } let s = { v: o, w: i }; return r && (s.name = r), s; } function oe2(e, n) { return z2(e, n.v, n.w, n.name); } var Fn = {}; Se(Fn, { read: () => Yn, write: () => An }); function An(e) { let n = { options: { directed: e.isDirected(), multigraph: e.isMultigraph(), compound: e.isCompound() }, nodes: Vn(e), edges: Dn(e) }, t = e.graph(); return t !== void 0 && (n.value = structuredClone(t)), n; } function Vn(e) { return e.nodes().map((n) => { let t = e.node(n), r = e.parent(n), o = { v: n }; return t !== void 0 && (o.value = t), r !== void 0 && (o.parent = r), o; }); } function Dn(e) { return e.edges().map((n) => { let t = e.edge(n), r = { v: n.v, w: n.w }; return n.name !== void 0 && (r.name = n.name), t !== void 0 && (r.value = t), r; }); } function Yn(e) { let n = new Q2(e.options); return e.value !== void 0 && n.setGraph(e.value), e.nodes.forEach((t) => { n.setNode(t.v, t.value), t.parent && n.setParent(t.v, t.parent); }), e.edges.forEach((t) => { n.setEdge({ v: t.v, w: t.w, name: t.name }, t.value); }), n; } var H = {}; Se(H, { CycleException: () => K, bellmanFord: () => Me2, components: () => Xn, dijkstra: () => J2, dijkstraAll: () => qn, findCycles: () => $n, floydWarshall: () => Jn, isAcyclic: () => Qn, postorder: () => et2, preorder: () => nt2, prim: () => tt2, shortestPaths: () => rt2, tarjan: () => Fe2, topsort: () => Ae2 }); var Wn = () => 1; function Me2(e, n, t, r) { return Bn(e, String(n), t || Wn, r || function(o) { var i; return (i = e.outEdges(o)) != null ? i : []; }); } function Bn(e, n, t, r) { let o = {}, i, s = 0, a = e.nodes(), l3 = function(c) { let f2 = o[c.v], h2 = o[c.w]; if (!f2 || !h2) return; let p = t(c); f2.distance + p < h2.distance && (o[c.w] = { distance: f2.distance + p, predecessor: c.v }, i = true); }, u = function() { a.forEach(function(c) { r(c).forEach(function(f2) { let h2 = f2.v === c ? f2.v : f2.w, p = h2 === f2.v ? f2.w : f2.v; l3({ v: h2, w: p }); }); }); }; a.forEach(function(c) { let f2 = c === n ? 0 : Number.POSITIVE_INFINITY; o[c] = { distance: f2, predecessor: "" }; }); let d = a.length; for (let c = 1; c < d && (i = false, s++, u(), !!i); c++) ; if (s === d - 1 && (i = false, u(), i)) throw new Error("The graph contains a negative weight cycle"); return o; } function Xn(e) { let n = {}, t = [], r; function o(i) { var s, a; i in n || (n[i] = true, r.push(i), (s = e.successors(i)) == null || s.forEach(o), (a = e.predecessors(i)) == null || a.forEach(o)); } return e.nodes().forEach(function(i) { r = [], o(i), r.length && t.push(r); }), t; } var Pe = class { constructor() { this._arr = [], this._keyIndices = {}; } size() { return this._arr.length; } keys() { return this._arr.map((e) => e.key); } has(e) { return e in this._keyIndices; } priority(e) { let n = this._keyIndices[e]; if (n !== void 0) return this._arr[n].priority; } min() { if (this.size() === 0) throw new Error("Queue underflow"); return this._arr[0].key; } add(e, n) { let t = this._keyIndices, r = String(e); if (!(r in t)) { let o = this._arr, i = o.length; return t[r] = i, o.push({ key: r, priority: n }), this._decrease(i), true; } return false; } removeMin() { if (this.size() === 0) throw new Error("Queue underflow"); this._swap(0, this._arr.length - 1); let e = this._arr.pop(); return delete this._keyIndices[e.key], this._heapify(0), e.key; } decrease(e, n) { let t = this._keyIndices[e]; if (t === void 0) throw new Error(`Key not found: ${e}`); let r = this._arr[t].priority; if (n > r) throw new Error(`New priority is greater than current priority. Key: ${e} Old: ${r} New: ${n}`); this._arr[t].priority = n, this._decrease(t); } _heapify(e) { let n = this._arr, t = 2 * e, r = t + 1, o = e; t < n.length && (o = n[t].priority < n[o].priority ? t : o, r < n.length && (o = n[r].priority < n[o].priority ? r : o), o !== e && (this._swap(e, o), this._heapify(o))); } _decrease(e) { let n = this._arr, t = n[e].priority, r; for (; e !== 0 && (r = e >> 1, !(n[r].priority < t)); ) this._swap(e, r), e = r; } _swap(e, n) { let t = this._arr, r = this._keyIndices, o = t[e], i = t[n]; t[e] = i, t[n] = o, r[i.key] = e, r[o.key] = n; } }; var zn = () => 1; function J2(e, n, t, r) { let o = function(i) { var s; return (s = e.outEdges(i)) != null ? s : []; }; return Hn(e, String(n), t || zn, r || o); } function Hn(e, n, t, r) { let o = {}, i = new Pe(), s, a, l3 = function(u) { let d = u.v !== s ? u.v : u.w, c = o[d]; if (!c) return; let f2 = t(u), h2 = a.distance + f2; if (f2 < 0) throw new Error("dijkstra does not allow negative edge weights. Bad edge: " + u + " Weight: " + f2); h2 < c.distance && (c.distance = h2, c.predecessor = s, i.decrease(d, h2)); }; for (e.nodes().forEach(function(u) { let d = u === n ? 0 : Number.POSITIVE_INFINITY; o[u] = { distance: d, predecessor: "" }, i.add(u, d); }); i.size() > 0; ) { s = i.removeMin(); let u = o[s]; if (!u || u.distance === Number.POSITIVE_INFINITY) break; a = u, r(s).forEach(l3); } return o; } function qn(e, n, t) { return e.nodes().reduce(function(r, o) { return r[o] = J2(e, o, n, t), r; }, {}); } function Fe2(e) { let n = 0, t = [], r = {}, o = []; function i(s) { var a; let l3 = r[s] = { onStack: true, lowlink: n, index: n++ }; if (t.push(s), (a = e.successors(s)) == null || a.forEach(function(u) { if (u in r) { let d = r[u]; d != null && d.onStack && (l3.lowlink = Math.min(l3.lowlink, d.index)); } else { i(u); let d = r[u]; d && (l3.lowlink = Math.min(l3.lowlink, d.lowlink)); } }), l3.lowlink === l3.index) { let u = [], d; do { d = t.pop(); let c = r[d]; c && (c.onStack = false), u.push(d); } while (s !== d); o.push(u); } } return e.nodes().forEach(function(s) { s in r || i(s); }), o; } function $n(e) { return Fe2(e).filter(function(n) { var t; let r = n[0]; return r ? n.length > 1 || n.length === 1 && ((t = e.outEdges(r, r)) != null ? t : []).length > 0 : false; }); } var Un = () => 1; function Jn(e, n, t) { return Kn(e, n || Un, t || function(r) { var o; return (o = e.outEdges(r)) != null ? o : []; }); } function Kn(e, n, t) { let r = {}, o = e.nodes(); return o.forEach(function(i) { let s = {}; r[i] = s, s[i] = { distance: 0, predecessor: "" }, o.forEach(function(a) { i !== a && (s[a] = { distance: Number.POSITIVE_INFINITY, predecessor: "" }); }), t(i).forEach(function(a) { let l3 = a.v === i ? a.w : a.v, u = n(a); s[l3] = { distance: u, predecessor: i }; }); }), o.forEach(function(i) { let s = r[i]; s && o.forEach(function(a) { let l3 = r[a]; l3 && o.forEach(function(u) { let d = l3[i], c = s[u], f2 = l3[u]; if (d && c && f2) { let h2 = d.distance + c.distance; h2 < f2.distance && (f2.distance = h2, f2.predecessor = c.predecessor); } }); }); }), r; } var K = class extends Error { constructor(e) { super(e), this.name = "CycleException"; } }; function Ae2(e) { let n = {}, t = {}, r = []; function o(i) { var s; if (i in t) throw new K(); i in n || (t[i] = true, n[i] = true, (s = e.predecessors(i)) == null || s.forEach(o), delete t[i], r.push(i)); } if (e.sinks().forEach(o), Object.keys(n).length !== e.nodeCount()) throw new K(); return r; } function Qn(e) { try { Ae2(e); } catch (n) { if (n instanceof K) return false; throw n; } return true; } function Zn(e, n, t, r, o) { Array.isArray(n) || (n = [n]); let i = ((a) => { var l3; return (l3 = e.isDirected() ? e.successors(a) : e.neighbors(a)) != null ? l3 : []; }), s = {}; return n.forEach(function(a) { if (!e.hasNode(a)) throw new Error("Graph does not have node: " + a); o = Ve2(e, a, t === "post", s, i, r, o); }), o; } function Ve2(e, n, t, r, o, i, s) { return n in r || (r[n] = true, t || (s = i(s, n)), o(n).forEach(function(a) { s = Ve2(e, a, t, r, o, i, s); }), t && (s = i(s, n))), s; } function De2(e, n, t) { return Zn(e, n, t, function(r, o) { return r.push(o), r; }, []); } function et2(e, n) { return De2(e, n, "post"); } function nt2(e, n) { return De2(e, n, "pre"); } function tt2(e, n) { var t; let r = new Q2(), o = {}, i = new Pe(), s; function a(d) { let c = d.v === s ? d.w : d.v, f2 = i.priority(c); if (f2 !== void 0) { let h2 = n(d); h2 < f2 && (o[c] = s, i.decrease(c, h2)); } } if (e.nodeCount() === 0) return r; e.nodes().forEach(function(d) { i.add(d, Number.POSITIVE_INFINITY), r.setNode(d); }); let l3 = e.nodes()[0]; l3 !== void 0 && i.decrease(l3, 0); let u = false; for (; i.size() > 0; ) { if (s = i.removeMin(), s in o) r.setEdge(s, o[s]); else { if (u) throw new Error("Input graph is not connected: " + e); u = true; } (t = e.nodeEdges(s)) == null || t.forEach(a); } return r; } function rt2(e, n, t, r) { return ot2(e, n, t, r != null ? r : ((o) => { var i; return (i = e.outEdges(o)) != null ? i : []; })); } function ot2(e, n, t, r) { if (t === void 0) return J2(e, n, t, r); let o = false, i = e.nodes(); for (let s = 0; s < i.length; s++) { let a = i[s]; if (a === void 0) continue; let l3 = r(a); for (let u = 0; u < l3.length; u++) { let d = l3[u]; if (!d) continue; let c = d.v === a ? d.v : d.w, f2 = c === d.v ? d.w : d.v; t({ v: c, w: f2 }) < 0 && (o = true); } if (o) return Me2(e, n, t, r); } return J2(e, n, t, r); } var T2 = Q2; function M(e, n, t, r) { let o = r; for (; e.hasNode(o); ) o = $2(r); return t.dummy = n, e.setNode(o, t), o; } function Ye2(e) { let n = new T2().setGraph(e.graph()); return e.nodes().forEach((t) => n.setNode(t, e.node(t))), e.edges().forEach((t) => { let r = n.edge(t.v, t.w) || { weight: 0, minlen: 1 }, o = e.edge(t); n.setEdge(t.v, t.w, { weight: r.weight + o.weight, minlen: Math.max(r.minlen, o.minlen) }); }), n; } function Z(e) { let n = new T2({ multigraph: e.isMultigraph() }).setGraph(e.graph()); return e.nodes().forEach((t) => { e.children(t).length || n.setNode(t, e.node(t)); }), e.edges().forEach((t) => { n.setEdge(t, e.edge(t)); }), n; } function se2(e, n) { let t = e.x, r = e.y, o = n.x - t, i = n.y - r, s = e.width / 2, a = e.height / 2; if (!o && !i) throw new Error("Not possible to find intersection inside of the rectangle"); let l3, u; return Math.abs(i) * s > Math.abs(o) * a ? (i < 0 && (a = -a), l3 = a * o / i, u = a) : (o < 0 && (s = -s), l3 = s, u = s * i / o), { x: t + l3, y: r + u }; } function P2(e) { let n = A2(de2(e) + 1).map(() => []); return e.nodes().forEach((t) => { let r = e.node(t), o = r.rank; o !== void 0 && (n[o] || (n[o] = []), n[o][r.order] = t); }), n; } function We2(e) { let n = e.nodes().map((r) => { let o = e.node(r).rank; return o === void 0 ? Number.MAX_VALUE : o; }), t = R2(Math.min, n); e.nodes().forEach((r) => { let o = e.node(r); Object.hasOwn(o, "rank") && (o.rank -= t); }); } function Be2(e) { let n = e.nodes().map((s) => e.node(s).rank).filter((s) => s !== void 0), t = R2(Math.min, n), r = []; e.nodes().forEach((s) => { let a = e.node(s).rank - t; r[a] || (r[a] = []), r[a].push(s); }); let o = 0, i = e.graph().nodeRankFactor; Array.from(r).forEach((s, a) => { s === void 0 && a % i !== 0 ? --o : s !== void 0 && o && s.forEach((l3) => e.node(l3).rank += o); }); } function ae(e, n, t, r) { let o = { width: 0, height: 0 }; return arguments.length >= 4 && (o.rank = t, o.order = r), M(e, "border", o, n); } function it2(e, n = Xe2) { let t = []; for (let r = 0; r < e.length; r += n) { let o = e.slice(r, r + n); t.push(o); } return t; } var Xe2 = 65535; function R2(e, n) { if (n.length > Xe2) { let t = it2(n); return e(...t.map((r) => e(...r))); } else return e(...n); } function de2(e) { let t = e.nodes().map((r) => { let o = e.node(r).rank; return o === void 0 ? Number.MIN_VALUE : o; }); return R2(Math.max, t); } function ze2(e, n) { let t = { lhs: [], rhs: [] }; return e.forEach((r) => { n(r) ? t.lhs.push(r) : t.rhs.push(r); }), t; } function le2(e, n) { let t = Date.now(); try { return n(); } finally { console.log(e + " time: " + (Date.now() - t) + "ms"); } } function q2(e, n) { return n(); } var st2 = 0; function $2(e) { let n = ++st2; return e + ("" + n); } function A2(e, n, t = 1) { n == null && (n = e, e = 0); let r = (i) => i < n; t < 0 && (r = (i) => n < i); let o = []; for (let i = e; r(i); i += t) o.push(i); return o; } function B2(e, n) { let t = {}; for (let r of n) e[r] !== void 0 && (t[r] = e[r]); return t; } function X2(e, n) { let t; return typeof n == "string" ? t = (r) => r[n] : t = n, Object.entries(e).reduce((r, [o, i]) => (r[o] = t(i, o), r), {}); } function He2(e, n) { return e.reduce((t, r, o) => (t[r] = n[o], t), {}); } var D2 = "\0"; function ee2(e, n, t) { var u, d, c, f2, h2, p; if (!(e && n && t && n.dummy === "edge" && t.dummy === "edge" && n.edgeObj && t.edgeObj && e[n.edgeObj.v] && e[t.edgeObj.v] && e[n.edgeObj.w] && e[t.edgeObj.w])) return 0; let r = true; n.edgeObj.w === t.edgeObj.w && (r = false); let o = r ? (d = (u = e[n.edgeObj.v]) == null ? void 0 : u.rank) != null ? d : NaN + 1 : (f2 = (c = e[n.edgeObj.w]) == null ? void 0 : c.rank) != null ? f2 : NaN - 1, i = Object.entries(e).find((E2) => { var y2, L2; return ((y2 = E2[1].edgeObj) == null ? void 0 : y2.v) === n.edgeObj.v && ((L2 = E2[1].edgeObj) == null ? void 0 : L2.w) === n.edgeObj.w && E2[1].rank === o; }), s = Object.entries(e).find((E2) => { var y2, L2; return ((y2 = E2[1].edgeObj) == null ? void 0 : y2.v) === t.edgeObj.v && ((L2 = E2[1].edgeObj) == null ? void 0 : L2.w) === t.edgeObj.w && E2[1].rank === o; }); if (!i || !s) return 0; let a = (h2 = i[1].order) != null ? h2 : NaN, l3 = (p = s[1].order) != null ? p : NaN; return isNaN(a - l3) ? 0 : a - l3; } var ue2 = "3.1.1"; var ce2 = class { constructor() { je2(this, "_sentinel"); let n = {}; n._next = n._prev = n, this._sentinel = n; } dequeue() { let n = this._sentinel, t = n._prev; if (t !== n) return qe2(t), t; } enqueue(n) { let t = this._sentinel; n._prev && n._next && qe2(n), n._next = t._next, t._next._prev = n, t._next = n, n._prev = t; } toString() { let n = [], t = this._sentinel, r = t._prev; for (; r !== t; ) n.push(JSON.stringify(r, at2)), r = r._prev; return "[" + n.join(", ") + "]"; } }; function qe2(e) { e._prev._next = e._next, e._next._prev = e._prev, delete e._next, delete e._prev; } function at2(e, n) { if (e !== "_next" && e !== "_prev") return n; } var $e2 = ce2; var dt2 = () => 1; function be2(e, n) { if (e.nodeCount() <= 1) return []; let t = ut2(e, n || dt2); return lt2(t.graph, t.buckets, t.zeroIdx).flatMap((o) => e.outEdges(o.v, o.w) || []); } function lt2(e, n, t) { var a; let r = [], o = n[n.length - 1], i = n[0], s; for (; e.nodeCount(); ) { for (; s = i.dequeue(); ) fe2(e, n, t, s); for (; s = o.dequeue(); ) fe2(e, n, t, s); if (e.nodeCount()) { for (let l3 = n.length - 2; l3 > 0; --l3) if (s = (a = n[l3]) == null ? void 0 : a.dequeue(), s) { r = r.concat(fe2(e, n, t, s, true) || []); break; } } } return r; } function fe2(e, n, t, r, o) { let i = [], s = o ? i : void 0; return (e.inEdges(r.v) || []).forEach((a) => { let l3 = e.edge(a), u = e.node(a.v); o && i.push({ v: a.v, w: a.w }), u.out -= l3, he2(n, t, u); }), (e.outEdges(r.v) || []).forEach((a) => { let l3 = e.edge(a), u = a.w, d = e.node(u); d.in -= l3, he2(n, t, d); }), e.removeNode(r.v), s; } function ut2(e, n) { let t = new T2(), r = 0, o = 0; e.nodes().forEach((a) => { t.setNode(a, { v: a, in: 0, out: 0 }); }), e.edges().forEach((a) => { let l3 = t.edge(a.v, a.w) || 0, u = n(a), d = l3 + u; t.setEdge(a.v, a.w, d); let c = t.node(a.v), f2 = t.node(a.w); o = Math.max(o, c.out += u), r = Math.max(r, f2.in += u); }); let i = ct2(o + r + 3).map(() => new $e2()), s = r + 1; return t.nodes().forEach((a) => { he2(i, s, t.node(a)); }), { graph: t, buckets: i, zeroIdx: s }; } function he2(e, n, t) { var r, o, i; t.out ? t.in ? (i = e[t.out - t.in + n]) == null || i.enqueue(t) : (o = e[e.length - 1]) == null || o.enqueue(t) : (r = e[0]) == null || r.enqueue(t); } function ct2(e) { let n = []; for (let t = 0; t < e; t++) n.push(t); return n; } function Ue2(e, n) { (e.graph().acyclicer === "greedy" ? be2(e, r(e)) : ft2(e, n != null ? n : null)).forEach((o) => { let i = e.edge(o); e.removeEdge(o), i.forwardName = o.name, i.reversed = true, e.setEdge(o.w, o.v, i, $2("rev")); }); function r(o) { return (i) => o.edge(i).weight; } } function ft2(e, n) { let t = [], r = {}, o = {}; function i(l3) { Object.hasOwn(o, l3) || (o[l3] = true, r[l3] = true, e.outEdges(l3).forEach((u) => { Object.hasOwn(r, u.w) ? t.push(u) : i(u.w); }), delete r[l3]); } function s(l3) { var u; Object.hasOwn(o, l3) || (o[l3] = true, r[l3] = true, (u = e.outEdges(l3)) == null || u.forEach((d) => { var c, f2; Object.hasOwn(r, d.w) || ((c = n.node(l3)) == null ? void 0 : c.rank) > ((f2 = n.node(d.w)) == null ? void 0 : f2.rank) && ht2(e, d.w, d) ? t.push(d) : s(d.w); }), delete r[l3]); } let a = i; return n && typeof n.node == "function" && (a = s), e.sources().forEach(a), e.nodes().forEach(a), t; } function Je2(e) { e.edges().forEach((n) => { let t = e.edge(n); if (t.reversed) { e.removeEdge(n); let r = t.forwardName; delete t.reversed, delete t.forwardName, e.setEdge(n.w, n.v, t, r); } }); } function ht2(e, n, t) { let r = /* @__PURE__ */ new Set(); function o(i) { var s; if (e.sources().includes(i)) return true; r.add(i); for (let a of (s = e.inEdges(i)) != null ? s : []) if (!(a.v === t.v && a.w === t.w) && !r.has(a.v) && o(a.v)) return true; return false; } return o(n); } function Ke2(e) { e.graph().dummyChains = [], e.edges().forEach((n) => gt2(e, n)); } function gt2(e, n) { let t = n.v, r = e.node(t).rank, o = n.w, i = e.node(o).rank, s = n.name, a = e.edge(n), l3 = a.labelRank; if (i === r + 1) return; e.removeEdge(n); let u, d, c; for (c = 0, ++r; r < i; ++c, ++r) a.points = [], d = { width: 0, height: 0, edgeLabel: a, edgeObj: n, rank: r }, u = M(e, "edge", d, "_d"), r === l3 && (d.width = a.width, d.height = a.height, d.dummy = "edge-label", d.labelpos = a.labelpos), e.setEdge(t, u, { weight: a.weight }, s), c === 0 && e.graph().dummyChains.push(u), t = u; e.setEdge(t, o, { weight: a.weight }, s); } function Qe2(e) { e.graph().dummyChains.forEach((n) => { let t = e.node(n), r = t.edgeLabel, o; for (e.setEdge(t.edgeObj, r); t.dummy; ) o = e.successors(n)[0], e.removeNode(n), r.points.push({ x: t.x, y: t.y }), t.dummy === "edge-label" && (r.x = t.x, r.y = t.y, r.width = t.width, r.height = t.height), n = o, t = e.node(n); }); } function U2(e) { let n = {}; function t(r) { let o = e.node(r); if (Object.hasOwn(n, r)) return o.rank; n[r] = true; let i = e.outEdges(r), s = i ? i.map((l3) => l3 == null ? Number.POSITIVE_INFINITY : t(l3.w) - e.edge(l3).minlen) : [], a = R2(Math.min, s); return a === Number.POSITIVE_INFINITY && (a = 0), o.rank = a; } e.sources().forEach(t); } function V2(e, n) { return e.node(n.w).rank - e.node(n.v).rank - e.edge(n).minlen; } var ne2 = mt2; function mt2(e) { let n = new T2({ directed: false }), t = e.nodes(); if (t.length === 0) throw new Error("Graph must have at least one node"); let r = t[0], o = e.nodeCount(); n.setNode(r, {}); let i, s; for (; Et(n, e) < o && (i = Lt(n, e), !!i); ) s = n.hasNode(i.v) ? V2(e, i) : -V2(e, i), yt2(n, e, s); return n; } function Et(e, n) { function t(r) { let o = n.nodeEdges(r); o && o.forEach((i) => { let s = i.v, a = r === s ? i.w : s; !e.hasNode(a) && !V2(n, i) && (e.setNode(a, {}), e.setEdge(r, a, {}), t(a)); }); } return e.nodes().forEach(t), e.nodeCount(); } function Lt(e, n) { return n.edges().reduce((r, o) => { let i = Number.POSITIVE_INFINITY; return e.hasNode(o.v) !== e.hasNode(o.w) && (i = V2(n, o)), i < r[0] ? [i, o] : r; }, [Number.POSITIVE_INFINITY, null])[1]; } function yt2(e, n, t) { e.nodes().forEach((r) => n.node(r).rank += t); } var { preorder: wt2, postorder: Nt } = H; var en = Y2; Y2.initLowLimValues = pe2; Y2.initCutValues = ge2; Y2.calcCutValue = nn; Y2.leaveEdge = rn; Y2.enterEdge = on; Y2.exchangeEdges = sn; function Y2(e) { e = Ye2(e), U2(e); let n = ne2(e); pe2(n), ge2(n, e); let t, r; for (; t = rn(n); ) r = on(n, e, t), sn(n, e, t, r); } function ge2(e, n) { let t = Nt(e, e.nodes()); t = t.slice(0, t.length - 1), t.forEach((r) => Gt(e, n, r)); } function Gt(e, n, t) { let o = e.node(t).parent, i = e.edge(t, o); i.cutvalue = nn(e, n, t); } function nn(e, n, t) { let o = e.node(t).parent, i = true, s = n.edge(t, o), a = 0; s || (i = false, s = n.edge(o, t)), a = s.weight; let l3 = n.nodeEdges(t); return l3 && l3.forEach((u) => { let d = u.v === t, c = d ? u.w : u.v; if (c !== o) { let f2 = d === i, h2 = n.edge(u).weight; if (a += f2 ? h2 : -h2, kt2(e, t, c)) { let E2 = e.edge(t, c).cutvalue; a += f2 ? -E2 : E2; } } }), a; } function pe2(e, n) { arguments.length < 2 && (n = e.nodes()[0]), tn(e, {}, 1, n); } function tn(e, n, t, r, o) { let i = t, s = e.node(r); n[r] = true; let a = e.neighbors(r); return a && a.forEach((l3) => { Object.hasOwn(n, l3) || (t = tn(e, n, t, l3, r)); }), s.low = i, s.lim = t++, o ? s.parent = o : delete s.parent, t; } function rn(e) { return e.edges().find((n) => e.edge(n).cutvalue < 0); } function on(e, n, t) { let r = t.v, o = t.w; n.hasEdge(r, o) || (r = t.w, o = t.v); let i = e.node(r), s = e.node(o), a = i, l3 = false; return i.lim > s.lim && (a = s, l3 = true), n.edges().filter((d) => l3 === Ze2(e, e.node(d.v), a) && l3 !== Ze2(e, e.node(d.w), a)).reduce((d, c) => V2(n, c) < V2(n, d) ? c : d); } function sn(e, n, t, r) { let o = t.v, i = t.w; e.removeEdge(o, i), e.setEdge(r.v, r.w, {}), pe2(e), ge2(e, n), vt(e, n); } function vt(e, n) { let t = e.nodes().find((o) => !e.node(o).parent); if (!t) return; let r = wt2(e, [t]); r = r.slice(1), r.forEach((o) => { let s = e.node(o).parent, a = n.edge(o, s), l3 = false; a || (a = n.edge(s, o), l3 = true), n.node(o).rank = n.node(s).rank + (l3 ? a.minlen : -a.minlen); }); } function kt2(e, n, t) { return e.hasEdge(n, t); } function Ze2(e, n, t) { return t.low <= n.lim && n.lim <= t.lim; } var dn = xt2; function xt2(e) { let n = e.graph().ranker; if (typeof n == "function") return n(e); switch (n) { case "network-simplex": an(e); break; case "tight-tree": Ot2(e); break; case "longest-path": _t(e); break; case "none": break; default: an(e); } } var _t = U2; function Ot2(e) { U2(e), ne2(e); } function an(e) { en(e); } var ln = Ct; function Ct(e) { let n = jt(e), t = e.graph(); if (!Array.isArray(t.dummyChains)) return; t.dummyChains.forEach((o) => { let i = e.node(o), s = i.edgeObj, a = Tt2(e, n, s.v, s.w), l3 = a.path, u = a.lca, d = 0, c = l3[d], f2 = true; for (; o !== s.w; ) { if (i = e.node(o), f2) { for (; (c = l3[d]) !== u && e.node(c).maxRank < i.rank; ) d++; c === u && (f2 = false); } if (!f2) { for (; d < l3.length - 1 && e.node(l3[d + 1]).minRank <= i.rank; ) d++; c = l3[d]; } c !== void 0 && e.setParent(o, c), o = e.successors(o)[0]; } }); } function Tt2(e, n, t, r) { let o = [], i = [], s = Math.min(n[t].low, n[r].low), a = Math.max(n[t].lim, n[r].lim), l3; l3 = t; do l3 = e.parent(l3), o.push(l3); while (l3 && (n[l3].low > s || a > n[l3].lim)); let u = l3, d = r; for (; (d = e.parent(d)) !== u; ) i.push(d); return { path: o.concat(i.reverse()), lca: u }; } function jt(e) { let n = {}, t = 0; function r(o) { let i = t; e.children(o).forEach(r), n[o] = { low: i, lim: t++ }; } return e.children(D2).forEach(r), n; } function un(e) { let n = M(e, "root", {}, "_root"), t = Rt2(e), r = Object.values(t), o = R2(Math.max, r) - 1, i = 2 * o + 1; e.graph().nestingRoot = n, e.edges().forEach((a) => e.edge(a).minlen *= i); let s = It(e) + 1; e.children(D2).forEach((a) => { cn(e, n, i, s, o, t, a); }), e.graph().nodeRankFactor = i; } function cn(e, n, t, r, o, i, s) { var c; let a = e.children(s); if (!a.length) { s !== n && e.setEdge(n, s, { weight: 0, minlen: t }); return; } let l3 = ae(e, "_bt"), u = ae(e, "_bb"), d = e.node(s); e.setParent(l3, s), d.borderTop = l3, e.setParent(u, s), d.borderBottom = u, a.forEach((f2) => { var b; cn(e, n, t, r, o, i, f2); let h2 = e.node(f2), p = h2.borderTop ? h2.borderTop : f2, E2 = h2.borderBottom ? h2.borderBottom : f2, y2 = h2.borderTop ? r : 2 * r, L2 = p !== E2 ? 1 : o - ((b = i[s]) != null ? b : 0) + 1; e.setEdge(l3, p, { weight: y2, minlen: L2, nestingEdge: true }), e.setEdge(E2, u, { weight: y2, minlen: L2, nestingEdge: true }); }), e.parent(s) || e.setEdge(n, l3, { weight: 0, minlen: o + ((c = i[s]) != null ? c : 0) }); } function Rt2(e) { let n = {}; function t(r, o) { let i = e.children(r); i && i.length && i.forEach((s) => t(s, o + 1)), n[r] = o; } return e.children(D2).forEach((r) => t(r, 1)), n; } function It(e) { return e.edges().reduce((n, t) => n + e.edge(t).weight, 0); } function fn2(e) { let n = e.graph(); e.removeNode(n.nestingRoot), delete n.nestingRoot, e.edges().forEach((t) => { e.edge(t).nestingEdge && e.removeEdge(t); }); } var bn = Mt; function Mt(e) { function n(t) { let r = e.children(t), o = e.node(t); if (r.length && r.forEach(n), o && Object.hasOwn(o, "minRank")) { o.borderLeft = [], o.borderRight = []; for (let i = o.minRank, s = o.maxRank + 1; i < s; ++i) hn(e, "borderLeft", "_bl", t, o, i), hn(e, "borderRight", "_br", t, o, i); } } e.children(D2).forEach(n); } function hn(e, n, t, r, o, i) { let s = { width: 0, height: 0, rank: i, borderType: n }, a = o[n][i - 1], l3 = M(e, "border", s, t); o[n][i] = l3, e.setParent(l3, r), a && e.setEdge(a, l3, { weight: 1 }); } function pn(e) { var t; let n = (t = e.graph().rankdir) == null ? void 0 : t.toLowerCase(); (n === "lr" || n === "rl") && En(e); } function mn2(e) { var t; let n = (t = e.graph().rankdir) == null ? void 0 : t.toLowerCase(); (n === "bt" || n === "rl") && Pt2(e), (n === "lr" || n === "rl") && (Ft(e), En(e)); } function En(e) { e.nodes().forEach((n) => gn2(e.node(n))), e.edges().forEach((n) => gn2(e.edge(n))); } function gn2(e) { let n = e.width; e.width = e.height, e.height = n; } function Pt2(e) { e.nodes().forEach((n) => me2(e.node(n))), e.edges().forEach((n) => { var r; let t = e.edge(n); (r = t.points) == null || r.forEach(me2), Object.hasOwn(t, "y") && me2(t); }); } function me2(e) { e.y = -e.y; } function Ft(e) { e.nodes().forEach((n) => Ee2(e.node(n))), e.edges().forEach((n) => { var r; let t = e.edge(n); (r = t.points) == null || r.forEach(Ee2), Object.hasOwn(t, "x") && Ee2(t); }); } function Ee2(e) { let n = e.x; e.x = e.y, e.y = n; } function Le2(e, n = null) { let t = {}, r = e.nodes().filter((d) => !e.children(d).length), o = r.map((d) => e.node(d).rank), i = R2(Math.max, o), s = A2(i + 1).map(() => []); function a(d) { if (t[d]) return; t[d] = true; let c = e.node(d); s[c.rank].push(d); let f2 = e.successors(d); f2 && [...f2].sort((p, E2) => u(p, E2)).forEach(a); } r.sort((d, c) => e.node(d).rank - e.node(c).rank).forEach(a); function u(d, c) { let f2 = e.node(d), h2 = e.node(c); return ee2(n, f2, h2); } return s; } function ye(e, n) { let t = 0; for (let r = 1; r < n.length; ++r) t += Vt(e, n[r - 1], n[r]); return t; } function Vt(e, n, t) { let r = He2(t, t.map((u, d) => d)), o = n.flatMap((u) => { let d = e.outEdges(u); return d ? d.map((c) => ({ pos: r[c.w], weight: e.edge(c).weight })).sort((c, f2) => c.pos - f2.pos) : []; }), i = 1; for (; i < t.length; ) i <<= 1; let s = 2 * i - 1; i -= 1; let a = new Array(s).fill(0), l3 = 0; return o.forEach((u) => { let d = u.pos + i; a[d] += u.weight; let c = 0; for (; d > 0; ) d % 2 && (c += a[d + 1]), d = d - 1 >> 1, a[d] += u.weight; l3 += u.weight * c; }), l3; } function we2(e, n = []) { return n.map((t) => { let r = e.inEdges(t); if (!r || !r.length) return { v: t }; { let o = r.reduce((i, s) => { let a = e.edge(s), l3 = e.node(s.v); return { sum: i.sum + a.weight * l3.order, weight: i.weight + a.weight }; }, { sum: 0, weight: 0 }); return { v: t, barycenter: o.sum / o.weight, weight: o.weight }; } }); } function Ne2(e, n) { let t = {}; e.forEach((o, i) => { let s = { indegree: 0, in: [], out: [], vs: [o.v], i }; o.barycenter !== void 0 && (s.barycenter = o.barycenter, s.weight = o.weight), t[o.v] = s; }), n.edges().forEach((o) => { let i = t[o.v], s = t[o.w]; i !== void 0 && s !== void 0 && (s.indegree++, i.out.push(s)); }); let r = Object.values(t).filter((o) => !o.indegree); return Dt(r); } function Dt(e) { let n = []; function t(o) { return (i) => { i.merged || (i.barycenter === void 0 || o.barycenter === void 0 || i.barycenter >= o.barycenter) && Yt(o, i); }; } function r(o) { return (i) => { i.in.push(o), --i.indegree === 0 && e.push(i); }; } for (; e.length; ) { let o = e.pop(); n.push(o), o.in.reverse().forEach(t(o)), o.out.forEach(r(o)); } return n.filter((o) => !o.merged).map((o) => B2(o, ["vs", "i", "barycenter", "weight"])); } function Yt(e, n) { let t = 0, r = 0; e.weight && (t += e.barycenter * e.weight, r += e.weight), n.weight && (t += n.barycenter * n.weight, r += n.weight), e.vs = n.vs.concat(e.vs), e.barycenter = t / r, e.weight = r, e.i = Math.min(n.i, e.i), n.merged = true; } function Ge2(e, n, t, r, o) { let i = {}, s = null, a = null, l3 = o; typeof n == "boolean" ? (l3 = n, i = {}) : n && (i = n, s = t != null ? t : null, a = r != null ? r : null); let u = ze2(e, (L2) => Object.hasOwn(L2, "barycenter")), d = u.lhs, c = u.rhs.sort((L2, b) => b.i - L2.i), f2 = [], h2 = 0, p = 0, E2 = 0; d.sort(Wt(a, s, !!l3)); for (let [L2, b] of Object.entries(i)) { let g = d.findIndex((m) => m.vs[0] === L2); d.splice(g + 1, 0, b); } E2 = Ln(f2, c, E2), d.forEach((L2) => { E2 += L2.vs.length, f2.push(L2.vs), h2 += L2.barycenter * L2.weight, p += L2.weight, E2 = Ln(f2, c, E2); }); let y2 = { vs: f2.flat(1) }; return p && (y2.barycenter = h2 / p, y2.weight = p), y2; } function Ln(e, n, t) { let r; for (; n.length && (r = n[n.length - 1]).i <= t; ) n.pop(), e.push(r.vs), t++; return t; } function Wt(e, n, t) { return (r, o) => { if (r.barycenter < o.barycenter) return -1; if (r.barycenter > o.barycenter) return 1; if (e && (typeof r.vs[0] == "string" || typeof o.vs[0] == "string")) { let i = e.node(r.vs[0]), s = e.node(o.vs[0]), a = ee2(n, i, s); if (a !== 0) return a; } return t ? o.i - r.i : r.i - o.i; }; } function te2(e, n, t, r, o) { var L2, b, g, m, w, k2, _2, C2, j2, I2, S2; let i = null, s = o; typeof r == "boolean" ? (s = r, i = null) : r !== void 0 && (i = r); let a = e.children(n), l3 = e.node(n), u = l3 ? l3.borderLeft : void 0, d = l3 ? l3.borderRight : void 0, c = {}; u && (a = a.filter((G2) => G2 !== u && G2 !== d)); let f2 = we2(e, a); f2.forEach((G2) => { if (e.children(G2.v).length) { let { result: x2 } = te2(e, G2.v, t, i, s); c[G2.v] = x2, Object.hasOwn(x2, "barycenter") && Xt(G2, x2); } }); let h2 = Ne2(f2, t); Bt(h2, c); let p = {}, E2 = false; for (let G2 = 0; G2 < h2.length; G2++) for (let x2 = G2 + 1; x2 < h2.length; x2++) if (!(!h2[G2] || !h2[x2] || !((L2 = h2[G2]) != null && L2.barycenter) || !((b = h2[x2]) != null && b.barycenter)) && ((g = h2[G2]) == null ? void 0 : g.barycenter) === h2[x2].barycenter) { let v = (w = (m = h2[G2]) == null ? void 0 : m.vs[0]) != null ? w : "", N2 = (_2 = (k2 = h2[x2]) == null ? void 0 : k2.vs[0]) != null ? _2 : "", O2 = e.node(v), W2 = e.node(N2); if (O2.dummy === "edge" && W2.dummy === "edge" && ((C2 = O2.edgeObj) == null ? void 0 : C2.v) === ((j2 = W2.edgeObj) == null ? void 0 : j2.v) && ((I2 = O2.edgeObj) == null ? void 0 : I2.w) === ((S2 = W2.edgeObj) == null ? void 0 : S2.w)) if (O2.edgeLabel.reversed) { p[N2] = h2[G2], h2.splice(G2, 1), G2--; break; } else p[v] = h2[x2], h2.splice(x2, 1), x2--; else E2 = true; } let y2 = Ge2(h2, p, i, e, s); if (u && d) { y2.vs = [u, y2.vs, d].flat(1); let G2 = e.predecessors(u); if (G2 && G2.length) { let x2 = e.node(G2[0]), v = e.predecessors(d), N2 = e.node(v[0]); Object.hasOwn(y2, "barycenter") || (y2.barycenter = 0, y2.weight = 0), y2.barycenter = (y2.barycenter * y2.weight + x2.order + N2.order) / (y2.weight + 2), y2.weight += 2; } } return Object.defineProperty(y2, "result", { value: y2, enumerable: false, configurable: true, writable: true }), Object.defineProperty(y2, "usedBias", { value: E2, enumerable: false, configurable: true, writable: true }), y2; } function Bt(e, n) { e.forEach((t) => { t.vs = t.vs.flatMap((r) => n[r] ? n[r].vs : r); }); } function Xt(e, n) { e.barycenter !== void 0 ? (e.barycenter = (e.barycenter * e.weight + n.barycenter * n.weight) / (e.weight + n.weight), e.weight += n.weight) : (e.barycenter = n.barycenter, e.weight = n.weight); } function ve2(e, n, t, r) { r || (r = e.nodes()); let o = zt(e), i = new T2({ compound: true }).setGraph({ root: o }).setDefaultNodeLabel((s) => e.node(s)); return r.forEach((s) => { let a = e.node(s), l3 = e.parent(s); if (a.rank === n || a.minRank <= n && n <= a.maxRank) { i.setNode(s), i.setParent(s, l3 || o); let u = e[t](s); u && u.forEach((d) => { let c = d.v === s ? d.w : d.v, f2 = i.edge(c, s), h2 = f2 !== void 0 ? f2.weight : 0; i.setEdge(c, s, { weight: e.edge(d).weight + h2 }); }), Object.hasOwn(a, "minRank") && i.setNode(s, { borderLeft: a.borderLeft[n], borderRight: a.borderRight[n] }); } }), i; } function zt(e) { let n; for (; e.hasNode(n = $2("_root")); ) ; return n; } function ke2(e, n, t) { let r = {}, o; t.forEach((i) => { let s = e.parent(i), a, l3; for (; s; ) { if (a = e.parent(s), a ? (l3 = r[a], r[a] = s) : (l3 = o, o = s), l3 && l3 !== s) { n.setEdge(l3, s); return; } s = a; } }); } function re2(e, n = {}, t = null) { if (typeof n.customOrder == "function") { n.customOrder(e, re2); return; } let r = de2(e), o = yn(e, A2(1, r + 1), "inEdges"), i = yn(e, A2(r - 1, -1, -1), "outEdges"), s = Le2(e, t); if (wn(e, s), n.disableOptimalOrderHeuristic) return; let a = Number.POSITIVE_INFINITY, l3, u = n.constraints || []; for (let d = 0, c = 0; c < 4; ++d, ++c) { Ht(d % 2 ? o : i, d % 4 >= 2, u, t), s = P2(e); let f2 = ye(e, s); f2 < a ? (c = 0, l3 = Object.assign({}, s), a = f2) : f2 === a && (l3 = structuredClone(s)); } wn(e, l3); } function yn(e, n, t) { let r = /* @__PURE__ */ new Map(), o = (i, s) => { r.has(i) || r.set(i, []), r.get(i).push(s); }; for (let i of e.nodes()) { let s = e.node(i); if (typeof s.rank == "number" && o(s.rank, i), typeof s.minRank == "number" && typeof s.maxRank == "number") for (let a = s.minRank; a <= s.maxRank; a++) a !== s.rank && o(a, i); } return n.map(function(i) { return ve2(e, i, t, r.get(i) || []); }); } function Ht(e, n, t, r) { let o = true, i = new T2(); e.forEach(function(s) { t.forEach((d) => i.setEdge(d.left, d.right)); let a = s.graph().root, { result: l3, usedBias: u } = te2(s, a, i, r, o); n && u && (o = !o), l3.vs.forEach((d, c) => s.node(d).order = c), ke2(s, i, l3.vs); }); } function wn(e, n) { Object.values(n).forEach((t) => t.forEach((r, o) => e.node(r).order = o)); } function qt(e, n) { let t = {}; function r(o, i) { let s = 0, a = 0, l3 = o.length, u = i[i.length - 1]; return i.forEach((d, c) => { let f2 = Ut(e, d), h2 = f2 ? e.node(f2).order : l3; (f2 || d === u) && (i.slice(a, c + 1).forEach((p) => { let E2 = e.predecessors(p); E2 && E2.forEach((y2) => { let L2 = e.node(y2), b = L2.order; (b < s || h2 < b) && !(L2.dummy && e.node(p).dummy) && Gn(t, y2, p); }); }), a = c + 1, s = h2); }), i; } return n.length && n.reduce(r), t; } function $t(e, n) { let t = {}; function r(i, s, a, l3, u) { A2(s, a).forEach((d) => { let c = i[d]; if (c !== void 0 && e.node(c).dummy) { let f2 = e.predecessors(c); f2 && f2.forEach((h2) => { if (h2 === void 0) return; let p = e.node(h2); p.dummy && (p.order < l3 || p.order > u) && Gn(t, h2, c); }); } }); } function o(i, s) { let a = -1, l3 = -1, u = 0; return s.forEach((d, c) => { if (e.node(d).dummy === "border") { let f2 = e.predecessors(d); if (f2 && f2.length) { let h2 = f2[0]; if (h2 === void 0) return; l3 = e.node(h2).order, r(s, u, c, a, l3), u = c, a = l3; } } r(s, u, s.length, l3, i.length); }), s; } return n.length && n.reduce(o), t; } function Ut(e, n) { if (e.node(n).dummy) { let t = e.predecessors(n); if (t) return t.find((r) => e.node(r).dummy); } } function Gn(e, n, t) { if (n > t) { let o = n; n = t, t = o; } let r = e[n]; r || (e[n] = r = {}), r[t] = true; } function Jt(e, n, t) { if (n > t) { let o = n; n = t, t = o; } let r = e[n]; return r !== void 0 && Object.hasOwn(r, t); } function Kt(e, n, t, r, o) { let i = {}, s = {}, a = {}; return n.forEach((l3) => { l3.forEach((u, d) => { i[u] = u, s[u] = u, a[u] = d; }); }), n.forEach((l3) => { let u = -1, d = -1, c = false, f2 = l3, h2 = l3.findIndex((p) => (o == null ? void 0 : o.includes(p)) || Nn(p, e, o)); h2 > 0 && (f2 = [l3[h2], ...l3.slice(0, h2), ...l3.slice(h2 + 1)], c = true), f2.forEach((p) => { var y2; let E2 = r(p); if (E2 && E2.length) { o != null && o.includes(p) && (E2 = E2.filter((g) => Nn(g, e, o))); let L2 = E2.sort((g, m) => { let w = a[g], k2 = a[m]; return (w !== void 0 ? w : 0) - (k2 !== void 0 ? k2 : 0); }), b = (L2.length - 1) / 2; for (let g = Math.floor(b), m = Math.ceil(b); g <= m; ++g) { let w = L2[g]; if (w === void 0) continue; let k2 = a[w]; if (k2 !== void 0 && s[p] === p && u < k2 && a[w] !== d && !Jt(t, p, w)) { let _2 = i[w]; _2 !== void 0 && (s[w] = p, s[p] = i[p] = _2, u = k2, c && (u = -1, d = (y2 = a[w]) != null ? y2 : -1, c = false)); } } } }); }), { root: i, align: s }; } function Qt(e, n, t, r, o = false) { let i = {}, s = Zt(e, n, t, o), a = o ? "borderLeft" : "borderRight"; function l3(h2, p) { let E2 = s.nodes().slice(), y2 = {}, L2 = E2.pop(); for (; L2; ) { if (y2[L2]) h2(L2); else { y2[L2] = true, E2.push(L2); for (let b of p(L2)) E2.push(b); } L2 = E2.pop(); } } function u(h2) { let p = s.inEdges(h2); p ? i[h2] = p.reduce((E2, y2) => { var g; let L2 = (g = i[y2.v]) != null ? g : 0, b = s.edge(y2); return Math.max(E2, L2 + (b !== void 0 ? b : 0)); }, 0) : i[h2] = 0; } function d(h2) { let p = s.outEdges(h2), E2 = Number.POSITIVE_INFINITY; p && (E2 = p.reduce((L2, b) => { let g = i[b.w], m = s.edge(b); return Math.min(L2, (g !== void 0 ? g : 0) - (m !== void 0 ? m : 0)); }, Number.POSITIVE_INFINITY)); let y2 = e.node(h2); E2 !== Number.POSITIVE_INFINITY && y2.borderType !== a && (i[h2] = Math.max(i[h2] !== void 0 ? i[h2] : 0, E2)); } function c(h2) { return s.predecessors(h2) || []; } function f2(h2) { return s.successors(h2) || []; } return l3(u, c), l3(d, f2), Object.keys(r).forEach((h2) => { var E2; let p = t[h2]; p !== void 0 && (i[h2] = (E2 = i[p]) != null ? E2 : 0); }), i; } function Zt(e, n, t, r) { let o = new T2(), i = e.graph(), s = rr(i.nodesep, i.edgesep, r); return n.forEach((a) => { let l3; a.forEach((u) => { let d = t[u]; if (d !== void 0) { if (o.setNode(d), l3 !== void 0) { let c = t[l3]; if (c !== void 0) { let f2 = o.edge(c, d); o.setEdge(c, d, Math.max(s(e, u, l3), f2 || 0)); } } l3 = u; } }); }), o; } function er(e, n) { return Object.values(n).reduce((t, r) => { let o = Number.NEGATIVE_INFINITY, i = Number.POSITIVE_INFINITY; Object.entries(r).forEach(([a, l3]) => { let u = or(e, a) / 2; o = Math.max(l3 + u, o), i = Math.min(l3 - u, i); }); let s = o - i; return s < t[0] && (t = [s, r]), t; }, [Number.POSITIVE_INFINITY, null])[1]; } function nr(e, n) { let t = Object.values(n), r = R2(Math.min, t), o = R2(Math.max, t); ["u", "d"].forEach((i) => { ["l", "r"].forEach((s) => { let a = i + s, l3 = e[a]; if (!l3 || l3 === n) return; let u = Object.values(l3), d = r - R2(Math.min, u); s !== "l" && (d = o - R2(Math.max, u)), d && (e[a] = X2(l3, (c) => c + d)); }); }); } function tr(e, n = void 0) { let t = e.ul; return t ? X2(t, (r, o) => { var s, a; if (n) { let l3 = n.toLowerCase(), u = e[l3]; if (u && u[o] !== void 0) return u[o]; } let i = Object.values(e).map((l3) => { let u = l3[o]; return u !== void 0 ? u : 0; }).sort((l3, u) => l3 - u); return (((s = i[1]) != null ? s : 0) + ((a = i[2]) != null ? a : 0)) / 2; }) : {}; } function vn(e, n) { let t = P2(e), r = Object.assign(qt(e, t), $t(e, t)), o = {}, i; ["u", "d"].forEach((a) => { i = a === "u" ? t : Object.values(t).reverse(), ["l", "r"].forEach((l3) => { l3 === "r" && (i = i.map((f2) => Object.values(f2).reverse())); let d = Kt(e, i, r, (f2) => (a === "u" ? e.predecessors(f2) : e.successors(f2)) || [], n), c = Qt(e, i, d.root, d.align, l3 === "r"); l3 === "r" && (c = X2(c, (f2) => -f2)), o[a + l3] = c; }); }); let s = er(e, o); return nr(o, s), tr(o, e.graph().align); } function rr(e, n, t) { return (r, o, i) => { let s = r.node(o), a = r.node(i), l3 = 0, u; if (l3 += s.width / 2, Object.hasOwn(s, "labelpos")) switch (s.labelpos.toLowerCase()) { case "l": u = -s.width / 2; break; case "r": u = s.width / 2; break; } if (u && (l3 += t ? u : -u), u = void 0, l3 += (s.dummy ? n : e) / 2, l3 += (a.dummy ? n : e) / 2, l3 += a.width / 2, Object.hasOwn(a, "labelpos")) switch (a.labelpos.toLowerCase()) { case "l": u = a.width / 2; break; case "r": u = -a.width / 2; break; } return u && (l3 += t ? u : -u), l3; }; } function or(e, n) { return e.node(n).width; } function Nn(e, n, t) { var s; if (!t) return false; let r = (s = n.node(e)) == null ? void 0 : s.edgeObj; if (!r || n.node(e).edgeLabel.reversed) return false; let o = t.indexOf(r == null ? void 0 : r.v), i = t.indexOf(r == null ? void 0 : r.w); return o !== -1 && i !== -1 && o === (i + 1) % t.length || o === (i - 1) % t.length; } function kn(e, n) { e = Z(e), ir(e), Object.entries(vn(e, n)).forEach(([t, r]) => e.node(t).x = r); } function ir(e) { let n = P2(e), t = e.graph(), r = t.ranksep, o = t.rankalign, i = 0; n.forEach((s) => { let a = s.reduce((l3, u) => { var c; let d = (c = e.node(u).height) != null ? c : 0; return l3 > d ? l3 : d; }, 0); s.forEach((l3) => { let u = e.node(l3); o === "top" ? u.y = i + u.height / 2 : o === "bottom" ? u.y = i + a - u.height / 2 : u.y = i + a / 2; }), i += a + r; }); } var xn2 = /* @__PURE__ */ new WeakMap(); function Oe2(e, n = {}) { return Rn2(e, q2, n), e; } function _n(e, n, t) { let r = n; for (; r !== void 0; ) { let o = e.parent(r); if (o === t) return r; r = o; } } function Rn2(e, n, t) { var L2; let r = e.nodes().filter((b) => e.children(b).length), o = {}; r.forEach((b) => { let g = e.node(b); if (g && g.rankdir) { let m = new T2({ multigraph: true, compound: true }); m.setGraph({ rankdir: g.rankdir }); let w = e.children(b); w.forEach((v) => { let N2 = { ...e.node(v) }; m.setNode(v, N2); let O2 = e.parent(v); O2 && O2 !== b && w.includes(O2) && m.setParent(v, O2); }); let k2 = /* @__PURE__ */ new Set(); e.edges().forEach((v) => { let N2 = _n(e, v.v, b), O2 = _n(e, v.w, b); if (N2 && O2 && N2 !== O2) { let W2 = `${N2}\0${O2}`; k2.has(W2) || (k2.add(W2), m.setEdge(N2, O2, { ...e.edge(v) })); } }), Rn2(m, n, t); let _2 = jn(m); On(_2, n, t, null), Cn(m, _2); let C2 = 1 / 0, j2 = 1 / 0, I2 = -1 / 0, S2 = -1 / 0; m.nodes().forEach((v) => { if (v === b) return; let N2 = m.node(v); N2 && typeof N2.x == "number" && typeof N2.y == "number" && typeof N2.width == "number" && typeof N2.height == "number" && (C2 = Math.min(C2, N2.x - N2.width / 2), I2 = Math.max(I2, N2.x + N2.width / 2), j2 = Math.min(j2, N2.y - N2.height / 2), S2 = Math.max(S2, N2.y + N2.height / 2)); }), (!isFinite(C2) || !isFinite(j2) || !isFinite(I2) || !isFinite(S2)) && (C2 = j2 = 0, I2 = S2 = 0); let G2 = I2 - C2, x2 = S2 - j2; o[b] = { minX: C2, minY: j2, maxX: I2, maxY: S2, width: G2, height: x2, offsetX: C2, offsetY: j2 }, g._dagreClusterSubgraph = m; } }); let i = [], s = (b) => { let g = [], m = (e.children(b) || []).filter((w) => w !== b); for (; m.length > 0; ) { let w = m.shift(); g.push(w), (e.children(w) || []).filter((k2) => k2 !== w).forEach((k2) => m.push(k2)); } return g; }, a = /* @__PURE__ */ new Map(); r.forEach((b) => { let g = e.node(b); g && g.rankdir && o[b] && a.set(b, (e.children(b) || []).filter((m) => m !== b)); }); let l3 = new Set([...a.values()].flat()), u = /* @__PURE__ */ new Map(); a.forEach((b, g) => { l3.has(g) || u.set(g, s(g)); }); let d = new Set([...u.values()].flat()), c = (b) => { for (let [g, m] of u) if (m.includes(b)) return g; return b; }, f2 = []; e.edges().forEach((b) => { (d.has(b.v) || d.has(b.w)) && f2.push({ edge: b, label: e.edge(b) }); }); let h2 = /* @__PURE__ */ new Map(); d.forEach((b) => { let g = e.parent(b); h2.set(b, typeof g == "string" ? g : void 0); }), u.forEach((b, g) => { let m = e.node(g), w = []; b.forEach((C2) => { let j2 = e.node(C2); j2 && (w.push({ id: C2, node: j2, parent: h2.get(C2) }), e.removeNode(C2)); }); let k2 = f2.filter(({ edge: C2 }) => b.includes(C2.v) || b.includes(C2.w)), _2 = o[g]; m && (i.push({ clusterId: g, subgraph: m._dagreClusterSubgraph, bounds: _2, children: b, removedNodes: w, removedEdges: k2 }), m.width = _2.width, m.height = _2.height); }); let p = /* @__PURE__ */ new Set(); f2.forEach(({ edge: b, label: g }) => { let m = c(b.v), w = c(b.w); if (m !== w && e.hasNode(m) && e.hasNode(w)) { let k2 = `${m}\0${w}`; p.has(k2) || (p.add(k2), e.setEdge(m, w, { ...g, width: 0, height: 0 })); } }); let E2 = jn(e), y2 = On(E2, n, t, (L2 = xn2.get(e)) != null ? L2 : null); xn2.set(e, y2), Cn(e, E2), p.forEach((b) => { let g = b.indexOf("\0"), m = b.slice(0, g), w = b.slice(g + 1); e.hasEdge(m, w) && e.removeEdge(m, w); }), i.forEach(({ clusterId: b, subgraph: g, bounds: m, removedNodes: w, removedEdges: k2 }) => { var G2, x2; let _2 = e.node(b), C2 = (G2 = _2 == null ? void 0 : _2.x) != null ? G2 : 0, j2 = (x2 = _2 == null ? void 0 : _2.y) != null ? x2 : 0, I2 = (m.minX + m.maxX) / 2, S2 = (m.minY + m.maxY) / 2; w.forEach(({ id: v, node: N2, parent: O2 }) => { e.setNode(v, N2), O2 !== void 0 && e.setParent(v, O2); }), k2.forEach(({ edge: v, label: N2 }) => { e.setEdge(v, N2); }), g.nodes().forEach((v) => { if (v === b) return; let N2 = g.node(v), O2 = e.node(v); O2 && N2 && typeof N2.x == "number" && typeof N2.y == "number" && (O2.x = C2 + (N2.x - I2), O2.y = j2 + (N2.y - S2)); }), delete _2._dagreClusterSubgraph; }), r.forEach((b) => { var w, k2; let g = e.node(b), m = o[b]; if (g && g.rankdir && g._dagreClusterSubgraph && m) { let _2 = g._dagreClusterSubgraph, C2 = (w = g.x) != null ? w : 0, j2 = (k2 = g.y) != null ? k2 : 0, I2 = (m.minX + m.maxX) / 2, S2 = (m.minY + m.maxY) / 2; _2.nodes().forEach((G2) => { if (G2 === b) return; let x2 = _2.node(G2), v = e.node(G2); if (v && x2 && typeof x2.x == "number" && typeof x2.y == "number") { let N2 = x2.x - I2, O2 = x2.y - S2; v.x = C2 + N2, v.y = j2 + O2; } }), delete g._dagreClusterSubgraph; } }); } function On(e, n, t, r = null) { var l3, u; let o = (t == null ? void 0 : t.useDynamic) !== false, i = o && (l3 = r == null ? void 0 : r.graph) != null ? l3 : null, s = o && (u = r == null ? void 0 : r.rawNodes) != null ? u : null; n(" makeSpaceForEdgeLabels", () => hr(e)), n(" removeSelfEdges", () => Nr(e)), n(" acyclic", () => Ue2(e, i)), n(" nestingGraph.run", () => un(e)), n(" rank", () => dn(Z(e))), n(" injectEdgeLabelProxies", () => br(e)), n(" removeEmptyRanks", () => Be2(e)), n(" nestingGraph.cleanup", () => fn2(e)), n(" normalizeRanks", () => We2(e)), n(" assignRankMinMax", () => gr(e)), n(" removeEdgeLabelProxies", () => pr(e)), n(" normalize.run", () => Ke2(e)), n(" parentDummyChains", () => ln(e)), n(" addBorderSegments", () => bn(e)), n(" order", () => re2(e, t, s)), n(" insertSelfEdges", () => Gr(e)), n(" adjustCoordinateSystem", () => pn(e)), n(" position", () => kn(e, t.corePath)), n(" positionSelfEdges", () => vr(e)); let a = JSON.parse(JSON.stringify(e._nodes)); return n(" removeBorderNodes", () => wr(e)), n(" normalize.undo", () => Qe2(e)), n(" fixupEdgeLabelCoords", () => Lr(e)), n(" undoCoordinateSystem", () => mn2(e)), n(" translateGraph", () => mr(e)), n(" assignNodeIntersects", () => Er(e)), n(" reversePoints", () => yr(e)), n(" acyclic.undo", () => Je2(e)), { graph: e, rawNodes: a }; } function Cn(e, n) { e.nodes().forEach((t) => { let r = e.node(t), o = n.node(t); r && (r.x = o.x, r.y = o.y, r.order = o.order, r.rank = o.rank, n.children(t).length && (r.width = o.width, r.height = o.height)); }), e.edges().forEach((t) => { let r = e.edge(t), o = n.edge(t); r.points = o.points, Object.hasOwn(o, "x") && (r.x = o.x, r.y = o.y); }), e.graph().width = n.graph().width, e.graph().height = n.graph().height; } var sr = ["nodesep", "edgesep", "ranksep", "marginx", "marginy"]; var ar = { ranksep: 50, edgesep: 20, nodesep: 50, rankdir: "TB", rankalign: "center" }; var dr = ["acyclicer", "ranker", "rankdir", "align", "rankalign"]; var lr = ["width", "height", "rank"]; var Tn2 = { width: 0, height: 0 }; var ur = ["minlen", "weight", "width", "height", "labeloffset"]; var cr = { minlen: 1, weight: 1, width: 0, height: 0, labeloffset: 10, labelpos: "r" }; var fr = ["labelpos"]; function jn(e) { let n = new T2({ multigraph: true, compound: true }), t = _e2(e.graph()); return n.setGraph(Object.assign({}, ar, xe2(t, sr), B2(t, dr))), e.nodes().forEach((r) => { let o = _e2(e.node(r)), i = xe2(o, lr); Object.keys(Tn2).forEach((a) => { i[a] === void 0 && (i[a] = Tn2[a]); }), n.setNode(r, i); let s = e.parent(r); s !== void 0 && n.setParent(r, s); }), e.edges().forEach((r) => { let o = _e2(e.edge(r)); n.setEdge(r, Object.assign({}, cr, xe2(o, ur), B2(o, fr))); }), n; } function hr(e) { let n = e.graph(); n.ranksep /= 2, e.edges().forEach((t) => { var o; let r = e.edge(t); r.minlen *= 2, ((o = r.labelpos) != null ? o : "r").toLowerCase() !== "c" && (n.rankdir === "TB" || n.rankdir === "BT" ? r.width += r.labeloffset : r.height += r.labeloffset); }); } function br(e) { e.edges().forEach((n) => { let t = e.edge(n); if (t.width && t.height) { let r = e.node(n.v), i = { rank: (e.node(n.w).rank - r.rank) / 2 + r.rank, e: n }; M(e, "edge-proxy", i, "_ep"); } }); } function gr(e) { let n = 0; e.nodes().forEach((t) => { let r = e.node(t); r.borderTop && (r.minRank = e.node(r.borderTop).rank, r.maxRank = e.node(r.borderBottom).rank, n = Math.max(n, r.maxRank)); }), e.graph().maxRank = n; } function pr(e) { e.nodes().forEach((n) => { let t = e.node(n); if (t.dummy === "edge-proxy") { let r = t; e.edge(r.e).labelRank = t.rank, e.removeNode(n); } }); } function mr(e) { let n = Number.POSITIVE_INFINITY, t = 0, r = Number.POSITIVE_INFINITY, o = 0, i = e.graph(), s = i.marginx || 0, a = i.marginy || 0; function l3(u) { let d = u.x, c = u.y, f2 = u.width, h2 = u.height; n = Math.min(n, d - f2 / 2), t = Math.max(t, d + f2 / 2), r = Math.min(r, c - h2 / 2), o = Math.max(o, c + h2 / 2); } e.nodes().forEach((u) => l3(e.node(u))), e.edges().forEach((u) => { let d = e.edge(u); Object.hasOwn(d, "x") && l3(d); }), n -= s, r -= a, e.nodes().forEach((u) => { let d = e.node(u); d.x -= n, d.y -= r; }), e.edges().forEach((u) => { let d = e.edge(u); d.points.forEach((c) => { c.x -= n, c.y -= r; }), Object.hasOwn(d, "x") && (d.x -= n), Object.hasOwn(d, "y") && (d.y -= r); }), i.width = t - n + s, i.height = o - r + a; } function Er(e) { e.edges().forEach((n) => { if (n.v === n.w) return; let t = e.edge(n), r = e.node(n.v), o = e.node(n.w), i, s; t.points ? (i = t.points[0], s = t.points[t.points.length - 1]) : (t.points = [], i = o, s = r), t.points.unshift(se2(r, i)), t.points.push(se2(o, s)); }); } function Lr(e) { e.edges().forEach((n) => { let t = e.edge(n); if (Object.hasOwn(t, "x")) switch ((t.labelpos === "l" || t.labelpos === "r") && (t.width -= t.labeloffset), t.labelpos) { case "l": t.x -= t.width / 2 + t.labeloffset; break; case "r": t.x += t.width / 2 + t.labeloffset; break; } }); } function yr(e) { e.edges().forEach((n) => { let t = e.edge(n); t.reversed && t.points.reverse(); }); } function wr(e) { e.nodes().forEach((n) => { if (e.children(n).length) { let t = e.node(n), r = e.node(t.borderTop), o = e.node(t.borderBottom), i = e.node(t.borderLeft[t.borderLeft.length - 1]), s = e.node(t.borderRight[t.borderRight.length - 1]); t.width = Math.abs(s.x - i.x), t.height = Math.abs(o.y - r.y), t.x = i.x + t.width / 2, t.y = r.y + t.height / 2; } }), e.nodes().forEach((n) => { e.node(n).dummy === "border" && e.removeNode(n); }); } function Nr(e) { e.edges().forEach((n) => { if (n.v === n.w) { let t = e.node(n.v); t.selfEdges || (t.selfEdges = []), t.selfEdges.push({ e: n, label: e.edge(n) }), e.removeEdge(n); } }); } function Gr(e) { P2(e).forEach((t) => { let r = 0; t.forEach((o, i) => { let s = e.node(o); typeof s.rank != "number" && (s.rank = 0), s.order = i + r, (s.selfEdges || []).forEach((a) => { M(e, "selfedge", { width: a.label.width, height: a.label.height, rank: s.rank, order: i + ++r, e: a.e, edgeLabel: a.label }, "_se"), (!Array.isArray(a.label.points) || a.label.points.length !== 7) && (a.label.points = [{ x: 0, y: -10 }, { x: 0, y: -10 }, { x: 0, y: 0 }, { x: 0, y: 10 }, { x: 0, y: 10 }, { x: 0, y: 0 }, { x: 0, y: 0 }]); }), delete s.selfEdges; }); }); } function vr(e) { e.nodes().forEach((n) => { let t = e.node(n), r = (o) => typeof o == "number" && isFinite(o); if (t.dummy === "selfedge") { let o = t, i = e.node(o.e.v), s = r(i == null ? void 0 : i.x) ? i.x : 0, a = r(i == null ? void 0 : i.y) ? i.y : 0, l3 = r(i == null ? void 0 : i.width) ? i.width : 0, u = r(i == null ? void 0 : i.height) ? i.height : 0, d = r(t.x) ? t.x : s, c = r(t.y) ? t.y : a, f2 = l3 / 2, h2 = u / 2; o.edgeLabel.points = [{ x: d + f2, y: c - h2 }, { x: d + f2, y: c - h2 }, { x: d, y: c }, { x: d - f2, y: c + h2 }, { x: d - f2, y: c + h2 }, { x: d, y: c }, { x: d, y: c }], o.edgeLabel.x = d, o.edgeLabel.y = c, e.setEdge(o.e, o.edgeLabel), e.removeNode(n); } else t && Array.isArray(t.selfEdges) && t.selfEdges.forEach((o) => { if (!Array.isArray(o.label.points) || o.label.points.length !== 7) { let i = r(t.x) ? t.x : 0, s = r(t.y) ? t.y : 0, a = r(t.width) ? t.width : 0, l3 = r(t.height) ? t.height : 0, u = a / 2, d = l3 / 2; o.label.points = [{ x: i + u, y: s - d }, { x: i + u, y: s - d }, { x: i, y: s }, { x: i - u, y: s + d }, { x: i - u, y: s + d }, { x: i, y: s }, { x: i, y: s }]; } }); }); } function xe2(e, n) { return X2(B2(e, n), Number); } function _e2(e) { let n = {}; return e && Object.entries(e).forEach(([t, r]) => { typeof t == "string" && (t = t.toLowerCase()), n[t] = r; }), n; } function Ce2(e) { let n = P2(e), t = new T2({ compound: true, multigraph: true }).setGraph({}); return e.nodes().forEach((r) => { t.setNode(r, { label: r }), t.setParent(r, "layer" + e.node(r).rank); }), e.edges().forEach((r) => t.setEdge(r.v, r.w, {}, r.name)), n.forEach((r, o) => { let i = "layer" + o; t.setNode(i, { rank: "same" }), r.reduce((s, a) => (t.setEdge(s, a, { style: "invis" }), a)); }), t; } var kr = { graphlib: ie2, version: ue2, layout: Oe2, debug: Ce2, util: { time: le2, notime: q2 } }; var $o = kr; // src/components/flow.js var FS2 = 13; var LH2 = 17; var TEXT_MAX = 150; var EDGE_FS = 11.5; var CLUSTER_FS = 11; var CLUSTER_LABEL_H = 15; var DIRS = /* @__PURE__ */ new Set(["TB", "LR", "BT", "RL"]); var BRACKETS = [ { open: "[(", close: ")]", shape: "db" }, { open: "[", close: "]", shape: "rect" }, { open: "(", close: ")", shape: "round" }, { open: "{", close: "}", shape: "diamond" } ]; var ARROW = /^\s*(-->|->)\s*/; var flow_default = { name: "flow", summary: "Flowchart / architecture diagram (automatic layout)", syntax: `\`\`\`flow [TB|LR|BT|RL] A -> B: label \u2190 solid line; text after the colon is the edge label A --> C \u2190 dashed line A -> B -> C \u2190 chain A -> B & C \u2190 fan-out (Start) {Valid?} [(Database)] [text with: a colon] \u2190 rounded / diamond / cylinder / rectangle *Key node \u2190 * prefix highlights group Group name: B, C \u2190 draw a group box around nodes \`\`\` - The text inside the brackets is the node's identity; later lines can refer to the node by that text alone. The default direction is TB (top to bottom). - Change markers show what a plan adds, removes and changes. A line can start with + (added), - (removed) or ~ (changed), followed by a space: \`\`\`flow LR Client -> Gateway + Gateway -> [(Cache)]: lookup + Cache -> Service: miss - Gateway -> Service ~ *Service \`\`\` - A marker applies to every link on its line. A node is removed when it appears only on - lines, added when it appears only on + lines, and unchanged when it also appears on an unmarked line, even if its links changed. ~ Node on a line without an arrow marks that node as changed. - ~ on a line with an arrow is an error: remove the old link with - and add the new one with +. That is also how a link's label changes. The same link both unmarked and marked is an error too. - + group Name: A, B and - group Name: A, B mark a group box. A group that is not removed and holds only removed nodes is a warning. Markers combine with * and every shape bracket. - The Changes view draws added in the theme's ok color, removed faded with a struck-through label, changed with a warn outline, and each marked node with a +, \u2212 or ~ badge. A count row and a Before / Changes / After switch sit under the diagram: Before and After show the diagram as it was and as it will be, plain and without the items that are not in that view. - A line that starts with a marker and a space is always read as a marker. To keep a node name that starts with "- ", write it in brackets: [- Gateway].`, example: "```flow LR\n(User) -> Gateway: HTTPS\nGateway -> Auth & *Service\nService -> [(Database)]\ngroup Backend: Auth, Service\n```", render(text, { args, uid, ui, warn, video, dir: pageDir = "ltr" }) { const model = parseFlow(text); model.warnings.forEach((w) => warn?.(w)); const flowDir = (args.match(/\b(TB|LR|BT|RL)\b/i)?.[1] ?? "TB").toUpperCase(); const html = `
    ${layout2(model, DIRS.has(flowDir) ? flowDir : "TB", uid(), ui, pageDir)}
    `; return withDelta(html, [...model.nodes.values(), ...model.edges, ...model.groups].map((x2) => x2.state ?? null), { ui, video }); } }; function parseFlow(text) { const nodes = /* @__PURE__ */ new Map(); const edges = []; const groups = []; const upsert = (spec, line, mark) => { const prev = nodes.get(spec.id); const seen = { before: mark !== "+", after: mark !== "-", tilde: mark === "~" }; if (!prev) nodes.set(spec.id, { ...spec, ...seen, line }); else { const merged = { before: prev.before || seen.before, after: prev.after || seen.after, tilde: prev.tilde || seen.tilde }; nodes.set(spec.id, { ...prev, ...merged, shape: spec.explicit ? spec.shape : prev.shape, hi: prev.hi || spec.hi }); } return spec.id; }; for (const { text: raw, line } of contentLines(text)) { const { mark, text: t } = splitMarker(raw); const g = t.match(/^group\s+(.+?)\s*[::]\s*(.+)$/i); if (g) { if (mark === "~") throw new ComponentError("flow: ~ marks a node, not a group. Use + group or - group to add or remove a group box", line); const state = markState(mark); groups.push({ name: g[1], members: g[2].split(/[,,]/).map((s) => s.trim()).filter(Boolean), line, ...state && { state } }); continue; } const { chain, label } = parseChain(t, line); if (mark === "~" && chain.length > 1) { throw new ComponentError('flow: ~ marks a node, not a link. To change a link, remove the old one and add the new one: "- A -> B" then "+ A -> C"', line); } const ids = chain.map((step) => ({ ...step, ids: step.nodes.map((n) => upsert(n, line, mark)) })); for (let k2 = 1; k2 < ids.length; k2++) { const isLast = k2 === ids.length - 1; for (const from of ids[k2 - 1].ids) { for (const to of ids[k2].ids) { const state = markState(mark); edges.push({ from, to, dashed: ids[k2].arrow === "-->", label: isLast ? label : "", line, ...state && { state } }); } } } } if (!nodes.size) throw new ComponentError("flow needs at least one node", 1); for (const grp of groups) { const missing = grp.members.filter((m) => !nodes.has(m)); if (missing.length) throw new ComponentError(`group ${grp.name} refers to nodes that do not exist: ${missing.join(", ")}`, grp.line); } checkLinks(edges); const settled = new Map([...nodes].map(([id, n]) => { const state = nodeState(n); return [id, state ? { ...n, state } : n]; })); return { nodes: settled, edges, groups, warnings: groupWarnings(groups, settled) }; } function nodeState({ before, after, tilde }) { if (before && !after) return "removed"; if (after && !before) return "added"; return tilde ? "changed" : null; } function checkLinks(edges) { const key = (e) => `${e.from}\0${e.to}`; for (const e of edges.filter((x2) => x2.state)) { const plain = edges.find((x2) => !x2.state && key(x2) === key(e)); if (plain) throw new ComponentError(`flow: the link ${e.from} -> ${e.to} is written unmarked on line ${plain.line} and as ${e.state === "added" ? "+" : "-"} here. Mark every line of that link, or none`, e.line); } } function groupWarnings(groups, nodes) { return groups.filter((g) => g.state !== "removed" && g.members.every((m) => nodes.get(m).state === "removed")).map((g) => ({ line: g.line, message: `group ${g.name} holds only removed nodes and would be an empty box after the change. Mark it with "- group ${g.name}: ${g.members.join(", ")}"` })); } function parseChain(t, line) { const chain = []; let pos = 0; let arrow = null; for (; ; ) { const group = []; for (; ; ) { const { node, end } = parseNode(t, pos, line); group.push(node); pos = end; const amp = t.slice(pos).match(/^\s*&\s*/); if (!amp) break; pos += amp[0].length; } chain.push({ arrow, nodes: group }); const a = t.slice(pos).match(ARROW); if (!a) break; arrow = a[1]; pos += a[0].length; } const rest = t.slice(pos).trim(); if (rest && !/^[::]/.test(rest)) { throw new ComponentError(`flow cannot parse "${t}". Write a link as A -> B: label`, line); } return { chain, label: rest.replace(/^[::]\s*/, "") }; } function parseNode(t, start, line) { let pos = start + t.slice(start).match(/^\s*/)[0].length; const hi = t[pos] === "*"; if (hi) pos++; const bracket = BRACKETS.find((b) => t.startsWith(b.open, pos)); let label; let end; if (bracket) { const close = t.indexOf(bracket.close, pos + bracket.open.length); if (close === -1) throw new ComponentError(`flow: unclosed shape bracket, missing ${bracket.close}`, line); label = t.slice(pos + bracket.open.length, close).trim(); end = close + bracket.close.length; } else { const m = t.slice(pos).match(/^(.*?)(?=\s*(?:-->|->|&|[::]|$))/); label = m[1].trim(); end = pos + m[0].length; } if (!label) throw new ComponentError(`flow has an empty node: "${t}"`, line); return { node: { id: label, label, shape: bracket?.shape ?? "rect", explicit: Boolean(bracket), hi }, end }; } function nodeSize(node) { const lines = wrap(node.label, TEXT_MAX, FS2); const tw = Math.max(...lines.map((l3) => measure(l3, FS2))); const th = lines.length * LH2; const w = Math.max(tw + 28, 64); const h2 = th + 18; const size = { rect: [w, h2], round: [w + 12, h2], diamond: [(tw + 28) * 1.5, h2 * 1.6], db: [w, h2 + 14] }[node.shape]; return { lines, width: size[0], height: size[1] }; } var nodeKey = (i) => `n${i}`; var gkey = (i) => `g${i}`; var reserveKey = (i) => `r${i}`; function runLayout({ nodes, edges, groups }, rankdir, rtl, widths, reserve) { const g = new $o.graphlib.Graph({ compound: groups.length > 0, multigraph: true }); g.setGraph({ rankdir, nodesep: 36, ranksep: 46, marginx: 14, marginy: groups.length ? 26 : 14 }); g.setDefaultEdgeLabel(() => ({})); const key = new Map([...nodes.keys()].map((name, i) => [name, nodeKey(i)])); const sizes = /* @__PURE__ */ new Map(); for (const n of nodes.values()) { const s = nodeSize(n); sizes.set(n.id, s); g.setNode(key.get(n.id), { width: s.width, height: s.height }); } const vertical = rankdir === "TB" || rankdir === "BT"; groups.forEach((grp, i) => { g.setNode(gkey(i), { label: grp.name }); grp.members.forEach((m) => g.setParent(key.get(m), gkey(i))); if (!reserve.has(i) || !vertical) return; g.setNode(reserveKey(i), { width: widths[i] + 4, height: CLUSTER_LABEL_H }); g.setParent(reserveKey(i), gkey(i)); const down = edges.map((e) => rankdir === "TB" ? [e.from, e.to] : [e.to, e.from]); const link = (a, b, name) => rankdir === "TB" ? g.setEdge(a, b, {}, name) : g.setEdge(b, a, {}, name); const inside = new Set(grp.members); const first = grp.members.filter((m) => !down.some(([a, b]) => b === m && a !== m && inside.has(a))); (first.length ? first : grp.members.slice(0, 1)).forEach((m, k2) => { link(reserveKey(i), key.get(m), `r${i}-${k2}`); down.forEach(([a, b], j2) => { if (b === m && !inside.has(a)) link(key.get(a), reserveKey(i), `r${i}-${k2}-${j2}`); }); }); }); edges.forEach((e, i) => { const label = e.label ? { label: e.label, width: measure(e.label, EDGE_FS) + 12, height: 18, labelpos: "c" } : {}; g.setEdge(key.get(e.from), key.get(e.to), label, `e${i}`); }); $o.layout(g); if (rtl) mirrorLayout(g); if (!vertical) { for (const [i, side] of reserve) { const c = g.node(gkey(i)); insertStrip(g, side === "top" ? top(c) + 2 : top(c) + c.height - 2, CLUSTER_LABEL_H + 3); } } return { g, key, sizes }; } function insertStrip(g, y2, d) { for (const v of g.nodes()) { const n = g.node(v); if (v.startsWith("g") && top(n) < y2 && top(n) + n.height > y2) { n.height += d; n.y += d / 2; } else if (n.y > y2) n.y += d; } for (const e of g.edges()) { const edge = g.edge(e); edge.points = edge.points.map((p) => p.y > y2 ? { ...p, y: p.y + d } : p); if (edge.y !== void 0 && edge.y > y2) edge.y += d; } g.graph().height += d; } var top = (n) => n.y - n.height / 2; function placeLabels({ g, key, sizes }, { nodes, edges, groups }, widths, rtl, reserve) { const boxes = []; for (const n of nodes.values()) { const { x: x2, y: y2 } = g.node(key.get(n.id)); const { width: w, height: h2 } = sizes.get(n.id); boxes.push([x2 - w / 2, y2 - h2 / 2, x2 + w / 2, y2 + h2 / 2]); } const segments = []; edges.forEach((e, i) => { const data = g.edge({ v: key.get(e.from), w: key.get(e.to), name: `e${i}` }); const pts = data.points; for (let k2 = 1; k2 < pts.length; k2++) segments.push([pts[k2 - 1], pts[k2]]); if (e.label) { const w = measure(e.label, EDGE_FS) + 10; boxes.push([data.x - w / 2, data.y - 9, data.x + w / 2, data.y + 9]); } }); return groups.map((grp, i) => { const c = g.node(gkey(i)); const left = c.x - c.width / 2; const right = left + c.width; const w = widths[i]; const own = grp.state ? [[rtl ? left - 8 : right - 8, top(c) - 8, rtl ? left + 8 : right + 8, top(c) + 8]] : []; const free = (x02, y0, y1) => { const box = [x02 - 5, y0 - 2, x02 + w + 5, y1 + 2]; const wide = [x02 - 10, y0 - 2, x02 + w + 10, y1 + 2]; return ![...boxes, ...own].some((b) => overlaps(wide, b)) && !segments.some(([p, q3]) => segmentHits(p, q3, box)); }; const room = g.hasNode(reserveKey(i)) ? g.node(reserveKey(i)) : null; const baselines = reserve.get(i) === "bottom" ? [top(c) + c.height - 6] : room ? [top(c) + 14, room.y + 4] : [top(c) + 14]; for (const baseline2 of baselines) { const [y0, y1] = [baseline2 - 11, baseline2 + 4]; for (let s = 0; s <= Math.max(0, c.width - 16 - w); s += 4) { const x02 = rtl ? right - 8 - w - s : left + 8 + s; if (free(x02, y0, y1)) { boxes.push([x02, y0, x02 + w, y1]); return [rtl ? x02 + w : x02, baseline2]; } } } if (!room) return null; const baseline = baselines.at(-1); const x0 = room.x - w / 2; boxes.push([x0, baseline - 11, x0 + w, baseline + 4]); return [rtl ? x0 + w : x0, baseline]; }); } var overlaps = (a, b) => a[0] < b[2] && b[0] < a[2] && a[1] < b[3] && b[1] < a[3]; function segmentHits(p, q3, [x0, y0, x1, y1]) { const dx = q3.x - p.x; const dy = q3.y - p.y; let t0 = 0; let t1 = 1; for (const [a, b] of [[-dx, p.x - x0], [dx, x1 - p.x], [-dy, p.y - y0], [dy, y1 - p.y]]) { if (a === 0) { if (b < 0) return false; } else { const t = b / a; if (a < 0) t0 = Math.max(t0, t); else t1 = Math.min(t1, t); if (t0 > t1) return false; } } return true; } var labelWidth = (name, pageDir) => measure(name, CLUSTER_FS, { mono: pageDir !== "rtl" }) + 2; function layout2(model, rankdir, id, ui, pageDir = "ltr") { const { nodes, edges, groups } = model; const rtl = pageDir === "rtl"; const widths = groups.map((grp) => labelWidth(grp.name, pageDir)); let run2 = runLayout(model, rankdir, rtl, widths, /* @__PURE__ */ new Set()); let spots = placeLabels(run2, model, widths, rtl, /* @__PURE__ */ new Map()); const reserve = /* @__PURE__ */ new Map(); for (const side of ["top", "bottom"]) { const crowded = spots.flatMap((s, i) => s ? [] : [i]); if (!crowded.length) break; for (const i of crowded) reserve.set(i, side); run2 = runLayout(model, rankdir, rtl, widths, reserve); spots = placeLabels(run2, model, widths, rtl, reserve); } const { g, key, sizes } = run2; const clusters = groups.map((grp, i) => { const c = g.node(gkey(i)); const x2 = c.x - c.width / 2; const y2 = c.y - c.height / 2; const mark = deltaAttr(grp.state); const badgeX = rtl ? x2 : x2 + c.width; const [labelX, labelY] = spots[i] ?? [rtl ? x2 + c.width - 8 : x2 + 8, y2 + 14]; return `${esc(svgLine(grp.name, pageDir))}${badgeSvg(grp.state, badgeX, y2)}`; }); const stepOf = new Map([.../* @__PURE__ */ new Set([...[...nodes.values()].map((n) => n.line), ...edges.map((e) => e.line)])].sort((a, b) => a - b).map((l3, k2) => [l3, k2])); const edgeSvg = edges.map((e, i) => { const data = g.edge({ v: key.get(e.from), w: key.get(e.to), name: `e${i}` }); const pts = clipEnds(data.points, g.node(key.get(e.from)), nodes.get(e.from).shape, g.node(key.get(e.to)), nodes.get(e.to).shape); const head = e.state ? `${id}-arrow-${e.state}` : `${id}-arrow`; const path = ``; const open = ``; if (!e.label) return `${open}${path}`; const w = measure(e.label, EDGE_FS) + 10; return `${open}${path}${textLines([e.label], data.x, data.y, LH2, "", pageDir)}`; }); const nodeSvg = [...nodes.values()].map((n) => { const { x: x2, y: y2 } = g.node(key.get(n.id)); const { width: w, height: h2, lines } = sizes.get(n.id); const badge = badgeSvg(n.state, ...badgePoint(n.shape, x2, y2, w, h2, rtl)); return `${shapeSvg(n.shape, x2, y2, w, h2)}${textLines(lines, x2, y2 + (n.shape === "db" ? 4 : 0), LH2, "", pageDir)}${badge}`; }); const { width, height } = g.graph(); const label = diagramLabel(ui, "flow", [...nodes.keys()].slice(0, 8)); const heads = ["added", "removed"].filter((state) => edges.some((e) => e.state === state)); return `${svgOpen(width, height, label, pageDir)}${arrowDefs(id, heads)}${clusters.join("")}${edgeSvg.join("")}${nodeSvg.join("")}`; } function badgeSvg(state, x2, y2) { if (!state) return ""; return `${SIGN[state]}`; } function badgePoint(shape, x2, y2, w, h2, rtl = false) { const s = rtl ? -1 : 1; if (shape === "diamond") return [x2 + s * (w / 4), y2 - h2 / 4]; if (shape === "round") return [x2 + s * (w / 2 - h2 * 0.15), y2 - h2 / 2 + h2 * 0.15]; return [x2 + s * (w / 2), y2 - h2 / 2]; } function shapeSvg(shape, x2, y2, w, h2) { const l3 = x2 - w / 2; const t = y2 - h2 / 2; if (shape === "diamond") { return ``; } if (shape === "db") { const ry = 7; return ``; } const rx = shape === "round" ? h2 / 2 : 3; return ``; } function clipEnds(points, from, fromShape, to, toShape) { const pts = points.map((p) => ({ ...p })); if (fromShape === "diamond" && pts.length > 1) pts[0] = diamondPoint(from, pts[1]); if (toShape === "diamond" && pts.length > 1) pts[pts.length - 1] = diamondPoint(to, pts[pts.length - 2]); return pts; } function diamondPoint(node, toward) { const dx = toward.x - node.x; const dy = toward.y - node.y; const k2 = Math.abs(dx) / (node.width / 2) + Math.abs(dy) / (node.height / 2); if (k2 === 0) return { x: node.x, y: node.y }; return { x: node.x + dx / k2, y: node.y + dy / k2 }; } // src/components/er.js var FS3 = 13; var FIELD_FS = 12; var KEY_FS = 11; var EDGE_FS2 = 11.5; var LH3 = 17; var HEAD_LH = 20; var FIELD_LH = 17; var PAD = 11; var KEY_GAP = 12; var SIGN_W = 14; var MIN_WIDTH = 92; var LOOP_OUT = 34; var LOOP_STEP = 20; var LOOP_END = 18; var LOOP_LABEL = 24; var LOOP_GAP = 6; var DIRS2 = /* @__PURE__ */ new Set(["TB", "LR", "BT", "RL"]); var CARDS = ["1\\.\\.\\*", "0\\.\\.1", "\\*", "1"]; var MARKERS = /* @__PURE__ */ new Set(["PK", "FK", "UK"]); var RELATIONSHIP = new RegExp(`^(\\S+)\\s+(${CARDS.join("|")})\\s*--\\s*(${CARDS.join("|")})\\s+(\\S+)\\s*(?::\\s*(.*))?$`); var MERMAID = /^(\S+)\s+([|o{}]{2})(?:--|\.\.)([|o{}]{2})\s+(\S+)\s*(?::\s*(.*))?$/; var MERMAID_CARDS = { "||": "1", "o|": "0..1", "|o": "0..1", "|{": "1..*", "}|": "1..*", "o{": "*", "}o": "*" }; var er_default = { name: "er", summary: "Entity relationship diagram (automatic layout, crow's-foot ends)", syntax: `\`\`\`er [TB|LR|BT|RL] *User \u2190 a line at column 0 is an entity; * highlights it id PK \u2190 an indented line is a field: name [type] [PK|FK|UK] email string UK Order id PK user_id FK -> User \u2190 FK -> Entity draws the many-to-one relationship (*--1) User 1--* Order: places \u2190 A -- B: label (optional) \`\`\` - Cardinalities are 1, 0..1, * and 1..*. The left end is drawn at the entity on the left, the right end at the entity on the right. - A field's FK -> Entity already draws its relationship, so a relationship line is optional. A relationship line that names the same two entities replaces the implied one and sets the cardinality and the label. Two FK fields that point at the same entity draw two relationships. - The type is optional: \`email UK\` is a field with a key but no type. - A Mermaid line such as USER ||--o{ ORDER is an error that shows the line written for this component. - An entity a field or a relationship names must be written at column 0. - A field that points at its own entity draws a loop: beside the box, or below it in LR and RL. - The default direction is TB (top to bottom). - Change markers show what a plan adds, removes and changes. A line can start with + (added), - (removed) or ~ (changed), followed by a space: an entity line at column 0, a field line after its indentation, or a relationship line: \`\`\`er User ~ email varchar(320) UK + phone string Order + coupon_id FK -> Coupon - legacy_ref string + Coupon id PK - AuditLog id PK + User 1--* Coupon: owns \`\`\` - An entity is marked only by its own line; a marked field does not mark it. The fields of a + or - entity inherit its marker, and a field with another marker is an error. ~ marks a field as changed and does not show the old value. Markers combine with *. - A relationship from an FK field takes the marker of that field; otherwise it is added or removed with an entity at either end. A written relationship line takes its own marker. To change one, remove the old line with - and add the new one with +; ~ on a relationship line is an error. - The Changes view draws added in the theme's ok color, removed faded with struck-through text, changed with a warn outline, and each marked entity with a +, \u2212 or ~ badge. A marked field row gets a tinted band and its sign at the start of the row. A count row (entities, fields and written relationship lines) and a Before / Changes / After switch sit under the diagram: Before and After show the schema as it was and as it will be, plain and without the items that are not in that view. - A line that starts with a marker and a space is always read as a marker.`, example: "```er LR\n*User\n id PK\n email string UK\nOrder\n id PK\n user_id FK -> User\nUser 1--* Order: places\n```", render(text, { args, ui, video, dir: pageDir = "ltr" }) { const model = parseEr(text); const dir = (args.match(/\b(TB|LR|BT|RL)\b/i)?.[1] ?? "TB").toUpperCase(); const html = `
    ${layout3(model, DIRS2.has(dir) ? dir : "TB", ui, pageDir)}
    `; const states = [...model.entities.values()].flatMap((e) => [e.state, ...e.fields.map((x2) => x2.state)]).concat(model.written.map((rel) => rel.state)); return withDelta(html, states.map((state) => state ?? null), { ui, video }); } }; function parseEr(text) { const entities = /* @__PURE__ */ new Map(); const written = []; let current = null; for (const { raw, text: line, line: n } of contentLines(text)) { const { mark, text: body } = splitMarker(line); const state = markState(mark); if (/^\s/.test(raw)) { if (!current) throw new ComponentError(`"${line}": a field line must follow an entity; write the entity name at the start of its own line first`, n); current.fields.push(settleField(parseField(body, n), mark, current, n)); continue; } const rel = body.match(RELATIONSHIP); if (rel) { if (mark === "~") throw new ComponentError('er: ~ marks an entity or a field, not a relationship. To change a relationship, remove the old line and add the new one: "- A 1--* B" then "+ A 1--1 B"', n); written.push({ from: rel[1], fromCard: rel[2], to: rel[4], toCard: rel[3], label: rel[5]?.trim() ?? "", line: n, ...state && { state } }); current = null; continue; } const mermaid = body.match(MERMAID); if (mermaid && MERMAID_CARDS[mermaid[2]] && MERMAID_CARDS[mermaid[3]]) { throw new ComponentError(`"${line}" is Mermaid syntax; write ${mark && mark !== "~" ? `${mark} ` : ""}${erLineOf(mermaid)}`, n); } if (body.includes("--") || body.includes("{") || body.includes("}")) { throw new ComponentError(`"${line}" is not an entity of this component; write A 1--* B for a relationship, or the entity name at column 0 on its own line`, n); } const hi = body.startsWith("*"); const name = (hi ? body.slice(1) : body).trim(); if (!name) throw new ComponentError("an entity needs a name", n); if (entities.has(name)) throw new ComponentError(`entity "${name}" is written twice`, n); current = { name, hi, fields: [], line: n, ...state && { state } }; entities.set(name, current); } if (!entities.size) throw new ComponentError("an entity relationship diagram needs at least one entity (a line at column 0)", 1); for (const entity of entities.values()) { for (const field of entity.fields) { if (field.ref && !entities.has(field.ref)) throw new ComponentError(`no entity "${field.ref}"; write it at column 0`, field.line); } } for (const rel of written) { for (const name of [rel.from, rel.to]) { if (!entities.has(name)) throw new ComponentError(`no entity "${name}"; write it at column 0`, rel.line); } } checkEnds(entities, written); return { entities, written }; } function checkEnds(entities, written) { const replaced = new Set(written.map((rel) => pairKey(rel.from, rel.to))); const implied = [...entities.values()].flatMap((entity) => entity.fields.filter((field) => field.ref && !replaced.has(pairKey(entity.name, field.ref))).map((field) => ({ from: entity.name, to: field.ref, line: field.line, state: field.state === "changed" ? void 0 : field.state }))); for (const rel of [...implied, ...written]) { const ends = [rel.from, rel.to].map((name) => entities.get(name)); const added = ends.find((e) => e.state === "added"); const removed = ends.find((e) => e.state === "removed"); if (rel.state === "added" && removed) { throw new ComponentError(`er: the + relationship ${rel.from} to ${rel.to} touches the removed entity ${removed.name}, so it would dangle in the After view. Remove the + or keep ${removed.name}`, rel.line); } if (rel.state === "removed" && added) { throw new ComponentError(`er: the - relationship ${rel.from} to ${rel.to} touches the added entity ${added.name}, so it would dangle in the Before view. Remove the - or do not add ${added.name}`, rel.line); } if (!rel.state && added && removed) { throw new ComponentError(`er: the relationship ${rel.from} to ${rel.to} joins the added entity ${added.name} and the removed entity ${removed.name}, so it exists in neither view. Remove it or change an entity marker`, rel.line); } } } function settleField(field, mark, entity, n) { const own = markState(mark); const inherited = entity.state === "added" || entity.state === "removed" ? entity.state : null; if (inherited && own && own !== inherited) { throw new ComponentError(`er: "${mark}" under the ${inherited} entity ${entity.name} contradicts it. The fields of a ${inherited} entity are ${inherited} too; remove the marker or move the field`, n); } const state = inherited ?? own; return state ? { ...field, state } : field; } function parseField(line, n) { const tokens = line.split(/\s+/); const name = tokens.shift(); const arrow = tokens.indexOf("->"); const head = arrow === -1 ? tokens : tokens.slice(0, arrow); const ref = arrow === -1 ? null : tokens[arrow + 1]; if (arrow !== -1 && (ref === void 0 || arrow + 2 !== tokens.length)) { throw new ComponentError(`"${line}": write the entity a field points at as "-> Entity", after the key marker`, n); } if (ref !== null && head.at(-1) !== "FK") { throw new ComponentError(`"${line}": only a FK field points at another entity; mark the field FK`, n); } const at3 = head.findIndex((t) => MARKERS.has(t)); if (at3 !== -1 && at3 !== head.length - 1) throw new ComponentError(`"${line}": "${head[at3]}" must be the last word before "->"`, n); const marker = at3 === -1 ? null : head[at3]; const type = head.slice(0, at3 === -1 ? head.length : at3).join(" "); return { name, type, marker, ref, line: n }; } function erLineOf(mermaid) { const [, from, left, right, to, label] = mermaid; const text = label?.trim(); return `${from} ${MERMAID_CARDS[left]}--${MERMAID_CARDS[right]} ${to}${text ? `: ${text}` : ""}`; } function relationships({ entities, written }) { const ends = (...names) => { const states = names.map((name) => entities.get(name).state); return ["removed", "added"].find((s) => states.includes(s)); }; const withState = (rel, state) => state ? { ...rel, state } : rel; const implied = []; for (const entity of entities.values()) { for (const field of entity.fields) { if (!field.ref) continue; const own = field.state === "changed" ? null : field.state; implied.push(withState({ from: entity.name, fromCard: "*", to: field.ref, toCard: "1", label: "", line: field.line }, own ?? ends(entity.name, field.ref))); } } const replaced = new Set(written.map((rel) => pairKey(rel.from, rel.to))); const lines = written.map((rel) => withState(rel, rel.state ?? ends(rel.from, rel.to) ?? implied.find((x2) => pairKey(x2.from, x2.to) === pairKey(rel.from, rel.to) && x2.state)?.state)); return [...implied.filter((rel) => !replaced.has(pairKey(rel.from, rel.to))), ...lines]; } var pairKey = (a, b) => [a, b].sort().join("\0"); var fieldText = (field) => field.type ? `${field.name} ${field.type}` : field.name; var nameWidth = (entity) => measure(entity.name, FS3, { bold: true }); var rowWidth = (entity, field) => measure(fieldText(field), FIELD_FS, { bold: entity.hi }) + keyWidth(entity, field) + signWidth(entity) + 2 * PAD; var signWidth = (entity) => entity.fields.some((x2) => x2.state) ? SIGN_W : 0; var keyWidth = (entity, field) => field.marker ? measure(field.marker, KEY_FS, { mono: true, bold: entity.hi }) + KEY_GAP : 0; function nodeSize2(entity) { const rows = entity.fields.map((field) => rowWidth(entity, field)); return { width: Math.ceil(Math.max(MIN_WIDTH, nameWidth(entity) + 2 * PAD, ...rows)), height: HEAD_LH + entity.fields.length * FIELD_LH + 2 * PAD }; } function layout3(model, rankdir, ui, pageDir = "ltr") { const { entities } = model; const rels = relationships(model); const loops = rels.filter((rel) => rel.from === rel.to); const g = new $o.graphlib.Graph({ multigraph: true }); g.setGraph({ rankdir, nodesep: 44, ranksep: 62, marginx: 14, marginy: 14 }); g.setDefaultEdgeLabel(() => ({})); const key = new Map([...entities.keys()].map((name, i) => [name, `n${i}`])); const below = rankdir === "LR" || rankdir === "RL"; const room = /* @__PURE__ */ new Map(); if (below) { for (const name of entities.keys()) { const own = loops.filter((rel) => rel.from === name); if (own.length) room.set(name, loopRoom(own)); } } const sizes = /* @__PURE__ */ new Map(); for (const entity of entities.values()) { const size = nodeSize2(entity); sizes.set(entity.name, size); g.setNode(key.get(entity.name), { width: size.width, height: size.height + (room.get(entity.name) ?? 0) }); } const centre = (name) => { const { x: x2, y: y2 } = g.node(key.get(name)); return { x: x2, y: y2 - (room.get(name) ?? 0) / 2 }; }; rels.filter((rel) => rel.from !== rel.to).forEach((rel, i) => { const label2 = rel.label ? { label: rel.label, width: measure(rel.label, EDGE_FS2) + 10, height: 18, labelpos: "c" } : {}; g.setEdge(key.get(rel.from), key.get(rel.to), label2, `e${i}`); }); $o.layout(g); const rtl = pageDir === "rtl"; if (rtl) mirrorLayout(g); const steps = [.../* @__PURE__ */ new Set([...[...entities.values()].map((e) => e.line), ...rels.map((r) => r.line)])].sort((a, b) => a - b); const stepOf = new Map(steps.map((line, i) => [line, i])); const straight = rels.filter((rel) => rel.from !== rel.to).map((rel, i) => { const data = g.edge({ v: key.get(rel.from), w: key.get(rel.to), name: `e${i}` }); const points = data.points.map((p) => ({ ...p })); if (room.has(rel.from)) points[0] = clipToBox(centre(rel.from), sizes.get(rel.from), data.points[1]); if (room.has(rel.to)) points[points.length - 1] = clipToBox(centre(rel.to), sizes.get(rel.to), data.points[data.points.length - 2]); return { rel, data, points }; }); const edgeSvg = straight.map(({ rel, data, points }) => { const label2 = rel.label ? labelSvg(rel.label, data.x, data.y, pageDir) : ""; return `${endsSvg(points, rel)}${label2}`; }); const nodeSvg = [...entities.values()].map((entity) => { const { x: x2, y: y2 } = centre(entity.name); const size = sizes.get(entity.name); return nodeSvgOf(entity, x2, y2, size, stepOf.get(entity.line), pageDir); }); const s = rtl ? -1 : 1; const seen = /* @__PURE__ */ new Map(); const loopSpecs = loops.map((rel) => { const { nth, depth } = seen.get(rel.from) ?? { nth: 0, depth: 0 }; seen.set(rel.from, { nth: nth + 1, depth: depth + LOOP_STEP + (rel.label ? LOOP_LABEL : 0) }); const { x: x2, y: y2 } = centre(rel.from); const size = sizes.get(rel.from); return below ? loopBelow(rel, x2, y2, size, depth, s) : loopBeside(rel, x2, y2, size, nth, s); }); const loopsSvg = loopSpecs.map((spec) => loopSvg(spec, stepOf.get(spec.rel.line), pageDir)); const xs = []; const ys = []; const at3 = (x2, y2) => { xs.push(x2); ys.push(y2); }; for (const entity of entities.values()) { const { x: x2, y: y2 } = centre(entity.name); const size = sizes.get(entity.name); at3(x2 - size.width / 2, y2 - size.height / 2); at3(x2 + size.width / 2, y2 + size.height / 2); } straight.forEach(({ rel, data, points }) => { for (const p of points) at3(p.x, p.y); if (rel.label) { const w = labelWidth2(rel.label); at3(data.x - w / 2, data.y - 9); at3(data.x + w / 2, data.y + 9); } }); for (const { corners } of loopSpecs) corners.forEach(([x2, y2]) => at3(x2, y2)); const margin = 14; const shiftX = margin - Math.min(...xs); const shiftY = margin - Math.min(...ys); const shift = shiftX || shiftY ? ` transform="translate(${f(shiftX)},${f(shiftY)})"` : ""; const width = Math.ceil(Math.max(...xs) - Math.min(...xs)) + 2 * margin; const height = Math.ceil(Math.max(...ys) - Math.min(...ys)) + 2 * margin; const label = diagramLabel(ui, "er", [...entities.keys()].slice(0, 8)); return `${svgOpen(width, height, label, pageDir)}${edgeSvg.join("")}${loopsSvg.join("")}${nodeSvg.join("")}`; } function loopRoom(own) { const steps = own.slice(0, -1).reduce((depth, rel) => depth + LOOP_STEP + (rel.label ? LOOP_LABEL : 0), 0); return LOOP_OUT + steps + (own.at(-1).label ? LOOP_LABEL : 0) + LOOP_GAP; } function clipToBox(centre, size, toward) { const dx = toward.x - centre.x; const dy = toward.y - centre.y; const k2 = Math.max(Math.abs(dx) / (size.width / 2), Math.abs(dy) / (size.height / 2)); if (k2 === 0) return { ...centre }; return { x: centre.x + dx / k2, y: centre.y + dy / k2 }; } function nodeSvgOf(entity, x2, y2, size, step, pageDir) { const left = x2 - size.width / 2; const top2 = y2 - size.height / 2; const [nameX, keyX] = pageDir === "rtl" ? [left + size.width - PAD, left + PAD] : [left + PAD, left + size.width - PAD]; const head = `${esc(svgLine(entity.name, pageDir))}`; const rule = ``; const rtl = pageDir === "rtl"; const shift = (rtl ? -1 : 1) * signWidth(entity); const fields2 = entity.fields.map((field, i) => { const cy = top2 + PAD + HEAD_LH + FIELD_LH * (i + 0.5) + 1; const mark = deltaAttr(field.state); const marker = field.marker ? `${esc(svgLine(field.marker, pageDir))}` : ""; const band = field.state ? `${esc(svgLine(SIGN[field.state], pageDir))}` : ""; return `${band}${esc(svgLine(fieldText(field), pageDir))}${marker}`; }).join(""); const badge = badgeSvg(entity.state, rtl ? left : left + size.width, top2); return `${head}${rule}${fields2}${badge}`; } function loopBeside(rel, x2, y2, size, nth, s) { const edge = x2 + s * size.width / 2; const out = edge + s * LOOP_OUT + s * nth * LOOP_STEP; const [ay, by] = [y2 - LOOP_END / 2, y2 + LOOP_END / 2]; const label = rel.label ? { x: out + s * 6 + s * labelWidth2(rel.label) / 2, y: y2 } : null; return { rel, a: { x: edge, y: ay }, b: { x: edge, y: by }, c: [{ x: out, y: ay }, { x: out, y: by }], away: { x: s, y: 0 }, label, corners: [[out + s * 6 + s * (rel.label ? labelWidth2(rel.label) : 0), by], [out, ay]] }; } function loopBelow(rel, x2, y2, size, depth, s) { const bottom = y2 + size.height / 2; const out = bottom + LOOP_OUT + depth; const [ax, bx] = [x2 - s * LOOP_END / 2, x2 + s * LOOP_END / 2]; const half = Math.max(LOOP_END / 2, rel.label ? labelWidth2(rel.label) / 2 : 0); return { rel, a: { x: ax, y: bottom }, b: { x: bx, y: bottom }, c: [{ x: ax, y: out }, { x: bx, y: out }], away: { x: 0, y: 1 }, label: rel.label ? { x: x2, y: out + 6 + 9 } : null, corners: [[x2 - half, bottom], [x2 + half, out + (rel.label ? 6 + 18 : 0)]] }; } function loopSvg({ rel, a, b, c, away, label }, step, pageDir) { const path = ``; const ends = `${endMark(a, { x: a.x + away.x, y: a.y + away.y }, rel.fromCard)}${endMark(b, { x: b.x + away.x, y: b.y + away.y }, rel.toCard)}`; const text = label ? labelSvg(rel.label, label.x, label.y, pageDir) : ""; return `${path}${ends}${text}`; } function labelWidth2(text) { return measure(text, EDGE_FS2) + 10; } function labelSvg(text, x2, y2, pageDir) { const w = labelWidth2(text); return `${textLines([text], x2, y2, LH3, "", pageDir)}`; } function endsSvg(points, rel) { if (points.length < 2) return ""; return endMark(points.at(-1), points.at(-2), rel.toCard) + endMark(points[0], points[1], rel.fromCard); } var R3 = { back: 12, side: 5, gap: 5 }; function endMark(point, toward, card) { const dx = point.x - toward.x; const dy = point.y - toward.y; const length = Math.hypot(dx, dy) || 1; const ux = dx / length; const uy = dy / length; const px = -uy; const py = ux; const at3 = (back, side) => ({ x: point.x - ux * back + px * side, y: point.y - uy * back + py * side }); const line = (a, b) => ``; const bar = (back) => line(at3(back, -R3.side), at3(back, R3.side)); const foot = (apexBack) => { const apex = at3(apexBack, 0); return [line(at3(0, -R3.side), apex), line(at3(0, 0), apex), line(at3(0, R3.side), apex)].join(""); }; const circle = (back) => { const c = at3(back, 0); return ``; }; const marks = { "1": () => bar(R3.back), "0..1": () => bar(R3.back) + circle(R3.back + R3.gap), "*": () => foot(R3.back), "1..*": () => foot(R3.back) + bar(R3.back + R3.gap) }[card](); return `${marks}`; } // src/components/ask.js var OPTION = /^([*-])\s+(.+)$/; var ask_default = { name: "ask", summary: "A decision the reader makes on the page (collected by Reply)", pageOnly: true, panelOnly: true, syntax: `\`\`\`ask [multi] The question, one sentence * The option you suggest | optional note \u2190 * marks the suggestion; it starts selected - Another option | optional note \`\`\` - The first line is the question. Each option starts with * or -. Write 2 to 6 options. - One choice: exactly one * option. multi lets the reader pick several; * marks the ones that start picked. - Place the ask in the panel whose content the answer changes. The Reply button turns the answers into one reply.`, example: "```ask\nWhich cache do we use?\n* Redis | keeps data after a restart\n- Memcached | simpler, no disk\n```", // The text the STE check reads: options are list items; the note reads as a second clause of the item. lint(text) { return text.split("\n").map((l3) => l3.replace(OPTION, (_2, mark, body) => `- ${fields(body).filter(Boolean).join(": ")}`)).join("\n"); }, render(text, { args, uid, ui = {} }) { const multi = /(^|\s)multi(\s|$)/.test(args); const lines = contentLines(text); const [first, ...rest] = lines; if (!first || OPTION.test(first.text)) throw new ComponentError("ask: the first line is the question", first?.line ?? 1); const options = rest.map(({ text: t, line }) => { const m = t.match(OPTION); if (!m) throw new ComponentError(`ask: "${t}" is not an option; start it with * (suggested) or -`, line); const [label, note2 = ""] = fields(m[2]); if (!label) throw new ComponentError("ask: an option needs a label before |", line); return { label, note: note2, suggested: m[1] === "*", line }; }); if (options.length < 2 || options.length > 6) throw new ComponentError(`ask: write 2 to 6 options, not ${options.length}`, first.line); const suggested = options.filter((o) => o.suggested).length; if (!multi && suggested !== 1) { throw new ComponentError(`ask: mark exactly one option with * as your suggestion (found ${suggested}); add multi to let the reader pick several`, first.line); } const seen = /* @__PURE__ */ new Set(); for (const o of options) { if (seen.has(o.label)) throw new ComponentError(`ask: two options are both "${o.label}"`, o.line); seen.add(o.label); } const id = uid(); const type = multi ? "checkbox" : "radio"; const items = options.map((o) => ``); return `
    ${mdInline(first.text)}${items.join("")}
    `; } }; // src/components/index.js var ALL2 = [callout_default, kv_default, timeline_default, annot_default, tree_default, limits_default, sequence_default, flow_default, er_default, ask_default]; var COMPONENTS = new Map(ALL2.map((c) => [c.name, c])); var RAW_LANGS = /* @__PURE__ */ new Set(["html", "svg"]); // src/templates/panel.js function panelHtml(panel, { cols = 3, grid = true, hints = {} } = {}) { const { attrs } = panel; const span = Math.min(Number(attrs.span) || 1, cols); const rows = Number(attrs.rows) || 1; const layout4 = [ grid && span > 1 ? `grid-column: span ${span}` : "", grid && rows > 1 ? `grid-row: span ${rows}` : "" ].filter(Boolean).join("; "); const style = layout4 ? ` style="${layout4}"` : ""; const hinted = Math.floor(Number(hints.span)); const data = grid && hinted >= 1 ? ` data-span="${Math.min(hinted, cols)}"` : ""; const cls = `${span > 1 ? " am-span-wide" : ""}${attrs.bare ? " am-panel--bare" : ""}`; const meta = attrs.meta ? `${esc(attrs.meta)}` : ""; const head = attrs.bare ? "" : `
    ${esc(panel.id)}

    ${esc(panel.title)}

    ${meta}
    `; return `
    ${head}
    ${panel.html}
    `; } var RESERVED = /* @__PURE__ */ new Set(["template", "theme", "style", "mode", "cols", "title", "subtitle", "lang"]); function headHtml(meta, introHtml, language = {}) { const extras = Object.entries(meta).filter(([k2, v]) => !RESERVED.has(k2) && v !== ""); const names = language.metaKeys ?? {}; const value = (v) => language.dir === "rtl" ? `${esc(v)}` : esc(v); const metaRow = extras.length ? `
    ${extras.map(([k2, v]) => `${esc(names[k2.toLowerCase()] ?? k2)}${value(v)}`).join("")}
    ` : ""; const sub = meta.subtitle ? `

    ${esc(meta.subtitle)}

    ` : ""; const intro = introHtml ? `
    ${introHtml}
    ` : ""; return `

    ${esc(meta.title || "Untitled")}

    ${sub}${metaRow}${intro}
    `; } // src/templates/sheet.js var ruler = (side, labels) => ``; function fillRows(panels, cols) { const spans = panels.map((p) => Math.max(1, Math.min(Number(p.attrs.span) || 1, cols))); if (panels.some((p) => Number(p.attrs.rows) > 1)) return spans; let used = 0; return spans.map((span, i) => { if (used + span > cols) used = 0; used += span; const next = spans[i + 1]; const fill2 = next === void 0 || used + next > cols ? cols - used : 0; used = fill2 || used === cols ? 0 : used; return span + fill2; }); } var WIDE_TABLE_COLS = 4; var TABLE_COLS_PER_SPAN = 2; var DIAGRAM_PX_PER_SPAN = 560; var CELL_SEP = /(? b.type === "md").flatMap((b) => { const lines = b.text.split("\n").map((l3) => l3.trim()); return lines.flatMap((l3, i) => l3.startsWith("|") && DELIMITER_ROW.test(lines[i + 1] ?? "") ? [l3.split(CELL_SEP).length - 2] : []); }); return Math.max(0, ...counts); } var svgWidth = (html) => Math.max(0, ...[...html.matchAll(/]*?\swidth="(\d+(?:\.\d+)?)"/g)].map((m) => Number(m[1]))); function minSpan(panel) { const tableCols = tableColumns(panel.blocks ?? []); const byTable = tableCols >= WIDE_TABLE_COLS ? Math.ceil(tableCols / TABLE_COLS_PER_SPAN) : 1; const byDiagram = Math.ceil(svgWidth(panel.html ?? "") / DIAGRAM_PX_PER_SPAN); return Math.max(1, byTable, byDiagram); } function sheet({ meta, introHtml, panels, language }) { const cols = Math.max(1, Math.min(Number(meta.cols) || 3, 12)); const sized = panels.map((p) => p.attrs.span === void 0 && minSpan(p) > 1 ? { ...p, attrs: { ...p.attrs, span: Math.min(minSpan(p), cols) } } : p); const spans = fillRows(sized, cols); const placed = sized.map((p, i) => ({ ...p, attrs: { ...p.attrs, span: spans[i] } })); const nums = Array.from({ length: 8 }, (_2, i) => i + 1); const letters = ["A", "B", "C", "D"]; return `
    ${headHtml(meta, introHtml, language)}
    ${ruler("top", nums)}${ruler("bottom", nums)}${ruler("left", letters)}${ruler("right", letters)}
    ${placed.map((p, i) => panelHtml(p, { cols, hints: { span: panels[i].attrs.span } })).join("\n")}
    `; } // src/templates/doc.js function doc({ meta, introHtml, panels, ui, language }) { const withToc = panels.length >= 3; const toc = withToc ? `
    ` : ""; return `
    ${headHtml(meta, introHtml, language)}
    ${toc}
    ${panels.map((p) => panelHtml(p, { grid: false })).join("\n")}
    `; } // src/templates/index.js var TEMPLATES = { sheet, doc }; // src/themes/index.js var block = (selector, vars) => `${selector} { ${Object.entries(vars).map(([k2, v]) => ` ${k2}: ${v};`).join("\n")} }`; function tokenCss(sel, { common = {}, light = {}, dark = {} }) { const parts = [block(`${sel}, ${sel}[data-mode="light"]`, { ...common, ...light })]; if (Object.keys(dark).length) { parts.push(block(`${sel}[data-mode="dark"]`, dark), `@media (prefers-color-scheme: dark) { ${block(`${sel}[data-mode="auto"]`, dark)} }`); } return parts.join("\n"); } var pageSel = (t) => `html[data-theme="${t.name}"]`; var videoSel = (t) => `html[data-video][data-theme="${t.name}"]`; var scoped = (css, sel) => css.replace(/&/g, sel); var languageFontCss = () => fontLanguages().map((l3) => block(langSelector(l3, "html", "[data-theme][data-mode]"), { "--font-sans": l3.fonts.sans })); var ownLanguageFont = (t) => fontLanguages().map((l3) => block(langSelector(l3, "html", `[data-theme="${t.name}"][data-mode]`), { "--font-sans": t.tokens.common["--font-sans"] })); function pageCss(list = themes("page"), { diff = false, delta = false, rtl = false, math: math2 = false } = {}) { const decorations = list.filter((t) => t.css).map((t) => scoped(t.css, pageSel(t))); const ownFonts = list.filter((t) => t.ownFont).flatMap(ownLanguageFont); return [list.map((t) => tokenCss(pageSel(t), t.tokens)).join("\n\n"), ...languageFontCss(), ...ownFonts, BASE_CSS, ...math2 ? [MATH_CSS] : [], ...diff ? [DIFF_CSS] : [], ...delta ? [DELTA_CSS] : [], ...rtl ? [RTL_CSS] : [], ...decorations].join("\n\n"); } function videoCss(list = themes("video"), { diff = false, delta = false, rtl = false } = {}) { const parts = list.filter((t) => t.video).flatMap((t) => [ t.video.tokens ? tokenCss(videoSel(t), t.video.tokens) : "", t.video.css ? scoped(t.video.css, videoSel(t)) : "", rtl && t.video.rtlCss ? scoped(t.video.rtlCss, videoSel(t)) : "" ]).filter(Boolean); return [pageCss(list.filter((t) => t.scope.includes("page")), { diff, delta, rtl }), VIDEO_CSS, ...parts].join("\n\n"); } // src/lint/wordlist.en.js var EN_WORDS2 = Object.freeze({ "utilize": "use", "utilise": "use", "utilization": "use", "commence": "start", "commenced": "started", "prior to": "before", "in order to": "to", "approximately": "about", "ensure": "make sure", "replenish": "fill", "terminate": "stop", "facilitate": "help", "leverage": "use", "numerous": "many", "subsequently": "then", "endeavor": "try", "ascertain": "find", "sufficient": "enough", "demonstrate": "show", "assist": "help", "obtain": "get", "initiate": "start", "modify": "change", "possess": "have", "purchase": "buy", "in the event that": "if", "due to the fact that": "because", "at this point in time": "now", "a number of": "some", "with regard to": "about", "in addition": "also" }); // src/lint/wordlist.zh.js var ZH_LIGHT_VERBS = Object.freeze([ { re: /进行(?![中时])了?([一-龥]{2})/g, label: "\u8FDB\u884C" }, { re: /(?:加以|予以)([一-龥]{2})/g, label: "\u52A0\u4EE5/\u4E88\u4EE5" }, { re: /[做作]出了?([一-龥]{2})/g, label: "\u505A\u51FA" } ]); var ZH_CLICHES = Object.freeze([ "\u8D4B\u80FD", "\u6293\u624B", "\u95ED\u73AF", "\u6253\u901A", "\u5168\u65B9\u4F4D", "\u591A\u7EF4\u5EA6", "\u6DF1\u5EA6\u878D\u5408", "\u663E\u8457\u63D0\u5347", "\u81F3\u5173\u91CD\u8981", "\u4E0D\u53EF\u6216\u7F3A", "\u4E0E\u6B64\u540C\u65F6", "\u7EFC\u4E0A\u6240\u8FF0", "\u503C\u5F97\u6CE8\u610F\u7684\u662F", "\u603B\u800C\u8A00\u4E4B", "\u4F17\u6240\u5468\u77E5", "\u6BCB\u5EB8\u7F6E\u7591", "\u4E00\u7AD9\u5F0F", "\u5E95\u5C42\u903B\u8F91", "\u9897\u7C92\u5EA6", "\u65B9\u6CD5\u8BBA" ]); var UNIT = String.raw`(?:个|次|秒|天|分钟|小时|倍|字|条|项|人|行|位|%|MB|GB|KB|TB|ms)?`; var ZH_WORDS = Object.freeze([ // typos { re: /登陆/g, suggestion: "\u767B\u5F55" }, { re: /帐号/g, suggestion: "\u8D26\u53F7" }, { re: /阀值/g, suggestion: "\u9608\u503C" }, { re: /布署/g, suggestion: "\u90E8\u7F72" }, // quantities without a number { re: /尽快/g, suggestion: "give a concrete deadline" }, { re: /若干/g, suggestion: "write the number" }, { re: /大概|大约/g, suggestion: 'use "\u7EA6" in descriptions, a value in steps' }, { re: /多次/g, suggestion: "write the count" }, // 以上/以下/以内 after a number: it is unclear whether the endpoint is included { re: new RegExp(String.raw`(?<=\d\s*${UNIT}\s*)(?:以上|以下)`, "g"), suggestion: "name the endpoint: \u5927\u4E8E / \u4E0D\u5C0F\u4E8E, \u5C0F\u4E8E / \u4E0D\u5927\u4E8E" }, { re: /(?<=\d[^。,;\n]{0,6})以内/g, suggestion: "\u4E0D\u8D85\u8FC7" }, // one meaning, one word { re: /单击|点按/g, suggestion: "\u70B9\u51FB" }, { re: /键入/g, suggestion: "\u8F93\u5165" }, { re: /登出/g, suggestion: "\u9000\u51FA\u767B\u5F55" }, { re: /入参/g, suggestion: "\u53C2\u6570" }, { re: /出参/g, suggestion: "\u8FD4\u56DE\u503C" }, { re: /缺省/g, suggestion: "\u9ED8\u8BA4" } ]); // src/lint/ste.js var LIMITS = { zh: { procedural: 35, descriptive: 45 }, en: { procedural: 20, descriptive: 25 } }; var MAX_SENTENCES = 6; var ABBR = /\b(e\.g|i\.e|etc|vs|cf|approx|Fig|No)\./gi; var PASSIVE = /\b(?:am|is|are|was|were|be|been|being)\s+(?:\w+ly\s+)?(\w+ed|known|done|made|given|taken|seen|written|built|shown|sent|kept|held|found|set|put|run|begun|chosen|driven|broken)\b/i; var EN_RE = Object.entries(EN_WORDS2).sort((a, b) => b[0].length - a[0].length).map(([word, suggestion]) => ({ re: new RegExp(`\\b${word.replace(/ /g, "\\s+")}\\b`, "gi"), word, suggestion })); function splitSentences(text) { const masked = text.replace(ABBR, (m) => m.replace(/\./g, "\0")); const parts = masked.match(/[^。!?;!?;]+?(?:[。!?;!?;]+|\.(?=\s|$)|$)|[^.]+?\.(?=\s|$)/g) ?? []; return parts.map((s) => s.replace(/\u0000/g, ".").trim()).filter(Boolean); } function sentenceLength(sentence) { const cjk = [...sentence].filter(isCJK).filter((c) => !/[,。!?;:、()「」『』“”‘’《》]/.test(c)).length; const words3 = sentence.match(/[A-Za-z0-9][\w'’-]*/g)?.length ?? 0; return cjk >= 4 || cjk > words3 ? { lang: "zh", count: cjk + words3 } : { lang: "en", count: words3 }; } var CJK_TEXT = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}]/gu; function neutralLength(sentence) { const han = sentence.match(CJK_TEXT)?.length ?? 0; const rest = sentence.replace(CJK_TEXT, " "); const words3 = countWords(rest); return han >= 4 || han > words3 ? { lang: "zh", count: han + words3 } : { lang: "en", count: words3 }; } var RULE_LANGUAGES = /* @__PURE__ */ new Set(["zh", "en", "ja"]); function ruleFamily(language) { if (!language) return "auto"; const base = language.tag.split("-")[0]; if (!RULE_LANGUAGES.has(base)) return "neutral"; return language.declared ? base : "auto"; } function formatWarning(w) { return `L${w.line} [${w.rule}] ${w.message}${w.suggestion ? ` \u2192 ${w.suggestion}` : ""}`; } function lintDoc(doc2, language) { const warnings = []; const family = ruleFamily(language); const blocks = [...doc2.intro, ...doc2.panels.flatMap((p) => p.blocks)]; for (const b of blocks) { if (b.type === "md") lintMarkdown(b.text, b.line, warnings, family); else if (b.lang === "callout") lintMarkdown(b.text, b.line + 1, warnings, family); else if (COMPONENTS.get(b.lang)?.lint) lintMarkdown(COMPONENTS.get(b.lang).lint(b.text), b.line + 1, warnings, family); } return warnings; } function clean(text) { return stripMath(text.replace(/~~[^~]*~~/g, "").replace(/`[^`]*`/g, "")).replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/<[^>]+>/g, "").replace(/[*_]{1,3}/g, ""); } function lintMarkdown(text, startLine, out, family) { let para = null; const flush = () => { if (para && para.count > MAX_SENTENCES) { out.push({ line: para.line, rule: "paragraph-length", message: `paragraph has ${para.count} sentences (max ${MAX_SENTENCES})` }); } para = null; }; let inHtml = false; let inMath = false; text.split("\n").forEach((raw, i) => { const line = startLine + i; const t = raw.trim(); if (/^<(div|svg|table|details|figure)/i.test(t)) inHtml = true; if (inHtml) { if (/<\/(div|svg|table|details|figure)>\s*$/i.test(t)) inHtml = false; return flush(); } if (inMath) { if (!t || closesDisplay(t)) inMath = false; return flush(); } if (opensDisplay(raw) || isDisplayLine(raw)) { inMath = opensDisplay(raw); return flush(); } if (!t || /^#{1,6}\s/.test(t) || /^[-*_]{3,}$/.test(t)) return flush(); if (t.startsWith("|")) { flush(); if (/^\|?[\s:|-]+\|?$/.test(t)) return; const cells = t.replace(/^\||\|$/g, "").split("|").map((c) => c.trim()); if (cells.some((c) => /^(no|✗|✘)(\s|$)/.test(c))) return; cells.forEach((c) => checkUnit(clean(c.replace(/^(ok|warn|✓|✔|⚠)(\s|$)/, "")), line, "descriptive", out, family)); return; } const list = t.match(/^(?:([-*+])|(\d+)[.)])\s+(.*)$/); if (list) { flush(); checkUnit(clean(list[3]), line, list[2] ? "procedural" : "descriptive", out, family); return; } const body = clean(t.replace(/^>\s*/, "")); const n = checkUnit(body, line, "descriptive", out, family); if (!para) para = { line, count: 0 }; para.count += n; }); flush(); } function checkUnit(text, line, kind2, out, family) { const sentences = splitSentences(text); const zhFamily = family === "auto" || family === "zh"; const ja = family === "ja" || zhFamily && isJapanese(text); const chineseRules = zhFamily && !ja; const englishRules = family !== "neutral"; for (const s of sentences) { const { lang, count: count2 } = family === "neutral" ? neutralLength(s) : sentenceLength(s); const limit = LIMITS[lang][kind2]; if (count2 > limit) { const unit = lang === "zh" ? "characters" : "words"; const preview2 = s.length > 24 ? `${s.slice(0, 24)}\u2026` : s; out.push({ line, rule: "sentence-length", message: `${kind2 === "procedural" ? "step" : "sentence"} has ${count2} ${unit} (max ${limit}): "${preview2}"` }); } if (englishRules && lang === "en" && PASSIVE.test(s)) { out.push({ line, rule: "passive", message: `possible passive voice: "${s.match(PASSIVE)[0]}"`, suggestion: "use active voice" }); } } const lexical = [ ...(englishRules ? EN_RE : []).flatMap(({ re: re3, suggestion }) => [...text.matchAll(re3)].map((m) => ({ index: m.index, rule: "word", message: `not recommended: "${m[0]}"`, suggestion }))), ...(chineseRules ? ZH_LIGHT_VERBS : []).flatMap(({ re: re3, label }) => [...text.matchAll(re3)].map((m) => ({ index: m.index, rule: "word", message: `light verb "${m[0]}" (${label})`, suggestion: `use "${m[1]}"` }))), ...(chineseRules ? ZH_WORDS : []).flatMap(({ re: re3, suggestion }) => [...text.matchAll(re3)].map((m) => ({ index: m.index, rule: "word", message: `not recommended: "${m[0]}"`, suggestion }))) ]; out.push(...lexical.sort((a, b) => a.index - b.index).map(({ index, ...w }) => ({ line, ...w }))); for (const s of sentences) { if (!zhFamily || isJapanese(s)) continue; if ((s.match(/的/g) ?? []).length >= 3) out.push({ line, rule: "de-chain", message: `chained "\u7684": ${s}`, suggestion: 'split the sentence or remove extra "\u7684"' }); } for (const c of chineseRules ? ZH_CLICHES : []) { if (text.includes(c)) out.push({ line, rule: "cliche", message: `clich\xE9 "${c}"`, suggestion: "delete it or state a concrete fact" }); } return sentences.length; } // src/page.js var SOURCE_OPEN = '`; } function readPage(html) { const s = String(html); const root = s.match(/]*>/)?.[0] ?? ""; const attr = (name) => root.match(new RegExp(`\\s${name}="([^"]+)"`))?.[1]; const video = /\sdata-video\b/.test(root); const open = s.lastIndexOf(SOURCE_OPEN); const m = open === -1 ? null : s.slice(open).match(SOURCE_RE); const before = open === -1 ? "" : s.slice(0, open).trimEnd(); return { source: m ? unescapeHtml(m[1]) : null, video, // A
    in the body does not count; the template's
    always comes before the body. template: video ? "video" : s.match(/
    ") && before.lastIndexOf(AUDIO_OPEN) > before.lastIndexOf("").replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, "&"); } // src/han-forms.js var PAIRS = "\u8FD9\u9019 \u4E2A\u500B \u4EEC\u5011 \u8BF4\u8AAA \u56FD\u570B \u4E3A\u70BA \u6765\u4F86 \u65F6\u6642 \u4F1A\u6703 \u8FC7\u904E \u5BF9\u5C0D \u5B66\u5B78 \u8FD8\u9084 \u6CA1\u6C92 \u6837\u6A23 \u5F00\u958B \u95E8\u9580 \u95EE\u554F \u95F4\u9593 \u70B9\u9EDE \u73B0\u73FE \u79CD\u7A2E \u7ECF\u7D93 \u52A8\u52D5 \u5B9E\u5BE6 \u673A\u6A5F \u5173\u95DC \u4E1A\u696D \u4E0E\u8207 \u65E0\u7121 \u7535\u96FB \u4E66\u66F8 \u9A6C\u99AC \u8F66\u8ECA \u89C1\u898B \u4E70\u8CB7 \u5356\u8CE3 \u8BFB\u8B80 \u8BED\u8A9E \u8BDD\u8A71 \u8BF7\u8ACB \u8BA9\u8B93 \u8BA4\u8A8D \u5E94\u61C9 \u5F53\u7576 \u603B\u7E3D \u5C06\u5C07 \u4F53\u9AD4 \u534E\u83EF \u58F0\u8072 \u542C\u807D \u89C2\u89C0 \u89C9\u89BA \u8BB0\u8A18 \u8BBE\u8A2D \u8BA1\u8A08 \u8BBA\u8AD6 \u8BAE\u8B70 \u8BB8\u8A31 \u8BC1\u8B49 \u8BC6\u8B58 \u8C03\u8ABF \u8BD5\u8A66 \u8BE5\u8A72 \u8BE6\u8A73 \u8BEF\u8AA4 \u8C08\u8AC7 \u8C22\u8B1D \u8C01\u8AB0 \u8BFE\u8AB2 \u8D1F\u8CA0 \u8D23\u8CAC \u8D35\u8CB4 \u8D44\u8CC7 \u8D39\u8CBB \u8D5B\u8CFD \u8D22\u8CA1 \u8D2D\u8CFC \u8D27\u8CA8 \u8D38\u8CBF \u8D28\u8CEA \u94B1\u9322 \u94F6\u9280 \u94C1\u9435 \u7F51\u7DB2 \u9875\u9801 \u7EA7\u7D1A \u7EBF\u7DDA \u7C7B\u985E \u6570\u6578 \u636E\u64DA \u5E93\u5EAB \u6237\u6236 \u52A1\u52D9 \u533A\u5340 \u4E1C\u6771 \u4E50\u6A02 \u4EA7\u7522 \u4EB2\u89AA \u513F\u5152 \u529E\u8FA6 \u5174\u8208 \u519B\u8ECD \u519C\u8FB2 \u51B5\u6CC1 \u5218\u5289 \u521B\u5275 \u5267\u5287 \u5355\u55AE \u53CC\u96D9 \u53F7\u865F \u5458\u54E1 \u56ED\u5712 \u56F4\u570D \u56FE\u5716 \u5706\u5713 \u573A\u5834 \u5757\u584A \u574F\u58DE \u5904\u8655 \u5907\u5099 \u5934\u982D \u5939\u593E \u594B\u596E \u5987\u5A66 \u5B59\u5B6B \u5B81\u5BE7 \u5B9D\u5BF6 \u5BA1\u5BE9 \u5C42\u5C64 \u5C5E\u5C6C \u5C81\u6B72 \u5E08\u5E2B \u5E26\u5E36 \u5E2E\u5E6B \u5E7F\u5EE3 \u5F02\u7570 \u5F20\u5F35 \u5F3A\u5F37 \u5F55\u9304 \u5F52\u6B78 \u5F7B\u5FB9 \u5F84\u5F91 \u60AC\u61F8 \u60CA\u9A5A \u6218\u6230 \u62A4\u8B77 \u62A5\u5831 \u62E9\u64C7 \u62C5\u64D4 \u62E5\u64C1 \u62DF\u64EC \u6362\u63DB \u635F\u640D \u654C\u6575 \u65AD\u65B7 \u65E7\u820A \u663E\u986F \u6653\u66C9 \u6682\u66AB \u672F\u8853 \u6742\u96DC \u6781\u6975 \u6784\u69CB \u6807\u6A19 \u680F\u6B04 \u6811\u6A39 \u6863\u6A94 \u6865\u6A4B \u68C0\u6AA2 \u697C\u6A13 \u6B22\u6B61 \u6BD5\u7562 \u6C14\u6C23 \u6C49\u6F22 \u6D4E\u6FDF \u6D4F\u700F \u6D4B\u6E2C \u6E7E\u7063 \u6EE1\u6EFF \u706D\u6EC5 \u706F\u71C8 \u7231\u611B \u72B6\u72C0 \u72EC\u7368 \u73AF\u74B0 \u753B\u756B \u7597\u7642 \u76D8\u76E4 \u7801\u78BC \u786E\u78BA \u79BB\u96E2 \u79EF\u7A4D \u79F0\u7A31 \u7A77\u7AAE \u7ADE\u7AF6 \u7B14\u7B46 \u7B80\u7C21 \u7CAE\u7CE7 \u7D27\u7DCA \u7EA2\u7D05 \u7EA6\u7D04 \u7EAA\u7D00 \u7EAF\u7D14 \u7EB8\u7D19 \u7EC4\u7D44 \u7EC6\u7D30 \u7EC7\u7E54 \u7EC8\u7D42 \u7ED3\u7D50 \u7ED9\u7D66 \u7EDC\u7D61 \u7EDF\u7D71 \u7EE7\u7E7C \u7EED\u7E8C \u7EF4\u7DAD \u7EFC\u7D9C \u7EFF\u7DA0 \u7F13\u7DE9 \u7F16\u7DE8 \u7F57\u7F85 \u4E60\u7FD2 \u8054\u806F \u804C\u8077 \u8111\u8166 \u8138\u81C9 \u8282\u7BC0 \u8425\u71DF \u84DD\u85CD \u8651\u616E \u867D\u96D6 \u8865\u88DC \u88C5\u88DD \u89C8\u89BD \u89C4\u898F \u89C6\u8996 \u89E6\u89F8 \u8BA2\u8A02 \u8BA8\u8A0E \u8BAD\u8A13 \u8BB2\u8B1B \u8BBF\u8A2A \u8BC4\u8A55 \u8BCD\u8A5E \u8BD1\u8B6F \u8BC9\u8A34 \u8F93\u8F38 \u8F91\u8F2F \u8FB9\u908A \u8FBE\u9054 \u8FC1\u9077 \u8FD0\u904B \u8FDC\u9060 \u8FDE\u9023 \u8FDB\u9032 \u9009\u9078 \u9012\u905E \u9002\u9069 \u903B\u908F \u9057\u907A \u90AE\u90F5 \u94FA\u92EA \u94FE\u93C8 \u9500\u92B7 \u9501\u9396 \u9519\u932F \u952E\u9375 \u955C\u93E1 \u957F\u9577 \u95EA\u9583 \u95ED\u9589 \u95FB\u805E \u9605\u95B1 \u961F\u968A \u9636\u968E \u9645\u969B \u9690\u96B1 \u96BE\u96E3 \u9759\u975C \u9876\u9802 \u9879\u9805 \u987A\u9806 \u987B\u9808 \u9898\u984C \u989D\u984D \u98CE\u98A8 \u98DE\u98DB \u996D\u98EF \u9986\u9928 \u9A8C\u9A57 \u9A97\u9A19 \u9C7C\u9B5A \u9E1F\u9CE5 \u9E21\u96DE \u9EA6\u9EA5 \u9F50\u9F4A \u9F7F\u9F52 \u9F99\u9F8D".split(" "); var SIMPLIFIED_ONLY = PAIRS.map((pair) => [...pair][0]).join(""); var TRADITIONAL_ONLY = PAIRS.map((pair) => [...pair][1]).join(""); // src/language.js var CJK_PER_LATIN = 3; var SCRIPT_LANGUAGES = [ [new RegExp("\\p{Script=Thai}", "u"), "th"], [new RegExp("\\p{Script=Hebrew}", "u"), "he"], [new RegExp("\\p{Script=Greek}", "u"), "el"], [new RegExp("\\p{Script=Arabic}", "u"), "ar"], [new RegExp("\\p{Script=Cyrillic}", "u"), "ru"] ]; var HANGUL = new RegExp("\\p{Script=Hangul}", "u"); var SIMPLIFIED = new Set(SIMPLIFIED_ONLY); var TRADITIONAL = new Set(TRADITIONAL_ONLY); function hanLanguage(text) { let simplified = 0; let traditional = 0; for (const ch of text) { if (SIMPLIFIED.has(ch)) simplified++; else if (TRADITIONAL.has(ch)) traditional++; } return traditional > simplified ? "zh-Hant" : "zh"; } function detectLang(text) { const draft = String(text); let cjk = 0; let hangul = 0; let latin = 0; const others = /* @__PURE__ */ new Map(); for (const ch of draft) { if (isCJK(ch)) { cjk++; if (HANGUL.test(ch)) hangul++; } else if (/[a-z]/i.test(ch)) { latin++; } else { const script = SCRIPT_LANGUAGES.find(([re3]) => re3.test(ch)); if (script) others.set(script[1], (others.get(script[1]) ?? 0) + 1); } } const otherTotal = [...others.values()].reduce((sum2, n) => sum2 + n, 0); if ((cjk + otherTotal) * CJK_PER_LATIN < latin) return "en"; const [topTag, topCount] = [...others].sort((a, b) => b[1] - a[1])[0] ?? [null, 0]; if (topCount > cjk) return topTag; if (hangul * 2 > cjk) return "ko"; return isJapanese(draft) ? "ja" : hanLanguage(draft); } function canonicalTag(value) { if (typeof value !== "string") return null; const text = value.trim().replace(/_/g, "-"); if (!text) return null; try { const tag = Intl.getCanonicalLocales(text)[0]; const { language } = new Intl.Locale(tag); return !language || language === "und" ? null : tag; } catch { return null; } } var baseLanguage = (tag) => new Intl.Locale(tag).language; function voiceHints(tag) { const locale = new Intl.Locale(tag).maximize(); return findLanguage(locale.language, locale.script)?.voice ?? null; } function directionOf(locale) { const info = typeof locale.getTextInfo === "function" ? locale.getTextInfo() : locale.textInfo; return info?.direction === "rtl" ? "rtl" : "ltr"; } function resolveLanguage({ declared, previous, text = "" }) { const declaredTag = canonicalTag(declared); const tag = declaredTag ?? canonicalTag(previous) ?? detectLang(text); const locale = new Intl.Locale(tag).maximize(); const entry = findLanguage(locale.language, locale.script); const labels = entry ?? FALLBACK; return Object.freeze({ tag, declared: declaredTag !== null, htmlLang: tag === "zh" ? "zh-CN" : tag, script: locale.script, dir: directionOf(locale), supported: Boolean(entry), labelKey: labels.id, ui: labels.deltaCounts ? { ...labels.ui, delta: { ...labels.ui.delta, counts: labels.deltaCounts } } : labels.ui, videoUi: labels.videoUi, metaKeys: labels.metaKeys ?? {}, dateOrder: labels.dateOrder ?? "ymd" }); } // src/images.js import { readFileSync as readFileSync2, statSync } from "node:fs"; import { homedir } from "node:os"; import { extname, isAbsolute, resolve } from "node:path"; import { fileURLToPath } from "node:url"; var TYPES = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp", ".avif": "image/avif", ".svg": "image/svg+xml" }; var MAX_IMAGE_BYTES = 5 * 1024 * 1024; var IMAGE_EXAMPLE = "![What the picture shows](/absolute/path/to/screenshot.png)"; var NOT_LOCAL = /^(?:(?!file:)[a-z][a-z0-9+.-]+:|\/\/|#)/i; var IMG_SRC = /]*?)\bsrc="([^"]*)"/g; var TEXT_IMAGE = /!\[[^\]\n]*\](?:\([^)\n]*\)|\[[^\]\n]*\])/; var EMBEDDED = /]*?\bdata-am-src="([^"]*)" src="(data:[^"]*)"/g; var ImageError = class extends Error { constructor(message, ref) { super(message); this.name = "ImageError"; this.ref = ref; } }; function decodeRef(src) { const ref = src.replace(/&/g, "&"); try { return decodeURI(ref); } catch { return ref; } } function localPath(ref, baseDir) { if (ref.startsWith("file:")) return fileURLToPath(ref); if (ref.startsWith("~/")) return resolve(homedir(), ref.slice(2)); return resolve(baseDir, ref); } function readImage(ref, baseDir) { const path = localPath(ref, baseDir); const type = TYPES[extname(path).toLowerCase()]; if (!type) throw new ImageError(`"${ref}" is not an image file; use ${Object.keys(TYPES).join(" ")}`, ref); let size; try { size = statSync(path).size; } catch { return null; } if (size > MAX_IMAGE_BYTES) { throw new ImageError(`"${ref}" is ${(size / 1048576).toFixed(1)} MB; the limit is ${MAX_IMAGE_BYTES / 1048576} MB. Shrink or crop the image first`, ref); } return `data:${type};base64,${readFileSync2(path).toString("base64")}`; } function assertNoImageText(html) { const left = html.replace(/<(code|pre)\b[\s\S]*?<\/\1>/g, "").replace(/<[^>]*>/g, "").match(TEXT_IMAGE); if (left) throw new ImageError(`"${left[0]}" was not read as an image. Check the path: write a space as %20, or put the path in < and >`, left[0]); } function inlineImages(html, { baseDir = process.cwd(), known = /* @__PURE__ */ new Map(), checkText = true } = {}) { if (checkText) assertNoImageText(html); return html.replace(IMG_SRC, (whole, before, src) => { const ref = decodeRef(src); if (NOT_LOCAL.test(ref)) return whole; const uri = readImage(ref, baseDir) ?? known.get(ref); if (!uri) throw new ImageError(`Image not found: "${ref}"${isAbsolute(ref) ? "" : ` (relative paths are read from ${baseDir})`}. Use the absolute path of an existing file`, ref); return ` [decodeRef(src), uri])); } // src/code.js import { readFileSync as readFileSync3, statSync as statSync2 } from "node:fs"; import { basename as basename2, extname as extname2, isAbsolute as isAbsolute2, relative, resolve as resolve2, sep } from "node:path"; // src/diff.js var DiffError = class extends Error { constructor(message, line) { super(message); this.name = "DiffError"; this.line = line; } }; var DIFF_EXAMPLE = "```diff file=src/code.js\n@@ -60,3 +60,3 @@\n export function parseCodeArgs(args) {\n- const attrs = parseAttrs(args);\n+ const attrs = parseAttrs(args, { diff: true });\n const opts = {};\n```"; var HUNK = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/; var GIT_HEAD = /^(?:diff --git |index |new file mode |deleted file mode |old mode |new mode |similarity index |rename (?:from|to) )/; var ELLIPSIS = /^ ?(?:…|\.\.\.)\s*$/; var KIND = { "+": "add", "-": "del", " ": "ctx" }; var headPath = (line) => { const path = line.slice(4).split(" ")[0].trim().replace(/^[ab]\//, ""); return path === "/dev/null" ? "" : path; }; function parseDiff(text, start) { const lines = text.split("\n"); while (lines.length && lines[lines.length - 1] === "") lines.pop(); const numbered = start !== void 0 || lines.some((l3) => HUNK.test(l3)); const rows = []; const warnings = []; const newLines = /* @__PURE__ */ new Set(); let title = ""; let from = ""; let o = start ?? 1; let n = start ?? 1; let hunk = null; let started = false; const closeHunk = () => { if (hunk && (hunk.got.o !== hunk.want.o || hunk.got.n !== hunk.want.n)) { warnings.push({ rule: "diff-counts", message: `the hunk "${hunk.header}" has ${hunk.got.o} old and ${hunk.got.n} new lines, but its header says ${hunk.want.o} and ${hunk.want.n}. Fix the numbers in the @@ line, or add the missing lines` }); } hunk = null; }; const preamble = () => !hunk && !started || hunk && hunk.got.o >= hunk.want.o && hunk.got.n >= hunk.want.n; lines.forEach((line, i) => { const fail = (message) => { throw new DiffError(message, i + 1); }; const meta = () => rows.push({ kind: "meta", text: line }); if (line.startsWith("diff --git ")) { closeHunk(); started = false; from = ""; return meta(); } if (GIT_HEAD.test(line) || line.startsWith("\\")) return meta(); if (preamble() && line.startsWith("--- ") && lines[i + 1]?.startsWith("+++ ")) { from = headPath(line); return meta(); } if (preamble() && line.startsWith("+++ ") && rows[rows.length - 1]?.text.startsWith("--- ")) { title ||= headPath(line) || from; return meta(); } const m = line.match(HUNK); if (m) { closeHunk(); o = Number(m[1]); n = Number(m[3]); hunk = { header: m[0], want: { o: m[2] === void 0 ? 1 : Number(m[2]), n: m[4] === void 0 ? 1 : Number(m[4]) }, got: { o: 0, n: 0 } }; return rows.push({ kind: "hunk", text: line }); } if (line.startsWith("@@")) fail(`line ${i + 1} is not a hunk header; write it as @@ -60,4 +60,5 @@ (old start and count, new start and count)`); if (ELLIPSIS.test(line)) fail(`line ${i + 1} stands for cut lines: the gutter cannot know how many lines were skipped. Split the diff into two hunks, each with its own @@ header`); if (!rows.length && line === "") return; const kind2 = line === "" ? "ctx" : KIND[line[0]]; if (!kind2) fail(`line ${i + 1} is not a diff line ("${line.length > 40 ? `${line.slice(0, 40)}\u2026` : line}"); start it with +, -, a space or @@. A line of code that did not change starts with a space`); started = true; const row = { kind: kind2, text: line }; if (kind2 !== "add") { row.o = o++; if (hunk) hunk.got.o++; } if (kind2 !== "del") { row.n = n; newLines.add(n++); if (hunk) hunk.got.n++; } rows.push(row); }); closeHunk(); const count2 = (kind2) => rows.filter((r) => r.kind === kind2).length; return { rows, title, add: count2("add"), del: count2("del"), numbered, newLines, warnings }; } function diffRowsHtml({ rows, numbered }, hl) { return rows.map((r) => { const marked2 = r.n !== void 0 && hl.has(r.n) ? " am-ln--hl" : ""; const nums = numbered ? `${r.o === void 0 ? "" : ` data-o="${r.o}"`}${r.n === void 0 ? "" : ` data-n="${r.n}"`}` : ""; return `${esc(r.text)}`; }).join(""); } // src/code.js var MAX_CODE_LINES = 200; var LONG_CODE_LINES = 40; var MAX_FILE_BYTES = 2 * 1024 * 1024; var KEYS = /* @__PURE__ */ new Set(["src", "lines", "hl", "title", "start"]); var DIFF_KEYS = /* @__PURE__ */ new Set([...KEYS, "file"]); var CODE_EXAMPLE = "```ts src=server/routes.ts lines=18-30 hl=22\n```"; var SECRET_FILE = new RegExp([ "^\\.env(?!\\.(?:example|sample|template)$)(?:\\..*)?$", "^(?:\\.netrc|\\.npmrc|\\.yarnrc(?:\\.yml)?|\\.pypirc|\\.pgpass|\\.my\\.cnf|\\.git-credentials|\\.htpasswd)$", "^(?:id_(?:rsa|dsa|ecdsa|ed25519).*|credentials.*|secrets?(?:\\..*)?|.*_history|.*\\.local\\.json)$", "\\.(?:pem|key|p12|pfx|keystore|jks|tfvars|tfstate(?:\\.backup)?|sqlite3?|db|kdbx|ovpn)$" ].join("|"), "i"); var SECRET_DIR = /* @__PURE__ */ new Set([".git", ".ssh", ".aws", ".azure", ".gnupg", ".kube", ".docker", ".password-store"]); var SECRET_TEXT = /sk-ant-|sk-[A-Za-z0-9]{32,}|AKIA[0-9A-Z]{16}|-----BEGIN [A-Z ]*PRIVATE KEY|gh[pousr]_[A-Za-z0-9]{30,}|github_pat_[A-Za-z0-9_]{30,}|xox[abeprs]-[A-Za-z0-9-]{10,}|AIza[0-9A-Za-z_-]{30,}|eyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{10,}\.|(?:password|passwd|secret|api[_-]?key|access[_-]?token|auth[_-]?token)["']?\s*[:=]\s*["'][^"'\s$<{]{12,}["']/i; var CodeError = class extends Error { constructor(message, line = 0, example = CODE_EXAMPLE) { super(message); this.name = "CodeError"; this.line = line; this.example = example; } }; function parseRange(value, name) { const m = String(value).match(/^(\d+)(?:-(\d+))?$/); if (!m) throw new CodeError(`${name}="${value}" is not a line range; write ${name}=18-30 or ${name}=18`); const from = Number(m[1]); const to = m[2] === void 0 ? from : Number(m[2]); if (from < 1 || to < from) throw new CodeError(`${name}="${value}": the first line must be 1 or more and not after the last line`); return { from, to }; } function parseHighlight(value) { const set = /* @__PURE__ */ new Set(); for (const part of String(value).split(",").map((s) => s.trim()).filter(Boolean)) { const { from, to } = parseRange(part, "hl"); for (let n = from; n <= to; n++) set.add(n); } return set; } function parseCodeArgs(args, diff = false) { const attrs = parseAttrs(args); const keys = diff ? DIFF_KEYS : KEYS; for (const [key, value] of Object.entries(attrs)) { if (value !== true && !keys.has(key)) throw new CodeError(`unknown code block setting "${key}"; use ${[...keys].join(", ")}`, 0, diff ? DIFF_EXAMPLE : CODE_EXAMPLE); } const text = (key) => typeof attrs[key] === "string" || typeof attrs[key] === "number" ? String(attrs[key]) : void 0; return { src: text("src"), lines: text("lines"), hl: text("hl"), title: text("title"), start: text("start"), file: text("file") }; } function localPath2(ref, baseDir) { const path = resolve2(baseDir, ref); const rel = relative(baseDir, path); return rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute2(rel) ? null : path; } function readSlice(ref, range, baseDir) { const path = localPath2(ref, baseDir); if (!path) return null; if (SECRET_FILE.test(basename2(path)) || relative(baseDir, path).split(sep).some((part) => SECRET_DIR.has(part))) { throw new CodeError(`"${ref}" is a file that holds keys or passwords by convention; it is not embedded. Write a sketch instead`); } let size; try { const stat = statSync2(path); if (!stat.isFile()) throw new CodeError(`"${ref}" is not a file`); size = stat.size; } catch (err) { if (err instanceof CodeError) throw err; return null; } if (size > MAX_FILE_BYTES) throw new CodeError(`"${ref}" is ${(size / 1048576).toFixed(1)} MB; code files up to ${MAX_FILE_BYTES / 1048576} MB are read`); const buf = readFileSync3(path); if (buf.includes(0)) throw new CodeError(`"${ref}" is a binary file, not code`); if (SECRET_TEXT.test(buf.toString("utf8"))) throw new CodeError(`"${ref}" looks like it holds a key or a token somewhere in the file; no part of it is embedded. Write a sketch instead`); const all = buf.toString("utf8").replace(/\r\n?/g, "\n").replace(/\n$/, "").split("\n"); const from = range?.from ?? 1; const to = range?.to ?? all.length; if (to > all.length) throw new CodeError(`"${ref}" has ${all.length} lines; lines=${range.from}-${range.to} goes past the end`); return all.slice(from - 1, to); } var EMBEDDED_BLOCK = /
    ]*?\sdata-am-src="([^"]*)"(?:\sdata-am-lines="([^"]*)")?[^>]*>([\s\S]*?)<\/figure>/g; var LINE_SPAN = /]*>([\s\S]*?)<\/span>/g; function readEmbeddedCode(html) { return new Map([...String(html).matchAll(EMBEDDED_BLOCK)].map(([, src, lines = "", inner]) => [ `${unescapeHtml(src)}#${lines}`, [...inner.matchAll(LINE_SPAN)].map((m) => unescapeHtml(m[1])) ])); } function renderCode({ lang, args, text }, { baseDir = process.cwd(), known = /* @__PURE__ */ new Map(), ui = {}, copy = true } = {}) { const isDiff = lang === "diff"; const opts = parseCodeArgs(args, isDiff); if (isDiff && opts.src) throw new CodeError("a diff block takes the diff you paste; src= is not supported for it yet, so paste the diff into the block", 0, DIFF_EXAMPLE); const range = opts.lines ? parseRange(opts.lines, "lines") : null; if (range && !opts.src) throw new CodeError("lines= needs src=; for code you type, use start= to number the lines"); let body; let first = 1; if (opts.src) { if (text.trim()) throw new CodeError("a block with src= takes its code from the file; leave the block empty"); body = readSlice(opts.src, range, baseDir) ?? known.get(`${opts.src}#${opts.lines ?? ""}`); if (!body) { const where2 = localPath2(opts.src, baseDir) ? "not found" : `outside the current folder ${baseDir}; only files inside it are quoted`; throw new CodeError(`Code file "${opts.src}" is ${where2}`); } first = range?.from ?? 1; } else { body = text.split("\n"); if (opts.start) first = parseRange(opts.start, "start").from; } if (body.length > MAX_CODE_LINES) throw new CodeError(`the block has ${body.length} lines; quote ${MAX_CODE_LINES} lines at most, and pick the lines that make the point`); const slice = body.join("\n"); if (SECRET_TEXT.test(slice)) throw new CodeError(`${opts.src ? `"${opts.src}"` : "the block"} looks like it holds a key or a token; it is not embedded. Quote other lines or write a sketch`); const copyButton = copy ? `` : ""; const long = body.length > LONG_CODE_LINES ? [{ rule: "code-length", message: `the block has ${body.length} lines; readers skim past long code. Pick the 10 to ${LONG_CODE_LINES} lines that make the point` }] : []; if (isDiff) return renderDiff(text, opts, { copyButton, long }); const last = first + body.length - 1; const hl = opts.hl ? parseHighlight(opts.hl) : /* @__PURE__ */ new Set(); const outside = [...hl].find((n) => n < first || n > last); if (outside !== void 0) throw new CodeError(`hl=${opts.hl}: line ${outside} is not in the block (lines ${first}-${last})`); const shownLang = lang || (opts.src ? extname2(opts.src).slice(1).toLowerCase() : ""); const numbered = Boolean(opts.src || opts.start); const where = opts.src ? `${opts.src}${range ? `:${range.from === range.to ? range.from : `${range.from}-${range.to}`}` : ""}` : ""; const title = opts.title ?? where; const lines = body.map((l3, i) => { const n = first + i; return `${esc(l3)}`; }).join(""); const source = opts.src ? ` data-am-src="${esc(opts.src)}"${opts.lines ? ` data-am-lines="${esc(opts.lines)}"` : ""}` : ""; const head = `
    ${esc(title)}${shownLang ? `${esc(shownLang)}` : ""}${copyButton}
    `; const html = `
    ${head}
    ${lines}
    `; return { html, file: where || null, warnings: long }; } function renderDiff(text, opts, { copyButton, long }) { const diff = (() => { try { return parseDiff(text, opts.start ? parseRange(opts.start, "start").from : void 0); } catch (err) { if (err instanceof DiffError) throw new CodeError(err.message, err.line, DIFF_EXAMPLE); throw err; } })(); const hl = opts.hl ? parseHighlight(opts.hl) : /* @__PURE__ */ new Set(); if (hl.size && !diff.numbered) throw new CodeError("hl= on a diff needs line numbers: add an @@ header or start="); const outside = [...hl].find((n) => !diff.newLines.has(n)); if (outside !== void 0) throw new CodeError(`hl=${opts.hl}: line ${outside} is not a new-side line of the diff`); const file = opts.file ?? diff.title; const shownLang = extname2(file).slice(1).toLowerCase() || "diff"; const stat = `+${diff.add} \u2212${diff.del}`; const head = `
    ${esc(opts.title ?? file)}${stat}${esc(shownLang)}${copyButton}
    `; const html = `
    ${head}
    ${diffRowsHtml(diff, hl)}
    `; return { html, file: null, warnings: [...diff.warnings, ...long] }; } // src/render.js var RenderError = class extends Error { constructor(message, { line, component, example } = {}) { super(message); this.name = "RenderError"; this.line = line; this.component = component; this.example = example; } }; var LintError = class extends Error { constructor(warnings) { super(`STE check failed (style: strict): ${warnings.length} warning${warnings.length === 1 ? "" : "s"}`); this.name = "LintError"; this.warnings = warnings; } }; function renderDoc(source, overrides = {}, defaults2 = {}, { themes: themes2 = BUILTIN, previousLanguage, baseDir, codeDir, knownImages, knownCode } = {}) { const choices = { theme: themes2.choices("page") }; const parsed = parseDoc(source, { defaults: defaults2, choices }); const meta = applyOverrides(parsed.meta, overrides, { ...CHOICES, ...choices }); if (meta.template === "video") throw new ParseError("template: video is a video draft; render it with am video", 0); const problem = themes2.problem(meta.theme, "page"); if (problem) throw new ParseError(problem, 0); const doc2 = { ...parsed, meta: meta.theme === AUTO ? { ...meta, theme: pickTheme({ scope: "page", template: meta.template, visuals: hasVisuals(parsed) }) } : meta }; const language = resolveLanguage({ declared: doc2.meta.lang, previous: previousLanguage, text: source }); const warnings = doc2.meta.style === "off" ? [] : lintDoc(doc2, language); if (doc2.meta.style === "strict" && warnings.length) throw new LintError(warnings); const stats = { panels: doc2.panels.length, components: {}, code: [], codeWarnings: [], componentWarnings: [], htmlWarnings: [], mathWarnings: [] }; const ui = language.ui; const ctx = { seq: 0, stats, ui, dir: language.dir, images: { baseDir, known: knownImages }, code: { baseDir: codeDir, known: knownCode } }; const loose = doc2.intro.find((b) => b.type === "fence" && COMPONENTS.get(b.lang)?.panelOnly); if (loose) throw new RenderError(`${loose.lang} belongs in a panel: put it under the ## heading of the panel the answer changes`, { line: loose.line, component: loose.lang, example: COMPONENTS.get(loose.lang).example }); const introHtml = renderBlocks(doc2.intro, ctx); const panels = doc2.panels.map((p) => ({ ...p, html: renderBlocks(p.blocks, ctx) })); const page = TEMPLATES[doc2.meta.template]({ meta: doc2.meta, introHtml, panels, ui, language }); const body = language.dir === "rtl" ? isolateLtrRuns(page) : page; const html = shell({ meta: doc2.meta, language, body, source, embedded: themes2.embedFor(doc2.meta.theme, "page") }); return { html, warnings, stats, meta: doc2.meta, language }; } function hasVisuals({ intro, panels }) { return [...intro, ...panels.flatMap((p) => p.blocks)].some((b) => b.type === "fence" && (COMPONENTS.has(b.lang) || RAW_LANGS.has(b.lang))); } function renderBlocks(blocks, ctx) { return blocks.map((b) => { const { result, notes } = collectHtmlNotes(() => b.type === "md" ? `
    ${md(b.text)}
    ` : renderFence(b, ctx), { math: !ctx.video }); noteHtml(b, notes.filter((n) => !n.math), ctx); noteMath(b, notes.filter((n) => n.math), ctx); return embedImages(b, result, ctx); }).join("\n"); } function noteHtml(block2, notes, ctx) { if (!notes.length || !ctx.stats.htmlWarnings) return; const lines = block2.text.split("\n"); const first = block2.type === "md" ? block2.line : block2.line + 1; const used = /* @__PURE__ */ new Map(); for (const { at: at3, message } of notes) { const tag = at3.split("\n")[0]; const places = lines.flatMap((l3, i) => Array(l3.split(tag).length - 1).fill(i)); const n = used.get(tag) ?? 0; used.set(tag, n + 1); ctx.stats.htmlWarnings.push({ line: first + (places[Math.min(n, places.length - 1)] ?? 0), message }); } } function noteMath(block2, notes, ctx) { if (!notes.length || !ctx.stats.mathWarnings) return; const first = block2.type === "md" ? block2.line : block2.line + 1; const next = /* @__PURE__ */ new Map(); for (const { at: at3, message } of notes) { const index = block2.text.indexOf(at3, next.get(at3) ?? 0); next.set(at3, index + 1); ctx.stats.mathWarnings.push({ line: first + (index < 0 ? 0 : block2.text.slice(0, index).split("\n").length - 1), message }); } } var MARKDOWN_FENCES = /* @__PURE__ */ new Set(["callout", "kv", "tree", "timeline"]); function embedImages(block2, html, ctx) { try { return inlineImages(html, { ...ctx.images, checkText: block2.type === "md" || MARKDOWN_FENCES.has(block2.lang) }); } catch (err) { if (!(err instanceof ImageError)) throw err; const idx = block2.text.split("\n").findIndex((l3) => l3.includes(err.ref) || l3.includes(encodeURI(err.ref))); const first = block2.type === "md" ? block2.line : block2.line + 1; throw new RenderError(err.message, { line: first + Math.max(idx, 0), component: "image", example: IMAGE_EXAMPLE }); } } function renderFence(block2, ctx) { const { lang, args, text, line } = block2; if (RAW_LANGS.has(lang)) return text; const comp = COMPONENTS.get(lang); if (!comp) return codeBlock(block2, ctx); if (comp.pageOnly && ctx.video) throw new RenderError(`${lang} works on a page only; a video cannot take answers`, { line, component: lang, example: comp.example }); ctx.stats.components[lang] = (ctx.stats.components[lang] ?? 0) + 1; try { const warn = ({ line: at3 = 0, message }) => ctx.stats.componentWarnings?.push({ line: line + at3, component: lang, message }); return comp.render(text, { args, uid: () => `am${++ctx.seq}`, ui: ctx.ui, dir: ctx.dir ?? "ltr", video: ctx.video, warn }); } catch (err) { if (!(err instanceof ComponentError)) throw err; throw new RenderError(err.message, { line: line + (err.line || 0), component: lang, example: comp.example }); } } function codeBlock(block2, ctx) { try { const { html, file, warnings } = renderCode(block2, { ...ctx.code, ui: ctx.ui, copy: !ctx.video }); if (file && ctx.stats.code) ctx.stats.code.push(file); if (ctx.stats.codeWarnings) ctx.stats.codeWarnings.push(...warnings.map((w) => ({ line: block2.line, ...w }))); return html; } catch (err) { if (!(err instanceof CodeError)) throw err; throw new RenderError(err.message, { line: block2.line + err.line, component: "code", example: err.example }); } } function timestamp(d = /* @__PURE__ */ new Date(), order = "ymd") { const p = (n) => String(n).padStart(2, "0"); const time = `${p(d.getHours())}:${p(d.getMinutes())}`; if (order === "dmy") return `${d.getDate()}.${d.getMonth() + 1}.${d.getFullYear()} ${time}`; return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${time}`; } function colophon(language) { const repo = "https://github.com/QingYunA/answer-me-with-html"; const link = `Answer me with HTML`; const star = `\u2605 Star on GitHub`; const label = esc(language.ui.generated ?? "Generated by"); const time = esc(timestamp(/* @__PURE__ */ new Date(), language.dateOrder)); if (language.dir === "rtl") return `
    ${label} ${link} ${VERSION} \xB7 ${time} \xB7 ${star}
    `; return `
    ${label} ${link} ${VERSION} \xB7 ${time} \xB7 ${star}
    `; } var hasDelta = (html) => html.includes('class="am-delta-bar"'); function lightboxShell(ui, hasDiagrams) { if (!hasDiagrams) return ""; return ` `; } function shell({ meta, language, body, source, embedded }) { const { ui, labelKey } = language; const pick = (name, label, values, current) => ``; const root = { lang: language.htmlLang, dir: language.dir, theme: meta.theme, mode: meta.mode, style: meta.style }; return ` ${rootTag(root)} ${esc(meta.title || "Answer me with HTML")}
    ${pick("theme", ui.theme, embedded.map((t) => [t.name, t.label[labelKey]]), meta.theme)} ${pick("mode", ui.modeLabel, Object.entries(ui.mode), meta.mode)}
    ${body} ${lightboxShell(ui, body.includes('class="am-diagram'))}${colophon(language)} ${sourceTag(source)} `; } // src/video/script.js var NARRATION = /^\s*>\s?(.*)$/; var FOCUS = /\[([^\]\n]+)\]/g; var VIDEO_THEMES = Object.freeze(themeNames("video")); function parseVideo(source, { defaults: defaults2 = {}, themeChoices = VIDEO_THEMES } = {}) { const doc2 = parseDoc(source, { defaults: { ...defaults2, template: "video" }, choices: { theme: themeChoices } }); const intro = splitNarration(doc2.intro); const scenes = doc2.panels.map((p) => { const { blocks, beats } = splitNarration(p.blocks); if (!beats.length) throw new ParseError(`Scene "${p.title}" has no narration: write at least one > narration line in every scene`, p.line); return { id: p.id, title: p.title, line: p.line, attrs: p.attrs, blocks, beats }; }); if (!scenes.length) throw new ParseError("A video draft needs at least one scene (## Scene title)", 1); return { meta: doc2.meta, doc: doc2, intro: intro.blocks, introBeats: intro.beats, scenes }; } function splitNarration(blocks) { const beats = []; const out = []; for (const b of blocks) { if (b.type !== "md") { out.push(b); continue; } const rest = []; b.text.split("\n").forEach((raw, i) => { const m = raw.match(NARRATION); if (m && m[1].trim()) beats.push(beat(m[1].trim(), b.line + i)); else if (!m) rest.push(raw); }); if (rest.some((l3) => l3.trim())) out.push({ ...b, text: rest.join("\n") }); } return { blocks: out, beats }; } function beat(raw, line) { const focus = [...raw.matchAll(FOCUS)].map((m) => m[1].trim()); return { raw, text: raw.replace(FOCUS, "$1"), focus: focus[0] ?? null, line }; } function estimateSeconds(text) { let cjk = 0; let rest = ""; for (const ch of text) { if (isCJK(ch)) cjk++; rest += isCJK(ch) ? " " : ch; } return Math.max(1.6, cjk / 4.2 + countWords(rest) / 2.6 + 0.3); } var TIMING = Object.freeze({ title: 2.4, // intro hold when there is no narration transition: 0.9, // scene change (including cross-scene morphs) gap: 0.35, // pause between two narration lines tail: 0.8, // hold after a scene's last line outro: 1.5 // outro hold }); function buildTimeline(video, durations) { let t = 0; let k2 = 0; const lay = (beats) => beats.map((b) => { const dur = durations[k2++]; const start = t; t += dur + TIMING.gap; return { text: b.text, focus: b.focus, start: round2(start), end: round2(start + dur) }; }); const titleBeats = lay(video.introBeats); if (!titleBeats.length) t = TIMING.title; const title = { start: 0, end: round2(t), beats: titleBeats }; const scenes = video.scenes.map((s) => { const start = t; t += TIMING.transition; const beats = lay(s.beats); t += TIMING.tail - TIMING.gap; return { id: s.id, title: s.title, start: round2(start), end: round2(t), beats }; }); t += TIMING.outro; return { duration: round2(t), title, scenes }; } var allBeats = (video) => [...video.introBeats, ...video.scenes.flatMap((s) => s.beats)]; var round2 = (x2) => Math.round(x2 * 1e3) / 1e3; // src/video/tts.js import { spawn, spawnSync as spawnSync2 } from "node:child_process"; import { createHash } from "node:crypto"; import { mkdirSync, readFileSync as readFileSync4, writeFileSync, existsSync as existsSync2, rmSync, mkdtempSync, renameSync } from "node:fs"; import { tmpdir } from "node:os"; import { join as join2 } from "node:path"; // src/sys.js import { spawnSync } from "node:child_process"; function hasCommand(cmd) { return spawnSync(process.platform === "win32" ? "where" : "which", [cmd], { stdio: "ignore" }).status === 0; } // src/video/tts.js var SAMPLE_RATE = 22050; var ELEVEN_DEFAULT_VOICE = "bIHbv24MWmeRgasZH58o"; var ELEVEN_DEFAULT_MODEL = "eleven_v4_turbo"; var ELEVEN_TIMEOUT_MS = 6e4; var LOCAL_TIMEOUT_MS = 3e5; var LOCAL_RATIO = Object.freeze([0.5, 2]); var LOCAL_ATTEMPTS = 3; var SILENCE = 300; var TtsError = class extends Error { constructor(message) { super(message); this.name = "TtsError"; } }; function pickProvider(choice, env, { platform = process.platform, which = hasCommand } = {}) { const eleven = () => elevenLabs(env); const system = () => systemVoice(platform, which); if (choice === "off") return null; if (choice === "elevenlabs") { if (!env.ELEVENLABS_API_KEY) throw new TtsError("voice=elevenlabs needs the ELEVENLABS_API_KEY environment variable"); return eleven(); } if (choice === "local") return localSpeech(env); if (choice === "system") { const p = system(); if (!p) throw new TtsError("No system TTS found: macOS has say built in; on Linux install espeak-ng"); return p; } if (env.ELEVENLABS_API_KEY) return eleven(); return system(); } function elevenLabs(env) { const voice = env.ELEVENLABS_VOICE_ID || ELEVEN_DEFAULT_VOICE; const model = env.ELEVENLABS_MODEL_ID || ELEVEN_DEFAULT_MODEL; return { name: "elevenlabs", voice: "elevenlabs", id: `elevenlabs:${voice}:${model}`, concurrency: 2, async synth(text) { const url = `https://api.elevenlabs.io/v1/text-to-speech/${voice}?output_format=pcm_${SAMPLE_RATE}`; let res; try { res = await fetch(url, { method: "POST", // No language_code: the API ignores it for the models that do not support it (multilingual_v2 is documented as // not supporting it) and every model reads the language from the text, so one voice reads every line. headers: { "xi-api-key": env.ELEVENLABS_API_KEY, "content-type": "application/json" }, body: JSON.stringify({ text, model_id: model }), signal: AbortSignal.timeout(ELEVEN_TIMEOUT_MS) }); } catch (e) { throw new TtsError(`Cannot connect to ElevenLabs: ${e.name === "TimeoutError" ? `no response within ${ELEVEN_TIMEOUT_MS / 1e3} seconds` : e.message}`); } if (!res.ok) throw new TtsError(`ElevenLabs returned ${res.status}: ${(await res.text()).slice(0, 200)}`); const buf = Buffer.from(await res.arrayBuffer()); return new Int16Array(buf.buffer, buf.byteOffset, Math.floor(buf.length / 2)).slice(); } }; } function localSpeech(env) { if (!env.AM_TTS_URL) throw new TtsError("voice=local needs the AM_TTS_URL environment variable (such as http://127.0.0.1:8000)"); const url = `${env.AM_TTS_URL.replace(/\/+$/, "").replace(/\/v1$/, "")}/v1/audio/speech`; let extra = {}; if (env.AM_TTS_EXTRA) { try { extra = JSON.parse(env.AM_TTS_EXTRA); } catch { extra = null; } if (!extra || typeof extra !== "object" || Array.isArray(extra)) throw new TtsError("AM_TTS_EXTRA must be a JSON object"); } const attempts = env.AM_TTS_ATTEMPTS ? Number(env.AM_TTS_ATTEMPTS) : LOCAL_ATTEMPTS; if (!Number.isInteger(attempts) || attempts < 1) throw new TtsError("AM_TTS_ATTEMPTS must be a positive integer"); const body = { ...extra, response_format: "wav", stream: false }; delete body.input; if (env.AM_TTS_MODEL) body.model = env.AM_TTS_MODEL; if (env.AM_TTS_VOICE) body.voice = env.AM_TTS_VOICE; const speed = typeof body.speed === "number" && body.speed > 0 ? body.speed : 1; const headers = { "content-type": "application/json", ...env.AM_TTS_API_KEY ? { authorization: `Bearer ${env.AM_TTS_API_KEY}` } : {} }; const request = async (text) => { const signal = AbortSignal.timeout(LOCAL_TIMEOUT_MS); const why = (e) => signal.aborted ? `not finished within ${LOCAL_TIMEOUT_MS / 1e3} seconds` : e.message; let res; try { res = await fetch(url, { method: "POST", headers, body: JSON.stringify({ ...body, input: text }), signal }); } catch (e) { throw new TtsError(`Cannot connect to the local TTS ${url}: ${why(e)}`); } let buf; try { buf = Buffer.from(await res.arrayBuffer()); } catch (e) { throw new TtsError(`Cannot read the local TTS response (HTTP ${res.status}): ${why(e)}`); } if (!res.ok) throw new TtsError(`The local TTS returned ${res.status}: ${buf.toString("utf8", 0, 200)}`); try { return readWav(buf); } catch (e) { throw new TtsError(`Cannot decode the audio from the local TTS (needs 16-bit PCM WAV): ${e.message}`); } }; return { name: "local", voice: "local", id: `local:${url}:${attempts}:${stableJson(body)}`, concurrency: 1, async synth(text) { const expected = estimateSeconds(text) / speed; let best = null; for (let i = 0; i < attempts; i++) { const samples = trimSilence(await request(text)); if (!samples.some((x2) => Math.abs(x2) >= SILENCE)) continue; const ratio = samples.length / SAMPLE_RATE / expected; if (!best || Math.abs(Math.log(ratio)) < Math.abs(Math.log(best.ratio))) best = { samples, ratio }; if (ratio >= LOCAL_RATIO[0] && ratio <= LOCAL_RATIO[1]) break; } if (!best) throw new TtsError(`The local TTS returned only silence ${attempts} time${attempts === 1 ? "" : "s"} in a row`); return best.samples; } }; } function stableJson(value) { if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`; if (value && typeof value === "object") { return `{${Object.keys(value).sort().map((k2) => `${JSON.stringify(k2)}:${stableJson(value[k2])}`).join(",")}}`; } return JSON.stringify(value); } function systemVoice(platform, which) { if (platform === "darwin" && which("say")) { const voices = macVoices(); return { name: "say", voice: "system", id: "say", concurrency: 4, usesLanguage: true, // The voice this machine has for a line's language, or null: the line then keeps its caption instead of being read // by a voice for another language. voiceFor: (language) => macVoiceFor(voices, language), // A caller that does not say which language a line is in (languageOf is optional) gets the system default voice. synth: (text, { voice } = {}) => withTemp(async (file) => { await run("say", [...voice ? ["-v", voice] : [], "-o", file, "--file-format=WAVE", `--data-format=LEI16@${SAMPLE_RATE}`, "-f", textFile(file, text)]); return readWav(readFileSync4(file)); }) }; } if (which("espeak-ng")) { const voices = espeakVoices(); return { name: "espeak-ng", voice: "system", id: "espeak-ng", concurrency: 4, usesLanguage: true, voiceFor: (language) => espeakVoiceFor(voices, language), // A caller that does not say which language a line is in gets the English voice, which every espeak-ng build has. synth: (text, { voice } = {}) => withTemp(async (file) => { await run("espeak-ng", ["-v", voice ?? "en-us", "-w", file, "-f", textFile(file, text)]); return readWav(readFileSync4(file)); }) }; } return null; } function macVoices() { return parseMacVoices(spawnSync2("say", ["-v", "?"], { encoding: "utf8" }).stdout || ""); } function espeakVoices() { return parseEspeakVoices(spawnSync2("espeak-ng", ["--voices"], { encoding: "utf8" }).stdout || ""); } function parseMacVoices(out) { return out.split("\n").map((l3) => l3.match(/^(.+?)\s+([a-z]{2,3}[_-][A-Za-z0-9]{2,4})\s+#/)).filter(Boolean).map((m) => ({ name: m[1].trim(), locale: m[2] })); } function parseEspeakVoices(out) { return out.split("\n").map((l3) => l3.match(/^\s*\d+\s+(\S+)\s/)).filter(Boolean).map((m) => m[1]); } var PREFERRED_VOICES = { zh: ["Tingting", "Ting-Ting", "Meijia", "Sinji"], en: ["Samantha", "Alex", "Daniel", "Ava"], ja: ["Kyoko", "Otoya"] }; var SHARED_VOICES = /* @__PURE__ */ new Set(["Eddy", "Flo", "Grandma", "Grandpa", "Reed", "Rocko", "Sandy", "Shelley"]); function macVoiceFor(voices, language) { if (!voices.length) return ""; const base = baseLanguage(language); const implied = new Intl.Locale(language).maximize(); const named = voiceHints(language)?.say; const wanted = [language, implied.region && `${implied.language}-${implied.region}`, named].filter(Boolean).map(localeKey); for (const locale of new Set(wanted)) { const voice = bestVoice(voices.filter((v) => localeKey(v.locale) === locale), base); if (voice) return voice; } return bestVoice(voices.filter((v) => localeKey(v.locale).split("_")[0] === base), base); } function espeakVoiceFor(voices, language) { const base = baseLanguage(language); const name = voiceHints(language)?.espeak ?? base; if (!voices.length) return name; const installed = voices.includes(name) || voices.includes(base) || voices.some((v) => v.startsWith(`${base}-`)); return installed ? name : null; } var localeKey = (locale) => String(locale).toLowerCase().replace(/-/g, "_"); function bestVoice(candidates, base) { if (!candidates.length) return null; for (const name of PREFERRED_VOICES[base] ?? []) { const hit = candidates.find((v) => stripName(v.name) === name); if (hit) return hit.name; } const own = candidates.filter((v) => !SHARED_VOICES.has(stripName(v.name))); return (own[0] ?? candidates[0]).name; } var stripName = (name) => name.replace(/\s*[((].*$/, ""); function run(cmd, args) { return new Promise((resolve5, reject) => { const p = spawn(cmd, args, { stdio: ["ignore", "ignore", "pipe"] }); let err = ""; p.stderr.on("data", (d) => { err += d; }); p.on("error", reject); p.on("close", (code) => code === 0 ? resolve5() : reject(new TtsError(`${cmd} failed (${code}): ${err.slice(0, 200)}`))); }); } function textFile(wavFile, text) { const p = `${wavFile}.txt`; writeFileSync(p, text); return p; } async function withTemp(fn3) { const dir = mkdtempSync(join2(tmpdir(), "am-tts-")); try { return await fn3(join2(dir, "out.wav")); } finally { rmSync(dir, { recursive: true, force: true }); } } function readWav(buf) { if (buf.toString("ascii", 0, 4) !== "RIFF" || buf.toString("ascii", 8, 12) !== "WAVE") throw new TtsError("Not a WAV file"); let pos = 12; let fmt = null; while (pos + 8 <= buf.length) { const id = buf.toString("ascii", pos, pos + 4); const size = buf.readUInt32LE(pos + 4); if (id === "fmt ") fmt = { channels: buf.readUInt16LE(pos + 10), rate: buf.readUInt32LE(pos + 12), bits: buf.readUInt16LE(pos + 22) }; if (id === "data") { if (!fmt || fmt.bits !== 16) throw new TtsError("Only 16-bit PCM WAV is supported"); const n = Math.floor(Math.min(size, buf.length - pos - 8) / 2 / fmt.channels); const samples = new Int16Array(n); for (let i = 0; i < n; i++) samples[i] = buf.readInt16LE(pos + 8 + i * 2 * fmt.channels); return fmt.rate === SAMPLE_RATE ? samples : resample({ rate: fmt.rate, samples }); } pos += 8 + size + size % 2; } throw new TtsError("The WAV has no data chunk"); } function resample(input) { if (input instanceof Int16Array) return input; const { rate, samples } = input; const n = Math.floor(samples.length * SAMPLE_RATE / rate); const out = new Int16Array(n); for (let i = 0; i < n; i++) { const x2 = i * rate / SAMPLE_RATE; const a = Math.floor(x2); const b = Math.min(a + 1, samples.length - 1); out[i] = Math.round(samples[a] + (samples[b] - samples[a]) * (x2 - a)); } return out; } async function synthAll(texts, provider, { cacheDir, languageOf } = {}) { if (cacheDir) mkdirSync(cacheDir, { recursive: true }); const results = new Array(texts.length); let next = 0; const worker = async () => { while (next < texts.length) { const i = next++; const language = provider.usesLanguage ? languageOf?.(texts[i]) : void 0; const voice = language && provider.voiceFor ? provider.voiceFor(language) : void 0; if (language && provider.voiceFor && voice === null) { results[i] = null; continue; } const key = `${provider.id}${voice ? `:${voice}` : ""}${language ? ` ${language}` : ""} ${texts[i]}`; const file = cacheDir && join2(cacheDir, `${createHash("sha1").update(key).digest("hex")}.pcm`); const cached = file && readCache(file); if (cached) { results[i] = cached; continue; } results[i] = trimSilence(await provider.synth(texts[i], { language, voice })); if (file) writeCache(file, results[i]); } }; await Promise.all(Array.from({ length: Math.min(provider.concurrency ?? 2, texts.length) }, worker)); return results; } function readCache(file) { if (!existsSync2(file)) return null; const buf = readFileSync4(file); if (!buf.length || buf.length % 2) return null; return new Int16Array(buf.buffer, buf.byteOffset, buf.length / 2).slice(); } function writeCache(file, samples) { const tmp = `${file}.${process.pid}.tmp`; writeFileSync(tmp, Buffer.from(samples.buffer, samples.byteOffset, samples.byteLength)); renameSync(tmp, file); } function trimSilence(samples, threshold = SILENCE) { let a = 0; let b = samples.length; while (a < b && Math.abs(samples[a]) < threshold) a++; while (b > a && Math.abs(samples[b - 1]) < threshold) b--; const pad = Math.floor(SAMPLE_RATE * 0.04); return samples.slice(Math.max(0, a - pad), Math.min(samples.length, b + pad)); } function mixTrack(clips, starts, duration) { const total = Math.ceil(duration * SAMPLE_RATE); const track = new Int16Array(total); clips.forEach((clip, i) => { if (!clip) return; const s0 = Math.round(starts[i] * SAMPLE_RATE); for (let j2 = 0; j2 < clip.length && s0 + j2 < total; j2++) track[s0 + j2] = clip[j2]; }); return wav(track); } function wav(samples) { const buf = Buffer.alloc(44 + samples.length * 2); buf.write("RIFF", 0); buf.writeUInt32LE(36 + samples.length * 2, 4); buf.write("WAVE", 8); buf.write("fmt ", 12); buf.writeUInt32LE(16, 16); buf.writeUInt16LE(1, 20); buf.writeUInt16LE(1, 22); buf.writeUInt32LE(SAMPLE_RATE, 24); buf.writeUInt32LE(SAMPLE_RATE * 2, 28); buf.writeUInt16LE(2, 32); buf.writeUInt16LE(16, 34); buf.write("data", 36); buf.writeUInt32LE(samples.length * 2, 40); Buffer.from(samples.buffer, samples.byteOffset, samples.byteLength).copy(buf, 44); return buf; } // src/video/render.js async function renderVideo(source, { provider = null, cacheDir, defaults: defaults2 = {}, overrides = {}, onProgress, themes: themes2 = BUILTIN, previousLanguage } = {}) { const themeChoices = themes2.choices("video"); const video = parseVideo(source, { defaults: defaults2, themeChoices }); const picked = applyOverrides(video.meta, overrides, { ...CHOICES, theme: themeChoices }); const problem = themes2.problem(picked.theme, "video"); if (problem) throw new ParseError(problem, 0); const meta = picked.theme === AUTO ? { ...picked, theme: pickTheme({ scope: "video" }) } : picked; const language = resolveLanguage({ declared: meta.lang, previous: previousLanguage, text: source }); const warnings = meta.style === "off" ? [] : lintDoc(video.doc, language).filter((w) => w.rule !== "paragraph-length"); if (meta.style === "strict" && warnings.length) throw new LintError(warnings); const beats = allBeats(video); const languageOf = (text) => language.declared ? language.tag : detectLang(text); const { clips, durations, captionsOnly } = await voiceBeats(beats, provider, cacheDir, onProgress, languageOf); const timeline = buildTimeline(video, durations); const flat = [...timeline.title.beats, ...timeline.scenes.flatMap((s) => s.beats)]; const wav2 = clips?.some(Boolean) ? mixTrack(clips, flat.map((b) => b.start), timeline.duration) : null; const stats = { panels: video.scenes.length, components: {}, componentWarnings: [], htmlWarnings: [] }; const scenesHtml = renderScenes(video, meta, timeline, { seq: 0, stats, ui: language.ui, dir: language.dir, video: true }, language.videoUi); const html = shell2({ meta, language, scenesHtml, data: playerData(video, meta, timeline), wav: wav2, voice: wav2 ? provider.voice : void 0, source, embedded: themes2.embedFor(meta.theme, "video") }); return { html, wav: wav2, warnings, stats, meta, language, duration: timeline.duration, beats: beats.length, captionsOnly }; } async function voiceBeats(beats, provider, cacheDir, onProgress, languageOf) { if (!provider) return { clips: null, captionsOnly: [], durations: beats.map((b) => estimateSeconds(b.text)) }; onProgress?.(`Voice-over: ${provider.name}, ${beats.length} line${beats.length === 1 ? "" : "s"}`); const clips = await synthAll(beats.map((b) => b.text), provider, { cacheDir, languageOf }); const silent = /* @__PURE__ */ new Map(); clips.forEach((clip, i) => { if (clip) return; const tag = languageOf(beats[i].text); silent.set(tag, (silent.get(tag) ?? 0) + 1); }); const captionsOnly = [...silent].map(([language, lines]) => ({ language, lines })); for (const { language, lines } of captionsOnly) { const n = `${lines} line${lines === 1 ? "" : "s"}`; onProgress?.(`No ${language} voice on this machine: captions only for ${n}`); } return { clips, captionsOnly, durations: clips.map((c, i) => c ? c.length / SAMPLE_RATE : estimateSeconds(beats[i].text)) }; } function playerData(video, meta, timeline) { return { duration: timeline.duration, fps: 30, segments: [timeline.title, ...timeline.scenes].map((s, i) => ({ start: s.start, end: s.end, id: i === 0 ? "" : s.id, title: i === 0 ? meta.title : s.title, beats: s.beats.map((b, k2) => ({ ...b, html: captionHtml(beatsOf(video, i)[k2].raw) })) })) }; } function renderScenes(video, meta, timeline, ctx, ui) { const total = video.scenes.length; return [ titleScene(meta, renderBlocks(video.intro, ctx), { scenes: total, duration: timeline.duration }, ui), ...video.scenes.map((s, i) => scene(s, i, total, renderBlocks(s.blocks, ctx), ui)) ].join("\n"); } var beatsOf = (video, i) => i === 0 ? video.introBeats : video.scenes[i - 1].beats; function captionHtml(raw) { return raw.split(/(\[[^\]\n]+\])/).map((part) => { const m = part.match(/^\[([^\]]+)\]$/); return m ? `${esc(m[1])}` : esc(part); }).join(""); } function formatClock(seconds) { const total = Math.max(0, Math.round(Number(seconds) || 0)); return `${Math.floor(total / 60)}:${String(total % 60).padStart(2, "0")}`; } function titleScene(meta, introHtml, { scenes, duration }, ui) { const mmss = formatClock(duration); const cells = [[ui.drawn, "Answer me with HTML"], [ui.date, timestamp().slice(0, 10)], [ui.scenes, String(scenes)], [ui.duration, mmss]]; const block2 = `
    ${cells.map(([k2, v]) => `
    ${esc(k2)}${esc(v)}
    `).join("")}
    `; return `

    ${esc(meta.title || "Answer me with HTML")}

    ${meta.subtitle ? `

    ${esc(meta.subtitle)}

    ` : ""}${introHtml ? `
    ${introHtml}
    ` : ""}${block2}
    `; } function scene(s, i, total, body, ui) { const pad = (n) => String(n).padStart(2, "0"); return `
    ${esc(s.id)}${esc(s.title)}${esc(ui.sheet.replace("{n}", pad(i + 1)).replace("{total}", pad(total)))}
    ${body}
    `; } function sheetFrame() { const ruler2 = (side, labels) => `
    ${labels.map((l3) => `${l3}`).join("")}
    `; const nums = [1, 2, 3, 4, 5, 6, 7, 8]; const letters = ["A", "B", "C", "D"]; return ``; } function shell2({ meta, language, scenesHtml, data, wav: wav2, voice, source, embedded }) { const ui = language.videoUi; const json = JSON.stringify(data).replace(/ ${rootTag({ lang: language.htmlLang, dir: language.dir, theme: meta.theme, mode: embedded.find((t) => t.name === meta.theme).mode ?? (meta.mode === "dark" ? "dark" : "light"), style: meta.style, voice, video: true })} ${esc(meta.title || "Answer me with HTML")}
    ${sheetFrame()}
    ${scenesHtml}
    0:00 / 0:00
    Answer me with HTML ${VERSION} \xB7 ${esc(timestamp())}
    ${wav2 ? audioTag(wav2) : ""} ${sourceTag(source)} `; } // src/video/export.js import { spawn as spawn2 } from "node:child_process"; import { existsSync as existsSync3, mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs"; import { tmpdir as tmpdir2 } from "node:os"; import { join as join3 } from "node:path"; import { pathToFileURL } from "node:url"; var ExportError = class extends Error { }; var CDP_TIMEOUT_MS = 3e4; var CHROME_START_TIMEOUT_MS = 2e4; var WIDTH = 1920; var HEIGHT = 1080; var QUALITY = 92; var WEBM_BITRATE = 35e5; var WEBM_SLICE = 1048572; var WEBM_POLL_MS = 250; var CHROME_PATHS = { darwin: [ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "/Applications/Chromium.app/Contents/MacOS/Chromium", "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge" ], win32: [ "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe", "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe", "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe" ], linux: ["google-chrome", "google-chrome-stable", "chromium", "chromium-browser", "microsoft-edge"] }; function findChrome(env = process.env, platform = process.platform) { const custom = env.AM_CHROME || env.CHROME_PATH; if (custom) return custom; const list = CHROME_PATHS[platform] ?? CHROME_PATHS.linux; return list.find((p) => p.includes("/") || p.includes("\\") ? existsSync3(p) : hasCommand(p)) ?? null; } async function openPlayer(htmlFile, { env, startTimeoutMs }) { const chromePath = findChrome(env); if (!chromePath) throw new ExportError("No Chrome / Chromium / Edge found. Set the browser path with the AM_CHROME environment variable"); const tmp = mkdtempSync2(join3(tmpdir2(), "am-export-")); const chrome = spawn2(chromePath, [ "--headless=new", "--remote-debugging-port=0", `--user-data-dir=${join3(tmp, "profile")}`, "--no-first-run", "--no-default-browser-check", "--hide-scrollbars", "--mute-audio", "--force-device-scale-factor=1", `--window-size=${WIDTH},${HEIGHT}`, "about:blank" ], { stdio: ["ignore", "ignore", "pipe"] }); let cdp = null; let closed = false; const close = async () => { if (closed) return; closed = true; cdp?.close(); await new Promise((r) => { if (chrome.exitCode !== null) return r(); const timer = setTimeout(r, 3e3); chrome.once("exit", () => { clearTimeout(timer); r(); }); chrome.kill(); }); try { rmSync2(tmp, { recursive: true, force: true, maxRetries: 3, retryDelay: 200 }); } catch { } }; try { cdp = await connect(await devtoolsUrl(chrome, startTimeoutMs)); const { targetId } = await cdp.send("Target.createTarget", { url: "about:blank" }); const { sessionId } = await cdp.send("Target.attachToTarget", { targetId, flatten: true }); const page = (method, params) => cdp.send(method, params, sessionId); await page("Emulation.setDeviceMetricsOverride", { width: WIDTH, height: HEIGHT, deviceScaleFactor: 1, mobile: false }); await page("Page.enable"); const loaded = cdp.once("Page.loadEventFired"); await page("Page.navigate", { url: pathToFileURL(htmlFile).href }); await loaded; const evaluate = async (expression) => { const r = await page("Runtime.evaluate", { expression, awaitPromise: true, returnByValue: true }); if (r.exceptionDetails) throw new ExportError(`Player page script error: ${r.exceptionDetails.exception?.description ?? r.exceptionDetails.text}`); return r.result.value; }; const info = await evaluate("document.fonts.ready.then(() => { window.__amv.exportMode(); return { duration: window.__amv.duration, fps: window.__amv.fps }; })"); const screenshot = async () => (await page("Page.captureScreenshot", { format: "jpeg", quality: QUALITY, clip: { x: 0, y: 0, width: WIDTH, height: HEIGHT, scale: 1 } })).data; return { info, evaluate, screenshot, close, tmp }; } catch (e) { await close(); throw e; } } async function exportMp4(htmlFile, mp4File, { wav: wav2, env = process.env, onProgress = () => { }, startTimeoutMs = CHROME_START_TIMEOUT_MS } = {}) { if (typeof WebSocket === "undefined") throw new ExportError("MP4 export needs Node.js 22 or later (built-in WebSocket)"); if (!hasCommand("ffmpeg")) throw new ExportError("MP4 export needs ffmpeg: on macOS run brew install ffmpeg; on Linux install it with the package manager"); const player = await openPlayer(htmlFile, { env, startTimeoutMs }); try { const { info, evaluate, screenshot, tmp } = player; const wavFile = wav2 ? join3(tmp, "voice.wav") : null; if (wav2) writeFileSync2(wavFile, wav2); const ffmpeg = spawn2("ffmpeg", [ "-y", "-loglevel", "error", "-f", "image2pipe", "-framerate", String(info.fps), "-c:v", "mjpeg", "-i", "-", ...wavFile ? ["-i", wavFile] : [], "-c:v", "libx264", "-pix_fmt", "yuv420p", "-crf", "20", "-preset", "medium", ...wavFile ? ["-c:a", "aac", "-b:a", "160k", "-shortest"] : [], "-movflags", "+faststart", mp4File ], { stdio: ["pipe", "ignore", "pipe"] }); let ffErr = ""; let exited = false; ffmpeg.stderr.on("data", (d) => { ffErr += d; }); ffmpeg.stdin.on("error", () => { }); const done = new Promise((resolve5, reject) => { ffmpeg.on("error", (e) => { exited = true; reject(new ExportError(`Cannot run ffmpeg: ${e.message}`)); }); ffmpeg.on("close", (code) => { exited = true; if (code === 0) resolve5(); else reject(new ExportError(`ffmpeg failed (${code}): ${ffErr.slice(0, 300)}`)); }); }); done.catch(() => { }); const frames = Math.ceil(info.duration * info.fps); for (let i = 0; i < frames && !exited; i++) { await evaluate(`render(${i / info.fps})`); const data = await screenshot(); if (exited) break; if (!ffmpeg.stdin.write(Buffer.from(data, "base64"))) { await Promise.race([new Promise((r) => ffmpeg.stdin.once("drain", r)), done.catch(() => { })]); } if (i % 30 === 0 || i === frames - 1) onProgress(i + 1, frames); } if (!exited) ffmpeg.stdin.end(); await done; return { frames, duration: info.duration }; } finally { await player.close(); } } async function exportWebm(htmlFile, webmFile, { env = process.env, onProgress = () => { }, startTimeoutMs = CHROME_START_TIMEOUT_MS, bitrate = WEBM_BITRATE } = {}) { if (typeof WebSocket === "undefined") throw new ExportError("The built-in encoder needs Node.js 22 or later (built-in WebSocket)"); const player = await openPlayer(htmlFile, { env, startTimeoutMs }); try { const { info, evaluate } = player; if (!await evaluate("window.__amvEnc.supported()")) { throw new ExportError("This browser cannot encode video (WebCodecs is missing). Install ffmpeg to export an MP4"); } const started = await evaluate(`window.__amvEnc.start(${JSON.stringify({ bitrate })})`); if (started.error) throw new ExportError(`The browser could not encode the video: ${started.error}`); let state = null; for (; ; ) { state = await evaluate("window.__amvEnc.progress()"); if (state.error) throw new ExportError(`The browser stopped encoding: ${state.error}`); onProgress(state.done, state.total); if (!state.running) break; await new Promise((r) => setTimeout(r, WEBM_POLL_MS)); } const size = await evaluate("window.__amvEnc.size()"); const file = Buffer.alloc(size); for (let at3 = 0; at3 < size; at3 += WEBM_SLICE) { const slice = await evaluate(`window.__amvEnc.bytes(${at3}, ${Math.min(WEBM_SLICE, size - at3)})`); Buffer.from(slice, "base64").copy(file, at3); } writeFileSync2(webmFile, file); return { frames: state.done, duration: info.duration, codec: state.codec, audio: Boolean(state.audio) }; } finally { await player.close(); } } function devtoolsUrl(chrome, timeoutMs = CHROME_START_TIMEOUT_MS) { return new Promise((resolve5, reject) => { let buf = ""; const fail = (msg) => { clearTimeout(timer); const tail = buf.trim().split("\n").slice(-5).join("\n"); reject(new ExportError(tail ? `${msg}. Chrome stderr: ${tail}` : msg)); }; const timer = setTimeout(() => fail(`Chrome did not start in time (${timeoutMs / 1e3} s)`), timeoutMs); chrome.on("error", (e) => fail(`Cannot start Chrome: ${e.message}`)); chrome.on("close", (code, signal) => fail(`Chrome exited (${code ?? signal}) before it started`)); chrome.stderr.on("data", (d) => { buf += d; const m = buf.match(/DevTools listening on (ws:\/\/\S+)/); if (m) { clearTimeout(timer); resolve5(m[1]); } }); }); } function connect(url) { return new Promise((resolve5, reject) => { const ws = new WebSocket(url); const pending = /* @__PURE__ */ new Map(); const waiters = /* @__PURE__ */ new Map(); let id = 0; ws.addEventListener("error", () => reject(new ExportError("Cannot connect to Chrome DevTools"))); ws.addEventListener("message", (ev) => { const msg = JSON.parse(ev.data); if (msg.id && pending.has(msg.id)) { const { ok, fail } = pending.get(msg.id); pending.delete(msg.id); if (msg.error) fail(new ExportError(`CDP ${msg.error.message}`)); else ok(msg.result); } else if (msg.method && waiters.has(msg.method)) { waiters.get(msg.method)(msg.params); waiters.delete(msg.method); } }); ws.addEventListener("open", () => resolve5({ send(method, params = {}, sessionId) { return new Promise((ok, fail) => { const msgId = ++id; const timer = setTimeout(() => { pending.delete(msgId); fail(new ExportError(`Chrome is not responding (${method} took more than ${CDP_TIMEOUT_MS / 1e3} seconds)`)); }, CDP_TIMEOUT_MS); pending.set(msgId, { ok: (v) => { clearTimeout(timer); ok(v); }, fail: (e) => { clearTimeout(timer); fail(e); } }); ws.send(JSON.stringify({ id: msgId, method, params, ...sessionId ? { sessionId } : {} })); }); }, once: (method) => new Promise((r) => waiters.set(method, r)), close: () => ws.close() })); }); } // src/housekeeping.js import { readdirSync as readdirSync2, lstatSync, rmSync as rmSync3 } from "node:fs"; import { join as join6 } from "node:path"; // src/state.js import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync as renameSync2 } from "node:fs"; import { join as join5 } from "node:path"; // src/home.js import { mkdirSync as mkdirSync2, existsSync as existsSync4, statSync as statSync3, chmodSync } from "node:fs"; import { homedir as homedir2 } from "node:os"; import { join as join4 } from "node:path"; var defaultHome = () => join4(homedir2(), ".answer-me-with-html"); function ensureHome(home) { const fresh = !existsSync4(home); mkdirSync2(home, { recursive: true, mode: 448 }); if (process.platform === "win32" || !(fresh || home === defaultHome())) return; if ((statSync3(home).mode & 511) !== 448) chmodSync(home, 448); } // src/state.js var DAY = 24 * 60 * 60 * 1e3; var statePath = (home) => join5(home, "state.json"); function readState(home) { try { const data = JSON.parse(readFileSync5(statePath(home), "utf8")); return data && typeof data === "object" && !Array.isArray(data) ? data : {}; } catch { return {}; } } function writeState(home, patch) { const next = { ...readState(home), ...patch }; ensureHome(home); const tmp = `${statePath(home)}.${process.pid}.tmp`; writeFileSync3(tmp, `${JSON.stringify(next, null, 2)} `); renameSync2(tmp, statePath(home)); return next; } // src/update.js import { spawn as spawn3 } from "node:child_process"; var UPDATE = Object.freeze({ checkEveryDays: 7, hintEveryDays: 3, timeoutMs: 5e3, url: "https://raw.githubusercontent.com/QingYunA/answer-me-with-html/main/package.json" }); var SEMVER = /^v?(\d+)\.(\d+)\.(\d+)$/; function parseVersion(v) { const m = String(v).trim().match(SEMVER); return m ? m.slice(1, 4).map(Number) : null; } function newer(a, b) { const pa = parseVersion(a); const pb = parseVersion(b); if (!pa || !pb) return false; const i = pa.findIndex((n, k2) => n !== pb[k2]); return i !== -1 && pa[i] > pb[i]; } function updateCommand(scriptPath = "") { if (/[\\/]\.claude[\\/]plugins[\\/]/.test(scriptPath)) { return "run claude plugin update answer-me-with-html@answer-me-with-html in a terminal (or click Update now on the Installed tab of /plugin), then /reload-plugins"; } if (/[\\/]bin[\\/]am\.js$/.test(scriptPath)) { return "run git pull && npm install in the answer-me-with-html repository"; } return "run npx skills update answer-me-with-html -y"; } var updateEnabled = (env, config) => !(config.update_check === false || env.CI || env.AM_NO_UPDATE_CHECK); function updateHint(state, current, scriptPath, now = Date.now()) { if (!newer(state.latestVersion, current)) return null; if (state.lastUpdateHint && now - state.lastUpdateHint < UPDATE.hintEveryDays * DAY) return null; return `! Update hint: Answer me with HTML ${state.latestVersion} is available (current ${current}). Ask the user whether to update: ${updateCommand(scriptPath)}.`; } function shouldCheckUpdate(state, env, config, now = Date.now()) { if (!updateEnabled(env, config)) return false; return !state.lastUpdateCheck || now - state.lastUpdateCheck >= UPDATE.checkEveryDays * DAY; } function spawnUpdateCheck(home, scriptPath) { writeState(home, { lastUpdateCheck: Date.now() }); try { spawn3(process.execPath, [scriptPath, "__update-check"], { detached: true, stdio: "ignore", env: { ...process.env, AM_HOME: home } }).on("error", () => { }).unref(); } catch { } } async function runUpdateCheck(home, fetchImpl = fetch) { try { const res = await fetchImpl(UPDATE.url, { signal: AbortSignal.timeout(UPDATE.timeoutMs) }); if (!res.ok) return null; const { version } = await res.json(); if (!parseVersion(version)) return null; writeState(home, { latestVersion: version.trim(), lastUpdateCheck: Date.now() }); return version.trim(); } catch { return null; } } // src/housekeeping.js var CLEAN = Object.freeze({ days: 30, // am clean deletes pages and videos older than 30 days by default bigBytes: 200 * 2 ** 20, // notify at once above 200 MB staleDays: 30, // more than 30 days since the last cleanup… staleBytes: 20 * 2 ** 20, // …and above 20 MB: notify hintEveryDays: 7 // the same notice at most once every 7 days }); var DIRS3 = ["pages", "videos", "cache"]; function walk(dir) { let entries; try { if (!lstatSync(dir).isDirectory()) return []; entries = readdirSync2(dir, { withFileTypes: true }); } catch { return []; } return entries.flatMap((e) => { const p = join6(dir, e.name); if (e.isDirectory()) return walk(p); try { const s = lstatSync(p); return s.isFile() ? [{ path: p, bytes: s.size, mtime: s.mtimeMs }] : []; } catch { return []; } }); } var sum = (files) => files.reduce((n, f2) => n + f2.bytes, 0); function usage(home) { const parts = Object.fromEntries(DIRS3.map((d) => { const files = walk(join6(home, d)); return [d, { count: files.length, bytes: sum(files) }]; })); return { ...parts, total: DIRS3.reduce((n, d) => n + parts[d].bytes, 0) }; } function clean2(home, { days = CLEAN.days, all = false, dryRun = false, now = Date.now() } = {}) { const cutoff = now - days * DAY; const victims = [ ...["pages", "videos"].flatMap((d) => walk(join6(home, d)).filter((f2) => all || f2.mtime < cutoff)), ...walk(join6(home, "cache")) ]; if (!dryRun) { for (const f2 of victims) rmSync3(f2.path, { force: true }); writeState(home, { lastClean: now, lastCleanHint: null }); } return { files: victims.length, bytes: sum(victims) }; } var mb = (bytes) => bytes < 2 ** 20 ? `${Math.ceil(bytes / 1024)} KB` : `${(bytes / 2 ** 20).toFixed(bytes < 10 * 2 ** 20 ? 1 : 0)} MB`; function cleanHint(state, use, now = Date.now()) { if (state.lastCleanHint && now - state.lastCleanHint < CLEAN.hintEveryDays * DAY) return null; const since = state.lastClean ?? state.firstSeen ?? now; const days = Math.floor((now - since) / DAY); const big = use.total >= CLEAN.bigBytes; const stale = days >= CLEAN.staleDays && use.total >= CLEAN.staleBytes; if (!big && !stale) return null; const pages = `${use.pages.count} page${use.pages.count === 1 ? "" : "s"}`; const parts = `${pages} ${mb(use.pages.bytes)}, videos ${mb(use.videos.bytes)}, voice-over cache ${mb(use.cache.bytes)}`; const when = state.lastClean ? `last cleaned ${days} days ago` : "never cleaned"; return `! Cleanup hint: the data directory uses ${mb(use.total)} (${parts}), ${when}. Ask the user whether to run am clean (deletes pages and videos older than ${CLEAN.days} days and empties the voice-over cache; am clean --all deletes everything).`; } function afterRender({ home, env, config, current, scriptPath, background, now = Date.now() }) { let state = readState(home); if (!state.firstSeen) state = writeState(home, { firstSeen: now }); const hints = []; const c = cleanHint(state, usage(home), now); if (c) { hints.push(c); state = writeState(home, { lastCleanHint: now }); } const u = updateEnabled(env, config) ? updateHint(state, current, scriptPath, now) : null; if (u) { hints.push(u); writeState(home, { lastUpdateHint: now }); } if (background && scriptPath && shouldCheckUpdate(state, env, config, now)) spawnUpdateCheck(home, scriptPath); return hints; } // src/config.js import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, rmSync as rmSync4, existsSync as existsSync5 } from "node:fs"; import { join as join7, dirname } from "node:path"; var ConfigError = class extends Error { constructor(message) { super(message); this.name = "ConfigError"; } }; var CONFIG_KEYS = Object.freeze({ open: { type: "bool", default: true, label: "Open the page in the browser after it is made" }, theme: { type: "enum", choices: CHOICES.theme, default: AUTO, label: "Default theme (auto: paper for long text, blueprint for diagrams)" }, mode: { type: "enum", choices: CHOICES.mode, default: "auto", label: "Default light/dark mode" }, style: { type: "enum", choices: CHOICES.style, default: "80", label: "STE writing-check strictness" }, update_check: { type: "bool", default: true, label: "Check for a new version once a week in the background and tell you (never updates by itself)" }, voice: { type: "enum", choices: VOICES, default: "auto", label: "Video narration voice-over (auto: ElevenLabs if ELEVENLABS_API_KEY is set, otherwise system TTS; local: the local service at AM_TTS_URL)" } }); var TRUE = /* @__PURE__ */ new Set(["on", "true", "yes", "1", "\u5F00", "\u5F00\u542F", "\u6253\u5F00"]); var FALSE = /* @__PURE__ */ new Set(["off", "false", "no", "0", "\u5173", "\u5173\u95ED"]); function amHome(env = process.env) { return env.AM_HOME || defaultHome(); } function configPath(env = process.env) { return join7(amHome(env), "config.json"); } var defaults = () => Object.fromEntries(Object.entries(CONFIG_KEYS).map(([k2, s]) => [k2, s.default])); function configChoices(key, themes2) { return key === "theme" ? [AUTO, ...themes2.names("page")] : CONFIG_KEYS[key].choices; } function coerce2(key, raw, themes2) { const spec = CONFIG_KEYS[key]; if (!spec) throw new ConfigError(`No setting named "${key}". Available: ${Object.keys(CONFIG_KEYS).join(" | ")}`); if (spec.type === "bool") { if (typeof raw === "boolean") return raw; const v2 = String(raw).trim().toLowerCase(); if (TRUE.has(v2)) return true; if (FALSE.has(v2)) return false; throw new ConfigError(`${key} accepts only on / off`); } const v = String(raw).trim(); const choices = configChoices(key, themes2); if (!choices.includes(v)) throw new ConfigError(`Invalid ${key} value "${v}". Choose one of: ${choices.join(" | ")}`); return v; } function readStored(env) { const file = configPath(env); if (!existsSync5(file)) return { stored: {} }; try { const data = JSON.parse(readFileSync6(file, "utf8")); return { stored: data && typeof data === "object" && !Array.isArray(data) ? data : {} }; } catch (e) { return { stored: {}, warning: `Cannot parse ${file}; using the default settings (${e.message})` }; } } function readConfig(env = process.env, themes2 = loadThemes(amHome(env))) { const { stored, warning } = readStored(env); const values = defaults(); const warnings = [warning]; for (const [k2, v] of Object.entries(stored)) { if (!CONFIG_KEYS[k2]) continue; try { values[k2] = coerce2(k2, v, themes2); } catch { if (k2 === "theme") warnings.push(`The default theme "${v}" cannot be used (${themes2.problem(String(v), "page")}); using ${values.theme}`); } } return { values, stored, warning: warnings.filter(Boolean).join("; ") || void 0, path: configPath(env) }; } function writeStored(stored, env) { const file = configPath(env); if (!Object.keys(stored).length) { rmSync4(file, { force: true }); return; } ensureHome(dirname(file)); writeFileSync4(file, `${JSON.stringify(stored, null, 2)} `); } function setConfig(key, raw, env = process.env, themes2 = loadThemes(amHome(env))) { const value = coerce2(key, raw, themes2); const { stored } = readStored(env); writeStored({ ...stored, [key]: value }, env); return value; } function resetConfig(key, env = process.env) { if (key !== void 0 && !CONFIG_KEYS[key]) coerce2(key, "", null); const { stored } = readStored(env); const next = key === void 0 ? {} : Object.fromEntries(Object.entries(stored).filter(([k2]) => k2 !== key)); writeStored(next, env); } // src/math-load.js async function loadMath(source) { if (!mayHaveMath(String(source))) return; useTemml((await Promise.resolve().then(() => (init_temml(), temml_exports))).default); } // src/patch.js var ATTR_BLOCK2 = /\s*\{([^{}]*)\}\s*$/; var PatchError = class extends Error { constructor(message) { super(message); this.name = "PatchError"; } }; function normalizePanelQuery(query) { let q3 = String(query ?? "").trim(); if (q3.startsWith("##")) q3 = q3.replace(/^##\s*/, ""); q3 = q3.replace(ATTR_BLOCK2, "").trim(); return q3; } function panelKeys(panel) { const title = panel.title.trim(); const id = String(panel.id || "").trim(); const keys = /* @__PURE__ */ new Set([title]); if (id) { keys.add(id); if (title) keys.add(`${id} ${title}`); } return keys; } function findPanel(doc2, query) { const q3 = normalizePanelQuery(query); if (!q3) throw new PatchError("Missing the --panel title"); const matches = doc2.panels.filter((p) => panelKeys(p).has(q3)); if (matches.length === 0) throw new PatchError(`No panel titled "${query}"`); if (matches.length > 1) throw new PatchError(`The title "${query}" matches more than one panel`); return matches[0]; } function asSinglePanelMarkdown(replacement) { const text = String(replacement).replace(/\r\n?/g, "\n"); if (!text.trim()) throw new PatchError("The new panel draft is empty"); const looksLikeHeading = /^\s*##\s+/.test(text); const doc2 = parseDoc(looksLikeHeading ? text : `## _ ${text}`); if (doc2.panels.length !== 1) throw new PatchError("The new panel draft must contain exactly one ## panel"); return { text, looksLikeHeading }; } function replacePanel(source, query, replacement) { const doc2 = parseDoc(source); const panel = findPanel(doc2, query); const idx = doc2.panels.indexOf(panel); const lines = String(source).replace(/\r\n?/g, "\n").split("\n"); const start = panel.line - 1; const end = doc2.panels[idx + 1] ? doc2.panels[idx + 1].line - 1 : lines.length; const { text, looksLikeHeading } = asSinglePanelMarkdown(replacement); const section = looksLikeHeading ? text : `${lines[start]} ${text.replace(/^\n+/, "")}`; const newLines = section.replace(/\n$/, "").split("\n"); return [...lines.slice(0, start), ...newLines, ...lines.slice(end)].join("\n"); } // src/serve.js import { createServer } from "node:http"; import { connect as connect2, isIP } from "node:net"; import { networkInterfaces, uptime } from "node:os"; import { createHmac, randomBytes, timingSafeEqual } from "node:crypto"; import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, renameSync as renameSync3, rmSync as rmSync5 } from "node:fs"; import { lstat, realpath, readFile } from "node:fs/promises"; import { join as join8, resolve as resolve3, dirname as dirname2, basename as basename3, sep as sep2 } from "node:path"; var DEFAULT_PORT = 8765; var HOST = "127.0.0.1"; var LAN_BIND = "0.0.0.0"; var TOKEN_LENGTH = 22; var PREFIX2 = Object.freeze({ p: "pages", v: "videos" }); var PREFIX_OF = Object.freeze({ pages: "p", videos: "v" }); var LOOPBACK_HOST = /^(127\.0\.0\.1|localhost|\[::1\])(:\d{1,5})?$/i; function lanRank(address) { const [a, b] = address.split(".").map(Number); if (a === 192 && b === 168) return 0; if (a === 10) return 1; if (a === 172 && b >= 16 && b <= 31) return 2; return 3; } var compareOctets = (x2, y2) => { const [p, q3] = [x2, y2].map((address) => address.split(".").map(Number)); const i = p.findIndex((n, k2) => n !== q3[k2]); return i === -1 ? 0 : p[i] - q3[i]; }; function lanIPv4Addresses(interfaces = networkInterfaces()) { return [...new Set(Object.values(interfaces).flatMap((entries) => entries ?? []).filter(({ address, family, internal }) => (family === "IPv4" || family === 4) && !internal && isIP(address) === 4 && address !== LAN_BIND && !address.startsWith("127.") && !address.startsWith("169.254.")).map(({ address }) => address))].sort((x2, y2) => lanRank(x2) - lanRank(y2) || compareOctets(x2, y2)); } function publicUrlOrigin(input) { const error = () => new ServeError("--public-url must be an http:// or https:// origin without a path, credentials, query or fragment"); if (typeof input !== "string" || !/^https?:\/\/[^\s/?#]+\/?$/i.test(input)) throw error(); let url; try { url = new URL(input); } catch { throw error(); } if (url.username || url.password || url.pathname !== "/" || url.search || url.hash || !url.hostname || url.hostname === LAN_BIND) throw error(); return url.origin; } function hostAllowed(host, additional) { if (LOOPBACK_HOST.test(host ?? "")) return true; if (typeof host !== "string") return false; const value = host.toLowerCase(); return additional.some((allowed) => { if (value === allowed.toLowerCase()) return true; if (isIP(allowed) !== 4 || !value.startsWith(allowed + ":")) return false; const port = value.slice(allowed.length + 1); return /^[1-9][0-9]{0,4}$/.test(port) && Number(port) <= 65535; }); } var SANDBOX = "sandbox allow-scripts allow-popups allow-popups-to-escape-sandbox allow-downloads"; var HEADERS = Object.freeze({ "Content-Security-Policy": `frame-ancestors 'none'; ${SANDBOX}`, "X-Frame-Options": "DENY", "X-Content-Type-Options": "nosniff", "Referrer-Policy": "no-referrer", "Cache-Control": "no-store", "Cross-Origin-Resource-Policy": "same-origin" }); var ServeError = class extends Error { constructor(message) { super(message); this.name = "ServeError"; } }; var infoPath = (home) => join8(home, "serve.json"); function pageToken(secret, dir, file) { return createHmac("sha256", secret).update(`${dir}/${file}`).digest("base64url").slice(0, TOKEN_LENGTH); } function pageLink({ port, secret, baseUrl }, dir, file) { const origin = baseUrl ?? `http://${HOST}:${port}`; return `${origin}/${PREFIX_OF[dir]}/${pageToken(secret, dir, file)}/${encodeURIComponent(file)}`; } function validFileName(name) { return typeof name === "string" && name.length > 0 && name.endsWith(".html") && !/[/\\\0]/.test(name) && !name.includes(".."); } function tokenMatches(secret, dir, file, token) { const expected = Buffer.from(pageToken(secret, dir, file)); const given = Buffer.from(token); return given.length === expected.length && timingSafeEqual(given, expected); } function alive(pid) { if (!Number.isInteger(pid) || pid <= 0) return false; try { process.kill(pid, 0); return true; } catch { return false; } } var BOOT_SLACK_MS = 5e3; var bootedBefore = (startedAt) => startedAt < Date.now() - uptime() * 1e3 - BOOT_SLACK_MS; function readServeInfo(home) { try { const info = JSON.parse(readFileSync7(infoPath(home), "utf8")); const validBase = info?.baseUrl === void 0 || typeof info.baseUrl === "string" && publicUrlOrigin(info.baseUrl) === info.baseUrl; const ok = info && Number.isInteger(info.port) && typeof info.secret === "string" && info.secret.length > 0 && Number.isFinite(info.startedAt) && !bootedBefore(info.startedAt) && alive(info.pid) && validBase; return ok ? { pid: info.pid, port: info.port, secret: info.secret, baseUrl: info.baseUrl } : null; } catch { return null; } } function serveLink(home, file) { try { const info = readServeInfo(home); if (!info) return null; const path = resolve3(file); const dir = ["pages", "videos"].find((d) => dirname2(path) === resolve3(home, d)); const name = basename3(path); return dir && validFileName(name) ? pageLink(info, dir, name) : null; } catch { return null; } } function writeInfo(home, info) { ensureHome(home); const tmp = `${infoPath(home)}.${process.pid}.tmp`; try { rmSync5(tmp, { force: true }); writeFileSync5(tmp, `${JSON.stringify(info)} `, { mode: 384, flag: "wx" }); renameSync3(tmp, infoPath(home)); } finally { rmSync5(tmp, { force: true }); } } function removeInfo(home, secret) { try { if (JSON.parse(readFileSync7(infoPath(home), "utf8")).secret === secret) rmSync5(infoPath(home), { force: true }); } catch { } } function reply(req, res, status, type, body = "") { res.writeHead(status, { ...HEADERS, "Content-Type": type, "Content-Length": Buffer.byteLength(body) }); res.end(req.method === "HEAD" ? void 0 : body); } var notFound = (req, res) => reply(req, res, 404, "text/plain; charset=utf-8", "Not found\n"); async function resolvePage(home, dir, file) { try { const root = await realpath(join8(home, dir)); const path = join8(root, file); if (!(await lstat(path)).isFile()) return null; const real = await realpath(path); return real.startsWith(root + sep2) ? real : null; } catch { return null; } } function handler(home, secret, allowedHosts) { return async (req, res) => { try { if (!hostAllowed(req.headers.host, allowedHosts)) return notFound(req, res); if (req.method !== "GET" && req.method !== "HEAD") { res.setHeader("Allow", "GET, HEAD"); return reply(req, res, 405, "text/plain; charset=utf-8", "Method not allowed\n"); } const parts = req.url.split(/[?#]/)[0].split("/"); if (parts.length !== 4 || parts[0] !== "") return notFound(req, res); const [, prefix, token, raw] = parts; const dir = Object.hasOwn(PREFIX2, prefix) ? PREFIX2[prefix] : void 0; if (!dir || !token || !raw) return notFound(req, res); let file; try { file = decodeURIComponent(raw); } catch { return notFound(req, res); } if (!validFileName(file) || !tokenMatches(secret, dir, file, token)) return notFound(req, res); const dest = req.headers["sec-fetch-dest"]; if (dest !== void 0 && dest !== "document") return notFound(req, res); const real = await resolvePage(home, dir, file); if (!real) return notFound(req, res); return reply(req, res, 200, "text/html; charset=utf-8", await readFile(real)); } catch { return notFound(req, res); } }; } function listening(port, timeout = 500) { return new Promise((done) => { const socket = connect2({ host: HOST, port }); const end = (up) => { socket.destroy(); done(up); }; socket.setTimeout(timeout, () => end(false)); socket.once("connect", () => end(true)); socket.once("error", () => end(false)); }); } async function startServer({ home, port = DEFAULT_PORT, lan = false, publicUrl, lanAddresses }) { const addresses = lan ? lanAddresses ?? lanIPv4Addresses() : []; if (lan && (!addresses.length || addresses.some((address) => isIP(address) !== 4 || address === LAN_BIND || address.startsWith("127.")))) { throw new ServeError("--lan needs at least one active non-loopback IPv4 interface"); } const publicOrigin = publicUrl === void 0 ? null : publicUrlOrigin(publicUrl); const allowedHosts = [...addresses, ...publicOrigin ? [new URL(publicOrigin).host] : []]; const running = readServeInfo(home); if (running && await listening(running.port)) throw new ServeError(`am serve is already running on ${running.baseUrl ?? `http://${HOST}:${running.port}`} (pid ${running.pid}); stop it first`); const secret = randomBytes(32).toString("hex"); const server = createServer(handler(home, secret, allowedHosts)); await new Promise((done, fail) => { server.once("error", (e) => fail(e.code === "EADDRINUSE" ? new ServeError(`Port ${port} is already in use; pick another with am serve --port `) : e)); server.listen(port, lan ? LAN_BIND : HOST, done); }); const actual = server.address().port; const baseUrl = publicOrigin ?? `http://${lan ? addresses[0] : HOST}:${actual}`; try { writeInfo(home, { pid: process.pid, port: actual, secret, startedAt: Date.now(), baseUrl }); } catch (e) { server.closeAllConnections(); await new Promise((done) => server.close(done)); throw new ServeError(`Cannot save serve.json; server stopped: ${e.message}`); } let closed; const close = () => { closed ??= new Promise((done) => { removeInfo(home, secret); server.close(() => done()); server.closeAllConnections(); }); return closed; }; return { port: actual, secret, close, baseUrl, addresses, lan }; } async function runServe({ home, port, lan = false, publicUrl, print, fail }) { let srv; try { srv = await startServer({ home, port, lan, publicUrl }); } catch (e) { if (!(e instanceof ServeError)) throw e; fail(`\u2717 ${e.message}`); return 1; } return new Promise((done) => { const stop = () => srv.close().then(() => done(0)); process.once("SIGINT", stop); process.once("SIGTERM", stop); process.once("SIGHUP", stop); process.once("exit", () => removeInfo(home, srv.secret)); if (lan) { print(`Serving pages on ${LAN_BIND}:${srv.port} (Ctrl-C to stop). am render prints a link for each page.`); print("WARNING: --lan exposes this port on every IPv4 interface, without built-in TLS. A page link is a secret: use a trusted LAN or an HTTPS reverse proxy."); for (const address of srv.addresses) print(`LAN address: http://${address}:${srv.port}`); } else { print(`Serving pages on http://${HOST}:${srv.port} (Ctrl-C to stop). am render prints a link for each page.`); if (!publicUrl) print(`Reaching it from another computer: run ssh -L ${srv.port}:${HOST}:${srv.port} user@host there, then open the links in its browser.`); } if (publicUrl) { print(`Public link origin: ${srv.baseUrl}`); if (srv.baseUrl.startsWith("http://")) print("WARNING: --public-url uses HTTP; links and their capability tokens are not encrypted in transit."); } }); } // src/cli.js var MAX_LISTED_WARNINGS = 20; var USAGE = `Answer me with HTML ${VERSION} \u2014 renders a Markdown draft into a single-file HTML explainer page Usage: am render [-o ] [--replace ] [--no-open] [--theme ${["auto", ...themeNames("page")].join("|")}] [--template sheet|doc] [--style off|80|strict] [--mode auto|light|dark] am patch --panel [file|-] [--from file] [--theme \u2026] [--no-open] replace one ## panel of an existing page and overwrite that HTML in place am video <file|-> [-o <path>] [--voice auto|elevenlabs|local|system|off] [--mp4] [--webm] [--no-open] [--theme ${["auto", ...themeNames("video")].join("|")}] [--mode light|dark] render a video draft into a 3b1b-style explainer video player page (--mp4 / --webm also save a video file) am lint <file|-> [--style off|80|strict] run only the STE controlled-writing check am config [set <key> <value> | get <key> | reset [key]] show or change settings am serve [--port 8765] [--lan] [--public-url <origin>] serve pages over http on 127.0.0.1 (--lan: on the local network); am render then prints a link: am clean [--days 30] [--all] [--dry-run] delete old pages, old videos and the voice-over cache am theme check <name|file.json> [--no-open] check a theme's colors and contrast, and render specimen pages am list list templates, themes and components am help [component|format|code|image|video|patch|theme] show component syntax / page draft format / code block / image syntax / video draft format / patch / theme usage - A file argument of - reads from stdin (good for heredoc: am render - <<'EOF' ... EOF). - Output goes to ~/.answer-me-with-html/pages/ by default (change it with the AM_HOME environment variable). - A page with STE or code warnings does not open. Fix the draft and render again with --replace <page>: the earlier page is deleted once the new one is written. - Set auto-open, the default theme and more with am config; --open / --no-open apply to this run only. - am patch reads the source draft from the page's hidden #am-source, changes only the ## section that --panel names, and writes the page back to the same path.`; var FORMAT = `Draft format (extended Markdown) --- template: sheet # sheet: blueprint board (default, multi-panel grid) | doc: linear explainer (one column + contents) theme: auto # auto (default): paper for the doc template or text only, blueprint with diagrams | blueprint | shadcn | paper; switchable in the page title: Page title # or use "# Title" as the first line of the body subtitle: Subtitle # optional cols: 3 # number of sheet grid columns, default 3 style: 80 # STE check strictness: off | 80 (default, warn only) | strict (no page if it fails) mode: auto # auto follows the system | light | dark source: asd-ste100.org # any other key shows in the page header meta line --- Intro (optional, shown below the title) ## A Panel title {span=2 meta="top-right note"} Plain Markdown: paragraphs, lists, tables, quotes, inline code... Write ok / no / warn in a table cell (text may follow, e.g. "ok approved") to get a \u2713 / \u2717 / ! badge. \`\`\`flow LR \u2190 fence language = component name, followed by component arguments A -> B \`\`\` \`\`\`html \u2190 html / svg fences are embedded as-is (escape hatch) <div>any content</div> \`\`\` - "## " starts a panel; the letter ID is optional (A, B, C... are assigned automatically). span is a hint: the page sizes panels to fit their content, so wide tables and diagrams need no span. Write span only for a panel that must stand out. - An image on its own line, ![what it shows](path), becomes a captioned figure and is embedded in the page; see am help image. - Math in text: $x^2$ is inline; a block is a line with only $$, the TeX, and a line with only $$ (or one line: $$ E = mc^2 $$). Put a long formula in a block: a block scrolls inside its panel, and an inline formula may wrap after = or +. \\$ writes a dollar sign. The TeX is typeset when the page is made (Temml): no script, no network. $5 and $10 stay text (a closing $ has no space before it and no digit after it), and so does $PATH:$HOME (a closing $ with a letter or _ after it does not close; write $n$-th, not $n$th). Code, diagrams and video take no math. In a table cell write \\vert x \\vert, not |x|. \\ref, \\eqref and \\label are not supported (they give a warning; \\tag{1} works in a block). TeX that cannot be read stays as raw TeX in red, with a warning and its line: fix it and render again with --replace. - A placeholder such as <host> is shown as text. Inside a sentence only text-level tags stay (b, i, kbd, sup, a, span, br, img ...), and tags that break the page (script, style, iframe ...) are shown as text too. Put raw markup in an html or svg fence and code in backticks. - Any other fence language is a code block; \`\`\`ts src=path lines=18-30 quotes real code from a file; see am help code. - For the component list see am list; for one component's syntax see am help <component>.`; var IMAGE_HELP = `Images: a screenshot, photo or render that already exists as a file ![What the picture shows](/absolute/path/to/screenshot.png) - Put the image alone on its line; the alt text becomes its caption, so write what the picture shows (the STE check reads it). - Use the absolute path. A relative path is read from the draft file's folder, or from the current folder when the draft comes from stdin. A space in the path is fine. - PNG, JPG, GIF, WebP, AVIF and SVG files up to 5 MB. The file is embedded in the page, which stays one file that opens offline. - http(s) URLs and data: URIs are left as they are. A URL needs the network when the page is opened. - The page keeps the path of each image. am patch embeds the image again from the file, or from the page when the file is gone. - Images are for things a diagram cannot show, such as a real screen. Do not generate or invent images.`; var CODE_HELP = `Code blocks: real code from a file, or code you type \`\`\`ts src=server/routes.ts lines=18-30 hl=22 \`\`\` \`\`\`ts title="limits.ts \xB7 sketch" export const LIMIT = 50 \`\`\` - A fence whose language is not a component is a code block. Each block gets a header and a Copy button. - src= quotes a file: the CLI reads the lines, so you do not type them, and the code is the real code. Leave the block empty. The path is read from the current folder, and only files inside it are quoted. lines=18-30 (or lines=18) picks the lines; without it the whole file is quoted. - The header shows path:lines, or title= when you set it. Write "sketch" in the title of code that does not exist yet. - hl=22 or hl=20-22,25 highlights lines by their shown number. start=38 numbers a typed block from 38. - A diff: a fence with the language diff holds a unified diff you paste, and the CLI draws it (see below). - 10 to ${LONG_CODE_LINES} lines make the point best: a longer block gets a warning, and more than ${MAX_CODE_LINES} lines is an error. - Files that hold keys by convention (.env, *.pem, id_rsa, .ssh/, .git/ \u2026) and files with anything that looks like a key or a token are refused. - The render lists every embedded file. The page keeps the path; am patch reads the file again, or keeps the page's copy when the file has moved. Diff blocks: show a change \`\`\`diff file=src/code.js @@ -60,3 +60,3 @@ export function parseCodeArgs(args) { - const attrs = parseAttrs(args); + const attrs = parseAttrs(args, { diff: true }); const opts = {}; \`\`\` - Paste the unified diff. Each line starts with + (added, green), - (removed, red) or a space (context); a line with nothing on it counts as context. - @@ -60,3 +60,3 @@ starts a hunk: the CLI shows it as a thin row and numbers the lines from it, with two gutters, old and new. Without @@, start=N numbers from N; with neither, the block has no numbers. - file= names the file in the header, and the language label comes from its extension. A +++ b/path line names it when file= is missing; title= wins over both. - The header shows a stat after the title: +1 \u22121. Copy copies the diff as written. - hl= uses new-side numbers and needs numbers (@@ or start=). - diff --git, index, ---, +++ and "\\ No newline at end of file" lines are accepted and not drawn. - Cut lines (a line with only ... or \u2026) are an error: the gutter cannot know how many lines were skipped. Split the diff into two hunks, each with its own @@ header. - A hunk whose line counts do not match its @@ header is a warning. src= is not supported for a diff yet: paste the diff. - The limits are the same as for other code blocks: a warning above ${LONG_CODE_LINES} lines, an error above ${MAX_CODE_LINES}, and the key and token check.`; var RAW_HELP = `LANG \u2014 embed as-is (escape hatch) When the fence language is LANG, the content goes into the page unprocessed. Use it only when no component can show the content; use theme variables for colors (such as var(--ink), var(--accent)) so the content stays readable across themes and light/dark modes. Example: \`\`\`LANG <div style="color: var(--accent)">any content</div> \`\`\``; var VIDEO_FORMAT = `Video draft format (am video) --- title: TCP three-way handshake subtitle: Why three steps # optional, subtitle on the title card theme: blueprint # blueprint: drawing style (auto picks it; follows the theme in am config) | shadcn: cards | 3b1b: dark mode: light # light | dark (blueprint + dark is a dark-blue drawing) --- > Title-card narration (optional; without it the title card stays for 2.4 seconds) ## Both ends are waiting \`\`\`sequence Client -> Server: SYN Server -> Client: SYN-ACK Client -> Server: ACK \`\`\` > The client sends SYN first to ask for a connection. > [Server] replies with SYN-ACK. > The client sends ACK, and the connection is open. - "## " starts a scene; a scene holds components or Markdown (the picture), and lines that start with > are narration (one beat per line). - When narration line N plays, step N of the picture appears: in flow / er / sequence / tree each source line is one step; timeline, limits, table rows, list items and paragraphs step item by item. With more steps than narration lines, the steps are spread across the lines; with more narration lines than steps, the extra first lines act as an opening and show nothing new. - Write [name] in narration: the camera zooms in on the element with that name and highlights it, and the word turns yellow in the caption. - Nodes / participants with the same name in adjacent scenes move smoothly from the old position to the new one (cross-scene morph). - Voice-over: --voice auto (default: ElevenLabs if ELEVENLABS_API_KEY is set, otherwise system TTS) | elevenlabs | local | system | off. The system voice is picked for each line from the languages installed on the machine: macOS say takes the installed voice of the line's language (a Taiwan voice for Traditional Chinese), Linux takes the espeak-ng voice for it. A line whose language has no installed voice keeps its caption without narration, and the run says which language that was. Set the ElevenLabs voice with ELEVENLABS_VOICE_ID and the model with ELEVENLABS_MODEL_ID (default eleven_v4_turbo). local calls a local OpenAI-compatible speech service (POST /v1/audio/speech, returns 16-bit PCM WAV): AM_TTS_URL (required, service base URL), AM_TTS_MODEL, AM_TTS_VOICE (required when the service has no default), AM_TTS_API_KEY is sent as a Bearer token when set; AM_TTS_EXTRA holds model-specific parameters (a JSON object); AM_TTS_MODEL / AM_TTS_VOICE override the same fields in it, and am always sets input, response_format and stream. A line whose duration is clearly wrong is synthesized again, up to AM_TTS_ATTEMPTS times per line (default 3; set 1 to turn this off). Example: AM_TTS_URL=http://127.0.0.1:8000 AM_TTS_MODEL=mlx-community/Qwen3-TTS-12Hz-1.7B-CustomVoice-4bit AM_TTS_VOICE=vivian am video draft.md --voice local - Output goes to ~/.answer-me-with-html/videos/. The player carries a chapter strip (a chip jumps into that scene), a speed button (0.5x to 2x) and an export button that saves the same video through the browser, without a terminal. - --mp4 also saves a 1080p .mp4 next to the page (H.264 + AAC; needs ffmpeg; about 1.3 times the video length). --webm saves a 1080p .webm that the page encodes itself (VP9 + Opus; no ffmpeg; the time follows how much of the page moves). Both need Chrome and Node 22+; give both to save both. The export button needs a secure context (a local file or localhost): WebCodecs is not available to a page served over plain HTTP.`; async function main(argv, io = {}) { const out = io.stdout ?? process.stdout; const err = io.stderr ?? process.stderr; const env = io.env ?? process.env; const print = (s = "") => out.write(`${s} `); const fail = (s) => err.write(`${s} `); let parsed; try { parsed = parseArgs({ args: argv, allowPositionals: true, options: { out: { type: "string", short: "o" }, replace: { type: "string" }, "no-open": { type: "boolean" }, open: { type: "boolean" }, theme: { type: "string" }, template: { type: "string" }, style: { type: "string" }, mode: { type: "string" }, voice: { type: "string" }, mp4: { type: "boolean" }, webm: { type: "boolean" }, panel: { type: "string" }, from: { type: "string" }, days: { type: "string" }, port: { type: "string" }, lan: { type: "boolean" }, "public-url": { type: "string" }, all: { type: "boolean" }, "dry-run": { type: "boolean" }, help: { type: "boolean", short: "h" }, version: { type: "boolean", short: "v" } } }); } catch (e) { fail(`\u2717 ${e.message} ${USAGE}`); return 2; } const { values: opts, positionals: [cmd, arg, ...rest] } = parsed; if (opts.version) return print(VERSION), 0; if (opts.help || !cmd) return print(USAGE), 0; const themes2 = loadThemes(amHome(env)); const ctx = { print, fail, env, io, themes: themes2 }; switch (cmd) { case "render": return withSource(arg, io, fail, (src, baseDir) => cmdRender(src, opts, ctx, baseDir)); case "patch": return cmdPatch(arg, rest[0], opts, ctx); case "video": return withSource(arg, io, fail, (src) => cmdVideo(src, opts, ctx)); case "lint": return withSource(arg, io, fail, (src) => cmdLint(src, opts, { print, fail })); case "config": return cmdConfig([arg, ...rest].filter((x2) => x2 !== void 0), ctx); case "theme": return cmdTheme(arg, rest[0], opts, ctx); case "clean": return cmdClean(opts, { print, fail, env }); case "serve": return cmdServe(opts, { print, fail, env }); case "__update-check": return await runUpdateCheck(amHome(env)) ? 0 : 1; case "list": return cmdList(ctx), 0; case "help": return cmdHelp(arg, { print, fail }); default: fail(`\u2717 Unknown command "${cmd}" ${USAGE}`); return 2; } } async function withSource(arg, io, fail, fn3) { if (!arg) { fail("\u2717 Missing the draft argument: pass a file path, or - to read from stdin"); return 2; } const cwd = io.cwd ?? process.cwd(); let src; try { src = arg === "-" ? await readStream(io.stdin ?? process.stdin) : readFileSync8(resolve4(cwd, arg), "utf8"); } catch (e) { fail(`\u2717 Cannot read the draft: ${e.message}`); return 2; } if (!src.trim()) { fail("\u2717 The draft is empty"); return 2; } return fn3(src, arg === "-" ? cwd : dirname3(resolve4(cwd, arg))); } async function readStream(stream) { const chunks = []; for await (const chunk of stream) chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk); return Buffer.concat(chunks).toString("utf8"); } function shouldOpen(opts, env, config) { if (opts.open) return true; if (opts["no-open"]) return false; if (env.AM_NO_OPEN && env.AM_NO_OPEN !== "0") return false; if (env.CI) return false; return config.open !== false; } async function cmdRender(src, opts, ctx, baseDir) { const { fail, print } = ctx; const replaced = opts.replace === void 0 ? null : pagePath(opts.replace, ctx); if (replaced === false) { fail(`\u2717 --replace takes a page that am render wrote (a .html file in ${join9(amHome(ctx.env), "pages")})`); return 2; } const config = loadConfig(ctx); const { theme, mode, style } = config.values; await loadMath(src); let result; try { result = renderDoc(src, { theme: opts.theme, template: opts.template, style: opts.style, mode: opts.mode }, { theme, mode, style }, { themes: ctx.themes, baseDir, codeDir: ctx.io.cwd ?? process.cwd() }); } catch (e) { return reportError(e, fail); } const file = outputPath("pages", result.meta.title, opts, ctx); emit(result, file, ctx); if (replaced && replaced !== resolve4(file)) rmSync6(replaced, { force: true }); const held = !opts.open && hasRetryWarnings(result) && shouldOpen(opts, ctx.env, config.values); if (held) print(` Not opened because of the warnings; to render again, add --replace ${file}`); return finish(file, held ? { ...opts, "no-open": true } : opts, config, ctx); } var hasRetryWarnings = (result) => result.warnings.length > 0 || (result.stats.codeWarnings ?? []).length > 0 || (result.stats.mathWarnings ?? []).length > 0; function pagePath(arg, { env, io }) { const path = resolve4(io.cwd ?? process.cwd(), arg); return dirname3(path) === resolve4(amHome(env), "pages") && basename4(path).endsWith(".html") ? path : false; } var PATCH_HELP = `Replace one panel of a rendered page in place Usage: am patch <html-file> --panel <title> < new-panel.md am patch <html-file> --panel <title> --from new-panel.md am patch <html-file> --panel <title> - - Reads the source draft from the hidden <textarea id="am-source"> in <html-file>. - --panel matches a ## section's title, its letter ID, or "ID title". - The new draft comes from stdin or from --from / a second file argument: it may include the ## heading or only the panel body. - Renders again with the current renderer and overwrites the same HTML path; it writes no new timestamped file. - Keeps the page's template, theme, light/dark mode and STE style (recorded on the page's root tag when it was made). Later config changes do not apply to patched pages; to change them add --theme / --mode / --style. - If the panel is not found, or the page has no #am-source, the exit code is non-zero and the file is not changed.`; var THEME_HELP = `Your own theme: one JSON file per theme in ~/.answer-me-with-html/themes/ (AM_HOME moves it) The file name is the theme name: themes/notes.json is theme "notes" (lowercase letters, digits and -; not a built-in name). Pick it like a built-in theme: theme: notes in the draft, --theme notes, or am config set theme notes. The draft does not change. { "label": "Notes", "tokens": { "common": { "--radius": "6px", "--font-sans": "\\"IBM Plex Sans\\", \\"Noto Sans CJK SC\\"" }, "light": { "--bg": "#f7f5ef", "--paper": "#fffdf8", "--ink": "#1f1d1a", ... }, "dark": { "--bg": "#14130f", "--paper": "#1c1b17", "--ink": "#eeeae0", ... } }, "css": "& .am-panel-head { letter-spacing: 0.01em; }", "video": { "tokens": { "light": { "--v-stage": "#fffdf8" } }, "css": "& .amv-title { font-weight: 500; }" } } - label: the name on the page's theme button: a string, or an object with ${languageIds().join(" / ")} strings. - tokens: light and dark must each set every color: ${COLOR_TOKENS.join(" ")}. common holds values shared by both; --radius --shadow --bw --head-font --font-sans --font-mono are optional. - Fonts: name installed fonts only; the default font stack is added as the fallback. No font files are embedded. - css (optional): start every selector with &, which stands for the theme's root, so the rules apply only under this theme. - video (optional): video-only variables (--v-stage, --v-title-font, --v-cap-fg, --v-cap-bg, --v-glow) and & css for am video. - A page carries the built-in themes plus its own theme, so it opens anywhere; readers without your fonts see the fallback. - A file with problems is skipped with a warning; am theme check <name|file.json> tells you why. am theme check <name|file.json> [--no-open] - Reports invalid colors, missing variables and contrast below WCAG AA in light and dark (text 4.5:1; status badges 3:1, warning below 4.5:1). - Exits with 1 when there is an error. Without errors it renders two specimen pages (light, dark) with every component.`; async function cmdPatch(htmlArg, fromArg, opts, ctx) { const { fail, io } = ctx; if (!htmlArg || htmlArg === "-") { fail(htmlArg ? "\u2717 patch needs the path of an existing HTML file; it cannot read the page from stdin" : "\u2717 Missing the HTML file path"); return 2; } if (!opts.panel || !String(opts.panel).trim()) { fail("\u2717 Missing --panel <title>"); return 2; } const cwd = io.cwd ?? process.cwd(); const file = resolve4(cwd, htmlArg); let html; try { html = readFileSync8(file, "utf8"); } catch (e) { fail(`\u2717 Cannot read the HTML: ${e.message}`); return 2; } const page = readPage(html); const { source, video } = page; if (source == null) { fail("\u2717 The page has no #am-source, so the source draft cannot be recovered"); return 1; } const from = opts.from ?? fromArg; let replacement; try { replacement = !from || from === "-" ? await readStream(io.stdin ?? process.stdin) : readFileSync8(resolve4(cwd, from), "utf8"); } catch (e) { fail(`\u2717 Cannot read the new panel draft: ${e.message}`); return 2; } let patched; try { patched = replacePanel(source, opts.panel, replacement); } catch (e) { if (!(e instanceof PatchError)) return reportError(e, fail); fail(`\u2717 ${e.message}`); return 1; } const problem = ctx.themes.problem(page.theme, video ? "video" : "page"); if (problem && !opts.theme) { fail(`\u2717 The page uses theme "${page.theme}", which is not installed or cannot be used (${problem}); add --theme <name> to pick another`); return 1; } const config = loadConfig(ctx); const { theme, mode, style } = config.values; const overrides = { template: video ? void 0 : opts.template ?? page.template, theme: opts.theme ?? page.theme, mode: opts.mode ?? page.mode, style: opts.style ?? page.style }; if (!video) await loadMath(patched); let result; try { if (video) { const voice = opts.voice ?? (page.voiced ? page.voice ?? config.values.voice : "off"); if (!validVoice(voice, fail)) return 2; result = await buildVideo(patched, voice, { ...opts, theme: overrides.theme, mode: overrides.mode, style: overrides.style, previousLanguage: page.lang }, config, ctx); } else { result = renderDoc(patched, overrides, { theme, mode, style }, { themes: ctx.themes, previousLanguage: page.lang, baseDir: cwd, codeDir: cwd, knownImages: readEmbeddedImages(html), knownCode: readEmbeddedCode(html) }); } } catch (e) { if (e instanceof TtsError) { fail(`\u2717 Voice-over failed: ${e.message}. Add --voice off for captions only`); return 1; } return reportError(e, fail); } emit(result, file, ctx, video ? " (an MP4 or WebM with the same name is not updated; run am video --mp4 or --webm again if you need it)" : ""); return finish(file, opts, config, ctx); } async function cmdVideo(src, opts, ctx) { const { fail } = ctx; const config = loadConfig(ctx); const voice = opts.voice ?? config.values.voice; if (!validVoice(voice, fail)) return 2; let result; try { result = await buildVideo(src, voice, opts, config, ctx); } catch (e) { if (!(e instanceof TtsError)) return reportError(e, fail); fail(`\u2717 Voice-over failed: ${e.message}. Add --voice off for captions only`); return 1; } const file = outputPath("videos", result.meta.title, opts, ctx); emit(result, file, ctx); for (const format of ["mp4", "webm"]) { if (opts[format] && !await exportVideo(file, result.wav, format, ctx)) return 1; } return finish(file, opts, config, ctx); } function validVoice(voice, fail) { if (VOICES.includes(voice)) return true; fail(`\u2717 Invalid voice value "${voice}". Choose one of: ${VOICES.join(" | ")}`); return false; } async function buildVideo(src, voice, opts, config, { fail, env, io, themes: themes2 }) { const provider = io.ttsProvider !== void 0 ? io.ttsProvider : pickProvider(voice, env); if (provider) ensureHome(amHome(env)); const result = await renderVideo(src, { provider, cacheDir: join9(amHome(env), "cache", "tts"), defaults: { style: config.values.style, theme: config.values.theme, mode: config.values.mode }, overrides: { style: opts.style, theme: opts.theme, mode: opts.mode }, previousLanguage: opts.previousLanguage, onProgress: (msg) => fail(` ${msg}`), themes: themes2 }); return { ...result, voiceName: voiceSummary(provider, result.captionsOnly) }; } function voiceSummary(provider, captionsOnly = []) { if (!provider) return "none (captions only)"; if (!captionsOnly.length) return provider.name; return `${provider.name} (no ${captionsOnly.map((c) => c.language).join(", ")} voice installed: captions only for those lines)`; } async function exportVideo(file, wav2, format, { print, fail, env }) { const out = `${file.replace(/\.html?$/i, "")}.${format}`; const started = Date.now(); try { if (format === "mp4") await exportMp4(file, out, { wav: wav2, env, onProgress: (i, n) => fail(` Exporting MP4: frame ${i}/${n}`) }); else await exportWebm(file, out, { env, onProgress: (i, n) => fail(` Exporting WebM: frame ${i}/${n}`) }); } catch (e) { if (!(e instanceof ExportError)) throw e; const hint = format === "mp4" && !hasCommand("ffmpeg") ? ". Or add --webm for a video file without ffmpeg" : ""; fail(`\u2717 Video export failed: ${e.message}${hint}. The player page was written and plays in a browser`); return false; } print(`\u2713 ${out} (exported in ${((Date.now() - started) / 1e3).toFixed(0)}s)`); return true; } function loadConfig({ fail, env, themes: themes2 }) { themeWarnings({ fail, themes: themes2 }); const config = readConfig(env, themes2); if (config.warning) fail(`! ${config.warning}`); return config; } function themeWarnings({ fail, themes: themes2 }) { themes2.warnings.forEach((w) => fail(`! ${w}`)); } function outputPath(dir, title, opts, { env, io }) { if (opts.out) return resolve4(io.cwd ?? process.cwd(), opts.out); return join9(amHome(env), dir, `${slug(title)}-${stamp(new Date(io.now?.() ?? Date.now()))}.html`); } function writePage(file, html, env) { const home = resolve4(amHome(env)); if (resolve4(file).startsWith(home + sep3)) ensureHome(home); mkdirSync3(dirname3(file), { recursive: true }); writeFileSync6(file, html); } function emit(result, file, { print, env }, note2 = "") { writePage(file, result.html, env); print(`\u2713 ${file}`); const link = serveLink(amHome(env), file); if (link) print(` link: ${link}`); print(` ${summaryLine(result)}${note2}`); if (result.stats.code?.length) print(` code embedded from: ${result.stats.code.join(", ")}`); const long = result.stats.codeWarnings ?? []; if (long.length) { print(` code ${count(long.length, "warning")} (trim the block and run again, or keep it if every line matters):`); long.forEach((w) => print(` L${w.line} [${w.rule}] ${w.message}`)); } printComponentWarnings(result.stats.componentWarnings, print); printHtmlWarnings(result.stats.htmlWarnings, print); printMathWarnings(result.stats.mathWarnings, print); printWarnings(result.warnings, print, result.meta.style); } function printComponentWarnings(notes = [], print) { if (!notes.length) return; print(` diagram ${count(notes.length, "warning")} (the page is written; fix the draft if that is not what you meant):`); notes.toSorted((a, b) => a.line - b.line).slice(0, MAX_LISTED_WARNINGS).forEach((w) => print(` L${w.line} [${w.component}] ${w.message}`)); if (notes.length > MAX_LISTED_WARNINGS) print(` \u2026 ${notes.length - MAX_LISTED_WARNINGS} more`); } function printHtmlWarnings(notes = [], print) { const lines = [...new Set(notes.toSorted((a, b) => a.line - b.line).map((w) => `L${w.line} [html] ${w.message}`))]; if (!lines.length) return; print(` html ${count(lines.length, "warning")} (the page differs from the draft here; fix the draft if that is not what you meant):`); lines.slice(0, MAX_LISTED_WARNINGS).forEach((l3) => print(` ${l3}`)); if (lines.length > MAX_LISTED_WARNINGS) print(` \u2026 ${lines.length - MAX_LISTED_WARNINGS} more`); } function printMathWarnings(notes = [], print) { if (!notes.length) return; print(` math ${count(notes.length, "warning")} (the page is written; fix the TeX and render again):`); notes.toSorted((a, b) => a.line - b.line).slice(0, MAX_LISTED_WARNINGS).forEach((w) => print(` L${w.line} [math] ${w.message}`)); if (notes.length > MAX_LISTED_WARNINGS) print(` \u2026 ${notes.length - MAX_LISTED_WARNINGS} more`); } function summaryLine(result) { const { meta, stats } = result; if (result.beats !== void 0) { return `video \xB7 ${meta.theme} \xB7 ${count(stats.panels, "scene")} \xB7 ${count(result.beats, "beat")} \xB7 ${result.duration.toFixed(1)}s \xB7 voice: ${result.voiceName}`; } const comps = Object.entries(stats.components).map(([k2, v]) => `${k2}\xD7${v}`).join(" "); return `${meta.template} \xB7 ${meta.theme} \xB7 ${count(stats.panels, "panel")}${comps ? ` \xB7 ${comps}` : ""}`; } var count = (n, noun) => `${n} ${noun}${n === 1 ? "" : "s"}`; function finish(file, opts, config, ctx) { printHints(config, ctx); if (shouldOpen(opts, ctx.env, config.values)) (ctx.io.open ?? openFile)(file); return 0; } function printHints(config, { env, io, print }) { try { const hints = afterRender({ home: amHome(env), env, config: config.values, current: VERSION, scriptPath: io.scriptPath, background: Boolean(io.background) }); hints.forEach((h2) => print(h2)); } catch { } } function cmdClean(opts, { print, fail, env }) { if (opts.days !== void 0 && !/^\d+$/.test(opts.days.trim())) { fail("\u2717 --days needs a non-negative integer"); return 2; } const days = opts.days === void 0 ? CLEAN.days : Number(opts.days); const home = amHome(env); const before = usage(home); const dry = Boolean(opts["dry-run"]); const r = clean2(home, { days, all: Boolean(opts.all), dryRun: dry }); const scope = opts.all ? "all pages and videos" : `pages and videos older than ${count(days, "day")}`; print(`Data directory: ${home} (${mb(before.total)} in total: ${count(before.pages.count, "page")}, ${count(before.videos.count, "video")}, ${mb(before.cache.bytes)} voice-over cache)`); print(dry ? `Would delete ${count(r.files, "file")}, freeing ${mb(r.bytes)} (${scope} + voice-over cache). Run without --dry-run to delete.` : `\u2713 Deleted ${count(r.files, "file")}, freeing ${mb(r.bytes)} (${scope} + voice-over cache). Settings were kept.`); return 0; } function cmdServe(opts, { print, fail, env }) { if (opts.port !== void 0 && !(/^\d+$/.test(opts.port.trim()) && Number(opts.port) <= 65535)) { fail("\u2717 --port needs a number from 0 to 65535 (0 picks a free port)"); return 2; } const port = opts.port === void 0 ? DEFAULT_PORT : Number(opts.port); return runServe({ home: amHome(env), port, lan: !!opts.lan, publicUrl: opts["public-url"], print, fail }); } function cmdLint(src, opts, { print, fail }) { let doc2; try { doc2 = parseDoc(src); } catch (e) { return reportError(e, fail); } const style = opts.style ?? doc2.meta.style; if (!CHOICES.style.includes(style)) { fail(`\u2717 Invalid style value "${style}". Choose one of: ${CHOICES.style.join(" | ")}`); return 2; } const language = resolveLanguage({ declared: doc2.meta.lang, text: src }); const warnings = style === "off" ? [] : lintDoc(doc2, language); printWarnings(warnings, print, style); return style === "strict" && warnings.length ? 1 : 0; } function printWarnings(warnings, print, style) { if (style === "off") return print(" STE check is off"); if (!warnings.length) return print(" STE \u2713 0 warnings"); print(` STE ${count(warnings.length, "warning")} (fix the draft and run again):`); warnings.slice(0, MAX_LISTED_WARNINGS).forEach((w) => print(` ${formatWarning(w)}`)); if (warnings.length > MAX_LISTED_WARNINGS) print(` \u2026 ${warnings.length - MAX_LISTED_WARNINGS} more; run am lint to see all`); } function reportError(e, fail) { if (e instanceof RenderError) { fail(`\u2717 L${e.line} [${e.component}] ${e.message}`); if (e.example) fail(` Correct example: ${e.example.replace(/^/gm, " ")}`); fail(` Full syntax: am help ${e.component}`); return 1; } if (e instanceof ParseError) { fail(`\u2717 ${e.line ? `L${e.line} ` : ""}Cannot parse the draft: ${e.message}`); return 1; } if (e instanceof LintError) { fail(`\u2717 ${e.message}; no page was written:`); e.warnings.forEach((w) => fail(` ${formatWarning(w)}`)); return 1; } throw e; } var showValue = (v) => typeof v === "boolean" ? v ? "on" : "off" : String(v); function cmdConfig(args, { print, fail, env, themes: themes2 }) { const [action, key, value] = args; try { if (action === "set") { if (key === void 0 || value === void 0) throw new ConfigError("Usage: am config set <key> <value>"); print(`\u2713 ${key} = ${showValue(setConfig(key, value, env, themes2))}`); return 0; } if (action === "get") { if (!CONFIG_KEYS[key]) throw new ConfigError(`No setting named "${key}". Available: ${Object.keys(CONFIG_KEYS).join(" | ")}`); print(showValue(readConfig(env, themes2).values[key])); return 0; } if (action === "reset") { resetConfig(key, env); print(key ? `\u2713 ${key} reset to default` : "\u2713 All settings reset to default"); return 0; } if (action !== void 0) throw new ConfigError(`Unknown action "${action}". Usage: am config [set <key> <value> | get <key> | reset [key]]`); } catch (e) { if (!(e instanceof ConfigError)) throw e; fail(`\u2717 ${e.message}`); return 2; } themeWarnings({ fail, themes: themes2 }); const { values, stored, warning, path } = readConfig(env, themes2); if (warning) fail(`! ${warning}`); print(`Config file: ${path}`); for (const [k2, spec] of Object.entries(CONFIG_KEYS)) { const mark = k2 in stored ? "*" : " "; const options = spec.type === "bool" ? "on | off" : configChoices(k2, themes2).join(" | "); print(`${mark} ${k2.padEnd(13)}${showValue(values[k2]).padEnd(10)}${spec.label} (${options})`); } if (env.AM_NO_OPEN && env.AM_NO_OPEN !== "0") print("Note: the AM_NO_OPEN environment variable is set and overrides the open setting."); print("* marks a value you changed. Change: am config set <key> <value>; reset to default: am config reset [key]"); return 0; } function cmdList({ print, fail, themes: themes2 }) { themeWarnings({ fail, themes: themes2 }); print("Templates (template):"); print(" sheet blueprint board: a grid of letter-numbered panels, for a one-screen overview (default)"); print(" doc linear explainer: one-column reading, with contents when there are 3+ panels"); print(" video explainer video: render with am video, see am help video"); print("\nThemes (theme):"); const note2 = (t) => t.user ? " (yours)" : t.scope.includes("page") ? "" : " (video only)"; for (const t of themes2.list("video")) print(` ${t.name.padEnd(10)}${t.summary}${note2(t)}`); print("\nComponents (fence language):"); for (const c of COMPONENTS.values()) print(` ${c.name.padEnd(10)}${c.summary}`); print(" html/svg embed as-is (escape hatch)"); print(" <other> code block; src=path lines=a-b quotes a file (am help code)"); print("\nSyntax: am help <component>; draft format: am help format"); } function specimenDraft(name, mode) { const sections = [...COMPONENTS.values()].map((c) => `## ${c.name} ${c.example}`); const table = "## table\n| Check | Status |\n|---|---|\n| Approved | ok passes |\n| Rejected | no fails |\n| Pending | warn needs a look |"; return `--- title: Theme ${name} (${mode}) lang: en --- ${[...sections, table].join("\n\n")} `; } function cmdTheme(action, target, opts, ctx) { const { print, fail, env, io } = ctx; if (action !== "check" || !target) { fail("\u2717 Usage: am theme check <name|file.json>"); return 2; } const isFile = /\.json$/i.test(target) || /[\\/]/.test(target); const path = isFile ? resolve4(io.cwd ?? process.cwd(), target) : join9(amHome(env), "themes", `${target}.json`); const name = isFile ? basename4(path).replace(/\.json$/i, "") : target; let theme; let errors = []; if (!isFile && getTheme(name)) { theme = getTheme(name); } else if (existsSync6(path)) { ({ theme, errors } = readThemeFile(path, themeNames("video"))); } else { fail(`\u2717 No theme named "${target}": ${path} does not exist`); return 2; } const tokens = theme?.tokens ?? theme?.video?.tokens; const colors = tokens ? checkColors({ tokens }) : { errors: [], warnings: [] }; const all = [...errors, ...colors.errors]; all.forEach((e) => print(`\u2717 ${e}`)); colors.warnings.forEach((w) => print(`! ${w}`)); print(`${name}: ${all.length} error${all.length === 1 ? "" : "s"}, ${colors.warnings.length} warning${colors.warnings.length === 1 ? "" : "s"}`); if (all.length) return 1; if (!theme.scope.includes("page")) return 0; const themes2 = isFile ? loadThemes(amHome(env), { extra: path }) : ctx.themes; const files = ["light", "dark"].map((mode) => { const result = renderDoc(specimenDraft(name, mode), { theme: name, mode, style: "off" }, {}, { themes: themes2 }); const file = outputPath("pages", `theme-${name}-${mode}`, {}, ctx); writePage(file, result.html, env); print(`\u2713 ${file}`); return file; }); const config = readConfig(env, themes2); if (shouldOpen(opts, env, config.values)) files.forEach((f2) => (io.open ?? openFile)(f2)); return 0; } function cmdHelp(name, { print, fail }) { if (!name) return print(USAGE), 0; if (name === "format") return print(FORMAT), 0; if (name === "image") return print(IMAGE_HELP), 0; if (name === "code") return print(CODE_HELP), 0; if (name === "video") return print(VIDEO_FORMAT), 0; if (name === "patch") return print(PATCH_HELP), 0; if (name === "theme") return print(THEME_HELP), 0; if (name === "html" || name === "svg") return print(RAW_HELP.replace(/LANG/g, name)), 0; const comp = COMPONENTS.get(name); if (!comp) { fail(`\u2717 No component named "${name}". Available: ${[...COMPONENTS.keys()].join(", ")}, html, svg, format, code, image, video, patch, theme`); return 2; } print(`${comp.name} \u2014 ${comp.summary} ${comp.syntax} Example: ${comp.example}`); return 0; } function slug(title) { const s = String(title || "page").trim().replace(/[^\p{L}\p{N}]+/gu, "-").replace(/^-+|-+$/g, "").slice(0, 40); return s || "page"; } function stamp(d) { const p = (n) => String(n).padStart(2, "0"); return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`; } function openFile(file) { const [cmd, args] = process.platform === "darwin" ? ["open", [file]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", file]] : ["xdg-open", [file]]; try { spawn4(cmd, args, { detached: true, stdio: "ignore" }).on("error", () => { }).unref(); } catch { } } // bin/am.js main(process.argv.slice(2), { background: true, scriptPath: process.argv[1] }).then( (code) => { process.exitCode = code; }, (err) => { process.stderr.write(`\u2717 Internal error: ${err.stack || err} `); process.exitCode = 1; } ); /*! Temml (https://github.com/ronkok/Temml), bundled for math in prose. The MIT License (MIT), Copyright (c) 2020 Ron Kok. * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject * to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * Temml carries two parts under the Apache License 2.0: its mhchem port (Copyright (c) 2011-2015 The MathJax Consortium, (c) 2015-2018 * Martin Hensel) and a hyphenate and escape pair adapted from Facebook's React. http://www.apache.org/licenses/LICENSE-2.0 */ /*! Bundled license information: @dagrejs/dagre/dist/dagre.esm.js: (*! For license information please see dagre.esm.js.LEGAL.txt *) */