domma-cms 0.54.2 → 0.55.1

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 B,applyFloat as P,applyHighlight as q,isCurrentItem as O}from"/public/js/menu-decor.mjs?v=20260905-menu-highlight";import{initFloatingMenus as R}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as j,markInvalidFields as V,showValidationPopup as J}from"/public/js/forms.js?v=20260821-wizard-grid";import{buildWizardSteps as W}from"/public/js/form-steps.mjs";$(()=>{const w=window.__CMS_NAV__||{},g=window.__CMS_SITE__||{};if(g.autoTheme?.enabled){let t=function(a){const e=(a||"07:00").split(":");return+e[0]*60+(+e[1]||0)},o=function(){const a=new Date,e=a.getHours()*60+a.getMinutes();return e>=t(n.dayStart)&&e<t(n.nightStart)?n.dayTheme:n.nightTheme};const n=g.autoTheme;Domma.theme.set(o()),setInterval(()=>Domma.theme.set(o()),6e4)}const x=$("#site-navbar");if(x.length&&w.brand){let o=function(i,c=0){return Array.isArray(i)?i.filter(r=>!(r&&r.type==="spacer")).filter(r=>!(r&&r.type==="separator"&&c===0)).map(r=>r&&r.type==="separator"?{divider:!0}:Array.isArray(r?.items)?{...r,items:o(r.items,c+1)}:r):i},a=function(i){return Array.isArray(i)?i.filter(c=>!(c&&(c.type==="separator"||c.type==="spacer"))):[]};const n={...w.brand},t=n.size&&n.size!=="md"?` navbar-brand-${n.size}`:"";if(n.logo||n.icon||n.tagline){let i="";n.logo?i+=`<img src="${n.logo}" class="navbar-brand-logo" alt="${n.text||""}">`:n.icon&&(i+=`<span data-icon="${n.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),n.text&&(i+=`<span class="navbar-brand-text${t}">${n.text}</span>`),n.tagline&&(i+=`<small class="navbar-brand-tagline">${n.tagline}</small>`),n.html=i}else t&&n.text&&(n.html=`<span class="navbar-brand-text${t}">${n.text}</span>`);const e=w.position==="floating",u=w.orientation==="vertical";Domma.elements.navbar("#site-navbar",{brand:n,items:o(w.items||[]),variant:w.variant||"dark",position:e?"static":w.position||"sticky",collapsible:!0,appearOnHover:!!w.appearOnHover}),Domma.icons.scan("#site-navbar");const h=x.get(0);u&&x.get(0).classList.add("dm-nav-vertical",w.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left"),q(h,w),e&&(P(x.get(0),w),R(x.get(0).parentNode||document));const y=h.querySelector(".navbar-toggle"),f=h.querySelector(".navbar-collapse");if(y&&f){let i=null;const c=()=>{f.classList.contains("show")&&y.click()},r=()=>{i&&(i.remove(),i=null),document.body.classList.remove("dm-drawer-open")},s=()=>{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")?s():r()})}),f.addEventListener("click",l=>{l.target.closest("a")&&requestAnimationFrame(r)}),document.addEventListener("keydown",l=>{l.key==="Escape"&&f.classList.contains("show")&&c()})}const m=h.querySelector(".navbar-nav");if(m){const i=Array.from(m.children);let c=0;for(const r of w.items||[])if(!(r&&r.type==="separator")){if(r&&r.type==="spacer"){const s=document.createElement("li"),l=["xs","sm","md","lg","xl","flex"].includes(r.size)?r.size:"md";s.className=`dm-menu-spacer dm-menu-spacer--${l}`,s.setAttribute("aria-hidden","true"),m.insertBefore(s,i[c]||null);continue}c++}}const b=a(w.items||[]);h.querySelectorAll(".navbar-link[data-index], .navbar-dropdown-item[data-index]").forEach(i=>{const c=b[parseInt(i.dataset.index,10)];if(!c)return;const r=i.dataset.subindex!=null,s=r&&Array.isArray(c.items)?c.items[parseInt(i.dataset.subindex,10)]:c;if(!s)return;s.colour&&N(i,s.colour),s.pill&&!r&&B(i,s.pill);const l=M(s.badge);l&&i.appendChild(l),O(s.url,location.pathname)&&(i.classList.add("active"),i.setAttribute("aria-current","page"))})}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;")},u=function(s){return e(s).replace(/'/g,"&#39;")},h=function(s){if(s&&s.type==="separator")return'<li class="footer-sep"><hr></li>';if(s&&s.type==="spacer")return`<li class="dm-menu-spacer dm-menu-spacer--${["xs","sm","md","lg","xl","flex"].includes(s.size)?s.size:"md"}" aria-hidden="true"></li>`;const l=u(s.url||"#"),d=e(s.text||""),v=Array.isArray(s.items)&&s.items.length?`<ul>${s.items.map(h).join("")}</ul>`:"";return`<li><a href="${l}">${d}</a>${v}</li>`},y=function(s,l){return(s||[]).forEach(d=>{d&&(d.type==="separator"||d.type==="spacer")||(l.push(d),Array.isArray(d.items)&&d.items.length&&y(d.items,l))}),l};const n=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 o='<div class="footer-inner container">';const a=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""};if(a.copyright&&(o+=`<p>${e(a.copyright)}</p>`),a.primary.length&&(o+=`<nav class="footer-nav footer-nav--primary"><ul>${a.primary.map(h).join("")}</ul></nav>`),a.legal.length&&(o+=`<nav class="footer-nav footer-nav--legal"><ul>${a.legal.map(h).join("")}</ul></nav>`),g.footer){const s=Object.keys(t).filter(l=>n[l]);s.length&&(o+='<div class="footer-social">',s.forEach(l=>{const{label:d,svg:v}=t[l];o+=`<a href="${n[l]}" target="_blank" rel="noopener noreferrer" aria-label="${d}" class="footer-social-link">${v}</a>`}),o+="</div>")}o+="</div>",C.html(o);const f=[...y(a.primary,[]),...y(a.legal,[])];C.get(0).querySelectorAll(".footer-nav a").forEach((s,l)=>{const d=f[l];if(!d)return;d.colour&&N(s,d.colour);const v=M(d.badge);v&&s.appendChild(v),O(d.url,location.pathname)&&(s.classList.add("is-active"),s.setAttribute("aria-current","page"))}),q(C.get(0).querySelector(".footer-nav--primary"),{highlight:a.primaryHighlight}),q(C.get(0).querySelector(".footer-nav--legal"),{highlight:a.legalHighlight});const b=g.footer?.showMotionToggle!==!1,i=S.get("reduced_motion"),c=i!==null?!!i:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),r=C.get(0).querySelector(".footer-inner");if(b&&r){const s=document.createElement("input");s.type="checkbox",s.className="form-switch-input",s.id="dm-motion-switch",s.checked=c,s.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 d=document.createElement("label");d.className="form-switch footer-motion-switch",d.title="Reduce motion",d.appendChild(s),d.appendChild(l),r.appendChild(d)}}$("#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 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)}),p.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)}),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 n=Array.from(this.querySelectorAll(".dm-progression-item")).map(a=>{const e={title:a.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:a.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return a.dataset.date&&(e.date=a.dataset.date),a.dataset.status&&(e.status=a.dataset.status),a.dataset.entryId&&(e._entryId=a.dataset.entryId),e}),t=this.dataset.layout==="centred"?"centered":this.dataset.layout||"vertical",o=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:o,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",o=n.dataset.revealAnimation||"slide-up",a=parseInt(n.dataset.revealDuration,10)||400,e=parseInt(n.dataset.revealStagger,10)||60,u=parseInt(n.dataset.revealDelay,10)||0,h=n.dataset.revealDirection==="rtl",y=Array.from(n.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((m,b)=>{m.style.opacity="0",m.style.transform=f[o]||"",m.style.transition=`opacity ${a}ms ease, transform ${a}ms ease`;const i=h?y.length-1-b:b;m.style.transitionDelay=t==="stagger"?`${u+i*e}ms`:`${u}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const m=new IntersectionObserver(b=>{b.forEach(i=>{i.isIntersecting&&(i.target.offsetWidth,i.target.style.opacity="1",i.target.style.transform="none",m.unobserve(i.target))})},{threshold:.1});y.forEach(b=>m.observe(b))}))}),p.find(".card[data-collapsible]").each(function(){const n=this.querySelector(".card-header");n&&n.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),p.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const n=this.dataset.soTarget,t=document.getElementById(n);if(!t)return;const o=E.slideover({title:t.dataset.soTitle||"",size:t.dataset.soSize||"md",position:t.dataset.soPosition||"right"});t.style.display="",o.setContent(t),o.open()})})}if(typeof $.setup=="function"){const n=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(t=>{try{const o=atob(t.dataset.config),a=JSON.parse(o);Object.assign(n,a)}catch{}}),Object.keys(n).length>0){const t={};for(const[o,a]of Object.entries(n)){const e=a?.events?.click,{confirm:u,toast:h,alert:y,prompt:f,...m}=e||{};u||h||y||f?($(o).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:m.promptPlaceholder||"",inputValue:m.promptDefault||""}),c===null))){if(m.target){const r=$(m.target);m.toggleClass&&r.toggleClass(m.toggleClass),m.addClass&&r.addClass(m.addClass),m.removeClass&&r.removeClass(m.removeClass),c!==null&&(m.setText&&r.text(c),m.setVal&&r.val(c),m.setAttr&&r.attr(m.setAttr,c))}m.href&&(window.location.href=m.href),h&&E.toast(h,{type:m.toastType||"success"}),y&&E.alert(y)}}),Object.keys(m).length&&(t[o]={...a,events:{...a.events,click:m}})):t[o]=a}$.setup(t)}}p.length&&D(p.get(0))});function D(w){w.querySelectorAll(".dm-cta-trigger").forEach(g=>{g.__ctaWired||(g.__ctaWired=!0,g.addEventListener("click",async()=>{const x=g.dataset.action,C=g.dataset.entry,k=g.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 n=Array.from(g.childNodes).map(o=>o.cloneNode(!0));g.disabled=!0,g.textContent="Running\u2026";const t=o=>fetch(`/api/actions/${x}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${o}`},body:JSON.stringify({entryId:C})});try{let o=await t(p);if(o.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:h}=await u.json();S.set("auth_token",h),p=h,o=await t(p)}}}const a=await o.json().catch(()=>({}));if(!o.ok)throw new Error(a.error||a.message||`Error ${o.status}`);E.toast(a.message||"Action completed.",{type:"success"})}catch(o){E.toast(o.message||"Action failed.",{type:"error"})}finally{g.disabled=!1,g.textContent="",n.forEach(o=>g.appendChild(o)),Domma.icons.scan(g)}}))})}(function(){const g=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!g.length)return;const x=S.get("auth_token");function C(p,n){const t=document.createElement("div");t.className="dm-collection-display dm-collection-empty";const o=document.createElement("p");if(n){o.appendChild(document.createTextNode("Please "));const a=document.createElement("a");a.href="/login",a.textContent="sign in",o.appendChild(a),o.appendChild(document.createTextNode(" to view your entries."))}else o.textContent=p;return t.appendChild(o),t}function k(p,n){const t=document.createElement("template");t.innerHTML=n||"",p.replaceWith(t.content)}g.forEach(async p=>{const n=p.dataset.collectionAttrs;if(!n)return;if(!x){p.replaceChildren(C("",!0));return}const t=o=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${o}`},body:JSON.stringify({attrs:n})});try{let o=x,a=await t(o);if(a.status===401){const u=S.get("auth_refresh_token");if(u){const h=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:u})});if(h.ok){const{token:y}=await h.json();S.set("auth_token",y),o=y,a=await t(o)}}}if(!a.ok){const{error:u}=await a.json().catch(()=>({}));p.replaceChildren(C(u||"Unable to load entries.",!1));return}const{html:e}=await a.json();k(p,e)}catch{p.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 g=document.querySelectorAll("[data-collection-table]");!g.length||typeof T>"u"||!T.create||g.forEach(x=>{let C;try{C=JSON.parse(atob(x.dataset.payload))}catch{return}const{columns:k,rows:p,search:n,sortable:t,exportable:o,pageSize:a,empty:e,ctaConfig:u,slug:h,exportCfg:y}=C;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 r=document.createElement("button");if(r.className=`btn btn-${u.style||"primary"} dm-cta-trigger`,r.dataset.action=u.action||"",r.dataset.entry=c._entryId||"",u.confirm&&(r.dataset.confirm=u.confirm),u.icon){const s=document.createElement("span");s.dataset.icon=u.icon,r.appendChild(s),r.appendChild(document.createTextNode(" "))}return r.appendChild(document.createTextNode(u.label||"Run")),r}})}const f="col-table-"+Math.random().toString(36).slice(2,7),m=document.createElement("div");m.id=f,x.replaceChildren(m),T.create("#"+f,{data:p,columns:k,search:n,sortable:t,exportable:o,pageSize:a,emptyMessage:e}),u&&D(m),x._dmTable={columns:k,rows:p,slug:h,exportCfg:y,render(b){T.create("#"+f,{data:b,columns:k,search:n,sortable:t,exportable:o,pageSize:a,emptyMessage:e}),u&&D(m)}}})})(),(function(){const g=document.querySelectorAll("[data-form-inline]");if(!g.length||typeof F>"u")return;function x(t){const o=t&&t.logic;return!!(o&&(o.visibility||o.requirement))}function C(t,o){const a={};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)),h=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":u?"chooser":e.type,y={...e.formConfig||{}};y.span==="full"&&o&&(y.span=o);const f={type:h,required:x(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)),a[e.name]=f}),a}function k(t){const o={};return(t||[]).forEach(a=>{if(!(!a.name||a.type==="page-break"||a.type==="spacer")&&(a.type==="select"||a.type==="multiselect")&&a.required){const e=(a.options||[])[0];e&&(o[a.name]=typeof e=="object"?e.value:e)}}),o}function p(t,o){(o||[]).forEach(a=>{if(a.type!=="date"||!a.name)return;const e=t.querySelector(`[name="${a.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function n(t,o,a){let e=t.querySelector(".cms-form-message");e||(e=document.createElement("p"),e.className="cms-form-message",t.appendChild(e)),e.textContent=o,e.style.cssText=a?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}g.forEach(t=>{let o;try{o=JSON.parse(atob(t.dataset.formInline))}catch{return}const a=o.fields||[],e=o.settings||{},u=Number(e.columns)===3?3:2,h=e.layout||"grid",y=a.some(c=>c.type==="page-break"),f=async c=>{try{const r=t.querySelector(".fb-form-honeypot"),s=r?.querySelector('input[type="text"]')||null,l=r?.querySelector('[name="_t"]')||null,d=Object.assign({},c);delete d._t,r?.dataset.hpName&&delete d[r.dataset.hpName],s!==null&&(d._hp=s.value),l!==null&&(d._t=l.value);const v=await H.post(`/api/forms/submit/${o.slug}`,d);if(v&&v.error){n(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);n(t,v?.message||e.successMessage||"Thank you for your submission.",!1)}catch(r){throw r.formSubmitFailed||n(t,r.message||"Submission failed. Please try again.",!0),r}},m=Date.now();function b(c){const r=c.querySelector("form");if(!r||r.querySelector(".fb-form-honeypot"))return;const s=document.createElement("div");s.className="fb-form-honeypot",s.setAttribute("aria-hidden","true");const l=document.createElement("input");l.name=r.querySelector('[name="website"]')?"_website":"website",s.dataset.hpName=l.name,l.type="text",l.tabIndex=-1,l.autocomplete="url",l.placeholder="https://",s.appendChild(l);const d=document.createElement("input");d.name="_t",d.type="hidden",d.value=m,s.appendChild(d),r.appendChild(s)}function i(c){if(!window.FormLogicEngine||!a.some(s=>s.logic))return null;const r=new window.FormLogicEngine.FormLogicRuntime(o,t,c?{baseValues:c}:void 0);if(r.init(),t._formLogicRuntime=r,t._formLogicObserved)return r;if(t._formLogicObserved=!0,t.parentNode&&typeof MutationObserver<"u"){const s=new MutationObserver(function(l){for(const d of l)for(const v of d.removedNodes)if(v===t||v.nodeType===1&&v.contains&&v.contains(t)){t._formLogicRuntime&&t._formLogicRuntime.destroy(),s.disconnect();return}});s.observe(t.parentNode,{childList:!0,subtree:!1}),t._formLogicObserver=s}return r}if(y&&F.wizard){const c=W(a,o.title),r=c.map(_=>({title:_.title,description:_.description,fields:C(_.fields,u)})),s=c.map(_=>_.fields);let l=0,d=null,v=null;const A=_=>{const I=window.FormLogicEngine;if(!I||!I.validateFields)return;const L=I.validateFields(t,s[l]||[]);L.valid||(_.preventDefault(),_.stopImmediatePropagation(),V(t,L.missing),J(L))},z=()=>{const _=s[l]||a;p(t,_),j(t,_),e.honeypot!==!1&&b(t),d&&d.destroy(),d=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:r},layout:h,columns:u,finishText:e.submitText||"Finish",onSubmit:f,onStepChange:_=>{l=_,setTimeout(z,0)}}),Promise.resolve(v).then(z)}else if(F.render){const c=F.render(t,C(a,u),k(a),{submitText:e.submitText||"Submit",layout:h,columns:u,onSubmit:f});Promise.resolve(c).then(function(){if(h==="grid"&&e.submitSpan==="full"){const r=t.querySelector(".form-buttons");r&&r.classList.add("col-span-full")}p(t,a),j(t,a),e.honeypot!==!1&&b(t),i()})}}),$(document).on("click",".dm-banner__dismiss",function(){$(this).closest(".dm-banner").remove()})})();
1
+ import{makeBadgeEl as M,applyColour as N,applyPill as B,applyFloat as P,applyHighlight as z,applyItemHighlight as R,itemHighlightAttrs as V,isCurrentItem as O,menuAlign as J}from"/public/js/menu-decor.mjs?v=20260918-item-highlight";import{initFloatingMenus as W}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as j,markInvalidFields as G,showValidationPopup as X}from"/public/js/forms.js?v=20260821-wizard-grid";import{buildWizardSteps as Y}from"/public/js/form-steps.mjs";$(()=>{const b=window.__CMS_NAV__||{},g=window.__CMS_SITE__||{};if(g.autoTheme?.enabled){let t=function(a){const e=(a||"07:00").split(":");return+e[0]*60+(+e[1]||0)},n=function(){const a=new Date,e=a.getHours()*60+a.getMinutes();return e>=t(o.dayStart)&&e<t(o.nightStart)?o.dayTheme:o.nightTheme};const o=g.autoTheme;Domma.theme.set(n()),setInterval(()=>Domma.theme.set(n()),6e4)}const x=$("#site-navbar");if(x.length&&b.brand){let n=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:n(s.items,c+1)}:s):i},a=function(i){return Array.isArray(i)?i.filter(c=>!(c&&(c.type==="separator"||c.type==="spacer"))):[]};const o={...b.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=b.position==="floating",d=b.orientation==="vertical";Domma.elements.navbar("#site-navbar",{brand:o,items:n(b.items||[]),variant:b.variant||"dark",position:e?"static":b.position||"sticky",collapsible:!0,appearOnHover:!!b.appearOnHover}),Domma.icons.scan("#site-navbar");const h=x.get(0);h.classList.add(`dm-nav-align-${J(b)||"centre"}`),d&&x.get(0).classList.add("dm-nav-vertical",b.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left"),z(h,b),e&&(P(x.get(0),b),W(x.get(0).parentNode||document));const y=h.querySelector(".navbar-toggle"),f=h.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 u=h.querySelector(".navbar-nav");if(u){const i=Array.from(u.children);let c=0;for(const s of b.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"),u.insertBefore(r,i[c]||null);continue}c++}}const w=a(b.items||[]);h.querySelectorAll(".navbar-link[data-index], .navbar-dropdown-item[data-index]").forEach(i=>{const c=w[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&&N(i,r.colour),r.pill&&!s&&B(i,r.pill),R(i,r);const l=M(r.badge);l&&i.appendChild(l),O(r.url,location.pathname)&&(i.classList.add("active"),i.setAttribute("aria-current","page"))})}const C=$("#site-footer");if(C.length){let e=function(r){return String(r??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")},d=function(r){return e(r).replace(/'/g,"&#39;")},h=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=d(r.url||"#"),m=e(r.text||""),v=Array.isArray(r.items)&&r.items.length?`<ul>${r.items.map(h).join("")}</ul>`:"",A=V(r),D=[A.hover&&` data-menu-item-hover="${d(A.hover)}"`,A.active&&` data-menu-item-active="${d(A.active)}"`].filter(Boolean).join(""),k=A.accent?` style="--dm-menu-highlight:${d(A.accent)}"`:"";return`<li><a href="${l}"${D}${k}>${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=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 a=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""};if(a.copyright&&(n+=`<p>${e(a.copyright)}</p>`),a.primary.length&&(n+=`<nav class="footer-nav footer-nav--primary"><ul>${a.primary.map(h).join("")}</ul></nav>`),a.legal.length&&(n+=`<nav class="footer-nav footer-nav--legal"><ul>${a.legal.map(h).join("")}</ul></nav>`),g.footer){const r=Object.keys(t).filter(l=>o[l]);r.length&&(n+='<div class="footer-social">',r.forEach(l=>{const{label:m,svg:v}=t[l];n+=`<a href="${o[l]}" target="_blank" rel="noopener noreferrer" aria-label="${m}" class="footer-social-link">${v}</a>`}),n+="</div>")}n+="</div>",C.html(n);const f=[...y(a.primary,[]),...y(a.legal,[])];C.get(0).querySelectorAll(".footer-nav a").forEach((r,l)=>{const m=f[l];if(!m)return;m.colour&&N(r,m.colour);const v=M(m.badge);v&&r.appendChild(v),O(m.url,location.pathname)&&(r.classList.add("is-active"),r.setAttribute("aria-current","page"))}),z(C.get(0).querySelector(".footer-nav--primary"),{highlight:a.primaryHighlight}),z(C.get(0).querySelector(".footer-nav--legal"),{highlight:a.legalHighlight});const w=g.footer?.showMotionToggle!==!1,i=S.get("reduced_motion"),c=i!==null?!!i:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),s=C.get(0).querySelector(".footer-inner");if(w&&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(a=>{const e={title:a.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:a.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return a.dataset.date&&(e.date=a.dataset.date),a.dataset.status&&(e.status=a.dataset.status),a.dataset.entryId&&(e._entryId=a.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",a=parseInt(o.dataset.revealDuration,10)||400,e=parseInt(o.dataset.revealStagger,10)||60,d=parseInt(o.dataset.revealDelay,10)||0,h=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((u,w)=>{u.style.opacity="0",u.style.transform=f[n]||"",u.style.transition=`opacity ${a}ms ease, transform ${a}ms ease`;const i=h?y.length-1-w:w;u.style.transitionDelay=t==="stagger"?`${d+i*e}ms`:`${d}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const u=new IntersectionObserver(w=>{w.forEach(i=>{i.isIntersecting&&(i.target.offsetWidth,i.target.style.opacity="1",i.target.style.transform="none",u.unobserve(i.target))})},{threshold:.1});y.forEach(w=>u.observe(w))}))}),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 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=atob(t.dataset.config),a=JSON.parse(n);Object.assign(o,a)}catch{}}),Object.keys(o).length>0){const t={};for(const[n,a]of Object.entries(o)){const e=a?.events?.click,{confirm:d,toast:h,alert:y,prompt:f,...u}=e||{};d||h||y||f?($(n).on("click",async function(i){if(i.preventDefault(),d&&!await E.confirm(d))return;let c=null;if(!(f&&(c=await E.prompt(f,{inputPlaceholder:u.promptPlaceholder||"",inputValue:u.promptDefault||""}),c===null))){if(u.target){const s=$(u.target);u.toggleClass&&s.toggleClass(u.toggleClass),u.addClass&&s.addClass(u.addClass),u.removeClass&&s.removeClass(u.removeClass),c!==null&&(u.setText&&s.text(c),u.setVal&&s.val(c),u.setAttr&&s.attr(u.setAttr,c))}u.href&&(window.location.href=u.href),h&&E.toast(h,{type:u.toastType||"success"}),y&&E.alert(y)}}),Object.keys(u).length&&(t[n]={...a,events:{...a.events,click:u}})):t[n]=a}$.setup(t)}}p.length&&L(p.get(0))});function L(b){b.querySelectorAll(".dm-cta-trigger").forEach(g=>{g.__ctaWired||(g.__ctaWired=!0,g.addEventListener("click",async()=>{const x=g.dataset.action,C=g.dataset.entry,_=g.dataset.confirm;let p=S.get("auth_token");if(!p){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/${x}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`},body:JSON.stringify({entryId:C})});try{let n=await t(p);if(n.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:h}=await d.json();S.set("auth_token",h),p=h,n=await t(p)}}}const a=await n.json().catch(()=>({}));if(!n.ok)throw new Error(a.error||a.message||`Error ${n.status}`);E.toast(a.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 x=S.get("auth_token");function C(p,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 a=document.createElement("a");a.href="/login",a.textContent="sign in",n.appendChild(a),n.appendChild(document.createTextNode(" to view your entries."))}else n.textContent=p;return t.appendChild(n),t}function _(p,o){const t=document.createElement("template");t.innerHTML=o||"",p.replaceWith(t.content)}g.forEach(async p=>{const o=p.dataset.collectionAttrs;if(!o)return;if(!x){p.replaceChildren(C("",!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=x,a=await t(n);if(a.status===401){const d=S.get("auth_refresh_token");if(d){const h=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:d})});if(h.ok){const{token:y}=await h.json();S.set("auth_token",y),n=y,a=await t(n)}}}if(!a.ok){const{error:d}=await a.json().catch(()=>({}));p.replaceChildren(C(d||"Unable to load entries.",!1));return}const{html:e}=await a.json();_(p,e)}catch{p.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(),L(document.body)},100)})(),(function(){const g=document.querySelectorAll("[data-collection-table]");!g.length||typeof T>"u"||!T.create||g.forEach(x=>{let C;try{C=JSON.parse(atob(x.dataset.payload))}catch{return}const{columns:_,rows:p,search:o,sortable:t,exportable:n,pageSize:a,empty:e,ctaConfig:d,slug:h,exportCfg:y}=C;if(!_?.length)return;if(d){const w=_.findIndex(i=>i.key==="_cta");w!==-1&&(_[w]={key:"_cta",title:"",render:(i,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 r=document.createElement("span");r.dataset.icon=d.icon,s.appendChild(r),s.appendChild(document.createTextNode(" "))}return s.appendChild(document.createTextNode(d.label||"Run")),s}})}const f="col-table-"+Math.random().toString(36).slice(2,7),u=document.createElement("div");u.id=f,x.replaceChildren(u),T.create("#"+f,{data:p,columns:_,search:o,sortable:t,exportable:n,pageSize:a,emptyMessage:e}),d&&L(u),x._dmTable={columns:_,rows:p,slug:h,exportCfg:y,render(w){T.create("#"+f,{data:w,columns:_,search:o,sortable:t,exportable:n,pageSize:a,emptyMessage:e}),d&&L(u)}}})})(),(function(){const g=document.querySelectorAll("[data-form-inline]");if(!g.length||typeof F>"u")return;function x(t){const n=t&&t.logic;return!!(n&&(n.visibility||n.requirement))}function C(t,n){const a={};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)),h=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":d?"chooser":e.type,y={...e.formConfig||{}};y.span==="full"&&n&&(y.span=n);const f={type:h,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&&(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)),a[e.name]=f}),a}function _(t){const n={};return(t||[]).forEach(a=>{if(!(!a.name||a.type==="page-break"||a.type==="spacer")&&(a.type==="select"||a.type==="multiselect")&&a.required){const e=(a.options||[])[0];e&&(n[a.name]=typeof e=="object"?e.value:e)}}),n}function p(t,n){(n||[]).forEach(a=>{if(a.type!=="date"||!a.name)return;const e=t.querySelector(`[name="${a.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function o(t,n,a){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=a?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}g.forEach(t=>{let n;try{n=JSON.parse(atob(t.dataset.formInline))}catch{return}const a=n.fields||[],e=n.settings||{},d=Number(e.columns)===3?3:2,h=e.layout||"grid",y=a.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/${n.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}},u=Date.now();function w(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=u,r.appendChild(m),s.appendChild(r)}function i(c){if(!window.FormLogicEngine||!a.some(r=>r.logic))return null;const s=new window.FormLogicEngine.FormLogicRuntime(n,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=Y(a,n.title),s=c.map(k=>({title:k.title,description:k.description,fields:C(k.fields,d)})),r=c.map(k=>k.fields);let l=0,m=null,v=null;const A=k=>{const I=window.FormLogicEngine;if(!I||!I.validateFields)return;const q=I.validateFields(t,r[l]||[]);q.valid||(k.preventDefault(),k.stopImmediatePropagation(),G(t,q.missing),X(q))},D=()=>{const k=r[l]||a;p(t,k),j(t,k),e.honeypot!==!1&&w(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:h,columns:d,finishText:e.submitText||"Finish",onSubmit:f,onStepChange:k=>{l=k,setTimeout(D,0)}}),Promise.resolve(v).then(D)}else if(F.render){const c=F.render(t,C(a,d),_(a),{submitText:e.submitText||"Submit",layout:h,columns:d,onSubmit:f});Promise.resolve(c).then(function(){if(h==="grid"&&e.submitSpan==="full"){const s=t.querySelector(".form-buttons");s&&s.classList.add("col-span-full")}p(t,a),j(t,a),e.honeypot!==!1&&w(t),i()})}}),$(document).on("click",".dm-banner__dismiss",function(){$(this).closest(".dm-banner").remove()})})();
package/scripts/setup.js CHANGED
@@ -241,10 +241,18 @@ section('4. Site Identity');
241
241
  rl.close();
242
242
 
243
243
  const site = JSON.parse(await readFile(SITE_CONFIG, 'utf8'));
244
+ const previousTitle = site.title;
244
245
  if (siteTitle) site.title = siteTitle;
245
246
  if (tagline) site.tagline = tagline;
246
247
  site.seo = site.seo ?? {};
247
248
  if (siteTitle) site.seo.defaultTitle = siteTitle;
249
+ // The scaffolder seeds smtp.fromName from the placeholder site name, so
250
+ // carry a renamed site through to it - otherwise every email a fresh
251
+ // install sends is signed "My Site". Only when it still holds the name the
252
+ // site had a moment ago, so a From name someone chose deliberately stands.
253
+ if (siteTitle && site.smtp && (!site.smtp.fromName || site.smtp.fromName === previousTitle)) {
254
+ site.smtp.fromName = siteTitle;
255
+ }
248
256
  await writeFile(SITE_CONFIG, JSON.stringify(site, null, 4) + '\n', 'utf8');
249
257
 
250
258
  const nav = JSON.parse(await readFile(NAV_CONFIG, 'utf8'));
@@ -45,6 +45,21 @@ export function escapeHtml(str) {
45
45
  .replace(/'/g, '&#39;');
46
46
  }
47
47
 
48
+ /**
49
+ * Does this site have somewhere real to send mail?
50
+ *
51
+ * The host is the only field that decides it: everything else has a usable
52
+ * default, and an empty host is what sends the whole site to Ethereal. Shared
53
+ * with the health check and the admin so all three agree on what "configured"
54
+ * means.
55
+ *
56
+ * @param {{host?: string}} smtp
57
+ * @returns {boolean}
58
+ */
59
+ export function isSmtpConfigured(smtp) {
60
+ return typeof smtp?.host === 'string' && smtp.host.trim() !== '';
61
+ }
62
+
48
63
  /**
49
64
  * Create a nodemailer transport.
50
65
  * Falls back to an Ethereal test account when no SMTP host is configured.
@@ -54,7 +69,7 @@ export function escapeHtml(str) {
54
69
  * @throws {Error} If Ethereal test account creation fails.
55
70
  */
56
71
  export async function createTransport(smtp) {
57
- if (smtp?.host) {
72
+ if (isSmtpConfigured(smtp)) {
58
73
  return nodemailer.createTransport({
59
74
  host: smtp.host,
60
75
  port: smtp.port || 587,
@@ -64,9 +79,20 @@ export async function createTransport(smtp) {
64
79
  });
65
80
  }
66
81
 
67
- // No SMTP configured - use Ethereal for dev/demo
82
+ // No SMTP configured - use Ethereal for dev/demo.
83
+ //
84
+ // This is the quiet failure mode of the whole email feature: Ethereal
85
+ // accepts the message, nodemailer reports success, the health check records
86
+ // a good send, and nothing is delivered to anyone. Password resets and form
87
+ // notifications all "work". Say so at warn level rather than as a friendly
88
+ // info line, and say it in terms of the consequence - the admin carries the
89
+ // same warning on the Email tab, and checkSmtpConfig() surfaces it in
90
+ // Health. A site scaffolded by the CLI or the fleet manager ships with a
91
+ // host precisely so it never lands here.
68
92
  const testAccount = await nodemailer.createTestAccount();
69
- console.log('[email] No SMTP configured. Using Ethereal test account:', testAccount.user);
93
+ console.warn('[email] NO SMTP HOST CONFIGURED - mail is going to an Ethereal test inbox '
94
+ + 'and will NOT reach its recipients. Set one in Settings > Email. '
95
+ + 'Ethereal account: ' + testAccount.user);
70
96
  return nodemailer.createTransport({
71
97
  host: 'smtp.ethereal.email',
72
98
  port: 587,
@@ -6,8 +6,8 @@
6
6
  import fs from 'fs/promises';
7
7
  import path from 'path';
8
8
  import {fileURLToPath} from 'url';
9
- import {config} from '../config.js';
10
- import {getLastSendResult} from './email.js';
9
+ import {config, getConfig} from '../config.js';
10
+ import {getLastSendResult, isSmtpConfigured} from './email.js';
11
11
  import {listPages} from './content.js';
12
12
  import {getPluginLoadFailures} from './plugins.js';
13
13
  import {FORMS_DIR} from './forms.js';
@@ -203,6 +203,26 @@ async function checkSmtp() {
203
203
  };
204
204
  }
205
205
 
206
+ // Distinct from checkSmtp above, which reports the LAST SEND. That check goes
207
+ // green on an Ethereal send, because as far as nodemailer is concerned the
208
+ // message was accepted - so on a site with no relay the two checks disagree on
209
+ // purpose: "last email send: ok" next to "SMTP host: not configured" is the
210
+ // whole story, and neither half tells it alone.
211
+ async function checkSmtpConfig() {
212
+ const smtp = getConfig('site')?.smtp;
213
+ if (isSmtpConfigured(smtp)) {
214
+ return { id: 'smtp-config', level: 'ok', label: 'SMTP host', value: smtp.host };
215
+ }
216
+ return {
217
+ id: 'smtp-config',
218
+ level: 'warn',
219
+ label: 'SMTP host',
220
+ value: 'not configured',
221
+ detail: ['Mail is going to an Ethereal test inbox and will not reach its recipients.',
222
+ 'Set a host in Settings > Email.']
223
+ };
224
+ }
225
+
206
226
  async function checkPluginLoad() {
207
227
  const failures = getPluginLoadFailures();
208
228
  return {
@@ -265,6 +285,7 @@ export async function getHealth() {
265
285
  safeCheck('missing-meta', checkMissingMeta),
266
286
  safeCheck('last-form', checkLastForm),
267
287
  safeCheck('smtp', checkSmtp),
288
+ safeCheck('smtp-config', checkSmtpConfig),
268
289
  safeCheck('plugin-load', checkPluginLoad)
269
290
  ]);
270
291
  const value = { status: worstLevel(checks), checks };
@@ -14,7 +14,7 @@ import {colourToCss, floatToCss, floatBehaviour, spacerSize} from './menus.js';
14
14
  // Highlight helpers live once, in the browser module, and are imported here -
15
15
  // the same shared-module arrangement collection-export.mjs has with
16
16
  // collections.js. Nothing in them touches the DOM.
17
- import {highlightAttrs, isCurrentItem} from '../../public/js/menu-decor.mjs';
17
+ import {highlightAttrs, itemHighlightAttrs, isCurrentItem, menuAlign} from '../../public/js/menu-decor.mjs';
18
18
 
19
19
  /** Attribute-safe escaping - mirrors escapeAttr in markdown.js. */
20
20
  function esc(str) {
@@ -57,9 +57,26 @@ export function renderMenuItemsAsUl(items, currentPath = '') {
57
57
  const current = isCurrentItem(it.url, currentPath);
58
58
  const classAttr = current ? ' class="is-active" aria-current="page"' : '';
59
59
 
60
+ // Per-item highlight override. `data-menu-item-*` rather than the
61
+ // menu's `data-menu-*` so the CSS can let the item win without the two
62
+ // rules colliding on specificity - see the note in menu-highlight.css.
63
+ // The sanitiser allows `data-*` and `style` on every tag, so both
64
+ // survive the `[menu]` shortcode's trip through sanitize-html.
65
+ const ihl = itemHighlightAttrs(it);
66
+ const ihlAttrs = [
67
+ ihl.hover && ` data-menu-item-hover="${esc(ihl.hover)}"`,
68
+ ihl.active && ` data-menu-item-active="${esc(ihl.active)}"`
69
+ ].filter(Boolean).join('');
70
+
60
71
  // Colour override (the server sanitiser allows inline style on <a>).
72
+ // The item's accent rides in the same style attribute: it inherits, so
73
+ // it shadows the container's `--dm-menu-highlight` for this item alone.
61
74
  const colourCss = it.colour ? colourToCss(it.colour) : '';
62
- const styleAttr = colourCss ? ` style="color:${colourCss}"` : '';
75
+ const styleCss = [
76
+ colourCss && `color:${colourCss}`,
77
+ ihl.accent && `--dm-menu-highlight:${ihl.accent}`
78
+ ].filter(Boolean).join(';');
79
+ const styleAttr = styleCss ? ` style="${esc(styleCss)}"` : '';
63
80
 
64
81
  // Badge - static text, or a count stamped on by resolveMenuDecorations.
65
82
  let badge = '';
@@ -69,7 +86,7 @@ export function renderMenuItemsAsUl(items, currentPath = '') {
69
86
  badge = `<span class="dm-menu-badge"${badgeStyle}>${esc(String(it.badge.text))}</span>`;
70
87
  }
71
88
 
72
- return `<li><a href="${href}"${classAttr}${styleAttr}>${text}${badge}</a>${child}</li>`;
89
+ return `<li><a href="${href}"${classAttr}${styleAttr}${ihlAttrs}>${text}${badge}</a>${child}</li>`;
73
90
  }).join('');
74
91
  return `<ul>${lis}</ul>`;
75
92
  }
@@ -99,6 +116,13 @@ export function buildMenuNav(menu, items, opts = {}) {
99
116
  : (['horizontal', 'vertical'].includes(menu.orientation) ? menu.orientation : null);
100
117
  const orientClass = orientation ? ` dm-menu--${orientation}` : '';
101
118
 
119
+ // Alignment only has anywhere to go in a horizontal layout - a vertical
120
+ // list is as wide as its box. Unlike the navbar, an unset value emits no
121
+ // class: a `[menu]` in the page body has always packed left and a silent
122
+ // change of that on upgrade is not wanted.
123
+ const align = orientation === 'horizontal' ? menuAlign(menu) : '';
124
+ const alignClass = align ? ` dm-menu--align-${align}` : '';
125
+
102
126
  const floatCss = opts.noFloat ? '' : floatToCss(menu);
103
127
  const floatClass = floatCss ? ' dm-menu-floating' : '';
104
128
  const floatAnchorAttr = floatCss
@@ -134,7 +158,7 @@ export function buildMenuNav(menu, items, opts = {}) {
134
158
  ? ` style="${esc([floatCss, hlStyle].filter(Boolean).join(';'))}"`
135
159
  : '';
136
160
 
137
- return `<nav class="dm-menu dm-menu--${esc(menu.slug)}${orientClass}${variantClass}${floatClass}${collapsedClass}${flexClass}${klass}"`
161
+ return `<nav class="dm-menu dm-menu--${esc(menu.slug)}${orientClass}${alignClass}${variantClass}${floatClass}${collapsedClass}${flexClass}${klass}"`
138
162
  + ` data-menu="${esc(menu.slug)}"${styleAttr}${floatAnchorAttr}${behAttrs}${hlAttrs}>`
139
163
  + buildFloatChrome(menu, beh)
140
164
  + renderMenuItemsAsUl(items, opts.currentPath || '')
@@ -17,7 +17,7 @@ import * as cache from './cache/index.js';
17
17
  // The highlight vocabularies are defined once, in the browser module, and
18
18
  // imported here so validation and the renderer can never drift apart.
19
19
  import {
20
- HIGHLIGHT_HOVER, HIGHLIGHT_ACTIVE,
20
+ HIGHLIGHT_HOVER, HIGHLIGHT_ACTIVE, MENU_ALIGN,
21
21
  LEGACY_STYLE_HOVER, LEGACY_STYLE_ACTIVE, legacyStyleHighlight,
22
22
  PRESET_COLOUR_VARS, PRESET_COLOUR_FALLBACKS, colourToCss
23
23
  } from '../../public/js/menu-decor.mjs';
@@ -437,6 +437,7 @@ export async function createMenu(input) {
437
437
  ...(input.variant != null && {variant: input.variant}),
438
438
  ...(input.position != null && {position: input.position}),
439
439
  ...(input.orientation != null && {orientation: input.orientation}),
440
+ ...(input.align != null && {align: input.align}),
440
441
  ...(input.side != null && {side: input.side}),
441
442
  ...(input.appearOnHover != null && {appearOnHover: !!input.appearOnHover}),
442
443
  ...(input.float != null && {float: input.float}),
@@ -481,6 +482,7 @@ export async function updateMenu(slug, input) {
481
482
  ...(input.variant != null && {variant: input.variant}),
482
483
  ...(input.position != null && {position: input.position}),
483
484
  ...(input.orientation != null && {orientation: input.orientation}),
485
+ ...(input.align != null && {align: input.align}),
484
486
  ...(input.side != null && {side: input.side}),
485
487
  ...(input.appearOnHover != null && {appearOnHover: !!input.appearOnHover}),
486
488
  ...(input.float != null && {float: input.float}),
@@ -677,6 +679,9 @@ export function validateMenu(menu) {
677
679
  }
678
680
  }
679
681
  }
682
+ if (menu.align != null && menu.align !== '' && !MENU_ALIGN.includes(menu.align)) {
683
+ errors.push(`align must be one of: ${MENU_ALIGN.join(', ')}`);
684
+ }
680
685
  if (menu.highlight != null) {
681
686
  if (typeof menu.highlight !== 'object' || Array.isArray(menu.highlight)) {
682
687
  errors.push('highlight must be an object');
@@ -760,6 +765,25 @@ export function validateMenu(menu) {
760
765
  if (item.colour != null && !isValidColour(item.colour)) {
761
766
  errors.push(`Item ${path}: colour must be a preset key or #rrggbb`);
762
767
  }
768
+ // Per-item highlight override. Same shape and same value sets as
769
+ // the menu-level `highlight` block validated above - an item that
770
+ // omits a half falls through to the menu's choice for that half.
771
+ if (item.highlight != null) {
772
+ if (typeof item.highlight !== 'object' || Array.isArray(item.highlight)) {
773
+ errors.push(`Item ${path}: highlight must be an object`);
774
+ } else {
775
+ const {hover, active, accent} = item.highlight;
776
+ if (hover != null && hover !== '' && !HIGHLIGHT_HOVER.includes(hover)) {
777
+ errors.push(`Item ${path}: highlight.hover must be one of: ${HIGHLIGHT_HOVER.join(', ')}`);
778
+ }
779
+ if (active != null && active !== '' && !HIGHLIGHT_ACTIVE.includes(active)) {
780
+ errors.push(`Item ${path}: highlight.active must be one of: ${HIGHLIGHT_ACTIVE.join(', ')}`);
781
+ }
782
+ if (accent != null && accent !== '' && !isValidColour(accent)) {
783
+ errors.push(`Item ${path}: highlight.accent must be a preset key or #rrggbb`);
784
+ }
785
+ }
786
+ }
763
787
  });
764
788
  }
765
789
  return errors;
@@ -140,6 +140,9 @@ export async function renderPage(page, opts = {}) {
140
140
  // the horizontal default, so __CMS_NAV__ stays unchanged for
141
141
  // existing sites.
142
142
  ...(navMenu.orientation === 'vertical' && {orientation: 'vertical', side: navMenu.side === 'right' ? 'right' : 'left'}),
143
+ // Where the items sit along the bar. Like `highlight`, the navbar
144
+ // is drawn in the browser, so the value has to travel to site.js.
145
+ ...(navMenu.align && {align: navMenu.align}),
143
146
  ...(navMenu.float && {float: navMenu.float}),
144
147
  ...(navMenu.appearOnHover && {appearOnHover: true}),
145
148
  // Item highlighting: the navbar is built in the browser, so the
@@ -717,6 +720,7 @@ export async function renderBlogPage(templatePath, data = {}, seoMeta = {}) {
717
720
  // the horizontal default, so __CMS_NAV__ stays unchanged for
718
721
  // existing sites.
719
722
  ...(navMenu.orientation === 'vertical' && {orientation: 'vertical', side: navMenu.side === 'right' ? 'right' : 'left'}),
723
+ ...(navMenu.align && {align: navMenu.align}),
720
724
  ...(navMenu.appearOnHover && {appearOnHover: true}),
721
725
  // Item highlighting: the navbar is built in the browser, so the
722
726
  // config has to travel to site.js rather than being rendered here.