domma-cms 0.47.2 → 0.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/public/js/site.js CHANGED
@@ -1 +1 @@
1
- import{makeBadgeEl as M,applyColour as N,applyPill as z,applyFloat as j}from"/public/js/menu-decor.mjs?v=20260817-menu-bindings";import{enhancePickerFields as O,markInvalidFields as P,showValidationPopup as R}from"/public/js/forms.js?v=20260821-wizard-grid";import{buildWizardSteps as B}from"/public/js/form-steps.mjs";$(()=>{const w=window.__CMS_NAV__||{},h=window.__CMS_SITE__||{};if(h.autoTheme?.enabled){let t=function(o){const e=(o||"07:00").split(":");return+e[0]*60+(+e[1]||0)},a=function(){const o=new Date,e=o.getHours()*60+o.getMinutes();return e>=t(n.dayStart)&&e<t(n.nightStart)?n.dayTheme:n.nightTheme};const n=h.autoTheme;Domma.theme.set(a()),setInterval(()=>Domma.theme.set(a()),6e4)}const x=$("#site-navbar");if(x.length&&w.brand){let a=function(r,f=0){return Array.isArray(r)?r.filter(c=>!(c&&c.type==="separator"&&f===0)).map(c=>c&&c.type==="separator"?{divider:!0}:Array.isArray(c?.items)?{...c,items:a(c.items,f+1)}:c):r},o=function(r){return Array.isArray(r)?r.filter(f=>!(f&&f.type==="separator")):[]};const n={...w.brand},t=n.size&&n.size!=="md"?` navbar-brand-${n.size}`:"";if(n.logo||n.icon||n.tagline){let r="";n.logo?r+=`<img src="${n.logo}" class="navbar-brand-logo" alt="${n.text||""}">`:n.icon&&(r+=`<span data-icon="${n.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),n.text&&(r+=`<span class="navbar-brand-text${t}">${n.text}</span>`),n.tagline&&(r+=`<small class="navbar-brand-tagline">${n.tagline}</small>`),n.html=r}else t&&n.text&&(n.html=`<span class="navbar-brand-text${t}">${n.text}</span>`);const e=w.position==="floating",d=w.orientation==="vertical";Domma.elements.navbar("#site-navbar",{brand:n,items:a(w.items||[]),variant:w.variant||"dark",position:e?"static":w.position||"sticky",collapsible:!0,appearOnHover:!!w.appearOnHover}),Domma.icons.scan("#site-navbar"),d&&x.get(0).classList.add("dm-nav-vertical",w.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left"),e&&j(x.get(0),w);const g=x.get(0),y=g.querySelector(".navbar-toggle"),p=g.querySelector(".navbar-collapse");if(y&&p){let r=null;const f=()=>{p.classList.contains("show")&&y.click()},c=()=>{r&&(r.remove(),r=null),document.body.classList.remove("dm-drawer-open")},s=()=>{r||(r=document.createElement("div"),r.className="dm-navbar-backdrop",r.addEventListener("click",f),document.body.appendChild(r),document.body.classList.add("dm-drawer-open"))};y.addEventListener("click",()=>{requestAnimationFrame(()=>{p.classList.contains("show")?s():c()})}),p.addEventListener("click",i=>{i.target.closest("a")&&requestAnimationFrame(c)}),document.addEventListener("keydown",i=>{i.key==="Escape"&&p.classList.contains("show")&&f()})}const m=o(w.items||[]);g.querySelectorAll(".navbar-link[data-index], .navbar-dropdown-item[data-index]").forEach(r=>{const f=m[parseInt(r.dataset.index,10)];if(!f)return;const c=r.dataset.subindex!=null,s=c&&Array.isArray(f.items)?f.items[parseInt(r.dataset.subindex,10)]:f;if(!s)return;s.colour&&N(r,s.colour),s.pill&&!c&&z(r,s.pill);const i=M(s.badge);i&&r.appendChild(i)})}const C=$("#site-footer");if(C.length){let e=function(s){return String(s??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")},d=function(s){return e(s).replace(/'/g,"&#39;")},g=function(s){if(s&&s.type==="separator")return'<li class="footer-sep"><hr></li>';const i=d(s.url||"#"),l=e(s.text||""),v=Array.isArray(s.items)&&s.items.length?`<ul>${s.items.map(g).join("")}</ul>`:"";return`<li><a href="${i}">${l}</a>${v}</li>`},y=function(s,i){return(s||[]).forEach(l=>{l&&l.type==="separator"||(i.push(l),Array.isArray(l.items)&&l.items.length&&y(l.items,i))}),i};const n=h.social||{},t={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 a='<div class="footer-inner container">';const o=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""};if(o.copyright&&(a+=`<p>${e(o.copyright)}</p>`),o.primary.length&&(a+=`<nav class="footer-nav footer-nav--primary"><ul>${o.primary.map(g).join("")}</ul></nav>`),o.legal.length&&(a+=`<nav class="footer-nav footer-nav--legal"><ul>${o.legal.map(g).join("")}</ul></nav>`),h.footer){const s=Object.keys(t).filter(i=>n[i]);s.length&&(a+='<div class="footer-social">',s.forEach(i=>{const{label:l,svg:v}=t[i];a+=`<a href="${n[i]}" target="_blank" rel="noopener noreferrer" aria-label="${l}" class="footer-social-link">${v}</a>`}),a+="</div>")}a+="</div>",C.html(a);const p=[...y(o.primary,[]),...y(o.legal,[])];C.get(0).querySelectorAll(".footer-nav a").forEach((s,i)=>{const l=p[i];if(!l)return;l.colour&&N(s,l.colour);const v=M(l.badge);v&&s.appendChild(v)});const r=S.get("reduced_motion"),f=r!==null?!!r:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),c=C.get(0).querySelector(".footer-inner");if(c){const s=document.createElement("input");s.type="checkbox",s.className="form-switch-input",s.id="dm-motion-switch",s.checked=f,s.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const i=document.createElement("span");i.className="form-switch-label",i.textContent="Reduce motion";const l=document.createElement("label");l.className="form-switch footer-motion-switch",l.title="Reduce motion",l.appendChild(s),l.appendChild(i),c.appendChild(l)}}$("#site-sidebar").length&&Domma.elements.sidebar("#site-sidebar",{autoGenerate:!0,selector:"h2, h3",collapsible:!1,push:!0,contentSelector:".site-content"}),Domma.icons.scan(),Domma.syntax?.scan&&Domma.syntax.scan();const u=$(".page-body");if(u.length){u.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),u.find(".tabs").each(function(){Domma.elements.tabs(this)}),u.find(".carousel").each(function(){const n={autoplay:this.dataset.autoplay==="true",interval:parseInt(this.dataset.interval,10)||5e3,loop:this.dataset.loop!=="false",animation:this.dataset.animation||"slide"};if(this.dataset.animationDuration){const t=parseInt(this.dataset.animationDuration,10);Number.isFinite(t)&&(n.animationDuration=t)}this.dataset.animationEasing&&(n.animationEasing=this.dataset.animationEasing),this.dataset.ctx&&(this._dmCtxSlides=Array.from(this.querySelectorAll(".carousel-slide")).map(t=>t.cloneNode(!0)),this._dmCarouselOpts=n),this._dmCarousel=Domma.elements.carousel(this,n)}),u.find(".dm-countdown").each(function(){const n={autoStart:!0};this.dataset.to&&(n.targetDate=new Date(this.dataset.to)),this.dataset.duration&&(n.duration=parseInt(this.dataset.duration,10)),this.dataset.format&&(n.format=this.dataset.format),Domma.elements.timer(this,n)}),u.find("[data-tooltip]").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),u.find(".dm-progression").each(function(){const n=Array.from(this.querySelectorAll(".dm-progression-item")).map(o=>{const e={title:o.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:o.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return o.dataset.date&&(e.date=o.dataset.date),o.dataset.status&&(e.status=o.dataset.status),o.dataset.entryId&&(e._entryId=o.dataset.entryId),e}),t=this.dataset.layout==="centred"?"centered":this.dataset.layout||"vertical",a=Domma.elements.progression(this,{layout:t,theme:this.dataset.theme||"minimal",mode:this.dataset.mode||"timeline",statusIcons:!0,allowHtml:!0,...n.length?{items:n}:{}});n.length&&(this._dmCtx={instance:a,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 t=n.dataset.revealMode||"stagger",a=n.dataset.revealAnimation||"slide-up",o=parseInt(n.dataset.revealDuration,10)||400,e=parseInt(n.dataset.revealStagger,10)||60,d=parseInt(n.dataset.revealDelay,10)||0,g=n.dataset.revealDirection==="rtl",y=Array.from(n.children),p={"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)"};y.forEach((m,r)=>{m.style.opacity="0",m.style.transform=p[a]||"",m.style.transition=`opacity ${o}ms ease, transform ${o}ms ease`;const f=g?y.length-1-r:r;m.style.transitionDelay=t==="stagger"?`${d+f*e}ms`:`${d}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const m=new IntersectionObserver(r=>{r.forEach(f=>{f.isIntersecting&&(f.target.offsetWidth,f.target.style.opacity="1",f.target.style.transform="none",m.unobserve(f.target))})},{threshold:.1});y.forEach(r=>m.observe(r))}))}),u.find(".card[data-collapsible]").each(function(){const n=this.querySelector(".card-header");n&&n.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),u.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const n=this.dataset.soTarget,t=document.getElementById(n);if(!t)return;const a=E.slideover({title:t.dataset.soTitle||"",size:t.dataset.soSize||"md",position:t.dataset.soPosition||"right"});t.style.display="",a.setContent(t),a.open()})})}if(typeof $.setup=="function"){const n=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(t=>{try{const a=atob(t.dataset.config),o=JSON.parse(a);Object.assign(n,o)}catch{}}),Object.keys(n).length>0){const t={};for(const[a,o]of Object.entries(n)){const e=o?.events?.click,{confirm:d,toast:g,alert:y,prompt:p,...m}=e||{};d||g||y||p?($(a).on("click",async function(f){if(f.preventDefault(),d&&!await E.confirm(d))return;let c=null;if(!(p&&(c=await E.prompt(p,{inputPlaceholder:m.promptPlaceholder||"",inputValue:m.promptDefault||""}),c===null))){if(m.target){const s=$(m.target);m.toggleClass&&s.toggleClass(m.toggleClass),m.addClass&&s.addClass(m.addClass),m.removeClass&&s.removeClass(m.removeClass),c!==null&&(m.setText&&s.text(c),m.setVal&&s.val(c),m.setAttr&&s.attr(m.setAttr,c))}m.href&&(window.location.href=m.href),g&&E.toast(g,{type:m.toastType||"success"}),y&&E.alert(y)}}),Object.keys(m).length&&(t[a]={...o,events:{...o.events,click:m}})):t[a]=o}$.setup(t)}}u.length&&D(u.get(0))});function D(w){w.querySelectorAll(".dm-cta-trigger").forEach(h=>{h.__ctaWired||(h.__ctaWired=!0,h.addEventListener("click",async()=>{const x=h.dataset.action,C=h.dataset.entry,k=h.dataset.confirm;let u=S.get("auth_token");if(!u){E.toast("Please log in to perform this action.",{type:"warning"});return}if(k&&!await E.confirm(k))return;const n=Array.from(h.childNodes).map(a=>a.cloneNode(!0));h.disabled=!0,h.textContent="Running\u2026";const t=a=>fetch(`/api/actions/${x}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify({entryId:C})});try{let a=await t(u);if(a.status===401){const e=S.get("auth_refresh_token");if(e){const d=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(d.ok){const{token:g}=await d.json();S.set("auth_token",g),u=g,a=await t(u)}}}const o=await a.json().catch(()=>({}));if(!a.ok)throw new Error(o.error||o.message||`Error ${a.status}`);E.toast(o.message||"Action completed.",{type:"success"})}catch(a){E.toast(a.message||"Action failed.",{type:"error"})}finally{h.disabled=!1,h.textContent="",n.forEach(a=>h.appendChild(a)),Domma.icons.scan(h)}}))})}(function(){const h=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!h.length)return;const x=S.get("auth_token");function C(u,n){const t=document.createElement("div");t.className="dm-collection-display dm-collection-empty";const a=document.createElement("p");if(n){a.appendChild(document.createTextNode("Please "));const o=document.createElement("a");o.href="/login",o.textContent="sign in",a.appendChild(o),a.appendChild(document.createTextNode(" to view your entries."))}else a.textContent=u;return t.appendChild(a),t}function k(u,n){const t=document.createElement("template");t.innerHTML=n||"",u.replaceWith(t.content)}h.forEach(async u=>{const n=u.dataset.collectionAttrs;if(!n)return;if(!x){u.replaceChildren(C("",!0));return}const t=a=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify({attrs:n})});try{let a=x,o=await t(a);if(o.status===401){const d=S.get("auth_refresh_token");if(d){const g=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:d})});if(g.ok){const{token:y}=await g.json();S.set("auth_token",y),a=y,o=await t(a)}}}if(!o.ok){const{error:d}=await o.json().catch(()=>({}));u.replaceChildren(C(d||"Unable to load entries.",!1));return}const{html:e}=await o.json();k(u,e)}catch{u.replaceChildren(C("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),typeof Domma<"u"&&Domma.syntax?.scan&&Domma.syntax.scan(),D(document.body)},100)})(),(function(){const h=document.querySelectorAll("[data-collection-table]");!h.length||typeof T>"u"||!T.create||h.forEach(x=>{let C;try{C=JSON.parse(atob(x.dataset.payload))}catch{return}const{columns:k,rows:u,search:n,sortable:t,exportable:a,pageSize:o,empty:e,ctaConfig:d,slug:g,exportCfg:y}=C;if(!k?.length)return;if(d){const r=k.findIndex(f=>f.key==="_cta");r!==-1&&(k[r]={key:"_cta",title:"",render:(f,c)=>{const s=document.createElement("button");if(s.className=`btn btn-${d.style||"primary"} dm-cta-trigger`,s.dataset.action=d.action||"",s.dataset.entry=c._entryId||"",d.confirm&&(s.dataset.confirm=d.confirm),d.icon){const i=document.createElement("span");i.dataset.icon=d.icon,s.appendChild(i),s.appendChild(document.createTextNode(" "))}return s.appendChild(document.createTextNode(d.label||"Run")),s}})}const p="col-table-"+Math.random().toString(36).slice(2,7),m=document.createElement("div");m.id=p,x.replaceChildren(m),T.create("#"+p,{data:u,columns:k,search:n,sortable:t,exportable:a,pageSize:o,emptyMessage:e}),d&&D(m),x._dmTable={columns:k,rows:u,slug:g,exportCfg:y,render(r){T.create("#"+p,{data:r,columns:k,search:n,sortable:t,exportable:a,pageSize:o,emptyMessage:e}),d&&D(m)}}})})(),(function(){const h=document.querySelectorAll("[data-form-inline]");if(!h.length||typeof F>"u")return;function x(t){const a=t&&t.logic;return!!(a&&(a.visibility||a.requirement))}function C(t,a){const o={};return(t||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const d=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),g=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":d?"chooser":e.type,y={...e.formConfig||{}};y.span==="full"&&a&&(y.span=a);const p={type:g,required:x(e)?!1:e.required,label:e.label,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{hint:e.helper},...y}};d&&(e.variant&&(p.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(p.multiple=!0),e.density&&(p.density=e.density),e.columns&&(p.columns=e.columns),e.accent&&(p.accent=e.accent),e.accentStyle&&(p.accentStyle=e.accentStyle),e.glow&&(p.glow=!0),e.glowColour&&(p.glowColour=e.glowColour),e.shadow&&(p.shadow=e.shadow),e.shadowColour&&(p.shadowColour=e.shadowColour)),o[e.name]=p}),o}function k(t){const a={};return(t||[]).forEach(o=>{if(!(!o.name||o.type==="page-break"||o.type==="spacer")&&(o.type==="select"||o.type==="multiselect")&&o.required){const e=(o.options||[])[0];e&&(a[o.name]=typeof e=="object"?e.value:e)}}),a}function u(t,a){(a||[]).forEach(o=>{if(o.type!=="date"||!o.name)return;const e=t.querySelector(`[name="${o.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function n(t,a,o){let e=t.querySelector(".cms-form-message");e||(e=document.createElement("p"),e.className="cms-form-message",t.appendChild(e)),e.textContent=a,e.style.cssText=o?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}h.forEach(t=>{let a;try{a=JSON.parse(atob(t.dataset.formInline))}catch{return}const o=a.fields||[],e=a.settings||{},d=Number(e.columns)===3?3:2,g=e.layout||"grid",y=o.some(c=>c.type==="page-break"),p=async c=>{try{const s=t.querySelector(".fb-form-honeypot"),i=s?.querySelector('input[type="text"]')||null,l=s?.querySelector('[name="_t"]')||null,v=Object.assign({},c);delete v._t,s?.dataset.hpName&&delete v[s.dataset.hpName],i!==null&&(v._hp=i.value),l!==null&&(v._t=l.value);const b=await H.post(`/api/forms/submit/${a.slug}`,v);if(b&&b.error){n(t,b.error,!0);const I=new Error(b.error);throw I.formSubmitFailed=!0,I}if(b?.redirect){window.location.href=b.redirect;return}for(;t.firstChild;)t.removeChild(t.firstChild);n(t,b?.message||e.successMessage||"Thank you for your submission.",!1)}catch(s){throw s.formSubmitFailed||n(t,s.message||"Submission failed. Please try again.",!0),s}},m=Date.now();function r(c){const s=c.querySelector("form");if(!s||s.querySelector(".fb-form-honeypot"))return;const i=document.createElement("div");i.className="fb-form-honeypot",i.setAttribute("aria-hidden","true");const l=document.createElement("input");l.name=s.querySelector('[name="website"]')?"_website":"website",i.dataset.hpName=l.name,l.type="text",l.tabIndex=-1,l.autocomplete="url",l.placeholder="https://",i.appendChild(l);const v=document.createElement("input");v.name="_t",v.type="hidden",v.value=m,i.appendChild(v),s.appendChild(i)}function f(c){if(!window.FormLogicEngine||!o.some(i=>i.logic))return null;const s=new window.FormLogicEngine.FormLogicRuntime(a,t,c?{baseValues:c}:void 0);if(s.init(),t._formLogicRuntime=s,t._formLogicObserved)return s;if(t._formLogicObserved=!0,t.parentNode&&typeof MutationObserver<"u"){const i=new MutationObserver(function(l){for(const v of l)for(const b of v.removedNodes)if(b===t||b.nodeType===1&&b.contains&&b.contains(t)){t._formLogicRuntime&&t._formLogicRuntime.destroy(),i.disconnect();return}});i.observe(t.parentNode,{childList:!0,subtree:!1}),t._formLogicObserver=i}return s}if(y&&F.wizard){const c=B(o,a.title),s=c.map(_=>({title:_.title,description:_.description,fields:C(_.fields,d)})),i=c.map(_=>_.fields);let l=0,v=null,b=null;const I=_=>{const A=window.FormLogicEngine;if(!A||!A.validateFields)return;const L=A.validateFields(t,i[l]||[]);L.valid||(_.preventDefault(),_.stopImmediatePropagation(),P(t,L.missing),R(L))},q=()=>{const _=i[l]||o;u(t,_),O(t,_),e.honeypot!==!1&&r(t),v&&v.destroy(),v=f(b?.getStepData?b.getStepData():void 0),t.querySelectorAll(".wizard-next, .wizard-submit").forEach(A=>A.addEventListener("click",I,!0))};b=F.wizard(t,{schema:{steps:s},layout:g,columns:d,finishText:e.submitText||"Finish",onSubmit:p,onStepChange:_=>{l=_,setTimeout(q,0)}}),Promise.resolve(b).then(q)}else if(F.render){const c=F.render(t,C(o,d),k(o),{submitText:e.submitText||"Submit",layout:g,columns:d,onSubmit:p});Promise.resolve(c).then(function(){if(g==="grid"&&e.submitSpan==="full"){const s=t.querySelector(".form-buttons");s&&s.classList.add("col-span-full")}u(t,o),O(t,o),e.honeypot!==!1&&r(t),f()})}}),$(document).on("click",".dm-banner__dismiss",function(){$(this).closest(".dm-banner").remove()})})();
1
+ import{makeBadgeEl as z,applyColour as M,applyPill as O,applyFloat as j}from"/public/js/menu-decor.mjs?v=20260829-float-panel";import{initFloatingMenus as B}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as N,markInvalidFields as P,showValidationPopup as R}from"/public/js/forms.js?v=20260821-wizard-grid";import{buildWizardSteps as V}from"/public/js/form-steps.mjs";$(()=>{const w=window.__CMS_NAV__||{},h=window.__CMS_SITE__||{};if(h.autoTheme?.enabled){let t=function(n){const e=(n||"07:00").split(":");return+e[0]*60+(+e[1]||0)},a=function(){const n=new Date,e=n.getHours()*60+n.getMinutes();return e>=t(o.dayStart)&&e<t(o.nightStart)?o.dayTheme:o.nightTheme};const o=h.autoTheme;Domma.theme.set(a()),setInterval(()=>Domma.theme.set(a()),6e4)}const C=$("#site-navbar");if(C.length&&w.brand){let a=function(i,c=0){return Array.isArray(i)?i.filter(s=>!(s&&s.type==="spacer")).filter(s=>!(s&&s.type==="separator"&&c===0)).map(s=>s&&s.type==="separator"?{divider:!0}:Array.isArray(s?.items)?{...s,items:a(s.items,c+1)}:s):i},n=function(i){return Array.isArray(i)?i.filter(c=>!(c&&(c.type==="separator"||c.type==="spacer"))):[]};const o={...w.brand},t=o.size&&o.size!=="md"?` navbar-brand-${o.size}`:"";if(o.logo||o.icon||o.tagline){let i="";o.logo?i+=`<img src="${o.logo}" class="navbar-brand-logo" alt="${o.text||""}">`:o.icon&&(i+=`<span data-icon="${o.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),o.text&&(i+=`<span class="navbar-brand-text${t}">${o.text}</span>`),o.tagline&&(i+=`<small class="navbar-brand-tagline">${o.tagline}</small>`),o.html=i}else t&&o.text&&(o.html=`<span class="navbar-brand-text${t}">${o.text}</span>`);const e=w.position==="floating",u=w.orientation==="vertical";Domma.elements.navbar("#site-navbar",{brand:o,items:a(w.items||[]),variant:w.variant||"dark",position:e?"static":w.position||"sticky",collapsible:!0,appearOnHover:!!w.appearOnHover}),Domma.icons.scan("#site-navbar"),u&&C.get(0).classList.add("dm-nav-vertical",w.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left"),e&&(j(C.get(0),w),B(C.get(0).parentNode||document));const g=C.get(0),y=g.querySelector(".navbar-toggle"),f=g.querySelector(".navbar-collapse");if(y&&f){let i=null;const c=()=>{f.classList.contains("show")&&y.click()},s=()=>{i&&(i.remove(),i=null),document.body.classList.remove("dm-drawer-open")},r=()=>{i||(i=document.createElement("div"),i.className="dm-navbar-backdrop",i.addEventListener("click",c),document.body.appendChild(i),document.body.classList.add("dm-drawer-open"))};y.addEventListener("click",()=>{requestAnimationFrame(()=>{f.classList.contains("show")?r():s()})}),f.addEventListener("click",l=>{l.target.closest("a")&&requestAnimationFrame(s)}),document.addEventListener("keydown",l=>{l.key==="Escape"&&f.classList.contains("show")&&c()})}const d=g.querySelector(".navbar-nav");if(d){const i=Array.from(d.children);let c=0;for(const s of w.items||[])if(!(s&&s.type==="separator")){if(s&&s.type==="spacer"){const r=document.createElement("li"),l=["xs","sm","md","lg","xl","flex"].includes(s.size)?s.size:"md";r.className=`dm-menu-spacer dm-menu-spacer--${l}`,r.setAttribute("aria-hidden","true"),d.insertBefore(r,i[c]||null);continue}c++}}const b=n(w.items||[]);g.querySelectorAll(".navbar-link[data-index], .navbar-dropdown-item[data-index]").forEach(i=>{const c=b[parseInt(i.dataset.index,10)];if(!c)return;const s=i.dataset.subindex!=null,r=s&&Array.isArray(c.items)?c.items[parseInt(i.dataset.subindex,10)]:c;if(!r)return;r.colour&&M(i,r.colour),r.pill&&!s&&O(i,r.pill);const l=z(r.badge);l&&i.appendChild(l)})}const x=$("#site-footer");if(x.length){let e=function(r){return String(r??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")},u=function(r){return e(r).replace(/'/g,"&#39;")},g=function(r){if(r&&r.type==="separator")return'<li class="footer-sep"><hr></li>';if(r&&r.type==="spacer")return`<li class="dm-menu-spacer dm-menu-spacer--${["xs","sm","md","lg","xl","flex"].includes(r.size)?r.size:"md"}" aria-hidden="true"></li>`;const l=u(r.url||"#"),m=e(r.text||""),v=Array.isArray(r.items)&&r.items.length?`<ul>${r.items.map(g).join("")}</ul>`:"";return`<li><a href="${l}">${m}</a>${v}</li>`},y=function(r,l){return(r||[]).forEach(m=>{m&&(m.type==="separator"||m.type==="spacer")||(l.push(m),Array.isArray(m.items)&&m.items.length&&y(m.items,l))}),l};const o=h.social||{},t={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 a='<div class="footer-inner container">';const n=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""};if(n.copyright&&(a+=`<p>${e(n.copyright)}</p>`),n.primary.length&&(a+=`<nav class="footer-nav footer-nav--primary"><ul>${n.primary.map(g).join("")}</ul></nav>`),n.legal.length&&(a+=`<nav class="footer-nav footer-nav--legal"><ul>${n.legal.map(g).join("")}</ul></nav>`),h.footer){const r=Object.keys(t).filter(l=>o[l]);r.length&&(a+='<div class="footer-social">',r.forEach(l=>{const{label:m,svg:v}=t[l];a+=`<a href="${o[l]}" target="_blank" rel="noopener noreferrer" aria-label="${m}" class="footer-social-link">${v}</a>`}),a+="</div>")}a+="</div>",x.html(a);const f=[...y(n.primary,[]),...y(n.legal,[])];x.get(0).querySelectorAll(".footer-nav a").forEach((r,l)=>{const m=f[l];if(!m)return;m.colour&&M(r,m.colour);const v=z(m.badge);v&&r.appendChild(v)});const b=h.footer?.showMotionToggle!==!1,i=S.get("reduced_motion"),c=i!==null?!!i:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),s=x.get(0).querySelector(".footer-inner");if(b&&s){const r=document.createElement("input");r.type="checkbox",r.className="form-switch-input",r.id="dm-motion-switch",r.checked=c,r.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const l=document.createElement("span");l.className="form-switch-label",l.textContent="Reduce motion";const m=document.createElement("label");m.className="form-switch footer-motion-switch",m.title="Reduce motion",m.appendChild(r),m.appendChild(l),s.appendChild(m)}}$("#site-sidebar").length&&Domma.elements.sidebar("#site-sidebar",{autoGenerate:!0,selector:"h2, h3",collapsible:!1,push:!0,contentSelector:".site-content"}),Domma.icons.scan(),Domma.syntax?.scan&&Domma.syntax.scan();const p=$(".page-body");if(p.length){p.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),p.find(".tabs").each(function(){Domma.elements.tabs(this)}),p.find(".carousel").each(function(){const o={autoplay:this.dataset.autoplay==="true",interval:parseInt(this.dataset.interval,10)||5e3,loop:this.dataset.loop!=="false",animation:this.dataset.animation||"slide"};if(this.dataset.animationDuration){const t=parseInt(this.dataset.animationDuration,10);Number.isFinite(t)&&(o.animationDuration=t)}this.dataset.animationEasing&&(o.animationEasing=this.dataset.animationEasing),this.dataset.ctx&&(this._dmCtxSlides=Array.from(this.querySelectorAll(".carousel-slide")).map(t=>t.cloneNode(!0)),this._dmCarouselOpts=o),this._dmCarousel=Domma.elements.carousel(this,o)}),p.find(".dm-countdown").each(function(){const o={autoStart:!0};this.dataset.to&&(o.targetDate=new Date(this.dataset.to)),this.dataset.duration&&(o.duration=parseInt(this.dataset.duration,10)),this.dataset.format&&(o.format=this.dataset.format),Domma.elements.timer(this,o)}),p.find("[data-tooltip]").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),p.find(".dm-progression").each(function(){const o=Array.from(this.querySelectorAll(".dm-progression-item")).map(n=>{const e={title:n.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:n.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return n.dataset.date&&(e.date=n.dataset.date),n.dataset.status&&(e.status=n.dataset.status),n.dataset.entryId&&(e._entryId=n.dataset.entryId),e}),t=this.dataset.layout==="centred"?"centered":this.dataset.layout||"vertical",a=Domma.elements.progression(this,{layout:t,theme:this.dataset.theme||"minimal",mode:this.dataset.mode||"timeline",statusIcons:!0,allowHtml:!0,...o.length?{items:o}:{}});o.length&&(this._dmCtx={instance:a,items:o})});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(o=>{const t=o.dataset.revealMode||"stagger",a=o.dataset.revealAnimation||"slide-up",n=parseInt(o.dataset.revealDuration,10)||400,e=parseInt(o.dataset.revealStagger,10)||60,u=parseInt(o.dataset.revealDelay,10)||0,g=o.dataset.revealDirection==="rtl",y=Array.from(o.children),f={"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)"};y.forEach((d,b)=>{d.style.opacity="0",d.style.transform=f[a]||"",d.style.transition=`opacity ${n}ms ease, transform ${n}ms ease`;const i=g?y.length-1-b:b;d.style.transitionDelay=t==="stagger"?`${u+i*e}ms`:`${u}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const d=new IntersectionObserver(b=>{b.forEach(i=>{i.isIntersecting&&(i.target.offsetWidth,i.target.style.opacity="1",i.target.style.transform="none",d.unobserve(i.target))})},{threshold:.1});y.forEach(b=>d.observe(b))}))}),p.find(".card[data-collapsible]").each(function(){const o=this.querySelector(".card-header");o&&o.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),p.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const o=this.dataset.soTarget,t=document.getElementById(o);if(!t)return;const a=E.slideover({title:t.dataset.soTitle||"",size:t.dataset.soSize||"md",position:t.dataset.soPosition||"right"});t.style.display="",a.setContent(t),a.open()})})}if(typeof $.setup=="function"){const o=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(t=>{try{const a=atob(t.dataset.config),n=JSON.parse(a);Object.assign(o,n)}catch{}}),Object.keys(o).length>0){const t={};for(const[a,n]of Object.entries(o)){const e=n?.events?.click,{confirm:u,toast:g,alert:y,prompt:f,...d}=e||{};u||g||y||f?($(a).on("click",async function(i){if(i.preventDefault(),u&&!await E.confirm(u))return;let c=null;if(!(f&&(c=await E.prompt(f,{inputPlaceholder:d.promptPlaceholder||"",inputValue:d.promptDefault||""}),c===null))){if(d.target){const s=$(d.target);d.toggleClass&&s.toggleClass(d.toggleClass),d.addClass&&s.addClass(d.addClass),d.removeClass&&s.removeClass(d.removeClass),c!==null&&(d.setText&&s.text(c),d.setVal&&s.val(c),d.setAttr&&s.attr(d.setAttr,c))}d.href&&(window.location.href=d.href),g&&E.toast(g,{type:d.toastType||"success"}),y&&E.alert(y)}}),Object.keys(d).length&&(t[a]={...n,events:{...n.events,click:d}})):t[a]=n}$.setup(t)}}p.length&&D(p.get(0))});function D(w){w.querySelectorAll(".dm-cta-trigger").forEach(h=>{h.__ctaWired||(h.__ctaWired=!0,h.addEventListener("click",async()=>{const C=h.dataset.action,x=h.dataset.entry,k=h.dataset.confirm;let p=S.get("auth_token");if(!p){E.toast("Please log in to perform this action.",{type:"warning"});return}if(k&&!await E.confirm(k))return;const o=Array.from(h.childNodes).map(a=>a.cloneNode(!0));h.disabled=!0,h.textContent="Running\u2026";const t=a=>fetch(`/api/actions/${C}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify({entryId:x})});try{let a=await t(p);if(a.status===401){const e=S.get("auth_refresh_token");if(e){const u=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(u.ok){const{token:g}=await u.json();S.set("auth_token",g),p=g,a=await t(p)}}}const n=await a.json().catch(()=>({}));if(!a.ok)throw new Error(n.error||n.message||`Error ${a.status}`);E.toast(n.message||"Action completed.",{type:"success"})}catch(a){E.toast(a.message||"Action failed.",{type:"error"})}finally{h.disabled=!1,h.textContent="",o.forEach(a=>h.appendChild(a)),Domma.icons.scan(h)}}))})}(function(){const h=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!h.length)return;const C=S.get("auth_token");function x(p,o){const t=document.createElement("div");t.className="dm-collection-display dm-collection-empty";const a=document.createElement("p");if(o){a.appendChild(document.createTextNode("Please "));const n=document.createElement("a");n.href="/login",n.textContent="sign in",a.appendChild(n),a.appendChild(document.createTextNode(" to view your entries."))}else a.textContent=p;return t.appendChild(a),t}function k(p,o){const t=document.createElement("template");t.innerHTML=o||"",p.replaceWith(t.content)}h.forEach(async p=>{const o=p.dataset.collectionAttrs;if(!o)return;if(!C){p.replaceChildren(x("",!0));return}const t=a=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify({attrs:o})});try{let a=C,n=await t(a);if(n.status===401){const u=S.get("auth_refresh_token");if(u){const g=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:u})});if(g.ok){const{token:y}=await g.json();S.set("auth_token",y),a=y,n=await t(a)}}}if(!n.ok){const{error:u}=await n.json().catch(()=>({}));p.replaceChildren(x(u||"Unable to load entries.",!1));return}const{html:e}=await n.json();k(p,e)}catch{p.replaceChildren(x("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),typeof Domma<"u"&&Domma.syntax?.scan&&Domma.syntax.scan(),D(document.body)},100)})(),(function(){const h=document.querySelectorAll("[data-collection-table]");!h.length||typeof T>"u"||!T.create||h.forEach(C=>{let x;try{x=JSON.parse(atob(C.dataset.payload))}catch{return}const{columns:k,rows:p,search:o,sortable:t,exportable:a,pageSize:n,empty:e,ctaConfig:u,slug:g,exportCfg:y}=x;if(!k?.length)return;if(u){const b=k.findIndex(i=>i.key==="_cta");b!==-1&&(k[b]={key:"_cta",title:"",render:(i,c)=>{const s=document.createElement("button");if(s.className=`btn btn-${u.style||"primary"} dm-cta-trigger`,s.dataset.action=u.action||"",s.dataset.entry=c._entryId||"",u.confirm&&(s.dataset.confirm=u.confirm),u.icon){const r=document.createElement("span");r.dataset.icon=u.icon,s.appendChild(r),s.appendChild(document.createTextNode(" "))}return s.appendChild(document.createTextNode(u.label||"Run")),s}})}const f="col-table-"+Math.random().toString(36).slice(2,7),d=document.createElement("div");d.id=f,C.replaceChildren(d),T.create("#"+f,{data:p,columns:k,search:o,sortable:t,exportable:a,pageSize:n,emptyMessage:e}),u&&D(d),C._dmTable={columns:k,rows:p,slug:g,exportCfg:y,render(b){T.create("#"+f,{data:b,columns:k,search:o,sortable:t,exportable:a,pageSize:n,emptyMessage:e}),u&&D(d)}}})})(),(function(){const h=document.querySelectorAll("[data-form-inline]");if(!h.length||typeof F>"u")return;function C(t){const a=t&&t.logic;return!!(a&&(a.visibility||a.requirement))}function x(t,a){const n={};return(t||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const u=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),g=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":u?"chooser":e.type,y={...e.formConfig||{}};y.span==="full"&&a&&(y.span=a);const f={type:g,required:C(e)?!1:e.required,label:e.label,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{hint:e.helper},...y}};u&&(e.variant&&(f.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(f.multiple=!0),e.density&&(f.density=e.density),e.columns&&(f.columns=e.columns),e.accent&&(f.accent=e.accent),e.accentStyle&&(f.accentStyle=e.accentStyle),e.glow&&(f.glow=!0),e.glowColour&&(f.glowColour=e.glowColour),e.shadow&&(f.shadow=e.shadow),e.shadowColour&&(f.shadowColour=e.shadowColour)),n[e.name]=f}),n}function k(t){const a={};return(t||[]).forEach(n=>{if(!(!n.name||n.type==="page-break"||n.type==="spacer")&&(n.type==="select"||n.type==="multiselect")&&n.required){const e=(n.options||[])[0];e&&(a[n.name]=typeof e=="object"?e.value:e)}}),a}function p(t,a){(a||[]).forEach(n=>{if(n.type!=="date"||!n.name)return;const e=t.querySelector(`[name="${n.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function o(t,a,n){let e=t.querySelector(".cms-form-message");e||(e=document.createElement("p"),e.className="cms-form-message",t.appendChild(e)),e.textContent=a,e.style.cssText=n?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}h.forEach(t=>{let a;try{a=JSON.parse(atob(t.dataset.formInline))}catch{return}const n=a.fields||[],e=a.settings||{},u=Number(e.columns)===3?3:2,g=e.layout||"grid",y=n.some(c=>c.type==="page-break"),f=async c=>{try{const s=t.querySelector(".fb-form-honeypot"),r=s?.querySelector('input[type="text"]')||null,l=s?.querySelector('[name="_t"]')||null,m=Object.assign({},c);delete m._t,s?.dataset.hpName&&delete m[s.dataset.hpName],r!==null&&(m._hp=r.value),l!==null&&(m._t=l.value);const v=await H.post(`/api/forms/submit/${a.slug}`,m);if(v&&v.error){o(t,v.error,!0);const A=new Error(v.error);throw A.formSubmitFailed=!0,A}if(v?.redirect){window.location.href=v.redirect;return}for(;t.firstChild;)t.removeChild(t.firstChild);o(t,v?.message||e.successMessage||"Thank you for your submission.",!1)}catch(s){throw s.formSubmitFailed||o(t,s.message||"Submission failed. Please try again.",!0),s}},d=Date.now();function b(c){const s=c.querySelector("form");if(!s||s.querySelector(".fb-form-honeypot"))return;const r=document.createElement("div");r.className="fb-form-honeypot",r.setAttribute("aria-hidden","true");const l=document.createElement("input");l.name=s.querySelector('[name="website"]')?"_website":"website",r.dataset.hpName=l.name,l.type="text",l.tabIndex=-1,l.autocomplete="url",l.placeholder="https://",r.appendChild(l);const m=document.createElement("input");m.name="_t",m.type="hidden",m.value=d,r.appendChild(m),s.appendChild(r)}function i(c){if(!window.FormLogicEngine||!n.some(r=>r.logic))return null;const s=new window.FormLogicEngine.FormLogicRuntime(a,t,c?{baseValues:c}:void 0);if(s.init(),t._formLogicRuntime=s,t._formLogicObserved)return s;if(t._formLogicObserved=!0,t.parentNode&&typeof MutationObserver<"u"){const r=new MutationObserver(function(l){for(const m of l)for(const v of m.removedNodes)if(v===t||v.nodeType===1&&v.contains&&v.contains(t)){t._formLogicRuntime&&t._formLogicRuntime.destroy(),r.disconnect();return}});r.observe(t.parentNode,{childList:!0,subtree:!1}),t._formLogicObserver=r}return s}if(y&&F.wizard){const c=V(n,a.title),s=c.map(_=>({title:_.title,description:_.description,fields:x(_.fields,u)})),r=c.map(_=>_.fields);let l=0,m=null,v=null;const A=_=>{const I=window.FormLogicEngine;if(!I||!I.validateFields)return;const L=I.validateFields(t,r[l]||[]);L.valid||(_.preventDefault(),_.stopImmediatePropagation(),P(t,L.missing),R(L))},q=()=>{const _=r[l]||n;p(t,_),N(t,_),e.honeypot!==!1&&b(t),m&&m.destroy(),m=i(v?.getStepData?v.getStepData():void 0),t.querySelectorAll(".wizard-next, .wizard-submit").forEach(I=>I.addEventListener("click",A,!0))};v=F.wizard(t,{schema:{steps:s},layout:g,columns:u,finishText:e.submitText||"Finish",onSubmit:f,onStepChange:_=>{l=_,setTimeout(q,0)}}),Promise.resolve(v).then(q)}else if(F.render){const c=F.render(t,x(n,u),k(n),{submitText:e.submitText||"Submit",layout:g,columns:u,onSubmit:f});Promise.resolve(c).then(function(){if(g==="grid"&&e.submitSpan==="full"){const s=t.querySelector(".form-buttons");s&&s.classList.add("col-span-full")}p(t,n),N(t,n),e.honeypot!==!1&&b(t),i()})}}),$(document).on("click",".dm-banner__dismiss",function(){$(this).closest(".dm-banner").remove()})})();
@@ -4117,7 +4117,10 @@ export async function parseMarkdown(raw, opts = {}) {
4117
4117
  ]),
4118
4118
  allowedAttributes: {
4119
4119
  ...sanitizeHtml.defaults.allowedAttributes,
4120
- '*': ['class', 'id', 'style', 'data-*'],
4120
+ // aria-hidden marks decorative markup (menu spacers, the floating panel's
4121
+ // drag grip) so it isn't announced as an empty item. Presentational only,
4122
+ // no script surface - unlike the on* handlers stripped globally.
4123
+ '*': ['class', 'id', 'style', 'data-*', 'aria-hidden'],
4121
4124
  img: ['src', 'alt', 'title', 'width', 'height', 'loading'],
4122
4125
  form: ['action', 'method'],
4123
4126
  input: ['type', 'name', 'placeholder', 'value', 'required', 'disabled',
@@ -10,7 +10,7 @@
10
10
  * Item filtering (visibility, `hidden`), depth capping and badge-count
11
11
  * resolution happen before this - callers hand in the items they want drawn.
12
12
  */
13
- import {colourToCss, floatToCss} from './menus.js';
13
+ import {colourToCss, floatToCss, floatBehaviour, spacerSize} from './menus.js';
14
14
 
15
15
  /** Attribute-safe escaping - mirrors escapeAttr in markdown.js. */
16
16
  function esc(str) {
@@ -35,6 +35,11 @@ export function renderMenuItemsAsUl(items) {
35
35
  if (!items || !items.length) return '';
36
36
  const lis = items.map(it => {
37
37
  if (it && it.type === 'separator') return '<li class="dm-menu-sep" role="separator"><hr></li>';
38
+ // Spacers are blank room, not content - hidden from assistive tech so
39
+ // they don't read as an empty list item.
40
+ if (it && it.type === 'spacer') {
41
+ return `<li class="dm-menu-spacer dm-menu-spacer--${esc(spacerSize(it))}" aria-hidden="true"></li>`;
42
+ }
38
43
  const href = esc(it.url || '#');
39
44
  const text = esc(it.text || '');
40
45
  const child = Array.isArray(it.items) && it.items.length ? renderMenuItemsAsUl(it.items) : '';
@@ -85,6 +90,61 @@ export function buildMenuNav(menu, items, opts = {}) {
85
90
  ? ` style="${esc(floatCss)}" data-float-anchor="${esc(menu.float?.anchor || 'TL')}"`
86
91
  : '';
87
92
 
88
- return `<nav class="dm-menu dm-menu--${esc(menu.slug)}${orientClass}${variantClass}${floatClass}${klass}"`
89
- + ` data-menu="${esc(menu.slug)}"${floatAttrs}>${renderMenuItemsAsUl(items)}</nav>`;
93
+ // Panel behaviour - only ever applies where the menu actually floats, so a
94
+ // shortcode rendered inline (noFloat) never grows a drag handle.
95
+ const beh = floatCss ? floatBehaviour(menu) : floatBehaviour(null);
96
+ const behAttrs = [
97
+ beh.collapsible && ' data-float-collapsible="1"',
98
+ beh.movable && ' data-float-movable="1"',
99
+ beh.resizable && ' data-float-resizable="1"'
100
+ ].filter(Boolean).join('');
101
+ // The author's default state is stamped as a class, not left to JS, so a
102
+ // panel that starts collapsed never flashes open on first paint.
103
+ const collapsedClass = beh.collapsed ? ' is-collapsed' : '';
104
+
105
+ const flexClass = hasFlexSpacer(items) ? ' dm-menu--has-flex-spacer' : '';
106
+
107
+ return `<nav class="dm-menu dm-menu--${esc(menu.slug)}${orientClass}${variantClass}${floatClass}${collapsedClass}${flexClass}${klass}"`
108
+ + ` data-menu="${esc(menu.slug)}"${floatAttrs}${behAttrs}>`
109
+ + buildFloatChrome(menu, beh)
110
+ + renderMenuItemsAsUl(items)
111
+ + `</nav>`;
112
+ }
113
+
114
+ /** Does this level contain a spacer that soaks up the leftover room? */
115
+ function hasFlexSpacer(items) {
116
+ return (items || []).some(it => it && it.type === 'spacer' && spacerSize(it) === 'flex');
117
+ }
118
+
119
+ /**
120
+ * The bar across the top of a floating panel: drag grip, title, and the
121
+ * hamburger that collapses the panel down to just that button.
122
+ *
123
+ * Returns '' when the menu is neither collapsible nor movable - a plain
124
+ * floating panel keeps the bare `<ul>` markup it has always had.
125
+ *
126
+ * @param {object} menu
127
+ * @param {object} beh Result of floatBehaviour(menu)
128
+ * @returns {string}
129
+ */
130
+ function buildFloatChrome(menu, beh) {
131
+ if (!beh.chrome) return '';
132
+
133
+ const grip = beh.movable
134
+ ? '<span class="dm-menu-float-grip" aria-hidden="true"></span>'
135
+ : '';
136
+ const title = beh.title
137
+ ? `<span class="dm-menu-float-title">${esc(beh.title)}</span>`
138
+ : '';
139
+ // No aria-expanded here: sanitize-html strips aria attributes from the
140
+ // `[menu]` shortcode path, so menu-float.js stamps the ARIA state on init
141
+ // and keeps it in step from then on.
142
+ const toggle = beh.collapsible
143
+ ? `<button type="button" class="dm-menu-float-toggle" data-float-toggle`
144
+ + ` aria-label="${esc(beh.title ? `Toggle ${beh.title}` : 'Toggle menu')}">`
145
+ + `<span class="dm-menu-float-icon" data-icon="bars"></span></button>`
146
+ : '';
147
+
148
+ return `<div class="dm-menu-float-bar"${beh.movable ? ' data-float-handle' : ''}>`
149
+ + `${grip}${title}${toggle}</div>`;
90
150
  }
@@ -184,6 +184,48 @@ export function floatToCss(menu) {
184
184
  return decls.join(';');
185
185
  }
186
186
 
187
+ // ---------------------------------------------------------------------------
188
+ // Floating panel behaviour - collapse to a hamburger, drag to move, resize.
189
+ //
190
+ // These are behaviour flags rather than CSS: `floatToCss` still owns where the
191
+ // panel starts, and `menu-float.js` owns what the visitor can then do to it.
192
+ // The server needs them at render time to emit the panel chrome (title bar and
193
+ // toggle button), so they resolve here and are mirrored in menu-decor.mjs.
194
+ //
195
+ // A visitor's own drag/size/collapse state lives in their browser, keyed by
196
+ // menu slug - the config below is only ever the starting point.
197
+ // ---------------------------------------------------------------------------
198
+ export const FLOAT_FLAGS = ['collapsible', 'collapsed', 'movable', 'resizable'];
199
+
200
+ /**
201
+ * Resolve a floating menu's behaviour flags, plus whether it needs the chrome
202
+ * bar drawn at all. Returns all-false for a menu that is not floating, so a
203
+ * `[menu]` shortcode rendered inline never grows a drag handle.
204
+ *
205
+ * `title` is what the bar shows: the author's override, else the menu name.
206
+ *
207
+ * @param {object} menu A menu record (uses .position, .float, .name)
208
+ * @returns {{collapsible: boolean, collapsed: boolean, movable: boolean,
209
+ * resizable: boolean, chrome: boolean, title: string}}
210
+ */
211
+ export function floatBehaviour(menu) {
212
+ const off = {collapsible: false, collapsed: false, movable: false, resizable: false, chrome: false, title: ''};
213
+ if (!menu || menu.position !== 'floating') return off;
214
+
215
+ const f = (menu.float && typeof menu.float === 'object' && !Array.isArray(menu.float)) ? menu.float : {};
216
+ const flags = {
217
+ collapsible: !!f.collapsible,
218
+ collapsed: !!f.collapsible && !!f.collapsed,
219
+ movable: !!f.movable,
220
+ resizable: !!f.resizable
221
+ };
222
+ // The bar carries the toggle and doubles as the drag handle; a menu that
223
+ // can only be resized needs no bar - the UA resize grip is enough.
224
+ const chrome = flags.collapsible || flags.movable;
225
+ const title = typeof f.title === 'string' && f.title !== '' ? f.title : (menu.name || '');
226
+ return {...flags, chrome, title: chrome ? title : ''};
227
+ }
228
+
187
229
  // ---------------------------------------------------------------------------
188
230
  // Orientation - how the menu flows where it renders. `horizontal` is the
189
231
  // historic behaviour and stays the default for every menu without the field.
@@ -193,6 +235,23 @@ export function floatToCss(menu) {
193
235
  // `side` only means anything for a vertical menu in the navbar slot - it picks
194
236
  // the viewport edge the rail docks to.
195
237
  // ---------------------------------------------------------------------------
238
+ // ---------------------------------------------------------------------------
239
+ // Spacer items - `{type: 'spacer'}`, the sibling of `{type: 'separator'}`.
240
+ //
241
+ // A separator draws a line; a spacer draws nothing and just takes up room. The
242
+ // fixed sizes map to the theme's spacing tokens so a spacer scales with the
243
+ // rest of the UI. `flex` is the interesting one: it soaks up all the leftover
244
+ // room, which is how you push the items after it to the far end of a navbar or
245
+ // down to the foot of a vertical rail.
246
+ // ---------------------------------------------------------------------------
247
+ export const SPACER_SIZES = new Set(['xs', 'sm', 'md', 'lg', 'xl', 'flex']);
248
+ export const SPACER_DEFAULT_SIZE = 'md';
249
+
250
+ /** A spacer's size, defaulted and validated. */
251
+ export function spacerSize(item) {
252
+ return SPACER_SIZES.has(item?.size) ? item.size : SPACER_DEFAULT_SIZE;
253
+ }
254
+
196
255
  export const ORIENTATIONS = new Set(['horizontal', 'vertical']);
197
256
  export const SIDES = new Set(['left', 'right']);
198
257
 
@@ -260,7 +319,7 @@ export async function resolveMenuDecorations(items, opts = {}) {
260
319
 
261
320
  async function walk(list) {
262
321
  return Promise.all((list || []).map(async (it) => {
263
- if (!it || it.type === 'separator') return it;
322
+ if (!it || it.type === 'separator' || it.type === 'spacer') return it;
264
323
  const next = {...it};
265
324
  if (it.badge && it.badge.countFrom) {
266
325
  try {
@@ -591,6 +650,15 @@ export function validateMenu(menu) {
591
650
  if (menu.float.accent != null && menu.float.accent !== '' && !isValidColour(menu.float.accent)) {
592
651
  errors.push('float.accent must be a preset key or #rrggbb');
593
652
  }
653
+ for (const key of FLOAT_FLAGS) {
654
+ const v = menu.float[key];
655
+ if (v != null && typeof v !== 'boolean') {
656
+ errors.push(`float.${key} must be true or false`);
657
+ }
658
+ }
659
+ if (menu.float.title != null && typeof menu.float.title !== 'string') {
660
+ errors.push('float.title must be a string');
661
+ }
594
662
  }
595
663
  }
596
664
  if (!Array.isArray(menu.items)) {
@@ -601,6 +669,14 @@ export function validateMenu(menu) {
601
669
  // Renderers translate them into <hr> / structural breaks.
602
670
  if (item && item.type === 'separator') return;
603
671
 
672
+ // Spacers are blank room, likewise with nothing to point at.
673
+ if (item && item.type === 'spacer') {
674
+ if (item.size != null && !SPACER_SIZES.has(item.size)) {
675
+ errors.push(`Item ${path}: spacer size must be one of: ${[...SPACER_SIZES].join(', ')}`);
676
+ }
677
+ return;
678
+ }
679
+
604
680
  if (!item.text || typeof item.text !== 'string') {
605
681
  errors.push(`Item ${path}: text is required`);
606
682
  }
@@ -116,9 +116,14 @@
116
116
  </script>
117
117
 
118
118
  <!-- Site initialisation -->
119
- <script src="/public/js/site.js?v=20260822-vertical-menu" type="module"></script>
119
+ <script src="/public/js/site.js?v=20260829-motion-toggle" type="module"></script>
120
120
  <script src="/public/js/collection-context.js?v=20260819-export" type="module"></script>
121
121
 
122
+ <!-- Floating menu panels: collapse to a hamburger, drag to move, resize.
123
+ Loaded here rather than from site.js so server-rendered overlays and
124
+ [menu] shortcodes are wired even on pages with no floating navbar. -->
125
+ <script src="/public/js/menu-float.js?v=20260829-float-panel" type="module"></script>
126
+
122
127
  <!-- Interactive Collection Browser (used by [collection] shortcodes with
123
128
  searchable / filterable / sortable / paginate attributes) -->
124
129
  <script src="/public/js/collection-browser.js?v=20260523-browser"></script>