domma-cms 1.7.1 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/CLAUDE.md +30 -0
  3. package/admin/dist/domma/domma-tools.css +3 -3
  4. package/admin/dist/domma/domma-tools.min.js +3 -3
  5. package/admin/js/app.js +2 -2
  6. package/admin/js/lib/editor-folding.js +6 -6
  7. package/admin/js/lib/simple-editor.js +8 -8
  8. package/admin/js/lib/smooth-caret.js +1 -0
  9. package/admin/js/lib/wordpress-import-arrange.js +1 -0
  10. package/admin/js/templates/docs/usage-tools.html +13 -1
  11. package/admin/js/templates/wordpress-import.html +223 -0
  12. package/admin/js/views/index.js +1 -1
  13. package/admin/js/views/page-editor.js +15 -8
  14. package/admin/js/views/wordpress-import.js +38 -0
  15. package/docs/screenshots/editor-caret-settings.png +0 -0
  16. package/package.json +2 -2
  17. package/plugins/_lib/admin/mail/font-controls.js +4 -0
  18. package/public/js/forms.js +2 -2
  19. package/public/js/site.js +1 -1
  20. package/public/js/smooth-caret.js +1 -0
  21. package/server/routes/api/wordpress-import.js +220 -0
  22. package/server/server.js +12 -0
  23. package/server/services/hooks.js +1 -1
  24. package/server/services/permissionRegistry.js +10 -0
  25. package/server/services/roles.js +1 -1
  26. package/server/services/tools.js +2 -1
  27. package/server/services/wpImport/convert.js +491 -0
  28. package/server/services/wpImport/fetch.js +264 -0
  29. package/server/services/wpImport/index.js +832 -0
  30. package/server/services/wpImport/plan.js +549 -0
  31. package/server/services/wpImport/store.js +242 -0
  32. package/server/services/wpImport/wxr.js +167 -0
  33. package/server/services/wpImport/xml.js +261 -0
  34. package/server/services/wpImport/zip.js +142 -0
  35. package/server/templates/page.html +2 -2
package/public/js/site.js CHANGED
@@ -1 +1 @@
1
- import{decodeBase64Json as P}from"/public/js/utf8-base64.mjs";import{makeBadgeEl as J,applyColour as X,applyPill as oe,applyFloat as ne,applyHighlight as R,applyItemHighlight as ae,itemHighlightAttrs as se,isCurrentItem as U,menuAlign as ie,applyClasses as O,cssClasses as W,applyEntrance as V}from"/public/js/menu-decor.mjs?v=20261014-menu-entrance";import{initFloatingMenus as re}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as G,markInvalidFields as K,showValidationPopup as Q}from"/public/js/forms.js?v=20261009-form-classes";import{buildWizardSteps as ce}from"/public/js/form-steps.mjs";import{applyFormClasses as Z,applySubmitButtons as le,applyWizardButtons as de}from"/public/js/form-submit.mjs";document.addEventListener("click",h=>{const c=h.target?.closest?.(".dm-banner__dismiss");c&&(h.preventDefault(),c.closest(".dm-banner")?.remove())}),$(()=>{const h=window.__CMS_NAV__||{},c=window.__CMS_SITE__||{};if(c.autoTheme?.enabled){let o=function(e){const a=(e||"07:00").split(":");return+a[0]*60+(+a[1]||0)},s=function(){const e=new Date,a=e.getHours()*60+e.getMinutes();return a>=o(n.dayStart)&&a<o(n.nightStart)?n.dayTheme:n.nightTheme},t=function(e){const a=n.apply&&n.apply[e]||{theme:e,custom:""};Domma.theme.set(a.theme);for(const d of[...document.body.classList])d.startsWith("dm-custom-")&&document.body.classList.remove(d);a.custom&&document.body.classList.add(a.custom)};const n=c.autoTheme;t(s()),setInterval(()=>t(s()),6e4)}const x=$("#site-navbar");if(x.length&&h.brand){let s=function(u,p=0){return Array.isArray(u)?u.filter(m=>!(m&&(m.type==="spacer"||m.type==="theme-switcher"))).filter(m=>!(m&&m.type==="separator"&&p===0)).map(m=>m&&m.type==="separator"?{divider:!0}:Array.isArray(m?.items)?{...m,items:s(m.items,p+1)}:m):u};const n={...h.brand},o=n.size&&n.size!=="md"?` navbar-brand-${n.size}`:"";if(n.logo||n.icon||n.tagline){let u="";n.logo?u+=`<img src="${n.logo}" class="navbar-brand-logo" alt="${n.text||""}">`:n.icon&&(u+=`<span data-icon="${n.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),n.text&&(u+=`<span class="navbar-brand-text${o}">${n.text}</span>`),n.tagline&&(u+=`<small class="navbar-brand-tagline">${n.tagline}</small>`),n.html=u}else o&&n.text&&(n.html=`<span class="navbar-brand-text${o}">${n.text}</span>`);const t=s(h.items||[]),e=h.position==="floating",a=h.orientation==="vertical";Domma.elements.navbar("#site-navbar",{brand:n,items:t,variant:h.variant||"dark",position:e?"static":h.position||"sticky",collapsible:!0,appearOnHover:!!h.appearOnHover}),Domma.icons.scan("#site-navbar");const d=x.get(0),f=()=>{if(!a)return;const p=d.querySelector('a[aria-current="page"]')?.closest(".navbar-dropdown");p&&!p.classList.contains("open")&&p.querySelector(":scope > .navbar-dropdown-toggle")?.click()};d.classList.add(`dm-nav-align-${ie(h)||"centre"}`),a&&x.get(0).classList.add("dm-nav-vertical",h.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left");const i=h.dock||{};a&&!e&&me(d,h.side==="right"),!a&&i.hideOnScroll&&ue(d),R(d,h),V(d,h),O(d,h.class),e&&(ne(x.get(0),h),re(x.get(0).parentNode||document));const l=d.querySelector(".navbar-toggle"),g=d.querySelector(".navbar-collapse");if(l&&g){let u=null;const p=()=>{g.classList.contains("show")&&l.click()},m=()=>{u&&(u.remove(),u=null),document.body.classList.remove("dm-drawer-open")},v=()=>{u||(u=document.createElement("div"),u.className="dm-navbar-backdrop",u.addEventListener("click",p),document.body.appendChild(u),document.body.classList.add("dm-drawer-open"))};l.addEventListener("click",()=>{requestAnimationFrame(()=>{g.classList.contains("show")?(f(),v()):m()})}),g.addEventListener("click",C=>{C.target.closest("a")&&requestAnimationFrame(m)}),document.addEventListener("keydown",C=>{C.key==="Escape"&&g.classList.contains("show")&&p()})}const b=d.querySelector(".navbar-nav");if(b){const u=Array.from(b.children);let p=0;for(const m of h.items||[])if(!(m&&m.type==="separator")){if(m&&m.type==="spacer"){const v=document.createElement("li"),C=["xs","sm","md","lg","xl","flex"].includes(m.size)?m.size:"md";v.className=`dm-menu-spacer dm-menu-spacer--${C}`,v.setAttribute("aria-hidden","true"),b.insertBefore(v,u[p]||null);continue}if(m&&m.type==="theme-switcher"){const v=document.createElement("li");v.className="dm-menu-theme-switcher";const C=document.createElement("span");C.dataset.themeSwitcher="menu",C.dataset.position="inline",v.appendChild(C),b.insertBefore(v,u[p]||null);continue}p++}window.CMSThemeSwitcher?.scan(b)}d.querySelectorAll("[data-path]").forEach(u=>{const p=u.dataset.path.split(".").map(Number),m=p.length>1,v=p.reduce((A,q)=>A?.items?.[q],{items:t});if(!v)return;v.colour&&X(u,v.colour),v.pill&&!m&&oe(u,v.pill),ae(u,v),O(u.closest("li"),v.class);const C=J(v.badge);C&&u.appendChild(C),U(v.url,location.pathname)&&(u.classList.add("active"),u.setAttribute("aria-current","page"))}),f()}const y=$("#site-footer");if(y.length){let e=function(r){return String(r??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")},a=function(r){return e(r).replace(/'/g,"&#39;")},d=function(r){if(r&&r.type==="separator")return'<li class="footer-sep"><hr></li>';if(r&&r.type==="spacer")return`<li class="dm-menu-spacer dm-menu-spacer--${["xs","sm","md","lg","xl","flex"].includes(r.size)?r.size:"md"}" aria-hidden="true"></li>`;if(r&&r.type==="theme-switcher")return'<li class="dm-menu-theme-switcher"><span data-theme-switcher="menu" data-position="inline"></span></li>';const M=a(r.url||"#"),L=e(r.text||""),D=Array.isArray(r.items)&&r.items.length?`<ul>${r.items.map(d).join("")}</ul>`:"",N=se(r),ee=[N.hover&&` data-menu-item-hover="${a(N.hover)}"`,N.active&&` data-menu-item-active="${a(N.active)}"`].filter(Boolean).join(""),te=N.accent?` style="--dm-menu-highlight:${a(N.accent)}"`:"",Y=W(r.class).join(" ");return`<li${Y?` class="${a(Y)}"`:""}><a href="${M}"${ee}${te}>${L}</a>${D}</li>`},f=function(){if(!c.footer)return"";const r=Object.keys(o).filter(L=>n[L]);if(!r.length)return"";const M=W(c.footer.socialClass).join(" ");return`<div class="footer-social">${r.map(L=>{const{label:D,svg:N}=o[L];return`<a href="${n[L]}" target="_blank" rel="noopener noreferrer" aria-label="${D}" class="footer-social-link${M?` ${a(M)}`:""}">${N}</a>`}).join("")}</div>`},b=function(r){if(!r||r.type==="separator"||r.type==="spacer")return"";if(r.type==="theme-switcher")return`<li class="cms-footer-col">${d(r).replace(/^<li[^>]*>|<\/li>$/g,"")}</li>`;const M=Array.isArray(r.items)&&r.items.length?`<ul>${r.items.map(d).join("")}</ul>`:"",L=g(r)?`<span class="cms-footer-col-title">${e(r.text||"")}</span>`:`<a class="cms-footer-col-title" href="${a(r.url||"#")}">${e(r.text||"")}</a>`,D=["cms-footer-col",...W(r.class)].join(" ");return`<li class="${a(D)}">${L}${M}</li>`},p=function(r,M,L=!1){return(r||[]).forEach(D=>{D&&(D.type==="separator"||D.type==="spacer"||D.type==="theme-switcher")||(L&&g(D)||M.push(D),Array.isArray(D.items)&&D.items.length&&p(D.items,M))}),M};const n=c.social||{},o={twitter:{label:"X / Twitter",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.742l7.73-8.835L1.254 2.25H8.08l4.259 5.629L18.244 2.25zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>'},facebook:{label:"Facebook",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>'},instagram:{label:"Instagram",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"/></svg>'},linkedin:{label:"LinkedIn",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>'},github:{label:"GitHub",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>'},youtube:{label:"YouTube",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.495 6.205a3.007 3.007 0 00-2.088-2.088c-1.87-.501-9.396-.501-9.396-.501s-7.507-.01-9.396.501A3.007 3.007 0 00.527 6.205a31.247 31.247 0 00-.522 5.805 31.247 31.247 0 00.522 5.783 3.007 3.007 0 002.088 2.088c1.868.502 9.396.502 9.396.502s7.506 0 9.396-.502a3.007 3.007 0 002.088-2.088 31.247 31.247 0 00.5-5.783 31.247 31.247 0 00-.5-5.805zM9.609 15.601V8.408l6.264 3.602z"/></svg>'}};let s='<div class="footer-inner container">';const t=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""},i=["columns","row"].includes(c.footer?.layout)?c.footer.layout:"auto",l=i==="columns"||i==="auto"&&t.primary.some(r=>Array.isArray(r?.items)&&r.items.length),g=r=>l&&!r.url&&Array.isArray(r.items)&&r.items.length>0;if(l){const r=h.brand||{},M=r.text||c.title||"",L=r.logo?`<img src="${a(r.logo)}" class="cms-footer-brand-logo" alt="">`:r.icon?'<span class="cms-footer-brand-icon"></span>':"",D=String(c.footer?.blurb||"").trim();s='<div class="footer-inner footer-inner--columns container"><div class="cms-footer-top">',s+=`<div class="cms-footer-brand">${M||L?`<a class="cms-footer-brand-name" href="${a(r.url||"/")}">${L}<span>${e(M)}</span></a>`:""}${D?`<p class="cms-footer-blurb">${e(D)}</p>`:""}${f()}</div>`,t.primary.length&&(s+=`<nav class="footer-nav footer-nav--primary footer-nav--columns" aria-label="Footer"><ul>${t.primary.map(b).join("")}</ul></nav>`),s+='</div><div class="cms-footer-bottom">',t.copyright&&(s+=`<p class="cms-footer-copyright">${e(t.copyright)}</p>`),t.legal.length&&(s+=`<nav class="footer-nav footer-nav--legal" aria-label="Legal"><ul>${t.legal.map(d).join("")}</ul></nav>`),s+="</div>"}else t.copyright&&(s+=`<p>${e(t.copyright)}</p>`),t.primary.length&&(s+=`<nav class="footer-nav footer-nav--primary"><ul>${t.primary.map(d).join("")}</ul></nav>`),t.legal.length&&(s+=`<nav class="footer-nav footer-nav--legal"><ul>${t.legal.map(d).join("")}</ul></nav>`),s+=f();s+="</div>",y.html(s),window.CMSThemeSwitcher?.scan(y.get(0));const u=y.get(0).querySelector(".cms-footer-brand-icon");u&&h.brand?.icon&&u.setAttribute("data-icon",h.brand.icon),O(y.get(0),c.footer?.class);const m=[...p(t.primary,[],!0),...p(t.legal,[])];y.get(0).querySelectorAll(".footer-nav a").forEach((r,M)=>{const L=m[M];if(!L)return;L.colour&&X(r,L.colour);const D=J(L.badge);D&&r.appendChild(D),U(L.url,location.pathname)&&(r.classList.add("is-active"),r.setAttribute("aria-current","page"))}),R(y.get(0).querySelector(".footer-nav--primary"),{highlight:t.primaryHighlight}),R(y.get(0).querySelector(".footer-nav--legal"),{highlight:t.legalHighlight}),V(y.get(0).querySelector(".footer-nav--primary"),{entrance:t.primaryEntrance}),V(y.get(0).querySelector(".footer-nav--legal"),{entrance:t.legalEntrance}),O(y.get(0).querySelector(".footer-nav--primary"),t.primaryClass),O(y.get(0).querySelector(".footer-nav--legal"),t.legalClass);const C=c.footer?.showMotionToggle!==!1,A=S.get("reduced_motion"),q=c.footer?.motionDefault,z=A!=null?!!A:q==="on"?!0:q==="off"?!1:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),B=c.footer?.motionControl==="checkbox",I=y.get(0).querySelector(".cms-footer-bottom")||y.get(0).querySelector(".footer-inner");if(C&&I){const r=document.createElement("input");r.type="checkbox",r.className=B?"footer-motion-checkbox":"form-switch-input",r.id="dm-motion-switch",r.checked=z,r.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const M=document.createElement("span");M.className=B?"footer-motion-label":"form-switch-label",M.textContent="Reduce motion";const L=document.createElement("label");L.className=B?"footer-motion-check":"form-switch footer-motion-switch",L.title="Reduce motion",L.appendChild(r),L.appendChild(M),I.appendChild(L)}}const k=document.getElementById("site-sidebar");k&&pe(k),Domma.icons.scan(),Domma.syntax?.scan&&Domma.syntax.scan();const w=$(".page-body");if(w.length){w.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),w.find(".tabs").each(function(){Domma.elements.tabs(this)}),w.find(".carousel").each(function(){const n={primary:"--dm-primary",secondary:"--dm-secondary",accent:"--dm-primary",text:"--dm-text",surface:"--dm-surface",background:"--dm-background",success:"--dm-success",warning:"--dm-warning",danger:"--dm-danger",info:"--dm-info"},o=f=>{const i=String(f||"").trim();return i?n[i.toLowerCase()]?`var(${n[i.toLowerCase()]})`:CSS.supports("color",i)?i:"":""},s=o(this.dataset.background),t=o(this.dataset.arrowBackground),e=o(this.dataset.arrowColor);s&&(this.style.setProperty("--dm-carousel-background",s),this.classList.add("dm-carousel-custom-background")),t&&this.style.setProperty("--dm-carousel-arrow-background",t),e&&this.style.setProperty("--dm-carousel-arrow-color",e);const a=["top","middle","bottom"].includes(this.dataset.arrowPosition)?this.dataset.arrowPosition:"middle";this.classList.add(`dm-carousel-arrows-${a}`);const d={autoplay:this.dataset.autoplay==="true",interval:parseInt(this.dataset.interval,10)||5e3,loop:this.dataset.loop!=="false",animation:this.dataset.animation||"slide",heightMode:this.dataset.heightMode==="average"?"average":"crop"};if(this.dataset.height){const f=Number(this.dataset.height);Number.isFinite(f)&&f>0&&(d.height=f)}if(this.dataset.animationDuration){const f=parseInt(this.dataset.animationDuration,10);Number.isFinite(f)&&(d.animationDuration=f)}this.dataset.animationEasing&&(d.animationEasing=this.dataset.animationEasing),this.dataset.ctx&&(this._dmCtxSlides=Array.from(this.querySelectorAll(".carousel-slide")).map(f=>f.cloneNode(!0)),this._dmCarouselOpts=d),this._dmCarousel=Domma.elements.carousel(this,d)}),w.find(".dm-countdown").each(function(){const n={autoStart:!0},o=String(this.dataset.format||"").trim(),s=String(this.dataset.to||"").trim(),t=/^(\d{4})-(\d{2})-(\d{2})$/.exec(s),e=t?new Date(+t[1],+t[2]-1,+t[3]).getTime():s?new Date(s).getTime():NaN;Number.isFinite(e)?n.duration=Math.max(0,e-Date.now()):this.dataset.duration&&(n.duration=(parseInt(this.dataset.duration,10)||0)*1e3);const a=/^dd:/i.test(o);n.format=a?"hh:mm:ss":o||(n.duration>=36e5?"hh:mm:ss":"mm:ss");const d=this,f=l=>String(l).padStart(2,"0"),i=l=>{const g=Math.max(0,Math.floor(l/1e3)),b=d.querySelector(".dm-timer-time");b&&(b.textContent=`${f(Math.floor(g/86400))}:${f(Math.floor(g%86400/3600))}:${f(Math.floor(g%3600/60))}:${f(g%60)}`)};a&&(n.onTick=i),Domma.elements.timer(this,n),a&&i(n.duration||0)}),w.find("[data-tooltip]").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),w.find(".dm-progression").each(function(){const n=Array.from(this.querySelectorAll(".dm-progression-item")).map(t=>{const e={title:t.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:t.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return t.dataset.date&&(e.date=t.dataset.date),t.dataset.status&&(e.status=t.dataset.status),t.dataset.eventIcon&&(e.icon=t.dataset.eventIcon),t.dataset.entryId&&(e._entryId=t.dataset.entryId),e}),o=this.dataset.layout==="centred"?"centered":this.dataset.layout||"vertical",s=Domma.elements.progression(this,{layout:o,theme:this.dataset.theme||"minimal",mode:this.dataset.mode||"timeline",statusIcons:!0,allowHtml:!0,...n.length?{items:n}:{}});if((this.dataset.mode||"timeline")==="timeline"){const t={completed:{label:"Completed",icon:"check-circle",tone:"success"},planned:{label:"Planned",icon:"circle",tone:"secondary"},"in-progress":{label:"In progress",icon:"clock",tone:"primary"},blocked:{label:"Blocked",icon:"alert-circle",tone:"danger"}};this.querySelectorAll(".dm-progression-item").forEach((e,a)=>{const d=n[a];if(!d)return;const f=e.querySelector(".dm-progression-title");if(d.icon&&f){const l=document.createElement("span");l.dataset.icon=d.icon,l.style.cssText="display:inline-flex;vertical-align:middle;margin-right:.4em",l.setAttribute("aria-hidden","true"),f.prepend(l)}const i=t[d.status];if(i&&f){const l=document.createElement("span");l.className=`badge badge-${i.tone} cms-timeline-status`,l.style.cssText="display:inline-flex;align-items:center;gap:.35em;margin:.4rem 0";const g=document.createElement("span");g.dataset.icon=i.icon,g.dataset.iconSize="14",g.setAttribute("aria-hidden","true"),l.append(g,document.createTextNode(i.label)),f.after(l)}})}Domma.icons.scan(this),n.length&&(this._dmCtx={instance:s,items:n})});try{Domma.effects.reveal(".page-body .hero",{animation:"slide-up",duration:480,threshold:.06,stagger:60,once:!1})}catch{}document.querySelectorAll(".page-body .row[data-reveal]").forEach(n=>{const o=n.dataset.revealMode||"stagger",s=n.dataset.revealAnimation||"slide-up",t=parseInt(n.dataset.revealDuration,10)||400,e=parseInt(n.dataset.revealStagger,10)||60,a=parseInt(n.dataset.revealDelay,10)||0,d=n.dataset.revealDirection==="rtl",f=Array.from(n.children),i={"slide-up":"translateY(30px)","slide-down":"translateY(-30px)","slide-left":"translateX(30px)","slide-right":"translateX(-30px)",zoom:"scale(0.85)",flip:"perspective(600px) rotateX(15deg)"};f.forEach((l,g)=>{l.style.opacity="0",l.style.transform=i[s]||"",l.style.transition=`opacity ${t}ms ease, transform ${t}ms ease`;const b=d?f.length-1-g:g;l.style.transitionDelay=o==="stagger"?`${a+b*e}ms`:`${a}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const l=new IntersectionObserver(g=>{g.forEach(b=>{b.isIntersecting&&(b.target.offsetWidth,b.target.style.opacity="1",b.target.style.transform="none",l.unobserve(b.target))})},{threshold:.1});f.forEach(g=>l.observe(g))}))}),w.find(".card[data-collapsible]").each(function(){const n=this.querySelector(".card-header");n&&n.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),w.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const n=this.dataset.soTarget,o=document.getElementById(n);if(!o)return;const s=E.slideover({title:o.dataset.soTitle||"",size:o.dataset.soSize||"md",position:o.dataset.soPosition||"right"});o.style.display="",s.setContent(o),s.open()})})}if(typeof $.setup=="function"){const n=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(o=>{try{const s=P(o.dataset.config);Object.assign(n,s)}catch{}}),Object.keys(n).length>0){const o={};for(const[s,t]of Object.entries(n)){const e=t?.events?.click,{confirm:a,toast:d,alert:f,prompt:i,...l}=e||{};a||d||f||i?($(s).on("click",async function(b){if(b.preventDefault(),a&&!await E.confirm(a))return;let u=null;if(!(i&&(u=await E.prompt(i,{inputPlaceholder:l.promptPlaceholder||"",inputValue:l.promptDefault||""}),u===null))){if(l.target){const p=$(l.target);l.toggleClass&&p.toggleClass(l.toggleClass),l.addClass&&p.addClass(l.addClass),l.removeClass&&p.removeClass(l.removeClass),u!==null&&(l.setText&&p.text(u),l.setVal&&p.val(u),l.setAttr&&p.attr(l.setAttr,u))}l.href&&(window.location.href=l.href),d&&E.toast(d,{type:l.toastType||"success"}),f&&E.alert(f)}}),Object.keys(l).length&&(o[s]={...t,events:{...t.events,click:l}})):o[s]=t}$.setup(o)}}w.length&&j(w.get(0))}),window.wireCTAButtons=j;function j(h){h.querySelectorAll(".dm-cta-trigger").forEach(c=>{c.__ctaWired||(c.__ctaWired=!0,c.addEventListener("click",async()=>{const x=c.dataset.action,y=c.dataset.entry,k=c.dataset.confirm;let w=S.get("auth_token");if(!w){E.toast("Please log in to perform this action.",{type:"warning"});return}if(k&&!await E.confirm(k))return;const n=Array.from(c.childNodes).map(s=>s.cloneNode(!0));c.disabled=!0,c.textContent="Running\u2026";const o=s=>fetch(`/api/actions/${x}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify(c.dataset.scope?{entryId:y,scope:c.dataset.scope}:{entryId:y})});try{let s=await o(w);if(s.status===401){const e=S.get("auth_refresh_token");if(e){const a=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(a.ok){const{token:d}=await a.json();S.set("auth_token",d),w=d,s=await o(w)}}}const t=await s.json().catch(()=>({}));if(!s.ok||t.success===!1)throw new Error(t.error||t.message||`Error ${s.status}`);E.toast(t.message||"Action completed.",{type:"success"})}catch(s){E.toast(s.message||"Action failed.",{type:"error"})}finally{c.disabled=!1,c.textContent="",n.forEach(s=>c.appendChild(s)),Domma.icons.scan(c)}}))})}(function(){const c=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!c.length)return;const x=S.get("auth_token");function y(w,n){const o=document.createElement("div");o.className="dm-collection-display dm-collection-empty";const s=document.createElement("p");if(n){s.appendChild(document.createTextNode("Please "));const t=document.createElement("a");t.href="/login",t.textContent="sign in",s.appendChild(t),s.appendChild(document.createTextNode(" to view your entries."))}else s.textContent=w;return o.appendChild(s),o}function k(w,n){const o=document.createElement("template");o.innerHTML=n||"",w.replaceWith(o.content)}c.forEach(async w=>{const n=w.dataset.collectionAttrs;if(!n)return;if(!x){w.replaceChildren(y("",!0));return}const o=s=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify({attrs:n})});try{let s=x,t=await o(s);if(t.status===401){const a=S.get("auth_refresh_token");if(a){const d=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:a})});if(d.ok){const{token:f}=await d.json();S.set("auth_token",f),s=f,t=await o(s)}}}if(!t.ok){const{error:a}=await t.json().catch(()=>({}));w.replaceChildren(y(a||"Unable to load entries.",!1));return}const{html:e}=await t.json();k(w,e),window.DommaCollectionBrowser?.hydrate&&window.DommaCollectionBrowser.hydrate(document)}catch{w.replaceChildren(y("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),typeof Domma<"u"&&Domma.syntax?.scan&&Domma.syntax.scan(),j(document.body)},100)})(),(function(){const c=document.querySelectorAll("[data-collection-table]");!c.length||typeof T>"u"||!T.create||c.forEach(x=>{let y;try{y=P(x.dataset.payload)}catch{return}const{columns:k,rows:w,search:n,sortable:o,exportable:s,pageSize:t,empty:e,ctaConfig:a,slug:d,exportCfg:f}=y;if(!k?.length)return;if(a){const g=k.findIndex(b=>b.key==="_cta");g!==-1&&(k[g]={key:"_cta",title:"",render:(b,u)=>{const p=document.createElement("button");if(p.className=`btn btn-${a.style||"primary"} dm-cta-trigger`,p.dataset.action=a.action||"",a.scope&&(p.dataset.scope=a.scope),p.dataset.entry=u._entryId||"",a.confirm&&(p.dataset.confirm=a.confirm),a.icon){const m=document.createElement("span");m.dataset.icon=a.icon,p.appendChild(m),p.appendChild(document.createTextNode(" "))}return p.appendChild(document.createTextNode(a.label||"Run")),p}})}const i="col-table-"+Math.random().toString(36).slice(2,7),l=document.createElement("div");l.id=i,x.replaceChildren(l),T.create("#"+i,{data:w,columns:k,search:n,sortable:o,exportable:s,pageSize:t,emptyMessage:e}),a&&j(l),x._dmTable={columns:k,rows:w,slug:d,exportCfg:f,render(g){T.create("#"+i,{data:g,columns:k,search:n,sortable:o,exportable:s,pageSize:t,emptyMessage:e}),a&&j(l)}}})})(),(function(){const c=document.querySelectorAll("[data-form-inline]");if(!c.length||typeof F>"u")return;function x(o){const s=o&&o.logic;return!!(s&&(s.visibility||s.requirement))}function y(o,s){const t={};return(o||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const a=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),d=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":a?"chooser":e.type,f={...e.formConfig||{}};f.span==="full"&&s&&(f.span=s);const i={type:d,required:x(e)?!1:e.required,label:e.label,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{hint:e.helper},...e.tooltip&&{tooltip:e.tooltip},...f}};e.minLength!==void 0&&(i.minLength=e.minLength),e.maxLength!==void 0&&(i.maxLength=e.maxLength),e.min!==void 0&&(i.min=e.min),e.max!==void 0&&(i.max=e.max),a&&(e.variant&&(i.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(i.multiple=!0),e.density&&(i.density=e.density),e.columns&&(i.columns=e.columns),e.accent&&(i.accent=e.accent),e.accentStyle&&(i.accentStyle=e.accentStyle),e.glow&&(i.glow=!0),e.glowColour&&(i.glowColour=e.glowColour),e.shadow&&(i.shadow=e.shadow),e.shadowColour&&(i.shadowColour=e.shadowColour)),t[e.name]=i}),t}function k(o){const s={};return(o||[]).forEach(t=>{if(!(!t.name||t.type==="page-break"||t.type==="spacer")&&(t.type==="select"||t.type==="multiselect")&&t.required){const e=(t.options||[])[0];e&&(s[t.name]=typeof e=="object"?e.value:e)}}),s}function w(o,s){(s||[]).forEach(t=>{if(t.type!=="date"||!t.name)return;const e=o.querySelector(`[name="${t.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function n(o,s,t){let e=o.querySelector(".cms-form-message");e||(e=document.createElement("p"),e.className="cms-form-message",o.appendChild(e)),e.textContent=s,e.style.cssText=t?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}c.forEach(o=>{let s;try{s=P(o.dataset.formInline)}catch{return}const t=s.fields||[],e=s.settings||{},a=Number(e.columns)===3?3:2,d=e.layout||"grid",f=t.some(p=>p.type==="page-break"),i=async p=>{if(o.dataset.triggerBlocked==="true"){const m=o._formLogicRuntime,v=m&&m.submitBlock?m.submitBlock().reasons:[];n(o,v.join(" ")||"This form cannot be submitted yet.",!0);const C=new Error("Blocked by a form trigger");throw C.formSubmitFailed=!0,C}try{const m=o.querySelector(".fb-form-honeypot"),v=m?.querySelector('input[type="text"]')||null,C=m?.querySelector('[name="_t"]')||null,A=Object.assign({},p);delete A._t,m?.dataset.hpName&&delete A[m.dataset.hpName],v!==null&&(A._hp=v.value),C!==null&&(A._t=C.value);const q=await H.post(`/api/forms/submit/${s.slug}`,A);if(q&&q.error){n(o,q.error,!0);const z=new Error(q.error);throw z.formSubmitFailed=!0,z}if(q?.redirect){window.location.href=q.redirect;return}for(;o.firstChild;)o.removeChild(o.firstChild);n(o,q?.message||e.successMessage||"Thank you for your submission.",!1)}catch(m){throw m.formSubmitFailed||n(o,m.message||"Submission failed. Please try again.",!0),m}},l=Date.now();function g(p){const m=p.querySelector("form");if(!m||m.querySelector(".fb-form-honeypot"))return;const v=document.createElement("div");v.className="fb-form-honeypot",v.setAttribute("aria-hidden","true");const C=document.createElement("input");C.name=m.querySelector('[name="website"]')?"_website":"website",v.dataset.hpName=C.name,C.type="text",C.tabIndex=-1,C.autocomplete="url",C.placeholder="https://",v.appendChild(C);const A=document.createElement("input");A.name="_t",A.type="hidden",A.value=l,v.appendChild(A),m.appendChild(v)}function b(p){if(!window.FormLogicEngine||!t.some(v=>v.logic||Array.isArray(v.triggers)&&v.triggers.length))return null;const m=new window.FormLogicEngine.FormLogicRuntime(s,o,p?{baseValues:p}:void 0);if(m.init(),o._formLogicRuntime=m,o._formLogicObserved)return m;if(o._formLogicObserved=!0,o.parentNode&&typeof MutationObserver<"u"){const v=new MutationObserver(function(C){for(const A of C)for(const q of A.removedNodes)if(q===o||q.nodeType===1&&q.contains&&q.contains(o)){o._formLogicRuntime&&o._formLogicRuntime.destroy(),v.disconnect();return}});v.observe(o.parentNode,{childList:!0,subtree:!1}),o._formLogicObserver=v}return m}let u=!1;if(o.addEventListener("dm-form-end",p=>{const m=p.detail?.validation;if(m&&!m.valid){K(o,m.missing),Q(m);return}u||(u=!0,i(p.detail?.values||{}).catch(()=>{}).finally(()=>{u=!1}))}),f&&F.wizard){const p=ce(t,s.title),m=p.map(I=>({title:I.title,description:I.description,fields:y(I.fields,a)})),v=p.map(I=>I.fields);let C=0,A=null,q=null;const z=I=>{const r=window.FormLogicEngine;if(!r||!r.validateFields)return;const M=r.validateFields(o,v[C]||[]);M.valid||(I.preventDefault(),I.stopImmediatePropagation(),K(o,M.missing),Q(M))},B=()=>{const I=v[C]||t;de(o,e),Z(o,e,I),w(o,I),G(o,I),e.honeypot!==!1&&g(o),A&&A.destroy(),A=b(q?.getStepData?q.getStepData():void 0),o.querySelectorAll(".wizard-next, .wizard-submit").forEach(r=>r.addEventListener("click",z,!0))};q=F.wizard(o,{schema:{steps:m},layout:d,columns:a,finishText:e.submitText||"Finish",onSubmit:i,onStepChange:I=>{C=I,setTimeout(B,0)}}),o.addEventListener("dm-form-trigger",I=>{if(I.detail?.type!=="goto-step"||!q?.goToStep)return;const r=Number(I.detail.action.step);!Number.isFinite(r)||r<0||r>=m.length||r!==C&&q.goToStep(r)}),Promise.resolve(q).then(B)}else if(F.render){const p=F.render(o,y(t,a),k(t),{submitText:e.submitText||"Submit",layout:d,columns:a,onSubmit:i});Promise.resolve(p).then(function(){le(o,e,d),Z(o,e,t),w(o,t),G(o,t),e.honeypot!==!1&&g(o),b()})}})})();function me(h,c){const x=document.body,y=x.classList,k=new Set((x.dataset.railSite||"").split(" ").filter(Boolean)),w=()=>window.matchMedia("(min-width: 993px)").matches;h.querySelectorAll(".navbar-link, .navbar-dropdown-toggle").forEach(a=>{const d=a.textContent.trim();d&&(a.setAttribute("title",d),a.querySelector("svg, [data-icon]")||(a.dataset.initial=d.charAt(0).toUpperCase()))});const n=document.createElement("button");n.type="button",n.className="dm-nav-collapse-toggle";const o=()=>{const a=y.contains("dm-vnav-collapsed");n.setAttribute("aria-pressed",String(a)),n.setAttribute("aria-label",a?"Expand menu":"Collapse menu"),n.title=n.getAttribute("aria-label");const d=c?"chevrons-left":"chevrons-right",f=c?"chevrons-right":"chevrons-left";n.innerHTML=`<span data-icon="${a?d:f}"></span>`,window.Domma&&Domma.icons&&Domma.icons.scan(n)},s=a=>{y.toggle("dm-vnav-collapsed",a),S.set("nav_collapsed",a),o()};n.addEventListener("click",()=>s(!y.contains("dm-vnav-collapsed"))),o();const t=h.querySelector(".navbar-container")||h;t.insertBefore(n,t.firstChild?.nextSibling||null);const e=a=>{y.toggle("dm-vnav-autohide",a),y.toggle("dm-vnav-collapsible",!a&&k.has("collapsible")),a?y.remove("dm-vnav-collapsed"):k.has("collapsible")&&S.get("nav_collapsed")===!0&&y.add("dm-vnav-collapsed"),h.classList.remove("dm-nav-revealed"),o()};h.addEventListener("pointerdown",a=>{y.contains("dm-vnav-autohide")&&a.pointerType!=="mouse"&&!h.classList.contains("dm-nav-revealed")&&(h.classList.add("dm-nav-revealed"),a.preventDefault())}),document.addEventListener("pointerdown",a=>{h.contains(a.target)||h.classList.remove("dm-nav-revealed")}),window.E&&typeof E.contextMenu=="function"&&E.contextMenu(h,{items:()=>{const a=y.contains("dm-vnav-autohide"),d=y.contains("dm-vnav-collapsed"),f=S.has("nav_autohide")||S.has("nav_collapsed");return[{header:"Menu"},{label:d?"Expand menu":"Collapse to icons",icon:d?"maximize":"minimize",disabled:!w()||a||!k.has("collapsible"),action:()=>s(!d)},{label:a?"Keep on screen":"Hide when not in use",icon:a?"eye":"eye-off",disabled:!w(),action:()=>{S.set("nav_autohide",!a),e(!a)}},{type:"divider"},{label:"Reset to the site's setting",icon:"refresh",disabled:!f,action:()=>{S.remove("nav_autohide"),S.remove("nav_collapsed"),e(k.has("autohide")),y.toggle("dm-vnav-collapsed",!k.has("autohide")&&k.has("start-collapsed")),o()}}]}})}function ue(h){h.classList.add("dm-nav-scroll-hide");let c=window.scrollY,x=!1;window.addEventListener("scroll",()=>{x||(x=!0,requestAnimationFrame(()=>{x=!1;const y=window.scrollY,k=h.matches(":focus-within")||h.querySelector(".navbar-dropdown.open, .navbar-collapse.show");y>c&&y>h.offsetHeight&&!k?h.classList.add("dm-nav-scrolled-away"):y<c&&h.classList.remove("dm-nav-scrolled-away"),c=y}))},{passive:!0})}function pe(h){let c={};try{c=JSON.parse(h.dataset.contents||"{}")}catch{}const x=h.closest(".site-main"),y=document.querySelector(".site-content"),k=new Set(String(c.exclude||"").split(",").map(i=>i.trim().toLowerCase()).filter(Boolean)),w=i=>!i.closest(".hero, .card, .accordion, .tabs, figure, #site-sidebar"),n=i=>(y?[...y.querySelectorAll(i)]:[]).filter(w).filter(l=>!k.has(l.textContent.trim().toLowerCase())),o=c.headings==="h2"?n("h2"):c.headings==="h3"?n("h3"):c.headings==="h2h3"?n("h2, h3"):n("h2").length?n("h2"):n("h3");if(!o.length){x?.classList.add("no-sidebar-items");return}const s=new Set([...document.querySelectorAll("[id]")].map(i=>i.id)),t=document.createElement("nav");t.className="dm-contents",t.setAttribute("aria-label",c.title||"Page contents"),c.hover&&(t.dataset.menuHover=c.hover),c.selected&&(t.dataset.menuActive=c.selected),c.accent&&t.style.setProperty("--dm-menu-highlight",c.accent.startsWith("#")?c.accent:`var(--dm-${c.accent})`),c.font&&(t.style.fontFamily=c.font),c.fontSize&&(t.style.fontSize=c.fontSize),c.fontWeight&&(t.style.fontWeight=c.fontWeight);const e=document.createElement("div");e.className="dm-contents-head";const a=document.createElement("p");a.className="dm-contents-title",a.textContent=c.title||"",e.appendChild(a),t.appendChild(e);const d=document.createElement("ol");d.className="dm-contents-list"+(c.numbered?" is-numbered":"");const f=[];for(const i of o){if(!i.id){const b=(i.textContent||"").toLowerCase().trim().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"section";let u=b;for(let p=2;s.has(u);p++)u=`${b}-${p}`;s.add(u),i.id=u}const l=document.createElement("li");l.className=`dm-contents-item dm-contents-${i.tagName.toLowerCase()}`;const g=document.createElement("a");g.href=`#${i.id}`,g.textContent=i.textContent.trim(),l.appendChild(g),d.appendChild(l),f.push([i,g])}if(t.appendChild(d),h.replaceChildren(t),c.scrollSpy!==!1&&"IntersectionObserver"in window){const i=new Map,l=()=>{const b=f.find(([u])=>i.get(u))||null;f.forEach(([,u])=>{const p=b&&u===b[1];u.classList.toggle("is-active",!!p),p?u.setAttribute("aria-current","location"):u.removeAttribute("aria-current")})},g=new IntersectionObserver(b=>{b.forEach(u=>i.set(u.target,u.isIntersecting)),l()},{rootMargin:"-80px 0px -60% 0px"});f.forEach(([b])=>g.observe(b))}if(x?.classList.contains("contents-collapsible")){const i=document.createElement("button");i.type="button",i.className="dm-contents-toggle";const l=()=>{const g=x.classList.contains("contents-collapsed");i.setAttribute("aria-expanded",String(!g)),i.setAttribute("aria-label",g?"Show the contents":"Hide the contents"),i.title=i.getAttribute("aria-label"),i.innerHTML=`<span data-icon="${g?"list":"x"}"></span>`,window.Domma?.icons&&Domma.icons.scan(i)};i.addEventListener("click",()=>{S.set("contents_collapsed",x.classList.toggle("contents-collapsed")),l()}),l(),e.insertBefore(i,e.firstChild)}if(x?.classList.contains("contents-floating")&&c.movable){t.classList.add("is-movable");const i=S.get("contents_position");i&&Number.isFinite(i.x)&&Object.assign(h.style,{left:`${i.x}px`,top:`${i.y}px`,right:"auto",bottom:"auto"}),e.addEventListener("pointerdown",l=>{if(l.target.closest("button"))return;const g=h.getBoundingClientRect(),b=l.clientX-g.left,u=l.clientY-g.top,p=v=>{const C=Math.max(0,Math.min(window.innerWidth-g.width,v.clientX-b)),A=Math.max(0,Math.min(window.innerHeight-40,v.clientY-u));Object.assign(h.style,{left:`${C}px`,top:`${A}px`,right:"auto",bottom:"auto"})},m=()=>{document.removeEventListener("pointermove",p),S.set("contents_position",{x:parseInt(h.style.left,10),y:parseInt(h.style.top,10)})};document.addEventListener("pointermove",p),document.addEventListener("pointerup",m,{once:!0})})}window.E&&typeof E.contextMenu=="function"&&E.contextMenu(h,{items:()=>{const i=x?.classList.contains("contents-collapsible"),l=x?.classList.contains("contents-collapsed"),g=f.find(([,b])=>b.classList.contains("is-active"));return[{header:"Contents"},...i?[{label:l?"Show the contents":"Collapse to a button",icon:l?"list":"minimize",action:()=>h.querySelector(".dm-contents-toggle")?.click()}]:[],{label:"Copy a link to this section",icon:"link",disabled:!g,action:async()=>{const b=`${location.origin}${location.pathname}#${g[0].id}`;try{await _.copyToClipboard(b),E.toast("Link copied.",{type:"success"})}catch{E.toast("Could not copy the link.",{type:"error"})}}},{label:"Back to the top",icon:"arrow-up",action:()=>window.scrollTo({top:0,behavior:"smooth"})}]}}),x?.classList.contains("contents-autohide")&&(h.addEventListener("pointerdown",i=>{i.pointerType!=="mouse"&&!h.classList.contains("is-revealed")&&(h.classList.add("is-revealed"),i.preventDefault())}),document.addEventListener("pointerdown",i=>{h.contains(i.target)||h.classList.remove("is-revealed")}))}
1
+ import{installSmoothCarets as oe}from"/public/js/smooth-caret.js";import{decodeBase64Json as P}from"/public/js/utf8-base64.mjs";import{makeBadgeEl as J,applyColour as X,applyPill as ne,applyFloat as ae,applyHighlight as R,applyItemHighlight as se,itemHighlightAttrs as ie,isCurrentItem as U,menuAlign as re,applyClasses as O,cssClasses as W,applyEntrance as V}from"/public/js/menu-decor.mjs?v=20261014-menu-entrance";import{initFloatingMenus as ce}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as G,markInvalidFields as K,showValidationPopup as Q}from"/public/js/forms.js?v=20261015-smooth-caret";import{buildWizardSteps as le}from"/public/js/form-steps.mjs";import{applyFormClasses as Z,applySubmitButtons as de,applyWizardButtons as me}from"/public/js/form-submit.mjs";document.addEventListener("click",h=>{const c=h.target?.closest?.(".dm-banner__dismiss");c&&(h.preventDefault(),c.closest(".dm-banner")?.remove())}),$(()=>{oe(document);const h=window.__CMS_NAV__||{},c=window.__CMS_SITE__||{};if(c.autoTheme?.enabled){let o=function(e){const a=(e||"07:00").split(":");return+a[0]*60+(+a[1]||0)},s=function(){const e=new Date,a=e.getHours()*60+e.getMinutes();return a>=o(n.dayStart)&&a<o(n.nightStart)?n.dayTheme:n.nightTheme},t=function(e){const a=n.apply&&n.apply[e]||{theme:e,custom:""};Domma.theme.set(a.theme);for(const d of[...document.body.classList])d.startsWith("dm-custom-")&&document.body.classList.remove(d);a.custom&&document.body.classList.add(a.custom)};const n=c.autoTheme;t(s()),setInterval(()=>t(s()),6e4)}const x=$("#site-navbar");if(x.length&&h.brand){let s=function(u,p=0){return Array.isArray(u)?u.filter(m=>!(m&&(m.type==="spacer"||m.type==="theme-switcher"))).filter(m=>!(m&&m.type==="separator"&&p===0)).map(m=>m&&m.type==="separator"?{divider:!0}:Array.isArray(m?.items)?{...m,items:s(m.items,p+1)}:m):u};const n={...h.brand},o=n.size&&n.size!=="md"?` navbar-brand-${n.size}`:"";if(n.logo||n.icon||n.tagline){let u="";n.logo?u+=`<img src="${n.logo}" class="navbar-brand-logo" alt="${n.text||""}">`:n.icon&&(u+=`<span data-icon="${n.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),n.text&&(u+=`<span class="navbar-brand-text${o}">${n.text}</span>`),n.tagline&&(u+=`<small class="navbar-brand-tagline">${n.tagline}</small>`),n.html=u}else o&&n.text&&(n.html=`<span class="navbar-brand-text${o}">${n.text}</span>`);const t=s(h.items||[]),e=h.position==="floating",a=h.orientation==="vertical";Domma.elements.navbar("#site-navbar",{brand:n,items:t,variant:h.variant||"dark",position:e?"static":h.position||"sticky",collapsible:!0,appearOnHover:!!h.appearOnHover}),Domma.icons.scan("#site-navbar");const d=x.get(0),f=()=>{if(!a)return;const p=d.querySelector('a[aria-current="page"]')?.closest(".navbar-dropdown");p&&!p.classList.contains("open")&&p.querySelector(":scope > .navbar-dropdown-toggle")?.click()};d.classList.add(`dm-nav-align-${re(h)||"centre"}`),a&&x.get(0).classList.add("dm-nav-vertical",h.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left");const i=h.dock||{};a&&!e&&ue(d,h.side==="right"),!a&&i.hideOnScroll&&pe(d),R(d,h),V(d,h),O(d,h.class),e&&(ae(x.get(0),h),ce(x.get(0).parentNode||document));const l=d.querySelector(".navbar-toggle"),g=d.querySelector(".navbar-collapse");if(l&&g){let u=null;const p=()=>{g.classList.contains("show")&&l.click()},m=()=>{u&&(u.remove(),u=null),document.body.classList.remove("dm-drawer-open")},v=()=>{u||(u=document.createElement("div"),u.className="dm-navbar-backdrop",u.addEventListener("click",p),document.body.appendChild(u),document.body.classList.add("dm-drawer-open"))};l.addEventListener("click",()=>{requestAnimationFrame(()=>{g.classList.contains("show")?(f(),v()):m()})}),g.addEventListener("click",C=>{C.target.closest("a")&&requestAnimationFrame(m)}),document.addEventListener("keydown",C=>{C.key==="Escape"&&g.classList.contains("show")&&p()})}const b=d.querySelector(".navbar-nav");if(b){const u=Array.from(b.children);let p=0;for(const m of h.items||[])if(!(m&&m.type==="separator")){if(m&&m.type==="spacer"){const v=document.createElement("li"),C=["xs","sm","md","lg","xl","flex"].includes(m.size)?m.size:"md";v.className=`dm-menu-spacer dm-menu-spacer--${C}`,v.setAttribute("aria-hidden","true"),b.insertBefore(v,u[p]||null);continue}if(m&&m.type==="theme-switcher"){const v=document.createElement("li");v.className="dm-menu-theme-switcher";const C=document.createElement("span");C.dataset.themeSwitcher="menu",C.dataset.position="inline",v.appendChild(C),b.insertBefore(v,u[p]||null);continue}p++}window.CMSThemeSwitcher?.scan(b)}d.querySelectorAll("[data-path]").forEach(u=>{const p=u.dataset.path.split(".").map(Number),m=p.length>1,v=p.reduce((A,q)=>A?.items?.[q],{items:t});if(!v)return;v.colour&&X(u,v.colour),v.pill&&!m&&ne(u,v.pill),se(u,v),O(u.closest("li"),v.class);const C=J(v.badge);C&&u.appendChild(C),U(v.url,location.pathname)&&(u.classList.add("active"),u.setAttribute("aria-current","page"))}),f()}const y=$("#site-footer");if(y.length){let e=function(r){return String(r??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")},a=function(r){return e(r).replace(/'/g,"&#39;")},d=function(r){if(r&&r.type==="separator")return'<li class="footer-sep"><hr></li>';if(r&&r.type==="spacer")return`<li class="dm-menu-spacer dm-menu-spacer--${["xs","sm","md","lg","xl","flex"].includes(r.size)?r.size:"md"}" aria-hidden="true"></li>`;if(r&&r.type==="theme-switcher")return'<li class="dm-menu-theme-switcher"><span data-theme-switcher="menu" data-position="inline"></span></li>';const M=a(r.url||"#"),L=e(r.text||""),D=Array.isArray(r.items)&&r.items.length?`<ul>${r.items.map(d).join("")}</ul>`:"",N=ie(r),ee=[N.hover&&` data-menu-item-hover="${a(N.hover)}"`,N.active&&` data-menu-item-active="${a(N.active)}"`].filter(Boolean).join(""),te=N.accent?` style="--dm-menu-highlight:${a(N.accent)}"`:"",Y=W(r.class).join(" ");return`<li${Y?` class="${a(Y)}"`:""}><a href="${M}"${ee}${te}>${L}</a>${D}</li>`},f=function(){if(!c.footer)return"";const r=Object.keys(o).filter(L=>n[L]);if(!r.length)return"";const M=W(c.footer.socialClass).join(" ");return`<div class="footer-social">${r.map(L=>{const{label:D,svg:N}=o[L];return`<a href="${n[L]}" target="_blank" rel="noopener noreferrer" aria-label="${D}" class="footer-social-link${M?` ${a(M)}`:""}">${N}</a>`}).join("")}</div>`},b=function(r){if(!r||r.type==="separator"||r.type==="spacer")return"";if(r.type==="theme-switcher")return`<li class="cms-footer-col">${d(r).replace(/^<li[^>]*>|<\/li>$/g,"")}</li>`;const M=Array.isArray(r.items)&&r.items.length?`<ul>${r.items.map(d).join("")}</ul>`:"",L=g(r)?`<span class="cms-footer-col-title">${e(r.text||"")}</span>`:`<a class="cms-footer-col-title" href="${a(r.url||"#")}">${e(r.text||"")}</a>`,D=["cms-footer-col",...W(r.class)].join(" ");return`<li class="${a(D)}">${L}${M}</li>`},p=function(r,M,L=!1){return(r||[]).forEach(D=>{D&&(D.type==="separator"||D.type==="spacer"||D.type==="theme-switcher")||(L&&g(D)||M.push(D),Array.isArray(D.items)&&D.items.length&&p(D.items,M))}),M};const n=c.social||{},o={twitter:{label:"X / Twitter",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.742l7.73-8.835L1.254 2.25H8.08l4.259 5.629L18.244 2.25zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>'},facebook:{label:"Facebook",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>'},instagram:{label:"Instagram",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"/></svg>'},linkedin:{label:"LinkedIn",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>'},github:{label:"GitHub",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>'},youtube:{label:"YouTube",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.495 6.205a3.007 3.007 0 00-2.088-2.088c-1.87-.501-9.396-.501-9.396-.501s-7.507-.01-9.396.501A3.007 3.007 0 00.527 6.205a31.247 31.247 0 00-.522 5.805 31.247 31.247 0 00.522 5.783 3.007 3.007 0 002.088 2.088c1.868.502 9.396.502 9.396.502s7.506 0 9.396-.502a3.007 3.007 0 002.088-2.088 31.247 31.247 0 00.5-5.783 31.247 31.247 0 00-.5-5.805zM9.609 15.601V8.408l6.264 3.602z"/></svg>'}};let s='<div class="footer-inner container">';const t=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""},i=["columns","row"].includes(c.footer?.layout)?c.footer.layout:"auto",l=i==="columns"||i==="auto"&&t.primary.some(r=>Array.isArray(r?.items)&&r.items.length),g=r=>l&&!r.url&&Array.isArray(r.items)&&r.items.length>0;if(l){const r=h.brand||{},M=r.text||c.title||"",L=r.logo?`<img src="${a(r.logo)}" class="cms-footer-brand-logo" alt="">`:r.icon?'<span class="cms-footer-brand-icon"></span>':"",D=String(c.footer?.blurb||"").trim();s='<div class="footer-inner footer-inner--columns container"><div class="cms-footer-top">',s+=`<div class="cms-footer-brand">${M||L?`<a class="cms-footer-brand-name" href="${a(r.url||"/")}">${L}<span>${e(M)}</span></a>`:""}${D?`<p class="cms-footer-blurb">${e(D)}</p>`:""}${f()}</div>`,t.primary.length&&(s+=`<nav class="footer-nav footer-nav--primary footer-nav--columns" aria-label="Footer"><ul>${t.primary.map(b).join("")}</ul></nav>`),s+='</div><div class="cms-footer-bottom">',t.copyright&&(s+=`<p class="cms-footer-copyright">${e(t.copyright)}</p>`),t.legal.length&&(s+=`<nav class="footer-nav footer-nav--legal" aria-label="Legal"><ul>${t.legal.map(d).join("")}</ul></nav>`),s+="</div>"}else t.copyright&&(s+=`<p>${e(t.copyright)}</p>`),t.primary.length&&(s+=`<nav class="footer-nav footer-nav--primary"><ul>${t.primary.map(d).join("")}</ul></nav>`),t.legal.length&&(s+=`<nav class="footer-nav footer-nav--legal"><ul>${t.legal.map(d).join("")}</ul></nav>`),s+=f();s+="</div>",y.html(s),window.CMSThemeSwitcher?.scan(y.get(0));const u=y.get(0).querySelector(".cms-footer-brand-icon");u&&h.brand?.icon&&u.setAttribute("data-icon",h.brand.icon),O(y.get(0),c.footer?.class);const m=[...p(t.primary,[],!0),...p(t.legal,[])];y.get(0).querySelectorAll(".footer-nav a").forEach((r,M)=>{const L=m[M];if(!L)return;L.colour&&X(r,L.colour);const D=J(L.badge);D&&r.appendChild(D),U(L.url,location.pathname)&&(r.classList.add("is-active"),r.setAttribute("aria-current","page"))}),R(y.get(0).querySelector(".footer-nav--primary"),{highlight:t.primaryHighlight}),R(y.get(0).querySelector(".footer-nav--legal"),{highlight:t.legalHighlight}),V(y.get(0).querySelector(".footer-nav--primary"),{entrance:t.primaryEntrance}),V(y.get(0).querySelector(".footer-nav--legal"),{entrance:t.legalEntrance}),O(y.get(0).querySelector(".footer-nav--primary"),t.primaryClass),O(y.get(0).querySelector(".footer-nav--legal"),t.legalClass);const C=c.footer?.showMotionToggle!==!1,A=S.get("reduced_motion"),q=c.footer?.motionDefault,z=A!=null?!!A:q==="on"?!0:q==="off"?!1:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),B=c.footer?.motionControl==="checkbox",I=y.get(0).querySelector(".cms-footer-bottom")||y.get(0).querySelector(".footer-inner");if(C&&I){const r=document.createElement("input");r.type="checkbox",r.className=B?"footer-motion-checkbox":"form-switch-input",r.id="dm-motion-switch",r.checked=z,r.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const M=document.createElement("span");M.className=B?"footer-motion-label":"form-switch-label",M.textContent="Reduce motion";const L=document.createElement("label");L.className=B?"footer-motion-check":"form-switch footer-motion-switch",L.title="Reduce motion",L.appendChild(r),L.appendChild(M),I.appendChild(L)}}const k=document.getElementById("site-sidebar");k&&he(k),Domma.icons.scan(),Domma.syntax?.scan&&Domma.syntax.scan();const w=$(".page-body");if(w.length){w.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),w.find(".tabs").each(function(){Domma.elements.tabs(this)}),w.find(".carousel").each(function(){const n={primary:"--dm-primary",secondary:"--dm-secondary",accent:"--dm-primary",text:"--dm-text",surface:"--dm-surface",background:"--dm-background",success:"--dm-success",warning:"--dm-warning",danger:"--dm-danger",info:"--dm-info"},o=f=>{const i=String(f||"").trim();return i?n[i.toLowerCase()]?`var(${n[i.toLowerCase()]})`:CSS.supports("color",i)?i:"":""},s=o(this.dataset.background),t=o(this.dataset.arrowBackground),e=o(this.dataset.arrowColor);s&&(this.style.setProperty("--dm-carousel-background",s),this.classList.add("dm-carousel-custom-background")),t&&this.style.setProperty("--dm-carousel-arrow-background",t),e&&this.style.setProperty("--dm-carousel-arrow-color",e);const a=["top","middle","bottom"].includes(this.dataset.arrowPosition)?this.dataset.arrowPosition:"middle";this.classList.add(`dm-carousel-arrows-${a}`);const d={autoplay:this.dataset.autoplay==="true",interval:parseInt(this.dataset.interval,10)||5e3,loop:this.dataset.loop!=="false",animation:this.dataset.animation||"slide",heightMode:this.dataset.heightMode==="average"?"average":"crop"};if(this.dataset.height){const f=Number(this.dataset.height);Number.isFinite(f)&&f>0&&(d.height=f)}if(this.dataset.animationDuration){const f=parseInt(this.dataset.animationDuration,10);Number.isFinite(f)&&(d.animationDuration=f)}this.dataset.animationEasing&&(d.animationEasing=this.dataset.animationEasing),this.dataset.ctx&&(this._dmCtxSlides=Array.from(this.querySelectorAll(".carousel-slide")).map(f=>f.cloneNode(!0)),this._dmCarouselOpts=d),this._dmCarousel=Domma.elements.carousel(this,d)}),w.find(".dm-countdown").each(function(){const n={autoStart:!0},o=String(this.dataset.format||"").trim(),s=String(this.dataset.to||"").trim(),t=/^(\d{4})-(\d{2})-(\d{2})$/.exec(s),e=t?new Date(+t[1],+t[2]-1,+t[3]).getTime():s?new Date(s).getTime():NaN;Number.isFinite(e)?n.duration=Math.max(0,e-Date.now()):this.dataset.duration&&(n.duration=(parseInt(this.dataset.duration,10)||0)*1e3);const a=/^dd:/i.test(o);n.format=a?"hh:mm:ss":o||(n.duration>=36e5?"hh:mm:ss":"mm:ss");const d=this,f=l=>String(l).padStart(2,"0"),i=l=>{const g=Math.max(0,Math.floor(l/1e3)),b=d.querySelector(".dm-timer-time");b&&(b.textContent=`${f(Math.floor(g/86400))}:${f(Math.floor(g%86400/3600))}:${f(Math.floor(g%3600/60))}:${f(g%60)}`)};a&&(n.onTick=i),Domma.elements.timer(this,n),a&&i(n.duration||0)}),w.find("[data-tooltip]").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),w.find(".dm-progression").each(function(){const n=Array.from(this.querySelectorAll(".dm-progression-item")).map(t=>{const e={title:t.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:t.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return t.dataset.date&&(e.date=t.dataset.date),t.dataset.status&&(e.status=t.dataset.status),t.dataset.eventIcon&&(e.icon=t.dataset.eventIcon),t.dataset.entryId&&(e._entryId=t.dataset.entryId),e}),o=this.dataset.layout==="centred"?"centered":this.dataset.layout||"vertical",s=Domma.elements.progression(this,{layout:o,theme:this.dataset.theme||"minimal",mode:this.dataset.mode||"timeline",statusIcons:!0,allowHtml:!0,...n.length?{items:n}:{}});if((this.dataset.mode||"timeline")==="timeline"){const t={completed:{label:"Completed",icon:"check-circle",tone:"success"},planned:{label:"Planned",icon:"circle",tone:"secondary"},"in-progress":{label:"In progress",icon:"clock",tone:"primary"},blocked:{label:"Blocked",icon:"alert-circle",tone:"danger"}};this.querySelectorAll(".dm-progression-item").forEach((e,a)=>{const d=n[a];if(!d)return;const f=e.querySelector(".dm-progression-title");if(d.icon&&f){const l=document.createElement("span");l.dataset.icon=d.icon,l.style.cssText="display:inline-flex;vertical-align:middle;margin-right:.4em",l.setAttribute("aria-hidden","true"),f.prepend(l)}const i=t[d.status];if(i&&f){const l=document.createElement("span");l.className=`badge badge-${i.tone} cms-timeline-status`,l.style.cssText="display:inline-flex;align-items:center;gap:.35em;margin:.4rem 0";const g=document.createElement("span");g.dataset.icon=i.icon,g.dataset.iconSize="14",g.setAttribute("aria-hidden","true"),l.append(g,document.createTextNode(i.label)),f.after(l)}})}Domma.icons.scan(this),n.length&&(this._dmCtx={instance:s,items:n})});try{Domma.effects.reveal(".page-body .hero",{animation:"slide-up",duration:480,threshold:.06,stagger:60,once:!1})}catch{}document.querySelectorAll(".page-body .row[data-reveal]").forEach(n=>{const o=n.dataset.revealMode||"stagger",s=n.dataset.revealAnimation||"slide-up",t=parseInt(n.dataset.revealDuration,10)||400,e=parseInt(n.dataset.revealStagger,10)||60,a=parseInt(n.dataset.revealDelay,10)||0,d=n.dataset.revealDirection==="rtl",f=Array.from(n.children),i={"slide-up":"translateY(30px)","slide-down":"translateY(-30px)","slide-left":"translateX(30px)","slide-right":"translateX(-30px)",zoom:"scale(0.85)",flip:"perspective(600px) rotateX(15deg)"};f.forEach((l,g)=>{l.style.opacity="0",l.style.transform=i[s]||"",l.style.transition=`opacity ${t}ms ease, transform ${t}ms ease`;const b=d?f.length-1-g:g;l.style.transitionDelay=o==="stagger"?`${a+b*e}ms`:`${a}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const l=new IntersectionObserver(g=>{g.forEach(b=>{b.isIntersecting&&(b.target.offsetWidth,b.target.style.opacity="1",b.target.style.transform="none",l.unobserve(b.target))})},{threshold:.1});f.forEach(g=>l.observe(g))}))}),w.find(".card[data-collapsible]").each(function(){const n=this.querySelector(".card-header");n&&n.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),w.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const n=this.dataset.soTarget,o=document.getElementById(n);if(!o)return;const s=E.slideover({title:o.dataset.soTitle||"",size:o.dataset.soSize||"md",position:o.dataset.soPosition||"right"});o.style.display="",s.setContent(o),s.open()})})}if(typeof $.setup=="function"){const n=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(o=>{try{const s=P(o.dataset.config);Object.assign(n,s)}catch{}}),Object.keys(n).length>0){const o={};for(const[s,t]of Object.entries(n)){const e=t?.events?.click,{confirm:a,toast:d,alert:f,prompt:i,...l}=e||{};a||d||f||i?($(s).on("click",async function(b){if(b.preventDefault(),a&&!await E.confirm(a))return;let u=null;if(!(i&&(u=await E.prompt(i,{inputPlaceholder:l.promptPlaceholder||"",inputValue:l.promptDefault||""}),u===null))){if(l.target){const p=$(l.target);l.toggleClass&&p.toggleClass(l.toggleClass),l.addClass&&p.addClass(l.addClass),l.removeClass&&p.removeClass(l.removeClass),u!==null&&(l.setText&&p.text(u),l.setVal&&p.val(u),l.setAttr&&p.attr(l.setAttr,u))}l.href&&(window.location.href=l.href),d&&E.toast(d,{type:l.toastType||"success"}),f&&E.alert(f)}}),Object.keys(l).length&&(o[s]={...t,events:{...t.events,click:l}})):o[s]=t}$.setup(o)}}w.length&&j(w.get(0))}),window.wireCTAButtons=j;function j(h){h.querySelectorAll(".dm-cta-trigger").forEach(c=>{c.__ctaWired||(c.__ctaWired=!0,c.addEventListener("click",async()=>{const x=c.dataset.action,y=c.dataset.entry,k=c.dataset.confirm;let w=S.get("auth_token");if(!w){E.toast("Please log in to perform this action.",{type:"warning"});return}if(k&&!await E.confirm(k))return;const n=Array.from(c.childNodes).map(s=>s.cloneNode(!0));c.disabled=!0,c.textContent="Running\u2026";const o=s=>fetch(`/api/actions/${x}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify(c.dataset.scope?{entryId:y,scope:c.dataset.scope}:{entryId:y})});try{let s=await o(w);if(s.status===401){const e=S.get("auth_refresh_token");if(e){const a=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(a.ok){const{token:d}=await a.json();S.set("auth_token",d),w=d,s=await o(w)}}}const t=await s.json().catch(()=>({}));if(!s.ok||t.success===!1)throw new Error(t.error||t.message||`Error ${s.status}`);E.toast(t.message||"Action completed.",{type:"success"})}catch(s){E.toast(s.message||"Action failed.",{type:"error"})}finally{c.disabled=!1,c.textContent="",n.forEach(s=>c.appendChild(s)),Domma.icons.scan(c)}}))})}(function(){const c=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!c.length)return;const x=S.get("auth_token");function y(w,n){const o=document.createElement("div");o.className="dm-collection-display dm-collection-empty";const s=document.createElement("p");if(n){s.appendChild(document.createTextNode("Please "));const t=document.createElement("a");t.href="/login",t.textContent="sign in",s.appendChild(t),s.appendChild(document.createTextNode(" to view your entries."))}else s.textContent=w;return o.appendChild(s),o}function k(w,n){const o=document.createElement("template");o.innerHTML=n||"",w.replaceWith(o.content)}c.forEach(async w=>{const n=w.dataset.collectionAttrs;if(!n)return;if(!x){w.replaceChildren(y("",!0));return}const o=s=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify({attrs:n})});try{let s=x,t=await o(s);if(t.status===401){const a=S.get("auth_refresh_token");if(a){const d=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:a})});if(d.ok){const{token:f}=await d.json();S.set("auth_token",f),s=f,t=await o(s)}}}if(!t.ok){const{error:a}=await t.json().catch(()=>({}));w.replaceChildren(y(a||"Unable to load entries.",!1));return}const{html:e}=await t.json();k(w,e),window.DommaCollectionBrowser?.hydrate&&window.DommaCollectionBrowser.hydrate(document)}catch{w.replaceChildren(y("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),typeof Domma<"u"&&Domma.syntax?.scan&&Domma.syntax.scan(),j(document.body)},100)})(),(function(){const c=document.querySelectorAll("[data-collection-table]");!c.length||typeof T>"u"||!T.create||c.forEach(x=>{let y;try{y=P(x.dataset.payload)}catch{return}const{columns:k,rows:w,search:n,sortable:o,exportable:s,pageSize:t,empty:e,ctaConfig:a,slug:d,exportCfg:f}=y;if(!k?.length)return;if(a){const g=k.findIndex(b=>b.key==="_cta");g!==-1&&(k[g]={key:"_cta",title:"",render:(b,u)=>{const p=document.createElement("button");if(p.className=`btn btn-${a.style||"primary"} dm-cta-trigger`,p.dataset.action=a.action||"",a.scope&&(p.dataset.scope=a.scope),p.dataset.entry=u._entryId||"",a.confirm&&(p.dataset.confirm=a.confirm),a.icon){const m=document.createElement("span");m.dataset.icon=a.icon,p.appendChild(m),p.appendChild(document.createTextNode(" "))}return p.appendChild(document.createTextNode(a.label||"Run")),p}})}const i="col-table-"+Math.random().toString(36).slice(2,7),l=document.createElement("div");l.id=i,x.replaceChildren(l),T.create("#"+i,{data:w,columns:k,search:n,sortable:o,exportable:s,pageSize:t,emptyMessage:e}),a&&j(l),x._dmTable={columns:k,rows:w,slug:d,exportCfg:f,render(g){T.create("#"+i,{data:g,columns:k,search:n,sortable:o,exportable:s,pageSize:t,emptyMessage:e}),a&&j(l)}}})})(),(function(){const c=document.querySelectorAll("[data-form-inline]");if(!c.length||typeof F>"u")return;function x(o){const s=o&&o.logic;return!!(s&&(s.visibility||s.requirement))}function y(o,s){const t={};return(o||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const a=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),d=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":a?"chooser":e.type,f={...e.formConfig||{}};f.span==="full"&&s&&(f.span=s);const i={type:d,required:x(e)?!1:e.required,label:e.label,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{hint:e.helper},...e.tooltip&&{tooltip:e.tooltip},...f}};e.minLength!==void 0&&(i.minLength=e.minLength),e.maxLength!==void 0&&(i.maxLength=e.maxLength),e.min!==void 0&&(i.min=e.min),e.max!==void 0&&(i.max=e.max),a&&(e.variant&&(i.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(i.multiple=!0),e.density&&(i.density=e.density),e.columns&&(i.columns=e.columns),e.accent&&(i.accent=e.accent),e.accentStyle&&(i.accentStyle=e.accentStyle),e.glow&&(i.glow=!0),e.glowColour&&(i.glowColour=e.glowColour),e.shadow&&(i.shadow=e.shadow),e.shadowColour&&(i.shadowColour=e.shadowColour)),t[e.name]=i}),t}function k(o){const s={};return(o||[]).forEach(t=>{if(!(!t.name||t.type==="page-break"||t.type==="spacer")&&(t.type==="select"||t.type==="multiselect")&&t.required){const e=(t.options||[])[0];e&&(s[t.name]=typeof e=="object"?e.value:e)}}),s}function w(o,s){(s||[]).forEach(t=>{if(t.type!=="date"||!t.name)return;const e=o.querySelector(`[name="${t.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function n(o,s,t){let e=o.querySelector(".cms-form-message");e||(e=document.createElement("p"),e.className="cms-form-message",o.appendChild(e)),e.textContent=s,e.style.cssText=t?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}c.forEach(o=>{let s;try{s=P(o.dataset.formInline)}catch{return}const t=s.fields||[],e=s.settings||{},a=Number(e.columns)===3?3:2,d=e.layout||"grid",f=t.some(p=>p.type==="page-break"),i=async p=>{if(o.dataset.triggerBlocked==="true"){const m=o._formLogicRuntime,v=m&&m.submitBlock?m.submitBlock().reasons:[];n(o,v.join(" ")||"This form cannot be submitted yet.",!0);const C=new Error("Blocked by a form trigger");throw C.formSubmitFailed=!0,C}try{const m=o.querySelector(".fb-form-honeypot"),v=m?.querySelector('input[type="text"]')||null,C=m?.querySelector('[name="_t"]')||null,A=Object.assign({},p);delete A._t,m?.dataset.hpName&&delete A[m.dataset.hpName],v!==null&&(A._hp=v.value),C!==null&&(A._t=C.value);const q=await H.post(`/api/forms/submit/${s.slug}`,A);if(q&&q.error){n(o,q.error,!0);const z=new Error(q.error);throw z.formSubmitFailed=!0,z}if(q?.redirect){window.location.href=q.redirect;return}for(;o.firstChild;)o.removeChild(o.firstChild);n(o,q?.message||e.successMessage||"Thank you for your submission.",!1)}catch(m){throw m.formSubmitFailed||n(o,m.message||"Submission failed. Please try again.",!0),m}},l=Date.now();function g(p){const m=p.querySelector("form");if(!m||m.querySelector(".fb-form-honeypot"))return;const v=document.createElement("div");v.className="fb-form-honeypot",v.setAttribute("aria-hidden","true");const C=document.createElement("input");C.name=m.querySelector('[name="website"]')?"_website":"website",v.dataset.hpName=C.name,C.type="text",C.tabIndex=-1,C.autocomplete="url",C.placeholder="https://",v.appendChild(C);const A=document.createElement("input");A.name="_t",A.type="hidden",A.value=l,v.appendChild(A),m.appendChild(v)}function b(p){if(!window.FormLogicEngine||!t.some(v=>v.logic||Array.isArray(v.triggers)&&v.triggers.length))return null;const m=new window.FormLogicEngine.FormLogicRuntime(s,o,p?{baseValues:p}:void 0);if(m.init(),o._formLogicRuntime=m,o._formLogicObserved)return m;if(o._formLogicObserved=!0,o.parentNode&&typeof MutationObserver<"u"){const v=new MutationObserver(function(C){for(const A of C)for(const q of A.removedNodes)if(q===o||q.nodeType===1&&q.contains&&q.contains(o)){o._formLogicRuntime&&o._formLogicRuntime.destroy(),v.disconnect();return}});v.observe(o.parentNode,{childList:!0,subtree:!1}),o._formLogicObserver=v}return m}let u=!1;if(o.addEventListener("dm-form-end",p=>{const m=p.detail?.validation;if(m&&!m.valid){K(o,m.missing),Q(m);return}u||(u=!0,i(p.detail?.values||{}).catch(()=>{}).finally(()=>{u=!1}))}),f&&F.wizard){const p=le(t,s.title),m=p.map(I=>({title:I.title,description:I.description,fields:y(I.fields,a)})),v=p.map(I=>I.fields);let C=0,A=null,q=null;const z=I=>{const r=window.FormLogicEngine;if(!r||!r.validateFields)return;const M=r.validateFields(o,v[C]||[]);M.valid||(I.preventDefault(),I.stopImmediatePropagation(),K(o,M.missing),Q(M))},B=()=>{const I=v[C]||t;me(o,e),Z(o,e,I),w(o,I),G(o,I),e.honeypot!==!1&&g(o),A&&A.destroy(),A=b(q?.getStepData?q.getStepData():void 0),o.querySelectorAll(".wizard-next, .wizard-submit").forEach(r=>r.addEventListener("click",z,!0))};q=F.wizard(o,{schema:{steps:m},layout:d,columns:a,finishText:e.submitText||"Finish",onSubmit:i,onStepChange:I=>{C=I,setTimeout(B,0)}}),o.addEventListener("dm-form-trigger",I=>{if(I.detail?.type!=="goto-step"||!q?.goToStep)return;const r=Number(I.detail.action.step);!Number.isFinite(r)||r<0||r>=m.length||r!==C&&q.goToStep(r)}),Promise.resolve(q).then(B)}else if(F.render){const p=F.render(o,y(t,a),k(t),{submitText:e.submitText||"Submit",layout:d,columns:a,onSubmit:i});Promise.resolve(p).then(function(){de(o,e,d),Z(o,e,t),w(o,t),G(o,t),e.honeypot!==!1&&g(o),b()})}})})();function ue(h,c){const x=document.body,y=x.classList,k=new Set((x.dataset.railSite||"").split(" ").filter(Boolean)),w=()=>window.matchMedia("(min-width: 993px)").matches;h.querySelectorAll(".navbar-link, .navbar-dropdown-toggle").forEach(a=>{const d=a.textContent.trim();d&&(a.setAttribute("title",d),a.querySelector("svg, [data-icon]")||(a.dataset.initial=d.charAt(0).toUpperCase()))});const n=document.createElement("button");n.type="button",n.className="dm-nav-collapse-toggle";const o=()=>{const a=y.contains("dm-vnav-collapsed");n.setAttribute("aria-pressed",String(a)),n.setAttribute("aria-label",a?"Expand menu":"Collapse menu"),n.title=n.getAttribute("aria-label");const d=c?"chevrons-left":"chevrons-right",f=c?"chevrons-right":"chevrons-left";n.innerHTML=`<span data-icon="${a?d:f}"></span>`,window.Domma&&Domma.icons&&Domma.icons.scan(n)},s=a=>{y.toggle("dm-vnav-collapsed",a),S.set("nav_collapsed",a),o()};n.addEventListener("click",()=>s(!y.contains("dm-vnav-collapsed"))),o();const t=h.querySelector(".navbar-container")||h;t.insertBefore(n,t.firstChild?.nextSibling||null);const e=a=>{y.toggle("dm-vnav-autohide",a),y.toggle("dm-vnav-collapsible",!a&&k.has("collapsible")),a?y.remove("dm-vnav-collapsed"):k.has("collapsible")&&S.get("nav_collapsed")===!0&&y.add("dm-vnav-collapsed"),h.classList.remove("dm-nav-revealed"),o()};h.addEventListener("pointerdown",a=>{y.contains("dm-vnav-autohide")&&a.pointerType!=="mouse"&&!h.classList.contains("dm-nav-revealed")&&(h.classList.add("dm-nav-revealed"),a.preventDefault())}),document.addEventListener("pointerdown",a=>{h.contains(a.target)||h.classList.remove("dm-nav-revealed")}),window.E&&typeof E.contextMenu=="function"&&E.contextMenu(h,{items:()=>{const a=y.contains("dm-vnav-autohide"),d=y.contains("dm-vnav-collapsed"),f=S.has("nav_autohide")||S.has("nav_collapsed");return[{header:"Menu"},{label:d?"Expand menu":"Collapse to icons",icon:d?"maximize":"minimize",disabled:!w()||a||!k.has("collapsible"),action:()=>s(!d)},{label:a?"Keep on screen":"Hide when not in use",icon:a?"eye":"eye-off",disabled:!w(),action:()=>{S.set("nav_autohide",!a),e(!a)}},{type:"divider"},{label:"Reset to the site's setting",icon:"refresh",disabled:!f,action:()=>{S.remove("nav_autohide"),S.remove("nav_collapsed"),e(k.has("autohide")),y.toggle("dm-vnav-collapsed",!k.has("autohide")&&k.has("start-collapsed")),o()}}]}})}function pe(h){h.classList.add("dm-nav-scroll-hide");let c=window.scrollY,x=!1;window.addEventListener("scroll",()=>{x||(x=!0,requestAnimationFrame(()=>{x=!1;const y=window.scrollY,k=h.matches(":focus-within")||h.querySelector(".navbar-dropdown.open, .navbar-collapse.show");y>c&&y>h.offsetHeight&&!k?h.classList.add("dm-nav-scrolled-away"):y<c&&h.classList.remove("dm-nav-scrolled-away"),c=y}))},{passive:!0})}function he(h){let c={};try{c=JSON.parse(h.dataset.contents||"{}")}catch{}const x=h.closest(".site-main"),y=document.querySelector(".site-content"),k=new Set(String(c.exclude||"").split(",").map(i=>i.trim().toLowerCase()).filter(Boolean)),w=i=>!i.closest(".hero, .card, .accordion, .tabs, figure, #site-sidebar"),n=i=>(y?[...y.querySelectorAll(i)]:[]).filter(w).filter(l=>!k.has(l.textContent.trim().toLowerCase())),o=c.headings==="h2"?n("h2"):c.headings==="h3"?n("h3"):c.headings==="h2h3"?n("h2, h3"):n("h2").length?n("h2"):n("h3");if(!o.length){x?.classList.add("no-sidebar-items");return}const s=new Set([...document.querySelectorAll("[id]")].map(i=>i.id)),t=document.createElement("nav");t.className="dm-contents",t.setAttribute("aria-label",c.title||"Page contents"),c.hover&&(t.dataset.menuHover=c.hover),c.selected&&(t.dataset.menuActive=c.selected),c.accent&&t.style.setProperty("--dm-menu-highlight",c.accent.startsWith("#")?c.accent:`var(--dm-${c.accent})`),c.font&&(t.style.fontFamily=c.font),c.fontSize&&(t.style.fontSize=c.fontSize),c.fontWeight&&(t.style.fontWeight=c.fontWeight);const e=document.createElement("div");e.className="dm-contents-head";const a=document.createElement("p");a.className="dm-contents-title",a.textContent=c.title||"",e.appendChild(a),t.appendChild(e);const d=document.createElement("ol");d.className="dm-contents-list"+(c.numbered?" is-numbered":"");const f=[];for(const i of o){if(!i.id){const b=(i.textContent||"").toLowerCase().trim().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"section";let u=b;for(let p=2;s.has(u);p++)u=`${b}-${p}`;s.add(u),i.id=u}const l=document.createElement("li");l.className=`dm-contents-item dm-contents-${i.tagName.toLowerCase()}`;const g=document.createElement("a");g.href=`#${i.id}`,g.textContent=i.textContent.trim(),l.appendChild(g),d.appendChild(l),f.push([i,g])}if(t.appendChild(d),h.replaceChildren(t),c.scrollSpy!==!1&&"IntersectionObserver"in window){const i=new Map,l=()=>{const b=f.find(([u])=>i.get(u))||null;f.forEach(([,u])=>{const p=b&&u===b[1];u.classList.toggle("is-active",!!p),p?u.setAttribute("aria-current","location"):u.removeAttribute("aria-current")})},g=new IntersectionObserver(b=>{b.forEach(u=>i.set(u.target,u.isIntersecting)),l()},{rootMargin:"-80px 0px -60% 0px"});f.forEach(([b])=>g.observe(b))}if(x?.classList.contains("contents-collapsible")){const i=document.createElement("button");i.type="button",i.className="dm-contents-toggle";const l=()=>{const g=x.classList.contains("contents-collapsed");i.setAttribute("aria-expanded",String(!g)),i.setAttribute("aria-label",g?"Show the contents":"Hide the contents"),i.title=i.getAttribute("aria-label"),i.innerHTML=`<span data-icon="${g?"list":"x"}"></span>`,window.Domma?.icons&&Domma.icons.scan(i)};i.addEventListener("click",()=>{S.set("contents_collapsed",x.classList.toggle("contents-collapsed")),l()}),l(),e.insertBefore(i,e.firstChild)}if(x?.classList.contains("contents-floating")&&c.movable){t.classList.add("is-movable");const i=S.get("contents_position");i&&Number.isFinite(i.x)&&Object.assign(h.style,{left:`${i.x}px`,top:`${i.y}px`,right:"auto",bottom:"auto"}),e.addEventListener("pointerdown",l=>{if(l.target.closest("button"))return;const g=h.getBoundingClientRect(),b=l.clientX-g.left,u=l.clientY-g.top,p=v=>{const C=Math.max(0,Math.min(window.innerWidth-g.width,v.clientX-b)),A=Math.max(0,Math.min(window.innerHeight-40,v.clientY-u));Object.assign(h.style,{left:`${C}px`,top:`${A}px`,right:"auto",bottom:"auto"})},m=()=>{document.removeEventListener("pointermove",p),S.set("contents_position",{x:parseInt(h.style.left,10),y:parseInt(h.style.top,10)})};document.addEventListener("pointermove",p),document.addEventListener("pointerup",m,{once:!0})})}window.E&&typeof E.contextMenu=="function"&&E.contextMenu(h,{items:()=>{const i=x?.classList.contains("contents-collapsible"),l=x?.classList.contains("contents-collapsed"),g=f.find(([,b])=>b.classList.contains("is-active"));return[{header:"Contents"},...i?[{label:l?"Show the contents":"Collapse to a button",icon:l?"list":"minimize",action:()=>h.querySelector(".dm-contents-toggle")?.click()}]:[],{label:"Copy a link to this section",icon:"link",disabled:!g,action:async()=>{const b=`${location.origin}${location.pathname}#${g[0].id}`;try{await _.copyToClipboard(b),E.toast("Link copied.",{type:"success"})}catch{E.toast("Could not copy the link.",{type:"error"})}}},{label:"Back to the top",icon:"arrow-up",action:()=>window.scrollTo({top:0,behavior:"smooth"})}]}}),x?.classList.contains("contents-autohide")&&(h.addEventListener("pointerdown",i=>{i.pointerType!=="mouse"&&!h.classList.contains("is-revealed")&&(h.classList.add("is-revealed"),i.preventDefault())}),document.addEventListener("pointerdown",i=>{h.contains(i.target)||h.classList.remove("is-revealed")}))}
@@ -0,0 +1 @@
1
+ const r=Object.freeze({refresh(){},destroy(){}}),t=()=>globalThis.Domma?.smoothCaret;export const attachSmoothCaret=e=>t()?.attach(e)||r,installSmoothCarets=e=>t()?.install(e)||r,getCaretPreference=()=>t()?.getPreference()||"auto",setCaretPreference=e=>t()?.setPreference(e);
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Import from WordPress - the API (mounted at /api/import/wordpress behind
3
+ * toolGate('wordpress-import'); every route needs `import.wordpress`). The
4
+ * work is services/wpImport.
5
+ *
6
+ * GET /status staged uploads, the running job, recent runs, what posts can become
7
+ * GET /count the sidebar badge
8
+ * POST /upload a WXR export (.xml, or a .zip holding one) - multipart, field "file"
9
+ * POST /staged/:id/uploads the wp-content/uploads folder as a .zip - multipart, field "file"
10
+ * DELETE /staged/:id/uploads drop that zip
11
+ * POST /staged/:id/preview {options} -> the plan; nothing is written
12
+ * POST /staged/:id/run {options} -> a background job
13
+ * DELETE /staged/:id discard an upload
14
+ * GET /jobs/:id a job's progress
15
+ * GET /history · GET|DELETE /history/:id
16
+ */
17
+ import fs from 'node:fs';
18
+ import {pipeline} from 'node:stream/promises';
19
+ import {Transform} from 'node:stream';
20
+ import {authenticate as defaultAuthenticate, requirePermission as defaultRequirePermission} from '../../middleware/auth.js';
21
+ import {
22
+ blogTarget, cleanOptions, createStaging, discardStaged, getJob, inspectStaged, previewImport, runningJob, stagingMeta,
23
+ stagingPath, startRun, updateStagingMeta, UPLOADS_ZIP_MAX_BYTES, WXR_MAX_BYTES, zipRel
24
+ } from '../../services/wpImport/index.js';
25
+ import {getReport, isId, listHistory, listStaging, removeHistory} from '../../services/wpImport/store.js';
26
+ import {openZip} from '../../services/wpImport/zip.js';
27
+ import {seoEngine} from '../../services/seo/index.js';
28
+ import {allowInGitignore} from '../../services/siteGitignore.js';
29
+ import {ago, clip, plural} from '../../services/sidebarBadges.js';
30
+
31
+ // Uploads, the mapping, the history and reports (names of who ran it, commenters) all stay out of the site's repo.
32
+ const IMPORT_RULE = '/content/imports/wordpress/';
33
+
34
+ /** Stream a multipart file to disk, stopping at `max` bytes. */
35
+ async function saveUpload(file, dest, max) {
36
+ let size = 0;
37
+ const counter = new Transform({
38
+ transform(chunk, _enc, cb) {
39
+ size += chunk.length;
40
+ if (size > max) return cb(Object.assign(new Error('too big'), {code: 'ETOOBIG'}));
41
+ cb(null, chunk);
42
+ }
43
+ });
44
+ await pipeline(file, counter, fs.createWriteStream(dest, {mode: 0o600}));
45
+ if (file.truncated) throw Object.assign(new Error('too big'), {code: 'ETOOBIG'});
46
+ return size;
47
+ }
48
+
49
+ const mb = (n) => `${Math.round(n / 1048576)} MB`;
50
+
51
+ /** What the screen shows of a plan - the plan minus internals. */
52
+ export function planForScreen(plan, blog) {
53
+ return {
54
+ source: plan.source,
55
+ site: plan.site,
56
+ blogBase: plan.blogBase,
57
+ blog: {available: blog.available, basePath: blog.basePath, comments: Boolean(blog.comments), reason: blog.reason || ''},
58
+ options: plan.options,
59
+ summary: plan.summary,
60
+ notes: plan.notes,
61
+ items: plan.items.map(r => ({
62
+ key: r.key, kind: r.kind, wpId: r.wpId, title: r.title, action: r.action, reason: r.reason || '',
63
+ target: r.target || '', oldUrl: r.oldUrl || '', status: r.status || '', wpStatus: r.wpStatus || '',
64
+ shortcodes: r.shortcodes || [], builder: r.builder || '', comments: r.comments || 0, count: r.count || 0,
65
+ replaces: Boolean(r.replaces), wanted: r.wanted || ''
66
+ })),
67
+ redirects: plan.redirects.map(r => ({from: r.from, to: r.to, action: r.action, reason: r.reason || ''}))
68
+ };
69
+ }
70
+
71
+ export async function wordpressImportRoutes(fastify, opts = {}) {
72
+ const authenticate = opts.authenticate || defaultAuthenticate;
73
+ const requirePermission = opts.requirePermission || defaultRequirePermission;
74
+ const may = {preHandler: [authenticate, requirePermission('import', 'wordpress')]};
75
+ const fail = (reply, err, fallback = 400) => reply.code(err.statusCode || fallback).send({error: err.message || String(err)});
76
+
77
+ fastify.get('/status', may, async () => {
78
+ const blog = await blogTarget();
79
+ const job = runningJob();
80
+ return {
81
+ staged: await listStaging(),
82
+ running: job ? {...job} : null,
83
+ history: (await listHistory()).slice(0, 50),
84
+ blog: {available: blog.available, basePath: blog.basePath, comments: Boolean(blog.comments), reason: blog.reason || ''},
85
+ redirects: Boolean(seoEngine()),
86
+ limits: {wxr: WXR_MAX_BYTES, uploads: UPLOADS_ZIP_MAX_BYTES}
87
+ };
88
+ });
89
+
90
+ fastify.get('/count', may, async () => {
91
+ const job = runningJob();
92
+ const runs = await listHistory();
93
+ const staged = await listStaging();
94
+ if (job) {
95
+ return {count: 1, label: `Importing - ${job.phase}`, tone: 'live',
96
+ details: {title: 'Import from WordPress', rows: [{label: 'Progress', value: `${job.done} of ${job.total}`}], items: [], empty: ''}};
97
+ }
98
+ const last = runs[0];
99
+ return {
100
+ count: runs.length,
101
+ label: runs.length ? `${plural(runs.length, 'import')} so far` : 'Nothing imported yet',
102
+ tone: 'info',
103
+ details: {
104
+ title: 'Import from WordPress',
105
+ rows: [
106
+ {label: 'Imports', value: String(runs.length)},
107
+ {label: 'Waiting to run', value: String(staged.length)},
108
+ ...(last ? [{label: 'Last', value: ago(last.at)}] : [])
109
+ ],
110
+ items: runs.slice(0, 5).map(r => ({text: clip(r.siteTitle || r.source || 'WordPress', 48), meta: `${ago(r.at)}${r.status === 'partial' ? ' · some failed' : ''}`, ...(r.status === 'partial' && {tone: 'danger'})})),
111
+ empty: 'Upload a WordPress export to start.'
112
+ }
113
+ };
114
+ });
115
+
116
+ fastify.post('/upload', may, async (request, reply) => {
117
+ const file = await request.file({limits: {fileSize: WXR_MAX_BYTES + 1, files: 1}}).catch(() => null);
118
+ if (!file) return reply.code(400).send({error: 'Choose the export file to upload.'});
119
+ const name = String(file.filename || '').slice(0, 200);
120
+ const kind = /\.zip$/i.test(name) ? 'zip' : /\.xml$/i.test(name) ? 'xml' : '';
121
+ if (!kind) {
122
+ file.file.resume();
123
+ return reply.code(400).send({error: 'Upload the .xml file WordPress exports (Tools › Export), or a .zip holding it.'});
124
+ }
125
+ try { allowInGitignore(process.cwd(), [IMPORT_RULE]); } catch { /* not a managed site repo */ }
126
+ const id = await createStaging({fileName: name.replace(/[^\w.() -]/g, '_'), kind, by: request.user?.email || ''});
127
+ try {
128
+ const size = await saveUpload(file.file, stagingPath(id, kind === 'zip' ? 'upload.zip' : 'export.xml'), WXR_MAX_BYTES);
129
+ await updateStagingMeta(id, {size});
130
+ return await inspectStaged(id);
131
+ } catch (err) {
132
+ await discardStaged(id);
133
+ if (err.code === 'ETOOBIG') return reply.code(413).send({error: `The export is over ${mb(WXR_MAX_BYTES)}. In WordPress, export the posts and the pages separately and import each.`});
134
+ return fail(reply, err);
135
+ }
136
+ });
137
+
138
+ fastify.post('/staged/:id/uploads', may, async (request, reply) => {
139
+ const {id} = request.params;
140
+ const meta = isId(id) ? await stagingMeta(id) : null;
141
+ if (!meta) return reply.code(404).send({error: 'That upload has gone - upload the export again.'});
142
+ if (runningJob()?.staged === id) return reply.code(409).send({error: 'That import is running.'});
143
+ const file = await request.file({limits: {fileSize: UPLOADS_ZIP_MAX_BYTES + 1, files: 1}}).catch(() => null);
144
+ if (!file) return reply.code(400).send({error: 'Choose the uploads zip.'});
145
+ if (!/\.zip$/i.test(file.filename || '')) {
146
+ file.file.resume();
147
+ return reply.code(400).send({error: 'Zip the wp-content/uploads folder and upload the .zip.'});
148
+ }
149
+ const dest = stagingPath(id, 'uploads.zip');
150
+ try {
151
+ const size = await saveUpload(file.file, dest, UPLOADS_ZIP_MAX_BYTES);
152
+ const zip = await openZip(dest);
153
+ const files = zip.entries.length;
154
+ const unsafe = zip.unsafe.length;
155
+ const sample = zip.entries.slice(0, 3).map(e => zipRel(e.name));
156
+ await zip.close();
157
+ if (!files) throw new Error('That zip has no files in it.');
158
+ return await updateStagingMeta(id, {uploads: {fileName: String(file.filename).slice(0, 200).replace(/[^\w.() -]/g, '_'), size, files, unsafe, sample}});
159
+ } catch (err) {
160
+ fs.rmSync(dest, {force: true});
161
+ await updateStagingMeta(id, {uploads: null});
162
+ if (err.code === 'ETOOBIG') return reply.code(413).send({error: `The zip is over ${mb(UPLOADS_ZIP_MAX_BYTES)}. Zip the uploads a few years at a time and import with each.`});
163
+ return fail(reply, err);
164
+ }
165
+ });
166
+
167
+ fastify.delete('/staged/:id/uploads', may, async (request, reply) => {
168
+ const {id} = request.params;
169
+ if (!isId(id) || !(await stagingMeta(id))) return reply.code(404).send({error: 'That upload has gone.'});
170
+ fs.rmSync(stagingPath(id, 'uploads.zip'), {force: true});
171
+ return updateStagingMeta(id, {uploads: null});
172
+ });
173
+
174
+ fastify.post('/staged/:id/preview', may, async (request, reply) => {
175
+ const {id} = request.params;
176
+ if (!isId(id)) return reply.code(404).send({error: 'That upload has gone.'});
177
+ try {
178
+ const {plan, state, meta} = await previewImport(id, cleanOptions(request.body?.options));
179
+ return {meta, plan: planForScreen(plan, state.blog), redirectsAvailable: state.redirectsAvailable};
180
+ } catch (err) {
181
+ return fail(reply, err);
182
+ }
183
+ });
184
+
185
+ fastify.post('/staged/:id/run', may, async (request, reply) => {
186
+ const {id} = request.params;
187
+ if (!isId(id)) return reply.code(404).send({error: 'That upload has gone.'});
188
+ try {
189
+ const job = await startRun(id, cleanOptions(request.body?.options), request.user, fastify.log);
190
+ return reply.code(202).send({job: {...job}});
191
+ } catch (err) {
192
+ return fail(reply, err);
193
+ }
194
+ });
195
+
196
+ fastify.delete('/staged/:id', may, async (request, reply) => {
197
+ const {id} = request.params;
198
+ if (!isId(id)) return reply.code(404).send({error: 'That upload has gone.'});
199
+ if (runningJob()?.staged === id) return reply.code(409).send({error: 'That import is running.'});
200
+ await discardStaged(id);
201
+ return {ok: true};
202
+ });
203
+
204
+ fastify.get('/jobs/:id', may, async (request, reply) => {
205
+ const job = getJob(String(request.params.id));
206
+ if (!job) return reply.code(404).send({error: 'No such import.'});
207
+ return {job: {...job}};
208
+ });
209
+
210
+ fastify.get('/history', may, async () => ({runs: await listHistory()}));
211
+ fastify.get('/history/:id', may, async (request, reply) => {
212
+ const report = await getReport(String(request.params.id));
213
+ if (!report) return reply.code(404).send({error: 'No such import.'});
214
+ return report;
215
+ });
216
+ fastify.delete('/history/:id', may, async (request, reply) => {
217
+ if (!(await removeHistory(String(request.params.id)))) return reply.code(404).send({error: 'No such import.'});
218
+ return {ok: true};
219
+ });
220
+ }
package/server/server.js CHANGED
@@ -600,6 +600,15 @@ await app.register(toolGate('contacts', contactsRoutes), {prefix: '/api/contacts
600
600
  await app.register(seoPlugin, {enabled: isToolEnabled('seo'), getBaseUrl, rolesService});
601
601
  }
602
602
  await app.register(toolGate('seo', seoRoutes), {prefix: '/api/seo'});
603
+ // Import from WordPress (services/wpImport): its API behind the Tool switch, and - always,
604
+ // whatever the switch - the hook that answers WordPress's query addresses (/?p=12) from what
605
+ // an import made: those redirects are part of the migrated site, not of the Tool.
606
+ {
607
+ const {wordpressImportPlugin} = await import('./services/wpImport/index.js');
608
+ const {wordpressImportRoutes} = await import('./routes/api/wordpress-import.js');
609
+ await app.register(wordpressImportPlugin);
610
+ await app.register(toolGate('wordpress-import', wordpressImportRoutes), {prefix: '/api/import/wordpress'});
611
+ }
603
612
  await app.register(toolsRoutes, {prefix: '/api'});
604
613
  // What plugins show beside a contact or a note (Calendar's Events) - 0.79.
605
614
  await app.register(relatedRoutes, {prefix: '/api/related'});
@@ -643,6 +652,9 @@ const coreToolItems = [
643
652
  for (const item of coreToolItems) {
644
653
  if (isToolEnabled(item.permission)) registerSidebarItem({item: {source: 'Built in', ...item}});
645
654
  }
655
+ if (isToolEnabled('wordpress-import')) {
656
+ registerSidebarItem({item: {source: 'Built in', text: 'Import from WordPress', url: '#/wordpress-import', icon: 'download', permission: 'import', countUrl: '/api/import/wordpress/count', inventory: true}});
657
+ }
646
658
  // Todo's to-dos on the calendar (registered when routes/api/todo.js loads).
647
659
  if (!isToolEnabled('todo')) {
648
660
  const {unregisterCalendarSource} = await import('./services/hooks.js');
@@ -80,7 +80,7 @@ const TOOL_GROUPS = new Map(Object.entries({
80
80
  Marketing: ['#/analytics', '#/seo', '#/plugins/analytics-pro/dashboard', '#/plugins/social', '#/plugins/feedback', '#/plugins/surveys'],
81
81
  Publishing: ['#/plugins/blog', '#/plugins/blog-pro', '#/plugins/docs', '#/plugins/theme-roller'],
82
82
  Business: ['#/plugins/shopping-cart', '#/plugins/booking', '#/plugins/invoice', '#/job-board', '#/plugins/garage'],
83
- Maintenance: ['#/plugins/security', '#/plugins/security-pro', '#/plugins/security-pro/my-2fa', '#/plugins/backup-pro', '#/plugins/data-transfer']
83
+ Maintenance: ['#/plugins/security', '#/plugins/security-pro', '#/plugins/security-pro/my-2fa', '#/plugins/backup-pro', '#/plugins/data-transfer', '#/wordpress-import']
84
84
  }).flatMap(([group, urls]) => urls.map(url => [url, group])));
85
85
 
86
86
  /**
@@ -251,6 +251,16 @@ export const REGISTRY = [
251
251
  {key: 'manage', label: 'Manage', description: 'Change page titles and descriptions, redirects and settings'}
252
252
  ]
253
253
  },
254
+ {
255
+ key: 'import',
256
+ label: 'Import',
257
+ description: 'Bring content in from other systems.',
258
+ icon: 'download',
259
+ group: 'Tools',
260
+ actions: [
261
+ {key: 'wordpress', label: 'From WordPress', description: 'Upload a WordPress export, preview it and import pages, posts, media, menus and redirects'}
262
+ ]
263
+ },
254
264
  {
255
265
  key: 'notes',
256
266
  label: 'Notes',
@@ -477,7 +477,7 @@ export async function grantPermissionOnce(roleName, permission, source) {
477
477
  * a later removal in System > Roles sticks. `contacts.manageGroups` is granted
478
478
  * the same way, separately (server.js).
479
479
  */
480
- export const ADMIN_TOOL_GRANTS = ['notes', 'todo', 'contacts', 'analytics', 'seo', 'calculator'];
480
+ export const ADMIN_TOOL_GRANTS = ['notes', 'todo', 'contacts', 'analytics', 'seo', 'calculator', 'import.wordpress'];
481
481
 
482
482
  /**
483
483
  * Grant the seeded `admin` role the built-in Tool permissions - once each.
@@ -50,7 +50,8 @@ export const CORE_TOOLS = Object.freeze([
50
50
  {name: 'todo', displayName: 'Todo', icon: 'check-square', description: 'To-do lists with due dates and reminders.'},
51
51
  {name: 'calculator', displayName: 'Calculator', icon: 'calculator', description: 'Basic and scientific calculations, on a page or in a floating panel.'},
52
52
  {name: 'analytics', displayName: 'Analytics', icon: 'chart-bar', description: 'Page views and visitors, counted on this server.'},
53
- {name: 'seo', displayName: 'SEO', icon: 'search', description: 'How the site looks to search engines: an audit of every page, redirects, the sitemap and robots.txt.'}
53
+ {name: 'seo', displayName: 'SEO', icon: 'search', description: 'How the site looks to search engines: an audit of every page, redirects, the sitemap and robots.txt.'},
54
+ {name: 'wordpress-import', displayName: 'Import from WordPress', icon: 'download', description: 'Bring a WordPress site across: pages, posts, media, menus, comments and redirects from the old addresses.'}
54
55
  ]);
55
56
 
56
57
  const CORE_NAMES = new Set(CORE_TOOLS.map(t => t.name));