domma-cms 1.6.32 → 1.6.33

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.
@@ -17,6 +17,7 @@
17
17
  <div class="tab-list">
18
18
  <button type="button" class="tab-item active">General <i class="stx-dot" data-bind-hidden="!tabs.value.general" title="Changed"></i></button>
19
19
  <button type="button" class="tab-item">Branding <i class="stx-dot" data-bind-hidden="!tabs.value.branding" title="Changed"></i></button>
20
+ <button type="button" class="tab-item">Favicon <i class="stx-dot" data-bind-hidden="!tabs.value.favicon" title="Changed"></i></button>
20
21
  <button type="button" class="tab-item">SEO <i class="stx-dot" data-bind-hidden="!tabs.value.seo" title="Changed"></i></button>
21
22
  <button type="button" class="tab-item">Footer <i class="stx-dot" data-bind-hidden="!tabs.value.footer" title="Changed"></i></button>
22
23
  <button type="button" class="tab-item">Email <i class="stx-dot" data-bind-hidden="!tabs.value.email" title="Changed"></i><i class="stx-dot is-bad" data-bind-hidden="!noSmtp.value || tabs.value.email" title="No SMTP host"></i></button>
@@ -102,13 +103,8 @@
102
103
  <div><label class="form-label" for="stx-brandIcon">Icon</label>
103
104
  <div data-brand-icon="brandIcon"></div>
104
105
  </div>
105
- <div><label class="form-label" for="stx-favicon">Browser icon (favicon)</label>
106
- <input id="stx-favicon" type="text" class="form-input" placeholder="/media/favicon.png" data-model="favicon.value">
107
- <button type="button" class="btn btn-sm btn-ghost" data-on-click="pickBrandImage('favicon')">Choose or upload image</button>
108
- <button type="button" class="btn btn-sm btn-ghost" data-on-click="clearBrandImage('favicon')">Remove</button>
109
- <img data-bind-src="favicon.value" data-bind-hidden="!favicon.value" alt="Browser icon (favicon) preview" style="display:block;max-width:180px;max-height:80px;object-fit:contain;margin-top:.75rem;">
110
- </div>
111
- </div><p class="text-muted">The logo takes priority over the icon. Leave the browser icon empty to use the public logo.</p></div>
106
+
107
+ </div><p class="text-muted">The logo takes priority over the icon. Set the browser icon in the Favicon tab.</p></div>
112
108
  <div class="stx-card"><div class="stx-card-title">Admin sidebar</div><div class="stx-grid">
113
109
  <div><label class="form-label" for="stx-adminBrandTitle">Admin name</label>
114
110
  <input id="stx-adminBrandTitle" type="text" class="form-input" placeholder="Uses the site title when empty" data-model="adminBrandTitle.value">
@@ -125,6 +121,28 @@
125
121
  </div><p class="text-muted">The logo takes priority over the icon. Leave both empty to use the default icon.</p></div>
126
122
  </div>
127
123
 
124
+ <!-- Favicon -->
125
+ <div class="tab-panel">
126
+ <div class="stx-card"><div class="stx-card-title">Generate favicon.ico</div>
127
+ <p>Choose or upload an image. Generate a single ICO file with 16, 32, 48 and 256 pixel icons. Images keep their proportions with transparent padding.</p>
128
+ <label class="form-label" for="stx-favicon-source">Source image</label>
129
+ <input id="stx-favicon-source" class="form-input" type="text" placeholder="/media/logo.png" data-model="faviconSource.value">
130
+ <button type="button" class="btn btn-secondary" data-on-click="chooseFaviconSource" data-bind-disabled="faviconGenerating.value">Choose or upload image</button>
131
+ <button type="button" class="btn btn-primary" data-on-click="generateFavicon" data-bind-disabled="faviconGenerating.value || !faviconSource.value">Generate favicon.ico</button>
132
+ <p class="text-danger" data-bind-text="faviconError.value" data-bind-hidden="!faviconError.value"></p>
133
+ <p data-bind-hidden="!faviconGenerated.value">Generated. Save Site Settings to apply it. <a data-bind-href="faviconGenerated.value" download="favicon.ico">Download favicon.ico</a></p>
134
+ </div>
135
+ <div class="stx-card"><div class="stx-card-title">Browser icon</div>
136
+ <div><label class="form-label" for="stx-favicon">Browser icon (favicon)</label>
137
+ <input id="stx-favicon" type="text" class="form-input" placeholder="/media/favicon.png" data-model="favicon.value">
138
+ <button type="button" class="btn btn-sm btn-ghost" data-on-click="pickBrandImage('favicon')">Choose or upload image</button>
139
+ <button type="button" class="btn btn-sm btn-ghost" data-on-click="clearBrandImage('favicon')">Remove</button>
140
+ <img data-bind-src="favicon.value" data-bind-hidden="!favicon.value" alt="Browser icon (favicon) preview" style="display:block;max-width:180px;max-height:80px;object-fit:contain;margin-top:.75rem;">
141
+ </div>
142
+ <p class="text-muted">Save to apply the browser icon. Leave it empty to use the public logo.</p>
143
+ </div>
144
+ </div>
145
+
128
146
  <!-- SEO -->
129
147
  <div class="tab-panel">
130
148
  <div class="stx-card">
@@ -1,13 +1,13 @@
1
- import{api as G,apiRequest as w}from"../api.js";import{placeTools as Ie}from"../lib/sidebar-grouping.js";import{addBannerAction as he}from"../lib/plugin-chrome.js";import{helpPopovers as $e}from"../lib/help-popover.js";import{openMenuAt as Q,sidebarCount as ve}from"../lib/tool-kit.js";import{changedTabs as Ae,describeCacheKey as X,draftOf as Pe,expiresIn as Be,formFromSettings as De,homeValue as He,payloadFromForm as je,siteUrlError as be,settingsProblems as Ke,snapshot as ge,TAB_FIELDS as _e}from"../lib/site-settings-arrange.js";import{pickMedia as qe}from"../lib/media-picker.js";import{makeIconInput as Ue}from"../lib/shortcode-modal.js";import{fmtDateTime as Fe}from"../lib/dates.js";const Z=/^\/settings(\?.*)?$/,ye="#/settings",T="settings.draft",ze=`
1
+ import{api as J,apiRequest as w}from"../api.js";import{placeTools as $e}from"../lib/sidebar-grouping.js";import{addBannerAction as ve}from"../lib/plugin-chrome.js";import{helpPopovers as Ae}from"../lib/help-popover.js";import{openMenuAt as Q,sidebarCount as be}from"../lib/tool-kit.js";import{changedTabs as Pe,describeCacheKey as X,draftOf as Be,expiresIn as De,formFromSettings as He,homeValue as je,payloadFromForm as Ke,siteUrlError as ge,settingsProblems as _e,snapshot as ye,TAB_FIELDS as qe}from"../lib/site-settings-arrange.js";import{pickMedia as Ue}from"../lib/media-picker.js";import{faviconGenerator as Fe}from"../lib/favicon-generator.js";import{makeIconInput as ze}from"../lib/shortcode-modal.js";import{fmtDateTime as Ne}from"../lib/dates.js";const Z=/^\/settings(\?.*)?$/,ke="#/settings",T="settings.draft",Ve=`
2
2
  <div class="stx-problem">
3
3
  <span class="stx-chip" data-bind-class="tone === 'danger' ? 'is-bad' : 'is-warn'">{{label}}</span>
4
4
  <span>{{text}}</span>
5
5
  <button type="button" class="btn btn-sm btn-ghost" data-go="{{tab}}">Go there</button>
6
- </div>`,Ne=`
6
+ </div>`,We=`
7
7
  <div class="stx-row" data-id="{{id}}" tabindex="0">
8
8
  <span class="stx-main"><strong>{{path}}</strong><small>{{meta}}</small></span>
9
9
  <span class="stx-chip">{{expires}}</span>
10
10
  <button type="button" class="stx-kebab" aria-label="Actions for {{path}}" title="Actions">\u22EE</button>
11
- </div>`,Ve=["general","branding","seo","footer","email","backToTop","cookies","breadcrumbs","css","cache"];async function We(l,p){const[r,L]=await Promise.all([H.get("/api/sidebar/menu").catch(()=>({items:[]})),H.get("/api/sidebar/registered-items").catch(()=>[])]),a=new Map([["/","Dashboard (built in)"]]),c=(C,m)=>{for(const i of C||[]){if(!i||i.hidden||i.restricted)continue;const f=typeof i.url=="string"?i.url:"";f.startsWith("#/")&&f.length>2&&!a.has(f.slice(1))&&a.set(f.slice(1),`${m?`${m} \u203A `:""}${i.text||f}`),c(i.items,i.url?m:i.text||m)}};c(Ie(r?.items||[],L||[]),""),a.has(p)||a.set(p,`${p} (not in the sidebar)`),l.replaceChildren();for(const[C,m]of a){const i=document.createElement("option");i.value=C,i.textContent=m,l.appendChild(i)}}let g=null,ke=!1;function Ye(){ke||!window.R?.use||(ke=!0,R.use((l,p,r)=>!(Z.test(p?.path||"")&&!Z.test(l?.path||""))||!g?.dirty()?r():E.confirm("You have unsaved settings. Leave anyway? They are kept as a draft for your next visit (not the SMTP password).").then(a=>{a?r():history.replaceState(null,"","#/settings")})),M.subscribe("router:afterChange",({to:l}={})=>{if(g&&!Z.test(l?.path||"")){const p=g;g=null,p.dispose()}}))}export const settingsView={templateUrl:"/admin/js/templates/settings.html",async onMount(l){if(Ye(),g){const e=g;g=null,e.dispose()}const p=[],r=e=>(p.push(e),e);let L=!1;const a=l.get(0).querySelector(".stx")||l.get(0),c=()=>!L&&a.isConnected,C=E.loader(l.get(0),{type:"dots"});let m,i;try{[m,i]=await Promise.all([G.settings.get(),w("/settings/custom-css").then(e=>e?.css||"").catch(()=>null)])}catch(e){C.destroy(),E.toast(`Could not load the settings: ${e.message||e}`,{type:"error"});return}if(C.destroy(),!a.isConnected)return;const f=i!==null,ee=M.observable(m||{}),te=De(m||{},i||""),n={};for(const[e,t]of Object.entries(te))n[e]=M.observable(t);const K=()=>Object.fromEntries(Object.keys(n).map(e=>[e,n[e].value])),se=()=>Object.fromEntries(Object.keys(n).map(e=>[e,n[e].peek()])),y=M.observable(te),_=M.computed(()=>ge(y.value)),O=M.computed(()=>ge(K())!==_.value),$=M.observable(!1),A=M.observable(""),q=M.observable(!1),ne=M.computed(()=>{const e=new Set(Ae(K(),y.value));return Object.fromEntries(Object.keys(_e).map(t=>[t,e.has(t)]))}),U=M.observable([]);r(M.effect(()=>{U.value=Ke(K()).map(e=>({...e,label:e.tone==="danger"?"Mail":"Check"}))}));const ae=M.observable(""),P=M.observable(!1),B=M.observable(""),D=M.observable(""),v=M.observable(null),F=M.observable([]),z=M.observable(""),N=M.observable(!0),V=M.observable(""),oe=M.observable([]);r(M.effect(()=>{const e=z.value.trim().toLowerCase(),t=Date.now();oe.value=[...F.value].reverse().filter(s=>!e||String(s.key).toLowerCase().includes(e)||(s.tags||[]).some(o=>String(o).toLowerCase().includes(e))).map(s=>{const o=X(s.key);return{id:s.key,path:o.path,meta:[o.page?`as ${o.role}`:"not a page",(s.tags||[]).join(", ")].filter(Boolean).join(" \xB7 "),expires:Be(s.expiresAt,t)}})}));const we={...n,saving:$,dirty:O,tabs:ne,problems:U,testTo:ae,testing:P,testResult:B,testTone:D,cacheQuery:z,cacheLoading:N,cacheError:V,cacheRows:oe,errorText:A,problemsText:M.computed(()=>U.value.length?"yes":""),siteUrlError:M.computed(()=>be(n.siteUrl.value)),noSmtp:M.computed(()=>!String(n.smtpHost.value||"").trim()),dirtyEmail:M.computed(()=>ne.value.email),seoLength:M.computed(()=>{const e=String(n.seoDescription.value||"").trim().length;return e?`${e} characters${e>160?" - search engines usually cut it off after about 150":""}`:""}),cssSize:M.computed(()=>{if(!f)return"Could not load content/custom.css - reload to try again. It will not be saved until it loads.";const e=new Blob([String(n.css.value||"")]).size/1024;return`${e.toFixed(1)} KB of 100 KB${e>100?" - too big to save":""}`}),cacheOn:M.computed(()=>v.value?.enabled===!0),cacheState:M.computed(()=>{const e=v.value;return e?e.enabled?`On (${e.driver})`:"Off":"Status unknown"}),cacheSize:M.computed(()=>{const e=v.value;return e?.enabled&&e.size!==null&&e.size!==void 0?`${e.size}${e.maxItems?` of ${e.maxItems}`:""} entries`:""}),cacheHits:M.computed(()=>{const e=v.value?.hitRate;return e==null?"":`${Math.round(e*100)}% served from the cache`}),cacheToggleText:M.computed(()=>v.value?.enabled?"Turn off":"Turn on"),cacheEmpty:M.computed(()=>z.value.trim()?"Nothing matches.":v.value?.enabled?"The cache is empty - pages are cached as visitors open them.":"The cache is off.")};async function W(){if($.peek())return;const e=se(),t=y.peek(),s=String(e.css)!==String(t.css);if(s&&!f){E.toast("The custom CSS did not load, so it cannot be saved - reload the screen first.",{type:"error"});return}const o=be(e.siteUrl);if(o){E.toast(`Site URL: ${o}`,{type:"error"});return}$.value=!0,A.value="";const h=String(e.title).trim()!==String(t.title).trim();try{await G.settings.save(je(e,ee.peek(),{adminHomeReady:q.peek()})),s&&await w("/settings/custom-css",{method:"PUT",body:JSON.stringify({css:String(e.css)})});const d=await G.settings.get().catch(()=>null);if(!c())return;d&&(ee.value=d);const J={...e,smtpPass:""};n.smtpPass.value="",y.value=J;try{S.remove?S.remove(T):S.set(T,null)}catch{}document.dispatchEvent(new CustomEvent("dm:admin-home-changed")),(h||["adminBrandTitle","adminBrandLogo","adminBrandIcon"].some(fe=>e[fe]!==t[fe]))&&document.dispatchEvent(new CustomEvent("dm:sidebar-rebuild")),ve(ye),E.toast("Settings saved.",{type:"success"})}catch(d){A.value=d.message||"Could not save the settings.",E.toast("Settings not saved - see the message at the top.",{type:"error"})}finally{$.value=!1}}async function Ee(){await E.confirm("Throw away your unsaved settings?")&&(re(y.peek()),A.value="")}function re(e){for(const[t,s]of Object.entries(e))n[t]&&(n[t].value=s);u&&u.getValue()!==String(n.css.peek())?u.setValue(String(n.css.peek())):!u&&b&&(b.value=String(n.css.peek()))}async function Se(){if(!P.peek()){P.value=!0,D.value="",B.value="Sending\u2026";try{const e=await w("/settings/test-email",{method:"POST",body:JSON.stringify({to:ae.peek().trim()||void 0})});if(!c())return;B.value=e?.message||"Test email sent.",D.value="is-ok"}catch(e){if(!c())return;B.value=e.message||"Could not send the test email.",D.value="is-bad"}finally{P.value=!1}}}async function j(){N.value=!0;try{const[e,t]=await Promise.all([w("/cache/status").catch(()=>null),w("/cache/keys")]);if(!c())return;v.value=e,F.value=Array.isArray(t?.entries)?t.entries:[],V.value=""}catch(e){if(!c())return;V.value=`Could not read the cache: ${e.message||e}`}finally{N.value=!1}}async function Ce(){if(await E.confirm("Clear the whole response cache? The next visitor to each page renders it afresh.")){try{await w("/cache/clear",{method:"POST"}),E.toast("Cache cleared.",{type:"success"})}catch(e){E.toast(e.message||"Could not clear the cache.",{type:"error"})}c()&&await j()}}async function Me(){const e=v.peek()?.enabled===!0;if(!(e&&!await E.confirm("Turn the response cache off? Every visit will render its page afresh until it is turned back on. The choice is saved (config/cache.json)."))){try{v.value=await w("/cache/toggle",{method:"POST",body:JSON.stringify({enabled:!e})}),E.toast(e?"Cache turned off.":"Cache turned on.",{type:"success"}),ve(ye)}catch(t){E.toast(t.message||"Could not change the cache.",{type:"error"})}c()&&await j()}}const ie=e=>F.peek().find(t=>t.key===e);function xe(e){const t=ie(e);if(!t)return[];const s=X(t.key);return[{header:s.path},{label:"Open the page",icon:"external-link",disabled:!s.page,action:()=>window.open(s.path,"_blank","noopener")},{label:"Copy key",icon:"copy",action:async()=>{try{await navigator.clipboard.writeText(t.key),E.toast("Key copied.",{type:"success"})}catch{E.toast("Could not copy - your browser blocked the clipboard.",{type:"warning"})}}},...s.page?[{label:"Copy address",icon:"link",action:async()=>{try{await navigator.clipboard.writeText(new URL(s.path,location.origin).href),E.toast("Address copied.",{type:"success"})}catch{E.toast("Could not copy - your browser blocked the clipboard.",{type:"warning"})}}}]:[]]}a.querySelector(".stx-problems").innerHTML=ze;const x=a.querySelector(".stx-list");x.innerHTML=Ne;const Te=E.tabs(a.querySelector(".stx-tabs"),{tabSelector:".tab-item",panelSelector:".tab-panel"}),Le=M.applyBindings(we,a,{methods:{save:W,discard:Ee,sendTest:Se,refreshCache:j,clearCache:Ce,toggleCache:Me,pickBrandImage:async e=>{const t=await qe({title:"Choose a branding image",current:n[e].peek()});t&&c()&&(n[e].value=t)},clearBrandImage:e=>{n[e].value=""},corner:e=>{n.bcPosition.value=e}}});r(()=>Le.dispose());for(const e of a.querySelectorAll("[data-brand-icon]")){const t=e.dataset.brandIcon,s=Ue("Choose an icon",n[t].peek());s.input.id=`stx-${t}`,s.input.classList.add("form-input");const o=()=>{n[t].value=s.input.value};s.input.addEventListener("input",o),r(()=>s.input.removeEventListener("input",o)),r(M.effect(()=>{s.input.value=n[t].value})),e.appendChild(s.el)}r($e(a,{position:"bottom"}));const Oe=e=>{const t=e.target.closest("[data-go]");if(!t)return;const s=Ve.indexOf(t.dataset.go);s>=0&&Te.activate(s)};a.querySelector(".stx-problems").addEventListener("click",Oe);const Y=a.querySelector("#stx-admin-home");We(Y,He(n.adminHome.peek())).then(()=>{c()&&(Y.value=n.adminHome.peek(),q.value=!0)}).catch(()=>{});const b=a.querySelector(".stx-css");b.value=String(n.css.peek());let u=null;if(!f)b.disabled=!0;else if(E.editor)try{u=E.editor(b,{mode:"code",language:"css",lineNumbers:!0,showToolbar:!1,minHeight:420,placeholder:"/* Add your custom CSS here */",characterCount:!0,onChange:e=>{n.css.value=e}})}catch{u=null}if(u)u._editorEl&&u._editorEl.addEventListener("keydown",e=>{if(!(e.ctrlKey||e.metaKey)||e.key!=="c"&&e.key!=="x")return;const t=u._editorEl;if(t.selectionStart!==t.selectionEnd||typeof t.value!="string")return;e.preventDefault();const s=t.value,o=t.selectionStart,h=s.lastIndexOf(`
11
+ </div>`,Ye=["general","branding","favicon","seo","footer","email","backToTop","cookies","breadcrumbs","css","cache"];async function Ge(l,p){const[r,L]=await Promise.all([H.get("/api/sidebar/menu").catch(()=>({items:[]})),H.get("/api/sidebar/registered-items").catch(()=>[])]),a=new Map([["/","Dashboard (built in)"]]),c=(C,m)=>{for(const i of C||[]){if(!i||i.hidden||i.restricted)continue;const f=typeof i.url=="string"?i.url:"";f.startsWith("#/")&&f.length>2&&!a.has(f.slice(1))&&a.set(f.slice(1),`${m?`${m} \u203A `:""}${i.text||f}`),c(i.items,i.url?m:i.text||m)}};c($e(r?.items||[],L||[]),""),a.has(p)||a.set(p,`${p} (not in the sidebar)`),l.replaceChildren();for(const[C,m]of a){const i=document.createElement("option");i.value=C,i.textContent=m,l.appendChild(i)}}let g=null,we=!1;function Je(){we||!window.R?.use||(we=!0,R.use((l,p,r)=>!(Z.test(p?.path||"")&&!Z.test(l?.path||""))||!g?.dirty()?r():E.confirm("You have unsaved settings. Leave anyway? They are kept as a draft for your next visit (not the SMTP password).").then(a=>{a?r():history.replaceState(null,"","#/settings")})),M.subscribe("router:afterChange",({to:l}={})=>{if(g&&!Z.test(l?.path||"")){const p=g;g=null,p.dispose()}}))}export const settingsView={templateUrl:"/admin/js/templates/settings.html",async onMount(l){if(Je(),g){const e=g;g=null,e.dispose()}const p=[],r=e=>(p.push(e),e);let L=!1;const a=l.get(0).querySelector(".stx")||l.get(0),c=()=>!L&&a.isConnected,C=E.loader(l.get(0),{type:"dots"});let m,i;try{[m,i]=await Promise.all([J.settings.get(),w("/settings/custom-css").then(e=>e?.css||"").catch(()=>null)])}catch(e){C.destroy(),E.toast(`Could not load the settings: ${e.message||e}`,{type:"error"});return}if(C.destroy(),!a.isConnected)return;const f=i!==null,ee=M.observable(m||{}),te=He(m||{},i||""),n={};for(const[e,t]of Object.entries(te))n[e]=M.observable(t);const K=()=>Object.fromEntries(Object.keys(n).map(e=>[e,n[e].value])),se=()=>Object.fromEntries(Object.keys(n).map(e=>[e,n[e].peek()])),y=M.observable(te),_=M.computed(()=>ye(y.value)),O=M.computed(()=>ye(K())!==_.value),$=M.observable(!1),A=M.observable(""),q=M.observable(!1),ne=M.computed(()=>{const e=new Set(Pe(K(),y.value));return Object.fromEntries(Object.keys(qe).map(t=>[t,e.has(t)]))}),U=M.observable([]);r(M.effect(()=>{U.value=_e(K()).map(e=>({...e,label:e.tone==="danger"?"Mail":"Check"}))}));const ae=M.observable(""),P=M.observable(!1),B=M.observable(""),D=M.observable(""),v=M.observable(null),F=M.observable([]),z=M.observable(""),N=M.observable(!0),V=M.observable(""),oe=M.observable([]);r(M.effect(()=>{const e=z.value.trim().toLowerCase(),t=Date.now();oe.value=[...F.value].reverse().filter(s=>!e||String(s.key).toLowerCase().includes(e)||(s.tags||[]).some(o=>String(o).toLowerCase().includes(e))).map(s=>{const o=X(s.key);return{id:s.key,path:o.path,meta:[o.page?`as ${o.role}`:"not a page",(s.tags||[]).join(", ")].filter(Boolean).join(" \xB7 "),expires:De(s.expiresAt,t)}})}));const re=Fe(n.favicon,n.brandLogo,c),Ee={...re.data,...n,saving:$,dirty:O,tabs:ne,problems:U,testTo:ae,testing:P,testResult:B,testTone:D,cacheQuery:z,cacheLoading:N,cacheError:V,cacheRows:oe,errorText:A,problemsText:M.computed(()=>U.value.length?"yes":""),siteUrlError:M.computed(()=>ge(n.siteUrl.value)),noSmtp:M.computed(()=>!String(n.smtpHost.value||"").trim()),dirtyEmail:M.computed(()=>ne.value.email),seoLength:M.computed(()=>{const e=String(n.seoDescription.value||"").trim().length;return e?`${e} characters${e>160?" - search engines usually cut it off after about 150":""}`:""}),cssSize:M.computed(()=>{if(!f)return"Could not load content/custom.css - reload to try again. It will not be saved until it loads.";const e=new Blob([String(n.css.value||"")]).size/1024;return`${e.toFixed(1)} KB of 100 KB${e>100?" - too big to save":""}`}),cacheOn:M.computed(()=>v.value?.enabled===!0),cacheState:M.computed(()=>{const e=v.value;return e?e.enabled?`On (${e.driver})`:"Off":"Status unknown"}),cacheSize:M.computed(()=>{const e=v.value;return e?.enabled&&e.size!==null&&e.size!==void 0?`${e.size}${e.maxItems?` of ${e.maxItems}`:""} entries`:""}),cacheHits:M.computed(()=>{const e=v.value?.hitRate;return e==null?"":`${Math.round(e*100)}% served from the cache`}),cacheToggleText:M.computed(()=>v.value?.enabled?"Turn off":"Turn on"),cacheEmpty:M.computed(()=>z.value.trim()?"Nothing matches.":v.value?.enabled?"The cache is empty - pages are cached as visitors open them.":"The cache is off.")};async function W(){if($.peek())return;const e=se(),t=y.peek(),s=String(e.css)!==String(t.css);if(s&&!f){E.toast("The custom CSS did not load, so it cannot be saved - reload the screen first.",{type:"error"});return}const o=ge(e.siteUrl);if(o){E.toast(`Site URL: ${o}`,{type:"error"});return}$.value=!0,A.value="";const h=String(e.title).trim()!==String(t.title).trim();try{await J.settings.save(Ke(e,ee.peek(),{adminHomeReady:q.peek()})),s&&await w("/settings/custom-css",{method:"PUT",body:JSON.stringify({css:String(e.css)})});const d=await J.settings.get().catch(()=>null);if(!c())return;d&&(ee.value=d);const G={...e,smtpPass:""};n.smtpPass.value="",y.value=G;try{S.remove?S.remove(T):S.set(T,null)}catch{}document.dispatchEvent(new CustomEvent("dm:admin-home-changed")),(h||["adminBrandTitle","adminBrandLogo","adminBrandIcon"].some(he=>e[he]!==t[he]))&&document.dispatchEvent(new CustomEvent("dm:sidebar-rebuild")),be(ke),E.toast("Settings saved.",{type:"success"})}catch(d){A.value=d.message||"Could not save the settings.",E.toast("Settings not saved - see the message at the top.",{type:"error"})}finally{$.value=!1}}async function Se(){await E.confirm("Throw away your unsaved settings?")&&(ie(y.peek()),A.value="")}function ie(e){for(const[t,s]of Object.entries(e))n[t]&&(n[t].value=s);u&&u.getValue()!==String(n.css.peek())?u.setValue(String(n.css.peek())):!u&&b&&(b.value=String(n.css.peek()))}async function Ce(){if(!P.peek()){P.value=!0,D.value="",B.value="Sending\u2026";try{const e=await w("/settings/test-email",{method:"POST",body:JSON.stringify({to:ae.peek().trim()||void 0})});if(!c())return;B.value=e?.message||"Test email sent.",D.value="is-ok"}catch(e){if(!c())return;B.value=e.message||"Could not send the test email.",D.value="is-bad"}finally{P.value=!1}}}async function j(){N.value=!0;try{const[e,t]=await Promise.all([w("/cache/status").catch(()=>null),w("/cache/keys")]);if(!c())return;v.value=e,F.value=Array.isArray(t?.entries)?t.entries:[],V.value=""}catch(e){if(!c())return;V.value=`Could not read the cache: ${e.message||e}`}finally{N.value=!1}}async function Me(){if(await E.confirm("Clear the whole response cache? The next visitor to each page renders it afresh.")){try{await w("/cache/clear",{method:"POST"}),E.toast("Cache cleared.",{type:"success"})}catch(e){E.toast(e.message||"Could not clear the cache.",{type:"error"})}c()&&await j()}}async function xe(){const e=v.peek()?.enabled===!0;if(!(e&&!await E.confirm("Turn the response cache off? Every visit will render its page afresh until it is turned back on. The choice is saved (config/cache.json)."))){try{v.value=await w("/cache/toggle",{method:"POST",body:JSON.stringify({enabled:!e})}),E.toast(e?"Cache turned off.":"Cache turned on.",{type:"success"}),be(ke)}catch(t){E.toast(t.message||"Could not change the cache.",{type:"error"})}c()&&await j()}}const ce=e=>F.peek().find(t=>t.key===e);function Te(e){const t=ce(e);if(!t)return[];const s=X(t.key);return[{header:s.path},{label:"Open the page",icon:"external-link",disabled:!s.page,action:()=>window.open(s.path,"_blank","noopener")},{label:"Copy key",icon:"copy",action:async()=>{try{await navigator.clipboard.writeText(t.key),E.toast("Key copied.",{type:"success"})}catch{E.toast("Could not copy - your browser blocked the clipboard.",{type:"warning"})}}},...s.page?[{label:"Copy address",icon:"link",action:async()=>{try{await navigator.clipboard.writeText(new URL(s.path,location.origin).href),E.toast("Address copied.",{type:"success"})}catch{E.toast("Could not copy - your browser blocked the clipboard.",{type:"warning"})}}}]:[]]}a.querySelector(".stx-problems").innerHTML=Ve;const x=a.querySelector(".stx-list");x.innerHTML=We;const Le=E.tabs(a.querySelector(".stx-tabs"),{tabSelector:".tab-item",panelSelector:".tab-panel"}),Oe=M.applyBindings(Ee,a,{methods:{...re.methods,save:W,discard:Se,sendTest:Ce,refreshCache:j,clearCache:Me,toggleCache:xe,pickBrandImage:async e=>{const t=await Ue({title:"Choose a branding image",current:n[e].peek()});t&&c()&&(n[e].value=t)},clearBrandImage:e=>{n[e].value=""},corner:e=>{n.bcPosition.value=e}}});r(()=>Oe.dispose());for(const e of a.querySelectorAll("[data-brand-icon]")){const t=e.dataset.brandIcon,s=ze("Choose an icon",n[t].peek());s.input.id=`stx-${t}`,s.input.classList.add("form-input");const o=()=>{n[t].value=s.input.value};s.input.addEventListener("input",o),r(()=>s.input.removeEventListener("input",o)),r(M.effect(()=>{s.input.value=n[t].value})),e.appendChild(s.el)}r(Ae(a,{position:"bottom"}));const Re=e=>{const t=e.target.closest("[data-go]");if(!t)return;const s=Ye.indexOf(t.dataset.go);s>=0&&Le.activate(s)};a.querySelector(".stx-problems").addEventListener("click",Re);const Y=a.querySelector("#stx-admin-home");Ge(Y,je(n.adminHome.peek())).then(()=>{c()&&(Y.value=n.adminHome.peek(),q.value=!0)}).catch(()=>{});const b=a.querySelector(".stx-css");b.value=String(n.css.peek());let u=null;if(!f)b.disabled=!0;else if(E.editor)try{u=E.editor(b,{mode:"code",language:"css",lineNumbers:!0,showToolbar:!1,minHeight:420,placeholder:"/* Add your custom CSS here */",characterCount:!0,onChange:e=>{n.css.value=e}})}catch{u=null}if(u)u._editorEl&&u._editorEl.addEventListener("keydown",e=>{if(!(e.ctrlKey||e.metaKey)||e.key!=="c"&&e.key!=="x")return;const t=u._editorEl;if(t.selectionStart!==t.selectionEnd||typeof t.value!="string")return;e.preventDefault();const s=t.value,o=t.selectionStart,h=s.lastIndexOf(`
12
12
  `,o-1)+1,d=s.indexOf(`
13
- `,o),J=d===-1?s.slice(h):s.slice(h,d+1);navigator.clipboard?.writeText(J).catch(()=>{}),e.key==="x"?(t.value=s.slice(0,h)+(d===-1?"":s.slice(d+1)),t.selectionStart=t.selectionEnd=h,t.dispatchEvent(new Event("input",{bubbles:!0})),E.toast("Line cut",{type:"info",duration:1500})):E.toast("Line copied",{type:"info",duration:1500})});else{const e=()=>{n.css.value=b.value};b.addEventListener("input",e),r(()=>b.removeEventListener("input",e))}r(()=>{try{u?.destroy?.()}catch{}});const Re=E.contextMenu(x,{cursor:"glyph",match:".stx-row",inherit:!1,density:"compact",items:e=>xe(e.dataset.id)});r(()=>Re?.destroy?.());const ce=e=>{const t=ie(e.dataset.id),s=t&&X(t.key);s?.page?window.open(s.path,"_blank","noopener"):Q(e)},le=e=>{const t=e.target.closest(".stx-row");if(!t)return;const s=e.target.closest(".stx-kebab");if(s){Q(s);return}ce(t)},ue=e=>{const t=e.target.closest(".stx-row");t&&(e.key==="F10"&&e.shiftKey?(e.preventDefault(),Q(t)):e.key==="Enter"&&e.target===t&&ce(t))};x.addEventListener("click",le),x.addEventListener("keydown",ue),r(()=>{x.removeEventListener("click",le),x.removeEventListener("keydown",ue)}),he(l,{id:"settings-view-site",icon:"external-link",title:"View site",onClick:()=>window.open("/","_blank","noopener")}),he(l,{id:"settings-save",icon:"save",title:"Save (Ctrl+S)",onClick:()=>W()});const de=e=>{!(e.ctrlKey||e.metaKey)||e.key!=="s"||(e.preventDefault(),e.stopImmediatePropagation(),W())};document.addEventListener("keydown",de,!0),r(()=>document.removeEventListener("keydown",de,!0));const pe=()=>{if(O.peek())try{S.set(T,{form:Pe(se()),base:_.peek(),at:new Date().toISOString()})}catch{}},me=e=>{O.peek()&&(pe(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",me),r(()=>window.removeEventListener("beforeunload",me)),g={dirty:()=>O.peek(),dispose(){L=!0,pe(),p.splice(0).reverse().forEach(e=>{try{e()}catch{}})}},I.scan(a),j();let k=null;try{k=S.get(T)}catch{}if(k?.form){try{S.remove?S.remove(T):S.set(T,null)}catch{}const e=k.base&&k.base!==_.peek(),t=k.at?` (${Fe(k.at)})`:"",s=e?`You left unsaved settings${t}, but the saved settings have changed since. Restore your version anyway?`:`You left unsaved settings${t}. Restore them?`;if(await E.confirm(s)&&c()){const o={...y.peek()};for(const[h,d]of Object.entries(k.form))h in o&&h!=="smtpPass"&&(o[h]=d);f||(o.css=y.peek().css),re(o),q.peek()&&(Y.value=n.adminHome.peek())}}}};
13
+ `,o),G=d===-1?s.slice(h):s.slice(h,d+1);navigator.clipboard?.writeText(G).catch(()=>{}),e.key==="x"?(t.value=s.slice(0,h)+(d===-1?"":s.slice(d+1)),t.selectionStart=t.selectionEnd=h,t.dispatchEvent(new Event("input",{bubbles:!0})),E.toast("Line cut",{type:"info",duration:1500})):E.toast("Line copied",{type:"info",duration:1500})});else{const e=()=>{n.css.value=b.value};b.addEventListener("input",e),r(()=>b.removeEventListener("input",e))}r(()=>{try{u?.destroy?.()}catch{}});const Ie=E.contextMenu(x,{cursor:"glyph",match:".stx-row",inherit:!1,density:"compact",items:e=>Te(e.dataset.id)});r(()=>Ie?.destroy?.());const le=e=>{const t=ce(e.dataset.id),s=t&&X(t.key);s?.page?window.open(s.path,"_blank","noopener"):Q(e)},ue=e=>{const t=e.target.closest(".stx-row");if(!t)return;const s=e.target.closest(".stx-kebab");if(s){Q(s);return}le(t)},de=e=>{const t=e.target.closest(".stx-row");t&&(e.key==="F10"&&e.shiftKey?(e.preventDefault(),Q(t)):e.key==="Enter"&&e.target===t&&le(t))};x.addEventListener("click",ue),x.addEventListener("keydown",de),r(()=>{x.removeEventListener("click",ue),x.removeEventListener("keydown",de)}),ve(l,{id:"settings-view-site",icon:"external-link",title:"View site",onClick:()=>window.open("/","_blank","noopener")}),ve(l,{id:"settings-save",icon:"save",title:"Save (Ctrl+S)",onClick:()=>W()});const pe=e=>{!(e.ctrlKey||e.metaKey)||e.key!=="s"||(e.preventDefault(),e.stopImmediatePropagation(),W())};document.addEventListener("keydown",pe,!0),r(()=>document.removeEventListener("keydown",pe,!0));const me=()=>{if(O.peek())try{S.set(T,{form:Be(se()),base:_.peek(),at:new Date().toISOString()})}catch{}},fe=e=>{O.peek()&&(me(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",fe),r(()=>window.removeEventListener("beforeunload",fe)),g={dirty:()=>O.peek(),dispose(){L=!0,me(),p.splice(0).reverse().forEach(e=>{try{e()}catch{}})}},I.scan(a),j();let k=null;try{k=S.get(T)}catch{}if(k?.form){try{S.remove?S.remove(T):S.set(T,null)}catch{}const e=k.base&&k.base!==_.peek(),t=k.at?` (${Ne(k.at)})`:"",s=e?`You left unsaved settings${t}, but the saved settings have changed since. Restore your version anyway?`:`You left unsaved settings${t}. Restore them?`;if(await E.confirm(s)&&c()){const o={...y.peek()};for(const[h,d]of Object.entries(k.form))h in o&&h!=="smtpPass"&&(o[h]=d);f||(o.css=y.peek().css),ie(o),q.peek()&&(Y.value=n.adminHome.peek())}}}};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domma-cms",
3
- "version": "1.6.32",
3
+ "version": "1.6.33",
4
4
  "description": "File-based CMS powered by Domma and Fastify. Run npx domma-cms my-site to create a new project.",
5
5
  "type": "module",
6
6
  "main": "server/server.js",
@@ -1 +1 @@
1
- (function(){"use strict";fetch("/api/effects/settings").then(function(g){return g.json()}).then(function(g){var z=g.respectMotion!==!1,k=g.defaultDuration||600,M=g.defaultAnimation||"fade",I=g.defaultThreshold!=null?g.defaultThreshold:.1,p=z&&window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches,m=document.querySelector(".page-body");if(!m)return;var A=m.querySelector?m.querySelector(".dm-fx-demo-controls"):null;if(A){A.hidden=!1;var S=A.querySelector("[data-fx-demo-status]"),E=A.querySelector("[data-fx-demo-play]");S.textContent=p?"Animations are disabled by your Reduce motion preference. The demos show static content.":"Animations are enabled. Scroll to each demo to see it, or replay the page.",E.textContent=p?"Enable animations on this site":"Replay demos",E.addEventListener("click",function(){if(p)try{localStorage.setItem("domma:reduced_motion","false")}catch{S.textContent="Your browser blocked saving the motion preference. Use its accessibility settings to allow animations.";return}window.location.reload()})}function r(t,e){return t.getAttribute("data-fx-"+e)}function i(t,e,n){var o=r(t,e),a=o!==null?parseFloat(o):NaN;return Number.isFinite(a)?a:n}function b(t,e,n){var o=r(t,e);return o===null?n:o==="true"||o==="1"}function C(t,e,n){var o=new IntersectionObserver(function(a){a[0].isIntersecting&&(o.unobserve(t),n())},{threshold:e});o.observe(t)}m.querySelectorAll(".dm-fx-reveal").forEach(function(t){var e=r(t,"animation")||M,n=i(t,"duration",k),o=i(t,"delay",0),a=i(t,"threshold",I),c=b(t,"once",!0);p||(t.style.opacity="0",t.style.transition="none",e==="slide-up"?t.style.transform="translateY(30px)":e==="slide-down"?t.style.transform="translateY(-30px)":e==="zoom"?t.style.transform="scale(0.85)":e==="flip"&&(t.style.transform="rotateY(90deg)"),requestAnimationFrame(function(){requestAnimationFrame(function(){t.style.transition="opacity "+n+"ms ease "+o+"ms, transform "+n+"ms ease "+o+"ms";var f=new IntersectionObserver(function(u){u.forEach(function(v){v.isIntersecting?(t.style.opacity="1",t.style.transform="",c&&f.unobserve(t)):c||(t.style.opacity="0",e==="slide-up"?t.style.transform="translateY(30px)":e==="slide-down"?t.style.transform="translateY(-30px)":e==="zoom"?t.style.transform="scale(0.85)":e==="flip"?t.style.transform="rotateY(90deg)":t.style.transform="")})},{threshold:a});f.observe(t)})}))}),m.querySelectorAll(".dm-fx-counter").forEach(function(t){var e=i(t,"to",0),n=i(t,"from",0),o=i(t,"duration",2e3),a=r(t,"prefix")||"",c=r(t,"suffix")||"",f=i(t,"decimals",0),u=r(t,"separator")||"";function v(l){var d=l.toFixed(f);if(u){var y=d.split(".");y[0]=y[0].replace(/\B(?=(\d{3})+(?!\d))/g,u),d=y.join(".")}return a+d+c}if(p){t.textContent=v(e);return}C(t,.5,function(){var l=null;requestAnimationFrame(function d(y){l||(l=y);var x=Math.min((y-l)/o,1),s=1-Math.pow(1-x,3);t.textContent=v(n+(e-n)*s),x<1&&requestAnimationFrame(d)})})}),p||m.querySelectorAll(".dm-fx-scramble").forEach(function(t){var e=i(t,"speed",50),n=t.textContent,o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%&",a=0;C(t,.3,function(){var c=setInterval(function(){for(var f="",u=0;u<n.length;u++)u<a||n[u]===" "?f+=n[u]:f+=o[Math.floor(Math.random()*o.length)];t.textContent=f,a<n.length?a++:clearInterval(c)},e)})}),p||m.querySelectorAll(".dm-fx-shake").forEach(function(t){var e=i(t,"intensity",5),n=i(t,"duration",500),o=r(t,"direction")||"horizontal",a=o==="vertical"?"translateY":"translateX";t.animate([{transform:a+"(0)"},{transform:a+"(-"+e+"px)"},{transform:a+"("+e+"px)"},{transform:a+"(-"+e+"px)"},{transform:a+"("+e+"px)"},{transform:a+"(0)"}],{duration:n,easing:"ease-in-out"})}),m.querySelectorAll(".dm-fx-ripple").forEach(function(t){t.style.position="relative",t.style.overflow="hidden",t.addEventListener("click",function(e){var n=r(t,"colour")||"rgba(255,255,255,0.3)",o=i(t,"duration",600),a=t.getBoundingClientRect(),c=Math.max(a.width,a.height)*2,f=document.createElement("span");f.style.cssText="position:absolute;border-radius:50%;pointer-events:none;width:"+c+"px;height:"+c+"px;left:"+(e.clientX-a.left-c/2)+"px;top:"+(e.clientY-a.top-c/2)+"px;background:"+n+";transform:scale(0);opacity:1;transition:transform "+o+"ms ease,opacity "+o+"ms ease;",t.appendChild(f),requestAnimationFrame(function(){f.style.transform="scale(1)",f.style.opacity="0"}),setTimeout(function(){f.remove()},o)})});var h=window.Domma&&window.Domma.effects;h&&(m.querySelectorAll(".dm-fx-breathe").forEach(function(t){if(!p){var e={};r(t,"amplitude")&&(e.amplitude=i(t,"amplitude",6)),r(t,"duration")&&(e.duration=i(t,"duration",3e3)),r(t,"easing")&&(e.easing=r(t,"easing")),r(t,"stagger")&&(e.stagger=i(t,"stagger",0)),h.breathe(t,e)}}),m.querySelectorAll(".dm-fx-pulse").forEach(function(t){if(!p){var e={};r(t,"scale")&&(e.scale=i(t,"scale",1.05)),r(t,"duration")&&(e.duration=i(t,"duration",2e3)),r(t,"easing")&&(e.easing=r(t,"easing")),h.pulse(t,e)}}),m.querySelectorAll(".dm-fx-scribe").forEach(function(t){var e={respectMotionPreference:z};[["speed","speed",50],["delete-speed","deleteSpeed",30],["loop-delay","loopDelay",1e3]].forEach(function(s){r(t,s[0])!==null&&(e[s[1]]=Math.max(0,i(t,s[0],s[2])))}),[["cursor","cursor",!0],["cursor-blink","cursorBlink",!0],["pause-on-hover","pauseOnHover",!1],["auto-start","autoStart",!0],["respect-motion-preference","respectMotionPreference",z]].forEach(function(s){r(t,s[0])!==null&&(e[s[1]]=b(t,s[0],s[2]))}),r(t,"cursor-char")!==null&&(e.cursorChar=r(t,"cursor-char"));var n=r(t,"mode");["typewriter","word","sentence"].includes(n)&&(e.mode=n);var o=r(t,"cursor-type");["caret","block","underline"].includes(o)&&(e.cursorType=o);var a=r(t,"loop");if(a!==null){var c=Number(a);e.loop=a==="true"?!0:Number.isInteger(c)&&c>=1?c:!1}var f=r(t,"effect");["none","fade","bounce","glow"].includes(f)||(f="none");var u=r(t,"actions");if(u){try{e.actions=JSON.parse(u)}catch{return}if(!Array.isArray(e.actions)||e.actions.some(function(s){return!s||typeof s!="object"}))return;e.actions=e.actions.map(function(s){return s.render!==void 0&&s.effect===void 0?Object.assign({},s,{effect:f}):s})}else{var v=t.textContent.trim();if(!v)return;e.actions=[{render:v,effect:f}]}var l=e.respectMotionPreference&&window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches;if(l){var d=e.actions.find(function(s){return s.render!=null});d&&(t.textContent=String(d.render));return}u||(t.textContent=""),[["onStart","start"],["onComplete","complete"],["onCharacter","character"],["onRender","render"],["onUndoRender","undo"],["onLoop","loop"]].forEach(function(s){e[s[0]]=function(w,T){t.dispatchEvent(new CustomEvent("scribe:"+s[1],{bubbles:!0,detail:{value:w,index:T}}))}});var y=h.scribe(t,e);if(y&&(b(t,"controls",!1)||e.autoStart===!1)){var x=document.createElement("span");x.className="dm-scribe-controls",x.setAttribute("role","group"),x.setAttribute("aria-label","Text animation playback"),[["Play","resume"],["Pause","pause"],["Replay","restart"]].forEach(function(s){var w=document.createElement("button");w.type="button",w.className="btn btn-ghost btn-sm",w.textContent=s[0],w.addEventListener("click",function(){y[s[1]]()}),x.appendChild(w)}),t.insertAdjacentElement("afterend",x)}}),m.querySelectorAll(".dm-fx-twinkle").forEach(function(t){var e={};r(t,"count")&&(e.count=i(t,"count",100)),r(t,"shape")&&(e.shape=r(t,"shape")),r(t,"colour")&&(e.colour=r(t,"colour")),r(t,"min-size")&&(e.minSize=i(t,"min-size",1)),r(t,"max-size")&&(e.maxSize=i(t,"max-size",3)),h.twinkle(t,e)}),typeof h.tickerTape=="function"&&m.querySelectorAll(".dm-fx-ticker-tape").forEach(function(t){if(!p){var e={};r(t,"palette")&&(e.palette=r(t,"palette")),r(t,"density")&&(e.density=i(t,"density",50)),r(t,"speed")&&(e.speed=i(t,"speed",1)),r(t,"sway")&&(e.sway=i(t,"sway",60)),r(t,"rotation-speed")&&(e.rotationSpeed=i(t,"rotation-speed",6)),r(t,"min-width")&&(e.minWidth=i(t,"min-width",5)),r(t,"max-width")&&(e.maxWidth=i(t,"max-width",9)),r(t,"min-height")&&(e.minHeight=i(t,"min-height",12)),r(t,"max-height")&&(e.maxHeight=i(t,"max-height",22)),r(t,"fade-start")&&(e.fadeStart=i(t,"fade-start",.55)),r(t,"burst")&&(e.burst=b(t,"burst",!1)),r(t,"burst-count")&&(e.burstCount=i(t,"burst-count",150)),r(t,"z-index")&&(e.zIndex=i(t,"z-index",1));var n=r(t,"mode");n==="page"?h.tickerTape(null,e):h.tickerTape(t,e)}}),typeof h.butterflies=="function"&&m.querySelectorAll(".dm-fx-butterflies").forEach(function(t){if(!p){var e={};r(t,"palette")&&(e.palette=r(t,"palette")),r(t,"density")&&(e.density=i(t,"density",18)),r(t,"speed")&&(e.speed=i(t,"speed",1)),r(t,"wander")&&(e.wander=i(t,"wander",1)),r(t,"rise-speed")&&(e.riseSpeed=i(t,"rise-speed",.4)),r(t,"flap-speed")&&(e.flapSpeed=i(t,"flap-speed",1)),r(t,"min-size")&&(e.minSize=i(t,"min-size",14)),r(t,"max-size")&&(e.maxSize=i(t,"max-size",26)),r(t,"two-tone")&&(e.twoTone=b(t,"two-tone",!0)),r(t,"burst")&&(e.burst=b(t,"burst",!1)),r(t,"burst-count")&&(e.burstCount=i(t,"burst-count",40)),r(t,"z-index")&&(e.zIndex=i(t,"z-index",1));var n=r(t,"mode");n==="page"?h.butterflies(null,e):h.butterflies(t,e)}}),typeof h.strobe=="function"&&m.querySelectorAll(".dm-fx-strobe").forEach(function(t){if(p)return;function e(f){var u=r(t,f);if(!u)return null;var v=u.split(",").map(function(l){return l.trim()}).filter(Boolean);return v.length?v:null}var n={};r(t,"preset")&&(n.preset=r(t,"preset")),r(t,"motion")&&(n.motion=r(t,"motion")),r(t,"speed")&&(n.speed=i(t,"speed",1)),r(t,"sweep-arc")&&(n.sweepArc=i(t,"sweep-arc",100)),r(t,"beam-width")&&(n.beamWidth=i(t,"beam-width",16)),r(t,"intensity")&&(n.intensity=i(t,"intensity",.7)),r(t,"flicker")&&(n.flicker=b(t,"flicker",!0)),r(t,"hz")&&(n.hz=i(t,"hz",10));var o=e("origins");o&&(n.origins=o);var a=e("colours");a&&(n.colours=a),r(t,"duration")&&(n.duration=i(t,"duration",null)),r(t,"z-index")&&(n.zIndex=i(t,"z-index",9999));var c=r(t,"mode");c==="page"?h.strobe(null,n):h.strobe(t,n)}));var q=m.querySelectorAll(".dm-fx-celebrate");q.length&&import("/public/js/celebrations/index.js?v=20261003-characters").then(function(t){var e=t.CelebrationsEffect;q.forEach(function(n){var o=r(n,"character");if(o){if(n.querySelector("canvas"))return;t.mountCharacter(n,{character:o,size:r(n,"size")||"medium",facing:r(n,"facing")||"right",animate:!p&&b(n,"animate",!0)}).then(function(d){var y=new MutationObserver(function(){n.isConnected||(d.destroy(),y.disconnect())});n.isConnected?y.observe(document.body,{childList:!0,subtree:!0}):d.destroy()}).catch(function(d){console.error("[celebrate]",d)});return}if(!p){var a=n.getAttribute("data-fx-theme")||"auto",c=n.getAttribute("data-fx-intensity")||"medium",f=n.getAttribute("data-fx-z-index")?parseInt(n.getAttribute("data-fx-z-index"),10):999;if(!(a==="auto"&&(a=e.getCurrentTheme(),!a))){var u={},v=["data-fx-theme","data-fx-intensity","data-fx-z-index"];Array.from(n.attributes).forEach(function(d){d.name.startsWith("data-fx-")&&!v.includes(d.name)&&(u[d.name.slice(8)]=d.value)});var l=new e({theme:a,intensity:c,enabled:!0,zIndex:f,overrides:u});l.init().then(function(){l.start()})}}})}).catch(function(){})}).catch(function(){})})();
1
+ (function(){"use strict";Promise.all([fetch("/api/effects/settings").then(function(z){return z.json()}),import("/public/js/scribe-style.mjs")]).then(function(z){var g=z[0],I=z[1].buildScribeStyle,C=g.respectMotion!==!1,Y=g.defaultDuration||600,F=g.defaultAnimation||"fade",N=g.defaultThreshold!=null?g.defaultThreshold:.1,p=C&&window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches,m=document.querySelector(".page-body");if(!m)return;var A=m.querySelector?m.querySelector(".dm-fx-demo-controls"):null;if(A){A.hidden=!1;var q=A.querySelector("[data-fx-demo-status]"),k=A.querySelector("[data-fx-demo-play]");q.textContent=p?"Animations are disabled by your Reduce motion preference. The demos show static content.":"Animations are enabled. Scroll to each demo to see it, or replay the page.",k.textContent=p?"Enable animations on this site":"Replay demos",k.addEventListener("click",function(){if(p)try{localStorage.setItem("domma:reduced_motion","false")}catch{q.textContent="Your browser blocked saving the motion preference. Use its accessibility settings to allow animations.";return}window.location.reload()})}function r(t,e){return t.getAttribute("data-fx-"+e)}function i(t,e,n){var a=r(t,e),o=a!==null?parseFloat(a):NaN;return Number.isFinite(o)?o:n}function x(t,e,n){var a=r(t,e);return a===null?n:a==="true"||a==="1"}function M(t,e,n){var a=new IntersectionObserver(function(o){o[0].isIntersecting&&(a.unobserve(t),n())},{threshold:e});a.observe(t)}m.querySelectorAll(".dm-fx-reveal").forEach(function(t){var e=r(t,"animation")||F,n=i(t,"duration",Y),a=i(t,"delay",0),o=i(t,"threshold",N),c=x(t,"once",!0);p||(t.style.opacity="0",t.style.transition="none",e==="slide-up"?t.style.transform="translateY(30px)":e==="slide-down"?t.style.transform="translateY(-30px)":e==="zoom"?t.style.transform="scale(0.85)":e==="flip"&&(t.style.transform="rotateY(90deg)"),requestAnimationFrame(function(){requestAnimationFrame(function(){t.style.transition="opacity "+n+"ms ease "+a+"ms, transform "+n+"ms ease "+a+"ms";var f=new IntersectionObserver(function(u){u.forEach(function(l){l.isIntersecting?(t.style.opacity="1",t.style.transform="",c&&f.unobserve(t)):c||(t.style.opacity="0",e==="slide-up"?t.style.transform="translateY(30px)":e==="slide-down"?t.style.transform="translateY(-30px)":e==="zoom"?t.style.transform="scale(0.85)":e==="flip"?t.style.transform="rotateY(90deg)":t.style.transform="")})},{threshold:o});f.observe(t)})}))}),m.querySelectorAll(".dm-fx-counter").forEach(function(t){var e=i(t,"to",0),n=i(t,"from",0),a=i(t,"duration",2e3),o=r(t,"prefix")||"",c=r(t,"suffix")||"",f=i(t,"decimals",0),u=r(t,"separator")||"";function l(v){var d=v.toFixed(f);if(u){var y=d.split(".");y[0]=y[0].replace(/\B(?=(\d{3})+(?!\d))/g,u),d=y.join(".")}return o+d+c}if(p){t.textContent=l(e);return}M(t,.5,function(){var v=null;requestAnimationFrame(function d(y){v||(v=y);var w=Math.min((y-v)/a,1),E=1-Math.pow(1-w,3);t.textContent=l(n+(e-n)*E),w<1&&requestAnimationFrame(d)})})}),p||m.querySelectorAll(".dm-fx-scramble").forEach(function(t){var e=i(t,"speed",50),n=t.textContent,a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%&",o=0;M(t,.3,function(){var c=setInterval(function(){for(var f="",u=0;u<n.length;u++)u<o||n[u]===" "?f+=n[u]:f+=a[Math.floor(Math.random()*a.length)];t.textContent=f,o<n.length?o++:clearInterval(c)},e)})}),p||m.querySelectorAll(".dm-fx-shake").forEach(function(t){var e=i(t,"intensity",5),n=i(t,"duration",500),a=r(t,"direction")||"horizontal",o=a==="vertical"?"translateY":"translateX";t.animate([{transform:o+"(0)"},{transform:o+"(-"+e+"px)"},{transform:o+"("+e+"px)"},{transform:o+"(-"+e+"px)"},{transform:o+"("+e+"px)"},{transform:o+"(0)"}],{duration:n,easing:"ease-in-out"})}),m.querySelectorAll(".dm-fx-ripple").forEach(function(t){t.style.position="relative",t.style.overflow="hidden",t.addEventListener("click",function(e){var n=r(t,"colour")||"rgba(255,255,255,0.3)",a=i(t,"duration",600),o=t.getBoundingClientRect(),c=Math.max(o.width,o.height)*2,f=document.createElement("span");f.style.cssText="position:absolute;border-radius:50%;pointer-events:none;width:"+c+"px;height:"+c+"px;left:"+(e.clientX-o.left-c/2)+"px;top:"+(e.clientY-o.top-c/2)+"px;background:"+n+";transform:scale(0);opacity:1;transition:transform "+a+"ms ease,opacity "+a+"ms ease;",t.appendChild(f),requestAnimationFrame(function(){f.style.transform="scale(1)",f.style.opacity="0"}),setTimeout(function(){f.remove()},a)})});var h=window.Domma&&window.Domma.effects;h&&(m.querySelectorAll(".dm-fx-breathe").forEach(function(t){if(!p){var e={};r(t,"amplitude")&&(e.amplitude=i(t,"amplitude",6)),r(t,"duration")&&(e.duration=i(t,"duration",3e3)),r(t,"easing")&&(e.easing=r(t,"easing")),r(t,"stagger")&&(e.stagger=i(t,"stagger",0)),h.breathe(t,e)}}),m.querySelectorAll(".dm-fx-pulse").forEach(function(t){if(!p){var e={};r(t,"scale")&&(e.scale=i(t,"scale",1.05)),r(t,"duration")&&(e.duration=i(t,"duration",2e3)),r(t,"easing")&&(e.easing=r(t,"easing")),h.pulse(t,e)}}),m.querySelectorAll(".dm-fx-scribe").forEach(function(t){var e={};["font","size","font-size","weight","colour","color","italic","align","line-height","spacing"].forEach(function(s){var b=r(t,s);b!==null&&(e[s]=b)});var n=I(e);n&&(t.style.cssText+=";"+n);var a={respectMotionPreference:C};[["speed","speed",50],["delete-speed","deleteSpeed",30],["loop-delay","loopDelay",1e3]].forEach(function(s){r(t,s[0])!==null&&(a[s[1]]=Math.max(0,i(t,s[0],s[2])))}),[["cursor","cursor",!0],["cursor-blink","cursorBlink",!0],["pause-on-hover","pauseOnHover",!1],["auto-start","autoStart",!0],["respect-motion-preference","respectMotionPreference",C]].forEach(function(s){r(t,s[0])!==null&&(a[s[1]]=x(t,s[0],s[2]))}),r(t,"cursor-char")!==null&&(a.cursorChar=r(t,"cursor-char"));var o=r(t,"mode");["typewriter","word","sentence"].includes(o)&&(a.mode=o);var c=r(t,"cursor-type");["caret","block","underline"].includes(c)&&(a.cursorType=c);var f=r(t,"loop");if(f!==null){var u=Number(f);a.loop=f==="true"?!0:Number.isInteger(u)&&u>=1?u:!1}var l=r(t,"effect");["none","fade","bounce","glow"].includes(l)||(l="none");var v=r(t,"actions");if(v){try{a.actions=JSON.parse(v)}catch{return}if(!Array.isArray(a.actions)||a.actions.some(function(s){return!s||typeof s!="object"}))return;a.actions=a.actions.map(function(s){return s.render!==void 0&&s.effect===void 0?Object.assign({},s,{effect:l}):s})}else{var d=t.textContent.trim();if(!d)return;a.actions=[{render:d,effect:l}]}var y=a.respectMotionPreference&&window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches;if(y){var w=a.actions.find(function(s){return s.render!=null});w&&(t.textContent=String(w.render));return}v||(t.textContent=""),[["onStart","start"],["onComplete","complete"],["onCharacter","character"],["onRender","render"],["onUndoRender","undo"],["onLoop","loop"]].forEach(function(s){a[s[0]]=function(b,R){t.dispatchEvent(new CustomEvent("scribe:"+s[1],{bubbles:!0,detail:{value:b,index:R}}))}});var E=h.scribe(t,a);if(E&&(x(t,"controls",!1)||a.autoStart===!1)){var S=document.createElement("span");S.className="dm-scribe-controls",S.setAttribute("role","group"),S.setAttribute("aria-label","Text animation playback"),[["Play","resume"],["Pause","pause"],["Replay","restart"]].forEach(function(s){var b=document.createElement("button");b.type="button",b.className="btn btn-ghost btn-sm",b.textContent=s[0],b.addEventListener("click",function(){E[s[1]]()}),S.appendChild(b)}),t.insertAdjacentElement("afterend",S)}}),m.querySelectorAll(".dm-fx-twinkle").forEach(function(t){var e={};r(t,"count")&&(e.count=i(t,"count",100)),r(t,"shape")&&(e.shape=r(t,"shape")),r(t,"colour")&&(e.colour=r(t,"colour")),r(t,"min-size")&&(e.minSize=i(t,"min-size",1)),r(t,"max-size")&&(e.maxSize=i(t,"max-size",3)),h.twinkle(t,e)}),typeof h.tickerTape=="function"&&m.querySelectorAll(".dm-fx-ticker-tape").forEach(function(t){if(!p){var e={};r(t,"palette")&&(e.palette=r(t,"palette")),r(t,"density")&&(e.density=i(t,"density",50)),r(t,"speed")&&(e.speed=i(t,"speed",1)),r(t,"sway")&&(e.sway=i(t,"sway",60)),r(t,"rotation-speed")&&(e.rotationSpeed=i(t,"rotation-speed",6)),r(t,"min-width")&&(e.minWidth=i(t,"min-width",5)),r(t,"max-width")&&(e.maxWidth=i(t,"max-width",9)),r(t,"min-height")&&(e.minHeight=i(t,"min-height",12)),r(t,"max-height")&&(e.maxHeight=i(t,"max-height",22)),r(t,"fade-start")&&(e.fadeStart=i(t,"fade-start",.55)),r(t,"burst")&&(e.burst=x(t,"burst",!1)),r(t,"burst-count")&&(e.burstCount=i(t,"burst-count",150)),r(t,"z-index")&&(e.zIndex=i(t,"z-index",1));var n=r(t,"mode");n==="page"?h.tickerTape(null,e):h.tickerTape(t,e)}}),typeof h.butterflies=="function"&&m.querySelectorAll(".dm-fx-butterflies").forEach(function(t){if(!p){var e={};r(t,"palette")&&(e.palette=r(t,"palette")),r(t,"density")&&(e.density=i(t,"density",18)),r(t,"speed")&&(e.speed=i(t,"speed",1)),r(t,"wander")&&(e.wander=i(t,"wander",1)),r(t,"rise-speed")&&(e.riseSpeed=i(t,"rise-speed",.4)),r(t,"flap-speed")&&(e.flapSpeed=i(t,"flap-speed",1)),r(t,"min-size")&&(e.minSize=i(t,"min-size",14)),r(t,"max-size")&&(e.maxSize=i(t,"max-size",26)),r(t,"two-tone")&&(e.twoTone=x(t,"two-tone",!0)),r(t,"burst")&&(e.burst=x(t,"burst",!1)),r(t,"burst-count")&&(e.burstCount=i(t,"burst-count",40)),r(t,"z-index")&&(e.zIndex=i(t,"z-index",1));var n=r(t,"mode");n==="page"?h.butterflies(null,e):h.butterflies(t,e)}}),typeof h.strobe=="function"&&m.querySelectorAll(".dm-fx-strobe").forEach(function(t){if(p)return;function e(f){var u=r(t,f);if(!u)return null;var l=u.split(",").map(function(v){return v.trim()}).filter(Boolean);return l.length?l:null}var n={};r(t,"preset")&&(n.preset=r(t,"preset")),r(t,"motion")&&(n.motion=r(t,"motion")),r(t,"speed")&&(n.speed=i(t,"speed",1)),r(t,"sweep-arc")&&(n.sweepArc=i(t,"sweep-arc",100)),r(t,"beam-width")&&(n.beamWidth=i(t,"beam-width",16)),r(t,"intensity")&&(n.intensity=i(t,"intensity",.7)),r(t,"flicker")&&(n.flicker=x(t,"flicker",!0)),r(t,"hz")&&(n.hz=i(t,"hz",10));var a=e("origins");a&&(n.origins=a);var o=e("colours");o&&(n.colours=o),r(t,"duration")&&(n.duration=i(t,"duration",null)),r(t,"z-index")&&(n.zIndex=i(t,"z-index",9999));var c=r(t,"mode");c==="page"?h.strobe(null,n):h.strobe(t,n)}));var T=m.querySelectorAll(".dm-fx-celebrate");T.length&&import("/public/js/celebrations/index.js?v=20261003-characters").then(function(t){var e=t.CelebrationsEffect;T.forEach(function(n){var a=r(n,"character");if(a){if(n.querySelector("canvas"))return;t.mountCharacter(n,{character:a,size:r(n,"size")||"medium",facing:r(n,"facing")||"right",animate:!p&&x(n,"animate",!0)}).then(function(d){var y=new MutationObserver(function(){n.isConnected||(d.destroy(),y.disconnect())});n.isConnected?y.observe(document.body,{childList:!0,subtree:!0}):d.destroy()}).catch(function(d){console.error("[celebrate]",d)});return}if(!p){var o=n.getAttribute("data-fx-theme")||"auto",c=n.getAttribute("data-fx-intensity")||"medium",f=n.getAttribute("data-fx-z-index")?parseInt(n.getAttribute("data-fx-z-index"),10):999;if(!(o==="auto"&&(o=e.getCurrentTheme(),!o))){var u={},l=["data-fx-theme","data-fx-intensity","data-fx-z-index"];Array.from(n.attributes).forEach(function(d){d.name.startsWith("data-fx-")&&!l.includes(d.name)&&(u[d.name.slice(8)]=d.value)});var v=new e({theme:o,intensity:c,enabled:!0,zIndex:f,overrides:u});v.init().then(function(){v.start()})}}})}).catch(function(){})}).catch(function(){})})();
@@ -0,0 +1 @@
1
+ import{FONT_MAP as c,SIZE_MAP as f,WEIGHT_MAP as g,COLOUR_TOKENS as p,SPACING_MAP as a}from"./text-style.mjs";export const SCRIBE_STYLE_FIELDS=[{name:"font",key:"font",label:"Font",type:"select",default:"",options:[["","Inherit"],["system","System"],["serif","Serif"],["mono","Monospace"],...Object.keys(c).map(e=>[e,e])]},{name:"size",key:"size",label:"Font size (e.g. 3xl, 48px, 3rem)",type:"text",default:""},{name:"weight",key:"weight",label:"Font weight",type:"select",default:"",options:[["","Inherit"],...Object.keys(g).map(e=>[e,e])]},{name:"colour",key:"colour",label:"Text colour (name or hex)",type:"text",default:""},{name:"italic",key:"italic",label:"Italic",type:"checkbox",default:!1},{name:"align",key:"align",label:"Alignment",type:"select",default:"",options:[["","Inherit"],["left","Left"],["center","Centre"],["right","Right"]]},{name:"line-height",key:"lineHeight",label:"Line height (e.g. 1.5)",type:"text",default:""},{name:"spacing",key:"spacing",label:"Letter spacing (e.g. wide, .05em)",type:"text",default:""}];const h=/^(?:\d+(?:\.\d+)?|\.\d+)(?:px|pt|rem|em|vw|vh|%)$/i,u={...c,system:"system-ui,sans-serif",serif:"serif",mono:"monospace"};export function buildScribeStyle(e={}){const t=[],i=e.size||e["font-size"];f[i]?t.push(`font-size:${f[i]}`):h.test(String(i||""))&&t.push(`font-size:${i}`),u[e.font]&&t.push(`font-family:${u[e.font]}`);const s=g[e.weight]||(/^[1-9]00$/.test(String(e.weight||""))?e.weight:"");s&&t.push(`font-weight:${s}`),(e.italic===!0||e.italic==="true")&&t.push("font-style:italic");const n=e.colour||e.color;p[n]?t.push(`color:${p[n]}`):/^(?:#[\da-f]{3,4}|#[\da-f]{6}|#[\da-f]{8}|[a-z]+)$/i.test(String(n||""))&&t.push(`color:${n}`);const o=e["line-height"]||e.lineHeight;(/^(?:\d+(?:\.\d+)?|\.\d+)$/.test(String(o||""))||h.test(String(o||"")))&&t.push(`line-height:${o}`);const l=e.spacing;return a[l]?t.push(`letter-spacing:${a[l]}`):/^-?(?:\d+(?:\.\d+)?|\.\d+)(?:px|rem|em)$/i.test(String(l||""))&&t.push(`letter-spacing:${l}`),["left","center","right"].includes(e.align)&&t.push(`display:block;text-align:${e.align}`),t.join(";")}
package/public/js/site.js CHANGED
@@ -1 +1 @@
1
- import{decodeBase64Json as P}from"/public/js/utf8-base64.mjs";import{makeBadgeEl as Y,applyColour as J,applyPill as te,applyFloat as oe,applyHighlight as R,applyItemHighlight as ne,itemHighlightAttrs as ae,isCurrentItem as X,menuAlign as se,applyClasses as O,cssClasses as W}from"/public/js/menu-decor.mjs?v=20261009-hover-darken";import{initFloatingMenus as ie}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as U,markInvalidFields as G,showValidationPopup as K}from"/public/js/forms.js?v=20261009-form-classes";import{buildWizardSteps as re}from"/public/js/form-steps.mjs";import{applyFormClasses as Q,applySubmitButtons as ce,applyWizardButtons as le}from"/public/js/form-submit.mjs";document.addEventListener("click",p=>{const c=p.target?.closest?.(".dm-banner__dismiss");c&&(p.preventDefault(),c.closest(".dm-banner")?.remove())}),$(()=>{const p=window.__CMS_NAV__||{},c=window.__CMS_SITE__||{};if(c.autoTheme?.enabled){let t=function(e){const a=(e||"07:00").split(":");return+a[0]*60+(+a[1]||0)},s=function(){const e=new Date,a=e.getHours()*60+e.getMinutes();return a>=t(n.dayStart)&&a<t(n.nightStart)?n.dayTheme:n.nightTheme},o=function(e){const a=n.apply&&n.apply[e]||{theme:e,custom:""};Domma.theme.set(a.theme);for(const f of[...document.body.classList])f.startsWith("dm-custom-")&&document.body.classList.remove(f);a.custom&&document.body.classList.add(a.custom)};const n=c.autoTheme;o(s()),setInterval(()=>o(s()),6e4)}const x=$("#site-navbar");if(x.length&&p.brand){let s=function(l,d=0){return Array.isArray(l)?l.filter(m=>!(m&&(m.type==="spacer"||m.type==="theme-switcher"))).filter(m=>!(m&&m.type==="separator"&&d===0)).map(m=>m&&m.type==="separator"?{divider:!0}:Array.isArray(m?.items)?{...m,items:s(m.items,d+1)}:m):l},o=function(l){return Array.isArray(l)?l.filter(d=>!(d&&(d.type==="separator"||d.type==="spacer"||d.type==="theme-switcher"))):[]};const n={...p.brand},t=n.size&&n.size!=="md"?` navbar-brand-${n.size}`:"";if(n.logo||n.icon||n.tagline){let l="";n.logo?l+=`<img src="${n.logo}" class="navbar-brand-logo" alt="${n.text||""}">`:n.icon&&(l+=`<span data-icon="${n.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),n.text&&(l+=`<span class="navbar-brand-text${t}">${n.text}</span>`),n.tagline&&(l+=`<small class="navbar-brand-tagline">${n.tagline}</small>`),n.html=l}else t&&n.text&&(n.html=`<span class="navbar-brand-text${t}">${n.text}</span>`);const e=p.position==="floating",a=p.orientation==="vertical";Domma.elements.navbar("#site-navbar",{brand:n,items:s(p.items||[]),variant:p.variant||"dark",position:e?"static":p.position||"sticky",collapsible:!0,appearOnHover:!!p.appearOnHover}),Domma.icons.scan("#site-navbar");const f=x.get(0);f.classList.add(`dm-nav-align-${se(p)||"centre"}`),a&&x.get(0).classList.add("dm-nav-vertical",p.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left");const v=p.dock||{};a&&!e&&de(f,p.side==="right"),!a&&v.hideOnScroll&&me(f),R(f,p),O(f,p.class),e&&(oe(x.get(0),p),ie(x.get(0).parentNode||document));const i=f.querySelector(".navbar-toggle"),u=f.querySelector(".navbar-collapse");if(i&&u){let l=null;const d=()=>{u.classList.contains("show")&&i.click()},m=()=>{l&&(l.remove(),l=null),document.body.classList.remove("dm-drawer-open")},g=()=>{l||(l=document.createElement("div"),l.className="dm-navbar-backdrop",l.addEventListener("click",d),document.body.appendChild(l),document.body.classList.add("dm-drawer-open"))};i.addEventListener("click",()=>{requestAnimationFrame(()=>{u.classList.contains("show")?g():m()})}),u.addEventListener("click",C=>{C.target.closest("a")&&requestAnimationFrame(m)}),document.addEventListener("keydown",C=>{C.key==="Escape"&&u.classList.contains("show")&&d()})}const h=f.querySelector(".navbar-nav");if(h){const l=Array.from(h.children);let d=0;for(const m of p.items||[])if(!(m&&m.type==="separator")){if(m&&m.type==="spacer"){const g=document.createElement("li"),C=["xs","sm","md","lg","xl","flex"].includes(m.size)?m.size:"md";g.className=`dm-menu-spacer dm-menu-spacer--${C}`,g.setAttribute("aria-hidden","true"),h.insertBefore(g,l[d]||null);continue}if(m&&m.type==="theme-switcher"){const g=document.createElement("li");g.className="dm-menu-theme-switcher";const C=document.createElement("span");C.dataset.themeSwitcher="menu",C.dataset.position="inline",g.appendChild(C),h.insertBefore(g,l[d]||null);continue}d++}window.CMSThemeSwitcher?.scan(h)}const b=o(p.items||[]);f.querySelectorAll(".navbar-link[data-index], .navbar-dropdown-item[data-index]").forEach(l=>{const d=b[parseInt(l.dataset.index,10)];if(!d)return;const m=l.dataset.subindex!=null,g=m&&Array.isArray(d.items)?d.items[parseInt(l.dataset.subindex,10)]:d;if(!g)return;g.colour&&J(l,g.colour),g.pill&&!m&&te(l,g.pill),ne(l,g),O(l.closest("li"),g.class);const C=Y(g.badge);C&&l.appendChild(C),X(g.url,location.pathname)&&(l.classList.add("active"),l.setAttribute("aria-current","page"))})}const y=$("#site-footer");if(y.length){let e=function(r){return String(r??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")},a=function(r){return e(r).replace(/'/g,"&#39;")},f=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>`;if(r&&r.type==="theme-switcher")return'<li class="dm-menu-theme-switcher"><span data-theme-switcher="menu" data-position="inline"></span></li>';const A=a(r.url||"#"),k=e(r.text||""),q=Array.isArray(r.items)&&r.items.length?`<ul>${r.items.map(f).join("")}</ul>`:"",N=ae(r),Z=[N.hover&&` data-menu-item-hover="${a(N.hover)}"`,N.active&&` data-menu-item-active="${a(N.active)}"`].filter(Boolean).join(""),ee=N.accent?` style="--dm-menu-highlight:${a(N.accent)}"`:"",V=W(r.class).join(" ");return`<li${V?` class="${a(V)}"`:""}><a href="${A}"${Z}${ee}>${k}</a>${q}</li>`},v=function(){if(!c.footer)return"";const r=Object.keys(t).filter(k=>n[k]);if(!r.length)return"";const A=W(c.footer.socialClass).join(" ");return`<div class="footer-social">${r.map(k=>{const{label:q,svg:N}=t[k];return`<a href="${n[k]}" target="_blank" rel="noopener noreferrer" aria-label="${q}" class="footer-social-link${A?` ${a(A)}`:""}">${N}</a>`}).join("")}</div>`},b=function(r){if(!r||r.type==="separator"||r.type==="spacer")return"";if(r.type==="theme-switcher")return`<li class="cms-footer-col">${f(r).replace(/^<li[^>]*>|<\/li>$/g,"")}</li>`;const A=Array.isArray(r.items)&&r.items.length?`<ul>${r.items.map(f).join("")}</ul>`:"",k=h(r)?`<span class="cms-footer-col-title">${e(r.text||"")}</span>`:`<a class="cms-footer-col-title" href="${a(r.url||"#")}">${e(r.text||"")}</a>`,q=["cms-footer-col",...W(r.class)].join(" ");return`<li class="${a(q)}">${k}${A}</li>`},d=function(r,A,k=!1){return(r||[]).forEach(q=>{q&&(q.type==="separator"||q.type==="spacer"||q.type==="theme-switcher")||(k&&h(q)||A.push(q),Array.isArray(q.items)&&q.items.length&&d(q.items,A))}),A};const n=c.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 o=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""},i=["columns","row"].includes(c.footer?.layout)?c.footer.layout:"auto",u=i==="columns"||i==="auto"&&o.primary.some(r=>Array.isArray(r?.items)&&r.items.length),h=r=>u&&!r.url&&Array.isArray(r.items)&&r.items.length>0;if(u){const r=p.brand||{},A=r.text||c.title||"",k=r.logo?`<img src="${a(r.logo)}" class="cms-footer-brand-logo" alt="">`:r.icon?'<span class="cms-footer-brand-icon"></span>':"",q=String(c.footer?.blurb||"").trim();s='<div class="footer-inner footer-inner--columns container"><div class="cms-footer-top">',s+=`<div class="cms-footer-brand">${A||k?`<a class="cms-footer-brand-name" href="${a(r.url||"/")}">${k}<span>${e(A)}</span></a>`:""}${q?`<p class="cms-footer-blurb">${e(q)}</p>`:""}${v()}</div>`,o.primary.length&&(s+=`<nav class="footer-nav footer-nav--primary footer-nav--columns" aria-label="Footer"><ul>${o.primary.map(b).join("")}</ul></nav>`),s+='</div><div class="cms-footer-bottom">',o.copyright&&(s+=`<p class="cms-footer-copyright">${e(o.copyright)}</p>`),o.legal.length&&(s+=`<nav class="footer-nav footer-nav--legal" aria-label="Legal"><ul>${o.legal.map(f).join("")}</ul></nav>`),s+="</div>"}else o.copyright&&(s+=`<p>${e(o.copyright)}</p>`),o.primary.length&&(s+=`<nav class="footer-nav footer-nav--primary"><ul>${o.primary.map(f).join("")}</ul></nav>`),o.legal.length&&(s+=`<nav class="footer-nav footer-nav--legal"><ul>${o.legal.map(f).join("")}</ul></nav>`),s+=v();s+="</div>",y.html(s),window.CMSThemeSwitcher?.scan(y.get(0));const l=y.get(0).querySelector(".cms-footer-brand-icon");l&&p.brand?.icon&&l.setAttribute("data-icon",p.brand.icon),O(y.get(0),c.footer?.class);const m=[...d(o.primary,[],!0),...d(o.legal,[])];y.get(0).querySelectorAll(".footer-nav a").forEach((r,A)=>{const k=m[A];if(!k)return;k.colour&&J(r,k.colour);const q=Y(k.badge);q&&r.appendChild(q),X(k.url,location.pathname)&&(r.classList.add("is-active"),r.setAttribute("aria-current","page"))}),R(y.get(0).querySelector(".footer-nav--primary"),{highlight:o.primaryHighlight}),R(y.get(0).querySelector(".footer-nav--legal"),{highlight:o.legalHighlight}),O(y.get(0).querySelector(".footer-nav--primary"),o.primaryClass),O(y.get(0).querySelector(".footer-nav--legal"),o.legalClass);const C=c.footer?.showMotionToggle!==!1,M=S.get("reduced_motion"),D=c.footer?.motionDefault,z=M!=null?!!M:D==="on"?!0:D==="off"?!1:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),B=c.footer?.motionControl==="checkbox",I=y.get(0).querySelector(".cms-footer-bottom")||y.get(0).querySelector(".footer-inner");if(C&&I){const r=document.createElement("input");r.type="checkbox",r.className=B?"footer-motion-checkbox":"form-switch-input",r.id="dm-motion-switch",r.checked=z,r.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const A=document.createElement("span");A.className=B?"footer-motion-label":"form-switch-label",A.textContent="Reduce motion";const k=document.createElement("label");k.className=B?"footer-motion-check":"form-switch footer-motion-switch",k.title="Reduce motion",k.appendChild(r),k.appendChild(A),I.appendChild(k)}}const L=document.getElementById("site-sidebar");L&&ue(L),Domma.icons.scan(),Domma.syntax?.scan&&Domma.syntax.scan();const w=$(".page-body");if(w.length){w.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),w.find(".tabs").each(function(){Domma.elements.tabs(this)}),w.find(".carousel").each(function(){const n={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)}),w.find(".dm-countdown").each(function(){const n={autoStart:!0},t=String(this.dataset.format||"").trim(),s=String(this.dataset.to||"").trim(),o=/^(\d{4})-(\d{2})-(\d{2})$/.exec(s),e=o?new Date(+o[1],+o[2]-1,+o[3]).getTime():s?new Date(s).getTime():NaN;Number.isFinite(e)?n.duration=Math.max(0,e-Date.now()):this.dataset.duration&&(n.duration=(parseInt(this.dataset.duration,10)||0)*1e3);const a=/^dd:/i.test(t);n.format=a?"hh:mm:ss":t||(n.duration>=36e5?"hh:mm:ss":"mm:ss");const f=this,v=u=>String(u).padStart(2,"0"),i=u=>{const h=Math.max(0,Math.floor(u/1e3)),b=f.querySelector(".dm-timer-time");b&&(b.textContent=`${v(Math.floor(h/86400))}:${v(Math.floor(h%86400/3600))}:${v(Math.floor(h%3600/60))}:${v(h%60)}`)};a&&(n.onTick=i),Domma.elements.timer(this,n),a&&i(n.duration||0)}),w.find("[data-tooltip]").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),w.find(".dm-progression").each(function(){const n=Array.from(this.querySelectorAll(".dm-progression-item")).map(o=>{const e={title:o.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:o.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return o.dataset.date&&(e.date=o.dataset.date),o.dataset.status&&(e.status=o.dataset.status),o.dataset.entryId&&(e._entryId=o.dataset.entryId),e}),t=this.dataset.layout==="centred"?"centered":this.dataset.layout||"vertical",s=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:s,items:n})});try{Domma.effects.reveal(".page-body .hero",{animation:"slide-up",duration:480,threshold:.06,stagger:60,once:!1})}catch{}document.querySelectorAll(".page-body .row[data-reveal]").forEach(n=>{const t=n.dataset.revealMode||"stagger",s=n.dataset.revealAnimation||"slide-up",o=parseInt(n.dataset.revealDuration,10)||400,e=parseInt(n.dataset.revealStagger,10)||60,a=parseInt(n.dataset.revealDelay,10)||0,f=n.dataset.revealDirection==="rtl",v=Array.from(n.children),i={"slide-up":"translateY(30px)","slide-down":"translateY(-30px)","slide-left":"translateX(30px)","slide-right":"translateX(-30px)",zoom:"scale(0.85)",flip:"perspective(600px) rotateX(15deg)"};v.forEach((u,h)=>{u.style.opacity="0",u.style.transform=i[s]||"",u.style.transition=`opacity ${o}ms ease, transform ${o}ms ease`;const b=f?v.length-1-h:h;u.style.transitionDelay=t==="stagger"?`${a+b*e}ms`:`${a}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const u=new IntersectionObserver(h=>{h.forEach(b=>{b.isIntersecting&&(b.target.offsetWidth,b.target.style.opacity="1",b.target.style.transform="none",u.unobserve(b.target))})},{threshold:.1});v.forEach(h=>u.observe(h))}))}),w.find(".card[data-collapsible]").each(function(){const n=this.querySelector(".card-header");n&&n.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),w.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const n=this.dataset.soTarget,t=document.getElementById(n);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 n=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(t=>{try{const s=P(t.dataset.config);Object.assign(n,s)}catch{}}),Object.keys(n).length>0){const t={};for(const[s,o]of Object.entries(n)){const e=o?.events?.click,{confirm:a,toast:f,alert:v,prompt:i,...u}=e||{};a||f||v||i?($(s).on("click",async function(b){if(b.preventDefault(),a&&!await E.confirm(a))return;let l=null;if(!(i&&(l=await E.prompt(i,{inputPlaceholder:u.promptPlaceholder||"",inputValue:u.promptDefault||""}),l===null))){if(u.target){const d=$(u.target);u.toggleClass&&d.toggleClass(u.toggleClass),u.addClass&&d.addClass(u.addClass),u.removeClass&&d.removeClass(u.removeClass),l!==null&&(u.setText&&d.text(l),u.setVal&&d.val(l),u.setAttr&&d.attr(u.setAttr,l))}u.href&&(window.location.href=u.href),f&&E.toast(f,{type:u.toastType||"success"}),v&&E.alert(v)}}),Object.keys(u).length&&(t[s]={...o,events:{...o.events,click:u}})):t[s]=o}$.setup(t)}}w.length&&j(w.get(0))}),window.wireCTAButtons=j;function j(p){p.querySelectorAll(".dm-cta-trigger").forEach(c=>{c.__ctaWired||(c.__ctaWired=!0,c.addEventListener("click",async()=>{const x=c.dataset.action,y=c.dataset.entry,L=c.dataset.confirm;let w=S.get("auth_token");if(!w){E.toast("Please log in to perform this action.",{type:"warning"});return}if(L&&!await E.confirm(L))return;const n=Array.from(c.childNodes).map(s=>s.cloneNode(!0));c.disabled=!0,c.textContent="Running\u2026";const t=s=>fetch(`/api/actions/${x}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify(c.dataset.scope?{entryId:y,scope:c.dataset.scope}:{entryId:y})});try{let s=await t(w);if(s.status===401){const e=S.get("auth_refresh_token");if(e){const a=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(a.ok){const{token:f}=await a.json();S.set("auth_token",f),w=f,s=await t(w)}}}const o=await s.json().catch(()=>({}));if(!s.ok||o.success===!1)throw new Error(o.error||o.message||`Error ${s.status}`);E.toast(o.message||"Action completed.",{type:"success"})}catch(s){E.toast(s.message||"Action failed.",{type:"error"})}finally{c.disabled=!1,c.textContent="",n.forEach(s=>c.appendChild(s)),Domma.icons.scan(c)}}))})}(function(){const c=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!c.length)return;const x=S.get("auth_token");function y(w,n){const t=document.createElement("div");t.className="dm-collection-display dm-collection-empty";const s=document.createElement("p");if(n){s.appendChild(document.createTextNode("Please "));const o=document.createElement("a");o.href="/login",o.textContent="sign in",s.appendChild(o),s.appendChild(document.createTextNode(" to view your entries."))}else s.textContent=w;return t.appendChild(s),t}function L(w,n){const t=document.createElement("template");t.innerHTML=n||"",w.replaceWith(t.content)}c.forEach(async w=>{const n=w.dataset.collectionAttrs;if(!n)return;if(!x){w.replaceChildren(y("",!0));return}const t=s=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify({attrs:n})});try{let s=x,o=await t(s);if(o.status===401){const a=S.get("auth_refresh_token");if(a){const f=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:a})});if(f.ok){const{token:v}=await f.json();S.set("auth_token",v),s=v,o=await t(s)}}}if(!o.ok){const{error:a}=await o.json().catch(()=>({}));w.replaceChildren(y(a||"Unable to load entries.",!1));return}const{html:e}=await o.json();L(w,e),window.DommaCollectionBrowser?.hydrate&&window.DommaCollectionBrowser.hydrate(document)}catch{w.replaceChildren(y("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),typeof Domma<"u"&&Domma.syntax?.scan&&Domma.syntax.scan(),j(document.body)},100)})(),(function(){const c=document.querySelectorAll("[data-collection-table]");!c.length||typeof T>"u"||!T.create||c.forEach(x=>{let y;try{y=P(x.dataset.payload)}catch{return}const{columns:L,rows:w,search:n,sortable:t,exportable:s,pageSize:o,empty:e,ctaConfig:a,slug:f,exportCfg:v}=y;if(!L?.length)return;if(a){const h=L.findIndex(b=>b.key==="_cta");h!==-1&&(L[h]={key:"_cta",title:"",render:(b,l)=>{const d=document.createElement("button");if(d.className=`btn btn-${a.style||"primary"} dm-cta-trigger`,d.dataset.action=a.action||"",a.scope&&(d.dataset.scope=a.scope),d.dataset.entry=l._entryId||"",a.confirm&&(d.dataset.confirm=a.confirm),a.icon){const m=document.createElement("span");m.dataset.icon=a.icon,d.appendChild(m),d.appendChild(document.createTextNode(" "))}return d.appendChild(document.createTextNode(a.label||"Run")),d}})}const i="col-table-"+Math.random().toString(36).slice(2,7),u=document.createElement("div");u.id=i,x.replaceChildren(u),T.create("#"+i,{data:w,columns:L,search:n,sortable:t,exportable:s,pageSize:o,emptyMessage:e}),a&&j(u),x._dmTable={columns:L,rows:w,slug:f,exportCfg:v,render(h){T.create("#"+i,{data:h,columns:L,search:n,sortable:t,exportable:s,pageSize:o,emptyMessage:e}),a&&j(u)}}})})(),(function(){const c=document.querySelectorAll("[data-form-inline]");if(!c.length||typeof F>"u")return;function x(t){const s=t&&t.logic;return!!(s&&(s.visibility||s.requirement))}function y(t,s){const o={};return(t||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const a=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),f=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":a?"chooser":e.type,v={...e.formConfig||{}};v.span==="full"&&s&&(v.span=s);const i={type:f,required:x(e)?!1:e.required,label:e.label,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{hint:e.helper},...e.tooltip&&{tooltip:e.tooltip},...v}};e.minLength!==void 0&&(i.minLength=e.minLength),e.maxLength!==void 0&&(i.maxLength=e.maxLength),e.min!==void 0&&(i.min=e.min),e.max!==void 0&&(i.max=e.max),a&&(e.variant&&(i.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(i.multiple=!0),e.density&&(i.density=e.density),e.columns&&(i.columns=e.columns),e.accent&&(i.accent=e.accent),e.accentStyle&&(i.accentStyle=e.accentStyle),e.glow&&(i.glow=!0),e.glowColour&&(i.glowColour=e.glowColour),e.shadow&&(i.shadow=e.shadow),e.shadowColour&&(i.shadowColour=e.shadowColour)),o[e.name]=i}),o}function L(t){const s={};return(t||[]).forEach(o=>{if(!(!o.name||o.type==="page-break"||o.type==="spacer")&&(o.type==="select"||o.type==="multiselect")&&o.required){const e=(o.options||[])[0];e&&(s[o.name]=typeof e=="object"?e.value:e)}}),s}function w(t,s){(s||[]).forEach(o=>{if(o.type!=="date"||!o.name)return;const e=t.querySelector(`[name="${o.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function n(t,s,o){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=o?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}c.forEach(t=>{let s;try{s=P(t.dataset.formInline)}catch{return}const o=s.fields||[],e=s.settings||{},a=Number(e.columns)===3?3:2,f=e.layout||"grid",v=o.some(d=>d.type==="page-break"),i=async d=>{if(t.dataset.triggerBlocked==="true"){const m=t._formLogicRuntime,g=m&&m.submitBlock?m.submitBlock().reasons:[];n(t,g.join(" ")||"This form cannot be submitted yet.",!0);const C=new Error("Blocked by a form trigger");throw C.formSubmitFailed=!0,C}try{const m=t.querySelector(".fb-form-honeypot"),g=m?.querySelector('input[type="text"]')||null,C=m?.querySelector('[name="_t"]')||null,M=Object.assign({},d);delete M._t,m?.dataset.hpName&&delete M[m.dataset.hpName],g!==null&&(M._hp=g.value),C!==null&&(M._t=C.value);const D=await H.post(`/api/forms/submit/${s.slug}`,M);if(D&&D.error){n(t,D.error,!0);const z=new Error(D.error);throw z.formSubmitFailed=!0,z}if(D?.redirect){window.location.href=D.redirect;return}for(;t.firstChild;)t.removeChild(t.firstChild);n(t,D?.message||e.successMessage||"Thank you for your submission.",!1)}catch(m){throw m.formSubmitFailed||n(t,m.message||"Submission failed. Please try again.",!0),m}},u=Date.now();function h(d){const m=d.querySelector("form");if(!m||m.querySelector(".fb-form-honeypot"))return;const g=document.createElement("div");g.className="fb-form-honeypot",g.setAttribute("aria-hidden","true");const C=document.createElement("input");C.name=m.querySelector('[name="website"]')?"_website":"website",g.dataset.hpName=C.name,C.type="text",C.tabIndex=-1,C.autocomplete="url",C.placeholder="https://",g.appendChild(C);const M=document.createElement("input");M.name="_t",M.type="hidden",M.value=u,g.appendChild(M),m.appendChild(g)}function b(d){if(!window.FormLogicEngine||!o.some(g=>g.logic||Array.isArray(g.triggers)&&g.triggers.length))return null;const m=new window.FormLogicEngine.FormLogicRuntime(s,t,d?{baseValues:d}:void 0);if(m.init(),t._formLogicRuntime=m,t._formLogicObserved)return m;if(t._formLogicObserved=!0,t.parentNode&&typeof MutationObserver<"u"){const g=new MutationObserver(function(C){for(const M of C)for(const D of M.removedNodes)if(D===t||D.nodeType===1&&D.contains&&D.contains(t)){t._formLogicRuntime&&t._formLogicRuntime.destroy(),g.disconnect();return}});g.observe(t.parentNode,{childList:!0,subtree:!1}),t._formLogicObserver=g}return m}let l=!1;if(t.addEventListener("dm-form-end",d=>{const m=d.detail?.validation;if(m&&!m.valid){G(t,m.missing),K(m);return}l||(l=!0,i(d.detail?.values||{}).catch(()=>{}).finally(()=>{l=!1}))}),v&&F.wizard){const d=re(o,s.title),m=d.map(I=>({title:I.title,description:I.description,fields:y(I.fields,a)})),g=d.map(I=>I.fields);let C=0,M=null,D=null;const z=I=>{const r=window.FormLogicEngine;if(!r||!r.validateFields)return;const A=r.validateFields(t,g[C]||[]);A.valid||(I.preventDefault(),I.stopImmediatePropagation(),G(t,A.missing),K(A))},B=()=>{const I=g[C]||o;le(t,e),Q(t,e,I),w(t,I),U(t,I),e.honeypot!==!1&&h(t),M&&M.destroy(),M=b(D?.getStepData?D.getStepData():void 0),t.querySelectorAll(".wizard-next, .wizard-submit").forEach(r=>r.addEventListener("click",z,!0))};D=F.wizard(t,{schema:{steps:m},layout:f,columns:a,finishText:e.submitText||"Finish",onSubmit:i,onStepChange:I=>{C=I,setTimeout(B,0)}}),t.addEventListener("dm-form-trigger",I=>{if(I.detail?.type!=="goto-step"||!D?.goToStep)return;const r=Number(I.detail.action.step);!Number.isFinite(r)||r<0||r>=m.length||r!==C&&D.goToStep(r)}),Promise.resolve(D).then(B)}else if(F.render){const d=F.render(t,y(o,a),L(o),{submitText:e.submitText||"Submit",layout:f,columns:a,onSubmit:i});Promise.resolve(d).then(function(){ce(t,e,f),Q(t,e,o),w(t,o),U(t,o),e.honeypot!==!1&&h(t),b()})}})})();function de(p,c){const x=document.body,y=x.classList,L=new Set((x.dataset.railSite||"").split(" ").filter(Boolean)),w=()=>window.matchMedia("(min-width: 993px)").matches;p.querySelectorAll(".navbar-link, .navbar-dropdown-toggle").forEach(a=>{const f=a.textContent.trim();f&&(a.setAttribute("title",f),a.querySelector("svg, [data-icon]")||(a.dataset.initial=f.charAt(0).toUpperCase()))});const n=document.createElement("button");n.type="button",n.className="dm-nav-collapse-toggle";const t=()=>{const a=y.contains("dm-vnav-collapsed");n.setAttribute("aria-pressed",String(a)),n.setAttribute("aria-label",a?"Expand menu":"Collapse menu"),n.title=n.getAttribute("aria-label");const f=c?"chevrons-left":"chevrons-right",v=c?"chevrons-right":"chevrons-left";n.innerHTML=`<span data-icon="${a?f:v}"></span>`,window.Domma&&Domma.icons&&Domma.icons.scan(n)},s=a=>{y.toggle("dm-vnav-collapsed",a),S.set("nav_collapsed",a),t()};n.addEventListener("click",()=>s(!y.contains("dm-vnav-collapsed"))),t();const o=p.querySelector(".navbar-container")||p;o.insertBefore(n,o.firstChild?.nextSibling||null);const e=a=>{y.toggle("dm-vnav-autohide",a),y.toggle("dm-vnav-collapsible",!a&&L.has("collapsible")),a?y.remove("dm-vnav-collapsed"):L.has("collapsible")&&S.get("nav_collapsed")===!0&&y.add("dm-vnav-collapsed"),p.classList.remove("dm-nav-revealed"),t()};p.addEventListener("pointerdown",a=>{y.contains("dm-vnav-autohide")&&a.pointerType!=="mouse"&&!p.classList.contains("dm-nav-revealed")&&(p.classList.add("dm-nav-revealed"),a.preventDefault())}),document.addEventListener("pointerdown",a=>{p.contains(a.target)||p.classList.remove("dm-nav-revealed")}),window.E&&typeof E.contextMenu=="function"&&E.contextMenu(p,{items:()=>{const a=y.contains("dm-vnav-autohide"),f=y.contains("dm-vnav-collapsed"),v=S.has("nav_autohide")||S.has("nav_collapsed");return[{header:"Menu"},{label:f?"Expand menu":"Collapse to icons",icon:f?"maximize":"minimize",disabled:!w()||a||!L.has("collapsible"),action:()=>s(!f)},{label:a?"Keep on screen":"Hide when not in use",icon:a?"eye":"eye-off",disabled:!w(),action:()=>{S.set("nav_autohide",!a),e(!a)}},{type:"divider"},{label:"Reset to the site's setting",icon:"refresh",disabled:!v,action:()=>{S.remove("nav_autohide"),S.remove("nav_collapsed"),e(L.has("autohide")),y.toggle("dm-vnav-collapsed",!L.has("autohide")&&L.has("start-collapsed")),t()}}]}})}function me(p){p.classList.add("dm-nav-scroll-hide");let c=window.scrollY,x=!1;window.addEventListener("scroll",()=>{x||(x=!0,requestAnimationFrame(()=>{x=!1;const y=window.scrollY,L=p.matches(":focus-within")||p.querySelector(".navbar-dropdown.open, .navbar-collapse.show");y>c&&y>p.offsetHeight&&!L?p.classList.add("dm-nav-scrolled-away"):y<c&&p.classList.remove("dm-nav-scrolled-away"),c=y}))},{passive:!0})}function ue(p){let c={};try{c=JSON.parse(p.dataset.contents||"{}")}catch{}const x=p.closest(".site-main"),y=document.querySelector(".site-content"),L=new Set(String(c.exclude||"").split(",").map(i=>i.trim().toLowerCase()).filter(Boolean)),w=i=>!i.closest(".hero, .card, .accordion, .tabs, figure, #site-sidebar"),n=i=>(y?[...y.querySelectorAll(i)]:[]).filter(w).filter(u=>!L.has(u.textContent.trim().toLowerCase())),t=c.headings==="h2"?n("h2"):c.headings==="h3"?n("h3"):c.headings==="h2h3"?n("h2, h3"):n("h2").length?n("h2"):n("h3");if(!t.length){x?.classList.add("no-sidebar-items");return}const s=new Set([...document.querySelectorAll("[id]")].map(i=>i.id)),o=document.createElement("nav");o.className="dm-contents",o.setAttribute("aria-label",c.title||"Page contents"),c.hover&&(o.dataset.menuHover=c.hover),c.selected&&(o.dataset.menuActive=c.selected),c.accent&&o.style.setProperty("--dm-menu-highlight",c.accent.startsWith("#")?c.accent:`var(--dm-${c.accent})`),c.font&&(o.style.fontFamily=c.font),c.fontSize&&(o.style.fontSize=c.fontSize),c.fontWeight&&(o.style.fontWeight=c.fontWeight);const e=document.createElement("div");e.className="dm-contents-head";const a=document.createElement("p");a.className="dm-contents-title",a.textContent=c.title||"",e.appendChild(a),o.appendChild(e);const f=document.createElement("ol");f.className="dm-contents-list"+(c.numbered?" is-numbered":"");const v=[];for(const i of t){if(!i.id){const b=(i.textContent||"").toLowerCase().trim().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"section";let l=b;for(let d=2;s.has(l);d++)l=`${b}-${d}`;s.add(l),i.id=l}const u=document.createElement("li");u.className=`dm-contents-item dm-contents-${i.tagName.toLowerCase()}`;const h=document.createElement("a");h.href=`#${i.id}`,h.textContent=i.textContent.trim(),u.appendChild(h),f.appendChild(u),v.push([i,h])}if(o.appendChild(f),p.replaceChildren(o),c.scrollSpy!==!1&&"IntersectionObserver"in window){const i=new Map,u=()=>{const b=v.find(([l])=>i.get(l))||null;v.forEach(([,l])=>{const d=b&&l===b[1];l.classList.toggle("is-active",!!d),d?l.setAttribute("aria-current","location"):l.removeAttribute("aria-current")})},h=new IntersectionObserver(b=>{b.forEach(l=>i.set(l.target,l.isIntersecting)),u()},{rootMargin:"-80px 0px -60% 0px"});v.forEach(([b])=>h.observe(b))}if(x?.classList.contains("contents-collapsible")){const i=document.createElement("button");i.type="button",i.className="dm-contents-toggle";const u=()=>{const h=x.classList.contains("contents-collapsed");i.setAttribute("aria-expanded",String(!h)),i.setAttribute("aria-label",h?"Show the contents":"Hide the contents"),i.title=i.getAttribute("aria-label"),i.innerHTML=`<span data-icon="${h?"list":"x"}"></span>`,window.Domma?.icons&&Domma.icons.scan(i)};i.addEventListener("click",()=>{S.set("contents_collapsed",x.classList.toggle("contents-collapsed")),u()}),u(),e.insertBefore(i,e.firstChild)}if(x?.classList.contains("contents-floating")&&c.movable){o.classList.add("is-movable");const i=S.get("contents_position");i&&Number.isFinite(i.x)&&Object.assign(p.style,{left:`${i.x}px`,top:`${i.y}px`,right:"auto",bottom:"auto"}),e.addEventListener("pointerdown",u=>{if(u.target.closest("button"))return;const h=p.getBoundingClientRect(),b=u.clientX-h.left,l=u.clientY-h.top,d=g=>{const C=Math.max(0,Math.min(window.innerWidth-h.width,g.clientX-b)),M=Math.max(0,Math.min(window.innerHeight-40,g.clientY-l));Object.assign(p.style,{left:`${C}px`,top:`${M}px`,right:"auto",bottom:"auto"})},m=()=>{document.removeEventListener("pointermove",d),S.set("contents_position",{x:parseInt(p.style.left,10),y:parseInt(p.style.top,10)})};document.addEventListener("pointermove",d),document.addEventListener("pointerup",m,{once:!0})})}window.E&&typeof E.contextMenu=="function"&&E.contextMenu(p,{items:()=>{const i=x?.classList.contains("contents-collapsible"),u=x?.classList.contains("contents-collapsed"),h=v.find(([,b])=>b.classList.contains("is-active"));return[{header:"Contents"},...i?[{label:u?"Show the contents":"Collapse to a button",icon:u?"list":"minimize",action:()=>p.querySelector(".dm-contents-toggle")?.click()}]:[],{label:"Copy a link to this section",icon:"link",disabled:!h,action:async()=>{const b=`${location.origin}${location.pathname}#${h[0].id}`;try{await _.copyToClipboard(b),E.toast("Link copied.",{type:"success"})}catch{E.toast("Could not copy the link.",{type:"error"})}}},{label:"Back to the top",icon:"arrow-up",action:()=>window.scrollTo({top:0,behavior:"smooth"})}]}}),x?.classList.contains("contents-autohide")&&(p.addEventListener("pointerdown",i=>{i.pointerType!=="mouse"&&!p.classList.contains("is-revealed")&&(p.classList.add("is-revealed"),i.preventDefault())}),document.addEventListener("pointerdown",i=>{p.contains(i.target)||p.classList.remove("is-revealed")}))}
1
+ import{decodeBase64Json as P}from"/public/js/utf8-base64.mjs";import{makeBadgeEl as Y,applyColour as J,applyPill as te,applyFloat as ne,applyHighlight as R,applyItemHighlight as oe,itemHighlightAttrs as ae,isCurrentItem as X,menuAlign as se,applyClasses as O,cssClasses as W}from"/public/js/menu-decor.mjs?v=20261009-hover-darken";import{initFloatingMenus as ie}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as U,markInvalidFields as G,showValidationPopup as K}from"/public/js/forms.js?v=20261009-form-classes";import{buildWizardSteps as re}from"/public/js/form-steps.mjs";import{applyFormClasses as Q,applySubmitButtons as ce,applyWizardButtons as le}from"/public/js/form-submit.mjs";document.addEventListener("click",p=>{const l=p.target?.closest?.(".dm-banner__dismiss");l&&(p.preventDefault(),l.closest(".dm-banner")?.remove())}),$(()=>{const p=window.__CMS_NAV__||{},l=window.__CMS_SITE__||{};if(l.autoTheme?.enabled){let n=function(e){const a=(e||"07:00").split(":");return+a[0]*60+(+a[1]||0)},s=function(){const e=new Date,a=e.getHours()*60+e.getMinutes();return a>=n(o.dayStart)&&a<n(o.nightStart)?o.dayTheme:o.nightTheme},t=function(e){const a=o.apply&&o.apply[e]||{theme:e,custom:""};Domma.theme.set(a.theme);for(const f of[...document.body.classList])f.startsWith("dm-custom-")&&document.body.classList.remove(f);a.custom&&document.body.classList.add(a.custom)};const o=l.autoTheme;t(s()),setInterval(()=>t(s()),6e4)}const x=$("#site-navbar");if(x.length&&p.brand){let s=function(d,m=0){return Array.isArray(d)?d.filter(u=>!(u&&(u.type==="spacer"||u.type==="theme-switcher"))).filter(u=>!(u&&u.type==="separator"&&m===0)).map(u=>u&&u.type==="separator"?{divider:!0}:Array.isArray(u?.items)?{...u,items:s(u.items,m+1)}:u):d},t=function(d){return Array.isArray(d)?d.filter(m=>!(m&&(m.type==="separator"||m.type==="spacer"||m.type==="theme-switcher"))):[]};const o={...p.brand},n=o.size&&o.size!=="md"?` navbar-brand-${o.size}`:"";if(o.logo||o.icon||o.tagline){let d="";o.logo?d+=`<img src="${o.logo}" class="navbar-brand-logo" alt="${o.text||""}">`:o.icon&&(d+=`<span data-icon="${o.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),o.text&&(d+=`<span class="navbar-brand-text${n}">${o.text}</span>`),o.tagline&&(d+=`<small class="navbar-brand-tagline">${o.tagline}</small>`),o.html=d}else n&&o.text&&(o.html=`<span class="navbar-brand-text${n}">${o.text}</span>`);const e=p.position==="floating",a=p.orientation==="vertical";Domma.elements.navbar("#site-navbar",{brand:o,items:s(p.items||[]),variant:p.variant||"dark",position:e?"static":p.position||"sticky",collapsible:!0,appearOnHover:!!p.appearOnHover}),Domma.icons.scan("#site-navbar");const f=x.get(0);f.classList.add(`dm-nav-align-${se(p)||"centre"}`),a&&x.get(0).classList.add("dm-nav-vertical",p.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left");const v=p.dock||{};a&&!e&&de(f,p.side==="right"),!a&&v.hideOnScroll&&me(f),R(f,p),O(f,p.class),e&&(ne(x.get(0),p),ie(x.get(0).parentNode||document));const i=f.querySelector(".navbar-toggle"),c=f.querySelector(".navbar-collapse");if(i&&c){let d=null;const m=()=>{c.classList.contains("show")&&i.click()},u=()=>{d&&(d.remove(),d=null),document.body.classList.remove("dm-drawer-open")},g=()=>{d||(d=document.createElement("div"),d.className="dm-navbar-backdrop",d.addEventListener("click",m),document.body.appendChild(d),document.body.classList.add("dm-drawer-open"))};i.addEventListener("click",()=>{requestAnimationFrame(()=>{c.classList.contains("show")?g():u()})}),c.addEventListener("click",C=>{C.target.closest("a")&&requestAnimationFrame(u)}),document.addEventListener("keydown",C=>{C.key==="Escape"&&c.classList.contains("show")&&m()})}const h=f.querySelector(".navbar-nav");if(h){const d=Array.from(h.children);let m=0;for(const u of p.items||[])if(!(u&&u.type==="separator")){if(u&&u.type==="spacer"){const g=document.createElement("li"),C=["xs","sm","md","lg","xl","flex"].includes(u.size)?u.size:"md";g.className=`dm-menu-spacer dm-menu-spacer--${C}`,g.setAttribute("aria-hidden","true"),h.insertBefore(g,d[m]||null);continue}if(u&&u.type==="theme-switcher"){const g=document.createElement("li");g.className="dm-menu-theme-switcher";const C=document.createElement("span");C.dataset.themeSwitcher="menu",C.dataset.position="inline",g.appendChild(C),h.insertBefore(g,d[m]||null);continue}m++}window.CMSThemeSwitcher?.scan(h)}const b=t(p.items||[]);f.querySelectorAll(".navbar-link[data-index], .navbar-dropdown-item[data-index]").forEach(d=>{const m=b[parseInt(d.dataset.index,10)];if(!m)return;const u=d.dataset.subindex!=null,g=u&&Array.isArray(m.items)?m.items[parseInt(d.dataset.subindex,10)]:m;if(!g)return;g.colour&&J(d,g.colour),g.pill&&!u&&te(d,g.pill),oe(d,g),O(d.closest("li"),g.class);const C=Y(g.badge);C&&d.appendChild(C),X(g.url,location.pathname)&&(d.classList.add("active"),d.setAttribute("aria-current","page"))})}const y=$("#site-footer");if(y.length){let e=function(r){return String(r??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")},a=function(r){return e(r).replace(/'/g,"&#39;")},f=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>`;if(r&&r.type==="theme-switcher")return'<li class="dm-menu-theme-switcher"><span data-theme-switcher="menu" data-position="inline"></span></li>';const A=a(r.url||"#"),k=e(r.text||""),q=Array.isArray(r.items)&&r.items.length?`<ul>${r.items.map(f).join("")}</ul>`:"",N=ae(r),Z=[N.hover&&` data-menu-item-hover="${a(N.hover)}"`,N.active&&` data-menu-item-active="${a(N.active)}"`].filter(Boolean).join(""),ee=N.accent?` style="--dm-menu-highlight:${a(N.accent)}"`:"",V=W(r.class).join(" ");return`<li${V?` class="${a(V)}"`:""}><a href="${A}"${Z}${ee}>${k}</a>${q}</li>`},v=function(){if(!l.footer)return"";const r=Object.keys(n).filter(k=>o[k]);if(!r.length)return"";const A=W(l.footer.socialClass).join(" ");return`<div class="footer-social">${r.map(k=>{const{label:q,svg:N}=n[k];return`<a href="${o[k]}" target="_blank" rel="noopener noreferrer" aria-label="${q}" class="footer-social-link${A?` ${a(A)}`:""}">${N}</a>`}).join("")}</div>`},b=function(r){if(!r||r.type==="separator"||r.type==="spacer")return"";if(r.type==="theme-switcher")return`<li class="cms-footer-col">${f(r).replace(/^<li[^>]*>|<\/li>$/g,"")}</li>`;const A=Array.isArray(r.items)&&r.items.length?`<ul>${r.items.map(f).join("")}</ul>`:"",k=h(r)?`<span class="cms-footer-col-title">${e(r.text||"")}</span>`:`<a class="cms-footer-col-title" href="${a(r.url||"#")}">${e(r.text||"")}</a>`,q=["cms-footer-col",...W(r.class)].join(" ");return`<li class="${a(q)}">${k}${A}</li>`},m=function(r,A,k=!1){return(r||[]).forEach(q=>{q&&(q.type==="separator"||q.type==="spacer"||q.type==="theme-switcher")||(k&&h(q)||A.push(q),Array.isArray(q.items)&&q.items.length&&m(q.items,A))}),A};const o=l.social||{},n={twitter:{label:"X / Twitter",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.742l7.73-8.835L1.254 2.25H8.08l4.259 5.629L18.244 2.25zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>'},facebook:{label:"Facebook",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>'},instagram:{label:"Instagram",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"/></svg>'},linkedin:{label:"LinkedIn",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>'},github:{label:"GitHub",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>'},youtube:{label:"YouTube",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.495 6.205a3.007 3.007 0 00-2.088-2.088c-1.87-.501-9.396-.501-9.396-.501s-7.507-.01-9.396.501A3.007 3.007 0 00.527 6.205a31.247 31.247 0 00-.522 5.805 31.247 31.247 0 00.522 5.783 3.007 3.007 0 002.088 2.088c1.868.502 9.396.502 9.396.502s7.506 0 9.396-.502a3.007 3.007 0 002.088-2.088 31.247 31.247 0 00.5-5.783 31.247 31.247 0 00-.5-5.805zM9.609 15.601V8.408l6.264 3.602z"/></svg>'}};let s='<div class="footer-inner container">';const t=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""},i=["columns","row"].includes(l.footer?.layout)?l.footer.layout:"auto",c=i==="columns"||i==="auto"&&t.primary.some(r=>Array.isArray(r?.items)&&r.items.length),h=r=>c&&!r.url&&Array.isArray(r.items)&&r.items.length>0;if(c){const r=p.brand||{},A=r.text||l.title||"",k=r.logo?`<img src="${a(r.logo)}" class="cms-footer-brand-logo" alt="">`:r.icon?'<span class="cms-footer-brand-icon"></span>':"",q=String(l.footer?.blurb||"").trim();s='<div class="footer-inner footer-inner--columns container"><div class="cms-footer-top">',s+=`<div class="cms-footer-brand">${A||k?`<a class="cms-footer-brand-name" href="${a(r.url||"/")}">${k}<span>${e(A)}</span></a>`:""}${q?`<p class="cms-footer-blurb">${e(q)}</p>`:""}${v()}</div>`,t.primary.length&&(s+=`<nav class="footer-nav footer-nav--primary footer-nav--columns" aria-label="Footer"><ul>${t.primary.map(b).join("")}</ul></nav>`),s+='</div><div class="cms-footer-bottom">',t.copyright&&(s+=`<p class="cms-footer-copyright">${e(t.copyright)}</p>`),t.legal.length&&(s+=`<nav class="footer-nav footer-nav--legal" aria-label="Legal"><ul>${t.legal.map(f).join("")}</ul></nav>`),s+="</div>"}else t.copyright&&(s+=`<p>${e(t.copyright)}</p>`),t.primary.length&&(s+=`<nav class="footer-nav footer-nav--primary"><ul>${t.primary.map(f).join("")}</ul></nav>`),t.legal.length&&(s+=`<nav class="footer-nav footer-nav--legal"><ul>${t.legal.map(f).join("")}</ul></nav>`),s+=v();s+="</div>",y.html(s),window.CMSThemeSwitcher?.scan(y.get(0));const d=y.get(0).querySelector(".cms-footer-brand-icon");d&&p.brand?.icon&&d.setAttribute("data-icon",p.brand.icon),O(y.get(0),l.footer?.class);const u=[...m(t.primary,[],!0),...m(t.legal,[])];y.get(0).querySelectorAll(".footer-nav a").forEach((r,A)=>{const k=u[A];if(!k)return;k.colour&&J(r,k.colour);const q=Y(k.badge);q&&r.appendChild(q),X(k.url,location.pathname)&&(r.classList.add("is-active"),r.setAttribute("aria-current","page"))}),R(y.get(0).querySelector(".footer-nav--primary"),{highlight:t.primaryHighlight}),R(y.get(0).querySelector(".footer-nav--legal"),{highlight:t.legalHighlight}),O(y.get(0).querySelector(".footer-nav--primary"),t.primaryClass),O(y.get(0).querySelector(".footer-nav--legal"),t.legalClass);const C=l.footer?.showMotionToggle!==!1,M=S.get("reduced_motion"),D=l.footer?.motionDefault,z=M!=null?!!M:D==="on"?!0:D==="off"?!1:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),B=l.footer?.motionControl==="checkbox",I=y.get(0).querySelector(".cms-footer-bottom")||y.get(0).querySelector(".footer-inner");if(C&&I){const r=document.createElement("input");r.type="checkbox",r.className=B?"footer-motion-checkbox":"form-switch-input",r.id="dm-motion-switch",r.checked=z,r.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const A=document.createElement("span");A.className=B?"footer-motion-label":"form-switch-label",A.textContent="Reduce motion";const k=document.createElement("label");k.className=B?"footer-motion-check":"form-switch footer-motion-switch",k.title="Reduce motion",k.appendChild(r),k.appendChild(A),I.appendChild(k)}}const L=document.getElementById("site-sidebar");L&&ue(L),Domma.icons.scan(),Domma.syntax?.scan&&Domma.syntax.scan();const w=$(".page-body");if(w.length){w.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),w.find(".tabs").each(function(){Domma.elements.tabs(this)}),w.find(".carousel").each(function(){const 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 n=parseInt(this.dataset.animationDuration,10);Number.isFinite(n)&&(o.animationDuration=n)}this.dataset.animationEasing&&(o.animationEasing=this.dataset.animationEasing),this.dataset.ctx&&(this._dmCtxSlides=Array.from(this.querySelectorAll(".carousel-slide")).map(n=>n.cloneNode(!0)),this._dmCarouselOpts=o),this._dmCarousel=Domma.elements.carousel(this,o)}),w.find(".dm-countdown").each(function(){const o={autoStart:!0},n=String(this.dataset.format||"").trim(),s=String(this.dataset.to||"").trim(),t=/^(\d{4})-(\d{2})-(\d{2})$/.exec(s),e=t?new Date(+t[1],+t[2]-1,+t[3]).getTime():s?new Date(s).getTime():NaN;Number.isFinite(e)?o.duration=Math.max(0,e-Date.now()):this.dataset.duration&&(o.duration=(parseInt(this.dataset.duration,10)||0)*1e3);const a=/^dd:/i.test(n);o.format=a?"hh:mm:ss":n||(o.duration>=36e5?"hh:mm:ss":"mm:ss");const f=this,v=c=>String(c).padStart(2,"0"),i=c=>{const h=Math.max(0,Math.floor(c/1e3)),b=f.querySelector(".dm-timer-time");b&&(b.textContent=`${v(Math.floor(h/86400))}:${v(Math.floor(h%86400/3600))}:${v(Math.floor(h%3600/60))}:${v(h%60)}`)};a&&(o.onTick=i),Domma.elements.timer(this,o),a&&i(o.duration||0)}),w.find("[data-tooltip]").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),w.find(".dm-progression").each(function(){const o=Array.from(this.querySelectorAll(".dm-progression-item")).map(t=>{const e={title:t.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:t.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return t.dataset.date&&(e.date=t.dataset.date),t.dataset.status&&(e.status=t.dataset.status),t.dataset.eventIcon&&(e.icon=t.dataset.eventIcon),t.dataset.entryId&&(e._entryId=t.dataset.entryId),e}),n=this.dataset.layout==="centred"?"centered":this.dataset.layout||"vertical",s=Domma.elements.progression(this,{layout:n,theme:this.dataset.theme||"minimal",mode:this.dataset.mode||"timeline",statusIcons:!0,allowHtml:!0,...o.length?{items:o}:{}});if((this.dataset.mode||"timeline")==="timeline"){const t={completed:{label:"Completed",icon:"check-circle",tone:"success"},planned:{label:"Planned",icon:"circle",tone:"secondary"},"in-progress":{label:"In progress",icon:"clock",tone:"primary"},blocked:{label:"Blocked",icon:"alert-circle",tone:"danger"}};this.querySelectorAll(".dm-progression-item").forEach((e,a)=>{const f=o[a];if(!f)return;const v=e.querySelector(".dm-progression-title");if(f.icon&&v){const c=document.createElement("span");c.dataset.icon=f.icon,c.style.cssText="display:inline-flex;vertical-align:middle;margin-right:.4em",c.setAttribute("aria-hidden","true"),v.prepend(c)}const i=t[f.status];if(i&&v){const c=document.createElement("span");c.className=`badge badge-${i.tone} cms-timeline-status`,c.style.cssText="display:inline-flex;align-items:center;gap:.35em;margin:.4rem 0";const h=document.createElement("span");h.dataset.icon=i.icon,h.dataset.iconSize="14",h.setAttribute("aria-hidden","true"),c.append(h,document.createTextNode(i.label)),v.after(c)}})}Domma.icons.scan(this),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 n=o.dataset.revealMode||"stagger",s=o.dataset.revealAnimation||"slide-up",t=parseInt(o.dataset.revealDuration,10)||400,e=parseInt(o.dataset.revealStagger,10)||60,a=parseInt(o.dataset.revealDelay,10)||0,f=o.dataset.revealDirection==="rtl",v=Array.from(o.children),i={"slide-up":"translateY(30px)","slide-down":"translateY(-30px)","slide-left":"translateX(30px)","slide-right":"translateX(-30px)",zoom:"scale(0.85)",flip:"perspective(600px) rotateX(15deg)"};v.forEach((c,h)=>{c.style.opacity="0",c.style.transform=i[s]||"",c.style.transition=`opacity ${t}ms ease, transform ${t}ms ease`;const b=f?v.length-1-h:h;c.style.transitionDelay=n==="stagger"?`${a+b*e}ms`:`${a}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const c=new IntersectionObserver(h=>{h.forEach(b=>{b.isIntersecting&&(b.target.offsetWidth,b.target.style.opacity="1",b.target.style.transform="none",c.unobserve(b.target))})},{threshold:.1});v.forEach(h=>c.observe(h))}))}),w.find(".card[data-collapsible]").each(function(){const o=this.querySelector(".card-header");o&&o.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),w.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const o=this.dataset.soTarget,n=document.getElementById(o);if(!n)return;const s=E.slideover({title:n.dataset.soTitle||"",size:n.dataset.soSize||"md",position:n.dataset.soPosition||"right"});n.style.display="",s.setContent(n),s.open()})})}if(typeof $.setup=="function"){const o=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(n=>{try{const s=P(n.dataset.config);Object.assign(o,s)}catch{}}),Object.keys(o).length>0){const n={};for(const[s,t]of Object.entries(o)){const e=t?.events?.click,{confirm:a,toast:f,alert:v,prompt:i,...c}=e||{};a||f||v||i?($(s).on("click",async function(b){if(b.preventDefault(),a&&!await E.confirm(a))return;let d=null;if(!(i&&(d=await E.prompt(i,{inputPlaceholder:c.promptPlaceholder||"",inputValue:c.promptDefault||""}),d===null))){if(c.target){const m=$(c.target);c.toggleClass&&m.toggleClass(c.toggleClass),c.addClass&&m.addClass(c.addClass),c.removeClass&&m.removeClass(c.removeClass),d!==null&&(c.setText&&m.text(d),c.setVal&&m.val(d),c.setAttr&&m.attr(c.setAttr,d))}c.href&&(window.location.href=c.href),f&&E.toast(f,{type:c.toastType||"success"}),v&&E.alert(v)}}),Object.keys(c).length&&(n[s]={...t,events:{...t.events,click:c}})):n[s]=t}$.setup(n)}}w.length&&j(w.get(0))}),window.wireCTAButtons=j;function j(p){p.querySelectorAll(".dm-cta-trigger").forEach(l=>{l.__ctaWired||(l.__ctaWired=!0,l.addEventListener("click",async()=>{const x=l.dataset.action,y=l.dataset.entry,L=l.dataset.confirm;let w=S.get("auth_token");if(!w){E.toast("Please log in to perform this action.",{type:"warning"});return}if(L&&!await E.confirm(L))return;const o=Array.from(l.childNodes).map(s=>s.cloneNode(!0));l.disabled=!0,l.textContent="Running\u2026";const n=s=>fetch(`/api/actions/${x}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify(l.dataset.scope?{entryId:y,scope:l.dataset.scope}:{entryId:y})});try{let s=await n(w);if(s.status===401){const e=S.get("auth_refresh_token");if(e){const a=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(a.ok){const{token:f}=await a.json();S.set("auth_token",f),w=f,s=await n(w)}}}const t=await s.json().catch(()=>({}));if(!s.ok||t.success===!1)throw new Error(t.error||t.message||`Error ${s.status}`);E.toast(t.message||"Action completed.",{type:"success"})}catch(s){E.toast(s.message||"Action failed.",{type:"error"})}finally{l.disabled=!1,l.textContent="",o.forEach(s=>l.appendChild(s)),Domma.icons.scan(l)}}))})}(function(){const l=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!l.length)return;const x=S.get("auth_token");function y(w,o){const n=document.createElement("div");n.className="dm-collection-display dm-collection-empty";const s=document.createElement("p");if(o){s.appendChild(document.createTextNode("Please "));const t=document.createElement("a");t.href="/login",t.textContent="sign in",s.appendChild(t),s.appendChild(document.createTextNode(" to view your entries."))}else s.textContent=w;return n.appendChild(s),n}function L(w,o){const n=document.createElement("template");n.innerHTML=o||"",w.replaceWith(n.content)}l.forEach(async w=>{const o=w.dataset.collectionAttrs;if(!o)return;if(!x){w.replaceChildren(y("",!0));return}const n=s=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify({attrs:o})});try{let s=x,t=await n(s);if(t.status===401){const a=S.get("auth_refresh_token");if(a){const f=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:a})});if(f.ok){const{token:v}=await f.json();S.set("auth_token",v),s=v,t=await n(s)}}}if(!t.ok){const{error:a}=await t.json().catch(()=>({}));w.replaceChildren(y(a||"Unable to load entries.",!1));return}const{html:e}=await t.json();L(w,e),window.DommaCollectionBrowser?.hydrate&&window.DommaCollectionBrowser.hydrate(document)}catch{w.replaceChildren(y("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),typeof Domma<"u"&&Domma.syntax?.scan&&Domma.syntax.scan(),j(document.body)},100)})(),(function(){const l=document.querySelectorAll("[data-collection-table]");!l.length||typeof T>"u"||!T.create||l.forEach(x=>{let y;try{y=P(x.dataset.payload)}catch{return}const{columns:L,rows:w,search:o,sortable:n,exportable:s,pageSize:t,empty:e,ctaConfig:a,slug:f,exportCfg:v}=y;if(!L?.length)return;if(a){const h=L.findIndex(b=>b.key==="_cta");h!==-1&&(L[h]={key:"_cta",title:"",render:(b,d)=>{const m=document.createElement("button");if(m.className=`btn btn-${a.style||"primary"} dm-cta-trigger`,m.dataset.action=a.action||"",a.scope&&(m.dataset.scope=a.scope),m.dataset.entry=d._entryId||"",a.confirm&&(m.dataset.confirm=a.confirm),a.icon){const u=document.createElement("span");u.dataset.icon=a.icon,m.appendChild(u),m.appendChild(document.createTextNode(" "))}return m.appendChild(document.createTextNode(a.label||"Run")),m}})}const i="col-table-"+Math.random().toString(36).slice(2,7),c=document.createElement("div");c.id=i,x.replaceChildren(c),T.create("#"+i,{data:w,columns:L,search:o,sortable:n,exportable:s,pageSize:t,emptyMessage:e}),a&&j(c),x._dmTable={columns:L,rows:w,slug:f,exportCfg:v,render(h){T.create("#"+i,{data:h,columns:L,search:o,sortable:n,exportable:s,pageSize:t,emptyMessage:e}),a&&j(c)}}})})(),(function(){const l=document.querySelectorAll("[data-form-inline]");if(!l.length||typeof F>"u")return;function x(n){const s=n&&n.logic;return!!(s&&(s.visibility||s.requirement))}function y(n,s){const t={};return(n||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const a=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),f=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":a?"chooser":e.type,v={...e.formConfig||{}};v.span==="full"&&s&&(v.span=s);const i={type:f,required:x(e)?!1:e.required,label:e.label,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{hint:e.helper},...e.tooltip&&{tooltip:e.tooltip},...v}};e.minLength!==void 0&&(i.minLength=e.minLength),e.maxLength!==void 0&&(i.maxLength=e.maxLength),e.min!==void 0&&(i.min=e.min),e.max!==void 0&&(i.max=e.max),a&&(e.variant&&(i.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(i.multiple=!0),e.density&&(i.density=e.density),e.columns&&(i.columns=e.columns),e.accent&&(i.accent=e.accent),e.accentStyle&&(i.accentStyle=e.accentStyle),e.glow&&(i.glow=!0),e.glowColour&&(i.glowColour=e.glowColour),e.shadow&&(i.shadow=e.shadow),e.shadowColour&&(i.shadowColour=e.shadowColour)),t[e.name]=i}),t}function L(n){const s={};return(n||[]).forEach(t=>{if(!(!t.name||t.type==="page-break"||t.type==="spacer")&&(t.type==="select"||t.type==="multiselect")&&t.required){const e=(t.options||[])[0];e&&(s[t.name]=typeof e=="object"?e.value:e)}}),s}function w(n,s){(s||[]).forEach(t=>{if(t.type!=="date"||!t.name)return;const e=n.querySelector(`[name="${t.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function o(n,s,t){let e=n.querySelector(".cms-form-message");e||(e=document.createElement("p"),e.className="cms-form-message",n.appendChild(e)),e.textContent=s,e.style.cssText=t?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}l.forEach(n=>{let s;try{s=P(n.dataset.formInline)}catch{return}const t=s.fields||[],e=s.settings||{},a=Number(e.columns)===3?3:2,f=e.layout||"grid",v=t.some(m=>m.type==="page-break"),i=async m=>{if(n.dataset.triggerBlocked==="true"){const u=n._formLogicRuntime,g=u&&u.submitBlock?u.submitBlock().reasons:[];o(n,g.join(" ")||"This form cannot be submitted yet.",!0);const C=new Error("Blocked by a form trigger");throw C.formSubmitFailed=!0,C}try{const u=n.querySelector(".fb-form-honeypot"),g=u?.querySelector('input[type="text"]')||null,C=u?.querySelector('[name="_t"]')||null,M=Object.assign({},m);delete M._t,u?.dataset.hpName&&delete M[u.dataset.hpName],g!==null&&(M._hp=g.value),C!==null&&(M._t=C.value);const D=await H.post(`/api/forms/submit/${s.slug}`,M);if(D&&D.error){o(n,D.error,!0);const z=new Error(D.error);throw z.formSubmitFailed=!0,z}if(D?.redirect){window.location.href=D.redirect;return}for(;n.firstChild;)n.removeChild(n.firstChild);o(n,D?.message||e.successMessage||"Thank you for your submission.",!1)}catch(u){throw u.formSubmitFailed||o(n,u.message||"Submission failed. Please try again.",!0),u}},c=Date.now();function h(m){const u=m.querySelector("form");if(!u||u.querySelector(".fb-form-honeypot"))return;const g=document.createElement("div");g.className="fb-form-honeypot",g.setAttribute("aria-hidden","true");const C=document.createElement("input");C.name=u.querySelector('[name="website"]')?"_website":"website",g.dataset.hpName=C.name,C.type="text",C.tabIndex=-1,C.autocomplete="url",C.placeholder="https://",g.appendChild(C);const M=document.createElement("input");M.name="_t",M.type="hidden",M.value=c,g.appendChild(M),u.appendChild(g)}function b(m){if(!window.FormLogicEngine||!t.some(g=>g.logic||Array.isArray(g.triggers)&&g.triggers.length))return null;const u=new window.FormLogicEngine.FormLogicRuntime(s,n,m?{baseValues:m}:void 0);if(u.init(),n._formLogicRuntime=u,n._formLogicObserved)return u;if(n._formLogicObserved=!0,n.parentNode&&typeof MutationObserver<"u"){const g=new MutationObserver(function(C){for(const M of C)for(const D of M.removedNodes)if(D===n||D.nodeType===1&&D.contains&&D.contains(n)){n._formLogicRuntime&&n._formLogicRuntime.destroy(),g.disconnect();return}});g.observe(n.parentNode,{childList:!0,subtree:!1}),n._formLogicObserver=g}return u}let d=!1;if(n.addEventListener("dm-form-end",m=>{const u=m.detail?.validation;if(u&&!u.valid){G(n,u.missing),K(u);return}d||(d=!0,i(m.detail?.values||{}).catch(()=>{}).finally(()=>{d=!1}))}),v&&F.wizard){const m=re(t,s.title),u=m.map(I=>({title:I.title,description:I.description,fields:y(I.fields,a)})),g=m.map(I=>I.fields);let C=0,M=null,D=null;const z=I=>{const r=window.FormLogicEngine;if(!r||!r.validateFields)return;const A=r.validateFields(n,g[C]||[]);A.valid||(I.preventDefault(),I.stopImmediatePropagation(),G(n,A.missing),K(A))},B=()=>{const I=g[C]||t;le(n,e),Q(n,e,I),w(n,I),U(n,I),e.honeypot!==!1&&h(n),M&&M.destroy(),M=b(D?.getStepData?D.getStepData():void 0),n.querySelectorAll(".wizard-next, .wizard-submit").forEach(r=>r.addEventListener("click",z,!0))};D=F.wizard(n,{schema:{steps:u},layout:f,columns:a,finishText:e.submitText||"Finish",onSubmit:i,onStepChange:I=>{C=I,setTimeout(B,0)}}),n.addEventListener("dm-form-trigger",I=>{if(I.detail?.type!=="goto-step"||!D?.goToStep)return;const r=Number(I.detail.action.step);!Number.isFinite(r)||r<0||r>=u.length||r!==C&&D.goToStep(r)}),Promise.resolve(D).then(B)}else if(F.render){const m=F.render(n,y(t,a),L(t),{submitText:e.submitText||"Submit",layout:f,columns:a,onSubmit:i});Promise.resolve(m).then(function(){ce(n,e,f),Q(n,e,t),w(n,t),U(n,t),e.honeypot!==!1&&h(n),b()})}})})();function de(p,l){const x=document.body,y=x.classList,L=new Set((x.dataset.railSite||"").split(" ").filter(Boolean)),w=()=>window.matchMedia("(min-width: 993px)").matches;p.querySelectorAll(".navbar-link, .navbar-dropdown-toggle").forEach(a=>{const f=a.textContent.trim();f&&(a.setAttribute("title",f),a.querySelector("svg, [data-icon]")||(a.dataset.initial=f.charAt(0).toUpperCase()))});const o=document.createElement("button");o.type="button",o.className="dm-nav-collapse-toggle";const n=()=>{const a=y.contains("dm-vnav-collapsed");o.setAttribute("aria-pressed",String(a)),o.setAttribute("aria-label",a?"Expand menu":"Collapse menu"),o.title=o.getAttribute("aria-label");const f=l?"chevrons-left":"chevrons-right",v=l?"chevrons-right":"chevrons-left";o.innerHTML=`<span data-icon="${a?f:v}"></span>`,window.Domma&&Domma.icons&&Domma.icons.scan(o)},s=a=>{y.toggle("dm-vnav-collapsed",a),S.set("nav_collapsed",a),n()};o.addEventListener("click",()=>s(!y.contains("dm-vnav-collapsed"))),n();const t=p.querySelector(".navbar-container")||p;t.insertBefore(o,t.firstChild?.nextSibling||null);const e=a=>{y.toggle("dm-vnav-autohide",a),y.toggle("dm-vnav-collapsible",!a&&L.has("collapsible")),a?y.remove("dm-vnav-collapsed"):L.has("collapsible")&&S.get("nav_collapsed")===!0&&y.add("dm-vnav-collapsed"),p.classList.remove("dm-nav-revealed"),n()};p.addEventListener("pointerdown",a=>{y.contains("dm-vnav-autohide")&&a.pointerType!=="mouse"&&!p.classList.contains("dm-nav-revealed")&&(p.classList.add("dm-nav-revealed"),a.preventDefault())}),document.addEventListener("pointerdown",a=>{p.contains(a.target)||p.classList.remove("dm-nav-revealed")}),window.E&&typeof E.contextMenu=="function"&&E.contextMenu(p,{items:()=>{const a=y.contains("dm-vnav-autohide"),f=y.contains("dm-vnav-collapsed"),v=S.has("nav_autohide")||S.has("nav_collapsed");return[{header:"Menu"},{label:f?"Expand menu":"Collapse to icons",icon:f?"maximize":"minimize",disabled:!w()||a||!L.has("collapsible"),action:()=>s(!f)},{label:a?"Keep on screen":"Hide when not in use",icon:a?"eye":"eye-off",disabled:!w(),action:()=>{S.set("nav_autohide",!a),e(!a)}},{type:"divider"},{label:"Reset to the site's setting",icon:"refresh",disabled:!v,action:()=>{S.remove("nav_autohide"),S.remove("nav_collapsed"),e(L.has("autohide")),y.toggle("dm-vnav-collapsed",!L.has("autohide")&&L.has("start-collapsed")),n()}}]}})}function me(p){p.classList.add("dm-nav-scroll-hide");let l=window.scrollY,x=!1;window.addEventListener("scroll",()=>{x||(x=!0,requestAnimationFrame(()=>{x=!1;const y=window.scrollY,L=p.matches(":focus-within")||p.querySelector(".navbar-dropdown.open, .navbar-collapse.show");y>l&&y>p.offsetHeight&&!L?p.classList.add("dm-nav-scrolled-away"):y<l&&p.classList.remove("dm-nav-scrolled-away"),l=y}))},{passive:!0})}function ue(p){let l={};try{l=JSON.parse(p.dataset.contents||"{}")}catch{}const x=p.closest(".site-main"),y=document.querySelector(".site-content"),L=new Set(String(l.exclude||"").split(",").map(i=>i.trim().toLowerCase()).filter(Boolean)),w=i=>!i.closest(".hero, .card, .accordion, .tabs, figure, #site-sidebar"),o=i=>(y?[...y.querySelectorAll(i)]:[]).filter(w).filter(c=>!L.has(c.textContent.trim().toLowerCase())),n=l.headings==="h2"?o("h2"):l.headings==="h3"?o("h3"):l.headings==="h2h3"?o("h2, h3"):o("h2").length?o("h2"):o("h3");if(!n.length){x?.classList.add("no-sidebar-items");return}const s=new Set([...document.querySelectorAll("[id]")].map(i=>i.id)),t=document.createElement("nav");t.className="dm-contents",t.setAttribute("aria-label",l.title||"Page contents"),l.hover&&(t.dataset.menuHover=l.hover),l.selected&&(t.dataset.menuActive=l.selected),l.accent&&t.style.setProperty("--dm-menu-highlight",l.accent.startsWith("#")?l.accent:`var(--dm-${l.accent})`),l.font&&(t.style.fontFamily=l.font),l.fontSize&&(t.style.fontSize=l.fontSize),l.fontWeight&&(t.style.fontWeight=l.fontWeight);const e=document.createElement("div");e.className="dm-contents-head";const a=document.createElement("p");a.className="dm-contents-title",a.textContent=l.title||"",e.appendChild(a),t.appendChild(e);const f=document.createElement("ol");f.className="dm-contents-list"+(l.numbered?" is-numbered":"");const v=[];for(const i of n){if(!i.id){const b=(i.textContent||"").toLowerCase().trim().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"section";let d=b;for(let m=2;s.has(d);m++)d=`${b}-${m}`;s.add(d),i.id=d}const c=document.createElement("li");c.className=`dm-contents-item dm-contents-${i.tagName.toLowerCase()}`;const h=document.createElement("a");h.href=`#${i.id}`,h.textContent=i.textContent.trim(),c.appendChild(h),f.appendChild(c),v.push([i,h])}if(t.appendChild(f),p.replaceChildren(t),l.scrollSpy!==!1&&"IntersectionObserver"in window){const i=new Map,c=()=>{const b=v.find(([d])=>i.get(d))||null;v.forEach(([,d])=>{const m=b&&d===b[1];d.classList.toggle("is-active",!!m),m?d.setAttribute("aria-current","location"):d.removeAttribute("aria-current")})},h=new IntersectionObserver(b=>{b.forEach(d=>i.set(d.target,d.isIntersecting)),c()},{rootMargin:"-80px 0px -60% 0px"});v.forEach(([b])=>h.observe(b))}if(x?.classList.contains("contents-collapsible")){const i=document.createElement("button");i.type="button",i.className="dm-contents-toggle";const c=()=>{const h=x.classList.contains("contents-collapsed");i.setAttribute("aria-expanded",String(!h)),i.setAttribute("aria-label",h?"Show the contents":"Hide the contents"),i.title=i.getAttribute("aria-label"),i.innerHTML=`<span data-icon="${h?"list":"x"}"></span>`,window.Domma?.icons&&Domma.icons.scan(i)};i.addEventListener("click",()=>{S.set("contents_collapsed",x.classList.toggle("contents-collapsed")),c()}),c(),e.insertBefore(i,e.firstChild)}if(x?.classList.contains("contents-floating")&&l.movable){t.classList.add("is-movable");const i=S.get("contents_position");i&&Number.isFinite(i.x)&&Object.assign(p.style,{left:`${i.x}px`,top:`${i.y}px`,right:"auto",bottom:"auto"}),e.addEventListener("pointerdown",c=>{if(c.target.closest("button"))return;const h=p.getBoundingClientRect(),b=c.clientX-h.left,d=c.clientY-h.top,m=g=>{const C=Math.max(0,Math.min(window.innerWidth-h.width,g.clientX-b)),M=Math.max(0,Math.min(window.innerHeight-40,g.clientY-d));Object.assign(p.style,{left:`${C}px`,top:`${M}px`,right:"auto",bottom:"auto"})},u=()=>{document.removeEventListener("pointermove",m),S.set("contents_position",{x:parseInt(p.style.left,10),y:parseInt(p.style.top,10)})};document.addEventListener("pointermove",m),document.addEventListener("pointerup",u,{once:!0})})}window.E&&typeof E.contextMenu=="function"&&E.contextMenu(p,{items:()=>{const i=x?.classList.contains("contents-collapsible"),c=x?.classList.contains("contents-collapsed"),h=v.find(([,b])=>b.classList.contains("is-active"));return[{header:"Contents"},...i?[{label:c?"Show the contents":"Collapse to a button",icon:c?"list":"minimize",action:()=>p.querySelector(".dm-contents-toggle")?.click()}]:[],{label:"Copy a link to this section",icon:"link",disabled:!h,action:async()=>{const b=`${location.origin}${location.pathname}#${h[0].id}`;try{await _.copyToClipboard(b),E.toast("Link copied.",{type:"success"})}catch{E.toast("Could not copy the link.",{type:"error"})}}},{label:"Back to the top",icon:"arrow-up",action:()=>window.scrollTo({top:0,behavior:"smooth"})}]}}),x?.classList.contains("contents-autohide")&&(p.addEventListener("pointerdown",i=>{i.pointerType!=="mouse"&&!p.classList.contains("is-revealed")&&(p.classList.add("is-revealed"),i.preventDefault())}),document.addEventListener("pointerdown",i=>{p.contains(i.target)||p.classList.remove("is-revealed")}))}
@@ -61,6 +61,10 @@ try {
61
61
  verbatimSymlinks: true,
62
62
  filter: (src) => {
63
63
  const rel = path.relative(ROOT, src);
64
+ // The dashboard tests expect empty traffic; live counters belong
65
+ // to the running site and must not enter the isolated test copy.
66
+ if ([path.join('content', 'analytics'), path.join('plugins', 'analytics', 'data'),
67
+ path.join('plugins', 'analytics', 'daily.json'), path.join('plugins', 'analytics', 'journeys.json')].includes(rel)) return false;
64
68
  return !rel || !SKIP.has(rel.split(path.sep)[0]);
65
69
  }
66
70
  });
@@ -21,7 +21,7 @@ import {
21
21
  listPreviewLinks,
22
22
  revokePreviewLink
23
23
  } from '../../services/previewLinks.js';
24
- import {getVersionCount} from '../../services/versions.js';
24
+ import {getVersionCount, versionAuthor} from '../../services/versions.js';
25
25
  import {authenticate, requirePermission} from '../../middleware/auth.js';
26
26
  import {canSeeArtefact, canSeePage} from '../../services/projects.js';
27
27
  import {getConfig, saveConfig} from '../../config.js';
@@ -249,11 +249,11 @@ export async function pagesRoutes(fastify) {
249
249
  // follow the move - drop them rather than leave them 404ing.
250
250
  await deletePreviewLinksForPage(urlPath);
251
251
 
252
- const page = await updatePage(newUrlPath, frontmatter || {}, body, {author: request.user.username});
252
+ const page = await updatePage(newUrlPath, frontmatter || {}, body, {author: await versionAuthor(request.user)});
253
253
  return page;
254
254
  }
255
255
 
256
- const page = await updatePage(urlPath, frontmatter || {}, body, {author: request.user.username});
256
+ const page = await updatePage(urlPath, frontmatter || {}, body, {author: await versionAuthor(request.user)});
257
257
  return page;
258
258
  });
259
259
 
@@ -6,7 +6,10 @@
6
6
  * GET /api/settings/_count - the sidebar badge: what is wrong with the settings
7
7
  */
8
8
  import {normaliseOrigin} from '../../services/passwordReset.js';
9
- import {getConfig, saveConfig} from '../../config.js';
9
+ import {config, getConfig, saveConfig} from '../../config.js';
10
+ import {faviconSource, generateFavicon} from '../../services/favicon.js';
11
+ import {saveMedia} from '../../services/content.js';
12
+ import {randomUUID} from 'node:crypto';
10
13
  import {authenticate, requirePermission} from '../../middleware/auth.js';
11
14
  import {createTransport} from '../../services/email.js';
12
15
  import fs from 'fs/promises';
@@ -69,6 +72,22 @@ export async function settingsRoutes(fastify) {
69
72
  const canRead = {preHandler: [authenticate, requirePermission('settings', 'read')]};
70
73
  const canUpdate = {preHandler: [authenticate, requirePermission('settings', 'update')]};
71
74
 
75
+ fastify.post('/settings/favicon', canUpdate, async (request, reply) => {
76
+ let source;
77
+ try { source = faviconSource(request.body?.source, config.content.mediaDir); }
78
+ catch (err) { return reply.code(400).send({error: err.message}); }
79
+ try {
80
+ const input = await fs.readFile(source);
81
+ const icon = await generateFavicon(input);
82
+ const result = await saveMedia(`favicon-${randomUUID()}.ico`, icon);
83
+ return {...result, sizes: [16, 32, 48, 256]};
84
+ } catch (err) {
85
+ if (err.code === 'ENOENT') return reply.code(404).send({error: 'That Media image no longer exists.'});
86
+ if (err.code) throw err;
87
+ return reply.code(400).send({error: 'Could not generate an icon from this image. Choose another image.'});
88
+ }
89
+ });
90
+
72
91
  fastify.get('/settings', canRead, async (request, reply) => {
73
92
  const config = getConfig('site');
74
93
  const safeConfig = { ...config };
@@ -15,7 +15,8 @@ import {
15
15
  getVersion,
16
16
  listVersions,
17
17
  pruneVersions,
18
- restoreVersion
18
+ restoreVersion,
19
+ versionAuthor
19
20
  } from '../../services/versions.js';
20
21
  import {authenticate, requirePermission} from '../../middleware/auth.js';
21
22
  import {getPage} from '../../services/content.js';
@@ -66,7 +67,7 @@ export async function versionsRoutes(fastify) {
66
67
  const urlPath = '/' + request.params['*'];
67
68
  const {label} = request.body || {};
68
69
  const entry = await createVersion(urlPath, {
69
- author: request.user.username,
70
+ author: await versionAuthor(request.user),
70
71
  type: 'manual',
71
72
  label: label || null
72
73
  });
@@ -79,7 +80,7 @@ export async function versionsRoutes(fastify) {
79
80
  const urlPath = '/' + request.params['*'];
80
81
  const filename = decodeURIComponent(request.params.filename);
81
82
  try {
82
- await restoreVersion(urlPath, filename, request.user.username);
83
+ await restoreVersion(urlPath, filename, await versionAuthor(request.user));
83
84
  return {success: true};
84
85
  } catch (err) {
85
86
  if (err.code === 'ENOENT') return reply.status(404).send({error: 'Version not found'});
@@ -0,0 +1,33 @@
1
+ import sharp from 'sharp';
2
+ import path from 'node:path';
3
+
4
+ /** Only local Media images can be read by the generator. */
5
+ export function faviconSource(source, mediaDir) {
6
+ if (typeof source !== 'string' || !source.startsWith('/media/')) throw new Error('Choose an image from Media.');
7
+ let name;
8
+ try { name = decodeURIComponent(source.slice(7)); } catch { throw new Error('Invalid image address.'); }
9
+ if (!name || name.includes('/') || name.includes('\\') || name === '.' || name === '..'
10
+ || !/\.(png|jpe?g|webp|gif|avif|svg|tiff?|bmp)$/i.test(name)) throw new Error('Choose a PNG, JPEG, WebP, GIF, AVIF or SVG image from Media.');
11
+ return path.resolve(mediaDir, name);
12
+ }
13
+
14
+ /** ICO directory with PNG images at standard browser/desktop sizes. */
15
+ export async function generateFavicon(input) {
16
+ const sizes = [16, 32, 48, 256];
17
+ const images = await Promise.all(sizes.map(size => sharp(input, {limitInputPixels: 40_000_000})
18
+ .rotate().resize(size, size, {fit: 'contain', background: {r: 0, g: 0, b: 0, alpha: 0}}).png().toBuffer()));
19
+ const header = Buffer.alloc(6 + 16 * sizes.length);
20
+ header.writeUInt16LE(1, 2);
21
+ header.writeUInt16LE(sizes.length, 4);
22
+ let offset = header.length;
23
+ images.forEach((image, i) => {
24
+ const entry = 6 + 16 * i;
25
+ header[entry] = header[entry + 1] = sizes[i] === 256 ? 0 : sizes[i];
26
+ header.writeUInt16LE(1, entry + 4);
27
+ header.writeUInt16LE(32, entry + 6);
28
+ header.writeUInt32LE(image.length, entry + 8);
29
+ header.writeUInt32LE(offset, entry + 12);
30
+ offset += image.length;
31
+ });
32
+ return Buffer.concat([header, ...images]);
33
+ }