domma-cms 1.10.16 → 1.10.18

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.
package/public/js/site.js CHANGED
@@ -1 +1 @@
1
- import{applyMobileMenu as se,initCarouselPopup as ie}from"/public/js/mobile-ui.mjs?v=20261016-mobile-options";import{installSmoothCarets as re}from"/public/js/smooth-caret.js";import{decodeBase64Json as W}from"/public/js/utf8-base64.mjs";import{makeBadgeEl as U,applyColour as G,applyPill as ce,applyFloat as le,applyHighlight as V,applyItemHighlight as de,itemHighlightAttrs as me,isCurrentItem as K,menuAlign as ue,applyClasses as P,cssClasses as Y,applyEntrance as J}from"/public/js/menu-decor.mjs?v=20261014-menu-entrance";import{initFloatingMenus as pe}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as Q,markInvalidFields as Z,showValidationPopup as ee}from"/public/js/forms.js?v=20261025-form-dropzone";import{buildWizardSteps as fe}from"/public/js/form-steps.mjs";import{applyFormClasses as te,applySubmitButtons as he,applyWizardButtons as ge}from"/public/js/form-submit.mjs";import{captureFileFields as oe,destroyDropzones as ye,fileBlueprint as ve,restoreFileFields as be,splitFiles as we,submitBody as Ce,wireFileFields as Se}from"/public/js/form-files.mjs";document.addEventListener("click",m=>{const c=m.target?.closest?.(".dm-banner__dismiss");c&&(m.preventDefault(),c.closest(".dm-banner")?.remove())}),$(()=>{re(document);const m=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&&m.brand){let s=function(u,y=0){return Array.isArray(u)?u.filter(h=>!(h&&(h.type==="spacer"||h.type==="theme-switcher"))).filter(h=>!(h&&h.type==="separator"&&y===0)).map(h=>h&&h.type==="separator"?{divider:!0}:Array.isArray(h?.items)?{...h,items:s(h.items,y+1)}:h):u};const n={...m.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(m.items||[]),e=m.position==="floating",a=m.orientation==="vertical";x.get(0).querySelector(":scope > .cms-nojs-links")?.remove(),Domma.elements.navbar("#site-navbar",{brand:n,items:t,variant:m.variant||"dark",position:e?"static":m.position||"sticky",collapsible:!0,appearOnHover:!!m.appearOnHover}),Domma.icons.scan("#site-navbar");const d=x.get(0);se(d,m.mobile);const p=()=>{if(!a)return;const y=d.querySelector('a[aria-current="page"]')?.closest(".navbar-dropdown");y&&!y.classList.contains("open")&&y.querySelector(":scope > .navbar-dropdown-toggle")?.click()};d.classList.add(`dm-nav-align-${ue(m)||"centre"}`),a&&x.get(0).classList.add("dm-nav-vertical",m.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left");const i=m.dock||{};a&&!e&&xe(d,m.side==="right"),!a&&i.hideOnScroll&&ke(d),V(d,m),J(d,m),P(d,m.class),e&&(le(x.get(0),m),pe(x.get(0).parentNode||document));const r=d.querySelector(".navbar-toggle"),g=d.querySelector(".navbar-collapse");if(r&&g){let u=null;const y=()=>{g.classList.contains("show")&&r.click()},h=()=>{u&&(u.remove(),u=null),document.body.classList.remove("dm-drawer-open")},f=()=>{u||(u=document.createElement("div"),u.className="dm-navbar-backdrop",u.addEventListener("click",y),document.body.appendChild(u),document.body.classList.add("dm-drawer-open"))};r.addEventListener("click",()=>{requestAnimationFrame(()=>{g.classList.contains("show")?(p(),f()):h()})}),g.addEventListener("click",w=>{w.target.closest("a")&&requestAnimationFrame(h)}),document.addEventListener("keydown",w=>{w.key==="Escape"&&g.classList.contains("show")&&y()})}const b=d.querySelector(".navbar-nav");if(b){const u=Array.from(b.children);let y=0;for(const h of m.items||[])if(!(h&&h.type==="separator")){if(h&&h.type==="spacer"){const f=document.createElement("li"),w=["xs","sm","md","lg","xl","flex"].includes(h.size)?h.size:"md";f.className=`dm-menu-spacer dm-menu-spacer--${w}`,f.setAttribute("aria-hidden","true"),b.insertBefore(f,u[y]||null);continue}if(h&&h.type==="theme-switcher"){const f=document.createElement("li");f.className="dm-menu-theme-switcher";const w=document.createElement("span");w.dataset.themeSwitcher="menu",w.dataset.position="inline",f.appendChild(w),b.insertBefore(f,u[y]||null);continue}y++}window.CMSThemeSwitcher?.scan(b)}d.querySelectorAll("[data-path]").forEach(u=>{const y=u.dataset.path.split(".").map(Number),h=y.length>1,f=y.reduce((A,I)=>A?.items?.[I],{items:t});if(!f)return;f.colour&&G(u,f.colour),f.pill&&!h&&ce(u,f.pill),de(u,f),P(u.closest("li"),f.class);const w=U(f.badge);w&&u.appendChild(w),K(f.url,location.pathname)&&(u.classList.add("active"),u.setAttribute("aria-current","page"))}),p()}const v=$("#site-footer");if(v.length){let e=function(l){return String(l??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")},a=function(l){return e(l).replace(/'/g,"&#39;")},d=function(l){if(l&&l.type==="separator")return'<li class="footer-sep"><hr></li>';if(l&&l.type==="spacer")return`<li class="dm-menu-spacer dm-menu-spacer--${["xs","sm","md","lg","xl","flex"].includes(l.size)?l.size:"md"}" aria-hidden="true"></li>`;if(l&&l.type==="theme-switcher")return'<li class="dm-menu-theme-switcher"><span data-theme-switcher="menu" data-position="inline"></span></li>';const D=a(l.url||"#"),L=e(l.text||""),q=Array.isArray(l.items)&&l.items.length?`<ul>${l.items.map(d).join("")}</ul>`:"",j=me(l),ne=[j.hover&&` data-menu-item-hover="${a(j.hover)}"`,j.active&&` data-menu-item-active="${a(j.active)}"`].filter(Boolean).join(""),ae=j.accent?` style="--dm-menu-highlight:${a(j.accent)}"`:"",X=Y(l.class).join(" ");return`<li${X?` class="${a(X)}"`:""}><a href="${D}"${ne}${ae}>${L}</a>${q}</li>`},i=function(){if(!c.footer)return"";const l=Object.keys(o).filter(L=>n[L]);if(!l.length)return"";const D=Y(c.footer.socialClass).join(" ");return`<div class="footer-social">${l.map(L=>{const{label:q,svg:j}=o[L];return`<a href="${n[L]}" target="_blank" rel="noopener noreferrer" aria-label="${q}" class="footer-social-link${D?` ${a(D)}`:""}">${j}</a>`}).join("")}</div>`},u=function(l){if(!l||l.type==="separator"||l.type==="spacer")return"";if(l.type==="theme-switcher")return`<li class="cms-footer-col">${d(l).replace(/^<li[^>]*>|<\/li>$/g,"")}</li>`;const D=Array.isArray(l.items)&&l.items.length?`<ul>${l.items.map(d).join("")}</ul>`:"",L=b(l)?`<span class="cms-footer-col-title">${e(l.text||"")}</span>`:`<a class="cms-footer-col-title" href="${a(l.url||"#")}">${e(l.text||"")}</a>`,q=["cms-footer-col",...Y(l.class)].join(" ");return`<li class="${a(q)}">${L}${D}</li>`},f=function(l,D,L=!1){return(l||[]).forEach(q=>{q&&(q.type==="separator"||q.type==="spacer"||q.type==="theme-switcher")||(L&&b(q)||D.push(q),Array.isArray(q.items)&&q.items.length&&f(q.items,D))}),D};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:""},p=(()=>{const l=c.cookieConsent;if(!l||l.showSettingsLink===!1)return"";const D=L=>(L||[]).some(q=>q&&(q.url==="#cookie-settings"||D(q.items)));return D(t.primary)||D(t.legal)?"":`<li class="cms-cookie-settings"><a href="#cookie-settings">${e(l.settingsLinkText||"Cookie settings")}</a></li>`})(),r=["columns","row"].includes(c.footer?.layout)?c.footer.layout:"auto",g=r==="columns"||r==="auto"&&t.primary.some(l=>Array.isArray(l?.items)&&l.items.length),b=l=>g&&!l.url&&Array.isArray(l.items)&&l.items.length>0;if(g){const l=m.brand||{},D=l.text||c.title||"",L=l.logo?`<img src="${a(l.logo)}" class="cms-footer-brand-logo" alt="">`:l.icon?'<span class="cms-footer-brand-icon"></span>':"",q=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">${D||L?`<a class="cms-footer-brand-name" href="${a(l.url||"/")}">${L}<span>${e(D)}</span></a>`:""}${q?`<p class="cms-footer-blurb">${e(q)}</p>`:""}${i()}</div>`,t.primary.length&&(s+=`<nav class="footer-nav footer-nav--primary footer-nav--columns" aria-label="Footer"><ul>${t.primary.map(u).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||p)&&(s+=`<nav class="footer-nav footer-nav--legal" aria-label="Legal"><ul>${t.legal.map(d).join("")}${p}</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||p)&&(s+=`<nav class="footer-nav footer-nav--legal"><ul>${t.legal.map(d).join("")}${p}</ul></nav>`),s+=i();s+="</div>",v.get(0).querySelector(":scope > .cms-nojs-links")?.remove();const y=v.get(0).querySelector(":scope > .cms-footer-blocks");y?.remove(),v.html(s),y&&v.get(0).prepend(y),window.CMSThemeSwitcher?.scan(v.get(0));const h=v.get(0).querySelector(".cms-footer-brand-icon");h&&m.brand?.icon&&h.setAttribute("data-icon",m.brand.icon),P(v.get(0),c.footer?.class);const w=[...f(t.primary,[],!0),...f(t.legal,[])];v.get(0).querySelectorAll(".footer-nav a").forEach((l,D)=>{const L=w[D];if(!L)return;L.colour&&G(l,L.colour);const q=U(L.badge);q&&l.appendChild(q),K(L.url,location.pathname)&&(l.classList.add("is-active"),l.setAttribute("aria-current","page"))}),V(v.get(0).querySelector(".footer-nav--primary"),{highlight:t.primaryHighlight}),V(v.get(0).querySelector(".footer-nav--legal"),{highlight:t.legalHighlight}),J(v.get(0).querySelector(".footer-nav--primary"),{entrance:t.primaryEntrance}),J(v.get(0).querySelector(".footer-nav--legal"),{entrance:t.legalEntrance}),P(v.get(0).querySelector(".footer-nav--primary"),t.primaryClass),P(v.get(0).querySelector(".footer-nav--legal"),t.legalClass);const I=c.footer?.showMotionToggle!==!1,N=S.get("reduced_motion"),O=c.footer?.motionDefault,B=N!=null?!!N:O==="on"?!0:O==="off"?!1:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),M=c.footer?.motionControl==="checkbox",z=v.get(0).querySelector(".cms-footer-bottom")||v.get(0).querySelector(".footer-inner");if(I&&z){const l=document.createElement("input");l.type="checkbox",l.className=M?"footer-motion-checkbox":"form-switch-input",l.id="dm-motion-switch",l.checked=B,l.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const D=document.createElement("span");D.className=M?"footer-motion-label":"form-switch-label",D.textContent="Reduce motion";const L=document.createElement("label");L.className=M?"footer-motion-check":"form-switch footer-motion-switch",L.title="Reduce motion",L.appendChild(l),L.appendChild(D),z.appendChild(L)}}const k=document.getElementById("site-sidebar");k&&Le(k),Domma.icons.scan(),Domma.syntax?.scan&&Domma.syntax.scan();const C=$(".page-body");if(C.length){C.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),C.find(".tabs").each(function(){Domma.elements.tabs(this)}),C.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=p=>{const i=String(p||"").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 p=Number(this.dataset.height);Number.isFinite(p)&&p>0&&(d.height=p)}if(this.dataset.animationDuration){const p=parseInt(this.dataset.animationDuration,10);Number.isFinite(p)&&(d.animationDuration=p)}this.dataset.animationEasing&&(d.animationEasing=this.dataset.animationEasing),this.dataset.ctx&&(this._dmCtxSlides=Array.from(this.querySelectorAll(".carousel-slide")).map(p=>p.cloneNode(!0)),this._dmCarouselOpts=d),this._dmCarousel=Domma.elements.carousel(this,d),ie(this)}),C.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,p=r=>String(r).padStart(2,"0"),i=r=>{const g=Math.max(0,Math.floor(r/1e3)),b=d.querySelector(".dm-timer-time");b&&(b.textContent=`${p(Math.floor(g/86400))}:${p(Math.floor(g%86400/3600))}:${p(Math.floor(g%3600/60))}:${p(g%60)}`)};a&&(n.onTick=i),Domma.elements.timer(this,n),a&&i(n.duration||0)}),C.find("[data-tooltip]:not(.dm-range-slider)").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),C.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 p=e.querySelector(".dm-progression-title");if(d.icon&&p){const r=document.createElement("span");r.dataset.icon=d.icon,r.style.cssText="display:inline-flex;vertical-align:middle;margin-right:.4em",r.setAttribute("aria-hidden","true"),p.prepend(r)}const i=t[d.status];if(i&&p){const r=document.createElement("span");r.className=`badge badge-${i.tone} cms-timeline-status`,r.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"),r.append(g,document.createTextNode(i.label)),p.after(r)}})}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",p=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)"};p.forEach((r,g)=>{r.style.opacity="0",r.style.transform=i[s]||"",r.style.transition=`opacity ${t}ms ease, transform ${t}ms ease`;const b=d?p.length-1-g:g;r.style.transitionDelay=o==="stagger"?`${a+b*e}ms`:`${a}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const r=new IntersectionObserver(g=>{g.forEach(b=>{b.isIntersecting&&(b.target.offsetWidth,b.target.style.opacity="1",b.target.style.transform="none",r.unobserve(b.target))})},{threshold:.1});p.forEach(g=>r.observe(g))}))}),C.find(".card[data-collapsible]").each(function(){const n=this.querySelector(".card-header");n&&n.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),C.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=W(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:p,prompt:i,...r}=e||{};a||d||p||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:r.promptPlaceholder||"",inputValue:r.promptDefault||""}),u===null))){if(r.target){const y=$(r.target);r.toggleClass&&y.toggleClass(r.toggleClass),r.addClass&&y.addClass(r.addClass),r.removeClass&&y.removeClass(r.removeClass),u!==null&&(r.setText&&y.text(u),r.setVal&&y.val(u),r.setAttr&&y.attr(r.setAttr,u))}r.href&&(window.location.href=r.href),d&&E.toast(d,{type:r.toastType||"success"}),p&&E.alert(p)}}),Object.keys(r).length&&(o[s]={...t,events:{...t.events,click:r}})):o[s]=t}$.setup(o)}}C.length&&R(C.get(0))}),window.wireCTAButtons=R;function R(m){m.querySelectorAll(".dm-cta-trigger").forEach(c=>{c.__ctaWired||(c.__ctaWired=!0,c.addEventListener("click",async()=>{const x=c.dataset.action,v=c.dataset.entry,k=c.dataset.confirm;let C=S.get("auth_token");if(!C){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:v,scope:c.dataset.scope}:{entryId:v})});try{let s=await o(C);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),C=d,s=await o(C)}}}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 v(C,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=C;return o.appendChild(s),o}function k(C,n){const o=document.createElement("template");o.innerHTML=n||"",C.replaceWith(o.content)}c.forEach(async C=>{const n=C.dataset.collectionAttrs;if(!n)return;if(!x){C.replaceChildren(v("",!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:p}=await d.json();S.set("auth_token",p),s=p,t=await o(s)}}}if(!t.ok){const{error:a}=await t.json().catch(()=>({}));C.replaceChildren(v(a||"Unable to load entries.",!1));return}const{html:e}=await t.json();k(C,e),window.DommaCollectionBrowser?.hydrate&&window.DommaCollectionBrowser.hydrate(document)}catch{C.replaceChildren(v("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),typeof Domma<"u"&&Domma.syntax?.scan&&Domma.syntax.scan(),R(document.body)},100)})(),(function(){const c=document.querySelectorAll("[data-collection-table]");!c.length||typeof T>"u"||!T.create||c.forEach(x=>{let v;try{v=W(x.dataset.payload)}catch{return}const{columns:k,rows:C,search:n,sortable:o,exportable:s,pageSize:t,empty:e,ctaConfig:a,slug:d,exportCfg:p}=v;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 y=document.createElement("button");if(y.className=`btn btn-${a.style||"primary"} dm-cta-trigger`,y.dataset.action=a.action||"",a.scope&&(y.dataset.scope=a.scope),y.dataset.entry=u._entryId||"",a.confirm&&(y.dataset.confirm=a.confirm),a.icon){const h=document.createElement("span");h.dataset.icon=a.icon,y.appendChild(h),y.appendChild(document.createTextNode(" "))}return y.appendChild(document.createTextNode(a.label||"Run")),y}})}const i="col-table-"+Math.random().toString(36).slice(2,7),r=document.createElement("div");r.id=i,x.replaceChildren(r),T.create("#"+i,{data:C,columns:k,search:n,sortable:o,exportable:s,pageSize:t,emptyMessage:e}),a&&R(r),x._dmTable={columns:k,rows:C,slug:d,exportCfg:p,render(g){T.create("#"+i,{data:g,columns:k,search:n,sortable:o,exportable:s,pageSize:t,emptyMessage:e}),a&&R(r)}}})})(),(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 v(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,p={...e.formConfig||{}};p.span==="full"&&s&&(p.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},...p}};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]=e.type==="file"?ve(e,i):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 C(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=W(o.dataset.formInline)}catch{return}o.querySelector(":scope > .cms-form-embed-summary")?.remove();const t=s.fields||[],e=s.settings||{},a=Number(e.columns)===3?3:2,d=e.layout||"grid",p=t.some(h=>h.type==="page-break"),i=new Map;Se(o,t);const r=async h=>{if(o.dataset.triggerBlocked==="true"){const f=o._formLogicRuntime,w=f&&f.submitBlock?f.submitBlock().reasons:[];n(o,w.join(" ")||"This form cannot be submitted yet.",!0);const A=new Error("Blocked by a form trigger");throw A.formSubmitFailed=!0,A}try{const f=o.querySelector(".fb-form-honeypot"),w=f?.querySelector('input[type="text"]')||null,A=f?.querySelector('[name="_t"]')||null,I=Object.assign({},h);delete I._t,f?.dataset.hpName&&delete I[f.dataset.hpName],w!==null&&(I._hp=w.value),A!==null&&(I._t=A.value);const N=new Map(i);oe(o,N);const O=we(I,t,N),B=await H.post(`/api/forms/submit/${s.slug}`,O.files.size?Ce(O.values,O.files).body:O.values);if(B&&B.error){n(o,B.error,!0);const M=new Error(B.error);throw M.formSubmitFailed=!0,M}if(B?.redirect){window.location.href=B.redirect;return}for(ye(o);o.firstChild;)o.removeChild(o.firstChild);n(o,B?.message||e.successMessage||"Thank you for your submission.",!1)}catch(f){throw f.formSubmitFailed||n(o,f.message||"Submission failed. Please try again.",!0),f}},g=Date.now();function b(h){const f=h.querySelector("form");if(!f||f.querySelector(".fb-form-honeypot"))return;const w=document.createElement("div");w.className="fb-form-honeypot",w.setAttribute("aria-hidden","true");const A=document.createElement("input");A.name=f.querySelector('[name="website"]')?"_website":"website",w.dataset.hpName=A.name,A.type="text",A.tabIndex=-1,A.autocomplete="url",A.placeholder="https://",w.appendChild(A);const I=document.createElement("input");I.name="_t",I.type="hidden",I.value=g,w.appendChild(I),f.appendChild(w)}function u(h){if(!window.FormLogicEngine||!t.some(w=>w.logic||Array.isArray(w.triggers)&&w.triggers.length))return null;const f=new window.FormLogicEngine.FormLogicRuntime(s,o,h?{baseValues:h}:void 0);if(f.init(),o._formLogicRuntime=f,o._formLogicObserved)return f;if(o._formLogicObserved=!0,o.parentNode&&typeof MutationObserver<"u"){const w=new MutationObserver(function(A){for(const I of A)for(const N of I.removedNodes)if(N===o||N.nodeType===1&&N.contains&&N.contains(o)){o._formLogicRuntime&&o._formLogicRuntime.destroy(),w.disconnect();return}});w.observe(o.parentNode,{childList:!0,subtree:!1}),o._formLogicObserver=w}return f}let y=!1;if(o.addEventListener("dm-form-end",h=>{const f=h.detail?.validation;if(f&&!f.valid){Z(o,f.missing),ee(f);return}y||(y=!0,r(h.detail?.values||{}).catch(()=>{}).finally(()=>{y=!1}))}),p&&F.wizard){const h=fe(t,s.title),f=h.map(M=>({title:M.title,description:M.description,fields:v(M.fields,a)})),w=h.map(M=>M.fields);let A=0,I=null,N=null;const O=M=>{const z=window.FormLogicEngine;if(!z||!z.validateFields)return;const l=z.validateFields(o,w[A]||[]);l.valid||(M.preventDefault(),M.stopImmediatePropagation(),Z(o,l.missing),ee(l))},B=()=>{const M=w[A]||t;ge(o,e),te(o,e,M),C(o,M),Q(o,M),be(o,i),e.honeypot!==!1&&b(o),I&&I.destroy(),I=u(N?.getStepData?N.getStepData():void 0),o.querySelectorAll(".wizard-next, .wizard-submit").forEach(z=>z.addEventListener("click",O,!0))};N=F.wizard(o,{schema:{steps:f},layout:d,columns:a,finishText:e.submitText||"Finish",onSubmit:r,onStepChange:M=>{oe(o,i),A=M,setTimeout(B,0)}}),o.addEventListener("dm-form-trigger",M=>{if(M.detail?.type!=="goto-step"||!N?.goToStep)return;const z=Number(M.detail.action.step);!Number.isFinite(z)||z<0||z>=f.length||z!==A&&N.goToStep(z)}),Promise.resolve(N).then(B)}else if(F.render){const h=F.render(o,v(t,a),k(t),{submitText:e.submitText||"Submit",layout:d,columns:a,onSubmit:r});Promise.resolve(h).then(function(){he(o,e,d),te(o,e,t),C(o,t),Q(o,t),e.honeypot!==!1&&b(o),u()})}})})();function xe(m,c){const x=document.body,v=x.classList,k=new Set((x.dataset.railSite||"").split(" ").filter(Boolean)),C=()=>window.matchMedia("(min-width: 993px)").matches;m.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=v.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",p=c?"chevrons-right":"chevrons-left";n.innerHTML=`<span data-icon="${a?d:p}"></span>`,window.Domma&&Domma.icons&&Domma.icons.scan(n)},s=a=>{v.toggle("dm-vnav-collapsed",a),S.set("nav_collapsed",a),o()};n.addEventListener("click",()=>s(!v.contains("dm-vnav-collapsed"))),o();const t=m.querySelector(".navbar-container")||m;t.insertBefore(n,t.firstChild?.nextSibling||null);const e=a=>{v.toggle("dm-vnav-autohide",a),v.toggle("dm-vnav-collapsible",!a&&k.has("collapsible")),a?v.remove("dm-vnav-collapsed"):k.has("collapsible")&&S.get("nav_collapsed")===!0&&v.add("dm-vnav-collapsed"),m.classList.remove("dm-nav-revealed"),o()};m.addEventListener("pointerdown",a=>{v.contains("dm-vnav-autohide")&&a.pointerType!=="mouse"&&!m.classList.contains("dm-nav-revealed")&&(m.classList.add("dm-nav-revealed"),a.preventDefault())}),document.addEventListener("pointerdown",a=>{m.contains(a.target)||m.classList.remove("dm-nav-revealed")}),window.E&&typeof E.contextMenu=="function"&&E.contextMenu(m,{items:()=>{const a=v.contains("dm-vnav-autohide"),d=v.contains("dm-vnav-collapsed"),p=S.has("nav_autohide")||S.has("nav_collapsed");return[{header:"Menu"},{label:d?"Expand menu":"Collapse to icons",icon:d?"maximize":"minimize",disabled:!C()||a||!k.has("collapsible"),action:()=>s(!d)},{label:a?"Keep on screen":"Hide when not in use",icon:a?"eye":"eye-off",disabled:!C(),action:()=>{S.set("nav_autohide",!a),e(!a)}},{type:"divider"},{label:"Reset to the site's setting",icon:"refresh",disabled:!p,action:()=>{S.remove("nav_autohide"),S.remove("nav_collapsed"),e(k.has("autohide")),v.toggle("dm-vnav-collapsed",!k.has("autohide")&&k.has("start-collapsed")),o()}}]}})}function ke(m){m.classList.add("dm-nav-scroll-hide");let c=window.scrollY,x=!1;window.addEventListener("scroll",()=>{x||(x=!0,requestAnimationFrame(()=>{x=!1;const v=window.scrollY,k=m.matches(":focus-within")||m.querySelector(".navbar-dropdown.open, .navbar-collapse.show");v>c&&v>m.offsetHeight&&!k?m.classList.add("dm-nav-scrolled-away"):v<c&&m.classList.remove("dm-nav-scrolled-away"),c=v}))},{passive:!0})}function Le(m){let c={};try{c=JSON.parse(m.dataset.contents||"{}")}catch{}const x=m.closest(".site-main"),v=document.querySelector(".site-content"),k=new Set(String(c.exclude||"").split(",").map(i=>i.trim().toLowerCase()).filter(Boolean)),C=i=>!i.closest(".hero, .card, .accordion, .tabs, figure, #site-sidebar"),n=i=>(v?[...v.querySelectorAll(i)]:[]).filter(C).filter(r=>!k.has(r.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 p=[];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 y=2;s.has(u);y++)u=`${b}-${y}`;s.add(u),i.id=u}const r=document.createElement("li");r.className=`dm-contents-item dm-contents-${i.tagName.toLowerCase()}`;const g=document.createElement("a");g.href=`#${i.id}`,g.textContent=i.textContent.trim(),r.appendChild(g),d.appendChild(r),p.push([i,g])}if(t.appendChild(d),m.replaceChildren(t),c.scrollSpy!==!1&&"IntersectionObserver"in window){const i=new Map,r=()=>{const b=p.find(([u])=>i.get(u))||null;p.forEach(([,u])=>{const y=b&&u===b[1];u.classList.toggle("is-active",!!y),y?u.setAttribute("aria-current","location"):u.removeAttribute("aria-current")})},g=new IntersectionObserver(b=>{b.forEach(u=>i.set(u.target,u.isIntersecting)),r()},{rootMargin:"-80px 0px -60% 0px"});p.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 r=()=>{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")),r()}),r(),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(m.style,{left:`${i.x}px`,top:`${i.y}px`,right:"auto",bottom:"auto"}),e.addEventListener("pointerdown",r=>{if(r.target.closest("button"))return;const g=m.getBoundingClientRect(),b=r.clientX-g.left,u=r.clientY-g.top,y=f=>{const w=Math.max(0,Math.min(window.innerWidth-g.width,f.clientX-b)),A=Math.max(0,Math.min(window.innerHeight-40,f.clientY-u));Object.assign(m.style,{left:`${w}px`,top:`${A}px`,right:"auto",bottom:"auto"})},h=()=>{document.removeEventListener("pointermove",y),S.set("contents_position",{x:parseInt(m.style.left,10),y:parseInt(m.style.top,10)})};document.addEventListener("pointermove",y),document.addEventListener("pointerup",h,{once:!0})})}window.E&&typeof E.contextMenu=="function"&&E.contextMenu(m,{items:()=>{const i=x?.classList.contains("contents-collapsible"),r=x?.classList.contains("contents-collapsed"),g=p.find(([,b])=>b.classList.contains("is-active"));return[{header:"Contents"},...i?[{label:r?"Show the contents":"Collapse to a button",icon:r?"list":"minimize",action:()=>m.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")&&(m.addEventListener("pointerdown",i=>{i.pointerType!=="mouse"&&!m.classList.contains("is-revealed")&&(m.classList.add("is-revealed"),i.preventDefault())}),document.addEventListener("pointerdown",i=>{m.contains(i.target)||m.classList.remove("is-revealed")}))}
1
+ import{applyMobileMenu as se,initCarouselPopup as ie}from"/public/js/mobile-ui.mjs?v=20261016-mobile-options";import{installSmoothCarets as re}from"/public/js/smooth-caret.js";import{decodeBase64Json as W}from"/public/js/utf8-base64.mjs";import{makeBadgeEl as U,applyColour as G,applyPill as ce,applyFloat as le,applyHighlight as V,applyItemHighlight as de,itemHighlightAttrs as me,isCurrentItem as K,menuAlign as ue,applyClasses as P,cssClasses as Y,applyEntrance as J}from"/public/js/menu-decor.mjs?v=20261014-menu-entrance";import{initFloatingMenus as pe}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as Q,markInvalidFields as Z,showValidationPopup as ee}from"/public/js/forms.js?v=20261025-forms-files-total";import{buildWizardSteps as fe}from"/public/js/form-steps.mjs";import{applyFormClasses as te,applySubmitButtons as he,applyWizardButtons as ge}from"/public/js/form-submit.mjs";import{captureFileFields as oe,destroyDropzones as ye,fileBlueprint as ve,restoreFileFields as be,setFileLimits as we,splitFiles as Ce,submitBody as Se,totalTooLarge as xe,wireFileFields as ke}from"/public/js/form-files.mjs";document.addEventListener("click",m=>{const c=m.target?.closest?.(".dm-banner__dismiss");c&&(m.preventDefault(),c.closest(".dm-banner")?.remove())}),$(()=>{re(document);const m=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&&m.brand){let s=function(u,y=0){return Array.isArray(u)?u.filter(h=>!(h&&(h.type==="spacer"||h.type==="theme-switcher"))).filter(h=>!(h&&h.type==="separator"&&y===0)).map(h=>h&&h.type==="separator"?{divider:!0}:Array.isArray(h?.items)?{...h,items:s(h.items,y+1)}:h):u};const n={...m.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(m.items||[]),e=m.position==="floating",a=m.orientation==="vertical";x.get(0).querySelector(":scope > .cms-nojs-links")?.remove(),Domma.elements.navbar("#site-navbar",{brand:n,items:t,variant:m.variant||"dark",position:e?"static":m.position||"sticky",collapsible:!0,appearOnHover:!!m.appearOnHover}),Domma.icons.scan("#site-navbar");const d=x.get(0);se(d,m.mobile);const p=()=>{if(!a)return;const y=d.querySelector('a[aria-current="page"]')?.closest(".navbar-dropdown");y&&!y.classList.contains("open")&&y.querySelector(":scope > .navbar-dropdown-toggle")?.click()};d.classList.add(`dm-nav-align-${ue(m)||"centre"}`),a&&x.get(0).classList.add("dm-nav-vertical",m.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left");const i=m.dock||{};a&&!e&&Le(d,m.side==="right"),!a&&i.hideOnScroll&&$e(d),V(d,m),J(d,m),P(d,m.class),e&&(le(x.get(0),m),pe(x.get(0).parentNode||document));const r=d.querySelector(".navbar-toggle"),g=d.querySelector(".navbar-collapse");if(r&&g){let u=null;const y=()=>{g.classList.contains("show")&&r.click()},h=()=>{u&&(u.remove(),u=null),document.body.classList.remove("dm-drawer-open")},f=()=>{u||(u=document.createElement("div"),u.className="dm-navbar-backdrop",u.addEventListener("click",y),document.body.appendChild(u),document.body.classList.add("dm-drawer-open"))};r.addEventListener("click",()=>{requestAnimationFrame(()=>{g.classList.contains("show")?(p(),f()):h()})}),g.addEventListener("click",w=>{w.target.closest("a")&&requestAnimationFrame(h)}),document.addEventListener("keydown",w=>{w.key==="Escape"&&g.classList.contains("show")&&y()})}const b=d.querySelector(".navbar-nav");if(b){const u=Array.from(b.children);let y=0;for(const h of m.items||[])if(!(h&&h.type==="separator")){if(h&&h.type==="spacer"){const f=document.createElement("li"),w=["xs","sm","md","lg","xl","flex"].includes(h.size)?h.size:"md";f.className=`dm-menu-spacer dm-menu-spacer--${w}`,f.setAttribute("aria-hidden","true"),b.insertBefore(f,u[y]||null);continue}if(h&&h.type==="theme-switcher"){const f=document.createElement("li");f.className="dm-menu-theme-switcher";const w=document.createElement("span");w.dataset.themeSwitcher="menu",w.dataset.position="inline",f.appendChild(w),b.insertBefore(f,u[y]||null);continue}y++}window.CMSThemeSwitcher?.scan(b)}d.querySelectorAll("[data-path]").forEach(u=>{const y=u.dataset.path.split(".").map(Number),h=y.length>1,f=y.reduce((q,I)=>q?.items?.[I],{items:t});if(!f)return;f.colour&&G(u,f.colour),f.pill&&!h&&ce(u,f.pill),de(u,f),P(u.closest("li"),f.class);const w=U(f.badge);w&&u.appendChild(w),K(f.url,location.pathname)&&(u.classList.add("active"),u.setAttribute("aria-current","page"))}),p()}const v=$("#site-footer");if(v.length){let e=function(l){return String(l??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")},a=function(l){return e(l).replace(/'/g,"&#39;")},d=function(l){if(l&&l.type==="separator")return'<li class="footer-sep"><hr></li>';if(l&&l.type==="spacer")return`<li class="dm-menu-spacer dm-menu-spacer--${["xs","sm","md","lg","xl","flex"].includes(l.size)?l.size:"md"}" aria-hidden="true"></li>`;if(l&&l.type==="theme-switcher")return'<li class="dm-menu-theme-switcher"><span data-theme-switcher="menu" data-position="inline"></span></li>';const D=a(l.url||"#"),A=e(l.text||""),M=Array.isArray(l.items)&&l.items.length?`<ul>${l.items.map(d).join("")}</ul>`:"",j=me(l),ne=[j.hover&&` data-menu-item-hover="${a(j.hover)}"`,j.active&&` data-menu-item-active="${a(j.active)}"`].filter(Boolean).join(""),ae=j.accent?` style="--dm-menu-highlight:${a(j.accent)}"`:"",X=Y(l.class).join(" ");return`<li${X?` class="${a(X)}"`:""}><a href="${D}"${ne}${ae}>${A}</a>${M}</li>`},i=function(){if(!c.footer)return"";const l=Object.keys(o).filter(A=>n[A]);if(!l.length)return"";const D=Y(c.footer.socialClass).join(" ");return`<div class="footer-social">${l.map(A=>{const{label:M,svg:j}=o[A];return`<a href="${n[A]}" target="_blank" rel="noopener noreferrer" aria-label="${M}" class="footer-social-link${D?` ${a(D)}`:""}">${j}</a>`}).join("")}</div>`},u=function(l){if(!l||l.type==="separator"||l.type==="spacer")return"";if(l.type==="theme-switcher")return`<li class="cms-footer-col">${d(l).replace(/^<li[^>]*>|<\/li>$/g,"")}</li>`;const D=Array.isArray(l.items)&&l.items.length?`<ul>${l.items.map(d).join("")}</ul>`:"",A=b(l)?`<span class="cms-footer-col-title">${e(l.text||"")}</span>`:`<a class="cms-footer-col-title" href="${a(l.url||"#")}">${e(l.text||"")}</a>`,M=["cms-footer-col",...Y(l.class)].join(" ");return`<li class="${a(M)}">${A}${D}</li>`},f=function(l,D,A=!1){return(l||[]).forEach(M=>{M&&(M.type==="separator"||M.type==="spacer"||M.type==="theme-switcher")||(A&&b(M)||D.push(M),Array.isArray(M.items)&&M.items.length&&f(M.items,D))}),D};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:""},p=(()=>{const l=c.cookieConsent;if(!l||l.showSettingsLink===!1)return"";const D=A=>(A||[]).some(M=>M&&(M.url==="#cookie-settings"||D(M.items)));return D(t.primary)||D(t.legal)?"":`<li class="cms-cookie-settings"><a href="#cookie-settings">${e(l.settingsLinkText||"Cookie settings")}</a></li>`})(),r=["columns","row"].includes(c.footer?.layout)?c.footer.layout:"auto",g=r==="columns"||r==="auto"&&t.primary.some(l=>Array.isArray(l?.items)&&l.items.length),b=l=>g&&!l.url&&Array.isArray(l.items)&&l.items.length>0;if(g){const l=m.brand||{},D=l.text||c.title||"",A=l.logo?`<img src="${a(l.logo)}" class="cms-footer-brand-logo" alt="">`:l.icon?'<span class="cms-footer-brand-icon"></span>':"",M=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">${D||A?`<a class="cms-footer-brand-name" href="${a(l.url||"/")}">${A}<span>${e(D)}</span></a>`:""}${M?`<p class="cms-footer-blurb">${e(M)}</p>`:""}${i()}</div>`,t.primary.length&&(s+=`<nav class="footer-nav footer-nav--primary footer-nav--columns" aria-label="Footer"><ul>${t.primary.map(u).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||p)&&(s+=`<nav class="footer-nav footer-nav--legal" aria-label="Legal"><ul>${t.legal.map(d).join("")}${p}</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||p)&&(s+=`<nav class="footer-nav footer-nav--legal"><ul>${t.legal.map(d).join("")}${p}</ul></nav>`),s+=i();s+="</div>",v.get(0).querySelector(":scope > .cms-nojs-links")?.remove();const y=v.get(0).querySelector(":scope > .cms-footer-blocks");y?.remove(),v.html(s),y&&v.get(0).prepend(y),window.CMSThemeSwitcher?.scan(v.get(0));const h=v.get(0).querySelector(".cms-footer-brand-icon");h&&m.brand?.icon&&h.setAttribute("data-icon",m.brand.icon),P(v.get(0),c.footer?.class);const w=[...f(t.primary,[],!0),...f(t.legal,[])];v.get(0).querySelectorAll(".footer-nav a").forEach((l,D)=>{const A=w[D];if(!A)return;A.colour&&G(l,A.colour);const M=U(A.badge);M&&l.appendChild(M),K(A.url,location.pathname)&&(l.classList.add("is-active"),l.setAttribute("aria-current","page"))}),V(v.get(0).querySelector(".footer-nav--primary"),{highlight:t.primaryHighlight}),V(v.get(0).querySelector(".footer-nav--legal"),{highlight:t.legalHighlight}),J(v.get(0).querySelector(".footer-nav--primary"),{entrance:t.primaryEntrance}),J(v.get(0).querySelector(".footer-nav--legal"),{entrance:t.legalEntrance}),P(v.get(0).querySelector(".footer-nav--primary"),t.primaryClass),P(v.get(0).querySelector(".footer-nav--legal"),t.legalClass);const I=c.footer?.showMotionToggle!==!1,z=S.get("reduced_motion"),B=c.footer?.motionDefault,O=z!=null?!!z:B==="on"?!0:B==="off"?!1:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),k=c.footer?.motionControl==="checkbox",N=v.get(0).querySelector(".cms-footer-bottom")||v.get(0).querySelector(".footer-inner");if(I&&N){const l=document.createElement("input");l.type="checkbox",l.className=k?"footer-motion-checkbox":"form-switch-input",l.id="dm-motion-switch",l.checked=O,l.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const D=document.createElement("span");D.className=k?"footer-motion-label":"form-switch-label",D.textContent="Reduce motion";const A=document.createElement("label");A.className=k?"footer-motion-check":"form-switch footer-motion-switch",A.title="Reduce motion",A.appendChild(l),A.appendChild(D),N.appendChild(A)}}const L=document.getElementById("site-sidebar");L&&Ee(L),Domma.icons.scan(),Domma.syntax?.scan&&Domma.syntax.scan();const C=$(".page-body");if(C.length){C.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),C.find(".tabs").each(function(){Domma.elements.tabs(this)}),C.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=p=>{const i=String(p||"").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 p=Number(this.dataset.height);Number.isFinite(p)&&p>0&&(d.height=p)}if(this.dataset.animationDuration){const p=parseInt(this.dataset.animationDuration,10);Number.isFinite(p)&&(d.animationDuration=p)}this.dataset.animationEasing&&(d.animationEasing=this.dataset.animationEasing),this.dataset.ctx&&(this._dmCtxSlides=Array.from(this.querySelectorAll(".carousel-slide")).map(p=>p.cloneNode(!0)),this._dmCarouselOpts=d),this._dmCarousel=Domma.elements.carousel(this,d),ie(this)}),C.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,p=r=>String(r).padStart(2,"0"),i=r=>{const g=Math.max(0,Math.floor(r/1e3)),b=d.querySelector(".dm-timer-time");b&&(b.textContent=`${p(Math.floor(g/86400))}:${p(Math.floor(g%86400/3600))}:${p(Math.floor(g%3600/60))}:${p(g%60)}`)};a&&(n.onTick=i),Domma.elements.timer(this,n),a&&i(n.duration||0)}),C.find("[data-tooltip]:not(.dm-range-slider)").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),C.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 p=e.querySelector(".dm-progression-title");if(d.icon&&p){const r=document.createElement("span");r.dataset.icon=d.icon,r.style.cssText="display:inline-flex;vertical-align:middle;margin-right:.4em",r.setAttribute("aria-hidden","true"),p.prepend(r)}const i=t[d.status];if(i&&p){const r=document.createElement("span");r.className=`badge badge-${i.tone} cms-timeline-status`,r.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"),r.append(g,document.createTextNode(i.label)),p.after(r)}})}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",p=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)"};p.forEach((r,g)=>{r.style.opacity="0",r.style.transform=i[s]||"",r.style.transition=`opacity ${t}ms ease, transform ${t}ms ease`;const b=d?p.length-1-g:g;r.style.transitionDelay=o==="stagger"?`${a+b*e}ms`:`${a}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const r=new IntersectionObserver(g=>{g.forEach(b=>{b.isIntersecting&&(b.target.offsetWidth,b.target.style.opacity="1",b.target.style.transform="none",r.unobserve(b.target))})},{threshold:.1});p.forEach(g=>r.observe(g))}))}),C.find(".card[data-collapsible]").each(function(){const n=this.querySelector(".card-header");n&&n.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),C.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=W(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:p,prompt:i,...r}=e||{};a||d||p||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:r.promptPlaceholder||"",inputValue:r.promptDefault||""}),u===null))){if(r.target){const y=$(r.target);r.toggleClass&&y.toggleClass(r.toggleClass),r.addClass&&y.addClass(r.addClass),r.removeClass&&y.removeClass(r.removeClass),u!==null&&(r.setText&&y.text(u),r.setVal&&y.val(u),r.setAttr&&y.attr(r.setAttr,u))}r.href&&(window.location.href=r.href),d&&E.toast(d,{type:r.toastType||"success"}),p&&E.alert(p)}}),Object.keys(r).length&&(o[s]={...t,events:{...t.events,click:r}})):o[s]=t}$.setup(o)}}C.length&&R(C.get(0))}),window.wireCTAButtons=R;function R(m){m.querySelectorAll(".dm-cta-trigger").forEach(c=>{c.__ctaWired||(c.__ctaWired=!0,c.addEventListener("click",async()=>{const x=c.dataset.action,v=c.dataset.entry,L=c.dataset.confirm;let C=S.get("auth_token");if(!C){E.toast("Please log in to perform this action.",{type:"warning"});return}if(L&&!await E.confirm(L))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:v,scope:c.dataset.scope}:{entryId:v})});try{let s=await o(C);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),C=d,s=await o(C)}}}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 v(C,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=C;return o.appendChild(s),o}function L(C,n){const o=document.createElement("template");o.innerHTML=n||"",C.replaceWith(o.content)}c.forEach(async C=>{const n=C.dataset.collectionAttrs;if(!n)return;if(!x){C.replaceChildren(v("",!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:p}=await d.json();S.set("auth_token",p),s=p,t=await o(s)}}}if(!t.ok){const{error:a}=await t.json().catch(()=>({}));C.replaceChildren(v(a||"Unable to load entries.",!1));return}const{html:e}=await t.json();L(C,e),window.DommaCollectionBrowser?.hydrate&&window.DommaCollectionBrowser.hydrate(document)}catch{C.replaceChildren(v("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),typeof Domma<"u"&&Domma.syntax?.scan&&Domma.syntax.scan(),R(document.body)},100)})(),(function(){const c=document.querySelectorAll("[data-collection-table]");!c.length||typeof T>"u"||!T.create||c.forEach(x=>{let v;try{v=W(x.dataset.payload)}catch{return}const{columns:L,rows:C,search:n,sortable:o,exportable:s,pageSize:t,empty:e,ctaConfig:a,slug:d,exportCfg:p}=v;if(!L?.length)return;if(a){const g=L.findIndex(b=>b.key==="_cta");g!==-1&&(L[g]={key:"_cta",title:"",render:(b,u)=>{const y=document.createElement("button");if(y.className=`btn btn-${a.style||"primary"} dm-cta-trigger`,y.dataset.action=a.action||"",a.scope&&(y.dataset.scope=a.scope),y.dataset.entry=u._entryId||"",a.confirm&&(y.dataset.confirm=a.confirm),a.icon){const h=document.createElement("span");h.dataset.icon=a.icon,y.appendChild(h),y.appendChild(document.createTextNode(" "))}return y.appendChild(document.createTextNode(a.label||"Run")),y}})}const i="col-table-"+Math.random().toString(36).slice(2,7),r=document.createElement("div");r.id=i,x.replaceChildren(r),T.create("#"+i,{data:C,columns:L,search:n,sortable:o,exportable:s,pageSize:t,emptyMessage:e}),a&&R(r),x._dmTable={columns:L,rows:C,slug:d,exportCfg:p,render(g){T.create("#"+i,{data:g,columns:L,search:n,sortable:o,exportable:s,pageSize:t,emptyMessage:e}),a&&R(r)}}})})(),(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 v(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,p={...e.formConfig||{}};p.span==="full"&&s&&(p.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},...p}};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]=e.type==="file"?ve(e,i):i}),t}function L(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 C(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=W(o.dataset.formInline)}catch{return}o.querySelector(":scope > .cms-form-embed-summary")?.remove();const t=s.fields||[],e=s.settings||{},a=Number(e.columns)===3?3:2,d=e.layout||"grid",p=t.some(h=>h.type==="page-break"),i=new Map;we(o,s.fileLimits,i),ke(o,t);const r=async h=>{if(o.dataset.triggerBlocked==="true"){const f=o._formLogicRuntime,w=f&&f.submitBlock?f.submitBlock().reasons:[];n(o,w.join(" ")||"This form cannot be submitted yet.",!0);const q=new Error("Blocked by a form trigger");throw q.formSubmitFailed=!0,q}try{const f=o.querySelector(".fb-form-honeypot"),w=f?.querySelector('input[type="text"]')||null,q=f?.querySelector('[name="_t"]')||null,I=Object.assign({},h);delete I._t,f?.dataset.hpName&&delete I[f.dataset.hpName],w!==null&&(I._hp=w.value),q!==null&&(I._t=q.value);const z=new Map(i);oe(o,z);const B=Ce(I,t,z),O=xe(o,B.files);if(O){n(o,O,!0);const N=new Error(O);throw N.formSubmitFailed=!0,N}const k=await H.post(`/api/forms/submit/${s.slug}`,B.files.size?Se(B.values,B.files).body:B.values);if(k&&k.error){n(o,k.error,!0);const N=new Error(k.error);throw N.formSubmitFailed=!0,N}if(k?.redirect){window.location.href=k.redirect;return}for(ye(o);o.firstChild;)o.removeChild(o.firstChild);n(o,k?.message||e.successMessage||"Thank you for your submission.",!1)}catch(f){throw f.formSubmitFailed||n(o,f.message||"Submission failed. Please try again.",!0),f}},g=Date.now();function b(h){const f=h.querySelector("form");if(!f||f.querySelector(".fb-form-honeypot"))return;const w=document.createElement("div");w.className="fb-form-honeypot",w.setAttribute("aria-hidden","true");const q=document.createElement("input");q.name=f.querySelector('[name="website"]')?"_website":"website",w.dataset.hpName=q.name,q.type="text",q.tabIndex=-1,q.autocomplete="url",q.placeholder="https://",w.appendChild(q);const I=document.createElement("input");I.name="_t",I.type="hidden",I.value=g,w.appendChild(I),f.appendChild(w)}function u(h){if(!window.FormLogicEngine||!t.some(w=>w.logic||Array.isArray(w.triggers)&&w.triggers.length))return null;const f=new window.FormLogicEngine.FormLogicRuntime(s,o,h?{baseValues:h}:void 0);if(f.init(),o._formLogicRuntime=f,o._formLogicObserved)return f;if(o._formLogicObserved=!0,o.parentNode&&typeof MutationObserver<"u"){const w=new MutationObserver(function(q){for(const I of q)for(const z of I.removedNodes)if(z===o||z.nodeType===1&&z.contains&&z.contains(o)){o._formLogicRuntime&&o._formLogicRuntime.destroy(),w.disconnect();return}});w.observe(o.parentNode,{childList:!0,subtree:!1}),o._formLogicObserver=w}return f}let y=!1;if(o.addEventListener("dm-form-end",h=>{const f=h.detail?.validation;if(f&&!f.valid){Z(o,f.missing),ee(f);return}y||(y=!0,r(h.detail?.values||{}).catch(()=>{}).finally(()=>{y=!1}))}),p&&F.wizard){const h=fe(t,s.title),f=h.map(k=>({title:k.title,description:k.description,fields:v(k.fields,a)})),w=h.map(k=>k.fields);let q=0,I=null,z=null;const B=k=>{const N=window.FormLogicEngine;if(!N||!N.validateFields)return;const l=N.validateFields(o,w[q]||[]);l.valid||(k.preventDefault(),k.stopImmediatePropagation(),Z(o,l.missing),ee(l))},O=()=>{const k=w[q]||t;ge(o,e),te(o,e,k),C(o,k),Q(o,k),be(o,i),e.honeypot!==!1&&b(o),I&&I.destroy(),I=u(z?.getStepData?z.getStepData():void 0),o.querySelectorAll(".wizard-next, .wizard-submit").forEach(N=>N.addEventListener("click",B,!0))};z=F.wizard(o,{schema:{steps:f},layout:d,columns:a,finishText:e.submitText||"Finish",onSubmit:r,onStepChange:k=>{oe(o,i),q=k,setTimeout(O,0)}}),o.addEventListener("dm-form-trigger",k=>{if(k.detail?.type!=="goto-step"||!z?.goToStep)return;const N=Number(k.detail.action.step);!Number.isFinite(N)||N<0||N>=f.length||N!==q&&z.goToStep(N)}),Promise.resolve(z).then(O)}else if(F.render){const h=F.render(o,v(t,a),L(t),{submitText:e.submitText||"Submit",layout:d,columns:a,onSubmit:r});Promise.resolve(h).then(function(){he(o,e,d),te(o,e,t),C(o,t),Q(o,t),e.honeypot!==!1&&b(o),u()})}})})();function Le(m,c){const x=document.body,v=x.classList,L=new Set((x.dataset.railSite||"").split(" ").filter(Boolean)),C=()=>window.matchMedia("(min-width: 993px)").matches;m.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=v.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",p=c?"chevrons-right":"chevrons-left";n.innerHTML=`<span data-icon="${a?d:p}"></span>`,window.Domma&&Domma.icons&&Domma.icons.scan(n)},s=a=>{v.toggle("dm-vnav-collapsed",a),S.set("nav_collapsed",a),o()};n.addEventListener("click",()=>s(!v.contains("dm-vnav-collapsed"))),o();const t=m.querySelector(".navbar-container")||m;t.insertBefore(n,t.firstChild?.nextSibling||null);const e=a=>{v.toggle("dm-vnav-autohide",a),v.toggle("dm-vnav-collapsible",!a&&L.has("collapsible")),a?v.remove("dm-vnav-collapsed"):L.has("collapsible")&&S.get("nav_collapsed")===!0&&v.add("dm-vnav-collapsed"),m.classList.remove("dm-nav-revealed"),o()};m.addEventListener("pointerdown",a=>{v.contains("dm-vnav-autohide")&&a.pointerType!=="mouse"&&!m.classList.contains("dm-nav-revealed")&&(m.classList.add("dm-nav-revealed"),a.preventDefault())}),document.addEventListener("pointerdown",a=>{m.contains(a.target)||m.classList.remove("dm-nav-revealed")}),window.E&&typeof E.contextMenu=="function"&&E.contextMenu(m,{items:()=>{const a=v.contains("dm-vnav-autohide"),d=v.contains("dm-vnav-collapsed"),p=S.has("nav_autohide")||S.has("nav_collapsed");return[{header:"Menu"},{label:d?"Expand menu":"Collapse to icons",icon:d?"maximize":"minimize",disabled:!C()||a||!L.has("collapsible"),action:()=>s(!d)},{label:a?"Keep on screen":"Hide when not in use",icon:a?"eye":"eye-off",disabled:!C(),action:()=>{S.set("nav_autohide",!a),e(!a)}},{type:"divider"},{label:"Reset to the site's setting",icon:"refresh",disabled:!p,action:()=>{S.remove("nav_autohide"),S.remove("nav_collapsed"),e(L.has("autohide")),v.toggle("dm-vnav-collapsed",!L.has("autohide")&&L.has("start-collapsed")),o()}}]}})}function $e(m){m.classList.add("dm-nav-scroll-hide");let c=window.scrollY,x=!1;window.addEventListener("scroll",()=>{x||(x=!0,requestAnimationFrame(()=>{x=!1;const v=window.scrollY,L=m.matches(":focus-within")||m.querySelector(".navbar-dropdown.open, .navbar-collapse.show");v>c&&v>m.offsetHeight&&!L?m.classList.add("dm-nav-scrolled-away"):v<c&&m.classList.remove("dm-nav-scrolled-away"),c=v}))},{passive:!0})}function Ee(m){let c={};try{c=JSON.parse(m.dataset.contents||"{}")}catch{}const x=m.closest(".site-main"),v=document.querySelector(".site-content"),L=new Set(String(c.exclude||"").split(",").map(i=>i.trim().toLowerCase()).filter(Boolean)),C=i=>!i.closest(".hero, .card, .accordion, .tabs, figure, #site-sidebar"),n=i=>(v?[...v.querySelectorAll(i)]:[]).filter(C).filter(r=>!L.has(r.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 p=[];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 y=2;s.has(u);y++)u=`${b}-${y}`;s.add(u),i.id=u}const r=document.createElement("li");r.className=`dm-contents-item dm-contents-${i.tagName.toLowerCase()}`;const g=document.createElement("a");g.href=`#${i.id}`,g.textContent=i.textContent.trim(),r.appendChild(g),d.appendChild(r),p.push([i,g])}if(t.appendChild(d),m.replaceChildren(t),c.scrollSpy!==!1&&"IntersectionObserver"in window){const i=new Map,r=()=>{const b=p.find(([u])=>i.get(u))||null;p.forEach(([,u])=>{const y=b&&u===b[1];u.classList.toggle("is-active",!!y),y?u.setAttribute("aria-current","location"):u.removeAttribute("aria-current")})},g=new IntersectionObserver(b=>{b.forEach(u=>i.set(u.target,u.isIntersecting)),r()},{rootMargin:"-80px 0px -60% 0px"});p.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 r=()=>{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")),r()}),r(),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(m.style,{left:`${i.x}px`,top:`${i.y}px`,right:"auto",bottom:"auto"}),e.addEventListener("pointerdown",r=>{if(r.target.closest("button"))return;const g=m.getBoundingClientRect(),b=r.clientX-g.left,u=r.clientY-g.top,y=f=>{const w=Math.max(0,Math.min(window.innerWidth-g.width,f.clientX-b)),q=Math.max(0,Math.min(window.innerHeight-40,f.clientY-u));Object.assign(m.style,{left:`${w}px`,top:`${q}px`,right:"auto",bottom:"auto"})},h=()=>{document.removeEventListener("pointermove",y),S.set("contents_position",{x:parseInt(m.style.left,10),y:parseInt(m.style.top,10)})};document.addEventListener("pointermove",y),document.addEventListener("pointerup",h,{once:!0})})}window.E&&typeof E.contextMenu=="function"&&E.contextMenu(m,{items:()=>{const i=x?.classList.contains("contents-collapsible"),r=x?.classList.contains("contents-collapsed"),g=p.find(([,b])=>b.classList.contains("is-active"));return[{header:"Contents"},...i?[{label:r?"Show the contents":"Collapse to a button",icon:r?"list":"minimize",action:()=>m.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")&&(m.addEventListener("pointerdown",i=>{i.pointerType!=="mouse"&&!m.classList.contains("is-revealed")&&(m.classList.add("is-revealed"),i.preventDefault())}),document.addEventListener("pointerdown",i=>{m.contains(i.target)||m.classList.remove("is-revealed")}))}
@@ -23,11 +23,13 @@ import {keepSubmissionsOut} from '../../services/siteGitignore.js';
23
23
  import {createRequire} from 'module';
24
24
  import {fileURLToPath} from 'url';
25
25
  import path from 'path';
26
- import {deleteForm, ensureCollectionForForm, ensureFormsDir, listForms, readForm, slugify, writeForm} from '../../services/forms.js';
26
+ import {deleteForm, ensureCollectionForForm, ensureFormsDir, listForms, maxSubmissionBytes, publicFileLimits, readForm, slugify, writeForm} from '../../services/forms.js';
27
27
  import {executeAction} from '../../services/actions.js';
28
28
  // The same rules the browser draws the buttons by (a Cancel link must be safe).
29
29
  import {buttonSettingsProblems, formClassProblems, publicButtonSettings} from '../../../public/js/form-submit.mjs';
30
- import {cleanUploadName, fileRefusal} from '../../../public/js/form-files.mjs';
30
+ import {cleanUploadName, countRefusal, fileRefusal, submissionTooLarge, fileRules, fileValueText, isFileValue} from '../../../public/js/form-files.mjs';
31
+ import {sanitiseCsv} from '../../../public/js/collection-export.mjs';
32
+ import {originOf, trustedOrigin} from '../../services/passwordReset.js';
31
33
  import {createTransport, mailFailure, sendFormEmail, TEST_EMAIL_FAILED} from '../../services/email.js';
32
34
  import {formActionFailed, formTriggerNotice} from '../../services/notification-sources.js';
33
35
  import {
@@ -44,7 +46,7 @@ import {getConfig} from '../../config.js';
44
46
  import {authenticate, requireAdmin, requirePermission} from '../../middleware/auth.js';
45
47
  import {hooks} from '../../services/hooks.js';
46
48
  import {pluginDenialFor} from '../../services/collectionAccess.js';
47
- import {saveMedia} from '../../services/content.js';
49
+ import {deleteMedia, saveMedia} from '../../services/content.js';
48
50
  import {servedType} from '../../services/mediaTypes.js';
49
51
  import {v4 as uuidv4} from 'uuid';
50
52
  import {ago, clip, plural} from '../../services/sidebarBadges.js';
@@ -146,79 +148,149 @@ function validateUpload(fieldCfg, part, size) {
146
148
  || servedTypeRefusal(fieldCfg, part.filename || '');
147
149
  }
148
150
 
151
+ // The per-submission cap lives in services/forms.js (the page is told it too), its words in form-files.mjs.
152
+ export {DEFAULT_MAX_SUBMISSION_BYTES, maxSubmissionBytes} from '../../services/forms.js';
153
+ export {submissionTooLarge};
154
+
149
155
  /**
150
156
  * Drain a multipart request into a JSON-shaped body. Text parts become string
151
- * values; file parts are validated against the matching form field, saved to
152
- * `content/media/`, and replaced by a `{ url, name, size, mime }` reference.
157
+ * values; file parts are checked against the matching form field and held in
158
+ * memory, and the field's value becomes its reference - `{url, name, size, mime}`
159
+ * for a single field, a list of them for a `multiple` one - with the url filled
160
+ * in by saveFormFiles() once the whole submission has passed.
153
161
  *
154
- * Throws on validation failure (caller handles HTTP 400). Files are streamed
155
- * into memory up to fastify-multipart's configured limit - fine for the
156
- * resume-pdf / portrait-photo scale; large uploads should still use the admin
157
- * media flow.
162
+ * Throws on validation failure (caller handles HTTP 400): any one file a field
163
+ * would refuse, or more files than a field takes (countRefusal), refuses the
164
+ * submission. Files are held in memory up to fastify-multipart's configured
165
+ * limit, at most fileRules().maxFiles per field, and at most maxSubmissionBytes()
166
+ * for all of them together - the request is refused the moment the running
167
+ * total passes it, before the rest is read. Fine for the resume-pdf /
168
+ * portrait-photo scale; large uploads should still use the admin media flow.
158
169
  *
159
170
  * @param {import('fastify').FastifyRequest} request
160
171
  * @param {object} form
161
- * @returns {Promise<object>}
172
+ * @returns {Promise<{body: object, pending: Map<string, {buffer: Buffer, filename: string}[]>}>}
162
173
  */
163
174
  async function parseMultipartForm(request, form) {
164
175
  const out = {};
165
- const pending = new Map(); // field name -> {buffer, filename}, saved by saveFormFiles()
176
+ const pending = new Map(); // field name -> [{buffer, filename}], saved by saveFormFiles()
166
177
  const fileFields = new Map(
167
178
  (form.fields || []).filter(f => f.type === 'file').map(f => [f.name, f])
168
179
  );
180
+ const drain = async (part) => { for await (const _ of part.file) { /* drain */ } };
181
+ const limit = maxSubmissionBytes();
182
+ let held = 0; // bytes of every file part so far, all fields
169
183
 
170
184
  for await (const part of request.parts()) {
171
185
  if (part.type === 'file') {
172
186
  const fieldCfg = fileFields.get(part.fieldname);
187
+ const list = pending.get(part.fieldname) || [];
188
+ const rules = fieldCfg ? fileRules(fieldCfg) : null;
173
189
  // An unknown file field, or a second file for one that holds one: drain and
174
190
  // skip rather than 400, so a form that gains a file input later doesn't reject
175
191
  // older submissions in-flight from a stale page.
176
- if (!fieldCfg || pending.has(part.fieldname)) {
177
- for await (const _ of part.file) { /* drain */ }
192
+ if (!fieldCfg || (!rules.multiple && list.length)) {
193
+ await drain(part);
178
194
  continue;
179
195
  }
196
+ // More than a multiple field takes refuses the submission, before the
197
+ // extra file is read into memory.
198
+ if (list.length >= rules.maxFiles) {
199
+ await drain(part);
200
+ throw new Error(countRefusal(fieldCfg, list.length + 1));
201
+ }
180
202
  const chunks = [];
181
- for await (const chunk of part.file) chunks.push(chunk);
203
+ let over = false;
204
+ for await (const chunk of part.file) {
205
+ if (over) continue; // the rest of this part is drained, not held
206
+ held += chunk.length;
207
+ if (held > limit) {
208
+ // Stop holding at once: drop what is read, then refuse
209
+ over = true;
210
+ chunks.length = 0;
211
+ pending.clear();
212
+ continue;
213
+ }
214
+ chunks.push(chunk);
215
+ }
216
+ if (over) throw new Error(submissionTooLarge(limit));
182
217
  const buffer = Buffer.concat(chunks);
183
218
  // Over the server's own upload cap the part arrives cut short, not refused
184
219
  const size = part.file.truncated ? Infinity : buffer.length;
185
220
  const err = validateUpload(fieldCfg, part, size);
186
221
  if (err) throw new Error(err);
187
222
  if (buffer.length === 0) continue; // empty file input - skip
188
- pending.set(part.fieldname, {buffer, filename: part.filename});
189
- out[part.fieldname] = {
190
- url: '',
191
- name: part.filename,
192
- size: buffer.length,
193
- mime: part.mimetype
194
- };
195
- } else if (!pending.has(part.fieldname)) {
196
- // Text field - last value wins on duplicates (browser-standard); a file
197
- // already sent under the name keeps it.
223
+ list.push({buffer, filename: part.filename});
224
+ pending.set(part.fieldname, list);
225
+ const ref = {url: '', name: part.filename, size: buffer.length, mime: part.mimetype};
226
+ if (rules.multiple) (Array.isArray(out[part.fieldname]) ? out[part.fieldname] : (out[part.fieldname] = [])).push(ref);
227
+ else out[part.fieldname] = ref;
228
+ } else if (!fileFields.has(part.fieldname)) {
229
+ // Text field - last value wins on duplicates (browser-standard). A file
230
+ // field's value only ever comes from its file parts (fileFieldsOnly).
198
231
  out[part.fieldname] = part.value;
199
232
  }
200
233
  }
201
234
  return {body: out, pending};
202
235
  }
203
236
 
237
+ /**
238
+ * A file field's value is only ever what this server made of the files it was
239
+ * sent: whatever a JSON body (or a text part) says one holds - a reference to
240
+ * any address - is dropped, so an entry never links to a file nobody uploaded.
241
+ *
242
+ * @param {object} form
243
+ * @param {object} body - mutated
244
+ * @param {Map<string, unknown>|null} pending
245
+ */
246
+ function fileFieldsOnly(form, body, pending) {
247
+ if (!body || typeof body !== 'object') return;
248
+ for (const f of form.fields || []) {
249
+ if (f.type !== 'file' || !f.name || pending?.has(f.name)) continue;
250
+ if (body[f.name] !== undefined && body[f.name] !== '') body[f.name] = '';
251
+ }
252
+ }
253
+
204
254
  /**
205
255
  * Write the files of a submission that passed every check, into the reference objects
206
256
  * parseMultipartForm() left in `body`. Only the fields that will be kept are written -
207
257
  * nothing a bot, a rate-limited or refused submission or a switched-off field sent
208
258
  * reaches content/media/.
209
259
  *
210
- * @param {Map<string, {buffer: Buffer, filename: string}>} pending
260
+ * If one cannot be written, the ones already written for this submission are removed
261
+ * and the error is thrown - a submission keeps all its files or none.
262
+ *
263
+ * @param {Map<string, {buffer: Buffer, filename: string}[]>} pending
211
264
  * @param {object} body
212
265
  * @param {Set<string>} keep - field names the submission keeps
266
+ * @returns {Promise<string[]>} the media file names written (for removeFormFiles)
213
267
  */
214
268
  async function saveFormFiles(pending, body, keep) {
215
- for (const [name, file] of pending) {
216
- if (!keep.has(name)) {
217
- delete body[name];
218
- continue;
269
+ const written = [];
270
+ try {
271
+ for (const [name, files] of pending) {
272
+ if (!keep.has(name)) {
273
+ delete body[name];
274
+ continue;
275
+ }
276
+ const refs = Array.isArray(body[name]) ? body[name] : [body[name]];
277
+ for (let i = 0; i < files.length; i++) {
278
+ const saved = await saveMedia(safeFilename(files[i].filename), files[i].buffer);
279
+ written.push(saved.name);
280
+ refs[i].url = saved.url;
281
+ }
219
282
  }
220
- const saved = await saveMedia(safeFilename(file.filename), file.buffer);
221
- body[name].url = saved.url;
283
+ } catch (err) {
284
+ await removeFormFiles(written);
285
+ throw err;
286
+ }
287
+ return written;
288
+ }
289
+
290
+ /** Remove a submission's written files again (it was not stored after all). Never throws. */
291
+ async function removeFormFiles(names) {
292
+ for (const name of names || []) {
293
+ try { await deleteMedia(name); } catch { /* already gone */ }
222
294
  }
223
295
  }
224
296
 
@@ -244,17 +316,34 @@ function isRateLimited(slug, ip, limitPerMinute) {
244
316
  return false;
245
317
  }
246
318
 
247
- function submissionsToCSV(form, entries) {
319
+ /** A stored answer as plain text: lists joined with "; ", objects as JSON. */
320
+ function answerCsvText(raw) {
321
+ if (raw == null) return '';
322
+ if (Array.isArray(raw)) return raw.map(v => (v && typeof v === 'object' ? JSON.stringify(v) : String(v))).join('; ');
323
+ if (typeof raw === 'object') return JSON.stringify(raw);
324
+ return String(raw);
325
+ }
326
+
327
+ /**
328
+ * One CSV cell: quoted, inner quotes doubled, and a leading =, +, -, @, TAB or CR
329
+ * neutralised (sanitiseCsv, as the public collection export does) - a visitor's
330
+ * answer must not run as a formula in the spreadsheet an admin opens.
331
+ */
332
+ function csvCell(text) {
333
+ return `"${sanitiseCsv(String(text ?? '')).replace(/"/g, '""')}"`;
334
+ }
335
+
336
+ export function submissionsToCSV(form, entries) {
248
337
  const fields = (form.fields || []).filter(f => f.type !== 'page-break' && f.type !== 'spacer');
249
- const headers = [...fields.map(f => `"${f.label || f.name}"`), '"Date"'];
338
+ const headers = [...fields.map(f => csvCell(f.label || f.name)), csvCell('Date')];
250
339
  const rows = entries.map(e => {
251
340
  const cols = fields.map(f => {
252
341
  const raw = e.data?.[f.name] ?? '';
253
- const str = Array.isArray(raw) ? raw.join('; ') : String(raw);
254
- const val = str.replace(/"/g, '""');
255
- return `"${val}"`;
342
+ // Files: "cv.pdf (/media/…)", several joined with "; " like any list. Anything
343
+ // else in the column (an older text answer) is written as it is.
344
+ return csvCell(isFileValue(raw) ? fileValueText(raw) : answerCsvText(raw));
256
345
  });
257
- cols.push(`"${e.meta?.createdAt || ''}"`);
346
+ cols.push(csvCell(e.meta?.createdAt || ''));
258
347
  return cols.join(',');
259
348
  });
260
349
  return [headers.join(','), ...rows].join('\n');
@@ -549,7 +638,7 @@ export async function formsRoutes(fastify) {
549
638
  // A Cancel link saved before the check (or written by hand) is
550
639
  // not handed to the page.
551
640
  if (safe.settings) safe.settings = publicButtonSettings(safe.settings);
552
- return safe;
641
+ return publicFileLimits(safe);
553
642
  } catch {
554
643
  return reply.status(404).send({ error: 'Form not found.' });
555
644
  }
@@ -812,6 +901,7 @@ export async function formsRoutes(fastify) {
812
901
  return reply.status(400).send({ error: err.message });
813
902
  }
814
903
  }
904
+ fileFieldsOnly(form, body, pendingFiles);
815
905
  const settings = form.settings || {};
816
906
 
817
907
  // Best-effort auth - never reject, just enrich.
@@ -915,7 +1005,15 @@ export async function formsRoutes(fastify) {
915
1005
  }
916
1006
 
917
1007
  // The checks are done: only now do a submission's files reach content/media/
918
- if (pendingFiles?.size) await saveFormFiles(pendingFiles, body, record ? visibleFieldNames : new Set());
1008
+ let writtenFiles = [];
1009
+ if (pendingFiles?.size) {
1010
+ try {
1011
+ writtenFiles = await saveFormFiles(pendingFiles, body, record ? visibleFieldNames : new Set());
1012
+ } catch (err) {
1013
+ fastify.log.warn(`[forms] Saving the files of a "${slug}" submission failed: ${err.message}`);
1014
+ return reply.status(500).send({error: 'Your files could not be saved, so the submission was not sent. Please try again.'});
1015
+ }
1016
+ }
919
1017
 
920
1018
  // Build submission data - only include visible fields
921
1019
  const data = {};
@@ -970,12 +1068,15 @@ export async function formsRoutes(fastify) {
970
1068
  // storage is explicitly disabled with no slug. Fail loudly
971
1069
  // rather than silently lose submissions.
972
1070
  fastify.log.warn(`[forms] Submission for "${slug}" had no target collection "${targetSlug}"`);
1071
+ await removeFormFiles(writtenFiles);
973
1072
  return reply.status(500).send({
974
1073
  error: `Submission not saved - target collection "${targetSlug}" does not exist. Please contact the site administrator.`
975
1074
  });
976
1075
  }
977
1076
  } catch (err) {
978
1077
  fastify.log.warn(`[forms] Collection write failed for "${slug}" → "${targetSlug}": ${err.message}`);
1078
+ // Not stored, so its files are not kept either
1079
+ await removeFormFiles(writtenFiles);
979
1080
  // Distinguish validation errors (user-actionable) from other
980
1081
  // failures (admin needs to look) so the message helps the right
981
1082
  // person. Validation errors typically start with "Validation failed:".
@@ -999,7 +1100,10 @@ export async function formsRoutes(fastify) {
999
1100
  subject: `${emailAction.subjectPrefix || `[${form.title}]`} New submission`,
1000
1101
  formTitle: form.title,
1001
1102
  fields: form.fields,
1002
- data
1103
+ data,
1104
+ // File links in the email are absolute: site.json baseUrl, else an address an
1105
+ // admin has signed in at (trustedOrigin - never just this request's Host)
1106
+ baseUrl: await trustedOrigin(originOf(request)).catch(() => '')
1003
1107
  });
1004
1108
  } catch (err) {
1005
1109
  fastify.log.warn(`[forms] Email send failed for "${slug}": ${err.message}`);
@@ -271,6 +271,9 @@ export function validateEntryData(schema, data, excused = []) {
271
271
  const skip = new Set(excused);
272
272
  for (const field of (schema.fields || [])) {
273
273
  if (skip.has(field.name)) continue;
274
+ // Files only arrive through a form, which enforces its own `required`; the entry
275
+ // editor cannot add one, so a required file field must not stop an entry saving.
276
+ if (field.type === 'file') continue;
274
277
  const val = data[field.name];
275
278
  const isEmpty = val === undefined || val === null || val === '';
276
279
  if (field.required && isEmpty) {
@@ -5,6 +5,7 @@
5
5
  import nodemailer from 'nodemailer';
6
6
  import {signedOnly} from './codePolicy.js';
7
7
  import {normaliseMailHost, resolveMailHost} from './mailHost.js';
8
+ import {fileRefs, fileUrl, fileValueText} from '../../public/js/form-files.mjs';
8
9
 
9
10
  /**
10
11
  * What a person is told when mail could not be sent. The mail server's own
@@ -283,14 +284,24 @@ export async function sendEmail(transport, {from, fromName, to, subject, html, t
283
284
  * Builds a generic table of field→value pairs from the submitted data.
284
285
  *
285
286
  * @param {import('nodemailer').Transporter} transport
286
- * @param {{ from: string, fromName: string, to: string, subject: string, formTitle: string, fields: Array<{name: string, label: string}>, data: Record<string, unknown> }} opts
287
+ * A file field's value (one reference, or a list from a `multiple` field) is shown
288
+ * as a link per file, made absolute with `baseUrl` (site.json) when the site has one.
289
+ *
290
+ * @param {import('nodemailer').Transporter} transport
291
+ * @param {{ from: string, fromName: string, to: string, subject: string, formTitle: string, fields: Array<{name: string, label: string, type?: string}>, data: Record<string, unknown>, baseUrl?: string }} opts
287
292
  * @returns {Promise<void>}
288
293
  * @throws {Error} If sending the email fails.
289
294
  */
290
- export async function sendFormEmail(transport, { from, fromName, to, subject, formTitle, fields, data }) {
295
+ export async function sendFormEmail(transport, { from, fromName, to, subject, formTitle, fields, data, baseUrl = '' }) {
296
+ const isFile = (field) => field.type === 'file' && fileRefs(data[field.name]).length > 0;
297
+ const fileLinks = (val) => fileRefs(val).map((f) => {
298
+ const url = fileUrl(f, baseUrl);
299
+ const name = escapeHtml(f.name || url);
300
+ return /^(https?:\/\/|\/)/i.test(url) ? `<a href="${escapeHtml(url)}">${name}</a>` : name;
301
+ }).join('<br>');
291
302
  const rows = fields.map(field => {
292
303
  const val = data[field.name] ?? '';
293
- const safe = escapeHtml(String(val)).replace(/\n/g, '<br>');
304
+ const safe = isFile(field) ? fileLinks(val) : escapeHtml(String(val)).replace(/\n/g, '<br>');
294
305
  const safeLabel = escapeHtml(field.label || field.name);
295
306
  return `
296
307
  <tr>
@@ -301,7 +312,7 @@ export async function sendFormEmail(transport, { from, fromName, to, subject, fo
301
312
 
302
313
  const plainRows = fields.map(field => {
303
314
  const val = data[field.name] ?? '';
304
- return `${field.label || field.name}: ${val}`;
315
+ return `${field.label || field.name}: ${isFile(field) ? fileValueText(val, {base: baseUrl}) : val}`;
305
316
  }).join('\n');
306
317
 
307
318
  const html = `