domma-cms 1.6.12 → 1.6.13

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{decodeBase64Json as N}from"/public/js/utf8-base64.mjs";import{makeBadgeEl as O,applyColour as j,applyPill as Y,applyFloat as G,applyHighlight as B,applyItemHighlight as U,itemHighlightAttrs as K,isCurrentItem as R,menuAlign as Q,applyClasses as D,cssClasses as P}from"/public/js/menu-decor.mjs?v=20261009-hover-darken";import{initFloatingMenus as Z}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as V,markInvalidFields as W,showValidationPopup as J}from"/public/js/forms.js?v=20261009-form-classes";import{buildWizardSteps as ee}from"/public/js/form-steps.mjs";import{applyFormClasses as X,applySubmitButtons as te,applyWizardButtons as ae}from"/public/js/form-submit.mjs";$(()=>{const x=window.__CMS_NAV__||{},g=window.__CMS_SITE__||{};if(g.autoTheme?.enabled){let t=function(e){const r=(e||"07:00").split(":");return+r[0]*60+(+r[1]||0)},n=function(){const e=new Date,r=e.getHours()*60+e.getMinutes();return r>=t(o.dayStart)&&r<t(o.nightStart)?o.dayTheme:o.nightTheme},s=function(e){const r=o.apply&&o.apply[e]||{theme:e,custom:""};Domma.theme.set(r.theme);for(const d of[...document.body.classList])d.startsWith("dm-custom-")&&document.body.classList.remove(d);r.custom&&document.body.classList.add(r.custom)};const o=g.autoTheme;s(n()),setInterval(()=>s(n()),6e4)}const A=$("#site-navbar");if(A.length&&x.brand){let n=function(c,f=0){return Array.isArray(c)?c.filter(i=>!(i&&(i.type==="spacer"||i.type==="theme-switcher"))).filter(i=>!(i&&i.type==="separator"&&f===0)).map(i=>i&&i.type==="separator"?{divider:!0}:Array.isArray(i?.items)?{...i,items:n(i.items,f+1)}:i):c},s=function(c){return Array.isArray(c)?c.filter(f=>!(f&&(f.type==="separator"||f.type==="spacer"||f.type==="theme-switcher"))):[]};const o={...x.brand},t=o.size&&o.size!=="md"?` navbar-brand-${o.size}`:"";if(o.logo||o.icon||o.tagline){let c="";o.logo?c+=`<img src="${o.logo}" class="navbar-brand-logo" alt="${o.text||""}">`:o.icon&&(c+=`<span data-icon="${o.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),o.text&&(c+=`<span class="navbar-brand-text${t}">${o.text}</span>`),o.tagline&&(c+=`<small class="navbar-brand-tagline">${o.tagline}</small>`),o.html=c}else t&&o.text&&(o.html=`<span class="navbar-brand-text${t}">${o.text}</span>`);const e=x.position==="floating",r=x.orientation==="vertical";Domma.elements.navbar("#site-navbar",{brand:o,items:n(x.items||[]),variant:x.variant||"dark",position:e?"static":x.position||"sticky",collapsible:!0,appearOnHover:!!x.appearOnHover}),Domma.icons.scan("#site-navbar");const d=A.get(0);d.classList.add(`dm-nav-align-${Q(x)||"centre"}`),r&&A.get(0).classList.add("dm-nav-vertical",x.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left"),B(d,x),D(d,x.class),e&&(G(A.get(0),x),Z(A.get(0).parentNode||document));const h=d.querySelector(".navbar-toggle"),p=d.querySelector(".navbar-collapse");if(h&&p){let c=null;const f=()=>{p.classList.contains("show")&&h.click()},i=()=>{c&&(c.remove(),c=null),document.body.classList.remove("dm-drawer-open")},a=()=>{c||(c=document.createElement("div"),c.className="dm-navbar-backdrop",c.addEventListener("click",f),document.body.appendChild(c),document.body.classList.add("dm-drawer-open"))};h.addEventListener("click",()=>{requestAnimationFrame(()=>{p.classList.contains("show")?a():i()})}),p.addEventListener("click",l=>{l.target.closest("a")&&requestAnimationFrame(i)}),document.addEventListener("keydown",l=>{l.key==="Escape"&&p.classList.contains("show")&&f()})}const m=d.querySelector(".navbar-nav");if(m){const c=Array.from(m.children);let f=0;for(const i of x.items||[])if(!(i&&i.type==="separator")){if(i&&i.type==="spacer"){const a=document.createElement("li"),l=["xs","sm","md","lg","xl","flex"].includes(i.size)?i.size:"md";a.className=`dm-menu-spacer dm-menu-spacer--${l}`,a.setAttribute("aria-hidden","true"),m.insertBefore(a,c[f]||null);continue}if(i&&i.type==="theme-switcher"){const a=document.createElement("li");a.className="dm-menu-theme-switcher";const l=document.createElement("span");l.dataset.themeSwitcher="menu",l.dataset.position="inline",a.appendChild(l),m.insertBefore(a,c[f]||null);continue}f++}window.CMSThemeSwitcher?.scan(m)}const b=s(x.items||[]);d.querySelectorAll(".navbar-link[data-index], .navbar-dropdown-item[data-index]").forEach(c=>{const f=b[parseInt(c.dataset.index,10)];if(!f)return;const i=c.dataset.subindex!=null,a=i&&Array.isArray(f.items)?f.items[parseInt(c.dataset.subindex,10)]:f;if(!a)return;a.colour&&j(c,a.colour),a.pill&&!i&&Y(c,a.pill),U(c,a),D(c.closest("li"),a.class);const l=O(a.badge);l&&c.appendChild(l),R(a.url,location.pathname)&&(c.classList.add("active"),c.setAttribute("aria-current","page"))})}const w=$("#site-footer");if(w.length){let e=function(a){return String(a??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")},r=function(a){return e(a).replace(/'/g,"&#39;")},d=function(a){if(a&&a.type==="separator")return'<li class="footer-sep"><hr></li>';if(a&&a.type==="spacer")return`<li class="dm-menu-spacer dm-menu-spacer--${["xs","sm","md","lg","xl","flex"].includes(a.size)?a.size:"md"}" aria-hidden="true"></li>`;if(a&&a.type==="theme-switcher")return'<li class="dm-menu-theme-switcher"><span data-theme-switcher="menu" data-position="inline"></span></li>';const l=r(a.url||"#"),u=e(a.text||""),C=Array.isArray(a.items)&&a.items.length?`<ul>${a.items.map(d).join("")}</ul>`:"",v=K(a),q=[v.hover&&` data-menu-item-hover="${r(v.hover)}"`,v.active&&` data-menu-item-active="${r(v.active)}"`].filter(Boolean).join(""),M=v.accent?` style="--dm-menu-highlight:${r(v.accent)}"`:"",k=P(a.class).join(" ");return`<li${k?` class="${r(k)}"`:""}><a href="${l}"${q}${M}>${u}</a>${C}</li>`},h=function(a,l){return(a||[]).forEach(u=>{u&&(u.type==="separator"||u.type==="spacer"||u.type==="theme-switcher")||(l.push(u),Array.isArray(u.items)&&u.items.length&&h(u.items,l))}),l};const o=g.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 n='<div class="footer-inner container">';const s=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""};if(s.copyright&&(n+=`<p>${e(s.copyright)}</p>`),s.primary.length&&(n+=`<nav class="footer-nav footer-nav--primary"><ul>${s.primary.map(d).join("")}</ul></nav>`),s.legal.length&&(n+=`<nav class="footer-nav footer-nav--legal"><ul>${s.legal.map(d).join("")}</ul></nav>`),g.footer){const a=Object.keys(t).filter(u=>o[u]),l=P(g.footer.socialClass).join(" ");a.length&&(n+='<div class="footer-social">',a.forEach(u=>{const{label:C,svg:v}=t[u];n+=`<a href="${o[u]}" target="_blank" rel="noopener noreferrer" aria-label="${C}" class="footer-social-link${l?` ${r(l)}`:""}">${v}</a>`}),n+="</div>")}n+="</div>",w.html(n),window.CMSThemeSwitcher?.scan(w.get(0)),D(w.get(0),g.footer?.class);const p=[...h(s.primary,[]),...h(s.legal,[])];w.get(0).querySelectorAll(".footer-nav a").forEach((a,l)=>{const u=p[l];if(!u)return;u.colour&&j(a,u.colour);const C=O(u.badge);C&&a.appendChild(C),R(u.url,location.pathname)&&(a.classList.add("is-active"),a.setAttribute("aria-current","page"))}),B(w.get(0).querySelector(".footer-nav--primary"),{highlight:s.primaryHighlight}),B(w.get(0).querySelector(".footer-nav--legal"),{highlight:s.legalHighlight}),D(w.get(0).querySelector(".footer-nav--primary"),s.primaryClass),D(w.get(0).querySelector(".footer-nav--legal"),s.legalClass);const b=g.footer?.showMotionToggle!==!1,c=S.get("reduced_motion"),f=c!==null?!!c:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),i=w.get(0).querySelector(".footer-inner");if(b&&i){const a=document.createElement("input");a.type="checkbox",a.className="form-switch-input",a.id="dm-motion-switch",a.checked=f,a.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 u=document.createElement("label");u.className="form-switch footer-motion-switch",u.title="Reduce motion",u.appendChild(a),u.appendChild(l),i.appendChild(u)}}if($("#site-sidebar").length){const o=document.querySelector(".site-content"),t=d=>!d.closest(".hero, .card, .accordion, .tabs, figure, #site-sidebar"),n=d=>o?[...o.querySelectorAll(d)].filter(t):[],s=n("h2").length?n("h2"):n("h3"),e=new Set([...document.querySelectorAll("[id]")].map(d=>d.id)),r=[];for(const d of s){if(!d.id){const h=(d.textContent||"").toLowerCase().trim().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"section";let p=h;for(let m=2;e.has(p);m++)p=`${h}-${m}`;e.add(p),d.id=p}r.push({text:d.textContent.trim(),url:`#${d.id}`,section:d.id})}r.length?Domma.elements.sidebar("#site-sidebar",{items:r,fixed:!1,push:!1,collapsible:!1,variant:"theme",scrollSpy:!0,position:document.querySelector(".site-main.sidebar-right")?"right":"left"}):document.querySelector(".site-main.with-sidebar")?.classList.add("no-sidebar-items")}Domma.icons.scan(),Domma.syntax?.scan&&Domma.syntax.scan();const y=$(".page-body");if(y.length){y.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),y.find(".tabs").each(function(){Domma.elements.tabs(this)}),y.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)}),y.find(".dm-countdown").each(function(){const o={autoStart:!0},t=String(this.dataset.format||"").trim(),n=String(this.dataset.to||"").trim(),s=/^(\d{4})-(\d{2})-(\d{2})$/.exec(n),e=s?new Date(+s[1],+s[2]-1,+s[3]).getTime():n?new Date(n).getTime():NaN;Number.isFinite(e)?o.duration=Math.max(0,e-Date.now()):this.dataset.duration&&(o.duration=(parseInt(this.dataset.duration,10)||0)*1e3);const r=/^dd:/i.test(t);o.format=r?"hh:mm:ss":t||(o.duration>=36e5?"hh:mm:ss":"mm:ss");const d=this,h=m=>String(m).padStart(2,"0"),p=m=>{const b=Math.max(0,Math.floor(m/1e3)),c=d.querySelector(".dm-timer-time");c&&(c.textContent=`${h(Math.floor(b/86400))}:${h(Math.floor(b%86400/3600))}:${h(Math.floor(b%3600/60))}:${h(b%60)}`)};r&&(o.onTick=p),Domma.elements.timer(this,o),r&&p(o.duration||0)}),y.find("[data-tooltip]").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),y.find(".dm-progression").each(function(){const o=Array.from(this.querySelectorAll(".dm-progression-item")).map(s=>{const e={title:s.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:s.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return s.dataset.date&&(e.date=s.dataset.date),s.dataset.status&&(e.status=s.dataset.status),s.dataset.entryId&&(e._entryId=s.dataset.entryId),e}),t=this.dataset.layout==="centred"?"centered":this.dataset.layout||"vertical",n=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:n,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",n=o.dataset.revealAnimation||"slide-up",s=parseInt(o.dataset.revealDuration,10)||400,e=parseInt(o.dataset.revealStagger,10)||60,r=parseInt(o.dataset.revealDelay,10)||0,d=o.dataset.revealDirection==="rtl",h=Array.from(o.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)"};h.forEach((m,b)=>{m.style.opacity="0",m.style.transform=p[n]||"",m.style.transition=`opacity ${s}ms ease, transform ${s}ms ease`;const c=d?h.length-1-b:b;m.style.transitionDelay=t==="stagger"?`${r+c*e}ms`:`${r}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const m=new IntersectionObserver(b=>{b.forEach(c=>{c.isIntersecting&&(c.target.offsetWidth,c.target.style.opacity="1",c.target.style.transform="none",m.unobserve(c.target))})},{threshold:.1});h.forEach(b=>m.observe(b))}))}),y.find(".card[data-collapsible]").each(function(){const o=this.querySelector(".card-header");o&&o.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),y.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const o=this.dataset.soTarget,t=document.getElementById(o);if(!t)return;const n=E.slideover({title:t.dataset.soTitle||"",size:t.dataset.soSize||"md",position:t.dataset.soPosition||"right"});t.style.display="",n.setContent(t),n.open()})})}if(typeof $.setup=="function"){const o=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(t=>{try{const n=N(t.dataset.config);Object.assign(o,n)}catch{}}),Object.keys(o).length>0){const t={};for(const[n,s]of Object.entries(o)){const e=s?.events?.click,{confirm:r,toast:d,alert:h,prompt:p,...m}=e||{};r||d||h||p?($(n).on("click",async function(c){if(c.preventDefault(),r&&!await E.confirm(r))return;let f=null;if(!(p&&(f=await E.prompt(p,{inputPlaceholder:m.promptPlaceholder||"",inputValue:m.promptDefault||""}),f===null))){if(m.target){const i=$(m.target);m.toggleClass&&i.toggleClass(m.toggleClass),m.addClass&&i.addClass(m.addClass),m.removeClass&&i.removeClass(m.removeClass),f!==null&&(m.setText&&i.text(f),m.setVal&&i.val(f),m.setAttr&&i.attr(m.setAttr,f))}m.href&&(window.location.href=m.href),d&&E.toast(d,{type:m.toastType||"success"}),h&&E.alert(h)}}),Object.keys(m).length&&(t[n]={...s,events:{...s.events,click:m}})):t[n]=s}$.setup(t)}}y.length&&I(y.get(0))}),window.wireCTAButtons=I;function I(x){x.querySelectorAll(".dm-cta-trigger").forEach(g=>{g.__ctaWired||(g.__ctaWired=!0,g.addEventListener("click",async()=>{const A=g.dataset.action,w=g.dataset.entry,_=g.dataset.confirm;let y=S.get("auth_token");if(!y){E.toast("Please log in to perform this action.",{type:"warning"});return}if(_&&!await E.confirm(_))return;const o=Array.from(g.childNodes).map(n=>n.cloneNode(!0));g.disabled=!0,g.textContent="Running\u2026";const t=n=>fetch(`/api/actions/${A}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`},body:JSON.stringify(g.dataset.scope?{entryId:w,scope:g.dataset.scope}:{entryId:w})});try{let n=await t(y);if(n.status===401){const e=S.get("auth_refresh_token");if(e){const r=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(r.ok){const{token:d}=await r.json();S.set("auth_token",d),y=d,n=await t(y)}}}const s=await n.json().catch(()=>({}));if(!n.ok)throw new Error(s.error||s.message||`Error ${n.status}`);E.toast(s.message||"Action completed.",{type:"success"})}catch(n){E.toast(n.message||"Action failed.",{type:"error"})}finally{g.disabled=!1,g.textContent="",o.forEach(n=>g.appendChild(n)),Domma.icons.scan(g)}}))})}(function(){const g=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!g.length)return;const A=S.get("auth_token");function w(y,o){const t=document.createElement("div");t.className="dm-collection-display dm-collection-empty";const n=document.createElement("p");if(o){n.appendChild(document.createTextNode("Please "));const s=document.createElement("a");s.href="/login",s.textContent="sign in",n.appendChild(s),n.appendChild(document.createTextNode(" to view your entries."))}else n.textContent=y;return t.appendChild(n),t}function _(y,o){const t=document.createElement("template");t.innerHTML=o||"",y.replaceWith(t.content)}g.forEach(async y=>{const o=y.dataset.collectionAttrs;if(!o)return;if(!A){y.replaceChildren(w("",!0));return}const t=n=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`},body:JSON.stringify({attrs:o})});try{let n=A,s=await t(n);if(s.status===401){const r=S.get("auth_refresh_token");if(r){const d=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:r})});if(d.ok){const{token:h}=await d.json();S.set("auth_token",h),n=h,s=await t(n)}}}if(!s.ok){const{error:r}=await s.json().catch(()=>({}));y.replaceChildren(w(r||"Unable to load entries.",!1));return}const{html:e}=await s.json();_(y,e),window.DommaCollectionBrowser?.hydrate&&window.DommaCollectionBrowser.hydrate(document)}catch{y.replaceChildren(w("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),typeof Domma<"u"&&Domma.syntax?.scan&&Domma.syntax.scan(),I(document.body)},100)})(),(function(){const g=document.querySelectorAll("[data-collection-table]");!g.length||typeof T>"u"||!T.create||g.forEach(A=>{let w;try{w=N(A.dataset.payload)}catch{return}const{columns:_,rows:y,search:o,sortable:t,exportable:n,pageSize:s,empty:e,ctaConfig:r,slug:d,exportCfg:h}=w;if(!_?.length)return;if(r){const b=_.findIndex(c=>c.key==="_cta");b!==-1&&(_[b]={key:"_cta",title:"",render:(c,f)=>{const i=document.createElement("button");if(i.className=`btn btn-${r.style||"primary"} dm-cta-trigger`,i.dataset.action=r.action||"",r.scope&&(i.dataset.scope=r.scope),i.dataset.entry=f._entryId||"",r.confirm&&(i.dataset.confirm=r.confirm),r.icon){const a=document.createElement("span");a.dataset.icon=r.icon,i.appendChild(a),i.appendChild(document.createTextNode(" "))}return i.appendChild(document.createTextNode(r.label||"Run")),i}})}const p="col-table-"+Math.random().toString(36).slice(2,7),m=document.createElement("div");m.id=p,A.replaceChildren(m),T.create("#"+p,{data:y,columns:_,search:o,sortable:t,exportable:n,pageSize:s,emptyMessage:e}),r&&I(m),A._dmTable={columns:_,rows:y,slug:d,exportCfg:h,render(b){T.create("#"+p,{data:b,columns:_,search:o,sortable:t,exportable:n,pageSize:s,emptyMessage:e}),r&&I(m)}}})})(),(function(){const g=document.querySelectorAll("[data-form-inline]");if(!g.length||typeof F>"u")return;function A(t){const n=t&&t.logic;return!!(n&&(n.visibility||n.requirement))}function w(t,n){const s={};return(t||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const r=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),d=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":r?"chooser":e.type,h={...e.formConfig||{}};h.span==="full"&&n&&(h.span=n);const p={type:d,required:A(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&&(p.minLength=e.minLength),e.maxLength!==void 0&&(p.maxLength=e.maxLength),e.min!==void 0&&(p.min=e.min),e.max!==void 0&&(p.max=e.max),r&&(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)),s[e.name]=p}),s}function _(t){const n={};return(t||[]).forEach(s=>{if(!(!s.name||s.type==="page-break"||s.type==="spacer")&&(s.type==="select"||s.type==="multiselect")&&s.required){const e=(s.options||[])[0];e&&(n[s.name]=typeof e=="object"?e.value:e)}}),n}function y(t,n){(n||[]).forEach(s=>{if(s.type!=="date"||!s.name)return;const e=t.querySelector(`[name="${s.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function o(t,n,s){let e=t.querySelector(".cms-form-message");e||(e=document.createElement("p"),e.className="cms-form-message",t.appendChild(e)),e.textContent=n,e.style.cssText=s?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}g.forEach(t=>{let n;try{n=N(t.dataset.formInline)}catch{return}const s=n.fields||[],e=n.settings||{},r=Number(e.columns)===3?3:2,d=e.layout||"grid",h=s.some(i=>i.type==="page-break"),p=async i=>{if(t.dataset.triggerBlocked==="true"){const a=t._formLogicRuntime,l=a&&a.submitBlock?a.submitBlock().reasons:[];o(t,l.join(" ")||"This form cannot be submitted yet.",!0);const u=new Error("Blocked by a form trigger");throw u.formSubmitFailed=!0,u}try{const a=t.querySelector(".fb-form-honeypot"),l=a?.querySelector('input[type="text"]')||null,u=a?.querySelector('[name="_t"]')||null,C=Object.assign({},i);delete C._t,a?.dataset.hpName&&delete C[a.dataset.hpName],l!==null&&(C._hp=l.value),u!==null&&(C._t=u.value);const v=await H.post(`/api/forms/submit/${n.slug}`,C);if(v&&v.error){o(t,v.error,!0);const q=new Error(v.error);throw q.formSubmitFailed=!0,q}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(a){throw a.formSubmitFailed||o(t,a.message||"Submission failed. Please try again.",!0),a}},m=Date.now();function b(i){const a=i.querySelector("form");if(!a||a.querySelector(".fb-form-honeypot"))return;const l=document.createElement("div");l.className="fb-form-honeypot",l.setAttribute("aria-hidden","true");const u=document.createElement("input");u.name=a.querySelector('[name="website"]')?"_website":"website",l.dataset.hpName=u.name,u.type="text",u.tabIndex=-1,u.autocomplete="url",u.placeholder="https://",l.appendChild(u);const C=document.createElement("input");C.name="_t",C.type="hidden",C.value=m,l.appendChild(C),a.appendChild(l)}function c(i){if(!window.FormLogicEngine||!s.some(l=>l.logic||Array.isArray(l.triggers)&&l.triggers.length))return null;const a=new window.FormLogicEngine.FormLogicRuntime(n,t,i?{baseValues:i}:void 0);if(a.init(),t._formLogicRuntime=a,t._formLogicObserved)return a;if(t._formLogicObserved=!0,t.parentNode&&typeof MutationObserver<"u"){const l=new MutationObserver(function(u){for(const C of u)for(const v of C.removedNodes)if(v===t||v.nodeType===1&&v.contains&&v.contains(t)){t._formLogicRuntime&&t._formLogicRuntime.destroy(),l.disconnect();return}});l.observe(t.parentNode,{childList:!0,subtree:!1}),t._formLogicObserver=l}return a}let f=!1;if(t.addEventListener("dm-form-end",i=>{const a=i.detail?.validation;if(a&&!a.valid){W(t,a.missing),J(a);return}f||(f=!0,p(i.detail?.values||{}).catch(()=>{}).finally(()=>{f=!1}))}),h&&F.wizard){const i=ee(s,n.title),a=i.map(k=>({title:k.title,description:k.description,fields:w(k.fields,r)})),l=i.map(k=>k.fields);let u=0,C=null,v=null;const q=k=>{const L=window.FormLogicEngine;if(!L||!L.validateFields)return;const z=L.validateFields(t,l[u]||[]);z.valid||(k.preventDefault(),k.stopImmediatePropagation(),W(t,z.missing),J(z))},M=()=>{const k=l[u]||s;ae(t,e),X(t,e,k),y(t,k),V(t,k),e.honeypot!==!1&&b(t),C&&C.destroy(),C=c(v?.getStepData?v.getStepData():void 0),t.querySelectorAll(".wizard-next, .wizard-submit").forEach(L=>L.addEventListener("click",q,!0))};v=F.wizard(t,{schema:{steps:a},layout:d,columns:r,finishText:e.submitText||"Finish",onSubmit:p,onStepChange:k=>{u=k,setTimeout(M,0)}}),t.addEventListener("dm-form-trigger",k=>{if(k.detail?.type!=="goto-step"||!v?.goToStep)return;const L=Number(k.detail.action.step);!Number.isFinite(L)||L<0||L>=a.length||L!==u&&v.goToStep(L)}),Promise.resolve(v).then(M)}else if(F.render){const i=F.render(t,w(s,r),_(s),{submitText:e.submitText||"Submit",layout:d,columns:r,onSubmit:p});Promise.resolve(i).then(function(){te(t,e,d),X(t,e,s),y(t,s),V(t,s),e.honeypot!==!1&&b(t),c()})}}),$(document).on("click",".dm-banner__dismiss",function(){$(this).closest(".dm-banner").remove()})})();
1
+ import{decodeBase64Json as N}from"/public/js/utf8-base64.mjs";import{makeBadgeEl as O,applyColour as j,applyPill as U,applyFloat as X,applyHighlight as B,applyItemHighlight as G,itemHighlightAttrs as K,isCurrentItem as R,menuAlign as Q,applyClasses as D,cssClasses as P}from"/public/js/menu-decor.mjs?v=20261009-hover-darken";import{initFloatingMenus as Z}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as V,markInvalidFields as W,showValidationPopup as J}from"/public/js/forms.js?v=20261009-form-classes";import{buildWizardSteps as ee}from"/public/js/form-steps.mjs";import{applyFormClasses as Y,applySubmitButtons as te,applyWizardButtons as ae}from"/public/js/form-submit.mjs";$(()=>{const h=window.__CMS_NAV__||{},g=window.__CMS_SITE__||{};if(g.autoTheme?.enabled){let t=function(e){const n=(e||"07:00").split(":");return+n[0]*60+(+n[1]||0)},s=function(){const e=new Date,n=e.getHours()*60+e.getMinutes();return n>=t(o.dayStart)&&n<t(o.nightStart)?o.dayTheme:o.nightTheme},i=function(e){const n=o.apply&&o.apply[e]||{theme:e,custom:""};Domma.theme.set(n.theme);for(const r of[...document.body.classList])r.startsWith("dm-custom-")&&document.body.classList.remove(r);n.custom&&document.body.classList.add(n.custom)};const o=g.autoTheme;i(s()),setInterval(()=>i(s()),6e4)}const C=$("#site-navbar");if(C.length&&h.brand){let s=function(m,l=0){return Array.isArray(m)?m.filter(a=>!(a&&(a.type==="spacer"||a.type==="theme-switcher"))).filter(a=>!(a&&a.type==="separator"&&l===0)).map(a=>a&&a.type==="separator"?{divider:!0}:Array.isArray(a?.items)?{...a,items:s(a.items,l+1)}:a):m},i=function(m){return Array.isArray(m)?m.filter(l=>!(l&&(l.type==="separator"||l.type==="spacer"||l.type==="theme-switcher"))):[]};const o={...h.brand},t=o.size&&o.size!=="md"?` navbar-brand-${o.size}`:"";if(o.logo||o.icon||o.tagline){let m="";o.logo?m+=`<img src="${o.logo}" class="navbar-brand-logo" alt="${o.text||""}">`:o.icon&&(m+=`<span data-icon="${o.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),o.text&&(m+=`<span class="navbar-brand-text${t}">${o.text}</span>`),o.tagline&&(m+=`<small class="navbar-brand-tagline">${o.tagline}</small>`),o.html=m}else t&&o.text&&(o.html=`<span class="navbar-brand-text${t}">${o.text}</span>`);const e=h.position==="floating",n=h.orientation==="vertical";Domma.elements.navbar("#site-navbar",{brand:o,items:s(h.items||[]),variant:h.variant||"dark",position:e?"static":h.position||"sticky",collapsible:!0,appearOnHover:!!h.appearOnHover}),Domma.icons.scan("#site-navbar");const r=C.get(0);r.classList.add(`dm-nav-align-${Q(h)||"centre"}`),n&&C.get(0).classList.add("dm-nav-vertical",h.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left");const y=h.dock||{};n&&!e&&oe(r,h.side==="right"),!n&&y.hideOnScroll&&ne(r),B(r,h),D(r,h.class),e&&(X(C.get(0),h),Z(C.get(0).parentNode||document));const p=r.querySelector(".navbar-toggle"),u=r.querySelector(".navbar-collapse");if(p&&u){let m=null;const l=()=>{u.classList.contains("show")&&p.click()},a=()=>{m&&(m.remove(),m=null),document.body.classList.remove("dm-drawer-open")},d=()=>{m||(m=document.createElement("div"),m.className="dm-navbar-backdrop",m.addEventListener("click",l),document.body.appendChild(m),document.body.classList.add("dm-drawer-open"))};p.addEventListener("click",()=>{requestAnimationFrame(()=>{u.classList.contains("show")?d():a()})}),u.addEventListener("click",c=>{c.target.closest("a")&&requestAnimationFrame(a)}),document.addEventListener("keydown",c=>{c.key==="Escape"&&u.classList.contains("show")&&l()})}const b=r.querySelector(".navbar-nav");if(b){const m=Array.from(b.children);let l=0;for(const a of h.items||[])if(!(a&&a.type==="separator")){if(a&&a.type==="spacer"){const d=document.createElement("li"),c=["xs","sm","md","lg","xl","flex"].includes(a.size)?a.size:"md";d.className=`dm-menu-spacer dm-menu-spacer--${c}`,d.setAttribute("aria-hidden","true"),b.insertBefore(d,m[l]||null);continue}if(a&&a.type==="theme-switcher"){const d=document.createElement("li");d.className="dm-menu-theme-switcher";const c=document.createElement("span");c.dataset.themeSwitcher="menu",c.dataset.position="inline",d.appendChild(c),b.insertBefore(d,m[l]||null);continue}l++}window.CMSThemeSwitcher?.scan(b)}const k=i(h.items||[]);r.querySelectorAll(".navbar-link[data-index], .navbar-dropdown-item[data-index]").forEach(m=>{const l=k[parseInt(m.dataset.index,10)];if(!l)return;const a=m.dataset.subindex!=null,d=a&&Array.isArray(l.items)?l.items[parseInt(m.dataset.subindex,10)]:l;if(!d)return;d.colour&&j(m,d.colour),d.pill&&!a&&U(m,d.pill),G(m,d),D(m.closest("li"),d.class);const c=O(d.badge);c&&m.appendChild(c),R(d.url,location.pathname)&&(m.classList.add("active"),m.setAttribute("aria-current","page"))})}const f=$("#site-footer");if(f.length){let e=function(a){return String(a??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")},n=function(a){return e(a).replace(/'/g,"&#39;")},r=function(a){if(a&&a.type==="separator")return'<li class="footer-sep"><hr></li>';if(a&&a.type==="spacer")return`<li class="dm-menu-spacer dm-menu-spacer--${["xs","sm","md","lg","xl","flex"].includes(a.size)?a.size:"md"}" aria-hidden="true"></li>`;if(a&&a.type==="theme-switcher")return'<li class="dm-menu-theme-switcher"><span data-theme-switcher="menu" data-position="inline"></span></li>';const d=n(a.url||"#"),c=e(a.text||""),A=Array.isArray(a.items)&&a.items.length?`<ul>${a.items.map(r).join("")}</ul>`:"",w=K(a),q=[w.hover&&` data-menu-item-hover="${n(w.hover)}"`,w.active&&` data-menu-item-active="${n(w.active)}"`].filter(Boolean).join(""),I=w.accent?` style="--dm-menu-highlight:${n(w.accent)}"`:"",L=P(a.class).join(" ");return`<li${L?` class="${n(L)}"`:""}><a href="${d}"${q}${I}>${c}</a>${A}</li>`},y=function(a,d){return(a||[]).forEach(c=>{c&&(c.type==="separator"||c.type==="spacer"||c.type==="theme-switcher")||(d.push(c),Array.isArray(c.items)&&c.items.length&&y(c.items,d))}),d};const o=g.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 s='<div class="footer-inner container">';const i=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""};if(i.copyright&&(s+=`<p>${e(i.copyright)}</p>`),i.primary.length&&(s+=`<nav class="footer-nav footer-nav--primary"><ul>${i.primary.map(r).join("")}</ul></nav>`),i.legal.length&&(s+=`<nav class="footer-nav footer-nav--legal"><ul>${i.legal.map(r).join("")}</ul></nav>`),g.footer){const a=Object.keys(t).filter(c=>o[c]),d=P(g.footer.socialClass).join(" ");a.length&&(s+='<div class="footer-social">',a.forEach(c=>{const{label:A,svg:w}=t[c];s+=`<a href="${o[c]}" target="_blank" rel="noopener noreferrer" aria-label="${A}" class="footer-social-link${d?` ${n(d)}`:""}">${w}</a>`}),s+="</div>")}s+="</div>",f.html(s),window.CMSThemeSwitcher?.scan(f.get(0)),D(f.get(0),g.footer?.class);const p=[...y(i.primary,[]),...y(i.legal,[])];f.get(0).querySelectorAll(".footer-nav a").forEach((a,d)=>{const c=p[d];if(!c)return;c.colour&&j(a,c.colour);const A=O(c.badge);A&&a.appendChild(A),R(c.url,location.pathname)&&(a.classList.add("is-active"),a.setAttribute("aria-current","page"))}),B(f.get(0).querySelector(".footer-nav--primary"),{highlight:i.primaryHighlight}),B(f.get(0).querySelector(".footer-nav--legal"),{highlight:i.legalHighlight}),D(f.get(0).querySelector(".footer-nav--primary"),i.primaryClass),D(f.get(0).querySelector(".footer-nav--legal"),i.legalClass);const b=g.footer?.showMotionToggle!==!1,k=S.get("reduced_motion"),m=k!==null?!!k:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),l=f.get(0).querySelector(".footer-inner");if(b&&l){const a=document.createElement("input");a.type="checkbox",a.className="form-switch-input",a.id="dm-motion-switch",a.checked=m,a.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const d=document.createElement("span");d.className="form-switch-label",d.textContent="Reduce motion";const c=document.createElement("label");c.className="form-switch footer-motion-switch",c.title="Reduce motion",c.appendChild(a),c.appendChild(d),l.appendChild(c)}}if($("#site-sidebar").length){const o=document.querySelector(".site-content"),t=r=>!r.closest(".hero, .card, .accordion, .tabs, figure, #site-sidebar"),s=r=>o?[...o.querySelectorAll(r)].filter(t):[],i=s("h2").length?s("h2"):s("h3"),e=new Set([...document.querySelectorAll("[id]")].map(r=>r.id)),n=[];for(const r of i){if(!r.id){const y=(r.textContent||"").toLowerCase().trim().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"section";let p=y;for(let u=2;e.has(p);u++)p=`${y}-${u}`;e.add(p),r.id=p}n.push({text:r.textContent.trim(),url:`#${r.id}`,section:r.id})}n.length?Domma.elements.sidebar("#site-sidebar",{items:n,fixed:!1,push:!1,collapsible:!1,variant:"theme",scrollSpy:!0,position:document.querySelector(".site-main.sidebar-right")?"right":"left"}):document.querySelector(".site-main.with-sidebar")?.classList.add("no-sidebar-items")}Domma.icons.scan(),Domma.syntax?.scan&&Domma.syntax.scan();const v=$(".page-body");if(v.length){v.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),v.find(".tabs").each(function(){Domma.elements.tabs(this)}),v.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)}),v.find(".dm-countdown").each(function(){const o={autoStart:!0},t=String(this.dataset.format||"").trim(),s=String(this.dataset.to||"").trim(),i=/^(\d{4})-(\d{2})-(\d{2})$/.exec(s),e=i?new Date(+i[1],+i[2]-1,+i[3]).getTime():s?new Date(s).getTime():NaN;Number.isFinite(e)?o.duration=Math.max(0,e-Date.now()):this.dataset.duration&&(o.duration=(parseInt(this.dataset.duration,10)||0)*1e3);const n=/^dd:/i.test(t);o.format=n?"hh:mm:ss":t||(o.duration>=36e5?"hh:mm:ss":"mm:ss");const r=this,y=u=>String(u).padStart(2,"0"),p=u=>{const b=Math.max(0,Math.floor(u/1e3)),k=r.querySelector(".dm-timer-time");k&&(k.textContent=`${y(Math.floor(b/86400))}:${y(Math.floor(b%86400/3600))}:${y(Math.floor(b%3600/60))}:${y(b%60)}`)};n&&(o.onTick=p),Domma.elements.timer(this,o),n&&p(o.duration||0)}),v.find("[data-tooltip]").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),v.find(".dm-progression").each(function(){const o=Array.from(this.querySelectorAll(".dm-progression-item")).map(i=>{const e={title:i.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:i.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return i.dataset.date&&(e.date=i.dataset.date),i.dataset.status&&(e.status=i.dataset.status),i.dataset.entryId&&(e._entryId=i.dataset.entryId),e}),t=this.dataset.layout==="centred"?"centered":this.dataset.layout||"vertical",s=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:s,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",s=o.dataset.revealAnimation||"slide-up",i=parseInt(o.dataset.revealDuration,10)||400,e=parseInt(o.dataset.revealStagger,10)||60,n=parseInt(o.dataset.revealDelay,10)||0,r=o.dataset.revealDirection==="rtl",y=Array.from(o.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((u,b)=>{u.style.opacity="0",u.style.transform=p[s]||"",u.style.transition=`opacity ${i}ms ease, transform ${i}ms ease`;const k=r?y.length-1-b:b;u.style.transitionDelay=t==="stagger"?`${n+k*e}ms`:`${n}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const u=new IntersectionObserver(b=>{b.forEach(k=>{k.isIntersecting&&(k.target.offsetWidth,k.target.style.opacity="1",k.target.style.transform="none",u.unobserve(k.target))})},{threshold:.1});y.forEach(b=>u.observe(b))}))}),v.find(".card[data-collapsible]").each(function(){const o=this.querySelector(".card-header");o&&o.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),v.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const o=this.dataset.soTarget,t=document.getElementById(o);if(!t)return;const s=E.slideover({title:t.dataset.soTitle||"",size:t.dataset.soSize||"md",position:t.dataset.soPosition||"right"});t.style.display="",s.setContent(t),s.open()})})}if(typeof $.setup=="function"){const o=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(t=>{try{const s=N(t.dataset.config);Object.assign(o,s)}catch{}}),Object.keys(o).length>0){const t={};for(const[s,i]of Object.entries(o)){const e=i?.events?.click,{confirm:n,toast:r,alert:y,prompt:p,...u}=e||{};n||r||y||p?($(s).on("click",async function(k){if(k.preventDefault(),n&&!await E.confirm(n))return;let m=null;if(!(p&&(m=await E.prompt(p,{inputPlaceholder:u.promptPlaceholder||"",inputValue:u.promptDefault||""}),m===null))){if(u.target){const l=$(u.target);u.toggleClass&&l.toggleClass(u.toggleClass),u.addClass&&l.addClass(u.addClass),u.removeClass&&l.removeClass(u.removeClass),m!==null&&(u.setText&&l.text(m),u.setVal&&l.val(m),u.setAttr&&l.attr(u.setAttr,m))}u.href&&(window.location.href=u.href),r&&E.toast(r,{type:u.toastType||"success"}),y&&E.alert(y)}}),Object.keys(u).length&&(t[s]={...i,events:{...i.events,click:u}})):t[s]=i}$.setup(t)}}v.length&&M(v.get(0))}),window.wireCTAButtons=M;function M(h){h.querySelectorAll(".dm-cta-trigger").forEach(g=>{g.__ctaWired||(g.__ctaWired=!0,g.addEventListener("click",async()=>{const C=g.dataset.action,f=g.dataset.entry,x=g.dataset.confirm;let v=S.get("auth_token");if(!v){E.toast("Please log in to perform this action.",{type:"warning"});return}if(x&&!await E.confirm(x))return;const o=Array.from(g.childNodes).map(s=>s.cloneNode(!0));g.disabled=!0,g.textContent="Running\u2026";const t=s=>fetch(`/api/actions/${C}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify(g.dataset.scope?{entryId:f,scope:g.dataset.scope}:{entryId:f})});try{let s=await t(v);if(s.status===401){const e=S.get("auth_refresh_token");if(e){const n=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(n.ok){const{token:r}=await n.json();S.set("auth_token",r),v=r,s=await t(v)}}}const i=await s.json().catch(()=>({}));if(!s.ok)throw new Error(i.error||i.message||`Error ${s.status}`);E.toast(i.message||"Action completed.",{type:"success"})}catch(s){E.toast(s.message||"Action failed.",{type:"error"})}finally{g.disabled=!1,g.textContent="",o.forEach(s=>g.appendChild(s)),Domma.icons.scan(g)}}))})}(function(){const g=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!g.length)return;const C=S.get("auth_token");function f(v,o){const t=document.createElement("div");t.className="dm-collection-display dm-collection-empty";const s=document.createElement("p");if(o){s.appendChild(document.createTextNode("Please "));const i=document.createElement("a");i.href="/login",i.textContent="sign in",s.appendChild(i),s.appendChild(document.createTextNode(" to view your entries."))}else s.textContent=v;return t.appendChild(s),t}function x(v,o){const t=document.createElement("template");t.innerHTML=o||"",v.replaceWith(t.content)}g.forEach(async v=>{const o=v.dataset.collectionAttrs;if(!o)return;if(!C){v.replaceChildren(f("",!0));return}const t=s=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify({attrs:o})});try{let s=C,i=await t(s);if(i.status===401){const n=S.get("auth_refresh_token");if(n){const r=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:n})});if(r.ok){const{token:y}=await r.json();S.set("auth_token",y),s=y,i=await t(s)}}}if(!i.ok){const{error:n}=await i.json().catch(()=>({}));v.replaceChildren(f(n||"Unable to load entries.",!1));return}const{html:e}=await i.json();x(v,e),window.DommaCollectionBrowser?.hydrate&&window.DommaCollectionBrowser.hydrate(document)}catch{v.replaceChildren(f("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),typeof Domma<"u"&&Domma.syntax?.scan&&Domma.syntax.scan(),M(document.body)},100)})(),(function(){const g=document.querySelectorAll("[data-collection-table]");!g.length||typeof T>"u"||!T.create||g.forEach(C=>{let f;try{f=N(C.dataset.payload)}catch{return}const{columns:x,rows:v,search:o,sortable:t,exportable:s,pageSize:i,empty:e,ctaConfig:n,slug:r,exportCfg:y}=f;if(!x?.length)return;if(n){const b=x.findIndex(k=>k.key==="_cta");b!==-1&&(x[b]={key:"_cta",title:"",render:(k,m)=>{const l=document.createElement("button");if(l.className=`btn btn-${n.style||"primary"} dm-cta-trigger`,l.dataset.action=n.action||"",n.scope&&(l.dataset.scope=n.scope),l.dataset.entry=m._entryId||"",n.confirm&&(l.dataset.confirm=n.confirm),n.icon){const a=document.createElement("span");a.dataset.icon=n.icon,l.appendChild(a),l.appendChild(document.createTextNode(" "))}return l.appendChild(document.createTextNode(n.label||"Run")),l}})}const p="col-table-"+Math.random().toString(36).slice(2,7),u=document.createElement("div");u.id=p,C.replaceChildren(u),T.create("#"+p,{data:v,columns:x,search:o,sortable:t,exportable:s,pageSize:i,emptyMessage:e}),n&&M(u),C._dmTable={columns:x,rows:v,slug:r,exportCfg:y,render(b){T.create("#"+p,{data:b,columns:x,search:o,sortable:t,exportable:s,pageSize:i,emptyMessage:e}),n&&M(u)}}})})(),(function(){const g=document.querySelectorAll("[data-form-inline]");if(!g.length||typeof F>"u")return;function C(t){const s=t&&t.logic;return!!(s&&(s.visibility||s.requirement))}function f(t,s){const i={};return(t||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const n=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),r=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":n?"chooser":e.type,y={...e.formConfig||{}};y.span==="full"&&s&&(y.span=s);const p={type:r,required:C(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},...y}};e.minLength!==void 0&&(p.minLength=e.minLength),e.maxLength!==void 0&&(p.maxLength=e.maxLength),e.min!==void 0&&(p.min=e.min),e.max!==void 0&&(p.max=e.max),n&&(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)),i[e.name]=p}),i}function x(t){const s={};return(t||[]).forEach(i=>{if(!(!i.name||i.type==="page-break"||i.type==="spacer")&&(i.type==="select"||i.type==="multiselect")&&i.required){const e=(i.options||[])[0];e&&(s[i.name]=typeof e=="object"?e.value:e)}}),s}function v(t,s){(s||[]).forEach(i=>{if(i.type!=="date"||!i.name)return;const e=t.querySelector(`[name="${i.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function o(t,s,i){let e=t.querySelector(".cms-form-message");e||(e=document.createElement("p"),e.className="cms-form-message",t.appendChild(e)),e.textContent=s,e.style.cssText=i?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}g.forEach(t=>{let s;try{s=N(t.dataset.formInline)}catch{return}const i=s.fields||[],e=s.settings||{},n=Number(e.columns)===3?3:2,r=e.layout||"grid",y=i.some(l=>l.type==="page-break"),p=async l=>{if(t.dataset.triggerBlocked==="true"){const a=t._formLogicRuntime,d=a&&a.submitBlock?a.submitBlock().reasons:[];o(t,d.join(" ")||"This form cannot be submitted yet.",!0);const c=new Error("Blocked by a form trigger");throw c.formSubmitFailed=!0,c}try{const a=t.querySelector(".fb-form-honeypot"),d=a?.querySelector('input[type="text"]')||null,c=a?.querySelector('[name="_t"]')||null,A=Object.assign({},l);delete A._t,a?.dataset.hpName&&delete A[a.dataset.hpName],d!==null&&(A._hp=d.value),c!==null&&(A._t=c.value);const w=await H.post(`/api/forms/submit/${s.slug}`,A);if(w&&w.error){o(t,w.error,!0);const q=new Error(w.error);throw q.formSubmitFailed=!0,q}if(w?.redirect){window.location.href=w.redirect;return}for(;t.firstChild;)t.removeChild(t.firstChild);o(t,w?.message||e.successMessage||"Thank you for your submission.",!1)}catch(a){throw a.formSubmitFailed||o(t,a.message||"Submission failed. Please try again.",!0),a}},u=Date.now();function b(l){const a=l.querySelector("form");if(!a||a.querySelector(".fb-form-honeypot"))return;const d=document.createElement("div");d.className="fb-form-honeypot",d.setAttribute("aria-hidden","true");const c=document.createElement("input");c.name=a.querySelector('[name="website"]')?"_website":"website",d.dataset.hpName=c.name,c.type="text",c.tabIndex=-1,c.autocomplete="url",c.placeholder="https://",d.appendChild(c);const A=document.createElement("input");A.name="_t",A.type="hidden",A.value=u,d.appendChild(A),a.appendChild(d)}function k(l){if(!window.FormLogicEngine||!i.some(d=>d.logic||Array.isArray(d.triggers)&&d.triggers.length))return null;const a=new window.FormLogicEngine.FormLogicRuntime(s,t,l?{baseValues:l}:void 0);if(a.init(),t._formLogicRuntime=a,t._formLogicObserved)return a;if(t._formLogicObserved=!0,t.parentNode&&typeof MutationObserver<"u"){const d=new MutationObserver(function(c){for(const A of c)for(const w of A.removedNodes)if(w===t||w.nodeType===1&&w.contains&&w.contains(t)){t._formLogicRuntime&&t._formLogicRuntime.destroy(),d.disconnect();return}});d.observe(t.parentNode,{childList:!0,subtree:!1}),t._formLogicObserver=d}return a}let m=!1;if(t.addEventListener("dm-form-end",l=>{const a=l.detail?.validation;if(a&&!a.valid){W(t,a.missing),J(a);return}m||(m=!0,p(l.detail?.values||{}).catch(()=>{}).finally(()=>{m=!1}))}),y&&F.wizard){const l=ee(i,s.title),a=l.map(L=>({title:L.title,description:L.description,fields:f(L.fields,n)})),d=l.map(L=>L.fields);let c=0,A=null,w=null;const q=L=>{const _=window.FormLogicEngine;if(!_||!_.validateFields)return;const z=_.validateFields(t,d[c]||[]);z.valid||(L.preventDefault(),L.stopImmediatePropagation(),W(t,z.missing),J(z))},I=()=>{const L=d[c]||i;ae(t,e),Y(t,e,L),v(t,L),V(t,L),e.honeypot!==!1&&b(t),A&&A.destroy(),A=k(w?.getStepData?w.getStepData():void 0),t.querySelectorAll(".wizard-next, .wizard-submit").forEach(_=>_.addEventListener("click",q,!0))};w=F.wizard(t,{schema:{steps:a},layout:r,columns:n,finishText:e.submitText||"Finish",onSubmit:p,onStepChange:L=>{c=L,setTimeout(I,0)}}),t.addEventListener("dm-form-trigger",L=>{if(L.detail?.type!=="goto-step"||!w?.goToStep)return;const _=Number(L.detail.action.step);!Number.isFinite(_)||_<0||_>=a.length||_!==c&&w.goToStep(_)}),Promise.resolve(w).then(I)}else if(F.render){const l=F.render(t,f(i,n),x(i),{submitText:e.submitText||"Submit",layout:r,columns:n,onSubmit:p});Promise.resolve(l).then(function(){te(t,e,r),Y(t,e,i),v(t,i),V(t,i),e.honeypot!==!1&&b(t),k()})}}),$(document).on("click",".dm-banner__dismiss",function(){$(this).closest(".dm-banner").remove()})})();function oe(h,g){const C=document.body,f=C.classList,x=new Set((C.dataset.railSite||"").split(" ").filter(Boolean)),v=()=>window.matchMedia("(min-width: 993px)").matches;h.querySelectorAll(".navbar-link, .navbar-dropdown-toggle").forEach(n=>{const r=n.textContent.trim();r&&(n.setAttribute("title",r),n.querySelector("svg, [data-icon]")||(n.dataset.initial=r.charAt(0).toUpperCase()))});const o=document.createElement("button");o.type="button",o.className="dm-nav-collapse-toggle";const t=()=>{const n=f.contains("dm-vnav-collapsed");o.setAttribute("aria-pressed",String(n)),o.setAttribute("aria-label",n?"Expand menu":"Collapse menu"),o.title=o.getAttribute("aria-label");const r=g?"chevrons-left":"chevrons-right",y=g?"chevrons-right":"chevrons-left";o.innerHTML=`<span data-icon="${n?r:y}"></span>`,window.Domma&&Domma.icons&&Domma.icons.scan(o)},s=n=>{f.toggle("dm-vnav-collapsed",n),S.set("nav_collapsed",n),t()};o.addEventListener("click",()=>s(!f.contains("dm-vnav-collapsed"))),t();const i=h.querySelector(".navbar-container")||h;i.insertBefore(o,i.firstChild?.nextSibling||null);const e=n=>{f.toggle("dm-vnav-autohide",n),f.toggle("dm-vnav-collapsible",!n&&x.has("collapsible")),n?f.remove("dm-vnav-collapsed"):x.has("collapsible")&&S.get("nav_collapsed")===!0&&f.add("dm-vnav-collapsed"),h.classList.remove("dm-nav-revealed"),t()};h.addEventListener("pointerdown",n=>{f.contains("dm-vnav-autohide")&&n.pointerType!=="mouse"&&!h.classList.contains("dm-nav-revealed")&&(h.classList.add("dm-nav-revealed"),n.preventDefault())}),document.addEventListener("pointerdown",n=>{h.contains(n.target)||h.classList.remove("dm-nav-revealed")}),window.E&&typeof E.contextMenu=="function"&&E.contextMenu(h,{items:()=>{const n=f.contains("dm-vnav-autohide"),r=f.contains("dm-vnav-collapsed"),y=S.has("nav_autohide")||S.has("nav_collapsed");return[{header:"Menu"},{label:r?"Expand menu":"Collapse to icons",icon:r?"maximize":"minimize",disabled:!v()||n||!x.has("collapsible"),action:()=>s(!r)},{label:n?"Keep on screen":"Hide when not in use",icon:n?"eye":"eye-off",disabled:!v(),action:()=>{S.set("nav_autohide",!n),e(!n)}},{type:"divider"},{label:"Reset to the site's setting",icon:"refresh",disabled:!y,action:()=>{S.remove("nav_autohide"),S.remove("nav_collapsed"),e(x.has("autohide")),f.toggle("dm-vnav-collapsed",!x.has("autohide")&&x.has("start-collapsed")),t()}}]}})}function ne(h){h.classList.add("dm-nav-scroll-hide");let g=window.scrollY,C=!1;window.addEventListener("scroll",()=>{C||(C=!0,requestAnimationFrame(()=>{C=!1;const f=window.scrollY,x=h.matches(":focus-within")||h.querySelector(".navbar-dropdown.open, .navbar-collapse.show");f>g&&f>h.offsetHeight&&!x?h.classList.add("dm-nav-scrolled-away"):f<g&&h.classList.remove("dm-nav-scrolled-away"),g=f}))},{passive:!0})}
@@ -12,7 +12,7 @@ import {fileURLToPath} from 'url';
12
12
  import {applyTransforms, getSanitizeExtensions, getShortcodeProcessors} from './hooks.js';
13
13
  import {getConfig} from '../config.js';
14
14
  import {getCollection, listEntries} from './collections.js';
15
- import {getMenu, resolveLocation, resolveMenuDecorations} from './menus.js';
15
+ import {getMenu, navbarOverrideFromAttrs, resolveLocation, resolveMenuDecorations} from './menus.js';
16
16
  import {buildMenuNav} from './menuRender.js';
17
17
  import {checkVisibility} from '../middleware/auth.js';
18
18
  import {getRoleHierarchy, getRoleLevel} from './roles.js';
@@ -3618,6 +3618,30 @@ function processSpacerBlocks(markdown) {
3618
3618
  * @param {string} markdown
3619
3619
  * @returns {string}
3620
3620
  */
3621
+ /**
3622
+ * [navbar ... /] - this page's own navbar settings, over the navbar menu's.
3623
+ *
3624
+ * Any Behaviour setting of the menu, as attributes (see NAVBAR_OVERRIDE_ATTRS in
3625
+ * menus.js): position, orientation, side, align, variant, hover-open, hover,
3626
+ * selected, accent, class, font, font-size, font-weight, letter-spacing,
3627
+ * icon-size, width, collapsible, collapsed, autohide, hide-on-scroll and the
3628
+ * float-* panel options. `true` / `false` switch a setting on or off; an empty
3629
+ * value clears it. Leaves a hidden marker the renderer reads (the first tag on
3630
+ * a page counts) - it draws nothing itself.
3631
+ *
3632
+ * [navbar collapsed="true" /]
3633
+ * [navbar autohide="true" width="200" /]
3634
+ * [navbar position="floating" float-anchor="TR" float-collapsible="true" /]
3635
+ */
3636
+ function processNavbarBlocks(markdown) {
3637
+ const {scrubbed, restore} = scrubCodeRegions(markdown);
3638
+ return restore(scrubbed.replace(/\[navbar(\s[^\]]*?)?\/?\]/gi, (_, attrStr) => {
3639
+ const override = navbarOverrideFromAttrs(parseShortcodeAttrs(attrStr || ''));
3640
+ if (!Object.keys(override).length) return '';
3641
+ return `<span class="dm-navbar-override" hidden data-navbar-override="${escapeAttr(JSON.stringify(override))}"></span>`;
3642
+ }));
3643
+ }
3644
+
3621
3645
  export const THEME_SWITCHER_POSITIONS = ['inline', 'top-left', 'top-right', 'left-center', 'right-center', 'bottom-left', 'bottom-right'];
3622
3646
 
3623
3647
  function processThemeSwitcherBlocks(markdown) {
@@ -4470,7 +4494,7 @@ export async function parseMarkdown(raw, opts = {}) {
4470
4494
  const withTimeline = processTimelineBlocks(withCountdown);
4471
4495
  const withListGroup = processListGroupBlocks(withTimeline);
4472
4496
  const withSpacer = processSpacerBlocks(withListGroup);
4473
- const withSwitcher = processThemeSwitcherBlocks(withSpacer);
4497
+ const withSwitcher = processNavbarBlocks(processThemeSwitcherBlocks(withSpacer));
4474
4498
  const withCenter = processCenterBlocks(withSwitcher);
4475
4499
  const withIcon = processIconBlocks(withCenter);
4476
4500
  const withDemo = processDemoBlocks(withIcon);
@@ -255,6 +255,112 @@ export function spacerSize(item) {
255
255
  // floating switcher itself is on.
256
256
  // ---------------------------------------------------------------------------
257
257
 
258
+ // ---------------------------------------------------------------------------
259
+ // Docked menu behaviour - `dock: {width, collapsible, collapsed, autohide, hideOnScroll}`.
260
+ //
261
+ // For the menu in the navbar slot when it is docked (not floating):
262
+ // width a vertical rail's width in px (160-480; 240 when unset)
263
+ // collapsible a vertical rail gets a toggle that shrinks it to its icons;
264
+ // the visitor's choice is remembered in their browser
265
+ // collapsed ...and starts that way until they choose
266
+ // autohide a vertical rail slides off-screen, leaving its edge, and comes
267
+ // back when the pointer reaches it (or focus moves into it)
268
+ // hideOnScroll a horizontal bar slides away while scrolling down and comes
269
+ // back on the way up
270
+ // ---------------------------------------------------------------------------
271
+ export const DOCK_WIDTH_MIN = 160;
272
+ export const DOCK_WIDTH_MAX = 480;
273
+
274
+ /** A dock block with only known keys and sane values; null when nothing is set. */
275
+ export function normaliseDock(dock) {
276
+ if (!dock || typeof dock !== 'object' || Array.isArray(dock)) return null;
277
+ const out = {};
278
+ const width = parseInt(dock.width, 10);
279
+ if (Number.isFinite(width)) out.width = Math.min(DOCK_WIDTH_MAX, Math.max(DOCK_WIDTH_MIN, width));
280
+ for (const key of ['collapsible', 'collapsed', 'autohide', 'hideOnScroll']) {
281
+ if (dock[key] === true) out[key] = true;
282
+ }
283
+ if (out.collapsed && !out.collapsible) out.collapsible = true; // starting collapsed needs the toggle back
284
+ return Object.keys(out).length ? out : null;
285
+ }
286
+
287
+ // ---------------------------------------------------------------------------
288
+ // Per-page overrides - the `[navbar ... /]` tag.
289
+ //
290
+ // A page can change any of its navbar menu's Behaviour settings for itself
291
+ // alone. The tag's attributes map onto the menu's keys below; `true`/`false`
292
+ // switch a setting on or off (so a page can turn off what the menu turns on),
293
+ // and an empty value clears it back to the default. The renderer merges the
294
+ // override over the menu, then validates the result - an override that would
295
+ // not pass validateMenu is dropped whole rather than half-applied.
296
+ // ---------------------------------------------------------------------------
297
+ export const NAVBAR_OVERRIDE_ATTRS = {
298
+ 'variant': ['variant'], 'position': ['position'], 'orientation': ['orientation'],
299
+ 'align': ['align'], 'side': ['side'], 'hover-open': ['appearOnHover', 'bool'], 'class': ['class'],
300
+ 'hover': ['highlight', 'hover'], 'selected': ['highlight', 'active'], 'accent': ['highlight', 'accent'],
301
+ 'font': ['style', 'fontFamily'], 'font-size': ['style', 'fontSize'], 'font-weight': ['style', 'fontWeight'],
302
+ 'letter-spacing': ['style', 'letterSpacing'], 'icon-size': ['style', 'iconSize'],
303
+ 'width': ['dock', 'width', 'num'], 'collapsible': ['dock', 'collapsible', 'bool'],
304
+ 'collapsed': ['dock', 'collapsed', 'bool'], 'autohide': ['dock', 'autohide', 'bool'],
305
+ 'hide-on-scroll': ['dock', 'hideOnScroll', 'bool'],
306
+ 'float-anchor': ['float', 'anchor'], 'float-x': ['float', 'offsetX', 'num'], 'float-y': ['float', 'offsetY', 'num'],
307
+ 'float-width': ['float', 'width', 'num'], 'float-radius': ['float', 'radius', 'num'],
308
+ 'float-shadow': ['float', 'shadow'], 'float-accent': ['float', 'accent'], 'float-opacity': ['float', 'opacity', 'num'],
309
+ 'float-title': ['float', 'title'], 'float-collapsible': ['float', 'collapsible', 'bool'],
310
+ 'float-collapsed': ['float', 'collapsed', 'bool'], 'float-movable': ['float', 'movable', 'bool'],
311
+ 'float-resizable': ['float', 'resizable', 'bool']
312
+ };
313
+
314
+ /**
315
+ * The override a tag's attributes describe: `{key: value}` for top-level settings and
316
+ * `{group: {key: value}}` for float / highlight / style / dock. `null` means "clear".
317
+ */
318
+ export function navbarOverrideFromAttrs(attrs = {}) {
319
+ const out = {};
320
+ for (const [name, raw] of Object.entries(attrs)) {
321
+ const spec = NAVBAR_OVERRIDE_ATTRS[name];
322
+ if (!spec) continue;
323
+ const [group, keyOrKind, kind] = spec;
324
+ const nested = spec.length === 3 || (spec.length === 2 && !['bool', 'num'].includes(keyOrKind));
325
+ const type = nested ? kind : keyOrKind;
326
+ let value = raw === true ? 'true' : String(raw).trim();
327
+ if (value === '') value = null;
328
+ else if (type === 'bool') value = value !== 'false' && value !== '0' && value !== 'no';
329
+ else if (type === 'num') value = Number.isFinite(Number(value)) ? Number(value) : value;
330
+ if (nested) (out[group] ||= {})[keyOrKind] = value;
331
+ else out[group] = value;
332
+ }
333
+ return out;
334
+ }
335
+
336
+ /**
337
+ * A menu with a page's override applied, or the menu as it was when the result would
338
+ * not validate. Groups merge key by key; `null` removes a key, `false` turns a switch off.
339
+ */
340
+ export function applyMenuOverride(menu, override) {
341
+ if (!menu || !override || !Object.keys(override).length) return menu;
342
+ const next = {...menu};
343
+ for (const [key, value] of Object.entries(override)) {
344
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
345
+ const group = {...(menu[key] || {})};
346
+ for (const [k, v] of Object.entries(value)) {
347
+ if (v === null || v === false) delete group[k]; else group[k] = v;
348
+ }
349
+ if (Object.keys(group).length) next[key] = group; else delete next[key];
350
+ } else if (value === null || value === false) {
351
+ delete next[key];
352
+ } else {
353
+ next[key] = value;
354
+ }
355
+ }
356
+ if (validateMenu(next).length) return menu;
357
+ if (next.dock) {
358
+ const dock = normaliseDock(next.dock);
359
+ if (dock) next.dock = dock; else delete next.dock;
360
+ }
361
+ return next;
362
+ }
363
+
258
364
  /** Items that hold no link: they are drawn, not navigated. */
259
365
  export const isStructuralItem = (it) => !!it && (it.type === 'separator' || it.type === 'spacer' || it.type === 'theme-switcher');
260
366
 
@@ -501,6 +607,7 @@ export async function createMenu(input) {
501
607
  ...(input.float != null && {float: input.float}),
502
608
  ...(input.highlight != null && {highlight: input.highlight}),
503
609
  ...(input.style != null && {style: input.style}),
610
+ ...(normaliseDock(input.dock) && {dock: normaliseDock(input.dock)}),
504
611
  ...(binding != null && {binding}),
505
612
  items: Array.isArray(input.items) ? input.items : [],
506
613
  meta: {
@@ -546,6 +653,7 @@ export async function updateMenu(slug, input) {
546
653
  ...(input.float != null && {float: input.float}),
547
654
  ...(input.highlight != null && {highlight: input.highlight}),
548
655
  ...(input.style != null && {style: input.style}),
656
+ ...(normaliseDock(input.dock) && {dock: normaliseDock(input.dock)}),
549
657
  ...(binding != null && {binding}),
550
658
  items: Array.isArray(input.items) ? input.items : [],
551
659
  meta: {
@@ -662,6 +770,16 @@ export function validateMenu(menu) {
662
770
  if (menu.side != null && !SIDES.has(menu.side)) {
663
771
  errors.push(`Invalid side "${menu.side}" - expected one of: ${[...SIDES].join(', ')}`);
664
772
  }
773
+ if (menu.dock != null) {
774
+ if (typeof menu.dock !== 'object' || Array.isArray(menu.dock)) {
775
+ errors.push('dock must be an object');
776
+ } else if (menu.dock.width != null && menu.dock.width !== '') {
777
+ const w = Number(menu.dock.width);
778
+ if (!Number.isFinite(w) || w < DOCK_WIDTH_MIN || w > DOCK_WIDTH_MAX) {
779
+ errors.push(`dock.width must be a number of px from ${DOCK_WIDTH_MIN} to ${DOCK_WIDTH_MAX}`);
780
+ }
781
+ }
782
+ }
665
783
  if (menu.style != null) {
666
784
  if (typeof menu.style !== 'object' || Array.isArray(menu.style)) {
667
785
  errors.push('style must be an object');
@@ -8,7 +8,7 @@ import {fileURLToPath} from 'url';
8
8
  import {getConfig} from '../config.js';
9
9
  import {getInjectionSnippets} from './plugins.js';
10
10
  import {applyTransforms} from './hooks.js';
11
- import {hasThemeSwitcherItem, resolveLocation, resolveMenuDecorations, resolveOverlaysForPage} from './menus.js';
11
+ import {applyMenuOverride, hasThemeSwitcherItem, resolveLocation, resolveMenuDecorations, resolveOverlaysForPage} from './menus.js';
12
12
  import {buildMenuNav} from './menuRender.js';
13
13
  import {getProjectForPage} from './projects.js';
14
14
  import {resolveContextMenusForPage} from './contextMenus.js';
@@ -122,6 +122,17 @@ function buildAnalyticsTag() {
122
122
  * html (body and overlay menus) and the item lists of its navbar and footers
123
123
  * @returns {{headTag: string, bodyTag: string}} Empty strings when nothing needs it
124
124
  */
125
+ /** The override a page's [navbar] tag left in its html (the first one), or null. */
126
+ function readNavbarOverride(html) {
127
+ const m = /data-navbar-override="([^"]*)"/.exec(html || '');
128
+ if (!m) return null;
129
+ try {
130
+ return JSON.parse(m[1].replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&'));
131
+ } catch {
132
+ return null;
133
+ }
134
+ }
135
+
125
136
  function buildThemeSwitcherAssets(themeCfg, page = {}) {
126
137
  const sw = themeCfg?.switcher || {};
127
138
  const html = page.html || '';
@@ -235,7 +246,7 @@ export async function renderPage(page, opts = {}) {
235
246
  project: page.resolvedProject
236
247
  || await getProjectForPage(page.urlPath || page.url || '/', page.project)
237
248
  };
238
- const [template, site, presets, injection, customCss, navMenu, footerPrimary, footerLegal] = await Promise.all([
249
+ const [template, site, presets, injection, customCss, navMenuOwn, footerPrimary, footerLegal] = await Promise.all([
239
250
  getTemplate(),
240
251
  Promise.resolve(getConfig('site')),
241
252
  Promise.resolve(getConfig('presets')),
@@ -246,6 +257,8 @@ export async function renderPage(page, opts = {}) {
246
257
  resolveLocation('footer-legal', opts.user || null, menuCtx)
247
258
  ]);
248
259
  const baseUrl = opts.baseUrl || site.baseUrl || '';
260
+ // The page's own navbar settings ([navbar ... /]) over the menu's, for this page only.
261
+ const navMenu = applyMenuOverride(navMenuOwn, readNavbarOverride(page.html));
249
262
 
250
263
  // Resolve live badge counts (countFrom) before the menus are injected as
251
264
  // globals. Static badges/colours/pills pass through unchanged.
@@ -277,6 +290,9 @@ export async function renderPage(page, opts = {}) {
277
290
  ...(navMenu.align && {align: navMenu.align}),
278
291
  ...(navMenu.float && {float: navMenu.float}),
279
292
  ...(navMenu.appearOnHover && {appearOnHover: true}),
293
+ // Docked behaviour (rail width, collapse, autohide, hide on scroll) - site.js
294
+ // wires it; width and autohide also shape the body below, from the first paint.
295
+ ...(navMenu.dock && {dock: navMenu.dock}),
280
296
  // Item highlighting: the navbar is built in the browser, so the
281
297
  // config has to travel to site.js rather than being rendered here.
282
298
  ...(navMenu.highlight && {highlight: navMenu.highlight}),
@@ -293,9 +309,7 @@ export async function renderPage(page, opts = {}) {
293
309
  const layoutWidth = pageWidth || (VALID_LAYOUT_WIDTHS.has(preset.width) ? preset.width : 'normal');
294
310
  // A vertical navbar docks as a side rail, so the body needs its gutter from
295
311
  // the first paint - stamping the class client-side would shift the layout.
296
- const verticalNavClass = (navigation.orientation === 'vertical' && navigation.position !== 'floating')
297
- ? `dm-vnav dm-vnav-${navigation.side === 'right' ? 'right' : 'left'}`
298
- : '';
312
+ const verticalNavClass = railBodyClass(navigation);
299
313
  const navbarClass = (navigation.orientation === 'vertical' && navigation.position !== 'floating')
300
314
  ? `dm-nav-vertical dm-nav-vertical-${navigation.side === 'right' ? 'right' : 'left'}`
301
315
  : '';
@@ -404,6 +418,7 @@ export async function renderPage(page, opts = {}) {
404
418
  layoutBodyClass,
405
419
  pageBodyStyle,
406
420
  navbarClass,
421
+ railDocked: /\bdm-vnav\b/.test(verticalNavClass),
407
422
  showNavbar: preset.navbar !== false,
408
423
  showFooter: preset.footer !== false,
409
424
  showSidebar: preset.sidebar === true || page.sidebar === true,
@@ -819,10 +834,30 @@ function escapeHtml(str) {
819
834
  .replace(/"/g, '&quot;');
820
835
  }
821
836
 
837
+ /**
838
+ * The body classes a docked vertical rail needs from the first paint: the gutter
839
+ * on its side, none when it autohides (it slides over the page), and a marker
840
+ * for a collapsible one (page.html's boot script adds the visitor's collapsed
841
+ * state before paint, so the gutter is right straight away).
842
+ */
843
+ function railBodyClass(navigation) {
844
+ if (navigation.orientation !== 'vertical' || navigation.position === 'floating') return '';
845
+ const dock = navigation.dock || {};
846
+ return [
847
+ 'dm-vnav', `dm-vnav-${navigation.side === 'right' ? 'right' : 'left'}`,
848
+ dock.autohide ? 'dm-vnav-autohide' : '',
849
+ dock.collapsible && !dock.autohide ? 'dm-vnav-collapsible' : '',
850
+ dock.collapsed && !dock.autohide ? 'dm-vnav-start-collapsed' : ''
851
+ ].filter(Boolean).join(' ');
852
+ }
853
+
822
854
  function buildNavbarStyleTag(nav) {
823
855
  const s = nav?.style || {};
856
+ // A docked rail's width is a token the rail and the body's gutter both read.
857
+ const railWidth = nav?.orientation === 'vertical' && Number.isFinite(nav?.dock?.width)
858
+ ? `:root { --dm-vnav-width: ${Math.round(nav.dock.width)}px; }` : '';
824
859
  if (!s.fontFamily && !s.fontSize && !s.fontWeight && !s.letterSpacing && !s.iconSize) {
825
- return {navbarFontLink: '', navbarStyleTag: ''};
860
+ return {navbarFontLink: '', navbarStyleTag: railWidth ? `<style>${railWidth}</style>` : ''};
826
861
  }
827
862
  let navbarFontLink = '';
828
863
  if (s.fontFamily && s.fontFamily !== 'system-ui' && s.fontFamily !== 'inherit') {
@@ -843,6 +878,7 @@ function buildNavbarStyleTag(nav) {
843
878
  const iconSel = '#site-navbar .navbar-link [data-icon], #site-navbar .navbar-link svg, #site-navbar .navbar-dropdown-toggle [data-icon], #site-navbar .navbar-dropdown-toggle svg';
844
879
  rules.push(`${iconSel} { width: ${s.iconSize} !important; height: ${s.iconSize} !important; }`);
845
880
  }
881
+ if (railWidth) rules.push(railWidth);
846
882
  const navbarStyleTag = `<style>${rules.join(' ')}</style>`;
847
883
  return {navbarFontLink, navbarStyleTag};
848
884
  }
@@ -1014,6 +1050,9 @@ export async function renderBlogPage(templatePath, data = {}, seoMeta = {}) {
1014
1050
  ...(navMenu.orientation === 'vertical' && {orientation: 'vertical', side: navMenu.side === 'right' ? 'right' : 'left'}),
1015
1051
  ...(navMenu.align && {align: navMenu.align}),
1016
1052
  ...(navMenu.appearOnHover && {appearOnHover: true}),
1053
+ // Docked behaviour (rail width, collapse, autohide, hide on scroll) - site.js
1054
+ // wires it; width and autohide also shape the body below, from the first paint.
1055
+ ...(navMenu.dock && {dock: navMenu.dock}),
1017
1056
  // Item highlighting: the navbar is built in the browser, so the
1018
1057
  // config has to travel to site.js rather than being rendered here.
1019
1058
  ...(navMenu.highlight && {highlight: navMenu.highlight}),
@@ -1119,9 +1158,10 @@ export async function renderBlogPage(templatePath, data = {}, seoMeta = {}) {
1119
1158
  fontStyleTag,
1120
1159
  layout: 'default',
1121
1160
  layoutBodyClass: ['dm-layout-normal', navigation.orientation === 'vertical'
1122
- ? `dm-vnav dm-vnav-${navigation.side === 'right' ? 'right' : 'left'}`
1161
+ ? railBodyClass(navigation)
1123
1162
  : '', ...cssClasses(site.bodyClass)].filter(Boolean).join(' '),
1124
1163
  pageBodyStyle: '',
1164
+ railDocked: navigation.orientation === 'vertical' && /\bdm-vnav\b/.test(railBodyClass(navigation)),
1125
1165
  showNavbar: true,
1126
1166
  showFooter: true,
1127
1167
  showSidebar: false,
@@ -25,7 +25,7 @@
25
25
  <link rel="stylesheet" href="/dist/domma/themes/domma-themes.css">
26
26
 
27
27
  <!-- Site CSS -->
28
- <link rel="stylesheet" href="/public/css/site.css?v=20261011-sidebar-right">
28
+ <link rel="stylesheet" href="/public/css/site.css?v=20261011-docked-menus">
29
29
  <link rel="stylesheet" href="/public/css/forms.css?v=20260927-submit-btn-css">
30
30
  <!-- Menu item hover / current-page effects. Loaded after site.css so a
31
31
  menu's own highlight wins over the blanket link styling there. -->
@@ -41,6 +41,25 @@
41
41
  {{headInjectLate}}
42
42
  </head>
43
43
  <body class="dm-cloaked dm-theme-{{dommaTheme}} {{customThemeClass}} {{layoutBodyClass}}" data-layout="{{layout}}">
44
+ {{#if railDocked}}
45
+ <script>
46
+ // A docked rail: the visitor's own choices - collapsed, or hidden when not in use
47
+ // (the rail's right-click menu sets them) - over the menu's, before the first paint,
48
+ // so the page's gutter is right straight away. site.js owns the controls.
49
+ (function () {
50
+ var b = document.body, cl = b.classList;
51
+ var get = function (k) { try { return JSON.parse(localStorage.getItem('domma:' + k)); } catch (e) { return null; } };
52
+ b.dataset.railSite = ['autohide', 'collapsible', 'start-collapsed']
53
+ .filter(function (k) { return cl.contains('dm-vnav-' + k); }).join(' ');
54
+ var hide = get('nav_autohide');
55
+ if (hide === true) { cl.add('dm-vnav-autohide'); cl.remove('dm-vnav-collapsible'); }
56
+ if (hide === false) { cl.remove('dm-vnav-autohide'); if (b.dataset.railSite.indexOf('collapsible') !== -1) cl.add('dm-vnav-collapsible'); }
57
+ if (!cl.contains('dm-vnav-collapsible')) return;
58
+ var c = get('nav_collapsed');
59
+ if (c === true || (c === null && cl.contains('dm-vnav-start-collapsed'))) cl.add('dm-vnav-collapsed');
60
+ })();
61
+ </script>
62
+ {{/if}}
44
63
 
45
64
  {{#if showNavbar}}
46
65
  <nav id="site-navbar" class="{{navbarClass}}"></nav>
@@ -137,7 +156,7 @@
137
156
  </script>
138
157
 
139
158
  <!-- Site initialisation -->
140
- <script src="/public/js/site.js?v=20261011-theme-switcher-item" type="module"></script>
159
+ <script src="/public/js/site.js?v=20261011-docked-menus" type="module"></script>
141
160
  <script src="/public/js/collection-context.js?v=20261010-utf8-payloads" type="module"></script>
142
161
  {{ctxMenusModule}}
143
162