domma-cms 1.10.15 → 1.10.16

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 ae,initCarouselPopup as se}from"/public/js/mobile-ui.mjs?v=20261016-mobile-options";import{installSmoothCarets as ie}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 re,applyFloat as ce,applyHighlight as V,applyItemHighlight as le,itemHighlightAttrs as de,isCurrentItem as K,menuAlign as me,applyClasses as P,cssClasses as Y,applyEntrance as J}from"/public/js/menu-decor.mjs?v=20261014-menu-entrance";import{initFloatingMenus as ue}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=20261015-smooth-caret";import{buildWizardSteps as pe}from"/public/js/form-steps.mjs";import{applyFormClasses as te,applySubmitButtons as fe,applyWizardButtons as he}from"/public/js/form-submit.mjs";document.addEventListener("click",p=>{const r=p.target?.closest?.(".dm-banner__dismiss");r&&(p.preventDefault(),r.closest(".dm-banner")?.remove())}),$(()=>{ie(document);const p=window.__CMS_NAV__||{},r=window.__CMS_SITE__||{};if(r.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 l of[...document.body.classList])l.startsWith("dm-custom-")&&document.body.classList.remove(l);a.custom&&document.body.classList.add(a.custom)};const n=r.autoTheme;t(s()),setInterval(()=>t(s()),6e4)}const x=$("#site-navbar");if(x.length&&p.brand){let s=function(f,u=0){return Array.isArray(f)?f.filter(m=>!(m&&(m.type==="spacer"||m.type==="theme-switcher"))).filter(m=>!(m&&m.type==="separator"&&u===0)).map(m=>m&&m.type==="separator"?{divider:!0}:Array.isArray(m?.items)?{...m,items:s(m.items,u+1)}:m):f};const n={...p.brand},o=n.size&&n.size!=="md"?` navbar-brand-${n.size}`:"";if(n.logo||n.icon||n.tagline){let f="";n.logo?f+=`<img src="${n.logo}" class="navbar-brand-logo" alt="${n.text||""}">`:n.icon&&(f+=`<span data-icon="${n.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),n.text&&(f+=`<span class="navbar-brand-text${o}">${n.text}</span>`),n.tagline&&(f+=`<small class="navbar-brand-tagline">${n.tagline}</small>`),n.html=f}else o&&n.text&&(n.html=`<span class="navbar-brand-text${o}">${n.text}</span>`);const t=s(p.items||[]),e=p.position==="floating",a=p.orientation==="vertical";x.get(0).querySelector(":scope > .cms-nojs-links")?.remove(),Domma.elements.navbar("#site-navbar",{brand:n,items:t,variant:p.variant||"dark",position:e?"static":p.position||"sticky",collapsible:!0,appearOnHover:!!p.appearOnHover}),Domma.icons.scan("#site-navbar");const l=x.get(0);ae(l,p.mobile);const h=()=>{if(!a)return;const u=l.querySelector('a[aria-current="page"]')?.closest(".navbar-dropdown");u&&!u.classList.contains("open")&&u.querySelector(":scope > .navbar-dropdown-toggle")?.click()};l.classList.add(`dm-nav-align-${me(p)||"centre"}`),a&&x.get(0).classList.add("dm-nav-vertical",p.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left");const i=p.dock||{};a&&!e&&ge(l,p.side==="right"),!a&&i.hideOnScroll&&ye(l),V(l,p),J(l,p),P(l,p.class),e&&(ce(x.get(0),p),ue(x.get(0).parentNode||document));const c=l.querySelector(".navbar-toggle"),g=l.querySelector(".navbar-collapse");if(c&&g){let f=null;const u=()=>{g.classList.contains("show")&&c.click()},m=()=>{f&&(f.remove(),f=null),document.body.classList.remove("dm-drawer-open")},v=()=>{f||(f=document.createElement("div"),f.className="dm-navbar-backdrop",f.addEventListener("click",u),document.body.appendChild(f),document.body.classList.add("dm-drawer-open"))};c.addEventListener("click",()=>{requestAnimationFrame(()=>{g.classList.contains("show")?(h(),v()):m()})}),g.addEventListener("click",C=>{C.target.closest("a")&&requestAnimationFrame(m)}),document.addEventListener("keydown",C=>{C.key==="Escape"&&g.classList.contains("show")&&u()})}const b=l.querySelector(".navbar-nav");if(b){const f=Array.from(b.children);let u=0;for(const m of p.items||[])if(!(m&&m.type==="separator")){if(m&&m.type==="spacer"){const v=document.createElement("li"),C=["xs","sm","md","lg","xl","flex"].includes(m.size)?m.size:"md";v.className=`dm-menu-spacer dm-menu-spacer--${C}`,v.setAttribute("aria-hidden","true"),b.insertBefore(v,f[u]||null);continue}if(m&&m.type==="theme-switcher"){const v=document.createElement("li");v.className="dm-menu-theme-switcher";const C=document.createElement("span");C.dataset.themeSwitcher="menu",C.dataset.position="inline",v.appendChild(C),b.insertBefore(v,f[u]||null);continue}u++}window.CMSThemeSwitcher?.scan(b)}l.querySelectorAll("[data-path]").forEach(f=>{const u=f.dataset.path.split(".").map(Number),m=u.length>1,v=u.reduce((D,q)=>D?.items?.[q],{items:t});if(!v)return;v.colour&&G(f,v.colour),v.pill&&!m&&re(f,v.pill),le(f,v),P(f.closest("li"),v.class);const C=U(v.badge);C&&f.appendChild(C),K(v.url,location.pathname)&&(f.classList.add("active"),f.setAttribute("aria-current","page"))}),h()}const y=$("#site-footer");if(y.length){let e=function(d){return String(d??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")},a=function(d){return e(d).replace(/'/g,"&#39;")},l=function(d){if(d&&d.type==="separator")return'<li class="footer-sep"><hr></li>';if(d&&d.type==="spacer")return`<li class="dm-menu-spacer dm-menu-spacer--${["xs","sm","md","lg","xl","flex"].includes(d.size)?d.size:"md"}" aria-hidden="true"></li>`;if(d&&d.type==="theme-switcher")return'<li class="dm-menu-theme-switcher"><span data-theme-switcher="menu" data-position="inline"></span></li>';const M=a(d.url||"#"),L=e(d.text||""),A=Array.isArray(d.items)&&d.items.length?`<ul>${d.items.map(l).join("")}</ul>`:"",B=de(d),oe=[B.hover&&` data-menu-item-hover="${a(B.hover)}"`,B.active&&` data-menu-item-active="${a(B.active)}"`].filter(Boolean).join(""),ne=B.accent?` style="--dm-menu-highlight:${a(B.accent)}"`:"",X=Y(d.class).join(" ");return`<li${X?` class="${a(X)}"`:""}><a href="${M}"${oe}${ne}>${L}</a>${A}</li>`},i=function(){if(!r.footer)return"";const d=Object.keys(o).filter(L=>n[L]);if(!d.length)return"";const M=Y(r.footer.socialClass).join(" ");return`<div class="footer-social">${d.map(L=>{const{label:A,svg:B}=o[L];return`<a href="${n[L]}" target="_blank" rel="noopener noreferrer" aria-label="${A}" class="footer-social-link${M?` ${a(M)}`:""}">${B}</a>`}).join("")}</div>`},f=function(d){if(!d||d.type==="separator"||d.type==="spacer")return"";if(d.type==="theme-switcher")return`<li class="cms-footer-col">${l(d).replace(/^<li[^>]*>|<\/li>$/g,"")}</li>`;const M=Array.isArray(d.items)&&d.items.length?`<ul>${d.items.map(l).join("")}</ul>`:"",L=b(d)?`<span class="cms-footer-col-title">${e(d.text||"")}</span>`:`<a class="cms-footer-col-title" href="${a(d.url||"#")}">${e(d.text||"")}</a>`,A=["cms-footer-col",...Y(d.class)].join(" ");return`<li class="${a(A)}">${L}${M}</li>`},v=function(d,M,L=!1){return(d||[]).forEach(A=>{A&&(A.type==="separator"||A.type==="spacer"||A.type==="theme-switcher")||(L&&b(A)||M.push(A),Array.isArray(A.items)&&A.items.length&&v(A.items,M))}),M};const n=r.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:""},h=(()=>{const d=r.cookieConsent;if(!d||d.showSettingsLink===!1)return"";const M=L=>(L||[]).some(A=>A&&(A.url==="#cookie-settings"||M(A.items)));return M(t.primary)||M(t.legal)?"":`<li class="cms-cookie-settings"><a href="#cookie-settings">${e(d.settingsLinkText||"Cookie settings")}</a></li>`})(),c=["columns","row"].includes(r.footer?.layout)?r.footer.layout:"auto",g=c==="columns"||c==="auto"&&t.primary.some(d=>Array.isArray(d?.items)&&d.items.length),b=d=>g&&!d.url&&Array.isArray(d.items)&&d.items.length>0;if(g){const d=p.brand||{},M=d.text||r.title||"",L=d.logo?`<img src="${a(d.logo)}" class="cms-footer-brand-logo" alt="">`:d.icon?'<span class="cms-footer-brand-icon"></span>':"",A=String(r.footer?.blurb||"").trim();s='<div class="footer-inner footer-inner--columns container"><div class="cms-footer-top">',s+=`<div class="cms-footer-brand">${M||L?`<a class="cms-footer-brand-name" href="${a(d.url||"/")}">${L}<span>${e(M)}</span></a>`:""}${A?`<p class="cms-footer-blurb">${e(A)}</p>`:""}${i()}</div>`,t.primary.length&&(s+=`<nav class="footer-nav footer-nav--primary footer-nav--columns" aria-label="Footer"><ul>${t.primary.map(f).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||h)&&(s+=`<nav class="footer-nav footer-nav--legal" aria-label="Legal"><ul>${t.legal.map(l).join("")}${h}</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(l).join("")}</ul></nav>`),(t.legal.length||h)&&(s+=`<nav class="footer-nav footer-nav--legal"><ul>${t.legal.map(l).join("")}${h}</ul></nav>`),s+=i();s+="</div>",y.get(0).querySelector(":scope > .cms-nojs-links")?.remove();const u=y.get(0).querySelector(":scope > .cms-footer-blocks");u?.remove(),y.html(s),u&&y.get(0).prepend(u),window.CMSThemeSwitcher?.scan(y.get(0));const m=y.get(0).querySelector(".cms-footer-brand-icon");m&&p.brand?.icon&&m.setAttribute("data-icon",p.brand.icon),P(y.get(0),r.footer?.class);const C=[...v(t.primary,[],!0),...v(t.legal,[])];y.get(0).querySelectorAll(".footer-nav a").forEach((d,M)=>{const L=C[M];if(!L)return;L.colour&&G(d,L.colour);const A=U(L.badge);A&&d.appendChild(A),K(L.url,location.pathname)&&(d.classList.add("is-active"),d.setAttribute("aria-current","page"))}),V(y.get(0).querySelector(".footer-nav--primary"),{highlight:t.primaryHighlight}),V(y.get(0).querySelector(".footer-nav--legal"),{highlight:t.legalHighlight}),J(y.get(0).querySelector(".footer-nav--primary"),{entrance:t.primaryEntrance}),J(y.get(0).querySelector(".footer-nav--legal"),{entrance:t.legalEntrance}),P(y.get(0).querySelector(".footer-nav--primary"),t.primaryClass),P(y.get(0).querySelector(".footer-nav--legal"),t.legalClass);const q=r.footer?.showMotionToggle!==!1,z=S.get("reduced_motion"),j=r.footer?.motionDefault,I=z!=null?!!z:j==="on"?!0:j==="off"?!1:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),N=r.footer?.motionControl==="checkbox",O=y.get(0).querySelector(".cms-footer-bottom")||y.get(0).querySelector(".footer-inner");if(q&&O){const d=document.createElement("input");d.type="checkbox",d.className=N?"footer-motion-checkbox":"form-switch-input",d.id="dm-motion-switch",d.checked=I,d.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const M=document.createElement("span");M.className=N?"footer-motion-label":"form-switch-label",M.textContent="Reduce motion";const L=document.createElement("label");L.className=N?"footer-motion-check":"form-switch footer-motion-switch",L.title="Reduce motion",L.appendChild(d),L.appendChild(M),O.appendChild(L)}}const k=document.getElementById("site-sidebar");k&&ve(k),Domma.icons.scan(),Domma.syntax?.scan&&Domma.syntax.scan();const w=$(".page-body");if(w.length){w.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),w.find(".tabs").each(function(){Domma.elements.tabs(this)}),w.find(".carousel").each(function(){const n={primary:"--dm-primary",secondary:"--dm-secondary",accent:"--dm-primary",text:"--dm-text",surface:"--dm-surface",background:"--dm-background",success:"--dm-success",warning:"--dm-warning",danger:"--dm-danger",info:"--dm-info"},o=h=>{const i=String(h||"").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 l={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 h=Number(this.dataset.height);Number.isFinite(h)&&h>0&&(l.height=h)}if(this.dataset.animationDuration){const h=parseInt(this.dataset.animationDuration,10);Number.isFinite(h)&&(l.animationDuration=h)}this.dataset.animationEasing&&(l.animationEasing=this.dataset.animationEasing),this.dataset.ctx&&(this._dmCtxSlides=Array.from(this.querySelectorAll(".carousel-slide")).map(h=>h.cloneNode(!0)),this._dmCarouselOpts=l),this._dmCarousel=Domma.elements.carousel(this,l),se(this)}),w.find(".dm-countdown").each(function(){const n={autoStart:!0},o=String(this.dataset.format||"").trim(),s=String(this.dataset.to||"").trim(),t=/^(\d{4})-(\d{2})-(\d{2})$/.exec(s),e=t?new Date(+t[1],+t[2]-1,+t[3]).getTime():s?new Date(s).getTime():NaN;Number.isFinite(e)?n.duration=Math.max(0,e-Date.now()):this.dataset.duration&&(n.duration=(parseInt(this.dataset.duration,10)||0)*1e3);const a=/^dd:/i.test(o);n.format=a?"hh:mm:ss":o||(n.duration>=36e5?"hh:mm:ss":"mm:ss");const l=this,h=c=>String(c).padStart(2,"0"),i=c=>{const g=Math.max(0,Math.floor(c/1e3)),b=l.querySelector(".dm-timer-time");b&&(b.textContent=`${h(Math.floor(g/86400))}:${h(Math.floor(g%86400/3600))}:${h(Math.floor(g%3600/60))}:${h(g%60)}`)};a&&(n.onTick=i),Domma.elements.timer(this,n),a&&i(n.duration||0)}),w.find("[data-tooltip]").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),w.find(".dm-progression").each(function(){const n=Array.from(this.querySelectorAll(".dm-progression-item")).map(t=>{const e={title:t.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:t.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return t.dataset.date&&(e.date=t.dataset.date),t.dataset.status&&(e.status=t.dataset.status),t.dataset.eventIcon&&(e.icon=t.dataset.eventIcon),t.dataset.entryId&&(e._entryId=t.dataset.entryId),e}),o=this.dataset.layout==="centred"?"centered":this.dataset.layout||"vertical",s=Domma.elements.progression(this,{layout:o,theme:this.dataset.theme||"minimal",mode:this.dataset.mode||"timeline",statusIcons:!0,allowHtml:!0,...n.length?{items:n}:{}});if((this.dataset.mode||"timeline")==="timeline"){const t={completed:{label:"Completed",icon:"check-circle",tone:"success"},planned:{label:"Planned",icon:"circle",tone:"secondary"},"in-progress":{label:"In progress",icon:"clock",tone:"primary"},blocked:{label:"Blocked",icon:"alert-circle",tone:"danger"}};this.querySelectorAll(".dm-progression-item").forEach((e,a)=>{const l=n[a];if(!l)return;const h=e.querySelector(".dm-progression-title");if(l.icon&&h){const c=document.createElement("span");c.dataset.icon=l.icon,c.style.cssText="display:inline-flex;vertical-align:middle;margin-right:.4em",c.setAttribute("aria-hidden","true"),h.prepend(c)}const i=t[l.status];if(i&&h){const c=document.createElement("span");c.className=`badge badge-${i.tone} cms-timeline-status`,c.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"),c.append(g,document.createTextNode(i.label)),h.after(c)}})}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,l=n.dataset.revealDirection==="rtl",h=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)"};h.forEach((c,g)=>{c.style.opacity="0",c.style.transform=i[s]||"",c.style.transition=`opacity ${t}ms ease, transform ${t}ms ease`;const b=l?h.length-1-g:g;c.style.transitionDelay=o==="stagger"?`${a+b*e}ms`:`${a}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const c=new IntersectionObserver(g=>{g.forEach(b=>{b.isIntersecting&&(b.target.offsetWidth,b.target.style.opacity="1",b.target.style.transform="none",c.unobserve(b.target))})},{threshold:.1});h.forEach(g=>c.observe(g))}))}),w.find(".card[data-collapsible]").each(function(){const n=this.querySelector(".card-header");n&&n.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),w.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const n=this.dataset.soTarget,o=document.getElementById(n);if(!o)return;const s=E.slideover({title:o.dataset.soTitle||"",size:o.dataset.soSize||"md",position:o.dataset.soPosition||"right"});o.style.display="",s.setContent(o),s.open()})})}if(typeof $.setup=="function"){const n=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(o=>{try{const s=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:l,alert:h,prompt:i,...c}=e||{};a||l||h||i?($(s).on("click",async function(b){if(b.preventDefault(),a&&!await E.confirm(a))return;let f=null;if(!(i&&(f=await E.prompt(i,{inputPlaceholder:c.promptPlaceholder||"",inputValue:c.promptDefault||""}),f===null))){if(c.target){const u=$(c.target);c.toggleClass&&u.toggleClass(c.toggleClass),c.addClass&&u.addClass(c.addClass),c.removeClass&&u.removeClass(c.removeClass),f!==null&&(c.setText&&u.text(f),c.setVal&&u.val(f),c.setAttr&&u.attr(c.setAttr,f))}c.href&&(window.location.href=c.href),l&&E.toast(l,{type:c.toastType||"success"}),h&&E.alert(h)}}),Object.keys(c).length&&(o[s]={...t,events:{...t.events,click:c}})):o[s]=t}$.setup(o)}}w.length&&R(w.get(0))}),window.wireCTAButtons=R;function R(p){p.querySelectorAll(".dm-cta-trigger").forEach(r=>{r.__ctaWired||(r.__ctaWired=!0,r.addEventListener("click",async()=>{const x=r.dataset.action,y=r.dataset.entry,k=r.dataset.confirm;let w=S.get("auth_token");if(!w){E.toast("Please log in to perform this action.",{type:"warning"});return}if(k&&!await E.confirm(k))return;const n=Array.from(r.childNodes).map(s=>s.cloneNode(!0));r.disabled=!0,r.textContent="Running\u2026";const o=s=>fetch(`/api/actions/${x}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify(r.dataset.scope?{entryId:y,scope:r.dataset.scope}:{entryId:y})});try{let s=await o(w);if(s.status===401){const e=S.get("auth_refresh_token");if(e){const a=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(a.ok){const{token:l}=await a.json();S.set("auth_token",l),w=l,s=await o(w)}}}const t=await s.json().catch(()=>({}));if(!s.ok||t.success===!1)throw new Error(t.error||t.message||`Error ${s.status}`);E.toast(t.message||"Action completed.",{type:"success"})}catch(s){E.toast(s.message||"Action failed.",{type:"error"})}finally{r.disabled=!1,r.textContent="",n.forEach(s=>r.appendChild(s)),Domma.icons.scan(r)}}))})}(function(){const r=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!r.length)return;const x=S.get("auth_token");function y(w,n){const o=document.createElement("div");o.className="dm-collection-display dm-collection-empty";const s=document.createElement("p");if(n){s.appendChild(document.createTextNode("Please "));const t=document.createElement("a");t.href="/login",t.textContent="sign in",s.appendChild(t),s.appendChild(document.createTextNode(" to view your entries."))}else s.textContent=w;return o.appendChild(s),o}function k(w,n){const o=document.createElement("template");o.innerHTML=n||"",w.replaceWith(o.content)}r.forEach(async w=>{const n=w.dataset.collectionAttrs;if(!n)return;if(!x){w.replaceChildren(y("",!0));return}const o=s=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify({attrs:n})});try{let s=x,t=await o(s);if(t.status===401){const a=S.get("auth_refresh_token");if(a){const l=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:a})});if(l.ok){const{token:h}=await l.json();S.set("auth_token",h),s=h,t=await o(s)}}}if(!t.ok){const{error:a}=await t.json().catch(()=>({}));w.replaceChildren(y(a||"Unable to load entries.",!1));return}const{html:e}=await t.json();k(w,e),window.DommaCollectionBrowser?.hydrate&&window.DommaCollectionBrowser.hydrate(document)}catch{w.replaceChildren(y("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),typeof Domma<"u"&&Domma.syntax?.scan&&Domma.syntax.scan(),R(document.body)},100)})(),(function(){const r=document.querySelectorAll("[data-collection-table]");!r.length||typeof T>"u"||!T.create||r.forEach(x=>{let y;try{y=W(x.dataset.payload)}catch{return}const{columns:k,rows:w,search:n,sortable:o,exportable:s,pageSize:t,empty:e,ctaConfig:a,slug:l,exportCfg:h}=y;if(!k?.length)return;if(a){const g=k.findIndex(b=>b.key==="_cta");g!==-1&&(k[g]={key:"_cta",title:"",render:(b,f)=>{const u=document.createElement("button");if(u.className=`btn btn-${a.style||"primary"} dm-cta-trigger`,u.dataset.action=a.action||"",a.scope&&(u.dataset.scope=a.scope),u.dataset.entry=f._entryId||"",a.confirm&&(u.dataset.confirm=a.confirm),a.icon){const m=document.createElement("span");m.dataset.icon=a.icon,u.appendChild(m),u.appendChild(document.createTextNode(" "))}return u.appendChild(document.createTextNode(a.label||"Run")),u}})}const i="col-table-"+Math.random().toString(36).slice(2,7),c=document.createElement("div");c.id=i,x.replaceChildren(c),T.create("#"+i,{data:w,columns:k,search:n,sortable:o,exportable:s,pageSize:t,emptyMessage:e}),a&&R(c),x._dmTable={columns:k,rows:w,slug:l,exportCfg:h,render(g){T.create("#"+i,{data:g,columns:k,search:n,sortable:o,exportable:s,pageSize:t,emptyMessage:e}),a&&R(c)}}})})(),(function(){const r=document.querySelectorAll("[data-form-inline]");if(!r.length||typeof F>"u")return;function x(o){const s=o&&o.logic;return!!(s&&(s.visibility||s.requirement))}function y(o,s){const t={};return(o||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const a=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),l=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":a?"chooser":e.type,h={...e.formConfig||{}};h.span==="full"&&s&&(h.span=s);const i={type:l,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},...h}};e.minLength!==void 0&&(i.minLength=e.minLength),e.maxLength!==void 0&&(i.maxLength=e.maxLength),e.min!==void 0&&(i.min=e.min),e.max!==void 0&&(i.max=e.max),a&&(e.variant&&(i.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(i.multiple=!0),e.density&&(i.density=e.density),e.columns&&(i.columns=e.columns),e.accent&&(i.accent=e.accent),e.accentStyle&&(i.accentStyle=e.accentStyle),e.glow&&(i.glow=!0),e.glowColour&&(i.glowColour=e.glowColour),e.shadow&&(i.shadow=e.shadow),e.shadowColour&&(i.shadowColour=e.shadowColour)),t[e.name]=i}),t}function k(o){const s={};return(o||[]).forEach(t=>{if(!(!t.name||t.type==="page-break"||t.type==="spacer")&&(t.type==="select"||t.type==="multiselect")&&t.required){const e=(t.options||[])[0];e&&(s[t.name]=typeof e=="object"?e.value:e)}}),s}function w(o,s){(s||[]).forEach(t=>{if(t.type!=="date"||!t.name)return;const e=o.querySelector(`[name="${t.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function n(o,s,t){let e=o.querySelector(".cms-form-message");e||(e=document.createElement("p"),e.className="cms-form-message",o.appendChild(e)),e.textContent=s,e.style.cssText=t?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}r.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,l=e.layout||"grid",h=t.some(u=>u.type==="page-break"),i=async u=>{if(o.dataset.triggerBlocked==="true"){const m=o._formLogicRuntime,v=m&&m.submitBlock?m.submitBlock().reasons:[];n(o,v.join(" ")||"This form cannot be submitted yet.",!0);const C=new Error("Blocked by a form trigger");throw C.formSubmitFailed=!0,C}try{const m=o.querySelector(".fb-form-honeypot"),v=m?.querySelector('input[type="text"]')||null,C=m?.querySelector('[name="_t"]')||null,D=Object.assign({},u);delete D._t,m?.dataset.hpName&&delete D[m.dataset.hpName],v!==null&&(D._hp=v.value),C!==null&&(D._t=C.value);const q=await H.post(`/api/forms/submit/${s.slug}`,D);if(q&&q.error){n(o,q.error,!0);const z=new Error(q.error);throw z.formSubmitFailed=!0,z}if(q?.redirect){window.location.href=q.redirect;return}for(;o.firstChild;)o.removeChild(o.firstChild);n(o,q?.message||e.successMessage||"Thank you for your submission.",!1)}catch(m){throw m.formSubmitFailed||n(o,m.message||"Submission failed. Please try again.",!0),m}},c=Date.now();function g(u){const m=u.querySelector("form");if(!m||m.querySelector(".fb-form-honeypot"))return;const v=document.createElement("div");v.className="fb-form-honeypot",v.setAttribute("aria-hidden","true");const C=document.createElement("input");C.name=m.querySelector('[name="website"]')?"_website":"website",v.dataset.hpName=C.name,C.type="text",C.tabIndex=-1,C.autocomplete="url",C.placeholder="https://",v.appendChild(C);const D=document.createElement("input");D.name="_t",D.type="hidden",D.value=c,v.appendChild(D),m.appendChild(v)}function b(u){if(!window.FormLogicEngine||!t.some(v=>v.logic||Array.isArray(v.triggers)&&v.triggers.length))return null;const m=new window.FormLogicEngine.FormLogicRuntime(s,o,u?{baseValues:u}:void 0);if(m.init(),o._formLogicRuntime=m,o._formLogicObserved)return m;if(o._formLogicObserved=!0,o.parentNode&&typeof MutationObserver<"u"){const v=new MutationObserver(function(C){for(const D of C)for(const q of D.removedNodes)if(q===o||q.nodeType===1&&q.contains&&q.contains(o)){o._formLogicRuntime&&o._formLogicRuntime.destroy(),v.disconnect();return}});v.observe(o.parentNode,{childList:!0,subtree:!1}),o._formLogicObserver=v}return m}let f=!1;if(o.addEventListener("dm-form-end",u=>{const m=u.detail?.validation;if(m&&!m.valid){Z(o,m.missing),ee(m);return}f||(f=!0,i(u.detail?.values||{}).catch(()=>{}).finally(()=>{f=!1}))}),h&&F.wizard){const u=pe(t,s.title),m=u.map(I=>({title:I.title,description:I.description,fields:y(I.fields,a)})),v=u.map(I=>I.fields);let C=0,D=null,q=null;const z=I=>{const N=window.FormLogicEngine;if(!N||!N.validateFields)return;const O=N.validateFields(o,v[C]||[]);O.valid||(I.preventDefault(),I.stopImmediatePropagation(),Z(o,O.missing),ee(O))},j=()=>{const I=v[C]||t;he(o,e),te(o,e,I),w(o,I),Q(o,I),e.honeypot!==!1&&g(o),D&&D.destroy(),D=b(q?.getStepData?q.getStepData():void 0),o.querySelectorAll(".wizard-next, .wizard-submit").forEach(N=>N.addEventListener("click",z,!0))};q=F.wizard(o,{schema:{steps:m},layout:l,columns:a,finishText:e.submitText||"Finish",onSubmit:i,onStepChange:I=>{C=I,setTimeout(j,0)}}),o.addEventListener("dm-form-trigger",I=>{if(I.detail?.type!=="goto-step"||!q?.goToStep)return;const N=Number(I.detail.action.step);!Number.isFinite(N)||N<0||N>=m.length||N!==C&&q.goToStep(N)}),Promise.resolve(q).then(j)}else if(F.render){const u=F.render(o,y(t,a),k(t),{submitText:e.submitText||"Submit",layout:l,columns:a,onSubmit:i});Promise.resolve(u).then(function(){fe(o,e,l),te(o,e,t),w(o,t),Q(o,t),e.honeypot!==!1&&g(o),b()})}})})();function ge(p,r){const x=document.body,y=x.classList,k=new Set((x.dataset.railSite||"").split(" ").filter(Boolean)),w=()=>window.matchMedia("(min-width: 993px)").matches;p.querySelectorAll(".navbar-link, .navbar-dropdown-toggle").forEach(a=>{const l=a.textContent.trim();l&&(a.setAttribute("title",l),a.querySelector("svg, [data-icon]")||(a.dataset.initial=l.charAt(0).toUpperCase()))});const n=document.createElement("button");n.type="button",n.className="dm-nav-collapse-toggle";const o=()=>{const a=y.contains("dm-vnav-collapsed");n.setAttribute("aria-pressed",String(a)),n.setAttribute("aria-label",a?"Expand menu":"Collapse menu"),n.title=n.getAttribute("aria-label");const l=r?"chevrons-left":"chevrons-right",h=r?"chevrons-right":"chevrons-left";n.innerHTML=`<span data-icon="${a?l:h}"></span>`,window.Domma&&Domma.icons&&Domma.icons.scan(n)},s=a=>{y.toggle("dm-vnav-collapsed",a),S.set("nav_collapsed",a),o()};n.addEventListener("click",()=>s(!y.contains("dm-vnav-collapsed"))),o();const t=p.querySelector(".navbar-container")||p;t.insertBefore(n,t.firstChild?.nextSibling||null);const e=a=>{y.toggle("dm-vnav-autohide",a),y.toggle("dm-vnav-collapsible",!a&&k.has("collapsible")),a?y.remove("dm-vnav-collapsed"):k.has("collapsible")&&S.get("nav_collapsed")===!0&&y.add("dm-vnav-collapsed"),p.classList.remove("dm-nav-revealed"),o()};p.addEventListener("pointerdown",a=>{y.contains("dm-vnav-autohide")&&a.pointerType!=="mouse"&&!p.classList.contains("dm-nav-revealed")&&(p.classList.add("dm-nav-revealed"),a.preventDefault())}),document.addEventListener("pointerdown",a=>{p.contains(a.target)||p.classList.remove("dm-nav-revealed")}),window.E&&typeof E.contextMenu=="function"&&E.contextMenu(p,{items:()=>{const a=y.contains("dm-vnav-autohide"),l=y.contains("dm-vnav-collapsed"),h=S.has("nav_autohide")||S.has("nav_collapsed");return[{header:"Menu"},{label:l?"Expand menu":"Collapse to icons",icon:l?"maximize":"minimize",disabled:!w()||a||!k.has("collapsible"),action:()=>s(!l)},{label:a?"Keep on screen":"Hide when not in use",icon:a?"eye":"eye-off",disabled:!w(),action:()=>{S.set("nav_autohide",!a),e(!a)}},{type:"divider"},{label:"Reset to the site's setting",icon:"refresh",disabled:!h,action:()=>{S.remove("nav_autohide"),S.remove("nav_collapsed"),e(k.has("autohide")),y.toggle("dm-vnav-collapsed",!k.has("autohide")&&k.has("start-collapsed")),o()}}]}})}function ye(p){p.classList.add("dm-nav-scroll-hide");let r=window.scrollY,x=!1;window.addEventListener("scroll",()=>{x||(x=!0,requestAnimationFrame(()=>{x=!1;const y=window.scrollY,k=p.matches(":focus-within")||p.querySelector(".navbar-dropdown.open, .navbar-collapse.show");y>r&&y>p.offsetHeight&&!k?p.classList.add("dm-nav-scrolled-away"):y<r&&p.classList.remove("dm-nav-scrolled-away"),r=y}))},{passive:!0})}function ve(p){let r={};try{r=JSON.parse(p.dataset.contents||"{}")}catch{}const x=p.closest(".site-main"),y=document.querySelector(".site-content"),k=new Set(String(r.exclude||"").split(",").map(i=>i.trim().toLowerCase()).filter(Boolean)),w=i=>!i.closest(".hero, .card, .accordion, .tabs, figure, #site-sidebar"),n=i=>(y?[...y.querySelectorAll(i)]:[]).filter(w).filter(c=>!k.has(c.textContent.trim().toLowerCase())),o=r.headings==="h2"?n("h2"):r.headings==="h3"?n("h3"):r.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",r.title||"Page contents"),r.hover&&(t.dataset.menuHover=r.hover),r.selected&&(t.dataset.menuActive=r.selected),r.accent&&t.style.setProperty("--dm-menu-highlight",r.accent.startsWith("#")?r.accent:`var(--dm-${r.accent})`),r.font&&(t.style.fontFamily=r.font),r.fontSize&&(t.style.fontSize=r.fontSize),r.fontWeight&&(t.style.fontWeight=r.fontWeight);const e=document.createElement("div");e.className="dm-contents-head";const a=document.createElement("p");a.className="dm-contents-title",a.textContent=r.title||"",e.appendChild(a),t.appendChild(e);const l=document.createElement("ol");l.className="dm-contents-list"+(r.numbered?" is-numbered":"");const h=[];for(const i of o){if(!i.id){const b=(i.textContent||"").toLowerCase().trim().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"section";let f=b;for(let u=2;s.has(f);u++)f=`${b}-${u}`;s.add(f),i.id=f}const c=document.createElement("li");c.className=`dm-contents-item dm-contents-${i.tagName.toLowerCase()}`;const g=document.createElement("a");g.href=`#${i.id}`,g.textContent=i.textContent.trim(),c.appendChild(g),l.appendChild(c),h.push([i,g])}if(t.appendChild(l),p.replaceChildren(t),r.scrollSpy!==!1&&"IntersectionObserver"in window){const i=new Map,c=()=>{const b=h.find(([f])=>i.get(f))||null;h.forEach(([,f])=>{const u=b&&f===b[1];f.classList.toggle("is-active",!!u),u?f.setAttribute("aria-current","location"):f.removeAttribute("aria-current")})},g=new IntersectionObserver(b=>{b.forEach(f=>i.set(f.target,f.isIntersecting)),c()},{rootMargin:"-80px 0px -60% 0px"});h.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 c=()=>{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")),c()}),c(),e.insertBefore(i,e.firstChild)}if(x?.classList.contains("contents-floating")&&r.movable){t.classList.add("is-movable");const i=S.get("contents_position");i&&Number.isFinite(i.x)&&Object.assign(p.style,{left:`${i.x}px`,top:`${i.y}px`,right:"auto",bottom:"auto"}),e.addEventListener("pointerdown",c=>{if(c.target.closest("button"))return;const g=p.getBoundingClientRect(),b=c.clientX-g.left,f=c.clientY-g.top,u=v=>{const C=Math.max(0,Math.min(window.innerWidth-g.width,v.clientX-b)),D=Math.max(0,Math.min(window.innerHeight-40,v.clientY-f));Object.assign(p.style,{left:`${C}px`,top:`${D}px`,right:"auto",bottom:"auto"})},m=()=>{document.removeEventListener("pointermove",u),S.set("contents_position",{x:parseInt(p.style.left,10),y:parseInt(p.style.top,10)})};document.addEventListener("pointermove",u),document.addEventListener("pointerup",m,{once:!0})})}window.E&&typeof E.contextMenu=="function"&&E.contextMenu(p,{items:()=>{const i=x?.classList.contains("contents-collapsible"),c=x?.classList.contains("contents-collapsed"),g=h.find(([,b])=>b.classList.contains("is-active"));return[{header:"Contents"},...i?[{label:c?"Show the contents":"Collapse to a button",icon:c?"list":"minimize",action:()=>p.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")&&(p.addEventListener("pointerdown",i=>{i.pointerType!=="mouse"&&!p.classList.contains("is-revealed")&&(p.classList.add("is-revealed"),i.preventDefault())}),document.addEventListener("pointerdown",i=>{p.contains(i.target)||p.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-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")}))}
@@ -27,6 +27,7 @@ import {deleteForm, ensureCollectionForForm, ensureFormsDir, listForms, readForm
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
31
  import {createTransport, mailFailure, sendFormEmail, TEST_EMAIL_FAILED} from '../../services/email.js';
31
32
  import {formActionFailed, formTriggerNotice} from '../../services/notification-sources.js';
32
33
  import {
@@ -44,6 +45,7 @@ import {authenticate, requireAdmin, requirePermission} from '../../middleware/au
44
45
  import {hooks} from '../../services/hooks.js';
45
46
  import {pluginDenialFor} from '../../services/collectionAccess.js';
46
47
  import {saveMedia} from '../../services/content.js';
48
+ import {servedType} from '../../services/mediaTypes.js';
47
49
  import {v4 as uuidv4} from 'uuid';
48
50
  import {ago, clip, plural} from '../../services/sidebarBadges.js';
49
51
  import {safeFetch} from '../../services/outbound.js';
@@ -110,18 +112,29 @@ async function detectFormCollectionMismatch(form) {
110
112
  * @returns {string}
111
113
  */
112
114
  function safeFilename(raw) {
113
- const cleaned = String(raw || 'upload')
114
- .toLowerCase()
115
- .replace(/[^\w.-]+/g, '-')
116
- .replace(/-+/g, '-')
117
- .replace(/^[-.]+|[-.]+$/g, '')
118
- .slice(0, 200) || 'upload';
119
- const prefix = uuidv4().slice(0, 8);
120
- return `${prefix}-${cleaned}`;
115
+ return `${uuidv4().slice(0, 8)}-${cleanUploadName(raw)}`;
116
+ }
117
+
118
+ /**
119
+ * The kinds of file a form may collect, by the type /media/ will serve the stored name as:
120
+ * images (not SVG), audio, video, PDF, Office and OpenDocument, plain text and CSV,
121
+ * archives. A name with no known type is served as a download. Anything else - above all
122
+ * anything a browser would render as a page on this site's address - is refused.
123
+ */
124
+ const SERVABLE = /^(image\/(?!svg)[\w.+-]+|audio\/[\w.+-]+|video\/[\w.+-]+|text\/(plain|csv)|application\/(pdf|rtf|zip|x-zip-compressed|gzip|x-gzip|x-tar|x-7z-compressed|x-rar-compressed|vnd\.rar|msword|vnd\.ms-[\w.+-]+|vnd\.openxmlformats-officedocument\.[\w.+-]+|vnd\.oasis\.opendocument\.[\w.+-]+|epub\+zip|octet-stream))$/i;
125
+
126
+ /** Why a stored name would be served as something a form may not collect, or null. */
127
+ function servedTypeRefusal(fieldCfg, filename) {
128
+ const served = servedType(cleanUploadName(filename));
129
+ if (!served || SERVABLE.test(served)) return null;
130
+ const label = fieldCfg.label || fieldCfg.name;
131
+ return `"${label}" file type "${served}" cannot be uploaded here.`;
121
132
  }
122
133
 
123
134
  /**
124
135
  * Check a multipart file part against a form's `type: file` field config.
136
+ * The rules live in public/js/form-files.mjs, which the browser's dropzone
137
+ * applies as files are added - one definition, so the two cannot drift.
125
138
  *
126
139
  * @param {object} fieldCfg - The form-field definition
127
140
  * @param {object} part - Multipart part (mimetype, filename)
@@ -129,25 +142,8 @@ function safeFilename(raw) {
129
142
  * @returns {string|null} - Error message, or null if OK
130
143
  */
131
144
  function validateUpload(fieldCfg, part, size) {
132
- const cfg = fieldCfg?.file || {};
133
- const max = Number(cfg.maxSize) || 5 * 1024 * 1024; // 5 MB default
134
- if (size > max) {
135
- return `"${fieldCfg.label || fieldCfg.name}" file is too large (max ${Math.round(max / 1024)} KB).`;
136
- }
137
- const accept = String(cfg.accept || '').trim();
138
- if (accept) {
139
- // Comma-separated list of mime types, allowing wildcards like image/*
140
- const patterns = accept.split(',').map(s => s.trim()).filter(Boolean);
141
- const mime = part.mimetype || '';
142
- const ok = patterns.some(p => {
143
- if (p.endsWith('/*')) return mime.startsWith(p.slice(0, -1));
144
- return p === mime;
145
- });
146
- if (!ok) {
147
- return `"${fieldCfg.label || fieldCfg.name}" file type "${mime}" not allowed (expected: ${accept}).`;
148
- }
149
- }
150
- return null;
145
+ return fileRefusal(fieldCfg, {mimetype: part.mimetype || '', filename: part.filename || '', size})
146
+ || servedTypeRefusal(fieldCfg, part.filename || '');
151
147
  }
152
148
 
153
149
  /**
@@ -166,6 +162,7 @@ function validateUpload(fieldCfg, part, size) {
166
162
  */
167
163
  async function parseMultipartForm(request, form) {
168
164
  const out = {};
165
+ const pending = new Map(); // field name -> {buffer, filename}, saved by saveFormFiles()
169
166
  const fileFields = new Map(
170
167
  (form.fields || []).filter(f => f.type === 'file').map(f => [f.name, f])
171
168
  );
@@ -173,32 +170,56 @@ async function parseMultipartForm(request, form) {
173
170
  for await (const part of request.parts()) {
174
171
  if (part.type === 'file') {
175
172
  const fieldCfg = fileFields.get(part.fieldname);
176
- if (!fieldCfg) {
177
- // Unknown file field - drain and skip rather than 400, so a
178
- // form that gains a file input later doesn't reject older
179
- // submissions in-flight from a stale page.
173
+ // An unknown file field, or a second file for one that holds one: drain and
174
+ // skip rather than 400, so a form that gains a file input later doesn't reject
175
+ // older submissions in-flight from a stale page.
176
+ if (!fieldCfg || pending.has(part.fieldname)) {
180
177
  for await (const _ of part.file) { /* drain */ }
181
178
  continue;
182
179
  }
183
180
  const chunks = [];
184
181
  for await (const chunk of part.file) chunks.push(chunk);
185
182
  const buffer = Buffer.concat(chunks);
186
- const err = validateUpload(fieldCfg, part, buffer.length);
183
+ // Over the server's own upload cap the part arrives cut short, not refused
184
+ const size = part.file.truncated ? Infinity : buffer.length;
185
+ const err = validateUpload(fieldCfg, part, size);
187
186
  if (err) throw new Error(err);
188
187
  if (buffer.length === 0) continue; // empty file input - skip
189
- const saved = await saveMedia(safeFilename(part.filename), buffer);
188
+ pending.set(part.fieldname, {buffer, filename: part.filename});
190
189
  out[part.fieldname] = {
191
- url: saved.url,
190
+ url: '',
192
191
  name: part.filename,
193
192
  size: buffer.length,
194
193
  mime: part.mimetype
195
194
  };
196
- } else {
197
- // Text field - last value wins on duplicates (browser-standard).
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.
198
198
  out[part.fieldname] = part.value;
199
199
  }
200
200
  }
201
- return out;
201
+ return {body: out, pending};
202
+ }
203
+
204
+ /**
205
+ * Write the files of a submission that passed every check, into the reference objects
206
+ * parseMultipartForm() left in `body`. Only the fields that will be kept are written -
207
+ * nothing a bot, a rate-limited or refused submission or a switched-off field sent
208
+ * reaches content/media/.
209
+ *
210
+ * @param {Map<string, {buffer: Buffer, filename: string}>} pending
211
+ * @param {object} body
212
+ * @param {Set<string>} keep - field names the submission keeps
213
+ */
214
+ async function saveFormFiles(pending, body, keep) {
215
+ for (const [name, file] of pending) {
216
+ if (!keep.has(name)) {
217
+ delete body[name];
218
+ continue;
219
+ }
220
+ const saved = await saveMedia(safeFilename(file.filename), file.buffer);
221
+ body[name].url = saved.url;
222
+ }
202
223
  }
203
224
 
204
225
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -783,9 +804,10 @@ export async function formsRoutes(fastify) {
783
804
  // then their { url, name, size, mime } reference object lands in `body`
784
805
  // under the field name. Downstream code sees a uniform JSON-shaped body.
785
806
  let body = request.body || {};
807
+ let pendingFiles = null;
786
808
  if (request.isMultipart && request.isMultipart()) {
787
809
  try {
788
- body = await parseMultipartForm(request, form);
810
+ ({body, pending: pendingFiles} = await parseMultipartForm(request, form));
789
811
  } catch (err) {
790
812
  return reply.status(400).send({ error: err.message });
791
813
  }
@@ -892,6 +914,9 @@ export async function formsRoutes(fastify) {
892
914
  return reply.status(429).send({ error: 'Too many submissions. Please try again later.' });
893
915
  }
894
916
 
917
+ // 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());
919
+
895
920
  // Build submission data - only include visible fields
896
921
  const data = {};
897
922
  const excused = [];
@@ -2,6 +2,7 @@
2
2
  * Media API
3
3
  * GET /api/media - list media files
4
4
  * GET /api/media/_count - the sidebar badge (plain text; see mediaBadge)
5
+ * GET /api/media/_limits - what an upload may be (types, size, name length)
5
6
  * POST /api/media - upload a file
6
7
  * DELETE /api/media/:name - delete a file
7
8
  */
@@ -27,6 +28,22 @@ const ALLOWED_MIME_TYPES = new Set([
27
28
  'audio/mpeg', 'audio/mp3', 'audio/ogg', 'audio/wav', 'audio/webm',
28
29
  ]);
29
30
 
31
+ /** The longest a sanitised file name may be. */
32
+ const MAX_NAME_LENGTH = 200;
33
+
34
+ /**
35
+ * What POST /media accepts, for the admin to refuse a file before sending it.
36
+ * `maxFileSize` is the multipart limit server.js registered at boot (config/server.json
37
+ * uploads.maxFileSize), so the browser and the server cannot disagree.
38
+ *
39
+ * @param {number|null|undefined} maxFileSize bytes
40
+ * @returns {{types: string[], maxFileSize: number|null, maxNameLength: number}}
41
+ */
42
+ export function uploadLimits(maxFileSize) {
43
+ const n = Number(maxFileSize);
44
+ return {types: [...ALLOWED_MIME_TYPES], maxFileSize: n > 0 ? n : null, maxNameLength: MAX_NAME_LENGTH};
45
+ }
46
+
30
47
  // Safe filename: strip path traversal and restrict to alphanumeric + safe chars
31
48
  function sanitiseFilename(name) {
32
49
  return path.basename(name).replace(/[^a-zA-Z0-9._-]/g, '_');
@@ -76,7 +93,7 @@ export function mediaBadge(files = [], now = Date.now()) {
76
93
  };
77
94
  }
78
95
 
79
- export async function mediaRoutes(fastify) {
96
+ export async function mediaRoutes(fastify, opts = {}) {
80
97
  const canRead = {preHandler: [authenticate, requirePermission('media', 'read')]};
81
98
  const canCreate = {preHandler: [authenticate, requirePermission('media', 'create')]};
82
99
  const canUpdate = {preHandler: [authenticate, requirePermission('media', 'update')]};
@@ -88,12 +105,13 @@ export async function mediaRoutes(fastify) {
88
105
 
89
106
  // `_count` is not a file route: the only GET under /media/:name is /media/:name/info.
90
107
  fastify.get('/media/_count', canRead, async () => mediaBadge(await listMedia()));
108
+ fastify.get('/media/_limits', canCreate, async () => uploadLimits(opts.maxFileSize));
91
109
 
92
110
  fastify.post('/media', canCreate, async (request, reply) => {
93
111
  const results = [];
94
112
  for await (const data of request.files()) {
95
113
  const filename = sanitiseFilename(data.filename);
96
- if (filename.length > 200) {
114
+ if (filename.length > MAX_NAME_LENGTH) {
97
115
  for await (const _ of data.file) { /* drain */
98
116
  }
99
117
  return reply.code(400).send({error: 'Filename is too long (max 200 characters).'});
@@ -108,6 +126,11 @@ export async function mediaRoutes(fastify) {
108
126
  const chunks = [];
109
127
  for await (const chunk of data.file) {
110
128
  chunks.push(chunk);
129
+ }
130
+ // Past uploads.maxFileSize the stream just stops: what arrived is the start of the
131
+ // file, and saving it left a broken file behind the 413.
132
+ if (data.file.truncated) {
133
+ return reply.code(413).send({error: 'request file too large'});
111
134
  }
112
135
  try {
113
136
  results.push(await saveMedia(filename, Buffer.concat(chunks)));
package/server/server.js CHANGED
@@ -34,6 +34,7 @@ import {parseTrustProxy} from './services/network.js';
34
34
  import {requestSerializer} from './services/request-log.js';
35
35
  import {rateLimitOptions} from './services/rate-limit.js';
36
36
  import {registerAdminAssets} from './services/adminAssets.js';
37
+ import {isActiveType, servedType} from './services/mediaTypes.js';
37
38
  import {connectSources, frameSources, imageSources} from './services/embedCsp.js';
38
39
  import {cspSources as gaSources} from './services/googleAnalytics.js';
39
40
 
@@ -419,8 +420,13 @@ await app.register(staticPlugin, {
419
420
  root: mediaDir,
420
421
  prefix: '/media/',
421
422
  decorateReply: false,
422
- setHeaders: (reply) => {
423
+ setHeaders: (reply, filePath) => {
423
424
  reply.header('X-Content-Type-Options', 'nosniff');
425
+ // A file a browser would run as a document (HTML, any XML, SVG, script) is
426
+ // served sandboxed: shown, never able to script this site's address.
427
+ if (isActiveType(servedType(filePath))) {
428
+ reply.header('Content-Security-Policy', "sandbox; default-src 'none'; img-src 'self' data:; style-src 'unsafe-inline'");
429
+ }
424
430
  }
425
431
  });
426
432
 
@@ -563,7 +569,7 @@ await app.register(contentPublicRoutes, {prefix: '/api'});
563
569
  await app.register(contentApiRoutes, {prefix: '/api'});
564
570
  await app.register(sidebarRoutes, {prefix: '/api'});
565
571
  await app.register(documentationRoutes, {prefix: '/api'});
566
- await app.register(mediaRoutes, { prefix: '/api' });
572
+ await app.register(mediaRoutes, { prefix: '/api', maxFileSize: serverConfig.uploads.maxFileSize });
567
573
  await app.register(usersRoutes, { prefix: '/api' });
568
574
  await app.register(pluginsRoutes, { prefix: '/api' });
569
575
  await app.register(pluginMarketplaceRoutes, { prefix: '/api' });
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The type /media/ serves a stored file as, and whether a browser would run it.
3
+ *
4
+ * Uploads are served by @fastify/static from the site's own address, so a file a browser
5
+ * renders as a document - HTML, any XML (an XHTML-namespace <script> runs in one), SVG,
6
+ * script - could act with an admin's session. The lookup is the one @fastify/static makes
7
+ * (its own copy of `mime`), so what is checked is what is served.
8
+ */
9
+ import {createRequire} from 'module';
10
+
11
+ const requireHere = createRequire(import.meta.url);
12
+ const mime = requireHere(requireHere.resolve('mime', {paths: [requireHere.resolve('@fastify/send')]}));
13
+
14
+ /** The Content-Type /media/ would send for a file name, or null when it has none. */
15
+ export function servedType(name) {
16
+ return mime.getType(String(name || '')) || null;
17
+ }
18
+
19
+ /** Whether a served type is one a browser runs as a document or script. */
20
+ export function isActiveType(type) {
21
+ return /^(text\/html|text\/xml|application\/xml|image\/svg)|\+xml$|javascript|ecmascript/i.test(String(type || ''));
22
+ }
@@ -25,8 +25,8 @@
25
25
  <link rel="stylesheet" href="/dist/domma/themes/domma-themes.css">
26
26
 
27
27
  <!-- Site CSS -->
28
- <link rel="stylesheet" href="/public/css/site.css?v=20261024-menu-highlight-fix">
29
- <link rel="stylesheet" href="/public/css/forms.css?v=20260927-submit-btn-css">
28
+ <link rel="stylesheet" href="/public/css/site.css?v=20261025-range-slider-css">
29
+ <link rel="stylesheet" href="/public/css/forms.css?v=20261025-dropzone-css">
30
30
  <!-- Menu item hover / current-page effects. Loaded after site.css so a
31
31
  menu's own highlight wins over the blanket link styling there. -->
32
32
  <link rel="stylesheet" href="/public/css/menu-highlight.css?v=20261024-menu-highlight-fix">
@@ -178,12 +178,14 @@
178
178
  reads window.FormLogicEngine and window.DommaCollectionBrowser. -->
179
179
  <!-- Interactive Collection Browser (used by [collection] shortcodes with
180
180
  searchable / filterable / sortable / paginate attributes) -->
181
- <script src="/public/js/collection-browser.js?v=20260927-scope-mine" defer></script>
181
+ <!-- Its range-filter arithmetic; a module that runs first and sets window.DommaCollectionRange -->
182
+ <script src="/public/js/collection-range.mjs?v=20261025-range-slider" type="module"></script>
183
+ <script src="/public/js/collection-browser.js?v=20261025-range-slider" defer></script>
182
184
  <!-- Core Forms logic engine (must run before forms.js and site.js) -->
183
- <script src="/public/js/form-logic-engine.js?v=20260927-submit-btn" defer></script>
185
+ <script src="/public/js/form-logic-engine.js?v=20261025-dropzone-disable" defer></script>
184
186
 
185
187
  <!-- Site initialisation -->
186
- <script src="/public/js/site.js?v=20261022-cookie-settings" type="module"></script>
188
+ <script src="/public/js/site.js?v=20261025-dropzone-range" type="module"></script>
187
189
  <script src="/public/js/collection-context.js?v=20261010-utf8-payloads" type="module"></script>
188
190
  {{ctxMenusModule}}
189
191
 
@@ -193,7 +195,7 @@
193
195
  <script src="/public/js/menu-float.js?v=20260927-float" type="module"></script>
194
196
 
195
197
  <!-- Core Forms renderer -->
196
- <script src="/public/js/forms.js?v=20261015-smooth-caret" type="module"></script>
198
+ <script src="/public/js/forms.js?v=20261025-form-dropzone" type="module"></script>
197
199
 
198
200
  <!-- Core effects runtime -->
199
201
  <script src="/public/js/effects.js?v=20261015-occasions" defer></script>