domma-cms 1.8.37 → 1.8.39
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/CHANGELOG.md +12 -0
- package/admin/css/admin.css +1 -1
- package/admin/index.html +2 -2
- package/admin/js/app.js +35 -9
- package/admin/js/lib/avatar.js +1 -1
- package/admin/js/lib/sidebar-count-cache.js +1 -0
- package/admin/js/lib/sidebar-renderer.js +4 -4
- package/admin/js/lib/site-settings-arrange.js +1 -1
- package/admin/js/lib/topbar-account.js +1 -0
- package/admin/js/lib/topbar-navigation.js +1 -0
- package/admin/js/lib/topbar-project.js +1 -0
- package/admin/js/lib/update-notice.js +1 -1
- package/admin/js/templates/docs/api-settings.html +2 -0
- package/admin/js/templates/docs/usage-site-settings.html +31 -0
- package/admin/js/templates/settings.html +83 -19
- package/admin/js/views/settings.js +3 -5
- package/docs/api-reference.md +1 -1
- package/docs/configuration.md +18 -0
- package/docs/screenshots/admin-navigation.png +0 -0
- package/docs/screenshots/admin-versions.png +0 -0
- package/docs/screenshots/site-settings-navigation.png +0 -0
- package/package.json +1 -1
- package/public/js/btt.js +1 -1
- package/public/js/cookie-consent.js +1 -1
- package/public/js/google-analytics.js +1 -0
- package/public/js/site.js +1 -1
- package/server/routes/api/auth.js +2 -1
- package/server/routes/api/settings.js +19 -3
- package/server/server.js +6 -3
- package/server/services/frameworkVersions.js +11 -0
- package/server/services/googleAnalytics.js +82 -0
- package/server/services/renderer.js +37 -4
- package/server/templates/page.html +1 -1
package/public/js/site.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{applyMobileMenu as ne,initCarouselPopup as ae}from"/public/js/mobile-ui.mjs?v=20261016-mobile-options";import{installSmoothCarets as se}from"/public/js/smooth-caret.js";import{decodeBase64Json as R}from"/public/js/utf8-base64.mjs";import{makeBadgeEl as X,applyColour as U,applyPill as ie,applyFloat as re,applyHighlight as W,applyItemHighlight as ce,itemHighlightAttrs as le,isCurrentItem as G,menuAlign as de,applyClasses as O,cssClasses as V,applyEntrance as Y}from"/public/js/menu-decor.mjs?v=20261014-menu-entrance";import{initFloatingMenus as me}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as K,markInvalidFields as Q,showValidationPopup as Z}from"/public/js/forms.js?v=20261015-smooth-caret";import{buildWizardSteps as ue}from"/public/js/form-steps.mjs";import{applyFormClasses as ee,applySubmitButtons as pe,applyWizardButtons as fe}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())}),$(()=>{se(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(u,f=0){return Array.isArray(u)?u.filter(m=>!(m&&(m.type==="spacer"||m.type==="theme-switcher"))).filter(m=>!(m&&m.type==="separator"&&f===0)).map(m=>m&&m.type==="separator"?{divider:!0}:Array.isArray(m?.items)?{...m,items:s(m.items,f+1)}:m):u};const n={...p.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(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);ne(l,p.mobile);const h=()=>{if(!a)return;const f=l.querySelector('a[aria-current="page"]')?.closest(".navbar-dropdown");f&&!f.classList.contains("open")&&f.querySelector(":scope > .navbar-dropdown-toggle")?.click()};l.classList.add(`dm-nav-align-${de(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&&he(l,p.side==="right"),!a&&i.hideOnScroll&&ge(l),W(l,p),Y(l,p),O(l,p.class),e&&(re(x.get(0),p),me(x.get(0).parentNode||document));const c=l.querySelector(".navbar-toggle"),g=l.querySelector(".navbar-collapse");if(c&&g){let u=null;const f=()=>{g.classList.contains("show")&&c.click()},m=()=>{u&&(u.remove(),u=null),document.body.classList.remove("dm-drawer-open")},v=()=>{u||(u=document.createElement("div"),u.className="dm-navbar-backdrop",u.addEventListener("click",f),document.body.appendChild(u),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")&&f()})}const b=l.querySelector(".navbar-nav");if(b){const u=Array.from(b.children);let f=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,u[f]||null);continue}if(m&&m.type==="theme-switcher"){const v=document.createElement("li");v.className="dm-menu-theme-switcher";const C=document.createElement("span");C.dataset.themeSwitcher="menu",C.dataset.position="inline",v.appendChild(C),b.insertBefore(v,u[f]||null);continue}f++}window.CMSThemeSwitcher?.scan(b)}l.querySelectorAll("[data-path]").forEach(u=>{const f=u.dataset.path.split(".").map(Number),m=f.length>1,v=f.reduce((q,L)=>q?.items?.[L],{items:t});if(!v)return;v.colour&&U(u,v.colour),v.pill&&!m&&ie(u,v.pill),ce(u,v),O(u.closest("li"),v.class);const C=X(v.badge);C&&u.appendChild(C),G(v.url,location.pathname)&&(u.classList.add("active"),u.setAttribute("aria-current","page"))}),h()}const y=$("#site-footer");if(y.length){let e=function(d){return String(d??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},a=function(d){return e(d).replace(/'/g,"'")},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 I=a(d.url||"#"),A=e(d.text||""),D=Array.isArray(d.items)&&d.items.length?`<ul>${d.items.map(l).join("")}</ul>`:"",z=le(d),te=[z.hover&&` data-menu-item-hover="${a(z.hover)}"`,z.active&&` data-menu-item-active="${a(z.active)}"`].filter(Boolean).join(""),oe=z.accent?` style="--dm-menu-highlight:${a(z.accent)}"`:"",J=V(d.class).join(" ");return`<li${J?` class="${a(J)}"`:""}><a href="${I}"${te}${oe}>${A}</a>${D}</li>`},h=function(){if(!r.footer)return"";const d=Object.keys(o).filter(A=>n[A]);if(!d.length)return"";const I=V(r.footer.socialClass).join(" ");return`<div class="footer-social">${d.map(A=>{const{label:D,svg:z}=o[A];return`<a href="${n[A]}" target="_blank" rel="noopener noreferrer" aria-label="${D}" class="footer-social-link${I?` ${a(I)}`:""}">${z}</a>`}).join("")}</div>`},b=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 I=Array.isArray(d.items)&&d.items.length?`<ul>${d.items.map(l).join("")}</ul>`:"",A=g(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>`,D=["cms-footer-col",...V(d.class)].join(" ");return`<li class="${a(D)}">${A}${I}</li>`},m=function(d,I,A=!1){return(d||[]).forEach(D=>{D&&(D.type==="separator"||D.type==="spacer"||D.type==="theme-switcher")||(A&&g(D)||I.push(D),Array.isArray(D.items)&&D.items.length&&m(D.items,I))}),I};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:""},i=["columns","row"].includes(r.footer?.layout)?r.footer.layout:"auto",c=i==="columns"||i==="auto"&&t.primary.some(d=>Array.isArray(d?.items)&&d.items.length),g=d=>c&&!d.url&&Array.isArray(d.items)&&d.items.length>0;if(c){const d=p.brand||{},I=d.text||r.title||"",A=d.logo?`<img src="${a(d.logo)}" class="cms-footer-brand-logo" alt="">`:d.icon?'<span class="cms-footer-brand-icon"></span>':"",D=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">${I||A?`<a class="cms-footer-brand-name" href="${a(d.url||"/")}">${A}<span>${e(I)}</span></a>`:""}${D?`<p class="cms-footer-blurb">${e(D)}</p>`:""}${h()}</div>`,t.primary.length&&(s+=`<nav class="footer-nav footer-nav--primary footer-nav--columns" aria-label="Footer"><ul>${t.primary.map(b).join("")}</ul></nav>`),s+='</div><div class="cms-footer-bottom">',t.copyright&&(s+=`<p class="cms-footer-copyright">${e(t.copyright)}</p>`),t.legal.length&&(s+=`<nav class="footer-nav footer-nav--legal" aria-label="Legal"><ul>${t.legal.map(l).join("")}</ul></nav>`),s+="</div>"}else t.copyright&&(s+=`<p>${e(t.copyright)}</p>`),t.primary.length&&(s+=`<nav class="footer-nav footer-nav--primary"><ul>${t.primary.map(l).join("")}</ul></nav>`),t.legal.length&&(s+=`<nav class="footer-nav footer-nav--legal"><ul>${t.legal.map(l).join("")}</ul></nav>`),s+=h();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 f=y.get(0).querySelector(".cms-footer-brand-icon");f&&p.brand?.icon&&f.setAttribute("data-icon",p.brand.icon),O(y.get(0),r.footer?.class);const v=[...m(t.primary,[],!0),...m(t.legal,[])];y.get(0).querySelectorAll(".footer-nav a").forEach((d,I)=>{const A=v[I];if(!A)return;A.colour&&U(d,A.colour);const D=X(A.badge);D&&d.appendChild(D),G(A.url,location.pathname)&&(d.classList.add("is-active"),d.setAttribute("aria-current","page"))}),W(y.get(0).querySelector(".footer-nav--primary"),{highlight:t.primaryHighlight}),W(y.get(0).querySelector(".footer-nav--legal"),{highlight:t.legalHighlight}),Y(y.get(0).querySelector(".footer-nav--primary"),{entrance:t.primaryEntrance}),Y(y.get(0).querySelector(".footer-nav--legal"),{entrance:t.legalEntrance}),O(y.get(0).querySelector(".footer-nav--primary"),t.primaryClass),O(y.get(0).querySelector(".footer-nav--legal"),t.legalClass);const q=r.footer?.showMotionToggle!==!1,L=S.get("reduced_motion"),B=r.footer?.motionDefault,P=L!=null?!!L:B==="on"?!0:B==="off"?!1:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),M=r.footer?.motionControl==="checkbox",N=y.get(0).querySelector(".cms-footer-bottom")||y.get(0).querySelector(".footer-inner");if(q&&N){const d=document.createElement("input");d.type="checkbox",d.className=M?"footer-motion-checkbox":"form-switch-input",d.id="dm-motion-switch",d.checked=P,d.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const I=document.createElement("span");I.className=M?"footer-motion-label":"form-switch-label",I.textContent="Reduce motion";const A=document.createElement("label");A.className=M?"footer-motion-check":"form-switch footer-motion-switch",A.title="Reduce motion",A.appendChild(d),A.appendChild(I),N.appendChild(A)}}const k=document.getElementById("site-sidebar");k&&ye(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),ae(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=R(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 u=null;if(!(i&&(u=await E.prompt(i,{inputPlaceholder:c.promptPlaceholder||"",inputValue:c.promptDefault||""}),u===null))){if(c.target){const f=$(c.target);c.toggleClass&&f.toggleClass(c.toggleClass),c.addClass&&f.addClass(c.addClass),c.removeClass&&f.removeClass(c.removeClass),u!==null&&(c.setText&&f.text(u),c.setVal&&f.val(u),c.setAttr&&f.attr(c.setAttr,u))}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&&j(w.get(0))}),window.wireCTAButtons=j;function j(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(),j(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=R(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,u)=>{const f=document.createElement("button");if(f.className=`btn btn-${a.style||"primary"} dm-cta-trigger`,f.dataset.action=a.action||"",a.scope&&(f.dataset.scope=a.scope),f.dataset.entry=u._entryId||"",a.confirm&&(f.dataset.confirm=a.confirm),a.icon){const m=document.createElement("span");m.dataset.icon=a.icon,f.appendChild(m),f.appendChild(document.createTextNode(" "))}return f.appendChild(document.createTextNode(a.label||"Run")),f}})}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&&j(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&&j(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=R(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(f=>f.type==="page-break"),i=async f=>{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,q=Object.assign({},f);delete q._t,m?.dataset.hpName&&delete q[m.dataset.hpName],v!==null&&(q._hp=v.value),C!==null&&(q._t=C.value);const L=await H.post(`/api/forms/submit/${s.slug}`,q);if(L&&L.error){n(o,L.error,!0);const B=new Error(L.error);throw B.formSubmitFailed=!0,B}if(L?.redirect){window.location.href=L.redirect;return}for(;o.firstChild;)o.removeChild(o.firstChild);n(o,L?.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(f){const m=f.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 q=document.createElement("input");q.name="_t",q.type="hidden",q.value=c,v.appendChild(q),m.appendChild(v)}function b(f){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,f?{baseValues:f}: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 q of C)for(const L of q.removedNodes)if(L===o||L.nodeType===1&&L.contains&&L.contains(o)){o._formLogicRuntime&&o._formLogicRuntime.destroy(),v.disconnect();return}});v.observe(o.parentNode,{childList:!0,subtree:!1}),o._formLogicObserver=v}return m}let u=!1;if(o.addEventListener("dm-form-end",f=>{const m=f.detail?.validation;if(m&&!m.valid){Q(o,m.missing),Z(m);return}u||(u=!0,i(f.detail?.values||{}).catch(()=>{}).finally(()=>{u=!1}))}),h&&F.wizard){const f=ue(t,s.title),m=f.map(M=>({title:M.title,description:M.description,fields:y(M.fields,a)})),v=f.map(M=>M.fields);let C=0,q=null,L=null;const B=M=>{const N=window.FormLogicEngine;if(!N||!N.validateFields)return;const d=N.validateFields(o,v[C]||[]);d.valid||(M.preventDefault(),M.stopImmediatePropagation(),Q(o,d.missing),Z(d))},P=()=>{const M=v[C]||t;fe(o,e),ee(o,e,M),w(o,M),K(o,M),e.honeypot!==!1&&g(o),q&&q.destroy(),q=b(L?.getStepData?L.getStepData():void 0),o.querySelectorAll(".wizard-next, .wizard-submit").forEach(N=>N.addEventListener("click",B,!0))};L=F.wizard(o,{schema:{steps:m},layout:l,columns:a,finishText:e.submitText||"Finish",onSubmit:i,onStepChange:M=>{C=M,setTimeout(P,0)}}),o.addEventListener("dm-form-trigger",M=>{if(M.detail?.type!=="goto-step"||!L?.goToStep)return;const N=Number(M.detail.action.step);!Number.isFinite(N)||N<0||N>=m.length||N!==C&&L.goToStep(N)}),Promise.resolve(L).then(P)}else if(F.render){const f=F.render(o,y(t,a),k(t),{submitText:e.submitText||"Submit",layout:l,columns:a,onSubmit:i});Promise.resolve(f).then(function(){pe(o,e,l),ee(o,e,t),w(o,t),K(o,t),e.honeypot!==!1&&g(o),b()})}})})();function he(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 ge(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 ye(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 u=b;for(let f=2;s.has(u);f++)u=`${b}-${f}`;s.add(u),i.id=u}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(([u])=>i.get(u))||null;h.forEach(([,u])=>{const f=b&&u===b[1];u.classList.toggle("is-active",!!f),f?u.setAttribute("aria-current","location"):u.removeAttribute("aria-current")})},g=new IntersectionObserver(b=>{b.forEach(u=>i.set(u.target,u.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,u=c.clientY-g.top,f=v=>{const C=Math.max(0,Math.min(window.innerWidth-g.width,v.clientX-b)),q=Math.max(0,Math.min(window.innerHeight-40,v.clientY-u));Object.assign(p.style,{left:`${C}px`,top:`${q}px`,right:"auto",bottom:"auto"})},m=()=>{document.removeEventListener("pointermove",f),S.set("contents_position",{x:parseInt(p.style.left,10),y:parseInt(p.style.top,10)})};document.addEventListener("pointermove",f),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 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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},a=function(d){return e(d).replace(/'/g,"'")},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")}))}
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* Password reset (the link, where it may point, the email) is services/passwordReset.js.
|
|
21
21
|
*/
|
|
22
22
|
import crypto from 'node:crypto';
|
|
23
|
+
import {frameworkVersions} from '../../services/frameworkVersions.js';
|
|
23
24
|
import {config, getConfig} from '../../config.js';
|
|
24
25
|
import {getRegisteredSidebarItems, hooks} from '../../services/hooks.js';
|
|
25
26
|
import {getMenu} from '../../services/menus.js';
|
|
@@ -254,7 +255,7 @@ export async function authRoutes(fastify) {
|
|
|
254
255
|
// Dashboard Pro's "Open on my boards" - for a user the admin does not confine.
|
|
255
256
|
if (!adminScope && isDashboardPro() && (await readPrefs(request.user)).home) adminHome = '/dashboards';
|
|
256
257
|
// adminBrand: the admin's name and mark for the top bar, sidebar header and tab title.
|
|
257
|
-
return {permissions, adminScope, adminHome, adminBrand: adminBrandFor(getConfig('site'))};
|
|
258
|
+
return {permissions, adminScope, adminHome, versions: frameworkVersions(), adminBrand: adminBrandFor(getConfig('site'))};
|
|
258
259
|
});
|
|
259
260
|
|
|
260
261
|
// GET /api/auth/permissions-registry - returns the full permissions registry,
|
|
@@ -20,6 +20,7 @@ import {fileURLToPath} from 'url';
|
|
|
20
20
|
import * as cache from '../../services/cache/index.js';
|
|
21
21
|
import {normaliseAdminPath} from '../../services/roles.js';
|
|
22
22
|
import {cssClassProblems} from '../../../public/js/css-classes.mjs';
|
|
23
|
+
import {invalidateMeasurementId, measurementIdOf, normaliseMeasurementId} from '../../services/googleAnalytics.js';
|
|
23
24
|
|
|
24
25
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
25
26
|
const CUSTOM_CSS_FILE = path.resolve(__dirname, '../../../content/custom.css');
|
|
@@ -46,7 +47,8 @@ export function settingsBadge(site = {}, cacheStats = {}) {
|
|
|
46
47
|
} else if (!String(smtp.fromAddress || '').trim()) {
|
|
47
48
|
problems.push({tone: 'warn', text: 'No From address', meta: 'mail has no sender; many servers refuse it'});
|
|
48
49
|
}
|
|
49
|
-
|
|
50
|
+
// Google Analytics shows the banner whatever its own switch says (services/googleAnalytics.js).
|
|
51
|
+
if ((cc.enabled !== false || measurementIdOf(site)) && !String(cc.privacyPolicyUrl || '').trim()) {
|
|
50
52
|
problems.push({tone: 'warn', text: 'Cookie banner without a privacy policy', meta: 'it links to nothing'});
|
|
51
53
|
}
|
|
52
54
|
const danger = problems.some(p => p.tone === 'danger');
|
|
@@ -108,7 +110,7 @@ export async function settingsRoutes(fastify) {
|
|
|
108
110
|
'title', 'tagline', 'description', 'logo', 'favicon',
|
|
109
111
|
'theme', 'adminTheme', 'fontFamily', 'fontSize',
|
|
110
112
|
'smtp', 'footer', 'analytics', 'social', 'locale',
|
|
111
|
-
'layoutOptions', 'seo', 'backToTop', 'cookieConsent', 'breadcrumbs', 'autoTheme',
|
|
113
|
+
'layoutOptions', 'seo', 'backToTop', 'goToBottom', 'cookieConsent', 'breadcrumbs', 'autoTheme',
|
|
112
114
|
'adminBrand',
|
|
113
115
|
// The screen `#/` (Dashboard) opens - see services/adminHome.js.
|
|
114
116
|
'adminHome',
|
|
@@ -120,7 +122,9 @@ export async function settingsRoutes(fastify) {
|
|
|
120
122
|
// payload into a save gets a 400 for keys it never touched.
|
|
121
123
|
'brand', 'url', 'baseTheme',
|
|
122
124
|
// Designer classes on <body>, every page (Site Settings > Appearance).
|
|
123
|
-
'bodyClass'
|
|
125
|
+
'bodyClass',
|
|
126
|
+
// Google Analytics (Site Settings > Analytics) - services/googleAnalytics.js.
|
|
127
|
+
'googleAnalytics'
|
|
124
128
|
]);
|
|
125
129
|
const unknownKeys = Object.keys(data).filter(k => !ALLOWED_KEYS.has(k));
|
|
126
130
|
if (unknownKeys.length > 0) {
|
|
@@ -149,6 +153,17 @@ export async function settingsRoutes(fastify) {
|
|
|
149
153
|
}
|
|
150
154
|
data.footer = {...data.footer, blocks: [...new Set(list)]};
|
|
151
155
|
}
|
|
156
|
+
if ('googleAnalytics' in data) {
|
|
157
|
+
// A GA4 measurement ID or nothing: it is written into every page and the security policy.
|
|
158
|
+
const ga = data.googleAnalytics;
|
|
159
|
+
if (!ga || typeof ga !== 'object' || Array.isArray(ga)) {
|
|
160
|
+
return reply.status(400).send({error: 'Google Analytics: send {measurementId}.'});
|
|
161
|
+
}
|
|
162
|
+
const raw = String(ga.measurementId ?? '').trim();
|
|
163
|
+
const id = normaliseMeasurementId(raw);
|
|
164
|
+
if (raw && !id) return reply.status(400).send({error: 'Google Analytics: a measurement ID looks like G-XXXXXXXXXX.'});
|
|
165
|
+
data.googleAnalytics = {measurementId: id};
|
|
166
|
+
}
|
|
152
167
|
if ('adminHome' in data) {
|
|
153
168
|
// An admin route, or blank for the built-in dashboard.
|
|
154
169
|
const home = data.adminHome == null || data.adminHome === '' ? '/' : normaliseAdminPath(data.adminHome);
|
|
@@ -185,6 +200,7 @@ export async function settingsRoutes(fastify) {
|
|
|
185
200
|
}
|
|
186
201
|
}
|
|
187
202
|
saveConfig('site', merged);
|
|
203
|
+
invalidateMeasurementId();
|
|
188
204
|
await cache.invalidateTags(['site']);
|
|
189
205
|
return { success: true };
|
|
190
206
|
});
|
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 {connectSources, frameSources} from './services/embedCsp.js';
|
|
37
|
+
import {cspSources as gaSources} from './services/googleAnalytics.js';
|
|
37
38
|
|
|
38
39
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
39
40
|
const ROOT = path.resolve(__dirname, '..');
|
|
@@ -123,12 +124,14 @@ await app.register(helmet, {
|
|
|
123
124
|
defaultSrc: ["'self'"],
|
|
124
125
|
// 'wasm-unsafe-eval' lets a page compile WebAssembly (the call page's background blur) - it allows
|
|
125
126
|
// nothing for JavaScript: eval and new Function stay blocked.
|
|
126
|
-
|
|
127
|
+
// Google Analytics' hosts are added per response, and only while a measurement ID is set
|
|
128
|
+
// (services/googleAnalytics.js) - a site without one keeps exactly this policy.
|
|
129
|
+
scriptSrc: ["'self'", "'unsafe-inline'", "'wasm-unsafe-eval'", 'https://cdn.jsdelivr.net', gaSources('script')],
|
|
127
130
|
styleSrc: ["'self'", "'unsafe-inline'", 'https://cdn.jsdelivr.net', 'https://fonts.googleapis.com'],
|
|
128
|
-
imgSrc: ["'self'", 'data:', 'blob:'],
|
|
131
|
+
imgSrc: ["'self'", 'data:', 'blob:', gaSources('img')],
|
|
129
132
|
fontSrc: ["'self'", 'data:', 'https://fonts.gstatic.com'],
|
|
130
133
|
// config/server.json csp.connectHosts: other sites a page may read from (services/embedCsp.js).
|
|
131
|
-
connectSrc: connectSources(serverConfig.csp?.connectHosts),
|
|
134
|
+
connectSrc: [...connectSources(serverConfig.csp?.connectHosts), gaSources('connect')],
|
|
132
135
|
// [embed] players and config/embeds.json hosts - without this, default-src blocked them all.
|
|
133
136
|
frameSrc: frameSources((() => { try { return getConfig('embeds')?.allowedHosts; } catch { return []; } })()),
|
|
134
137
|
upgradeInsecureRequests: null, // prevent browsers forcing HTTPS on HTTP deployments
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {createRequire} from 'node:module';
|
|
2
|
+
const require = createRequire(import.meta.url);
|
|
3
|
+
|
|
4
|
+
/** Domma bundles its pinned Reactive dependency into the served JS distribution. */
|
|
5
|
+
export function frameworkVersions() {
|
|
6
|
+
try {
|
|
7
|
+
const pkg = require('domma-js/package.json');
|
|
8
|
+
const reactive = pkg.dependencies?.['domma-reactive'] || pkg.devDependencies?.['domma-reactive'];
|
|
9
|
+
return {js: pkg.version, reactive: /^\d+\.\d+\.\d+(?:-[\w.-]+)?$/.test(reactive || '') ? reactive : null};
|
|
10
|
+
} catch { return {}; }
|
|
11
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Analytics (GA4) - Site Settings > Analytics, stored as site.json `googleAnalytics.measurementId`.
|
|
3
|
+
*
|
|
4
|
+
* Off until a measurement ID is set, and then still off for each visitor until they accept analytics
|
|
5
|
+
* cookies: the renderer turns the cookie banner on (with its Analytics category) whenever an ID is set,
|
|
6
|
+
* and public/js/google-analytics.js loads Google's tag only after that consent. GA sets cookies, so
|
|
7
|
+
* under UK GDPR/PECR it may not run first and ask afterwards - unlike the built-in Analytics Tool,
|
|
8
|
+
* which is cookieless.
|
|
9
|
+
*
|
|
10
|
+
* The Content-Security-Policy gains Google's hosts only while an ID is set: server.js hands helmet the
|
|
11
|
+
* cspSources() functions, which helmet calls per response.
|
|
12
|
+
*/
|
|
13
|
+
import fs from 'fs';
|
|
14
|
+
import path from 'path';
|
|
15
|
+
import {getConfig} from '../config.js';
|
|
16
|
+
|
|
17
|
+
/** A GA4 measurement ID: "G-" and letters/digits. Nothing else reaches the page or the policy. */
|
|
18
|
+
export const MEASUREMENT_ID_RE = /^G-[A-Z0-9]{4,20}$/;
|
|
19
|
+
|
|
20
|
+
/** Google's hosts for GA4 (its own CSP guidance), per directive. */
|
|
21
|
+
export const GA_SOURCES = {
|
|
22
|
+
script: ['https://*.googletagmanager.com'],
|
|
23
|
+
img: ['https://*.google-analytics.com', 'https://*.googletagmanager.com'],
|
|
24
|
+
connect: ['https://*.google-analytics.com', 'https://*.analytics.google.com', 'https://*.googletagmanager.com']
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** Typed value -> the ID, or '' when it is empty or not one (" g-abc123 " -> "G-ABC123"). */
|
|
28
|
+
export function normaliseMeasurementId(value) {
|
|
29
|
+
const v = String(value ?? '').trim().toUpperCase();
|
|
30
|
+
return MEASUREMENT_ID_RE.test(v) ? v : '';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The site's measurement ID, or '' - re-checked here because site.json can be edited by hand. */
|
|
34
|
+
export function measurementIdOf(site = {}) {
|
|
35
|
+
return normaliseMeasurementId(site?.googleAnalytics?.measurementId);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The cookie banner the page gets: the site's own while it is on; while GA is set it is on regardless,
|
|
40
|
+
* with its Analytics category offered, since that category is the only way GA ever loads. Its consent
|
|
41
|
+
* version gains "+ga" then, so every visitor is asked again: an answer given before GA existed (or while
|
|
42
|
+
* the Analytics category was hidden) was not consent to it.
|
|
43
|
+
*
|
|
44
|
+
* @returns {object|null} null = no banner
|
|
45
|
+
*/
|
|
46
|
+
export function consentSettingsFor(site = {}) {
|
|
47
|
+
const cc = site.cookieConsent || {};
|
|
48
|
+
if (measurementIdOf(site)) {
|
|
49
|
+
return {...cc, enabled: true, showAnalytics: true, consentVersion: `${cc.consentVersion || '1.0'}+ga`};
|
|
50
|
+
}
|
|
51
|
+
return cc.enabled ? cc : null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// site.json read once per change, not three times per response (one per directive).
|
|
55
|
+
const SITE_JSON = path.resolve('config', 'site.json');
|
|
56
|
+
let memo = {key: '', id: ''};
|
|
57
|
+
|
|
58
|
+
/** Forget the memo - the settings save calls it, since two writes can share an mtime tick. */
|
|
59
|
+
export function invalidateMeasurementId() {
|
|
60
|
+
memo = {key: '', id: ''};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function currentMeasurementId() {
|
|
64
|
+
try {
|
|
65
|
+
const {mtimeMs, size} = fs.statSync(SITE_JSON);
|
|
66
|
+
const key = `${mtimeMs}:${size}`;
|
|
67
|
+
if (key !== memo.key) memo = {key, id: measurementIdOf(getConfig('site'))};
|
|
68
|
+
return memo.id;
|
|
69
|
+
} catch {
|
|
70
|
+
return '';
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* A helmet directive entry: Google's hosts for `kind` while an ID is set, nothing otherwise.
|
|
76
|
+
*
|
|
77
|
+
* @param {'script'|'img'|'connect'} kind
|
|
78
|
+
* @returns {() => string}
|
|
79
|
+
*/
|
|
80
|
+
export function cspSources(kind) {
|
|
81
|
+
return () => (currentMeasurementId() ? GA_SOURCES[kind].join(' ') : '');
|
|
82
|
+
}
|
|
@@ -17,6 +17,7 @@ import {resolveContextMenusForPage} from './contextMenus.js';
|
|
|
17
17
|
import {buildCustomThemeStyleTag, buildOverrideStyleTag, getThemeTokenMap, listThemeIds, loadThemeConfig, resolveThemeClasses} from './themeSettings.js';
|
|
18
18
|
import {getSearchSettings} from './search.js';
|
|
19
19
|
import {isToolEnabled} from './tools.js';
|
|
20
|
+
import {consentSettingsFor, measurementIdOf} from './googleAnalytics.js';
|
|
20
21
|
import {fmtDateTime} from './dates.js';
|
|
21
22
|
import {cssClasses} from '../../public/js/css-classes.mjs';
|
|
22
23
|
import {parseMarkdown} from './markdown.js';
|
|
@@ -82,6 +83,8 @@ function buildSearchAssets() {
|
|
|
82
83
|
|
|
83
84
|
const THEME_SWITCHER_ASSET_V = '20261011-ts-inline';
|
|
84
85
|
const ANALYTICS_ASSET_V = '20260922-analytics';
|
|
86
|
+
const CONSENT_ASSET_V = '20261022-consent-reopen';
|
|
87
|
+
const GOOGLE_ANALYTICS_ASSET_V = '20261022-ga4';
|
|
85
88
|
|
|
86
89
|
/**
|
|
87
90
|
* The page-view beacon.
|
|
@@ -95,6 +98,30 @@ const ANALYTICS_ASSET_V = '20260922-analytics';
|
|
|
95
98
|
*
|
|
96
99
|
* @returns {string} the body tag
|
|
97
100
|
*/
|
|
101
|
+
/**
|
|
102
|
+
* The cookie banner and Google Analytics: what they add to window.__CMS_SITE__ and their scripts.
|
|
103
|
+
*
|
|
104
|
+
* GA turns the banner on (services/googleAnalytics.js consentSettingsFor) and its loader runs after the
|
|
105
|
+
* banner's script, so it can read the visitor's answer. Both render paths - the standing trap.
|
|
106
|
+
*
|
|
107
|
+
* @param {object} site - config/site.json
|
|
108
|
+
* @returns {{siteFields: object, bodyTag: string}}
|
|
109
|
+
*/
|
|
110
|
+
function buildConsentAssets(site) {
|
|
111
|
+
const consent = consentSettingsFor(site);
|
|
112
|
+
const measurementId = measurementIdOf(site);
|
|
113
|
+
return {
|
|
114
|
+
siteFields: {
|
|
115
|
+
...(consent ? {cookieConsent: consent} : {}),
|
|
116
|
+
...(measurementId ? {googleAnalytics: {measurementId}} : {})
|
|
117
|
+
},
|
|
118
|
+
bodyTag: [
|
|
119
|
+
consent ? `<script src="/public/js/cookie-consent.js?v=${CONSENT_ASSET_V}" defer></script>` : '',
|
|
120
|
+
measurementId ? `<script src="/public/js/google-analytics.js?v=${GOOGLE_ANALYTICS_ASSET_V}" defer></script>` : ''
|
|
121
|
+
].filter(Boolean).join('\n')
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
98
125
|
function buildAnalyticsTag() {
|
|
99
126
|
// Unless the site has switched Analytics off (config/tools.json).
|
|
100
127
|
if (!isToolEnabled('analytics')) return '';
|
|
@@ -319,6 +346,7 @@ export async function renderPage(page, opts = {}) {
|
|
|
319
346
|
resolveLocation('footer-legal', opts.user || null, menuCtx)
|
|
320
347
|
]);
|
|
321
348
|
const baseUrl = opts.baseUrl || site.baseUrl || '';
|
|
349
|
+
const consentAssets = buildConsentAssets(site);
|
|
322
350
|
// The page's own navbar settings ([navbar ... /]) over the menu's, for this page only.
|
|
323
351
|
const navMenu = applyMenuOverride(navMenuOwn, readNavbarOverride(page.html));
|
|
324
352
|
|
|
@@ -523,7 +551,8 @@ export async function renderPage(page, opts = {}) {
|
|
|
523
551
|
social: site.social || null
|
|
524
552
|
},
|
|
525
553
|
site.backToTop?.enabled ? { backToTop: site.backToTop } : {},
|
|
526
|
-
site.
|
|
554
|
+
site.goToBottom?.enabled ? { goToBottom: site.goToBottom } : {},
|
|
555
|
+
consentAssets.siteFields,
|
|
527
556
|
autoTheme ? {autoTheme} : {}
|
|
528
557
|
)).replace(/<\/script>/gi, '<\\/script>'),
|
|
529
558
|
headInject: [injection.head, navbarFontLink].filter(Boolean).join('\n'),
|
|
@@ -545,8 +574,8 @@ export async function renderPage(page, opts = {}) {
|
|
|
545
574
|
searchAssets.bodyTag,
|
|
546
575
|
switcherAssets.bodyTag,
|
|
547
576
|
buildAnalyticsTag(),
|
|
548
|
-
site.backToTop?.enabled
|
|
549
|
-
|
|
577
|
+
(site.backToTop?.enabled || site.goToBottom?.enabled) ? '<script src="/public/js/btt.js?v=20261022-page-navigation" defer></script>' : '',
|
|
578
|
+
consentAssets.bodyTag,
|
|
550
579
|
(page.usedComponents || [])
|
|
551
580
|
.map(n => `<script type="module" src="/api/components/${n}.js"></script>`)
|
|
552
581
|
.join('\n')
|
|
@@ -1136,6 +1165,8 @@ export async function renderBlogPage(templatePath, data = {}, seoMeta = {}) {
|
|
|
1136
1165
|
resolveLocation('footer-legal', user, menuCtx)
|
|
1137
1166
|
]);
|
|
1138
1167
|
const baseUrl = seoMeta.baseUrl || site.baseUrl || '';
|
|
1168
|
+
// The cookie banner on these pages too (it was missing here before GA needed it).
|
|
1169
|
+
const consentAssets = buildConsentAssets(site);
|
|
1139
1170
|
|
|
1140
1171
|
// Resolve live badge counts (countFrom) before the menus are injected as
|
|
1141
1172
|
// globals. Static badges/colours/pills pass through unchanged.
|
|
@@ -1307,7 +1338,8 @@ export async function renderBlogPage(templatePath, data = {}, seoMeta = {}) {
|
|
|
1307
1338
|
links: (site.footer.links || []).filter(l => !l.hidden)
|
|
1308
1339
|
}
|
|
1309
1340
|
: site.footer,
|
|
1310
|
-
social: site.social || null
|
|
1341
|
+
social: site.social || null,
|
|
1342
|
+
...(bare ? {} : consentAssets.siteFields)
|
|
1311
1343
|
}).replace(/<\/script>/gi, '<\\/script>'),
|
|
1312
1344
|
headInject: [bare ? '' : injection.head, navbarFontLink].filter(Boolean).join('\n'),
|
|
1313
1345
|
// Theme overrides land BEFORE content/custom.css: the overrides are
|
|
@@ -1321,6 +1353,7 @@ export async function renderBlogPage(templatePath, data = {}, seoMeta = {}) {
|
|
|
1321
1353
|
searchAssets.bodyTag,
|
|
1322
1354
|
switcherAssets.bodyTag,
|
|
1323
1355
|
bare ? '' : buildAnalyticsTag(),
|
|
1356
|
+
bare ? '' : consentAssets.bodyTag,
|
|
1324
1357
|
(seoMeta.usedComponents || [])
|
|
1325
1358
|
.map(n => `<script type="module" src="/api/components/${n}.js"></script>`)
|
|
1326
1359
|
.join('\n')
|
|
@@ -183,7 +183,7 @@
|
|
|
183
183
|
<script src="/public/js/form-logic-engine.js?v=20260927-submit-btn" defer></script>
|
|
184
184
|
|
|
185
185
|
<!-- Site initialisation -->
|
|
186
|
-
<script src="/public/js/site.js?v=20261022-
|
|
186
|
+
<script src="/public/js/site.js?v=20261022-cookie-settings" type="module"></script>
|
|
187
187
|
<script src="/public/js/collection-context.js?v=20261010-utf8-payloads" type="module"></script>
|
|
188
188
|
{{ctxMenusModule}}
|
|
189
189
|
|